# 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 |
|------------------------------------------|------------------------------------------|------------------------------------------|
|  |  |  |
# 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 extends State> 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