diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..9b24191 --- /dev/null +++ b/.gitleaks.toml @@ -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})''' \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d746a67..44d1a3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,4 +9,8 @@ repos: args: [ --fix ] # Run the formatter. - id: ruff-format - types_or: [ python, pyi ] \ No newline at end of file + types_or: [ python, pyi ] +- repo: https://github.com/gitleaks/gitleaks + rev: v8.24.2 + hooks: + - id: gitleaks \ No newline at end of file diff --git a/config.yaml.full b/config.yaml.full index 0706343..1bdda69 100644 --- a/config.yaml.full +++ b/config.yaml.full @@ -98,7 +98,7 @@ database: # [optional] for prompt optimization in cli/app -agent_pilot: +prompt_pilot: api_key: diff --git a/pyproject.toml b/pyproject.toml index 1c7fc34..163eb57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = [ diff --git a/tests/test_cloud.py b/tests/test_cloud.py index 0c7b041..47fbdaf 100644 --- a/tests/test_cloud.py +++ b/tests/test_cloud.py @@ -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" @@ -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 diff --git a/veadk/cli/cli.py b/veadk/cli/cli.py new file mode 100644 index 0000000..3eae9a4 --- /dev/null +++ b/veadk/cli/cli.py @@ -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() diff --git a/veadk/cli/cli_deploy.py b/veadk/cli/cli_deploy.py new file mode 100644 index 0000000..1b50bd2 --- /dev/null +++ b/veadk/cli/cli_deploy.py @@ -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)) diff --git a/veadk/cli/cli_init.py b/veadk/cli/cli_init.py new file mode 100644 index 0000000..fc95b27 --- /dev/null +++ b/veadk/cli/cli_init.py @@ -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) diff --git a/veadk/cli/cli_prompt.py b/veadk/cli/cli_prompt.py new file mode 100644 index 0000000..c7df61e --- /dev/null +++ b/veadk/cli/cli_prompt.py @@ -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}") diff --git a/veadk/cli/cli_studio.py b/veadk/cli/cli_studio.py new file mode 100644 index 0000000..3afd7f0 --- /dev/null +++ b/veadk/cli/cli_studio.py @@ -0,0 +1,55 @@ +# 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="Path to the agent directory") +def studio(path: str) -> None: + """Run VeADK Studio with the specified agent. The VeADK Studio will be deprecated soon.""" + import os + from pathlib import Path + + import uvicorn + + from veadk.cli.studio.fast_api import get_fast_api_app + from veadk.utils.misc import load_module_from_file + + path = str(Path(path).resolve()) + + agent_py_path = os.path.join(path, "agent.py") + + module = load_module_from_file( + module_name="local_agent", file_path=str(agent_py_path) + ) + + agent = None + short_term_memory = None + try: + agent = module.agent + short_term_memory = module.short_term_memory + except AttributeError as e: + missing = str(e).split("'")[1] if "'" in str(e) else "unknown" + raise AttributeError(f"agent.py is missing required variable: {missing}") + + app = get_fast_api_app(agent, short_term_memory) + + uvicorn.run( + app, + host="127.0.0.1", + port=8000, + log_level="info", + loop="asyncio", # for deepeval + ) diff --git a/veadk/cli/cli_web.py b/veadk/cli/cli_web.py new file mode 100644 index 0000000..ce66d3a --- /dev/null +++ b/veadk/cli/cli_web.py @@ -0,0 +1,85 @@ +# 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("--host", default="127.0.0.1", help="Host to run the web server on") +def web(host: str) -> None: + """Launch web with long term and short term memory.""" + import os + from typing import Any + + from google.adk.cli.utils.shared_value import SharedValue + + from veadk.memory.short_term_memory import ShortTermMemory + from veadk.utils.logger import get_logger + + logger = get_logger(__name__) + + def init_for_veadk( + self, + *, + agent_loader: Any, + session_service: Any, + memory_service: Any, + artifact_service: Any, + credential_service: Any, + eval_sets_manager: Any, + eval_set_results_manager: Any, + agents_dir: str, + ): + self.agent_loader = agent_loader + self.artifact_service = artifact_service + self.credential_service = credential_service + self.eval_sets_manager = eval_sets_manager + self.eval_set_results_manager = eval_set_results_manager + self.agents_dir = agents_dir + self.runners_to_clean = set() + self.current_app_name_ref = SharedValue(value="") + self.runner_dict = {} + + short_term_memory_backend = os.getenv("SHORT_TERM_MEMORY_BACKEND") + if not short_term_memory_backend: # prevent None or empty string + short_term_memory_backend = "local" + logger.info(f"Short term memory: backend={short_term_memory_backend}") + + long_term_memory_backend = os.getenv("LONG_TERM_MEMORY_BACKEND") + long_term_memory = None + + if long_term_memory_backend: + from veadk.memory.long_term_memory import LongTermMemory + + logger.info(f"Long term memory: backend={long_term_memory_backend}") + long_term_memory = LongTermMemory(backend=long_term_memory_backend) # type: ignore + else: + logger.info("No long term memory backend settings detected.") + + self.session_service = ShortTermMemory( + backend=short_term_memory_backend # type: ignore + ).session_service + + self.memory_service = long_term_memory + + import google.adk.cli.adk_web_server + + google.adk.cli.adk_web_server.AdkWebServer.__init__ = init_for_veadk + + import google.adk.cli.cli_tools_click as cli_tools_click + + agents_dir = os.getcwd() + logger.info(f"Load agents from {agents_dir}") + + cli_tools_click.cli_web.main(args=[agents_dir, "--host", host]) diff --git a/veadk/cli/main.py b/veadk/cli/main.py deleted file mode 100644 index d4ccf0b..0000000 --- a/veadk/cli/main.py +++ /dev/null @@ -1,284 +0,0 @@ -# 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 __future__ import annotations - -import importlib.util -import os -import shutil -import sys -from importlib.util import module_from_spec, spec_from_file_location -from pathlib import Path - -import typer -import uvicorn - -from veadk.utils.logger import get_logger -from veadk.version import VERSION - -logger = get_logger(__name__) - -app = typer.Typer(name="vego") - - -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) - - print("Your project has beed created.") - - -@app.command() -def init(): - """Init a veadk project that can be deployed to Volcengine VeFaaS.""" - from rich.prompt import Confirm, Prompt - - cwd = Path.cwd() - template_dir = Path(__file__).parent.resolve() / "services" / "vefaas" / "template" - - local_dir_name = Prompt.ask("Local directory name", default="veadk-cloud-proj") - - target_dir = cwd / local_dir_name - - if target_dir.exists(): - response = Confirm.ask( - f"Target directory '{target_dir}' already exists, do you want to overwrite it?: " - ) - if not response: - print("Operation cancelled.") - return - else: - shutil.rmtree(target_dir) - print(f"Deleted existing directory: {target_dir}") - - vefaas_application_name = Prompt.ask( - "Volcengine FaaS application name", default="veadk-cloud-agent" - ) - - gateway_name = Prompt.ask( - "Volcengine gateway instance name", default="", show_default=True - ) - - gateway_service_name = Prompt.ask( - "Volcengine gateway service name", default="", show_default=True - ) - - gateway_upstream_name = Prompt.ask( - "Volcengine gateway upstream name", default="", show_default=True - ) - - deploy_mode_options = { - "1": "A2A/MCP Server", - "2": "VeADK Studio", - "3": "VeADK Web / Google ADK Web", - } - - deploy_mode = Prompt.ask( - """Choose your deploy mode: -1. A2A/MCP Server -2. VeADK Studio -3. VeADK Web / Google ADK Web -""", - default="1", - ) - - if deploy_mode in deploy_mode_options: - deploy_mode = deploy_mode_options[deploy_mode] - else: - print("Invalid deploy mode, set default to A2A Server") - deploy_mode = deploy_mode_options["1"] - - setting_values = { - "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"], - } - - shutil.copytree(template_dir, target_dir) - set_variable_in_file(target_dir / "deploy.py", setting_values) - - -@app.command() -def web( - session_service_uri: str = typer.Option( - None, - "--session_service_uri", - ), - host: str = typer.Option( - "127.0.0.1", - "--host", - ), -): - from google.adk.memory import in_memory_memory_service - - from veadk.memory.long_term_memory import LongTermMemory - - in_memory_memory_service.InMemoryMemoryService = LongTermMemory - - from google.adk.cli import cli_tools_click - - importlib.reload(cli_tools_click) - agents_dir = os.getcwd() - if not session_service_uri: - session_service_uri = "" - - cli_tools_click.cli_web.main( - args=[agents_dir, "--session_service_uri", session_service_uri, "--host", host] - ) - - -@app.command() -def studio( - path: str = typer.Option(".", "--path", help="Project path"), -): - from veadk.cli.studio.fast_api import get_fast_api_app - - path = Path(path).resolve() - - agent_py_path = os.path.join(path, "agent.py") - if not os.path.exists(agent_py_path): - raise FileNotFoundError(f"agent.py not found in {path}") - - spec = spec_from_file_location("agent", agent_py_path) - if spec is None: - raise ImportError(f"Could not load spec for agent from {agent_py_path}") - - module = module_from_spec(spec) - - try: - spec.loader.exec_module(module) - except Exception as e: - raise ImportError(f"Failed to execute agent.py: {e}") - - agent = None - short_term_memory = None - try: - agent = module.agent - short_term_memory = module.short_term_memory - except AttributeError as e: - missing = str(e).split("'")[1] if "'" in str(e) else "unknown" - raise AttributeError(f"agent.py is missing required variable: {missing}") - - app = get_fast_api_app(agent, short_term_memory) - - uvicorn.run( - app, - host="127.0.0.1", - port=8000, - log_level="info", - loop="asyncio", # for deepeval - ) - - -@app.command() -def prompt( - path: str = typer.Option( - ..., - "--path", - help="Your agent file path. Please ensure that your agent(s) are global variable(s).", - ), - feedback: str = typer.Option( - "", - "--feedback", - help="Feedback of prompt from agent evaluation.", - ), - api_key: str = typer.Option( - ..., "--api-key", help="API Key of AgentPilot Platform" - ), - model_name: str = typer.Option( - "doubao-1.5-pro-32k-250115", - "--model-name", - help="Model name for prompt optimization", - ), -): - from veadk import Agent - - """ - NOTE(nkfyz): Detecting agents from a file is not fully correct, we will fix this feature asap. - """ - module_name = "veadk_agent" - path = Path(path).resolve() - logger.info(f"Detect agents in {path}") - - spec = importlib.util.spec_from_file_location(module_name, path) - module = importlib.util.module_from_spec(spec) - sys.modules[module_name] = module - spec.loader.exec_module(module) - globals_in_module = vars(module) # get all global variables in 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) - logger.info(f"Found {len(agents)} agent(s) in {path}") - - if len(agents) == 0: - logger.info( - "No agent found. Please put your agent definition as a global variable in your agent file." - ) - print( - f"No agent found in {path}. Please put your agent definition as a global variable in your agent file." - ) - return - - from veadk.cli.services.agentpilot import AgentPilot - - ap = AgentPilot(api_key) - ap.optimize(agents=agents, feedback=feedback, model_name=model_name) - - -@app.command() -def deploy( - access_key: str = typer.Option(..., "--access-key", help="Access Key"), - secret_key: str = typer.Option(..., "--secret-key", help="Secret Key"), - name: str = typer.Option(..., "--name", help="Deployment name"), - path: str = typer.Option(".", "--path", help="Project path"), -): - from veadk.cli.services.vefaas import VeFaaS - - path = Path(path).resolve() - vefaas = VeFaaS(access_key, secret_key) - vefaas.deploy(name=name, path=path) - - -@app.command() -def version(): - print(f"VeADK {VERSION}") - - -if __name__ == "__main__": - app() diff --git a/veadk/cli/studio/studio_processor.py b/veadk/cli/studio/studio_processor.py index ae41899..7074b8d 100644 --- a/veadk/cli/studio/studio_processor.py +++ b/veadk/cli/studio/studio_processor.py @@ -20,10 +20,10 @@ from google.adk.models.llm_response import LlmResponse from veadk import Agent -from veadk.cli.services.agentpilot.agentpilot import AgentPilot from veadk.config import getenv from veadk.evaluation.deepeval_evaluator import DeepevalEvaluator from veadk.evaluation.eval_set_recorder import EvalSetRecorder +from veadk.integrations.ve_prompt_pilot.ve_prompt_pilot import VePromptPilot from veadk.memory.short_term_memory import ShortTermMemory from veadk.runner import Runner from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer @@ -60,7 +60,7 @@ def __init__( user_id=user_id, ) self.session_id = session_id - self.agent_pilot = AgentPilot(api_key=getenv("AGENT_PILOT_API_KEY")) + self.agent_pilot = VePromptPilot(api_key=getenv("AGENT_PILOT_API_KEY")) self.eval_set_recorder = EvalSetRecorder( session_service=self.short_term_memory.session_service, eval_set_id="studio" diff --git a/veadk/cloud/cloud_agent_engine.py b/veadk/cloud/cloud_agent_engine.py index ffd4934..b4be73c 100644 --- a/veadk/cloud/cloud_agent_engine.py +++ b/veadk/cloud/cloud_agent_engine.py @@ -18,9 +18,9 @@ from pydantic import BaseModel -from veadk.cli.services.vefaas import VeFaaS from veadk.cloud.cloud_app import CloudApp from veadk.config import getenv +from veadk.integrations.ve_faas.ve_faas import VeFaaS from veadk.utils.logger import get_logger from veadk.utils.misc import formatted_timestamp @@ -79,7 +79,11 @@ def _prepare(self, path: str, name: str): logger.info( f"No `{template_file}` detected in local agent project path `{path}`. Prepare it." ) - template_file_path = f"{Path(__file__).resolve().parent.resolve().parent.resolve()}/cli/services/vefaas/template/src/{template_file}" + import veadk.integrations.ve_faas as vefaas + + template_file_path = ( + Path(vefaas.__file__).parent / "template" / "src" / template_file + ) import shutil shutil.copy(template_file_path, os.path.join(path, template_file)) diff --git a/veadk/cloud/cloud_app.py b/veadk/cloud/cloud_app.py index 111cdbc..5540249 100644 --- a/veadk/cloud/cloud_app.py +++ b/veadk/cloud/cloud_app.py @@ -79,7 +79,7 @@ def _get_vefaas_endpoint( volcengine_ak: str = getenv("VOLCENGINE_ACCESS_KEY"), volcengine_sk: str = getenv("VOLCENGINE_SECRET_KEY"), ) -> str: - from veadk.cli.services.vefaas.vefaas import VeFaaS + from veadk.integrations.ve_faas.ve_faas import VeFaaS vefaas_client = VeFaaS(access_key=volcengine_ak, secret_key=volcengine_sk) @@ -156,7 +156,7 @@ def delete_self( print("Delete cancelled.") return else: - from veadk.cli.services.vefaas.vefaas import VeFaaS + from veadk.integrations.ve_faas.ve_faas import VeFaaS vefaas_client = VeFaaS(access_key=volcengine_ak, secret_key=volcengine_sk) vefaas_client.delete(self.vefaas_application_id) diff --git a/veadk/cli/services/vefaas/template/__init__.py b/veadk/integrations/ve_apig/__init__.py similarity index 100% rename from veadk/cli/services/vefaas/template/__init__.py rename to veadk/integrations/ve_apig/__init__.py diff --git a/veadk/cli/services/veapig/apig.py b/veadk/integrations/ve_apig/ve_apig.py similarity index 100% rename from veadk/cli/services/veapig/apig.py rename to veadk/integrations/ve_apig/ve_apig.py diff --git a/veadk/cli/services/veapig/apig_utils.py b/veadk/integrations/ve_apig/ve_apig_utils.py similarity index 100% rename from veadk/cli/services/veapig/apig_utils.py rename to veadk/integrations/ve_apig/ve_apig_utils.py diff --git a/veadk/cli/services/vefaas/__init__.py b/veadk/integrations/ve_faas/__init__.py similarity index 92% rename from veadk/cli/services/vefaas/__init__.py rename to veadk/integrations/ve_faas/__init__.py index 60142fd..7f46320 100644 --- a/veadk/cli/services/vefaas/__init__.py +++ b/veadk/integrations/ve_faas/__init__.py @@ -11,7 +11,3 @@ # 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 .vefaas import VeFaaS - -__all__ = ["VeFaaS"] diff --git a/veadk/cli/services/vefaas/template/README.md b/veadk/integrations/ve_faas/template/README.md similarity index 100% rename from veadk/cli/services/vefaas/template/README.md rename to veadk/integrations/ve_faas/template/README.md diff --git a/veadk/cli/services/veapig/__init__.py b/veadk/integrations/ve_faas/template/__init__.py similarity index 91% rename from veadk/cli/services/veapig/__init__.py rename to veadk/integrations/ve_faas/template/__init__.py index 4869452..7f46320 100644 --- a/veadk/cli/services/veapig/__init__.py +++ b/veadk/integrations/ve_faas/template/__init__.py @@ -11,7 +11,3 @@ # 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 .apig import APIGateway - -__all__ = ["APIGateway"] diff --git a/veadk/cli/services/vefaas/template/config.yaml.example b/veadk/integrations/ve_faas/template/config.yaml.example similarity index 100% rename from veadk/cli/services/vefaas/template/config.yaml.example rename to veadk/integrations/ve_faas/template/config.yaml.example diff --git a/veadk/cli/services/vefaas/template/deploy.py b/veadk/integrations/ve_faas/template/deploy.py similarity index 100% rename from veadk/cli/services/vefaas/template/deploy.py rename to veadk/integrations/ve_faas/template/deploy.py diff --git a/veadk/cli/services/vefaas/template/src/__init__.py b/veadk/integrations/ve_faas/template/src/__init__.py similarity index 100% rename from veadk/cli/services/vefaas/template/src/__init__.py rename to veadk/integrations/ve_faas/template/src/__init__.py diff --git a/veadk/cli/services/vefaas/template/src/agent.py b/veadk/integrations/ve_faas/template/src/agent.py similarity index 100% rename from veadk/cli/services/vefaas/template/src/agent.py rename to veadk/integrations/ve_faas/template/src/agent.py diff --git a/veadk/cli/services/vefaas/template/src/app.py b/veadk/integrations/ve_faas/template/src/app.py similarity index 100% rename from veadk/cli/services/vefaas/template/src/app.py rename to veadk/integrations/ve_faas/template/src/app.py diff --git a/veadk/cli/services/vefaas/template/src/requirements.txt b/veadk/integrations/ve_faas/template/src/requirements.txt similarity index 100% rename from veadk/cli/services/vefaas/template/src/requirements.txt rename to veadk/integrations/ve_faas/template/src/requirements.txt diff --git a/veadk/cli/services/vefaas/template/src/run.sh b/veadk/integrations/ve_faas/template/src/run.sh similarity index 90% rename from veadk/cli/services/vefaas/template/src/run.sh rename to veadk/integrations/ve_faas/template/src/run.sh index 34f2695..cfe6c36 100755 --- a/veadk/cli/services/vefaas/template/src/run.sh +++ b/veadk/integrations/ve_faas/template/src/run.sh @@ -43,6 +43,9 @@ python3 -m pip install fastmcp USE_STUDIO=${USE_STUDIO:-False} USE_ADK_WEB=${USE_ADK_WEB:-False} +export SHORT_TERM_MEMORY_BACKEND= # can be `mysql` +export LONG_TERM_MEMORY_BACKEND= # can be `opensearch` + if [ "$USE_STUDIO" = "True" ]; then echo "USE_STUDIO is True, running veadk studio" # running veadk studio @@ -54,7 +57,7 @@ elif [ "$USE_STUDIO" = "False" ]; then echo "USE_ADK_WEB is True, running veadk web" # running veadk web cd ../ - exec python3 -m veadk.cli.main web --host "0.0.0.0" + exec python3 -m veadk.cli.cli web --host "0.0.0.0" else echo "USE_ADK_WEB is False, running a2a server" exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio diff --git a/veadk/cli/services/vefaas/template/src/studio_app.py b/veadk/integrations/ve_faas/template/src/studio_app.py similarity index 100% rename from veadk/cli/services/vefaas/template/src/studio_app.py rename to veadk/integrations/ve_faas/template/src/studio_app.py diff --git a/veadk/cli/services/vefaas/vefaas.py b/veadk/integrations/ve_faas/ve_faas.py similarity index 98% rename from veadk/cli/services/vefaas/vefaas.py rename to veadk/integrations/ve_faas/ve_faas.py index eeddba2..3dee196 100644 --- a/veadk/cli/services/vefaas/vefaas.py +++ b/veadk/integrations/ve_faas/ve_faas.py @@ -27,13 +27,15 @@ ) import veadk.config -from veadk.cli.services.veapig.apig import APIGateway +from veadk.integrations.ve_apig.ve_apig import APIGateway +from veadk.integrations.ve_faas.ve_faas_utils import ( + signed_request, + zip_and_encode_folder, +) from veadk.utils.logger import get_logger from veadk.utils.misc import formatted_timestamp from veadk.utils.volcengine_sign import ve_request -from .vefaas_utils import signed_request, zip_and_encode_folder - logger = get_logger(__name__) diff --git a/veadk/cli/services/vefaas/vefaas_utils.py b/veadk/integrations/ve_faas/ve_faas_utils.py similarity index 100% rename from veadk/cli/services/vefaas/vefaas_utils.py rename to veadk/integrations/ve_faas/ve_faas_utils.py diff --git a/veadk/cli/services/agentpilot/__init__.py b/veadk/integrations/ve_prompt_pilot/__init__.py similarity index 91% rename from veadk/cli/services/agentpilot/__init__.py rename to veadk/integrations/ve_prompt_pilot/__init__.py index 4c019bd..7f46320 100644 --- a/veadk/cli/services/agentpilot/__init__.py +++ b/veadk/integrations/ve_prompt_pilot/__init__.py @@ -11,7 +11,3 @@ # 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 .agentpilot import AgentPilot - -__all__ = ["AgentPilot"] diff --git a/veadk/cli/services/agentpilot/agentpilot.py b/veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py similarity index 93% rename from veadk/cli/services/agentpilot/agentpilot.py rename to veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py index 74c7406..b04ffa3 100644 --- a/veadk/cli/services/agentpilot/agentpilot.py +++ b/veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py @@ -24,8 +24,10 @@ logger = get_logger(__name__) -class AgentPilot: - def __init__(self, api_key: str, path: str = "") -> None: +class VePromptPilot: + def __init__( + self, api_key: str, path: str = "", task_id: str | None = None + ) -> None: self.api_key = api_key self.path = path @@ -37,7 +39,7 @@ def optimize( ) -> str: for idx, agent in enumerate(agents): optimized_prompt = "" - if feedback == "": + if not feedback: logger.info("Optimizing prompt without feedback.") task_description = prompt_optimization.render_prompt_with_jinja2(agent) else: @@ -72,5 +74,3 @@ def optimize( logger.info(f"Token usage: {usage['total_tokens']}") return optimized_prompt - return optimized_prompt - return optimized_prompt diff --git a/veadk/utils/misc.py b/veadk/utils/misc.py index 1fe8f5a..1585252 100644 --- a/veadk/utils/misc.py +++ b/veadk/utils/misc.py @@ -12,7 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import importlib.util +import sys import time +import types + import requests @@ -40,3 +44,19 @@ def read_png_to_bytes(png_path: str) -> bytes: with open(png_path, "rb") as f: data = f.read() return data + + +def load_module_from_file(module_name: str, file_path: str) -> types.ModuleType: + spec = importlib.util.spec_from_file_location(module_name, file_path) + if spec: + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + if spec.loader: + spec.loader.exec_module(module) + return module + else: + raise ImportError( + f"Could not find loader for module {module_name} from {file_path}" + ) + else: + raise ImportError(f"Could not load module {module_name} from {file_path}")