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 架構中使用模型功能時,它是自然的選擇。

AgentScope Java 著重於代理程式執行階段:重複的推理與行動、型別化生命週期事件、權限、中斷、harness 工作區、子代理程式、可恢復的工作階段,以及分散式代理程式操作。

請根據應用程式的主要問題進行選擇:

需求可能的起點
將聊天、RAG 或工具呼叫加入 Spring 服務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 上閱讀或評論(發表評論需要登入)