# AgentScope Java **Build Production-Ready AI Agents in Java** --- ## What is AgentScope Java? AgentScope Java is an agent-oriented programming framework for building LLM-powered applications. It provides everything you need to create intelligent agents: ReAct reasoning, tool calling, memory management, multi-agent collaboration, and more. ## Highlights ### Smart Agents, Full Control AgentScope adopts the ReAct (Reasoning-Acting) paradigm, enabling agents to autonomously plan and execute complex tasks. Unlike rigid workflow-based approaches, ReAct agents dynamically decide which tools to use and when, adapting to changing requirements in real-time. However, autonomy without control is a liability in production. AgentScope provides comprehensive runtime intervention mechanisms: - **Safe Interruption** - Pause agent execution at any point while preserving full context and tool state, enabling seamless resumption without data loss - **Graceful Cancellation** - Terminate long-running or unresponsive tool calls without corrupting agent state, allowing immediate recovery and redirection - **Human-in-the-Loop** - Inject corrections, additional context, or guidance at any reasoning step through the Hook system, maintaining human oversight over critical decisions ### Built-in Tools AgentScope includes production-ready tools that address common challenges in agent development: - **PlanNotebook** - A structured task management system that decomposes complex objectives into ordered, trackable steps. Agents can create, modify, pause, and resume multiple concurrent plans, ensuring systematic execution of multi-step workflows. - **Structured Output** - A self-correcting output parser that guarantees type-safe responses. When LLM output deviates from the expected format, the system automatically detects errors and guides the model to produce valid output, mapping results directly to Java POJOs without manual parsing. - **Long-term Memory** - Persistent memory storage with semantic search capabilities across sessions. Supports automatic management, agent-controlled recording, or hybrid modes. Enables multi-tenant isolation for enterprise deployments where agents serve multiple users independently. - **RAG (Retrieval-Augmented Generation)** - Seamless integration with enterprise knowledge bases. Supports both self-hosted embedding-based retrieval and managed services like Alibaba Cloud Bailian, grounding agent responses in authoritative data sources. ### Seamless Integration AgentScope is designed to integrate with existing enterprise infrastructure without requiring extensive modifications: - **MCP Protocol** - Integrate with any MCP-compatible server to instantly extend agent capabilities. Connect to the growing ecosystem of MCP tools and services—from file systems and databases to web browsers and code interpreters—without writing custom integration code. - **A2A Protocol** - Enable distributed multi-agent collaboration through standard service discovery. Register agent capabilities to Nacos or similar registries, allowing agents to discover and invoke each other as naturally as calling microservices. ### Production Grade Built for enterprise deployment requirements: - **High Performance** - Reactive architecture based on Project Reactor ensures non-blocking execution. GraalVM native image compilation achieves 200ms cold start times, making AgentScope suitable for serverless and auto-scaling environments. - **Security Sandbox** - AgentScope Runtime provides isolated execution environments for untrusted tool code. Includes pre-built sandboxes for GUI automation, file system operations, and mobile device interaction, preventing unauthorized access to system resources. - **Observability** - Native integration with OpenTelemetry for distributed tracing across the entire agent execution pipeline. AgentScope Studio provides visual debugging, real-time monitoring, and comprehensive logging for development and production environments. ## Requirements - **JDK 17 or higher** - Maven or Gradle ## Quick Start Follow these steps to get started with AgentScope Java: 1. **[Installation](quickstart/installation.md)** - Set up AgentScope Java in your project 2. **[Key Concepts](quickstart/key-concepts.md)** - Understand core concepts and architecture 3. **[Build Your First Agent](quickstart/agent.md)** - Create a working agent ## Quick Example ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.message.Msg; // Create an agent with inline model configuration ReActAgent agent = ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful AI assistant.") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) .build(); Msg response = agent.call(Msg.builder() .textContent("Hello!") .build()).block(); System.out.println(response.getTextContent()); ``` ## Features Once you're familiar with the basics, explore these features: ### Model Integration - **[Model Integration](task/model.md)** - Configure different LLM providers - **[Multimodal](task/multimodal.md)** - Vision and multimodal capabilities ### Tools & Knowledge - **[Tool System](task/tool.md)** - Create and use tools with annotation-based registration - **[MCP](task/mcp.md)** - Model Context Protocol support for advanced tool integration - **[RAG](task/rag.md)** - Retrieval-Augmented Generation for knowledge-enhanced responses - **[Structured Output](task/structured-output.md)** - Type-safe output parsing with automatic correction ### Behavior Control - **[Hook System](task/hook.md)** - Monitor and customize agent behavior with event hooks - **[Memory Management](task/memory.md)** - Manage conversation history and long-term memory - **[Planning](task/plan.md)** - Plan management for complex multi-step tasks - **[Agent Configuration](task/agent-config.md)** - Advanced agent configuration options ### Multi-Agent Systems - **[Pipeline](multi-agent/pipeline.md)** - Build multi-agent workflows with sequential and parallel execution - **[MsgHub](multi-agent/msghub.md)** - Message hub for multi-agent communication - **[A2A Protocol](task/a2a.md)** - Agent2Agent protocol support - **[State Management](task/state.md)** - Persist and restore agent state across sessions ### Observability & Debugging - **[AgentScope Studio](task/studio.md)** - Visual debugging and monitoring ## AI-Powered Development AgentScope documentation supports the [`llms.txt` standard](https://llmstxt.org/), enabling AI coding assistants like Claude Code, Cursor, and Windsurf to understand AgentScope APIs and generate accurate code. **Quick Setup for Cursor:** 1. Open Cursor Settings -> Features -> Docs 2. Click "+ Add new Doc" 3. Add URL: `https://java.agentscope.io/llms-full.txt` For other tools and detailed configuration, see **[Coding with AI](task/ai-coding.md)**. ## Community - **GitHub**: [agentscope-ai/agentscope-java](https://github.com/agentscope-ai/agentscope-java) | [Discord](https://discord.gg/eYMpfnkG8h) | DingTalk | WeChat | |------------------------------------------|------------------------------------------|------------------------------------------| | ![QR Code](../imgs/discord.png) | ![QR Code](../imgs/dingtalk_qr_code.jpg) | ![QR Code](../imgs/wechat.png) | # Installation AgentScope Java supports multiple models, RAG backends, and extensions, each requiring different third-party SDKs. Bundling everything together would bloat your project, so we offer two ways to add dependencies: - **All-in-one**: Single dependency with DashScope SDK and MCP SDK included, get started quickly - **Core + extensions**: Minimal core package, add extension modules as needed, for strict dependency control For most cases, all-in-one is enough. Switch to core + extensions when you need fine-grained dependency control. **Requirements: JDK 17+** ## Dependency Options | Approach | Use Case | Features | |----------|----------|----------| | **all-in-one** | Quick start, most users | Single dependency, includes DashScope SDK | | **core + extensions** | Fine-grained control | On-demand imports, minimal dependencies | ## All-in-One (Recommended) **Maven:** ```xml io.agentscope agentscope 1.0.8 ``` **Gradle:** ```gradle implementation 'io.agentscope:agentscope:1.0.8' ``` ### Included Dependencies The all-in-one package includes these dependencies by default: - DashScope SDK (Qwen series models) - MCP SDK (Model Context Protocol) - Reactor Core, Jackson, SLF4J (base frameworks) ### Additional Dependencies When using other models or features, add the corresponding dependencies: | Feature | Dependency | Maven Coordinates | |---------------------------|------------------------------------------------------------------------------------------|----------------------------------| | **OpenAI Models** | [OpenAI Java SDK](https://central.sonatype.com/artifact/com.openai/openai-java) | `com.openai:openai-java` | | **Google Gemini Models** | [Google GenAI SDK](https://central.sonatype.com/artifact/com.google.genai/google-genai) | `com.google.genai:google-genai` | | **Anthropic Models** | [Anthropic Java SDK](https://central.sonatype.com/artifact/com.anthropic/anthropic-java) | `com.anthropic:anthropic-java` | | **Mem0 Long-term Memory** | [OkHttp](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp) | `com.squareup.okhttp3:okhttp` | | **ReME Long-term Memory** | [OkHttp](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp) | `com.squareup.okhttp3:okhttp` | | **Bailian RAG** | [Bailian SDK](https://central.sonatype.com/artifact/com.aliyun/bailian20231229) | `com.aliyun:bailian20231229` | | **Qdrant RAG** | [Qdrant Client](https://central.sonatype.com/artifact/io.qdrant/client) | `io.qdrant:client` | | **PgVector RAG** | [PostgreSQL Driver](https://central.sonatype.com/artifact/org.postgresql/postgresql) + [pgvector](https://central.sonatype.com/artifact/com.pgvector/pgvector) | `org.postgresql:postgresql` + `com.pgvector:pgvector` | | **Dify RAG** | [OkHttp](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp) | `com.squareup.okhttp3:okhttp` | | **RAGFlow RAG** | [OkHttp](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp) | `com.squareup.okhttp3:okhttp` | | **HayStack RAG** | [OkHttp](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp) | `com.squareup.okhttp3:okhttp` | | **Elasticsearch RAG** | [Elasticsearch Java Client](https://www.elastic.co/docs/reference/elasticsearch/clients/java) |`co.elastic.clients:elasticsearch-java` | | **MySQL Session** | [MySQL Connector](https://central.sonatype.com/artifact/com.mysql/mysql-connector-j) | `com.mysql:mysql-connector-j` | | **Redis Session** | [Jedis](https://central.sonatype.com/artifact/redis.clients/jedis) | `redis.clients:jedis` | | **PDF Processing** | [Apache PDFBox](https://central.sonatype.com/artifact/org.apache.pdfbox/pdfbox) | `org.apache.pdfbox:pdfbox` | | **Word Processing** | [Apache POI](https://central.sonatype.com/artifact/org.apache.poi/poi-ooxml) | `org.apache.poi:poi-ooxml` | | **Document Processing** | [Apache Tika Core](https://central.sonatype.com/artifact/org.apache.tika/tika-core) + [Apache Tika Parsers](https://central.sonatype.com/artifact/org.apache.tika/tika-parsers-standard-package) | `org.apache.tika:tika-core` + `org.apache.tika:tika-parsers-standard-package` | | **Nacos Registry** | [Nacos Client](https://central.sonatype.com/artifact/com.alibaba.nacos/nacos-client) | `com.alibaba.nacos:nacos-client` | #### Example: Using OpenAI Models ```xml com.openai openai-java ``` #### Example: Using Qdrant RAG + PDF Processing ```xml io.qdrant client org.apache.pdfbox pdfbox ``` ### Studio Integration Connect to [AgentScope Studio](https://github.com/modelscope/agentscope) for visualization and debugging: | Dependency | Maven Coordinates | |------------|-------------------| | [OkHttp](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp) | `com.squareup.okhttp3:okhttp` | | [Socket.IO Client](https://central.sonatype.com/artifact/io.socket/socket.io-client) | `io.socket:socket.io-client` | | [OpenTelemetry API](https://central.sonatype.com/artifact/io.opentelemetry/opentelemetry-api) | `io.opentelemetry:opentelemetry-api` | | [OpenTelemetry OTLP Exporter](https://central.sonatype.com/artifact/io.opentelemetry/opentelemetry-exporter-otlp) | `io.opentelemetry:opentelemetry-exporter-otlp` | | [OpenTelemetry Reactor](https://central.sonatype.com/artifact/io.opentelemetry.instrumentation/opentelemetry-reactor-3.1) | `io.opentelemetry.instrumentation:opentelemetry-reactor-3.1` | Full configuration: ```xml com.squareup.okhttp3 okhttp io.socket socket.io-client io.opentelemetry opentelemetry-api io.opentelemetry opentelemetry-exporter-otlp io.opentelemetry.instrumentation opentelemetry-reactor-3.1 ``` ## Core + Extensions For fine-grained dependency control, use `agentscope-core` with extension modules: **Maven:** ```xml io.agentscope agentscope-core 1.0.8 ``` **Gradle:** ```gradle implementation 'io.agentscope:agentscope-core:1.0.8' ``` ### Extension Modules #### Long-term Memory | Module | Feature | Maven Coordinates | |--------|---------|-------------------| | [agentscope-extensions-mem0](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-mem0) | Mem0 Long-term Memory | `io.agentscope:agentscope-extensions-mem0` | | [agentscope-extensions-reme](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-reme) | ReME Long-term Memory | `io.agentscope:agentscope-extensions-reme` | | [agentscope-extensions-autocontext-memory](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-autocontext-memory) | AutoContext Memory | `io.agentscope:agentscope-extensions-autocontext-memory` | #### RAG | Module | Feature | Maven Coordinates | |--------|---------|-------------------| | [agentscope-extensions-rag-bailian](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-rag-bailian) | Bailian RAG | `io.agentscope:agentscope-extensions-rag-bailian` | | [agentscope-extensions-rag-simple](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-rag-simple) | Simple RAG (Qdrant, Milvus, PgVector, InMemory, Elasticsearch) | `io.agentscope:agentscope-extensions-rag-simple` | | [agentscope-extensions-rag-dify](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-rag-dify) | Dify RAG | `io.agentscope:agentscope-extensions-rag-dify` | | [agentscope-extensions-rag-ragflow](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-rag-ragflow) | RAGFlow RAG | `io.agentscope:agentscope-extensions-rag-ragflow` | | [agentscope-extensions-rag-haystack](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-rag-haystack) | HayStack RAG | `io.agentscope:agentscope-extensions-rag-haystack` | #### Session Storage | Module | Feature | Maven Coordinates | |--------|---------|-------------------| | [agentscope-extensions-session-mysql](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-session-mysql) | MySQL Session | `io.agentscope:agentscope-extensions-session-mysql` | | [agentscope-extensions-session-redis](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-session-redis) | Redis Session | `io.agentscope:agentscope-extensions-session-redis` | #### Multi-Agent Collaboration | Module | Feature | Maven Coordinates | |--------|---------|-------------------| | [agentscope-extensions-a2a-client](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-a2a-client) | A2A Client | `io.agentscope:agentscope-extensions-a2a-client` | | [agentscope-extensions-a2a-server](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-a2a-server) | A2A Server | `io.agentscope:agentscope-extensions-a2a-server` | #### Scheduling | Module | Feature | Maven Coordinates | |--------|---------|-------------------| | [agentscope-extensions-scheduler-common](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-scheduler-common) | Scheduler Common | `io.agentscope:agentscope-extensions-scheduler-common` | | [agentscope-extensions-scheduler-xxl-job](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-scheduler-xxl-job) | XXL-Job Scheduler | `io.agentscope:agentscope-extensions-scheduler-xxl-job` | #### User Interface | Module | Feature | Maven Coordinates | |--------|---------|-------------------| | [agentscope-extensions-studio](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-studio) | Studio Integration | `io.agentscope:agentscope-extensions-studio` | | [agentscope-extensions-agui](https://central.sonatype.com/artifact/io.agentscope/agentscope-extensions-agui) | AG-UI Protocol | `io.agentscope:agentscope-extensions-agui` | Extension modules automatically include their required third-party dependencies. #### Example: Core + Mem0 Extension ```xml io.agentscope agentscope-extensions-mem0 1.0.8 ``` ## Framework Integration ### Spring Boot ```xml io.agentscope agentscope-spring-boot-starter 1.0.8 ``` Additional starters: | Starter | Feature | Maven Coordinates | |---------|---------|-------------------| | agentscope-a2a-spring-boot-starter | A2A Integration | `io.agentscope:agentscope-a2a-spring-boot-starter` | | agentscope-agui-spring-boot-starter | AG-UI Integration | `io.agentscope:agentscope-agui-spring-boot-starter` | ### Quarkus ```xml io.agentscope agentscope-quarkus-extension 1.0.8 ``` ### Micronaut ```xml io.agentscope agentscope-micronaut-extension 1.0.8 ``` # Key Concepts This chapter introduces the core concepts in AgentScope from an engineering perspective to help you understand the framework's design philosophy. > **Note**: The goal of this document is to clarify the problems AgentScope solves in engineering practice and the help it provides to developers, rather than providing rigorous academic definitions. ## Core Flow Before diving into each concept, let's understand how an agent works. The core of AgentScope is the **ReAct Loop** (Reasoning + Acting): ``` User Input (Message) | v +-----------------------------------------------------------------------+ | ReActAgent | | | | +---------------------------+ +---------------------------+ | | | Memory | | Toolkit | | | | (stores conversation | | (manages callable tool | | | | history and context) | | functions) | | | +---------------------------+ +---------------------------+ | | | | | | v | | | +-----------------------------------------------+---------------+ | | | 1. Reasoning | | | | +-------------+ +-----------+ +---------------+ | | | | | Memory | --> | Formatter | --> | Model | | | | | | (read hist) | | (convert) | | (call LLM) | | | | | +-------------+ +-----------+ +---------------+ | | | +---------------------------------------------------------------+ | | | | | v | | Need tool call? | | / \ | | Yes No | | / \ | | v v | | +---------------------------+ +---------------------------+ | | | 2. Acting | | Return final response | | | | +---------+ | +---------------------------+ | | | | Toolkit | (exec tool) | | | | | +---------+ | | | | | | | | | | | v | | | | | Store result in Memory | | | | | | | | | | | v | | | | | Back to step 1 | | | | +---------------------------+ | | | | | +-----------------------------------------------------------------------+ | v Agent Response (Message) ``` Now let's explore each concept in detail. --- ## Message **Problem solved**: Agents need a unified data structure to carry various types of information—text, images, tool calls, etc. Message is the most fundamental data structure in AgentScope, used for: - Exchanging information between agents - Storing conversation history in memory - Serving as a unified medium for LLM API interactions **Core fields**: | Field | Description | |-------|-------------| | `name` | Sender's name, used to distinguish identities in multi-agent scenarios | | `role` | Role: `USER`, `ASSISTANT`, `SYSTEM`, or `TOOL` | | `content` | List of content blocks, supports multiple types | | `metadata` | Optional structured data | **Content types**: - `TextBlock` - Plain text - `ImageBlock` / `AudioBlock` / `VideoBlock` - Multimodal content - `ThinkingBlock` - Reasoning traces (for reasoning models) - `ToolUseBlock` - Tool invocation initiated by LLM - `ToolResultBlock` - Tool execution result **Response Metadata**: Messages returned by Agent contain additional metadata to help understand execution state: | Method | Description | |--------|-------------| | `getGenerateReason()` | Reason for message generation, used to determine next actions | | `getChatUsage()` | Token usage statistics (input/output tokens, time) | **GenerateReason Values**: | Value | Description | |-------|-------------| | `MODEL_STOP` | Task completed normally | | `TOOL_SUSPENDED` | Tool needs external execution, waiting for result | | `REASONING_STOP_REQUESTED` | Paused by Hook during Reasoning phase (HITL) | | `ACTING_STOP_REQUESTED` | Paused by Hook during Acting phase (HITL) | | `INTERRUPTED` | Agent was interrupted | | `MAX_ITERATIONS` | Maximum iterations reached | **Example**: ```java // Create a text message Msg msg = Msg.builder() .name("user") .textContent("What's the weather like in Beijing today?") .build(); // Create a multimodal message Msg imgMsg = Msg.builder() .name("user") .content(List.of( TextBlock.builder().text("What is in this image?").build(), ImageBlock.builder().source(new URLSource("https://example.com/photo.jpg")).build() )) .build(); ``` --- ## Agent **Problem solved**: Need a unified abstraction to encapsulate the logic of "receive message → process → return response". The Agent interface defines the core contract: ```java public interface Agent { Mono call(Msg msg); // Process message and return response Flux stream(Msg msg); // Stream response in real-time void interrupt(); // Stop execution } ``` ### Stateful Design Agents in AgentScope are **stateful objects**. Each Agent instance holds its own: - **Memory**: Conversation history - **Toolkit**: Tool collection and their state - **Configuration**: System prompt, model settings, etc. > **Important**: Since both Agent and Toolkit are stateful, **the same instance cannot be called concurrently**. If you need to handle multiple concurrent requests, create independent Agent instances for each request or use an object pool. ```java // ❌ Wrong: sharing the same agent instance across threads ReActAgent agent = ReActAgent.builder()...build(); executor.submit(() -> agent.call(msg1)); // Concurrency issue! executor.submit(() -> agent.call(msg2)); // Concurrency issue! // ✅ Correct: use independent agent instance for each request executor.submit(() -> { ReActAgent agent = ReActAgent.builder()...build(); agent.call(msg1); }); ``` ### ReActAgent `ReActAgent` is the main implementation provided by the framework, using the ReAct algorithm (Reasoning + Acting loop): ```java ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) .sysPrompt("You are a helpful assistant.") .toolkit(toolkit) // Optional: add tools .build(); // Call the agent Msg response = agent.call(userMsg).block(); ``` > For detailed configuration, see [Creating a ReAct Agent](agent.md). --- ## Tool **Problem solved**: LLMs can only generate text and cannot perform actual operations. Tools enable agents to query databases, call APIs, perform calculations, etc. In AgentScope, a "tool" is a Java method annotated with `@Tool`, supporting: - Instance methods, static methods, class methods - Synchronous or asynchronous calls - Streaming or non-streaming returns **Example**: ```java public class WeatherService { @Tool(name = "get_weather", description = "Get weather for a specified city") public String getWeather( @ToolParam(name = "city", description = "City name") String city) { // Call weather API return "Beijing: Sunny, 25°C"; } } // Register tools Toolkit toolkit = new Toolkit(); toolkit.registerTool(new WeatherService()); ``` > **Important**: `@ToolParam` must explicitly specify the `name` attribute because Java does not preserve method parameter names at runtime. --- ## Memory **Problem solved**: Agents need to remember conversation history to have contextual conversations. Memory manages conversation history. `ReActAgent` automatically: - Adds user messages to memory - Adds tool calls and results to memory - Adds agent responses to memory - Reads memory as context during reasoning Uses `InMemoryMemory` (in-memory storage) by default. For cross-session persistence, see [State Management](../task/state.md). --- ## Formatter **Problem solved**: Different LLM providers have different API formats, requiring an adapter layer to abstract away differences. Formatter is responsible for converting AgentScope messages to the format required by specific LLM APIs, including: - Prompt engineering (adding system prompts, formatting multi-turn conversations) - Message validation - Identity handling in multi-agent scenarios **Built-in implementations**: - `DashScopeFormatter` - Alibaba Cloud DashScope (Qwen series) - `OpenAIFormatter` - OpenAI and compatible APIs > Formatter is automatically selected based on Model type; manual configuration is usually not needed. --- ## Hook **Problem solved**: Need to insert custom logic at various stages of agent execution, such as logging, monitoring, message modification, etc. Hook provides extension points at key nodes of the ReAct loop through an event mechanism: | Event Type | Trigger Point | Modifiable | |------------|---------------|------------| | `PreCallEvent` | Before agent starts processing | ✓ | | `PostCallEvent` | After agent completes processing | ✓ | | `PreReasoningEvent` | Before calling LLM | ✓ | | `PostReasoningEvent` | After LLM returns | ✓ | | `ReasoningChunkEvent` | During LLM streaming output | - | | `PreActingEvent` | Before executing tool | ✓ | | `PostActingEvent` | After tool execution | ✓ | | `ActingChunkEvent` | During tool streaming output | - | | `ErrorEvent` | When error occurs | - | **Hook Priority**: Hooks execute in priority order (lower value = higher priority), default is 100. **Example**: ```java Hook loggingHook = new Hook() { @Override public Mono onEvent(T event) { return switch (event) { case PreCallEvent e -> { System.out.println("Agent starting..."); yield Mono.just(event); } case ReasoningChunkEvent e -> { System.out.print(e.getIncrementalChunk().getTextContent()); // Print streaming output yield Mono.just(event); } case PostCallEvent e -> { System.out.println("Completed: " + e.getFinalMessage().getTextContent()); yield Mono.just(event); } default -> Mono.just(event); }; } @Override public int priority() { return 50; // High priority } }; ReActAgent agent = ReActAgent.builder() // ... other configurations .hook(loggingHook) .build(); ``` > For detailed usage, see [Hook System](../task/hook.md). --- ## State Management and Session **Problem solved**: Agent state such as conversation history and configuration needs to be saved and restored to support session persistence. AgentScope separates "initialization" from "state": - `saveState()` - Export current state as a serializable Map - `loadState()` - Restore from saved state **Session** provides persistent storage across runs: ```java // Save session SessionManager.forSessionId("user123") .withSession(new JsonSession(Path.of("sessions"))) .addComponent(agent) .saveSession(); // Restore session SessionManager.forSessionId("user123") .withSession(new JsonSession(Path.of("sessions"))) .addComponent(agent) .loadIfExists(); ``` --- ## Reactive Programming **Problem solved**: LLM calls and tool execution typically involve I/O operations; synchronous blocking wastes resources. AgentScope is built on [Project Reactor](https://projectreactor.io/), using: - `Mono` - Returns 0 or 1 result - `Flux` - Returns 0 to N results (for streaming) ```java // Non-blocking call Mono responseMono = agent.call(msg); // Block when result is needed Msg response = responseMono.block(); // Or handle asynchronously responseMono.subscribe(response -> System.out.println(response.getTextContent()) ); ``` --- ## Next Steps - [Creating a ReAct Agent](agent.md) - Complete agent creation tutorial - [Tool System](../task/tool.md) - Learn advanced tool usage - [Hook System](../task/hook.md) - Customize agent behavior - [Model Integration](../task/model.md) - Integrate different LLM providers # Create ReAct Agent AgentScope provides an out-of-the-box ReAct agent `ReActAgent` for developers. It supports the following features: - **Basic Features** - Hooks around `reasoning` and `acting` - Structured output - **Realtime Steering** - User interruption - Custom interrupt handling - **Tools** - Sync/async tool functions - Streaming tool responses - Parallel tool calls - MCP server integration - **Memory** - Agent-controlled long-term memory - Static long-term memory management ## Creating ReActAgent The `ReActAgent` class exposes the following parameters in its constructor: | Parameter | Further Reading | Description | |-----------|-----------------|-------------| | `name` (required) | | Agent's name | | `sysPrompt` (required) | | System prompt | | `model` (required) | [Model Integration](../task/model.md) | Model for generating responses | | `toolkit` | [Tool System](../task/tool.md) | Module for registering/calling tool functions | | `memory` | [Memory Management](../task/memory.md) | Short-term memory for conversation history | | `longTermMemory` | [Long-term Memory](../task/long-term-memory.md) | Long-term memory | | `longTermMemoryMode` | [Long-term Memory](../task/long-term-memory.md) | Long-term memory mode: `AGENT_CONTROL`, `STATIC_CONTROL`, or `BOTH` | | `maxIters` | | Max iterations for generating response (default: 10) | | `hooks` | [Hook System](../task/hook.md) | Event hooks for customizing agent behavior | | `modelExecutionConfig` | | Timeout/retry config for model calls | | `toolExecutionConfig` | | Timeout/retry config for tool calls | Using DashScope API as an example, we create an agent as follows: ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.message.Msg; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.tool.Toolkit; import io.agentscope.core.tool.Tool; import io.agentscope.core.tool.ToolParam; public class QuickStart { public static void main(String[] args) { // Prepare tools Toolkit toolkit = new Toolkit(); toolkit.registerTool(new SimpleTools()); // Create agent ReActAgent jarvis = ReActAgent.builder() .name("Jarvis") .sysPrompt("You are an assistant named Jarvis.") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) .toolkit(toolkit) .build(); // Send message Msg msg = Msg.builder() .textContent("Hello Jarvis, what time is it now?") .build(); Msg response = jarvis.call(msg).block(); System.out.println(response.getTextContent()); } } // Tool class class SimpleTools { @Tool(name = "get_time", description = "Get current time") public String getTime( @ToolParam(name = "zone", description = "Timezone, e.g., Beijing") String zone) { return java.time.LocalDateTime.now() .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } } ``` ## Additional Configuration ### Execution Control ```java ReActAgent agent = ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful assistant.") .model(model) .maxIters(10) // Max iterations (default: 10) .checkRunning(true) // Prevent concurrent calls (default: true) .build(); ``` ### Timeout and Retry ```java ExecutionConfig modelConfig = ExecutionConfig.builder() .timeout(Duration.ofMinutes(2)) .maxAttempts(3) .build(); ExecutionConfig toolConfig = ExecutionConfig.builder() .timeout(Duration.ofSeconds(30)) .maxAttempts(1) // Tools typically don't retry .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .modelExecutionConfig(modelConfig) .toolExecutionConfig(toolConfig) .build(); ``` ### Tool Execution Context Pass business context (e.g., user info) to tools without exposing to LLM: ```java ToolExecutionContext context = ToolExecutionContext.builder() .register(new UserContext("user-123")) .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .toolExecutionContext(context) .build(); // Auto-injected in tool @Tool(name = "query", description = "Query data") public String query( @ToolParam(name = "sql") String sql, UserContext ctx // Auto-injected, no @ToolParam needed ) { return "Query result for user " + ctx.getUserId(); } ``` ### Plan Management Enable PlanNotebook for complex multi-step tasks: ```java // Quick enable ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .enablePlan() .build(); // Custom configuration PlanNotebook planNotebook = PlanNotebook.builder() .maxSubtasks(15) .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .planNotebook(planNotebook) .build(); ``` ## UserAgent An agent that receives external input (e.g., command line, Web UI): ```java UserAgent user = UserAgent.builder() .name("User") .build(); Msg userInput = user.call(null).block(); ``` model.md # Model This guide introduces the LLM models supported by AgentScope Java and how to configure them. ## Supported Models | Provider | Class | Streaming | Tools | Vision | Reasoning | |------------|-------------------------|-----------|-------|--------|-----------| | DashScope | `DashScopeChatModel` | ✅ | ✅ | ✅ | ✅ | | OpenAI | `OpenAIChatModel` | ✅ | ✅ | ✅ | | | Anthropic | `AnthropicChatModel` | ✅ | ✅ | ✅ | ✅ | | Gemini | `GeminiChatModel` | ✅ | ✅ | ✅ | ✅ | | Ollama | `OllamaChatModel` | ✅ | ✅ | ✅ | ✅ | > **Note**: > - `OpenAIChatModel` is compatible with OpenAI API specification, works with vLLM, DeepSeek, etc. > - `GeminiChatModel` supports both Gemini API and Vertex AI ## Getting API Keys | Provider | URL | Environment Variable | |----------|-----|----------------------| | DashScope | [Alibaba Cloud Bailian Console](https://bailian.console.aliyun.com/) | `DASHSCOPE_API_KEY` | | OpenAI | [OpenAI Platform](https://platform.openai.com/api-keys) | `OPENAI_API_KEY` | | Anthropic | [Anthropic Console](https://console.anthropic.com/settings/keys) | `ANTHROPIC_API_KEY` | | Gemini | [Google AI Studio](https://aistudio.google.com/apikey) | `GEMINI_API_KEY` | | DeepSeek | [DeepSeek Platform](https://platform.deepseek.com/api_keys) | - | ## DashScope Alibaba Cloud LLM platform, providing Qwen series models. ```java DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build(); ``` ### Configuration | Option | Description | |--------|-------------| | `apiKey` | DashScope API key | | `modelName` | Model name, e.g., `qwen3-max`, `qwen-vl-max` | | `baseUrl` | Custom API endpoint (optional) | | `stream` | Enable streaming, default `true` | | `enableThinking` | Enable thinking mode to show reasoning process | | `enableSearch` | Enable web search for real-time information | ### Thinking Mode ```java DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .enableThinking(true) // Automatically enables streaming .defaultOptions(GenerateOptions.builder() .thinkingBudget(5000) // Token budget for thinking .build()) .build(); OllamaChatModel model = OllamaChatModel.builder() .modelName("qwen3-max") .baseUrl("http://localhost:11434") .defaultOptions(OllamaOptions.builder() .thinkOption(ThinkOption.ThinkBoolean.ENABLED) .temperature(0.8) .build()) .build(); ``` ## OpenAI OpenAI models and compatible APIs. ```java OpenAIChatModel model = OpenAIChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-4o") .build(); ``` ### Compatible APIs For DeepSeek, vLLM, and other compatible providers: ```java OpenAIChatModel model = OpenAIChatModel.builder() .apiKey("your-api-key") .modelName("deepseek-chat") .baseUrl("https://api.deepseek.com") .build(); ``` ### Configuration | Option | Description | |--------|-------------| | `apiKey` | API key | | `modelName` | Model name, e.g., `gpt-4o`, `gpt-4o-mini` | | `baseUrl` | Custom API endpoint (optional) | | `stream` | Enable streaming, default `true` | ## Anthropic Anthropic's Claude series models. ```java AnthropicChatModel model = AnthropicChatModel.builder() .apiKey(System.getenv("ANTHROPIC_API_KEY")) .modelName("claude-sonnet-4-5-20250929") // Default .build(); ``` ### Configuration | Option | Description | |--------|-------------| | `apiKey` | Anthropic API key | | `modelName` | Model name, default `claude-sonnet-4-5-20250929` | | `baseUrl` | Custom API endpoint (optional) | | `stream` | Enable streaming, default `true` | ## Gemini Google's Gemini series models, supporting both Gemini API and Vertex AI. ### Gemini API ```java GeminiChatModel model = GeminiChatModel.builder() .apiKey(System.getenv("GEMINI_API_KEY")) .modelName("gemini-2.5-flash") // Default .build(); ``` ### Vertex AI ```java GeminiChatModel model = GeminiChatModel.builder() .modelName("gemini-2.0-flash") .project("your-gcp-project") .location("us-central1") .vertexAI(true) .credentials(GoogleCredentials.getApplicationDefault()) .build(); ``` ### Configuration | Option | Description | |--------|-------------| | `apiKey` | Gemini API key | | `modelName` | Model name, default `gemini-2.5-flash` | | `project` | GCP project ID (Vertex AI) | | `location` | GCP region (Vertex AI) | | `vertexAI` | Whether to use Vertex AI | | `credentials` | GCP credentials (Vertex AI) | | `streamEnabled` | Enable streaming, default `true` | ## Ollama Self-hosted open-source LLM platform supporting various models. ```java OllamaChatModel model = OllamaChatModel.builder() .modelName("qwen3-max") .baseUrl("http://localhost:11434") // Default .build(); ``` ### Configuration | Option | Description | |--------|---------------------------------------------------------------------| | `modelName` | Model name, e.g., `qwen3-max`,`llama3.2`, `mistral`, `phi3` | | `baseUrl` | Ollama server endpoint (optional, default `http://localhost:11434`) | | `defaultOptions` | Default generation options | | `formatter` | Message formatter (optional) | | `httpTransport` | HTTP transport configuration (optional) | ### Advanced Configuration For advanced model loading and generation parameters: ```java OllamaOptions options = OllamaOptions.builder() .numCtx(4096) // Context window size .temperature(0.7) // Generation randomness .topK(40) // Top-K sampling .topP(0.9) // Nucleus sampling .repeatPenalty(1.1) // Repetition penalty .build(); OllamaChatModel model = OllamaChatModel.builder() .modelName("qwen3-max") .baseUrl("http://localhost:11434") .defaultOptions(options) .build(); ``` ### GenerateOptions Support Ollama also supports `GenerateOptions` for standard configuration: ```java GenerateOptions options = GenerateOptions.builder() .temperature(0.7) // Maps to Ollama's temperature .topP(0.9) // Maps to Ollama's top_p .topK(40) // Maps to Ollama's top_k .maxTokens(2000) // Maps to Ollama's num_predict .seed(42L) // Maps to Ollama's seed .frequencyPenalty(0.5) // Maps to Ollama's frequency_penalty .presencePenalty(0.5) // Maps to Ollama's presence_penalty .additionalBodyParam(OllamaOptions.ParamKey.NUM_CTX.getKey(), 4096) // Context window size .additionalBodyParam(OllamaOptions.ParamKey.NUM_GPU.getKey(), -1) // Offload all layers to GPU .additionalBodyParam(OllamaOptions.ParamKey.REPEAT_PENALTY.getKey(), 1.1) // Repetition penalty .additionalBodyParam(OllamaOptions.ParamKey.MAIN_GPU.getKey(), 0) // Main GPU index .additionalBodyParam(OllamaOptions.ParamKey.LOW_VRAM.getKey(), false) // Low VRAM mode .additionalBodyParam(OllamaOptions.ParamKey.F16_KV.getKey(), true) // 16-bit KV cache .additionalBodyParam(OllamaOptions.ParamKey.NUM_THREAD.getKey(), 8) // Number of CPU threads .build(); OllamaChatModel model = OllamaChatModel.builder() .modelName("qwen3-max") .baseUrl("http://localhost:11434") .defaultOptions(OllamaOptions.fromGenerateOptions(options)) // Will be converted to OllamaOptions internally .build(); ``` ### Available Parameters Ollama supports over 40 parameters for fine-tuning: #### Model Loading Parameters - `numCtx`: Context window size (default: 2048) - `numBatch`: Batch size for prompt processing (default: 512) - `numGPU`: Number of layers to offload to GPU (-1 for all) - `lowVRAM`: Enable low VRAM mode for limited GPU memory - `useMMap`: Use memory mapping for model loading - `useMLock`: Lock model in memory to prevent swapping #### Generation Parameters - `temperature`: Generation randomness (0.0-2.0) - `topK`: Top-K sampling (standard: 40) - `topP`: Nucleus sampling (standard: 0.9) - `minP`: Minimum probability threshold (default: 0.0) - `numPredict`: Max tokens to generate (-1 for infinite) - `repeatPenalty`: Penalty for repetitions (default: 1.1) - `presencePenalty`: Penalty based on token presence - `frequencyPenalty`: Penalty based on token frequency - `seed`: Random seed for reproducible results - `stop`: Strings that stop generation immediately #### Sampling Strategies - `mirostat`: Mirostat sampling (0=disabled, 1=Mirostat v1, 2=Mirostat v2) - `mirostatTau`: Target entropy for Mirostat (default: 5.0) - `mirostatEta`: Learning rate for Mirostat (default: 0.1) - `tfsZ`: Tail-free sampling (default: 1.0 disables) - `typicalP`: Typical probability sampling (default: 1.0) ## Generation Options Configure generation parameters with `GenerateOptions`: ```java GenerateOptions options = GenerateOptions.builder() .temperature(0.7) // Randomness (0.0-2.0) .topP(0.9) // Nucleus sampling .topK(40) // Top-K sampling .maxTokens(2000) // Maximum output tokens .seed(42L) // Random seed .toolChoice(new ToolChoice.auto()) // Tool choice strategy .build(); DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .defaultOptions(options) .build(); OllamaChatModel model = OllamaChatModel.builder() .modelName("qwen3-max") .baseUrl("http://localhost:11434") .defaultOptions(OllamaOptions.fromGenerateOptions(options)) .build(); ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `temperature` | Double | Controls randomness, 0.0-2.0 | | `topP` | Double | Nucleus sampling threshold, 0.0-1.0 | | `topK` | Integer | Limits candidate tokens | | `maxTokens` | Integer | Maximum tokens to generate | | `thinkingBudget` | Integer | Token budget for thinking | | `seed` | Long | Random seed | | `toolChoice` | ToolChoice | Tool choice strategy | ### Tool Choice Strategy ```java ToolChoice.auto() // Model decides (default) ToolChoice.none() // Disable tool calling ToolChoice.required() // Force tool calling ToolChoice.specific("tool_name") // Force specific tool ``` ### Additional Parameters Support for provider-specific parameters: ```java GenerateOptions options = GenerateOptions.builder() .additionalHeader("X-Custom-Header", "value") .additionalBodyParam("custom_param", "value") .additionalQueryParam("version", "v2") .build(); ``` ## Timeout and Retry ```java ExecutionConfig execConfig = ExecutionConfig.builder() .timeout(Duration.ofMinutes(2)) .maxAttempts(3) .initialBackoff(Duration.ofSeconds(1)) .maxBackoff(Duration.ofSeconds(10)) .backoffMultiplier(2.0) .build(); GenerateOptions options = GenerateOptions.builder() .executionConfig(execConfig) .build(); ``` ## Formatter Formatter converts AgentScope's unified message format to each LLM provider's API format. Each provider has two types of Formatter: | Provider | Single-Agent | Multi-Agent | |----------|--------------|-------------| | DashScope | `DashScopeChatFormatter` | `DashScopeMultiAgentFormatter` | | OpenAI | `OpenAIChatFormatter` | `OpenAIMultiAgentFormatter` | | Anthropic | `AnthropicChatFormatter` | `AnthropicMultiAgentFormatter` | | Gemini | `GeminiChatFormatter` | `GeminiMultiAgentFormatter` | | Ollama | `OllamaChatFormatter` | `OllamaMultiAgentFormatter` | ### Default Behavior When no Formatter is specified, the model uses the corresponding `ChatFormatter`, suitable for single-agent scenarios. ### Multi-Agent Scenarios In multi-agent collaboration (such as Pipeline, MsgHub), use `MultiAgentFormatter`. It will: - Merge messages from multiple agents into conversation history - Use `` tags to structure historical messages - Distinguish between current agent and other agents' messages ```java // DashScope multi-agent DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .formatter(new DashScopeMultiAgentFormatter()) .build(); // OpenAI multi-agent OpenAIChatModel model = OpenAIChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-4o") .formatter(new OpenAIMultiAgentFormatter()) .build(); // Anthropic multi-agent AnthropicChatModel model = AnthropicChatModel.builder() .apiKey(System.getenv("ANTHROPIC_API_KEY")) .formatter(new AnthropicMultiAgentFormatter()) .build(); // Gemini multi-agent GeminiChatModel model = GeminiChatModel.builder() .apiKey(System.getenv("GEMINI_API_KEY")) .formatter(new GeminiMultiAgentFormatter()) .build(); // Ollama multi-agent OllamaChatModel model = OllamaChatModel.builder() .modelName("qwen3-max") .formatter(new OllamaMultiAgentFormatter()) .build(); ``` ### Custom History Prompt You can customize the conversation history prompt: ```java String customPrompt = "# Conversation Record\nBelow is the previous conversation:\n"; DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .formatter(new DashScopeMultiAgentFormatter(customPrompt)) .build(); ``` ### When to Use MultiAgentFormatter | Scenario | Recommended Formatter | |----------|----------------------| | Single-agent conversation | `ChatFormatter` (default) | | Pipeline sequential execution | `MultiAgentFormatter` | | MsgHub group chat | `MultiAgentFormatter` | | Multi-agent debate | `MultiAgentFormatter` | # Tool The tool system enables agents to perform external operations such as API calls, database queries, file operations, etc. ## Core Features - **Annotation-Based**: Quickly define tools using `@Tool` and `@ToolParam` - **Reactive Programming**: Native support for `Mono`/`Flux` async execution - **Auto Schema**: Automatically generate JSON Schema for LLM understanding - **Tool Groups**: Dynamically activate/deactivate tool collections - **Preset Parameters**: Hide sensitive parameters (e.g., API Keys) - **Parallel Execution**: Support parallel invocation of multiple tools ## Quick Start ### Define Tools ```java public class WeatherService { @Tool(description = "Get weather for a specified city") public String getWeather( @ToolParam(name = "city", description = "City name") String city) { return city + " weather: Sunny, 25°C"; } } ``` > **Note**: The `name` attribute of `@ToolParam` is required because Java doesn't preserve parameter names by default. ### Register and Use ```java Toolkit toolkit = new Toolkit(); toolkit.registerTool(new WeatherService()); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .build(); ``` ## Tool Types ### Sync Tools Return results directly, suitable for quick operations: ```java @Tool(description = "Calculate sum of two numbers") public int add( @ToolParam(name = "a", description = "First number") int a, @ToolParam(name = "b", description = "Second number") int b) { return a + b; } ``` ### Async Tools Return `Mono` or `Flux`, suitable for I/O operations: ```java @Tool(description = "Async search") public Mono search( @ToolParam(name = "query", description = "Search query") String query) { return webClient.get() .uri("/search?q=" + query) .retrieve() .bodyToMono(String.class); } ``` ### Streaming Tools Use `ToolEmitter` to send intermediate progress, suitable for long-running tasks: ```java @Tool(description = "Generate data") public ToolResultBlock generate( @ToolParam(name = "count") int count, ToolEmitter emitter) { // Auto-injected, no @ToolParam needed for (int i = 0; i < count; i++) { emitter.emit(ToolResultBlock.text("Progress " + i)); } return ToolResultBlock.text("Completed"); } ``` ### Return Types | Return Type | Description | |-------------|-------------| | `String`, `int`, `Object`, etc. | Sync execution, auto-converted to `ToolResultBlock` | | `Mono` | Async execution | | `Flux` | Streaming execution | | `ToolResultBlock` | Direct control over return format (text, image, error, etc.) | ## Tool Groups Manage tools by scenario with dynamic activation/deactivation: ```java // Create tool groups toolkit.createToolGroup("basic", "Basic Tools", true); // Active by default toolkit.createToolGroup("admin", "Admin Tools", false); // Inactive by default // Register to tool groups toolkit.registration() .tool(new BasicTools()) .group("basic") .apply(); // Dynamic switching toolkit.updateToolGroups(List.of("admin"), true); // Activate toolkit.updateToolGroups(List.of("basic"), false); // Deactivate ``` **Use Cases**: - Permission control: Activate different tools based on user roles - Scenario switching: Use different tool sets at different conversation stages - Performance optimization: Reduce the number of tools visible to LLM ## Preset Parameters Hide sensitive parameters (e.g., API Key) from LLM: ```java public class EmailService { @Tool(description = "Send email") public String send( @ToolParam(name = "to") String to, @ToolParam(name = "subject") String subject, @ToolParam(name = "apiKey") String apiKey) { // Preset, invisible to LLM return "Sent"; } } toolkit.registration() .tool(new EmailService()) .presetParameters(Map.of( "send", Map.of("apiKey", System.getenv("EMAIL_API_KEY")) )) .apply(); ``` **Effect**: LLM only sees `to` and `subject` parameters; `apiKey` is auto-injected. ## Tool Execution Context Pass business objects (e.g., user info) to tools without exposing to LLM: ```java // 1. Define context class public class UserContext { private final String userId; public UserContext(String userId) { this.userId = userId; } public String getUserId() { return userId; } } // 2. Register to Agent ToolExecutionContext context = ToolExecutionContext.builder() .register(new UserContext("user-123")) .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .toolExecutionContext(context) .build(); // 3. Use in tools (auto-injected) @Tool(description = "Query user data") public String query( @ToolParam(name = "sql") String sql, UserContext ctx) { // Auto-injected, no @ToolParam needed return "Data for user " + ctx.getUserId(); } ``` > See [Agent](../quickstart/agent.md) documentation for detailed configuration. ## Built-in Tools ### File Tools ```java import io.agentscope.core.tool.file.ReadFileTool; import io.agentscope.core.tool.file.WriteFileTool; // Basic registration toolkit.registerTool(new ReadFileTool()); toolkit.registerTool(new WriteFileTool()); // Secure mode (recommended): Restrict file access scope toolkit.registerTool(new ReadFileTool("/safe/workspace")); toolkit.registerTool(new WriteFileTool("/safe/workspace")); ``` | Tool | Method | Description | |------|--------|-------------| | `ReadFileTool` | `view_text_file` | View files by line range | | `WriteFileTool` | `write_text_file` | Create/overwrite/replace file content | | `WriteFileTool` | `insert_text_file` | Insert content at specified line | ### Shell Command Tool | Tool | Features | |------|----------| | `ShellCommandTool` | Execute shell commands with whitelist, callback approval, and timeout support | **Quick Start:** ```java import io.agentscope.core.tool.coding.ShellCommandTool; Function callback = cmd -> askUserForApproval(cmd); toolkit.registerTool(new ShellCommandTool(allowedCommands, callback)); ``` ### Multimodal Tools ```java import io.agentscope.core.tool.multimodal.DashScopeMultiModalTool; import io.agentscope.core.tool.multimodal.OpenAIMultiModalTool; toolkit.registerTool(new DashScopeMultiModalTool(System.getenv("DASHSCOPE_API_KEY"))); toolkit.registerTool(new OpenAIMultiModalTool(System.getenv("OPENAI_API_KEY"))); ``` | Tool | Capabilities | |------|--------------| | `DashScopeMultiModalTool` | Text-to-image, image-to-text, text-to-speech, speech-to-text | | `OpenAIMultiModalTool` | Text-to-image, image editing, image variations, image-to-text, text-to-speech, speech-to-text | ### Sub-agent Tools Agents can be registered as tools for other agents to call. See [Agent as Tool](../multi-agent/agent-as-tool.md) for details. ## AgentTool Interface For fine-grained control, implement the interface directly: ```java public class CustomTool implements AgentTool { @Override public String getName() { return "custom_tool"; } @Override public String getDescription() { return "Custom tool"; } @Override public Map getParameters() { return Map.of( "type", "object", "properties", Map.of( "query", Map.of("type", "string", "description", "Query") ), "required", List.of("query") ); } @Override public Mono callAsync(ToolCallParam param) { String query = (String) param.getInput().get("query"); return Mono.just(ToolResultBlock.text("Result: " + query)); } } ``` ## Configuration Options ```java Toolkit toolkit = new Toolkit(ToolkitConfig.builder() .parallel(true) // Parallel execution of multiple tools .allowToolDeletion(false) // Prevent tool deletion .executionConfig(ExecutionConfig.builder() .timeout(Duration.ofSeconds(30)) .build()) .build()); ``` | Option | Description | Default | |--------|-------------|---------| | `parallel` | Whether to execute multiple tools in parallel | `true` | | `allowToolDeletion` | Whether to allow tool deletion | `true` | | `executionConfig.timeout` | Tool execution timeout | 5 minutes | ## Meta Tools Allow agents to autonomously manage tool groups: ```java toolkit.registerMetaTool(); // Agent can call "reset_equipped_tools" to activate/deactivate tool groups ``` When there are many tool groups, agents can autonomously choose which groups to activate based on task requirements. ## Tool Suspend When a tool throws `ToolSuspendException`, the Agent execution pauses and returns to the caller, allowing external systems to perform the actual execution before resuming. **Use Cases**: - Tool requires external system execution (e.g., remote API, user manual operation) - Need to asynchronously wait for external results **Usage**: ```java @Tool(name = "external_api", description = "Call external API") public ToolResultBlock callExternalApi( @ToolParam(name = "url") String url) { // Throw exception to suspend execution throw new ToolSuspendException("Awaiting external API response: " + url); } ``` **Resume Execution**: ```java Msg response = agent.call(userMsg).block(); // Check if suspended if (response.getGenerateReason() == GenerateReason.TOOL_SUSPENDED) { // Get pending tool calls List pendingTools = response.getContentBlocks(ToolUseBlock.class); // After external execution, provide result Msg toolResult = Msg.builder() .role(MsgRole.TOOL) .content(ToolResultBlock.of(toolUse.getId(), toolUse.getName(), TextBlock.builder().text("External execution result").build())) .build(); // Resume execution response = agent.call(toolResult).block(); } ``` ## Schema Only Tool Register only the tool's schema (name, description, parameters) without execution logic. When LLM calls this tool, the framework automatically triggers suspension and returns to the caller for execution. **Use Cases**: - Tool implemented by external systems (e.g., frontend, other services) - Dynamically register third-party tools **Usage**: ```java // Method 1: Using ToolSchema ToolSchema schema = ToolSchema.builder() .name("query_database") .description("Query external database") .parameters(Map.of( "type", "object", "properties", Map.of("sql", Map.of("type", "string")), "required", List.of("sql") )) .build(); toolkit.registerSchema(schema); // Method 2: Batch registration toolkit.registerSchemas(List.of(schema1, schema2)); // Check if it's an external tool boolean isExternal = toolkit.isExternalTool("query_database"); // true ``` The call flow is the same as Tool Suspend: LLM calls → returns `TOOL_SUSPENDED` → external execution → provide result to resume. # MCP (Model Context Protocol) AgentScope Java provides full support for Model Context Protocol (MCP), enabling agents to connect to external tool servers and use tools from the MCP ecosystem. ## What is MCP? MCP is a standard protocol for connecting AI applications to external data sources and tools. It enables: - **Unified Tool Interface**: Access diverse tools through a single protocol - **External Tool Servers**: Connect to specialized services (filesystem, git, databases, etc.) - **Ecosystem Integration**: Use tools from the growing MCP ecosystem - **Flexible Transport**: Support for StdIO, SSE, and HTTP transports ## Transport Types AgentScope supports three MCP transport mechanisms: | Transport | Use Case | Connection | State | |-----------|----------|------------|-------| | **StdIO** | Local process communication | Spawns child process | Stateful | | **SSE** | HTTP Server-Sent Events | HTTP streaming | Stateful | | **HTTP** | Streamable HTTP | Request/response | Stateless | ## Quick Start ### 1. Connect to MCP Server ```java import io.agentscope.core.tool.mcp.McpClientBuilder; import io.agentscope.core.tool.mcp.McpClientWrapper; // StdIO transport - connect to local MCP server McpClientWrapper mcpClient = McpClientBuilder.create("filesystem-mcp") .stdioTransport("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp") .buildAsync() .block(); ``` ### 2. Register MCP Tools ```java import io.agentscope.core.tool.Toolkit; Toolkit toolkit = new Toolkit(); // Register all tools from MCP server toolkit.registerMcpClient(mcpClient).block(); ``` ### 3. Configure MCP in Agent ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.memory.InMemoryMemory; ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) // MCP tools are now available .memory(new InMemoryMemory()) .build(); ``` ## Transport Configuration ### StdIO Transport For local process communication: ```java // Filesystem server McpClientWrapper fsClient = McpClientBuilder.create("fs-mcp") .stdioTransport("npx", "-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir") .buildAsync() .block(); // Git server McpClientWrapper gitClient = McpClientBuilder.create("git-mcp") .stdioTransport("python", "-m", "mcp_server_git") .buildAsync() .block(); // Custom command McpClientWrapper customClient = McpClientBuilder.create("custom-mcp") .stdioTransport("/path/to/executable", "arg1", "arg2") .buildAsync() .block(); ``` ### SSE Transport For HTTP Server-Sent Events: ```java McpClientWrapper sseClient = McpClientBuilder.create("remote-mcp") .sseTransport("https://mcp.example.com/sse") .header("Authorization", "Bearer " + apiToken) .queryParam("queryKey", "queryValue") .timeout(Duration.ofSeconds(60)) .buildAsync() .block(); ``` ### HTTP Transport For stateless HTTP: ```java McpClientWrapper httpClient = McpClientBuilder.create("http-mcp") .streamableHttpTransport("https://mcp.example.com/http") .header("X-API-Key", apiKey) .queryParam("queryKey", "queryValue") .buildAsync() .block(); ``` ## Tool Filtering Control which MCP tools to register: ### Enable Specific Tools ```java // Only enable specific tools List enableTools = List.of("read_file", "write_file", "list_directory"); toolkit.registration().mcpClient(mcpClient).enableTools(enableTools).apply(); ``` ### Disable Specific Tools ```java // Enable all except blacklisted tools List disableTools = List.of("delete_file", "move_file"); toolkit.registration().mcpClient(mcpClient).disableTools(disableTools).apply(); ``` ### Both Enable and Disable ```java // Whitelist with blacklist List enableTools = List.of("read_file", "list_directory"); List disableTools = List.of("write_file"); toolkit.registration().mcpClient(mcpClient).enableTools(enableTools).disableTools(disableTools).apply(); ``` ## Tool Groups Assign MCP tools to a group for selective activation: ```java // Create tool group and activate Toolkit toolkit = new Toolkit(); String groupName = "filesystem"; toolkit.createToolGroup(groupName, "Tools for operating system files", true); // Register MCP tools in a group toolkit.registration().mcpClient(mcpClient).group("groupName").apply(); // Create agent that only uses specific groups ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .build(); ``` ## Configuration Options ### Timeouts ```java import java.time.Duration; McpClientWrapper client = McpClientBuilder.create("mcp") .stdioTransport("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp") .timeout(Duration.ofSeconds(120)) // Request timeout .initializationTimeout(Duration.ofSeconds(30)) // Init timeout .buildAsync() .block(); ``` ### HTTP Headers ```java McpClientWrapper client = McpClientBuilder.create("mcp") .sseTransport("https://mcp.example.com/sse") .header("Authorization", "Bearer " + token) .header("X-Client-Version", "1.0") .header("X-Custom-Header", "value") .buildAsync() .block(); ``` ### Query Parameters Add URL query parameters for HTTP transports: ```java // Single parameter McpClientWrapper client = McpClientBuilder.create("mcp") .sseTransport("https://mcp.example.com/sse") .queryParam("queryKey1", "queryValue1") .queryParam("queryKey2", "queryValue2") .buildAsync() .block(); // Multiple parameters at once McpClientWrapper client = McpClientBuilder.create("mcp") .streamableHttpTransport("https://mcp.example.com/http") .queryParams(Map.of("queryKey1", "queryValue1", "queryKey2", "queryValue2")) .buildAsync() .block(); // Merge with existing URL parameters (additional params take precedence) McpClientWrapper client = McpClientBuilder.create("mcp") .sseTransport("https://mcp.example.com/sse?version=v1") .queryParam("queryKey", "queryValue") // Result: ?version=v1&queryKey=queryValue .buildAsync() .block(); ``` > **Note**: Query parameters only apply to HTTP transports (SSE and HTTP). They are ignored for StdIO transport. ### Synchronous vs Asynchronous Clients ```java // Asynchronous client (recommended) McpClientWrapper asyncClient = McpClientBuilder.create("async-mcp") .stdioTransport("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp") .buildAsync() .block(); // Synchronous client (for blocking operations) McpClientWrapper syncClient = McpClientBuilder.create("sync-mcp") .stdioTransport("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp") .buildSync(); ``` ## Managing MCP Clients ### List Tools from MCP Server ```java // After registration, tools appear in toolkit Set toolNames = toolkit.getToolNames(); System.out.println("Available tools: " + toolNames); ``` ### Remove MCP Client ```java // Remove MCP client and all its tools toolkit.removeMcpClient("filesystem-mcp").block(); ``` ## Higress AI Gateway Integration AgentScope provides a Higress AI Gateway extension that enables unified access to MCP tools through the Higress gateway, with semantic search capabilities to automatically select the most suitable tools. ### Add Dependency ```xml io.agentscope agentscope-extensions-higress ${agentscope.version} ``` ### Basic Usage ```java import io.agentscope.extensions.higress.HigressMcpClientBuilder; import io.agentscope.extensions.higress.HigressMcpClientWrapper; import io.agentscope.extensions.higress.HigressToolkit; // 1. Create Higress MCP client HigressMcpClientWrapper higressClient = HigressMcpClientBuilder .create("higress") .streamableHttpEndpoint("your higress mcp server endpoint") .buildAsync() .block(); // 2. Register with HigressToolkit HigressToolkit toolkit = new HigressToolkit(); toolkit.registerMcpClient(higressClient).block(); ``` ### Enable Semantic Tool Search Use the `toolSearch()` method to enable semantic search. Higress will automatically select the most relevant tools for your query: ```java // Enable tool search, return top 5 most relevant tools HigressMcpClientWrapper higressClient = HigressMcpClientBuilder .create("higress") .streamableHttpEndpoint("http://your-higress-gateway/mcp-servers/union-tools-search") .toolSearch("query weather and map information", 5) // query and topK .buildAsync() .block(); ``` ### Higress Example See the complete Higress example: - `agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/HigressToolExample.java` ## Complete Example See the complete MCP example: - `agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/McpToolExample.java` Run the example: ```bash cd agentscope-examples/quickstart mvn exec:java -Dexec.mainClass="io.agentscope.examples.quickstart.McpToolExample" ``` # Agent Skills ## Overview Agent Skills are modular skill packages that extend agent capabilities. Each Skill contains instructions, metadata, and optional resources (such as scripts, reference documentation, examples, etc.), which agents will automatically use for relevant tasks. **Reference**: [Claude Agent Skills Official Documentation](https://platform.claude.com/docs/zh-CN/agents-and-tools/agent-skills/overview) ## Core Features ### Progressive Disclosure Mechanism Adopts **three-stage on-demand loading** to optimize context: Initially loads only metadata (~100 tokens/Skill) → AI loads complete instructions when needed (<5k tokens) → On-demand access to resource files. Tools are also progressively disclosed, activated only when the Skill is in use. **Workflow:** User Query → AI Identifies Relevant Skill → Calls `load_skill_through_path` Tool to Load Content and Activate Bound Tools → On-Demand Resource Access → Task Completion **Unified Loading Tool**: `load_skill_through_path(skillId, resourcePath)` provides a single entry point for loading skill resources - `skillId` uses an enum field, ensuring selection only from registered Skills, guaranteeing accuracy - `resourcePath` is the resource path relative to the Skill root directory (e.g., `references/api-doc.md`) - Returns a list of all available resource paths when the path is incorrect, helping the LLM correct errors ### Adaptive Design We have further abstracted skills so that their discovery and content loading are no longer dependent on the file system. Instead, the LLM discovers and loads skill content and resources through tools. At the same time, to maintain compatibility with the existing skill ecosystem and resources, skills are still organized according to file system structure for their content and resources. **Organize your skill content and resources just like organizing a skill directory in a file system!** Taking the [Skill Structure](#skill-structure) as an example, this directory-structured skill is represented in our system as: ```java AgentSkill skill = new AgentSkill.builder() .name("data_analysis") .description("Use this skill when analyzing data, calculating statistics, or generating reports") .skillContent("# Data Analysis\n...") .addResource("references/api-doc.md", "# API Reference\n...") .addResource("references/best-practices.md", "# Best Practices\n...") .addResource("examples/example1.java", "public class Example1 {\n...\n}") .addResource("scripts/process.py", "def process(data): ...\n") .build(); ``` ### Skill Structure ```text skill-name/ ├── SKILL.md # Required: Entry file with YAML frontmatter and instructions ├── references/ # Optional: Detailed reference documentation │ ├── api-doc.md │ └── best-practices.md ├── examples/ # Optional: Working examples │ └── example1.java └── scripts/ # Optional: Executable scripts └── process.py ``` ### SKILL.md Format Specification ```yaml --- name: skill-name # Required: Skill name (lowercase letters, numbers, underscores) description: This skill should be used when... # Required: Trigger description, explaining when to use --- # Skill Name ## Feature Overview [Detailed description of the skill's functionality] ## Usage Instructions [Usage steps and best practices] ## Available Resources - references/api-doc.md: API reference documentation - scripts/process.py: Data processing script ``` **Required Fields:** - `name` - Skill name (lowercase letters, numbers, underscores) - `description` - Skill functionality and usage scenarios, helps AI determine when to use ## Quick Start ### 1. Create a Skill #### Method 1: Using Builder ```java AgentSkill skill = AgentSkill.builder() .name("data_analysis") .description("Use when analyzing data...") .skillContent("# Data Analysis\n...") .addResource("references/formulas.md", "# Common Formulas\n...") .source("custom") .build(); ``` #### Method 2: Create from Markdown ```java // Prepare SKILL.md content String skillMd = """ --- name: data_analysis description: Use this skill when analyzing data, calculating statistics, or generating reports --- # Skill Name Content... """; // Prepare resource files (optional) Map resources = Map.of( "references/formulas.md", "# Common Formulas\n...", "examples/sample.csv", "name,value\nA,100\nB,200" ); // Create Skill AgentSkill skill = SkillUtil.createFrom(skillMd, resources); ``` #### Method 3: Direct Construction ```java AgentSkill skill = new AgentSkill( "data_analysis", // name "Use when analyzing data...", // description "# Data Analysis\n...", // skillContent resources // resources (can be null) ); ``` ### 2. Integrate with ReActAgent #### Using SkillBox ```java Toolkit toolkit = new Toolkit(); SkillBox skillBox = new SkillBox(toolkit); skillBox.registerSkill(skill1); ReActAgent agent = ReActAgent.builder() .name("DataAnalyst") .model(model) .toolkit(toolkit) .skillBox(skillBox) // Automatically registers skill tools and hook .memory(new InMemoryMemory()) .build(); ``` ### 3. Use Skills ## Simplified Integration ```java SkillBox skillBox = new SkillBox(); skillBox.registerSkill(dataSkill); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .skillBox(skillBox) .build(); ``` ## Advanced Features ### Feature 1: Progressive Disclosure of Tools Bind Tools to Skills for on-demand activation. Avoids context pollution from pre-registering all Tools, only passing relevant Tools to LLM when the Skill is actively used. **Lifecycle of Progressively Disclosed Tools**: Tool lifecycle remains consistent with Skill lifecycle. Once a Skill is activated, Tools remain available throughout the entire session, avoiding the call failures caused by Tool deactivation after each conversation round in the old mechanism. **Example Code**: ```java Toolkit toolkit = new Toolkit(); SkillBox skillBox = new SkillBox(toolkit); AgentSkill dataSkill = AgentSkill.builder() .name("data_analysis") .description("Comprehensive data analysis capabilities") .skillContent("# Data Analysis\n...") .build(); AgentTool loadDataTool = new AgentTool(...); skillBox.registration() .skill(dataSkill) .tool(loadDataTool) .apply(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .skillBox(skillBox) .build(); ``` ### Feature 2: Code Execution Capabilities Provides an isolated code execution folder for Skills, supporting Shell commands, file read/write operations, etc. Uses Builder pattern for flexible configuration of required tools. **Basic Usage**: ```java SkillBox skillBox = new SkillBox(toolkit); // Enable all code execution tools (Shell, read file, write file) skillBox.codeExecution() .withShell() .withRead() .withWrite() .enable(); ``` **Custom Configuration**: ```java // Customize working directory and Shell command whitelist ShellCommandTool customShell = new ShellCommandTool( null, // baseDir will be automatically set to workDir Set.of("python3", "node", "npm"), command -> askUserApproval(command) // Optional command approval callback ); skillBox.codeExecution() .workDir("/path/to/workdir") // Specify working directory .withShell(customShell) // Use custom Shell tool .withRead() // Enable file reading .withWrite() // Enable file writing .enable(); // Or enable only file operations, without Shell skillBox.codeExecution() .withRead() .withWrite() .enable(); ``` **Core Features**: - **Unified Working Directory**: All tools share the same `workDir`, ensuring file isolation - **Selective Enabling**: Flexibly combine Shell, read file, and write file tools as needed - **Flexible Configuration**: Supports custom ShellCommandTool to meet customization requirements - **Automatic Management**: Automatically creates temporary directory when `workDir` is not specified, with automatic cleanup on program exit ### Feature 3: Skill Persistence Storage **Why is this feature needed?** Skills need to remain available after application restart, or be shared across different environments. Persistence storage supports: #### File System Storage ```java AgentSkillRepository repo = new FileSystemSkillRepository(Path.of("./skills")); repo.save(List.of(skill), false); AgentSkill loaded = repo.getSkill("data_analysis"); ``` #### MySQL Database Storage (not yet implemented) #### Git Repository (Read-Only) Used to load Skills from a Git repository (read-only). Supports HTTPS and SSH. **Update mechanism** - By default, each read triggers a lightweight remote reference check; a pull runs only when the remote HEAD changes. - You can disable auto-sync via the constructor and call `sync()` manually when you want to refresh. ```java AgentSkillRepository repo = new GitSkillRepository( "https://github.com/your-org/your-skills-repo.git"); AgentSkill skill = repo.getSkill("data-analysis"); List allSkills = repo.getAllSkills(); GitSkillRepository manualRepo = new GitSkillRepository( "https://github.com/your-org/your-skills-repo.git", false); manualRepo.sync(); ``` If the repository contains a `skills/` subdirectory, it will be used; otherwise the repo root is used. #### Classpath Repository (Read-Only) Used to load pre-packaged Skills from classpath resources. Automatically compatible with standard JARs and Spring Boot Fat JARs. ```java try (ClasspathSkillRepository repository = new ClasspathSkillRepository("skills")) { AgentSkill skill = repository.getSkill("data-analysis"); List allSkills = repository.getAllSkills(); } catch //... ``` Resource structure: Place multiple skill subdirectories under `src/main/resources/skills/`, each containing a `SKILL.md`. > Note: `JarSkillRepositoryAdapter` is deprecated. Use `ClasspathSkillRepository` instead. ### Performance Optimization Recommendations 1. **Control SKILL.md Size**: Keep under 5k tokens, recommended 1.5-2k tokens 2. **Organize Resources Properly**: Place detailed documentation in references/ rather than SKILL.md 3. **Regularly Clean Versions**: Use `clearSkillOldVersions()` to clean up old versions no longer needed 4. **Avoid Duplicate Registration**: Leverage duplicate registration protection mechanism; same Skill object with multiple Tools won't create duplicate versions ## Related Documentation - [Claude Agent Skills Official Documentation](https://platform.claude.com/docs/zh-CN/agents-and-tools/agent-skills/overview) - Complete concept and architecture introduction - [Tool Usage Guide](./tool.md) - Tool system usage methods - [Agent Configuration](./agent.md) - Agent configuration and usage # RAG (Retrieval-Augmented Generation) AgentScope provides built-in RAG support, enabling Agents to access external knowledge bases. ## Overview ### Core Components The RAG module in AgentScope consists of two core components: - **Reader**: Responsible for reading and chunking input documents, converting them into processable units - **Knowledge**: Responsible for storing documents, generating embeddings, and retrieving relevant information ### Scope of Support AgentScope supports multiple types of knowledge base implementations: | Type | Implementation | Supported Features | Document Management | Use Cases | |------|----------------|-------------------|---------------------|-----------| | **Local Knowledge Base** | `SimpleKnowledge` | Full document management and retrieval | Via code (using Reader) | Development, testing, full data control | | **Cloud-hosted Knowledge Base** | `BailianKnowledge` | Retrieval only | [Bailian Console](https://bailian.console.aliyun.com/) | Enterprise, multi-turn conversations, query rewriting | | **Dify Knowledge Base** | `DifyKnowledge` | Retrieval only | Dify Console | Multiple retrieval modes, reranking | | **RAGFlow Knowledge Base** | `RAGFlowKnowledge` | Retrieval only | RAGFlow Console | Powerful OCR, knowledge graph, multi-dataset | ### Integration Modes AgentScope supports two RAG integration modes: | Mode | Description | Pros | Cons | |------|-------------|------|------| | **Generic Mode** | Automatically retrieves and injects knowledge before each inference step | Simple, works with any LLM | Retrieves even when not needed | | **Agentic Mode** | Agent uses tools to decide when to retrieve | Flexible, retrieves only when needed | Requires strong reasoning capabilities | #### Generic Mode In Generic mode, knowledge is automatically retrieved and injected into the user's message: ```java ReActAgent agent = ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful assistant with access to a knowledge base.") .model(chatModel) .toolkit(new Toolkit()) // Enable Generic RAG mode .knowledge(knowledge) .ragMode(RAGMode.GENERIC) .retrieveConfig( RetrieveConfig.builder() .limit(3) .scoreThreshold(0.3) .build()) .enableOnlyForUserQueries(true) // Retrieve only for user messages .build(); ``` How it works: 1. User sends a query 2. Knowledge base automatically retrieves relevant documents 3. Retrieved documents are prepended to the user message 4. Agent processes the enhanced message and responds #### Agentic Mode In Agentic mode, the Agent has a `retrieve_knowledge` tool and decides when to use it: ```java ReActAgent agent = ReActAgent.builder() .name("Agent") .sysPrompt("You are a helpful assistant with a knowledge retrieval tool. " + "Use the retrieve_knowledge tool when you need information.") .model(chatModel) .toolkit(new Toolkit()) // Enable Agentic RAG mode .knowledge(knowledge) .ragMode(RAGMode.AGENTIC) .retrieveConfig( RetrieveConfig.builder() .limit(3) .scoreThreshold(0.5) .build()) .build(); ``` **How it works:** 1. User sends a query 2. Agent reasons and decides whether to retrieve knowledge 3. If needed, Agent calls `retrieve_knowledge(query="...")` 4. Retrieved documents are returned as tool results 5. Agent reasons again using the retrieved information ## Local Knowledge Base (SimpleKnowledge) ### Quick Start ```java // 1. Create knowledge base EmbeddingModel embeddingModel = DashScopeTextEmbedding.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("text-embedding-v3") .dimensions(1024) .build(); Knowledge knowledge = SimpleKnowledge.builder() .embeddingModel(embeddingModel) .embeddingStore(InMemoryStore.builder().dimensions(1024).build()) .build(); // 2. Add documents TextReader reader = new TextReader(512, SplitStrategy.PARAGRAPH, 50); List docs = reader.read(ReaderInput.fromString("Text content...")).block(); knowledge.addDocuments(docs).block(); // 3. Retrieve List results = knowledge.retrieve("query", RetrieveConfig.builder().limit(3).scoreThreshold(0.5).build()).block(); ``` ### Reader Configuration AgentScope provides multiple built-in Readers for SimpleKnowledge: Split strategies: `CHARACTER`, `PARAGRAPH`, `SENTENCE`, `TOKEN` ```java // Text new TextReader(512, SplitStrategy.PARAGRAPH, 50); // PDF new PDFReader(512, SplitStrategy.PARAGRAPH, 50); // Word (supports images and tables) new WordReader(512, SplitStrategy.PARAGRAPH, 50, true, true, TableFormat.MARKDOWN); // Image (requires multimodal embedding model) new ImageReader(false); ``` ### Vector Store ```java // In-memory store (development/testing) InMemoryStore.builder().dimensions(1024).build(); // Qdrant (production) QdrantStore.builder() .location("localhost:6334") .collectionName("my_collection") .dimensions(1024) .build(); ``` ## Cloud-hosted Knowledge Base (Bailian) Alibaba Cloud Bailian Knowledge Base, supporting reranking, query rewriting, and multi-turn conversations. Manage documents via [Bailian Console](https://bailian.console.aliyun.com/). ### Quick Start ```java // Create knowledge base BailianConfig config = BailianConfig.builder() .accessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")) .accessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")) .workspaceId("llm-xxx") .indexId("mymxbdxxxx") .build(); BailianKnowledge knowledge = BailianKnowledge.builder().config(config).build(); // Retrieve List results = knowledge.retrieve("query", RetrieveConfig.builder().limit(5).scoreThreshold(0.3).build()).block(); ``` ### Advanced Configuration ```java BailianConfig config = BailianConfig.builder() .accessKeyId(accessKeyId).accessKeySecret(accessKeySecret) .workspaceId("llm-xxx").indexId("mymxbdxxxx") .denseSimilarityTopK(20) .enableReranking(true) .rerankConfig(RerankConfig.builder() .modelName("gte-rerank-hybrid").rerankMinScore(0.3f).rerankTopN(5).build()) .enableRewrite(true) .rewriteConfig(RewriteConfig.builder().modelName("conv-rewrite-qwen-1.8b").build()) .build(); ``` ### Multi-turn Conversation Retrieval ```java RetrieveConfig config = RetrieveConfig.builder() .limit(5).scoreThreshold(0.3) .conversationHistory(conversationHistory) // Auto-rewrite query .build(); ``` ### Complete Configuration Example ```java BailianConfig config = BailianConfig.builder() // === Connection Configuration (Required) === .accessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")) .accessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")) .workspaceId("llm-xxx") // Bailian workspace ID .indexId("mymxbdxxxx") // Knowledge base index ID // === Endpoint Configuration (Optional) === .endpoint("bailian.cn-beijing.aliyuncs.com") // Default value // Other available endpoints: // - bailian.cn-shanghai-finance-1.aliyuncs.com (Finance Cloud) // - bailian-vpc.cn-beijing.aliyuncs.com (VPC) // === Retrieval Configuration (Optional) === .denseSimilarityTopK(100) // Vector retrieval Top K, range 0-100, default 100 .sparseSimilarityTopK(100) // Keyword retrieval Top K, range 0-100, default 100 // Note: denseSimilarityTopK + sparseSimilarityTopK <= 200 // === Reranking Configuration (Optional) === .enableReranking(true) // Enable reranking, default true .rerankConfig(RerankConfig.builder() .modelName("gte-rerank-hybrid") // Rerank model .rerankMinScore(0.3f) // Minimum score threshold .rerankTopN(5) // Number of results to return .build()) // === Query Rewriting Configuration (Optional, for multi-turn conversations) === .enableRewrite(true) // Enable query rewriting, default false .rewriteConfig(RewriteConfig.builder() .modelName("conv-rewrite-qwen-1.8b") // Rewrite model .build()) // === Other Configuration (Optional) === .searchFilters(List.of(Map.of("tag", "value"))) // Search filters .saveRetrieverHistory(false) // Save retrieval history, default false .build(); ``` ## Dify Knowledge Base Integration Supports cloud service and self-hosting, providing four retrieval modes: keyword, semantic, hybrid, and fulltext. Manage documents via [Dify Console](https://cloud.dify.ai). ### Quick Start ```java DifyRAGConfig config = DifyRAGConfig.builder() .apiKey(System.getenv("DIFY_RAG_API_KEY")) .datasetId("your-dataset-id") .retrievalMode(RetrievalMode.HYBRID_SEARCH) .topK(10).scoreThreshold(0.5) .build(); DifyKnowledge knowledge = DifyKnowledge.builder().config(config).build(); List results = knowledge.retrieve("query", RetrieveConfig.builder().limit(5).build()).block(); ``` ### Retrieval Modes ```java .retrievalMode(RetrievalMode.KEYWORD) // Keyword search .retrievalMode(RetrievalMode.SEMANTIC_SEARCH) // Semantic search .retrievalMode(RetrievalMode.HYBRID_SEARCH) // Hybrid search (recommended) .retrievalMode(RetrievalMode.FULLTEXT) // Fulltext search ``` ### Advanced Configuration ```java DifyRAGConfig config = DifyRAGConfig.builder() .apiKey(apiKey).datasetId(datasetId) .retrievalMode(RetrievalMode.HYBRID_SEARCH) .weights(0.6) // Hybrid search semantic weight // Reranking .enableRerank(true) .rerankConfig(RerankConfig.builder() .providerName("cohere").modelName("rerank-english-v2.0").build()) // Metadata filtering .metadataFilter(MetadataFilter.builder() .logicalOperator("AND") .addCondition(MetadataFilterCondition.builder() .name("category").comparisonOperator("=").value("AI").build()) .build()) .build(); ``` ### Complete Configuration Example ```java DifyRAGConfig config = DifyRAGConfig.builder() // === Connection Configuration (Required) === .apiKey(System.getenv("DIFY_RAG_API_KEY")) // Dataset API Key .datasetId("your-dataset-uuid") // Dataset ID (UUID format) // === Endpoint Configuration (Optional) === .apiBaseUrl("https://api.dify.ai/v1") // Dify Cloud (default) // .apiBaseUrl("https://your-dify.com/v1") // Self-hosted instance // === Retrieval Configuration (Optional) === .retrievalMode(RetrievalMode.HYBRID_SEARCH) // Retrieval mode, default HYBRID_SEARCH // Available modes: KEYWORD, SEMANTIC_SEARCH, HYBRID_SEARCH, FULLTEXT .topK(10) // Number of results, range 1-100, default 10 .scoreThreshold(0.5) // Similarity threshold, range 0.0-1.0, default 0.0 .weights(0.6) // Hybrid search semantic weight, range 0.0-1.0 // === Reranking Configuration (Optional) === .enableRerank(true) // Enable reranking, default false .rerankConfig(RerankConfig.builder() .providerName("cohere") // Rerank model provider .modelName("rerank-english-v2.0") // Rerank model name .topN(5) // Number of results after reranking .build()) // === Metadata Filtering (Optional) === .metadataFilter(MetadataFilter.builder() .logicalOperator("AND") // Logical operator: AND or OR .addCondition(MetadataFilterCondition.builder() .name("category") // Metadata field name .comparisonOperator("=") // Comparison operator .value("documentation") // Filter value .build()) .build()) // === HTTP Configuration (Optional) === .connectTimeout(Duration.ofSeconds(30)) // Connection timeout, default 30s .readTimeout(Duration.ofSeconds(60)) // Read timeout, default 60s .maxRetries(3) // Max retries, default 3 .addCustomHeader("X-Custom-Header", "value") // Custom headers .build(); ``` ## RAGFlow Knowledge Base Integration Open-source RAG engine, supporting Docker deployment, powerful OCR, knowledge graph, and multi-dataset retrieval. ### Deployment ```bash git clone https://github.com/infiniflow/ragflow.git && cd ragflow docker compose up -d ``` ### Quick Start ```java RAGFlowConfig config = RAGFlowConfig.builder() .apiKey("ragflow-your-api-key") // Required: API Key .baseUrl("http://address") // Required: RAGFlow service address .addDatasetId("dataset-id") // Required: Set at least dataset_ids or document_ids .topK(10).similarityThreshold(0.3) .build(); RAGFlowKnowledge knowledge = RAGFlowKnowledge.builder().config(config).build(); List results = knowledge.retrieve("query", RetrieveConfig.builder().limit(5).build()).block(); ``` ### Multi-dataset and Document Filtering > **Note**: `dataset_ids` and `document_ids` **require at least one to be set**. If only setting `document_ids`, ensure all documents use the same embedding model. ```java // Method 1: Set only datasets (search entire datasets) RAGFlowConfig config1 = RAGFlowConfig.builder() .apiKey("ragflow-your-api-key") .baseUrl("http://localhost:9380") .addDatasetId("dataset-1") .addDatasetId("dataset-2") .build(); // Method 2: Set only documents (search specified documents directly, must use same embedding model) RAGFlowConfig config2 = RAGFlowConfig.builder() .apiKey("ragflow-your-api-key") .baseUrl("http://localhost:9380") .addDocumentId("doc-id-1") .addDocumentId("doc-id-2") .build(); // Method 3: Set both (search specified documents within datasets) RAGFlowConfig config3 = RAGFlowConfig.builder() .apiKey("ragflow-your-api-key") .baseUrl("http://localhost:9380") .addDatasetId("dataset-1") .addDocumentId("doc-id-1") // Limit to specified documents in the dataset .build(); ``` ### Metadata Filtering ```java Map condition = Map.of( "logic", "and", "conditions", List.of( Map.of("name", "author", "comparison_operator", "=", "value", "Toby"), Map.of("name", "date", "comparison_operator", "=", "value", "2024-01-01") ) ); RAGFlowConfig config = RAGFlowConfig.builder() .apiKey("ragflow-your-api-key") .baseUrl("http://localhost:9380") .addDatasetId("dataset-id") .metadataCondition(condition) .build(); ``` ### Complete Configuration Example ```java RAGFlowConfig config = RAGFlowConfig.builder() // === Connection Configuration (Required) === .apiKey("ragflow-your-api-key") // RAGFlow API Key .baseUrl("http://address") // RAGFlow service address (Required) // === Dataset/Document Configuration (At least one required) === .addDatasetId("datasetId1") .addDatasetId("datasetId2") // Supports multiple datasets // Or batch set: .datasetIds(List.of("id1", "id2")) // === Document Filtering (Optional, limits search scope) === .addDocumentId("documentId1") .addDocumentId("documentId2") // Or batch set: .documentIds(List.of("doc1", "doc2")) // Note: If only setting document_ids, ensure all documents use the same embedding model // === Retrieval Configuration (Optional) === .topK(1024) // Number of chunks for vector computation, default 1024 .similarityThreshold(0.2) // Similarity threshold, range 0.0-1.0, default 0.2 .vectorSimilarityWeight(0.3) // Vector similarity weight, range 0.0-1.0, default 0.3 // (1 - weight) is term similarity weight //=== Pagination Parameters === .page(1) // Page number, default 1 .pageSize(30) // Page size, default 30 // === Advanced Retrieval Features (Optional) === .useKg(false) // Knowledge graph multi-hop query, default false .tocEnhance(false) // TOC-enhanced retrieval, default false .rerankId(1) // Rerank model ID .keyword(false) // Keyword matching, default false .highlight(false) // Highlight matched results, default false .addCrossLanguage("en") // Add target language // Or batch set: .crossLanguages(List.of("en", "zh", "ja")) // === Metadata Filtering (Optional) === .metadataCondition(Map.of( "logic", "and", // Logical operator: and or or "conditions", List.of( Map.of( "name", "author", // Metadata field name "comparison_operator", "=", // Comparison operator "value", "Toby" // Filter value ), Map.of( "name", "date", "comparison_operator", ">=", "value", "2024-01-01" ) ) )) // === HTTP Configuration (Optional) === .timeout(Duration.ofSeconds(30)) // HTTP timeout, default 30s .maxRetries(3) // Max retries, default 3 .addCustomHeader("X-Custom-Header", "value") // Custom headers .build(); ``` **Supported Comparison Operators:** - `=` - Equals - `≠` - Not equals - `>`, `<`, `≥`, `≤` - Numeric comparisons - `contains` - Contains - `not contains` - Does not contain - `start with` - Starts with - `empty` - Is empty - `not empty` - Is not empty ## Custom RAG Components AgentScope encourages custom RAG components. You can extend the following base classes: | Base Class | Description | Abstract Methods | |------------|-------------|------------------| | `Reader` | Document reader base class | `read()`, `getSupportedFormats()` | | `VDBStoreBase` | Vector store base class | `add()`, `search()` | | `Knowledge` | Knowledge base implementation base class | `addDocuments()`, `retrieve()` | ### Custom Reader Example ```java public class CustomReader implements Reader { @Override public Mono> read(ReaderInput input) throws ReaderException { return Mono.fromCallable(() -> { // Your custom reading logic String content = processInput(input); List chunks = chunkContent(content); return createDocuments(chunks); }); } @Override public List getSupportedFormats() { return List.of("custom", "fmt"); } private List createDocuments(List chunks) { // Create Document objects with metadata // ... } } ``` ## Best Practices 1. **Chunk Size**: Choose chunk size based on model context window and use case. Typical values: 256-1024 characters. 2. **Overlap**: Use 10-20% overlap to maintain context continuity between chunks. 3. **Score Threshold**: Start with 0.3-0.5, adjust based on retrieval quality. 4. **Top-K**: Initially retrieve 3-5 documents, adjust based on context window limits. 5. **Mode Selection**: - Use **Generic Mode**: Simple Q&A, consistent retrieval patterns, weaker LLMs - Use **Agentic Mode**: Complex tasks, selective retrieval, powerful LLMs 6. **Vector Store Selection**: - Use **InMemoryStore**: Development, testing, small datasets (<10K documents) - Use **QdrantStore**: Production, large datasets, persistence required - Use **ElasticsearchStore**: Production environments, large-scale datasets, and self-hosted (private deployment) services. ## Complete Examples - **Local Knowledge Base Example**: [RAGExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/RAGExample.java) - **Bailian Knowledge Base Example**: [BailianRAGExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/BailianRAGExample.java) - **Dify Knowledge Base Example**: [DifyRAGExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/DifyRAGExample.java) - **RAGFlow Knowledge Base Example**: [RAGFlowRAGExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/RAGFlowRAGExample.java) - **Elasticsearch Knowledge Base Example**: [ElasticsearchRAGExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/ElasticsearchRAGExample.java) - **PgVector Knowledge Base Example**: [PgVectorRAGExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/PgVectorRAGExample.java) - # Hook Hooks provide extension points to monitor and modify agent behavior at specific execution stages. ## Hook Overview AgentScope Java uses a **unified event model** where all hooks implement the `onEvent(HookEvent)` method: - **Event-based**: All agent activities generate events - **Type-safe**: Pattern matching on event types - **Priority-ordered**: Hooks execute by priority (lower value = higher priority) - **Modifiable**: Some events allow modification of execution context ## Supported Events | Event Type | Timing | Modifiable | Description | |-----------------------|---------------------------|------------|------------------------------------------| | PreCallEvent | Before agent call | ❌ | Before agent starts processing (notification-only) | | PostCallEvent | After agent call | ✅ | After agent completes response (can modify final message) | | PreReasoningEvent | Before reasoning | ✅ | Before LLM reasoning (can modify input messages) | | PostReasoningEvent | After reasoning | ✅ | After LLM reasoning (can modify reasoning result) | | ReasoningChunkEvent | During reasoning stream | ❌ | Each chunk of streaming reasoning (notification-only) | | PreActingEvent | Before tool execution | ✅ | Before tool execution (can modify tool parameters) | | PostActingEvent | After tool execution | ✅ | After tool execution (can modify tool result) | | ActingChunkEvent | During tool stream | ❌ | Tool execution progress chunks (notification-only) | | ErrorEvent | On error | ❌ | When errors occur (notification-only) | ## Creating Hooks ### Basic Hook ```java import io.agentscope.core.hook.Hook; import io.agentscope.core.hook.HookEvent; import io.agentscope.core.hook.PreCallEvent; import io.agentscope.core.hook.PostCallEvent; import reactor.core.publisher.Mono; public class LoggingHook implements Hook { @Override public Mono onEvent(T event) { if (event instanceof PreCallEvent) { System.out.println("Agent starting: " + event.getAgent().getName()); return Mono.just(event); } if (event instanceof PostCallEvent) { System.out.println("Agent finished: " + event.getAgent().getName()); return Mono.just(event); } return Mono.just(event); } } ``` ### Hook with Priority ```java public class HighPriorityHook implements Hook { @Override public int priority() { return 10; // Lower number = higher priority (default is 100) } @Override public Mono onEvent(T event) { // This hook executes before hooks with priority > 10 return Mono.just(event); } } ``` ### Modifying Events Some events allow modification: ```java import io.agentscope.core.hook.Hook; import io.agentscope.core.hook.HookEvent; import io.agentscope.core.hook.PreReasoningEvent; import io.agentscope.core.message.Msg; import io.agentscope.core.message.MsgRole; import io.agentscope.core.message.TextBlock; import reactor.core.publisher.Mono; import java.util.ArrayList; import java.util.List; public class PromptEnhancingHook implements Hook { @Override public Mono onEvent(T event) { if (event instanceof PreReasoningEvent e) { List messages = new ArrayList<>(e.getInputMessages()); messages.add(0, Msg.builder() .role(MsgRole.SYSTEM) .content(List.of(TextBlock.builder().text("Think step by step.").build())) .build()); e.setInputMessages(messages); return Mono.just(event); } return Mono.just(event); } } ``` ## Configure Hooks in Agent Register hooks when building an agent: ```java import io.agentscope.core.ReActAgent; import java.util.List; ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .hooks(List.of( new LoggingHook(), new HighPriorityHook(), new PromptEnhancingHook() )) .build(); ``` Hooks are immutable after agent construction. ## Hook Examples ### Monitoring Tool Execution Track tool calls: ```java import io.agentscope.core.hook.Hook; import io.agentscope.core.hook.HookEvent; import io.agentscope.core.hook.PostActingEvent; import io.agentscope.core.hook.PreActingEvent; import io.agentscope.core.message.TextBlock; import reactor.core.publisher.Mono; public class ToolMonitorHook implements Hook { @Override public Mono onEvent(T event) { if (event instanceof PreActingEvent e) { System.out.println("Calling tool: " + e.getToolUse().getName()); System.out.println("Arguments: " + e.getToolUse().getInput()); return Mono.just(event); } if (event instanceof PostActingEvent e) { String resultText = e.getToolResult().getOutput().stream() .filter(block -> block instanceof TextBlock) .map(block -> ((TextBlock) block).getText()) .findFirst() .orElse(""); System.out.println("Tool result: " + resultText); return Mono.just(event); } return Mono.just(event); } } ``` ### Monitoring Errors Monitor and handle errors: ```java import io.agentscope.core.hook.ErrorEvent; import io.agentscope.core.hook.Hook; import io.agentscope.core.hook.HookEvent; import reactor.core.publisher.Mono; public class ErrorHandlingHook implements Hook { @Override public Mono onEvent(T event) { if (event instanceof ErrorEvent e) { System.err.println("Error in agent: " + e.getAgent().getName()); System.err.println("Error message: " + e.getError().getMessage()); return Mono.just(event); } return Mono.just(event); } } ``` ## Complete Example See the complete Hook example: - `agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/HookExample.java` Run the example: ```bash cd agentscope-examples/quickstart mvn exec:java -Dexec.mainClass="io.agentscope.examples.quickstart.HookExample" ``` # Human-in-the-Loop Human-in-the-Loop lets you insert human review checkpoints during agent execution. When the agent is about to call tools, you can pause for user confirmation before proceeding. ## Two Pause Points Agent execution has two phases: "reasoning" and "acting". You can pause at either point: **Pause after reasoning**: After the model decides which tools to call, but before execution. You can see the tool names and parameters, letting users decide whether to allow execution. **Pause after acting**: After tool execution completes, but before the next reasoning iteration. You can see the results, letting users decide whether to continue. ## Example: Confirming Sensitive Operations This example shows how to require user confirmation before executing sensitive operations like deleting files or sending emails: ```java // 1. Create confirmation hook Hook confirmationHook = new Hook() { private static final List SENSITIVE_TOOLS = List.of("delete_file", "send_email"); @Override public Mono onEvent(T event) { if (event instanceof PostReasoningEvent e) { Msg reasoningMsg = e.getReasoningMessage(); List toolCalls = reasoningMsg.getContentBlocks(ToolUseBlock.class); // Pause if sensitive tools are involved boolean hasSensitive = toolCalls.stream() .anyMatch(t -> SENSITIVE_TOOLS.contains(t.getName())); if (hasSensitive) { e.stopAgent(); } } return Mono.just(event); } }; // 2. Create agent ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .hook(confirmationHook) .build(); ``` ## Handling Pause and Resume When the agent pauses, the returned message contains the pending tool information. Display it to the user and decide next steps based on their choice: ```java Msg response = agent.call(userMsg).block(); // Check for pending tool calls while (response.hasContentBlocks(ToolUseBlock.class)) { // Display pending tools List pending = response.getContentBlocks(ToolUseBlock.class); for (ToolUseBlock tool : pending) { System.out.println("Tool: " + tool.getName()); System.out.println("Input: " + tool.getInput()); } if (userConfirms()) { // User confirmed, continue execution response = agent.call().block(); } else { // User declined, return cancellation Msg cancelResult = Msg.builder() .role(MsgRole.TOOL) .content(pending.stream() .map(t -> ToolResultBlock.of(t.getId(), t.getName(), TextBlock.builder().text("Operation cancelled").build())) .toArray(ToolResultBlock[]::new)) .build(); response = agent.call(cancelResult).block(); } } // Final response System.out.println(response.getTextContent()); ``` ## Quick Reference **Pause methods**: - `PostReasoningEvent.stopAgent()` — Pause after reasoning - `PostActingEvent.stopAgent()` — Pause after acting **Resume methods**: - `agent.call()` — Continue executing pending tools - `agent.call(toolResultMsg)` — Provide custom tool result and continue **Check pause reason**: - `response.getGenerateReason()` returns `REASONING_STOP_REQUESTED` or `ACTING_STOP_REQUESTED` # Memory ## Overview Memory manages conversation history and context for agents in AgentScope. AgentScope provides two types of memory: - **Short-term Memory**: Stores conversation history for the current session, requires Session for persistence and recovery - **Long-term Memory**: Stores user preferences and knowledge across sessions, automatically persisted by external memory components (e.g., Mem0, ReMe) ## Memory Architecture In ReActAgent, short-term memory and long-term memory work together: ``` ┌────────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────────┐ ┌────────────┐ │ User Input │──▶│ Short-term │──▶│ LLM │──▶│ Short-term │──▶│ User Reply │ └────────────┘ │ Memory │ │ (Reason) │ │ Memory │ └────────────┘ └──────┬──────┘ └──────────┘ └──────┬──────┘ │ │ │ Recall │ Async Store ▼ ▼ ┌───────────────────────────────────────────────┐ │ Long-term Memory (Independent) │ └───────────────────────────────────────────────┘ ``` **Division of Responsibilities**: - **Short-term Memory**: Stores current session messages, provides context to LLM, supports reasoning loop - **Long-term Memory** (Independent Component): - Internally integrates LLM (memory extraction/summarization) and vector database (storage/retrieval) - **Recall**: At conversation start, recalls relevant memories and injects into short-term memory - **Store**: After user reply, asynchronously stores to long-term memory for extraction and persistence ## Memory Interface All short-term memory implementations extend the `Memory` interface: ```java public interface Memory extends StateModule { void addMessage(Msg message); List getMessages(); void deleteMessage(int index); void clear(); } ``` `Memory` extends `StateModule`, supporting state serialization and deserialization, can be combined with `SessionManager` for persistence. ## Short-term Memory ### InMemoryMemory The default short-term memory implementation, stores messages in memory. **Characteristics**: - Simple in-memory storage - No context management capability, messages grow indefinitely - Suitable for simple short conversations **Usage Example**: ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.memory.InMemoryMemory; ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .memory(new InMemoryMemory()) .build(); // Messages are automatically stored agent.call(msg1).block(); agent.call(msg2).block(); // Access history List history = agent.getMemory().getMessages(); System.out.println("Total messages: " + history.size()); ``` ### AutoContextMemory Intelligent context memory management system that automatically compresses, offloads, and summarizes conversation history. **Characteristics**: - Has context management capability, automatically controls token usage - 6 progressive compression strategies - Supports large message offloading and on-demand reload - Suitable for long conversations, token cost optimization, complex Agent tasks **Core Features**: - Automatic compression: Triggers automatically when message count or token count exceeds thresholds - Intelligent summarization: Uses LLM models for intelligent conversation summarization - Content offloading: Offloads large content to external storage, reloads on-demand via UUID - Dual storage mechanism: Working storage (compressed) and original storage (complete history) **Usage Example**: ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.memory.autocontext.AutoContextConfig; import io.agentscope.core.memory.autocontext.AutoContextMemory; import io.agentscope.core.memory.autocontext.ContextOffloadTool; import io.agentscope.core.tool.Toolkit; // Configuration AutoContextConfig config = AutoContextConfig.builder() .msgThreshold(30) .lastKeep(10) .tokenRatio(0.3) .build(); // Create memory AutoContextMemory memory = new AutoContextMemory(config, model); // Register context reload tool Toolkit toolkit = new Toolkit(); toolkit.registerTool(new ContextOffloadTool(memory)); // Create Agent ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .memory(memory) .toolkit(toolkit) .build(); ``` **Detailed Documentation**: [AutoContextMemory Documentation](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-extensions/agentscope-extensions-autocontext-memory/README.md) ### Short-term Memory Persistence Short-term memory requires `SessionManager` for persistence to support session recovery after restart. ```java import io.agentscope.core.session.JsonSession; import io.agentscope.core.session.SessionManager; // Create Agent and Memory InMemoryMemory memory = new InMemoryMemory(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .memory(memory) .build(); // Create SessionManager, register components to persist SessionManager sessionManager = SessionManager.forSessionId(sessionId) .withSession(new JsonSession(sessionPath)) .addComponent(agent) .addComponent(memory); // Load existing session (if exists) sessionManager.loadIfExists(); // ... conversation interactions ... // Save session sessionManager.saveSession(); ``` **Complete Example**: `agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/SessionExample.java` ## Long-term Memory ### LongTermMemory Interface Long-term memory is used to store and recall user preferences and knowledge across sessions: ```java public interface LongTermMemory { // Record messages to long-term memory (called by framework after Agent reply) Mono record(List msgs); // Retrieve relevant memories based on input message (called by framework before reasoning) Mono retrieve(Msg msg); } ``` **Persistence Note**: Long-term memory relies on external memory components (e.g., Mem0, ReMe services), data is automatically persisted to external storage, no manual management required. **LongTermMemoryMode**: Configure long-term memory working mode in ReActAgent: - `STATIC_CONTROL`: Static control mode, framework automatically recalls memories before reasoning and records after reply - `AGENT_CONTROL`: Agent control mode, lets Agent decide when to record and recall through tools - `BOTH`: Enable both modes simultaneously ### Mem0LongTermMemory Long-term memory implementation based on [Mem0](https://mem0.ai/). #### Background The OpenAPI interfaces provided by self-hosted Mem0 and Platform Mem0 are inconsistent (different endpoint paths and response formats). `Mem0LongTermMemory` internally provides a compatibility adapter mechanism. By specifying the Mem0 deployment type through the `apiType` parameter, it automatically selects the correct API endpoints and response parsing methods. #### Usage Examples **Platform Mem0 (default)**: ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.memory.LongTermMemoryMode; import io.agentscope.core.memory.mem0.Mem0LongTermMemory; // Using Platform Mem0 (default, no need to specify apiType) Mem0LongTermMemory longTermMemory = Mem0LongTermMemory.builder() .agentName("SmartAssistant") .userId("user-001") .apiBaseUrl("https://api.mem0.ai") .apiKey(System.getenv("MEM0_API_KEY")) .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .longTermMemory(longTermMemory) .longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL) .build(); ``` **Self-hosted Mem0**: ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.memory.LongTermMemoryMode; import io.agentscope.core.memory.mem0.Mem0ApiType; import io.agentscope.core.memory.mem0.Mem0LongTermMemory; // Using self-hosted Mem0, need to specify apiType as Mem0ApiType.SELF_HOSTED Mem0LongTermMemory selfHostedMemory = Mem0LongTermMemory.builder() .agentName("SmartAssistant") .userId("user-001") .apiBaseUrl("http://localhost:8000") // Self-hosted Mem0 service address .apiKey(System.getenv("MEM0_API_KEY")) // Optional, depends on self-hosted service config .apiType(Mem0ApiType.SELF_HOSTED) // Specify as self-hosted Mem0 .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .longTermMemory(selfHostedMemory) .longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL) .build(); ``` **Configuration Notes**: - `apiType`: Optional parameter to specify Mem0 deployment type - `Mem0ApiType.PLATFORM` (default): Uses Platform Mem0 API endpoints - `Mem0ApiType.SELF_HOSTED`: Uses self-hosted Mem0 API endpoints - `apiBaseUrl`: Base URL of the Mem0 service - Platform Mem0: Usually `https://api.mem0.ai` - Self-hosted Mem0: Usually `http://localhost:8000` or your server address - `apiKey`: API key (optional) - Platform Mem0: Required - Self-hosted Mem0: Depends on your service configuration, may not be needed **Complete Example**: `agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/Mem0Example.java` **Run Example**: ```bash # Platform Mem0 (default) export MEM0_API_KEY=your_api_key export MEM0_API_BASE_URL=https://api.mem0.ai # Optional, defaults to this value cd agentscope-examples/advanced mvn exec:java -Dexec.mainClass="io.agentscope.examples.advanced.Mem0Example" # Self-hosted Mem0 export MEM0_API_KEY=your_api_key # Optional, depends on service configuration export MEM0_API_BASE_URL=http://localhost:8000 export MEM0_API_TYPE=self-hosted cd agentscope-examples/advanced mvn exec:java -Dexec.mainClass="io.agentscope.examples.advanced.Mem0Example" ``` ### ReMeLongTermMemory Long-term memory implementation based on [ReMe](https://github.com/agentscope-ai/ReMe). **Usage Example**: ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.memory.LongTermMemoryMode; import io.agentscope.core.memory.reme.ReMeLongTermMemory; ReMeLongTermMemory longTermMemory = ReMeLongTermMemory.builder() .userId("example_user") .apiBaseUrl("http://localhost:8002") .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .longTermMemory(longTermMemory) .longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL) .build(); ``` **Complete Example**: `agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/ReMeExample.java` **Run Example**: ```bash # Requires REME_API_BASE_URL environment variable (optional, defaults to http://localhost:8002) cd examples/advanced mvn exec:java -Dexec.mainClass="io.agentscope.examples.advanced.ReMeExample" ``` ## Related Documentation - [AutoContextMemory Documentation](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-extensions/agentscope-extensions-autocontext-memory/README.md) - [Session Management](./session.md) - [ReActAgent Guide](./react-agent.md) # Plan PlanNotebook provides planning capabilities for agents, helping them break down complex tasks into structured subtasks and execute them step by step. ## Enable Planning ### Option 1: Use Default Configuration (Recommended) ```java ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .enablePlan() // Enable planning .build(); ``` ### Option 2: Custom Configuration ```java PlanNotebook planNotebook = PlanNotebook.builder() .maxSubtasks(10) // Limit number of subtasks .build(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .planNotebook(planNotebook) .build(); ``` ## Usage Example ```java // Create an agent with planning support ReActAgent agent = ReActAgent.builder() .name("PlanAgent") .sysPrompt("You are a systematic assistant that breaks down complex tasks into plans.") .model(model) .toolkit(toolkit) .enablePlan() .build(); // Assign a complex task to the agent Msg task = Msg.builder() .role(MsgRole.USER) .content(List.of(TextBlock.builder() .text("Build a simple calculator web app with HTML, CSS and JavaScript.") .build())) .build(); // Agent will automatically create a plan and execute it step by step Msg response = agent.call(task).block(); ``` ## Planning Tools When planning is enabled, the agent automatically gets access to these tools: | Tool Name | Purpose | |-----------|---------| | `create_plan` | Create a new plan | | `revise_current_plan` | Revise the current plan | | `update_subtask_state` | Update subtask state (todo/in_progress/abandoned) | | `finish_subtask` | Mark subtask as done | | `view_subtasks` | View subtask details | | `finish_plan` | Complete or abandon the entire plan | | `view_historical_plans` | View historical plans | | `recover_historical_plan` | Recover a historical plan | The agent will call these tools automatically based on the task - no manual intervention needed. ## Workflow 1. **Create Plan**: Agent analyzes the task and calls `create_plan` to create a plan with multiple subtasks 2. **Execute Subtasks**: Execute each subtask in sequence 3. **Update Status**: Call `finish_subtask` to update status after completing each subtask 4. **Complete Plan**: Call `finish_plan` after all subtasks are done During execution, the system automatically injects plan hints before each reasoning step to guide the agent. ## Complete Example See `agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/PlanNotebookExample.java` ## Configuration Options ### Limit Subtask Count ```java PlanNotebook planNotebook = PlanNotebook.builder() .maxSubtasks(10) // Maximum 10 subtasks .build(); ``` ### Custom Storage ```java PlanNotebook planNotebook = PlanNotebook.builder() .storage(new InMemoryPlanStorage()) // Default in-memory storage .build(); ``` ### Custom Hint Generation ```java PlanNotebook planNotebook = PlanNotebook.builder() .planToHint(new DefaultPlanToHint()) // Custom plan-to-hint strategy .build(); ``` # State State provides serialization and deserialization capabilities for component state, serving as the foundation for Session persistence. --- ## Core Interfaces ### StateModule Interface All state-aware components implement the `StateModule` interface: ```java public interface StateModule { // Save state to Session void saveTo(Session session, SessionKey sessionKey); // Load state from Session void loadFrom(Session session, SessionKey sessionKey); // Load state from Session (if exists), returns whether loading succeeded default boolean loadIfExists(Session session, SessionKey sessionKey) { if (session.exists(sessionKey)) { loadFrom(session, sessionKey); return true; } return false; } } ``` **Built-in support**: `ReActAgent`, `InMemoryMemory`, `PlanNotebook`, etc. all implement this interface. ### State Interface `State` is a marker interface that identifies objects that can be stored by Session: ```java public interface State { // Marker interface, no methods to implement } ``` --- ## Usage ### Recommended: Use Agent's saveTo/loadFrom For most scenarios, call the state management methods directly on Agent: ```java import io.agentscope.core.session.JsonSession; // Create Session Session session = new JsonSession(Path.of("sessions")); // Save agent.saveTo(session, "user123"); // Load (silently skip if doesn't exist) agent.loadIfExists(session, "user123"); // Load (throw exception if doesn't exist) agent.loadFrom(session, "user123"); ``` ### Direct Session API Usage Session provides type-safe state storage API: ```java import io.agentscope.core.session.Session; import io.agentscope.core.state.SessionKey; import io.agentscope.core.state.SimpleSessionKey; import io.agentscope.core.state.State; // Define state class public record UserPreferences(String theme, String language) implements State {} // Save single state session.save(sessionKey, "preferences", new UserPreferences("dark", "en")); // Get single state Optional prefs = session.get(sessionKey, "preferences", UserPreferences.class); // Save state list session.save(sessionKey, "history", List.of(msg1State, msg2State)); // Get state list List history = session.getList(sessionKey, "history", MsgState.class); ``` --- ## Custom Components Implement `StateModule` to enable persistence for custom components: ```java import io.agentscope.core.session.Session; import io.agentscope.core.state.SessionKey; import io.agentscope.core.state.State; import io.agentscope.core.state.StateModule; public class MyComponent implements StateModule { private String data; private int counter; // Define component's state class public record MyState(String data, int counter) implements State {} @Override public void saveTo(Session session, SessionKey sessionKey) { session.save(sessionKey, "myComponent", new MyState(data, counter)); } @Override public void loadFrom(Session session, SessionKey sessionKey) { session.get(sessionKey, "myComponent", MyState.class) .ifPresent(state -> { this.data = state.data(); this.counter = state.counter(); }); } } ``` --- ## SessionKey `SessionKey` identifies a session. `SimpleSessionKey` is the common implementation: ```java import io.agentscope.core.state.SimpleSessionKey; import io.agentscope.core.state.SessionKey; // Create SessionKey SessionKey key = SimpleSessionKey.of("user123"); // Get session ID String sessionId = ((SimpleSessionKey) key).sessionId(); ``` --- ## Related Documentation - [Session](./session.md) - Session management API - [Memory](./memory.md) - Memory management # Session Session enables persistent storage and recovery of Agent state, allowing conversations to maintain continuity across application runs. --- ## Core Features - **Persistent Storage**: Save Agent, Memory, and other component states - **Simple API**: Call `saveTo()` / `loadFrom()` directly on Agent - **Multiple Backends**: Supports JSON files, in-memory, and custom storage - **Flexible Identification**: Use simple string session IDs or custom `SessionKey` --- ## Quick Start ```java import io.agentscope.core.session.JsonSession; import io.agentscope.core.session.Session; import java.nio.file.Path; // 1. Create components InMemoryMemory memory = new InMemoryMemory(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .memory(memory) .build(); // 2. Create Session and load existing session Path sessionPath = Path.of(System.getProperty("user.home"), ".agentscope", "sessions"); Session session = new JsonSession(sessionPath); agent.loadIfExists(session, "userId"); // 3. Use Agent Msg response = agent.call(userMsg).block(); // 4. Save session agent.saveTo(session, "userId"); ``` --- ## Session Implementations AgentScope provides two Session implementations: | Implementation | Persistence | Use Case | |---------------|-------------|----------| | `JsonSession` | File system | Production, cross-restart persistence | | `InMemorySession` | Memory | Testing, single-process temporary storage | ### JsonSession (Recommended) Stores state as JSON files on the filesystem. ```java import io.agentscope.core.session.JsonSession; // Create JsonSession Path sessionPath = Path.of("/path/to/sessions"); Session session = new JsonSession(sessionPath); // Save session agent.saveTo(session, "user123"); // Load session (silently skip if doesn't exist) agent.loadIfExists(session, "user123"); // Load session (throw exception if doesn't exist) agent.loadFrom(session, "user123"); ``` **Features**: - Directory format: One directory per session `{sessionId}/` - Single states: `{key}.json` files - List states: `{key}.jsonl` files (JSONL format, incremental append) - UTF-8 encoding, automatic directory creation > **⚠️ Security Note**: `JsonSession` uses the `sessionId` directly as the session directory name. If `sessionId` comes from untrusted sources (e.g., HTTP cookies or query parameters), an attacker could inject path traversal characters like `..` or path separators to read/write files outside the intended session directory. **Always validate and sanitize `sessionId` before use** - only allow safe characters (alphanumeric, underscore, hyphen) and reject values containing path separators or `..` sequences. ### InMemorySession Stores state in memory, suitable for testing and single-process temporary scenarios. ```java import io.agentscope.core.session.InMemorySession; // Create in-memory session (typically used as singleton) InMemorySession session = new InMemorySession(); // Save agent.saveTo(session, "user123"); // Load agent.loadIfExists(session, "user123"); // Management features session.listSessionKeys(); // Get all session keys ``` **Notes**: - State is lost when application restarts - Not suitable for distributed environments - Memory usage grows with session count --- ## Agent State Management API ### Save Operations ```java // Save session state agent.saveTo(session, "sessionId"); ``` ### Load Operations ```java // Load session (silently skip if doesn't exist, returns false) boolean loaded = agent.loadIfExists(session, "sessionId"); // Load session (throw exception if doesn't exist) agent.loadFrom(session, "sessionId"); ``` ### Session Management Operations ```java import io.agentscope.core.state.SimpleSessionKey; // Check if session exists boolean exists = session.exists(SimpleSessionKey.of("sessionId")); // Delete session session.delete(SimpleSessionKey.of("sessionId")); // List all sessions Set keys = session.listSessionKeys(); ``` --- ## Complete Example ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.memory.InMemoryMemory; import io.agentscope.core.message.Msg; import io.agentscope.core.message.MsgRole; import io.agentscope.core.message.TextBlock; import io.agentscope.core.session.JsonSession; import io.agentscope.core.session.Session; import java.nio.file.Path; public class SessionExample { public static void main(String[] args) { // Create components InMemoryMemory memory = new InMemoryMemory(); ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .memory(memory) .build(); // Set up session String sessionId = "user_session_001"; Path sessionPath = Path.of(System.getProperty("user.home"), ".agentscope", "sessions"); Session session = new JsonSession(sessionPath); // Load existing session (if exists) if (agent.loadIfExists(session, sessionId)) { System.out.println("Loaded session: " + sessionId); } else { System.out.println("New session: " + sessionId); } // Use Agent Msg userMsg = Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder().text("Hello!").build()) .build(); Msg response = agent.call(userMsg).block(); // Save session agent.saveTo(session, sessionId); System.out.println("Session saved"); } } ``` --- ## Custom Session Implement the `Session` interface to create custom storage backends: ```java import io.agentscope.core.session.Session; import io.agentscope.core.state.SessionKey; import io.agentscope.core.state.State; import java.util.List; import java.util.Optional; import java.util.Set; public class DatabaseSession implements Session { @Override public void save(SessionKey sessionKey, String key, State value) { // Save single state to database } @Override public void save(SessionKey sessionKey, String key, List values) { // Save state list to database } @Override public Optional get(SessionKey sessionKey, String key, Class type) { // Get single state from database return Optional.empty(); } @Override public List getList(SessionKey sessionKey, String key, Class itemType) { // Get state list from database return List.of(); } @Override public boolean exists(SessionKey sessionKey) { // Check if session exists return false; } @Override public void delete(SessionKey sessionKey) { // Delete session } @Override public Set listSessionKeys() { // List all sessions return Set.of(); } @Override public void close() { // Close connection } } // Usage Session session = new DatabaseSession(dbConnection); agent.saveTo(session, "user123"); ``` --- ## Data Format and Migration ### Old vs New Data Format Comparison The new Session API uses a completely different storage format that is incompatible with the old `SessionManager` approach: | Feature | Old Format (SessionManager) | New Format (saveTo/loadFrom) | |---------|----------------------------|------------------------------| | Storage Structure | Single JSON file | Directory + multiple files | | File Path | `sessions/{sessionId}.json` | `sessions/{sessionId}/` directory | | State Storage | Nested Map structure | Independent typed files | | List Data | JSON array | JSONL format (incremental append) | #### Old Format Example ``` sessions/ └── user123.json # Single JSON file containing all state ``` File content: ```json { "agent": { "name": "Assistant", "iteration": 5 }, "memory": { "messages": [ {"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi!"} ] } } ``` #### New Format Example ``` sessions/ └── user123/ # One directory per session ├── memory_messages.jsonl # Message list (JSONL format) └── plan_notebook.json # Plan state (JSON format) ``` `memory_messages.jsonl` content (one message per line): ```json {"role":"USER","name":"user","content":[{"type":"text","text":"Hello"}]} {"role":"ASSISTANT","name":"Assistant","content":[{"type":"text","text":"Hi!"}]} ``` ### Data Migration Due to format incompatibility, **old data cannot be directly read by the new API**. If migration is needed, follow these steps: #### Option 1: Start Fresh (Recommended) If old data is not important, simply delete old session files and use the new API to create new sessions: ```bash # Delete old session files rm -rf ~/.agentscope/sessions/*.json ``` #### Option 2: Manual Migration If you need to preserve historical conversation data, write a migration script: ```java import com.fasterxml.jackson.databind.ObjectMapper; import io.agentscope.core.memory.InMemoryMemory; import io.agentscope.core.message.Msg; import io.agentscope.core.session.JsonSession; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; public class SessionMigration { public static void migrate(Path oldSessionFile, Path newSessionDir) throws Exception { ObjectMapper mapper = new ObjectMapper(); // 1. Read old format data String json = Files.readString(oldSessionFile); Map oldData = mapper.readValue(json, Map.class); // 2. Extract message history Map memoryData = (Map) oldData.get("memory"); List> messages = (List>) memoryData.get("messages"); // 3. Create new Memory and populate data InMemoryMemory memory = new InMemoryMemory(); for (Map msgData : messages) { // Build Msg object based on old data structure Msg msg = convertToMsg(msgData); memory.addMessage(msg); } // 4. Save using new API JsonSession session = new JsonSession(newSessionDir); String sessionId = oldSessionFile.getFileName().toString().replace(".json", ""); memory.saveTo(session, sessionId); System.out.println("Migration complete: " + sessionId); } private static Msg convertToMsg(Map msgData) { // Implement conversion logic based on actual old data structure // ... } } ``` #### Option 3: Dual Version Parallel During transition, keep old code for reading historical data while using new API for new sessions: ```java // Check if old format data exists Path oldFile = sessionPath.resolve(sessionId + ".json"); Path newDir = sessionPath.resolve(sessionId); if (Files.exists(newDir)) { // Load using new API agent.loadIfExists(session, sessionId); } else if (Files.exists(oldFile)) { // Old format exists, perform migration migrateOldSession(oldFile, sessionPath); agent.loadIfExists(session, sessionId); } ``` ### Database Backend Migration #### MySQL Table Structure Change The new API uses a different table structure. If you previously used `MysqlSession`, you need to migrate the table structure: **Old Table Structure**: ```sql CREATE TABLE agentscope_sessions ( session_id VARCHAR(255) PRIMARY KEY, state_data JSON NOT NULL, created_at TIMESTAMP, updated_at TIMESTAMP ); ``` **New Table Structure**: ```sql CREATE TABLE agentscope_sessions ( session_id VARCHAR(255) NOT NULL, state_key VARCHAR(255) NOT NULL, item_index INT NOT NULL DEFAULT 0, state_data LONGTEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (session_id, state_key, item_index) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` The `item_index` column enables true incremental list storage: - Single states: stored with `item_index = 0` - List states: each item stored in a separate row with `item_index = 0, 1, 2, ...` **Migration Steps**: 1. **Backup old data**: ```sql CREATE TABLE agentscope_sessions_backup AS SELECT * FROM agentscope_sessions; ``` 2. **Drop old table**: ```sql DROP TABLE agentscope_sessions; ``` 3. **Recreate** (use `createIfNotExist=true` for automatic creation): ```java MysqlSession session = new MysqlSession(dataSource, true); ``` Or manually execute the new table structure SQL above. #### Redis Storage Structure Change The new API uses a different Redis key structure: **Old Structure**: ``` agentscope:session:{sessionId} -> JSON string (all states) ``` **New Structure**: ``` agentscope:session:{sessionId}:{stateKey} -> JSON string (single state) agentscope:session:{sessionId}:{stateKey}:list -> Redis List (list state) agentscope:session:{sessionId}:_keys -> Redis Set (state index) ``` **Migration Steps**: 1. **Clear old data** (if not needed): ```bash redis-cli KEYS "agentscope:session:*" | xargs redis-cli DEL ``` 2. New data will automatically use the new structure. --- ## More Resources - **Complete Example**: [SessionExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/SessionExample.java) - **State Documentation**: [state.md](./state.md) - **Agent Configuration**: [agent-config.md](./agent-config.md) # Multimodal Multimodal capabilities enable Agents to understand and generate images, audio, video, and other media content. --- ## Core Features - **Architecture Unified**: ContentBlock system handles text, images, audio, and video uniformly - **Flexible Sources**: Support both Base64 encoding and URL media loading methods - **Mixed Messages**: Single message can contain multiple media types and text - **Model Adaptation**: Automatic conversion to different model API format requirements --- ## Core Concepts ### ContentBlock Architecture AgentScope uses a unified ContentBlock system to handle all types of content: ``` ContentBlock (Base Class) ├── TextBlock - Text content ├── ImageBlock - Image content ├── AudioBlock - Audio content ├── VideoBlock - Video content ├── ThinkingBlock - Reasoning process ├── ToolUseBlock - Tool invocation └── ToolResultBlock - Tool result ``` ### Media Sources Two media source methods are supported: - **Base64 Encoding**: Encode media files as strings (recommended, best compatibility) - **URL Reference**: Reference via HTTP/HTTPS URL or local file path --- ## Quick Start ### Step 1: Create Media Content Blocks ```java import io.agentscope.core.message.*; import java.util.Base64; import java.nio.file.Files; import java.nio.file.Paths; // Image: Base64 method (recommended) String base64Image = Base64.getEncoder().encodeToString( Files.readAllBytes(Paths.get("image.png")) ); ImageBlock imageBlock = ImageBlock.builder() .source(Base64Source.builder() .data(base64Image) .mediaType("image/png") .build()) .build(); // Image: URL method ImageBlock urlImage = ImageBlock.builder() .source(URLSource.builder() .url("https://example.com/image.jpg") .build()) .build(); // Audio AudioBlock audioBlock = AudioBlock.builder() .source(Base64Source.builder() .data(base64AudioData) .mediaType("audio/mp3") .build()) .build(); // Video VideoBlock videoBlock = VideoBlock.builder() .source(URLSource.builder() .url("https://example.com/video.mp4") .build()) .build(); ``` **Supported MIME Types**: - Images: `image/png`, `image/jpeg`, `image/gif`, `image/webp` - Audio: `audio/mp3`, `audio/wav`, `audio/mpeg` - Video: `video/mp4`, `video/mpeg` ### Step 2: Build Multimodal Messages ```java // Single image message Msg singleImageMsg = Msg.builder() .role(MsgRole.USER) .content(List.of( TextBlock.builder().text("What color is this image?").build(), imageBlock )) .build(); // Multiple images message Msg multiImageMsg = Msg.builder() .role(MsgRole.USER) .content(List.of( TextBlock.builder().text("Compare these two images").build(), ImageBlock.builder().source(base64Source1).build(), ImageBlock.builder().source(base64Source2).build() )) .build(); ``` ### Step 3: Configure Vision Agent ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter; import io.agentscope.core.model.DashScopeChatModel; ReActAgent agent = ReActAgent.builder() .name("VisionAssistant") .sysPrompt("You are an AI assistant with vision capabilities.") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen-vl-max") .stream(true) .formatter(new DashScopeChatFormatter()) // Required .build()) .build(); // Send request Msg response = agent.call(singleImageMsg).block(); System.out.println(response.getTextContent()); ``` **Key Configuration**: - DashScope vision models **require** `DashScopeChatFormatter` - Base64-encoded images are recommended for best compatibility --- ## Complete Example ```java package io.agentscope.examples; import io.agentscope.core.ReActAgent; import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter; import io.agentscope.core.memory.InMemoryMemory; import io.agentscope.core.message.*; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.tool.Toolkit; import java.util.List; public class VisionExample { public static void main(String[] args) throws Exception { String apiKey = System.getenv("DASHSCOPE_API_KEY"); // 1. Create Vision Agent ReActAgent agent = ReActAgent.builder() .name("VisionAssistant") .sysPrompt("You are an AI assistant with vision capabilities.") .model(DashScopeChatModel.builder() .apiKey(apiKey) .modelName("qwen-vl-max") .stream(true) .formatter(new DashScopeChatFormatter()) .build()) .memory(new InMemoryMemory()) .toolkit(new Toolkit()) .build(); // 2. Create multimodal message String base64Image = "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64pa..."; Msg userMsg = Msg.builder() .role(MsgRole.USER) .content(List.of( TextBlock.builder() .text("What color is this image?") .build(), ImageBlock.builder() .source(Base64Source.builder() .data(base64Image) .mediaType("image/png") .build()) .build() )) .build(); // 3. Send request and get response Msg response = agent.call(userMsg).block(); System.out.println(response.getTextContent()); } } ``` --- ## Supported Models ### DashScope (Alibaba Cloud) ```java DashScopeChatModel.builder() .modelName("qwen-vl-max") .modelName("qwen-vl-plus") .modelName("qwen-audio-turbo") .formatter(new DashScopeChatFormatter()) .build(); ``` ### OpenAI ```java .modelName("gpt-4o") .modelName("gpt-4-vision-preview") ``` ### Anthropic ```java .modelName("claude-3-opus") .modelName("claude-3-sonnet") ``` --- ## More Resources - **Complete Example Code**: [VisionExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/VisionExample.java) - **Message Mechanism**: [message.md](../quickstart/message.md) - Learn about message structure - **Model Configuration**: [model.md](./model.md) - Learn about model configuration options # Structured Output Structured output enables Agents to generate typed data conforming to predefined schemas, achieving reliable conversion from natural language to structured data. --- ## Quick Start ### 1. Define Schema ```java public class ProductInfo { public String name; public Double price; public List features; public ProductInfo() {} // Must have no-arg constructor } ``` ### 2. Request Structured Output ```java // Send query with output type Msg response = agent.call(userMsg, ProductInfo.class).block(); // Extract typed data ProductInfo data = response.getStructuredData(ProductInfo.class); System.out.println("Product: " + data.name); System.out.println("Price: $" + data.price); ``` --- ## Two Modes | Mode | Features | Use Case | |------|----------|----------| | `TOOL_CHOICE` (default) | Forces tool call, single API call | Models supporting tool_choice (qwen3-max, gpt-4) | | `PROMPT` | Prompt-guided, may require multiple calls | Compatible with older models | ```java ReActAgent agent = ReActAgent.builder() .name("Agent") .model(model) .structuredOutputReminder(StructuredOutputReminder.TOOL_CHOICE) // or PROMPT .build(); ``` --- ## Schema Definition ### Supported Types ```java public class Schema { // Basic types public String name; public Integer count; public Double score; public Boolean active; // Collection types public List tags; public Map metadata; // Nested objects public Address address; } ``` ### Nested Structures ```java public class Person { public String name; public Address address; public List hobbies; } public class Address { public String city; public String street; } ``` ### Jackson Annotations ```java public class CustomSchema { @JsonProperty("product_name") // Custom field name public String productName; @JsonIgnore // Ignore field public String internal; } ``` --- ## Error Handling ```java try { Msg response = agent.call(userMsg, ProductInfo.class).block(); ProductInfo data = response.getStructuredData(ProductInfo.class); // Business validation if (data.price < 0) { throw new IllegalArgumentException("Invalid price"); } } catch (Exception e) { System.err.println("Processing failed: " + e.getMessage()); } ``` --- ## More Resources - **Complete Example**: [StructuredOutputExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/quickstart/src/main/java/io/agentscope/examples/quickstart/StructuredOutputExample.java) - **Agent Configuration**: [agent-config.md](./agent-config.md) # Studio (Visual Debugging) Studio provides a Web interface for real-time visualization of Agent execution processes, supporting interactive debugging and message tracing. --- ## Core Features - **Real-time Visualization**: Web interface displays Agent reasoning and execution processes - **Interactive Input**: Interact with Agent through Web UI - **Message Tracing**: View complete message flow and Trace - **Multi-Run Management**: Support organization and comparison of multiple experimental runs --- ## Quick Start ### 1. Start Studio Server Start from source code ```bash git clone https://github.com/agentscope-ai/agentscope-studio cd agentscope-studio npm install npm run dev ``` Install via npm ```bash npm install -g @agentscope/studio # or npm install @agentscope/studio as_studio ``` Studio will run at http://localhost:5173 ![Studio Server Page](../../imgs/studioServer.png) ### 2. Java Application Integration ```java import io.agentscope.core.studio.StudioManager; import io.agentscope.core.studio.StudioMessageHook; import io.agentscope.core.studio.StudioUserAgent; // Initialize Studio connection StudioManager.init() .studioUrl("http://localhost:3000") .project("MyProject") .runName("demo_" + System.currentTimeMillis()) .initialize() .block(); // Create Agent with Hook ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .hook(new StudioMessageHook(StudioManager.getClient())) .build(); // Agent messages automatically sent to Studio agent.call(msg).block(); // Clean up resources StudioManager.shutdown(); ``` ### 3. View Trace Information in AgentScope Studio ![Trace Information](../../imgs/studioServer-trace.png) ## StudioUserAgent Receive user input through Web UI. ```java import io.agentscope.core.studio.StudioUserAgent; // Create user Agent StudioUserAgent user = StudioUserAgent.builder() .name("User") .studioClient(StudioManager.getClient()) .webSocketClient(StudioManager.getWebSocketClient()) .build(); // Wait for Web UI user input Msg userInput = user.call(null).block(); ``` You can find the Project in Studio's Projects and debug through the WebUI ![Studio Server Web UI Input Interface](../../imgs/studioServer-webUI.png) ### Conversation Loop ```java Msg msg = null; while (true) { // Get user input from Web UI msg = user.call(msg).block(); if (msg == null || "exit".equalsIgnoreCase(msg.getTextContent())) { break; } // Agent processes msg = agent.call(msg).block(); } ``` --- ## Complete Example ```java package io.agentscope.examples; import io.agentscope.core.ReActAgent; import io.agentscope.core.message.Msg; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.studio.StudioManager; import io.agentscope.core.studio.StudioMessageHook; import io.agentscope.core.studio.StudioUserAgent; public class StudioExample { public static void main(String[] args) throws Exception { String apiKey = System.getenv("DASHSCOPE_API_KEY"); System.out.println("Connecting to Studio at http://localhost:3000..."); // Initialize Studio StudioManager.init() .studioUrl("http://localhost:3000") .project("JavaExamples") .runName("studio_demo_" + System.currentTimeMillis()) .initialize() .block(); System.out.println("Connected to Studio\n"); try { // Create Agent (with Studio Hook) ReActAgent agent = ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful AI assistant.") .model(DashScopeChatModel.builder() .apiKey(apiKey) .modelName("qwen3-max") .build()) .hook(new StudioMessageHook(StudioManager.getClient())) .build(); // Create user Agent StudioUserAgent user = StudioUserAgent.builder() .name("User") .studioClient(StudioManager.getClient()) .webSocketClient(StudioManager.getWebSocketClient()) .build(); // Conversation loop System.out.println("Starting conversation (type 'exit' to quit)"); System.out.println("Open http://localhost:3000 to interact\n"); Msg msg = null; int turn = 1; while (true) { System.out.println("[Turn " + turn + "] Waiting for user input..."); msg = user.call(msg).block(); if (msg == null || "exit".equalsIgnoreCase(msg.getTextContent())) { System.out.println("\nConversation ended"); break; } System.out.println("[Turn " + turn + "] User: " + msg.getTextContent()); msg = agent.call(msg).block(); if (msg != null) { System.out.println("[Turn " + turn + "] Agent: " + msg.getTextContent() + "\n"); } turn++; } } finally { System.out.println("\nShutting down..."); StudioManager.shutdown(); System.out.println("Done\n"); } } } ``` --- ## Advanced Usage ### Manual Message Pushing ```java StudioClient client = StudioManager.getClient(); Msg customMsg = Msg.builder() .role(MsgRole.ASSISTANT) .content(TextBlock.builder().text("Custom message").build()) .build(); client.pushMessage(customMsg).block(); ``` ### Multi-Agent Visualization ```java // Add Hook to each Agent ReActAgent agent1 = ReActAgent.builder() .name("Agent1") .hook(new StudioMessageHook(client)) .build(); ReActAgent agent2 = ReActAgent.builder() .name("Agent2") .hook(new StudioMessageHook(client)) .build(); // Studio will display messages from both Agents separately ``` --- ## More Resources - **Complete Example**: [StudioExample.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/StudioExample.java) - **Studio Repository**: https://github.com/agentscope-ai/agentscope-studio - **Hook Documentation**: [hook.md](./hook.md) # AG-UI Protocol Integration AG-UI is a frontend-backend communication protocol for exposing agents to web frontends. With AG-UI, you can quickly integrate AgentScope agents with compatible frontend frameworks. --- ## Quick Start ### 1. Add Dependencies ```xml io.agentscope agentscope-agui-spring-boot-starter org.springframework.boot spring-boot-starter-webflux ``` ### 2. Register Agent #### 2.1. Use customizer to register agent ```java @Configuration public class AgentConfiguration { @Bean public AguiAgentRegistryCustomizer aguiAgentRegistryCustomizer() { return registry -> registry.registerFactory("default", this::createAgent); } private Agent createAgent() { return ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful assistant.") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .stream(true) .build()) .memory(new InMemoryMemory()) .build(); } } ``` #### 2.2. Annotation-process to register agent ```java @Configuration public class AgentConfiguration { @Bean @AguiAgentId("default") public Agent agent() { return ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful assistant.") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .stream(true) .build()) .memory(new InMemoryMemory()) .build(); } } ``` ### 3. Configure ```yaml # application.yml agentscope: agui: path-prefix: /agui cors-enabled: true server-side-memory: true # Enable server-side session persistence ``` ### 4. Start and Test ```bash # Test after starting the application curl -N -X POST http://localhost:8080/agui/run \ -H "Content-Type: application/json" \ -d '{"threadId":"t1","runId":"r1","messages":[{"id":"m1","role":"user","content":"Hello"}]}' ``` --- ## Frontend Integration ### Using @ag-ui/client (Recommended) The official AG-UI client library for vanilla JavaScript/TypeScript projects: ```html ``` ### Using CopilotKit For React applications, you can use [CopilotKit](https://copilotkit.ai): ```typescript import { CopilotKit } from "@copilotkit/react-core"; function App() { return ( ); } ``` --- ## Example Project See complete example at [agentscope-examples/agui](https://github.com/agentscope-ai/agentscope-java/tree/main/agentscope-examples/agui): ```bash export DASHSCOPE_API_KEY=your-key cd agentscope-examples/agui mvn spring-boot:run ``` Visit http://localhost:8080 to see the demo. --- ## More Resources - [@ag-ui/client Documentation](https://www.npmjs.com/package/@ag-ui/client) - [AG-UI Protocol Documentation](https://docs.ag-ui.com) - [CopilotKit](https://copilotkit.ai) # A2A (Agent2Agent) A2A is AgentScope's support for the [A2A protocol](https://a2a-protocol.org/latest/specification/), including client (calling remote Agents) and server (exposing local Agents) components. --- ## Client: A2aAgent Use remote A2A services as local Agents. ### Quick Start ```java import io.agentscope.core.a2a.agent.A2aAgent; import io.agentscope.core.a2a.agent.card.WellKnownAgentCardResolver; // Create A2A Agent A2aAgent agent = A2aAgent.builder() .name("remote-agent") .agentCardResolver(new WellKnownAgentCardResolver( "http://127.0.0.1:8080", "/.well-known/agent-card.json", Map.of())) .build(); // Call remote Agent Msg response = agent.call(userMsg).block(); ``` ### Configuration Options | Parameter | Type | Description | |-----------|------|-------------| | `agentCard` | AgentCard | Provide AgentCard directly | | `agentCardResolver` | AgentCardResolver | Obtain AgentCard through resolver | | `memory` | Memory | Memory component | | `hook` / `hooks` | Hook | Hook functions | ### AgentCard Resolution ```java // Option 1: Provide directly A2aAgent.builder() .agentCard(agentCard) .build(); // Option 2: From well-known path A2aAgent.builder() .agentCardResolver(new WellKnownAgentCardResolver(url, path, headers)) .build(); // Option 3: From Nacos A2aAgent.builder() .agentCardResolver(new NacosAgentCardResolver(nacosClient)) .build(); // Option 4: Custom resolver A2aAgent.builder() .agentCardResolver(agentName -> customGetAgentCard(agentName)) .build(); ``` #### Automatically Discovering A2A Services from Nacos Using Nacos as an A2A registry allows AgentScope to automatically discover A2A services from Nacos for invocation. ```java import io.agentscope.core.a2a.agent.A2aAgent; import io.agentscope.core.nacos.a2a.discovery.NacosAgentCardResolver; import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.ai.AiFactory; import com.alibaba.nacos.api.ai.AiService; // Set Nacos address Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, "localhost:8848"); // Create Nacos Client AiService aiService = AiFactory.createAiService(properties); // Create Nacos AgentCardResolver NacosAgentCardResolver nacosAgentCardResolver = new NacosAgentCardResolver(aiService); // Create A2A Agent A2aAgent agent = A2aAgent.builder() .name("remote-agent") .agentCardResolver(nacosAgentCardResolver) .build(); ``` --- ## Server: A2A Server Expose local Agents as A2A services. ### Spring Boot (Recommended) ```xml io.agentscope agentscope-a2a-spring-boot-starter ${agentscope.version} ``` ```yaml # application.yml agentscope: dashscope: api-key: your-api-key agent: name: my-assistant a2a: server: enabled: true card: name: My Assistant description: An intelligent assistant based on AgentScope ``` ```java @SpringBootApplication public class A2aServerApplication { public static void main(String[] args) { SpringApplication.run(A2aServerApplication.class, args); } } ``` ### Manual Setup ```java import io.agentscope.core.a2a.server.AgentScopeA2aServer; import io.agentscope.core.a2a.server.transport.DeploymentProperties; // Create A2A Server AgentScopeA2aServer server = AgentScopeA2aServer.builder( ReActAgent.builder() .name("my-assistant") .sysPrompt("You are a helpful assistant")) .deploymentProperties(DeploymentProperties.builder() .host("localhost") .port(8080) .build()) .build(); // Get transport handler for web framework JsonRpcTransportWrapper transport = server.getTransportWrapper("JSON-RPC", JsonRpcTransportWrapper.class); // Call when web service is ready server.postEndpointReady(); ``` ### Configure AgentCard ```java import io.agentscope.core.a2a.server.card.ConfigurableAgentCard; ConfigurableAgentCard agentCard = new ConfigurableAgentCard.Builder() .name("My Assistant") .description("Intelligent assistant") .version("1.0.0") .skills(List.of( new AgentSkill("text-generation", "Text Generation"), new AgentSkill("question-answering", "Q&A"))) .build(); AgentScopeA2aServer.builder(agentBuilder) .agentCard(agentCard) .build(); ``` ### Auto Register to Registry Using Nacos as an A2A registry allows AgentScope's A2A services to be automatically registered to Nacos. - For `Spring Boot approach` (Recommended) ```xml io.agentscope agentscope-a2a-spring-boot-starter ${agentscope.version} io.agentscope agentscope-nacos-spring-boot-starter ${agentscope.version} ``` ```yaml # application.yml agentscope: dashscope: api-key: your-api-key agent: name: my-assistant a2a: server: enabled: true card: name: My Assistant description: An intelligent assistant based on AgentScope # Adding Nacos properties under `agentscope.a2a` nacos: server-addr: ${NACOS_SERVER_ADDRESS:127.0.0.1:8848} username: ${NACOS_USERNAME:nacos} password: ${NACOS_PASSWORD:nacos} ``` ```java @SpringBootApplication public class A2aServerApplication { public static void main(String[] args) { SpringApplication.run(A2aServerApplication.class, args); } } ``` - For `manual creation approach` ```xml io.agentscope agentscope-extensions-nacos-a2a ${agentscope.version} ``` ```java import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.ai.AiFactory; import com.alibaba.nacos.api.ai.AiService; import io.agentscope.core.a2a.server.AgentScopeA2aServer; import io.agentscope.core.a2a.server.transport.DeploymentProperties; import io.agentscope.core.nacos.a2a.registry.NacosAgentRegistry; // Set Nacos address Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, "localhost:8848"); // Create Nacos Client AiService aiService = AiFactory.createAiService(properties); // Add Nacos AgentRegistry AgentScopeA2aServer server = AgentScopeA2aServer.builder( ReActAgent.builder() .name("my-assistant") .sysPrompt("You are a helpful assistant")) .deploymentProperties(DeploymentProperties.builder() .host("localhost") .port(8080) .build()) .withAgentRegistry(NacosAgentRegistry.builder(aiService).build()) .build(); ``` #### Configuration Options ```java NacosA2aRegistryProperties registryProperties = NacosA2aRegistryProperties.builder() .setAsLatest(true) .enabledRegisterEndpoint(true) .overwritePreferredTransport("http") .build(); NacosAgentRegistry agentRegistry = NacosAgentRegistry .builder(aiService) .nacosA2aProperties(registryProperties) .build(); ``` | Parameter | Type | Description | |-------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------| | `setAsLatest` | boolean | Always register the A2A service as the latest version, default is `false`. | | `enabledRegisterEndpoint` | boolean | Automatically register all `Transport` as Endpoints for this A2A service, default is `true`. When set to `false`, only Agent Card will be published. | | `overwritePreferredTransport` | String | When registering A2A services, use this `Transport` to override the `preferredTransport` and `url` in the Agent Card, default is `null`. | --- ## Task Interruption ```java // Client interruption agent.interrupt(); // Interrupt with message agent.interrupt(Msg.builder() .textContent("User cancelled the operation") .build()); ``` --- ## More Resources - **A2A Protocol Specification**: https://a2a-protocol.org/latest/specification/ - **Agent Interface**: [Agent.java](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-core/src/main/java/io/agentscope/core/agent/Agent.java) - **Nacos Quick Start**: https://nacos.io/docs/latest/quickstart/quick-start - **Nacos Java SDK**: https://nacos.io/docs/latest/manual/user/java-sdk/usage - **Nacos Java SDK Additional Configuration Parameters**: https://nacos.io/docs/latest/manual/user/java-sdk/properties - **Nacos Community**: https://github.com/alibaba/nacos # Coding with AI AgentScope Java documentation supports the [`llms.txt` standard](https://llmstxt.org/), providing a machine-readable index optimized for Large Language Models. This allows you to use the documentation as context in your AI-powered development environment. ## What is llms.txt? `llms.txt` is a standardized text file that acts as a map for LLMs, listing the most important documentation pages and their descriptions. This helps AI tools understand the structure of the documentation and retrieve relevant information. AgentScope provides the following files: | File | Best For | URL | |------|----------|-----| | `llms.txt` | Tools that can fetch links dynamically | `https://java.agentscope.io/llms.txt` | | `llms-full.txt` | Tools that need a single, static text dump | `https://java.agentscope.io/llms-full.txt` | ## Development Tools ### Claude Code [Claude Code](https://docs.anthropic.com/en/docs/claude-code) can be configured to query the AgentScope documentation by adding an MCP server. **Installation:** ```bash claude mcp add agentscope-docs -- uvx --from mcpdoc mcpdoc --urls AgentScopeJava:https://java.agentscope.io/llms.txt ``` **Usage:** Once installed, you can ask questions about AgentScope directly in Claude Code: > How do I create a tool with AgentScope Java? ### Cursor [Cursor](https://cursor.com/) IDE can be configured to access the AgentScope documentation in two ways. **Method 1: Docs Feature (Recommended)** 1. Open **Cursor Settings** -> **Features** -> **Docs** 2. Click **+ Add new Doc** 3. Add URL: `https://java.agentscope.io/llms-full.txt` **Method 2: MCP Server** 1. Open **Cursor Settings** -> **Tools & MCP** 2. Click **New MCP Server** to edit `mcp.json` 3. Add the following configuration: ```json { "mcpServers": { "agentscope-docs": { "command": "uvx", "args": [ "--from", "mcpdoc", "mcpdoc", "--urls", "AgentScopeJava:https://java.agentscope.io/llms.txt" ] } } } ``` **Usage:** Once configured, you can prompt the coding agent: > Use the AgentScope docs to build a ReActAgent with a weather tool. ### Windsurf [Windsurf](https://codeium.com/windsurf) supports MCP servers for documentation access. **Configuration:** 1. Open Windsurf Settings 2. Navigate to MCP configuration 3. Add the following server: ```json { "mcpServers": { "agentscope-docs": { "command": "uvx", "args": [ "--from", "mcpdoc", "mcpdoc", "--urls", "AgentScopeJava:https://java.agentscope.io/llms.txt" ] } } } ``` ### Other Tools Any tool that supports the `llms.txt` standard or can ingest documentation from a URL can benefit from these files. **For tools with Docs/Knowledge Base feature:** - Add URL: `https://java.agentscope.io/llms-full.txt` **For tools with MCP support:** - Use the MCP configuration template above with `mcpdoc` **Prerequisites:** MCP configurations require [`uv`](https://docs.astral.sh/uv/) to be installed, as they use `uvx` to run the documentation server. # Pipeline Pipelines provide composition patterns for multi-agent workflows in AgentScope. They serve as syntax sugar for chaining agents together, simplifying complex orchestration logic. ## Overview AgentScope provides two main pipeline types: - **SequentialPipeline**: Agents execute in order, each receiving the previous agent's output - **FanoutPipeline**: Multiple agents process the same input (in parallel or sequentially) Additionally, the `Pipelines` utility class provides static factory methods for quick pipeline creation. ## SequentialPipeline SequentialPipeline executes agents one by one, where the output of the previous agent becomes the input of the next agent. ``` Input → Agent1 → Agent2 → Agent3 → Output ``` ### Basic Usage Use the `Pipelines.sequential()` static method for quick execution: ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.message.Msg; import io.agentscope.core.message.MsgRole; import io.agentscope.core.message.TextBlock; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.pipeline.Pipelines; import java.util.List; // Create model DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build(); // Create agents for different stages ReActAgent researcher = ReActAgent.builder() .name("Researcher") .sysPrompt("You are a researcher. Analyze the topic and provide key findings.") .model(model) .build(); ReActAgent writer = ReActAgent.builder() .name("Writer") .sysPrompt("You are a writer. Based on the research findings, write a concise summary.") .model(model) .build(); ReActAgent editor = ReActAgent.builder() .name("Editor") .sysPrompt("You are an editor. Polish and finalize the summary.") .model(model) .build(); // Create input message Msg input = Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder().text("Artificial Intelligence in Healthcare").build()) .build(); // Execute sequential pipeline // Researcher → Writer → Editor Msg result = Pipelines.sequential(List.of(researcher, writer, editor), input).block(); System.out.println("Final result: " + result.getTextContent()); ``` ### Using Builder Pattern For reusable pipelines, use `SequentialPipeline.builder()`: ```java import io.agentscope.core.pipeline.SequentialPipeline; // Create a reusable pipeline SequentialPipeline pipeline = SequentialPipeline.builder() .addAgent(researcher) .addAgent(writer) .addAgent(editor) .build(); // Execute the pipeline Msg result1 = pipeline.execute(input).block(); // Reuse with different input Msg anotherInput = Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder().text("Climate Change Solutions").build()) .build(); Msg result2 = pipeline.execute(anotherInput).block(); ``` ### Structured Output Support The last agent in the pipeline can produce structured output: ```java // Define output structure public class ArticleSummary { public String title; public String summary; public List keyPoints; } // Execute with structured output (only applies to the last agent) Msg result = pipeline.execute(input, ArticleSummary.class).block(); // Extract structured data ArticleSummary article = result.getStructuredData(ArticleSummary.class); System.out.println("Title: " + article.title); System.out.println("Summary: " + article.summary); ``` ## FanoutPipeline FanoutPipeline distributes the same input to multiple agents and collects all their responses. This is useful when you want to gather different perspectives or expertise on the same topic. ``` ┌→ Agent1 → Output1 Input →──┼→ Agent2 → Output2 └→ Agent3 → Output3 ``` ### Basic Usage Use the `Pipelines.fanout()` static method for concurrent execution: ```java import io.agentscope.core.pipeline.Pipelines; // Create agents with different perspectives ReActAgent optimist = ReActAgent.builder() .name("Optimist") .sysPrompt("You are an optimist. Analyze the positive aspects of the topic.") .model(model) .build(); ReActAgent pessimist = ReActAgent.builder() .name("Pessimist") .sysPrompt("You are a pessimist. Analyze the potential risks and challenges.") .model(model) .build(); ReActAgent realist = ReActAgent.builder() .name("Realist") .sysPrompt("You are a realist. Provide a balanced analysis.") .model(model) .build(); // Execute fanout pipeline (concurrent by default) List results = Pipelines.fanout( List.of(optimist, pessimist, realist), input ).block(); // Process all results for (Msg result : results) { System.out.println(result.getName() + ": " + result.getTextContent()); } ``` ### Concurrent vs Sequential Execution FanoutPipeline supports two execution modes: | Mode | Method | Behavior | Use Case | |------|--------|----------|----------| | **Concurrent** | `fanout()` | All agents run in parallel using `boundedElastic()` scheduler | Better performance for I/O-bound operations | | **Sequential** | `fanoutSequential()` | Agents run one by one | Predictable ordering, resource control | ```java // Concurrent execution (default) - better for API calls List concurrent = Pipelines.fanout(agents, input).block(); // Sequential execution - predictable order List sequential = Pipelines.fanoutSequential(agents, input).block(); ``` ### Using Builder Pattern ```java import io.agentscope.core.pipeline.FanoutPipeline; // Create concurrent fanout pipeline FanoutPipeline concurrentPipeline = FanoutPipeline.builder() .addAgent(optimist) .addAgent(pessimist) .addAgent(realist) .concurrent() // Default mode .build(); // Create sequential fanout pipeline FanoutPipeline sequentialPipeline = FanoutPipeline.builder() .addAgent(optimist) .addAgent(pessimist) .addAgent(realist) .sequential() .build(); // Execute List results = concurrentPipeline.execute(input).block(); ``` ## Pipelines Utility Class The `Pipelines` class provides static factory methods for quick pipeline operations: ### Method Reference | Method | Return Type | Description | |--------|-------------|-------------| | `sequential(agents, input)` | `Mono` | Execute agents sequentially with input | | `sequential(agents)` | `Mono` | Execute agents sequentially without input | | `sequential(agents, input, outputClass)` | `Mono` | Sequential with structured output | | `fanout(agents, input)` | `Mono>` | Execute agents concurrently | | `fanout(agents)` | `Mono>` | Execute agents concurrently without input | | `fanoutSequential(agents, input)` | `Mono>` | Execute agents sequentially (same input) | | `createSequential(agents)` | `SequentialPipeline` | Create reusable sequential pipeline | | `createFanout(agents)` | `FanoutPipeline` | Create reusable concurrent fanout pipeline | | `createFanoutSequential(agents)` | `FanoutPipeline` | Create reusable sequential fanout pipeline | ### Pipeline Composition You can compose multiple pipelines: ```java // Create two sequential pipelines SequentialPipeline research = Pipelines.createSequential(List.of(researcher, analyst)); SequentialPipeline writing = Pipelines.createSequential(List.of(writer, editor)); // Compose them into a larger pipeline Pipeline combined = Pipelines.compose(research, writing); // Execute the combined pipeline Msg result = combined.execute(input).block(); ``` ## Combining Pipeline with MsgHub For complex workflows, you can combine Pipeline with MsgHub: ```java import io.agentscope.core.pipeline.MsgHub; // Stage 1: Parallel analysis using FanoutPipeline List analyses = Pipelines.fanout(List.of(optimist, pessimist, realist), input).block(); // Stage 2: Group discussion using MsgHub try (MsgHub hub = MsgHub.builder() .participants(optimist, pessimist, realist) .build()) { hub.enter().block(); // Broadcast all analyses hub.broadcast(analyses).block(); // Each agent responds to others' analyses optimist.call().block(); pessimist.call().block(); realist.call().block(); } // Stage 3: Final synthesis using SequentialPipeline ReActAgent synthesizer = ReActAgent.builder() .name("Synthesizer") .sysPrompt("Synthesize all perspectives into a final conclusion.") .model(model) .build(); Msg conclusion = synthesizer.call(input).block(); ``` ## Related Documentation - [MsgHub](./msghub.md) - Message broadcasting for multi-agent conversations - [Multi-Agent Debate](./multiagent-debate.md) - Debate workflow pattern # MsgHub MsgHub is a message broadcasting center for multi-agent conversations in AgentScope. It manages message distribution among a group of agents, eliminating the need for manual message passing code. ## Overview When building multi-agent applications, you often need agents to communicate with each other. Without MsgHub, you would have to manually pass messages between agents: ```java // Without MsgHub (verbose and error-prone) Msg aliceReply = alice.call().block(); bob.observe(aliceReply).block(); charlie.observe(aliceReply).block(); Msg bobReply = bob.call().block(); alice.observe(bobReply).block(); charlie.observe(bobReply).block(); ``` With MsgHub, this becomes much simpler: ```java // With MsgHub (clean and automatic) try (MsgHub hub = MsgHub.builder() .participants(alice, bob, charlie) .build()) { hub.enter().block(); alice.call().block(); // Bob and Charlie automatically receive this bob.call().block(); // Alice and Charlie automatically receive this } ``` ## Core Features - **Automatic Broadcasting**: Messages from any participant are automatically broadcast to all other participants - **Dynamic Participants**: Add or remove agents during conversation - **Announcement Messages**: Send initial messages when entering the hub - **Manual Broadcasting**: Broadcast messages manually when needed - **Lifecycle Management**: Automatic cleanup with try-with-resources ## Basic Usage ### Creating and Using MsgHub ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.formatter.dashscope.DashScopeMultiAgentFormatter; import io.agentscope.core.memory.InMemoryMemory; import io.agentscope.core.message.Msg; import io.agentscope.core.message.MsgRole; import io.agentscope.core.message.TextBlock; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.pipeline.MsgHub; // Create model with MultiAgentFormatter (important!) DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .formatter(new DashScopeMultiAgentFormatter()) .build(); // Create agents ReActAgent alice = ReActAgent.builder() .name("Alice") .sysPrompt("You are Alice, a friendly teacher. Be brief in your responses.") .model(model) .memory(new InMemoryMemory()) .build(); ReActAgent bob = ReActAgent.builder() .name("Bob") .sysPrompt("You are Bob, a curious student. Be brief in your responses.") .model(model) .memory(new InMemoryMemory()) .build(); ReActAgent charlie = ReActAgent.builder() .name("Charlie") .sysPrompt("You are Charlie, a thoughtful observer. Be brief in your responses.") .model(model) .memory(new InMemoryMemory()) .build(); // Create announcement message Msg announcement = Msg.builder() .name("system") .role(MsgRole.SYSTEM) .content(TextBlock.builder() .text("Welcome to the discussion! Please introduce yourself briefly.") .build()) .build(); // Use MsgHub with try-with-resources try (MsgHub hub = MsgHub.builder() .name("Introduction") .participants(alice, bob, charlie) .announcement(announcement) .enableAutoBroadcast(true) // Default is true .build()) { // Enter the hub (broadcasts announcement to all participants) hub.enter().block(); // Each agent introduces themselves // Their responses are automatically broadcast to others Msg aliceReply = alice.call().block(); System.out.println("Alice: " + aliceReply.getTextContent()); Msg bobReply = bob.call().block(); System.out.println("Bob: " + bobReply.getTextContent()); Msg charlieReply = charlie.call().block(); System.out.println("Charlie: " + charlieReply.getTextContent()); } // Hub is automatically closed, subscribers are cleaned up ``` > **Important**: When using MsgHub, use `DashScopeMultiAgentFormatter` (or equivalent for other providers) instead of the standard formatter. This formatter properly handles messages from multiple agents with different names. ### Lifecycle Methods MsgHub follows an enter/exit lifecycle: ```java MsgHub hub = MsgHub.builder() .participants(alice, bob) .build(); // Enter: sets up subscriptions and broadcasts announcements hub.enter().block(); // ... conversation happens ... // Exit: cleans up subscriptions hub.exit().block(); ``` When using try-with-resources, `close()` is called automatically, which internally calls `exit()`. ## Dynamic Participant Management You can add or remove participants during a conversation: ### Adding Participants ```java try (MsgHub hub = MsgHub.builder() .participants(alice, bob) .build()) { hub.enter().block(); // Alice and Bob talk alice.call().block(); bob.call().block(); // Add Charlie mid-conversation hub.add(charlie).block(); // Now Charlie receives messages too alice.call().block(); // Charlie receives this charlie.call().block(); // Alice and Bob receive this } ``` ### Removing Participants ```java try (MsgHub hub = MsgHub.builder() .participants(alice, bob, charlie) .build()) { hub.enter().block(); // All three talk alice.call().block(); bob.call().block(); charlie.call().block(); // Remove Bob from the conversation hub.delete(bob).block(); // Bob won't receive these messages alice.call().block(); charlie.call().block(); } ``` > **Note**: Newly added participants will NOT receive previous messages. They only receive messages from the point they join. ## Manual Broadcasting You can disable automatic broadcasting and manually control message distribution: ### Disabling Auto-Broadcast ```java try (MsgHub hub = MsgHub.builder() .participants(alice, bob, charlie) .enableAutoBroadcast(false) // Disable automatic broadcasting .build()) { hub.enter().block(); // Alice speaks, but others don't automatically receive it Msg aliceReply = alice.call().block(); // Manually broadcast to all participants hub.broadcast(aliceReply).block(); // Now Bob and Charlie have received Alice's message bob.call().block(); } ``` ### Toggling Auto-Broadcast You can toggle auto-broadcast mode during conversation: ```java try (MsgHub hub = MsgHub.builder() .participants(alice, bob) .enableAutoBroadcast(true) .build()) { hub.enter().block(); // Auto-broadcast is on alice.call().block(); // Bob automatically receives // Turn off auto-broadcast hub.setAutoBroadcast(false); Msg bobReply = bob.call().block(); // Alice doesn't automatically receive Bob's reply // Manually broadcast specific messages hub.broadcast(bobReply).block(); // Turn auto-broadcast back on hub.setAutoBroadcast(true); } ``` ### Broadcasting Multiple Messages ```java List messages = List.of(msg1, msg2, msg3); hub.broadcast(messages).block(); ``` ## Reactive Programming Style MsgHub supports fully reactive programming with Project Reactor: ```java MsgHub hub = MsgHub.builder() .participants(alice, bob, charlie) .announcement(announcement) .build(); // Fully reactive chain hub.enter() .then(alice.call()) .doOnSuccess(msg -> System.out.println("Alice: " + msg.getTextContent())) .then(bob.call()) .doOnSuccess(msg -> System.out.println("Bob: " + msg.getTextContent())) .then(charlie.call()) .doOnSuccess(msg -> System.out.println("Charlie: " + msg.getTextContent())) .then(hub.exit()) .block(); // Only block once at the end ``` ## API Reference ### Builder Methods | Method | Description | Default | |--------|-------------|---------| | `name(String)` | Set hub name | UUID | | `participants(AgentBase...)` | Set participants (required) | - | | `participants(List)` | Set participants from list | - | | `announcement(Msg...)` | Set announcement messages | None | | `announcement(List)` | Set announcements from list | None | | `enableAutoBroadcast(boolean)` | Enable/disable auto-broadcast | `true` | ### Instance Methods | Method | Return Type | Description | |--------|-------------|-------------| | `enter()` | `Mono` | Enter hub context, setup subscriptions | | `exit()` | `Mono` | Exit hub context, cleanup subscriptions | | `close()` | `void` | AutoCloseable implementation | | `add(AgentBase...)` | `Mono` | Add new participants | | `add(List)` | `Mono` | Add participants from list | | `delete(AgentBase...)` | `Mono` | Remove participants | | `delete(List)` | `Mono` | Remove participants from list | | `broadcast(Msg)` | `Mono` | Broadcast single message | | `broadcast(List)` | `Mono` | Broadcast multiple messages | | `setAutoBroadcast(boolean)` | `void` | Toggle auto-broadcast | | `getName()` | `String` | Get hub name | | `getParticipants()` | `List` | Get current participants | | `isAutoBroadcastEnabled()` | `boolean` | Check auto-broadcast status | ## Related Documentation - [Pipeline](./pipeline.md) - Sequential and parallel agent execution - [Multi-Agent Debate](./multiagent-debate.md) - Debate workflow pattern # Agent as Tool ```{admonition} Experimental Feature :class: warning This feature is currently experimental and the API may change. If you encounter any issues, please provide feedback via [GitHub Issues](https://github.com/agentscope-ai/agentscope-java/issues). ``` ## Overview Agent as Tool allows registering an agent as a tool that can be called by other agents. This pattern is useful for building hierarchical or collaborative multi-agent systems: - **Expert Specialization**: Main agent calls different expert agents based on task type - **Task Delegation**: Delegate complex subtasks to specialized agents - **Multi-turn Conversation**: Sub-agents can maintain conversation state for continuous interaction ## How It Works When a parent agent calls a sub-agent tool, the system: 1. **Creates Sub-agent Instance**: Creates a new agent instance via the Provider factory 2. **Restores Conversation State**: If `session_id` is provided, restores previous state from Session 3. **Executes Conversation**: Sub-agent processes the message and generates a response 4. **Saves State**: Saves sub-agent state to Session for future calls 5. **Returns Result**: Returns the response and `session_id` to the parent agent ``` Parent Agent ──call──→ SubAgentTool ──create──→ Sub-agent │ │ │←──── return result ───┘ │ Session (state persistence) ``` ## Quick Start ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.tool.Toolkit; import io.agentscope.core.model.DashScopeChatModel; // Create model DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen-plus") .build(); // Create sub-agent Provider (factory) // Note: Must use lambda to ensure new instance is created for each call Toolkit toolkit = new Toolkit(); toolkit.registration() .subAgent(() -> ReActAgent.builder() .name("Expert") .sysPrompt("You are a domain expert responsible for answering professional questions.") .model(model) .build()) .apply(); // Create main agent with toolkit ReActAgent mainAgent = ReActAgent.builder() .name("Coordinator") .sysPrompt("You are a coordinator. When facing professional questions, call the call_expert tool to consult the expert.") .model(model) .toolkit(toolkit) .build(); // Main agent will automatically call expert agent when needed Msg response = mainAgent.call(userMsg).block(); ``` ## Configuration Options Customize sub-agent tool behavior with `SubAgentConfig`: ```java import io.agentscope.core.tool.subagent.SubAgentConfig; import io.agentscope.core.session.JsonSession; import java.nio.file.Path; SubAgentConfig config = SubAgentConfig.builder() .toolName("ask_expert") // Custom tool name .description("Consult the expert") // Custom description .forwardEvents(true) // Forward sub-agent events .session(new JsonSession(Path.of("sessions"))) // Persistent session .build(); toolkit.registration() .subAgent(() -> createExpertAgent(), config) .apply(); ``` | Option | Description | Default | |--------|-------------|---------| | `toolName` | Tool name | Generated from agent name, e.g., `call_expert` | | `description` | Tool description | Uses agent's description | | `forwardEvents` | Whether to forward sub-agent streaming events | `true` | | `session` | Session storage implementation | `InMemorySession` (in-memory) | ## Multi-turn Conversation Sub-agents support multi-turn conversations, maintaining state via the `session_id` parameter: ```java // First call: omit session_id to start a new session // Tool returns: // session_id: abc-123-def // // Expert response content... // Subsequent calls: provide session_id to continue the conversation // Parent agent automatically extracts session_id from previous response ``` The sub-agent tool exposes two parameters: - `message` (required): Message to send to the sub-agent - `session_id` (optional): Session ID. Omit to start new session, provide to continue existing one ## Persistent Sessions By default, `InMemorySession` is used and state is lost on process restart. Use `JsonSession` to persist state to files: ```java import io.agentscope.core.session.JsonSession; import java.nio.file.Path; SubAgentConfig config = SubAgentConfig.builder() .session(new JsonSession(Path.of("./agent-sessions"))) .build(); toolkit.registration() .subAgent(() -> createAgent(), config) .apply(); // State will be saved to ./agent-sessions/{session_id}.json ``` ## Tool Group Support Sub-agent tools can be added to tool groups like regular tools: ```java toolkit.createToolGroup("experts", "Expert Agents", true); toolkit.registration() .subAgent(() -> createLegalExpert()) .group("experts") .apply(); toolkit.registration() .subAgent(() -> createTechExpert()) .group("experts") .apply(); ``` # Multi-Agent Debate Multi-Agent Debate is a workflow pattern that simulates a multi-turn discussion between different agents. This pattern is particularly useful for problem-solving tasks where multiple perspectives can lead to better solutions. ## Overview The debate workflow typically involves: - **Solver Agents (Debaters)**: Generate and exchange their answers, arguing from different perspectives - **Aggregator Agent (Moderator)**: Collects and evaluates the arguments, deciding when a correct answer has been reached This pattern is inspired by research showing that multi-agent debate can improve reasoning accuracy in Large Language Models (reference: "Encouraging Divergent Thinking in Large Language Models through Multi-Agent Debate", EMNLP 2024). ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Debate Loop │ │ │ │ ┌─────────┐ MsgHub ┌─────────┐ │ │ │ Debater │ ◄──────────► │ Debater │ │ │ │ Alice │ broadcast │ Bob │ │ │ └────┬────┘ └────┬────┘ │ │ │ │ │ │ └──────────┬─────────────┘ │ │ ▼ │ │ ┌────────────┐ │ │ │ Moderator │ ── Structured Output ──► finished?│ │ └────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ## Implementation ### Step 1: Create Debater Agents ```java import io.agentscope.core.ReActAgent; import io.agentscope.core.formatter.dashscope.DashScopeMultiAgentFormatter; import io.agentscope.core.memory.InMemoryMemory; import io.agentscope.core.model.DashScopeChatModel; // Define the debate topic String topic = """ The two circles are externally tangent and there is no relative sliding. The radius of circle A is 1/3 the radius of circle B. Circle A rolls around circle B one trip back to its starting point. How many times will circle A revolve in total? """; // Create model with MultiAgentFormatter for multi-agent communication DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .formatter(new DashScopeMultiAgentFormatter()) .build(); // Create debater Alice ReActAgent alice = ReActAgent.builder() .name("Alice") .sysPrompt(String.format(""" You're a debater named Alice. Welcome to the debate competition. It's unnecessary to fully agree with each other's perspectives, as our objective is to find the correct answer. The debate topic is: %s """, topic)) .model(model) .memory(new InMemoryMemory()) .build(); // Create debater Bob ReActAgent bob = ReActAgent.builder() .name("Bob") .sysPrompt(String.format(""" You're a debater named Bob. Welcome to the debate competition. It's unnecessary to fully agree with each other's perspectives, as our objective is to find the correct answer. The debate topic is: %s """, topic)) .model(model) .memory(new InMemoryMemory()) .build(); ``` ### Step 2: Create Moderator Agent The moderator evaluates the debate and decides when a correct answer has been found: ```java // Create moderator agent ReActAgent moderator = ReActAgent.builder() .name("Moderator") .sysPrompt(String.format(""" You're a moderator. There will be two debaters involved in a debate. They will present their answers and discuss their perspectives on the topic: ``` %s ``` At the end of each round, you will evaluate both sides' answers and decide which one is correct. If you determine the correct answer, set 'finished' to true and provide the 'correctAnswer'. """, topic)) .model(model) .memory(new InMemoryMemory()) .build(); ``` ### Step 3: Define Structured Output for Judgment Use a structured output class to capture the moderator's decision: ```java /** * Structured output model for the moderator's judgment. */ public class JudgeResult { /** * Whether the debate has reached a conclusion. */ public boolean finished; /** * The correct answer, if the debate is finished. */ public String correctAnswer; } ``` ### Step 4: Implement the Debate Loop ```java import io.agentscope.core.message.Msg; import io.agentscope.core.message.MsgRole; import io.agentscope.core.message.TextBlock; import io.agentscope.core.pipeline.MsgHub; public void runDebate() { int maxRounds = 5; for (int round = 1; round <= maxRounds; round++) { System.out.println("\n=== Round " + round + " ===\n"); // Debaters discuss within MsgHub // Their messages are automatically broadcast to each other try (MsgHub hub = MsgHub.builder() .participants(alice, bob, moderator) .build()) { hub.enter().block(); // Alice presents her argument (affirmative side) Msg aliceMsg = alice.call(Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder() .text("You are the affirmative side. Please express your viewpoints.") .build()) .build()).block(); System.out.println("Alice: " + aliceMsg.getTextContent()); // Bob presents his argument (negative side) Msg bobMsg = bob.call(Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder() .text("You are the negative side. You may disagree. Provide your reason and answer.") .build()) .build()).block(); System.out.println("Bob: " + bobMsg.getTextContent()); } // Moderator evaluates (outside MsgHub - debaters don't need to see this) Msg judgeMsg = moderator.call( Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder() .text("Now you have heard the answers from both debaters. " + "Has the debate finished? Can you determine the correct answer?") .build()) .build(), JudgeResult.class // Request structured output ).block(); // Extract structured judgment JudgeResult result = judgeMsg.getStructuredData(JudgeResult.class); if (result.finished) { System.out.println("\n=== Debate Concluded ==="); System.out.println("The correct answer is: " + result.correctAnswer); return; } System.out.println("Moderator: Debate continues to next round..."); } System.out.println("\n=== Max rounds reached without conclusion ==="); } ``` ## Complete Example Here's a complete, runnable example: ```java package io.agentscope.examples; import io.agentscope.core.ReActAgent; import io.agentscope.core.formatter.dashscope.DashScopeMultiAgentFormatter; import io.agentscope.core.memory.InMemoryMemory; import io.agentscope.core.message.Msg; import io.agentscope.core.message.MsgRole; import io.agentscope.core.message.TextBlock; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.pipeline.MsgHub; public class MultiAgentDebateExample { public static class JudgeResult { public boolean finished; public String correctAnswer; } public static void main(String[] args) { // Topic String topic = """ The two circles are externally tangent and there is no relative sliding. The radius of circle A is 1/3 the radius of circle B. Circle A rolls around circle B one trip back to its starting point. How many times will circle A revolve in total? """; // Create model DashScopeChatModel model = DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .formatter(new DashScopeMultiAgentFormatter()) .build(); // Create debaters ReActAgent alice = ReActAgent.builder() .name("Alice") .sysPrompt("You're a debater named Alice. Topic: " + topic) .model(model) .memory(new InMemoryMemory()) .build(); ReActAgent bob = ReActAgent.builder() .name("Bob") .sysPrompt("You're a debater named Bob. Topic: " + topic) .model(model) .memory(new InMemoryMemory()) .build(); // Create moderator ReActAgent moderator = ReActAgent.builder() .name("Moderator") .sysPrompt("You're a moderator evaluating a debate on: " + topic) .model(model) .memory(new InMemoryMemory()) .build(); // Run debate for (int round = 1; round <= 5; round++) { System.out.println("\n=== Round " + round + " ===\n"); try (MsgHub hub = MsgHub.builder() .participants(alice, bob, moderator) .build()) { hub.enter().block(); Msg aliceMsg = alice.call(Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder() .text("Present your argument.") .build()) .build()).block(); System.out.println("Alice: " + aliceMsg.getTextContent()); Msg bobMsg = bob.call(Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder() .text("Respond to Alice and present your argument.") .build()) .build()).block(); System.out.println("Bob: " + bobMsg.getTextContent()); } // Moderator judgment Msg judgeMsg = moderator.call( Msg.builder() .name("user") .role(MsgRole.USER) .content(TextBlock.builder() .text("Evaluate the debate. Is there a correct answer?") .build()) .build(), JudgeResult.class ).block(); JudgeResult result = judgeMsg.getStructuredData(JudgeResult.class); if (result.finished) { System.out.println("\n=== Debate Concluded ==="); System.out.println("Answer: " + result.correctAnswer); break; } } } } ``` ## Variations ### Multiple Debaters You can extend the pattern to include more than two debaters: ```java try (MsgHub hub = MsgHub.builder() .participants(alice, bob, charlie, moderator) .build()) { hub.enter().block(); alice.call(prompt).block(); bob.call(prompt).block(); charlie.call(prompt).block(); } ``` ### Debate Without Moderator For simpler scenarios, you can run a debate without a moderator and let the debaters reach consensus: ```java // Fixed number of rounds without moderator evaluation for (int round = 0; round < 3; round++) { try (MsgHub hub = MsgHub.builder() .participants(alice, bob) .build()) { hub.enter().block(); alice.call().block(); bob.call().block(); } } // Final synthesis ReActAgent synthesizer = ReActAgent.builder() .name("Synthesizer") .sysPrompt("Synthesize all perspectives and provide a final answer.") .model(model) .build(); Msg finalAnswer = synthesizer.call(summaryMessage).block(); ``` ## Related Documentation - [MsgHub](./msghub.md) - Message broadcasting for multi-agent conversations - [Pipeline](./pipeline.md) - Sequential and parallel agent execution - [Structured Output](../task/structured-output.md) - Extracting structured data from agent responses