From b93cfd11f3d360d704bada6174dd530532275eda Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Mon, 18 Aug 2025 18:42:09 +0800 Subject: [PATCH 1/2] update deploy templates --- veadk/cli/cli_web.py | 111 +++++++++--- veadk/cloud/cloud_agent_engine.py | 87 +++++++++- veadk/integrations/ve_faas/template/deploy.py | 103 +++++++----- .../ve_faas/template/src/__init__.py | 1 - .../ve_faas/template/src/agent.py | 20 ++- .../integrations/ve_faas/template/src/app.py | 159 +++++++++--------- .../ve_faas/template/src/requirements.txt | 7 - .../integrations/ve_faas/template/src/run.sh | 24 +-- .../ve_faas/template/src/studio_app.py | 47 ------ .../template/src/weather_agent/__init__.py | 14 ++ .../template/src/weather_agent/agent.py | 39 +++++ .../src/weather_agent/requirements.txt | 1 + veadk/tracing/base_tracer.py | 15 ++ veadk/types.py | 25 ++- 14 files changed, 428 insertions(+), 225 deletions(-) delete mode 100644 veadk/integrations/ve_faas/template/src/requirements.txt delete mode 100644 veadk/integrations/ve_faas/template/src/studio_app.py create mode 100644 veadk/integrations/ve_faas/template/src/weather_agent/__init__.py create mode 100644 veadk/integrations/ve_faas/template/src/weather_agent/agent.py create mode 100644 veadk/integrations/ve_faas/template/src/weather_agent/requirements.txt diff --git a/veadk/cli/cli_web.py b/veadk/cli/cli_web.py index ce66d3ad..b92766ee 100644 --- a/veadk/cli/cli_web.py +++ b/veadk/cli/cli_web.py @@ -12,8 +12,95 @@ # See the License for the specific language governing permissions and # limitations under the License. + import click +from veadk.memory.long_term_memory import LongTermMemory +from veadk.memory.short_term_memory import ShortTermMemory + + +def _get_stm_from_module(module) -> ShortTermMemory: + return module.agent_run_config.short_term_memory + + +def _get_stm_from_env() -> ShortTermMemory: + import os + + from veadk.utils.logger import get_logger + + logger = get_logger(__name__) + + 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}") + + return ShortTermMemory(backend=short_term_memory_backend) # type: ignore + + +def _get_ltm_from_module(module) -> LongTermMemory | None: + agent = module.agent_run_config.agent + + if not hasattr(agent, "long_term_memory"): + return None + else: + return agent.long_term_memory + + +def _get_ltm_from_env() -> LongTermMemory | None: + import os + + from veadk.utils.logger import get_logger + + logger = get_logger(__name__) + + long_term_memory_backend = os.getenv("LONG_TERM_MEMORY_BACKEND") + + if long_term_memory_backend: + logger.info(f"Long term memory: backend={long_term_memory_backend}") + return LongTermMemory(backend=long_term_memory_backend) # type: ignore + else: + logger.warning("No long term memory backend settings detected.") + return None + + +def _get_memory( + module_path: str, +) -> tuple[ShortTermMemory, LongTermMemory | None]: + from veadk.utils.logger import get_logger + from veadk.utils.misc import load_module_from_file + + logger = get_logger(__name__) + + # 1. load user module + try: + module_file_path = module_path + module = load_module_from_file( + module_name="agent_and_mem", file_path=f"{module_file_path}/agent.py" + ) + except Exception as e: + logger.error( + f"Failed to get memory config from `agent.py`: {e}. Fallback to get memory from environment variables." + ) + return _get_stm_from_env(), _get_ltm_from_env() + + if not hasattr(module, "agent_run_config"): + logger.error( + "You must export `agent_run_config` as a global variable in `agent.py`. Fallback to get memory from environment variables." + ) + return _get_stm_from_env(), _get_ltm_from_env() + + # 2. try to get short term memory + # short term memory must exist in user code, as we use `default_factory` to init it + short_term_memory = _get_stm_from_module(module) + + # 3. try to get long term memory + long_term_memory = _get_ltm_from_module(module) + if not long_term_memory: + long_term_memory = _get_ltm_from_env() + + return short_term_memory, long_term_memory + @click.command() @click.option("--host", default="127.0.0.1", help="Host to run the web server on") @@ -24,7 +111,6 @@ def web(host: str) -> None: 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__) @@ -51,26 +137,9 @@ def init_for_veadk( 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 - + # parse VeADK memories + short_term_memory, long_term_memory = _get_memory(module_path=agents_dir) + self.session_service = short_term_memory.session_service self.memory_service = long_term_memory import google.adk.cli.adk_web_server diff --git a/veadk/cloud/cloud_agent_engine.py b/veadk/cloud/cloud_agent_engine.py index b4be73c0..67c96229 100644 --- a/veadk/cloud/cloud_agent_engine.py +++ b/veadk/cloud/cloud_agent_engine.py @@ -13,6 +13,9 @@ # limitations under the License. import os +import socket +import subprocess +import time from pathlib import Path from typing import Any @@ -22,7 +25,7 @@ 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 +from veadk.utils.misc import formatted_timestamp, load_module_from_file logger = get_logger(__name__) @@ -65,9 +68,9 @@ def _prepare(self, path: str, name: str): # prepare template files if not have template_files = [ "app.py", - "studio_app.py", + # "studio_app.py", "run.sh", - "requirements.txt", + # "requirements.txt", "__init__.py", ] for template_file in template_files: @@ -88,6 +91,70 @@ def _prepare(self, path: str, name: str): shutil.copy(template_file_path, os.path.join(path, template_file)) + # copy user's requirements.txt + if os.path.exists(os.path.join(path, "requirements.txt")): + logger.warning( + f"Local agent project path `{path}` contains a `requirements.txt` file. Skip copy requirements." + ) + return + + module = load_module_from_file( + module_name="agent_source", file_path=f"{path}/agent.py" + ) + + requirement_file_path = module.agent_run_config.requirement_file_path + shutil.copy(requirement_file_path, os.path.join(path, "requirements.txt")) + + logger.info( + f"Copy requirement file: from {requirement_file_path} to {path}/requirements.txt" + ) + + def _try_launch_fastapi_server(self, path: str): + """Try to launch a fastapi server for tests according to user's configuration. + + Args: + path (str): Local agent project path. + """ + RUN_SH = f"{path}/run.sh" + + HOST = "0.0.0.0" + PORT = 8000 + + # Prepare environment variables + os.environ["_FAAS_FUNC_TIMEOUT"] = "900" + env = os.environ.copy() + + process = subprocess.Popen( + ["bash", RUN_SH], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + env=env, + bufsize=1, + ) + + timeout = 30 + start_time = time.time() + + for line in process.stdout: # type: ignore + print(line, end="") + + if time.time() - start_time > timeout: + process.terminate() + raise RuntimeError(f"FastAPI server failed to start on {HOST}:{PORT}") + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(0.1) + s.connect(("127.0.0.1", PORT)) + logger.info(f"FastAPI server is listening on {HOST}:{PORT}") + logger.info("Local deplyment test successfully.") + break + except (ConnectionRefusedError, socket.timeout): + continue + + process.terminate() + process.wait() + def deploy( self, application_name: str, @@ -97,15 +164,22 @@ def deploy( gateway_upstream_name: str = "", use_studio: bool = False, use_adk_web: bool = False, + local_test: bool = False, ) -> CloudApp: """Deploy local agent project to Volcengine FaaS platform. Args: + application_name (str): Expected VeFaaS application name. path (str): Local agent project path. - name (str): Volcengine FaaS function name. + gateway_name (str): Gateway name. + gateway_service_name (str): Gateway service name. + gateway_upstream_name (str): Gateway upstream name. + use_studio (bool): Whether to use Studio [deprecated]. + use_adk_web (bool): Whether to use ADK Web. + local_test (bool): Whether to run local test for FastAPI Server. Returns: - str: Volcengine FaaS function endpoint. + CloudApp: The deployed cloud application instance. """ assert not (use_studio and use_adk_web), ( "use_studio and use_adk_web can not be True at the same time." @@ -136,6 +210,9 @@ def deploy( path = str(Path(path).resolve()) self._prepare(path, application_name) + if local_test: + self._try_launch_fastapi_server(path) + if not gateway_name: gateway_name = f"{application_name}-gw-{formatted_timestamp()}" if not gateway_service_name: diff --git a/veadk/integrations/ve_faas/template/deploy.py b/veadk/integrations/ve_faas/template/deploy.py index 42301bc4..691d3483 100644 --- a/veadk/integrations/ve_faas/template/deploy.py +++ b/veadk/integrations/ve_faas/template/deploy.py @@ -15,10 +15,11 @@ import asyncio from pathlib import Path +from a2a.types import TextPart +from fastmcp.client import Client from veadk.cloud.cloud_agent_engine import CloudAgentEngine -from fastmcp.client import Client -from veadk.cloud.cloud_app import get_message_id +from veadk.cloud.cloud_app import CloudApp, get_message_id SESSION_ID = "cloud_app_test_session" USER_ID = "cloud_app_test_user" @@ -27,59 +28,81 @@ GATEWAY_NAME = "" GATEWAY_SERVICE_NAME = "" GATEWAY_UPSTREAMNAME = "" -USE_STUDIO = False USE_ADK_WEB = False +async def _send_msg_with_a2a(cloud_app: CloudApp, message: str) -> None: + print("===== A2A example =====") + + response_message = await cloud_app.message_send(message, SESSION_ID, USER_ID) + + if not response_message or not response_message.parts: + print( + "No response from VeFaaS application. Something wrong with cloud application." + ) + return + + print(f"Message ID: {get_message_id(response_message)}") + + if isinstance(response_message.parts[0].root, TextPart): + print( + f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root.text}" + ) + else: + print( + f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root}" + ) + + +async def _send_msg_with_mcp(cloud_app: CloudApp, message: str) -> None: + print("===== MCP example =====") + + endpoint = cloud_app._get_vefaas_endpoint() + print(f"MCP server endpoint: {endpoint}/mcp") + + # Connect to MCP server + client = Client(f"{endpoint}/mcp") + + async with client: + # List available tools + tools = await client.list_tools() + print(f"Available tools: {tools}") + + # Call run_agent tool, pass user input and session information + res = await client.call_tool( + "run_agent", + { + "user_input": message, + "session_id": SESSION_ID, + "user_id": USER_ID, + }, + ) + print(f"Response from {cloud_app.vefaas_endpoint}: {res}") + + async def main(): engine = CloudAgentEngine() + cloud_app = engine.deploy( path=str(Path(__file__).parent / "src"), application_name=VEFAAS_APPLICATION_NAME, gateway_name=GATEWAY_NAME, gateway_service_name=GATEWAY_SERVICE_NAME, gateway_upstream_name=GATEWAY_UPSTREAMNAME, - use_studio=USE_STUDIO, use_adk_web=USE_ADK_WEB, + local_test=False, # Set to True for local testing before deploy to VeFaaS ) - query_example = "How is the weather like in Beijing?" - if not USE_STUDIO and (not USE_ADK_WEB): - print("### A2A example ###") - response_message = await cloud_app.message_send( - query_example, SESSION_ID, USER_ID - ) - print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") - print(f"Message ID: {get_message_id(response_message)}") - print( - f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root.text}" - ) + print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") - print("### MCP example ###") - # cloud_app = CloudApp(vefaas_application_name=VEFAAS_APPLICATION_NAME) - endpoint = cloud_app._get_vefaas_endpoint() - print(f"endpoint:{endpoint}") - # Connect to MCP server - client = Client(f"{endpoint}/mcp") - - async with client: - # List available tools - tools = await client.list_tools() - print(f"tools: {tools}\n") - - # Call run_agent tool, pass user input and session information - res = await client.call_tool( - "run_agent", - { - "user_input": query_example, - "session_id": SESSION_ID, - "user_id": USER_ID, - }, - ) - print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") - print(f"Response from {cloud_app.vefaas_endpoint}: {res}") - - else: + if USE_ADK_WEB: print(f"Web is running at: {cloud_app.vefaas_endpoint}") + else: + # Test with deployed cloud application + message = "How is the weather like in Beijing?" + print(f"Test message: {message}") + + await _send_msg_with_a2a(cloud_app=cloud_app, message=message) + await _send_msg_with_mcp(cloud_app=cloud_app, message=message) if __name__ == "__main__": diff --git a/veadk/integrations/ve_faas/template/src/__init__.py b/veadk/integrations/ve_faas/template/src/__init__.py index 3d2ceb5a..7f463206 100644 --- a/veadk/integrations/ve_faas/template/src/__init__.py +++ b/veadk/integrations/ve_faas/template/src/__init__.py @@ -11,4 +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 . import agent # noqa F401 diff --git a/veadk/integrations/ve_faas/template/src/agent.py b/veadk/integrations/ve_faas/template/src/agent.py index 8e579486..8bbe658d 100644 --- a/veadk/integrations/ve_faas/template/src/agent.py +++ b/veadk/integrations/ve_faas/template/src/agent.py @@ -12,14 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from veadk import Agent -from veadk.memory.short_term_memory import ShortTermMemory -from veadk.tools.demo_tools import get_city_weather -app_name: str = "weather-reporter" # <--- export your app name -agent: Agent = Agent(tools=[get_city_weather]) # <--- export your agent -short_term_memory: ShortTermMemory = ( - ShortTermMemory() -) # <--- export your short term memory +from weather_agent.agent import agent # import your agent here + +from veadk.memory.short_term_memory import ShortTermMemory +from veadk.types import AgentRunConfig -root_agent = agent +# [required] instantiate the agent run configuration +agent_run_config = AgentRunConfig( + app_name="weather-report", + agent=agent, + requirement_file_path="./weather_agent/requirements.txt", # relative path to the requirements file + short_term_memory=ShortTermMemory(), # default is a in-memory short term memory +) diff --git a/veadk/integrations/ve_faas/template/src/app.py b/veadk/integrations/ve_faas/template/src/app.py index 34b104c9..52a868fd 100644 --- a/veadk/integrations/ve_faas/template/src/app.py +++ b/veadk/integrations/ve_faas/template/src/app.py @@ -13,58 +13,99 @@ # limitations under the License. import os -from agent import agent, app_name, short_term_memory -from veadk.a2a.ve_a2a_server import init_app -from veadk.tracing.base_tracer import BaseTracer -from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer -from veadk.runner import Runner from contextlib import asynccontextmanager -from fastmcp import FastMCP +from typing import Callable + +from agent import agent_run_config from fastapi import FastAPI +from fastmcp import FastMCP -# ============================================================================== -# Tracer Config ================================================================ +from veadk.a2a.ve_a2a_server import init_app +from veadk.runner import Runner +from veadk.tracing.telemetry.exporters.apmplus_exporter import APMPlusExporter +from veadk.tracing.telemetry.exporters.cozeloop_exporter import CozeloopExporter +from veadk.tracing.telemetry.exporters.tls_exporter import TLSExporter +from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer +from veadk.types import AgentRunConfig +from veadk.utils.logger import get_logger -TRACERS: list[BaseTracer] = [] +logger = get_logger(__name__) + +assert isinstance(agent_run_config, AgentRunConfig), ( + f"Invalid agent_run_config type: {type(agent_run_config)}, expected `AgentRunConfig`" +) -exporters = [] -if os.getenv("VEADK_TRACER_APMPLUS", "").lower() == "true": - from veadk.tracing.telemetry.exporters.apmplus_exporter import APMPlusExporter +app_name = agent_run_config.app_name +agent = agent_run_config.agent +short_term_memory = agent_run_config.short_term_memory + + +def load_tracer() -> None: + EXPORTER_REGISTRY = { + "VEADK_TRACER_APMPLUS": APMPlusExporter, + "VEADK_TRACER_COZELOOP": CozeloopExporter, + "VEADK_TRACER_TLS": TLSExporter, + } + + exporters = [] + for env_var, exporter_cls in EXPORTER_REGISTRY.items(): + if os.getenv(env_var, "").lower() == "true": + if ( + agent.tracers + and isinstance(agent.tracers[0], OpentelemetryTracer) + and any(isinstance(e, exporter_cls) for e in agent.tracers[0].exporters) + ): + logger.warning( + f"Exporter {exporter_cls.__name__} is already defined in agent.tracers[0].exporters. These two exporters will be used at the same time. As a result, your data may be uploaded twice." + ) + else: + exporters.append(exporter_cls()) + + tracer = OpentelemetryTracer( + name="veadk_tracer", app_name=agent_run_config.app_name, exporters=exporters + ) + agent_run_config.agent.tracers.extend([tracer]) + tracer.do_hooks(agent=agent_run_config.agent) - exporters.append(APMPlusExporter()) -if os.getenv("VEADK_TRACER_COZELOOP", "").lower() == "true": - from veadk.tracing.telemetry.exporters.cozeloop_exporter import CozeloopExporter +def build_mcp_run_agent_func() -> Callable: + runner = Runner( + agent=agent, + short_term_memory=short_term_memory, + app_name=app_name, + user_id="", + ) - exporters.append(CozeloopExporter()) + async def run_agent( + user_input: str, + user_id: str = "mcp_user", + session_id: str = "mcp_session", + ) -> str: + # Set user_id for runner + runner.user_id = user_id -if os.getenv("VEADK_TRACER_TLS", "").lower() == "true": - from veadk.tracing.telemetry.exporters.tls_exporter import TLSExporter + # Running agent and get final output + final_output = await runner.run( + messages=user_input, + session_id=session_id, + ) + return final_output - exporters.append(TLSExporter()) + run_agent_doc = f"""{agent.description} + Args: + user_input: User's input message (required). + user_id: User identifier. Defaults to "mcp_user". + session_id: Session identifier. Defaults to "mcp_session". + Returns: + Final agent response as a string.""" -TRACERS.append(OpentelemetryTracer(exporters=exporters)) + run_agent.__doc__ = run_agent_doc + return run_agent -agent.tracers.extend(TRACERS) -if not getattr(agent, "before_model_callback", None): - agent.before_model_callback = [] -if not getattr(agent, "after_model_callback", None): - agent.after_model_callback = [] -if not getattr(agent, "after_tool_callback", None): - agent.after_tool_callback = [] -for tracer in TRACERS: - if tracer.tracer_hook_before_model not in agent.before_model_callback: - agent.before_model_callback.append(tracer.tracer_hook_before_model) - if tracer.tracer_hook_after_model not in agent.after_model_callback: - agent.after_model_callback.append(tracer.tracer_hook_after_model) - if tracer.tracer_hook_after_tool not in agent.after_tool_callback: - agent.after_tool_callback.append(tracer.tracer_hook_after_tool) -# Tracer Config ================================================================ -# ============================================================================== +load_tracer() -# Create A2A app a2a_app = init_app( server_url="0.0.0.0", app_name=app_name, @@ -72,46 +113,12 @@ short_term_memory=short_term_memory, ) -# Add a2a app to fastmcp -runner = Runner( - agent=agent, - short_term_memory=short_term_memory, - app_name=app_name, - user_id="", -) - -# Prepare doc string -run_agent_doc = f"""{agent.description} -Args: - user_input: User's input message (required). - user_id: User identifier. Defaults to "mcp_user". - session_id: Session identifier. Defaults to "mcp_session". -Returns: - Final agent response as a string.""" - - -async def run_agent( - user_input: str, - user_id: str = "mcp_user", - session_id: str = "mcp_session", -) -> str: - # Set user_id for runner - runner.user_id = user_id - - # Running agent and get final output - final_output = await runner.run( - messages=user_input, - session_id=session_id, - ) - return final_output +# Build a run_agent function for building MCP server +run_agent_func = build_mcp_run_agent_func() +a2a_app.post("/run_agent", operation_id="run_agent", tags=["mcp"])(run_agent_func) -run_agent.__doc__ = run_agent_doc - -# Add post route to run_agent -run_agent = a2a_app.post("/run_agent", operation_id="run_agent", tags=["mcp"])( - run_agent -) +# === Build mcp server === mcp = FastMCP.from_fastapi(app=a2a_app, name=app_name, include_tags={"mcp"}) @@ -135,3 +142,5 @@ async def combined_lifespan(app: FastAPI): # Mount MCP server at /mcp endpoint app.mount("/mcp", mcp_app) + +# === Build mcp server end === diff --git a/veadk/integrations/ve_faas/template/src/requirements.txt b/veadk/integrations/ve_faas/template/src/requirements.txt deleted file mode 100644 index 3297e021..00000000 --- a/veadk/integrations/ve_faas/template/src/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -veadk-python[eval] @ git+https://github.com/volcengine/veadk-python.git # extra eval for prompt optimization in veadk studio -opensearch-py==2.8.0 -agent-pilot-sdk>=0.0.9 # extra dep for prompt optimization in veadk studio -typer>=0.16.0 -uvicorn[standard] -fastapi -fastmcp \ No newline at end of file diff --git a/veadk/integrations/ve_faas/template/src/run.sh b/veadk/integrations/ve_faas/template/src/run.sh index cfe6c362..0659068f 100755 --- a/veadk/integrations/ve_faas/template/src/run.sh +++ b/veadk/integrations/ve_faas/template/src/run.sh @@ -40,29 +40,15 @@ python3 -m pip install fastapi 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 - exec python3 -m uvicorn studio_app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio -elif [ "$USE_STUDIO" = "False" ]; then - echo "USE_STUDIO is False" - - if [ "$USE_ADK_WEB" = "True" ]; then - echo "USE_ADK_WEB is True, running veadk web" - # running veadk web - cd ../ - 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 - fi +if [ "$USE_ADK_WEB" = "True" ]; then + echo "USE_ADK_WEB is True, running veadk web" + exec python3 -m veadk.cli.cli web --host $HOST else - # running a2a server + echo "USE_ADK_WEB is False, running A2A and MCP server" exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio -fi \ No newline at end of file +fi diff --git a/veadk/integrations/ve_faas/template/src/studio_app.py b/veadk/integrations/ve_faas/template/src/studio_app.py deleted file mode 100644 index 9e36effe..00000000 --- a/veadk/integrations/ve_faas/template/src/studio_app.py +++ /dev/null @@ -1,47 +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. - -import os -from importlib.util import module_from_spec, spec_from_file_location -from pathlib import Path - -from veadk.cli.studio.fast_api import get_fast_api_app - -path = Path(__file__).parent.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) diff --git a/veadk/integrations/ve_faas/template/src/weather_agent/__init__.py b/veadk/integrations/ve_faas/template/src/weather_agent/__init__.py new file mode 100644 index 00000000..4ee68243 --- /dev/null +++ b/veadk/integrations/ve_faas/template/src/weather_agent/__init__.py @@ -0,0 +1,14 @@ +# +# 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 . import agent # noqa diff --git a/veadk/integrations/ve_faas/template/src/weather_agent/agent.py b/veadk/integrations/ve_faas/template/src/weather_agent/agent.py new file mode 100644 index 00000000..89c113d4 --- /dev/null +++ b/veadk/integrations/ve_faas/template/src/weather_agent/agent.py @@ -0,0 +1,39 @@ +# 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. + +"""Note + +Once you put your own agent project in this `src/` directory, you may: + +1. remove the template code +2. update the imports in this file to point to your agent's location (e.g., from demo_agent.agent import root_agent, ...) +3. MUST export three global variables from this file: + - `app_name` + - `root_agent`: The root agent instance + - `short_term_memory`: The short-term memory instance +""" + +from veadk import Agent +from veadk.tools.demo_tools import get_city_weather + +# define your agent here +agent: Agent = Agent( + name="weather_reporter", + description="A reporter for weather updates", + instruction="Once user ask you weather of a city, you need to provide the weather report for that city by calling `get_city_weather`.", + tools=[get_city_weather], +) + +# required from Google ADK Web +root_agent = agent diff --git a/veadk/integrations/ve_faas/template/src/weather_agent/requirements.txt b/veadk/integrations/ve_faas/template/src/weather_agent/requirements.txt new file mode 100644 index 00000000..a9ea2598 --- /dev/null +++ b/veadk/integrations/ve_faas/template/src/weather_agent/requirements.txt @@ -0,0 +1 @@ +git+https://github.com/volcengine/veadk-python.git \ No newline at end of file diff --git a/veadk/tracing/base_tracer.py b/veadk/tracing/base_tracer.py index 1a0b2c2f..deeffddd 100644 --- a/veadk/tracing/base_tracer.py +++ b/veadk/tracing/base_tracer.py @@ -208,3 +208,18 @@ def tracer_hook_after_tool( def set_app_name(self, app_name): self.app_name = app_name + + def do_hooks(self, agent) -> None: + if not getattr(agent, "before_model_callback", None): + agent.before_model_callback = [] + if not getattr(agent, "after_model_callback", None): + agent.after_model_callback = [] + if not getattr(agent, "after_tool_callback", None): + agent.after_tool_callback = [] + + if self.tracer_hook_before_model not in agent.before_model_callback: + agent.before_model_callback.append(self.tracer_hook_before_model) + if self.tracer_hook_after_model not in agent.after_model_callback: + agent.after_model_callback.append(self.tracer_hook_after_model) + if self.tracer_hook_after_tool not in agent.after_tool_callback: + agent.after_tool_callback.append(self.tracer_hook_after_tool) diff --git a/veadk/types.py b/veadk/types.py index 9210cd0c..de4d65bb 100644 --- a/veadk/types.py +++ b/veadk/types.py @@ -12,7 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pydantic import BaseModel +from pydantic import BaseModel, Field + +from veadk.agent import Agent +from veadk.memory.short_term_memory import ShortTermMemory class MediaMessage(BaseModel): @@ -21,3 +24,23 @@ class MediaMessage(BaseModel): media: str """Media file (e.g., `.pdf`, `.docx`, `.png`, `.jpg`, `.jpeg`, `.mp4`, `.mp3`, `.wav`, `.txt`) path""" + + +class AgentRunConfig(BaseModel): + """Configuration for running an agent on VeFaaS platform.""" + + model_config = {"arbitrary_types_allowed": True} + + app_name: str = Field( + default="veadk_vefaas_app", description="The name of the application" + ) + + agent: Agent = Field(..., description="The root agent instance") + + short_term_memory: ShortTermMemory = Field( + default_factory=ShortTermMemory, description="The short-term memory instance" + ) + + requirement_file_path: str = Field( + ..., description="The relative path to the requirements file" + ) From 38aa5a1bcbeb7809c8e141d3435a594c934bdc57 Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Tue, 19 Aug 2025 12:07:43 +0800 Subject: [PATCH 2/2] chore: add save tracing file option --- veadk/runner.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/veadk/runner.py b/veadk/runner.py index 7327ed3e..3d7e80a2 100644 --- a/veadk/runner.py +++ b/veadk/runner.py @@ -145,6 +145,7 @@ async def run( messages: RunnerMessage, session_id: str, stream: bool = False, + save_tracing_file: bool = False, ): converted_messages: list = self._convert_messages(messages) @@ -159,7 +160,8 @@ async def run( final_output = await self._run(session_id, converted_message, stream) # try to save tracing file - self.save_tracing_file(session_id) + if save_tracing_file: + self.save_tracing_file(session_id) return final_output @@ -173,6 +175,7 @@ def save_tracing_file(self, session_id: str) -> str: return "" if not self.agent.tracers: + logger.warning(("No tracer found in your agent. Skip dump tracing file.")) return "" try: