From b6d35768a02fd3964088f43f983620aa74fd68bd Mon Sep 17 00:00:00 2001 From: xuehuitian45 <13069167198@163.com> Date: Wed, 10 Dec 2025 18:09:08 +0800 Subject: [PATCH] feat: add cookbook & fix bugs in cookbook --- cookbook/new-zh/CHANGELOG.md | 0 cookbook/new-zh/concept.md | 156 +++++ cookbook/new-zh/contribute.md | 92 +++ cookbook/new-zh/deployment.md | 76 +++ .../new-zh/deployment/advanced_deployment.md | 322 ++++++++++ cookbook/new-zh/deployment/agent_app.md | 602 ++++++++++++++++++ cookbook/new-zh/deployment/react_agent.md | 0 cookbook/new-zh/install.md | 139 ++++ cookbook/new-zh/intro.md | 65 ++ cookbook/new-zh/quickstart.md | 414 ++++++++++++ cookbook/new-zh/sandbox/advanced.md | 331 ++++++++++ cookbook/new-zh/sandbox/sandbox.md | 510 +++++++++++++++ cookbook/new-zh/sandbox/training_sandbox.md | 271 ++++++++ cookbook/new-zh/sandbox/troubleshooting.md | 72 +++ cookbook/new-zh/service/memory.md | 117 ++++ cookbook/new-zh/service/sandbox.md | 152 +++++ cookbook/new-zh/service/service.md | 203 ++++++ cookbook/new-zh/service/session_history.md | 110 ++++ cookbook/new-zh/service/state.md | 98 +++ cookbook/new-zh/tool.md | 53 ++ .../services/sandbox/SandboxService.java | 4 + .../runtime/sandbox/box/GuiMixin.java | 3 + .../sandbox/manager/SandboxManager.java | 5 +- .../client/config/DockerClientConfig.java | 23 +- .../sandbox/manager/model/ManagerConfig.java | 4 +- .../manager/RedisSandboxManagerTest.java | 93 +-- .../agentscope/MyAgentScopeAgentHandler.java | 1 - .../protocol/a2a/GraphAgentExecutor.java | 4 - 28 files changed, 3843 insertions(+), 77 deletions(-) create mode 100644 cookbook/new-zh/CHANGELOG.md create mode 100644 cookbook/new-zh/concept.md create mode 100644 cookbook/new-zh/contribute.md create mode 100644 cookbook/new-zh/deployment.md create mode 100644 cookbook/new-zh/deployment/advanced_deployment.md create mode 100644 cookbook/new-zh/deployment/agent_app.md create mode 100644 cookbook/new-zh/deployment/react_agent.md create mode 100644 cookbook/new-zh/install.md create mode 100644 cookbook/new-zh/intro.md create mode 100644 cookbook/new-zh/quickstart.md create mode 100644 cookbook/new-zh/sandbox/advanced.md create mode 100644 cookbook/new-zh/sandbox/sandbox.md create mode 100644 cookbook/new-zh/sandbox/training_sandbox.md create mode 100644 cookbook/new-zh/sandbox/troubleshooting.md create mode 100644 cookbook/new-zh/service/memory.md create mode 100644 cookbook/new-zh/service/sandbox.md create mode 100644 cookbook/new-zh/service/service.md create mode 100644 cookbook/new-zh/service/session_history.md create mode 100644 cookbook/new-zh/service/state.md create mode 100644 cookbook/new-zh/tool.md diff --git a/cookbook/new-zh/CHANGELOG.md b/cookbook/new-zh/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/cookbook/new-zh/concept.md b/cookbook/new-zh/concept.md new file mode 100644 index 00000000..e20258d6 --- /dev/null +++ b/cookbook/new-zh/concept.md @@ -0,0 +1,156 @@ +# 概念 + +本章介绍了AgentScope Runtime的核心概念。 + +## 架构 + +AgentScope Runtime使用模块化架构,包含几个关键组件: + +```mermaid +flowchart LR + %% 服务模块 + subgraph Service["💼 服务"] + MS["记忆服务"] + SS["会话服务"] + STS["状态服务"] + SBS["沙箱服务"] + end + + %% 沙箱模块 + subgraph Sandbox["🐳 沙箱"] + BS["浏览器沙箱"] + FS["文件系统沙箱"] + GS["GUI 沙箱"] + CSB["云沙箱"] + MSB["移动端沙箱"] + ETC["更多..."] + end + + %% 适配器模块(大块) + subgraph Adapter["🔌 适配器"] + MAD["记忆适配器"] + SAD["会话适配器"] + STAD["状态适配器"] + SBAD["沙箱工具适配器"] + end + + %% Agent 模块(大块) + subgraph Agent["🤖 智能体"] + AG["AgentScope Java"] + AG_NOTE["(更多...)"] + end + + %% 应用层 + subgraph AgentAPP["📦 智能体应用"] + RA["运行器"] + end + + %% 部署模块 + subgraph Deployer["🚀 部署器"] + CT["容器部署"] + KD["K8s 部署"] + DP["云部署"] + LD["本地部署"] + end + + %% 外部协议 + OAI["OpenAI Responses API SDK"]:::ext + A2A["Google A2A 协议"]:::ext + CUS["自定义端点"]:::ext + + MS --> MAD + SS --> SAD + STS --> STAD + SBS --> SBAD + + BS --> SBS + FS --> SBS + GS --> SBS + CSB --> SBS + MSB --> SBS + ETC --> SBS + + %% 大块到大块的连接 + Adapter --> Agent + + AG --> RA + RA --> CT + RA --> KD + RA --> DP + RA --> LD + + %% 整个部署模块连接到外部协议 + Deployer --> OAI + Deployer --> A2A + Deployer --> CUS + + %% 样式 + classDef small fill:#0066FF,stroke:#004CBE,color:#FFFFFF,font-weight:bold + classDef big fill:#99D6FF,stroke:#004CBE,color:#FFFFFF,font-weight:bold + classDef ext fill:#FFFFFF,stroke:#000000,color:#000000,font-weight:bold + + class Tools,Service,Sandbox,Adapter,Agent,AgentAPP,Deployer big + class RT,ST,PT,MS,SS,STS,SBS,BS,FS,GS,CSB,MSB,ETC,TAD,MAD,SAD,STAD,SBAD,AG,AG_NOTE,RA,CT,KD,DP,LD small + +``` + +- **Agent**:处理请求并生成响应的核心AI组件,在Runtime中,Agent的构建推荐使用AgentScope框架。 +- **AgentApp**: 作为智能体应用入口,负责对外提供 API 接口、路由注册、配置加载,并将请求交由 Runner 调用执行 +- **Runner**:在运行时编排智能体执行并管理部署。它处理智能体生命周期、会话管理、流式响应和服务部署。 +- **Deployer**:将Runner部署为服务,提供健康检查、监控、生命周期管理、使用SSE的实时响应流式传输、错误处理、日志记录和优雅关闭。 +- **Tool**: 提供开箱即用的沙箱工具支持。 +- **Service**:提供智能体所需要的管理服务,比如记忆管理,沙箱管理等。 +- **Adapter**:将Runtime提供的组件/模块适配到Agent框架的适配器 + +### 关键组件 + +#### 1. Agent + +`Agent` 是处理请求并生成响应的核心组件。市面上已有大量的开发框架包含了Agent类,Runtime本身不提供额外的Agent类,开发者可以使用AgentScope开发需要的Agent。 + +#### 2. AgentApp + +`AgentApp` 是 AgentScope Runtime 中的 **应用入口点**,用于将 Agent 部署为可对外提供服务的 API 应用。 + +它的职责是: + +- 初始化并绑定 **Agent** 和 **AgentScopeAgentHandler**,自动构建 **Runner**,将请求委托给运行时处理 +- 提供标准化的 **HTTP API 接口**(含健康检查) +- 支持 **Server-Sent Events (SSE)** 以及标准 JSON 响应 +- 允许注册中间件、任务队列(Celery)以及自定义路由 +- 管理应用生命周期(支持 `before_start` / `after_finish` 钩子) +- 将Agent应用部署为服务 + +#### 3. AgentScopeAgentHandler + +`AgentScopeAgentHandler` 类提供灵活且可扩展的智能体执行逻辑。它管理: + +- 通过 `streamQuery` 支持用户自定义请求执行逻辑 +- 流式响应 + +#### 4. Deployer + +`Deployer`(实现为 `deployer-maven-plugin`插件)提供生产级别的部署功能: + +- maven打包过程中自动**构建 Docker 镜像** +- 可选推送至远程仓库、一键部署到 **K8s 集群**以及部署到 **AgentRun** + +#### 5. Sandbox & Tool + +Runtime提供两种工具接入方式 +- 即用型工具,即由服务提供商提供的开箱即用的服务,比如RAG +- 工具沙箱,即运行在runtime里安全可控的工具,比如浏览器 + + +#### 6. Service + +`Service`包含如下几种: + +- `state_service` 状态服务 +- `memory_service` 智能体记忆服务 +- `sandbox_service` 即沙箱服务 +- `session_history_service` 即会话历史记录保存服务 + +#### 7. Adapter + +`Adapter`按照不同Agent框架分类,包含记忆适配器、会话适配器、消息协议适配器等。 diff --git a/cookbook/new-zh/contribute.md b/cookbook/new-zh/contribute.md new file mode 100644 index 00000000..a2cd02e7 --- /dev/null +++ b/cookbook/new-zh/contribute.md @@ -0,0 +1,92 @@ +# 如何贡献 + +感谢您对AgentScope Runtime Java 项目的关注。 + +AgentScope Runtime Java 是一个专注于智能体部署和安全工具执行的开源项目,拥有一个乐于帮助新贡献者的友好开发者社区。我们欢迎各种类型的贡献,从代码改进到文档编写。 + +## 社区 + +参与 AgentScope Runtime Java 项目的第一步是加入我们的讨论,通过不同的沟通渠道与我们联系。以下是与我们建立联系的几种方式: + +- **GitHub Discussions**: 提问和分享经验(请使用**英语**) +- **Discord**: 加入我们的 [Discord频道](https://discord.gg/eYMpfnkG8h) 进行实时讨论 +- **DingTalk**: 中文用户可以加入我们的 [钉钉群](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) + +## 报告问题 + +### Bugs + +如果您在 AgentScope Runtime Java 中发现了bug,请首先使用最新版本进行测试,确保您的问题尚未被修复。如果没有,请在 GitHub上搜索我们的问题列表,查看是否已有类似问题被提出。 + +- 如果确认该 bug 尚未被报告,请在编写任何代码之前先提交一个 bug 问题。提交问题时,请包含: +- 清晰的问题描述 +- 重现步骤 +- 代码/错误信息 + - 环境详情(操作系统、JDK 版本) + +- 受影响的组件(例如 Engine 模块、Sandbox 模块等) + +### 安全问题 + +如果您在 AgentScope Runtime Java 中发现安全问题,请通过 [阿里巴巴安全响应中心(ASRC)](https://security.alibaba.com/)向我们报告。 + +## 功能需求 + +如果您希望AgentScope Runtime Java 具有某个不存在的功能,请在 GitHub 上提交功能请求问题,描述 + +- 功能及其目的 +- 应该如何工作 +- 安全考虑(如果适用) + +## 贡献代码 + +如果您想为 AgentScope Runtime Java 贡献新功能或bug 修复,请首先在 GitHub 问题中讨论您的想法。如果没有相关问题,请创建一个。可能已经有人在处理它,或者它可能有特殊的复杂性(特别是 Sandbox 功能的安全考虑),您在开始编码之前应该了解这些。 + +### Fork 和创建分支 + +Fork [AgentScope Runtime Java 主分支代码](https://github.com/agentscope-ai/agentscope-runtime-java) 并将其克隆到本地机器。有关帮助,请参见 GitHub 帮助页面。 + +创建一个具有描述性名称的分支。 + +```bash +git checkout -b feature/your-feature-name +``` + +### 进行更改 + +- 编写清晰、注释良好的代码 +- 遵循现有代码风格 +- 为新功能/修复添加测试 +- 根据需要更新文档 Test Your Changes + +### 测试您的更改 + +运行测试套件以确保您的更改不会破坏现有功能: + +```bash +mvn test +``` + +### 提交您的更改 + +1. 使用清晰的消息提交您的更改: + +```bash +git commit -m "Add: brief description of your changes" +``` + +2. 推送到您的Fork: + +```bash +git push origin feature/your-feature-name +``` + +3. 从您的分支向主仓库创建Pull Request (PR),并提供**清晰的描述** + +### 代码审查流程 + +- 所有 PR 都需要维护者的审查 +- 处理任何反馈或请求的更改 +- 一旦批准,您的 PR 将被合并 + +感谢您为 AgentScope Runtime Java 做出贡献! diff --git a/cookbook/new-zh/deployment.md b/cookbook/new-zh/deployment.md new file mode 100644 index 00000000..252663f0 --- /dev/null +++ b/cookbook/new-zh/deployment.md @@ -0,0 +1,76 @@ +# 部署 + +本章节聚焦于如何在 AgentScope Runtime Java 上部署智能体。在完成概念与快速上手之后,部署是让实验性原型进入稳定运行的关键一步。我们将首先给出部署思路与整体流程,然后串联后续的服务、简单部署、进阶部署以及 React Agent 样例 子章节,帮助你快速定位适合的路径。 + +## 为什么需要部署 + +- **获得稳定的运维能力**:Runtime 提供标准化的服务生命周期、健康检查与扩展能力,简化监控与回滚。 +- **复用生态能力**:通过统一的部署方式,可以复用记忆、沙箱、状态等基础服务,避免重复造轮子。 + +## 部署路径概览 + +部署过程通常包含以下几个阶段: + +1. **准备工作**:安装 Runtime、准备模型与工具、配置环境变量与凭证。 +2. **基础服务启动**:根据业务需要选择记忆、会话、沙箱等服务实现。 +3. **Agent App 定义**:基于 `AgentApp` 模块编排智能体、工具与工作流逻辑,形成可部署的应用入口。 +4. **运行**:在本地、容器或云原生环境中启动 Runtime。 +5. **升级与扩展**:使用高级部署与 React Agent 能力实现多区域部署、混合编排或 UI 交互。 + +## 前置准备清单 + +- Java 环境(**JDK 17+**)及必要依赖。 +- 至少一个可用的大模型接入(例如 DashScope、OpenAI 或自建推理服务)。 +- 目标部署平台(本地、Docker、Kubernetes 等)的访问与权限。 +- 对应的工具/沙箱权限,如浏览器自动化、文件系统或自定义工具服务。 + +## 子章节导读 + +### 服务 + +`Service` 章节介绍了 Runtime 内建的会话历史、记忆、沙箱、状态等基础服务,以及统一的生命周期接口。阅读该章节可以了解如何选择合适的实现(内存、Redis、Tablestore 等),以及如何通过 `start()`、`stop()`、`health()` 管理服务,确保部署环境具备稳定的支撑能力。 +详细文档见 [服务与适配器](service/service.md)。 + +### 简单部署 + +Runtime 包含一个简单的部署工具 `AgentApp` 。它是将多个智能体、工具与上下文串联的应用形态。子章节会说明: + +- 如何定义 `AgentApp` 配置、路由和会话管理。 +- 如何在部署阶段绑定服务、注入沙箱,并暴露 HTTP/gRPC/CLI 等接口。 +- 如何编写自定义 handler 与插件,满足不同业务流。 + +在实际部署中,Agent App 通常作为主要入口进程,与基础服务共同组成运行时。 +完整示例可参考 [简单部署](deployment/agent_app.md)。 + +### 高级部署 + +当需要满足更高的可用性与可观测性要求时,可参考高级部署章节,内容包括: + +- 使用容器编排(如 Docker、Kubernetes)运行多服务拓扑。 +- 使用阿里云函数计算 AgentRun 运行函数化 Agent +- 配置多地区/多模型冗余、灰度发布与扩缩策略。 + +该章节适合有生产场景或需要多团队协作的读者。 +更多方案见 [高级部署](deployment/advanced_deployment.md)。 + +### 参考: 完整部署样例 + +完整部署样例章节介绍了一个完整包含沙箱服务的Agent部署的例子,内容包括: + +- 引入浏览器沙箱 +- 构建AgentApp +- 服务启动 + +如果想完整回顾部署的所有环节,该章节是重要参考。 +请参考 [参考:完整部署样例](deployment/react_agent.md) 获取具体步骤。 + +## 下一步 + +阅读本章节后,可按照以下顺序深入: + +1. 进入 [服务](service/service.md) 章节,确认需要的基础设施与实现。 +2. 在 [简单部署](deployment/agent_app.md) 中完成业务逻辑编排与本地验证。 +3. 根据规模需求选择 [高级部署](deployment/advanced_deployment.md) 指南,完成生产化配置。 +4. 若需要 Web 交互层,继续阅读 [完整部署样例](deployment/react_agent.md) 章节并完成前端部署。 + +通过上述步骤,你可以渐进式地把智能体从实验环境部署到可观测、可维护、可扩展的生产系统。 \ No newline at end of file diff --git a/cookbook/new-zh/deployment/advanced_deployment.md b/cookbook/new-zh/deployment/advanced_deployment.md new file mode 100644 index 00000000..f4fe6a12 --- /dev/null +++ b/cookbook/new-zh/deployment/advanced_deployment.md @@ -0,0 +1,322 @@ +# 高级部署 + +章节演示了 AgentScope Runtime Java 中可用的三种高级部署方法,为不同场景提供生产就绪的解决方案:**本地Docker打包**、**Kubernetes部署**和**AgentRun部署**。 + +## 部署方法概述 + +AgentScope Runtime提供三种不同的部署方式,每种都针对特定的使用场景: + +| 部署类型 | 使用场景 | 扩展性 | 管理方式 | 资源隔离 | +|---------|---------|--------|---------|---------| +| **本地Docker打包** | 开发与测试 | 单容器 | 手动 | 容器级 | +| **Kubernetes** | 企业与云端 | K8s 引擎自动编排 | 编排 | 容器级 | +| **AgentRun** | AgentRun平台 | 云端管理 | 平台管理 | 容器级 | + +## 前置条件 + +### 🔧 安装要求 + +添加 AgentScope Runtime Java 提供的 **打包依赖**: + +```xml + + io.agentscope + deployer-maven-plugin + 1.0.0 + + ${project.basedir}/deployer.yml + + + + deployer + package + + build + + + + +``` + +### 🔑 参数配置 + +在 yaml 文件中配置部署参数,yaml 文件的读取路径为 maven 插件配置的 `configFile` 路径 + +```yaml +build: + imageName: agentscope-use-example # 构建的镜像名称 + imageTag: latest # 构建的镜像标签 + baseImage: eclipse-temurin:17-jre # 基础镜像 + port: 10001 # 应用内部端口,即 AgentApp 配置的端口号 + pushToRegistry: false # 是否将镜像推送到 Docker Registry(如果设置部署到 K8s 集群,该项必须为 true) + deployToK8s: false # 是否部署到 Kubernetes 集群 + deployToAgentRun: true # 是否部署到阿里云函数计算 AgentRun + +# ============================================ +# Docker 镜像仓库配置,注意,如果需要后续部署到 K8s,需要保证 K8s 具有该镜像仓库的访问权限 +# ============================================ +registry: + url: "" # 镜像仓库地址 + username: "" # 镜像仓库用户名 + password: "" # 镜像仓库密码 + namespace: "" # 镜像仓库命名空间 + +# ============================================ +# K8s 部署配置 +# ============================================ +kubernetes: + replicas: 1 # 部署副本数 + kubeconfigPath: "" # Kubeconfig 文件路径 + namespace: "default" # 部署命名空间 + +# ============================================ +# OSS 配置(部署 AgentRun 使用) +# ============================================ +oss: + region: cn-hangzhou # OSS 所在区域 + accessKeyId: "" # OSS 访问密钥 ID + accessKeySecret: "" # OSS 访问密钥 Secret + bucket: "" # OSS 存储桶名称 + +# ============================================ +# 传递给应用的环境变量 +# ============================================ +environment: + AI_DASHSCOPE_API_KEY: "" + SPRING_PROFILES_ACTIVE: production + +# ============================================ +# AgentRun 部署配置,注意,如果需要部署到 AgentRun,需要先配置 OSS,OSS 和 AgentRun 共享同一套访问密钥 +# ============================================ +agentrun: + region: cn-hangzhou # AgentRun 所在区域 + runtimeNamePrefix: agentscope-use-example # 部署的运行时名称前缀 + cpu: 2 # CPU 核数 + memorySize: 2048 # 内存大小,单位 MB + sessionConcurrencyLimit: 1 # 会话并发数限制 + sessionIdleTimeoutSeconds: 600 # 会话空闲超时时间,单位秒 + networkMode: PUBLIC # 网络模式,PUBLIC 或 VPC +``` + +### 📦 各部署类型的前置条件 + +#### 所有部署类型 + +- **Java 17** 或更高版本 +- **Maven 3.6** 或更高版本 +- **Docker**(用于镜像打包) + +#### Kubernetes 部署 +- **Kubernetes** 集群访问权限 +- 已配置 **kubectl** +- **容器镜像仓库**访问权限(用于推送镜像) + +#### AgentRun 部署 + +* **AgentRun** 访问参数 + +## 通用智能体配置 + +所有部署方法共享相同的智能体和端点配置。参照 [简单部署](agent_app.md) 首先构建一个 web 应用 + +## 方法1:打包本地 Docker 镜像 + +**最适合**:开发、测试和需要手动控制的持久服务的单用户场景。 + +### 特性 +- 一键构建 web 应用镜像 +- 手动生命周期管理 +- 交互式控制和监控 +- 直接资源共享 + +### 使用 + +使用 [通用智能体配置](###通用智能体配置) 部分定义的智能体和端点,配置打包参数: + +```yaml +build: + imageName: agentscope-use-example # 构建的镜像名称 + imageTag: latest # 构建的镜像标签 + baseImage: eclipse-temurin:17-jre # 基础镜像 + port: 10001 # 应用内部端口,即 AgentApp 配置的端口号 + +# ============================================ +# 传递给应用的环境变量 +# ============================================ +environment: + AI_DASHSCOPE_API_KEY: "" + SPRING_PROFILES_ACTIVE: production +``` + +**关键点**: + +- 服务会被打包为指定镜像 +- 通过 `docker run` 命令手动管理容器生命周期 +- 最适合开发和测试 + +### 测试部署的服务 + +部署后,您可以使用 curl 测试端点: + +**使用 curl:** + +```bash +curl --location --request POST 'http://localhost:10001/a2a/' \ +--header 'Content-Type: application/json' \ +--header 'Accept: */*' \ +--header 'Host: localhost:10001' \ +--header 'Connection: keep-alive' \ +--data-raw '{ + "method": "message/stream", + "id": "2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc", + "jsonrpc": "2.0", + "params": { + "configuration": { + "blocking": false + }, + "message": { + "role": "user", + "kind": "message", + "metadata": { + "userId": "me", + "sessionId": "test1" + }, + "parts": [ + { + "text": "你好,给我用python计算一下第10个斐波那契数", + "kind": "text" + } + ], + "messageId": "c4911b64c8404b7a8bf7200dd225b152" + } + } +}' +``` + + +## 方法2:Kubernetes部署 + +**最适合**:需要扩展性、高可用性和云原生编排的企业生产环境。 + +### 特性 +- 基于容器的部署 +- 水平扩展支持 +- 云原生编排 +- 资源管理和限制 +- 健康检查和自动恢复 + +### Kubernetes部署前置条件 + +```bash +# 确保Docker正在运行 +docker --version + +# 验证Kubernetes访问 +kubectl cluster-info + +# 检查镜像仓库访问(以阿里云为例) +docker login your-registry +``` + +### 使用 + +使用 [通用智能体配置](###通用智能体配置) 部分定义的智能体和端点,配置 K8s 部署需要使用到的打包参数: + +```yaml +build: + imageName: agentscope-use-example # 构建的镜像名称 + imageTag: latest # 构建的镜像标签 + baseImage: eclipse-temurin:17-jre # 基础镜像 + port: 10001 # 应用内部端口,即 AgentApp 配置的端口号 + pushToRegistry: true # 是否将镜像推送到 Docker Registry(如果设置部署到 K8s 集群,该项必须为 true) + deployToK8s: true # 是否部署到 Kubernetes 集群 + +# ============================================ +# Docker 镜像仓库配置,注意,如果需要后续部署到 K8s,需要保证 K8s 具有该镜像仓库的访问权限 +# ============================================ +registry: + url: "" # 镜像仓库地址 + username: "" # 镜像仓库用户名 + password: "" # 镜像仓库密码 + namespace: "" # 镜像仓库命名空间 + +# ============================================ +# K8s 部署配置 +# ============================================ +kubernetes: + replicas: 1 # 部署副本数 + kubeconfigPath: "" # Kubeconfig 文件路径 + namespace: "default" # 部署命名空间 + + +# ============================================ +# 传递给应用的环境变量 +# ============================================ +environment: + AI_DASHSCOPE_API_KEY: "" + SPRING_PROFILES_ACTIVE: production +``` + +**关键点**: + +- 容器化部署,支持自动扩展 +- 配置资源限制和健康检查 +- 可使用 `kubectl scale deployment` 进行扩展 + +## 方法3:Serverless部署:AgentRun + +**最适合**:阿里云用户,需要将智能体部署到 AgentRun 服务,实现自动化的构建、上传和部署流程。 + +### 特性 +- 阿里云 AgentRun 服务的托管部署 +- 自动构建和打包项目 +- OSS 集成用于制品存储 +- 完整的生命周期管理 +- 自动创建和管理运行时端点 + +### 使用 + +使用 [通用智能体配置](###通用智能体配置) 部分定义的智能体和端点,配置 AgentRun 部署需要使用到的参数: + +```yaml +build: + imageName: agentscope-use-example # 构建的镜像名称 + imageTag: latest # 构建的镜像标签 + baseImage: eclipse-temurin:17-jre # 基础镜像 + port: 10001 # 应用内部端口,即 AgentApp 配置的端口号 + deployToAgentRun: true # 是否部署到阿里云函数计算 AgentRun + +# ============================================ +# OSS 配置(部署 AgentRun 使用,OSS 仓库中存放构建制品) +# ============================================ +oss: + region: cn-hangzhou # OSS 所在区域 + accessKeyId: "" # OSS 访问密钥 ID + accessKeySecret: "" # OSS 访问密钥 Secret + bucket: "" # OSS 存储桶名称 + +# ============================================ +# 传递给应用的环境变量 +# ============================================ +environment: + AI_DASHSCOPE_API_KEY: "" + SPRING_PROFILES_ACTIVE: production + +# ============================================ +# AgentRun 部署配置,注意,如果需要部署到 AgentRun,需要先配置 OSS,OSS 和 AgentRun 共享同一套访问密钥 +# ============================================ +agentrun: + region: cn-hangzhou # AgentRun 所在区域 + runtimeNamePrefix: agentscope-use-example # 部署的运行时名称前缀 + cpu: 2 # CPU 核数 + memorySize: 2048 # 内存大小,单位 MB + sessionConcurrencyLimit: 1 # 会话并发数限制 + sessionIdleTimeoutSeconds: 600 # 会话空闲超时时间,单位秒 + networkMode: PUBLIC # 网络模式,PUBLIC 或 VPC +``` + +**关键点**: +- 自动构建项目并打包为 jar 文件 +- 上传制品到 OSS +- 在 AgentRun 服务中创建和管理运行时 +- 自动创建公共访问端点 diff --git a/cookbook/new-zh/deployment/agent_app.md b/cookbook/new-zh/deployment/agent_app.md new file mode 100644 index 00000000..36b44554 --- /dev/null +++ b/cookbook/new-zh/deployment/agent_app.md @@ -0,0 +1,602 @@ +# 简单部署 + +`AgentApp` 是 **AgentScope Runtime Java** 中的全能型应用服务封装器。 +它为你的 agent 逻辑提供 HTTP 服务框架,并可将其作为 API 暴露,支持以下功能: + +- **流式响应(SSE)**,实现实时输出 +- 内置 **健康检查** 接口 +- 内置 **A2A协议** 支持 + +**重要说明**: +在当前版本中,`AgentApp` 不会自动包含 `/process` 端点。 +你必须显式地注册一个控制器以及对应的请求处理函数,服务才能使用自定义端点处理传入的请求。 + +下面的章节将通过具体示例深入介绍每项功能。 + +------ + +## 初始化与基本运行 + +**功能** + +创建一个最小的 `AgentApp` 实例,并启动基于 `SpringBoot` 的 HTTP 服务骨架。 +初始状态下,服务只提供: + +- 欢迎页 `/` +- 健康检查 `/health` +- 就绪探针 `/readiness` +- 存活探针 `/liveness` +- A2A 协议支持 `/a2a` + +**注意**: + +- 默认不会暴露 `/process` 业务处理端点。 +- `Handler` 类需要实现 `AgentScopeAgentHandler` 中的`streamQuery` 方法,返回 `Flux` 流式结果。 + +**用法示例** + +```java +MyAgentScopeAgentHandler agentHandler = new MyAgentScopeAgentHandler(); +// 初始化 agentHandler 属性 + +AgentApp agentApp = new AgentApp(agentHandler); +// 服务会暴露在 http://localhost:10001 +agentApp.run("localhost",10001); +``` + +------ + +## A2A 流式输出(SSE) + +**功能** +让客户端实时接收生成结果(适合聊天、代码生成等逐步输出场景)。 + +**用法示例(客户端)** + +```bash +curl --location --request POST 'http://localhost:10001/a2a/' \ +--header 'Content-Type: application/json' \ +--header 'Accept: */*' \ +--header 'Host: localhost:10001' \ +--header 'Connection: keep-alive' \ +--data-raw '{ + "method": "message/stream", + "id": "2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc", + "jsonrpc": "2.0", + "params": { + "configuration": { + "blocking": false + }, + "message": { + "role": "user", + "kind": "message", + "metadata": { + "userId": "me", + "sessionId": "test1" + }, + "parts": [ + { + "text": "你好,给我用python计算一下第 30 个斐波那契数", + "kind": "text" + } + ], + "messageId": "c4911b64c8404b7a8bf7200dd225b152" + } + } +}' +``` + +**返回格式** + +```bash +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"id":"8a3021a6-1b4e-4fc9-b70b-a39f03aa476c","contextId":"ea00d2b3-ce89-4707-b457-ef58a68b35ff","status":{"state":"submitted","timestamp":"2025-12-10T08:18:30.104001Z"},"artifacts":[],"history":[{"role":"user","parts":[{"text":"你好,给我用python计算一下第30个斐波那契数","kind":"text"}],"messageId":"c4911b64c8404b7a8bf7200dd225b152","contextId":"ea00d2b3-ce89-4707-b457-ef58a68b35ff","taskId":"8a3021a6-1b4e-4fc9-b70b-a39f03aa476c","metadata":{"userId":"me","sessionId":"test1"},"kind":"message"}],"kind":"task"}} + +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"8a3021a6-1b4e-4fc9-b70b-a39f03aa476c","status":{"state":"working","timestamp":"2025-12-10T08:18:30.107693Z"},"contextId":"ea00d2b3-ce89-4707-b457-ef58a68b35ff","final":false,"kind":"status-update"}} + +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"8a3021a6-1b4e-4fc9-b70b-a39f03aa476c","artifact":{"artifactId":"ef4de8cc-5042-4d17-800a-78257b7d51ba","name":"agent-response","parts":[{"text":"Calling tool run_ipython_cell with arguments: {\"code\":\"def fibonacci(n):\\n if n <= 0:\\n return 'Input should be a positive integer.'\\n elif n == 1:\\n return 0\\n elif n == 2:\\n return 1\\n else:\\n a, b = 0, 1\\n for _ in range(2, n):\\n a, b = b, a + b\\n return b\\n\\n# Calculate the 30th Fibonacci number\\nfibonacci(30)\"} (call ID: call_3b7697e5158b44ea95cc11)","kind":"text"}],"metadata":{"type":"toolCall"}},"contextId":"ea00d2b3-ce89-4707-b457-ef58a68b35ff","append":false,"lastChunk":false,"kind":"artifact-update"}} + +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"8a3021a6-1b4e-4fc9-b70b-a39f03aa476c","artifact":{"artifactId":"ef4de8cc-5042-4d17-800a-78257b7d51ba","name":"agent-response","parts":[{"text":"Tool run_ipython_cell returned result: [{\"text\":\"{\\\"meta\\\":null,\\\"content\\\":[{\\\"type\\\":\\\"text\\\",\\\"text\\\":\\\"Out[1]: 514229\\\\n\\\",\\\"annotations\\\":null,\\\"description\\\":\\\"stdout\\\"}],\\\"isError\\\":false}\",\"type\":\"text\"}] (call ID: call_3b7697e5158b44ea95cc11)","kind":"text"}],"metadata":{"type":"toolResponse"}},"contextId":"ea00d2b3-ce89-4707-b457-ef58a68b35ff","append":true,"lastChunk":false,"kind":"artifact-update"}} + +...... + +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"8a3021a6-1b4e-4fc9-b70b-a39f03aa476c","status":{"state":"completed","message":{"role":"agent","parts":[{"text":"run_ipython_cellrun_ipython_cell第30个斐波那契数是 514229。","kind":"text"}],"messageId":"a4258fb3-5b79-461d-934a-8802dd0bebb8","contextId":"ea00d2b3-ce89-4707-b457-ef58a68b35ff","taskId":"8a3021a6-1b4e-4fc9-b70b-a39f03aa476c","metadata":{"type":"final_response"},"kind":"message"},"timestamp":"2025-12-10T08:18:38.298168Z"},"contextId":"ea00d2b3-ce89-4707-b457-ef58a68b35ff","final":true,"kind":"status-update"}} +``` + +------ + +## 健康检查接口 + +**功能** + +自动提供健康探针接口,方便容器或集群部署。 + +**接口列表** + +- `GET /health`:返回状态与时间戳 +- `GET /readiness`:判断是否就绪 +- `GET /liveness`:判断是否存活 +- `GET /`:欢迎信息 + +**用法示例** + +```bash +curl http://localhost:8090/health +curl http://localhost:8090/readiness +curl http://localhost:8090/liveness +curl http://localhost:8090/ +``` + +------ + +## 自定义 Agent 访问逻辑 + +**功能** + +实现 `AgentScopeAgentHandler` 的 `streamQuery` 方法,作为 Agent 被调用时实际触发的执行逻辑 + +### 基本用法 + +```java +public Flux streamQuery(AgentRequest request, Object messages) { + String sessionId = request.getSessionId(); + String userId = request.getUserId(); + + try { + // 1. 从状态服务导出会话状态(如有) + Map state = null; + if (stateService != null) { + try { + state = stateService.exportState(userId, sessionId, null).join(); + } catch (Exception e) { + logger.warn("导出会话状态失败: {}", e.getMessage()); + } + } + + // 2. 初始化工具集并注册可用工具 + Toolkit toolkit = new Toolkit(); + + if (sandboxService != null) { + try { + // 为当前会话创建沙箱实例 + Sandbox sandbox = sandboxService.connect(userId, sessionId, BaseSandbox.class); + // 注册 Python 代码执行工具 + toolkit.registerTool(ToolkitInit.RunPythonCodeTool(sandbox)); + logger.debug("已注册 execute_python_code 工具"); + } catch (Exception e) { + logger.warn("创建沙箱或注册工具失败,跳过工具注册: {}", e.getMessage()); + } + } + + // 3. 创建短期记忆适配器(基于会话历史服务) + MemoryAdapter memory = null; + if (sessionHistoryService != null) { + memory = new MemoryAdapter(sessionHistoryService, userId, sessionId); + } + + // 4. 创建长期记忆适配器(如有) + LongTermMemoryAdapter longTermMemory = null; + if (memoryService != null) { + longTermMemory = new LongTermMemoryAdapter(memoryService, userId, sessionId); + } + + // 5. 构建 ReAct 智能体 + ReActAgent.Builder agentBuilder = ReActAgent.builder() + .name("Friday") + .sysPrompt("你是一个名为 Friday 的智能助手。") + .toolkit(toolkit) + .model( + DashScopeChatModel.builder() + .apiKey(apiKey) + .modelName("qwen-max") + .stream(true) + .formatter(new DashScopeChatFormatter()) + .build() + ); + + // 配置长期记忆(如存在) + if (longTermMemory != null) { + agentBuilder.longTermMemory(longTermMemory) + .longTermMemoryMode(LongTermMemoryMode.BOTH); + logger.debug("已配置长期记忆"); + } + + // 配置短期记忆(如存在) + if (memory != null) { + agentBuilder.memory(memory); + logger.debug("已配置短期记忆适配器"); + } + + ReActAgent agent = agentBuilder.build(); + + // 6. 加载会话状态(如存在) + if (state != null && !state.isEmpty()) { + try { + agent.loadStateDict(state); + logger.debug("已加载会话状态,会话 ID: {}", sessionId); + } catch (Exception e) { + logger.warn("加载状态失败: {}", e.getMessage()); + } + } + + // 7. 将输入消息转换为 Msg 列表 + List agentMessages; + if (messages instanceof List) { + @SuppressWarnings("unchecked") + List msgList = (List) messages; + agentMessages = msgList; + } else if (messages instanceof Msg) { + agentMessages = List.of((Msg) messages); + } else { + logger.warn("输入消息类型不支持: {},使用空消息列表", + messages != null ? messages.getClass().getName() : "null"); + agentMessages = List.of(); + } + + // 8. 准备查询消息:多条消息时,前 N-1 条存入记忆,最后一条作为当前查询 + Msg queryMessage; + if (agentMessages.isEmpty()) { + queryMessage = Msg.builder() + .role(io.agentscope.core.message.MsgRole.USER) + .build(); + } else if (agentMessages.size() == 1) { + queryMessage = agentMessages.get(0); + } else { + for (int i = 0; i < agentMessages.size() - 1; i++) { + agent.getMemory().addMessage(agentMessages.get(i)); + } + queryMessage = agentMessages.get(agentMessages.size() - 1); + } + + // 9. 配置流式响应选项:包含推理与工具调用事件,启用增量输出 + StreamOptions streamOptions = StreamOptions.builder() + .eventTypes(EventType.REASONING, EventType.TOOL_RESULT) + .incremental(true) + .build(); + + // 10. 启动智能体流式推理,并在流结束时保存最终状态 + return agent.stream(queryMessage, streamOptions) + .doOnNext(event -> logger.info("智能体事件: {}", event)) + .doFinally(signalType -> { + if (stateService != null) { + try { + Map finalState = agent.stateDict(); + if (finalState != null && !finalState.isEmpty()) { + stateService.saveState(userId, finalState, sessionId, null) + .exceptionally(e -> { + logger.error("保存会话状态失败: {}", e.getMessage(), e); + return null; + }); + } + } catch (Exception e) { + logger.error("保存状态时发生异常: {}", e.getMessage(), e); + } + } + }) + .doOnError(error -> logger.error("智能体流式推理出错: {}", error.getMessage(), error)); + + } catch (Exception e) { + logger.error("streamQuery 执行异常: {}", e.getMessage(), e); + return Flux.error(e); + } +} +``` + +### 关键特性 + +1. **函数签名**: + - `request`:封装了本次智能体调用的**请求信息** + - `messages`:表示传入的**消息内容**,可能为单条消息、消息列表、或其他中间表示 +2. **流式输出**:函数作为生成器,使用流式返回结果 +3. **状态管理**:可以使用 `this.stateService` 进行状态保存和恢复 +4. **沙箱管理**:可以使用 `this.sandboxService` 构建沙箱工具 +5. **会话历史**:可以使用 `this.sessionHistoryService` 管理会话历史 +6. **记忆管理**:可以使用 `this.memoryService` 管理长期记忆 + + +### 完整示例:带状态管理的 AgentApp + +**AgentScopeDeployExample.java** + +```java +import io.agentscope.runtime.app.AgentApp; +import io.agentscope.runtime.engine.services.agent_state.InMemoryStateService; +import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService; +import io.agentscope.runtime.engine.services.memory.persistence.session.InMemorySessionHistoryService; +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.KubernetesClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; +import org.jetbrains.annotations.NotNull; + +/** + * AgentScope 智能体部署示例。 + * + *

本示例演示如何启动一个基于 ReActAgent 的本地智能体服务,包含以下能力: + *

    + *
  • 会话状态管理(内存存储)
  • + *
  • 短期记忆(会话历史)与长期记忆(用户级记忆库)
  • + *
  • Python 沙箱工具支持(通过 Kubernetes 启动隔离执行环境)
  • + *
  • 通过 HTTP 接口提供流式智能体推理服务
  • + *
+ * + *

启动前请确保已设置环境变量 {@code AI_DASHSCOPE_API_KEY}。 + */ +public class AgentScopeDeployExample { + + public static void main(String[] args) { + // 校验 DashScope API 密钥是否已配置 + if (System.getenv("AI_DASHSCOPE_API_KEY") == null) { + System.err.println("错误:未设置环境变量 AI_DASHSCOPE_API_KEY"); + System.exit(1); + } + + runAgent(); + } + + /** + * 初始化并启动智能体服务。 + */ + private static void runAgent() { + // 创建自定义智能体处理器 + MyAgentScopeAgentHandler agentHandler = new MyAgentScopeAgentHandler(); + + // 配置内存版状态与记忆服务(适用于开发/测试) + agentHandler.setStateService(new InMemoryStateService()); + agentHandler.setSessionHistoryService(new InMemorySessionHistoryService()); + agentHandler.setMemoryService(new InMemoryMemoryService()); + + // 配置沙箱服务(用于安全执行 Python 工具代码) + agentHandler.setSandboxService(buildSandboxService()); + + // 启动 Agent 服务应用,监听 localhost:10001 + AgentApp agentApp = new AgentApp(agentHandler); + agentApp.run("localhost", 10001); + } + + /** + * 构建沙箱服务实例,默认使用 Kubernetes 客户端配置。 + * + * @return 配置完成的 SandboxService 实例 + */ + @NotNull + private static SandboxService buildSandboxService() { + // 使用默认 Kubernetes 配置(实际部署时可替换为真实集群配置) + var clientConfig = KubernetesClientConfig.builder().build(); + var managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + + return new SandboxService(new SandboxManager(managerConfig)); + } +} +``` + +**MyAgentScopeAgentHandler.java** + +```java +import java.util.List; +import java.util.Map; + +import io.agentscope.core.ReActAgent; +import io.agentscope.core.agent.EventType; +import io.agentscope.core.agent.StreamOptions; +import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter; +import io.agentscope.core.memory.LongTermMemoryMode; +import io.agentscope.core.message.Msg; +import io.agentscope.core.model.DashScopeChatModel; +import io.agentscope.core.tool.Toolkit; +import io.agentscope.runtime.adapters.agentscope.AgentScopeAgentHandler; +import io.agentscope.runtime.adapters.agentscope.memory.LongTermMemoryAdapter; +import io.agentscope.runtime.adapters.agentscope.memory.MemoryAdapter; +import io.agentscope.runtime.engine.agents.agentscope.tools.ToolkitInit; +import io.agentscope.runtime.engine.schemas.AgentRequest; +import io.agentscope.runtime.sandbox.box.BaseSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; + +/** + * AgentScope 智能体处理器的示例实现。 + * + *

该实现支持以下核心功能: + *

    + *
  • 从状态服务加载和保存会话状态
  • + *
  • 集成短期记忆(基于会话历史)与长期记忆(基于用户记忆库)
  • + *
  • 动态注册工具(如 Python 沙箱执行环境)
  • + *
  • 使用 Qwen 大模型进行流式推理
  • + *
  • 以事件流形式返回推理全过程(包括推理步骤、工具调用等)
  • + *
+ */ +public class MyAgentScopeAgentHandler extends AgentScopeAgentHandler { + private static final Logger logger = LoggerFactory.getLogger(MyAgentScopeAgentHandler.class); + private final String apiKey; + + /** + * 构造函数:从环境变量加载 DashScope API 密钥。 + */ + public MyAgentScopeAgentHandler() { + this.apiKey = System.getenv("AI_DASHSCOPE_API_KEY"); + } + + @Override + public Flux streamQuery(AgentRequest request, Object messages) { + String sessionId = request.getSessionId(); + String userId = request.getUserId(); + + try { + // 1. 尝试从状态服务加载当前会话的持久化状态 + Map state = null; + if (stateService != null) { + try { + state = stateService.exportState(userId, sessionId, null).join(); + } catch (Exception e) { + logger.warn("加载会话状态失败: {}", e.getMessage()); + } + } + + // 2. 初始化工具集并注册可用工具 + Toolkit toolkit = new Toolkit(); + if (sandboxService != null) { + try { + Sandbox sandbox = sandboxService.connect(userId, sessionId, BaseSandbox.class); + toolkit.registerTool(ToolkitInit.RunPythonCodeTool(sandbox)); + logger.debug("已注册 Python 代码执行工具"); + } catch (Exception e) { + logger.warn("沙箱初始化或工具注册失败,跳过工具支持: {}", e.getMessage()); + } + } + + // 3. 创建短期记忆适配器(用于管理当前会话消息历史) + MemoryAdapter memory = null; + if (sessionHistoryService != null) { + memory = new MemoryAdapter(sessionHistoryService, userId, sessionId); + } + + // 4. 创建长期记忆适配器(用于访问用户级别的持久化记忆) + LongTermMemoryAdapter longTermMemory = null; + if (memoryService != null) { + longTermMemory = new LongTermMemoryAdapter(memoryService, userId, sessionId); + } + + // 5. 构建 ReAct 智能体实例 + ReActAgent.Builder agentBuilder = ReActAgent.builder() + .name("Friday") + .sysPrompt("你是一个名为 Friday 的智能助手。") + .toolkit(toolkit) + .model( + DashScopeChatModel.builder() + .apiKey(apiKey) + .modelName("qwen-max") + .stream(true) + .formatter(new DashScopeChatFormatter()) + .build() + ); + + if (longTermMemory != null) { + agentBuilder.longTermMemory(longTermMemory) + .longTermMemoryMode(LongTermMemoryMode.BOTH); + logger.debug("已启用长期记忆"); + } + + if (memory != null) { + agentBuilder.memory(memory); + logger.debug("已配置短期记忆适配器"); + } + + ReActAgent agent = agentBuilder.build(); + + // 6. 若存在状态数据,则恢复智能体内部状态 + if (state != null && !state.isEmpty()) { + try { + agent.loadStateDict(state); + logger.debug("成功恢复会话状态,会话 ID: {}", sessionId); + } catch (Exception e) { + logger.warn("恢复状态失败: {}", e.getMessage()); + } + } + + // 7. 将输入消息转换为标准 Msg 列表 + List agentMessages; + if (messages instanceof List) { + @SuppressWarnings("unchecked") + List msgList = (List) messages; + agentMessages = msgList; + } else if (messages instanceof Msg) { + agentMessages = List.of((Msg) messages); + } else { + logger.warn("不支持的消息类型: {},使用空消息列表", + messages != null ? messages.getClass().getName() : "null"); + agentMessages = List.of(); + } + + // 8. 准备查询消息:多条消息时,前 N-1 条存入记忆,最后一条作为当前查询 + Msg queryMessage; + if (agentMessages.isEmpty()) { + queryMessage = Msg.builder() + .role(io.agentscope.core.message.MsgRole.USER) + .build(); + } else if (agentMessages.size() == 1) { + queryMessage = agentMessages.get(0); + } else { + for (int i = 0; i < agentMessages.size() - 1; i++) { + agent.getMemory().addMessage(agentMessages.get(i)); + } + queryMessage = agentMessages.get(agentMessages.size() - 1); + } + + // 9. 配置流式输出选项:包含推理过程和工具调用结果,启用增量模式 + StreamOptions streamOptions = StreamOptions.builder() + .eventTypes(EventType.REASONING, EventType.TOOL_RESULT) + .incremental(true) + .build(); + + // 10. 启动流式推理,并在流结束时自动保存最终状态 + return agent.stream(queryMessage, streamOptions) + .doOnNext(event -> logger.info("智能体事件: {}", event)) + .doFinally(signalType -> { + if (stateService != null) { + try { + Map finalState = agent.stateDict(); + if (finalState != null && !finalState.isEmpty()) { + stateService.saveState(userId, finalState, sessionId, null) + .exceptionally(e -> { + logger.error("保存会话状态失败: {}", e.getMessage(), e); + return null; + }); + } + } catch (Exception e) { + logger.error("保存状态时发生异常: {}", e.getMessage(), e); + } + } + }) + .doOnError(error -> logger.error("智能体流式推理出错: {}", error.getMessage(), error)); + + } catch (Exception e) { + logger.error("streamQuery 执行异常: {}", e.getMessage(), e); + return Flux.error(e); + } + } + + @Override + public boolean isHealthy() { + return true; + } + + @Override + public String getName() { + return "DemoAgent"; + } + + @Override + public String getDescription() { + return "基于 AgentScope 实现的智能助手示例。"; + } +} +``` + +## 本地部署 + +**功能** + +通过 `run()` 方法一键拉起本地应用。 + +**用法示例** + +```java +agentApp.run("localhost", 10001); +``` + +更多部署选项和详细说明,请参考 [高级部署](deployment/advanced_deployment.md) 文档。 + +AgentScope Runtime 提供了 Serverless 的部署方案,您可以将您的 Agent 应用部署到 K8s 或 AgentRun 上。参考 [高级部署](deployment/advanced_deployment.md) 文档,查看 K8s 和 AgentRun 部署部分获取更多配置详情. diff --git a/cookbook/new-zh/deployment/react_agent.md b/cookbook/new-zh/deployment/react_agent.md new file mode 100644 index 00000000..e69de29b diff --git a/cookbook/new-zh/install.md b/cookbook/new-zh/install.md new file mode 100644 index 00000000..80f395d6 --- /dev/null +++ b/cookbook/new-zh/install.md @@ -0,0 +1,139 @@ +# 安装 + +准备好开始使用 AgentScope Runtime Java 了吗?本指南将帮助您在几分钟内快速搭建和运行**AgentScope Runtime Java**。 + +## 前置要求 + +- **Java 17** 或更高版本 +- **Maven 3.6** 或更高版本 +- **Docker**(可选,用于沙箱工具执行) + +## 安装方式 + +### 通过 Maven Central 安装(推荐) + +AgentScope Runtime Java 已经发布到 Maven Central,您可以直接通过 Maven 依赖使用。 + +> 当前稳定版本:1.0.0 +> +> 您可以在 [Maven Central](https://central.sonatype.com/artifact/io.agentscope/agentscope-runtime-core) 上查找和下载所有模块。 + +在您的 `pom.xml` 中添加相应的依赖即可使用: + +#### 核心运行时 (Core) + +在您的 `pom.xml` 中添加核心运行时依赖: + +```xml + + io.agentscope + agentscope-runtime-core + 1.0.0 + +``` + +#### AgentScope Agent 集成 + +如果需要使用 AgentScope Agent: + +```xml + + io.agentscope + agentscope-runtime-agentscope + 1.0.0 + +``` + +#### 一键部署 (Web) + +如果需要使用一键部署功能: + +```xml + + io.agentscope + agentscope-runtime-web + 1.0.0 + +``` + +#### 协议集成 + +如果需要使用 A2A (Agent-to-Agent) 协议: + +```xml + + io.agentscope + spring-boot-starter-runtime-a2a + 1.0.0 + +``` + +#### 自动化部署 + +如果想要自动将 Agent 应用打包为容器,并自动部署到 K8s 或 AgentRun 上,可以使用提供的插件: + +```xml + + io.agentscope + deployer-maven-plugin + 1.0.0 + + deployer.yml + 8080 + + +``` + +### (可选)从源码安装 + +如果您想要使用最新的开发版本或为项目做贡献,可以从源码安装: + +```bash +git clone https://github.com/agentscope-ai/agentscope-runtime-java.git + +cd agentscope-runtime-java + +mvn clean install -Dskiptests +``` + +安装完成后,依赖项将安装在本地 Maven 仓库中,您可以在项目中使用它们。 + +> 从源码安装会使用 SNAPSHOT 版本,适合开发和测试场景。生产环境建议使用 Maven Central 上的稳定版本。 + +### 使用 Maven 检查依赖 + +您也可以使用 Maven 命令检查依赖是否正确解析: + +```bash +mvn dependency:tree | grep agentscope +``` + +这将显示所有与 agentscope 相关的依赖及其版本。 + + +## 安装选项说明 + +这个图展示了安装选项的层次结构,从底层核心运行时(agentscope-runtime-core)开始——其中 **包含 Agent 运行框架 和 Sandbox 依赖**。可选模块(例如 agentscope、web、a2a-starter等)堆叠在核心之上,每个模块都增加了特定的功能(如多Agent框架支持、自动化)。查看所有安装选项的详细信息,请参见项目的 [pom.xml](https://github.com/agentscope-ai/agentscope-runtime-java/blob/main/pom.xml)。 + +| **组件** | **Maven 坐标** | **用途** | +| --------------------- | ----------------------------------------------- | ------------------------------------------------------------ | +| 核心运行时 | `io.agentscope:agentscope-runtime-core` | 最小依赖,提供沙箱管理、记忆管理等基础运行时能力 | +| AgentScope Agent 集成 | `io.agentscope:agentscope-runtime-agentscope` | AgentScope 集成,支持原生 AgentScope Agent 到 Runtime Agent 的转换,并内置 Sandbox Tool 到 AgentScope Tool 的映射逻辑 | +| 一键启动 | `io.agentscope:agentscope-runtime-web` | 通过 LocalDeployer 实现 Agent 应用的一键启动与本地运行 | +| 协议集成 | `io.agentscope:spring-boot-starter-runtime-a2a` | 在用户构建好的 Spring Boot 应用中自动注册 A2A(Agent-to-Agent)通信端点及 Responses API 接口 | +| 自动化部署 | `deployer-maven-plugin` | 将 Agent 应用打包为一个容器,并可选部署到 K8s 或 AgentRun 上 | + +## 版本信息 + +- **当前稳定版本**:`1.0.0` +- **发布位置**:[Maven Central](https://central.sonatype.com/artifact/io.agentscope/agentscope-runtime-core) +- **GroupId**:`io.agentscope` + +### 在 Maven Central 上查找 + +您可以在 Maven Central 上搜索和查看所有可用模块: + +- [agentscope-runtime-core](https://central.sonatype.com/artifact/io.agentscope/agentscope-runtime-core) +- [agentscope-runtime-agentscope](https://central.sonatype.com/artifact/io.agentscope/agentscope-runtime-agentscope) +- [agentscope-runtime-web](https://central.sonatype.com/artifact/io.agentscope/agentscope-runtime-web) +- [spring-boot-starter-runtime-a2a](https://central.sonatype.com/artifact/io.agentscope/spring-boot-starter-runtime-a2a) \ No newline at end of file diff --git a/cookbook/new-zh/intro.md b/cookbook/new-zh/intro.md new file mode 100644 index 00000000..9b8d640e --- /dev/null +++ b/cookbook/new-zh/intro.md @@ -0,0 +1,65 @@ +# 欢迎来到AgentScope Runtime Java Cookbook + +[![License](https://img.shields.io/badge/license-Apache%202.0-red.svg?logo=apache&label=License)](LICENSE) +[![GitHub Stars](https://img.shields.io/github/stars/agentscope-ai/agentscope-runtime-java?style=flat&logo=github&color=yellow&label=Stars)](https://github.com/agentscope-ai/agentscope-runtime-java/stargazers) +[![GitHub Forks](https://img.shields.io/github/forks/agentscope-ai/agentscope-runtime-java?style=flat&logo=github&color=purple&label=Forks)](https://github.com/agentscope-ai/agentscope-runtime-java/network) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.agentscope/agentscope-runtime/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.agentscope/agentscope-runtime) +[![License](https://img.shields.io/badge/license-Apache%202.0-red.svg?logo=apache&label=License)](https://github.com/agentscope-ai/agentscope-runtime/blob/main/LICENSE) +[![Cookbook](https://img.shields.io/badge/📚_Cookbook-English|中文-teal.svg)](https://runtime.agentscope.io) +[![A2A](https://img.shields.io/badge/A2A-Agent_to_Agent-blue.svg?label=A2A)](https://a2a-protocol.org/) +[![MCP](https://img.shields.io/badge/MCP-Model_Context_Protocol-purple.svg?logo=plug&label=MCP)](https://modelcontextprotocol.io/) +[![DingTalk](https://img.shields.io/badge/DingTalk-Join_Us-orange.svg)](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) + +## AgentScope Runtime V1.0 发布 + +AgentScope Runtime Java V1.0 在高效智能体部署与安全沙箱执行的坚实基础上,推出了 **统一的 “Agent 作为 API” 开发体验**,覆盖完整智能体从本地开发到生产部署的生命周期,并扩展了更多沙箱类型、协议兼容性与更丰富的内置工具集。 + +同时,智能体服务的接入方式从过去的 **黑盒化模块替换** 升级为 ***白盒化适配器模式*** —— 开发者可以在保留原有智能体框架接口与行为的前提下,将状态管理、会话记录、工具注册等运行时能力按需嵌入应用生命周期,实现更灵活的定制与跨框架无缝集成。 + +**V1.0 主要改进:** + +- **统一的开发/生产范式** —— 在开发环境与生产环境中 智能体功能性保持一致 +- **原生多智能体支持** —— 完全兼容 AgentScope Java 的多智能体范式 +- **主流 SDK 与协议集成** —— 支持 OpenAI Responses API SDK 与 Google A2A 协议 +- **可视化 Web UI** —— 部署后即可立即体验的开箱即用 Web 聊天界面 +- **扩展沙箱类型** —— GUI、浏览器、文件系统(大部分可通过 VNC 可视化) +- **更丰富的内置工具** —— 面向生产的搜索、RAG、AIGC、支付等模块 +- **灵活的部署模式** —— 本地线程/进程、Docker、Kubernetes、或托管云端 + +更详细的变更说明,以及迁移指南请参考:[CHANGELOG](CHANGELOG.md) + +## 什么是AgentScope Runtime Java? + +**AgentScope Runtime Java** 是一个全面的智能体运行时框架,旨在解决两个关键挑战:**高效的智能体部署**和**沙箱执行**。它内置了基础服务(长短期记忆、智能体状态持久化)和安全沙箱基础设施。无论您需要大规模部署智能体还是确保安全的工具交互,AgentScope Runtime Java 都能提供具有完整可观测性和开发者友好部署的核心基础设施。 + +在 V1.0 中,这些运行时服务通过 **适配器模式** 对外开放,允许开发者在保留原有智能体框架接口与行为的基础上,将 AgentScope Java 的状态管理、会话记录、工具调用等模块按需嵌入到应用生命周期中。从过去的 “黑盒化替换” 变为 “白盒化集成”,开发者可以显式地控制服务初始化、工具注册与状态持久化流程,从而在不同框架间实现无缝整合,同时获得更高的扩展性与灵活性。 + +本指南将指导您使用 **AgentScope Runtime Java** 构建服务级的智能体应用程序。 + +## 核心架构 + +**⚙️ 智能体web应用部署 (Web)** + +提供`AgentApp`作为智能体应用主入口,同时配备部署、管理和监控智能体应用的生产级基础设施,并内置了会话历史、长期记忆以及智能体状态等服务。 + +**🔒 沙箱执行运行时 (Sandbox)** + +安全隔离的环境,让您的智能体能够安全地执行代码、控制浏览器、管理文件并集成MCP 工具——所有这些都不会危及您的系统安全。 + +**🛠️ 生产级工具服务 (Tool)** + +基于可信第三方 API 能力(如搜索、RAG、AIGC、支付等),通过统一的 SDK 封装对外提供标准化调用接口,使智能体能够以一致的方式集成和使用这些服务,而无需关心底层 API 的差异与复杂性。 + +**🔌 适配器模式 (Adapter)** + +将 Runtime 内的各类服务模块(状态管理、会话记录、工具执行等)适配到智能体框架的原生模块接口中,使开发者能够在保留原生行为的同时直接调用这些能力,实现无缝对接与灵活扩展。 + +## 为什么选择 AgentScope Runtime Java? + +- 🤖 **AS原生运行时框架**:由 AgentScope 官方构建和维护,与其多智能体范式、适配器模式及工具使用深度集成,确保最佳兼容性与性能 +- **🏗️ 部署基础设施**:内置长短期记忆、智能体状态和沙箱环境控制服务 +- **🔒 沙箱执行**:隔离的沙箱确保工具安全执行,不会危及系统 +- ⚡ **开发者友好**:简单部署,功能强大的自定义选项 +- **📊 可观测性**:针对运行时操作的全面追踪和监控 + +立即开始使用 AgentScope Runtime Java 部署你的智能体并尝试工具沙箱吧! diff --git a/cookbook/new-zh/quickstart.md b/cookbook/new-zh/quickstart.md new file mode 100644 index 00000000..24fd76ce --- /dev/null +++ b/cookbook/new-zh/quickstart.md @@ -0,0 +1,414 @@ +# 快速开始 + +本教程演示如何在 **AgentScope Runtime Java** 框架中构建一个简单的智能体应用并将其部署为服务。 + +## 前置条件 + +### 🔧 安装要求 + +添加 AgentScope Runtime Java 针对 AgentScope 框架的适配器依赖和应用启动依赖,基础依赖已通过适配器依赖传递依赖完成: + +```xml + + io.agentscope + agentscope-runtime-agentscope + 1.0.0 + + + + io.agentscope + agentscope-runtime-web + 1.0.0 + +``` + +### 🔑 API密钥配置 + +您需要为所选的大语言模型提供商提供API密钥。本示例使用阿里云的Qwen模型,服务提供方是DashScope,所以需要使用其API_KEY,您可以按如下方式将key作为环境变量: + +```bash +export DASHSCOPE_API_KEY="your_api_key_here" +``` + +## 分步实现 + +### 步骤1:构建 Agent 及其执行逻辑 + +#### 1.1 导入依赖 + +首先导入所有必要的依赖: + +```java +import java.util.List; +import java.util.Map; + +import io.agentscope.core.ReActAgent; +import io.agentscope.core.agent.EventType; +import io.agentscope.core.agent.StreamOptions; +import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter; +import io.agentscope.core.memory.LongTermMemoryMode; +import io.agentscope.core.message.Msg; +import io.agentscope.core.model.DashScopeChatModel; +import io.agentscope.core.tool.Toolkit; +import io.agentscope.runtime.adapters.agentscope.AgentScopeAgentHandler; +import io.agentscope.runtime.adapters.agentscope.memory.LongTermMemoryAdapter; +import io.agentscope.runtime.adapters.agentscope.memory.MemoryAdapter; +import io.agentscope.runtime.engine.agents.agentscope.tools.ToolkitInit; +import io.agentscope.runtime.engine.schemas.AgentRequest; +import io.agentscope.runtime.sandbox.box.BaseSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; +``` + +#### 1.2 构建 AgentScopeAgentHandler 接口的实现 + +下面的四个方法分别定义了整个应用的属性和执行逻辑,`isHealthy`方法用于返回当前应用的健康状况,`getName`方法用于返回当前应用的名称,`getDescription`方法用于返回当前应用的描述,**`streamQuery`**方法则是整个AgentScopeAgentHandler的逻辑执行核心,用于用户设置记忆、构建 Agent 以及自定义 Agent 执行逻辑 + +```java +public class MyAgentScopeAgentHandler extends AgentScopeAgentHandler { + @Override + public boolean isHealthy() { + return false; + } + + @Override + public String getName() { + return ""; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public Flux streamQuery(AgentRequest request, Object messages) { + return null; + } +} +``` + +#### 1.3 实现核心执行逻辑 streamQuery 方法 + +##### 1.3.1 获取到必要的 id 信息 + +```java +String sessionId = request.getSessionId(); +String userId = request.getUserId(); +``` + +##### 1.3.2 从 StateService 导出历史状态 + +```java +Map state = null; +if (stateService != null) { + try { + state = stateService.exportState(userId, sessionId, null).join(); + } + catch (Exception e) { + logger.warn("Failed to export state: {}", e.getMessage()); + } +} +``` + +- **目的**:恢复该用户在该会话中的**上一轮 Agent 状态**(例如:内部变量、对话阶段、任务进度等)。 +- `roundId = null` 表示取**最新一轮**的状态。 +- 使用 `.join()` 阻塞等待(因为后续构建 Agent 需要同步状态)。 +- 失败时仅警告,不影响主流程(Agent 可从空状态开始)。 + +##### 1.3.3 创建 Toolkit 并注册工具 + +```java +Toolkit toolkit = new Toolkit(); +if (sandboxService != null) { + Sandbox sandbox = sandboxService.connect(userId, sessionId, BaseSandbox.class); + toolkit.registerTool(ToolkitInit.RunPythonCodeTool(sandbox)); +} +``` + +- **Toolkit**:Agent 可调用的工具集合。 +- **Sandbox**:安全沙箱环境,具体包含基础沙箱、文件系统沙箱、浏览器沙箱等,每个 `(userId, sessionId)` 独立实例。 +- 注册沙箱工具。 +- 如果沙箱创建失败,跳过工具注册,Agent 仍可运行,但无法调用此工具。 + +##### 1.3.4 创建短期记忆适配器(MemoryAdapter) + +```java +MemoryAdapter memory = null; +if (sessionHistoryService != null) { + memory = new MemoryAdapter(sessionHistoryService, userId, sessionId); +} +``` + +- **作用**:提供**当前会话的历史消息记录**(如用户和 Agent 的对话历史)。 +- `sessionHistoryService` 是底层存储服务。 +- 适配器将通用服务接口转换为 AgentScope 框架所需的 `Memory` 接口。 + +##### 1.3.5 创建长期记忆适配器(LongTermMemoryAdapter) + +```java +LongTermMemoryAdapter longTermMemory = null; +if (memoryService != null) { + longTermMemory = new LongTermMemoryAdapter(memoryService, userId, sessionId); +} +``` + +- **作用**:访问用户的**跨会话长期记忆**(如个人偏好、知识库摘要等)。 +- 通常基于向量数据库或结构化存储。 +- 后续会配置 Agent 在生成回复时**同时参考短期和长期记忆**。 + +##### 1.3.6 构建 ReActAgent 实例 + +```java +ReActAgent.Builder agentBuilder = ReActAgent.builder() + .name("Friday") + .sysPrompt("You're a helpful assistant named Friday.") + .toolkit(toolkit) + .model( + DashScopeChatModel.builder() + .apiKey(apiKey) + .modelName("qwen-max") + .stream(true) + .formatter(new DashScopeChatFormatter()) + .build()); +``` + +- 使用 **Builder 模式**组装 Agent: + - 名称、系统提示词(system prompt) + - 绑定工具集(`toolkit`) + - 配置大模型(这里用通义千问 `qwen-max`,通过 DashScope API) + - 启用流式输出(`.stream(true)`) +- **注意**:此时 Agent 尚未加载状态或记忆。 + +##### 1.3.7 注入记忆模块 + +```java +if (longTermMemory != null) { + agentBuilder.longTermMemory(longTermMemory) + .longTermMemoryMode(LongTermMemoryMode.BOTH); +} +if (memory != null) { + agentBuilder.memory(memory); +} +``` + +- **`memory`** → 短期记忆(当前会话历史) +- **`longTermMemory`** → 长期记忆(跨会话知识) +- `LongTermMemoryMode.BOTH`:表示在**思考和生成**阶段都使用长期记忆。 + +##### 1.3.8 加载历史状态到 Agent + +```java +if (state != null && !state.isEmpty()) { + agent.loadStateDict(state); +} +``` + +- 将 Step 2 获取的状态字典反序列化到 Agent 内部。 +- 使 Agent 能“接续”上一次的对话状态(如继续未完成的任务)。 + +##### 1.3.9 处理输入消息(messages) + +```java +List agentMessages; +if (messages instanceof List) { + @SuppressWarnings("unchecked") + List msgList = (List) messages; + agentMessages = msgList; +} +else if (messages instanceof Msg) { + agentMessages = List.of((Msg) messages); +} +else { + logger.warn("Unexpected messages type: {}, using empty list", + messages != null ? messages.getClass().getName() : "null"); + agentMessages = List.of(); +} + +Msg queryMessage; +if (agentMessages.size() > 1) { + // 将前 N-1 条消息加入 memory + for (int i = 0; i < agentMessages.size() - 1; i++) { + agent.getMemory().addMessage(agentMessages.get(i)); + } + queryMessage = agentMessages.get(agentMessages.size() - 1); // 最后一条作为当前查询 +} else { + queryMessage = agentMessages.get(0) or empty Msg; +} +``` + +##### 1.3.10 启动流式推理并返回事件流 + +```java +StreamOptions streamOptions = StreamOptions.builder() + .eventTypes(EventType.REASONING, EventType.TOOL_RESULT) + .incremental(true) + .build(); + +Flux agentScopeEvents = agent.stream(queryMessage, streamOptions); +``` + +- **`stream()`**:启动 ReAct 循环(Thought → Action → Observation → ... → Final Answer) +- **`StreamOptions`**: + - `eventTypes`:只返回推理步骤和工具结果(过滤掉内部日志等) + - `incremental = true`:启用增量流式输出(如逐字生成) +- 返回的是 **AgentScope 原生 Event 流**(不是 Runtime Event),由外层 `StreamAdapter` 转换。 + +##### 1.3.11 流完成时保存最终状态 + +```java +return agentScopeEvents + .doOnNext(event -> { + logger.info("Agent event: {}", event); + }) + .doFinally(signalType -> { + if (stateService != null) { + try { + Map finalState = agent.stateDict(); + if (finalState != null && !finalState.isEmpty()) { + stateService.saveState(userId, finalState, sessionId, null) + .exceptionally(e -> { + logger.error("Failed to save state: {}", e.getMessage(), e); + return null; + }); + } + } + catch (Exception e) { + logger.error("Error saving state: {}", e.getMessage(), e); + } + } + }) + .doOnError(error -> { + logger.error("Error in agent stream: {}", error.getMessage(), error); + }); +``` + +- **无论成功/失败/取消**,在流结束时保存 Agent 的**最终状态**。 +- `roundId = null` → 自动分配新轮次 ID(见 `InMemoryStateService` 实现)。 +- 使用 `exceptionally` 处理保存异常,避免影响主流程。 + +> 🔁 实现了“状态持久化闭环”:加载 → 执行 → 保存。 + +### 步骤2:构建 AgentApp + +#### 2.1 导入依赖 + +```java +import io.agentscope.runtime.app.AgentApp; +import io.agentscope.runtime.engine.services.agent_state.InMemoryStateService; +import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService; +import io.agentscope.runtime.engine.services.memory.persistence.session.InMemorySessionHistoryService; +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.KubernetesClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; +import org.jetbrains.annotations.NotNull; +``` + +#### 2.2 构建 SandboxService 沙箱管理服务 + +```java +private static SandboxService buidSandboxService() { + BaseClientConfig clientConfig = KubernetesClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + return new SandboxService( + new SandboxManager(managerConfig) + ); +} +``` + +* 沙箱运行环境支持 **Docker**、**K8s **以及 **AgentRun**,未配置 `clientConfig` 默认使用**本地 Docker **作为运行环境 +* 使用`managerConfig`构建 **SandboxManager**,并由此构建 **SandboxService** + +#### 2.3 构建 AgentApp + +##### 2.3.1 初始化 agentHandler + +```java +MyAgentScopeAgentHandler agentHandler = new MyAgentScopeAgentHandler(); +agentHandler.setStateService(new InMemoryStateService()); +agentHandler.setSessionHistoryService(new InMemorySessionHistoryService()); +agentHandler.setMemoryService(new InMemoryMemoryService()); +agentHandler.setSandboxService(buidSandboxService()); +``` + +实例化刚刚编写的 **AgentScopeAgentHandler** 类,并注册服务 + +##### 2.3.2 构建 AgentApp 并一键启动 + +```java +AgentApp agentApp = new AgentApp(agentHandler); +agentApp.run(10001); +``` + +使用实例化的 **AgentScopeAgentHandler** 类初始化 **AgentApp**,并在10001端口上启动 + +### 步骤3:通过 A2A 协议访问 Agent + +```bash +curl --location --request POST 'http://localhost:10001/a2a/' \ +--header 'Content-Type: application/json' \ +--header 'Accept: */*' \ +--header 'Host: localhost:10001' \ +--header 'Connection: keep-alive' \ +--data-raw '{ + "method": "message/stream", + "id": "2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc", + "jsonrpc": "2.0", + "params": { + "configuration": { + "blocking": false + }, + "message": { + "role": "user", + "kind": "message", + "metadata": { + "userId": "me", + "sessionId": "test1" + }, + "parts": [ + { + "text": "你好,给我用python计算一下第10个斐波那契数", + "kind": "text" + } + ], + "messageId": "c4911b64c8404b7a8bf7200dd225b152" + } + } +}' +``` + +你将会看到以**Server-Sent Events(SSE)**格式流式输出的响应: + +``` +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"id":"92ccdc36-006f-4d66-a47c-18d0cb171506","contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","status":{"state":"submitted","timestamp":"2025-12-09T10:53:47.612001Z"},"artifacts":[],"history":[{"role":"user","parts":[{"text":"你好,给我用python计算一下第10个斐波那契数","kind":"text"}],"messageId":"c4911b64c8404b7a8bf7200dd225b152","contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","metadata":{"userId":"me","sessionId":"test1"},"kind":"message"}],"kind":"task"}} + +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","status":{"state":"working","timestamp":"2025-12-09T10:53:47.614736Z"},"contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","final":false,"kind":"status-update"}} + +...... + +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","artifact":{"artifactId":"293bb1b0-1442-4ca2-997f-575b798dfad1","name":"agent-response","parts":[{"text":"是55。","kind":"text"}],"metadata":{"type":"chunk"}},"contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","append":true,"lastChunk":false,"kind":"artifact-update"}} + +id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc +event:jsonrpc +data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","status":{"state":"completed","message":{"role":"agent","parts":[{"text":"run_ipython_cellrun_ipython_cell第10个斐波那契数是55。","kind":"text"}],"messageId":"7b878071-d63e-4710-81e0-91d50a57c373","contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","metadata":{"type":"final_response"},"kind":"message"},"timestamp":"2025-12-09T10:53:51.538933Z"},"contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","final":true,"kind":"status-update"}} +``` + +## 章节导读 + +后续的章节包括如下几个部分 +- [沙箱与工具](tool.md): 帮助您在Agent中加入工具 +- [部署](deployment.md): 帮助您部署Agent,打包成服务 +- [使用](use.md): 帮助您调用部署后的服务 +- [如何贡献](contribute.md): 贡献代码给本项目的参考文档 \ No newline at end of file diff --git a/cookbook/new-zh/sandbox/advanced.md b/cookbook/new-zh/sandbox/advanced.md new file mode 100644 index 00000000..60d6f997 --- /dev/null +++ b/cookbook/new-zh/sandbox/advanced.md @@ -0,0 +1,331 @@ +# 工具沙箱高级用法 + +>本节介绍沙箱的高级用法。我们强烈建议在继续之前先完成上一节的基础教程[沙箱](sandbox.md)。 + +## 沙箱管理器配置参考 + +#### ManagerConfig 配置 + +| Parameter | Type | Description | Default | Notes | +| ----------------------| ------------ | ---------------------- | -------------------------- | ------------------------------------------------------------ | +| `defaultSandboxType` | `List` | 默认沙箱类型(可多个) | `SandboxType.BASE` | 可以是单个类型,也可以是多个类型的列表,从而启用多个独立的沙箱预热池。合法取值包括 `BASE`、`BROWSER`、`FILESYSTEM`、`GUI` 等 | +| `bearerToken` | `String` | 调用远程runtime沙箱的身份验证令牌 | `null` | 如果设置为 `null`,将在连接的时候不会进行身份验证 | +| `baseUrl` | `String` | 调用远程runtime沙箱的服务器绑定地址 | `null` | 如果设置为 `null`,将默认使用本地沙箱管理 | +| `containerDeployment` | `BaseClientConfig` | 容器运行时 | `DockerClientConfig` | 目前支持 `Docker`、`K8s` 和 `AgentRun` | +| `poolSize` | `int` | 预热容器池大小 | `0` | 缓存的容器以实现更快启动。 `poolSize` 参数控制预创建并缓存在就绪状态的容器数量。当用户请求新沙箱时,系统将首先尝试从这个预热池中分配,相比从零开始创建容器显著减少启动时间。例如,使用 `poolSize=10`,系统维护 10 个就绪容器,可以立即分配给新请求 | +| `fileSystemConfig` | `FileSystemConfig` | 容器文件系统配置 | `LocalFileSystemConfig` | 管理容器文件系统的下载方式,默认使用`本地文件系统`,也可以使用 `oss` | +| `redisConfig` | `RedisManagerConfig` | redis支持配置 | `null` | 启用 Redis 支持,分布式部署或工作进程数大于 `1` 时必需,默认不启用 | + +#### Redis 配置 + +> **何时使用 Redis:** +> - **单个工作进程(`WORKERS=1`)**:Redis 是可选的。系统可以使用内存缓存来管理沙箱状态,这更简单且延迟更低。 +> - **多个工作进程(`WORKERS>1`)**:需要 Redis 来在工作进程间共享沙箱状态并确保一致性。 + +Redis 为沙箱状态和状态管理提供缓存。如果只有一个工作进程,您可以使用内存缓存: + +| Parameter | Description | Default | Notes | +| ----------------------- | ---------------- | ------------------------------------------- | ---------------- | +| `redisServer` | Redis 服务器地址 | localhost | Redis 主机 | +| `redisPort` | Redis 端口 | 6379 | 标准 Redis 端口 | +| `redisDb` | Redis 数据库编号 | `0` | 0-15 | +| `redisUser` | Redis 用户名 | `null` | 用于 Redis6+ ACL | +| `redisPassword` | Redis 密码 | `null` | 身份验证 | +| `redisPortKey` | 端口跟踪键 | `_runtime_sandbox_container_occupied_ports` | 内部使用 | +| `redisContainerPoolKey` | 容器池键 | `_runtime_sandbox_container_container_pool` | 内部使用 | + +#### FileSystemConfig 配置 + +默认使用 `LocalFileSystemConfig`,本地条件下无需配置,使用 `oss` 情况下需配置 + +##### OSS 配置 + +使用[阿里云对象存储服务](https://www.aliyun.com/product/oss)进行分布式文件存储: + +| Parameter | Description | Default | Notes | +| -------------------- | ---------------- | ------- | --------------- | +| `ossEndpoint` | OSS 端点URL | `null` | 区域端点 | +| `ossAccessKeyId` | OSS 访问密钥 ID | `null` | 来自 OSS 控制台 | +| `ossAccessKeySecret` | OSS 访问密钥秘钥 | `null` | 保持安全 | +| `ossBucketName` | OSS 存储桶名称 | `null` | 预创建的存储桶 | + +#### ClientConfig 配置 + +默认使用本地 `Docker` 作为运行时环境,可以选择如下三种自定义配置方式 + +##### (可选)Docker 设置 + +要在沙盒服务器中配置特定 Docker 的设置,请在 `containerDeployment` 中传递 `DockerClientConfig` 参数 。可以考虑调整以下参数: + +| Parameter | Description | Default | Notes | +| ----------------- | ---------------------------- | --------- | ---------------------------------- | +| `portRange` | 沙箱服务可分配的**动态端口范围**(用于暴露容器内服务 | `(49152, 59152)` | 必须是未被占用的高端口范围;避免与系统服务冲突 | +| `host` | Docker 守护进程(Docker Daemon)的监听地址 | `localhost` | 若 Docker 运行在远程主机或 Docker Desktop,需设为对应 IP 或 socket 路径 | +| `port` | Docker 守护进程的 TCP 监听端口 | `2375` | ⚠️ 仅当 Docker 配置了 `tcp://0.0.0.0:2375`时使用;生产环境应禁用(不安全) | +| `certPath` | **TLS 证书目录路径**(用于安全连接 Docker Daemon) | `null` | 若启用了 TLS(端口通常为 `2376`),需提供包含 `ca.pem`, `cert.pem`, `key.pem` 的目录 | + +##### (可选)K8s 设置 + +要在沙盒服务器中配置特定于 Kubernetes 的设置,请在 `containerDeployment` 中传递 `KubernetesClientConfig` 参数。可以考虑调整以下参数: + +| Parameter | Description | Default | Notes | +| ---------------- | ------------------------------------------------------------ | --------- | ------------------------------------------------------------ | +| `namespace` | 沙箱 Pod、Service 等资源将被创建到的 **Kubernetes 命名空间** | `default` | 建议使用专用命名空间(如 `agentscope-sandbox`),便于隔离和清理 | +| `kubeConfigPath` | **kubeconfig 文件的本地路径**,用于认证和连接目标 Kubernetes 集群 | `None` | 若未指定,将尝试使用 `~/.kube/config` | + +##### (可选)AgentRun设置 + +AgentRun是阿里云推出的基于Serverless架构的智能Agent开发框架,提供了一套完整的工具集,帮助开发者快速构建、部署和管理AI Agent应用。您可将沙盒服务器部署到AgentRun上。 + +要在沙盒服务器中配置特定于 [AgentRun](https://functionai.console.aliyun.com/cn-hangzhou/agent/) 的设置,请在 `containerDeployment` 中传递 `AgentRunClientConfig` 参数。可以考虑调整以下参数: + +| Parameter | Description | Default | Notes | +|-------------------------------| ------------------------ |----------------------------------|-------------------------------------------------------------------------------------------| +| `agentRunAccountId` | 阿里云账号ID | `null` | 阿里云主账号ID,登录阿里云[RAM控制台](https://ram.console.aliyun.com/profile/access-keys)获取阿里云账号ID和AK、SK | +| `agentRunAccessKeyId` | 访问密钥ID | `null` | 阿里云AccessKey ID,需要`AliyunAgentRunFullAccess`权限 | +| `agentRunAccessKeySecret` | 访问密钥Secret | `null` | 阿里云AccessKey Secret | +| `agentRunRegionId` | 部署区域ID | `cn-hangzhou` | Agentrun部署地域ID | +| `agentRunCpu` | CPU规格 | `2.0f` | vCPU规格 | +| `agentRunMemory` | 内存规格 | `2048` | 内存规格 (MB) | +| `agentRunVpcId` | VPC ID | `null` | VPC网络ID(可选) | +| `agentRunVswitchIds` | 交换机ID列表 | `null` | VSwitch ID列表(可选) | +| `agentRunSecurityGroupId` | 安全组ID | `null` | 安全组ID(可选) | +| `agentRunPrefix` | 资源名称前缀 | `agentscope-sandbox_` | 创建的资源名称前缀 | +| `agentrunLogProject` | SLS日志项目 | `null` | SLS日志项目名称(可选) | +| `agentrunLogStore` | SLS日志库 | `null` | SLS日志库名称(可选) | + +### 导入自定义沙箱 + +除了默认提供的基础沙箱类型外,您还可以通过编写扩展模块并使用 `--extension` 参数加载,实现自定义沙箱的功能,例如修改镜像、增加环境变量、定义超时时间等。 + +#### 编写自定义沙箱扩展(例如 `CustomSandbox.java`) + +参考[自定义沙箱类](###创建自定义沙箱类) + +> - `@RegisterSandbox` 会将该类注册到沙箱管理器中,启动时可被识别和使用。 +> - `environment` 字段可以向沙箱注入外部 API Key 或其他必要配置。 +> - 类继承自 `Sandbox`,可覆盖其方法来实现更多自定义逻辑。 + +## 自定义构建沙箱 + +虽然内置沙箱类型涵盖了常见用例,但您可能会遇到需要专门环境或独特工具组合的场景。创建自定义沙箱允许您根据特定需求定制执行环境。本节演示如何构建和注册您的自定义沙箱类型。 + +### 从源码安装(自定义沙箱必需) + +要创建自定义沙箱,您需要使用Python版本 [AgentScope Runtime](https://github.com/agentscope-ai/agentscope-runtime)。以可编辑模式从源码安装 AgentScope Runtime,这允许您修改代码并立即看到更改: + +```bash +git clone https://github.com/agentscope-ai/agentscope-runtime.git +cd agentscope-runtime +git submodule update --init --recursive +pip install -e . +``` + +> 创建自定义沙箱时,`-e`(可编辑)标志是必需的,因为它允许您: +> - 修改沙箱代码并立即看到更改而无需重新安装 +> - 将您的自定义沙箱类添加到注册表中 +> - 迭代开发和测试自定义工具 + +### 创建自定义沙箱类 + +您可以定义自定义沙箱类型并将其注册到系统中以满足特殊需求。只需继承 `Sandbox` 并使用 `SandboxRegistry.register`装饰器,然后将文件放在 `src/agentscope_runtime/sandbox/custom` 中(例如,`src/agentscope_runtime/sandbox/custom/custom_sandbox.py`): + +```python +import os + +from typing import Optional + +from agentscope_runtime.sandbox.utils import build_image_uri +from agentscope_runtime.sandbox.registry import SandboxRegistry +from agentscope_runtime.sandbox.enums import SandboxType +from agentscope_runtime.sandbox.box.sandbox import Sandbox + +SANDBOXTYPE = "my_custom_sandbox" + + +@SandboxRegistry.register( + build_image_uri(f"runtime-sandbox-{SANDBOXTYPE}"), + sandbox_type=SANDBOXTYPE, + security_level="medium", + timeout=60, + description="my sandbox", + environment={ + "TAVILY_API_KEY": os.getenv("TAVILY_API_KEY", ""), + "AMAP_MAPS_API_KEY": os.getenv("AMAP_MAPS_API_KEY", ""), + }, +) +class MyCustomSandbox(Sandbox): + def __init__( + self, + sandbox_id: Optional[str] = None, + timeout: int = 3000, + base_url: Optional[str] = None, + bearer_token: Optional[str] = None, + ): + super().__init__( + sandbox_id, + timeout, + base_url, + bearer_token, + SandboxType(SANDBOXTYPE), + ) +``` + +### 准备Docker镜像 + +创建自定义沙箱还需要准备相应的 Docker镜像。镜像应包含您特定用例所需的所有依赖项、工具和配置。 + +**注意:构建镜像操作需要在 Python 代码仓库中进行** + +> **配置选项:** +> +> - **简单 MCP 服务器更改**:要简单更改沙箱中的初始MCP 服务器,请修改 `mcp_server_configs.json` 文件 +> - **高级定制**:对于更高级的用法和定制,您必须非常熟悉Dockerfile 语法和Docker 最佳实践 + +这里是一个自定义沙箱的Dockerfile 示例,它在一个沙箱中集成了文件系统、浏览器和一些有用的 MCP 工具: + + +```dockerfile +FROM node:22-slim + +# Set ENV variables +ENV NODE_ENV=production +ENV WORKSPACE_DIR=/workspace + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --fix-missing \ + curl \ + python3 \ + python3-pip \ + python3-venv \ + build-essential \ + libssl-dev \ + git \ + supervisor \ + vim \ + nginx \ + gettext-base + +WORKDIR /agentscope_runtime +RUN python3 -m venv venv +ENV PATH="/agentscope_runtime/venv/bin:$PATH" + +# Copy application files +COPY src/agentscope_runtime/sandbox/box/shared/app.py ./ +COPY src/agentscope_runtime/sandbox/box/shared/routers/ ./routers/ +COPY src/agentscope_runtime/sandbox/box/shared/dependencies/ ./dependencies/ +COPY src/agentscope_runtime/sandbox/box/shared/artifacts/ ./ext_services/artifacts/ +COPY examples/custom_sandbox/box/third_party/markdownify-mcp/ ./mcp_project/markdownify-mcp/ +COPY examples/custom_sandbox/box/third_party/steel-browser/ ./ext_services/steel-browser/ +COPY examples/custom_sandbox/box/ ./ + +RUN pip install -r requirements.txt + +# Install Google Chrome & fonts +RUN curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add - && \ + echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \ + apt-get update && apt-get install -y --fix-missing google-chrome-stable \ + google-chrome-stable \ + fonts-wqy-zenhei \ + fonts-wqy-microhei + +# Install steel browser +WORKDIR /agentscope_runtime/ext_services/steel-browser +RUN npm ci --omit=dev \ + && npm install -g webpack webpack-cli \ + && npm run build -w api \ + && rm -rf node_modules/.cache + +# Install artifacts backend +WORKDIR /agentscope_runtime/ext_services/artifacts +RUN npm install \ + && rm -rf node_modules/.cache + +# Install mcp_project/markdownify-mcp +WORKDIR /agentscope_runtime/mcp_project/markdownify-mcp +RUN npm install -g pnpm \ + && pnpm install \ + && pnpm run build \ + && rm -rf node_modules/.cache + +WORKDIR ${WORKSPACE_DIR} +RUN mv /agentscope_runtime/config/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +RUN mv /agentscope_runtime/config/nginx.conf.template /etc/nginx/nginx.conf.template +RUN git init \ + && chmod +x /agentscope_runtime/scripts/start.sh + +COPY .gitignore ${WORKSPACE_DIR} + +# MCP required environment variables +ENV TAVILY_API_KEY=123 +ENV AMAP_MAPS_API_KEY=123 + +# Cleanup to reduce image size +RUN pip cache purge \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /tmp/* \ + && rm -rf /var/tmp/* \ + && npm cache clean --force \ + && rm -rf ~/.npm/_cacache + +CMD ["/bin/sh", "-c", "envsubst '$SECRET_TOKEN' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf"] +``` + +### 构建您的自定义镜像 + +准备好Dockerfile 和自定义沙箱类后,使用内置构建器工具构建您的自定义沙箱镜像: + +```bash +runtime-sandbox-builder my_custom_sandbox --dockerfile_path examples/custom_sandbox/Dockerfile --extension PATH_TO_YOUR_SANDBOX_MODULE +``` + +**命令参数:** + +- `custom_sandbox`: 您的自定义沙箱镜像的名称/标签 +- `--dockerfile_path`: 您的自定义Dockerfile 的路径 +- `--extension`: 自定义沙箱模块的路径 + +构建完成后,您的自定义沙箱镜像将准备好与您定义的相应沙箱类一起使用。 + +#### 本地构建内置镜像 + +您也可以使用构建器在本地构建内置沙箱镜像: + +```bash +# 构建所有内置镜像 +runtime-sandbox-builder all + +# 构建基础镜像(约1GB) +runtime-sandbox-builder base + +# 构建GUI镜像(约2GB) +runtime-sandbox-builder gui + +# 构建浏览器镜像(约2GB) +runtime-sandbox-builder browser + +# 构建文件系统镜像(约2GB) +runtime-sandbox-builder filesystem + +# 构建移动端镜像(约3GB) +runtime-sandbox-builder mobile +``` + +上述命令在以下情况下很有用: + +- 在本地构建镜像而不是从Docker拉取 +- 在构建自己的镜像之前定制基础镜像 +- 确保您拥有内置镜像的最新版本 +- 在网络隔离的环境中工作 + +### 更改 Sandbox 镜像相关配置 + +Sandbox 模块运行所用的 Docker 镜像由以下三个环境变量共同决定,你可以根据需要修改其中任意一个,来改变镜像的来源或版本。 + +| 环境变量 | 作用 | 默认值 | 修改示例 | +| --------------------------------- | ------------------------------------------------------- | -------------- | ------------------------------------------------------------ | +| `RUNTIME_SANDBOX_REGISTRY` | 镜像注册中心地址(Registry)。为空表示使用 Docker Hub。 | `""` | `export RUNTIME_SANDBOX_REGISTRY="agentscope-registry.ap-southeast-1.cr.aliyuncs.com"` | +| `RUNTIME_SANDBOX_IMAGE_NAMESPACE` | 镜像命名空间(Namespace),类似账号名。 | `"agentscope"` | `export RUNTIME_SANDBOX_IMAGE_NAMESPACE="my_namespace"` | +| `RUNTIME_SANDBOX_IMAGE_TAG` | 镜像版本标签(Tag)。 | `"latest"` | `export RUNTIME_SANDBOX_IMAGE_TAG="my_custom"` | diff --git a/cookbook/new-zh/sandbox/sandbox.md b/cookbook/new-zh/sandbox/sandbox.md new file mode 100644 index 00000000..6ddc6e39 --- /dev/null +++ b/cookbook/new-zh/sandbox/sandbox.md @@ -0,0 +1,510 @@ +# 沙箱 + +AgentScope Runtime Java 的 Sandbox 提供了一个**安全**且**隔离**的环境,用于工具执行、浏览器自动化、文件系统操作、训练评测等功能。在本教程中,您将学习如何设置工具沙箱依赖项并在沙箱环境中运行工具。 + +## 前提条件 + +```{note} +当前的沙箱环境默认使用 Docker 进行隔离。此外,我们还支持 Kubernetes (K8s) 以及阿里云函数计算 AgentRun 作为远程服务后端。未来,我们计划在即将发布的版本中加入更多第三方托管解决方案。 +``` + + +>对于使用**苹果芯片**(如M1/M2)的设备,我们建议以下选项来运行**x86** Docker环境以获得最大兼容性: +> * Docker Desktop:请参阅[Docker Desktop安装指南](https://docs.docker.com/desktop/setup/install/mac-install/)以启用Rosetta2,确保与x86_64镜像的兼容性。 +> * Colima:确保启用Rosetta 2支持。您可以使用以下命令启动[Colima](https://github.com/abiosoft/colima)以实现兼容性:`colima start --vm-type=vz --vz-rosetta --memory 8 --cpu 1` + + +- Docker(默认) +- Kubernetes +- 阿里云函数计算 AgentRun + +## 安装 + +### 安装依赖项 + +首先,安装 AgentScope Runtime: + +```bash +pip install agentscope-runtime +``` + +### 准备 Docker 镜像 + +沙箱为不同功能使用不同的 Docker 镜像。您可以只拉取需要的镜像,或者拉取所有镜像以获得完整功能: + +#### 选项1:拉取所有镜像(推荐) + +为了确保完整的沙箱体验并启用所有功能,请按照以下步骤从我们的仓库拉取并标记必要的 Docker 镜像: + +> **镜像来源:阿里云容器镜像服务** +> +> 所有Docker镜像都托管在阿里云容器镜像服务(ACR)上,以在全球范围内实现可获取和可靠性。镜像从ACR拉取后使用标准名称重命名,以与AgentScope Runtime无缝集成。 + +```bash +# 基础镜像 +docker pull agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-base:latest && docker tag agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-base:latest agentscope/runtime-sandbox-base:latest + +# GUI镜像 +docker pull agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-gui:latest && docker tag agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-gui:latest agentscope/runtime-sandbox-gui:latest + +# 文件系统镜像 +docker pull agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-filesystem:latest && docker tag agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-filesystem:latest agentscope/runtime-sandbox-filesystem:latest + +# 浏览器镜像 +docker pull agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-browser:latest && docker tag agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-browser:latest agentscope/runtime-sandbox-browser:latest + +# 移动端镜像 +docker pull agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-mobile:latest && docker tag agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-mobile:latest agentscope/runtime-sandbox-mobile:latest +``` + +#### 选项2:拉取特定镜像 + +根据您的具体需求选择镜像: + +| Image | Purpose | When to Use | +| -------------------- | ------------------------- | ------------------------------------------------------------ | +| **Base Image** | Python代码执行,shell命令 | 基本工具执行必需 | +| **GUI Image** | 计算机操作 | 当你需要图形操作页面时 | +| **Filesystem Image** | 文件系统操作 | 当您需要文件读取/写入/管理时 | +| **Browser Image** | Web浏览器自动化 | 当您需要网络爬取或浏览器控制时 | +| **Mobile Image** | 移动端操作 | 当您需要操作移动端设备时 | +| **Training Image** | 训练和评估智能体 | 当你需要在某些基准数据集上训练和评估智能体时 (详情请参考 [训练用沙箱](training_sandbox.md) ) | + +### (可选)从头构建Docker镜像 + +如果您更倾向于在本地自己通过 `Dockerfile` 构建镜像或需要自定义修改,可以从头构建它们。请参阅 [工具沙箱高级用法](sandbox/advanced.md) 了解详细说明。 + +## 沙箱使用 + +### 创建沙箱 + +前面的部分介绍了以工具为中心的使用方法,而本节介绍以沙箱为中心的使用方法。 + +您可以通过`sandbox` SDK创建不同类型的沙箱。通过 `SandboxService` 管理沙箱生命周期,支持会话管理和沙箱复用。 + + +```java +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.box.BaseSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.DockerClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; + +import com.google.gson.Gson; + +public class Main { + public static void main(String[] args) { +// 创建并启动沙箱服务 + BaseClientConfig clientConfig = DockerClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) + ); + sandboxService.start(); + +// 连接沙箱(沙箱会在执行后自动删除) + try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", BaseSandbox.class)){ + Gson gson = new Gson(); + String tools = gson.toJson(sandbox.listTools("")); + System.out.println("Available tools: "); + System.out.println(tools); + } + catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +返回的结果如下所示: +```json +{ + "generic": { + "run_ipython_cell": { + "json_schema": { + "function": { + "name": "run_ipython_cell", + "description": "Run an IPython cell.", + "parameters": { + "type": "object", + "properties": { + "code": { + "description": "IPython code to execute", + "type": "string" + } + }, + "required": [ + "code" + ] + } + }, + "type": "function" + }, + "name": "run_ipython_cell" + }, + "run_shell_command": { + "json_schema": { + "function": { + "name": "run_shell_command", + "description": "Run a shell command.", + "parameters": { + "type": "object", + "properties": { + "command": { + "description": "Shell command to execute", + "type": "string" + } + }, + "required": [ + "command" + ] + } + }, + "type": "function" + }, + "name": "run_shell_command" + } + } +} +``` + +可以看到基础沙箱中提供了**运行命令**和**执行python代码**两种工具 + + +* **基础沙箱(Base Sandbox)**:用于在隔离环境中运行 **Python 代码** 或 **Shell 命令**。 + +```java +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", BaseSandbox.class)){ + System.out.println(sandbox.listTools("")); + if(sandbox instanceof BaseSandbox baseSandbox) { + String pythonResult = baseSandbox.runIpythonCell("print('Hello from the sandbox!')"); + System.out.println("Sandbox execution result: " + pythonResult); + String shellResult = baseSandbox.runShellCommand("echo Hello, World!"); + System.out.println("Shell command result: " + shellResult); + } +} +``` + +* **GUI 沙箱 (GUI Sandbox)**: 提供**可视化桌面环境**,可执行鼠标、键盘以及屏幕相关操作。 + + GUI Sandbox + +```json +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", GuiSandbox.class)){ + Gson gson = new Gson(); + String tools = gson.toJson(sandbox.listTools("")); + System.out.println("Available tools: "); + System.out.println(tools); + + if(sandbox instanceof GuiSandbox guiSandbox) { + String desktopUrl = guiSandbox.getDesktopUrl(); + System.out.println("GUI Desktop URL: " + desktopUrl); + String cursorPosition = guiSandbox.computerUse("get_cursor_position"); + System.out.println("Cursor Position: " + cursorPosition); + String screenShot = guiSandbox.computerUse("get_screenshot"); + System.out.println("Screenshot (base64): " + screenShot); + } +} +``` + +* **文件系统沙箱 (Filesystem Sandbox)**:基于 GUI 的隔离沙箱,可进行文件系统操作,如创建、读取和删除文件。 + + GUI Sandbox + +```java +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", FilesystemSandbox.class)){ + Gson gson = new Gson(); + String tools = gson.toJson(sandbox.listTools("")); + System.out.println("Available tools: "); + System.out.println(tools); + + if(sandbox instanceof FilesystemSandbox filesystemSandbox) { + String desktopUrl = filesystemSandbox.getDesktopUrl(); + System.out.println("GUI Desktop URL: " + desktopUrl); + String cursorPosition = filesystemSandbox.createDirectory("test"); + System.out.println("Created directory 'test' at: " + cursorPosition); + } +} +``` + +* **浏览器沙箱(Browser Sandbox)**: 基于 GUI 的沙箱,可进行浏览器操作。 + + GUI Sandbox + +```java +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", BrowserSandbox.class)){ + Gson gson = new Gson(); + String tools = gson.toJson(sandbox.listTools("")); + System.out.println("Available tools: "); + System.out.println(tools); + + if(sandbox instanceof BrowserSandbox browserSandbox) { + String desktopUrl = browserSandbox.getDesktopUrl(); + System.out.println("GUI Desktop URL: " + desktopUrl); + String navigateResult = browserSandbox.navigate("https://cn.bing.com"); + System.out.println("Navigate Result: " + navigateResult); + } +} +``` + +* **TrainingSandbox**:训练评估沙箱,详情请参考:[训练用沙箱](training_sandbox.md)。 + +```java +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", APPWorldSandbox.class)){ + if(sandbox instanceof APPWorldSandbox appWorldSandbox){ + String profileList = appWorldSandbox.getEnvProfile("appworld","train",null); + System.out.println("Profile List: " + profileList); + } else { + System.err.println("Failed to connect to TrainingSandbox."); + } +} +``` + +> 更多沙箱类型正在开发中,敬请期待! + +### 向沙箱添加MCP服务器 + +MCP(模型上下文协议)是一个标准化协议,使AI应用程序能够安全地连接到外部数据源和工具。通过将MCP服务器集成到您的沙箱中,您可以在不影响安全性的情况下使用专门的工具和服务扩展沙箱的功能。 + +沙箱支持通过`add_mcp_servers`方法集成MCP服务器。添加后,您可以使用`list_tools`发现可用工具并使用`call_tool`执行它们。 + +```java +try { + String mcpServerConfig = """ + { + "mcpServers": { + "time": { + "command": "uvx", + "args": [ + "mcp-server-time", + "--local-timezone=America/New_York" + ] + } + } + } + """; + List mcpTools = ToolkitInit.getMcpTools( + mcpServerConfig, + SandboxType.BASE, + sandboxService.getManagerApi()); + + System.out.println("MCP Tools: " + mcpTools); +} +``` + +### 连接到远程沙箱 + +> 沙箱远程部署特别适用于: +> * 将计算密集型任务分离到专用服务器 +> * 多个客户端共享同一沙箱环境 +> * 在资源受限的本地机器上开发,同时在高性能服务器上执行 +> * K8s 集群部署沙盒服务 +> +> 有关sandbox-server的更高级用法,请参阅[工具沙箱高级用法](sandbox_advanced.md)了解详细说明。 + +您可以在本地机器或不同机器上启动沙箱服务器,以便于远程访问。您可以先启动一个runtime,作为远程沙箱管理器 + +要连接到远程沙箱服务,只需要在构建managerConfig的时候添加远程runtime的实际启动地址,其余操作和本地沙箱相同,在进行沙箱管理和工具调用的时候会自动将操作转发到远程runtime处理: + +```java +ManagerConfig managerConfig = ManagerConfig.builder() + .baseUrl("http://remote-host:port") + .build(); +``` + +## 沙箱服务 + +### 使用沙箱服务管理沙箱 + +`SandboxService` 提供了统一的沙箱管理接口,支持通过 `session_id` 和 `user_id` 来管理不同用户会话的沙箱环境。使用 `SandboxService` 可以让您更好地控制沙箱的生命周期,并实现沙箱的复用。 + +```java +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.box.BaseSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.DockerClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; + +public class Main { + public static void main(String[] args) { +// 创建并启动沙箱服务 + BaseClientConfig clientConfig = DockerClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) + ); + sandboxService.start(); + + try { +// 连接到沙箱,指定需要的沙箱类型 + Sandbox sandbox = sandboxService.connect("sessionId", "userId", BaseSandbox.class); + if(sandbox instanceof BaseSandbox baseSandbox) { +// 直接在沙箱实例上调用工具方法 + String pythonResult = baseSandbox.runIpythonCell("a=1"); + System.out.println("Sandbox execution result: " + pythonResult); + } +// 使用相同的 session_id 和 user_id 会复用同一个沙箱实例 + sandbox = sandboxService.connect("sessionId", "userId", BaseSandbox.class); + if(sandbox instanceof BaseSandbox baseSandbox) { +// 变量 a 仍然存在,因为复用了同一个沙箱 + String pythonResult = baseSandbox.runIpythonCell("print(a)"); + System.out.println("Sandbox execution result: " + pythonResult); + } +// 停止沙箱服务 + sandbox.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +### 使用沙箱服务添加MCP服务器 + +```{code-cell} +from agentscope_runtime.engine.services.sandbox import SandboxService + +async def main(): + sandbox_service = SandboxService() + await sandbox_service.start() + + session_id = "session_mcp" + user_id = "user_mcp" + + sandboxes = sandbox_service.connect( + session_id=session_id, + user_id=user_id, + sandbox_types=["base"], + ) + + sandbox = sandboxes[0] + + mcp_server_configs = { + "mcpServers": { + "time": { + "command": "uvx", + "args": [ + "mcp-server-time", + "--local-timezone=America/New_York", + ], + }, + }, + } + + # 将MCP服务器添加到沙箱 + sandbox.add_mcp_servers(server_configs=mcp_server_configs) + + # 列出所有可用工具(现在包括MCP工具) + print(sandbox.list_tools()) + + # 使用MCP服务器提供的时间工具 + print( + sandbox.call_tool( + "get_current_time", + arguments={ + "timezone": "America/New_York", + }, + ), + ) + + await sandbox_service.stop() + +await main() +``` + +### 使用沙箱服务连接远程沙箱 + +```java +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.box.BaseSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.DockerClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; + +public class Main { + public static void main(String[] args) { +// 创建并启动沙箱服务 + ManagerConfig managerConfig = ManagerConfig.builder() + .baseUrl("http://remote-host:port") + .build(); + SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) + ); + sandboxService.start(); + + try { +// 连接到沙箱,指定需要的沙箱类型 + Sandbox sandbox = sandboxService.connect("sessionId", "userId", BaseSandbox.class); + if(sandbox instanceof BaseSandbox baseSandbox) { +// 直接在沙箱实例上调用工具方法 + String pythonResult = baseSandbox.runIpythonCell("a=1"); + System.out.println("Sandbox execution result: " + pythonResult); + } +// 停止沙箱服务 + sandbox.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +## 工具列表 + +* 基础工具(在所有沙箱类型中可用) +* 计算机操作工具(在`GuiSandbox`中可用) +* 文件系统工具(在`FilesystemSandbox`中可用) +* 浏览器工具(在`BrowserSandbox`中可用) + + +| 分类 | 工具名称 | 描述 | +| -------------------------------------- | ------------------------------------------- | ------------------------------------------------------------ | +| **基础工具** | `runIpythonCell(code: String)` | 在IPython环境中执行Python代码 | +| | `runShellCommand(command: String)` | 在沙箱中执行shell命令 | +| **文件系统工具** | `readFile(path: String)` | 读取文件的完整内容 | +| | `readMultipleFiles(paths: List)` | 同时读取多个文件 | +| | `writeFile(path: String, content: String)` | 创建或覆盖文件内容 | +| | `editFile(path: String, edits: Object[], dryRun: boolean)` | 对文本文件进行基于行的编辑 | +| | `createDirectory(path: String)` | 创建新目录 | +| | `listDirectory(path: String)` | 列出路径中的所有文件和目录 | +| | `directoryTree(path: String)` | 获取目录结构的递归树视图 | +| | `moveFile(source: String, destination: String)` | 移动或重命名文件和目录 | +| | `searchFiles(path: String, pattern: String, excludePatterns: String[])` | 搜索匹配模式的文件 | +| | `getFileInfo(path: String)` | 获取文件或目录的详细元数据 | +| | `listAllowedDirectories()` | 列出服务器可以访问的目录 | +| **浏览器工具** | `navigate(url: String)` | 导航到特定URL | +| | `navigateBack()` | 返回到上一页 | +| | `navigateForward()` | 前进到下一页 | +| | `closeBrowser()` | 关闭当前浏览器页面 | +| | `resize(width: Double, height: Double)` | 调整浏览器窗口大小 | +| | `click(element: String, ref: String)` | 点击Web元素 | +| | `type(element: String, ref: String, text: String)` | 在输入框中输入文本 | +| | `hover(element: String, ref: String)` | 悬停在Web元素上 | +| | `drag(startElement: String, startRef: String, endElement: String, endRef: String)` | 在元素之间拖拽 | +| | `selectOption(element: String, ref: String, values: String[])` | 在下拉菜单中选择选项 | +| | `pressKey(key: String)` | 按键盘按键 | +| | `fileUpload(paths: String[])` | 上传文件到页面 | +| | `snapshot()` | 捕获当前页面的可访问性快照 | +| | `takeScreenshot(raw: Boolean, filename: String, element: String, ref: String)` | 截取页面或元素的屏幕快照 | +| | `pdfSave(filename: String)` | 将当前页面保存为PDF | +| | `tabList()` | 列出所有打开的浏览器标签页 | +| | `tabNew(url: String)` | 打开新标签页 | +| | `tabSelect(index: Integer)` | 切换到特定标签页 | +| | `tabClose(index: Integer)` | 关闭标签页(如果未指定索引则关闭当前标签页) | +| | `waitFor(time: Double, text: String, textGone: String)` | 等待条件或时间流逝 | +| | `consoleMessages()` | 获取页面的所有控制台消息 | +| | `networkRequests()` | 获取页面加载以来的所有网络请求 | +| | `handleDialog(accept: Boolean, promptText: String)` | 处理浏览器对话框(警告、确认、提示) | +| **计算机操作工具** | `computerUse(action: String, coordinate: List, text: String)` | 使用鼠标和键盘与桌面 GUI 互动,支持以下操作:移动光标、点击、输入文字以及截图 | diff --git a/cookbook/new-zh/sandbox/training_sandbox.md b/cookbook/new-zh/sandbox/training_sandbox.md new file mode 100644 index 00000000..38024dae --- /dev/null +++ b/cookbook/new-zh/sandbox/training_sandbox.md @@ -0,0 +1,271 @@ +# 训练用沙箱 + +> 本节介绍训练沙箱的用法,我们强烈建议在继续之前先完成上一节的基础教程([沙箱](sandbox.md)) 中的 Docker 安装和注意事项。 + +## 背景介绍 + +AgentScope Runtime Java 的 Training Sandbox 主要用于训练评测的功能。训练沙箱的数据主要基于公开数据集(如 Appworld、 Webshop、 BFCL 等),提供用于 Agent 训练的数据供给、 Agent 使用数据集内供给的工具调用、实时的 Reward 验证。 + +训练用沙箱内主要通过 Ray 实现高并发的数据调用,在创建沙箱后,支持外部 Agent 高并发对不同样本的实例创建、执行、评测。 + ++ [APPWorld](https://github.com/StonyBrookNLP/appworld):APPWorld 是一个高效的测试环境,用于测试和评估 AI Agent 在执行复杂多步骤任务的能力。 ++ [BFCL](https://github.com/ShishirPatil/gorilla):BFCL 是首个专门评估大语言模型(LLMs)函数调用能力的全面且可执行的评测平台。与以往的评测不同,BFCL涵盖了多种形式的函数调用、丰富的场景,并关注函数调用的可执行性。 + +## 安装 + +### Appworld 案例 +#### 拉取所需的镜像 + +请按照以下步骤从我们的仓库拉取并标记必要的训练用沙盒Docker镜像: + +> **镜像来源:阿里云容器镜像服务** +> +> 所有 Docker 镜像都托管在阿里云容器镜像服务(ACR)上,以在全球范围内实现可获取和可靠性。镜像从ACR拉取后使用标准名称重命名,以与 AgentScope Runtime Java 无缝集成。 + +```bash +# 从 DockerHub 拉取 Appworld 镜像 +docker pull agentscope/runtime-sandbox-appworld:latest + +# 从 ACR 拉取 Appworld 镜像并打标签 +docker pull agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-appworld:latest && docker tag agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-appworld:latest agentscope/runtime-sandbox-appworld:latest +``` + +#### 验证安装 + +您可以通过调用`getEnvProfile`来验证一切设置是否正确,如果正确将返回数据集ID: + +```python +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.box.BaseSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.DockerClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; + +import com.google.gson.Gson; + +public class Main { + public static void main(String[] args) { +// 创建并启动沙箱服务 + BaseClientConfig clientConfig = DockerClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) + ); + sandboxService.start(); + +// 连接沙箱(沙箱会在执行后自动删除) + try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", APPWorldSandbox.class)){ + if(sandbox instanceof APPWorldSandbox appWorldSandbox){ + String profileList = appWorldSandbox.getEnvProfile("appworld","train", null); + System.out.println("Profile List: " + profileList); + } else { + System.err.println("Failed to connect to TrainingSandbox."); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +#### (可选)从头构建Docker镜像 + +如果您更倾向于在本地自己通过`Dockerfile`构建镜像或需要自定义修改,可以从头构建它们。请参阅[工具沙箱高级用法](advanced.md)了解详细说明。 + +对于训练用沙箱,不同数据集使用不同的DockerFile,其路径在 Python 版本 [AgentScope Runtime](https://github.com/agentscope-ai/agentscope-runtime) 仓库`src/agentscope_runtime/sandbox/box/training_box/environments/{dataset_name}` 目录下 + +以appworld为例: + +```bash +docker build -f src/agentscope_runtime/sandbox/box/training_box/environments/appworld/Dockerfile -t agentscope/runtime-sandbox-appworld:latest . +``` + +#### 训练样本使用 + +您可以创建某一个具体的训练用沙箱(默认为 `Appworld` ),随后可以并行创建多个不同的训练样本,并且分别执行、评测。 + +#### 查看数据集样本 + +构建 Docker 镜像后,我们可以首先查看数据集样本。 + +例如,我们可以使用 `getEnvProfile` 方法获取训练ID列表。 + +```java +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", APPWorldSandbox.class)){ + if(sandbox instanceof APPWorldSandbox appWorldSandbox){ + String profileList = appWorldSandbox.getEnvProfile("appworld","train", null); + System.out.println("Profile List: " + profileList); + } else { + System.err.println("Failed to connect to TrainingSandbox."); + } +} +``` + +#### 创建训练样本 + +以取训练集中的第1个query为例,可以通过`createInstance`创建1个训练实例(Instance),并分配了一个实例ID(Instance ID)。 +一个Query可以创建多个实例,一个实例唯一对应一个训练样本(基于您创建时,指定的样本ID) +其中,训练集提供的prompt (`system prompt`) 和 实际问题 (`user prompt`) 均会以`Message List`返回,具体位置于返回值的`state` +中 + +```java +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", APPWorldSandbox.class)){ + if(sandbox instanceof APPWorldSandbox appWorldSandbox){ + String profileList = appWorldSandbox.getEnvProfile("appworld","train", null); + System.out.println("Profile List: " + profileList); + + Gson gson = new Gson(); + Type listType = new TypeToken>(){}.getType(); + List list = gson.fromJson(profileList, listType); + String initResponse = appWorldSandbox.createInstance("appworld", list.get(0)); + Type instanceType = new TypeToken>(){}.getType(); + Map instance = gson.fromJson(initResponse, instanceType); + String instanceInfo = instance.get("info").toString(); + Type infoType = new TypeToken>(){}.getType(); + Map infoMap = gson.fromJson(instanceInfo, infoType); + String instanceId = (String) infoMap.get("instance_id"); + String query = instance.get("state").toString(); + System.out.println("Created instance " + instanceId + " with query: " + query); + } else { + System.err.println("Failed to connect to TrainingSandbox."); + } +} +``` + +#### 使用训练样本 + +使用`step`方法,并指定具体的`instanceId`和`action`,可以得到环境内反馈结果。 +该方法目前仅支持输入Message格式,建议以` "role": "assistant"` 方式输入。 + +```java +Map action = Map.of( + "role", "assistant", + "content", "```python\\nprint('hello appworld!!')\\n```" +); +String result = appWorldSandbox.step(instanceId, action, null); +System.out.println("Step Result: " + result); +``` + +#### 评测训练样本 + +使用`evaluate`方法,并评测某个实例的状态,并获取`Reward`。不同的数据集可能含有额外的测评参数,通过`params`传入。 + +```python +String score = appWorldSandbox.evaluate(instanceId, Map.of(), Map.of("sparse", true)); +System.out.println("Evaluation Score: " + score); +``` + +#### 释放训练样本 + +为了减少内存开销,建议在使用完样本后使用`releaseInstance`方法。 +同时,在训练用沙箱运行期间,每5分钟亦会定期清除非活跃实例。 + +```java +String success = appWorldSandbox.releaseInstance(instanceId); +System.out.println("Instance released: " + success); +``` + +### BFCL案例 + +#### 拉取所需的镜像 +请按照以下步骤从我们的仓库拉取并标记必要的训练用沙盒Docker镜像: + +> **镜像来源:阿里云容器镜像服务** +> +> 所有 Docker 镜像都托管在阿里云容器镜像服务(ACR)上,以在全球范围内实现可获取和可靠性。镜像从 ACR 拉取后使用标准名称重命名,以与 AgentScope Runtime Java 无缝集成。 + +```bash +# 从 DockerHub 拉取 BFCL 镜像 +docker pull agentscope/runtime-sandbox-bfcl:latest + +# 从 ACR 拉取 BFCL 镜像并打标签 +docker pull agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-bfcl:latest && docker tag agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/runtime-sandbox-bfcl:latest agentscope/runtime-sandbox-bfcl:latest +``` + +
+ (可选) 建立自己的Docker镜像 + 在 AgentScope Runtime Python 根目录运行以下代码: +
+ + +```bash +docker build -f src/agentscope_runtime/sandbox/box/training_box/environments/bfcl/Dockerfile -t agentscope/runtime-sandbox-bfcl:latest . +``` + + + +#### 初始化 +BFCL 有多个子数据库 *all, all_scoring, multi_turn, single_turn, live, non_live, non_python, python*.在初始化沙盒前请选择一个数据库,然后填上自己的openai_api_key。 + +**注意:这里需要设置 OPENAI_API_KEY 以及 DATASET_SUB_TYPE 环境变量,二者默认分别为 "" 和 "multi_turn"** + + +```java +try (Sandbox sandbox = sandboxService.connect("sessionId", "userId", BFCLSandbox.class)){ + if(sandbox instanceof BFCLSandbox bfclSandbox){ + String profileList = bfclSandbox.getEnvProfile("bfcl"); + System.out.println("Connected to BFCLSandbox. Profile List: " + profileList); + + Gson gson = new Gson(); + Type listType = new TypeToken>(){}.getType(); + List list = gson.fromJson(profileList, listType); + String initResponse = bfclSandbox.createInstance("bfcl", list.get(0)); + Type instanceType = new TypeToken>(){}.getType(); + Map instance = gson.fromJson(initResponse, instanceType); + String instanceInfo = instance.get("info").toString(); + Type infoType = new TypeToken>(){}.getType(); + Map infoMap = gson.fromJson(instanceInfo, infoType); + String instanceId = (String) infoMap.get("instance_id"); + String query = instance.get("state").toString(); + System.out.println("Created instance " + instanceId + " with query: " + query); + } else { + System.err.println("Failed to connect to TrainingSandbox."); + } +} +``` + +#### 使用训练样本 +参考以下模拟的对话: +
模拟对话 + +```java +List> assistantMessages = List.of( + Map.of("role", "assistant", "content", "'\\n{\"name\": \"cd\", \"arguments\": {\"folder\": \"document\"}}\\n\\n\\n{\"name\": \"mkdir\", \"arguments\": {\"dir_name\": \"temp\"}}\\n\\n\\n{\"name\": \"mv\", \"arguments\": {\"source\": \"final_report.pdf\", \"destination\": \"temp\"}}\\n'"), + Map.of("role", "assistant", "content", "'ok.1'"), + Map.of("role", "assistant", "content", "'\\n{\"name\": \"cd\", \"arguments\": {\"folder\": \"temp\"}}\\n\\n\\n{\"name\": \"grep\", \"arguments\": {\"file_name\": \"final_report.pdf\", \"pattern\": \"budget analysis\"}}\\n'"), + Map.of("role", "assistant", "content", "'ok.2'"), + Map.of("role", "assistant", "content", "'\\n{\"name\": \"sort\", \"arguments\": {\"file_name\": \"final_report.pdf\"}}\\n'"), + Map.of("role", "assistant", "content", "'ok.2'"), + Map.of("role", "assistant", "content", "'\\n{\"name\": \"cd\", \"arguments\": {\"folder\": \"..\"}}\\n\\n\\n{\"name\": \"mv\", \"arguments\": {\"source\": \"previous_report.pdf\", \"destination\": \"temp\"}}\\n\\n\\n{\"name\": \"cd\", \"arguments\": {\"folder\": \"temp\"}}\\n\\n\\n{\"name\": \"diff\", \"arguments\": {\"file_name1\": \"final_report.pdf\", \"file_name2\": \"previous_report.pdf\"}}\\n'"), + Map.of("role", "assistant", "content", "'ok.2'") +); +``` + +
+ +```python +for (int i = 1; i <= assistantMessages.size(); ++i) { + Map msg = assistantMessages.get(i); + String response = bfclSandbox.step(instanceId, msg, null); + Map responseMap = gson.fromJson(response, instanceType); + System.out.println("[ TURN " + i + " term=" + responseMap.get("is_terminated") + " reward=" + responseMap.get("reward") + "\n state: " + responseMap.getOrDefault("state", "")); + if((boolean) responseMap.get("is_terminated")){ + break; + } +} +``` + +#### 评估实例 +```python +String score = bfclSandbox.evaluate(instanceId, Map.of("sparse", true), null); +System.out.println("[RESULT] sparse_score = " + score); +``` +#### 释放实例 +```java +bfclSandbox.releaseInstance(instanceId); +``` diff --git a/cookbook/new-zh/sandbox/troubleshooting.md b/cookbook/new-zh/sandbox/troubleshooting.md new file mode 100644 index 00000000..d410adcb --- /dev/null +++ b/cookbook/new-zh/sandbox/troubleshooting.md @@ -0,0 +1,72 @@ +# 沙盒故障排除 +如果您在使用浏览器模块时遇到任何问题,以下是一些故障排查步骤: + +## Docker 连接错误 + +如果您遇到以下错误: + +```bash +警告: Failed to connect to Docker using configured host (localhost:2375): com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.HttpHostConnectException: Connect to http://localhost:2375 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused +12月 10, 2025 2:02:43 下午 io.agentscope.runtime.sandbox.manager.client.DockerClient openDockerClient +信息: Falling back to default Docker configuration +14:02:43.470 [main] INFO com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec -- Recoverable I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:2375 +``` + +此错误通常表示 Docker Java SDK 无法连接到 Docker 服务。如果您使用的是 Colima ,需要确保 Docker Java SDK 配置为使用 Colima 的 Docker 服务。您可以通过设置 `DOCKER_HOST` 环境变量来实现: + +```bash +export DOCKER_HOST=unix://$HOME/.colima/docker.sock +``` + +设置 `DOCKER_HOST` 环境变量后,请重新尝试运行您的命令。这应该可以解决连接问题。 + +## 沙盒启动超时 + +如果您遇到以下错误: + +```bash +Failed to establish connection to sandbox: Sandbox service did not start within timeout: 60s +``` + +说明沙盒健康检查失败,你可能需要登录到容器中查看日志,以便进行进一步的故障排查。 + +1. **列出正在运行的容器** + + ```bash + docker ps + ``` + + 找到与你的沙盒相关的容器,记下它的 **CONTAINER ID** 或 **NAMES**。 + +2. **进入容器** + + ```bash + docker exec -it /bin/bash + ``` + +3. **进入日志目录** + + ```bash + cd /var/log && ls -l + ``` + +4. **识别并查看日志文件** + + - `agentscope_runtime.err.log` — `agentscope_runtime` 服务的错误输出 + - `agentscope_runtime.out.log` — `agentscope_runtime` 服务的标准输出 + - `supervisord.log` — Supervisor 进程管理日志 + - `nginx.err.log` — Nginx 错误日志 + - `nginx.out.log` — Nginx 访问/标准输出日志 + + 查看日志的示例命令: + + ```bash + cat agentscope_runtime.err.log + ``` + +5. **常见的日志提示** + + - 如果看到缺少环境变量的错误,请确保运行沙盒管理器的环境中已设置所需的 API 密钥。 + - 如果看到网络错误,请检查防火墙、代理或云端 Shell 网络设置。 + +> 在容器内部查看日志通常是最快定位沙盒健康检查失败原因的方法。 diff --git a/cookbook/new-zh/service/memory.md b/cookbook/new-zh/service/memory.md new file mode 100644 index 00000000..fc0efa22 --- /dev/null +++ b/cookbook/new-zh/service/memory.md @@ -0,0 +1,117 @@ +# 记忆服务 + +## 概述 + +**记忆服务**(Memory Service)用于管理智能体的**长期记忆**,将用户对话和其他关联信息进行存储、检索、管理,以便在后续交互中进行知识引用、个性化回应或者任务跟踪。 + +与**会话历史服务**的区别在于: + +- 会话历史服务主要保存**短期上下文**(最近几轮对话) +- 记忆服务则可以保存**长期、跨会话**的信息,例如用户的偏好、长期任务计划、知识库等 + +记忆服务的核心接口定义了四种重要功能: + +- **新增记忆**:将一批消息或信息存入记忆存储 +- **搜索记忆**:根据当前用户的查询或上下文内容筛选相关信息 +- **列出记忆**:支持分页遍历某用户的所有记忆内容 +- **删除记忆**:清理指定会话或用户的全部记忆 + +类似会话历史服务,记忆服务也有多种后端实现,支持不同的存储方式和生产级需求。 + +> 推荐总是通过**适配器(adapter)**使用记忆服务,而不是在业务逻辑中直接调用底层实现类。 +> 这样可以: +> +> - 无感知切换存储类型 +> - 生命周期由框架统一管理 +> - 与业务逻辑解耦 + +## 在 AgentScope 中使用 Adapter + +在 **AgentScope** 框架中,可以通过 `LongTermMemoryAdapter` 或其他记忆适配器,将底层 `MemoryService` 封装为智能体的 **LongTermMemory** 模块进行调用: + +```java +import io.agentscope.runtime.adapters.agentscope.memory.LongTermMemoryAdapter; +import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService; +import io.agentscope.runtime.engine.services.memory.service.MemoryService; + +public class Main { + public static void main(String[] args) { +// 选择后端实现(此例为 InMemory,方便本地测试) + MemoryService memoryService = new InMemoryMemoryService(); + LongTermMemoryAdapter longTermMemory = null; + +// 用 adapter 包装,绑定到 LongTermMemory 模块 + longTermMemory = new LongTermMemoryAdapter( + memoryService, + "User1", + "Test Session" + ); + +// 之后在 Agent 内即可直接使用 long_term_memory 存取跨会话的长期记忆 + } +} +``` + +## 可选的后端实现类型 + +虽然通过适配器使用时无需关心底层调用,但为了配置和选型,需要了解可用实现类型的特点: + +| 服务类型 | 导入路径 | 存储位置 | 持久化 | 生产可用性 | 特点 & 优缺点 | 适用场景 | +| --------------------------- | ------------------------------------------------------------ | ----------------- | --------------- | ---------- | ------------------------------------------- | ------------------------------ | +| **InMemoryMemoryService** | `import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService` | 进程内存 | ❌ 否 | ❌ | 高速、无依赖;进程退出即丢失 | 开发调试、单元测试 | +| **RedisMemoryService** | `import io.agentscope.runtime.engine.services.memory.persistence.memory.service.RedisMemoryService` | Redis 内存数据库 | ✅ 是(RDB/AOF) | ✅ | 高性能、跨进程共享、可集群;需运维 Redis | 高性能生产部署、分布式共享记忆 | +| **TablestoreMemoryService** | `import io.agentscope.runtime.engine.services.memory.persistence.memory.service.TableStoreMemoryService` | 阿里云 Tablestore | ✅ 是 | ✅ | 海量存储、全文/向量检索、高可用;需云资源 | 企业级生产、长期知识存档 | +| **Mem0MemoryService** | `import io.agentscope.runtime.engine.services.memory.persistence.memory.service.Mem0MemoryService` | mem0.ai 云服务 | ✅ 是 | ✅ | 内置 AI 语义记忆,外部 API 调用;需 API key | 语义化长期记忆、智能化匹配 | + +## 切换不同实现的方法 + +适配器让切换存储后端非常简单,只需替换 `service` 实例即可。 + +### 示例:切换为 Redis 存储 + +```java +RedisTemplate redisTemplate = new RedisTemplate<>(); +// 配置redisTemplate的连接工厂等属性 +MemoryService memoryService = new RedisMemoryService(redisTemplate); + +LongTermMemoryAdapter longTermMemory = null; +longTermMemory = new LongTermMemoryAdapter( + memoryService, + "User1", + "Test Session" +); +``` + +### 示例:切换为 Tablestore 存储 + +```java +SyncClient client = new SyncClient( + "https://your-instance.cn-region.ots.aliyuncs.com", + "your-access-key-id", + "your-access-key-secret", + "your-instance-name" +); +MemoryService memoryService = new TableStoreMemoryService(client); + +LongTermMemoryAdapter longTermMemory = null; +longTermMemory = new LongTermMemoryAdapter( + memoryService, + "User1", + "Test Session" +); +``` + +## 选型建议 + +- **开发调试 / 原型验证** → `InMemoryMemoryService` +- **生产环境高性能共享记忆** → `RedisMemoryService` +- **企业级生产 & 海量长期存储** → `TablestoreMemoryService` +- **需要语义查询的智能记忆** → `Mem0MemoryService` + +------ + +## 小结 + +- 记忆服务是**跨会话、长期知识存储**的核心组件 +- 使用 `LongTermMemoryAdapter` 适配器,可与业务逻辑解耦 +- 多种后端实现可按场景灵活选择、随时切换 diff --git a/cookbook/new-zh/service/sandbox.md b/cookbook/new-zh/service/sandbox.md new file mode 100644 index 00000000..16ca3c4e --- /dev/null +++ b/cookbook/new-zh/service/sandbox.md @@ -0,0 +1,152 @@ +# 沙箱服务 + +## 概述 + +**沙箱服务**用于为不同用户和会话提供隔离的**工具执行环境**(sandbox),让智能体可以在受控的安全环境中使用工具(如浏览器、代码执行器等)。关于沙箱,请参考[沙箱](../sandbox/sandbox.md) + +在智能体运行过程中,沙箱服务的典型作用包括: + +- **创建执行环境**:为一个新的用户/会话生成对应的沙箱实例(如浏览器沙箱)。 +- **连接已有环境**:在对话多轮执行过程中,智能体连接到之前的沙箱继续操作。 +- **工具调用**:提供可调用的方法(如 `navigate`、`takeScreenshot` 等),可在 Agent 内注册为工具。 +- **释放环境**:会话结束或需求变化时,释放对应环境资源。 +- **多类型支持**:支持不同类型的沙箱(`BASE`、`BROWSER`、`FILESYSTEM`、`GUI` 等)。 + +沙箱服务在不同实现中,差异主要体现在: +**运行模式**(嵌入式/远程)、**支持的类型**、**管理方式**以及**可扩展性**。 + +> 在业务代码中,不建议直接编写沙箱服务与 `SandboxManager` 的底层管理逻辑。 +> +> 更推荐 **使用AgentScope Runtime Java封装好的沙箱方法** 绑定到智能体框架的工具模块**: +> - 屏蔽底层沙箱 API 细节 +> - 由 Runner/Engine 统一管理生命周期 +> - 保证切换运行模式或沙箱类型时不影响业务逻辑 + +## 在 AgentScope 中使用沙箱工具 + +在 **AgentScope** 框架中,我们可用 **封装的沙箱方法**(`ToolkitInit`),并注册到 Agent 的 `Toolkit` 中: + +```java +import io.agentscope.core.tool.Toolkit; +import io.agentscope.runtime.engine.agents.agentscope.tools.ToolkitInit; +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.box.BrowserSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.KubernetesClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; + +public class Main { + public static void main(String[] args) { +// 1. 启动服务(通常由 Runner/Engine 托管) + BaseClientConfig clientConfig = KubernetesClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) + ); + sandboxService.start(); + +// 2. 连接或创建沙箱(此处创建浏览器类型) + Sandbox sandbox = sandboxService.connect("TestSession", "User1", BrowserSandbox.class); + +// 3. 获取工具方法并注册到 Agent 的 Toolkit + Toolkit toolkit = new Toolkit(); + toolkit.registerTool(ToolkitInit.BrowserNavigateTool(sandbox)); + toolkit.registerTool(ToolkitInit.BrowserTakeScreenshotTool(sandbox)); + +// 此后,Agent 即可调用这些工具在沙箱中进行安全操作 + } +} +``` + +## 可选运行模式与类型 + +### 1. **嵌入式模式(Embedded Mode)** + +- **特点**:沙箱管理器与 AgentScope Runtime Java 在同一进程中运行。 +- **配置**:`baseUrl=null` +- **优点**:部署简单,无需外部 API;适合本地开发和单机测试。 +- **缺点**:进程退出则环境释放;不适合分布式部署。 + +### 2. **远程 API 模式** + +- **特点**:通过沙箱管理 API(`SandboxManager`)连接远程沙箱实例。 +- **配置**:`baseUrl="http://host:port"`, `bearerToken="..."` +- **优点**:可跨进程/跨机器共享环境,支持分布式扩展。 +- **缺点**:需要部署和运维远程沙箱管理服务。 + +### 支持的沙箱类型 + +| 类型值 | 功能描述 | 常见用途示例 | +| ------------ | ---------------------------- | ----------------------------------------- | +| `DUMMY` | 空实现/占位沙箱 | 测试流程,模拟沙箱接口但不执行实际操作 | +| `BASE` | 基础沙箱环境 | 通用工具运行环境 | +| `BROWSER` | 浏览器沙箱 | 网页导航、截图、数据抓取 | +| `FILESYSTEM` | 文件系统沙箱 | 在安全隔离的文件系统中读写文件 | +| `GUI` | 图形界面沙箱 | 与 GUI 应用交互(点击、输入、截屏) | +| `APPWORLD` | 应用世界仿真沙箱 | 在虚拟环境中模拟跨应用交互 | +| `BFCL` | BFCL(特定业务领域执行环境) | 运行业务流程脚本(具体取决于实现) | + +## 切换运行模式示例 + +### **嵌入式模式(适合开发测试)** + +```java +// 本地模式(默认使用本地 Docker) +ManagerConfig managerConfig = ManagerConfig.builder() + .build(); +SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) +); +sandboxService.start(); + +Sandbox sandbox = sandboxService.connect("DevSession", "User1", BrowserSandbox.class); +``` + +### **远程模式(适合生产部署)** + +```java +// 本地模式(默认使用本地 Docker) +ManagerConfig managerConfig = ManagerConfig.builder() + .baseUrl("https://sandbox-manager.com") + .bearerToken("YOUR_AUTH_TOKEN") + .build(); +SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) +); +sandboxService.start(); + +Sandbox sandbox = sandboxService.connect("ProdSession", "UserABC", BrowserSandbox.class); +``` + +### 释放环境 + +会话结束时显式释放资源: + +```java +// 通过容器名称直接释放 +sandboxService.getManagerApi().release("container_name"); +``` + +## 选型建议 + +- 快速原型 / 单机开发调试: + - 嵌入式模式 (`baseUrl=null`) + - 选用 `BROWSER`/`BASE` 类型按需创建 +- 生产环境 / 多用户分布式: + - 远程 API 模式(需部署 `SandboxManager` 服务) + - 考虑集群和认证机制(`bearerToken`) +- 安全或隔离要求高的场景: + - 为不同用户会话创建独立沙箱 + - 使用 `release()` 及时释放资源 + +## 小结 + +- **SandboxService** 是管理沙箱执行环境的核心组件,支持多类型环境。 +- 推荐通过 **封装的沙箱方法**(`ToolkitInit`)将沙箱方法注册到工具模块,避免直接操作底层 API。 +- 可选 **嵌入式模式**(简单,单机)或 **远程模式**(可扩展,生产级)。 +- 生命周期由 `Runner/Engine` 管理,确保启动、健康检查与释放一致。 +- 切换模式或类型只需更换服务初始化参数,不影响 Agent 业务逻辑。 diff --git a/cookbook/new-zh/service/service.md b/cookbook/new-zh/service/service.md new file mode 100644 index 00000000..9ffbdda8 --- /dev/null +++ b/cookbook/new-zh/service/service.md @@ -0,0 +1,203 @@ +# 服务与适配器 + +## 概述 + +AgentScope Runtime Java 中的服务(`Service`)为智能体运行环境提供核心能力,包括: + +- **会话历史管理** +- **记忆存储** +- **沙箱管理** +- **智能体状态管理** + +所有服务都实现了统一的抽象接口 `ServiceWithLifecycleManager`(生命周期管理模式),提供标准方法: + +- `start()`:启动服务 +- `stop()`:停止服务 +- `health()`:检查服务健康状态 + +> 在实际编写智能体应用时,我们通常**不会直接操作这些服务的各种底层方法**,而是通过 **框架适配器Adapters** 来使用。适配器会: +> +> 1. 负责把 Runtime 的服务对象注入到智能体框架的兼容模块中 +> 2. 让框架内的 agent 能无缝调用 Runtime 提供的功能(如会话记忆、工具沙箱等) +> 3. 保证服务生命周期与 Runner/Engine 一致 + +## 为什么要通过适配器使用服务? + +- **解耦**:智能体框架不用直接感知底层服务实现 +- **跨框架复用**:相同的服务可以接入不同的智能体框架 +- **统一生命周期**:Runner/Engine 统一启动和关闭所有服务 +- **增强可维护性**:更换服务实现(如切换为数据库存储)时,无需修改智能体业务代码 + +## 可用服务及适配器用法 + +### 1. 会话历史服务(SessionHistoryService) + +管理用户-智能体的对话会话,存储并检索会话消息历史。 + +#### AgentScope用法 + +在 AgentScope 框架中,通过 Runtime 的 `MemoryAdapter` 适配器来绑定会话历史服务到 `Memory` 模块: + +```java +import io.agentscope.runtime.adapters.agentscope.memory.MemoryAdapter; +import io.agentscope.runtime.engine.services.memory.persistence.session.InMemorySessionHistoryService; +import io.agentscope.runtime.engine.services.memory.service.SessionHistoryService; + +public class Main { + public static void main(String[] args) { + SessionHistoryService sessionHistoryService = new InMemorySessionHistoryService(); + MemoryAdapter memory = null; + + memory = new MemoryAdapter( + sessionHistoryService, + "User1", + "TestSession" + ); + } +} +``` + +更多可用服务类型与详细的用法请参见[会话历史服务](session_history.md)。 + +### 2. 记忆服务(MemoryService) + +`MemoryService` 管理长期记忆存储。在Agent 中,记忆储存终端用户之前的对话。 例如,终端用户可能在之前的对话中提到他们的姓名。 记忆服务通常用来**跨会话**的存储这些信息,以便智能体在下次对话中使用。 + +#### AgentScope用法 + +在 AgentScope 框架中,通过Runtime的`AgentScopeLongTermMemory`适配器来绑定会话历史服务到`LongTermMemory`模块: + +```java +import io.agentscope.runtime.adapters.agentscope.memory.LongTermMemoryAdapter; +import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService; +import io.agentscope.runtime.engine.services.memory.service.MemoryService; + +public class Main { + public static void main(String[] args) { + MemoryService memoryService = new InMemoryMemoryService(); + LongTermMemoryAdapter longTermMemory = null; + + longTermMemory = new LongTermMemoryAdapter( + memoryService, + "User1", + "Test Session" + ); + } +} +``` + +更多可用服务类型与详细的用法请参见[记忆服务](memory.md)。 + +### 3. 沙箱服务(SandboxService) + +**沙箱服务** 管理并为不同用户和会话提供沙箱化工具执行环境的访问。沙箱通过会话ID和用户ID的复合键组织,为每个用户会话提供隔离的执行环境。 + +#### AgentScope用法 + +在 AgentScope 框架中,通过Runtime **封装的沙箱方法**(`ToolkitInit`) 来绑定沙箱服务提供的沙箱的方法到`ToolKit`模块: + +```java +import io.agentscope.core.tool.Toolkit; +import io.agentscope.runtime.engine.agents.agentscope.tools.ToolkitInit; +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.box.BrowserSandbox; +import io.agentscope.runtime.sandbox.box.Sandbox; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.KubernetesClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; + +public class Main { + public static void main(String[] args) { + BaseClientConfig clientConfig = KubernetesClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + SandboxService sandboxService = new SandboxService( + new SandboxManager(managerConfig) + ); + sandboxService.start(); + + Sandbox sandbox = sandboxService.connect("TestSession", "User1", BrowserSandbox.class); + + Toolkit toolkit = new Toolkit(); + toolkit.registerTool(ToolkitInit.BrowserNavigateTool(sandbox)); + toolkit.registerTool(ToolkitInit.BrowserTakeScreenshotTool(sandbox)); + } +} +``` + +更多可用服务类型与详细的用法请参见[沙箱服务](sandbox.md)。 + +### 4. StateService + +存取智能体的可序列化状态,让智能体在多轮会话甚至跨会话间保持上下文。 + +#### AgentScope用法 + +在 AgentScope 框架中,无需通过适配器,直接调用`StateService`的`export_state`和`save_state`来保: + +```{code-cell} +from agentscope_runtime.engine.services.agent_state import InMemoryStateService + +state_service = InMemoryStateService() +state = await state_service.export_state(session_id, user_id) +agent.load_state_dict(state) + +await state_service.save_state(session_id, user_id, state=agent.state_dict()) +``` + +更多可用服务类型与详细的用法请参见[智能体状态服务](state.md)。 + +## 服务的接口 + +所有服务必须实现 `ServiceWithLifecycleManager` 抽象类,例如: + +```java +import io.agentscope.runtime.engine.shared.ServiceWithLifecycleManager; + +import java.util.concurrent.CompletableFuture; + +public class MockService extends ServiceWithLifecycleManager { + @Override + public CompletableFuture start() { + return null; + } + + @Override + public CompletableFuture stop() { + return null; + } + + @Override + public CompletableFuture health() { + return null; + } +} +``` + +生命周期模式示例: + +```java +import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService; +import io.agentscope.runtime.engine.services.memory.service.MemoryService; + +import java.util.concurrent.CompletableFuture; + +public class Main { + public static void main(String[] args) { + MemoryService memoryService = new InMemoryMemoryService(); + memoryService.start(); + CompletableFuture healthFuture = memoryService.health(); + + healthFuture.thenAccept(isHealthy -> { + if (isHealthy) { + System.out.println("Service is healthy!"); + } else { + System.err.println("Service is DOWN!"); + } + }); + memoryService.stop(); + } +} +``` diff --git a/cookbook/new-zh/service/session_history.md b/cookbook/new-zh/service/session_history.md new file mode 100644 index 00000000..84654b06 --- /dev/null +++ b/cookbook/new-zh/service/session_history.md @@ -0,0 +1,110 @@ +# 会话历史服务 + +## 概述 + +**会话历史服务**用于管理用户的对话会话,为智能体在多轮对话中提供处理对话历史和消息存储的结构化方式。每个会话(Session)都有一个唯一的 `session_id`,包含该会话从开始到当前的全部消息列表(消息对象 `Message` )。 + +在智能体运行过程中,会话历史服务的典型作用包括: + +- **新建会话**:在用户首次发起对话时,为其创建一个会话。 +- **读取会话**:在对话过程中获取已有的历史记录,保证上下文连贯。 +- **追加消息**:智能体或用户发送的消息都会追加到会话的存储中。 +- **列出会话**:查看某个用户的所有会话。 +- **删除会话**:根据业务需求清理某个会话。 + +会话历史服务在不同实现中,差异主要体现在**存储位置**、**是否持久化**、**可扩展性**以及**生产可用性**。 + +> 在大多数情况下,**不建议在业务代码中直接调用底层会话历史服务类**(如 `InMemorySessionHistoryService`、`RedisSessionHistoryService` 等)。 +> 更推荐通过 **适配器(adapter)** 的方式使用,这样可以: +> +> - 屏蔽底层实现细节,业务无感知切换存储类型 +> - 统一由 Runner/Engine 管理生命周期 +> - 保证跨框架复用与解耦 + +## 在 AgentScope 中使用 Adapter + +在 **AgentScope** 框架中,我们使用 `MemoryAdapter` 适配器,将底层会话历史服务绑定为智能体的 `Memory` 模块: + +```java +import io.agentscope.runtime.adapters.agentscope.memory.MemoryAdapter; +import io.agentscope.runtime.engine.services.memory.persistence.session.InMemorySessionHistoryService; +import io.agentscope.runtime.engine.services.memory.service.SessionHistoryService; + +public class Main { + public static void main(String[] args) { +// 选择后端实现(此例为 InMemory,方便本地测试) + SessionHistoryService sessionHistoryService = new InMemorySessionHistoryService(); + MemoryAdapter memory = null; + +// 用 adapter 包装,绑定到 Memory 模块 + memory = new MemoryAdapter( + sessionHistoryService, + "User1", + "TestSession" + ); + +// 之后在 Agent 内即可直接使用 memory 存取会话历史 + } +} +``` + +## 可选的后端实现类型 + +虽然通过适配器使用时无需关心底层调用,但为了配置和选型,需要了解可用实现类型的特点: + +| 服务类型 | 导入路径 | 存储位置 | 持久化 | 生产可用性 | 特点 & 优缺点 | 适用场景 | +| ----------------------------------- | ------------------------------------------------------------ | ------------------------- | --------------- | ---------- | ---------------------------------------- | ------------------------------ | +| **InMemorySessionHistoryService** | `import io.agentscope.runtime.engine.services.memory.persistence.session.InMemorySessionHistoryService` | 进程内存 | ❌ 否 | ❌ | 快速、无依赖,退出即丢失 | 开发调试、单元测试 | +| **RedisSessionHistoryService** | `import io.agentscope.runtime.engine.services.memory.persistence.session.RedisSessionHistoryService` | Redis 内存数据库 | ✅ 是(RDB/AOF) | ✅ | 快速、支持集群、跨进程共享;需运维 Redis | 高性能生产部署、分布式会话共享 | +| **TableStoreSessionHistoryService** | `import io.agentscope.runtime.engine.services.memory.persistence.session.TableStoreSessionHistoryService` | 阿里云 Tablestore云数据库 | ✅ 是 | ✅ | 海量存储、高可用、复杂索引查询;需云服务 | 企业级生产、长期历史存档 | + +## 切换不同实现的方法 + +adapter 的好处是,你只需替换传入的 `service` 实例,就能切换存储后端,而不用修改业务逻辑: + +```java +RedisTemplate redisTemplate = new RedisTemplate<>(); +// 配置redisTemplate的连接工厂等属性 + +SessionHistoryService sessionHistoryService = new RedisSessionHistoryService(redisTemplate); +MemoryAdapter memory = null; +memory = new MemoryAdapter( + sessionHistoryService, + "User1", + "TestSession" +); + +// Agent 中代码无需变动 +``` + +例如,将 `InMemory` 切换为 `Tablestore`: + +```java +SyncClient client = new SyncClient( + "https://your-instance.cn-region.ots.aliyuncs.com", + "your-access-key-id", + "your-access-key-secret", + "your-instance-name" +); + +TableStoreSessionHistoryService sessionHistoryService = new TableStoreSessionHistoryService(client); +MemoryAdapter memory = null; +memory = new MemoryAdapter( + sessionHistoryService, + "User1", + "TestSession" +); +``` + +## 选型建议 + +- **开发调试/快速原型**:`InMemorySessionHistoryService` +- **生产环境中高性能共享会话**:`RedisSessionHistoryService`(可配合 Redis 集群和持久化机制) +- **企业级生产 & 海量数据存储**:`TablestoreSessionHistoryService`(需要阿里云账号与资源) + +## 小结 + +- 会话历史服务是智能体保持上下文记忆的核心组件 +- 推荐通过 **适配器**(如 `MemoryAdapter`)来使用,实现与业务逻辑解耦 +- 选型时根据数据量、持久化要求和运维条件选择后端实现 +- `adapter` 让存储后端的替换变得非常简单,无需修改智能体逻辑 diff --git a/cookbook/new-zh/service/state.md b/cookbook/new-zh/service/state.md new file mode 100644 index 00000000..9ee4623f --- /dev/null +++ b/cookbook/new-zh/service/state.md @@ -0,0 +1,98 @@ +# 智能体状态服务 + +## 概述 + +**智能体状态服务**是用于**存储和管理智能体可序列化状态**的核心组件,它可以让智能体在多轮甚至跨会话的交互中保持上下文信息。 +与**会话历史服务**主要保存文本消息不同,**状态服务**更关注保存智能体内部的**结构化可序列化数据**,例如: + +- 变量值 +- 执行进度 +- 工具使用的中间结果 +- 环境配置或偏好等 + +状态服务按照以下维度组织数据: + +- `userId`:区分用户 +- `sessionId`:区分会话(默认 `"default"`) +- `roundId`:区分会话轮数(可选,如省略则自动生成最新轮数) + +在智能体运行过程中的作用: + +- **保存状态**(`saveState`):把某个时刻的智能体状态保存下来 +- **恢复状态**(`exportState`):在下一轮对话或下次会话中恢复之前的状态 + +> 状态是完全可序列化的字典,通常由智能体的 state_dict() 生成并可直接加载回去 (load_state_dict),便于封装和跨平台传输。 + +## 在 AgentScope 中的使用方法 + +与**会话历史服务**不同,**智能体状态服务**在 AgentScope 中通常**无需适配器**。它可以直接调用 `saveState` 和 `exportState` 方法来持久化和加载状态。 + +```{code-cell} +from agentscope_runtime.engine.services.agent_state import InMemoryStateService + +# 初始化服务 +state_service = InMemoryStateService() +await state_service.start() + +# 假设 agent 有 state_dict 方法 +agent_state = agent.state_dict() + +# 保存状态(返回 round_id) +round_id = await state_service.save_state( + user_id="User1", + session_id="TestSession", + state=agent_state +) +print(f"State saved in round {round_id}") + +# 导出最新状态(或指定 round_id) +loaded_state = await state_service.export_state( + user_id="User1", + session_id="TestSession" +) +agent.load_state_dict(loaded_state) + +``` + +## 可选的后端实现类型 + +与会话历史类似,智能体状态服务也有不同的存储后端实现: + +| 服务类型 | 导入路径 | 存储位置 | 持久化 | 生产可用性 | 特点 | 适用场景 | +| ------------------------ | ------------------------------------------------------------ | ---------------- | ---------- | ---------- | ------------------------------------------------------- | ------------------------------ | +| **InMemoryStateService** | `from agentscope_runtime.engine.services.agent_state import InMemoryStateService` | 进程内存 | ❌ 无 | ❌ | 简单快速,无需外部依赖,进程结束数据丢失 | 开发调试、单元测试 | +| **RedisStateService** | `from agentscope_runtime.engine.services.agent_state import RedisStateService` | Redis 内存数据库 | ✅ 可持久化 | ✅ | 支持分布式共享状态,跨进程,Redis 可选持久化(RDB/AOF) | 高性能生产部署、跨进程数据共享 | + +## 切换不同实现 + +由于业务代码对 `StateService` 接口一致,切换后端非常简单,只需替换实例化的类型。 + +InMemory → Redis: + +```{code-cell} +from agentscope_runtime.engine.services.agent_state import RedisStateService + +state_service = RedisStateService(redis_url="redis://localhost:6379/0") +await state_service.start() + +# 保存状态 +await state_service.save_state(user_id="User1", session_id="ProdSession", state=agent.state_dict()) + +# 导出状态 +state = await state_service.export_state(user_id="User1", session_id="ProdSession") +agent.load_state_dict(state) +``` + +## 选型建议 + +- **开发阶段、调试或测试**:`InMemoryStateService`,无外部依赖,快速迭代。 +- **生产环境、需要跨进程共享状态**:`RedisStateService`,可配合 Redis 持久化和高可用集群。 +- **长周期和强一致性、审计**:可考虑自行实现数据库版 `StateService`。 + +## 小结 + +- **智能体状态服务**负责保存可序列化的内部状态,区别于会话历史只存消息。 +- 支持 `user_id`、`session_id` 和 `round_id` 三维组织数据。 +- 在 AgentScope 中通常直接调用,无需适配器。 +- 切换存储后端简单,接口定义统一。 +- 按需选择 InMemory、Redis 或自行扩展实现。 diff --git a/cookbook/new-zh/tool.md b/cookbook/new-zh/tool.md new file mode 100644 index 00000000..5d5f5010 --- /dev/null +++ b/cookbook/new-zh/tool.md @@ -0,0 +1,53 @@ +# 沙箱与工具 + +在 AgentScope Runtime Java 中,工具是智能体落地业务能力的关键组成部分。无论是直接调用模型服务、执行浏览器自动化,还是集成企业内部 API,工具体系需要安全、可控、易于扩展。本章将给出整体思路,并串联后续子章节(即用型工具、沙箱基础/进阶、训练沙箱、沙箱故障排查),帮助你根据场景选择合适的路径。 + +## 工具接入模式 + +Runtime 支持两种种常见方式接入工具: + +1. **即用型工具**:由服务商或 Runtime 直接提供,如 RAG 检索等,零部署即可调用。 +2. **沙箱工具**:通过 Browser/FileSystem 等沙箱环境,以受控方式运行。 + + +## 子章节导读 + +### 沙箱 + +介绍工具沙箱的概念、生命周期与常见类型(浏览器、文件系统、Python 执行等)。你将学习如何: + +- 通过 `Sandbox` SDK 创建、连接、释放沙箱。 +- 在多会话场景下复用与隔离资源。 + +操作细节见 [沙箱](sandbox/sandbox.md)。 + +#### 沙箱进阶 + +深入探讨多后端、远程沙箱服务等高级特性。适合需要大规模稳定运行或满足企业安全要求的团队,内容包括: + +- 更多沙箱设置。 +- Kubernetes、远程容器集群的集成方式。 +- 自定义沙箱类型的扩展接口。 + +完整指南参见 [沙箱进阶](sandbox/advanced.md)。 + +#### 训练沙箱 + +聚焦于用于评测、训练或自博弈场景的特殊沙箱能力: + +更多内容参考 [训练沙箱](sandbox/training_sandbox.md)。 + +#### 沙箱故障排查 + +提供常见问题的定位与修复建议,如沙箱无法启动、工具超时、权限不足等,并给出检查清单(日志、健康检查、资源占用)与常见错误码说明。 + +排障步骤详见 [沙箱故障排除](sandbox/troubleshooting.md)。 + +## 推荐路径 + +1. 从即用型工具出发,确定需要的调用方式。 +2. 根据副作用与安全需求,选择是否启用沙箱及其级别。 +3. 参照进阶章节完成批量验证与生产化部署。 +4. 遇到稳定性问题时,快速查阅故障排查章节定位根因。 + +通过以上步骤,可构建既安全可靠又具备高扩展性的工具体系,让智能体具备持续演化的能力。 diff --git a/core/src/main/java/io/agentscope/runtime/engine/services/sandbox/SandboxService.java b/core/src/main/java/io/agentscope/runtime/engine/services/sandbox/SandboxService.java index ae17c57e..41940b15 100644 --- a/core/src/main/java/io/agentscope/runtime/engine/services/sandbox/SandboxService.java +++ b/core/src/main/java/io/agentscope/runtime/engine/services/sandbox/SandboxService.java @@ -51,6 +51,10 @@ public SandboxService(SandboxManager sandboxManager) { this.managerApi = sandboxManager; } + public SandboxManager getManagerApi() { + return managerApi; + } + @Override public void start() { if (managerApi != null) { diff --git a/core/src/main/java/io/agentscope/runtime/sandbox/box/GuiMixin.java b/core/src/main/java/io/agentscope/runtime/sandbox/box/GuiMixin.java index b6599f87..0fe8defd 100644 --- a/core/src/main/java/io/agentscope/runtime/sandbox/box/GuiMixin.java +++ b/core/src/main/java/io/agentscope/runtime/sandbox/box/GuiMixin.java @@ -75,6 +75,9 @@ public static String getDesktopUrl(SandboxManager managerApi, String sandboxId, if (!containerUrl.endsWith("/")) { containerUrl += "/"; } + if(containerUrl.endsWith("fastapi/")){ + containerUrl = containerUrl.replace("fastapi/", ""); + } return containerUrl + path.substring(1) + "?" + params; } else { // Use base_url with sandbox ID diff --git a/core/src/main/java/io/agentscope/runtime/sandbox/manager/SandboxManager.java b/core/src/main/java/io/agentscope/runtime/sandbox/manager/SandboxManager.java index 89d49fa6..8405d53d 100644 --- a/core/src/main/java/io/agentscope/runtime/sandbox/manager/SandboxManager.java +++ b/core/src/main/java/io/agentscope/runtime/sandbox/manager/SandboxManager.java @@ -103,7 +103,7 @@ public SandboxManager(ManagerConfig managerConfig, String baseUrl, String bearer this.poolSize = managerConfig.getPoolSize(); this.defaultType = defaultType; this.redisEnabled = managerConfig.getRedisEnabled(); - this.portManager = new PortManager(managerConfig.getPortRange()); // Thread-safe port manager + this.portManager = new PortManager(managerConfig.getPortRange()); logger.info("Initializing SandboxManager with container manager: " + this.containerManagerType); logger.info("Container pool size: " + this.poolSize); @@ -152,7 +152,6 @@ public void start() { // Set port range and redis config from ManagerConfig dockerClientConfig.setPortRange(managerConfig.getPortRange()); dockerClientConfig.setRedisEnabled(managerConfig.getRedisEnabled()); - dockerClientConfig.setRedisConfig(managerConfig.getRedisConfig()); DockerClient dockerClient = new DockerClient(dockerClientConfig); this.containerClient = dockerClient; @@ -957,7 +956,7 @@ public String listTools(String identity, String toolType) { private SandboxClient establishConnection(String sandboxId) { try { ContainerModel containerInfo = getInfo(sandboxId); - if (containerInfo.getVersion().contains("sandbox-appworld") || containerInfo.getVersion().contains("sandbox-bfclient")) { + if (containerInfo.getVersion().contains("sandbox-appworld") || containerInfo.getVersion().contains("sandbox-bfcl")) { return new TrainingSandboxClient(containerInfo, 60); } return new SandboxHttpClient(containerInfo, 60); diff --git a/core/src/main/java/io/agentscope/runtime/sandbox/manager/client/config/DockerClientConfig.java b/core/src/main/java/io/agentscope/runtime/sandbox/manager/client/config/DockerClientConfig.java index 4f5f9fe5..c7544dc3 100644 --- a/core/src/main/java/io/agentscope/runtime/sandbox/manager/client/config/DockerClientConfig.java +++ b/core/src/main/java/io/agentscope/runtime/sandbox/manager/client/config/DockerClientConfig.java @@ -18,7 +18,6 @@ import io.agentscope.runtime.sandbox.manager.model.container.ContainerManagerType; import io.agentscope.runtime.sandbox.manager.model.container.PortRange; -import io.agentscope.runtime.sandbox.manager.model.container.RedisManagerConfig; public class DockerClientConfig extends BaseClientConfig { private String host; @@ -26,21 +25,18 @@ public class DockerClientConfig extends BaseClientConfig { private String certPath; private PortRange portRange; private boolean redisEnabled; - private RedisManagerConfig redisConfig; private DockerClientConfig() { super(ContainerManagerType.DOCKER); } private DockerClientConfig(String host, int port, String certPath, - PortRange portRange, boolean redisEnabled, RedisManagerConfig redisConfig) { + PortRange portRange) { super(ContainerManagerType.DOCKER); this.host = host; this.port = port; this.certPath = certPath; this.portRange = portRange; - this.redisEnabled = redisEnabled; - this.redisConfig = redisConfig; } public static Builder builder() { @@ -87,22 +83,12 @@ public void setRedisEnabled(boolean redisEnabled) { this.redisEnabled = redisEnabled; } - public RedisManagerConfig getRedisConfig() { - return redisConfig; - } - - public void setRedisConfig(RedisManagerConfig redisConfig) { - this.redisConfig = redisConfig; - } - public static class Builder { private String host = "localhost"; private int port = 2375; private String certPath; private PortRange portRange; private boolean redisEnabled = false; - private RedisManagerConfig redisConfig; - private Builder() { } @@ -131,13 +117,8 @@ public Builder redisEnabled(boolean redisEnabled) { return this; } - public Builder redisConfig(RedisManagerConfig redisConfig) { - this.redisConfig = redisConfig; - return this; - } - public DockerClientConfig build() { - return new DockerClientConfig(host, port, certPath, portRange, redisEnabled, redisConfig); + return new DockerClientConfig(host, port, certPath, portRange); } } } diff --git a/core/src/main/java/io/agentscope/runtime/sandbox/manager/model/ManagerConfig.java b/core/src/main/java/io/agentscope/runtime/sandbox/manager/model/ManagerConfig.java index 58811d7e..d36b6aa9 100644 --- a/core/src/main/java/io/agentscope/runtime/sandbox/manager/model/ManagerConfig.java +++ b/core/src/main/java/io/agentscope/runtime/sandbox/manager/model/ManagerConfig.java @@ -187,8 +187,8 @@ public Builder containerDeployment(BaseClientConfig clientConfig) { return this; } - public Builder portRange(int start, int end) { - this.portRange = new PortRange(start, end); + public Builder portRange(PortRange portRange) { + this.portRange = portRange; return this; } diff --git a/core/src/test/java/io/agentscope/runtime/sandbox/manager/RedisSandboxManagerTest.java b/core/src/test/java/io/agentscope/runtime/sandbox/manager/RedisSandboxManagerTest.java index b6502bac..27e08983 100644 --- a/core/src/test/java/io/agentscope/runtime/sandbox/manager/RedisSandboxManagerTest.java +++ b/core/src/test/java/io/agentscope/runtime/sandbox/manager/RedisSandboxManagerTest.java @@ -20,6 +20,7 @@ import io.agentscope.runtime.sandbox.manager.model.container.RedisManagerConfig; import io.agentscope.runtime.sandbox.manager.model.container.SandboxKey; import io.agentscope.runtime.sandbox.manager.model.container.SandboxType; +import io.agentscope.runtime.sandbox.manager.model.container.PortRange; import io.agentscope.runtime.sandbox.manager.model.fs.LocalFileSystemConfig; import org.junit.jupiter.api.*; import org.testcontainers.containers.GenericContainer; @@ -65,37 +66,37 @@ public void testSandboxManagerWithRedis() { RedisManagerConfig redisConfig = createRedisConfig( "_test_runtime_sandbox_occupied_ports", "_test_runtime_sandbox_pool"); - + ManagerConfig config = ManagerConfig.builder() .containerPrefixKey("redis_test_") .redisConfig(redisConfig) .poolSize(2) - .portRange(50000, 51000) + .portRange(new PortRange(50000, 51000)) .fileSystemConfig(LocalFileSystemConfig.builder() .mountDir("sessions_mount_dir") .build()) .build(); - + try (SandboxManager manager = new SandboxManager(config)) { manager.start(); assertNotNull(manager, "SandboxManager should be initialized"); - + ContainerModel container = manager.getSandbox( - SandboxType.BASE, - TEST_USER_ID, + SandboxType.BASE, + TEST_USER_ID, TEST_SESSION_ID ); - + assertNotNull(container, "Container should be created"); assertNotNull(container.getContainerName(), "Container should have a name"); assertNotNull(container.getBaseUrl(), "Container should have a base URL"); assertTrue(container.getBaseUrl().contains("localhost"), "Base URL should contain localhost"); - + Map allSandboxes = manager.getAllSandboxes(); assertEquals(1, allSandboxes.size(), "Should have 1 sandbox"); - + manager.stopAndRemoveSandbox(SandboxType.BASE, TEST_USER_ID, TEST_SESSION_ID); - + } catch (Exception e) { fail("Redis test failed: " + e.getMessage(), e); } @@ -107,29 +108,29 @@ public void testSandboxManagerWithoutRedis() { ManagerConfig config = ManagerConfig.builder() .containerPrefixKey("inmemory_test_") .poolSize(0) - .portRange(51000, 52000) + .portRange(new PortRange(51000, 52000)) .fileSystemConfig(LocalFileSystemConfig.builder() .mountDir("sessions_mount_dir") .build()) .build(); - + try (SandboxManager manager = new SandboxManager(config)) { assertNotNull(manager, "SandboxManager should be initialized"); - + assertFalse(manager.getManagerConfig().getRedisEnabled(), "Redis should not be enabled"); - + ContainerModel container = manager.getSandbox( - SandboxType.BASE, - "memory_user", + SandboxType.BASE, + "memory_user", "memory_session" ); - + assertNotNull(container, "Container should be created"); assertNotNull(container.getContainerName(), "Container should have a name"); manager.stopAndRemoveSandbox(SandboxType.BASE, "memory_user", "memory_session"); - + } catch (Exception e) { fail("In-memory test failed: " + e.getMessage(), e); } @@ -141,44 +142,44 @@ public void testSharedStateWithRedis() { RedisManagerConfig redisConfig = createRedisConfig( "_shared_test_ports", "_shared_test_pool"); - + ManagerConfig config = ManagerConfig.builder() .containerPrefixKey("shared_test_") .redisConfig(redisConfig) .poolSize(0) - .portRange(52000, 53000) + .portRange(new PortRange(52000, 53000)) .fileSystemConfig(LocalFileSystemConfig.builder() .mountDir("sessions_mount_dir") .build()) .build(); - + ContainerModel container1; String containerName1; - + try (SandboxManager manager1 = new SandboxManager(config)) { container1 = manager1.getSandbox( - SandboxType.BASE, - "shared_user", + SandboxType.BASE, + "shared_user", "shared_session" ); - + assertNotNull(container1, "Container should be created by manager1"); containerName1 = container1.getContainerName(); - + try (SandboxManager manager2 = new SandboxManager(config)) { ContainerModel container2 = manager2.getSandbox( - SandboxType.BASE, - "shared_user", + SandboxType.BASE, + "shared_user", "shared_session" ); - + assertNotNull(container2, "Container should be accessible from manager2"); - assertEquals(containerName1, container2.getContainerName(), + assertEquals(containerName1, container2.getContainerName(), "Both managers should access the same container"); - + manager2.stopAndRemoveSandbox(SandboxType.BASE, "shared_user", "shared_session"); } - + } catch (Exception e) { fail("Shared state test failed: " + e.getMessage(), e); } @@ -190,31 +191,31 @@ public void testContainerPoolWithRedis() { RedisManagerConfig redisConfig = createRedisConfig( "_pool_test_ports", "_pool_test_queue"); - + ManagerConfig config = ManagerConfig.builder() .containerPrefixKey("pool_test_") .redisConfig(redisConfig) .poolSize(2) - .portRange(53000, 54000) + .portRange(new PortRange(53000, 54000)) .fileSystemConfig(LocalFileSystemConfig.builder() .mountDir("sessions_mount_dir") .build()) .build(); - + try (SandboxManager manager = new SandboxManager(config)) { assertNotNull(manager, "SandboxManager should be initialized"); - + ContainerModel container = manager.createFromPool( SandboxType.BASE, "pool_user", "pool_session" ); - + assertNotNull(container, "Container should be created from pool"); assertNotNull(container.getContainerName(), "Container should have a name"); manager.release(container.getContainerName()); - + } catch (Exception e) { fail("Container pool test failed: " + e.getMessage(), e); } @@ -226,36 +227,36 @@ public void testContainerPersistenceInRedis() { RedisManagerConfig redisConfig = createRedisConfig( "_persist_test_ports", "_persist_test_pool"); - + ManagerConfig config = ManagerConfig.builder() .containerPrefixKey("persist_test_") .redisConfig(redisConfig) .poolSize(0) - .portRange(54000, 55000) + .portRange(new PortRange(54000, 55000)) .fileSystemConfig(LocalFileSystemConfig.builder() .mountDir("sessions_mount_dir") .build()) .build(); - + String containerName; - + try (SandboxManager manager = new SandboxManager(config)) { ContainerModel container = manager.getSandbox( SandboxType.BASE, "persist_user", "persist_session" ); - + containerName = container.getContainerName(); assertNotNull(containerName, "Container should have a name"); - + Map sandboxes = manager.getAllSandboxes(); assertFalse(sandboxes.isEmpty(), "Should have at least one sandbox in Redis"); - + manager.stopAndRemoveSandbox(SandboxType.BASE, "persist_user", "persist_session"); } } - + @AfterEach public void afterEach() throws InterruptedException { Thread.sleep(1000); diff --git a/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/MyAgentScopeAgentHandler.java b/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/MyAgentScopeAgentHandler.java index 5946cd48..aa1b34ca 100644 --- a/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/MyAgentScopeAgentHandler.java +++ b/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/MyAgentScopeAgentHandler.java @@ -19,7 +19,6 @@ import java.util.Map; import io.agentscope.core.ReActAgent; -import io.agentscope.core.agent.Event; import io.agentscope.core.agent.EventType; import io.agentscope.core.agent.StreamOptions; import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter; diff --git a/web/src/main/java/io/agentscope/runtime/protocol/a2a/GraphAgentExecutor.java b/web/src/main/java/io/agentscope/runtime/protocol/a2a/GraphAgentExecutor.java index 0cd068d0..5059d9d2 100644 --- a/web/src/main/java/io/agentscope/runtime/protocol/a2a/GraphAgentExecutor.java +++ b/web/src/main/java/io/agentscope/runtime/protocol/a2a/GraphAgentExecutor.java @@ -154,7 +154,6 @@ else if (output instanceof Message message) { if (dataContent.getData() == null || !dataContent.getData().containsKey("name") || dataContent.getData().get("name").toString().isEmpty()) { continue; } - System.out.println("Processing tool call: " + dataContent.getData()); String toolName = dataContent.getData().get("name").toString(); String arguments = dataContent.getData().get("arguments").toString(); String callId = dataContent.getData().get("call_id").toString(); @@ -171,7 +170,6 @@ else if (output instanceof Message message) { if (dataContent.getData() == null || !dataContent.getData().containsKey("name") || dataContent.getData().get("name").toString().isEmpty()) { continue; } - System.out.println("Processing tool call: " + dataContent.getData()); String toolResult = dataContent.getData().get("output").toString(); String toolName = dataContent.getData().get("name").toString(); String callId = dataContent.getData().get("call_id").toString(); @@ -272,7 +270,6 @@ private void processStreamingOutput(Flux resultFlux, TaskUpdater taskUpda if (dataContent.getData() == null || !dataContent.getData().containsKey("name") || dataContent.getData().get("name").toString().isEmpty()) { continue; } - System.out.println("Processing tool call: " + dataContent.getData()); String toolName = dataContent.getData().get("name").toString(); String arguments = dataContent.getData().get("arguments").toString(); String callId = dataContent.getData().get("call_id").toString(); @@ -297,7 +294,6 @@ private void processStreamingOutput(Flux resultFlux, TaskUpdater taskUpda if (dataContent.getData() == null || !dataContent.getData().containsKey("name") || dataContent.getData().get("name").toString().isEmpty()) { continue; } - System.out.println("Processing tool call: " + dataContent.getData()); String toolResult = dataContent.getData().get("output").toString(); String toolName = dataContent.getData().get("name").toString(); String callId = dataContent.getData().get("call_id").toString();