Skip to content

chore: refine and reconstruct cli #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[extend]
useDefault = true

[[rules]]
id = "aklt-key-pattern"
description = "AKLT key pattern"
regex = '''AKLT\w{40,70}'''

[[rules]]
id = "akap-key-pattern"
description = "AKAP key pattern"
regex = '''AKAP\w{40,70}'''

[[rules]]
id = "akip-key-pattern"
description = "AKIP key pattern"
regex = '''AKI\w{40,70}'''

[[rules]]
id = "token-transformer-id-pattern"
description = "Tokenizer/Transformer/Token ID patterns"
regex = '''(tokenizer|transformer|token_id|tokenid|attention_head).{0,20}'''

[[rules]]
id = "aws-style-key-pattern"
description = "AWS-style key pattern"
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''

[[rules]]
id = "alibaba-ltai-pattern"
description = "Alibaba LTAI key pattern"
regex = '''(LTAI)[a-z0-9]{20}'''

[[rules]]
id = "aktp-key-pattern"
description = "AKTP key pattern"
regex = '''AKTP\w{40,70}'''

[[rules]]
id = "app-id-pattern"
description = "App ID patterns"
regex = '''([^*<\s|:>]{0,7})(app_id|appid)([^]()!<>;/@&,]{0,10}[(=:]\s{0,6}["']{0,1}[0-9]{6,32}["']{0,1})'''

[[rules]]
id = "byted-org-domains"
description = "byted.org domains"
regex = '''.{0,15}\.?byted.org.{0,20}'''

[[rules]]
id = "bytedance-net-domains"
description = "bytedance.net domains"
regex = '''.{0,15}\.?bytedance.net.{0,20}'''

[[rules]]
id = "feishu-cn-domains"
description = "bytedance.feishu.cn domains"
regex = '''.{0,20}.bytedance\.feishu\.cn.{0,50}'''

[[rules]]
id = "larkoffice-com-domains"
description = "bytedance.larkoffice.com domains"
regex = '''.{0,20}.bytedance\.larkoffice\.com.{0,50}'''

[[rules]]
id = "private-ip-10-range"
description = "Private IP address pattern (10.x.x.x)"
regex = '''(10\.\d{1,3}\.\d{1,3}\.\d{1,3})'''
6 changes: 5 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ repos:
args: [ --fix ]
# Run the formatter.
- id: ruff-format
types_or: [ python, pyi ]
types_or: [ python, pyi ]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.2
hooks:
- id: gitleaks
2 changes: 1 addition & 1 deletion config.yaml.full
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ database:


# [optional] for prompt optimization in cli/app
agent_pilot:
prompt_pilot:
api_key:


Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ dependencies = [
"opentelemetry-exporter-otlp>=1.35.0",
"opentelemetry-instrumentation-logging>=0.56b0",
"wrapt>=1.17.2", # For patching built-in functions
"typer>=0.16.0", # For command-line implementation
]

[project.scripts]
veadk = "veadk.cli.main:app"
veadk = "veadk.cli.cli:veadk"

