From c83332365b470269042b9d0ab7ed1a9ca0f7d2c5 Mon Sep 17 00:00:00 2001 From: "hanzhi.421" Date: Wed, 6 Aug 2025 21:13:55 +0800 Subject: [PATCH] feat: deploy with web and studio --- pyproject.toml | 2 +- veadk/cli/main.py | 6 +- .../cli/services/vefaas/template/__init__.py | 13 +++ veadk/cli/services/vefaas/template/deploy.py | 4 +- .../services/vefaas/template/src/__init__.py | 14 +++ .../cli/services/vefaas/template/src/agent.py | 2 + .../vefaas/template/src/requirements.txt | 1 + veadk/cli/services/vefaas/template/src/run.sh | 22 +++-- veadk/cli/services/vefaas/vefaas.py | 97 ++++++++----------- veadk/cli/studio/studio_processor.py | 1 + veadk/cloud/cloud_agent_engine.py | 13 +++ veadk/cloud/template/agent.py | 1 + veadk/cloud/template/run.sh | 22 +++-- .../deepeval_evaluator/deepeval_evaluator.py | 31 +++--- 14 files changed, 134 insertions(+), 95 deletions(-) create mode 100644 veadk/cli/services/vefaas/template/__init__.py create mode 100644 veadk/cli/services/vefaas/template/src/__init__.py diff --git a/pyproject.toml b/pyproject.toml index bd5eea6f..6d976580 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,4 +70,4 @@ exclude = ["assets*", "ide*", "tests*"] include-package-data = true [tool.setuptools.package-data] -"veadk.cli.studio.web" = ["_next/**/*"] \ No newline at end of file +"veadk" = ["**/*"] \ No newline at end of file diff --git a/veadk/cli/main.py b/veadk/cli/main.py index 6587d410..cda57c89 100644 --- a/veadk/cli/main.py +++ b/veadk/cli/main.py @@ -129,6 +129,10 @@ def web( None, "--session_service_uri", ), + host: str = typer.Option( + "127.0.0.1", + "--host", + ), ): from google.adk.memory import in_memory_memory_service @@ -144,7 +148,7 @@ def web( session_service_uri = "" cli_tools_click.cli_web.main( - args=[agents_dir, "--session_service_uri", session_service_uri] + args=[agents_dir, "--session_service_uri", session_service_uri, "--host", host] ) diff --git a/veadk/cli/services/vefaas/template/__init__.py b/veadk/cli/services/vefaas/template/__init__.py new file mode 100644 index 00000000..7f463206 --- /dev/null +++ b/veadk/cli/services/vefaas/template/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/veadk/cli/services/vefaas/template/deploy.py b/veadk/cli/services/vefaas/template/deploy.py index 3fa5db44..6b8de282 100644 --- a/veadk/cli/services/vefaas/template/deploy.py +++ b/veadk/cli/services/vefaas/template/deploy.py @@ -21,6 +21,7 @@ USER_ID = "cloud_app_test_user" USE_STUDIO = False +USE_ADK_WEB = False async def main(): @@ -29,10 +30,11 @@ async def main(): path=str(Path(__file__).parent / "src"), name="weather-reporter", # <--- set your application name use_studio=USE_STUDIO, + use_adk_web=USE_ADK_WEB, # gateway_name="", # <--- set your gateway instance name if you have one ) - if not USE_STUDIO: + if not USE_STUDIO and not USE_ADK_WEB: response_message = await cloud_app.message_send( "How is the weather like in Beijing?", SESSION_ID, USER_ID ) diff --git a/veadk/cli/services/vefaas/template/src/__init__.py b/veadk/cli/services/vefaas/template/src/__init__.py new file mode 100644 index 00000000..3d2ceb5a --- /dev/null +++ b/veadk/cli/services/vefaas/template/src/__init__.py @@ -0,0 +1,14 @@ +# 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 . import agent # noqa F401 diff --git a/veadk/cli/services/vefaas/template/src/agent.py b/veadk/cli/services/vefaas/template/src/agent.py index 79155143..8e579486 100644 --- a/veadk/cli/services/vefaas/template/src/agent.py +++ b/veadk/cli/services/vefaas/template/src/agent.py @@ -21,3 +21,5 @@ short_term_memory: ShortTermMemory = ( ShortTermMemory() ) # <--- export your short term memory + +root_agent = agent diff --git a/veadk/cli/services/vefaas/template/src/requirements.txt b/veadk/cli/services/vefaas/template/src/requirements.txt index 423f10f6..33982df8 100644 --- a/veadk/cli/services/vefaas/template/src/requirements.txt +++ b/veadk/cli/services/vefaas/template/src/requirements.txt @@ -1,4 +1,5 @@ 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 uvicorn[standard] fastapi \ No newline at end of file diff --git a/veadk/cli/services/vefaas/template/src/run.sh b/veadk/cli/services/vefaas/template/src/run.sh index 1b7e4a54..dc5b268d 100755 --- a/veadk/cli/services/vefaas/template/src/run.sh +++ b/veadk/cli/services/vefaas/template/src/run.sh @@ -39,21 +39,25 @@ python3 -m pip install uvicorn[standard] python3 -m pip install fastapi USE_STUDIO=${USE_STUDIO:-False} +USE_ADK_WEB=${USE_ADK_WEB:-False} 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, running a2a server" - - # running a2a server - exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio + 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.main 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 else - echo "USE_STUDIO is an invalid value: $USE_STUDIO, running a2a server." - # running a2a 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 \ No newline at end of file diff --git a/veadk/cli/services/vefaas/vefaas.py b/veadk/cli/services/vefaas/vefaas.py index 60f6cb2d..3902098b 100644 --- a/veadk/cli/services/vefaas/vefaas.py +++ b/veadk/cli/services/vefaas/vefaas.py @@ -26,6 +26,7 @@ TagForCreateFunctionInput, ) +import veadk.config from veadk.cli.services.veapig.apig import APIGateway from veadk.utils.logger import get_logger from veadk.utils.misc import formatted_timestamp @@ -58,27 +59,16 @@ def __init__(self, access_key: str, secret_key: str, region: str = "cn-beijing") self.template_id = "6874f3360bdbc40008ecf8c7" - def _create_function(self, name: str, path: str): - function_name = f"{name}-fn-{formatted_timestamp()}" - - # 1. Create a function instance in cloud - typer.echo( - typer.style("Runtime: native-python3.10/v1", fg=typer.colors.BRIGHT_BLACK) - ) - + def _create_function(self, function_name: str, path: str): + # 1. Read envs envs = [] - - import veadk.config - for key, value in veadk.config.veadk_environments.items(): envs.append(EnvForCreateFunctionInput(key=key, value=value)) - typer.echo( - typer.style( - f"Fetch {len(envs)} environment variables.", - fg=typer.colors.BRIGHT_BLACK, - ) + logger.info( + f"Fetch {len(envs)} environment variables.", ) + # 2. Create function res = self.client.create_function( volcenginesdkvefaas.CreateFunctionRequest( command="./run.sh", @@ -88,21 +78,17 @@ def _create_function(self, name: str, path: str): runtime="native-python3.10/v1", request_timeout=1800, envs=envs, - # tls_config=TlsConfigForCreateFunctionInput(enable_log=True), ) ) function_id = res.id - # 2. Get a temp bucket to store code - # proj_path = get_project_path() + # 3. Get a temp bucket to store code code_zip_data, code_zip_size, error = zip_and_encode_folder(path) - typer.echo( - typer.style( - f"Zipped project size: {code_zip_size / 1024 / 1024:.2f} MB", - fg=typer.colors.BRIGHT_BLACK, - ) + logger.info( + f"Zipped project size: {code_zip_size / 1024 / 1024:.2f} MB", ) + # 4. Upload code to VeFaaS temp bucket req = volcenginesdkvefaas.GetCodeUploadAddressRequest( function_id=function_id, content_length=code_zip_size ) @@ -113,14 +99,11 @@ def _create_function(self, name: str, path: str): "Content-Type": "application/zip", } response = requests.put(url=upload_url, data=code_zip_data, headers=headers) - if 200 <= response.status_code < 300: - # print(f"Upload successful! Size: {code_zip_size / 1024 / 1024:.2f} MB") - pass - else: + if not (200 <= response.status_code < 300): error_message = f"Upload failed to {upload_url} with status code {response.status_code}: {response.text}" raise ValueError(error_message) - # 3. Mount the TOS bucket to function instance + # 5. Mount the TOS bucket to function instance res = signed_request( ak=self.ak, sk=self.sk, @@ -128,13 +111,6 @@ def _create_function(self, name: str, path: str): body={"FunctionId": function_id}, ) - typer.echo( - typer.style( - f"Function ID on VeFaaS service: {function_id}", - fg=typer.colors.BRIGHT_BLACK, - ) - ) - return function_name, function_id def _create_application( @@ -183,23 +159,21 @@ def _release_application(self, app_id: str): host="open.volcengineapi.com", ) + logger.info(f"Start to release VeFaaS application {app_id}.") status, full_response = self._get_application_status(app_id) while status not in ["deploy_success", "deploy_fail"]: time.sleep(10) - typer.echo( - typer.style( - f"Current status: {status}", - fg=typer.colors.BRIGHT_BLACK, - ) - ) status, full_response = self._get_application_status(app_id) assert status == "deploy_success", ( f"Release application failed. Response: {full_response}" ) + cloud_resource = full_response["Result"]["CloudResource"] cloud_resource = json.loads(cloud_resource) + url = cloud_resource["framework"]["url"]["system_url"] + return url def _get_application_status(self, app_id: str): @@ -257,15 +231,29 @@ def delete(self, app_id: str): def deploy( self, - name: str, # application name + name: str, path: str, gateway_name: str = "", gateway_service_name: str = "", gateway_upstream_name: str = "", ) -> tuple[str, str, str]: + """Deploy an agent project to VeFaaS service. + + Args: + name (str): Application name (warning: not function name). + path (str): Project path. + gateway_name (str, optional): Gateway name. Defaults to "". + gateway_service_name (str, optional): Gateway service name. Defaults to "". + gateway_upstream_name (str, optional): Gateway upstream name. Defaults to "". + + Returns: + tuple[str, str, str]: (url, app_id, function_id) + """ + # Naming check if "_" in name: raise ValueError("Function or Application name cannot contain '_'.") + # Give default names if not gateway_name: gateway_name = f"{name}-gw-{formatted_timestamp()}" @@ -286,14 +274,15 @@ def deploy( if not gateway_upstream_name: gateway_upstream_name = f"{name}-gw-us-{formatted_timestamp()}" - typer.echo( - typer.style("[1/3] ", fg=typer.colors.GREEN) - + "Create VeFaaS service on cloud." + function_name = f"{name}-fn" + + logger.info( + f"Start to create VeFaaS function {function_name} with path {path}. Gateway: {gateway_name}, Gateway Service: {gateway_service_name}, Gateway Upstream: {gateway_upstream_name}." ) - typer.echo(typer.style(f"Project path: {path}", fg=typer.colors.BRIGHT_BLACK)) - function_name, function_id = self._create_function(name, path) + function_name, function_id = self._create_function(function_name, path) + logger.info(f"VeFaaS function {function_name} with ID {function_id} created.") - typer.echo(typer.style("[2/3] ", fg=typer.colors.GREEN) + "Create application.") + logger.info(f"Start to create VeFaaS application {name}.") app_id = self._create_application( name, function_name, @@ -302,13 +291,11 @@ def deploy( gateway_service_name, ) - typer.echo( - typer.style("[3/3] ", fg=typer.colors.GREEN) + "Release application." - ) + logger.info(f"VeFaaS application {name} with ID {app_id} created.") + logger.info(f"Start to release VeFaaS application {app_id}.") url = self._release_application(app_id) + logger.info(f"VeFaaS application {name} with ID {app_id} released.") - typer.echo( - typer.style(f"\nSuccessfully deployed on:\n\n{url}", fg=typer.colors.BLUE) - ) + logger.info(f"VeFaaS application {name} with ID {app_id} deployed on {url}.") return url, app_id, function_id diff --git a/veadk/cli/studio/studio_processor.py b/veadk/cli/studio/studio_processor.py index bfc5728b..ae418998 100644 --- a/veadk/cli/studio/studio_processor.py +++ b/veadk/cli/studio/studio_processor.py @@ -94,6 +94,7 @@ async def evaluate(self): metrics = [ GEval( name="Correctness&MatchDegree", + model=self.evaluator.judge_model, criteria="Judge the correctness and match degree of the model output with the expected output.", evaluation_params=[ LLMTestCaseParams.INPUT, diff --git a/veadk/cloud/cloud_agent_engine.py b/veadk/cloud/cloud_agent_engine.py index b58de229..8d4b1d0a 100644 --- a/veadk/cloud/cloud_agent_engine.py +++ b/veadk/cloud/cloud_agent_engine.py @@ -109,6 +109,7 @@ def deploy( gateway_service_name: str = "", gateway_upstream_name: str = "", use_studio: bool = False, + use_adk_web: bool = False, ) -> CloudApp: """Deploy local agent project to Volcengine FaaS platform. @@ -119,6 +120,9 @@ def deploy( Returns: str: Volcengine FaaS function endpoint. """ + assert not (use_studio and use_adk_web), ( + "use_studio and use_adk_web can not be True at the same time." + ) # prevent deepeval writing operations import veadk.config @@ -133,6 +137,15 @@ def deploy( veadk.config.veadk_environments["USE_STUDIO"] = "False" + if use_adk_web: + import veadk.config + + veadk.config.veadk_environments["USE_ADK_WEB"] = "True" + else: + import veadk.config + + veadk.config.veadk_environments["USE_ADK_WEB"] = "False" + # convert `path` to absolute path path = str(Path(path).resolve()) self._prepare(path, name) diff --git a/veadk/cloud/template/agent.py b/veadk/cloud/template/agent.py index b9e51893..3cb2a081 100644 --- a/veadk/cloud/template/agent.py +++ b/veadk/cloud/template/agent.py @@ -17,3 +17,4 @@ agent: Agent = ... app_name: str = ... short_term_memory: ShortTermMemory = ... +root_agent = agent diff --git a/veadk/cloud/template/run.sh b/veadk/cloud/template/run.sh index 1b7e4a54..dc5b268d 100644 --- a/veadk/cloud/template/run.sh +++ b/veadk/cloud/template/run.sh @@ -39,21 +39,25 @@ python3 -m pip install uvicorn[standard] python3 -m pip install fastapi USE_STUDIO=${USE_STUDIO:-False} +USE_ADK_WEB=${USE_ADK_WEB:-False} 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, running a2a server" - - # running a2a server - exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio + 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.main 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 else - echo "USE_STUDIO is an invalid value: $USE_STUDIO, running a2a server." - # running a2a 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 \ No newline at end of file diff --git a/veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py b/veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py index e33467c4..f326115d 100644 --- a/veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +++ b/veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py @@ -15,8 +15,8 @@ import time from deepeval import evaluate +from deepeval.evaluate import CacheConfig from deepeval.evaluate.types import EvaluationResult -from deepeval.key_handler import KEY_FILE_HANDLER, ModelKeyValues from deepeval.metrics import BaseMetric from deepeval.models import LocalModel from deepeval.test_case import LLMTestCase @@ -57,27 +57,14 @@ def __init__( super().__init__(agent=agent, name=name) self.judge_model_name = judge_model_name - self.judge_model = self.create_judge_model( - model_name=judge_model_name, + self.judge_model = LocalModel( + model=judge_model_name, + base_url=judge_model_api_base, api_key=judge_model_api_key, - api_base=judge_model_api_base, ) self.prometheus_config = prometheus_config - def create_judge_model( - self, - model_name: str, - api_key: str, - api_base: str, - ): - KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_NAME, model_name) - KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_BASE_URL, api_base) - KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_API_KEY, api_key) - KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_LOCAL_MODEL, "YES") - KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_AZURE_OPENAI, "NO") - return LocalModel() - @override async def eval( self, @@ -86,7 +73,9 @@ async def eval( eval_id: str = f"test_{formatted_timestamp()}", ): """Target to Google ADK, we will use the same evaluation case format as Google ADK.""" - + for metric in metrics: + if not metric.model: + metric.model = self.judge_model # Get evaluation data by parsing eval set file self.generate_eval_data(eval_set_file_path) # Get actual data by running agent @@ -137,7 +126,11 @@ async def eval( # Run Deepeval evaluation according to metrics logger.info("Start to run Deepeval evaluation according to metrics.") - test_results = evaluate(test_cases=test_cases, metrics=metrics) + test_results = evaluate( + test_cases=test_cases, + metrics=metrics, + cache_config=CacheConfig(write_cache=False), + ) for test_result in test_results.test_results: eval_result_data = EvalResultData(metric_results=[]) for metrics_data_item in test_result.metrics_data: