AgentScope

最后更新: 2023-05-05 11:54:52 +0800 +0800
tags: Ai | Agents | Java | Spring-Boot | Llm
原文以英文撰写。您正在阅读AI翻译版本。

AgentScope 是一个用于构建和运行应用程序的开源框架。在这些应用程序中,大语言模型可以进行推理、调用工具、保留状态,并与用户或其他智能体协作。它由阿里巴巴发起,并以独立的 Python 和 Java 项目形式提供。

普通的模型调用具有一条由应用程序控制的短路径:

text
application -> prompt -> model -> response

智能体会增加一个执行循环:

text
user goal
   |
   v
model reasons -> chooses a tool -> application executes it
      ^                                  |
      +----------- tool result <---------+
   |
   v
final answer, interruption, or next action

模型本身仍不会执行 Java 或 Python 函数。它会输出结构化请求,而 AgentScope 通过其工具系统验证并分发该请求。框架还负责管理围绕这一循环的消息、事件流、权限、会话状态以及重复的模型调用。

本文介绍 AgentScope 2.0,重点讨论 AgentScope Java 2.0.0。AgentScope 发展迅速,因此面向早期 1.x API 的示例可能无法直接运行。

Python 和 Java 是独立的实现 #

AgentScope 这个名称指的是一个生态系统,而不是一个可以在所有语言中原样运行的库。

Python 项目 要求 Python 3.11 或更高版本。它提供异步智能体框架、事件系统、工具和资源权限、中间件、工作区、沙箱集成、多会话服务以及多智能体团队。

AgentScope Java 是原生 JVM 实现,要求 JDK 17 或更高版本。它使用 Project Reactor 提供非阻塞执行 API,并提供 Java 原生的模型、工具、消息、事件、状态存储、中间件、权限、工作区、沙箱和分布式部署组件。

两个实现共享设计思想,但不共享进程内对象,也不共享源代码兼容的 API。Java 应用程序有两种合理的集成方式:

  1. 直接将 AgentScope Java 嵌入 JVM。
  2. 将 Python AgentScope 应用程序作为服务运行,并通过版本化网络 API 或 A2A 等智能体协议调用它。

不要为每个请求启动一个 Python 解释器,也不要尝试将运行中的 Python 智能体序列化到 Java 中。进程边界要求明确的服务契约、身份验证、超时行为和会话所有权。

核心概念 #

消息和内容 #

消息通过智能体传递输入和输出。AgentScope Java 使用 UserMessage 表示用户输入,并支持用于文本、图像、文件、音频、视频、工具调用和工具结果的类型化内容块。

角色验证非常重要。模型响应、用户指令和工具结果具有不同的信任级别,不应被压平为一个无结构的提示词。

模型 #

模型层提供一个带有供应商特定扩展的通用聊天模型契约。AgentScope Java 2.0 包含面向以下供应商的模块:

  • Alibaba DashScope 和 Qwen;
  • OpenAI 及 OpenAI 兼容端点;
  • Anthropic Claude;
  • Google Gemini;
  • Ollama;以及
  • 通过受支持扩展公开的其他供应商。

抽象层使模型可以在 Java API 边界处替换,但不同模型的行为并不相同。工具调用质量、结构化输出、多模态支持、上下文限制、推理控制和流式传输语义仍取决于所选模型。

工具和工具包 #

工具是模型可以请求使用的一项能力。AgentScope 从工具定义生成 JSON Schema,将该 Schema 发送给模型,解析模型的工具请求,并调用 Java 实现。

Toolkit 将智能体可用的工具分组。限制工具包是最重要的安全控制措施之一:如果智能体看不到某个工具,就无法请求框架调用它。

工具描述是提供给模型的操作指令。描述应说明操作的功能、参数的含义,以及操作是否会产生外部或不可逆的影响。含糊的描述会导致错误调用。

ReAct 循环 #

ReActAgent 是 AgentScope Java 的基础推理和行动智能体。在一次运行期间,它会重复执行以下步骤:

  1. 根据系统提示词、消息和可用工具构建模型上下文;
  2. 调用模型;
  3. 发出类型化事件;
  4. 在获得许可时执行请求的工具;
  5. 将工具结果添加到上下文;以及
  6. 持续运行,直到生成最终结果或达到停止条件。

当应用程序需要一个专注的对话或工具调用循环,并且自行控制周边基础设施的大部分内容时,应使用 ReActAgent。

HarnessAgent #

HarnessAgent 在同一推理核心之上构建,用于长时间运行的开放式任务。其 harness 可以添加:

  • 工作区和文件系统抽象;
  • 上下文压缩;
  • 分层记忆;
  • 可复用技能;
  • 规划;
  • 子智能体;
  • 沙箱执行;以及
  • 持久化状态和恢复。

调用三个业务服务的客户支持助手可能只需要 ReActAgent。探索代码仓库一小时、编辑文件、委托审查并能在重启后继续工作的编码智能体,则适合使用 HarnessAgent。更高的自主性也会带来更高的风险,因此不应仅因大型 harness 提供更多功能就将其设为默认选项。

事件 #

智能体执行会产生类型化事件流。事件涵盖文本增量、模型调用、工具调用、结果、确认请求、错误和最终结果。因此,Web UI 无需解析日志文本即可显示进度。

事件流也是接入可观测性和审计逻辑的合适位置。生产应用程序通常会记录工具名称、耗时、结果、模型用量、会话和已认证主体,同时对可能包含秘密的消息和工具内容进行脱敏。

运行时上下文和状态 #

RuntimeContext 标识一次调用。它可以携带 userId、sessionId 和类型化的应用程序对象。Java 工具可以接收此类对象,而不必将其暴露在发送给模型的 JSON Schema 中。

这一差异可以避免严重的授权错误。模型可以提供 orderId,但不应提供用于授权该订单的可信 tenantId 或用户身份。应用程序应从 Spring Security 获取身份,并通过 RuntimeContext 注入。

会话历史和其他可恢复状态应存储在 AgentStateStore 中。本地 JSON 存储适合开发环境。多副本部署需要使用受支持的 Redis 或关系数据库实现等共享存储,以便另一个副本能够恢复同一会话。

最小化 Python 智能体 #

使用以下命令安装 Python 框架:

bash
pip install agentscope

AgentScope Python 2.0 使用异步 API。一个最小化智能体如下所示:

python
import asyncio
import os

from agentscope.agent import Agent
from agentscope.credential import DashScopeCredential
from agentscope.message import UserMsg
from agentscope.model import DashScopeChatModel


async def main() -> None:
    agent = Agent(
        name="assistant",
        system_prompt="You are a concise technical assistant.",
        model=DashScopeChatModel(
            credential=DashScopeCredential(
                api_key=os.environ["DASHSCOPE_API_KEY"],
            ),
            model="qwen3.6-plus",
        ),
    )

    async for event in agent.reply_stream(
        UserMsg("alice", "Explain optimistic locking."),
    ):
        print(event)


asyncio.run(main())

实际应用通常会按类型选择事件,注册经过明确限制的工具包,持久化会话状态,并在向用户公开智能体之前配置权限。

开始使用 AgentScope Java #

添加依赖 #

AgentScope Java 2.0 要求 JDK 17 或更高版本。基本的 ReActAgent 需要核心模块和一个模型供应商扩展:

xml
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.agentscope</groupId>
      <artifactId>agentscope-bom</artifactId>
      <version>2.0.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>io.agentscope</groupId>
    <artifactId>agentscope-core</artifactId>
  </dependency>
  <dependency>
    <groupId>io.agentscope</groupId>
    <artifactId>agentscope-extensions-model-dashscope</artifactId>
  </dependency>
</dependencies>

当应用程序需要 HarnessAgent 及其面向工作区的能力时,可以使用 agentscope-harness 替代核心依赖,或在核心依赖之外添加它。其他模型扩展包括 agentscope-extensions-model-openai、agentscope-extensions-model-anthropic、agentscope-extensions-model-gemini 和 agentscope-extensions-model-ollama。

创建智能体 #

以下程序使用 DashScope。它从环境变量而非源代码读取 API 密钥:

java
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.UserMessage;
import io.agentscope.extensions.model.dashscope.DashScopeChatModel;

public class FirstAgent {

    public static void main(String[] args) {
        var model = DashScopeChatModel.builder()
                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                .modelName("qwen-plus")
                .stream(true)
                .build();

        var agent = ReActAgent.builder()
                .name("technical-assistant")
                .sysPrompt("""
                        You are a technical assistant.
                        Distinguish facts from assumptions.
                        Ask before performing a destructive operation.
                        """)
                .model(model)
                .build();

        Msg result = agent.call(
                new UserMessage("Explain write skew in two paragraphs.")
        ).block();

        System.out.println(result.getTextContent());
    }
}

AgentScope 暴露 Reactor 类型。在命令行程序的最外层边界调用 block() 是可以接受的。响应式服务器应返回 Mono 或 Flux,而不是阻塞请求线程。

添加 Java 工具 #

假设应用程序已经有一个 OrderService。智能体适配器应委托给该服务,而不是重复业务逻辑或调用应用程序自身的 REST 端点:

java
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;

public final class OrderTools {

    private final OrderService orders;

    public OrderTools(OrderService orders) {
        this.orders = orders;
    }

    @Tool(
            name = "get_order",
            description = "Get the current state of an order visible to the caller",
            readOnly = true)
    public OrderView getOrder(
            @ToolParam(
                    name = "order_id",
                    description = "Numeric order ID")
            long orderId,
            RequestIdentity identity) {
        return orders.get(identity.tenantId(), identity.userId(), orderId);
    }

    @Tool(
            name = "cancel_order",
            description = """
                    Cancel an order that has not shipped. This changes business
                    state and must be approved by the user before execution.
                    """)
    public OrderView cancelOrder(
            @ToolParam(
                    name = "order_id",
                    description = "Numeric order ID")
            long orderId,
            @ToolParam(
                    name = "reason",
                    description = "Business reason for cancellation")
            String reason,
            RequestIdentity identity) {
        return orders.cancel(
                identity.tenantId(),
                identity.userId(),
                orderId,
                reason);
    }
}

orderId 和 reason 使用 @ToolParam 注解,因此模型会在工具 Schema 中看到它们。RequestIdentity 没有注解。AgentScope 会从每次调用的 RuntimeContext 中注入它,因此模型无法通过伪造该参数来冒充其他租户。

将工具对象注册到工具包中:

java
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new OrderTools(orderService));

ReActAgent agent = ReActAgent.builder()
        .name("order-assistant")
        .sysPrompt("""
                Help users inspect their orders.
                Never cancel an order without explicit approval.
                """)
        .model(model)
        .toolkit(toolkit)
        .build();

Java 注解使简单的同步工具更易于实现。若需要更精细地控制权限检查、异步执行或结构化元数据,AgentScope Java 也提供更底层的工具抽象。

将 AgentScope Java 集成到 Spring Boot #

AgentScope Java 与框架无关,因此 Spring Boot 应用程序可以像集成普通 Java 对象一样集成它。推荐的边界如下:

text
REST or WebSocket controller
        |
        v
AgentScope agent factory
        |
        +----> model provider
        +----> application tools ----> existing Spring services
        +----> shared state store

以下示例公开一个 SSE 聊天端点。示例特意为每次调用创建新的 ReActAgent。ReActAgent 实例不支持并发调用,而模型和状态存储可以共享。工具包会在构建智能体期间复制。

配置共享组件 #

java
import io.agentscope.core.ReActAgent;
import io.agentscope.core.state.AgentStateStore;
import io.agentscope.core.state.JsonFileAgentStateStore;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.extensions.model.dashscope.DashScopeChatModel;
import java.nio.file.Path;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@ConfigurationProperties(prefix = "app.agent")
record AgentProperties(String apiKey, String model) {
}

@Configuration
@EnableConfigurationProperties(AgentProperties.class)
class AgentConfiguration {

    @Bean
    DashScopeChatModel agentModel(AgentProperties properties) {
        return DashScopeChatModel.builder()
                .apiKey(properties.apiKey())
                .modelName(properties.model())
                .stream(true)
                .build();
    }

    @Bean
    AgentStateStore agentStateStore() {
        return new JsonFileAgentStateStore(
                Path.of(".agentscope", "sessions"));
    }

    @Bean
    Toolkit orderToolkit(OrderService orders) {
        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new OrderTools(orders));
        return toolkit;
    }

    @Bean
    OrderAgentFactory orderAgentFactory(
            DashScopeChatModel model,
            AgentStateStore stateStore,
            Toolkit orderToolkit) {
        return new OrderAgentFactory(model, stateStore, orderToolkit);
    }
}

按常规方式配置非机密值,并通过密钥管理器或环境变量提供 API 密钥:

yaml
app:
  agent:
    model: qwen-plus
    api-key: ${DASHSCOPE_API_KEY}

不要向浏览器暴露模型凭据。

工厂会在复用安全基础设施的同时,创建隔离的智能体运行实例:

java
public record OrderAgentFactory(
        DashScopeChatModel model,
        AgentStateStore stateStore,
        Toolkit toolkit) {

    ReActAgent create() {
        return ReActAgent.builder()
                .name("order-assistant")
                .sysPrompt("""
                        Help the authenticated user inspect orders.
                        Use tools for current order data.
                        Never claim that a mutation succeeded unless its tool
                        returned a successful result.
                        """)
                .model(model)
                .stateStore(stateStore)
                .toolkit(toolkit)
                .build();
    }
}

对于本地开发,JsonFileAgentStateStore 已经足够。在运行多个应用程序副本之前,应将其替换为受支持的共享状态存储。容器内的本地目录不是持久的会话存储。

暴露 SSE 端点 #

java
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.event.TextBlockDeltaEvent;
import io.agentscope.core.message.UserMessage;
import java.security.Principal;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

record ChatRequest(String sessionId, String message) {
}

record RequestIdentity(String tenantId, String userId) {
}

@RestController
@RequestMapping("/api/assistant")
class AgentController {

    private final OrderAgentFactory agents;
    private final TenantResolver tenants;

    AgentController(OrderAgentFactory agents, TenantResolver tenants) {
        this.agents = agents;
        this.tenants = tenants;
    }

    @PostMapping(
            path = "/messages",
            produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    Flux<String> chat(
            @RequestBody ChatRequest request,
            Principal principal) {
        String userId = principal.getName();
        String tenantId = tenants.currentTenant();

        RuntimeContext context = RuntimeContext.builder()
                .userId(userId)
                .sessionId(request.sessionId())
                .put(
                        RequestIdentity.class,
                        new RequestIdentity(tenantId, userId))
                .build();

        return agents.create()
                .streamEvents(
                        new UserMessage(request.message()),
                        context)
                .filter(TextBlockDeltaEvent.class::isInstance)
                .cast(TextBlockDeltaEvent.class)
                .map(TextBlockDeltaEvent::getDelta);
    }
}

应用程序必须验证提交的会话属于已认证用户。在许多系统中,服务器应发放不透明的会话 ID 并存储其所有权,而不是接受任意由客户端生成的 ID。即使 RuntimeContext.userId 不同,两个用户使用同一个 sessionId 也可能导致会话泄露,具体取决于所选存储的分区方式。

对于简单 UI,仅流式传输文本增量很方便;但更丰富的 API 应保留事件类型。浏览器可能需要区分模型文本、工具进度、审批请求、可恢复错误和最终完成事件。AG-UI 或应用程序专用的事件封装比单纯的字符串流更具表现力。

处理阻塞工具 #

智能体 API 是响应式的,但带注解的工具可能调用阻塞式数据库驱动或旧式 HTTP 客户端。使用响应式类型并不会使该操作变为非阻塞。可以采取以下方式之一:

  • 使用非阻塞依赖和异步工具 API;
  • 将阻塞工作调度到有界工作线程池;
  • 使用明确限制并发度的传统请求模型。

不要在 Reactor 的事件循环线程上运行无界阻塞工作。同时,应分别为模型调用、工具调用和整个智能体运行设置超时。智能体可能进行多次模型和工具调用,因此总延迟会大于单次模型请求的延迟。

权限和人工审批 #

“删除前询问”等提示词指令很有用,但不能构成强制措施。模型可能误解指令,检索到的内容可能尝试提示词注入,未来的提示词修改也可能削弱控制。

AgentScope Java 的权限引擎可以将工具调用分类为:

  • allow:无需中断即可执行;
  • ask:暂停并请求用户决定;
  • deny:拒绝操作。

当调用需要审批时,智能体会发出或返回确认状态。应用程序显示确切的操作和参数,记录用户决定,并根据该决定恢复运行。在无头环境中,需要审批的操作应默认拒绝,而不是静默变为允许。

权限规则补充正常的应用程序授权:

text
permission rule:
  Is this category of tool allowed in this agent flow?

OrderService authorization:
  May this authenticated user cancel this specific order?

两项检查都不可缺少。权限规则不能替代租户检查、所有权检查、事务不变量或数据库约束。

记忆、工作区和沙箱 #

这些概念解决的是不同问题:

会话状态 保留继续会话所需的消息和智能体状态。

长期记忆 保存跨会话的选定事实或摘要。它应具备保留、更正、删除和租户隔离策略。永久保存每条模型发言并不构成记忆策略。

工作区 为 harness 提供用于计划、工件、技能和大型工具结果的持久区域。当工作区文件的内容可能受到用户或工具影响时,应将其视为不可信输入。

沙箱 隔离代码和命令执行。仅仅因为本地子进程拥有不同的进程 ID,并不意味着它构成强安全边界。在生产环境执行模型生成的命令时,应使用适当加固的容器、Kubernetes 或受支持的远程沙箱,并限制资源和网络访问。

多智能体系统 #

AgentScope 支持智能体团队和子智能体,但多个智能体并不会自动优于单个智能体。它们会增加模型调用、状态转换、故障模式和协调成本。

当工作具有真实边界时,可以使用子智能体,例如:

