From f815e019ae1b5ed0b140b86afa2655ea34633154 Mon Sep 17 00:00:00 2001 From: Anycodes Date: Sun, 14 Dec 2025 01:11:58 +0800 Subject: [PATCH 1/9] add docs --- docs/tutorial/00-overview.md | 79 ++++ docs/tutorial/01-installation.md | 157 +++++++ docs/tutorial/02-first-agent.md | 213 +++++++++ docs/tutorial/03-model-basics.md | 268 ++++++++++++ docs/tutorial/04-tool-basics.md | 277 ++++++++++++ docs/tutorial/05-sandbox-basics.md | 505 ++++++++++++++++++++++ docs/tutorial/06-framework-integration.md | 330 ++++++++++++++ docs/tutorial/07-deploy-production.md | 288 ++++++++++++ docs/tutorial/08-best-practices.md | 467 ++++++++++++++++++++ docs/tutorial/intro.md | 9 - docs/tutorial/quick-start.md | 240 ++++++---- 11 files changed, 2751 insertions(+), 82 deletions(-) create mode 100644 docs/tutorial/00-overview.md create mode 100644 docs/tutorial/01-installation.md create mode 100644 docs/tutorial/02-first-agent.md create mode 100644 docs/tutorial/03-model-basics.md create mode 100644 docs/tutorial/04-tool-basics.md create mode 100644 docs/tutorial/05-sandbox-basics.md create mode 100644 docs/tutorial/06-framework-integration.md create mode 100644 docs/tutorial/07-deploy-production.md create mode 100644 docs/tutorial/08-best-practices.md delete mode 100644 docs/tutorial/intro.md diff --git a/docs/tutorial/00-overview.md b/docs/tutorial/00-overview.md new file mode 100644 index 0000000..41f7707 --- /dev/null +++ b/docs/tutorial/00-overview.md @@ -0,0 +1,79 @@ +--- +sidebar_position: 1 +--- + +# SDK 概览与架构介绍 + +## 什么是 AgentRun + +AgentRun 是以高代码为核心,开放生态、灵活组装的一站式 Agentic AI 基础设施平台,为企业级 Agentic 应用提供开发、部署与运维全生命周期管理。平台基于 Serverless 架构提供强隔离的运行时与沙箱环境,深度集成开源生态,为用户提供模型高可用和数据不出域能力。 + +AgentRun Python SDK 是平台的官方客户端库,提供了完整的 Python API 来管理和调用 AgentRun 服务。通过这个 SDK,开发者可以在本地开发环境中编写、测试 Agent 应用,并无缝部署到云端运行。 + +## SDK 的核心价值 + +AgentRun Python SDK 采用面向对象的设计理念,将云端资源抽象为 Python 对象,使开发者能够像操作本地对象一样管理云端资源。SDK 同时提供同步和异步两套 API,支持多种认证方式,并包含完整的类型注解,为 IDE 提供良好的代码提示支持。 + +SDK 的一个重要特性是对主流 AI 开发框架的深度集成。您可以继续使用熟悉的框架(如 LangChain、AgentScope、LangGraph、CrewAI 等)编写 Agent 逻辑,SDK 会自动处理与 AgentRun 平台的对接工作,包括模型调用、工具执行、沙箱管理等底层细节。 + +## 核心概念 + +### Agent Runtime + +Agent Runtime 是 AgentRun 平台上运行 Agent 应用的基本单元。每个 Agent Runtime 封装了您的 Agent 代码、依赖环境和运行配置。SDK 提供了完整的 Agent Runtime 生命周期管理能力,包括创建、更新、删除和监控等操作。 + +Agent Runtime 支持两种运行方式:代码模式和容器模式。代码模式下,您只需提供 Python 代码和依赖列表,平台会自动构建运行环境;容器模式则允许您使用自定义的容器镜像,适合有特殊环境需求的场景。 + +每个 Agent Runtime 可以创建多个访问端点(Endpoint),支持不同的路由策略和版本管理。例如,您可以创建一个生产端点和一个测试端点,分别指向不同版本的 Agent 代码,实现灰度发布和 A/B 测试。 + +### Model + +AgentRun 提供了统一的模型管理能力,屏蔽不同模型供应商的 API 差异。SDK 支持两种模型接入方式:Model Service 用于接入自建或第三方模型服务,Model Proxy 则提供模型代理和治理功能,包括负载均衡、故障转移、请求限流等企业级特性。 + +通过 SDK 的模型 API,您可以使用统一的接口调用不同供应商的模型。SDK 会自动处理请求格式转换、认证、重试等细节,让您专注于业务逻辑的开发。同时,SDK 提供了便捷的转换函数,可以将 AgentRun 模型对象转换为各个框架所需的格式。 + +### Sandbox + +沙箱环境是 AgentRun 的核心安全特性之一。SDK 目前支持两类沙箱:代码解释器沙箱(Code Interpreter)用于执行动态生成的代码,浏览器沙箱(Browser)则提供完整的浏览器自动化能力。 + +代码解释器沙箱支持 Python 和 Shell 脚本执行,提供了完整的文件系统操作、进程管理和执行上下文管理功能。您可以在沙箱中上传文件、执行代码、下载结果,所有操作都在隔离的容器环境中进行,确保宿主系统的安全。 + +浏览器沙箱基于 Chromium 内核,集成了 Playwright 自动化框架。SDK 提供了同步和异步两套 Playwright API 封装,支持页面导航、元素操作、截图录制等完整的浏览器控制能力。沙箱还支持远程调试和实时预览,方便开发调试。 + +### ToolSet + +ToolSet 是 AgentRun 的工具管理系统。SDK 支持多种工具定义方式,包括基于 OpenAPI 规范的 HTTP 工具和基于 MCP(Model Context Protocol)的标准化工具。您可以通过 SDK 获取平台上已注册的工具集,并自动转换为各个框架所需的工具格式。 + +对于 OpenAPI 工具,SDK 会解析 OpenAPI 规范文档,自动生成工具描述和参数定义,并处理 HTTP 请求的构建和发送。对于 MCP 工具,SDK 实现了完整的 MCP 协议客户端,可以与任何符合 MCP 规范的工具服务通信。 + +### Credential + +凭证管理用于存储和管理访问第三方服务所需的密钥。SDK 支持多种凭证类型,包括 API Key、Basic Auth、OAuth Token 等。您可以在 AgentRun 平台上集中管理凭证,Agent 运行时会自动注入所需的凭证,避免在代码中硬编码敏感信息。 + +### Integration + +SDK 的集成模块提供了统一的适配器层,将 AgentRun 的模型、工具、沙箱等能力转换为各个框架的原生对象。目前支持的框架包括 LangChain、LangGraph、AgentScope、CrewAI、Google ADK 和 Pydantic AI。 + +集成的核心设计是 CommonModel 和 CommonToolSet 抽象。这两个抽象类封装了 AgentRun 的底层 API,并提供了转换方法(如 `to_langchain()`、`to_agentscope()`),可以将对象转换为对应框架的格式。这种设计使得您可以编写框架无关的代码,在需要时轻松切换框架。 + +### Server + +AgentRunServer 是一个轻量级的 HTTP 服务器组件,可以将您的 Agent 封装为 HTTP API。Server 内置了 OpenAI Chat Completions 协议的实现,使您的 Agent 可以直接替换 OpenAI API 使用。 + +Server 基于 FastAPI 构建,采用插件化的协议设计。除了内置的 OpenAI 协议,您也可以实现自定义协议处理器,支持其他 API 规范。Server 自动处理请求解析、响应格式化、流式输出等细节,让您只需关注 Agent 的业务逻辑。 + +## 架构设计 + +AgentRun Python SDK 采用分层架构设计。最底层是 Control API 和 Data API,分别负责资源管理和数据操作。Control API 用于创建、更新、删除云端资源,Data API 则用于与运行中的资源交互,如调用模型、执行沙箱命令等。 + +在 API 层之上是资源对象层,将云端资源抽象为 Python 类,如 AgentRuntime、ModelService、Sandbox 等。这些类封装了底层 API 调用,提供了面向对象的操作接口。资源对象支持方法链调用,可以流畅地完成复杂操作。 + +最上层是集成模块和工具类。集成模块提供框架适配器,工具类则包含配置管理、日志记录、异常处理等辅助功能。这种分层设计既保证了底层能力的完整性,又为上层使用提供了便利。 + +## 适用场景 + +AgentRun Python SDK 适用于需要构建企业级 AI Agent 应用的场景。典型应用包括智能客服系统、数据分析助手、代码生成工具、网页自动化机器人等。通过 SDK 提供的沙箱环境和工具系统,您可以让 Agent 安全地执行代码、访问外部 API、操作浏览器,完成复杂的任务流程。 + +对于已经使用某个 AI 开发框架的团队,SDK 的集成能力可以帮助您快速迁移到 AgentRun 平台,享受平台提供的模型高可用、资源弹性伸缩、运维监控等企业级能力,而无需重写已有代码。 + +对于需要严格数据安全和合规要求的场景,AgentRun 的数据不出域特性和隔离沙箱环境可以确保敏感数据的安全处理。您可以在私有网络环境中部署模型服务和工具服务,通过 SDK 连接到 AgentRun 平台,实现数据和计算的物理隔离。 \ No newline at end of file diff --git a/docs/tutorial/01-installation.md b/docs/tutorial/01-installation.md new file mode 100644 index 0000000..6392242 --- /dev/null +++ b/docs/tutorial/01-installation.md @@ -0,0 +1,157 @@ +--- +sidebar_position: 2 +--- + +# 安装与配置 + +## 环境要求 + +AgentRun Python SDK 要求 Python 版本在 3.10 或更高。建议使用虚拟环境来管理项目依赖,避免与系统 Python 环境产生冲突。您可以使用 venv、virtualenv 或 conda 等工具创建虚拟环境。 + +在安装 SDK 之前,请确保您已拥有阿里云账号,并已开通 AgentRun 服务。您需要准备好阿里云的 Access Key ID、Access Key Secret 和账号 ID,这些凭证将用于 SDK 与云端服务的认证。 + +## 基础安装 + +使用 pip 包管理器可以快速安装 AgentRun SDK。在终端中执行以下命令即可完成基础安装: + +```bash +pip install agentrun-sdk +``` + +基础安装包含了 SDK 的核心功能,包括 Agent Runtime 管理、模型调用、凭证管理等基础能力。对于大多数简单场景,基础安装已经足够使用。 + +## 可选依赖安装 + +AgentRun SDK 采用模块化设计,将不同的功能特性作为可选依赖项提供。这样可以避免安装不必要的依赖包,减小环境体积。根据您的实际需求,可以选择安装以下可选依赖: + +**server** 依赖用于启用 HTTP 服务器功能。如果您需要使用 AgentRunServer 将 Agent 封装为 HTTP API,需要安装此依赖。该依赖包含 FastAPI 和 Uvicorn 等 Web 框架组件。 + +**playwright** 依赖用于支持浏览器沙箱功能。当您需要让 Agent 进行网页自动化操作时,需要安装此依赖。安装后还需要运行 Playwright 的初始化命令来下载浏览器驱动。 + +**mcp** 依赖用于支持 MCP(Model Context Protocol)工具集。如果您的 Agent 需要调用基于 MCP 协议的工具服务,需要安装此依赖。 + +**框架集成依赖**包括 langchain、agentscope、google-adk、crewai、pydantic-ai 等。这些依赖分别对应不同的 AI 开发框架。如果您使用某个框架进行 Agent 开发,需要安装对应的集成依赖。 + +安装可选依赖时,在包名后用方括号指定依赖项名称,多个依赖项之间用逗号分隔。例如,如果您需要使用 AgentScope 框架,同时需要浏览器沙箱和 MCP 工具支持,可以执行: + +```bash +pip install agentrun-sdk[playwright,mcp,agentscope] +``` + +对于使用 LangChain 框架并需要 HTTP 服务器功能的场景: + +```bash +pip install agentrun-sdk[server,langchain] +``` + +如果您不确定需要哪些依赖,可以先安装基础版本,在遇到功能缺失时再按需补充安装。 + +## 配置认证信息 + +安装完成后,您需要配置认证信息才能使用 SDK。AgentRun SDK 支持多种配置方式,包括环境变量、代码配置和配置文件,您可以根据实际场景选择合适的方式。 + +### 使用环境变量配置 + +环境变量是推荐的配置方式,特别适合生产环境和容器化部署。SDK 会自动读取以下环境变量: + +```bash +export AGENTRUN_ACCESS_KEY_ID="your-access-key-id" +export AGENTRUN_ACCESS_KEY_SECRET="your-access-key-secret" +export AGENTRUN_ACCOUNT_ID="your-account-id" +export AGENTRUN_REGION="cn-hangzhou" +``` + +这四个环境变量是必需的,分别对应阿里云的访问密钥 ID、访问密钥 Secret、账号 ID 和服务区域。SDK 还支持以 `ALIBABA_CLOUD_` 为前缀的备用环境变量名,例如 `ALIBABA_CLOUD_ACCESS_KEY_ID` 等,这样可以与其他阿里云 SDK 共享配置。 + +在开发环境中,建议将环境变量配置写入项目根目录的 `.env` 文件,然后使用 python-dotenv 等工具加载。这样既方便管理,又避免了在代码中硬编码敏感信息。请确保将 `.env` 文件添加到 `.gitignore`,避免误提交到版本控制系统。 + +对于使用 STS 临时凭证的场景,还需要设置安全令牌: + +```bash +export AGENTRUN_SECURITY_TOKEN="your-sts-token" +``` + +如果您需要自定义服务端点或启用调试模式,可以设置以下可选环境变量: + +```bash +export AGENTRUN_CONTROL_ENDPOINT="https://custom-control-endpoint" +export AGENTRUN_DATA_ENDPOINT="https://custom-data-endpoint" +export AGENTRUN_SDK_DEBUG="true" +``` + +### 使用代码配置 + +您也可以在代码中直接创建 Config 对象来配置认证信息。这种方式适合需要动态切换配置的场景: + +```python +from agentrun.utils.config import Config + +config = Config( + access_key_id="your-access-key-id", + access_key_secret="your-access-key-secret", + account_id="your-account-id", + region_id="cn-hangzhou", + timeout=30 +) +``` + +创建的 Config 对象可以在调用 SDK API 时作为参数传入。如果同时存在环境变量配置和代码配置,SDK 会优先使用代码中显式传入的配置。 + +对于需要支持多账号或多区域的应用,可以创建多个 Config 对象并在调用时指定。例如,某些操作在国内区域执行,某些操作在海外区域执行: + +```python +cn_config = Config( + access_key_id="cn-key-id", + access_key_secret="cn-secret", + account_id="cn-account-id", + region_id="cn-hangzhou" +) + +us_config = Config( + access_key_id="us-key-id", + access_key_secret="us-secret", + account_id="us-account-id", + region_id="us-west-1" +) +``` + +### 配置参数说明 + +除了必需的认证凭证,Config 还支持以下可选参数: + +**timeout** 参数控制 HTTP 请求的超时时间,单位为秒,默认值为 600 秒。对于预期执行时间较长的操作,可以适当增大此值。 + +**headers** 参数允许您为所有请求添加自定义 HTTP 头。这在需要传递额外的元数据或跟踪信息时很有用。 + +**control_endpoint** 和 **data_endpoint** 参数用于自定义服务端点。一般情况下无需设置,SDK 会根据 region_id 自动选择合适的端点。只有在使用私有部署或特殊网络环境时才需要自定义。 + +## 验证安装 + +完成安装和配置后,建议运行一个简单的测试脚本来验证 SDK 是否正常工作。创建一个 Python 文件并输入以下代码: + +```python +from agentrun.agent_runtime import AgentRuntime + +# 列出所有 Agent Runtime +runtimes = AgentRuntime.list() +print(f"找到 {len(runtimes)} 个 Agent Runtime") +``` + +执行这个脚本,如果能成功输出而没有报错,说明 SDK 已正确安装并配置。如果遇到认证错误,请检查您的 Access Key 配置是否正确;如果遇到网络错误,请检查您的网络连接和防火墙设置。 + +对于需要使用可选依赖的功能,建议先进行单独验证。例如,验证 Playwright 安装: + +```python +from agentrun.sandbox import Sandbox, TemplateType + +# 这会检查 Playwright 是否可用 +print("Playwright 支持已启用") +``` + +如果提示缺少 Playwright,需要执行 Playwright 的安装命令: + +```bash +playwright install chromium +``` + +完成所有验证后,您就可以开始使用 AgentRun SDK 开发 Agent 应用了。 \ No newline at end of file diff --git a/docs/tutorial/02-first-agent.md b/docs/tutorial/02-first-agent.md new file mode 100644 index 0000000..cb6d0c0 --- /dev/null +++ b/docs/tutorial/02-first-agent.md @@ -0,0 +1,213 @@ +--- +sidebar_position: 3 +--- + +# 创建第一个 Agent + +本教程将引导您在 10 分钟内创建并部署一个完整的 Agent 应用。我们将使用 LangChain 框架构建一个支持代码执行的智能助手,该助手能够通过编写和运行代码来解决各种问题。 + +## 使用脚手架创建项目 + +AgentRun 提供了开箱即用的项目模板,可以快速生成包含最佳实践的项目结构。在开始之前,请确保您已按照前面的章节完成了 Serverless Devs 工具的安装。 + +在终端中执行项目初始化命令: + +```bash +s init agentrun-quick-start-langchain +``` + +这个命令会从模板仓库下载项目脚手架,并在当前目录创建一个名为 `agentrun-quick-start-langchain` 的文件夹。整个过程通常只需要几秒钟。创建完成后,进入项目目录并安装 Python 依赖: + +```bash +cd agentrun-quick-start-langchain/code +uv venv && uv pip install -r requirements.txt +``` + +这里使用 uv 工具创建虚拟环境并安装依赖。如果您的系统没有安装 uv,也可以使用传统的 pip 方式: + +```bash +python -m venv venv +source venv/bin/activate # Windows 上使用 venv\Scripts\activate +pip install -r requirements.txt +``` + +依赖安装完成后,项目就具备了运行的基础条件。 + +## 理解项目结构 + +项目模板的核心文件是 `app.py`,其中定义了 Agent 的主要逻辑。让我们先了解项目的整体结构: + +``` +agentrun-quick-start-langchain/ +├── code/ # Agent 代码目录 +│ ├── app.py # 主程序入口 +│ ├── requirements.txt # Python 依赖声明 +│ └── .env # 环境变量配置(需要创建) +└── s.yaml # Serverless Devs 部署配置 +``` + +打开 `app.py` 文件,您会看到以下关键代码结构: + +```python +from agentrun.integration.langchain import model, sandbox_toolset +from agentrun.sandbox import TemplateType +from agentrun.server import AgentRequest, AgentRunServer +from agentrun.utils.log import logger + +# 配置模型和沙箱 +MODEL_NAME = "" +SANDBOX_NAME = "" + +if MODEL_NAME.startswith("<"): + raise ValueError("请将 MODEL_NAME 替换为您已经创建的模型名称") + +code_interpreter_tools = [] +if SANDBOX_NAME and not SANDBOX_NAME.startswith("<"): + code_interpreter_tools = sandbox_toolset( + template_name=SANDBOX_NAME, + template_type=TemplateType.CODE_INTERPRETER, + sandbox_idle_timeout_seconds=300, + ) +else: + logger.warning("SANDBOX_NAME 未设置或未替换,跳过加载沙箱工具。") + +# 启动 HTTP Server +AgentRunServer(invoke_agent=invoke_agent).start() +``` + +这段代码完成了三件事:首先从 AgentRun 获取模型和沙箱工具,然后将它们转换为 LangChain 格式,最后启动一个 HTTP 服务器来接收外部请求。 + +在实际运行之前,您需要在 AgentRun 平台上创建模型和沙箱资源。模型用于提供大语言模型的推理能力,沙箱则为 Agent 提供安全的代码执行环境。 + +## 准备云端资源 + +在使用模板代码之前,您需要在 AgentRun 控制台创建必要的云端资源。登录 AgentRun 控制台,完成以下操作: + +创建一个模型服务或模型代理。模型服务用于接入您自己部署的模型,模型代理则用于接入第三方模型 API(如 OpenAI、通义千问等)。创建完成后,记录下模型的名称。 + +创建一个代码解释器类型的沙箱模板。沙箱模板定义了代码执行环境的配置,包括可用的系统资源、网络访问权限等。创建时选择 Python 3.10 或更高版本作为运行时,确保与您的本地开发环境匹配。 + +回到项目代码,打开 `app.py` 文件,将 `MODEL_NAME` 和 `SANDBOX_NAME` 的值替换为您刚才创建的资源名称: + +```python +MODEL_NAME = "my-model" # 替换为实际的模型名称 +SANDBOX_NAME = "my-sandbox" # 替换为实际的沙箱模板名称 +``` + +同时,在 `code` 目录下创建 `.env` 文件,配置认证信息: + +```bash +AGENTRUN_ACCESS_KEY_ID=your-access-key-id +AGENTRUN_ACCESS_KEY_SECRET=your-access-key-secret +AGENTRUN_ACCOUNT_ID=your-account-id +AGENTRUN_REGION=cn-hangzhou +``` + +确保这些环境变量的值与您在安装配置章节中设置的一致。 + +## 本地运行测试 + +完成配置后,就可以在本地启动 Agent 服务进行测试了。在 `code` 目录下执行: + +```bash +python app.py +``` + +程序启动后,会输出类似以下的日志信息: + +``` +INFO: Started server process +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Uvicorn running on http://0.0.0.0:9000 +``` + +这表示 HTTP 服务器已成功启动,正在监听 9000 端口。现在打开另一个终端窗口,使用 curl 命令测试 Agent 的功能: + +```bash +curl 127.0.0.1:9000/openai/v1/chat/completions \ + -XPOST \ + -H "content-type: application/json" \ + -d '{"messages": [{"role": "user", "content": "通过代码查询现在是几点?"}], "stream":true}' +``` + +这个请求模拟用户向 Agent 提问"现在是几点"。Agent 会分析这个问题,决定通过编写和执行 Python 代码来获取答案。整个过程大致如下:Agent 收到问题后,会判断需要使用代码解释器工具;然后生成一段 Python 代码(如 `import datetime; print(datetime.datetime.now())`);将代码提交到沙箱执行;获取执行结果后,将答案返回给用户。 + +由于我们在请求中设置了 `"stream":true`,响应会以流式方式返回,您可以看到 Agent 的思考和执行过程。如果一切正常,最终会返回当前的时间信息。 + +测试成功后,可以尝试更多问题来验证 Agent 的能力,例如: + +```bash +# 数学计算 +curl 127.0.0.1:9000/openai/v1/chat/completions \ + -XPOST \ + -H "content-type: application/json" \ + -d '{"messages": [{"role": "user", "content": "帮我计算斐波那契数列的第20项"}], "stream":true}' + +# 数据处理 +curl 127.0.0.1:9000/openai/v1/chat/completions \ + -XPOST \ + -H "content-type: application/json" \ + -d '{"messages": [{"role": "user", "content": "生成一个包含10个随机数的列表并计算平均值"}], "stream":true}' +``` + +本地测试通过后,就可以准备将 Agent 部署到云端了。 + +## 部署到云端 + +AgentRun 使用 Serverless Devs 作为部署工具。项目根目录的 `s.yaml` 文件定义了部署配置,包括 Agent 的名称、计算资源规格、环境变量等。在部署之前,需要先配置部署权限。 + +打开 `s.yaml` 文件,找到 `role` 字段。这个字段指定了 Agent Runtime 运行时使用的 RAM 角色,该角色需要具备访问 AgentRun 服务的权限。如果您还没有创建角色,可以点击[快速授权链接](https://ram.console.aliyun.com/authorize?request=%7B%22template%22%3A%22OldRoleCommonAuthorize%22%2C%22referrer%22%3A%22https%3A%2F%2Ffunctionai.console.aliyun.com%2Fcn-hangzhou%2Fexplore%22%2C%22payloads%22%3A%5B%7B%22missionId%22%3A%22OldRoleCommonAuthorize.FC%22%2C%22roleName%22%3A%22agentRunRole%22%2C%22roleDescription%22%3A%22AgentRun%20auto%20created%20role.%22%2C%22rolePolicies%22%3A%5B%7B%22policyName%22%3A%22AliyunAgentRunFullAccess%22%7D%2C%7B%22policyName%22%3A%22AliyunDevsFullAccess%22%7D%5D%7D%5D%2C%22callback%22%3A%22https%3A%2F%2Ffunctionai.console.aliyun.com%22%7D)自动创建一个名为 `agentRunRole` 的角色。 + +角色创建完成后,将其 ARN 填入 `s.yaml` 的 `role` 字段: + +```yaml +role: acs:ram::{您的阿里云主账号 ID}:role/agentRunRole +``` + +接下来配置 Serverless Devs 的访问密钥。在项目根目录执行: + +```bash +s config add +``` + +按照提示输入您的阿里云 Access Key ID 和 Access Key Secret,并为这组密钥设置一个名称,例如 `agentrun-deploy`。这组密钥用于 Serverless Devs 调用阿里云 API 进行部署操作,与 Agent Runtime 使用的密钥是独立的。 + +配置完成后,执行构建和部署命令。构建过程会在 Docker 容器中安装项目依赖,确保部署包与云端运行环境兼容: + +```bash +s build +s deploy -a agentrun-deploy +``` + +其中 `-a agentrun-deploy` 指定使用刚才配置的密钥。如果您将密钥名称写入了 `s.yaml` 的 `access` 字段,则可以省略这个参数。 + +部署过程可能需要几分钟时间,具体取决于项目的大小和网络状况。部署成功后,终端会输出 Agent 的访问信息。 + +## 调用您的 Agent + +部署完成后,控制台会显示类似以下的输出: + +``` +endpoints: + - + id: abc123 + arn: arn:aliyun:agentrun:cn-hangzhou:123456789:agent-runtimes/myagent/endpoints/prod + name: prod + url: https://12345.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/myagent/endpoints/prod/invocations +``` + +其中的 `url` 字段是您的 Agent 基础访问地址。将 API 路径拼接到这个 URL 后面,即可调用云端 Agent。例如,调用 OpenAI 兼容的 Chat Completions API: + +```bash +curl https://12345.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/myagent/endpoints/prod/invocations/openai/v1/chat/completions \ + -XPOST \ + -H "content-type: application/json" \ + -d '{"messages": [{"role": "user", "content": "通过代码查询现在是几点?"}], "stream":true}' +``` + +这个请求与本地测试时使用的请求完全相同,只是 URL 地址指向了云端服务。Agent 会在 AgentRun 平台上执行,享受平台提供的弹性伸缩、负载均衡、监控告警等企业级能力。 + +至此,您已经完成了第一个 Agent 的创建、测试和部署。这个 Agent 虽然简单,但已经具备了完整的功能:它可以理解自然语言问题,决策使用合适的工具,在安全的沙箱环境中执行代码,并将结果返回给用户。 + +在此基础上,您可以进一步扩展 Agent 的能力。例如,添加更多类型的工具(如网络搜索、数据库查询),集成文件存储实现上下文记忆,或者使用更复杂的推理链来处理多步骤任务。后续章节将详细介绍这些高级功能的实现方法。 \ No newline at end of file diff --git a/docs/tutorial/03-model-basics.md b/docs/tutorial/03-model-basics.md new file mode 100644 index 0000000..d02cdc4 --- /dev/null +++ b/docs/tutorial/03-model-basics.md @@ -0,0 +1,268 @@ +--- +sidebar_position: 4 +--- + +# 模型使用基础 + +模型是 Agent 的核心能力来源。AgentRun 提供了统一的模型管理接口,屏蔽不同模型供应商的 API 差异,让您可以用一致的方式调用各种大语言模型。本章将介绍如何创建和使用模型,以及如何在实际应用中发挥模型的最大价值。 + +## 理解模型服务与模型代理 + +AgentRun 支持两种模型接入方式:ModelService(模型服务)和 ModelProxy(模型代理)。理解它们的区别有助于您根据实际需求选择合适的方案。 + +ModelService 用于接入您自己部署的模型服务。如果您在自己的服务器或容器中运行了开源模型(如 Llama、ChatGLM 等),或者搭建了私有化的模型推理服务,可以通过 ModelService 将这些服务注册到 AgentRun 平台。ModelService 会记录您的服务端点信息和认证配置,当 Agent 调用模型时,请求会被转发到您的服务地址。 + +ModelProxy 则是一个更高级的抽象,提供了模型治理能力。它可以代理一个或多个模型服务,并在其上实现负载均衡、故障转移、请求限流等企业级特性。例如,您可以创建一个 ModelProxy,配置多个后端模型(可能来自不同供应商),当某个模型响应缓慢或出现故障时,请求会自动路由到其他可用的模型。这种机制大大提升了 Agent 应用的可靠性和性能。 + +对于大多数场景,建议使用 ModelProxy。即使您只有一个模型,通过 ModelProxy 也能获得更好的监控、限流、错误处理等能力。 + +## 创建模型服务 + +创建模型服务需要提供服务的基本信息和访问配置。以下示例展示了如何通过 SDK 创建一个 ModelService: + +```python +from agentrun.model import ModelService, ModelServiceCreateInput, Provider + +# 创建模型服务 +input_data = ModelServiceCreateInput( + name="my-model-service", + description="我的自建模型服务", + provider=Provider.OPENAI, + model_name="gpt-3.5-turbo", + api_endpoint="https://api.openai.com/v1", + credential_name="my-openai-credential" +) + +service = ModelService.create(input_data) +print(f"模型服务创建成功: {service.name}") +``` + +在这个例子中,我们创建了一个指向 OpenAI API 的模型服务。`provider` 参数指定模型供应商,SDK 会根据供应商类型处理请求格式转换。`credential_name` 引用了一个预先创建的凭证对象,该凭证中存储了 API Key 等敏感信息。 + +创建完成后,您可以通过服务名称获取模型对象并进行调用: + +```python +# 获取已创建的模型服务 +service = ModelService.get_by_name("my-model-service") + +# 查看模型信息 +info = service.model_info() +print(f"模型类型: {info.model_type}") +print(f"支持的特性: {info.features}") +``` + +## 创建模型代理 + +ModelProxy 的创建过程与 ModelService 类似,但需要配置后端模型列表和路由策略: + +```python +from agentrun.model import ModelProxy, ModelProxyCreateInput, ProxyConfig, ProxyMode + +# 创建模型代理 +input_data = ModelProxyCreateInput( + name="my-model-proxy", + description="智能路由的模型代理", + proxy_config=ProxyConfig( + mode=ProxyMode.LOAD_BALANCE, + endpoints=[ + ProxyConfigEndpoint( + model_name="gpt-3.5-turbo", + weight=70 + ), + ProxyConfigEndpoint( + model_name="gpt-4", + weight=30 + ) + ] + ) +) + +proxy = ModelProxy.create(input_data) +print(f"模型代理创建成功: {proxy.name}") +``` + +这个配置创建了一个支持负载均衡的模型代理,70% 的请求会路由到 gpt-3.5-turbo,30% 路由到 gpt-4。这种配置既保证了成本效益,又能让部分请求享受到更强大模型的能力。 + +## 基础对话 + +模型创建完成后,就可以进行对话调用了。AgentRun SDK 提供了 `completions` 方法来发起对话请求: + +```python +# 获取模型 +service = ModelService.get_by_name("my-model-service") + +# 构造对话消息 +messages = [ + {"role": "system", "content": "你是一个有帮助的助手。"}, + {"role": "user", "content": "请介绍一下 Python 的特点。"} +] + +# 调用模型 +response = service.completions(messages=messages) + +# 获取回复内容 +for choice in response.choices: + print(choice.message.content) +``` + +`completions` 方法接受标准的 OpenAI 格式消息列表,包含角色(role)和内容(content)两个字段。角色可以是 `system`、`user` 或 `assistant`,分别表示系统提示、用户输入和助手回复。 + +对于需要更多控制的场景,可以使用 `responses` 方法并传入额外参数: + +```python +# 使用更多参数控制生成 +response = service.completions( + messages=messages, + temperature=0.7, + max_tokens=500, + top_p=0.9 +) +``` + +这些参数控制模型的生成行为。`temperature` 影响输出的随机性,值越高输出越多样化;`max_tokens` 限制生成的最大长度;`top_p` 控制核采样的概率阈值。 + +## 流式输出 + +在实际应用中,用户往往希望尽快看到模型的响应,而不是等待整个回答生成完毕。流式输出通过逐步返回生成的内容来改善用户体验: + +```python +# 启用流式输出 +response = service.completions( + messages=messages, + stream=True +) + +# 逐块处理返回的内容 +for chunk in response: + if hasattr(chunk.choices[0].delta, 'content'): + content = chunk.choices[0].delta.content + if content: + print(content, end='', flush=True) +``` + +流式模式下,`completions` 方法返回一个迭代器,每次迭代产生一个响应块。响应块的 `delta` 字段包含增量内容,您需要将这些增量拼接起来得到完整的回答。 + +流式输出特别适合构建交互式应用。例如在聊天界面中,用户可以看到回答逐字显示,就像真人在打字一样,这大大提升了体验的流畅感。 + +## 模型切换与负载均衡 + +使用 ModelProxy 可以实现灵活的模型切换和负载均衡。前面的例子展示了基于权重的负载均衡,此外还支持故障转移和优先级路由等策略。 + +故障转移模式下,请求会优先发送到主模型,当主模型出现错误或超时时,自动切换到备用模型: + +```python +proxy_config = ProxyConfig( + mode=ProxyMode.FALLBACK, + endpoints=[ + ProxyConfigEndpoint( + model_name="primary-model", + priority=1 + ), + ProxyConfigEndpoint( + model_name="backup-model", + priority=2 + ) + ] +) +``` + +这种配置确保了服务的高可用性。即使主模型暂时不可用,Agent 仍然能够正常工作,只是会使用备用模型来处理请求。 + +您也可以在运行时动态选择使用哪个模型。ModelProxy 的 `completions` 方法支持 `model` 参数来指定具体的后端模型: + +```python +# 使用代理中的特定模型 +response = proxy.completions( + messages=messages, + model="gpt-4" # 明确指定使用 gpt-4 +) +``` + +这种灵活性使得您可以根据任务的重要性或复杂度来选择合适的模型。例如,简单的问答使用较小的模型,复杂的推理任务则使用更强大的模型。 + +## 实战案例:构建智能客服 + +让我们通过一个完整的例子来综合运用前面学到的知识。这个智能客服 Agent 能够回答常见问题,并在必要时升级到更强大的模型来处理复杂问题。 + +首先创建模型配置: + +```python +from agentrun.model import ModelProxy, ModelProxyCreateInput, ProxyConfig, ProxyMode + +# 创建支持多模型的代理 +proxy_input = ModelProxyCreateInput( + name="customer-service-proxy", + description="客服专用模型代理", + proxy_config=ProxyConfig( + mode=ProxyMode.LOAD_BALANCE, + endpoints=[ + ProxyConfigEndpoint( + model_name="gpt-3.5-turbo", + weight=80 + ), + ProxyConfigEndpoint( + model_name="gpt-4", + weight=20 + ) + ] + ) +) + +proxy = ModelProxy.create(proxy_input) +``` + +然后实现客服逻辑: + +```python +def handle_customer_query(query: str, use_advanced_model: bool = False): + """处理客户查询""" + proxy = ModelProxy.get_by_name("customer-service-proxy") + + # 构造系统提示 + system_prompt = """你是一个专业的客服助手。请遵循以下原则: + 1. 友好、耐心地回答客户问题 + 2. 如果问题涉及技术细节,提供准确的信息 + 3. 如果不确定答案,诚实地告知并建议联系人工客服 + """ + + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": query} + ] + + # 根据问题复杂度选择模型 + model_name = "gpt-4" if use_advanced_model else None + + # 调用模型 + response = proxy.completions( + messages=messages, + model=model_name, + temperature=0.7, + stream=True + ) + + # 流式返回答案 + answer = "" + for chunk in response: + if hasattr(chunk.choices[0].delta, 'content'): + content = chunk.choices[0].delta.content + if content: + answer += content + print(content, end='', flush=True) + + return answer + +# 使用示例 +print("客户问题:如何重置密码?") +handle_customer_query("如何重置密码?") + +print("\n\n客户问题:你们的API限流策略是什么?") +handle_customer_query( + "你们的API限流策略和计费规则具体是怎样的?", + use_advanced_model=True # 复杂问题使用高级模型 +) +``` + +这个客服系统通过模型代理实现了成本和性能的平衡。大部分简单问题由 gpt-3.5-turbo 处理,成本较低;遇到复杂问题时,可以主动切换到 gpt-4 获得更准确的答案。系统提示确保了回答的专业性和一致性,流式输出则提供了良好的交互体验。 + +在实际部署时,您可以进一步优化这个系统。例如,添加问题分类逻辑,自动判断何时需要使用高级模型;集成历史对话记录,提供上下文感知的回答;连接知识库,让 Agent 能够引用企业内部的文档和政策。这些高级功能将在后续章节中详细介绍。 \ No newline at end of file diff --git a/docs/tutorial/04-tool-basics.md b/docs/tutorial/04-tool-basics.md new file mode 100644 index 0000000..1e9390c --- /dev/null +++ b/docs/tutorial/04-tool-basics.md @@ -0,0 +1,277 @@ +--- +sidebar_position: 5 +--- + +# 工具使用基础 + +工具是扩展 Agent 能力的关键机制。通过工具,Agent 可以突破纯文本对话的限制,访问外部 API、查询数据库、操作文件系统等。AgentRun 提供了完整的工具管理系统,支持 OpenAPI 规范和 MCP 协议两种标准,让您能够快速集成各类工具服务。 + +## 什么是 ToolSet + +ToolSet 是 AgentRun 中工具的组织单元。一个 ToolSet 包含一组相关的工具,这些工具通常来自同一个服务或针对同一个领域。例如,天气服务的 ToolSet 可能包含查询当前天气、预报未来天气、查询历史天气等多个工具。 + +AgentRun 支持两种类型的工具定义方式。基于 OpenAPI 规范的工具通过标准的 REST API 来提供服务,SDK 会自动解析 OpenAPI 文档,提取工具的描述、参数定义和调用方式。基于 MCP(Model Context Protocol)的工具则遵循统一的工具协议,提供了更标准化的工具发现和调用机制。 + +在 AgentRun 控制台创建 ToolSet 时,您需要提供工具服务的端点信息、认证配置和 OpenAPI 规范文档或 MCP 服务地址。创建完成后,ToolSet 会自动注册到平台,Agent 就可以通过 SDK 获取和使用这些工具。 + +## 获取内置工具集 + +使用 ToolSet 的第一步是从平台获取已创建的工具集。SDK 提供了 ToolSetClient 来管理工具集: + +```python +from agentrun.toolset import ToolSetClient + +# 创建客户端 +client = ToolSetClient() + +# 获取指定名称的工具集 +toolset = client.get("weather-api") +print(f"工具集: {toolset.name}") +print(f"描述: {toolset.description}") +``` + +获取到的 ToolSet 对象包含了工具集的完整元数据,包括名称、描述、工具列表等信息。您可以查看工具集包含哪些工具: + +```python +# 列出工具集中的所有工具 +tools = toolset.list_tools() +for tool in tools: + print(f"工具名称: {tool.name}") + print(f"工具描述: {tool.description}") + print(f"参数: {tool.parameters}") + print("---") +``` + +这个方法返回的是标准化的 ToolInfo 对象列表,每个对象描述一个工具的完整信息。工具信息采用 JSON Schema 格式定义参数,与大多数 AI 框架兼容。 + +如果您需要浏览平台上所有可用的工具集,可以使用列表方法: + +```python +# 列出所有工具集 +all_toolsets = client.list() +for ts in all_toolsets: + print(f"{ts.name}: {ts.description}") +``` + +## 工具调用示例 + +获取工具集后,就可以调用其中的工具了。工具调用需要提供工具名称和参数,SDK 会自动处理 HTTP 请求的构建和发送: + +```python +# 获取工具集 +toolset = ToolSetClient().get("weather-api") + +# 调用工具查询天气 +result = toolset.call_tool( + name="get_current_weather", + arguments={ + "location": "杭州", + "unit": "celsius" + } +) + +print(f"查询结果: {result}") +``` + +工具调用是同步的,会等待远程服务返回结果。对于可能耗时较长的工具,建议使用异步版本: + +```python +import asyncio + +async def query_weather(): + toolset = ToolSetClient().get("weather-api") + + # 异步调用工具 + result = await toolset.call_tool_async( + name="get_current_weather", + arguments={"location": "杭州"} + ) + + return result + +# 运行异步任务 +result = asyncio.run(query_weather()) +``` + +异步调用在处理多个工具请求时特别有用,可以并发执行多个查询来提升性能。 + +工具调用的结果是一个字典,包含工具返回的所有数据。结果的具体格式取决于工具的实现,但通常会包含状态码、数据内容和可能的错误信息。您需要根据工具的文档来解析结果: + +```python +result = toolset.call_tool( + name="get_current_weather", + arguments={"location": "杭州"} +) + +# 解析结果 +if result.get("success"): + weather_data = result.get("data", {}) + temperature = weather_data.get("temperature") + description = weather_data.get("description") + print(f"当前温度: {temperature}°C") + print(f"天气状况: {description}") +else: + error_msg = result.get("error", "未知错误") + print(f"查询失败: {error_msg}") +``` + +## 工具与 Agent 集成 + +工具的真正价值在于与 Agent 的集成。Agent 可以根据用户的问题自主决策是否需要使用工具,选择合适的工具,构造参数并调用,然后将结果整合到回答中。 + +AgentRun SDK 的集成模块提供了便捷的工具转换功能。以 LangChain 为例,可以这样集成工具: + +```python +from agentrun.integration.langchain import toolset +from langchain.agents import create_openai_functions_agent +from langchain.prompts import ChatPromptTemplate + +# 获取 AgentRun 工具集并转换为 LangChain 格式 +tools = toolset("weather-api") + +# 创建 Agent +prompt = ChatPromptTemplate.from_messages([ + ("system", "你是一个有帮助的助手,可以查询天气信息。"), + ("user", "{input}"), + ("assistant", "{agent_scratchpad}") +]) + +# 这里的 model 是之前章节创建的 LangChain 模型 +agent = create_openai_functions_agent(model, tools, prompt) +``` + +转换后的工具对象可以直接传递给 LangChain 的 Agent 创建函数。LangChain 会自动处理工具调用的逻辑,包括决策何时使用工具、解析工具结果等。 + +对于其他框架,SDK 也提供了相应的转换方法。例如 AgentScope: + +```python +from agentrun.integration.agentscope import toolset + +# 转换为 AgentScope 工具格式 +tools = toolset("weather-api") + +# tools 现在可以用于 AgentScope Agent +``` + +这种设计使得您可以无缝地在不同框架之间切换,而不需要重写工具集成代码。 + +## 使用 ApiSet 进行高级操作 + +对于需要更精细控制的场景,可以使用 ApiSet 类。ApiSet 提供了更底层的工具操作接口,支持从 OpenAPI 规范动态创建工具集: + +```python +from agentrun.toolset.api.openapi import ApiSet + +# 从 OpenAPI 规范创建工具集 +apiset = ApiSet.from_openapi_schema( + schema="https://api.example.com/openapi.json", + base_url="https://api.example.com", + headers={"Authorization": "Bearer your-token"} +) + +# 列出所有工具 +tools = apiset.tools() +for tool in tools: + print(f"{tool.name}: {tool.description}") + +# 调用工具 +result = apiset.invoke( + name="get_weather", + arguments={"location": "北京"} +) +``` + +ApiSet 还支持将工具转换为 Python 函数,这在某些框架中很有用: + +```python +# 获取单个工具并转换为函数 +get_weather = apiset.to_function_tool("get_weather") + +# 现在可以像调用普通函数一样使用 +result = get_weather(location="上海", unit="celsius") +``` + +这个功能对于 Google ADK 等需要原生 Python 函数的框架特别有用。 + +## 实战案例:构建天气查询 Agent + +让我们通过一个完整的例子来展示如何构建一个能够查询天气的 Agent。这个 Agent 会理解用户的自然语言问题,提取地点信息,调用天气 API,并用友好的方式回答。 + +首先在 AgentRun 控制台创建一个天气 API 的 ToolSet。假设您已经有一个提供天气查询的 REST API,可以将其 OpenAPI 规范上传到平台。规范可能类似这样: + +```yaml +openapi: 3.0.0 +info: + title: Weather API + version: 1.0.0 +paths: + /current: + get: + operationId: getCurrentWeather + summary: 查询当前天气 + parameters: + - name: location + in: query + required: true + schema: + type: string + - name: unit + in: query + schema: + type: string + enum: [celsius, fahrenheit] +``` + +创建完 ToolSet 后,编写 Agent 代码: + +```python +from agentrun.integration.langchain import model, toolset +from langchain.agents import create_openai_functions_agent, AgentExecutor +from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder + +# 获取模型和工具 +llm = model("my-model") +tools = toolset("weather-api") + +# 定义 Agent 提示词 +prompt = ChatPromptTemplate.from_messages([ + ("system", """你是一个天气查询助手。你可以帮用户查询世界各地的天气信息。 + +使用工具时请注意: +1. 从用户问题中提取准确的地点名称 +2. 默认使用摄氏度作为温度单位 +3. 将查询结果用通俗易懂的语言告诉用户 + +如果用户没有提供地点,请友好地询问。"""), + ("user", "{input}"), + MessagesPlaceholder(variable_name="agent_scratchpad") +]) + +# 创建 Agent +agent = create_openai_functions_agent(llm, tools, prompt) +agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) + +# 处理用户查询 +def query_weather(user_input: str): + result = agent_executor.invoke({"input": user_input}) + return result["output"] + +# 测试 +print(query_weather("杭州今天天气怎么样?")) +print(query_weather("北京和上海哪里更热?")) +``` + +这个 Agent 展示了工具集成的完整流程。当用户问"杭州今天天气怎么样"时,Agent 会: + +1. 理解用户意图是查询天气 +2. 识别地点是"杭州" +3. 决定使用 getCurrentWeather 工具 +4. 构造参数 `{"location": "杭州", "unit": "celsius"}` +5. 调用工具获取天气数据 +6. 将结果整合成自然语言回答 + +对于"北京和上海哪里更热"这样的复杂问题,Agent 会自动进行两次工具调用,分别查询两个城市的天气,然后对比温度给出答案。 + +您可以进一步增强这个 Agent。例如,添加历史天气查询工具,让 Agent 能够回答"杭州过去一周的天气";添加天气预报工具,支持"明天会下雨吗"这类问题;或者集成地图工具,让 Agent 能够根据模糊的地点描述找到准确的位置。 + +工具的组合使用为 Agent 提供了强大而灵活的能力。通过合理设计工具集和 Agent 逻辑,您可以构建出能够处理复杂任务的智能应用。 \ No newline at end of file diff --git a/docs/tutorial/05-sandbox-basics.md b/docs/tutorial/05-sandbox-basics.md new file mode 100644 index 0000000..870286c --- /dev/null +++ b/docs/tutorial/05-sandbox-basics.md @@ -0,0 +1,505 @@ +--- +sidebar_position: 6 +--- + +# 沙箱使用基础 + +沙箱是 AgentRun 提供的隔离执行环境,让 Agent 能够安全地执行代码、操作文件和控制浏览器。每个沙箱运行在独立的容器中,与其他环境完全隔离,确保了安全性和资源隔离。AgentRun 目前提供两类沙箱:代码解释器沙箱用于执行动态生成的代码,浏览器沙箱则提供完整的浏览器自动化能力。 + +## 代码解释器沙箱 + +代码解释器沙箱为 Agent 提供了执行 Python 和 Shell 代码的能力。这是构建数据分析、科学计算、文件处理等类型 Agent 的基础设施。沙箱提供了完整的 Linux 环境,预装了常用的科学计算库和工具。 + +### 创建和连接沙箱 + +使用代码解释器沙箱的第一步是创建沙箱模板。模板定义了沙箱的运行环境配置,包括 Python 版本、预装的依赖包、资源限制等。您可以在 AgentRun 控制台创建模板,或者通过 SDK 创建: + +```python +from agentrun.sandbox import Sandbox, TemplateType + +# 创建代码解释器沙箱 +sandbox = Sandbox.create( + template_type=TemplateType.CODE_INTERPRETER, + template_name="my-code-sandbox", + sandbox_idle_timeout_seconds=600 +) + +print(f"沙箱 ID: {sandbox.sandbox_id}") +print(f"状态: {sandbox.status}") +``` + +`sandbox_idle_timeout_seconds` 参数指定沙箱的空闲超时时间。超过这个时间没有操作,沙箱会自动停止以节省资源。对于需要长时间保持状态的场景,可以适当增大这个值。 + +沙箱创建后会自动启动,状态会从 Creating 变为 Running。如果您已经有一个正在运行的沙箱,可以通过 ID 连接到它: + +```python +# 连接到已存在的沙箱 +sandbox = Sandbox.connect( + sandbox_id="existing-sandbox-id", + template_type=TemplateType.CODE_INTERPRETER +) +``` + +连接沙箱不会创建新的实例,而是获取已存在沙箱的控制权。这在需要恢复之前的执行上下文时特别有用。 + +### 执行代码 + +代码执行是代码解释器沙箱的核心功能。SDK 提供了基于上下文的代码执行机制,每个上下文维护独立的变量空间和执行状态: + +```python +# 创建执行上下文 +context = sandbox.context.create( + language="python", + cwd="/home/user" +) + +# 在上下文中执行代码 +result = context.execute(""" +import math +import datetime + +# 计算圆的面积 +radius = 5 +area = math.pi * radius ** 2 + +# 获取当前时间 +current_time = datetime.datetime.now() + +print(f"半径为 {radius} 的圆面积: {area:.2f}") +print(f"当前时间: {current_time}") +""") + +print("执行结果:") +print(result.get("stdout")) +``` + +执行结果包含标准输出、标准错误和退出码。如果代码抛出异常,异常信息会出现在标准错误中。上下文会保持执行状态,后续的代码可以访问之前定义的变量: + +```python +# 继续在同一上下文中执行 +result = context.execute(""" +# 使用之前定义的 radius 变量 +circumference = 2 * math.pi * radius +print(f"周长: {circumference:.2f}") +""") +``` + +这种机制使得 Agent 可以进行多步骤的代码执行,每一步都基于前一步的结果。例如,先加载数据,然后清洗数据,最后进行分析,每个步骤都在同一个上下文中进行。 + +对于需要异步执行的场景,可以使用异步版本的方法: + +```python +import asyncio + +async def run_analysis(): + sandbox = await Sandbox.create_async( + template_type=TemplateType.CODE_INTERPRETER, + template_name="my-code-sandbox" + ) + + context = await sandbox.context.create_async() + + # 并发执行多个分析任务 + tasks = [ + context.execute_async("result1 = 1 + 1"), + context.execute_async("result2 = 2 + 2"), + context.execute_async("result3 = 3 + 3") + ] + + results = await asyncio.gather(*tasks) + return results + +results = asyncio.run(run_analysis()) +``` + +### 文件操作 + +代码解释器沙箱提供了完整的文件系统操作能力。您可以在沙箱中读写文件、管理目录结构,以及在本地和沙箱之间传输文件。 + +读写文件是最基础的操作: + +```python +# 写入文件 +sandbox.file.write( + path="/home/user/data.txt", + content="这是测试数据\n第二行数据", + encoding="utf-8" +) + +# 读取文件 +content = sandbox.file.read("/home/user/data.txt") +print(content) +``` + +对于需要从本地上传文件的场景,可以使用上传方法: + +```python +# 上传本地文件到沙箱 +sandbox.file_system.upload( + local_file_path="/local/path/dataset.csv", + target_file_path="/home/user/dataset.csv" +) + +# 在沙箱中处理文件 +context.execute(""" +import pandas as pd + +df = pd.read_csv('/home/user/dataset.csv') +print(f"数据集包含 {len(df)} 行") +print(df.head()) +""") +``` + +处理完成后,可以将结果文件下载到本地: + +```python +# 下载沙箱中的文件 +sandbox.file_system.download( + path="/home/user/result.csv", + save_path="/local/path/result.csv" +) +``` + +文件系统操作还包括目录管理、文件移动、权限设置等功能: + +```python +# 创建目录 +sandbox.file_system.mkdir("/home/user/analysis") + +# 列出目录内容 +files = sandbox.file_system.list("/home/user") +for file in files: + print(f"{file['name']}: {file['type']}") + +# 移动文件 +sandbox.file_system.move( + source="/home/user/data.txt", + destination="/home/user/analysis/data.txt" +) + +# 获取文件信息 +stat = sandbox.file_system.stat("/home/user/analysis/data.txt") +print(f"文件大小: {stat['size']} 字节") +print(f"修改时间: {stat['mtime']}") +``` + +### 案例:数据分析 Agent + +让我们通过一个完整的例子来展示代码解释器沙箱的实际应用。这个数据分析 Agent 可以接收 CSV 数据文件,进行统计分析,并生成可视化报告: + +```python +from agentrun.sandbox import Sandbox, TemplateType +from agentrun.integration.langchain import model, sandbox_toolset + +# 创建沙箱 +sandbox = Sandbox.create( + template_type=TemplateType.CODE_INTERPRETER, + template_name="data-analysis-sandbox", + sandbox_idle_timeout_seconds=1800 +) + +# 上传数据文件 +sandbox.file_system.upload( + local_file_path="./sales_data.csv", + target_file_path="/home/user/sales_data.csv" +) + +# 创建执行上下文 +context = sandbox.context.create() + +# 安装分析所需的库(如果模板中未预装) +context.execute(""" +import subprocess +subprocess.run(['pip', 'install', 'pandas', 'matplotlib', 'seaborn']) +""") + +# 执行数据分析 +analysis_code = """ +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns + +# 加载数据 +df = pd.read_csv('/home/user/sales_data.csv') + +# 基础统计 +stats = { + 'total_rows': len(df), + 'columns': list(df.columns), + 'summary': df.describe().to_dict() +} + +# 生成可视化 +plt.figure(figsize=(10, 6)) +sns.barplot(data=df, x='category', y='sales') +plt.title('Sales by Category') +plt.xticks(rotation=45) +plt.tight_layout() +plt.savefig('/home/user/sales_chart.png') + +print("分析完成") +print(f"数据集包含 {len(df)} 行,{len(df.columns)} 列") +""" + +result = context.execute(analysis_code) +print(result.get("stdout")) + +# 下载生成的图表 +sandbox.file_system.download( + path="/home/user/sales_chart.png", + save_path="./sales_chart.png" +) + +print("分析报告已生成: sales_chart.png") +``` + +这个 Agent 展示了沙箱的完整工作流:上传数据、执行分析代码、生成可视化、下载结果。整个过程在隔离的环境中进行,不会影响宿主系统,也不会被其他任务干扰。 + +在实际应用中,您可以将这个流程与 LLM 结合,让 Agent 根据用户的自然语言问题自动生成分析代码。例如,用户问"显示各个类别的销售额对比",Agent 会理解需求,生成相应的 pandas 和 matplotlib 代码,在沙箱中执行,然后返回可视化结果。 + +## 浏览器沙箱 + +浏览器沙箱为 Agent 提供了完整的网页自动化能力。它基于 Chromium 浏览器和 Playwright 自动化框架,支持页面导航、元素操作、JavaScript 执行、截图录制等丰富功能。 + +### 创建和连接沙箱 + +浏览器沙箱的创建方式与代码解释器类似: + +```python +from agentrun.sandbox import Sandbox, TemplateType + +# 创建浏览器沙箱 +sandbox = Sandbox.create( + template_type=TemplateType.BROWSER, + template_name="my-browser-sandbox", + sandbox_idle_timeout_seconds=600 +) + +print(f"沙箱 ID: {sandbox.sandbox_id}") +``` + +浏览器沙箱启动后,会创建一个 Chromium 实例并等待连接。SDK 提供了两种使用浏览器的方式:通过 Playwright API 进行编程控制,或通过 CDP(Chrome DevTools Protocol)和 VNC 进行远程调试。 + +### 使用 Playwright 操作浏览器 + +Playwright 是 AgentRun 推荐的浏览器控制方式。SDK 提供了同步和异步两套 Playwright API 封装: + +```python +# 获取 Playwright 同步客户端 +browser = sandbox.sync_playwright() + +# 打开浏览器并导航到网页 +browser.open() +browser.goto("https://www.example.com") + +# 等待页面加载 +browser.wait(2000) # 等待 2 秒 + +# 获取页面内容 +html = browser.html_content() +print(html) +``` + +Playwright 提供了丰富的页面操作方法。您可以点击元素、填写表单、提取内容等: + +```python +# 填写搜索表单 +browser.fill("input[name='search']", "AgentRun") +browser.click("button[type='submit']") + +# 等待结果加载 +browser.wait(3000) + +# 提取搜索结果 +results = browser.evaluate(""" +() => { + const items = document.querySelectorAll('.result-item'); + return Array.from(items).map(item => ({ + title: item.querySelector('h3').textContent, + link: item.querySelector('a').href + })); +} +""") + +for result in results: + print(f"{result['title']}: {result['link']}") +``` + +`evaluate` 方法允许在页面上下文中执行 JavaScript 代码,这是提取动态内容和与网页交互的强大工具。 + +对于需要异步执行的场景,可以使用异步版本: + +```python +import asyncio + +async def scrape_website(): + sandbox = await Sandbox.create_async( + template_type=TemplateType.BROWSER, + template_name="my-browser-sandbox" + ) + + browser = sandbox.async_playwright() + await browser.open() + + # 访问多个页面 + urls = [ + "https://example1.com", + "https://example2.com", + "https://example3.com" + ] + + for url in urls: + await browser.goto(url) + await browser.wait(2000) + html = await browser.html_content() + print(f"抓取 {url}: {len(html)} 字符") + + await browser.close() + +asyncio.run(scrape_website()) +``` + +### 截图和录制 + +浏览器沙箱支持截图和视频录制功能,这在调试和监控自动化任务时非常有用: + +```python +# 获取启用录制的浏览器客户端 +browser = sandbox.sync_playwright(record=True) +browser.open() + +# 执行一系列操作 +browser.goto("https://www.example.com") +browser.click("a.more-info") +browser.wait(2000) + +# 截图 +screenshot_data = browser.screenshot(full_page=True) +with open("screenshot.png", "wb") as f: + f.write(screenshot_data) + +# 关闭浏览器后,录制会自动保存 +browser.close() + +# 列出所有录制文件 +recordings = sandbox.list_recordings() +for rec in recordings: + print(f"录制文件: {rec['filename']}, 大小: {rec['size']}") + +# 下载录制 +if recordings: + sandbox.download_recording( + filename=recordings[0]['filename'], + save_path="./browser_session.mkv" + ) +``` + +录制功能会记录整个浏览器操作过程,生成视频文件。这对于审计自动化任务、调试问题或展示 Agent 的工作过程都很有价值。 + +### 案例:网页爬虫 Agent + +让我们构建一个能够自动浏览网页并提取信息的爬虫 Agent。这个 Agent 可以处理动态加载的内容,填写表单,并智能地提取所需数据: + +```python +from agentrun.sandbox import Sandbox, TemplateType + +class WebScraperAgent: + def __init__(self): + self.sandbox = Sandbox.create( + template_type=TemplateType.BROWSER, + template_name="scraper-sandbox" + ) + self.browser = self.sandbox.sync_playwright() + self.browser.open() + + def search_and_extract(self, keyword: str): + """在搜索引擎中搜索关键词并提取结果""" + # 访问搜索引擎 + self.browser.goto("https://www.google.com") + + # 填写搜索框 + self.browser.fill("input[name='q']", keyword) + self.browser.click("input[type='submit']") + + # 等待结果加载 + self.browser.wait(3000) + + # 提取搜索结果 + results = self.browser.evaluate(""" + () => { + const items = document.querySelectorAll('div.g'); + return Array.from(items).slice(0, 10).map(item => { + const title = item.querySelector('h3'); + const link = item.querySelector('a'); + const snippet = item.querySelector('.VwiC3b'); + + return { + title: title ? title.textContent : '', + url: link ? link.href : '', + snippet: snippet ? snippet.textContent : '' + }; + }); + } + """) + + return results + + def visit_and_analyze(self, url: str): + """访问页面并分析内容""" + self.browser.goto(url) + self.browser.wait(2000) + + # 获取页面元数据 + metadata = self.browser.evaluate(""" + () => { + return { + title: document.title, + description: document.querySelector('meta[name="description"]')?.content, + keywords: document.querySelector('meta[name="keywords"]')?.content, + headings: Array.from(document.querySelectorAll('h1, h2, h3')).map(h => h.textContent), + links: Array.from(document.querySelectorAll('a')).length, + images: Array.from(document.querySelectorAll('img')).length + }; + } + """) + + # 截图保存 + screenshot = self.browser.screenshot() + + return { + 'metadata': metadata, + 'screenshot': screenshot + } + + def close(self): + self.browser.close() + +# 使用示例 +agent = WebScraperAgent() + +# 搜索关键词 +results = agent.search_and_extract("AgentRun platform") +print(f"找到 {len(results)} 条结果") + +for i, result in enumerate(results, 1): + print(f"\n{i}. {result['title']}") + print(f" URL: {result['url']}") + print(f" 摘要: {result['snippet'][:100]}...") + +# 分析第一个结果 +if results: + analysis = agent.visit_and_analyze(results[0]['url']) + print(f"\n页面分析:") + print(f"标题: {analysis['metadata']['title']}") + print(f"描述: {analysis['metadata']['description']}") + print(f"包含 {analysis['metadata']['links']} 个链接") + print(f"包含 {analysis['metadata']['images']} 张图片") + +agent.close() +``` + +这个爬虫 Agent 展示了浏览器沙箱的强大能力。它可以处理 JavaScript 渲染的动态内容,执行复杂的页面交互,并提取结构化数据。结合 LLM,Agent 可以根据用户的自然语言指令生成相应的抓取脚本,实现真正的智能爬虫。 + +在实际应用中,您可以扩展这个 Agent 的能力。例如,添加登录功能来访问需要认证的网站;实现分页处理来抓取大量数据;集成代理池来应对反爬虫机制;或者使用机器学习模型来智能识别页面结构。浏览器沙箱为这些高级功能提供了坚实的基础。 + +沙箱的使用显著扩展了 Agent 的能力边界。通过代码执行和浏览器控制,Agent 不再局限于文本对话,而是可以完成真实世界中的复杂任务。无论是数据处理、科学计算,还是网页自动化、信息抓取,沙箱都提供了安全可靠的执行环境。 \ No newline at end of file diff --git a/docs/tutorial/06-framework-integration.md b/docs/tutorial/06-framework-integration.md new file mode 100644 index 0000000..1d9895b --- /dev/null +++ b/docs/tutorial/06-framework-integration.md @@ -0,0 +1,330 @@ +--- +sidebar_position: 7 +--- + +# 框架集成入门 + +AgentRun 的设计理念是开放生态和灵活组装。您可以继续使用熟悉的 AI 开发框架编写 Agent 逻辑,同时享受 AgentRun 平台提供的模型管理、沙箱环境、工具系统等企业级能力。本章将介绍如何将 AgentRun 与主流框架集成,以及如何在集成过程中发挥各自的优势。 + +## 集成设计理念 + +AgentRun SDK 的集成模块采用适配器模式,将平台的核心能力抽象为 CommonModel 和 CommonToolSet 两个通用对象。这两个对象封装了与 AgentRun 平台的所有交互细节,并提供了转换方法来生成各个框架所需的原生对象。 + +这种设计带来了几个重要优势。首先是代码的可移植性,您可以编写框架无关的核心逻辑,在需要时轻松切换框架。其次是渐进式集成,可以先在某个框架中验证 Agent 的可行性,然后迁移到更适合生产环境的框架。最后是混合使用,在同一个应用中可以同时使用多个框架的特性,取长补短。 + +CommonModel 对象代表一个 AgentRun 模型,可以是 ModelService 或 ModelProxy。它封装了模型调用的底层 API,并提供了 `to_langchain()`、`to_agentscope()` 等转换方法。这些方法返回对应框架的模型对象,可以直接用于框架的 Agent 构建。 + +CommonToolSet 对象则代表一组工具,可以来自 ToolSet、沙箱模板或自定义工具定义。它同样提供了转换方法,将工具描述转换为各个框架的工具格式。工具转换时会自动处理参数映射、调用包装等细节,确保工具在不同框架中的行为一致。 + +## 选择合适的框架 + +AgentRun 目前支持六个主流 AI 开发框架,它们各有特点和适用场景。 + +LangChain 是最成熟和生态最丰富的框架,提供了完整的 Agent 开发工具链,包括提示词模板、记忆管理、链式调用等。它特别适合构建复杂的对话流程和知识密集型应用。LangChain 的社区活跃,有大量的示例和第三方集成可以参考。 + +AgentScope 是阿里云推出的多智能体框架,强调分布式部署和多 Agent 协作。它提供了消息传递机制和 Agent 编排能力,适合构建需要多个 Agent 协同工作的复杂系统。AgentScope 的设计考虑了生产环境的稳定性和可观测性。 + +LangGraph 是 LangChain 团队开发的状态图框架,用于构建具有复杂控制流的 Agent。它将 Agent 的行为建模为状态机,每个状态对应一个处理节点,通过边连接形成执行图。LangGraph 适合需要精确控制执行流程的场景,如审批流程、多步骤任务等。 + +CrewAI 专注于团队协作场景,提供了角色、任务、团队等高层抽象。在 CrewAI 中,您定义多个 Agent 作为团队成员,每个 Agent 有特定的角色和技能,然后协作完成复杂任务。它内部使用 LangChain,可以看作是 LangChain 的高层封装。 + +Google ADK(Agent Development Kit)是 Google 推出的 Agent 开发工具包,强调简洁性和工程化。它使用 Python 函数作为工具,通过类型注解来定义工具参数,提供了类型安全的 API。Google ADK 适合追求代码整洁和类型安全的团队。 + +Pydantic AI 是基于 Pydantic 的 Agent 框架,利用 Pydantic 的数据验证能力来确保 Agent 输入输出的正确性。它支持 OpenAI 兼容的模型接口,提供了函数调用和结构化输出等特性。Pydantic AI 适合需要严格数据验证的应用场景。 + +选择框架时,建议考虑以下因素:如果您已经在使用某个框架,优先选择集成该框架以降低迁移成本;如果是新项目,LangChain 是最安全的选择,它的生态和文档最完善;如果需要多 Agent 协作,考虑 AgentScope 或 CrewAI;如果强调类型安全,选择 Google ADK 或 Pydantic AI;如果需要复杂的控制流,LangGraph 是最佳选择。 + +## LangChain 集成详解 + +LangChain 是使用最广泛的框架,我们先详细了解它的集成方式。AgentRun SDK 提供了 `agentrun.integration.langchain` 模块,包含三个核心函数:`model`、`toolset` 和 `sandbox_toolset`。 + +`model` 函数用于获取 AgentRun 模型并转换为 LangChain 的 BaseChatModel: + +```python +from agentrun.integration.langchain import model + +# 从模型名称创建 +llm = model("my-model") + +# 从 ModelProxy 对象创建 +from agentrun.model import ModelProxy +proxy = ModelProxy.get_by_name("my-proxy") +llm = model(proxy) + +# 从 ModelService 对象创建 +from agentrun.model import ModelService +service = ModelService.get_by_name("my-service") +llm = model(service) +``` + +返回的 `llm` 对象是一个标准的 LangChain ChatModel,可以直接用于 LangChain 的各种组件。它支持同步和异步调用,支持流式输出和函数调用等特性。 + +`toolset` 函数将 AgentRun ToolSet 转换为 LangChain StructuredTool 列表: + +```python +from agentrun.integration.langchain import toolset + +# 从工具集名称创建 +tools = toolset("weather-api") + +# 从 ToolSet 对象创建 +from agentrun.toolset import ToolSet +ts = ToolSet.get_by_name("weather-api") +tools = toolset(ts) +``` + +`sandbox_toolset` 函数则将沙箱模板转换为工具列表: + +```python +from agentrun.integration.langchain import sandbox_toolset +from agentrun.sandbox import TemplateType + +# 创建代码解释器工具 +code_tools = sandbox_toolset( + template_name="my-sandbox", + template_type=TemplateType.CODE_INTERPRETER, + sandbox_idle_timeout_seconds=300 +) + +# 创建浏览器工具 +browser_tools = sandbox_toolset( + template_name="my-browser", + template_type=TemplateType.BROWSER +) +``` + +有了这些组件,就可以构建 LangChain Agent 了: + +```python +from langchain.agents import create_openai_functions_agent, AgentExecutor +from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder + +# 准备组件 +llm = model("my-model") +tools = toolset("my-toolset") + sandbox_toolset("my-sandbox", TemplateType.CODE_INTERPRETER) + +# 定义提示词 +prompt = ChatPromptTemplate.from_messages([ + ("system", "你是一个有帮助的助手。"), + ("user", "{input}"), + MessagesPlaceholder(variable_name="agent_scratchpad") +]) + +# 创建 Agent +agent = create_openai_functions_agent(llm, tools, prompt) +agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) + +# 运行 Agent +result = agent_executor.invoke({"input": "帮我计算斐波那契数列的第20项"}) +print(result["output"]) +``` + +这个 Agent 结合了远程模型和代码执行能力。当用户提问时,Agent 会自动决定是否需要编写代码,在沙箱中执行代码,并基于结果回答问题。 + +## AgentScope 集成详解 + +AgentScope 的集成方式与 LangChain 类似,但返回的是 AgentScope 原生对象。使用 `agentrun.integration.agentscope` 模块: + +```python +from agentrun.integration.agentscope import model, toolset, sandbox_toolset +from agentscope.agents import DialogAgent + +# 获取模型 +llm = model("my-model") + +# 获取工具 +tools = toolset("my-toolset") + +# 创建 AgentScope Agent +agent = DialogAgent( + name="assistant", + model_config_name=llm, + use_memory=True, + sys_prompt="你是一个有帮助的助手。" +) + +# 与 Agent 对话 +response = agent({"content": "你好"}) +print(response.content) +``` + +AgentScope 强调多 Agent 协作。您可以创建多个 Agent,让它们相互交互: + +```python +from agentscope.agents import DialogAgent, UserAgent +from agentscope.pipelines import SequentialPipeline + +# 创建多个 Agent +user = UserAgent(name="user") +assistant = DialogAgent( + name="assistant", + model_config_name=model("my-model"), + sys_prompt="你是一个有帮助的助手。" +) +analyst = DialogAgent( + name="analyst", + model_config_name=model("my-model"), + sys_prompt="你是一个数据分析专家。" +) + +# 定义协作流程 +pipeline = SequentialPipeline([user, assistant, analyst]) +result = pipeline.run() +``` + +在这个例子中,用户的输入先发送给助手 Agent,助手处理后转给分析师 Agent,最后返回结果。这种协作模式适合需要多个专业角色配合的场景。 + +## 其他框架快速参考 + +LangGraph 的集成与 LangChain 相同,因为 LangGraph 本身就是 LangChain 生态的一部分: + +```python +from agentrun.integration.langgraph import model, toolset +from langgraph.prebuilt import create_react_agent + +llm = model("my-model") +tools = toolset("my-toolset") + +# 创建 LangGraph Agent +agent = create_react_agent(llm, tools) +result = agent.invoke({"messages": [{"role": "user", "content": "你好"}]}) +``` + +CrewAI 的集成也类似,因为它内部使用 LangChain: + +```python +from agentrun.integration.crewai import model, toolset +from crewai import Agent, Task, Crew + +llm = model("my-model") +tools = toolset("my-toolset") + +# 创建 CrewAI Agent +agent = Agent( + role="助手", + goal="帮助用户解决问题", + backstory="你是一个经验丰富的助手", + llm=llm, + tools=tools +) + +task = Task(description="回答用户问题", agent=agent) +crew = Crew(agents=[agent], tasks=[task]) +result = crew.kickoff() +``` + +Google ADK 使用原生 Python 函数作为工具: + +```python +from agentrun.integration.google_adk import model, toolset + +llm = model("my-model") +tools = toolset("my-toolset") + +# Google ADK 的 Agent 创建 +# tools 已经是 Python 函数列表,可以直接使用 +``` + +Pydantic AI 支持 OpenAI 兼容接口: + +```python +from agentrun.integration.pydantic_ai import model, toolset + +llm = model("my-model") +tools = toolset("my-toolset") + +# Pydantic AI 的 Agent 创建 +``` + +所有框架的集成都遵循相同的模式:使用 `model` 函数获取模型对象,使用 `toolset` 或 `sandbox_toolset` 获取工具列表,然后按照框架的标准方式创建 Agent。 + +## 实战案例:构建多轮对话 Agent + +让我们通过一个完整的案例来展示框架集成的实际应用。这个 Agent 能够进行多轮对话,记住上下文,并在需要时使用工具获取信息。我们使用 LangChain 实现,但同样的逻辑也适用于其他框架。 + +首先定义 Agent 的能力范围和工具集: + +```python +from agentrun.integration.langchain import model, toolset, sandbox_toolset +from agentrun.sandbox import TemplateType +from langchain.agents import create_openai_functions_agent, AgentExecutor +from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain.memory import ConversationBufferMemory + +# 准备模型 +llm = model("my-model") + +# 准备工具:天气查询 + 代码执行 +tools = [] +tools.extend(toolset("weather-api")) +tools.extend(sandbox_toolset( + template_name="my-sandbox", + template_type=TemplateType.CODE_INTERPRETER, + sandbox_idle_timeout_seconds=300 +)) + +# 创建记忆组件 +memory = ConversationBufferMemory( + memory_key="chat_history", + return_messages=True +) + +# 定义提示词模板 +prompt = ChatPromptTemplate.from_messages([ + ("system", """你是一个智能助手,可以帮助用户查询天气和执行代码。 + +你拥有以下能力: +1. 查询世界各地的天气信息 +2. 编写和执行 Python 代码来进行计算或数据处理 + +在对话中请注意: +- 记住之前的对话内容,提供连贯的服务 +- 当用户提到"刚才"、"之前"等词时,参考对话历史 +- 主动使用工具来获取准确信息,不要猜测 +- 用友好、专业的语气回答"""), + MessagesPlaceholder(variable_name="chat_history"), + ("user", "{input}"), + MessagesPlaceholder(variable_name="agent_scratchpad") +]) + +# 创建 Agent +agent = create_openai_functions_agent(llm, tools, prompt) +agent_executor = AgentExecutor( + agent=agent, + tools=tools, + memory=memory, + verbose=True, + max_iterations=5 +) + +# 封装为对话接口 +def chat(user_input: str) -> str: + result = agent_executor.invoke({"input": user_input}) + return result["output"] +``` + +现在可以进行多轮对话了: + +```python +# 第一轮:查询天气 +response1 = chat("杭州今天天气怎么样?") +print(f"助手: {response1}\n") + +# 第二轮:基于上下文继续 +response2 = chat("那北京呢?") +print(f"助手: {response2}\n") + +# 第三轮:使用代码工具 +response3 = chat("帮我计算一下,如果明天杭州是今天的温度加上10度,会是多少?") +print(f"助手: {response3}\n") + +# 第四轮:引用更早的上下文 +response4 = chat("对比一下我问过的两个城市的温度") +print(f"助手: {response4}\n") +``` + +这个对话展示了 Agent 的几个关键能力。在第一轮中,Agent 识别出需要查询天气,调用天气工具获取杭州的天气信息。第二轮中,Agent 理解"那北京呢"指的是也要查询北京的天气,这展示了上下文理解能力。第三轮中,Agent 需要先回忆第一轮的杭州温度,然后使用代码工具进行计算。第四轮中,Agent 需要综合第一轮和第二轮的信息进行对比。 + +这个案例展示了框架集成的实际价值。通过 LangChain 的记忆管理和 Agent 执行器,我们获得了对话上下文管理能力。通过 AgentRun 的工具系统和沙箱环境,我们给 Agent 提供了实际的能力。两者结合,创造出了一个既智能又实用的对话系统。 + +您可以进一步扩展这个系统。例如,添加更多类型的工具(如搜索引擎、数据库查询),使 Agent 能够处理更广泛的问题;调整记忆策略,使用摘要记忆或向量记忆来处理长对话;添加用户画像,让 Agent 能够个性化回答;或者使用多 Agent 架构,让不同的 Agent 专注于不同的任务。这些高级特性的实现都建立在框架集成的基础之上。 \ No newline at end of file diff --git a/docs/tutorial/07-deploy-production.md b/docs/tutorial/07-deploy-production.md new file mode 100644 index 0000000..545856c --- /dev/null +++ b/docs/tutorial/07-deploy-production.md @@ -0,0 +1,288 @@ +--- +sidebar_position: 8 +--- + +# 生产环境部署 + +将 Agent 部署到生产环境需要考虑稳定性、安全性、性能和可维护性等多个方面。本章将介绍如何使用 Serverless Devs 工具将 Agent 部署到 AgentRun 平台,并提供生产环境的最佳实践建议。 + +## 部署前检查清单 + +在开始部署之前,建议先完成以下准备工作,确保部署过程顺利进行。 + +**环境准备**方面,确认您已安装 Serverless Devs 工具并配置了有效的阿里云访问凭证。这些凭证需要具备创建和管理 AgentRun 资源的权限,包括创建 Agent Runtime、配置端点、管理网络等操作。同时确认您的账号已开通 AgentRun 服务,并了解服务的配额限制。 + +**代码准备**方面,确保您的 Agent 代码在本地环境已经过充分测试。所有依赖项都应明确声明在 `requirements.txt` 或等效的依赖文件中,避免使用隐式依赖导致部署后运行失败。代码中不应包含硬编码的密钥或敏感信息,这些信息应通过环境变量或凭证管理系统传递。 + +**资源规划**方面,根据您的 Agent 负载特征评估所需的计算资源。CPU 和内存的配置直接影响 Agent 的响应速度和并发处理能力。如果 Agent 需要访问数据库或其他内网资源,需要提前规划 VPC 网络配置,包括准备 VPC ID、交换机 ID 和安全组 ID。 + +**权限配置**方面,为 Agent Runtime 准备一个 RAM 角色。这个角色需要具备 Agent 运行所需的各种权限,例如访问 OSS 存储、写入日志到 SLS、调用其他云服务等。建议遵循最小权限原则,只授予必需的权限。 + +**监控规划**方面,如果需要使用日志服务,提前创建好 SLS 项目和日志库。日志配置应在部署配置中明确指定,这样 Agent 运行时的日志才能被正确收集和持久化。 + +## 配置部署文件 + +AgentRun 使用 Serverless Devs 作为部署工具,配置文件采用 YAML 格式。一个基础的生产环境配置文件结构如下: + +```yaml +edition: 3.0.0 +name: production-agent +access: default + +resources: + my-agent: + component: agentrun + props: + region: cn-hangzhou + agent: + name: production-agent + description: "生产环境 Agent" + + code: + src: ./code + language: python3.12 + command: + - python3 + - main.py + + cpu: 2.0 + memory: 4096 + diskSize: 1024 + timeout: 600 + port: 8000 + instanceConcurrency: 50 + + role: acs:ram::123456789:role/AliyunAgentRunRole + + internetAccess: true + + environmentVariables: + ENVIRONMENT: production + LOG_LEVEL: info + + logConfig: + project: agentrun-logs + logstore: production-agent +``` + +这个配置定义了 Agent 的基本信息、代码来源、资源规格和运行参数。`name` 字段是 Agent Runtime 的唯一标识,部署后无法修改,建议使用有意义的命名规则。`code` 部分指定了代码的位置和运行方式,`src` 可以是本地目录、ZIP 文件或 OSS 路径。 + +资源配置需要根据实际负载调整。`cpu` 和 `memory` 决定了单个实例的计算能力,`instanceConcurrency` 控制单个实例可以同时处理的请求数。这三个参数的配置需要平衡性能和成本,过低会导致响应缓慢,过高则会浪费资源。 + +`role` 字段指定 Agent Runtime 使用的 RAM 角色。您可以使用 AgentRun 提供的快速授权链接创建一个默认角色,或者根据实际需求创建自定义角色。角色需要包含 AliyunAgentRunFullAccess 权限策略,如果使用了日志服务,还需要添加日志写入权限。 + +## 网络配置策略 + +AgentRun 支持三种网络访问模式,您需要根据 Agent 的实际需求选择合适的配置。 + +**纯公网模式**适合不需要访问内网资源的场景。这种模式下,Agent 只能访问公网服务,配置最简单,只需设置 `internetAccess: true`。这是测试和开发环境的推荐配置。 + +**纯 VPC 模式**适合需要访问 RDS、Redis 等内网资源的生产环境。在这种模式下,Agent 运行在指定的 VPC 网络中,可以访问 VPC 内的所有资源,但无法直接访问公网。配置示例: + +```yaml +agent: + vpcConfig: + vpcId: vpc-bp1234567890abcdef + vSwitchIds: + - vsw-bp1111111111111111 + - vsw-bp2222222222222222 + securityGroupId: sg-bp1234567890abcdef + internetAccess: false +``` + +配置 VPC 时需要注意几点:`vpcId` 必须是您账号下已存在的 VPC,交换机必须属于该 VPC,且与 Agent Runtime 在同一个地域。安全组需要配置适当的入站和出站规则,确保 Agent 能够访问所需的服务端口。 + +**混合模式**同时支持 VPC 内网和公网访问,适合需要访问内网资源同时对外提供服务的场景。配置时同时指定 `vpcConfig` 和 `internetAccess: true`。这种模式提供了最大的灵活性,但也需要更仔细地配置安全组规则。 + +## 环境变量管理 + +生产环境的环境变量管理需要特别注意安全性。敏感信息如 API Key、数据库密码等不应直接写入配置文件,而应通过环境变量注入。 + +Serverless Devs 支持从本地环境变量读取配置值。您可以创建一个 `.env` 文件来管理环境变量: + +```env +API_KEY=your_secret_key +DB_HOST=rds.example.com +DB_PASSWORD=your_db_password +MODEL_ENDPOINT=https://model.example.com +``` + +在 `s.yaml` 中引用这些变量: + +```yaml +agent: + environmentVariables: + API_KEY: ${env(API_KEY)} + DB_HOST: ${env(DB_HOST)} + DB_PASSWORD: ${env(DB_PASSWORD)} + MODEL_ENDPOINT: ${env(MODEL_ENDPOINT)} + ENVIRONMENT: production +``` + +部署时,Serverless Devs 会自动读取 `.env` 文件并替换配置中的变量引用。务必将 `.env` 文件添加到 `.gitignore`,避免敏感信息被提交到版本控制系统。 + +对于团队协作场景,可以提供一个 `.env.example` 模板文件,列出所有需要配置的环境变量,但不包含实际的值。团队成员复制这个模板并填入自己的配置。 + +## 日志配置 + +完善的日志配置是生产环境监控和故障排查的基础。AgentRun 支持将日志输出到阿里云日志服务(SLS),提供持久化存储和强大的查询分析能力。 + +配置日志服务需要先在 SLS 中创建项目和日志库,然后在部署配置中引用: + +```yaml +agent: + logConfig: + project: agentrun-production + logstore: agent-runtime-logs + + role: acs:ram::123456789:role/AliyunAgentRunLogRole +``` + +执行角色需要具备写入指定日志库的权限。可以为角色附加以下权限策略: + +```json +{ + "Version": "1", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "log:PostLogStoreLogs" + ], + "Resource": "acs:log:*:*:project/agentrun-production/logstore/agent-runtime-logs" + } + ] +} +``` + +日志配置完成后,Agent 的标准输出和标准错误都会被自动收集到 SLS。您可以在 SLS 控制台查询和分析日志,设置告警规则,或者将日志导出到其他系统。 + +建议在代码中使用结构化日志格式,例如 JSON,这样便于后续的日志分析。同时合理设置日志级别,避免在生产环境输出过多的调试信息。 + +## 执行部署 + +完成配置后,就可以执行部署操作了。首先确保您已配置 Serverless Devs 的访问凭证: + +```bash +s config add +``` + +按照提示输入您的 Access Key ID 和 Access Key Secret,并为这组凭证设置一个名称,例如 `production`。 + +如果您的项目使用了 Python 依赖,建议先执行构建步骤。构建过程会在 Docker 容器中安装依赖,确保与云端运行环境兼容: + +```bash +s build +``` + +构建完成后执行部署: + +```bash +s deploy -a production +``` + +其中 `-a production` 指定使用名为 `production` 的访问凭证。您也可以在 `s.yaml` 的 `access` 字段中指定凭证名称,这样就不需要每次都传递 `-a` 参数。 + +部署过程可能需要几分钟时间。Serverless Devs 会显示部署进度,包括代码上传、资源创建、实例启动等步骤。部署成功后会输出 Agent 的访问信息,包括 Agent ID、ARN 和端点地址。 + +如果您配置了多个端点,部署输出会显示每个端点的详细信息: + +``` +agent: + id: 1062cdd0-042e-407b-8a3f-234370c2c68c + name: production-agent + status: READY + endpoints: + - id: endpoint-prod + name: production + url: https://xxx.agentrun-data.cn-hangzhou.aliyuncs.com/... + - id: endpoint-canary + name: canary + url: https://xxx.agentrun-data.cn-hangzhou.aliyuncs.com/... +``` + +## 端点管理与灰度发布 + +生产环境通常需要多个端点来支持不同的发布策略。最基本的配置是创建一个稳定的生产端点: + +```yaml +agent: + endpoints: + - name: production + version: 1 + description: "生产环境主端点" +``` + +当需要发布新版本时,可以采用灰度发布策略来降低风险。首先修改代码并更新配置,创建一个新版本的端点,并配置较小的流量权重: + +```yaml +agent: + endpoints: + - name: production + version: 1 + description: "当前生产版本" + + - name: canary + version: 2 + description: "金丝雀测试" +``` + +重新部署后,`canary` 端点会运行新版本的代码。此时可以通过 `canary` 端点的 URL 直接测试新版本,验证功能是否正常。 + +确认新版本稳定后,可以逐步增加其流量比例。通过配置 `weight` 参数来控制流量分配,例如将 10% 的流量切换到新版本: + +```yaml +endpoints: + - name: production + version: 1 + + - name: canary + version: 2 + weight: 0.1 +``` + +继续观察新版本的运行指标,如果一切正常,可以进一步提高流量比例,最终将全部流量切换到新版本。这个过程可以分多个阶段进行,例如 10% → 30% → 50% → 100%,每个阶段观察一段时间确保稳定。 + +如果新版本出现问题,可以快速回滚到旧版本,只需将流量权重调回或删除新版本端点即可。这种灰度发布机制大大降低了发布风险。 + +## 性能优化建议 + +生产环境的性能优化需要根据实际负载特征来调整配置参数。 + +**资源配置**方面,CPU 和内存的分配要匹配 Agent 的计算需求。如果 Agent 主要是 I/O 密集型(大量网络请求或数据库查询),可以适当降低 CPU 配置,增加实例并发数。如果是 CPU 密集型(复杂计算或大量数据处理),则需要增加 CPU 配置。建议通过压测来确定最优配置。 + +**并发控制**通过 `instanceConcurrency` 参数配置。这个值决定了单个实例可以同时处理多少个请求。设置过低会导致频繁启动新实例,增加冷启动延迟和成本;设置过高可能导致实例过载,请求响应变慢。一般建议从较低的值(如 10)开始,根据监控数据逐步调整。 + +**超时配置**通过 `timeout` 参数控制。这个值应该略大于 Agent 处理最复杂请求所需的时间,但也不宜设置过大,避免异常请求长时间占用资源。可以通过日志分析来确定合理的超时时间。 + +**磁盘配置**通过 `diskSize` 参数设置。如果 Agent 需要处理大文件或生成临时文件,需要增加磁盘空间。默认的 512MB 通常足够普通应用使用。 + +**网络优化**方面,如果 Agent 需要频繁访问某些云服务(如 OSS、RDS),建议配置 VPC 网络,利用内网传输来降低延迟和成本。同时确保安全组规则不会限制必要的网络连接。 + +## 监控与告警 + +生产环境需要完善的监控体系来及时发现和处理问题。AgentRun 与阿里云的监控服务深度集成,提供了丰富的监控指标。 + +通过 SLS 日志服务,您可以查询和分析 Agent 的运行日志。建议配置以下几类告警规则:错误日志告警,当错误日志数量超过阈值时发送通知;响应时间告警,当 Agent 响应时间持续超过预期值时发送通知;资源使用告警,当 CPU 或内存使用率持续较高时发送通知。 + +除了系统级监控,还应该在应用层添加业务监控。例如记录关键操作的成功率、耗时分布、异常类型等。这些指标可以帮助您快速定位问题,了解系统的健康状况。 + +建议建立监控大盘,将关键指标可视化展示。这样团队成员可以直观地了解系统状态,在出现异常时快速响应。 + +## 故障排查指南 + +当生产环境出现问题时,需要快速定位原因并恢复服务。以下是一些常见问题的排查方法。 + +**部署失败**是最常见的问题。首先检查 Serverless Devs 的输出日志,通常会有明确的错误信息。常见原因包括权限不足、配置参数错误、代码依赖缺失等。可以使用 `s deploy --debug` 启用详细日志来获取更多信息。 + +**Agent 无法启动**通常是代码或环境配置问题。查看 SLS 日志中的错误信息,检查是否缺少环境变量、依赖包是否安装正确、代码是否有语法错误等。可以先在本地环境复现问题,修复后再重新部署。 + +**Agent 响应缓慢**可能是资源不足或代码性能问题。检查实例的 CPU 和内存使用率,如果接近上限,需要增加资源配置或优化代码。也可能是外部服务响应慢,检查网络连接和依赖服务的状态。 + +**网络连接问题**通常与 VPC 配置或安全组规则有关。确认交换机和安全组配置正确,出站规则允许访问目标服务的端口。如果需要访问公网,确保 `internetAccess` 设置为 `true`。 + +**日志无法收集**可能是日志配置或权限问题。确认 SLS 项目和日志库存在且名称正确,执行角色具有写入日志的权限。检查 RAM 角色的权限策略是否包含 `log:PostLogStoreLogs` 操作。 + +遇到无法解决的问题时,可以使用 `s info` 命令查看 Agent 的详细信息,包括当前状态、配置参数、错误信息等。这些信息对于问题排查很有帮助。 + +通过本章介绍的部署流程和最佳实践,您应该能够将 Agent 稳定地部署到生产环境,并通过合理的监控和运维策略保障服务的可靠性。 \ No newline at end of file diff --git a/docs/tutorial/08-best-practices.md b/docs/tutorial/08-best-practices.md new file mode 100644 index 0000000..d72deb5 --- /dev/null +++ b/docs/tutorial/08-best-practices.md @@ -0,0 +1,467 @@ +--- +sidebar_position: 8 +--- + +# 最佳实践 + +在生产环境中使用 AgentRun SDK 时,遵循一些最佳实践可以帮助您构建更健壮、更高效、更安全的应用。本章汇总了在实际开发和部署过程中的经验和建议,帮助您规避常见陷阱并充分发挥平台的能力。 + +## 错误处理 + +正确的错误处理是构建可靠应用的基础。AgentRun SDK 定义了完整的异常体系,通过捕获和处理这些异常,您的应用可以优雅地应对各种故障场景。 + +SDK 的所有异常都继承自 `AgentRunError` 基类。在最外层使用这个基类捕获异常可以确保不会漏掉任何 SDK 抛出的错误: + +```python +from agentrun.utils.exception import AgentRunError +from agentrun.agent_runtime import AgentRuntime + +try: + runtime = AgentRuntime.get_by_id("my-agent") + # 执行操作 +except AgentRunError as e: + print(f"操作失败: {e}") + # 记录日志、告警或降级处理 +``` + +对于需要区分不同错误类型的场景,可以使用更具体的异常类。`ResourceNotExistError` 表示请求的资源不存在,这通常意味着资源 ID 错误或资源已被删除: + +```python +from agentrun.utils.exception import ResourceNotExistError + +try: + runtime = AgentRuntime.get_by_id("non-existent-id") +except ResourceNotExistError: + print("Agent Runtime 不存在,可能已被删除") + # 创建新的或使用默认的 +except AgentRunError as e: + print(f"其他错误: {e}") +``` + +`ResourceAlreadyExistError` 在尝试创建已存在的资源时抛出。如果您的应用支持幂等性,可以捕获这个异常并直接使用现有资源: + +```python +from agentrun.utils.exception import ResourceAlreadyExistError +from agentrun.model import ModelService, ModelServiceCreateInput + +try: + service = ModelService.create( + ModelServiceCreateInput(name="my-model", ...) + ) +except ResourceAlreadyExistError: + # 资源已存在,直接获取 + service = ModelService.get_by_name("my-model") +``` + +HTTP 相关的错误分为 `ClientError` 和 `ServerError` 两类。ClientError 表示客户端请求有误(如参数错误、认证失败),通常需要修正请求;ServerError 表示服务端错误,通常是临时性的,可以通过重试解决: + +```python +from agentrun.utils.exception import ClientError, ServerError +import time + +def call_with_retry(func, max_retries=3): + """带重试的函数调用""" + for attempt in range(max_retries): + try: + return func() + except ClientError as e: + # 客户端错误不重试 + print(f"请求错误: {e}") + raise + except ServerError as e: + # 服务器错误重试 + if attempt < max_retries - 1: + wait_time = 2 ** attempt # 指数退避 + print(f"服务器错误,{wait_time}秒后重试...") + time.sleep(wait_time) + else: + print(f"重试{max_retries}次后仍失败") + raise +``` + +在异步代码中,也需要注意错误处理。异步异常的传播机制与同步代码略有不同,确保在 async 函数中正确捕获: + +```python +async def safe_create_runtime(input_config): + try: + runtime = await AgentRuntime.create_async(input_config) + return runtime + except ResourceAlreadyExistError: + return await AgentRuntime.get_by_id_async(input_config.name) + except AgentRunError as e: + # 记录错误并返回 None 或抛出 + print(f"创建失败: {e}") + return None +``` + +## 安全性考虑 + +安全性在企业级应用中至关重要。AgentRun SDK 提供了多层安全机制,正确使用这些机制可以有效保护您的应用和数据。 + +首要原则是避免在代码中硬编码敏感信息。Access Key、API Token 等凭证应该通过环境变量或密钥管理服务提供: + +```python +import os +from agentrun.utils.config import Config + +# 推荐:从环境变量读取 +config = Config() # 自动读取 AGENTRUN_* 环境变量 + +# 避免:硬编码敏感信息 +# config = Config( +# access_key_id="LTAI...", # 不要这样做 +# access_key_secret="..." +# ) +``` + +在容器化部署时,建议使用 Kubernetes Secret 或云服务商的密钥管理服务来注入环境变量。在本地开发时,使用 `.env` 文件并确保它被 `.gitignore` 排除。 + +对于需要访问第三方服务的工具,使用 AgentRun 的凭证管理系统来存储 API Key。这样凭证会被加密存储在平台上,Agent 运行时自动注入,避免了在代码中传递敏感信息: + +```python +from agentrun.credential import Credential, CredentialCreateInput, CredentialConfig + +# 创建凭证 +credential = Credential.create( + CredentialCreateInput( + name="third-party-api-key", + config=CredentialConfig.outbound_tool_api_key( + api_key=os.getenv("THIRD_PARTY_KEY") + ) + ) +) + +# 在 ToolSet 中引用凭证名称,而不是直接传递 API Key +``` + +沙箱环境提供了代码执行的安全隔离。在让 Agent 执行用户提供的代码时,务必使用沙箱而不是直接在宿主环境中执行。沙箱限制了文件系统访问、网络访问和系统资源使用,即使恶意代码也无法危害宿主系统: + +```python +from agentrun.sandbox import Sandbox, TemplateType + +# 在沙箱中执行代码 +sandbox = Sandbox.create( + template_type=TemplateType.CODE_INTERPRETER, + template_name="python-sandbox" +) + +# 执行用户提供的代码 +result = sandbox.context.execute( + code=user_provided_code, + timeout=30 # 设置超时防止无限循环 +) + +# 使用完毕后清理 +sandbox.delete() +``` + +对于 HTTP 服务器,确保在生产环境中配置适当的认证和授权机制。虽然 AgentRunServer 提供了 OpenAI 兼容的 API,但它本身不包含认证逻辑,您需要在前端添加 API Gateway 或使用反向代理来实现认证: + +```python +from agentrun.server import AgentRunServer + +# 服务器本身不包含认证 +server = AgentRunServer(invoke_agent=my_agent) + +# 在生产环境中,应该在前面加一层认证 +# 例如使用 Nginx 反向代理 + JWT 验证 +# 或者使用阿里云 API Gateway +``` + +## 性能优化 + +合理的性能优化可以显著降低成本并提升用户体验。AgentRun SDK 提供了多种机制来帮助您优化应用性能。 + +优先使用异步 API。在处理并发请求或需要调用多个远程服务时,异步 API 可以大幅提升吞吐量。SDK 的大部分方法都提供了异步版本: + +```python +import asyncio +from agentrun.model import ModelProxy + +async def process_batch(queries): + """并发处理多个查询""" + proxy = ModelProxy.get_by_name("my-proxy") + + # 创建并发任务 + tasks = [ + proxy.completions_async( + messages=[{"role": "user", "content": query}] + ) + for query in queries + ] + + # 等待所有任务完成 + results = await asyncio.gather(*tasks) + return results + +# 处理 10 个查询只需要一次最慢查询的时间 +queries = ["问题1", "问题2", ..., "问题10"] +results = asyncio.run(process_batch(queries)) +``` + +使用模型代理的负载均衡功能可以提升模型服务的可用性和性能。将请求分散到多个模型实例上,避免单点瓶颈: + +```python +from agentrun.model import ModelProxy, ModelProxyCreateInput, ProxyConfig + +# 配置负载均衡 +proxy = ModelProxy.create( + ModelProxyCreateInput( + name="balanced-proxy", + proxy_config=ProxyConfig( + mode="LOAD_BALANCE", + endpoints=[ + {"model_name": "model-1", "weight": 50}, + {"model_name": "model-2", "weight": 50} + ] + ) + ) +) +``` + +合理设置超时时间。默认的请求超时是 600 秒,对于大多数场景足够,但某些长时间运行的任务可能需要更长的超时: + +```python +from agentrun.utils.config import Config + +# 为长时间运行的任务设置更长的超时 +config = Config( + timeout=1800, # 30 分钟 + read_timeout=100000 # 读取超时 +) + +# 在调用时传入配置 +result = sandbox.context.execute(code, config=config) +``` + +沙箱的复用可以减少创建和销毁的开销。如果您需要执行多次代码,考虑复用同一个沙箱实例而不是每次都创建新的: + +```python +# 不推荐:每次都创建新沙箱 +for code_snippet in code_list: + sandbox = Sandbox.create(...) + result = sandbox.context.execute(code_snippet) + sandbox.delete() + +# 推荐:复用沙箱 +sandbox = Sandbox.create(...) +try: + for code_snippet in code_list: + result = sandbox.context.execute(code_snippet) +finally: + sandbox.delete() +``` + +使用流式输出改善用户体验。即使后端处理需要时间,用户也能尽早看到部分结果,感知上的响应速度会大幅提升: + +```python +# 启用流式输出 +response = model.completions( + messages=messages, + stream=True +) + +# 逐步返回内容 +for chunk in response: + content = chunk.choices[0].delta.content + if content: + print(content, end='', flush=True) +``` + +## 资源管理 + +良好的资源管理习惯可以避免资源泄漏和不必要的费用。AgentRun 的资源都有生命周期,需要在使用完毕后正确清理。 + +沙箱是需要特别注意清理的资源。创建沙箱会分配计算资源,如果不及时释放会持续产生费用。使用 try-finally 模式确保沙箱总是被删除: + +```python +sandbox = Sandbox.create(template_type=TemplateType.CODE_INTERPRETER) +try: + # 使用沙箱 + result = sandbox.context.execute(code) + # 处理结果 +finally: + # 确保清理 + sandbox.delete() +``` + +对于长时间运行的沙箱,设置合适的空闲超时时间。沙箱在一段时间无活动后会自动停止,避免忘记清理导致的资源浪费: + +```python +sandbox = Sandbox.create( + template_type=TemplateType.CODE_INTERPRETER, + template_name="my-template", + sandbox_idle_timeout_seconds=300 # 5分钟无活动后自动停止 +) +``` + +在创建资源时使用等待方法来确保资源就绪。某些资源创建是异步的,立即使用可能会失败: + +```python +# 创建 Agent Runtime +runtime = AgentRuntime.create(input_config) + +# 等待就绪 +runtime.wait_until_ready_or_failed( + interval_seconds=5, + timeout_seconds=300 +) + +# 现在可以安全使用 +endpoint = runtime.create_endpoint(endpoint_config) +``` + +删除资源时也可以等待删除完成,确保资源真正被清理: + +```python +# 删除并等待完成 +runtime.delete_and_wait_until_finished( + interval_seconds=5, + timeout_seconds=300 +) +``` + +定期清理不再使用的资源。可以编写清理脚本定期检查和删除长期未使用的 Agent Runtime、模型服务等: + +```python +from datetime import datetime, timedelta + +# 清理超过 7 天未使用的测试环境 +runtimes = AgentRuntime.list() +cutoff_time = datetime.now() - timedelta(days=7) + +for runtime in runtimes: + if runtime.name.startswith("test-"): + if runtime.last_modified_time < cutoff_time: + print(f"清理旧资源: {runtime.name}") + runtime.delete() +``` + +## 日志和监控 + +完善的日志和监控对于排查问题和优化性能至关重要。SDK 提供了内置的日志功能,合理使用可以帮助您快速定位问题。 + +在开发和调试时,启用 DEBUG 日志可以看到 SDK 的详细执行过程: + +```python +import os + +# 设置环境变量启用 DEBUG 日志 +os.environ["AGENTRUN_SDK_DEBUG"] = "true" + +# 或者在代码中配置 +from agentrun.utils.log import logger +logger.setLevel("DEBUG") +``` + +DEBUG 模式会输出所有 HTTP 请求和响应的详细信息,包括请求 URL、Headers、Body 等。注意在生产环境中关闭 DEBUG 模式,避免日志过多和敏感信息泄露。 + +在生产环境中使用结构化日志。记录关键操作的结果、耗时和上下文信息,便于后续分析: + +```python +import time +from agentrun.utils.log import logger + +start_time = time.time() +try: + result = model.completions(messages=messages) + duration = time.time() - start_time + + logger.info( + "模型调用成功", + extra={ + "model": model.name, + "duration": duration, + "tokens": result.usage.total_tokens + } + ) +except Exception as e: + duration = time.time() - start_time + logger.error( + "模型调用失败", + extra={ + "model": model.name, + "duration": duration, + "error": str(e) + } + ) +``` + +利用 Agent Runtime 的日志配置将应用日志投递到阿里云日志服务(SLS)。在 `s.yaml` 中配置日志投递后,所有的标准输出和标准错误都会自动收集: + +```yaml +logConfig: + project: my-sls-project + logstore: my-logstore + enableRequestMetrics: true + enableInstanceMetrics: true +``` + +这样您可以在 SLS 控制台查询和分析日志,设置告警规则,构建监控大盘。 + +## 常见陷阱规避 + +基于实际使用经验,以下是一些容易遇到的问题和避免方法。 + +不要在循环中重复创建客户端对象。客户端对象可以复用,重复创建会产生不必要的开销: + +```python +# 不推荐 +for item in items: + client = ModelClient() # 每次都创建 + model = client.get("my-model") + # ... + +# 推荐 +client = ModelClient() +for item in items: + model = client.get("my-model") + # ... +``` + +注意异步上下文的正确使用。在异步函数中调用同步方法可能导致事件循环阻塞: + +```python +import asyncio + +# 错误:在异步函数中调用同步方法 +async def bad_example(): + result = model.completions(messages) # 阻塞事件循环 + return result + +# 正确:使用异步版本 +async def good_example(): + result = await model.completions_async(messages) + return result +``` + +不要忽略资源状态检查。某些操作要求资源处于特定状态,直接操作可能失败: + +```python +# 检查状态 +runtime = AgentRuntime.get_by_id("my-agent") +if runtime.status != "Ready": + print(f"Agent 尚未就绪,当前状态: {runtime.status}") + runtime.wait_until_ready_or_failed() + +# 现在可以安全操作 +endpoint = runtime.create_endpoint(config) +``` + +合理使用配置合并机制。Config 对象支持链式合并,后面的配置会覆盖前面的: + +```python +# 全局配置 +global_config = Config( + access_key_id="...", + region_id="cn-hangzhou" +) + +# 特定操作的配置 +operation_config = Config(timeout=1800) + +# 合并配置,operation_config 的 timeout 会覆盖 global_config 的默认值 +merged_config = global_config.update(operation_config) +``` + +这些最佳实践来自于实际项目中的经验总结。遵循这些建议可以帮助您构建更加健壮和高效的 Agent 应用,减少在生产环境中遇到问题的概率。当然,每个项目都有其特殊性,您需要根据具体场景灵活应用这些原则。 \ No newline at end of file diff --git a/docs/tutorial/intro.md b/docs/tutorial/intro.md deleted file mode 100644 index 6473f46..0000000 --- a/docs/tutorial/intro.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -sidebar_position: 1 ---- - -# AgentRun 是什么 - -AgentRun 是一站式的 AI Agent 基础设施平台,为企业级 Agent 提供从开发、部署到运维的全生命周期支持。 - -它以函数计算(FC)为底座,继承了极致弹性、按量付费、零运维的核心优势。AgentRun 针对 Agent 场景提供高性能 Sandbox 与企业级隔离、模型统一代理与治理、全链路可观测与成本管控、工具与 MCP 统一管理、以及完善的安全治理能力。平台深度集成主流开源生态,支持一键部署托管,并可模块化按需使用,与已有系统灵活对接。通过深度集成云原生服务,让开发者专注于业务逻辑,扫清 AI Agent 规模化落地中的基础设施障碍。 diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index d0643f5..d1c4550 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -2,137 +2,231 @@ sidebar_position: 2 --- -# 🚀 快速开始 +# 快速开始 -你可以使用任意您喜欢的框架进行 Agent 开发,这里以 langchain 为例 +这是一个端到端的实践指南,将带您在 15 分钟内完成一个智能体的开发和部署。我们会使用 LangChain 框架构建一个支持代码执行能力的对话智能体,并将其部署到 AgentRun 云平台供业务系统调用。 -## 1. 安装 Serverless Devs +## 前置准备 -运行脚手架,您需要使用 Serverless Devs 工具,请参考对应 [安装教程](https://serverless-devs.com/docs/user-guide/install) +在开始之前,请确保您的开发环境满足以下要求。首先需要安装 Serverless Devs 工具,这是 AgentRun 使用的项目脚手架和部署工具。如果您的本地环境已经安装了 NodeJS,可以通过 npm 快速完成安装: -> 如果您拥有 NodeJS 开发环境,可以使用 `npm i -g @serverless-devs/s` 快速安装 Serverless Devs -> 您也可以直接下载 [Serverless Devs 二进制程序](https://github.com/Serverless-Devs/Serverless-Devs/releases) 使用 Serverless Devs +```bash +npm install -g @serverless-devs/s +``` + +如果您不希望安装 NodeJS 环境,也可以直接下载 [Serverless Devs 二进制程序](https://github.com/Serverless-Devs/Serverless-Devs/releases)使用。安装完成后,执行 `s --version` 验证安装是否成功。 -## 2. 创建模板 +另外,请确认您的 Python 版本在 3.10 或以上,这是 AgentRun SDK 的运行要求。可以通过 `python --version` 查看当前版本。 -使用快速创建脚手架创建您的 Agent +## 创建项目 -:::warning -您需要确保您的 python 环境在 3.10 以上 -::: +使用 Serverless Devs 的初始化命令创建一个基于 LangChain 的智能体项目。执行以下命令并按照提示完成项目创建: ```bash -# 初始化模板 s init agentrun-quick-start-langchain +``` -# 按照实际情况进入代码目录 -cd agentrun-quick-start-langchain/code +命令执行后会在当前目录生成 `agentrun-quick-start-langchain` 文件夹,这是一个完整的 Agent 项目模板。进入代码目录并安装依赖: -# 初始化虚拟环境并安装依赖 +```bash +cd agentrun-quick-start-langchain/code uv venv && uv pip install -r requirements.txt ``` -## 3. 配置认证信息 +项目使用 uv 作为 Python 包管理器以提供更快的依赖安装速度。如果您更习惯使用 pip,可以替换为 `python -m venv .venv && pip install -r requirements.txt`。 + +## 配置认证信息 -设置环境变量(建议通过 `.env` 配置您的环境变量) +AgentRun SDK 需要您的阿里云账号凭证来访问云端资源。推荐通过环境变量的方式配置认证信息,这样可以避免在代码中硬编码敏感信息。在项目根目录创建 `.env` 文件: ```bash -export AGENTRUN_ACCESS_KEY_ID="your-access-key-id" -export AGENTRUN_ACCESS_KEY_SECRET="your-access-key-secret" -export AGENTRUN_ACCOUNT_ID="your-account-id" -export AGENTRUN_REGION="cn-hangzhou" +AGENTRUN_ACCESS_KEY_ID=your-access-key-id +AGENTRUN_ACCESS_KEY_SECRET=your-access-key-secret +AGENTRUN_ACCOUNT_ID=your-account-id +AGENTRUN_REGION=cn-hangzhou ``` -## 4. 了解 Agent 如何与 LangChain 集成 +将上述配置项中的值替换为您的真实凭证。其中 `AGENTRUN_ACCESS_KEY_ID` 和 `AGENTRUN_ACCESS_KEY_SECRET` 可以在阿里云控制台的 [AccessKey 管理页面](https://ram.console.aliyun.com/manage/ak)获取,`AGENTRUN_ACCOUNT_ID` 是您的阿里云主账号 ID。 -使用 `from agentrun.integration.langchain import model, sandbox_toolset` 导入 langchain 的集成能力,这里默认提供了 `model`、`sandbox_toolset`、`toolset`,可以快速创建 langchain 可识别的大模型、工具 -同时,通过 AgentRunServer 可以快速开放 HTTP Server 供其他业务集成 +配置完成后,AgentRun SDK 会在初始化时自动读取这些环境变量。如果您希望通过代码显式配置,也可以使用 Config 对象: + +```python +from agentrun.utils.config import Config + +config = Config( + access_key_id="your-access-key-id", + access_key_secret="your-access-key-secret", + account_id="your-account-id", + region_id="cn-hangzhou" +) +``` + +## 理解 Agent 代码结构 + +打开项目中的 `index.py` 文件,这是智能体的核心实现。代码主要分为三个部分:资源初始化、智能体逻辑和服务启动。 + +首先是资源初始化部分,这里通过 AgentRun 的集成模块获取 LangChain 可用的模型和工具: ```python from agentrun.integration.langchain import model, sandbox_toolset from agentrun.sandbox import TemplateType -from agentrun.server import AgentRequest, AgentRunServer -from agentrun.utils.log import logger -# 请替换为您已经创建的 模型 和 沙箱 名称 -MODEL_NAME = "" -SANDBOX_NAME = "" +# 指定要使用的模型名称(需要提前在控制台创建) +MODEL_NAME = "your-model-name" +# 指定沙箱模板名称(用于执行代码) +SANDBOX_NAME = "your-sandbox-name" + +# 获取 LangChain 格式的模型客户端 +llm = model(MODEL_NAME) -if MODEL_NAME.startswith("<"): - raise ValueError("请将 MODEL_NAME 替换为您已经创建的模型名称") +# 获取代码执行沙箱工具 +code_interpreter_tools = sandbox_toolset( + template_name=SANDBOX_NAME, + template_type=TemplateType.CODE_INTERPRETER, + sandbox_idle_timeout_seconds=300 +) +``` + +这段代码展示了 AgentRun 的核心能力之一:通过简单的函数调用将云端资源转换为框架原生对象。`model()` 函数返回的是标准的 LangChain `BaseChatModel` 对象,`sandbox_toolset()` 返回的是 LangChain 工具列表,这意味着您可以无缝使用 LangChain 生态中的任何组件。 -code_interpreter_tools = [] -if SANDBOX_NAME and not SANDBOX_NAME.startswith("<"): - code_interpreter_tools = sandbox_toolset( - template_name=SANDBOX_NAME, - template_type=TemplateType.CODE_INTERPRETER, - sandbox_idle_timeout_seconds=300, - ) -else: - logger.warning("SANDBOX_NAME 未设置或未替换,跳过加载沙箱工具。") +接下来是智能体逻辑的实现。项目模板中使用 LangChain 的 Agent 框架构建了一个支持函数调用的对话智能体: -# ... +```python +from langchain.agents import create_tool_calling_agent, AgentExecutor +from langchain_core.prompts import ChatPromptTemplate + +# 定义智能体的系统提示词 +prompt = ChatPromptTemplate.from_messages([ + ("system", "你是一个智能助手,可以帮助用户执行代码。"), + ("placeholder", "{chat_history}"), + ("human", "{input}"), + ("placeholder", "{agent_scratchpad}") +]) + +# 创建智能体 +agent = create_tool_calling_agent(llm, code_interpreter_tools, prompt) +agent_executor = AgentExecutor(agent=agent, tools=code_interpreter_tools) + +# 定义调用入口函数 +def invoke_agent(request: AgentRequest): + """接收 HTTP 请求并返回智能体响应""" + user_input = request.messages[-1].content + result = agent_executor.invoke({"input": user_input}) + return result["output"] +``` + +这里的 `invoke_agent` 函数是智能体的统一入口,它接收一个 `AgentRequest` 对象(包含对话历史等信息),执行智能体逻辑后返回响应。返回值可以是字符串、生成器(用于流式输出)或者 `AgentResponse` 对象,AgentRun 会自动处理这些不同的返回类型并转换为标准的 OpenAI 协议响应。 + +最后是服务启动部分,使用 `AgentRunServer` 将智能体封装为 HTTP 服务: + +```python +from agentrun.server import AgentRunServer -# 自动启动 http server,提供 OpenAI 协议 +# 启动 OpenAI 协议兼容的 HTTP 服务 AgentRunServer(invoke_agent=invoke_agent).start() ``` -## 5. 调用 Agent +这行代码会启动一个监听在 9000 端口的 HTTP 服务器,自动提供 `/v1/chat/completions` 等 OpenAI 兼容的 API 端点。 + +## 准备云端资源 + +在运行代码之前,需要在 AgentRun 控制台创建模型和沙箱模板资源。登录 [AgentRun 控制台](https://functionai.console.aliyun.com/),按照以下步骤操作。 + +首先创建模型服务。在左侧菜单中选择"模型管理",点击"创建模型服务"按钮。填写模型名称(例如 `qwen-max`),选择模型提供商为"通义千问",配置 API 凭证后保存。如果您希望使用其他模型提供商(如 OpenAI、Anthropic),需要先在"凭证管理"中创建对应的 API Key 凭证。 + +接下来创建沙箱模板。在左侧菜单中选择"沙箱管理",点击"创建模板"按钮。选择模板类型为"代码解释器",填写模板名称(例如 `code-interpreter-python`),选择运行时为 Python 3.10,配置资源规格后保存。沙箱模板创建完成后,智能体就可以在隔离的容器环境中安全地执行用户提交的代码。 + +完成资源创建后,回到代码中修改 `MODEL_NAME` 和 `SANDBOX_NAME` 为您刚才创建的资源名称。 + +## 本地测试 + +现在可以在本地启动智能体服务进行测试。激活虚拟环境后执行: ```bash -curl 127.0.0.1:9000/openai/v1/chat/completions \ - -XPOST \ - -H "content-type: application/json" \ - -d '{"messages": [{"role": "user", "content": "通过代码查询现在是几点?"}], "stream":true}' +source .venv/bin/activate # Linux/macOS +# .venv\Scripts\activate # Windows +python index.py ``` -## 6. 部署项目 +服务启动后会看到类似 "Application startup complete" 的日志输出,表示服务已成功运行在 `http://127.0.0.1:9000`。 -项目中已经存在 `s.yaml` 文件,这是 Serverless Devs 的部署配置文件,通过这个文件,您可以配置当前 Agent 在 Agent Run 上的名称、CPU/内存规格、日志投递信息 +打开新的终端窗口,使用 curl 命令测试智能体的对话能力: -在示例情况下,您只需要简单修改该文件即可。修改 `role` 字段为授信给阿里云函数计算(FC)服务,需要拥有AliyunAgentRunFullAccess权限的角色(如果您拥有精细化权限控制的需求,可以根据实际使用的 API 收敛权限) +```bash +curl http://127.0.0.1:9000/v1/chat/completions \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + {"role": "user", "content": "使用 Python 代码计算 1 到 100 的和"} + ], + "stream": true + }' +``` -> 您可以点击此[快速授权链接](https://ram.console.aliyun.com/authorize?request=%7B%22template%22%3A%22OldRoleCommonAuthorize%22%2C%22referrer%22%3A%22https%3A%2F%2Ffunctionai.console.aliyun.com%2Fcn-hangzhou%2Fexplore%22%2C%22payloads%22%3A%5B%7B%22missionId%22%3A%22OldRoleCommonAuthorize.FC%22%2C%22roleName%22%3A%22agentRunRole%22%2C%22roleDescription%22%3A%22AgentRun%20auto%20created%20role.%22%2C%22rolePolicies%22%3A%5B%7B%22policyName%22%3A%22AliyunAgentRunFullAccess%22%7D%2C%7B%22policyName%22%3A%22AliyunDevsFullAccess%22%7D%5D%7D%5D%2C%22callback%22%3A%22https%3A%2F%2Ffunctionai.console.aliyun.com%22%7D),创建一个符合相关权限的角色agentRunRole。 -> -> 此快速创建角色的RoleArn为:acs:ram::\{您的阿里云主账号 ID\}:role/agentRunRole +如果一切正常,您会看到智能体首先分析问题,然后调用代码执行工具运行 Python 代码,最后返回计算结果。响应格式遵循 OpenAI Chat Completions API 标准,这意味着任何支持 OpenAI 协议的客户端都可以直接对接您的智能体服务。 -```yaml -role: acs:ram::\{您的阿里云主账号 ID\}:role/\{您的阿里云角色名称\} -``` +您也可以尝试其他类型的问题,例如数据分析、文件操作等,观察智能体如何利用代码执行能力解决复杂任务。 -> 如果在未来的使用中遇到了任何 Serverless Devs 相关问题,都可以参考 [Serverless Devs 相关文档](https://serverless-devs.com/docs/overview) +## 部署到云端 -在部署前,您需要配置您的部署密钥,使用 `s config add` 进入交互式密钥管理,并按照引导录入您在阿里云的 Access Key ID 与 Access Key Secret。在录入过程中,您需要短期记忆一下您输入的密钥对名称(假设为 `agentrun-deploy`) +本地测试通过后,可以将智能体部署到 AgentRun 云平台,获得生产级的可扩展性、可观测性和安全能力。 -配置完成后,需要首先执行`s build`构建,该步骤依赖本地的`docker`服务,对代码目录下的`requirements.txt`进行构建,以便部署在云端。 +首先需要配置部署凭证。执行 `s config add` 进入交互式配置流程,按照提示输入您的阿里云 Access Key 信息。在配置过程中需要为这组凭证指定一个别名(例如 `agentrun-deploy`),后续部署时会用到这个名称。 -随后即可执行`s deploy`进行部署操作。 +接下来修改项目根目录的 `s.yaml` 配置文件。这个文件定义了智能体在云端的运行配置,包括资源规格、环境变量、日志配置等。您需要重点关注 `role` 字段,这是授权给函数计算服务的 RAM 角色: +```yaml +role: acs:ram::{您的阿里云主账号ID}:role/{角色名称} +``` + +如果您还没有创建过相关角色,可以使用[快速授权链接](https://ram.console.aliyun.com/authorize?request=%7B%22template%22%3A%22OldRoleCommonAuthorize%22%2C%22referrer%22%3A%22https%3A%2F%2Ffunctionai.console.aliyun.com%2Fcn-hangzhou%2Fexplore%22%2C%22payloads%22%3A%5B%7B%22missionId%22%3A%22OldRoleCommonAuthorize.FC%22%2C%22roleName%22%3A%22agentRunRole%22%2C%22roleDescription%22%3A%22AgentRun%20auto%20created%20role.%22%2C%22rolePolicies%22%3A%5B%7B%22policyName%22%3A%22AliyunAgentRunFullAccess%22%7D%2C%7B%22policyName%22%3A%22AliyunDevsFullAccess%22%7D%5D%7D%5D%2C%22callback%22%3A%22https%3A%2F%2Ffunctionai.console.aliyun.com%22%7D)一键创建,创建后的角色 ARN 格式为 `acs:ram::{您的主账号ID}:role/agentRunRole`。 + +准备工作完成后,执行构建和部署命令。构建步骤会在 Docker 容器中安装项目依赖,确保云端环境的一致性(因此需要确保本地 Docker 服务正在运行): ```bash s build s deploy -a agentrun-deploy -# agentrun-deploy 是您使用的密钥对名称,也可以将该名称写入到 s.yaml 开头的 access: 字段中 ``` -## 7. 在线上进行调用 +其中 `-a agentrun-deploy` 指定使用前面配置的凭证别名。如果您希望避免每次部署都输入这个参数,可以在 `s.yaml` 文件开头添加 `access: agentrun-deploy` 配置项。 -部署完成后,您可以看到如下格式的输出 +部署过程需要几分钟时间,完成后会输出智能体的访问端点信息: ``` endpoints: - - - id: ... - arn: ... - name: ... - url: https://12345.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/abcd/endpoints/prod/invocations + - + id: ep-xxxxxx + name: prod + url: https://12345.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/ar-xxxxxx/endpoints/prod/invocations ``` -此处的 url 为您的 Agent 调用地址,将实际的请求 path 拼接到该 base url 后,即可调用云上的 Agent 资源 +这个 URL 就是您的智能体在云端的访问地址。与本地测试类似,将实际的 API 路径拼接到这个基础 URL 后即可调用: ```bash -curl https://12345.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/abcd/endpoints/prod/invocations/openai/v1/chat/completions \ - -XPOST \ - -H "content-type: application/json" \ - -d '{"messages": [{"role": "user", "content": "通过代码查询现在是几点?"}], "stream":true}' +curl https://12345.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/ar-xxxxxx/endpoints/prod/invocations/v1/chat/completions \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + {"role": "user", "content": "使用 Python 代码计算斐波那契数列的第 20 项"} + ], + "stream": true + }' ``` + +云端部署的智能体具备自动扩缩容能力,可以根据请求量动态调整实例数量。您还可以在 AgentRun 控制台查看智能体的运行日志、性能指标和调用统计,便于监控和优化。 + +## 下一步 + +完成快速开始后,您已经掌握了 AgentRun 的基本使用流程。根据实际需求,可以继续探索以下主题: + +**增强智能体能力**:除了代码执行沙箱,AgentRun 还提供浏览器沙箱(用于网页自动化)、向量数据库(用于 RAG 应用)、HTTP 工具集(用于 API 调用)等多种工具。您可以参考"工具集成实战"教程了解如何组合使用这些工具。 + +**模型策略优化**:如果您需要在多个模型之间切换(例如根据问题复杂度选择不同模型),或者希望实现模型调用的负载均衡和容错,可以参考"模型集成实战"教程学习模型代理的配置方法。 + +**多框架集成**:AgentRun 不仅支持 LangChain,还可以与 CrewAI、LangGraph、AgentScope 等主流框架集成。如果您希望使用其他框架或者在项目中切换框架,可以参考"框架集成指南"教程。 + +**生产环境部署**:在将智能体用于生产环境之前,建议阅读"生产环境部署清单",了解性能优化、错误处理、安全加固等最佳实践。 + +如果在使用过程中遇到问题,可以查阅"问题排查指南"或访问 [AgentRun 官方文档](https://docs.agent.run/)获取帮助。 \ No newline at end of file From 17e55fccc2c6d8b38b6e0dd5d05840c7645e01ae Mon Sep 17 00:00:00 2001 From: Anycodes Date: Sun, 14 Dec 2025 01:17:20 +0800 Subject: [PATCH 2/9] Enhance overview with SDK value and image Added an image and expanded on the core value of the SDK. --- docs/tutorial/00-overview.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/00-overview.md b/docs/tutorial/00-overview.md index 41f7707..0dc4239 100644 --- a/docs/tutorial/00-overview.md +++ b/docs/tutorial/00-overview.md @@ -10,6 +10,9 @@ AgentRun 是以高代码为核心,开放生态、灵活组装的一站式 Agen AgentRun Python SDK 是平台的官方客户端库,提供了完整的 Python API 来管理和调用 AgentRun 服务。通过这个 SDK,开发者可以在本地开发环境中编写、测试 Agent 应用,并无缝部署到云端运行。 + + + ## SDK 的核心价值 AgentRun Python SDK 采用面向对象的设计理念,将云端资源抽象为 Python 对象,使开发者能够像操作本地对象一样管理云端资源。SDK 同时提供同步和异步两套 API,支持多种认证方式,并包含完整的类型注解,为 IDE 提供良好的代码提示支持。 @@ -76,4 +79,4 @@ AgentRun Python SDK 适用于需要构建企业级 AI Agent 应用的场景。 对于已经使用某个 AI 开发框架的团队,SDK 的集成能力可以帮助您快速迁移到 AgentRun 平台,享受平台提供的模型高可用、资源弹性伸缩、运维监控等企业级能力,而无需重写已有代码。 -对于需要严格数据安全和合规要求的场景,AgentRun 的数据不出域特性和隔离沙箱环境可以确保敏感数据的安全处理。您可以在私有网络环境中部署模型服务和工具服务,通过 SDK 连接到 AgentRun 平台,实现数据和计算的物理隔离。 \ No newline at end of file +对于需要严格数据安全和合规要求的场景,AgentRun 的数据不出域特性和隔离沙箱环境可以确保敏感数据的安全处理。您可以在私有网络环境中部署模型服务和工具服务,通过 SDK 连接到 AgentRun 平台,实现数据和计算的物理隔离。 From 08bece47f711ae9180db4be0b30698d76f29a10d Mon Sep 17 00:00:00 2001 From: Anycodes Date: Sun, 14 Dec 2025 01:25:44 +0800 Subject: [PATCH 3/9] add docs --- docs/tutorial/00-overview.md | 2 +- docs/tutorial/01-installation.md | 2 +- docs/tutorial/02-first-agent.md | 2 +- docs/tutorial/03-model-basics.md | 2 +- docs/tutorial/04-tool-basics.md | 2 +- docs/tutorial/05-sandbox-basics.md | 2 +- docs/tutorial/06-framework-integration.md | 2 +- docs/tutorial/07-deploy-production.md | 2 +- docs/tutorial/08-best-practices.md | 2 +- .../09-best-preactices-opinion-analysis.md | 370 ++++++++++++++++++ 10 files changed, 379 insertions(+), 9 deletions(-) create mode 100644 docs/tutorial/09-best-preactices-opinion-analysis.md diff --git a/docs/tutorial/00-overview.md b/docs/tutorial/00-overview.md index 0dc4239..e15b1ea 100644 --- a/docs/tutorial/00-overview.md +++ b/docs/tutorial/00-overview.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 10 --- # SDK 概览与架构介绍 diff --git a/docs/tutorial/01-installation.md b/docs/tutorial/01-installation.md index 6392242..e0ae8ee 100644 --- a/docs/tutorial/01-installation.md +++ b/docs/tutorial/01-installation.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 11 --- # 安装与配置 diff --git a/docs/tutorial/02-first-agent.md b/docs/tutorial/02-first-agent.md index cb6d0c0..5ff8d40 100644 --- a/docs/tutorial/02-first-agent.md +++ b/docs/tutorial/02-first-agent.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 12 --- # 创建第一个 Agent diff --git a/docs/tutorial/03-model-basics.md b/docs/tutorial/03-model-basics.md index d02cdc4..4a12d0a 100644 --- a/docs/tutorial/03-model-basics.md +++ b/docs/tutorial/03-model-basics.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 13 --- # 模型使用基础 diff --git a/docs/tutorial/04-tool-basics.md b/docs/tutorial/04-tool-basics.md index 1e9390c..5d0858e 100644 --- a/docs/tutorial/04-tool-basics.md +++ b/docs/tutorial/04-tool-basics.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 14 --- # 工具使用基础 diff --git a/docs/tutorial/05-sandbox-basics.md b/docs/tutorial/05-sandbox-basics.md index 870286c..2fd7930 100644 --- a/docs/tutorial/05-sandbox-basics.md +++ b/docs/tutorial/05-sandbox-basics.md @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 15 --- # 沙箱使用基础 diff --git a/docs/tutorial/06-framework-integration.md b/docs/tutorial/06-framework-integration.md index 1d9895b..52ad95d 100644 --- a/docs/tutorial/06-framework-integration.md +++ b/docs/tutorial/06-framework-integration.md @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 16 --- # 框架集成入门 diff --git a/docs/tutorial/07-deploy-production.md b/docs/tutorial/07-deploy-production.md index 545856c..c234c0e 100644 --- a/docs/tutorial/07-deploy-production.md +++ b/docs/tutorial/07-deploy-production.md @@ -1,5 +1,5 @@ --- -sidebar_position: 8 +sidebar_position: 17 --- # 生产环境部署 diff --git a/docs/tutorial/08-best-practices.md b/docs/tutorial/08-best-practices.md index d72deb5..eed00f5 100644 --- a/docs/tutorial/08-best-practices.md +++ b/docs/tutorial/08-best-practices.md @@ -1,5 +1,5 @@ --- -sidebar_position: 8 +sidebar_position: 18 --- # 最佳实践 diff --git a/docs/tutorial/09-best-preactices-opinion-analysis.md b/docs/tutorial/09-best-preactices-opinion-analysis.md new file mode 100644 index 0000000..3585b25 --- /dev/null +++ b/docs/tutorial/09-best-preactices-opinion-analysis.md @@ -0,0 +1,370 @@ +--- +sidebar_position: 19 +--- + +# 最佳实践案例:构建舆情分析系统 + +本章通过一个完整的舆情分析系统案例,展示 AgentRun SDK 在实际项目中的应用。该系统实现了从数据收集、深度分析到报告生成的完整流程,并提供了流式输出、多沙箱管理、智能探索等高级特性。 + +## 系统概述 + +这个舆情分析系统的核心目标是自动化地监测和分析网络舆情。系统接收用户提供的关键词,自动在多个平台搜索相关信息,使用浏览器沙箱深度抓取内容,通过 AI 进行情感分析和趋势预测,最终生成专业的可视化报告。整个过程通过 Server-Sent Events 实时推送状态更新,让用户可以实时观察分析进展。 + +系统采用代码控制流程的设计理念,将整个分析任务拆解为明确的阶段:数据收集、数据分析、报告撰写、HTML 渲染。每个阶段由专门的工具函数实现,Agent 只负责按顺序调用这些工具,而不是让 LLM 自主决策流程。这种设计保证了流程的可控性和稳定性。 + +## 架构设计 + +系统基于 FastAPI 构建 HTTP 服务,使用 PydanticAI 作为 Agent 框架,集成 AgentRun 的浏览器沙箱能力。核心架构包含三个层次:HTTP 层处理前端请求和 SSE 通信,Agent 层控制分析流程和工具调用,工具层实现具体的数据操作。 + +HTTP 层的设计采用异步流式响应。当收到分析请求时,系统立即返回一个 SSE 连接,然后在后台启动 Agent 任务。Agent 执行过程中的所有状态变化都通过事件队列实时推送到前端,前端可以展示进度条、日志输出、中间结果等信息。这种设计让用户能够清楚地了解系统正在做什么,即使分析过程需要几分钟时间也不会感到焦虑。 + +```python +@app.post("/api/agent") +async def agent_endpoint(request: Request): + body = await request.json() + run_input = RunAgentInput.model_validate(body) + run_id = run_input.run_id + + # 获取事件队列 + queue = event_manager.get_queue(run_id) + + async def event_generator(): + # 发送开始事件 + yield f"data: {json.dumps({'type': 'RUN_STARTED', 'runId': run_id})}\n\n" + + # 后台启动 Agent + agent_task = asyncio.create_task(run_agent_in_background(run_input, deps, run_id)) + + # 消费事件队列 + while not agent_task.done(): + try: + event = queue.get_nowait() + yield f"data: {json.dumps(event.model_dump())}\n\n" + except asyncio.QueueEmpty: + await asyncio.sleep(0.1) + + # 发送完成事件 + yield f"data: {json.dumps({'type': 'RUN_FINISHED', 'runId': run_id})}\n\n" + + return StreamingResponse(event_generator(), media_type="text/event-stream") +``` + +Agent 层使用 PydanticAI 框架,定义了严格的工具调用顺序。系统提示词明确指示 Agent 必须按照收集数据、分析数据、撰写报告、渲染 HTML 的顺序执行,不允许跳过步骤或改变顺序。这种设计避免了 LLM 的不确定性带来的问题。 + +```python +opinion_agent = Agent( + agentrun_model, + deps_type=StateDeps, + system_prompt="""你是舆情分析系统的执行者。 + +你的任务是按照以下严格流程执行舆情分析: + +1. 收到关键词后,调用 collect_data 工具收集数据 +2. 数据收集完成后,调用 analyze_data 工具分析数据 +3. 分析完成后,调用 write_report 工具撰写报告 +4. 报告完成后,调用 render_html 工具生成 HTML + +必须按顺序调用工具,每个工具只调用一次,不要跳过任何步骤。""", + retries=3, +) +``` + +## 多沙箱管理策略 + +浏览器沙箱是系统的核心资源,用于模拟真实浏览器访问网页和提取内容。系统实现了完善的多沙箱管理机制,支持创建、复用、销毁和自动恢复。 + +沙箱管理使用全局字典存储所有活跃的沙箱实例,每个沙箱通过唯一的 sandbox_id 标识。系统提供了创建新沙箱、获取可用沙箱、移除沙箱等基础操作,并通过异步锁保证线程安全。 + +```python +_sandboxes: Dict[str, BrowserSandbox] = {} +_sandbox_lock = asyncio.Lock() + +async def create_browser_sandbox() -> Optional[BrowserSandbox]: + async with _sandbox_lock: + sandbox = await Sandbox.create_async( + template_type=TemplateType.BROWSER, + template_name=agentrun_browser_sandbox_name, + ) + _sandboxes[sandbox.sandbox_id] = sandbox + return sandbox + +async def get_browser_sandbox(sandbox_id: str = None) -> Optional[BrowserSandbox]: + async with _sandbox_lock: + if sandbox_id and sandbox_id in _sandboxes: + return _sandboxes[sandbox_id] + + # 返回任意可用沙箱 + for sid, sandbox in _sandboxes.items(): + return sandbox + + # 没有可用沙箱时创建新的 + return await create_browser_sandbox() +``` + +系统实现了智能的错误恢复机制。当检测到沙箱已关闭的错误时,会自动创建新的沙箱替换失效的实例。这个机制通过解析错误消息判断是否是沙箱关闭导致的异常,如果是则触发重建流程。 + +```python +async def recreate_sandbox_if_closed(sandbox_id: str, error_message: str): + closed_error_patterns = [ + "Target page, context or browser has been closed", + "Browser has been closed", + "Session closed", + ] + + is_closed_error = any(pattern.lower() in error_message.lower() + for pattern in closed_error_patterns) + + if is_closed_error: + await remove_sandbox(sandbox_id) + new_sandbox = await create_browser_sandbox() + return new_sandbox + + return None +``` + +在数据收集过程中,系统会持续监控沙箱状态。一旦捕获到异常,首先尝试判断是否是沙箱关闭错误,如果是则重建沙箱并重新连接 Playwright。这种设计保证了即使沙箱意外失效,任务也能自动恢复继续执行。 + +## 智能内容抓取 + +数据收集是舆情分析的基础。系统通过多层次的抓取策略来获取高质量数据。首先使用必应搜索 API 进行关键词搜索,获取搜索结果的标题、摘要和链接。然后对高相关性的结果进行深度抓取,使用 Playwright 访问原始页面提取完整内容。 + +搜索策略采用多角度查询。系统根据关键词生成多组搜索查询,包括基础搜索、平台特定搜索、时效性搜索、观点类搜索等。不同类型的查询用于获取不同维度的信息,综合起来形成全面的数据视图。 + +```python +def generate_search_queries(keyword: str) -> List[Dict[str, str]]: + queries = [] + + # 基础搜索 + queries.extend([ + {"query": f"{keyword}", "category": "general"}, + {"query": f"{keyword} 最新消息", "category": "general"}, + ]) + + # 知乎搜索 + queries.extend([ + {"query": f"{keyword} 知乎", "category": "zhihu"}, + {"query": f"{keyword} 如何评价 知乎", "category": "zhihu"}, + ]) + + # 微博搜索 + queries.extend([ + {"query": f"{keyword} 微博", "category": "weibo"}, + {"query": f"{keyword} 微博热搜", "category": "weibo"}, + ]) + + # 新闻、评论、B站等其他类别... + + return queries +``` + +相关性评估是数据质量控制的关键环节。系统对每个搜索结果计算相关性得分,只保留得分超过阈值的结果。评估考虑关键词匹配度、时效性、舆情相关性、平台来源等多个因素。这个机制有效过滤了广告、无关内容和低质量页面。 + +深度抓取使用 Playwright 访问原始页面,根据不同平台使用不同的内容提取策略。知乎提取问题描述和回答内容,微博提取微博正文,B站提取视频描述和评论,新闻网站提取文章正文。系统针对每个平台的页面结构编写了专门的选择器规则,确保能够准确提取核心内容。 + +```python +if "zhihu.com" in url: + content_selectors = [ + ".QuestionRichText", + ".RichContent-inner", + ".Post-RichText", + ] + for sel in content_selectors: + elems = await detail_page.query_selector_all(sel) + for elem in elems[:3]: + text = await elem.inner_text() + if text and len(text) > 50: + detailed_content += text[:1000] + "\n\n" +``` + +系统还实现了 LLM 辅助的智能探索机制。对于已经打开的页面,系统会询问 LLM 是否需要进一步探索(如点击评论区、查看更多回复等)。LLM 根据当前页面内容、搜索关键词、已获取的信息量等因素做出决策。这种设计在深度和效率之间取得了平衡,避免了盲目抓取所有可能的内容。 + +```python +async def llm_decide_exploration(keyword, page_url, page_content, source, available_actions): + prompt = f"""根据以下信息决定是否需要进一步探索页面: + +【关键词】{keyword} +【当前页面】{page_url} +【已获取内容】{page_content[:500]} +【可用操作】{available_actions} + +如果当前内容已经足够丰富,可能不需要进一步探索。 +如果是微博/B站等平台,评论区通常包含重要舆情信息。 + +返回 JSON: {{"should_explore": true/false, "action": "操作名", "reason": "原因"}} +""" + + result = await explorer.run(prompt) + return json.loads(result.output) +``` + +## 流式分析输出 + +数据分析和报告生成是计算密集型任务,可能需要较长时间。系统使用流式输出让用户能够实时看到进展,而不是等待所有工作完成。 + +流式输出的核心是 PydanticAI 的 `run_stream` API。这个 API 返回一个异步生成器,可以逐 token 地获取模型输出。系统在生成过程中定期检查内容增量,当累积一定字符数或经过一定时间后,就将当前状态推送到前端。 + +```python +async with analyzer.run_stream(analysis_prompt) as result: + response_text = "" + last_length = 0 + last_time = asyncio.get_event_loop().time() + + async for text in result.stream_text(): + response_text = text + current_time = asyncio.get_event_loop().time() + content_delta = len(response_text) - last_length + time_delta = current_time - last_time + + # 每 200 字符或每 0.5 秒更新一次 + if content_delta >= 200 or (content_delta > 0 and time_delta >= 0.5): + state.analysis_progress = f"正在分析中... ({len(response_text)} 字)\n" + await push_state_event(run_id, state) + last_length = len(response_text) + last_time = current_time +``` + +状态推送通过事件队列实现。每个分析任务有独立的事件队列,工具函数执行过程中可以随时向队列推送状态更新事件。HTTP 层的 SSE 连接持续消费这个队列,将事件转换为 SSE 消息发送给前端。 + +```python +async def push_state_event(run_id: str, state: OpinionState): + event = StateSnapshotEvent( + type=EventType.STATE_SNAPSHOT, + snapshot=state.model_dump(), + timestamp=int(time.time() * 1000) + ) + await event_manager.push_event(run_id, event) +``` + +报告撰写使用相同的流式机制。系统使用更细的粒度控制,每 100 字符或每 0.3 秒推送一次更新,让用户可以看到报告文本逐步生成的过程。这种实时反馈显著改善了用户体验,即使完整报告需要几分钟才能生成,用户也不会觉得系统无响应。 + +## 数据质量控制 + +高质量的数据是准确分析的前提。系统在多个环节实施质量控制,确保最终使用的数据与关键词高度相关且内容有价值。 + +相关性评分是第一道关卡。系统实现了严格的评分算法,要求关键词必须在标题或摘要中出现,否则直接判定为不相关。对于中文关键词,还会检查结果是否包含中文内容,避免返回无关的英文结果。评分考虑关键词完全匹配、部分匹配、时效性关键词、舆情关键词、平台来源等多个维度。 + +```python +async def evaluate_relevance(keyword: str, title: str, snippet: str) -> float: + text = f"{title} {snippet}" + text_lower = text.lower() + + # 检测语言匹配 + has_chinese_keyword = any('\u4e00' <= char <= '鿿' for char in keyword) + result_has_chinese = any('一' <= char <= '鿿' for char in text) + + if has_chinese_keyword and not result_has_chinese: + return 0.0 + + score = 0.0 + + # 关键词完全匹配 + if keyword in text: + score += 0.6 + else: + # 部分匹配检查,匹配率低于 50% 则返回 0 + # ... + + # 时效性加分 + time_keywords = ["最新", "今日", "近日", "2024", "2025"] + if any(tk in text for tk in time_keywords): + score += 0.1 + + # 舆情相关性加分 + opinion_keywords = ["评价", "评论", "看法", "观点", "讨论"] + if any(ok in text for ok in opinion_keywords): + score += 0.1 + + return max(0.0, min(1.0, score)) +``` + +系统还实现了动态质量监控机制。在搜索过程中,系统跟踪每个搜索类别的效果。如果某个类别连续多次返回低相关性结果,系统会自动跳过该类别后续的查询,避免浪费时间在低效的搜索路径上。 + +```python +category_low_relevance_count: Dict[str, int] = {} +skipped_categories: set = set() + +# 检查该类别是否已被跳过 +if category in skipped_categories: + continue + +# 统计低相关性结果 +if new_results_in_query == 0 and low_relevance_in_query > 3: + category_low_relevance_count[category] += 1 + if category_low_relevance_count[category] >= max_low_relevance_per_category: + skipped_categories.add(category) +``` + +数据去重确保不会重复收集相同的内容。系统使用集合记录已访问的 URL,在处理新结果前检查 URL 是否已存在。这个简单但有效的机制避免了重复抓取和数据冗余。 + +## 错误处理与恢复 + +分布式系统中错误不可避免,完善的错误处理是系统稳定性的保障。舆情分析系统实现了多层次的错误处理和自动恢复机制。 + +沙箱错误是最常见的问题之一。浏览器沙箱可能因为超时、资源限制或其他原因意外关闭。系统通过解析异常消息识别沙箱关闭错误,一旦检测到就立即创建新的沙箱实例替换。整个过程对用户透明,只会在日志中显示沙箱重建的提示。 + +网络错误通过重试机制处理。搜索和页面访问都设置了合理的超时时间,超时后会记录日志但继续处理其他任务。系统不会因为个别页面访问失败而中断整个流程,而是尽可能收集更多可用数据。 + +```python +try: + await page.goto(search_url, timeout=30000) + await page.wait_for_load_state("domcontentloaded") +except Exception as e: + error_msg = str(e) + + # 检测是否是沙箱关闭错误 + new_sandbox = await recreate_sandbox_if_closed(sandbox.sandbox_id, error_msg) + if new_sandbox: + sandbox = new_sandbox + # 重新连接浏览器 + browser = await playwright.chromium.connect_over_cdp(sandbox.get_cdp_url()) + context = browser.contexts[0] + page = context.pages[0] + continue +``` + +LLM 调用失败通过降级策略处理。分析和报告生成都依赖 LLM,如果调用失败系统会使用预定义的模板或算法生成结果。虽然质量可能不如 LLM 生成的版本,但能保证任务完成并给用户一个基本可用的结果。 + +```python +try: + async with analyzer.run_stream(analysis_prompt) as result: + # 流式获取分析结果 + pass +except Exception as e: + # 使用默认值 + state.analysis = AnalysisResult( + keywords=[state.keyword], + sentiment_score=0, + sentiment_distribution={"正面": 33, "中性": 34, "负面": 33}, + ) +``` + +状态同步确保前端始终能获取最新信息。即使某个环节出错,系统也会推送包含错误信息的状态更新,让用户了解发生了什么问题。这种设计避免了前端长时间等待却得不到任何反馈的糟糕体验。 + +## 性能优化实践 + +舆情分析涉及大量的网络请求和计算任务,性能优化对用户体验至关重要。系统在多个方面进行了优化。 + +异步并发是性能提升的关键。虽然搜索查询是顺序执行的,但在深度抓取阶段系统可以并发处理多个页面。通过 asyncio.gather 同时打开多个页面提取内容,显著减少了总耗时。 + +```python +# 并发深度抓取 +tasks = [] +for url in high_relevance_urls[:5]: + tasks.append(extract_detailed_content(url)) + +results = await asyncio.gather(*tasks, return_exceptions=True) +``` + +沙箱复用减少了创建销毁的开销。数据收集过程创建一个沙箱后会持续使用,直到任务结束才销毁。这避免了频繁创建沙箱带来的延迟和资源消耗。 + +早期过滤减少了无效处理。系统在多个阶段进行过滤:搜索结果级别的相关性评估、URL 去重、平台类别动态跳过。这些机制确保系统只对高价值的数据进行深度处理,避免在低质量内容上浪费资源。 + +流式输出本身也是一种性能优化。虽然完整的分析需要几分钟,但用户可以在几秒内就看到初步结果和进展信息。这种即时反馈让用户感知上的等待时间大大缩短。 + +## 总结 + +这个舆情分析系统展示了如何使用 AgentRun SDK 构建复杂的生产级应用。通过代码控制流程、多沙箱管理、智能内容抓取、流式输出、数据质量控制、错误恢复等机制的综合运用,系统实现了稳定、高效、用户体验良好的自动化舆情分析能力。 + +关键经验包括:使用明确的工具序列而不是让 LLM 自由决策,实现完善的错误检测和自动恢复,通过流式输出保持与用户的实时交互,建立多层次的数据质量控制体系。这些实践不仅适用于舆情分析,也可以推广到其他需要 Agent 进行复杂任务处理的场景。 + +在实际部署时,还需要考虑更多工程因素:日志和监控的完善性、成本控制、并发限制、数据存储、用户权限管理等。但核心的架构设计和最佳实践已经在这个案例中得到充分体现,可以作为构建类似系统的参考基础。 \ No newline at end of file From ad1c5078cc1b1b845d268aceee88607e4c6d1344 Mon Sep 17 00:00:00 2001 From: Anycodes Date: Sun, 14 Dec 2025 01:32:47 +0800 Subject: [PATCH 4/9] Enhance tutorial with AgentRun exploration steps Added quick start guide for AgentRun console exploration. --- .../09-best-preactices-opinion-analysis.md | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/09-best-preactices-opinion-analysis.md b/docs/tutorial/09-best-preactices-opinion-analysis.md index 3585b25..e172c13 100644 --- a/docs/tutorial/09-best-preactices-opinion-analysis.md +++ b/docs/tutorial/09-best-preactices-opinion-analysis.md @@ -6,6 +6,8 @@ sidebar_position: 19 本章通过一个完整的舆情分析系统案例,展示 AgentRun SDK 在实际项目中的应用。该系统实现了从数据收集、深度分析到报告生成的完整流程,并提供了流式输出、多沙箱管理、智能探索等高级特性。 +> 目前该代码已经发布到 Serverless Registry,可以通过 Serverless Devs 工具进行下载:`s init opinion_analysis` + ## 系统概述 这个舆情分析系统的核心目标是自动化地监测和分析网络舆情。系统接收用户提供的关键词,自动在多个平台搜索相关信息,使用浏览器沙箱深度抓取内容,通过 AI 进行情感分析和趋势预测,最终生成专业的可视化报告。整个过程通过 Server-Sent Events 实时推送状态更新,让用户可以实时观察分析进展。 @@ -367,4 +369,30 @@ results = await asyncio.gather(*tasks, return_exceptions=True) 关键经验包括:使用明确的工具序列而不是让 LLM 自由决策,实现完善的错误检测和自动恢复,通过流式输出保持与用户的实时交互,建立多层次的数据质量控制体系。这些实践不仅适用于舆情分析,也可以推广到其他需要 Agent 进行复杂任务处理的场景。 -在实际部署时,还需要考虑更多工程因素:日志和监控的完善性、成本控制、并发限制、数据存储、用户权限管理等。但核心的架构设计和最佳实践已经在这个案例中得到充分体现,可以作为构建类似系统的参考基础。 \ No newline at end of file +在实际部署时,还需要考虑更多工程因素:日志和监控的完善性、成本控制、并发限制、数据存储、用户权限管理等。但核心的架构设计和最佳实践已经在这个案例中得到充分体现,可以作为构建类似系统的参考基础。 + +------ + +为了便于大家进行快速体验,可以通过 AgentRun 控制台探索页面进行快速体验 + +- 访问[https://functionai.console.aliyun.com/cn-hangzhou/agent/explore](AgentRun 探索页面): + + + +- 按照要求,填写模型和沙箱信息: + + + +- 进行 Agent 创建: + + + +- 访问创建后的 Agent 进行体验 + + + + + + + + From 81f8eddd22e365804343525f92745e5c764aeff6 Mon Sep 17 00:00:00 2001 From: Anycodes Date: Sun, 14 Dec 2025 01:48:31 +0800 Subject: [PATCH 5/9] add docs --- ... => 09-best-practices-opinion-analysis.md} | 0 .../10-best-prectices-a2a-by-me-a-coffee.md | 948 ++++++++++++++++++ 2 files changed, 948 insertions(+) rename docs/tutorial/{09-best-preactices-opinion-analysis.md => 09-best-practices-opinion-analysis.md} (100%) create mode 100644 docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md diff --git a/docs/tutorial/09-best-preactices-opinion-analysis.md b/docs/tutorial/09-best-practices-opinion-analysis.md similarity index 100% rename from docs/tutorial/09-best-preactices-opinion-analysis.md rename to docs/tutorial/09-best-practices-opinion-analysis.md diff --git a/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md b/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md new file mode 100644 index 0000000..3a6d96a --- /dev/null +++ b/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md @@ -0,0 +1,948 @@ +--- +sidebar_position: 20 +--- + +# 最佳实践:多 Agent 协作系统 + +> 目前该代码已经发布到 Serverless Registry,可以通过 Serverless Devs 工具进行下载:`s init buy-me-a-coffee` + +## 案例概述 + +本案例展示了如何使用 AgentRun 和 Google ADK 构建一个完整的多 Agent 协作系统。该系统模拟了一个智能咖啡订购平台,包含日常助手、咖啡订购和配送服务三个独立的 Agent,它们通过 A2A(Agent-to-Agent)协议进行通信和协作。 + +整个系统的设计遵循微服务架构理念,每个 Agent 负责特定的业务领域,既可以独立部署运行,也可以统一部署在一个进程中。这种架构模式具有良好的可扩展性和维护性,是构建企业级 AI 应用的最佳实践。 + +## 架构设计 + +系统采用分层架构,自下而上分为数据层、服务层、Agent 层和网关层。数据层使用 SQLite 数据库存储咖啡商品和订单信息,每个业务领域维护独立的数据库文件。服务层提供 RESTful API 接口,封装数据库操作并对外提供标准的 HTTP 服务。Agent 层基于 Google ADK 框架实现,每个 Agent 封装特定的业务逻辑,并通过工具函数调用服务层接口。网关层作为系统的统一入口,负责路由用户请求到相应的 Agent,并管理 Agent 间的协作。 + +系统中的三个核心 Agent 各司其职。日常助手 Agent 提供天气查询、时间管理、提醒和日程安排等基础功能,这些功能通过内存数据结构实现,无需持久化存储。咖啡店 Agent 负责咖啡订购业务,包括菜单展示、商品搜索、订单创建和状态查询,所有操作通过 HTTP 请求调用咖啡店后端 API 完成。配送 Agent 专注于配送服务,支持创建配送单、查询配送状态和更新配送进度,同样通过 HTTP 调用配送服务 API。 + +Agent 间的通信采用两种机制。日常助手直接集成在网关层,作为本地子 Agent 运行。咖啡店和配送 Agent 则独立部署,通过 A2A 协议暴露服务,网关层通过 RemoteA2aAgent 与它们通信。这种混合架构既保证了核心功能的响应速度,又提供了业务模块的独立性和可扩展性。 + +## Google ADK 框架深度解析 + +### ADK 核心概念 + +Google ADK(Agent Development Kit)是一个用于构建智能 Agent 的框架。它的核心理念是将 Agent 抽象为可组合的模块,每个 Agent 包含模型、工具、子 Agent 和系统指令四个基本要素。 + +模型是 Agent 的推理引擎,负责理解用户意图和生成响应。在本案例中,所有 Agent 都使用 AgentRun 提供的模型,通过以下方式创建: + +```python +from agentrun.integration.google_adk import model + +MODEL_NAME = "qwen-max" +AGENTRUN_MODEL_NAME = "my-model-service" +DEFAULT_LLM = model(AGENTRUN_MODEL_NAME, model=MODEL_NAME) +``` + +这段代码展示了 AgentRun 与 Google ADK 的集成。`model` 函数接收两个参数:`AGENTRUN_MODEL_NAME` 是在 AgentRun 平台上创建的模型服务或代理名称,`model` 参数指定具体要调用的模型(如 qwen-max、gpt-4 等)。函数返回一个 ADK 兼容的模型对象,可以直接传递给 Agent 的 `model` 参数。 + +工具是 Agent 与外部世界交互的接口。ADK 要求工具必须是 Python 函数,函数签名和文档字符串会被自动解析为工具描述。以咖啡店的菜单查询工具为例: + +```python +def tool_get_menu(category: str = None) -> dict: + """ + 获取希希咖啡店的菜单 + + 调用 API: GET /api/coffee/products + + Args: + category: 商品分类,可选值:经典咖啡、特调饮品、甜点。不指定则返回全部菜单 + + Returns: + 菜单信息,按分类整理 + """ + return tools.get_menu(category) +``` + +这个函数的文档字符串非常详细,说明了函数的用途、对应的 API 接口、参数含义和返回值格式。ADK 的模型会读取这些信息,理解工具的功能并在合适的时候调用。参数类型注解(`category: str`)和默认值(`= None`)也会被解析,帮助模型理解参数是否必填。 + +### Agent 的创建和配置 + +创建一个 Agent 需要指定名称、模型、描述、系统指令和工具列表。咖啡店 Agent 的完整定义如下: + +```python +coffee_agent = Agent( + name="coffee_agent", + model=DEFAULT_LLM, + description="希希咖啡店智能服务,可以帮助点咖啡和查询订单", + instruction=""" +你是"希希咖啡"智能服务(Root Agent),管理两个子助手:下单助手(order_agent)与查询助手(query_agent)。请遵守下列规则并用中文与顾客交互,语气热情且简洁。 + +重要行为规则(必须遵守): +- 每次用户询问"订单状态/我的咖啡做好了么/订单进度"等相关问题时,必须发起真实的后端查询调用: + - 若用户提供了订单号,**必须**调用 `tool_query_order(order_id)` 并使用该工具的返回结果构建回复;不可仅凭上下文或记忆直接回答。 + - 若用户未提供订单号,**必须**调用 `tool_get_recent_orders(limit=5)`,把最近订单列表返回给用户并在必要时提示用户选择或提供订单号;不可跳过该步骤。 +- 禁止在未调用上述工具的情况下就断定或推测订单状态。每一次用户的"我的咖啡做好了么"都要触发后端查询(不使用缓存结果来回应用户)。 + +总体规则(补充): +- 用户想点单、看菜单或需要推荐 → 转交给 `order_agent` 处理或直接调用下单相关工具。 +- 对于门店基础信息(地址、营业时间、配送方式等)可直接回答:地址:人民路88号;营业时间:8:00-22:00;支持堂食/自取/外卖。 +- 尽量减少轮次,不要无谓追问;但在缺少必要字段(例如订单号)时,应直接调用 `tool_get_recent_orders` 或询问最少的必要信息以完成工具调用。 +""", + tools=[ + tool_get_menu, + tool_search_product, + tool_create_order, + tool_query_order, + tool_get_recent_orders, + tool_update_order_status, + ], +) +``` + +这段配置展示了 Agent 定义的关键要素。`name` 是 Agent 的唯一标识符,在 A2A 通信中用于识别 Agent。`description` 是对 Agent 能力的简短描述,会出现在 Agent Card 中供其他 Agent 理解。 + +`instruction` 是 Agent 的系统指令,这是整个配置中最关键的部分。系统指令采用自然语言编写,详细说明了 Agent 的行为规则。注意指令中使用了大量的强调词汇(如"必须"、"禁止"),这是因为大语言模型有时会基于上下文推测而不是真实调用工具。通过明确的规则约束,可以确保 Agent 始终按照预期行为。 + +指令还包含了具体的业务规则,例如门店地址和营业时间。这些信息可以直接写入指令中,Agent 就能直接回答而不需要额外的工具。对于变化不频繁的静态信息,这种方式比创建专门的工具更高效。 + +### 工具函数的实现细节 + +工具函数的实现需要处理同步和异步的兼容性问题。ADK 要求工具必须是同步函数,但实际的业务逻辑(如数据库访问、HTTP 请求)往往是异步的。本案例采用了线程池隔离的方案: + +```python +import requests +from concurrent.futures import ThreadPoolExecutor + +_http_executor = ThreadPoolExecutor(max_workers=10, thread_name_prefix="http_worker_") + +def _make_request(method: str, url: str, **kwargs) -> dict: + """在独立线程中执行 HTTP 请求""" + try: + response = requests.request(method, url, timeout=30.0, **kwargs) + response.raise_for_status() + result = response.json() + return {"success": True, "data": result} + except requests.RequestException as e: + return {"success": False, "error": str(e)} + +class APIClient: + def _execute(self, method: str, path: str, **kwargs) -> dict: + """执行 HTTP 请求(通过线程池)""" + url = self._build_url(path) + future = _http_executor.submit(_make_request, method, url, **kwargs) + result = future.result(timeout=35.0) + if not result["success"]: + raise Exception(result["error"]) + return result["data"] +``` + +这段代码展示了如何在同步函数中安全地执行异步操作。`_make_request` 在独立的工作线程中执行实际的 HTTP 请求,`_execute` 方法通过线程池提交任务并等待结果。这种设计避免了在 Agent 的主事件循环中执行阻塞操作,确保系统的响应性。 + +工具函数还需要处理数据验证和错误恢复。以创建订单工具为例: + +```python +def _normalize_order_items(items: list) -> List[Dict[str, Any]]: + """清洗并补全订单项数据,避免后端校验失败""" + if not isinstance(items, list) or len(items) == 0: + raise ValueError("订单至少需要包含一件商品。") + + catalog = _get_product_catalog() + normalized_items: List[Dict[str, Any]] = [] + + for raw_item in items: + if isinstance(raw_item, dict): + item = raw_item.copy() + name = item.get("name") or item.get("product_name") or item.get("title") + product_id = item.get("product_id") or item.get("productId") or item.get("id") + quantity = item.get("quantity") or item.get("qty") or item.get("count") or 1 + price = item.get("price") or item.get("amount") + elif isinstance(raw_item, str): + name, quantity = _extract_name_and_quantity(raw_item) + product_id = None + price = None + else: + raise ValueError("无法解析的商品格式,请重新确认点单信息。") + + product_id = _safe_int(product_id) + quantity = max(1, _safe_int(quantity, default=1)) + price_value = _parse_price(price) + + matched_product = None + if product_id is not None: + matched_product = next((p for p in catalog if int(p["id"]) == product_id), None) + + if not matched_product and name: + matched_product = _find_product_by_name(name, catalog) + if matched_product: + product_id = int(matched_product["id"]) + name = matched_product["name"] + + if not matched_product: + raise ValueError(f"未找到商品"{name or product_id}",请重新选择菜单中的商品。") + + if price_value is None: + price_value = matched_product["price"] + + normalized_items.append({ + "product_id": int(product_id), + "name": name or matched_product["name"], + "price": float(price_value), + "quantity": int(quantity), + }) + + return normalized_items +``` + +这个函数处理了多种可能的输入格式。用户可能说"给我一杯拿铁",也可能说"拿铁咖啡x2",还可能直接提供商品 ID。函数会尝试解析这些不同的表达方式,并与产品目录匹配。如果匹配成功,会补全缺失的字段(如价格);如果匹配失败,会抛出清晰的错误信息告诉用户问题所在。 + +## A2A 协议实现详解 + +### A2A 协议原理 + +A2A(Agent-to-Agent)协议是 Google ADK 定义的 Agent 间通信标准。它基于 JSON-RPC 2.0 规范,定义了 Agent 如何发现彼此的能力、如何发起任务请求以及如何交换中间状态。 + +A2A 协议的核心是 Agent Card,这是一个 JSON 文档,描述了 Agent 的基本信息: + +```json +{ + "name": "coffee_agent", + "description": "希希咖啡店智能服务,可以帮助点咖啡和查询订单", + "url": "http://localhost:8003", + "capabilities": { + "streaming": true, + "tools": [ + { + "name": "tool_get_menu", + "description": "获取希希咖啡店的菜单", + "parameters": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "商品分类,可选值:经典咖啡、特调饮品、甜点" + } + } + } + } + ] + } +} +``` + +Agent Card 包含了其他 Agent 需要了解的所有信息。`name` 和 `description` 帮助调用者理解 Agent 的用途,`url` 指向 RPC 接口地址,`capabilities` 列出了 Agent 支持的功能和工具。 + +### 构建 A2A 服务端 + +将一个 ADK Agent 暴露为 A2A 服务需要几个步骤。首先创建必要的服务组件: + +```python +from a2a.server.apps import A2AStarletteApplication +from a2a.server.request_handlers import DefaultRequestHandler +from a2a.server.tasks import InMemoryTaskStore +from google.adk.runners import Runner +from google.adk.sessions import InMemorySessionService +from google.adk.a2a.utils.agent_card_builder import AgentCardBuilder +from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor + +async def build_a2a_app(agent: BaseAgent, base_url: str) -> A2AStarletteApplication: + """构建 A2A 应用""" + + async def create_runner() -> Runner: + return Runner( + app_name=agent.name, + agent=agent, + artifact_service=InMemoryArtifactService(), + session_service=InMemorySessionService(), + memory_service=InMemoryMemoryService(), + credential_service=InMemoryCredentialService(), + ) + + task_store = InMemoryTaskStore() + agent_executor = A2aAgentExecutor(runner=create_runner) + request_handler = DefaultRequestHandler( + agent_executor=agent_executor, + task_store=task_store, + ) + + rpc_url = f"{base_url}/" + card_builder = AgentCardBuilder(agent=agent, rpc_url=rpc_url) + agent_card = await card_builder.build() + + a2a_app = A2AStarletteApplication( + agent_card=agent_card, + http_handler=request_handler, + ) + + return a2a_app +``` + +这段代码展示了 A2A 服务的构建过程。`create_runner` 函数定义了如何创建 Agent 运行器,运行器负责执行 Agent 的推理和工具调用。这里使用的都是内存实现(InMemory*),适合开发和测试。生产环境可以替换为持久化实现,例如使用 Redis 存储会话状态。 + +`task_store` 管理 A2A 任务的状态。当一个 Agent 调用另一个 Agent 时,会创建一个任务,任务的执行过程和结果都存储在 task_store 中。`agent_executor` 负责执行具体的 Agent 逻辑,`request_handler` 处理 HTTP 请求并调用 executor。 + +`AgentCardBuilder` 根据 Agent 的定义自动生成 Agent Card。它会读取 Agent 的名称、描述和工具列表,并转换为标准的 JSON 格式。生成的 Agent Card 会作为 A2AStarletteApplication 的构造参数。 + +A2A 应用需要注册到 FastAPI 路由中: + +```python +async def lifespan(app): + a2a_app = await build_a2a_app(coffee_agent, COFFEE_A2A_URL) + + a2a_app.add_routes_to_app( + app, + agent_card_url="/.well-known/agent-card.json", + rpc_url="/", + ) + +app = build_fastapi_app(COFFEE_A2A_PORT, name="咖啡店 A2A 服务", lifespan=lifespan) +``` + +`add_routes_to_app` 方法会在 FastAPI 应用上注册两个路由。`/.well-known/agent-card.json` 返回 Agent Card,这是一个标准路径,其他 Agent 可以通过这个路径发现 Agent 的能力。`/` 路径处理 JSON-RPC 请求,执行实际的 Agent 任务。 + +### 创建 A2A 客户端 + +调用远程 A2A Agent 非常简单,只需要创建 RemoteA2aAgent 对象: + +```python +from google.adk.agents.remote_a2a_agent import RemoteA2aAgent + +coffee_a2a_agent = RemoteA2aAgent( + name="remote_coffee_agent", + agent_card="http://localhost:8003/.well-known/agent-card.json", + description="远程咖啡店服务 Agent", +) +``` + +RemoteA2aAgent 在初始化时会自动获取 Agent Card,解析其中的工具定义和 RPC 地址。之后就可以像使用本地 Agent 一样使用它: + +```python +root_agent = Agent( + name="root_agent", + model=DEFAULT_LLM, + description="智能助手系统,整合日常助手、咖啡服务和配送服务", + instruction=system_instruction, + sub_agents=[assistant_agent, coffee_a2a_agent, delivery_a2a_agent], +) +``` + +Root Agent 将远程 Agent 作为子 Agent 使用。当用户的请求需要咖啡服务时,Root Agent 会将请求委托给 coffee_a2a_agent。框架会自动构建 JSON-RPC 请求,发送到远程 Agent 的 RPC 端点,等待响应并解析结果。整个过程对 Root Agent 是透明的,它不需要知道子 Agent 是本地还是远程。 + +### A2A 通信流程 + +完整的 A2A 通信包含多个步骤。当用户向 Root Agent 发送消息"我要一杯拿铁"时,流程如下: + +1. Root Agent 收到消息,分析用户意图,判断需要调用咖啡服务。 + +2. Root Agent 决定将任务委托给 coffee_a2a_agent,构建子任务描述。 + +3. RemoteA2aAgent 将任务转换为 JSON-RPC 请求: +```json +{ + "jsonrpc": "2.0", + "method": "execute_task", + "params": { + "task_id": "task-12345", + "message": "我要一杯拿铁", + "context": {} + }, + "id": "req-67890" +} +``` + +4. 请求发送到咖啡 Agent 的 RPC 端点(`http://localhost:8003/`)。 + +5. 咖啡 Agent 的 request_handler 接收请求,创建任务并调用 agent_executor。 + +6. agent_executor 创建 Runner,Runner 使用模型分析消息并决定调用工具。 + +7. 咖啡 Agent 分析后认为需要先展示菜单,调用 tool_get_menu 工具。 + +8. tool_get_menu 通过 HTTP 请求访问咖啡店后端 API,获取菜单数据。 + +9. 咖啡 Agent 将菜单展示给用户,并询问具体需求(尺寸、温度等)。 + +10. 中间状态通过 Server-Sent Events 流式返回给 Root Agent。 + +11. Root Agent 将咖啡 Agent 的回复转发给用户。 + +12. 用户补充信息后,整个流程再次执行,直到订单创建完成。 + +这个流程展示了 A2A 协议的强大之处。Root Agent 不需要了解咖啡业务的细节,只需要知道有一个咖啡服务可以处理相关请求。咖啡 Agent 也不需要知道调用者是谁,它只专注于处理咖啡订购逻辑。这种松耦合的设计使得系统易于扩展和维护。 + +## 核心模块深度剖析 + +### 数据库层实现 + +数据库层使用 aiosqlite 实现异步访问。咖啡店数据库的初始化逻辑展示了如何设计数据结构和初始化数据: + +```python +class CoffeeDatabase: + async def init_db(self): + """初始化数据库,创建表和初始数据""" + async with aiosqlite.connect(self.db_path) as db: + await db.execute(""" + CREATE TABLE IF NOT EXISTS products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + price REAL NOT NULL, + description TEXT, + category TEXT NOT NULL, + image_url TEXT, + available INTEGER DEFAULT 1 + ) + """) + + await db.execute(""" + CREATE TABLE IF NOT EXISTS orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + items TEXT NOT NULL, + total REAL NOT NULL, + status TEXT DEFAULT 'pending', + customer_name TEXT, + customer_phone TEXT, + customer_address TEXT, + notes TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + """) + + await db.commit() + + cursor = await db.execute("SELECT COUNT(*) FROM products") + count = await cursor.fetchone() + if count[0] == 0: + await self._insert_initial_products(db) +``` + +表设计遵循了 SQLite 的最佳实践。商品表使用自增主键,价格字段使用 REAL 类型存储浮点数,available 字段用整数表示布尔值(0 表示下架,1 表示在售)。订单表的 items 字段存储 JSON 字符串,这是一种反范式化设计,虽然不符合关系型数据库的标准范式,但对于订单这种历史记录来说是合理的,因为我们需要保存下单时的完整快照,即使商品信息后来发生变化也不影响历史订单。 + +订单创建方法展示了如何处理事务和返回结果: + +```python +async def create_order( + self, + items: list[dict], + customer_name: str, + customer_phone: str, + customer_address: Optional[str] = None, + notes: Optional[str] = None, +) -> dict: + """创建订单""" + total = sum(item["price"] * item["quantity"] for item in items) + now = datetime.now().isoformat() + + async with aiosqlite.connect(self.db_path) as db: + cursor = await db.execute( + """ + INSERT INTO orders (items, total, status, customer_name, customer_phone, + customer_address, notes, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + json.dumps(items, ensure_ascii=False), + total, + "pending", + customer_name, + customer_phone, + customer_address, + notes, + now, + now, + ), + ) + await db.commit() + order_id = cursor.lastrowid + + return { + "id": order_id, + "items": items, + "total": total, + "status": "pending", + "customer_name": customer_name, + "customer_phone": customer_phone, + "customer_address": customer_address, + "notes": notes, + "created_at": now, + "updated_at": now, + } +``` + +这个方法在数据库插入完成后,立即返回包含所有字段的字典。这样做的好处是调用者不需要再查询一次数据库获取完整信息。方法使用 `cursor.lastrowid` 获取自动生成的订单 ID,这是 SQLite 提供的标准方式。 + +### FastAPI 服务层 + +服务层使用 FastAPI 实现 RESTful API。路由定义和请求处理的代码结构清晰: + +```python +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +router = APIRouter(tags=["希希咖啡"]) + +class OrderItem(BaseModel): + """订单项""" + product_id: int + name: str + price: float + quantity: int + +class CreateOrderRequest(BaseModel): + """创建订单请求""" + items: list[OrderItem] + customer_name: str + customer_phone: str + customer_address: Optional[str] = None + notes: Optional[str] = None + +@router.post("/orders") +async def create_order(request: CreateOrderRequest): + """创建订单""" + items = [item.model_dump() for item in request.items] + order = await coffee_db.create_order( + items=items, + customer_name=request.customer_name, + customer_phone=request.customer_phone, + customer_address=request.customer_address, + notes=request.notes, + ) + + return { + "success": True, + "data": order, + "message": f"订单创建成功,订单号:{order['id']}", + } +``` + +使用 Pydantic 模型定义请求和响应格式是 FastAPI 的最佳实践。Pydantic 会自动验证请求数据的类型和格式,如果验证失败会返回详细的错误信息。OrderItem 模型确保每个订单项都包含必需的字段,且字段类型正确。 + +API 统一使用包含 success、data 和 message 三个字段的响应格式。success 标志操作是否成功,data 包含实际的业务数据,message 提供人类可读的描述。这种统一格式使得前端或 Agent 可以用相同的逻辑处理所有 API 响应。 + +应用的生命周期管理通过 lifespan 机制实现: + +```python +async def lifespan(app): + from .database import coffee_db + await coffee_db.init_db() + yield + +app = build_fastapi_app( + COFFEE_API_PORT, + name="希希咖啡店后端服务", + description="希希咖啡店的 REST API 服务,提供商品和订单管理", + lifespan=lifespan, +) + +app.include_router(router, prefix="/api/coffee") +``` + +lifespan 函数在应用启动时初始化数据库,在应用关闭时可以执行清理操作(虽然这里没有)。`yield` 语句将函数分为启动阶段和关闭阶段,yield 之前的代码在启动时执行,yield 之后的代码在关闭时执行。 + +### HTTP 客户端层 + +HTTP 客户端封装了与后端 API 的通信逻辑,处理了同步异步兼容性问题: + +```python +import requests +from concurrent.futures import ThreadPoolExecutor + +_http_executor = ThreadPoolExecutor(max_workers=10, thread_name_prefix="http_worker_") + +def _make_request(method: str, url: str, **kwargs) -> dict: + """在独立线程中执行 HTTP 请求""" + try: + logger.info(f"🌐 [HTTP Worker] {method} {url}") + response = requests.request(method, url, timeout=30.0, **kwargs) + response.raise_for_status() + result = response.json() + logger.info(f"🌐 [HTTP Worker] {method} {url} -> {response.status_code}") + return {"success": True, "data": result} + except requests.Timeout: + logger.error(f"🌐 [HTTP Worker] {method} {url} 超时") + return {"success": False, "error": f"HTTP {method} {url} 超时"} + except requests.RequestException as e: + logger.error(f"🌐 [HTTP Worker] {method} {url} 失败: {e}") + return {"success": False, "error": str(e)} +``` + +使用独立线程池的原因是 Google ADK 的工具必须是同步函数,但我们希望使用 async/await 编写优雅的异步代码。线程池提供了一个隔离层,在工作线程中执行阻塞的 HTTP 请求,不会影响主事件循环。 + +APIClient 类提供了高层封装: + +```python +class APIClient: + def __init__(self, base_url: str = "http://localhost:8000"): + self.base_url = base_url + + def _build_url(self, path: str) -> str: + """构建完整 URL""" + return f"{self.base_url}{path}" + + def _execute(self, method: str, path: str, **kwargs) -> dict: + """执行 HTTP 请求(通过线程池)""" + url = self._build_url(path) + future = _http_executor.submit(_make_request, method, url, **kwargs) + result = future.result(timeout=35.0) + if not result["success"]: + raise Exception(result["error"]) + return result["data"] + + def get(self, path: str, params: Optional[dict] = None) -> dict: + """发送 GET 请求""" + return self._execute("GET", path, params=params) + + def post(self, path: str, json: Optional[dict] = None) -> dict: + """发送 POST 请求""" + return self._execute("POST", path, json=json) +``` + +客户端使用了工厂模式,为不同的服务创建独立的客户端实例: + +```python +_coffee_api_client: Optional[APIClient] = None + +def get_coffee_api_client() -> APIClient: + """获取咖啡店 API 客户端""" + global _coffee_api_client + if _coffee_api_client is None: + _coffee_api_client = APIClient(base_url=COFFEE_API_URL) + return _coffee_api_client +``` + +单例模式确保了每个服务只创建一个客户端实例,避免了重复创建线程池的开销。 + +### 网关层实现 + +网关层是系统的统一入口,负责路由和 API 转发。路由转发的实现使用了 FastAPI 的通配符路由: + +```python +@app.api_route( + "/api/coffee/{path:path}", + methods=["GET", "POST", "PUT", "DELETE", "PATCH"] +) +async def proxy_coffee_api(request: Request, path: str): + """转发咖啡店 API 请求""" + return await proxy_request(request, COFFEE_API_URL, f"/api/coffee/{path}") + +async def proxy_request( + request: Request, + target_base_url: str, + path: str, +) -> Response: + """转发请求到目标服务""" + client = await get_http_client() + target_url = f"{target_base_url}{path}" + if request.url.query: + target_url = f"{target_url}?{request.url.query}" + + body = await request.body() + headers = dict(request.headers) + headers.pop("host", None) + + try: + response = await client.request( + method=request.method, + url=target_url, + headers=headers, + content=body, + ) + + return Response( + content=response.content, + status_code=response.status_code, + headers=dict(response.headers), + media_type=response.headers.get("content-type"), + ) + except httpx.RequestError as e: + return JSONResponse( + status_code=502, + content={ + "success": False, + "error": f"无法连接到后端服务: {str(e)}", + }, + ) +``` + +这段代码实现了一个透明的 HTTP 代理。网关接收前端的请求,保持原始的 HTTP 方法、请求头和请求体,转发到后端服务,然后将后端的响应原样返回给前端。唯一需要移除的是 host 请求头,因为转发后的目标地址不同。 + +Root Agent 的创建展示了如何组合本地和远程 Agent: + +```python +def create_root_agent(a2a_urls: List[str]): + """创建根 Agent(A2A 模式)""" + a2a_agents = [] + for url in a2a_urls: + if url.strip() == "": + continue + a2a_agents.append( + RemoteA2aAgent( + name=f"remote_agent_{len(a2a_agents)+1}", + agent_card=url, + description="远程 A2A 服务 Agent", + ) + ) + + agent = Agent( + name="root_agent", + model=DEFAULT_LLM, + description="智能助手系统,整合日常助手、咖啡服务和配送服务", + instruction=system_instruction, + sub_agents=[assistant_agent, *a2a_agents], + ) + + return agent +``` + +通过遍历配置的 A2A URL 列表,动态创建多个 RemoteA2aAgent 并添加到子 Agent 列表中。这种设计使得系统可以方便地添加或移除远程服务,只需修改配置即可。 + +聊天接口的实现展示了如何使用 ADK 的 Runner 执行 Agent: + +```python +@app.post("/api/chat/stream") +async def chat_stream(request: ChatRequest): + """流式聊天(SSE)""" + session_id = request.session_id or str(uuid.uuid4()) + + runner = Runner( + agent=get_root_agent(), + app_name=APP_NAME, + session_service=session_service, + ) + + session = await session_service.get_session( + app_name=APP_NAME, user_id=USER_ID, session_id=session_id + ) + if session is None: + session = await session_service.create_session( + app_name=APP_NAME, user_id=USER_ID, session_id=session_id + ) + + message = types.Content( + role="user", + parts=[types.Part(text=request.message)], + ) + + run_config = RunConfig(streaming_mode=StreamingMode.SSE) + + async def generate(): + """生成 SSE 事件""" + yield {"event": "session", "data": json.dumps({"session_id": session_id})} + + try: + async for event in runner.run_async( + user_id=USER_ID, + session_id=session_id, + new_message=message, + run_config=run_config, + ): + if hasattr(event, "content") and event.content: + for part in event.content.parts: + if hasattr(part, "text") and part.text: + yield { + "event": "message", + "data": json.dumps({ + "type": "text", + "content": part.text, + }) + } + except Exception as e: + yield {"event": "error", "data": json.dumps({"error": str(e)})} + + yield {"event": "done", "data": "{}"} + + return EventSourceResponse(generate()) +``` + +这段代码展示了 ADK 的会话管理和流式输出机制。每个用户会话通过 session_id 标识,会话状态存储在 session_service 中。Runner 的 run_async 方法异步执行 Agent,返回一个事件流。每个事件可能包含文本内容、工具调用或其他类型的数据。代码遍历事件流,提取文本部分并转换为 Server-Sent Events 格式发送给客户端。 + +## 部署和运行 + +### 环境配置详解 + +系统的配置管理使用了环境变量和合理的默认值相结合的方式: + +```python +def get_env_with_default(default_value: str, *env_names: str) -> str: + """按优先级获取环境变量值""" + for name in env_names: + value = os.getenv(name) + if value is not None: + return value + return default_value + +GATEWAY_PORT = int( + get_env_with_default( + "8000", "GATEWAY_PORT", "FC_SERVER_PORT", "FC_CUSTOM_LISTEN_PORT" + ) +) + +COFFEE_API_URL = os.getenv("COFFEE_API_URL", f"http://localhost:{COFFEE_API_PORT}") +``` + +这种配置方式支持多个环境变量名,优先级从前到后。例如 GATEWAY_PORT 会依次尝试读取 GATEWAY_PORT、FC_SERVER_PORT、FC_CUSTOM_LISTEN_PORT 三个环境变量,如果都不存在则使用默认值 8000。这样既兼容了 AgentRun 的函数计算环境(使用 FC_ 前缀的变量),也支持自定义配置。 + +AgentRun 集成通过 SDK 的 integration 模块实现: + +```python +from agentrun.integration.google_adk import model, toolset + +MODEL_NAME = get_env_with_default("", "MODEL_NAME") +AGENTRUN_MODEL_NAME = get_env_with_default("", "AGENTRUN_MODEL_NAME") +DEFAULT_LLM = model(AGENTRUN_MODEL_NAME, model=MODEL_NAME) + +COFFEE_TOOLSET_NAME = get_env_with_default("", "COFFEE_TOOLSET_NAME") +COFFEE_TOOLSET = toolset(COFFEE_TOOLSET_NAME) if COFFEE_TOOLSET_NAME else [] +``` + +如果配置了 COFFEE_TOOLSET_NAME,系统会从 AgentRun 加载远程工具集;否则使用本地定义的工具函数。这种设计使得工具的实现可以灵活切换,例如在开发时使用本地工具,在生产环境使用托管在 AgentRun 上的工具服务。 + +### 统一部署实践 + +统一部署模式下,所有服务在同一进程中启动。项目提供了统一的启动脚本: + +```bash +python -m gateway.main +``` + +这个命令会启动网关服务,网关的 lifespan 钩子会初始化所有数据库和 Agent: + +```python +async def lifespan(app: FastAPI): + """应用生命周期""" + global _root_agent + + # 初始化数据库 + from coffee.database import coffee_db + from delivery.database import delivery_db + await coffee_db.init_db() + await delivery_db.init_db() + + # 创建 Root Agent + _root_agent = create_root_agent(A2A_URLS) +``` + +统一部署的优势是简单快速,适合开发测试。缺点是所有服务共享同一进程的资源,某个服务的问题可能影响整个系统。 + +### 分布式部署实践 + +分布式部署需要依次启动各个服务: + +```bash +# 终端 1:启动咖啡店后端 API +python -m coffee.main + +# 终端 2:启动配送后端 API +python -m delivery.main + +# 终端 3:启动咖啡店 A2A 服务 +python -m coffee.a2a + +# 终端 4:启动配送 A2A 服务 +python -m delivery.a2a + +# 终端 5:启动网关 +export COFFEE_A2A_URL="http://localhost:8003/.well-known/agent-card.json" +export DELIVERY_A2A_URL="http://localhost:8004/.well-known/agent-card.json" +python -m gateway.main +``` + +每个服务监听不同的端口,通过环境变量配置服务间的通信地址。这种部署方式的优势是每个服务独立运行,可以单独扩展和更新。例如咖啡订单量大时,可以启动多个咖啡 API 实例并配置负载均衡。 + +### Docker 容器化部署 + +项目可以很容易地容器化。每个服务的 Dockerfile 结构类似: + +```dockerfile +FROM python:3.10-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +ENV PYTHONPATH=/app +CMD ["python", "-m", "coffee.main"] +``` + +使用 Docker Compose 可以方便地管理多个容器: + +```yaml +version: '3.8' + +services: + coffee-api: + build: . + command: python -m coffee.main + ports: + - "8001:8001" + environment: + - COFFEE_API_PORT=8001 + + coffee-a2a: + build: . + command: python -m coffee.a2a + ports: + - "8003:8003" + environment: + - COFFEE_A2A_PORT=8003 + - COFFEE_API_URL=http://coffee-api:8001 + + gateway: + build: . + command: python -m gateway.main + ports: + - "8000:8000" + environment: + - GATEWAY_PORT=8000 + - COFFEE_A2A_URL=http://coffee-a2a:8003/.well-known/agent-card.json + depends_on: + - coffee-api + - coffee-a2a +``` + +容器化部署的优势是环境一致性和易于扩展。所有服务使用相同的基础镜像,避免了环境差异导致的问题。 + +## 最佳实践总结 + +### 系统指令的编写技巧 + +系统指令是 Agent 行为的核心控制手段,编写时应该遵循以下原则。首先要明确 Agent 的职责边界,清楚地说明什么应该做、什么不应该做。例如咖啡店 Agent 的指令明确规定只处理咖啡订购相关的请求,其他问题应该拒绝或转发。 + +其次要详细描述工作流程。不要假设模型会自动理解业务逻辑,而应该像编写操作手册一样,一步步说明应该如何处理请求。例如下单流程应该规定:展示菜单 -> 确认商品和数量 -> 询问顾客信息 -> 创建订单,每一步都要明确。 + +使用强调词汇(如"必须"、"禁止"、"始终")来约束关键行为。大语言模型有时会基于概率而不是规则行事,通过强调可以提高模型遵守规则的可能性。但不要过度使用强调词,否则会让指令显得啰嗦。 + +包含具体的示例可以帮助模型理解预期行为。例如在指令中写上"示例回复:订单123的状态是...",让模型知道应该使用什么样的格式和语气回复。 + +### 工具设计的最佳实践 + +工具函数应该保持单一职责,每个工具只做一件事。不要创建一个"万能工具"来处理所有操作,而应该将功能拆分为多个小工具。这样既便于模型理解和选择,也便于测试和维护。 + +工具的文档字符串应该包含完整的信息:功能描述、参数说明(包括类型、是否必填、可选值范围)、返回值格式、可能的错误情况。文档字符串越详细,模型使用工具的成功率越高。 + +工具的返回值应该使用结构化格式,建议统一使用字典并包含 success、data、message 三个字段。这种统一格式使得 Agent 可以用相同的逻辑处理所有工具的返回值。 + +错误处理要做到友好和可操作。当工具执行失败时,不要只返回技术性的错误信息(如"Connection refused"),而应该解释问题原因并给出建议(如"无法连接到后端服务,请稍后重试")。 + +### A2A 集成的注意事项 + +设计 Agent 时要考虑它是否需要独立部署。如果一个 Agent 可能被多个系统使用,或者需要独立扩展,就应该支持 A2A 协议。如果只是内部使用的辅助 Agent,作为本地子 Agent 集成更简单。 + +Agent Card 的描述要准确清晰,这是其他 Agent 理解你的 Agent 能力的唯一途径。描述应该说明 Agent 能做什么、不能做什么、适用于什么场景。避免使用模糊的描述,例如"智能助手"就太宽泛了。 + +A2A 通信涉及网络请求,会有延迟和失败的可能。要设置合理的超时时间,并实现重试机制。同时要处理网络错误,当远程 Agent 不可用时,应该给出友好的错误提示而不是让整个系统崩溃。 + +### 性能优化建议 + +数据库查询应该添加必要的索引。例如订单表的 order_id、status 字段经常用于查询,应该创建索引。虽然 SQLite 的性能已经很好,但随着数据量增长,没有索引的查询会明显变慢。 + +HTTP 请求是系统的性能瓶颈之一。可以考虑实现请求缓存,对于不经常变化的数据(如菜单),可以缓存一段时间避免重复请求。同时可以使用连接池复用 HTTP 连接,减少建立连接的开销。 + +Agent 的响应时间很大程度上取决于模型的推理速度和工具调用次数。优化系统指令可以减少不必要的工具调用,例如将常见的固定信息(如营业时间、联系方式)直接写在指令中,Agent 就不需要调用工具获取这些信息。 + +流式输出可以显著改善用户体验。虽然总的处理时间没有减少,但用户可以更早看到响应,感觉系统更快更流畅。建议所有面向最终用户的接口都支持流式输出。 + +### 监控和运维要点 + +每个服务都应该实现健康检查端点。健康检查不仅要返回 HTTP 200,还应该验证服务的关键依赖(如数据库连接)是否正常。监控系统应该定期探测健康检查端点,发现问题及时告警。 + +日志记录要做到适度和有用。记录太少会导致问题难以排查,记录太多会产生大量噪音且影响性能。建议使用不同的日志级别:DEBUG 用于开发调试,INFO 记录关键操作,WARNING 记录异常但可恢复的情况,ERROR 记录需要人工介入的问题。 + +使用结构化日志格式(如 JSON)便于日志分析。每条日志应该包含时间戳、日志级别、服务名称、请求 ID、具体消息等字段。请求 ID 可以用于追踪一个请求在多个服务间的完整流程。 + +性能监控应该关注几个关键指标:响应时间、错误率、请求量、资源使用率。建立这些指标的基准值,当指标异常时及时告警。例如如果平均响应时间突然增加 50%,可能表示系统出现了性能问题。 \ No newline at end of file From b6da041b14d6743c41d63f6005b6b49117c589e5 Mon Sep 17 00:00:00 2001 From: Anycodes Date: Sun, 14 Dec 2025 01:56:56 +0800 Subject: [PATCH 6/9] Revise tutorial title and enhance quick start section Updated the title and added a section for quick experience with AgentRun. --- .../10-best-prectices-a2a-by-me-a-coffee.md | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md b/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md index 3a6d96a..37e0877 100644 --- a/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md +++ b/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md @@ -2,10 +2,14 @@ sidebar_position: 20 --- -# 最佳实践:多 Agent 协作系统 +# 最佳实践案例:通过 A2A 构建多 Agent 协作系统 + + + > 目前该代码已经发布到 Serverless Registry,可以通过 Serverless Devs 工具进行下载:`s init buy-me-a-coffee` + ## 案例概述 本案例展示了如何使用 AgentRun 和 Google ADK 构建一个完整的多 Agent 协作系统。该系统模拟了一个智能咖啡订购平台,包含日常助手、咖啡订购和配送服务三个独立的 Agent,它们通过 A2A(Agent-to-Agent)协议进行通信和协作。 @@ -20,6 +24,9 @@ sidebar_position: 20 Agent 间的通信采用两种机制。日常助手直接集成在网关层,作为本地子 Agent 运行。咖啡店和配送 Agent 则独立部署,通过 A2A 协议暴露服务,网关层通过 RemoteA2aAgent 与它们通信。这种混合架构既保证了核心功能的响应速度,又提供了业务模块的独立性和可扩展性。 +image + + ## Google ADK 框架深度解析 ### ADK 核心概念 @@ -945,4 +952,37 @@ Agent 的响应时间很大程度上取决于模型的推理速度和工具调 使用结构化日志格式(如 JSON)便于日志分析。每条日志应该包含时间戳、日志级别、服务名称、请求 ID、具体消息等字段。请求 ID 可以用于追踪一个请求在多个服务间的完整流程。 -性能监控应该关注几个关键指标:响应时间、错误率、请求量、资源使用率。建立这些指标的基准值,当指标异常时及时告警。例如如果平均响应时间突然增加 50%,可能表示系统出现了性能问题。 \ No newline at end of file +性能监控应该关注几个关键指标:响应时间、错误率、请求量、资源使用率。建立这些指标的基准值,当指标异常时及时告警。例如如果平均响应时间突然增加 50%,可能表示系统出现了性能问题。 + + +------ + +为了便于大家进行快速体验,可以通过 AgentRun 控制台探索页面进行快速体验 + +- 访问[https://functionai.console.aliyun.com/cn-hangzhou/agent/explore](AgentRun 探索页面): + + + +- 按照要求,填写模型信息: + + + + +- 进行 Agent 创建: + + + +- 访问创建后的 Agent 进行体验 + + + + + + + + + + + + + From ee0798e2484d09a6e6848b25624101a42ac09dec Mon Sep 17 00:00:00 2001 From: Anycodes Date: Tue, 16 Dec 2025 10:26:16 +0800 Subject: [PATCH 7/9] Update docs/tutorial/09-best-practices-opinion-analysis.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/tutorial/09-best-practices-opinion-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/09-best-practices-opinion-analysis.md b/docs/tutorial/09-best-practices-opinion-analysis.md index e172c13..e809f57 100644 --- a/docs/tutorial/09-best-practices-opinion-analysis.md +++ b/docs/tutorial/09-best-practices-opinion-analysis.md @@ -375,7 +375,7 @@ results = await asyncio.gather(*tasks, return_exceptions=True) 为了便于大家进行快速体验,可以通过 AgentRun 控制台探索页面进行快速体验 -- 访问[https://functionai.console.aliyun.com/cn-hangzhou/agent/explore](AgentRun 探索页面): +- 访问[AgentRun 探索页面](https://functionai.console.aliyun.com/cn-hangzhou/agent/explore): From b6f5573243d3ba31e643102ef646e03599106229 Mon Sep 17 00:00:00 2001 From: Anycodes Date: Tue, 16 Dec 2025 10:28:42 +0800 Subject: [PATCH 8/9] Rename 10-best-prectices-a2a-by-me-a-coffee.md to 10-best-practices-a2a-by-me-a-coffee.md --- ...-by-me-a-coffee.md => 10-best-practices-a2a-by-me-a-coffee.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/tutorial/{10-best-prectices-a2a-by-me-a-coffee.md => 10-best-practices-a2a-by-me-a-coffee.md} (100%) diff --git a/docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md b/docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md similarity index 100% rename from docs/tutorial/10-best-prectices-a2a-by-me-a-coffee.md rename to docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md From 0cc47f355167f9be0d31faeb590d2e03049e504a Mon Sep 17 00:00:00 2001 From: Anycodes Date: Tue, 16 Dec 2025 10:34:19 +0800 Subject: [PATCH 9/9] Update docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md b/docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md index 37e0877..06fba01 100644 --- a/docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md +++ b/docs/tutorial/10-best-practices-a2a-by-me-a-coffee.md @@ -959,7 +959,7 @@ Agent 的响应时间很大程度上取决于模型的推理速度和工具调 为了便于大家进行快速体验,可以通过 AgentRun 控制台探索页面进行快速体验 -- 访问[https://functionai.console.aliyun.com/cn-hangzhou/agent/explore](AgentRun 探索页面): +- 访问[AgentRun 探索页面](https://functionai.console.aliyun.com/cn-hangzhou/agent/explore):