[project.optional-dependencies]
database = [
Expand Down
6 changes: 3 additions & 3 deletions tests/test_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

import os
import tempfile
import pytest
from unittest.mock import AsyncMock, Mock, patch

from unittest.mock import Mock, patch, AsyncMock
import pytest

os.environ["VOLCENGINE_ACCESS_KEY"] = "test_access_key"
os.environ["VOLCENGINE_SECRET_KEY"] = "test_secret_key"
Expand Down Expand Up @@ -110,7 +110,7 @@ async def test_cloud():
# Test CloudApp delete_self functionality
with patch("builtins.input", return_value="y"):
with patch(
"veadk.cli.services.vefaas.vefaas.VeFaaS"
"veadk.integrations.ve_faas.ve_faas.VeFaaS"
) as mock_vefaas_in_app:
mock_vefaas_client = Mock()
mock_vefaas_in_app.return_value = mock_vefaas_client
Expand Down
41 changes: 41 additions & 0 deletions veadk/cli/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import click

from veadk.cli.cli_deploy import deploy
from veadk.cli.cli_init import init
from veadk.cli.cli_prompt import prompt
from veadk.cli.cli_studio import studio
from veadk.cli.cli_web import web
from veadk.version import VERSION


@click.group()
@click.version_option(
version=VERSION, prog_name="Volcengine Agent Development Kit (VeADK)"
)
def veadk():
"""Volcengine ADK command line tools"""
pass


veadk.add_command(deploy)
veadk.add_command(init)
veadk.add_command(prompt)
veadk.add_command(studio)
veadk.add_command(web)

if __name__ == "__main__":
veadk()
46 changes: 46 additions & 0 deletions veadk/cli/cli_deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import click


@click.command()
@click.option(
"--access-key",
default=None,
help="Volcengine access key",
)
@click.option(
"--secret-key",
default=None,
help="Volcengine secret key",
)
@click.option("--name", help="Expected Volcengine FaaS application name")
@click.option("--path", default=".", help="Local project path")
def deploy(access_key: str, secret_key: str, name: str, path: str) -> None:
"""Deploy a user project to Volcengine FaaS application."""
from pathlib import Path

from veadk.config import getenv
from veadk.integrations.ve_faas.ve_faas import VeFaaS

if not access_key:
access_key = getenv("VOLCENGINE_ACCESS_KEY")
if not secret_key:
secret_key = getenv("VOLCENGINE_SECRET_KEY")

user_proj_abs_path = Path(path).resolve()

ve_faas = VeFaaS(access_key=access_key, secret_key=secret_key)
ve_faas.deploy(name=name, path=str(user_proj_abs_path))
110 changes: 110 additions & 0 deletions veadk/cli/cli_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any

import click


def _set_variable_in_file(file_path: str, setting_values: dict):
import ast

with open(file_path, "r", encoding="utf-8") as f:
source_code = f.read()

tree = ast.parse(source_code)

class VariableTransformer(ast.NodeTransformer):
def visit_Assign(self, node: ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id in setting_values:
node.value = ast.Constant(value=setting_values[target.id])
return node

transformer = VariableTransformer()
new_tree = transformer.visit(tree)
ast.fix_missing_locations(new_tree)
new_source_code = ast.unparse(new_tree)

with open(file_path, "w", encoding="utf-8") as f:
f.write(new_source_code)

click.echo("Your project has beed created.")


def _render_prompts() -> dict[str, Any]:
vefaas_application_name = click.prompt(
"Volcengine FaaS application name", default="veadk-cloud-agent"
)

gateway_name = click.prompt(
"Volcengine gateway instance name", default="", show_default=True
)

gateway_service_name = click.prompt(
"Volcengine gateway service name", default="", show_default=True
)

gateway_upstream_name = click.prompt(
"Volcengine gateway upstream name", default="", show_default=True
)

deploy_mode_options = {
"1": "A2A/MCP Server",
"2": "VeADK Studio",
"3": "VeADK Web / Google ADK Web",
}

click.echo("Choose a deploy mode:")
for key, value in deploy_mode_options.items():
click.echo(f" {key}. {value}")

deploy_mode = click.prompt(
"Enter your choice", type=click.Choice(deploy_mode_options.keys())
)

return {
"VEFAAS_APPLICATION_NAME": vefaas_application_name,
"GATEWAY_NAME": gateway_name,
"GATEWAY_SERVICE_NAME": gateway_service_name,
"GATEWAY_UPSTREAM_NAME": gateway_upstream_name,
"USE_STUDIO": deploy_mode == deploy_mode_options["2"],
"USE_ADK_WEB": deploy_mode == deploy_mode_options["3"],
}


@click.command()
def init() -> None:
"""Init a veadk project that can be deployed to Volcengine VeFaaS."""
import shutil
from pathlib import Path

import veadk.integrations.ve_faas as vefaas

cwd = Path.cwd()
local_dir_name = click.prompt("Directory name", default="veadk-cloud-proj")
target_dir_path = cwd / local_dir_name

if target_dir_path.exists():
click.confirm(
f"Directory '{target_dir_path}' already exists, do you want to overwrite it",
abort=True,
)
shutil.rmtree(target_dir_path)

setting_values = _render_prompts()

template_dir_path = Path(vefaas.__file__).parent / "template"
shutil.copytree(template_dir_path, target_dir_path)
_set_variable_in_file(target_dir_path / "deploy.py", setting_values)
64 changes: 64 additions & 0 deletions veadk/cli/cli_prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import click


@click.command()
@click.option(
"--path", default=".", help="Agent file path with global variable `agent=...`"
)
@click.option("--feedback", default=None, help="Suggestions for prompt optimization")
@click.option("--api-key", default=None, help="API Key of PromptPilot")
@click.option(
"--model-name",
default="doubao-1.5-pro-32k-250115",
help="Model name for prompt optimization",
)
def prompt(path: str, feedback: str, api_key: str, model_name: str) -> None:
"""Optimize agent system prompt from a local file."""
from pathlib import Path

from veadk.agent import Agent
from veadk.config import getenv
from veadk.integrations.ve_prompt_pilot.ve_prompt_pilot import VePromptPilot
from veadk.utils.misc import load_module_from_file

module_name = "agents_for_prompt_pilot"
module_abs_path = Path(path).resolve()

module = load_module_from_file(
module_name=module_name, file_path=str(module_abs_path)
)

# get all global variables from module
globals_in_module = vars(module)

agents = []
for global_variable_name, global_variable_value in globals_in_module.items():
if isinstance(global_variable_value, Agent):
agent = global_variable_value
agents.append(agent)

if len(agents) > 0:
click.echo(f"Found {len(agents)} agents in {module_abs_path}")

if not api_key:
api_key = getenv("PROMPT_PILOT_API_KEY")
ve_prompt_pilot = VePromptPilot(api_key)
ve_prompt_pilot.optimize(
agents=agents, feedback=feedback, model_name=model_name
)
else:
click.echo(f"No agents found in {module_abs_path}")
Loading