  • 不同的工具和权限集;
  • 专用的上下文或系统提示词;
  • 独立的并行研究;
  • 可单独恢复的长时间运行任务;或
  • 需要隔离的信任边界。

对于确定性的顺序执行,应使用普通 Java 方法或工作流代码。让一个模型请求另一个模型执行两数相加,比调用函数更慢且可靠性更低。

对于独立部署的智能体之间的通信,应使用服务契约。A2A 用于智能体间的发现、消息、任务和工件。MCP 将智能体连接到工具和资源;它不同于将目标委托给一个自主的远程智能体。

与 Python AgentScope 服务集成 #

某个组织可能已经运行一个使用 AgentScope Python 编写的智能体。Java 服务应将其视为远程系统:

text
Spring Boot service
    -> authenticated HTTP, A2A, or messaging contract
    -> Python AgentScope service
    -> model and tools

需要定义:

  • 请求、事件和工件 Schema;
  • 协议和版本;
  • 身份验证和租户传播;
  • 谁创建并拥有会话 ID;
  • 幂等行为;
  • 流式传输和重连行为;
  • 取消和超时语义;以及
  • 错误映射。

如果 Python 服务提供传统 REST API,可以根据 OpenAPI 契约生成 Java HTTP 客户端。如果远程组件是一个具有长时间运行任务和多轮协商能力的独立智能体,则使用 A2A。应让 Java 服务不依赖 Python 实现类。

嵌入式 Java 方式和远程 Python 方式可以共存。例如,Spring Boot 订单智能体可以使用本地 Java 工具执行低延迟业务操作,并通过 A2A 将专门的文档分析任务委托给 Python 智能体。

AgentScope Java 与 Spring AI #

Spring AI 与 AgentScope Java 存在重叠,但关注重点不同。

Spring AI 是面向 Spring 的原生基础设施,提供可移植的模型 API、ChatClient、Advisors、检索增强生成、向量存储、结构化输出、工具、可观测性和 MCP。当应用程序主要需要在现有 Spring 架构中使用模型功能时,Spring AI 是自然的选择。

AgentScope Java 专注于智能体执行运行时:重复的推理和行动、类型化生命周期事件、权限、中断、harness 工作区、子智能体、可恢复会话以及分布式智能体运行。

应根据应用程序的主要问题进行选择:

需求可能的起点
向 Spring 服务添加聊天、RAG 或工具调用Spring AI
运行长时间、可中断、使用工具的智能体AgentScope Java
向多个 AI 主机发布工具MCP 服务器
与独立部署的智能体协作A2A

两个库的模型、消息和工具接口不可互换。不要仅为了在一个推理循环中混用框架而编写适配器。在稳定边界处进行集成更清晰,例如共享 Spring 业务服务、MCP 工具、A2A 或应用程序 API。

生产检查清单 #

在将 AgentScope 应用程序暴露给真实用户之前:

  1. 对每个入口点进行身份验证,并从可信上下文中派生身份。
  2. 按租户和用户隔离会话、状态、记忆、工作区和缓存。
  3. 为每个智能体仅提供其角色所需的工具。
  4. 在业务服务内部强制执行授权,而不仅仅是在提示词中声明。
  5. 对财务、破坏性或外部操作要求明确审批。
  6. 将模型生成的代码置于沙箱中,并限制其文件系统和网络访问。
  7. 限制模型轮次、工具调用、令牌数、执行时间、结果大小和并发度。
  8. 对可能发生重试的状态变更工具实现幂等性。
  9. 在扩展到多个副本之前,将可恢复状态存储在共享存储中。
  10. 在脱敏秘密和个人数据的同时,跟踪模型和工具事件。
  11. 评估完整执行轨迹,而不仅仅是最终的文字回答。
  12. 测试取消、模型失败、工具超时、重启和重复交付路径。

智能体框架提供执行机制,但不会决定哪些操作安全、用户可以访问哪些数据,也不会判断模型的回答是否正确。这些仍然是应用程序和运维的责任。

延伸阅读 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)