chore: initialize sandbox and overwrite remote content
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled

This commit is contained in:
codex-bot
2026-03-02 22:32:27 +08:00
commit a64378956a
584 changed files with 93604 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
# Agent Skills in AgentScope
[Agent Skill](https://claude.com/blog/skills) is an approach proposed by
Anthropic to improve agent capabilities on specific tasks.
In this example, we demonstrate how to integrate Agent Skills into an
ReAct agent in AgentScope via the `toolkit.register_agent_skill` API.
Specifically, we prepare a demonstration skill that helps the agent to
learn about the AgentScope framework itself in the `skill` directory.
In `main.py`, we register this skill to the agent's toolkit, and ask it
to answer questions about AgentScope.
## Quick Start
Install the latest version of AgentScope to run this example:
```bash
pip install agentscope --upgrade
```
Then, run the example with:
```bash
python main.py
```
> Note:
> - The example is built with DashScope chat model. If you want to change the model used in this example, don't
> forget to change the formatter at the same time! The corresponding relationship between built-in models and
> formatters are list in [our tutorial](https://doc.agentscope.io/tutorial/task_prompt.html#id1)
> - For local models, ensure the model service (like Ollama) is running before starting the agent.

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""The main entry point of the agent skill example."""
import asyncio
import os
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import (
Toolkit,
execute_shell_command,
execute_python_code,
view_text_file,
)
async def main() -> None:
"""The main entry point for the ReAct agent example."""
toolkit = Toolkit()
# To use agent skills, your agent must be equipped with text file viewing
# tools.
toolkit.register_tool_function(execute_shell_command)
toolkit.register_tool_function(execute_python_code)
toolkit.register_tool_function(view_text_file)
# Register the agent skill
toolkit.register_agent_skill(
"./skill/analyzing-agentscope-library",
)
agent = ReActAgent(
name="Friday",
sys_prompt="""You are a helpful assistant named Friday.
# IMPORTANT
- Don't make any assumptions. All your knowledge about AgentScope library must come from your equipped skills.
""", # noqa: E501
model=DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen3-max",
enable_thinking=False,
stream=True,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
)
# First, let's take a look at the agent's system prompt
print("\033[1;32mAgent System Prompt:\033[0m")
print(agent.sys_prompt)
print("\n")
print(
"\033[1;32mResponse to Question 'What skills do you have?':\033[0m",
)
# We prepare two questions
await agent(
Msg("user", "What skills do you have?", "user"),
)
print(
"\n\033[1;32mResponse to Question 'How to create my own tool function "
"for the agent in agentscope?':\033[0m",
)
# The second question
await agent(
Msg(
"user",
"How to custom tool function for the agent in agentscope?",
"user",
),
)
asyncio.run(main())

View File

@@ -0,0 +1,78 @@
---
name: Analyzing AgentScope Library
description: This skill provides a way to retrieve information from the AgentScope library for analysis and decision-making.
---
# Analyzing AgentScope Library
## Overview
This guide covers the essential operations for retrieving and answering questions about the AgentScope library.
If you need to answer questions regarding the AgentScope library, or look up specific information, functions/classes,
examples or guidance, this skill will help you achieve that.
## Quick Start
The skill provides the following key scripts:
- Search for guidance in the AgentScope tutorial.
- Search for official examples and recommended implementations provided by AgentScope.
- A quick interface to view AgentScope's Python library by given a module name (e.g. agentscope), and return the module's submodules, classes, and functions.
When being asked an AgentScope-related question, you can follow the steps below to find the relevant information:
First decide which of the three scripts to use based on the user's question.
- If user asks for "how to use" types of questions, use the "Search for Guidance" script to find the relevant tutorial
- If user asks for "how to implement/build" types of questions, first search for relevant examples. If not found, then
consider what functions are needed and search in the guide/tutorial
- If user asks for "how to initialize" types of questions, first search for relevant tutorials. If not found, then
consider to search for the corresponding modules, classes, or functions in the library.
### Search for Examples
First ask for the user's permission to clone the agentscope GitHub repository if you haven't done so:
```bash
git clone -b main https://github.com/agentscope-ai/agentscope
```
In this repo, the `examples` folder contains various examples demonstrating how to use different features of the
AgentScope library.
They are organized in a tree structure by different functionalities. You should use shell command like `ls` or `cat` to
navigate and view the examples. Avoid using `find` command to search for examples, as the name of the example
files may not directly relate to the functionality being searched for.
### Search for Guidance
Similarly, first ensure you have cloned the agentscope GitHub repository.
The source agentscope tutorial is located in the `docs/tutorials` folder of the agentscope GitHub repository. It's
organized by the different sections. To search for guidance, go to the `docs/tutorials` folder and view the tutorial
files by shell command like `ls` or `cat`.
### Search for Targeted Modules
First, ensure you have installed the agentscope library in your environment:
```bash
pip list | grep agentscope
```
If not installed, ask the user for permission to install it by command:
```bash
pip install agentscope
```
Then, run the following script to search for specific modules, classes, or functions. It's suggested to start with
`agentscope` as the root module name, and then specify the submodule name you want to search for.
```bash
python view_agentscope_module.py --module agentscope
```
About detailed usage, please refer to the `./view_agentscope_module.py` script (located in the same folder as this
SKILL.md file).

View File

@@ -0,0 +1,306 @@
# -*- coding: utf-8 -*-
# pylint: skip-file
"""Get the signatures of functions and classes in the agentscope library."""
from typing import Literal, Callable
import agentscope
import inspect
from pydantic import BaseModel
def get_class_signature(cls: type) -> str:
"""Get the signature of a class.
Args:
cls (`type`):
A class object.
Returns:
str: The signature of the class.
"""
# Obtain class name and docstring
class_name = cls.__name__
class_docstring = cls.__doc__ or ""
# Construct the class string
class_str = f"class {class_name}:\n"
if class_docstring:
class_str += f' """{class_docstring}"""\n'
# Obtain the module of the class
methods = []
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
# Skip methods that are not part of the class
if method.__qualname__.split(".")[0] != class_name:
continue
if name.startswith("_") and name not in ["__init__", "__call__"]:
continue
# Obtain the method's signature
sig = inspect.signature(method)
# Construct the method string
method_str = f" def {name}{sig}:\n"
# Add the method's docstring if it exists
method_docstring = method.__doc__ or ""
if method_docstring:
method_str += f' """{method_docstring}"""\n'
methods.append(method_str)
class_str += "\n".join(methods)
return class_str
def get_function_signature(func: Callable) -> str:
"""Get the signature of a function."""
sig = inspect.signature(func)
method_str = f"def {func.__name__}{sig}:\n"
method_docstring = func.__doc__ or ""
if method_docstring:
method_str += f' """{method_docstring}"""\n'
return method_str
class FuncOrCls(BaseModel):
"""The class records the module, signature, docstring, reference, and
type"""
module: str
"""The module of the function or class."""
signature: str
"""The signature of the function or class."""
docstring: str
"""The docstring of the function or class."""
reference: str
"""The reference to the source code of the function or class"""
type: Literal["function", "class"]
"""The type of the function or class, either 'function' or 'class'."""
def __init__(
self,
module: str,
signature: str,
docstring: str,
reference: str,
# pylint: disable=redefined-builtin
type: Literal["function", "class"],
) -> None:
"""Initialize the FuncOrCls instance."""
super().__init__(
module=module,
signature=signature.strip(),
docstring=docstring.strip(),
reference=reference,
type=type,
)
def _truncate_docstring(docstring: str, max_length: int = 200) -> str:
"""Truncate the docstring to a maximum length.
Args:
docstring (`str`):
The docstring to truncate.
max_length (`int`, *optional*, defaults to 200):
The maximum length of the docstring.
Returns:
`str`:
The truncated docstring.
"""
if len(docstring) > max_length:
return docstring[:max_length] + "..."
return docstring
def get_agentscope_module_signatures() -> list[FuncOrCls]:
"""Get the signatures of functions and classes in the agentscope library.
Returns:
`list[FuncOrCls]`:
A list of FuncOrCls instances representing the functions and
classes in the agentscope library.
"""
signatures = []
for module in agentscope.__all__:
as_module = getattr(agentscope, module)
path_module = ".".join(["agentscope", module])
# Functions
if inspect.isfunction(as_module):
file = inspect.getfile(as_module)
source_lines, start_line = inspect.getsourcelines(as_module)
signatures.append(
FuncOrCls(
module=path_module,
signature=get_function_signature(as_module),
docstring=_truncate_docstring(as_module.__doc__ or ""),
reference=f"{file}: {start_line}-"
f"{start_line + len(source_lines)}",
type="function",
),
)
else:
if not hasattr(as_module, "__all__"):
continue
# Modules with __all__ attribute
for name in as_module.__all__:
func_or_cls = getattr(as_module, name)
path_func_or_cls = ".".join([path_module, name])
if inspect.isclass(func_or_cls):
file = inspect.getfile(func_or_cls)
source_lines, start_line = inspect.getsourcelines(
func_or_cls,
)
signatures.append(
FuncOrCls(
module=path_func_or_cls,
signature=get_class_signature(func_or_cls),
docstring=_truncate_docstring(
func_or_cls.__doc__ or "",
),
reference=(
f"{file}: {start_line}-"
f"{start_line + len(source_lines)}"
),
type="class",
),
)
elif inspect.isfunction(func_or_cls):
file = inspect.getfile(func_or_cls)
source_lines, start_line = inspect.getsourcelines(
func_or_cls,
)
signatures.append(
FuncOrCls(
module=path_func_or_cls,
signature=get_function_signature(func_or_cls),
docstring=_truncate_docstring(
func_or_cls.__doc__ or "",
),
reference=(
f"{file}: {start_line}-"
f"{start_line + len(source_lines)}"
),
type="function",
),
)
return signatures
def view_agentscope_library(
module: str,
) -> str:
"""View AgentScope's Python library by given a module name
(e.g. agentscope), and return the module's submodules, classes, and
functions. Given a class name, return the class's documentation, methods,
and their signatures. Given a function name, return the function's
documentation and signature. If you don't have any information about
AgentScope library, try to use "agentscope" to view the available top
modules.
Note this function only provide the module's brief information.
For more information, you should view the source code.
Args:
module (`str`):
The module name to view, which should be a module path separated
by dots (e.g. "agentscope.models"). It can refer to a module,
a class, or a function.
"""
if not module.startswith("agentscope"):
return (
f"Module '{module}' is invalid. The input module should be "
f"'agentscope' or submodule of 'agentscope.xxx.xxx' "
f"(separated by dots)."
)
agentscope_top_modules = {}
for as_module in agentscope.__all__:
if as_module in ["__version__", "logger"]:
continue
agentscope_top_modules[as_module] = getattr(
agentscope,
as_module,
).__doc__
# top modules
if module == "agentscope":
top_modules_description = (
[
"The top-level modules in AgentScope library:",
]
+ [
f"- agentscope.{k}: {v}"
for k, v in agentscope_top_modules.items()
]
+ [
"You can further view the classes/function within above "
"modules by calling this function with the above module name.",
]
)
return "\n".join(top_modules_description)
# class, functions
modules = get_agentscope_module_signatures()
for as_module in modules:
if as_module.module == module:
return f"""- The signature of '{module}':
```python
{as_module.signature}
```
- Source code reference: {as_module.reference}"""
# two-level modules
collected_modules = []
for as_module in modules:
if as_module.module.startswith(module):
collected_modules.append(as_module)
if len(collected_modules) > 0:
collected_modules_content = (
[
f"The classes/functions and their truncated docstring in "
f"'{module}' module:",
]
+ [f"- {_.module}: {repr(_.docstring)}" for _ in collected_modules]
+ [
"The docstring is truncated for limited context. For detailed "
"signature and methods, call this function with the above "
"module name",
]
)
return "\n".join(collected_modules_content)
return (
f"Module '{module}' not found. Use 'agentscope' to view the "
f"top-level modules to ensure the given module is valid."
)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--module",
type=str,
default="agentscope",
help="The module name to view, e.g. 'agentscope'",
)
args = parser.parse_args()
res = view_agentscope_library(module=args.module)
print(res)

View File

@@ -0,0 +1,158 @@
# Mem0 Long-Term Memory in AgentScope
This example demonstrates how to
- use Mem0LongTermMemory to provide persistent semantic memory storage for AgentScope agents,
- record and retrieve conversation history and user preferences across sessions,
- integrate long-term memory with ReAct agents for context-aware conversations, and
- configure DashScope embedding models and Qdrant vector store for memory management.
## Prerequisites
- Python 3.10 or higher
- DashScope API key from Alibaba Cloud
## QuickStart
Install agentscope and ensure you have a valid DashScope API key in your environment variables.
> Note: The example is built with DashScope chat model and embedding model. If you want to use OpenAI models instead,
> modify the model initialization in the example code accordingly.
```bash
# Install agentscope from source
cd {PATH_TO_AGENTSCOPE}
pip install -e .
# Install dependencies
pip install mem0ai
```
Set up your API key:
```bash
export DASHSCOPE_API_KEY='YOUR_API_KEY'
```
Run the example:
```bash
python memory_example.py
```
The example will:
1. Initialize a Mem0LongTermMemory instance with DashScope models and Qdrant vector store
2. Record a basic conversation to long-term memory
3. Retrieve memories using semantic search
4. Demonstrate ReAct agent integration with long-term memory for storing and retrieving user preferences
## Key Features
- **Vector-based Storage**: Uses Qdrant vector database for efficient semantic search and retrieval
- **Flexible Configuration**: Support for multiple embedding models (OpenAI, DashScope) and vector stores
- **Async Operations**: Full async support for non-blocking memory operations
- **ReAct Agent Integration**: Seamless integration with AgentScope's ReActAgent and tool system
## Basic Usage
### Initialize Memory
```python
import os
from agentscope.memory import Mem0LongTermMemory
from agentscope.model import DashScopeChatModel
from agentscope.embedding import DashScopeTextEmbedding
from mem0.vector_stores.configs import VectorStoreConfig
# Initialize with DashScope models and Qdrant vector store
long_term_memory = Mem0LongTermMemory(
agent_name="Friday",
user_name="user_123",
model=DashScopeChatModel(
model_name="qwen-max-latest",
api_key=os.environ.get("DASHSCOPE_API_KEY")
),
embedding_model=DashScopeTextEmbedding(
model_name="text-embedding-v3",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
dimensions=1024
),
vector_store_config=VectorStoreConfig(
provider="qdrant",
config={
"on_disk": True,
"path": "./qdrant_data", # Your customized storage path
"embedding_model_dims": 1024
}
)
)
```
> **Important**: If you change to a different embedding model or modify `embedding_model_dims`, you must either set a new storage path or delete the existing database files. Otherwise, a dimension mismatch error will occur.
### Integrate with ReAct Agent
```python
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.tool import Toolkit
# Create a ReAct agent with long-term memory
toolkit = Toolkit()
agent = ReActAgent(
name="Friday",
sys_prompt=(
"You are a helpful assistant named Friday. "
"If you think there is relevant information about "
"the user's preferences, you can record it to long-term "
"memory using the tool `record_to_memory`. "
"If you need to retrieve information from long-term "
"memory, use the tool `retrieve_from_memory`."
),
model=DashScopeChatModel(
model_name="qwen-max-latest",
api_key=os.environ.get("DASHSCOPE_API_KEY")
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
long_term_memory=long_term_memory,
long_term_memory_mode="both"
)
# Use the agent
msg = Msg(
role="user",
content="When I travel to Hangzhou, I prefer to stay in a homestay",
name="user"
)
response = await agent(msg)
```
## Advanced Configuration
You can customize the mem0 config by directly set :
```python
long_term_memory = Mem0LongTermMemory(
agent_name="Friday",
user_name="user_123",
mem0_config=your_mem0_config # Pass your custom mem0 configuration
)
```
For more configuration options, refer to the [mem0 documentation](https://github.com/mem0ai/mem0).
## What's in the Example
The `memory_example.py` file demonstrates:
1. **Basic Memory Recording**: Recording user conversations to long-term memory
2. **Memory Retrieval**: Searching for stored memories using semantic similarity
3. **ReAct Agent Integration**: Using long-term memory with ReAct agents to store and retrieve user preferences automatically
## Reference
- [mem0 Documentation](https://github.com/mem0ai/mem0)
- [Qdrant Vector Database](https://qdrant.tech/)

View File

@@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
"""Memory example demonstrating long-term memory functionality with mem0.
This module provides examples of how to use the Mem0LongTermMemory class
for recording and retrieving persistent memories.
"""
import asyncio
import os
from dotenv import load_dotenv
from mem0.vector_stores.configs import VectorStoreConfig
from agentscope.memory import Mem0LongTermMemory
from agentscope.agent import ReActAgent
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
load_dotenv()
async def main() -> None:
"""Run the memory examples."""
# Initialize long term memory
long_term_memory = Mem0LongTermMemory(
agent_name="Friday",
user_name="user_123",
model=DashScopeChatModel(
model_name="qwen-max-latest",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
embedding_model=DashScopeTextEmbedding(
model_name="text-embedding-v3",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
dimensions=1024,
),
vector_store_config=VectorStoreConfig(
provider="qdrant",
config={
"on_disk": True,
"path": "../memory/qdrant_data", # Specify custom path
"embedding_model_dims": 1024,
},
),
)
# If you want to also use graph memory in mem0,
# the following is an example of using Neo4j graph store.
# from mem0.configs.base import MemoryConfig
# from mem0.graphs.configs import GraphStoreConfig
# long_term_memory = Mem0LongTermMemory(
# agent_name="Friday",
# user_name="user_123",
# embedding_model=DashScopeTextEmbedding(
# model_name="text-embedding-v3",
# api_key=os.environ.get("DASHSCOPE_API_KEY"),
# dimensions=1024,
# ),
# model=DashScopeChatModel(
# model_name="qwen-max-latest",
# api_key=os.environ.get("DASHSCOPE_API_KEY"),
# stream=False,
# ),
# vector_store_config=VectorStoreConfig(
# provider="qdrant",
# config={
# "on_disk": True,
# "path": "../memory/qdrant_data", # Specify custom path
# "embedding_model_dims": 1024,
# }),
# mem0_config=MemoryConfig(
# graph_store=GraphStoreConfig(
# provider="neo4j",
# config={
# "url": os.environ.get("NEO4J_URL",
# "neo4j://localhost:7687"),
# "username": os.environ.get("NEO4J_USER", "neo4j"),
# "password": os.environ.get("NEO4J_PASSWORD",
# "12345678"),
# "database": "neo4j",
# },
# ),
# ),
# )
print("=== Long Term Memory Examples with mem0 ===\n")
# Example 1: Basic conversation recording
print("1. Basic Conversation Recording")
print("-" * 40)
results = await long_term_memory.record(
msgs=[
Msg(
role="user",
content="Please help me book a hotel, preferably homestay",
name="user",
),
],
)
print(f"Recorded conversation: {results}\n")
# Example 2: Retrieving memories
print("2. Retrieving Memories")
print("-" * 40)
print("Searching for weather-related memories...")
weather_memories = await long_term_memory.retrieve(
msg=[
Msg(
role="user",
content="What's the weather like today?",
name="user",
),
],
)
print(f"Retrieved weather memories: {weather_memories}\n")
print("Searching for user preference memories...")
preference_memories = await long_term_memory.retrieve(
msg=[
Msg(
role="user",
content=(
"I prefer temperatures in Celsius and wind speed in km/h"
),
name="user",
),
],
)
print(f"Retrieved preference memories: {preference_memories}\n")
# Example 3: ReActAgent with long term memory
print("3. ReActAgent with long term memory")
print("-" * 40)
toolkit = Toolkit()
agent = ReActAgent(
name="Friday",
sys_prompt=(
"You are a helpful assistant named Friday. "
"If you think there is relevant information about "
"user's preference, you can record it to the long term "
"memory by tool call `record_to_memory`. "
"If you need to retrieve information from the long term "
"memory, you can use the tool call `retrieve_from_memory`."
),
model=DashScopeChatModel(
model_name="qwen-max-latest",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
long_term_memory=long_term_memory,
long_term_memory_mode="both",
)
await agent.memory.clear()
msg = Msg(
role="user",
content="When I travel to Hangzhou, I prefer to stay in a homestay",
name="user",
)
msg = await agent(msg)
print(f"ReActAgent response: {msg.get_text_content()}\n")
msg = Msg(role="user", content="what preference do I have?", name="user")
msg = await agent(msg)
print(f"ReActAgent response: {msg.get_text_content()}\n")
msg = Msg(
role="user",
content="I prefer to visit the West Lake",
name="user",
)
msg = await agent(msg)
print(f"ReActAgent response: {msg.get_text_content()}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,609 @@
# ReMe Long-Term Memory in AgentScope
This example demonstrates how to:
- Use ReMe (Reflection Memory) to provide three specialized types of persistent memory storage for AgentScope agents
- Record and retrieve personal information, task execution trajectories, and tool usage patterns across sessions
- Integrate long-term memory with ReActAgent for context-aware conversations and continuous learning
- Configure DashScope embedding models and vector stores for efficient memory management
## Overview
ReMe (Reflection Memory) provides three types of long-term memory for intelligent agents:
1. **Personal Memory** (`ReMePersonalLongTermMemory`) - Records and retrieves persistent personal information, preferences, and facts about users
2. **Task Memory** (`ReMeTaskLongTermMemory`) - Learns from task execution trajectories and retrieves relevant past experiences for similar tasks
3. **Tool Memory** (`ReMeToolLongTermMemory`) - Records tool execution results and generates usage guidelines to improve tool calling
## Prerequisites
- Python 3.12 or higher
- DashScope API key from Alibaba Cloud (for the examples)
## QuickStart
### Installation
```bash
# Install agentscope from source
cd {PATH_TO_AGENTSCOPE}
pip install -e .
# Install required dependencies
pip install reme-ai python-dotenv
```
### Setup
Set up your API key:
```bash
export DASHSCOPE_API_KEY='YOUR_API_KEY'
```
Or create a `.env` file:
```bash
DASHSCOPE_API_KEY=YOUR_API_KEY
```
### Run Examples
```bash
# Personal Memory Example - 5 core interfaces
python personal_memory_example.py
# Task Memory Example - 5 core interfaces
python task_memory_example.py
# Tool Memory Example - Complete workflow with ReActAgent
python tool_memory_example.py
```
> **Note**: The examples use DashScope models by default. To use OpenAI or other models, modify the model initialization in the example code accordingly.
## Key Features
- **Three Specialized Memory Types**: Personal, Task, and Tool memory for different use cases
- **Dual Interface Design**: Both tool functions (for agent calling) and direct methods (for programmatic use)
- **Vector-based Retrieval**: Efficient semantic search using embedding models and vector stores
- **Async-first Architecture**: Full async/await support for non-blocking operations
- **ReActAgent Integration**: Seamless integration with AgentScope's ReActAgent and Toolkit
- **Automatic Context Management**: Uses async context managers for proper resource handling
## Core Concepts
### Memory Types and Their Use Cases
| Memory Type | Purpose | When to Use |
|------------|---------|-------------|
| **Personal Memory** | Store user preferences, habits, and personal facts | User profiles, personalized assistants, long-term user context |
| **Task Memory** | Learn from task execution trajectories | Problem-solving, debugging, repeated workflows, learning from past successes |
| **Tool Memory** | Record tool usage patterns and generate guidelines | Tool-using agents, improving tool call accuracy, avoiding past errors |
### Interface Design
**Personal Memory** and **Task Memory** provide **5 core interfaces**:
1. **`record_to_memory()`** - Tool function for agents to record memories (returns `ToolResponse`)
2. **`retrieve_from_memory()`** - Tool function for agents to retrieve memories (returns `ToolResponse`)
3. **`record()`** - Direct method for programmatic recording (returns `None`)
4. **`retrieve()`** - Direct method for programmatic retrieval (returns `str`)
5. **ReActAgent Integration** - Use memory with `long_term_memory` and `long_term_memory_mode` parameters
**Tool Memory** provides **2 core interfaces** (no tool functions):
1. **`record()`** - Direct method for recording tool execution results (returns `None`)
2. **`retrieve()`** - Direct method for retrieving tool usage guidelines (returns `str`)
## Usage Examples
### 1. Personal Memory
**Use Case**: Record and retrieve user preferences, habits, and personal information.
```python
import asyncio
import os
from agentscope.memory import ReMePersonalLongTermMemory
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
async def main():
# Initialize personal memory
personal_memory = ReMePersonalLongTermMemory(
agent_name="Friday",
user_name="user_123",
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
embedding_model=DashScopeTextEmbedding(
model_name="text-embedding-v4",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
dimensions=1024,
),
)
# Use async context manager (required!)
async with personal_memory:
# Interface 1: record_to_memory (tool function)
result = await personal_memory.record_to_memory(
thinking="User sharing travel preferences",
content=[
"I prefer to stay in homestays when traveling to Hangzhou",
"I like to visit the West Lake in the morning",
"I enjoy drinking Longjing tea",
],
)
# Interface 2: retrieve_from_memory (tool function)
result = await personal_memory.retrieve_from_memory(
keywords=["Hangzhou travel", "tea preference"],
)
# Interface 3: record (direct method)
await personal_memory.record(
msgs=[
Msg(role="user", content="I work as a software engineer", name="user"),
Msg(role="assistant", content="Got it!", name="assistant"),
],
)
# Interface 4: retrieve (direct method)
memories = await personal_memory.retrieve(
msg=Msg(role="user", content="What do you know about my work?", name="user"),
)
print(memories)
asyncio.run(main())
```
**Integration with ReActAgent** (Interface 5):
```python
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.tool import Toolkit
async def use_with_agent():
personal_memory = ReMePersonalLongTermMemory(...)
async with personal_memory:
agent = ReActAgent(
name="Friday",
sys_prompt="You are Friday with long-term memory. Always record user information and retrieve memories when needed.",
model=DashScopeChatModel(...),
formatter=DashScopeChatFormatter(),
toolkit=Toolkit(),
memory=InMemoryMemory(),
long_term_memory=personal_memory, # Attach personal memory
long_term_memory_mode="both", # Enable both record and retrieve tools
)
# Agent can now use record_to_memory and retrieve_from_memory as tools
msg = Msg(role="user", content="I prefer staying in homestays", name="user")
response = await agent(msg)
```
### 2. Task Memory
**Use Case**: Learn from task execution trajectories and retrieve relevant experiences.
```python
from agentscope.memory import ReMeTaskLongTermMemory
async def main():
# Initialize task memory
task_memory = ReMeTaskLongTermMemory(
agent_name="TaskAssistant",
user_name="task_workspace_123", # Acts as workspace_id
model=DashScopeChatModel(...),
embedding_model=DashScopeTextEmbedding(...),
)
async with task_memory:
# Interface 1: record_to_memory with score
result = await task_memory.record_to_memory(
thinking="Recording successful debugging approach",
content=[
"For API 404 errors: Check route definition, verify URL path, ensure correct port",
"Always use linter to catch typos in route paths",
],
score=0.95, # High score for successful trajectory
)
# Interface 2: retrieve_from_memory
result = await task_memory.retrieve_from_memory(
keywords=["debugging", "API errors"],
)
# Interface 3: record with score in direct method
await task_memory.record(
msgs=[
Msg(role="user", content="I'm getting a 404 error", name="user"),
Msg(role="assistant", content="Let's check the route path...", name="assistant"),
Msg(role="user", content="Found the typo!", name="user"),
],
score=0.95, # Optional score for this trajectory
)
# Interface 4: retrieve (direct method)
experiences = await task_memory.retrieve(
msg=Msg(role="user", content="How to debug API errors?", name="user"),
)
print(experiences)
asyncio.run(main())
```
**Integration with ReActAgent** (Interface 5):
```python
async def use_with_agent():
task_memory = ReMeTaskLongTermMemory(...)
async with task_memory:
agent = ReActAgent(
name="TaskAssistant",
sys_prompt="You are a task assistant. Record solutions and retrieve past experiences before solving problems.",
model=DashScopeChatModel(...),
formatter=DashScopeChatFormatter(),
toolkit=Toolkit(),
memory=InMemoryMemory(),
long_term_memory=task_memory,
long_term_memory_mode="both",
)
# Agent learns from task executions over time
msg = Msg(role="user", content="How should I optimize database queries?", name="user")
response = await agent(msg)
```
### 3. Tool Memory
**Use Case**: Record tool execution results and generate usage guidelines for better tool calling.
**Complete Workflow**:
```python
import json
from datetime import datetime
from agentscope.memory import ReMeToolLongTermMemory
from agentscope.tool import Toolkit, ToolResponse
from agentscope.message import Msg, TextBlock
# Step 1: Define tools
async def web_search(query: str, max_results: int = 5) -> ToolResponse:
"""Search the web for information."""
result = f"Found {max_results} results for query: '{query}'"
return ToolResponse(content=[TextBlock(type="text", text=result)])
async def main():
# Initialize tool memory
tool_memory = ReMeToolLongTermMemory(
agent_name="ToolBot",
user_name="tool_workspace_demo",
model=DashScopeChatModel(...),
embedding_model=DashScopeTextEmbedding(...),
)
async with tool_memory:
# Step 2: Record tool execution history (accepts JSON strings in msgs)
tool_result = {
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "web_search",
"input": {"query": "Python asyncio tutorial", "max_results": 10},
"output": "Found 10 results for query: 'Python asyncio tutorial'",
"token_cost": 150,
"success": True,
"time_cost": 2.3
}
# Interface 1: record (accepts JSON strings in message content)
await tool_memory.record(
msgs=[Msg(role="assistant", content=json.dumps(tool_result), name="assistant")],
)
# Step 3: Retrieve tool guidelines
# Interface 2: retrieve returns summarized guidelines
guidelines = await tool_memory.retrieve(
msg=Msg(role="user", content="web_search", name="user"),
)
# Step 4: Inject guidelines into agent system prompt
toolkit = Toolkit()
toolkit.register_tool_function(web_search)
base_prompt = "You are ToolBot, a helpful AI assistant."
enhanced_prompt = f"{base_prompt}\n\n# Tool Guidelines:\n{guidelines}"
agent = ReActAgent(
name="ToolBot",
sys_prompt=enhanced_prompt, # Guidelines enhance tool usage
model=DashScopeChatModel(...),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
)
# Agent now uses tools with learned guidelines
msg = Msg(role="user", content="Search for Python design patterns", name="user")
response = await agent(msg)
asyncio.run(main())
```
> **Note**: Tool Memory does NOT provide `record_to_memory()` and `retrieve_from_memory()` tool functions. It only provides direct `record()` and `retrieve()` methods. Tool Memory is designed to be used programmatically to enhance agent system prompts, not as agent-callable tools.
## API Reference
### Common Parameters
All memory types share these initialization parameters:
```python
ReMePersonalLongTermMemory(
agent_name: str, # Name of the agent using this memory
user_name: str, # User identifier (acts as workspace_id in ReMe)
model: ModelWrapper, # LLM for summarization and processing
embedding_model: EmbeddingWrapper, # Embedding model for vector retrieval
vector_store_dir: str = "./memory_vector_store", # Storage location
)
```
### Interface Specifications
#### Personal Memory
| Interface | Type | Signature | Returns | Description |
|-----------|------|-----------|---------|-------------|
| `record_to_memory` | Tool Function | `(thinking: str, content: list[str])` | `ToolResponse` | Record personal information with reasoning |
| `retrieve_from_memory` | Tool Function | `(keywords: list[str], limit: int = 3)` | `ToolResponse` | Retrieve memories by keywords |
| `record` | Direct Method | `(msgs: list[Msg])` | `None` | Record message conversations |
| `retrieve` | Direct Method | `(msg: Msg, top_k: int = 3)` | `str` | Query-based retrieval |
**Parameters**:
- `thinking`: Reasoning about what to record
- `content`: List of strings to remember
- `keywords`: Search keywords
- `limit`: Results per keyword (tool function, default: 3)
- `top_k`: Total results to retrieve (direct method, default: 3)
#### Task Memory
| Interface | Type | Signature | Returns | Description |
|-----------|------|-----------|---------|-------------|
| `record_to_memory` | Tool Function | `(thinking: str, content: list[str], score: float = 1.0)` | `ToolResponse` | Record task trajectory with score |
| `retrieve_from_memory` | Tool Function | `(keywords: list[str], top_k: int = 5)` | `ToolResponse` | Retrieve experiences by keywords |
| `record` | Direct Method | `(msgs: list[Msg], score: float = 1.0)` | `None` | Record message conversations with score |
| `retrieve` | Direct Method | `(msg: Msg, top_k: int = 5)` | `str` | Query-based experience retrieval |
**Parameters**:
- `thinking`: Reasoning about the task execution
- `content`: Task execution information and insights
- `score`: Success score for the trajectory (0.0-1.0, default: 1.0)
- `keywords`: Search keywords (e.g., task type, domain)
- `top_k`: Number of results to retrieve (default: 5)
#### Tool Memory
| Interface | Type | Signature | Returns | Description |
|-----------|------|-----------|---------|-------------|
| `record` | Direct Method | `(msgs: list[Msg])` | `None` | Record tool results as messages (JSON format) |
| `retrieve` | Direct Method | `(msg: Msg)` | `str` | Retrieve guidelines for tools |
**Parameters**:
- `msgs`: List of messages where `content` contains JSON strings with tool execution metadata:
- `create_time`: Timestamp (`"%Y-%m-%d %H:%M:%S"`)
- `tool_name`: Tool identifier
- `input`: Parameters used (dict)
- `output`: Execution result (str)
- `token_cost`: Token usage (int)
- `success`: Execution status (bool)
- `time_cost`: Duration in seconds (float)
- `msg`: Message containing tool name to retrieve guidelines for
- **Note**: Tool Memory does NOT provide tool functions (`record_to_memory` and `retrieve_from_memory`). It only provides direct methods for programmatic use.
### ReActAgent Integration Modes
When attaching **Personal Memory** or **Task Memory** to ReActAgent, use the `long_term_memory_mode` parameter:
```python
agent = ReActAgent(
name="Assistant",
long_term_memory=memory, # ReMePersonalLongTermMemory or ReMeTaskLongTermMemory
long_term_memory_mode="both", # Options: "record", "retrieve", "both"
# ... other parameters
)
```
**Modes**:
- `"record"`: Only adds `record_to_memory` tool to agent
- `"retrieve"`: Only adds `retrieve_from_memory` tool to agent
- `"both"`: Adds both tools (recommended for most use cases)
> **Note**: Tool Memory does NOT support ReActAgent integration with tool functions. Use Tool Memory programmatically to enhance system prompts as shown in the Tool Memory example.
### Async Context Manager (Required!)
All ReMe memory types **must** be used with async context managers:
```python
async with long_term_memory:
# All memory operations must be within this context
await long_term_memory.record(msgs=[...])
result = await long_term_memory.retrieve(msg=...)
```
This ensures:
- Proper initialization of the ReMe backend
- Resource cleanup after operations
- Vector store connection management
### Custom Configuration
```python
from agentscope.memory import ReMePersonalLongTermMemory
# Custom storage location and models
memory = ReMePersonalLongTermMemory(
agent_name="Friday",
user_name="user_123",
model=your_custom_model, # Any AgentScope-compatible LLM
embedding_model=your_embedding, # Any AgentScope-compatible embedding model
vector_store_dir="./custom_path", # Custom storage directory
)
```
## Example Files Overview
### `personal_memory_example.py`
Demonstrates **5 core interfaces** for personal memory:
1. **`record_to_memory()`** - Record user preferences using tool function
2. **`retrieve_from_memory()`** - Search memories by keywords using tool function
3. **`record()`** - Direct recording of message conversations
4. **`retrieve()`** - Direct query-based retrieval
5. **ReActAgent Integration** - Agent autonomously uses memory tools
**Key Features**:
- Recording travel preferences, work habits, and personal information
- Keyword-based and query-based retrieval
- System prompt guidelines for agent memory usage
- Automatic memory tool calling by ReActAgent
### `task_memory_example.py`
Demonstrates **5 core interfaces** for task memory:
1. **`record_to_memory()`** - Record task experiences with scores
2. **`retrieve_from_memory()`** - Retrieve relevant experiences by keywords
3. **`record()`** - Direct recording with trajectory scores
4. **`retrieve()`** - Direct experience retrieval
5. **ReActAgent Integration** - Agent learns from past task executions
**Key Features**:
- Recording project planning, debugging, and development experiences
- Score-based trajectory evaluation (0.0-1.0)
- Learning from successful and failed attempts
- Continuous improvement through experience retrieval
### `tool_memory_example.py`
Demonstrates the **complete workflow** for tool memory:
1. **Mock tools** - Define and register tools to Toolkit
2. **Record tool history** - Store execution results with metadata using `record()`
3. **Retrieve guidelines** - Get summarized usage guidelines using `retrieve()`
4. **Enhance agent prompt** - Inject guidelines into system prompt
5. **Use ReActAgent** - Agent uses tools with learned guidelines
**Key Features**:
- JSON-formatted tool execution recording via direct `record()` method
- Automatic guideline generation through summarization
- Multi-tool guideline retrieval via direct `retrieve()` method
- System prompt enhancement for better tool usage
- **Note**: Tool Memory does NOT provide agent-callable tool functions
## Architecture
### Inheritance Hierarchy
```
ReMeLongTermMemoryBase (abstract base)
├── ReMePersonalLongTermMemory
├── ReMeTaskLongTermMemory
└── ReMeToolLongTermMemory
```
**`ReMeLongTermMemoryBase`** provides:
- Integration with ReMe library's `ReMeApp`
- Async context manager implementation
- Common interface definitions
- Vector store and embedding management
### Memory Storage
- **Location**: `./memory_vector_store/` (configurable)
- **Isolation**: Each `user_name` maintains separate storage
- **Persistence**: Memories persist across sessions
- **Format**: Vector embeddings with metadata
## Best Practices
### 1. System Prompt Design
For agents with long-term memory, clearly specify when to record and retrieve:
```python
sys_prompt = """
You are an assistant with long-term memory.
Recording Guidelines:
- Record when users share personal information, preferences, or important facts
- Record successful task execution approaches and solutions
- Record tool execution results with detailed metadata
Retrieval Guidelines:
- ALWAYS retrieve before answering questions about past information
- Retrieve when dealing with similar tasks to past executions
- Check tool guidelines before using tools
"""
```
### 2. Score Assignment (Task Memory)
Use meaningful scores to prioritize experiences:
```python
# Successful trajectory
await task_memory.record_to_memory(..., score=0.95)
# Partially successful
await task_memory.record_to_memory(..., score=0.6)
# Failed trajectory (still useful to learn from)
await task_memory.record_to_memory(..., score=0.2)
```
### 3. Tool Memory Workflow
Follow this pattern for tool memory:
```
1. Execute tool → 2. Record result → 3. Trigger summarization → 4. Retrieve guidelines → 5. Use in agent
```
## Troubleshooting
### Common Issues
**Issue**: `RuntimeError: Memory not initialized`
- **Solution**: Always use `async with memory:` context manager
**Issue**: No memories retrieved
- **Solution**: Ensure you've recorded memories first and check `user_name` matches
**Issue**: Tool memory not generating guidelines
- **Solution**: Record multiple tool executions to trigger summarization
**Issue**: Agent not using memory tools
- **Solution**: Check `long_term_memory_mode="both"` and verify system prompt encourages memory usage
## References
- [ReMe Library](https://github.com/modelscope/ReMe) - Core memory implementation
- [AgentScope Documentation](https://github.com/modelscope/agentscope) - Framework documentation
- [DashScope API](https://dashscope.aliyun.com/) - Model API for examples

View File

@@ -0,0 +1,295 @@
# -*- coding: utf-8 -*-
"""Personal memory example demonstrating ReMe personal memory.
This module provides examples of how to use the ReMePersonalMemory
class.
The example demonstrates 5 core interfaces:
1. record_to_memory - Tool function for explicit memory recording
2. retrieve_from_memory - Tool function for keyword-based retrieval
3. record - Direct method for recording message conversations
4. retrieve - Direct method for query-based retrieval
5. ReActAgent integration - Using personal memory with ReActAgent
"""
import asyncio
import os
from dotenv import load_dotenv
from agentscope.agent import ReActAgent
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.memory import ReMePersonalLongTermMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import ToolResponse, Toolkit
load_dotenv()
async def test_record_to_memory(
memory: ReMePersonalLongTermMemory,
) -> None:
"""Test the record_to_memory tool function interface."""
print("Interface 1: record_to_memory (Tool Function)")
print("-" * 70)
print("Purpose: Explicit memory recording with structured content")
print("Test case: Recording user's travel preferences...")
result: ToolResponse = await memory.record_to_memory(
thinking=("The user is sharing their travel preferences and habits"),
content=[
"I prefer to stay in homestays when traveling to Hangzhou",
"I like to visit the West Lake in the morning",
"I enjoy drinking Longjing tea",
],
)
result_text = " ".join(
block.get("text", "")
for block in result.content
if block.get("type") == "text"
)
print(f"✓ Result: {result_text}")
print(
f"✓ Status: {'Success' if 'Success' in result_text else 'Failed'}",
)
print()
async def test_retrieve_from_memory(
memory: ReMePersonalLongTermMemory,
) -> None:
"""Test the retrieve_from_memory tool function interface."""
print("Interface 2: retrieve_from_memory (Tool Function)")
print("-" * 70)
print("Purpose: Keyword-based memory retrieval")
print()
result = await memory.retrieve_from_memory(
keywords=["Hangzhou travel", "tea preference"],
)
retrieved_text = " ".join(
block.get("text", "")
for block in result.content
if block.get("type") == "text"
)
print("✓ Retrieved memories:")
print(f"{retrieved_text}")
print()
async def test_record_direct(memory: ReMePersonalLongTermMemory) -> None:
"""Test the direct record method interface."""
print("Interface 3: record (Direct Recording)")
print("-" * 70)
print("Purpose: Direct recording of message conversations")
print()
print("Test case: Recording work preferences and habits...")
try:
await memory.record(
msgs=[
Msg(
role="user",
content=(
"I work as a software engineer and prefer "
"remote work"
),
name="user",
),
Msg(
role="assistant",
content=(
"Understood! You're a software engineer who "
"values remote work flexibility."
),
name="assistant",
),
Msg(
role="user",
content=(
"I usually start my day at 9 AM with a "
"cup of coffee"
),
name="user",
),
],
)
print("✓ Status: Successfully recorded conversation messages")
print(
"✓ Messages recorded: 3 messages (user-assistant dialogue)",
)
except Exception as e:
print(f"✗ Status: Failed - {str(e)}")
print()
async def test_retrieve_direct(memory: ReMePersonalLongTermMemory) -> None:
"""Test the direct retrieve method interface."""
print("Interface 4: retrieve (Direct Retrieval)")
print("-" * 70)
print("Purpose: Query-based memory retrieval using messages")
print()
print(
"Test case: Querying 'What do you know about my "
"work preferences?'...",
)
memories = await memory.retrieve(
msg=Msg(
role="user",
content="What do you know about my work preferences?",
name="user",
),
)
print("✓ Retrieved memories:")
print(f"{memories if memories else 'No memories found'}")
status = (
"Success - Found memories"
if memories
else "No relevant memories found"
)
print(f"✓ Status: {status}")
print()
async def test_react_agent_with_memory(
memory: ReMePersonalLongTermMemory,
) -> None:
"""Test ReActAgent integration with personal memory."""
print("Interface 5: ReActAgent with Personal Memory")
print("-" * 70)
print(
"Purpose: Demonstrate how ReActAgent uses personal memory tools",
)
print()
print("Test case: Agent-driven memory recording and retrieval...")
toolkit = Toolkit()
agent = ReActAgent(
name="Friday",
sys_prompt=(
"You are a helpful assistant named Friday with long-term "
"memory capabilities. "
"\n\n## Memory Management Guidelines:\n"
"1. **Recording Memories**: When users share personal "
"information, preferences, "
"habits, or facts about themselves, ALWAYS record them "
"using `record_to_memory` "
"for future reference.\n"
"\n2. **Retrieving Memories**: BEFORE answering questions "
"about the user's preferences, "
"past information, or personal details, you MUST FIRST "
"call `retrieve_from_memory` "
"to check if you have any relevant stored information. "
"Do NOT rely solely on the "
"current conversation context.\n"
"\n3. **When to Retrieve**: Call `retrieve_from_memory` "
"when:\n"
" - User asks questions like 'what do I like?', "
"'what are my preferences?', "
"'what do you know about me?'\n"
" - User asks about their past behaviors, habits, or "
"preferences\n"
" - User refers to information they mentioned before\n"
" - You need context about the user to provide "
"personalized responses\n"
"\nAlways check your memory first before claiming you "
"don't know something about the user."
),
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
long_term_memory=memory,
long_term_memory_mode="both",
)
await agent.memory.clear()
print(
"→ User: 'When I travel to Hangzhou, I prefer to stay in "
"a homestay'",
)
msg = Msg(
role="user",
content=(
"When I travel to Hangzhou, I prefer to stay in " "a homestay"
),
name="user",
)
msg = await agent(msg)
print(f"✓ Agent response: {msg.get_text_content()}\n")
print("→ User: 'what preference do I have?'")
msg = Msg(
role="user",
content="what preference do I have?",
name="user",
)
msg = await agent(msg)
print(f"✓ Agent response: {msg.get_text_content()}\n")
print(
"✓ Status: Successfully demonstrated ReActAgent with "
"personal memory",
)
print()
async def main() -> None:
"""Demonstrate the 5 core interfaces of ReMePersonalMemory.
This example shows how to use:
1. record_to_memory - Tool function for explicit memory recording
2. retrieve_from_memory - Tool function for keyword-based retrieval
3. record - Direct method for recording message conversations
4. retrieve - Direct method for query-based retrieval
5. ReActAgent integration - Using personal memory with ReActAgent
"""
long_term_memory = ReMePersonalLongTermMemory(
agent_name="Friday",
user_name="user_123",
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
embedding_model=DashScopeTextEmbedding(
model_name="text-embedding-v4",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
dimensions=1024,
),
)
print("=" * 70)
print("ReMePersonalMemory - Testing 5 Core Interfaces")
print("=" * 70)
print()
# Use async context manager to ensure proper initialization
async with long_term_memory:
# await test_record_to_memory(long_term_memory)
# await test_retrieve_from_memory(long_term_memory)
# await test_record_direct(long_term_memory)
# await test_retrieve_direct(long_term_memory)
await test_react_agent_with_memory(long_term_memory)
# Alternative way: manually call __aenter__ and __aexit__
# This is equivalent to using "async with long_term_memory" above
# await long_term_memory.__aenter__()
# await test_react_agent_with_memory(long_term_memory)
# await long_term_memory.__aexit__()
print("=" * 70)
print("Testing Complete: All 5 Core Interfaces Verified!")
print("=" * 70)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,342 @@
# -*- coding: utf-8 -*-
"""Task memory example demonstrating ReMe task memory.
This module provides examples of how to use the ReMeTaskMemory class
using the ReMe library.
The example demonstrates 5 core interfaces:
1. record_to_memory - Tool function for recording task information
2. retrieve_from_memory - Tool function for keyword-based retrieval
3. record - Direct method for recording message conversations
with scores
4. retrieve - Direct method for retrieving task experiences
5. ReActAgent integration - Using task memory with ReActAgent
"""
import asyncio
import os
from dotenv import load_dotenv
from agentscope.agent import ReActAgent
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.memory import ReMeTaskLongTermMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import ToolResponse, Toolkit
load_dotenv()
async def test_record_to_memory(memory: ReMeTaskLongTermMemory) -> None:
"""Test the record_to_memory tool function interface."""
print("Interface 1: record_to_memory (Tool Function)")
print("-" * 70)
print(
"Purpose: Record task execution information with thinking and content",
)
print()
print("Test case: Recording project planning task information...")
result: ToolResponse = await memory.record_to_memory(
thinking=(
"Recording project planning best practices and "
"development approach"
),
content=[
"For web application projects, break down into phases: "
"Requirements gathering, Design, Development, Testing, "
"Deployment",
"Development phase recommendations: Frontend (React), "
"Backend (FastAPI), Database (PostgreSQL), Agile "
"methodology with 2-week sprints",
"Dependency management: Use npm for frontend and pip for "
"Python backend, maintain requirements.txt and "
"package.json files",
],
score=0.9, # Optional: score for this trajectory (1.0)
)
result_text = " ".join(
block.get("text", "")
for block in result.content
if block.get("type") == "text"
)
print(f"✓ Result: {result_text}")
print(
f"✓ Status: {'Success' if 'Success' in result_text else 'Failed'}",
)
print()
async def test_retrieve_from_memory(
memory: ReMeTaskLongTermMemory,
) -> None:
"""Test the retrieve_from_memory tool function interface."""
print("Interface 2: retrieve_from_memory (Tool Function)")
print("-" * 70)
print("Purpose: Keyword-based retrieval of task experiences")
print()
print(
"Test case: Searching with keywords 'project planning', "
"'development phase'...",
)
result = await memory.retrieve_from_memory(
keywords=["project planning", "development phase"],
)
retrieved_text = " ".join(
block.get("text", "")
for block in result.content
if block.get("type") == "text"
)
print("✓ Retrieved experiences:")
print(f"{retrieved_text}")
has_experiences = (
retrieved_text and "No task experiences found" not in retrieved_text
)
status = (
"Success - Found experiences"
if has_experiences
else "No relevant experiences found"
)
print(f"✓ Status: {status}")
print()
async def test_record_direct(memory: ReMeTaskLongTermMemory) -> None:
"""Test the direct record method interface."""
print("Interface 3: record (Direct Recording)")
print("-" * 70)
print("Purpose: Direct recording of message conversations with scores")
print()
print("Test case: Recording debugging task conversation...")
try:
await memory.record(
msgs=[
Msg(
role="user",
content="I'm getting a 404 error on my API endpoint",
name="user",
),
Msg(
role="assistant",
content=(
"Let's troubleshoot: 1) Check if the route is "
"properly defined, 2) Verify the URL path, "
"3) Ensure the server is running on the correct "
"port"
),
name="assistant",
),
Msg(
role="user",
content="Found it! The route path had a typo.",
name="user",
),
Msg(
role="assistant",
content=(
"Great! Always double-check route paths and use "
"a linter to catch typos early."
),
name="assistant",
),
],
score=0.95, # Optional: score (default: 1.0)
)
print("✓ Status: Successfully recorded debugging trajectory")
print("✓ Messages recorded: 4 messages with score 0.95")
except Exception as e:
print(f"✗ Status: Failed - {str(e)}")
print()
async def test_retrieve_direct(memory: ReMeTaskLongTermMemory) -> None:
"""Test the direct retrieve method interface."""
print("Interface 4: retrieve (Direct Retrieval)")
print("-" * 70)
print("Purpose: Query-based retrieval using messages")
print()
print("Test case: Querying 'How to debug API errors?'...")
memories = await memory.retrieve(
msg=Msg(
role="user",
content=(
"How should I approach debugging API errors in my "
"application?"
),
name="user",
),
)
print("✓ Retrieved experiences:")
print(f"{memories if memories else 'No experiences found'}")
status = (
"Success - Found experiences"
if memories
else "No relevant experiences found"
)
print(f"✓ Status: {status}")
print()
async def test_react_agent_with_memory(
memory: ReMeTaskLongTermMemory,
) -> None:
"""Test ReActAgent integration with task memory."""
print("Interface 5: ReActAgent with Task Memory")
print("-" * 70)
print(
"Purpose: Demonstrate how ReActAgent uses task memory tools",
)
print()
print(
"Test case: Agent-driven task experience recording and "
"retrieval...",
)
toolkit = Toolkit()
agent = ReActAgent(
name="TaskAssistant",
sys_prompt=(
"You are a helpful task assistant named TaskAssistant "
"with long-term task memory. "
"\n\n## Task Memory Management Guidelines:\n"
"1. **Recording Task Experiences**: When you provide "
"technical solutions, solve problems, "
"or complete tasks, ALWAYS record the key insights using "
"`record_to_memory`. Include:\n"
" - Specific techniques and approaches used\n"
" - Best practices and implementation details\n"
" - Lessons learned and important considerations\n"
" - Step-by-step procedures that worked well\n"
"\n2. **Retrieving Past Experiences**: BEFORE solving a "
"problem or answering technical "
"questions, you MUST FIRST call `retrieve_from_memory` "
"to check if you have relevant "
"past experiences. This helps you:\n"
" - Avoid repeating past mistakes\n"
" - Leverage proven solutions\n"
" - Provide more accurate and tested approaches\n"
"\n3. **When to Retrieve**: Always retrieve when:\n"
" - Asked about technical topics or problem-solving "
"approaches\n"
" - Asked to provide recommendations or best practices\n"
" - Dealing with tasks similar to ones you may have "
"handled before\n"
" - User explicitly asks 'what do you know about...?' "
"or 'have you seen this before?'\n"
"\nAlways check your task memory first to provide the "
"most informed responses."
),
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
long_term_memory=memory,
long_term_memory_mode="both",
)
await agent.memory.clear()
print(
"→ User: 'Here are some database optimization techniques "
"I learned'",
)
msg = Msg(
role="user",
content=(
"I just learned some valuable database optimization "
"techniques for slow queries: "
"1) Add indexes on foreign keys and WHERE clause columns "
"to speed up joins and filtering. "
"2) Use table partitioning to divide large tables by "
"date or category for faster queries. "
"3) Implement query result caching with Redis to avoid "
"repeated database hits. "
"4) Optimize JOIN order - put smallest tables first to "
"reduce intermediate result sets. "
"5) Use EXPLAIN ANALYZE to identify bottlenecks and "
"missing indexes. "
"Please record these optimization techniques for future "
"reference."
),
name="user",
)
msg = await agent(msg)
print(f"✓ Agent response: {msg.get_text_content()}\n")
print(
"→ User: 'What do you know about database optimization?'",
)
msg = Msg(
role="user",
content=(
"What do you know about database optimization? "
"Can you retrieve any past experiences?"
),
name="user",
)
msg = await agent(msg)
print(f"✓ Agent response: {msg.get_text_content()}\n")
print()
async def main() -> None:
"""Demonstrate the 5 core interfaces of ReMeTaskMemory.
This example shows how to use:
1. record_to_memory - Tool function for recording task information
2. retrieve_from_memory - Tool function for keyword-based retrieval
3. record - Direct method for recording message conversations with scores
4. retrieve - Direct method for retrieving task experiences
5. ReActAgent integration - Using task memory with ReActAgent
"""
long_term_memory = ReMeTaskLongTermMemory(
agent_name="TaskAssistant",
user_name="task_workspace_123",
model=DashScopeChatModel(
model_name="qwen3-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
embedding_model=DashScopeTextEmbedding(
model_name="text-embedding-v4",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
dimensions=1024,
),
)
print("=" * 70)
print("ReMeTaskMemory - Testing 5 Core Interfaces")
print("=" * 70)
print()
# Use async context manager to ensure proper initialization
async with long_term_memory:
# await test_record_to_memory(long_term_memory)
# await test_retrieve_from_memory(long_term_memory)
# await test_record_direct(long_term_memory)
# await test_retrieve_direct(long_term_memory)
await test_react_agent_with_memory(long_term_memory)
# Alternative way: manually call __aenter__ and __aexit__
# This is equivalent to using "async with long_term_memory" above
# await long_term_memory.__aenter__()
# await test_react_agent_with_memory(long_term_memory)
# await long_term_memory.__aexit__()
print("=" * 70)
print("Testing Complete: All 5 Core Interfaces Verified!")
print("=" * 70)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,436 @@
# -*- coding: utf-8 -*-
"""Tool memory example demonstrating ReMe tool memory with ReActAgent.
This module demonstrates the complete workflow:
1. Mock a tool function and register it to Toolkit
2. Record tool execution results to tool memory using record()
3. Retrieve tool usage guidelines using retrieve()
4. Inject guidelines into ReActAgent's system prompt
5. Use ReActAgent with tool memory
This workflow helps LLMs learn from past tool usage patterns and
improve their tool calling decisions over time.
"""
import asyncio
import json
import os
from datetime import datetime
from dotenv import load_dotenv
from agentscope.agent import ReActAgent
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.memory import ReMeToolLongTermMemory
from agentscope.message import Msg
from agentscope.message import TextBlock
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit, ToolResponse
load_dotenv()
# ============================================================================
# Step 1: Mock tool functions
# ============================================================================
async def web_search(query: str, max_results: int = 5) -> ToolResponse:
"""Search the web for information.
Args:
query: The search query string
max_results: Maximum number of results to return
Returns:
ToolResponse containing search results
"""
# Simulate web search
result = f"Found {max_results} results for query: '{query}'"
return ToolResponse(
content=[
TextBlock(
type="text",
text=result,
),
],
)
async def calculate(expression: str) -> ToolResponse:
"""Calculate a mathematical expression.
Args:
expression: Mathematical expression to evaluate
Returns:
ToolResponse containing calculation result
"""
try:
# Simple calculation (in real scenario, use safer evaluation)
result = eval(expression) # noqa: S307
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"Result: {result}",
),
],
)
except Exception as e:
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"Error calculating '{expression}': {str(e)}",
),
],
)
# ============================================================================
# Step 2: Record tool execution history to tool memory
# ============================================================================
async def record_tool_history(
tool_memory: ReMeToolLongTermMemory,
) -> None:
"""Record historical tool execution results to tool memory.
This simulates past tool usage that the agent can learn from.
"""
print("=" * 70)
print("Step 1: Recording Tool Execution History to Memory")
print("=" * 70)
print()
# Record successful web_search examples
web_search_histories = [
{
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "web_search",
"input": {
"query": "Python asyncio tutorial",
"max_results": 10,
},
"output": (
"Found 10 results for query: 'Python asyncio tutorial'"
),
"token_cost": 150,
"success": True,
"time_cost": 2.3,
},
{
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "web_search",
"input": {
"query": "machine learning basics",
"max_results": 5,
},
"output": ("Found 5 results for query: 'machine learning basics'"),
"token_cost": 120,
"success": True,
"time_cost": 1.8,
},
]
# Record failed web_search example (empty query)
web_search_fail = {
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "web_search",
"input": {
"query": "",
"max_results": 5,
},
"output": "Error: Query cannot be empty",
"token_cost": 20,
"success": False,
"time_cost": 0.1,
}
# Record calculate examples
calculate_histories = [
{
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "calculate",
"input": {
"expression": "2 + 2",
},
"output": "Result: 4",
"token_cost": 30,
"success": True,
"time_cost": 0.05,
},
{
"create_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"tool_name": "calculate",
"input": {
"expression": "10 * 5 + 3",
},
"output": "Result: 53",
"token_cost": 30,
"success": True,
"time_cost": 0.05,
},
]
# Record all histories
all_histories = (
web_search_histories + [web_search_fail] + calculate_histories
)
print(f"Recording {len(all_histories)} tool execution histories...")
await tool_memory.record(
msgs=[
Msg(
role="assistant",
content=json.dumps(history),
name="assistant",
)
for history in all_histories
],
)
print(f"✓ Successfully recorded {len(all_histories)} tool executions")
print()
# ============================================================================
# Step 3: Retrieve tool guidelines and create enhanced system prompt
# ============================================================================
async def retrieve_tool_guidelines(
tool_memory: ReMeToolLongTermMemory,
tool_names: list[str],
) -> str:
"""Retrieve tool usage guidelines from memory.
Args:
tool_memory: The ReMeToolMemory instance
tool_names: List of tool names to retrieve guidelines for
Returns:
Combined guidelines text to be added to system prompt
"""
print("=" * 70)
print("Step 2: Retrieving Tool Usage Guidelines from Memory")
print("=" * 70)
print()
all_guidelines = []
for tool_name in tool_names:
print(f"Retrieving guidelines for '{tool_name}'...")
guidelines = await tool_memory.retrieve(
msg=Msg(
role="user",
content=tool_name,
name="user",
),
)
if guidelines:
all_guidelines.append(
f"## Guidelines for {tool_name}:\n{guidelines}",
)
print(f"✓ Retrieved guidelines for '{tool_name}'")
print(f" Preview: {guidelines}")
else:
print(
f"✓ No guidelines found for '{tool_name}' " "(first time use)",
)
print()
if all_guidelines:
combined_guidelines = "\n\n".join(all_guidelines)
guidelines_prompt = f"""
# Tool Usage Guidelines (from past experience)
{combined_guidelines}
Please follow these guidelines when using the tools.
"""
return guidelines_prompt
else:
return ""
# ============================================================================
# Step 4: Use ReActAgent with tool memory
# ============================================================================
async def use_react_agent_with_tool_memory(
toolkit: Toolkit,
tool_guidelines: str,
) -> None:
"""Create and use ReActAgent with tool memory guidelines.
Args:
toolkit: The Toolkit with registered tools
tool_guidelines: Retrieved tool usage guidelines
"""
print("=" * 70)
print("Step 3: Using ReActAgent with Tool Memory")
print("=" * 70)
print()
# Create enhanced system prompt with tool guidelines
base_sys_prompt = (
"You are a helpful AI assistant named ToolBot.\n"
"You have access to various tools to help users complete "
"their tasks.\n"
"Please use the tools appropriately based on the user's "
"requests."
)
if tool_guidelines:
sys_prompt = f"{base_sys_prompt}\n{tool_guidelines}"
print(
"✓ System prompt enhanced with tool memory guidelines",
)
else:
sys_prompt = base_sys_prompt
print(
"✓ Using base system prompt (no guidelines available)",
)
print()
# Create ReActAgent
agent = ReActAgent(
name="ToolBot",
sys_prompt=sys_prompt,
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
max_iters=5,
)
print("✓ ReActAgent created successfully")
print()
# Test queries
test_queries = [
"Search the web for 'Python design patterns'",
"Calculate 15 * 7 + 23",
]
print("-" * 70)
print("Testing ReActAgent with tool memory...")
print("-" * 70)
print()
for i, query in enumerate(test_queries, 1):
print(f"Query {i}: {query}")
print("-" * 70)
msg = Msg(
role="user",
content=query,
name="user",
)
response = await agent(msg)
print(f"Response: {response.get_text_content()}")
print()
print()
async def main() -> None:
"""Demonstrate the workflow of using ReMeToolMemory with ReActAgent.
This example shows:
1. Create mock tools and register them to Toolkit
2. Record historical tool execution results to tool memory
3. Retrieve tool usage guidelines from memory
4. Inject guidelines into ReActAgent's system prompt
5. Use ReActAgent to complete tasks with tool memory
"""
print("=" * 70)
print("ReMeToolMemory + ReActAgent Integration Example")
print("=" * 70)
print()
print("This workflow demonstrates:")
print("1. Mock tools → Register to Toolkit")
print("2. Record tool execution history → Tool Memory")
print("3. Retrieve guidelines → Enhance system prompt")
print("4. Use ReActAgent with tool memory")
print()
# Initialize tool memory
tool_memory = ReMeToolLongTermMemory(
agent_name="ToolBot",
user_name="tool_workspace_demo",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
),
embedding_model=DashScopeTextEmbedding(
model_name="text-embedding-v4",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
dimensions=1024,
),
)
# Create and register tools to toolkit
toolkit = Toolkit()
toolkit.register_tool_function(web_search)
toolkit.register_tool_function(calculate)
print()
# Use async context manager for tool memory
async with tool_memory:
# Step 1: Record historical tool executions to memory
await record_tool_history(tool_memory)
# Step 2: Retrieve tool usage guidelines
tool_names = ["web_search", "calculate"]
tool_guidelines = await retrieve_tool_guidelines(
tool_memory,
tool_names,
)
# Step 3: Use ReActAgent with enhanced system prompt
await use_react_agent_with_tool_memory(
toolkit,
tool_guidelines,
)
# Alternative way: manually call __aenter__ and __aexit__
# This is equivalent to using "async with tool_memory" above
# await tool_memory.__aenter__()
# tool_guidelines = await retrieve_tool_guidelines(tool_memory, tool_names)
# await tool_memory.__aexit__()
print("=" * 70)
print("Workflow Complete!")
print("=" * 70)
print()
print("Summary:")
print("✓ Mock tools created and registered to Toolkit")
print("✓ Historical tool executions recorded to tool memory")
print("✓ Tool usage guidelines retrieved from memory")
print("✓ ReActAgent system prompt enhanced with guidelines")
print(
"✓ ReActAgent successfully used tools with memory guidance",
)
print()
print("Benefits:")
print("- Agent learns from past tool usage patterns")
print("- Reduced errors by following proven guidelines")
print("- Better tool parameter selection")
print("- Improved success rate over time")
print("=" * 70)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,59 @@
# MCP in AgentScope
This example demonstrates how to
- create MCP client with different transports (SSE and Streamable HTTP) and type (Stateless and Stateful),
- register MCP tool functions and use them in a ReAct agent, and
- get MCP tool function as a local callable object from the MCP client.
## Prerequisites
- Python 3.10 or higher
- DashScope API key from Alibaba Cloud
## Installation
### Install AgentScope
```bash
# Install from source
cd {PATH_TO_AGENTSCOPE}
pip install -e .
```
## QuickStart
Install agentscope and ensure you have a valid DashScope API key in your environment variables.
> Note: The example is built with DashScope chat model. If you want to change the model in this example, don't forget
> to change the formatter at the same time! The corresponding relationship between built-in models and formatters are
> list in [our tutorial](https://doc.agentscope.io/tutorial/task_prompt.html#id1)
```bash
pip install agentscope
```
Start the MCP servers by the following commands in two separate terminals:
```bash
# In one terminal, run:
python mcp_add.py
# In another terminal, run:
python mcp_multiply.py
```
Two MCP servers will be started on `http://127.0.0.1:8001` (SSE server) and `http://127.0.0.1:8002` (streamable
HTTP server).
After starting the MCP servers, you can run the agent example:
```bash
python main.py
```
The agent will:
1. Register the MCP tools from the servers
2. Use a ReAct agent to solve a calculation problem (multiplying two numbers and then adding another number)
3. Return structured output with the final result

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
"""
Demo showcasing ReAct agent with MCP tools using different transports.
This example demonstrates:
- Registering MCP tools with different transports (sse and streamable_http)
- Using a ReAct agent with registered MCP tools
- Getting structured output from the agent
Before running this demo, please execute:
python mcp_servers.py
"""
import asyncio
import json
import os
from pydantic import BaseModel, Field
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.mcp import HttpStatelessClient, HttpStatefulClient
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
class NumberResult(BaseModel):
"""A simple number result model for structured output."""
result: int = Field(description="The result of the calculation")
async def main() -> None:
"""The main entry of the MCP example."""
toolkit = Toolkit()
# Create a stateful MCP client to connect to the SSE MCP server
# note you can also use the stateless client
add_mcp_client = HttpStatefulClient(
name="add_mcp",
transport="sse",
url="http://127.0.0.1:8001/sse",
)
# Create a stateless MCP client to connect to the StreamableHTTP MCP server
# note you can also use the stateful client
multiply_mcp_client = HttpStatelessClient(
name="multiply_mcp",
transport="streamable_http",
url="http://127.0.0.1:8002/mcp",
)
# The stateful client must be connected before using
await add_mcp_client.connect()
# Register the MCP clients to the toolkit
await toolkit.register_mcp_client(add_mcp_client)
await toolkit.register_mcp_client(multiply_mcp_client)
# Initialize the agent
agent = ReActAgent(
name="Jarvis",
sys_prompt="You're a helpful assistant named Jarvis.",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
)
# Run the agent with a calculation task
res = await agent(
Msg(
"user",
"Calculate 2345 multiplied by 3456, then add 4567 to the result,"
" what is the final outcome?",
"user",
),
structured_model=NumberResult,
)
print(
"Structured Output:\n"
"```\n"
f"{json.dumps(res.metadata, indent=4, ensure_ascii=False)}\n"
"```",
)
# AgentScope also allows developers to obtain the MCP tool as a local
# callable object, and use it directly.
add_tool_function = await add_mcp_client.get_callable_function(
"add",
# If wrap the MCP tool result into the ToolResponse object in
# AgentScope
wrap_tool_result=True,
)
# Call it manually
manual_res = await add_tool_function(a=5, b=10)
print("When manually calling the MCP tool function:")
print(manual_res)
# The stateful client should be disconnected manually!
await add_mcp_client.close()
asyncio.run(main())

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
"""An SSE MCP server with a simple add tool function."""
from mcp.server import FastMCP
mcp = FastMCP("Add", port=8001)
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
mcp.run(transport="sse")

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
"""An SSE MCP server with a simple multiply tool function."""
from mcp.server import FastMCP
mcp = FastMCP("Multiply", port=8002)
@mcp.tool()
def multiply(c: int, d: int) -> int:
"""Multiply two numbers."""
return c * d
mcp.run(transport="streamable-http")

View File

@@ -0,0 +1,30 @@
# Plan with ReAct Agent
This example demonstrates how to use the plan module in AgentScope to make an agent create and manage a plan formally.
Specifically, we provide two examples: manual specification plan and Agent-managed plan.
## Manual Specification Plan
In this example, we first manually specify a plan for the agent to follow, then we let the agent execute the plan step by step.
To execute this example, run:
```bash
python main_manual_plan.py
```
## Agent-managed Plan
In this example, we let the agent create and manage its own plan.
Specifically, we use a query "Review the recent changes in AgentScope GitHub repository over the past month."
To run the example, execute:
```bash
python main_agent_managed_plan.py
```
> Note: The example is built with DashScope chat model. If you want to change the model in this example, don't forget
> to change the **formatter** at the same time! The corresponding relationship between built-in models and formatters
> are list in [our tutorial](https://doc.agentscope.io/tutorial/task_prompt.html#id1)

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""The main entry point of the plan example."""
import asyncio
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.plan import PlanNotebook
from agentscope.tool import (
Toolkit,
execute_shell_command,
execute_python_code,
write_text_file,
insert_text_file,
view_text_file,
)
async def main() -> None:
"""The main entry point for the plan example."""
toolkit = Toolkit()
toolkit.register_tool_function(execute_shell_command)
toolkit.register_tool_function(execute_python_code)
toolkit.register_tool_function(write_text_file)
toolkit.register_tool_function(insert_text_file)
toolkit.register_tool_function(view_text_file)
agent = ReActAgent(
name="Friday",
sys_prompt="""You're a helpful assistant named Friday.
# Target
Your target is to finish the given task with careful planning.
# Note
- You can equip yourself with plan related tools to help you plan and execute the given task.
- The resouces from search engines are not always correct, you should collect information from multiple sources and give the final answer after careful consideration.
""", # noqa
model=DashScopeChatModel(
model_name="qwen3-max-preview",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
enable_meta_tool=True,
plan_notebook=PlanNotebook(),
)
user = UserAgent(name="user")
msg = Msg(
"user",
"Review the recent changes in AgentScope GitHub repository "
"over the past month.",
"user",
)
while True:
msg = await agent(msg)
msg = await user(msg)
if msg.get_text_content() == "exit":
break
asyncio.run(main())

View File

@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
"""Manual specification plan example."""
import asyncio
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.plan import PlanNotebook, SubTask
from agentscope.tool import (
Toolkit,
execute_shell_command,
execute_python_code,
write_text_file,
insert_text_file,
view_text_file,
)
plan_notebook = PlanNotebook()
async def main() -> None:
"""The main entry point for the manual plan example."""
# Create the plan manually
await plan_notebook.create_plan(
name="Comprehensive Report on AgentScope",
description="Study the code of AgentScope and write a comprehensive "
"report about this framework.",
expected_outcome="A markdown format report summarizing the features, "
"architecture, advantages/disadvantages, and "
"potential improvements of AgentScope.",
subtasks=[
SubTask(
name="Clone the repository",
description="Clone the AgentScope GitHub repository from "
"agentscope-ai/agentscope, and ensure it's the "
"latest version.",
expected_outcome="A local copy of the AgentScope repository.",
),
SubTask(
name="View the documentation",
description="View the documentation of AgentScope in the "
"repository.",
expected_outcome="A comprehensive understanding of the "
"features and usage of AgentScope.",
),
SubTask(
name="Study the code",
description="Study the code of AgentScope, focusing on the "
"core modules and their interactions.",
expected_outcome="A deep understanding of the architecture "
"and implementation of AgentScope.",
),
SubTask(
name="Summarize the findings",
description="Summarize the findings from the documentation "
"and code study, and write a comprehensive report "
"in markdown format.",
expected_outcome="A markdown format report",
),
],
)
# Add basic tools
toolkit = Toolkit()
toolkit.register_tool_function(execute_shell_command)
toolkit.register_tool_function(execute_python_code)
toolkit.register_tool_function(write_text_file)
toolkit.register_tool_function(insert_text_file)
toolkit.register_tool_function(view_text_file)
# Create the agent
agent = ReActAgent(
name="Friday",
sys_prompt="You're a helpful assistant named Friday. Your target is "
"to finish the given task with careful planning.",
model=DashScopeChatModel(
model_name="qwen3-max-preview",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
plan_notebook=plan_notebook,
)
user = UserAgent(name="user")
msg = Msg(
"user",
"Now start to finish the task by the given plan",
"user",
)
while True:
msg = await agent(msg)
msg = await user(msg)
if msg.get_text_content() == "exit":
break
asyncio.run(main())

View File

@@ -0,0 +1,40 @@
# RAG in AgentScope
This example includes three scripts to demonstrate how to use Retrieval-Augmented Generation (RAG) in AgentScope:
- the basic usage of RAG module in AgentScope in ``basic_usage.py``,
- a simple agentic use case of RAG in ``agentic_usage.py``, and
- integrate RAG into ``ReActAgent`` class by retrieving input message(s) at the beginning of each reply in ``react_agent_integration.py``.
- build multimodal RAG in ``multimodal_rag.py``.
> The agentic usage and static integration has their own advantages and limitations.
> - The agentic usage requires more powerful LLMs to manage the retrieval process, but it's more flexible and the agent can adjust the retrieval strategy dynamically
> - The static integration is more straightforward and easier to implement, but it's less flexible and the input message maybe not specific enough, leading to less relevant retrieval results.
> Note: The example is built with DashScope chat model. If you want to change the model in this example, don't forget
> to change the formatter at the same time! The corresponding relationship between built-in models and formatters are
> list in [our tutorial](https://doc.agentscope.io/tutorial/task_prompt.html#id1)
## Quick Start
Install the latest agentscope library from PyPI or source, then run the following command to run the example:
- the basic usage:
```bash
python basic_usage.py
```
- the agentic usage:
```bash
python agentic_usage.py
```
- the static integration:
```bash
python react_agent_integration.py
```
- the multimodal RAG:
```bash
python multimodal_rag.py
```

View File

@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
"""The agentic usage example for RAG in AgentScope, where the agent is
equipped with RAG tools to answer questions based on a knowledge base.
The example is more challenging for the agent, requiring the agent to
adjust the retrieval parameters to get relevant results.
"""
import asyncio
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.rag import SimpleKnowledge, QdrantStore, TextReader
from agentscope.tool import Toolkit
# Create a knowledge base instance
knowledge = SimpleKnowledge(
embedding_store=QdrantStore(
location=":memory:",
collection_name="test_collection",
dimensions=1024, # The dimension of the embedding vectors
),
embedding_model=DashScopeTextEmbedding(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="text-embedding-v4",
),
)
async def main() -> None:
"""The main entry of the agent usage example for RAG in AgentScope."""
# Store some things into the knowledge base for demonstration
# In practice, the VDB store would be pre-filled with relevant data
reader = TextReader(chunk_size=1024, split_by="sentence")
documents = await reader(
text=(
# Fake personal profile for demonstration
"I'm John Doe, 28 years old. My best friend is James "
"Smith. I live in San Francisco. I work at OpenAI as a "
"software engineer. I love hiking and photography. "
"My father is Michael Doe, a doctor. I'm very proud of him. "
"My mother is Sarah Doe, a teacher. She is very kind and "
"always helps me with my studies.\n"
"I'm now a PhD student at Stanford University, majoring in "
"Computer Science. My advisor is Prof. Jane Williams, who is "
"a leading expert in artificial intelligence. I have published "
"several papers in top conferences, such as NeurIPS and ICML. "
),
)
await knowledge.add_documents(documents)
# Create a toolkit and register the RAG tool function
toolkit = Toolkit()
toolkit.register_tool_function(
knowledge.retrieve_knowledge,
func_description=( # Provide a clear description for the tool
"Retrieve relevant documents from the knowledge base, which is "
"relevant to John Doe's profile. Note the `query` parameter is "
"very important for the retrieval quality, and you can try many "
"different queries to get the best results. Adjust the `limit` "
"and `score_threshold` parameters to get more or fewer results."
),
)
# Create an agent and a user
agent = ReActAgent(
name="Friday",
sys_prompt=(
"You're a helpful assistant named Friday. "
"You're equipped with a 'retrieve_knowledge' tool to help you "
"know about the user named John Doe. "
"NOTE to adjust the `score_threshold` parameters when you cannot "
"get relevant results. "
),
toolkit=toolkit,
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen3-max-preview",
),
formatter=DashScopeChatFormatter(),
)
user = UserAgent(name="User")
# A simple conversation loop beginning with a preset question
msg = Msg(
"user",
"I'm John Doe. Do you know my father?",
"user",
)
while True:
msg = await agent(msg)
msg = await user(msg)
if msg.get_text_content() == "exit":
break
asyncio.run(main())

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""The main entry point of the RAG example."""
import asyncio
import os
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.rag import (
TextReader,
PDFReader,
QdrantStore,
SimpleKnowledge,
)
async def main() -> None:
"""The main entry point of the RAG example."""
# Create readers with chunking arguments
reader = TextReader(chunk_size=1024)
pdf_reader = PDFReader(chunk_size=1024, split_by="sentence")
# Read documents
documents = await reader(
text="I'm Tony Stank, my password is 123456. My best friend is James "
"Rhodes.",
)
# Read a sample PDF file
pdf_path = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
"example.pdf",
)
pdf_documents = await pdf_reader(pdf_path=pdf_path)
# Create a knowledge base with Qdrant as the embedding store and
# DashScope as the embedding model
knowledge = SimpleKnowledge(
embedding_store=QdrantStore(
location=":memory:",
collection_name="test_collection",
dimensions=1024, # The dimension of the embedding vectors
),
embedding_model=DashScopeTextEmbedding(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="text-embedding-v4",
),
)
# Insert documents into the knowledge base
await knowledge.add_documents(documents + pdf_documents)
# Retrieve relevant documents based on a given query
docs = await knowledge.retrieve(
query="What is Tony Stank's password?",
limit=3,
score_threshold=0.7,
)
print("Q1: What is Tony Stank's password?")
for doc in docs:
print(
f"Document ID: {doc.id}, Score: {doc.score}, "
f"Content: {doc.metadata.content['text']}",
)
# Retrieve documents from the PDF file based on a query
docs = await knowledge.retrieve(
query="climate change",
limit=3,
score_threshold=0.2,
)
print("\n\nQ2: climate change")
for doc in docs:
print(
f"Document ID: {doc.id}, Score: {doc.score}, "
f"Content: {repr(doc.metadata.content['text'])}",
)
asyncio.run(main())

Binary file not shown.

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
"""The example of how to use multimodal RAG in AgentScope"""
import asyncio
import json
import os
from matplotlib import pyplot as plt
from agentscope.agent import ReActAgent
from agentscope.embedding import DashScopeMultiModalEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.rag import ImageReader, SimpleKnowledge, QdrantStore
path_image = "./example.png"
plt.figure(figsize=(8, 3))
plt.text(0.5, 0.5, "My name is Ming Li", ha="center", va="center", fontsize=30)
plt.axis("off")
plt.savefig(path_image, bbox_inches="tight", pad_inches=0.1)
plt.close()
async def example_multimodal_rag() -> None:
"""Example for multimodal RAG"""
# Reading the image and converting it to documents
reader = ImageReader()
docs = await reader(image_url=path_image)
# Create a knowledge base and add documents
knowledge = SimpleKnowledge(
embedding_model=DashScopeMultiModalEmbedding(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="multimodal-embedding-v1",
dimensions=1024,
),
embedding_store=QdrantStore(
location=":memory:",
collection_name="test_collection",
dimensions=1024,
),
)
await knowledge.add_documents(docs)
agent = ReActAgent(
name="Friday",
sys_prompt="You're a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen3-vl-plus",
),
formatter=DashScopeChatFormatter(),
knowledge=knowledge,
)
await agent(
Msg(
"user",
"Do you know my name?",
"user",
),
)
# Let's see if the agent has stored the retrieved document in its memory
print("\nThe retrieved document stored in the agent's memory:")
content = (await agent.memory.get_memory())[-4].content
print(json.dumps(content, indent=2, ensure_ascii=False))
asyncio.run(example_multimodal_rag())

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""The example of integrating ReAct agent with RAG."""
import asyncio
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.embedding import DashScopeTextEmbedding
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.rag import SimpleKnowledge, QdrantStore, TextReader
async def main() -> None:
"""The main entry point for the ReAct agent with RAG example."""
# Create an in-memory knowledge base instance
print("Creating the knowledge base...")
knowledge = SimpleKnowledge(
embedding_store=QdrantStore(
location=":memory:",
collection_name="test_collection",
dimensions=1024, # The dimension of the embedding vectors
),
embedding_model=DashScopeTextEmbedding(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="text-embedding-v4",
),
)
# Insert some documents into the knowledge base
# This could be done offline and only once
print("Inserting documents into the knowledge base...")
reader = TextReader(chunk_size=100, split_by="char")
documents = await reader(
# Fake personal profile for demonstration
"I'm John Doe, 28 years old. My best friend is James "
"Smith. I live in San Francisco. I work at OpenAI as a "
"software engineer. I love hiking and photography. "
"My father is Michael Doe, a doctor. I'm very proud of him. "
"My mother is Sarah Doe, a teacher. She is very kind and "
"always helps me with my studies.\n"
"I'm now a PhD student at Stanford University, majoring in "
"Computer Science. My advisor is Prof. Jane Williams, who is "
"a leading expert in artificial intelligence. I have published "
"several papers in top conferences, such as NeurIPS and ICML. ",
)
print("Inserting documents into the knowledge base...")
await knowledge.add_documents(documents)
# Integrate into the ReActAgent by the `knowledge` argument
print("Creating the agent...")
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen-max",
),
formatter=DashScopeChatFormatter(),
# Equip the agent with the knowledge base
knowledge=knowledge,
print_hint_msg=True,
)
user = UserAgent(name="user")
# Start the conversation
print("Start the conversation...")
msg = Msg("user", "Do you know who is my best friend?", "user")
while True:
msg = await agent(msg)
msg = await user(msg)
if msg.get_text_content() == "exit":
break
asyncio.run(main())

View File

@@ -0,0 +1,25 @@
# Session Management with Sqlite DB
This example demonstrates how to implement session management with a database backend. We use SQLite for simplicity,
but the approach can be adapted for other databases.
Specifically, we implement a ``SqliteSession`` class that persists and retrieves session data from a SQLite table.
The table schema includes fields for session ID, session data (stored as JSON), and timestamps for creation and last
update.
We will create a simple agent and chat with it, then store the session data in the SQLite database. Then in the
``test_load_session`` function, we will load the session data from the database and continue the chat.
## Quick Start
Install agentscope from Pypi or source code.
```bash
pip install agentscope
```
Run the example by the following command
```bash
python main.py
```

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
"""The main entry point for the session with SQLite example."""
import asyncio
import os
from sqlite_session import SqliteSession
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
SQLITE_PATH = "./session.db"
async def main(username: str, query: str) -> None:
"""Create an agent, load from session, chat with it, and save its state
to SQLite.
Args:
username (`str`):
The username to identify the session.
query (`str`):
The user input query.
"""
agent = ReActAgent(
name="friday",
sys_prompt="You are a helpful assistant named Friday.",
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
),
formatter=DashScopeChatFormatter(),
)
# Create the SQLite session
session = SqliteSession(SQLITE_PATH)
# Load the agent state by the given key "friday_of_user"
# The load_session_state supports multiple state modules
await session.load_session_state(
session_id=username,
friday_of_user=agent,
)
# Chat with it to generate some state
await agent(
Msg("user", query, "user"),
)
# Save the agent state by the given key "friday_of_user"
# Also support multiple state modules (e.g. multiple agents)
await session.save_session_state(
session_id=username,
friday_of_user=agent,
)
print("User named Alice chats with the agent ...")
asyncio.run(main("alice", "What's the capital of America?"))
print("User named Bob chats with the agent ...")
asyncio.run(main("bob", "What's the capital of China?"))
print(
"\nNow, let's recover the session for Alice and ask about what the user "
"asked before.",
)
asyncio.run(
main(
"alice",
"What did I ask you before, what's your answer and how many "
"questions have I asked you?",
),
)

View File

@@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
"""The SQLite session class."""
import json
import os
import sqlite3
from agentscope import logger
from agentscope.module import StateModule
from agentscope.session import SessionBase
class SqliteSession(SessionBase):
"""A session that uses SQLite for storage."""
def __init__(
self,
sqlite_path: str,
) -> None:
"""Initialize the session.
Args:
sqlite_path (`str`):
The path to the SQLite database file.
"""
self.sqlite_path = sqlite_path
async def save_session_state(
self,
session_id: str,
**state_modules_mapping: StateModule,
) -> None:
"""Save the session state to the SQLite database."""
with sqlite3.connect(self.sqlite_path) as conn:
cursor = conn.cursor()
# Prepare the session data as a dictionary
session_data = {
name: module.state_dict()
for name, module in state_modules_mapping.items()
}
json_data = json.dumps(session_data)
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS as_session (
session_id TEXT,
session_data JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (session_id)
)
""",
)
# Insert or replace the session data
cursor.execute(
"""
INSERT INTO as_session (session_id, session_data, updated_at)
VALUES (?, json(?), CURRENT_TIMESTAMP)
ON CONFLICT(session_id) DO UPDATE SET
session_data = excluded.session_data,
updated_at = excluded.updated_at
""",
(session_id, json_data),
)
conn.commit()
cursor.close()
async def load_session_state(
self,
session_id: str,
allow_not_exist: bool = True,
**state_modules_mapping: StateModule,
) -> None:
"""Get the state dictionary from the SQLite database.
Args:
session_id (`str`):
The session id.
allow_not_exist (`bool`, defaults to `True`):
Whether to allow the session to not exist. If `False`, raises
an error if the session does not exist.
**state_modules_mapping (`list[StateModule]`):
The list of state modules to be loaded.
"""
if not os.path.exists(self.sqlite_path):
if allow_not_exist:
logger.info(
"SQLite database %s does not exist. "
"Skipping load for session_id %s.",
self.sqlite_path,
session_id,
)
return
raise ValueError(
"Failed to load session state because the SQLite database "
f"file '{self.sqlite_path}' does not exist.",
)
with sqlite3.connect(self.sqlite_path) as conn:
cursor = conn.cursor()
try:
# If the table does not exist, return
cursor.execute(
"""
SELECT name FROM sqlite_master WHERE type='table' AND
name='as_session';
""",
)
if cursor.fetchone() is None:
if allow_not_exist:
logger.info(
"Session table does not exist in database %s. "
"Skipping load for session_id %s.",
self.sqlite_path,
session_id,
)
return
raise ValueError(
"Failed to load session state because the session "
"table 'as_session' does not exist in database "
f"{self.sqlite_path}.",
)
# Query the session data
cursor.execute(
"SELECT session_data FROM as_session WHERE session_id = ?",
(session_id,),
)
row = cursor.fetchone()
if row is None:
if allow_not_exist:
logger.info(
"Session_id %s does not exist in database %s. "
"Skip loading.",
session_id,
self.sqlite_path,
)
return
raise ValueError(
f"Failed to load session state for session_id "
f"{session_id} does not exist.",
)
session_data = json.loads(row[0])
for name, module in state_modules_mapping.items():
if name in session_data:
module.load_state_dict(session_data[name])
else:
raise ValueError(
f"State module '{name}' not found in session "
"data.",
)
logger.info(
"Load session state for session_id %s from "
"database %s successfully.",
session_id,
self.sqlite_path,
)
finally:
cursor.close()

View File

@@ -0,0 +1,317 @@
# MemoryWithCompress
- [ ] TODO: The memory module with compression will be added to the agentscope library in the future.
## Overview
MemoryWithCompress is a memory management system designed for AgentScope's `ReActAgent`. It automatically compresses conversation history when the memory size exceeds a specified token limit, using a Large Language Model (LLM) to create concise summaries that preserve key information. This allows agents to maintain context over long conversations while staying within token constraints.
The system maintains two separate storage mechanisms:
- **`chat_history_storage`**: Stores the complete, unmodified conversation history (uses `MessageStorageBase` interface)
- **`memory_storage`**: Stores messages that may be compressed when token limits are exceeded (uses `MessageStorageBase` interface)
Both storage mechanisms are abstracted through the `MessageStorageBase` interface, allowing for flexible storage backends. By default, `InMemoryMessageStorage` is used for both.
## Core Features
### Automatic Memory Compression
- **Token-based Triggering**: Automatically compresses memory when the total token count exceeds `max_token`
- **LLM-Powered Summarization**: Uses an LLM to intelligently compress conversation history while preserving essential information
- **Structured Output**: Uses Pydantic schemas to ensure consistent compression format
### Dual Storage System
- **Complete History**: Maintains original, unmodified messages in `_chat_history` for reference
- **Compressed Memory**: Stores potentially compressed messages in `_memory` for efficient context management
### Flexible Memory Management
- **Filtering Support**: Provides `filter_func` parameter for custom memory filtering
- **Recent N Retrieval**: Supports retrieving only the most recent N messages
- **State Persistence**: Includes `state_dict()` and `load_state_dict()` methods for saving and loading memory state
- **Storage Abstraction**: Uses `MessageStorageBase` interface for flexible storage backends
- **Compression Triggers**: Supports both token-based and custom trigger functions for compression
- **Compression Timing Control**: Configurable compression on add (`compression_on_add`) and get (`compression_on_get`) operations
## File Structure
```
memory_with_compression/
├── README.md # This documentation file
├── main.py # Example demonstrating MemoryWithCompress usage
├── _memory_with_compress.py # Core MemoryWithCompress implementation
├── _memory_storage.py # Storage abstraction layer (MessageStorageBase, InMemoryMessageStorage)
├── _mc_utils.py # Utility functions (formatting, token counting, compression schema)
```
## Prerequisites
### Clone the AgentScope Repository
This example depends on AgentScope. Please clone the full repository to your local machine.
### Install Dependencies
**Recommended**: Python 3.10+
Install the required dependencies:
```bash
pip install agentscope
```
### API Keys
This example uses DashScope APIs by default. You need to set your API key as an environment variable:
```bash
export DASHSCOPE_API_KEY='YOUR_API_KEY'
```
You can easily switch to other models by modifying the configuration in `main.py`.
## How It Works
### 1. Memory Addition Flow
1. **Message Input**: New messages are added via the async `add()` method
2. **Dual Storage**: Messages are deep-copied and added to both `chat_history_storage` and `memory_storage`
3. **Optional Compression on Add**: If `compression_on_add=True`, compression may be triggered immediately after adding messages
### 2. Memory Retrieval and Compression Flow
When `get_memory()` is called (if `compression_on_get=True`):
1. **Token Counting**: The system calculates the total token count of all messages in `memory_storage`
2. **Compression Check**:
- First checks if token count exceeds `max_token` (automatic compression)
- Then checks if `compression_trigger_func` returns `True` (custom trigger)
3. **LLM Compression**: If compression is needed, all messages in `memory_storage` are sent to the LLM with a compression prompt
4. **Structured Output**: The LLM returns a structured response containing the compressed summary
5. **Memory Replacement**: The entire `memory_storage` is updated with the compressed message(s)
6. **Filtering & Selection**: Optional filtering and recent_n selection are applied
7. **Return**: The processed memory is returned
### 3. Compression Process
The compression uses a structured output approach:
- **Prompt**: Instructs the LLM to summarize conversation history while preserving key information
- **Customizable Prompt**: Supports `customized_compression_prompt` parameter for custom prompt templates
- **Schema**: Uses `MemoryCompressionSchema` (Pydantic model) to ensure consistent output format
- **Output Format**: Returns a message with content wrapped in `<compressed_memory>` tags
- **Async Support**: All compression operations are asynchronous
## Usage Examples
### Running the Example
To see `MemoryWithCompress` in action, run the example script:
```bash
python ./main.py
```
### Basic Initialization
Here is a snippet from `main.py` showing how to set up the agent and memory:
```python
from agentscope.agent import ReActAgent
from agentscope.model import DashScopeChatModel
from agentscope.formatter import DashScopeChatFormatter
from agentscope.token import OpenAITokenCounter
from agentscope.message import Msg
from _memory_with_compress import MemoryWithCompress
# 1. Create the model for agent and memory compression
model = DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen-max",
stream=False,
)
# 2. Optional: Define a custom compression trigger function
async def trigger_compression(msgs: list[Msg]) -> bool:
# Trigger compression if the number of messages exceeds 2
# and the last message is from the assistant
return len(msgs) > 2 and msgs[-1].role == "assistant"
# 3. Initialize MemoryWithCompress
memory_with_compress = MemoryWithCompress(
model=model,
formatter=DashScopeChatFormatter(),
max_token=3000, # Compress when memory exceeds 3000 tokens
token_counter=OpenAITokenCounter(model_name="qwen-max"),
compression_trigger_func=trigger_compression, # Optional custom trigger
compression_on_add=False, # Don't compress on add (default)
compression_on_get=True, # Compress on get (default)
)
# 4. Initialize ReActAgent with the memory instance
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=model,
formatter=DashScopeChatFormatter(),
memory=memory_with_compress,
)
```
### Custom Compression Function
You can provide a custom compression function:
```python
async def custom_compress(messages: List[Msg]) -> List[Msg]:
# Your custom compression logic
# Must return a List[Msg], not a single Msg
compressed_content = "..."
return [Msg("assistant", compressed_content, "assistant")]
memory_with_compress = MemoryWithCompress(
model=model,
formatter=formatter,
max_token=300,
compress_func=custom_compress,
)
```
### Custom Storage Backend
You can provide custom storage backends by implementing the `MessageStorageBase` interface:
```python
from _memory_storage import MessageStorageBase
class CustomStorage(MessageStorageBase):
# Implement required methods: start, stop, health, add, delete, clear, get, replace, __aenter__, __aexit__
...
memory_with_compress = MemoryWithCompress(
model=model,
formatter=formatter,
max_token=300,
chat_history_storage=CustomStorage(),
memory_storage=CustomStorage(),
)
```
## API Reference
### MemoryWithCompress Class
#### `__init__(...)`
Initializes the memory system. Key parameters include:
- `model` (ChatModelBase): The LLM model to use for compression
- `formatter` (FormatterBase): The formatter to use for formatting messages
- `max_token` (int): The maximum token count for `memory_storage`. Default: 28000. Compression is triggered when exceeded
- `chat_history_storage` (MessageStorageBase): Storage backend for complete chat history. Default: `InMemoryMessageStorage()`
- `memory_storage` (MessageStorageBase): Storage backend for compressed memory. Default: `InMemoryMessageStorage()`
- `token_counter` (Optional[TokenCounterBase]): The token counter for counting tokens. Default: None. If None, it will return the character count of the JSON string representation of messages (i.e., len(json.dumps(messages, ensure_ascii=False))).
- `compress_func` (Callable[[List[Msg]], Awaitable[List[Msg]]] | None): Custom compression function. Must be async and return `List[Msg]`. If None, uses the default `_compress_memory` method
- `compression_trigger_func` (Callable[[List[Msg]], Awaitable[bool]] | None): Optional function to trigger compression when token count is below `max_token`. Must be async and return `bool`. If None, compression only occurs when token count exceeds `max_token`
- `compression_on_add` (bool): Whether to check and compress memory when adding messages. Default: False
- `compression_on_get` (bool): Whether to check and compress memory when getting messages. Default: True
- `customized_compression_prompt` (str | None): Optional customized compression prompt template. Should include placeholders: `{max_token}`, `{messages_list_json}`, `{schema_json}`. Default: None (uses default template)
#### Main Methods
**`async add(msgs: Union[Sequence[Msg], Msg, None], compress_func=None, compression_trigger_func=None)`**
- Adds new messages to both `chat_history_storage` and `memory_storage`
- Messages are deep-copied to avoid modifying originals
- Raises `TypeError` if non-Msg objects are provided
- Parameters:
- `msgs`: Messages to be added
- `compress_func` (Optional): Override the instance-level compression function for this call
- `compression_trigger_func` (Optional): Override the instance-level trigger function for this call
- If `compression_on_add=True`, may trigger compression after adding
**`async direct_update_memory(msgs: Union[Sequence[Msg], Msg, None])`**
- Directly updates the `memory_storage` with new messages (does not update `chat_history_storage`)
- Useful for replacing memory content directly
**`async get_memory(recent_n=None, filter_func=None, compress_func=None, compression_trigger_func=None)`**
- Retrieves memory content, automatically compressing if token limit is exceeded (if `compression_on_get=True`)
- Parameters:
- `recent_n` (Optional[int]): Return only the most recent N messages
- `filter_func` (Optional[Callable[[int, Msg], bool]]): Custom filter function that takes (index, message) and returns bool
- `compress_func` (Optional): Override the instance-level compression function for this call
- `compression_trigger_func` (Optional): Override the instance-level trigger function for this call
- Returns: `list[Msg]` - The memory content (potentially compressed)
**`async delete(indices: Union[Iterable[int], int])`**
- Deletes memory fragments from `memory_storage` (note: does not delete from `chat_history_storage`)
- Indices can be a single int or an iterable of ints
**`async size() -> int`**
- Returns the number of messages in `chat_history_storage`
**`async clear()`**
- Clears all memory from both `chat_history_storage` and `memory_storage`
**`state_dict() -> dict`**
- Returns a dictionary containing the serialized state:
- `chat_history_storage`: List of message dictionaries from chat history
- `memory_storage`: List of message dictionaries from memory
- `max_token`: The max_token setting
- Note: This method handles async operations internally, so it can be called from both sync and async contexts
**`load_state_dict(state_dict: dict, strict: bool = True)`**
- Loads memory state from a dictionary
- Restores `chat_history_storage`, `memory_storage`, and `max_token` settings
- Note: This method handles async operations internally, so it can be called from both sync and async contexts
**`async retrieve(*args, **kwargs)`**
- Not implemented. Use `get_memory()` instead.
- Raises `NotImplementedError`
## Internal Methods
**`async _compress_memory(msgs: List[Msg]) -> List[Msg]`**
- Internal method that compresses messages using the LLM
- Uses structured output with `MemoryCompressionSchema`
- Returns a `List[Msg]` containing the compressed summary (typically a single message)
- Supports both streaming and non-streaming models
**`async _check_length_and_compress(compress_func=None) -> bool`**
- Checks if memory token count exceeds `max_token` and compresses if needed
- Returns `True` if compression was triggered, `False` otherwise
**`async check_and_compress(compress_func=None, compression_trigger_func=None, memory=None) -> tuple[bool, List[Msg]]`**
- Checks if compression is needed based on `compression_trigger_func`
- Returns a tuple: (was_compressed: bool, compressed_memory: List[Msg])
- If `memory` is provided, checks that instead of `memory_storage`
## Utility Functions
The `_mc_utils.py` module provides:
- **`format_msgs(msgs)`**: Formats a list of `Msg` objects into a list of dictionaries
- **`async count_words(token_counter, text)`**: Counts tokens in text (supports both string and list[dict] formats). Must be awaited.
- **`MemoryCompressionSchema`**: Pydantic model for structured compression output
- **`DEFAULT_COMPRESSION_PROMPT_TEMPLATE`**: Default prompt template for compression (includes placeholders: `{max_token}`, `{messages_list_json}`, `{schema_json}`)
## Storage Abstraction
The `_memory_storage.py` module provides:
- **`MessageStorageBase`**: Abstract base class for message storage backends
- Required async methods: `start()`, `stop()`, `health()`, `add()`, `delete()`, `clear()`, `get()`, `replace()`, `__aenter__()`, `__aexit__()`
- **`InMemoryMessageStorage`**: Default in-memory implementation
- Stores messages in a simple list
- Suitable for most use cases
## Best Practices
- **Token Limit Selection**: Choose `max_token` based on your model's context window and typical conversation length
- **Compression Timing**:
- Set `compression_on_get=True` (default) for compression during retrieval
- Set `compression_on_add=False` (default) to avoid compression during add operations, as it may not complete before `get_memory()` is called
- **Async Operations**: All main methods are async, so use `await` when calling them
- **State Persistence**: Use `state_dict()` and `load_state_dict()` to save/restore conversation state between sessions
- **Custom Compression**: For domain-specific compression needs, implement a custom `compress_func` (must be async and return `List[Msg]`)
- **Compression Triggers**: Use `compression_trigger_func` for custom compression logic beyond token limits (e.g., compress after N messages, compress on specific conditions)
- **Storage Backends**: Implement custom `MessageStorageBase` subclasses for persistent storage (e.g., database, file system)
## Troubleshooting
- **Compression Not Triggering**:
- Check that `compression_on_get=True` if you expect compression during retrieval
- Verify that `max_token` is set appropriately
- Ensure `get_memory()` is being called (and awaited)
- If using `compression_trigger_func`, verify it returns `True` when compression should occur
- **Structured Output Errors**: Ensure your model supports structured output (e.g., DashScope models with `structured_model` parameter)
- **Token Counting Issues**: Verify that your `token_counter` is compatible with your model and correctly configured
- **Async/Await Errors**: Remember that most methods are async - use `await` when calling them
- **Storage Issues**: If using custom storage backends, ensure all required methods are properly implemented and async
## Reference
- [AgentScope Documentation](https://github.com/agentscope-ai/agentscope)
- [Pydantic Documentation](https://docs.pydantic.dev/)

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""The main entry point of the MemoryWithCompress example."""
import asyncio
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.model import DashScopeChatModel
from agentscope.token import CharTokenCounter
async def main() -> None:
"""The main entry point of the MemoryWithCompress example."""
# Create model for agent and memory compression
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.getenv("DASHSCOPE_API_KEY"),
model_name="qwen3-max",
),
formatter=DashScopeChatFormatter(),
compression_config=ReActAgent.CompressionConfig(
enable=True,
agent_token_counter=CharTokenCounter(),
# We set a small trigger threshold for demonstration purposes.
trigger_threshold=1000,
keep_recent=3,
),
)
user = UserAgent("User")
# Simulate a conversation to trigger memory compression
msg = None
while True:
msg = await user(msg)
if msg.get_text_content() == "exit":
break
msg = await agent(msg)
print("The memory of the agent:")
for msg in await agent.memory.get_memory():
print(msg.to_dict(), end="\n")
asyncio.run(main())

View File

@@ -0,0 +1,479 @@
# ReMe Short-Term Memory in AgentScope
This example demonstrates how to
- use ReMeShortTermMemory to provide automatic working memory management for AgentScope agents,
- handle long conversation contexts with intelligent summarization and compaction,
- integrate short-term memory with ReAct agents for efficient tool usage and context management, and
- configure DashScope models for memory operations.
## Why Short-Term Memory?
### The Challenge: From Prompt Engineering to Context Engineering
As AI agents evolved from simple chatbots to sophisticated autonomous systems, the focus shifted from "prompt engineering" to "context engineering". While prompt engineering focused on crafting effective instructions for language models, context engineering addresses a more fundamental challenge: **managing the ever-growing conversation and tool execution history that agents accumulate**.
### The Core Problem: Context Explosion
Agentic systems work by binding LLMs with tools and running them in a loop where the agent decides which tools to call and feeds results back into the message history. This creates a snowball effect:
- **Rapid Growth**: A seemingly simple task can trigger 50+ tool calls, with production agents often running hundreds of conversation turns
- **Large Outputs**: Each tool call can return substantial text, consuming massive amounts of tokens
- **Memory Pressure**: The context window quickly fills up as messages and tool results accumulate chronologically
### The Consequence: Context Rot
When context grows too large, model performance degrades significantly—a phenomenon known as **"context rot"**:
- **Repetitive Responses**: The model starts generating redundant or circular answers
- **Slower Reasoning**: Inference becomes noticeably slower as context length increases
- **Quality Degradation**: Overall response quality and coherence decline
- **Lost Focus**: The model struggles to identify relevant information in the bloated context
### The Fundamental Paradox
Agents face a critical tension:
- **Need Rich Context**: Agents require comprehensive historical information to make informed decisions
- **Suffer from Large Context**: Excessive context causes performance degradation and inefficiency
**Context management aims to keep "just enough" information in the window**—sufficient for effective decision-making while leaving room for retrieval and expansion, without overwhelming the model.
### Why Short-Term Memory Management Matters
Effective short-term memory management is essential for:
1. **Maintaining Performance**: Keeping context within optimal size prevents quality degradation
2. **Enabling Long-Running Tasks**: Agents can handle complex, multi-step workflows without hitting context limits
3. **Cost Efficiency**: Reducing token usage directly lowers API costs
4. **Preserving Reasoning Quality**: Clean, focused context helps models maintain coherent reasoning chains
5. **Scalability**: Proper memory management allows agents to scale to production workloads
### The Solution: Intelligent Context Management
ReMeShortTermMemory implements proven context management strategies:
- **Context Offloading**: Moving large tool outputs to external storage while keeping references
- **Context Reduction**: Compacting tool results into minimal representations and summarizing when necessary
- **Smart Retention**: Keeping recent messages intact to maintain continuity and provide usage examples
- **Automatic Triggering**: Monitoring token usage and applying strategies before performance degrades
By implementing these strategies, ReMeShortTermMemory enables agents to handle arbitrarily long conversations and complex tasks while maintaining optimal performance throughout.
## Prerequisites
- Python 3.10 or higher
- DashScope API key from Alibaba Cloud
## QuickStart
Install agentscope and ensure you have a valid DashScope API key in your environment variables.
> Note: The example is built with DashScope chat model. If you want to use OpenAI models instead,
> modify the model initialization in the example code accordingly.
```bash
# Install agentscope from source
cd {PATH_TO_AGENTSCOPE}
pip install -e .
# Install dependencies
pip install reme-ai python-dotenv
```
Set up your API key:
```bash
export DASHSCOPE_API_KEY='YOUR_API_KEY'
```
Or create a `.env` file:
```bash
DASHSCOPE_API_KEY=YOUR_API_KEY
```
Run the example:
```bash
python short_term_memory_example.py
```
The example will:
1. Initialize a ReMeShortTermMemory instance with DashScope models
2. Demonstrate automatic memory compaction for long tool responses
3. Show integration with ReActAgent for context-aware conversations
4. Use grep and read_file tools to search and retrieve information from files
## Key Features
- **Automatic Memory Management**: Intelligent summarization and compaction of working memory to handle long contexts
- **Tool Response Optimization**: Automatic truncation and summarization of large tool responses to stay within token limits
- **Flexible Configuration**: Configurable thresholds for compaction ratio, token limits, and recent message retention
- **ReAct Agent Integration**: Seamless integration with AgentScope's ReActAgent and tool system
- **Async Operations**: Full async support for non-blocking memory operations
## Basic Usage
This section provides a detailed walkthrough of the `short_term_memory_example.py` code, explaining how each component works together to create an agent with intelligent context management.
### Configuration Parameters
#### `ReMeShortTermMemory` Class Parameters
The `ReMeShortTermMemory` class accepts the following initialization parameters:
- **`model`** (`DashScopeChatModel | OpenAIChatModel | None`): Language model for compression operations. Must be either `DashScopeChatModel` or `OpenAIChatModel`. This model is used for LLM-based compression when generating compact state snapshots. **Required**.
- **`reme_config_path`** (`str | None`): Optional path to ReMe configuration file for custom settings. Use this to provide advanced ReMe configurations beyond the standard parameters. Default: `None`.
- **`working_summary_mode`** (`str`): Strategy for working memory management. Controls how the memory system handles context overflow:
- `"compact"`: Only compact verbose tool messages by storing full content externally and keeping short previews in the active context.
- `"compress"`: Only apply LLM-based compression to generate compact state snapshots of conversation history.
- `"auto"`: First run compaction, then optionally run compression if the compaction ratio exceeds `compact_ratio_threshold`. This is the recommended mode for most use cases.
Default: `"auto"`.
- **`compact_ratio_threshold`** (`float`): Threshold for compaction effectiveness in AUTO mode. If `(compacted_tokens / original_tokens) > compact_ratio_threshold`, compression is applied after compaction. This ensures compression only runs when compaction alone isn't sufficient. Valid range: 0.0 to 1.0. Default: `0.75`.
- **`max_total_tokens`** (`int`): Maximum token count threshold before compression is triggered. This limit does **not** include `keep_recent_count` messages or system messages, which are always preserved. Should be set to 20%-50% of your model's context window size to leave room for new tool calls and responses. Default: `20000`.
- **`max_tool_message_tokens`** (`int`): Maximum token count for individual tool messages before compaction. Tool messages exceeding this limit are stored externally in files, with only a short preview kept in the active context. This is the maximum tolerable length for a single tool response. Default: `2000`.
- **`group_token_threshold`** (`int | None`): Maximum token count per compression group when splitting messages for LLM compression. When set to a positive integer, long message sequences are split into smaller batches for compression. If `None` or `0`, all messages are compressed in a single group. Use this to control the granularity of compression operations. Default: `None`.
- **`keep_recent_count`** (`int`): Number of most recent messages to preserve without compression or compaction. These messages remain in full in the active context to maintain conversation continuity and provide usage examples for the agent. The example uses `1` for demonstration purposes; **in production, a value of `10` is recommended** to maintain better conversation flow. Default: `10`.
- **`store_dir`** (`str`): Directory path for storing offloaded message content and compressed history files. This is where external files containing full tool responses and compressed message history are saved. The directory will be created automatically if it doesn't exist. Default: `"inmemory"`.
- **`**kwargs`** (`Any`): Additional arguments passed to `ReMeApp` initialization. Use this to pass any extra configuration options supported by the underlying ReMe application.
#### Parameter Relationships and Best Practices
- **Token Budget Strategy**: Set `max_total_tokens` to 20%-50% of your model's context window. For example, if your model has a 128K context window, set `max_total_tokens` between 25,600 and 64,000 tokens.
- **Compaction vs Compression**:
- Compaction is fast and lossless (full content is stored externally)
- Compression is slower but more aggressive (uses LLM to summarize)
- Use `"auto"` mode to benefit from both strategies
- **Recent Message Retention**: Higher `keep_recent_count` values (e.g., 10) provide better context continuity but consume more tokens. Lower values (e.g., 1) are more aggressive but may lose important recent context.
- **Tool Message Handling**: Adjust `max_tool_message_tokens` based on your typical tool response sizes. If your tools frequently return large outputs (e.g., file contents, API responses), consider a higher threshold or ensure compaction is enabled.
### Code Flow Diagram
```mermaid
flowchart TD
A[Start: Load Environment] --> B[Create Toolkit]
B --> C[Register Tools: grep & read_file]
C --> D[Initialize LLM Model]
D --> E[Create ReMeShortTermMemory]
E --> F[Enter Async Context Manager]
F --> G[Add Initial Messages with Large Tool Response]
G --> H[Memory Auto-Compacts Large Content]
H --> I[Create ReActAgent with Memory]
I --> J[User Sends Query]
J --> K[Agent Uses Tools to Search/Read]
K --> L[Tool Responses Added to Memory]
L --> M{Memory Token Limit?}
M -->|Exceeded| N[Auto-Compact/Summarize]
M -->|OK| O[Agent Generates Response]
N --> O
O --> P[Return Response to User]
P --> Q[Exit Context Manager]
Q --> End[End]
style H fill:#e1f5ff
style N fill:#ffe1e1
style O fill:#e1ffe1
```
### Step-by-Step Code Walkthrough
The example demonstrates a complete workflow from tool registration to agent interaction. Here's a detailed breakdown:
#### 1. Environment Setup and Imports
```python
import asyncio
import os
from dotenv import load_dotenv
load_dotenv()
```
The code starts by loading environment variables (including the DashScope API key) from a `.env` file.
#### 2. Tool Registration
The example defines two custom tools that demonstrate how to integrate retrieval operations:
**`grep` Tool**: Searches for patterns in files using regular expressions
```python
async def grep(file_path: str, pattern: str, limit: str) -> ToolResponse:
"""A powerful search tool for finding patterns in files..."""
from reme_ai.retrieve.working import GrepOp
op = GrepOp()
await op.async_call(file_path=file_path, pattern=pattern, limit=limit)
return ToolResponse(
content=[TextBlock(type="text", text=op.output)],
)
```
**`read_file` Tool**: Reads specific line ranges from files
```python
async def read_file(file_path: str, offset: int, limit: int) -> ToolResponse:
"""Reads and returns the content of a specified file..."""
from reme_ai.retrieve.working import ReadFileOp
op = ReadFileOp()
await op.async_call(file_path=file_path, offset=offset, limit=limit)
return ToolResponse(
content=[TextBlock(type="text", text=op.output)],
)
```
> **Important Note on Tool Replaceability**:
> - The `grep` and `read_file` tools shown here are **example implementations** using ReMe's built-in operations
> - You can **replace them with your own retrieval tools**, such as:
> - Vector database embedding retrieval (e.g., ChromaDB, Pinecone, Weaviate)
> - Web search APIs (e.g., Google Search, Bing Search)
> - Database query tools (e.g., SQL queries, MongoDB queries)
> - Custom domain-specific search solutions
> - Similarly, the **offline write operations** (used internally by ReMeShortTermMemory to store compacted content) can be customized by modifying the `write_text_file` function in AgentScope's tool system
> - The key requirement is that your tools return `ToolResponse` objects with appropriate content blocks
#### 3. LLM Model Initialization
```python
llm = DashScopeChatModel(
model_name="qwen3-coder-30b-a3b-instruct",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
generate_kwargs={
"temperature": 0.001,
"seed": 0,
},
)
```
The model is configured with low temperature for consistent, deterministic responses. This same model will be used for both agent reasoning and memory summarization operations.
#### 4. Short-Term Memory Initialization
```python
short_term_memory = ReMeShortTermMemory(
model=llm,
working_summary_mode="auto", # Automatic memory management
compact_ratio_threshold=0.75, # Trigger compaction at 75% capacity
max_total_tokens=20000, # Set to 20%-50% of model's context window
max_tool_message_tokens=2000, # Maximum tolerable tool response length
group_token_threshold=None, # Max tokens per LLM compression batch; None means no splitting
keep_recent_count=1, # Keep 1 recent message intact (set to 1 for demo; use 10 in production)
store_dir="inmemory", # Storage directory for offloaded content
)
```
This configuration enables automatic memory management that will:
- Monitor token usage
- Automatically compact large tool responses when they exceed `max_tool_message_tokens`
- Trigger summarization when total tokens exceed `max_total_tokens` and compaction ratio exceeds `compact_ratio_threshold`
#### 5. Async Context Manager Usage
```python
async with short_term_memory:
# All memory operations happen here
```
The `async with` statement ensures proper initialization and cleanup of memory resources. This is the recommended approach for using `ReMeShortTermMemory`.
#### 6. Simulating Long Context
The example demonstrates memory compaction by adding a large tool response:
```python
# Read README content and multiply it 10 times to simulate a large response
f = open("../../../../README.md", encoding="utf-8")
readme_content = f.read()
f.close()
memories = [
{
"role": "user",
"content": "搜索下项目资料",
},
{
"role": "assistant",
"content": None,
"tool_calls": [...], # Tool call metadata
},
{
"role": "tool",
"content": readme_content * 10, # Large tool response (10x README)
"tool_call_id": "call_6596dafa2a6a46f7a217da",
},
]
await short_term_memory.add(
ReMeShortTermMemory.list_to_msg(memories),
allow_duplicates=True,
)
```
When this large content is added, `ReMeShortTermMemory` will:
1. Detect that the tool response exceeds `max_tool_message_tokens` (the maximum tolerable tool response length, set to 2000 in this example)
2. Automatically compact it by storing the full content in an external file
3. Keep only a short preview in the active memory
4. This happens transparently without manual intervention
#### 7. ReAct Agent Creation
```python
agent = ReActAgent(
name="react",
sys_prompt=(
"You are a helpful assistant. "
"工具调用的调用可能会被缓存到本地。"
"可以先使用`Grep`匹配关键词或者正则表达式所在行数,然后通过`ReadFile`读取位置附近的代码。"
# ... more instructions
),
model=llm,
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=short_term_memory, # Memory is integrated here
max_iters=20,
)
```
The agent is configured with:
- The same LLM model used for memory operations
- The toolkit containing `grep` and `read_file` tools
- The `short_term_memory` instance for automatic context management
- A system prompt that guides the agent on tool usage patterns
#### 8. Agent Interaction
```python
msg = Msg(
role="user",
content=("项目资料中agentscope_v1论文的一作是谁"),
name="user",
)
msg = await agent(msg)
print(f"✓ Agent response: {msg.get_text_content()}\n")
```
When the agent processes this message:
1. It receives the user query
2. Decides to use tools (e.g., `grep` to search for "agentscope_v1")
3. Tool responses are automatically added to memory
4. If memory grows too large, automatic compaction occurs
5. The agent generates a response based on the managed context
6. The response is returned to the user
### Complete Example Code Structure
```python
async def main() -> None:
# 1. Create toolkit and register tools
toolkit = Toolkit()
toolkit.register_tool_function(grep)
toolkit.register_tool_function(read_file)
# 2. Initialize LLM
llm = DashScopeChatModel(...)
# 3. Create short-term memory
short_term_memory = ReMeShortTermMemory(...)
# 4. Use async context manager
async with short_term_memory:
# 5. Add initial messages (with large content to demo compaction)
await short_term_memory.add(messages, allow_duplicates=True)
# 6. Create agent with memory
agent = ReActAgent(..., memory=short_term_memory, ...)
# 7. Interact with agent
response = await agent(user_message)
```
### Key Takeaways
1. **Automatic Memory Management**: Memory compaction and summarization happen automatically when thresholds are exceeded
2. **Tool Integration**: Tools return `ToolResponse` objects that are seamlessly integrated into memory
3. **Async Context Manager**: Always use `async with short_term_memory:` to ensure proper resource management
4. **Flexible Tool System**: The `grep` and `read_file` tools are examples—you can replace them with any retrieval mechanism that fits your use case
5. **Transparent Operation**: Memory management is transparent to the agent—it just sees a clean, focused context
### Using Async Context Manager
`ReMeShortTermMemory` implements the async context manager protocol, which ensures proper initialization and cleanup of resources. There are two ways to use it:
#### Recommended: Using `async with` Statement
The recommended approach is to use the `async with` statement, which automatically handles resource management:
```python
async with short_term_memory:
# Memory is initialized here
await short_term_memory.add(messages)
response = await agent(msg)
# Memory is automatically cleaned up when exiting the block
```
#### Alternative: Manual `__aenter__` and `__aexit__` Calls
You can also manually call `__aenter__` and `__aexit__` if you need more control:
```python
# Manually initialize
await short_term_memory.__aenter__()
try:
# Use the memory
await short_term_memory.add(messages)
response = await agent(msg)
finally:
# Manually cleanup
await short_term_memory.__aexit__(None, None, None)
```
> **Note**: It's recommended to use the `async with` statement as it ensures proper resource cleanup even if an exception occurs.
## Advanced Configuration
You can customize the ReMe config by passing a config path:
```python
short_term_memory = ReMeShortTermMemory(
model=llm,
reme_config_path="path/to/your/config.yaml", # Pass your custom ReMe configuration
# ... other parameters
)
```
For more configuration options, refer to the [ReMe documentation](https://github.com/agentscope-ai/ReMe).
## What's in the Example
The `short_term_memory_example.py` file demonstrates:
1. **Tool Integration**: Registering `grep` and `read_file` tools for searching and reading files
2. **Memory Initialization**: Setting up ReMeShortTermMemory with appropriate parameters for handling long contexts
3. **Long Context Handling**: Adding a large tool response (README content × 10) to demonstrate automatic memory compaction
4. **ReAct Agent Usage**: Using the agent with short-term memory to answer questions based on retrieved information
## Example Workflow
The example shows a typical workflow:
1. User asks to search for project information
2. Agent uses `grep` tool to find relevant content
3. Agent uses `read_file` tool to read specific sections
4. Large tool responses are automatically compacted by the memory system
5. Agent answers the user's question based on the retrieved information

View File

@@ -0,0 +1,349 @@
# -*- coding: utf-8 -*-
"""ReMe-based short-term memory implementation for AgentScope."""
import json
from pathlib import Path
from typing import Any, List
from uuid import uuid4
from agentscope import logger
from agentscope._utils._common import _json_loads_with_repair
from agentscope.formatter import DashScopeChatFormatter, OpenAIChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg, TextBlock, ToolUseBlock, ToolResultBlock
from agentscope.model import DashScopeChatModel, OpenAIChatModel
from agentscope.tool import write_text_file
class ReMeShortTermMemory(InMemoryMemory):
"""Short-term memory implementation using ReMe for message management.
This class provides automatic working-memory management through a
multi-stage pipeline that reduces token usage while preserving
essential information:
1. **Compaction**: Truncates large tool messages by storing full
content in external files and keeping only short previews in the
active context.
2. **Compression**: Uses LLM to generate dense summaries of older
conversation history, creating a compact state snapshot.
3. **Offload**: Orchestrates compaction and optional compression
based on the configured working_summary_mode (COMPACT, COMPRESS,
or AUTO).
The memory management is triggered automatically when `get_memory()`
is called, ensuring the agent's context stays within token limits
while maintaining access to detailed historical information through
external storage.
"""
def __init__(
self,
model: DashScopeChatModel | OpenAIChatModel | None = None,
reme_config_path: str | None = None,
working_summary_mode: str = "auto",
compact_ratio_threshold: float = 0.75,
max_total_tokens: int = 20000,
max_tool_message_tokens: int = 2000,
group_token_threshold: int | None = None,
keep_recent_count: int = 10,
store_dir: str = "inmemory",
**kwargs: Any,
) -> None:
"""Initialize ReMe-based short-term memory.
Args:
model: Language model for compression operations. Must be
either DashScopeChatModel or OpenAIChatModel.
reme_config_path: Optional path to ReMe configuration file
for custom settings.
working_summary_mode: Strategy for working memory management.
- "compact": Only compact verbose tool messages by
storing full content externally and keeping short
previews.
- "compress": Only apply LLM-based compression to
generate compact state snapshots.
- "auto": First run compaction, then optionally run
compression if the compaction ratio exceeds
compact_ratio_threshold.
Defaults to "auto".
compact_ratio_threshold: Threshold for compaction
effectiveness in AUTO mode. If (compacted_tokens /
original_tokens) > this threshold, compression is
applied. Defaults to 0.75.
max_total_tokens: Maximum token count threshold before
compression is triggered. Does not include
keep_recent_count messages or system messages.
Defaults to 20000.
max_tool_message_tokens: Maximum token count for individual
tool messages before compaction. Tool messages exceeding
this are stored externally. Defaults to 2000.
group_token_threshold: Maximum token count per compression
group when splitting messages for LLM compression. If
None or 0, all messages are compressed in a single
group. Defaults to None.
keep_recent_count: Number of most recent messages to
preserve without compression or compaction. These
messages remain in full in the active context.
Defaults to 1.
store_dir: Directory path for storing offloaded message
content and compressed history files. Defaults to
"working_memory".
**kwargs: Additional arguments passed to ReMeApp
initialization.
Raises:
ValueError: If model is not a DashScopeChatModel or
OpenAIChatModel.
ImportError: If reme_ai library is not installed.
"""
super().__init__()
# Store working memory parameters
self.working_summary_mode = working_summary_mode
self.compact_ratio_threshold = compact_ratio_threshold
self.max_total_tokens = max_total_tokens
self.max_tool_message_tokens = max_tool_message_tokens
self.group_token_threshold = group_token_threshold
self.keep_recent_count = keep_recent_count
self.store_dir = store_dir
config_args = []
if isinstance(model, DashScopeChatModel):
llm_api_base = "https://dashscope.aliyuncs.com/compatible-mode/v1"
llm_api_key = model.api_key
self.formatter = DashScopeChatFormatter()
elif isinstance(model, OpenAIChatModel):
llm_api_base = str(getattr(model.client, "base_url", None))
llm_api_key = str(getattr(model.client, "api_key", None))
self.formatter = OpenAIChatFormatter()
else:
raise ValueError(
"model must be a DashScopeChatModel or "
"OpenAIChatModel instance. "
f"Got {type(model).__name__} instead.",
)
llm_model_name = model.model_name
if llm_model_name:
config_args.append(f"llm.default.model_name={llm_model_name}")
try:
from reme_ai import ReMeApp
except ImportError as e:
raise ImportError(
"The 'reme_ai' library is required for ReMe-based "
"short-term memory. Please try `pip install reme-ai`,"
"and visit: https://github.com/agentscope-ai/ReMe for more "
"information.",
) from e
self.app = ReMeApp(
*config_args,
llm_api_key=llm_api_key,
llm_api_base=llm_api_base,
embedding_api_key=llm_api_key, # fake api key
embedding_api_base=llm_api_base, # fake api base
config_path=reme_config_path,
**kwargs,
)
self._app_started = False
async def __aenter__(self) -> "ReMeShortTermMemory":
"""Async context manager entry.
Initializes the ReMe application for async operations.
"""
if self.app is not None:
await self.app.__aenter__()
self._app_started = True
return self
async def __aexit__(
self,
exc_type: Any = None,
exc_val: Any = None,
exc_tb: Any = None,
) -> None:
"""Async context manager exit.
Cleans up the ReMe application resources.
"""
if self.app is not None:
await self.app.__aexit__(exc_type, exc_val, exc_tb)
self._app_started = False
async def get_memory(self) -> list[Msg]:
"""Retrieve and manage working memory with automatic summarization.
This method performs the core working-memory management pipeline:
1. **Format messages**: Converts internal Msg objects to standard
message format using the appropriate formatter (DashScope or
OpenAI).
2. **Execute offload pipeline**: Calls ReMe's
summary_working_memory_for_as operation which orchestrates:
- Message compaction: Large tool messages are truncated and
stored externally with only previews kept in context.
- Message compression: If needed (based on
working_summary_mode), older messages are compressed using
LLM into dense summaries.
- File storage: Offloaded content is written to external
files for potential retrieval.
3. **Update content**: Replaces the internal message list with
the managed version, ensuring subsequent operations work with
the optimized context.
The operation respects configuration parameters like
max_total_tokens, keep_recent_count, and working_summary_mode to
balance context size with information preservation.
Returns:
List of Msg objects representing the managed working memory,
with large tool messages compacted and/or older history
compressed as needed.
Note:
This method automatically writes offloaded content to files
in the configured store_dir. The write_file_dict metadata
contains paths and content for all externally stored
messages.
"""
messages: list[dict[str, Any]] = await self.formatter.format(
msgs=self.content, # type: ignore[has-type]
)
for message in messages:
if isinstance(message.get("content"), list):
msg_content = message.get("content")
logger.warning(
"Skipping message with content as list. content=%s",
msg_content,
)
message["content"] = ""
# Execute ReMe's working memory offload pipeline
# This orchestrates compaction and/or compression based on
# working_summary_mode
result: dict = await self.app.async_execute(
name="summary_working_memory_for_as",
messages=messages,
working_summary_mode=self.working_summary_mode,
compact_ratio_threshold=self.compact_ratio_threshold,
max_total_tokens=self.max_total_tokens,
max_tool_message_tokens=self.max_tool_message_tokens,
group_token_threshold=self.group_token_threshold,
keep_recent_count=self.keep_recent_count,
store_dir=self.store_dir,
chat_id=uuid4().hex,
)
logger.info(
"summary_working_memory_for_as.result=%s",
json.dumps(result, ensure_ascii=False, indent=2),
)
# Extract managed messages and file write operations from result
messages = result.get("answer", [])
write_file_dict: dict = result.get("metadata", {}).get(
"write_file_dict",
{},
)
# Write offloaded content to external files
# This includes full tool message content and compressed message
# history
if write_file_dict:
for path, content_str in write_file_dict.items():
file_dir = Path(path).parent
if not file_dir.exists():
file_dir.mkdir(parents=True, exist_ok=True)
await write_text_file(path, content_str)
# Update internal content with managed messages
self.content = self.list_to_msg(messages)
return self.content
@staticmethod
def list_to_msg(messages: list[dict[str, Any]]) -> list[Msg]:
"""Convert a list of message dictionaries to Msg objects.
This method handles the conversion from standard message format
(used by ReMe and LLM APIs) back to AgentScope's Msg objects.
It properly handles:
- Text content for user, system, and assistant messages
- Tool result blocks (converting role="tool" to role="system")
- Tool use blocks from tool_calls in assistant messages
Args:
messages: List of message dictionaries with role, content,
and optional tool_calls or tool-related fields.
Returns:
List of Msg objects with properly structured content blocks.
"""
msg_list: list[Msg] = []
for msg_dict in messages:
role = msg_dict["role"]
content_blocks: List[
TextBlock | ToolUseBlock | ToolResultBlock
] = []
content = msg_dict.get("content")
# Convert text content to appropriate content blocks
if content:
if role in ["user", "system", "assistant"]:
content_blocks.append(TextBlock(type="text", text=content))
elif role in ["tool"]:
# Tool messages are converted to system messages with
# ToolResultBlock
role = "system"
content_blocks.append(
ToolResultBlock(
type="tool_result",
name=msg_dict.get("name"),
id=msg_dict.get("tool_call_id"),
output=[TextBlock(type="text", text=content)],
),
)
# Convert tool_calls to ToolUseBlock content blocks
if msg_dict.get("tool_calls"):
for tool_call in msg_dict["tool_calls"]:
# Parse tool arguments with repair for malformed JSON
input_ = _json_loads_with_repair(
tool_call["function"].get(
"arguments",
"{}",
)
or "{}",
)
content_blocks.append(
ToolUseBlock(
type="tool_use",
name=tool_call["function"]["name"],
input=input_,
id=tool_call["id"],
),
)
msg_obj = Msg(
name=role,
content=content_blocks,
role=role,
metadata=msg_dict.get("metadata"),
)
msg_list.append(msg_obj)
return msg_list
async def retrieve(self, *args: Any, **kwargs: Any) -> None:
"""Retrieve operation is not implemented for ReMe short-term memory.
ReMe focuses on working memory management (compaction and compression)
rather than retrieval from long-term storage.
Raises:
NotImplementedError: This operation is not supported.
"""
raise NotImplementedError

View File

@@ -0,0 +1,188 @@
# -*- coding: utf-8 -*-
"""Example demonstrating ReMeShortTermMemory usage with ReActAgent."""
# noqa: E402
import asyncio
import os
from dotenv import load_dotenv
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.message import Msg, TextBlock
from agentscope.model import DashScopeChatModel
from agentscope.tool import ToolResponse, Toolkit, view_text_file
load_dotenv()
async def main() -> None:
"""Main function demonstrating ReMeShortTermMemory with tool usage."""
from reme_short_term_memory import ReMeShortTermMemory
toolkit = Toolkit()
async def grep(file_path: str, pattern: str, limit: str) -> ToolResponse:
"""A powerful search tool for finding patterns in files using regular
expressions.
Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+"),
glob pattern filtering, and result limiting. Ideal for searching code
or text content across multiple files.
Args:
file_path (`str`):
The path to the file to search in. Can be an absolute or
relative path.
pattern (`str`):
The search pattern or regular expression to match. Supports
full regex syntax for complex pattern matching.
limit (`str`):
The maximum number of matching results to return. Use this to
control output size for large files. Should not exceed 50.
"""
from reme_ai.retrieve.working import GrepOp
op = GrepOp()
await op.async_call(file_path=file_path, pattern=pattern, limit=limit)
return ToolResponse(
content=[
TextBlock(
type="text",
text=op.output,
),
],
)
async def read_file(
file_path: str,
offset: int,
limit: int,
) -> ToolResponse:
"""Reads and returns the content of a specified file.
For text files, it can read specific line ranges using the 'offset' and
'limit' parameters. Use offset and limit to paginate through large
files.
Note: It's recommended to use the `grep` tool first to locate the line
numbers of interest before calling this function.
Args:
file_path (`str`):
The path to the file to read. Can be an absolute or relative
path.
offset (`int`):
The starting line number to read from (0-indexed). Use this to
skip to a specific position in the file.
limit (`int`):
The maximum number of lines to read from the offset position.
Helps control memory usage when reading large files. Should
not exceed 100.
"""
return await view_text_file(file_path, ranges=[offset, offset + limit])
# These two tools are provided as examples. You can replace them with your
# own retrieval tools, such as vector database embedding retrieval or other
# search solutions that fit your use case.
toolkit.register_tool_function(grep)
toolkit.register_tool_function(read_file)
llm = DashScopeChatModel(
model_name="qwen3-max",
# model_name="qwen3-coder-30b-a3b-instruct",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
stream=False,
generate_kwargs={
"temperature": 0.001,
"seed": 0,
},
)
short_term_memory = ReMeShortTermMemory(
model=llm,
working_summary_mode="auto",
compact_ratio_threshold=0.75,
max_total_tokens=20000,
max_tool_message_tokens=2000,
group_token_threshold=None, # Max tokens per compression batch
keep_recent_count=1, # Set to 1 for demo; use 10 in production
store_dir="inmemory",
)
async with short_term_memory:
# Simulate ultra long context
f = open("../../../../README.md", encoding="utf-8")
readme_content = f.read()
f.close()
memories = [
{
"role": "user",
"content": "Search for project information",
},
{
"role": "assistant",
"content": None,
"tool_calls": [
{
"index": 0,
"id": "call_6596dafa2a6a46f7a217da",
"function": {
"arguments": "{}",
"name": "web_search",
},
"type": "function",
},
],
},
{
"role": "tool",
"content": readme_content * 10,
"tool_call_id": "call_6596dafa2a6a46f7a217da",
},
]
await short_term_memory.add(
ReMeShortTermMemory.list_to_msg(memories),
allow_duplicates=True,
)
agent = ReActAgent(
name="react",
sys_prompt=(
"You are a helpful assistant. "
"Tool calls may be cached locally. "
"You can first use `Grep` to match keywords or regular "
"expressions to find line numbers, then use `ReadFile` "
"to read the code near that location. "
"If no matches are found, never give up trying - try "
"other parameters or relax the matching conditions, such "
"as searching for only partial keywords. "
"After `Grep`, you can use the `ReadFile` command to "
"view content starting from a specified offset position "
"`offset` with length `limit`. "
"The maximum limit is 100. "
"If the current content is insufficient, the `ReadFile` "
"command can continuously try different `offset` and "
"`limit` parameters."
),
model=llm,
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=short_term_memory,
max_iters=20,
)
msg = Msg(
role="user",
content=(
"In the project documentation, who is the first author "
"of the agentscope_v1 paper?"
),
name="user",
)
msg = await agent(msg)
print(f"✓ Agent response: {msg.get_text_content()}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,28 @@
# Stream Printing Messages
The AgentScope agent is designed to communicate with the user and the other agents by passing messages explicitly.
However, we notice the requirements that obtain the printing messages from the agent in a streaming manner.
Therefore, in example we demonstrate how to gather and yield the printing messages from a single agent and
multi-agent systems in a streaming manner.
## Quick Start
Run the following command to see the streaming printing messages from the agent.
Note the messages with the same ID are the chunks of the same message in accumulated manner.
- For single-agent:
```bash
python single_agent.py
```
- For multi-agent:
```bash
python multi_agent.py
```
> Note: The example is built with DashScope chat model. If you want to change the model in this example, don't forget
> to change the formatter at the same time! The corresponding relationship between built-in models and formatters are
> list in [our tutorial](https://doc.agentscope.io/tutorial/task_prompt.html#id1)

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""Example for gather the printing messages from multiple agents."""
import asyncio
import os
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeMultiAgentFormatter
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import MsgHub, stream_printing_messages
def create_agent(name: str) -> ReActAgent:
"""Create an agent with the given name."""
return ReActAgent(
name=name,
sys_prompt=f"You are a student named {name}.",
model=DashScopeChatModel(
api_key=os.environ["DASHSCOPE_API_KEY"],
model_name="qwen-max",
stream=False, # close streaming for simplicity
),
formatter=DashScopeMultiAgentFormatter(),
)
async def workflow(
alice: ReActAgent,
bob: ReActAgent,
charlie: ReActAgent,
) -> None:
"""The example workflow for multiple agents."""
async with MsgHub(
participants=[alice, bob, charlie],
announcement=Msg(
"user",
"Alice, Bob and Charlie, welcome to the meeting! Let's "
"meet each other first.",
"user",
),
):
# agent speaks in turn
await alice()
await bob()
await charlie()
async def main() -> None:
"""The main entry for the example."""
# Create agents
alice, bob, charlie = [
create_agent(_) for _ in ["Alice", "Bob", "Charlie"]
]
async for msg, last in stream_printing_messages(
agents=[alice, bob, charlie],
coroutine_task=workflow(alice, bob, charlie),
):
print(msg, last)
asyncio.run(main())

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""The example demonstrating how to obtain the messages from the agent in a
streaming way."""
import asyncio
import os
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import stream_printing_messages
from agentscope.tool import (
Toolkit,
execute_shell_command,
view_text_file,
execute_python_code,
)
async def main() -> None:
"""The main function."""
toolkit = Toolkit()
toolkit.register_tool_function(execute_shell_command)
toolkit.register_tool_function(execute_python_code)
toolkit.register_tool_function(view_text_file)
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
# Change the model and formatter together if you want to try other
# models
model=DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen-max",
enable_thinking=False,
stream=True,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
)
# Prepare a user message
user_msg = Msg(
"user",
"Hi! Who are you?",
"user",
)
# We disable the terminal printing to avoid messy outputs
agent.set_console_output_enabled(False)
# obtain the printing messages from the agent in a streaming way
async for msg, last in stream_printing_messages(
agents=[agent],
coroutine_task=agent(user_msg),
):
print(msg, last)
asyncio.run(main())

View File

@@ -0,0 +1,96 @@
# Structured Output Example
## What This Example Demonstrates
This example showcases **structured output generation** using AgentScope with Pydantic models. It demonstrates how to constrain AI model outputs to follow specific data structures and formats, ensuring consistent and parseable responses.
### Key Features:
- **Structured Data Generation**: Forces agent responses to conform to
predefined schemas
- **Pydantic Integration**: Uses Pydantic models to define output structure with validation
- **Type Safety**: Ensures output data types match expected formats
- **Field Validation**: Includes constraints like age limits (0-120) and enum choices
- **JSON Output**: Generates clean, structured JSON responses
### Example Models:
1. **TableModel**: Structured person information
- `name`: Person's name (string)
- `age`: Person's age (integer,0-120)
- `intro`: One-sentence introduction (string)
- `honors`: List of honors/achievements (array of strings)
2. **ChoiceModel**: Constrained choice selection
- `choice`: Must be one of "apple", "banana", or "orange"
### Use Cases:
- **Data Extraction**: Extract structured information from unstructured text
- **Form Generation**: Generate consistent data for databases or APIs
- **Survey Responses**: Ensure responses fit predefined categories
- **Content Classification**: Categorize content into specific types
## How to Run This Example
1. **Set Environment Variable:**
```bash
export DASHSCOPE_API_KEY="your_dashscope_api_key_here"
```
2. **Run the script:**
```bash
python main.py
```
3. **Expected Output:**
The program will generate two structured responses like below:
```
Structured Output 1:
{
"name": "Albert Einstein",
"age": 76,
"intro": 1,
"honors": [
"Nobel Prize in Physics (1921)",
"Copley Medal (1925)"
]
}
Structured Output 2:
{
"choice": "apple"
}
```
>💡**Note:** The specific content will vary with each run since the agent generates different responses, but the JSON structure will always conform to the predefined Pydantic models (`TableModel` and `ChoiceModel`).
## How It Works:
1. The agent receives a query along with a structured_model parameter
2. The agent generates a response that conforms to the Pydantic model schema
3. The structured data is returned in res.metadata as a validated JSON object
4. Pydantic ensures all field types and constraints are satisfied
## Custom Pydantic Models
Create your own structured output models for specific use cases, for example:
```
from typing import Optional
from pydantic import BaseModel, Field, EmailStr
class BusinessModel(BaseModel):
"""Business information extraction model."""
company_name: str = Field(description="Name of the company")
industry: str = Field(description="Industry sector")
founded_year: int = Field(description="Year founded", ge=1800, le=2024)
headquarters: str = Field(description="Location of headquarters")
employee_count: Optional[int] = Field(description="Number of employees", ge=1)
email: Optional[EmailStr] = Field(description="Contact email address")
website: Optional[str] = Field(description="Company website URL")
# Usage
query = Msg("user", "Tell me about Tesla Inc.", "user")
res = await agent(query, structured_model=BusinessModel)
```
## Best Practices for Structured Output
1. **Use Descriptive Field Names:** Make field purposes clear
2. **Add Field Descriptions:** Help the agent understand what data to generate
3. **Set Validation Constraints:** Use Pydantic validators for data integrity
4. **Choose Appropriate Types:** Use specific types like EmailStr, datetime, etc.

View File

@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
"""The main entry point of the structured output example."""
import asyncio
import json
import os
from typing import Literal
from pydantic import BaseModel, Field
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
class TableModel(BaseModel):
"""A simple table model for structured output."""
name: str = Field(description="The name of the person")
age: int = Field(description="The age of the person", ge=0, le=120)
intro: str = Field(description="A one-sentence introduction of the person")
honors: list[str] = Field(
description="A list of honors received by this person",
)
class ChoiceModel(BaseModel):
"""A simple choice model for structured output."""
choice: Literal["apple", "banana", "orange"] = Field(
description="Your choice of fruit",
)
async def main() -> None:
"""The main entry point for the structured output example."""
toolkit = Toolkit()
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen-max",
stream=True,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
)
query_msg_1 = Msg(
"user",
"Please introduce Einstein",
"user",
)
res = await agent(query_msg_1, structured_model=TableModel)
print(
"Structured Output 1:\n"
"```\n"
f"{json.dumps(res.metadata, indent=4)}\n"
"```",
)
query_msg_2 = Msg(
"user",
"Choose one of your favorite fruit",
"user",
)
res = await agent(query_msg_2, structured_model=ChoiceModel)
print(
"Structured Output 2:\n"
"```\n"
f"{json.dumps(res.metadata, indent=4)}\n"
"```",
)
asyncio.run(main())

View File

@@ -0,0 +1,13 @@
# TTS (Text-to-Speech) in AgentScope
This example demonstrates how to integrate DashScope Realtime TTS model with `ReActAgent` to enable audio output.
The agent can speak its responses in real-time.
This example uses DashScope's Realtime TTS model, you can also change to other TTS models supported by AgentScope, e.g.
OpenAI, Gemini, etc.
To run the example, execute:
```bash
python main.py
```

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
"""The main entry point of the ReAct agent example."""
import asyncio
import os
from agentscope.agent import ReActAgent, UserAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.model import DashScopeChatModel
from agentscope.tool import (
Toolkit,
execute_shell_command,
execute_python_code,
view_text_file,
)
from agentscope.tts import DashScopeRealtimeTTSModel
async def main() -> None:
"""The main entry point for the ReAct agent example."""
toolkit = Toolkit()
toolkit.register_tool_function(execute_shell_command)
toolkit.register_tool_function(execute_python_code)
toolkit.register_tool_function(view_text_file)
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen3-max",
enable_thinking=False,
stream=True,
),
formatter=DashScopeChatFormatter(),
toolkit=toolkit,
memory=InMemoryMemory(),
# Specify the TTS model for real-time speech synthesis
tts_model=DashScopeRealtimeTTSModel(
model_name="qwen3-tts-flash-realtime",
api_key=os.environ.get("DASHSCOPE_API_KEY"),
voice="Cherry",
stream=False,
),
)
user = UserAgent("User")
msg = None
while True:
msg = await user(msg)
if msg.get_text_content() == "exit":
break
msg = await agent(msg)
asyncio.run(main())

View File

@@ -0,0 +1,422 @@
# AlibabaCloud MySQL Vector Store Example
This example demonstrates how to use the `AlibabaCloudMySQLStore` class in AgentScope's RAG system for vector storage and similarity search operations using AlibabaCloud MySQL (RDS) with native vector functions.
## Features
AlibabaCloudMySQLStore provides:
- Vector storage using MySQL's native VECTOR data type
- Automatic vector index creation (CREATE VECTOR INDEX) based on distance metric
- Vector functions (VEC_FROMTEXT, VEC_DISTANCE_COSINE, VEC_DISTANCE_EUCLIDEAN)
- Database-level distance calculation and sorting via ORDER BY
- Two distance metrics: COSINE and EUCLIDEAN (supported by AlibabaCloud MySQL)
- Metadata filtering support
- CRUD operations (Create, Read, Update, Delete)
- Support for chunked documents
- Direct access to the underlying MySQL connection for advanced operations
- Full integration with AlibabaCloud RDS MySQL instances
## Prerequisites
### 1. AlibabaCloud RDS MySQL Instance
You need an AlibabaCloud RDS MySQL instance with vector support:
- **Version**: MySQL 8.0+
- **Vector Plugin**: Ensure the vector search plugin is enabled (check `vidx_disabled` parameter is OFF)
- **Network Access**: Configure security group and whitelist to allow access
#### Create RDS MySQL Instance on AlibabaCloud:
1. Go to [AlibabaCloud RDS Console](https://rdsnext.console.aliyun.com/)
2. Click "Create Instance"
3. Select MySQL 8.0 or higher
4. Configure specifications based on your needs
5. Set up network and security settings
6. Note down the connection endpoint (e.g., `rm-xxxxx.mysql.rds.aliyuncs.com`)
#### Configure Database:
```sql
-- Connect to your RDS MySQL instance
mysql -h rm-xxxxx.mysql.rds.aliyuncs.com -P 3306 -u your_username -p
-- Check if vector capability is enabled (vidx_disabled should be OFF)
SHOW VARIABLES LIKE 'vidx_disabled';
-- Expected result: vidx_disabled | OFF
-- If OFF, vector capability is enabled
-- If ON, contact AlibabaCloud support to enable vector search plugin
-- Create database
CREATE DATABASE agentscope_test;
-- Use the database
USE agentscope_test;
-- Verify vector functions are available
SELECT VEC_FROMTEXT('[1,2,3]');
```
### 2. Python Dependencies
```bash
pip install mysql-connector-python agentscope
```
### 3. Network Configuration
Ensure your local machine or server can access the RDS instance:
- Add your IP to the RDS whitelist
- Configure security group rules
- Use SSL connection if required
## Configuration
Update the connection parameters in `main.py`:
```python
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com", # Your RDS endpoint
port=3306,
user="your_username", # Your RDS username
password="your_password", # Your RDS password
database="agentscope_test",
table_name="test_vectors",
dimensions=768, # Set to your embedding dimension
distance="COSINE",
# Optional: SSL configuration
# connection_kwargs={
# "ssl_ca": "/path/to/ca.pem",
# "ssl_verify_cert": True,
# }
)
```
## Running the Example
```bash
python main.py
```
## Example Tests
The example includes three comprehensive tests:
### 1. Basic CRUD Operations
- Initialize AlibabaCloudMySQLStore
- Add documents with embeddings
- Search for similar documents
- Delete documents
- Get the underlying MySQL connection
### 2. Search with Metadata Filtering
- Add documents with different categories
- Search with and without filters
- Use SQL WHERE clauses for filtering
### 3. Different Distance Metrics
- Test COSINE similarity (best for normalized vectors)
- Test EUCLIDEAN distance (best for absolute distance)
## Key Features Explained
### Distance Metrics
AlibabaCloud MySQL supports two distance metrics:
- **COSINE**: Measures the cosine of the angle between vectors. Values range from 0 (identical) to 2 (opposite). Best for text embeddings and normalized vectors.
- **EUCLIDEAN**: Measures the straight-line Euclidean distance between vectors. Lower values indicate similarity. Best for absolute distance measurements.
**Note**: Unlike some other vector databases, AlibabaCloud MySQL currently only supports COSINE and EUCLIDEAN distance functions. Inner Product (IP) is not supported.
### Metadata Filtering
Use SQL WHERE clauses to filter search results:
```python
results = await store.search(
query_embedding=embedding,
limit=10,
filter='doc_id LIKE "ai%" AND chunk_id > 0',
)
```
### Table Structure
The implementation automatically creates a table with the following structure:
```sql
CREATE TABLE IF NOT EXISTS table_name (
id VARCHAR(255) PRIMARY KEY,
embedding VECTOR(dimensions) NOT NULL,
doc_id VARCHAR(255) NOT NULL,
chunk_id INT NOT NULL,
content TEXT NOT NULL,
total_chunks INT NOT NULL,
INDEX idx_doc_id (doc_id),
INDEX idx_chunk_id (chunk_id),
VECTOR INDEX (embedding) M=16 DISTANCE=cosine -- or DISTANCE=euclidean
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
**Note**: The vector index is created directly within the `CREATE TABLE` statement, not as a separate SQL command. The `M` parameter controls the HNSW algorithm's graph connectivity (default: 16).
### Performance Considerations
- **VECTOR Data Type**: Uses MySQL's native VECTOR type for efficient storage
- **Vector Index**: Automatically creates a vector index with the specified distance metric for fast similarity search
- **Database-Level Distance Calculation**: Vector distance calculations are performed at the database level using MySQL's native vector functions (VEC_DISTANCE_COSINE, VEC_DISTANCE_EUCLIDEAN), with sorting done via SQL ORDER BY
- **Native Vector Support**: MySQL 8.0+ has built-in vector functions that are highly optimized for vector operations
- **Supported Distance Metrics**: Only COSINE and EUCLIDEAN are supported
- **Small to Medium Datasets**: AlibabaCloudMySQLStore performs well for datasets up to 100K vectors
- **Large Datasets**: For datasets with millions of vectors, consider using dedicated vector databases (MilvusLite, Qdrant) with specialized indexing (HNSW, IVF, etc.)
- **RDS Performance**: Leverage AlibabaCloud RDS features like read replicas, backup, and monitoring
## Advanced Usage
### Direct Database Access
```python
# Get the MySQL connection for advanced operations
conn = store.get_client()
cursor = conn.cursor()
# Execute custom SQL queries
cursor.execute("SELECT COUNT(*) FROM test_vectors")
count = cursor.fetchone()
print(f"Total vectors: {count[0]}")
```
### Using MySQL Native Vector Functions
MySQL's native vector functions can be used directly in SQL queries:
```python
conn = store.get_client()
cursor = conn.cursor()
# Use MySQL native vector functions directly
query_vector = "[0.1,0.2,0.3,0.4]"
cursor.execute("""
SELECT
doc_id,
VEC_DISTANCE_COSINE(vector, VEC_FROMTEXT(%s)) as distance
FROM test_vectors
ORDER BY distance ASC
LIMIT 10
""", (query_vector,))
results = cursor.fetchall()
# Available MySQL vector functions in AlibabaCloud:
# - VEC_FROMTEXT(text) - Convert text "[1,2,3]" to vector
# - VEC_DISTANCE_COSINE(v1, v2) - Cosine distance
# - VEC_DISTANCE_EUCLIDEAN(v1, v2) - Euclidean distance
```
### SSL Connection
For secure connections to AlibabaCloud RDS:
```python
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com",
port=3306,
user="your_username",
password="your_password",
database="agentscope_test",
table_name="vectors",
dimensions=768,
distance="COSINE",
connection_kwargs={
"ssl_ca": "/path/to/ca.pem",
"ssl_verify_cert": True,
"ssl_verify_identity": True,
},
)
```
### Batch Operations
```python
# Add large batches of documents
batch_size = 1000
for i in range(0, len(all_documents), batch_size):
batch = all_documents[i:i + batch_size]
await store.add(batch)
```
### Connection Pooling
```python
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com",
port=3306,
user="your_username",
password="your_password",
database="agentscope_test",
table_name="vectors",
dimensions=768,
distance="COSINE",
connection_kwargs={
"pool_name": "mypool",
"pool_size": 10,
"pool_reset_session": True,
},
)
```
## Troubleshooting
### MySQL Version Check
Ensure your RDS MySQL version supports vector functions:
```sql
SELECT VERSION();
-- Should be MySQL 8.0 or higher
-- Check if vector capability is enabled (critical check)
SHOW VARIABLES LIKE 'vidx_disabled';
-- Expected result: vidx_disabled | OFF (vector capability enabled)
-- Test vector functions
SELECT VEC_FROMTEXT('[1,2,3]');
```
### Connection Errors
If you get connection errors:
1. **Check Whitelist**: Ensure your IP is in the RDS whitelist
2. **Security Group**: Verify security group rules allow port 3306
3. **Network Type**: Ensure you're using the correct endpoint (public/private)
4. **Credentials**: Double-check username and password
```bash
# Test connection from command line
mysql -h rm-xxxxx.mysql.rds.aliyuncs.com -P 3306 -u your_username -p
```
### Vector Function Errors
If you get errors about VEC_DISTANCE_COSINE or VECTOR type not being recognized:
1. **Check if vector capability is enabled**:
```sql
-- Check vidx_disabled parameter (must be OFF)
SHOW VARIABLES LIKE 'vidx_disabled';
-- Expected result: vidx_disabled | OFF
-- If ON, vector capability is disabled, contact AlibabaCloud support
```
2. Verify MySQL version is 8.0 or higher
```sql
SELECT VERSION();
```
3. Test vector functions availability:
```sql
-- Check if vector functions are available
SELECT VEC_FROMTEXT('[1,2,3]');
-- Check if VECTOR type is supported
CREATE TABLE test_vector (v VECTOR(3));
DROP TABLE test_vector;
-- List vector indexes
SHOW INDEX FROM your_table_name WHERE Index_type = 'VECTOR';
```
If `vidx_disabled` is ON, contact AlibabaCloud support to enable the vector search plugin for your RDS instance.
### Performance Optimization
For large datasets on AlibabaCloud RDS:
1. **Upgrade Instance**: Consider higher specifications (CPU, Memory)
2. **Read Replicas**: Use read replicas for read-heavy workloads
3. **Indexes**: Add indexes on frequently filtered columns
4. **Connection Pool**: Use connection pooling for concurrent operations
5. **Monitor**: Use AlibabaCloud CloudMonitor for performance insights
### Timeout Errors
If you experience timeout errors:
```python
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com",
port=3306,
user="your_username",
password="your_password",
database="agentscope_test",
table_name="vectors",
dimensions=768,
distance="COSINE",
connection_kwargs={
"connect_timeout": 30,
"read_timeout": 60,
"write_timeout": 60,
},
)
```
## AlibabaCloud RDS Best Practices
1. **Backup**: Enable automatic backups in RDS console
2. **Monitoring**: Set up alerts for CPU, memory, and connection usage
3. **Security**: Use private network connections when possible
4. **Scaling**: Consider read-only instances for read-heavy workloads
5. **Cost Optimization**: Use reserved instances for long-term usage
## Related Resources
- [AlibabaCloud RDS Documentation](https://www.alibabacloud.com/help/en/apsaradb-for-rds)
- [AlibabaCloud MySQL Vector Functions](https://www.alibabacloud.com/help/en/rds/apsaradb-rds-for-mysql/vector-storage-1)
- [AgentScope RAG Tutorial](https://doc.agentscope.io/tutorial/task_rag.html)
- [MySQL Connector Python](https://dev.mysql.com/doc/connector-python/en/)
## Example Use Cases
### RAG System with AlibabaCloud
```python
from agentscope.rag import AlibabaCloudMySQLStore, KnowledgeBase
# Initialize vector store with AlibabaCloud RDS
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com",
port=3306,
user="your_username",
password="your_password",
database="rag_system",
table_name="knowledge_vectors",
dimensions=768,
distance="COSINE",
)
# Create knowledge base
kb = KnowledgeBase(store=store)
# Add documents
await kb.add_documents(documents)
# Search
results = await kb.search("What is AI?", top_k=5)
```
## Support
For issues related to:
- **AlibabaCloudMySQLStore**: Open an issue on AgentScope GitHub
- **RDS MySQL**: Contact AlibabaCloud Support
- **Vector Functions**: Check MySQL documentation or AlibabaCloud support
## License
This example is part of the AgentScope project and follows the same license.

View File

@@ -0,0 +1,282 @@
# -*- coding: utf-8 -*-
"""Example of using AlibabaCloudMySQLStore in AgentScope RAG system."""
import asyncio
from agentscope.rag import (
AlibabaCloudMySQLStore,
Document,
DocMetadata,
)
from agentscope.message import TextBlock
async def example_basic_operations() -> None:
"""The example of basic CRUD operations with AlibabaCloudMySQLStore."""
print("\n" + "=" * 60)
print("Test 1: Basic CRUD Operations")
print("=" * 60)
# Initialize AlibabaCloudMySQLStore
# Replace with your AlibabaCloud MySQL connection details
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com", # Your RDS endpoint
port=3306,
user="your_username",
password="your_password",
database="agentscope_test",
table_name="test_vectors",
dimensions=4, # Small dimension for testing
distance="COSINE",
)
print("✓ AlibabaCloudMySQLStore initialized")
# Create test documents with embeddings
test_docs = [
Document(
metadata=DocMetadata(
content=TextBlock(
text="Artificial Intelligence is the future",
),
doc_id="doc_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Machine Learning is a subset of AI"),
doc_id="doc_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Deep Learning uses neural networks"),
doc_id="doc_3",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
]
# Test add operation
await store.add(test_docs)
print(f"✓ Added {len(test_docs)} documents to the store")
# Test search operation
query_embedding = [0.15, 0.25, 0.35, 0.45]
results = await store.search(
query_embedding=query_embedding,
limit=2,
)
print(f"\n✓ Search completed, found {len(results)} results:")
for i, result in enumerate(results, 1):
print(f" {i}. Score: {result.score:.4f}")
print(f" Content: {result.metadata.content}")
print(f" Doc ID: {result.metadata.doc_id}")
# Test search with score threshold
results_filtered = await store.search(
query_embedding=query_embedding,
limit=5,
score_threshold=0.9,
)
print(f"\n✓ Search with threshold (>0.9): {len(results_filtered)} results")
# Test delete operation
await store.delete(filter='doc_id = "doc_2"')
print("\n✓ Deleted document with doc_id='doc_2'")
# Verify deletion
results_after_delete = await store.search(
query_embedding=query_embedding,
limit=5,
)
print(f"✓ After deletion: {len(results_after_delete)} documents remain")
# Get client for advanced operations
client = store.get_client()
print(f"\n✓ Got MySQL connection: {type(client).__name__}")
# Close connection
store.close()
print("✓ Connection closed")
async def example_filter_search() -> None:
"""The example of search with metadata filtering."""
print("\n" + "=" * 60)
print("Test 2: Search with Metadata Filtering")
print("=" * 60)
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com",
port=3306,
user="your_username",
password="your_password",
database="agentscope_test",
table_name="filter_vectors",
dimensions=4,
distance="COSINE",
)
# Create documents with different categories
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(text="Python is a programming language"),
doc_id="prog_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(
text="Java is used for enterprise applications",
),
doc_id="prog_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Neural networks are used in AI"),
doc_id="ai_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Deep learning requires GPUs"),
doc_id="ai_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.4, 0.5, 0.6, 0.7],
),
]
await store.add(docs)
print(f"✓ Added {len(docs)} documents with different doc_id prefixes")
# Search without filter
query_embedding = [0.25, 0.35, 0.45, 0.55]
all_results = await store.search(
query_embedding=query_embedding,
limit=4,
)
print(f"\n✓ Search without filter: {len(all_results)} results")
for i, result in enumerate(all_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
# Search with filter for programming docs
prog_results = await store.search(
query_embedding=query_embedding,
limit=4,
filter='doc_id LIKE "prog%"',
)
filter_msg = 'doc_id LIKE "prog%"'
print(f"\n✓ Search with filter ({filter_msg}): {len(prog_results)}")
for i, result in enumerate(prog_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
# Search with filter for AI docs
ai_results = await store.search(
query_embedding=query_embedding,
limit=4,
filter='doc_id LIKE "ai%"',
)
filter_msg = 'doc_id LIKE "ai%"'
print(f"\n✓ Search with filter ({filter_msg}): {len(ai_results)}")
for i, result in enumerate(ai_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
store.close()
async def example_distance_metrics() -> None:
"""The example of different distance metrics."""
print("\n" + "=" * 60)
print("Test 3: Different Distance Metrics")
print("=" * 60)
# Test with different metrics
# Note: AlibabaCloud MySQL only supports COSINE and EUCLIDEAN
metrics = ["COSINE", "EUCLIDEAN"]
for metric in metrics:
print(f"\n--- Testing {metric} metric ---")
store = AlibabaCloudMySQLStore(
host="rm-xxxxx.mysql.rds.aliyuncs.com",
port=3306,
user="your_username",
password="your_password",
database="agentscope_test",
table_name=f"{metric.lower()}_vectors",
dimensions=4,
distance=metric,
)
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(text=f"Test doc for {metric}"),
doc_id=f"doc_{metric}_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
]
await store.add(docs)
results = await store.search(
query_embedding=[0.1, 0.2, 0.3, 0.4],
limit=1,
)
print(f"{metric} metric: Score = {results[0].score:.4f}")
store.close()
async def main() -> None:
"""Run all examples."""
print("\n" + "=" * 60)
print("AlibabaCloud MySQL Vector Store Test Suite")
print("=" * 60)
try:
await example_basic_operations()
await example_filter_search()
await example_distance_metrics()
print("\n" + "=" * 60)
print("✓ All tests completed successfully!")
print("=" * 60)
except Exception as e:
print(f"\n✗ Test failed with error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,128 @@
# MilvusLite Vector Store
This example demonstrates how to use **MilvusLiteStore** for vector storage and semantic search in AgentScope.
It includes four test scenarios covering CRUD operations, metadata filtering, document chunking, and distance metrics.
### Quick Start
Install agentscope first, and then the MilvusLite dependency:
```bash
# In MacOS/Linux
pip install pymilvus\[milvus_lite\]
# In Windows
pip install pymilvus[milvus_lite]
```
Run the example script, which showcases adding, searching with/without filters in MilvusLite vector store:
```bash
python milvuslite_store.py
```
> **Note:** The script creates `.db` files in the current directory. You can delete them after testing.
## Usage
### Initialize Store
```python
from agentscope.rag import MilvusLiteStore
store = MilvusLiteStore(
uri="./milvus_test.db",
collection_name="test_collection",
dimensions=768, # Match your embedding model
distance="COSINE", # COSINE, L2, or IP
)
```
### Add Documents
```python
from agentscope.rag import Document, DocMetadata
from agentscope.message import TextBlock
doc = Document(
metadata=DocMetadata(
content=TextBlock(type="text", text="Your document text"),
doc_id="doc_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, ...], # Your embedding vector
)
await store.add([doc])
```
### Search
```python
results = await store.search(
query_embedding=[0.15, 0.25, ...],
limit=5,
score_threshold=0.9, # Optional
filter='doc_id like "prefix%"', # Optional
)
```
### Delete
```python
await store.delete(filter_expr='doc_id == "doc_1"')
```
## Distance Metrics
| Metric | Description | Best For |
|--------|-------------|----------|
| **COSINE** | Cosine similarity | Text embeddings (recommended) |
| **L2** | Euclidean distance | Spatial data |
| **IP** | Inner Product | Recommendation systems |
## Filter Expressions
```python
# Exact match
filter='doc_id == "doc_1"'
# Pattern matching
filter='doc_id like "prefix%"'
# Numeric and logical operators
filter='chunk_id >= 0 and total_chunks > 1'
```
## Advanced Usage
### Access Underlying Client
```python
client = store.get_client()
stats = client.get_collection_stats(collection_name="test_collection")
```
### Document Metadata
- `content`: Text content (TextBlock)
- `doc_id`: Unique document identifier
- `chunk_id`: Chunk position (0-indexed)
- `total_chunks`: Total chunks in document
## FAQ
**What embedding dimension should I use?**
Match your embedding model's output dimension (e.g., 768 for BERT, 1536 for OpenAI ada-002).
**Can I change the distance metric after creation?**
No, create a new collection with the desired metric.
**How do I delete the database?**
Delete the `.db` file specified in the `uri` parameter.
**Is this suitable for production?**
MilvusLite works well for development and small-scale applications. For production at scale, consider Milvus standalone or cluster mode.
## References
- [Milvus Documentation](https://milvus.io/docs)
- [AgentScope RAG Tutorial](https://doc.agentscope.io/tutorial/task_rag.html)

View File

@@ -0,0 +1,327 @@
# -*- coding: utf-8 -*-
"""Example of using MilvusLiteStore in AgentScope RAG system."""
import asyncio
from agentscope.rag import (
MilvusLiteStore,
Document,
DocMetadata,
)
from agentscope.message import TextBlock
async def example_basic_operations() -> None:
"""The example of basic CRUD operations with MilvusLiteStore."""
print("\n" + "=" * 60)
print("Test 1: Basic CRUD Operations")
print("=" * 60)
# Initialize MilvusLiteStore with a local file
store = MilvusLiteStore(
uri="./milvus_test.db",
collection_name="test_collection",
dimensions=4, # Small dimension for testing
distance="COSINE",
)
print("✓ MilvusLiteStore initialized")
# Create test documents with embeddings
test_docs = [
Document(
metadata=DocMetadata(
content=TextBlock(
text="Artificial Intelligence is the future",
),
doc_id="doc_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Machine Learning is a subset of AI"),
doc_id="doc_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Deep Learning uses neural networks"),
doc_id="doc_3",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
]
# Test add operation
await store.add(test_docs)
print(f"✓ Added {len(test_docs)} documents to the store")
# Test search operation
query_embedding = [0.15, 0.25, 0.35, 0.45]
results = await store.search(
query_embedding=query_embedding,
limit=2,
)
print(f"\n✓ Search completed, found {len(results)} results:")
for i, result in enumerate(results, 1):
print(f" {i}. Score: {result.score:.4f}")
print(f" Content: {result.metadata.content}")
print(f" Doc ID: {result.metadata.doc_id}")
# Test search with score threshold
results_filtered = await store.search(
query_embedding=query_embedding,
limit=5,
score_threshold=0.9,
)
print(f"\n✓ Search with threshold (>0.9): {len(results_filtered)} results")
# Test delete operation
# Note: We need to use filter expression to delete by doc_id
await store.delete(filter='doc_id == "doc_2"')
print("\n✓ Deleted document with doc_id='doc_2'")
# Verify deletion
results_after_delete = await store.search(
query_embedding=query_embedding,
limit=5,
)
print(f"✓ After deletion: {len(results_after_delete)} documents remain")
# Get client for advanced operations
client = store.get_client()
print(f"\n✓ Got MilvusClient: {type(client).__name__}")
async def example_filter_search() -> None:
"""The example of search with metadata filtering."""
print("\n" + "=" * 60)
print("Test 2: Search with Metadata Filtering")
print("=" * 60)
store = MilvusLiteStore(
uri="./milvus_filter_test.db",
collection_name="filter_collection",
dimensions=4,
distance="COSINE",
)
# Create documents with different categories
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(text="Python is a programming language"),
doc_id="prog_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(
text="Java is used for enterprise applications",
),
doc_id="prog_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Neural networks are used in AI"),
doc_id="ai_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Deep learning requires GPUs"),
doc_id="ai_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.4, 0.5, 0.6, 0.7],
),
]
await store.add(docs)
print(f"✓ Added {len(docs)} documents with different doc_id prefixes")
# Search without filter
query_embedding = [0.25, 0.35, 0.45, 0.55]
all_results = await store.search(
query_embedding=query_embedding,
limit=4,
)
print(f"\n✓ Search without filter: {len(all_results)} results")
for i, result in enumerate(all_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
# Search with filter for programming docs
prog_results = await store.search(
query_embedding=query_embedding,
limit=4,
filter='doc_id like "prog%"',
)
filter_msg = "doc_id like 'prog%'"
print(f"\n✓ Search with filter ({filter_msg}): {len(prog_results)}")
for i, result in enumerate(prog_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
# Search with filter for AI docs
ai_results = await store.search(
query_embedding=query_embedding,
limit=4,
filter='doc_id like "ai%"',
)
filter_msg = "doc_id like 'ai%'"
print(f"\n✓ Search with filter ({filter_msg}): {len(ai_results)}")
for i, result in enumerate(ai_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
async def example_multiple_chunks() -> None:
"""The example of documents with multiple chunks."""
print("\n" + "=" * 60)
print("Test 3: Documents with Multiple Chunks")
print("=" * 60)
store = MilvusLiteStore(
uri="./milvus_chunks_test.db",
collection_name="chunks_collection",
dimensions=4,
distance="COSINE",
)
# Create a document split into multiple chunks
chunks = [
Document(
metadata=DocMetadata(
content=TextBlock(text="Chapter 1: Introduction to AI"),
doc_id="book_1",
chunk_id=0,
total_chunks=3,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Chapter 2: Machine Learning Basics"),
doc_id="book_1",
chunk_id=1,
total_chunks=3,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Chapter 3: Deep Learning Advanced"),
doc_id="book_1",
chunk_id=2,
total_chunks=3,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
]
await store.add(chunks)
print(f"✓ Added document with {len(chunks)} chunks")
# Search and verify chunk information
query_embedding = [0.2, 0.3, 0.4, 0.5]
results = await store.search(
query_embedding=query_embedding,
limit=3,
)
print("\n✓ Search results for multi-chunk document:")
for i, result in enumerate(results, 1):
chunk_info = (
f"{result.metadata.chunk_id}/{result.metadata.total_chunks}"
)
print(f" {i}. Chunk {chunk_info}")
print(f" Content: {result.metadata.content}")
print(f" Score: {result.score:.4f}")
async def example_distance_metrics() -> None:
"""The example of different distance metrics."""
print("\n" + "=" * 60)
print("Test 4: Different Distance Metrics")
print("=" * 60)
# Test with different metrics
metrics = ["COSINE", "L2", "IP"]
for metric in metrics:
print(f"\n--- Testing {metric} metric ---")
store = MilvusLiteStore(
uri=f"./milvus_{metric.lower()}_test.db",
collection_name=f"{metric.lower()}_collection",
dimensions=4,
distance=metric,
)
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(text=f"Test doc for {metric}"),
doc_id=f"doc_{metric}_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
]
await store.add(docs)
results = await store.search(
query_embedding=[0.1, 0.2, 0.3, 0.4],
limit=1,
)
print(f"{metric} metric: Score = {results[0].score:.4f}")
async def main() -> None:
"""Run all example."""
print("\n" + "=" * 60)
print("MilvusLiteStore Comprehensive Test Suite")
print("=" * 60)
try:
await example_basic_operations()
await example_filter_search()
await example_multiple_chunks()
await example_distance_metrics()
print("\n" + "=" * 60)
print("✓ All tests completed successfully!")
print("=" * 60)
except Exception as e:
print(f"\n✗ Test failed with error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,211 @@
# MongoDB Vector Store
This example demonstrates how to use **MongoDBStore** for vector storage and semantic search in AgentScope using MongoDB's Vector Search capabilities.
It includes comprehensive test scenarios covering CRUD operations, metadata filtering, document chunking, and distance metrics.
### Quick Start
Install agentscope first, and then the MongoDB dependency:
```bash
pip install pymongo
```
**Important:** Before running the example, you need to set the `MONGODB_HOST`
environment variable with your MongoDB connection string:
```bash
# For local MongoDB
export MONGODB_HOST="mongodb://localhost:27017/?directConnection=true"
# For MongoDB Atlas (replace with your connection string)
# export MONGODB_HOST=${YOUR_MONGODB_HOST}
```
Run the example script, which showcases adding, searching, and deleting in MongoDB vector store:
```bash
python main.py
```
> **Note:** The script connects to MongoDB Atlas or local MongoDB instance. Make sure you have a valid MongoDB connection string.
## Prerequisites
- Confirm your MongoDB instance supports Vector Search functionality
- Valid MongoDB connection string (local or Atlas)
## Usage
### Initialize Store
```python
from agentscope.rag import MongoDBStore
# For MongoDB Atlas
store = MongoDBStore(
host="mongodb+srv://username:password@cluster.mongodb.net/",
db_name="test_db",
collection_name="test_collection",
dimensions=768, # Match your embedding model
distance="cosine", # cosine, euclidean, or dotProduct
)
# For local MongoDB
store = MongoDBStore(
host="mongodb://localhost:27017/?directConnection=true",
db_name="test_db",
collection_name="test_collection",
dimensions=768,
distance="cosine",
)
# To enable filtering in search, specify filter_fields:
store = MongoDBStore(
host="mongodb://localhost:27017/?directConnection=true",
db_name="test_db",
collection_name="test_collection",
dimensions=768,
distance="cosine",
filter_fields=["payload.doc_id", "payload.chunk_id"], # Fields for filtering
)
# No manual initialization needed - everything is automatic!
# Database, collection, and vector search index are created automatically
# when you first call add() or search()
```
### Add Documents
```python
from agentscope.rag import Document, DocMetadata
from agentscope.message import TextBlock
doc = Document(
metadata=DocMetadata(
content=TextBlock(type="text", text="Your document text"),
doc_id="doc_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, ...], # Your embedding vector
)
await store.add([doc])
```
### Search
```python
results = await store.search(
query_embedding=[0.15, 0.25, ...],
limit=5,
score_threshold=0.9, # Optional
filter={"payload.doc_id": {"$in": ["doc_1", "doc_2"]}}, # Optional filter
)
# Note:
# - To use filter, the field must be declared in filter_fields when creating store
# - MongoDB $vectorSearch filter supports: $gt, $gte, $lt, $lte,
# $eq, $ne, $in, $nin, $exists, $not (NOT $regex)
```
### Delete
```python
# Delete by document IDs (no initialization needed)
await store.delete(ids=["doc_1", "doc_2"])
# Delete entire collection (use with caution)
await store.delete_collection()
# Delete entire database (use with caution)
await store.delete_database()
```
## Distance Metrics
| Metric | Description | Best For |
|--------|-------------|----------|
| **cosine** | Cosine similarity | Text embeddings (recommended) |
| **euclidean** | Euclidean distance | Spatial data |
| **dotProduct** | Inner Product | Recommendation systems |
## Advanced Usage
### Access Underlying Client
```python
client = store.get_client()
# Use MongoDB client for advanced operations
stats = await client[store.db_name].command("collStats", store.collection_name)
```
### Document Metadata
- `content`: Text content (TextBlock)
- `doc_id`: Unique document identifier
- `chunk_id`: Chunk position (0-indexed)
- `total_chunks`: Total chunks in document
### Vector Search Index
MongoDBStore automatically creates vector search indexes with the following configuration:
```python
{
"fields": [
{
"type": "vector",
"path": "vector",
"similarity": "cosine", # or euclidean, dotProduct
"numDimensions": 768
}
]
}
```
## Connection Examples
### MongoDB Atlas
```python
store = MongoDBStore(
host="<YOUR_MONGO_ATLAS_CONNECTION_STRING>",
db_name="production_db",
collection_name="documents",
dimensions=1536,
distance="cosine",
)
```
### Local MongoDB
#### Without Authentication
```python
store = MongoDBStore(
host="mongodb://localhost:27017?directConnection=true",
db_name="local_db",
collection_name="test_collection",
dimensions=768,
distance="cosine",
)
```
#### With Authentication
```python
store = MongoDBStore(
host="mongodb://user:pass@localhost:27017/?directConnection=true",
db_name="test_db",
collection_name="test_collection",
dimensions=768,
distance="cosine",
)
```
## References
- [MongoDB Vector Search Documentation](https://www.mongodb.com/docs/atlas/atlas-search/vector-search/)
- [MongoDB Atlas Documentation](https://www.mongodb.com/docs/atlas/)
- [AgentScope RAG Tutorial](https://doc.agentscope.io/tutorial/task_rag.html)

View File

@@ -0,0 +1,351 @@
# -*- coding: utf-8 -*-
"""Example of using MongoDBStore in AgentScope RAG system."""
import asyncio
import os
from agentscope.rag import (
MongoDBStore,
Document,
DocMetadata,
)
from agentscope.message import TextBlock
async def example_basic_operations() -> None:
"""The example of basic CRUD operations with MongoDBStore."""
print("\n" + "=" * 60)
print("Test 1: Basic CRUD Operations")
print("=" * 60)
# Initialize MongoDBStore with MongoDB connection
store = MongoDBStore(
host=os.getenv("MONGODB_HOST"),
db_name="test_db",
collection_name="test_collection",
dimensions=4, # Small dimension for testing
distance="cosine",
)
print("✓ MongoDBStore initialized")
# Create test documents with embeddings
test_docs = [
Document(
metadata=DocMetadata(
content=TextBlock(
text="Artificial Intelligence is the future",
),
doc_id="doc_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Machine Learning is a subset of AI"),
doc_id="doc_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Deep Learning uses neural networks"),
doc_id="doc_3",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
]
# Test add operation (automatically creates database, collection,
# and index)
await store.add(test_docs)
print(f"✓ Added {len(test_docs)} documents to the store")
# Test search operation (automatically waits for index to be ready)
query_embedding = [0.15, 0.25, 0.35, 0.45]
results = await store.search(
query_embedding=query_embedding,
limit=2,
)
print(f"\n✓ Search completed, found {len(results)} results:")
for i, result in enumerate(results, 1):
print(f" {i}. Score: {result.score:.4f}")
print(f" Content: {result.metadata.content}")
print(f" Doc ID: {result.metadata.doc_id}")
# Test search with score threshold (also waits for index if needed)
results_filtered = await store.search(
query_embedding=query_embedding,
limit=5,
score_threshold=0.3,
)
print(f"\n✓ Search with threshold (>0.3): {len(results_filtered)} results")
# Test delete operation (no initialization needed)
# Note: MongoDBStore uses ids parameter for deletion
await store.delete(ids=["doc_2", "doc_3", "doc_1"])
print("\n✓ Deleted documents with specified doc_ids")
# Verify deletion (search will wait for index if needed)
results_after_delete = await store.search(
query_embedding=query_embedding,
limit=5,
)
print(f"✓ After deletion: {len(results_after_delete)} documents remain")
# Get client for advanced operations
client = store.get_client()
print(f"\n✓ Got MongoDB Client: {type(client).__name__}")
await store.close()
async def example_filter_search() -> None:
"""The example of search with metadata filtering."""
print("\n" + "=" * 60)
print("Test 2: Search with Metadata Filtering")
print("=" * 60)
# To use filter in search, specify filter_fields when creating the store.
# These fields will be indexed for filtering in $vectorSearch.
store = MongoDBStore(
host=os.getenv("MONGODB_HOST"),
db_name="filter_test_db",
collection_name="filter_collection",
dimensions=4,
distance="cosine",
filter_fields=["payload.doc_id"], # Enable filtering on doc_id
)
# Create documents with different categories
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(text="Python is a programming language"),
doc_id="prog_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(
text="Java is used for enterprise applications",
),
doc_id="prog_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Neural networks are used in AI"),
doc_id="ai_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Deep learning requires GPUs"),
doc_id="ai_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.4, 0.5, 0.6, 0.7],
),
]
# Add documents (automatically creates database, collection, and index)
await store.add(docs)
print(f"✓ Added {len(docs)} documents with different doc_id prefixes")
# Search without filter (automatically waits for index if needed)
query_embedding = [0.25, 0.35, 0.45, 0.55]
all_results = await store.search(
query_embedding=query_embedding,
limit=4,
)
print(f"\n✓ Search without filter: {len(all_results)} results")
for i, result in enumerate(all_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
# Search with filter for programming docs
# Note: doc_id is stored in payload.doc_id in MongoDB documents
# MongoDB $vectorSearch filter supports: $gt, $gte, $lt, $lte, $eq, $ne,
# $in, $nin, $exists, $not (NOT $regex)
prog_results = await store.search(
query_embedding=query_embedding,
limit=4,
filter={"payload.doc_id": {"$in": ["prog_1", "prog_2"]}},
)
print(f"\n✓ Search with filter (prog docs): {len(prog_results)} results")
for i, result in enumerate(prog_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
# Search with filter for AI docs
ai_results = await store.search(
query_embedding=query_embedding,
limit=4,
filter={"payload.doc_id": {"$in": ["ai_1", "ai_2"]}},
)
print(f"\n✓ Search with filter (ai docs): {len(ai_results)} results")
for i, result in enumerate(ai_results, 1):
doc_id = result.metadata.doc_id
score = result.score
print(f" {i}. Doc ID: {doc_id}, Score: {score:.4f}")
await store.close()
async def example_multiple_chunks() -> None:
"""The example of documents with multiple chunks."""
print("\n" + "=" * 60)
print("Test 3: Documents with Multiple Chunks")
print("=" * 60)
store = MongoDBStore(
host=os.getenv("MONGODB_HOST"),
db_name="chunks_test_db",
collection_name="chunks_collection",
dimensions=4,
distance="cosine",
)
# Create a document split into multiple chunks
chunks = [
Document(
metadata=DocMetadata(
content=TextBlock(text="Chapter 1: Introduction to AI"),
doc_id="book_1",
chunk_id=0,
total_chunks=3,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Chapter 2: Machine Learning Basics"),
doc_id="book_1",
chunk_id=1,
total_chunks=3,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(text="Chapter 3: Deep Learning Advanced"),
doc_id="book_1",
chunk_id=2,
total_chunks=3,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
]
# Add chunks (automatically creates database, collection, and index)
await store.add(chunks)
print(f"✓ Added document with {len(chunks)} chunks")
# Search and verify chunk information (automatically waits for index if
# needed)
query_embedding = [0.2, 0.3, 0.4, 0.5]
results = await store.search(
query_embedding=query_embedding,
limit=3,
)
print("\n✓ Search results for multi-chunk document:")
for i, result in enumerate(results, 1):
chunk_info = (
f"{result.metadata.chunk_id}/{result.metadata.total_chunks}"
)
print(f" {i}. Chunk {chunk_info}")
print(f" Content: {result.metadata.content}")
print(f" Score: {result.score:.4f}")
await store.close()
async def example_distance_metrics() -> None:
"""The example of different distance metrics."""
print("\n" + "=" * 60)
print("Test 4: Different Distance Metrics")
print("=" * 60)
# Test with different metrics
metrics = ["cosine", "euclidean", "dotProduct"]
for metric in metrics:
print(f"\n--- Testing {metric} metric ---")
store = MongoDBStore(
host=os.getenv("MONGODB_HOST"),
db_name=f"{metric}_test_db",
collection_name=f"{metric}_collection",
dimensions=4,
distance=metric,
)
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(text=f"Test doc for {metric}"),
doc_id=f"doc_{metric}_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
]
# Add and search (automatically creates database/collection/index
# and waits for index)
await store.add(docs)
results = await store.search(
query_embedding=[0.1, 0.2, 0.3, 0.4],
limit=1,
)
print(f"{metric} metric: Score = {results[0].score:.4f}")
await store.close()
async def main() -> None:
"""Run all example."""
print("\n" + "=" * 60)
print("MongoDBStore Comprehensive Test Suite")
print("=" * 60)
try:
# await example_basic_operations()
# await example_filter_search()
# await example_multiple_chunks()
await example_distance_metrics()
print("\n" + "=" * 60)
print("✓ All tests completed successfully!")
print("=" * 60)
except Exception as e:
print(f"\n✗ Test failed with error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,164 @@
# OceanBase Vector Store
This example demonstrates how to use **OceanBaseStore** for vector storage and semantic search in AgentScope.
It includes CRUD operations, metadata filtering, document chunking, and distance metric tests.
### Quick Start
Install dependencies (including `pyobvector`):
```bash
pip install -e .[full]
```
Start seekdb (a minimal OceanBase-compatible instance):
```bash
docker run -d -p 2881:2881 oceanbase/seekdb
```
Run the example script:
```bash
python main.py
```
> **Note:** The script defaults to `127.0.0.1:2881`, user `root`, database `test`.
> If you use a multi-tenant OceanBase account (e.g., `root@test`), override via environment variables.
## Usage
### Initialize Store
```python
from agentscope.rag import OceanBaseStore
store = OceanBaseStore(
collection_name="test_collection",
dimensions=768,
distance="COSINE",
uri="127.0.0.1:2881",
user="root",
password="",
db_name="test",
)
```
### Add Documents
```python
from agentscope.rag import Document, DocMetadata
from agentscope.message import TextBlock
doc = Document(
metadata=DocMetadata(
content=TextBlock(type="text", text="Your document text"),
doc_id="doc_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3],
)
await store.add([doc])
```
### Search
```python
results = await store.search(
query_embedding=[0.1, 0.2, 0.3],
limit=5,
score_threshold=0.9,
)
```
### Filter Search
```python
client = store.get_client()
table = client.load_table(collection_name="test_collection")
results = await store.search(
query_embedding=[0.1, 0.2, 0.3],
limit=5,
flter=[table.c["doc_id"].like("doc%")],
)
```
> Note: The parameter name is `flter` (missing the "i") to avoid clashing with
> Python's built-in `filter` and follows the underlying library's convention.
### Delete
```python
client = store.get_client()
table = client.load_table(collection_name="test_collection")
await store.delete(where=[table.c["doc_id"] == "doc_1"])
```
## Distance Metrics
| Metric | Description | Best For |
|--------|-------------|----------|
| **COSINE** | Cosine similarity | Text embeddings (recommended) |
| **L2** | Euclidean distance | Spatial data |
| **IP** | Inner product | Recommendation systems |
## Filter Expressions
Build filters using SQLAlchemy expressions and pass them via `flter`:
```python
table = store.get_client().load_table("test_collection")
filters = [
table.c["doc_id"] == "doc_1",
table.c["doc_id"].like("prefix%"),
table.c["chunk_id"] >= 0,
]
```
## Advanced Usage
### Access Underlying Client
```python
client = store.get_client()
stats = client.get_collection_stats(collection_name="test_collection")
```
### Document Metadata
- `content`: Text content (TextBlock)
- `doc_id`: Unique document identifier
- `chunk_id`: Chunk position (0-indexed)
- `total_chunks`: Total chunks in document
## FAQ
**What embedding dimension should I use?**
Match your embedding model's output dimension (e.g., 768 for BERT, 1536 for OpenAI ada-002).
**Can I change the distance metric after creation?**
No, create a new collection with the desired metric.
**How do I clean up test data?**
Drop the collection via the underlying client or remove the seekdb container volume.
## Environment Variables
The script supports the following environment variables to override connection settings:
```bash
export OCEANBASE_URI="127.0.0.1:2881"
export OCEANBASE_USER="root"
export OCEANBASE_PASSWORD=""
export OCEANBASE_DB="test"
```
## References
- [OceanBase Vector Store](https://github.com/oceanbase/pyobvector)
- [AgentScope RAG Tutorial](https://doc.agentscope.io/tutorial/task_rag.html)

View File

@@ -0,0 +1,350 @@
# -*- coding: utf-8 -*-
"""Example of using OceanBaseStore in AgentScope RAG system."""
import asyncio
import os
from agentscope.rag import (
OceanBaseStore,
Document,
DocMetadata,
)
from agentscope.message import TextBlock
def _create_store(
collection_name: str,
dimensions: int = 4,
distance: str = "COSINE",
) -> OceanBaseStore:
return OceanBaseStore(
collection_name=collection_name,
dimensions=dimensions,
distance=distance,
uri=os.getenv("OCEANBASE_URI", "127.0.0.1:2881"),
user=os.getenv("OCEANBASE_USER", "root"),
password=os.getenv("OCEANBASE_PASSWORD", ""),
db_name=os.getenv("OCEANBASE_DB", "test"),
)
async def example_basic_operations() -> None:
"""The example of basic CRUD operations with OceanBaseStore."""
print("\n" + "=" * 60)
print("Test 1: Basic CRUD Operations")
print("=" * 60)
store = _create_store(collection_name="ob_basic_collection")
store.get_client().drop_collection("ob_basic_collection")
print("✓ OceanBaseStore initialized")
test_docs = [
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Artificial Intelligence is the future",
),
doc_id="doc_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Machine Learning is a subset of AI",
),
doc_id="doc_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Deep Learning uses neural networks",
),
doc_id="doc_3",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
]
await store.add(test_docs)
print(f"✓ Added {len(test_docs)} documents to the store")
query_embedding = [0.15, 0.25, 0.35, 0.45]
results = await store.search(
query_embedding=query_embedding,
limit=2,
)
print(f"\n✓ Search completed, found {len(results)} results:")
for i, result in enumerate(results, 1):
print(f" {i}. Score: {result.score:.4f}")
print(f" Content: {result.metadata.content}")
print(f" Doc ID: {result.metadata.doc_id}")
results_filtered = await store.search(
query_embedding=query_embedding,
limit=5,
score_threshold=0.9,
)
print(f"\n✓ Search with threshold (>0.9): {len(results_filtered)} results")
client = store.get_client()
table = client.load_table(collection_name="ob_basic_collection")
await store.delete(where=[table.c["doc_id"] == "doc_2"])
print("\n✓ Deleted document with doc_id='doc_2'")
results_after_delete = await store.search(
query_embedding=query_embedding,
limit=5,
)
print(f"✓ After deletion: {len(results_after_delete)} documents remain")
print(f"\n✓ Got MilvusLikeClient: {type(client).__name__}")
async def example_filter_search() -> None:
"""The example of search with metadata filtering."""
print("\n" + "=" * 60)
print("Test 2: Search with Metadata Filtering")
print("=" * 60)
store = _create_store(collection_name="ob_filter_collection")
client = store.get_client()
client.drop_collection("ob_filter_collection")
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Python is a programming language",
),
doc_id="prog_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Java is used for enterprise applications",
),
doc_id="prog_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Neural networks are used in AI",
),
doc_id="ai_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Deep learning requires GPUs",
),
doc_id="ai_2",
chunk_id=0,
total_chunks=1,
),
embedding=[0.4, 0.5, 0.6, 0.7],
),
]
await store.add(docs)
print(f"✓ Added {len(docs)} documents with different doc_id prefixes")
query_embedding = [0.25, 0.35, 0.45, 0.55]
all_results = await store.search(
query_embedding=query_embedding,
limit=4,
)
print(f"\n✓ Search without filter: {len(all_results)} results")
for i, result in enumerate(all_results, 1):
print(
f" {i}. Doc ID: {result.metadata.doc_id}, "
f"Score: {result.score:.4f}",
)
table = client.load_table(collection_name="ob_filter_collection")
prog_results = await store.search(
query_embedding=query_embedding,
limit=4,
flter=[table.c["doc_id"].like("prog%")],
)
print("\n✓ Search with filter (doc_id like 'prog%'):")
for i, result in enumerate(prog_results, 1):
print(
f" {i}. Doc ID: {result.metadata.doc_id}, "
f"Score: {result.score:.4f}",
)
ai_results = await store.search(
query_embedding=query_embedding,
limit=4,
flter=[table.c["doc_id"].like("ai%")],
)
print("\n✓ Search with filter (doc_id like 'ai%'):")
for i, result in enumerate(ai_results, 1):
print(
f" {i}. Doc ID: {result.metadata.doc_id}, "
f"Score: {result.score:.4f}",
)
async def example_multiple_chunks() -> None:
"""The example of documents with multiple chunks."""
print("\n" + "=" * 60)
print("Test 3: Documents with Multiple Chunks")
print("=" * 60)
store = _create_store(collection_name="ob_chunks_collection")
store.get_client().drop_collection("ob_chunks_collection")
chunks = [
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Chapter 1: Introduction to AI",
),
doc_id="book_1",
chunk_id=0,
total_chunks=3,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Chapter 2: Machine Learning Basics",
),
doc_id="book_1",
chunk_id=1,
total_chunks=3,
),
embedding=[0.2, 0.3, 0.4, 0.5],
),
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text="Chapter 3: Deep Learning Advanced",
),
doc_id="book_1",
chunk_id=2,
total_chunks=3,
),
embedding=[0.3, 0.4, 0.5, 0.6],
),
]
await store.add(chunks)
print(f"✓ Added document with {len(chunks)} chunks")
query_embedding = [0.2, 0.3, 0.4, 0.5]
results = await store.search(
query_embedding=query_embedding,
limit=3,
)
print("\n✓ Search results for multi-chunk document:")
for i, result in enumerate(results, 1):
chunk_info = (
f"{result.metadata.chunk_id}/{result.metadata.total_chunks}"
)
print(f" {i}. Chunk {chunk_info}")
print(f" Content: {result.metadata.content}")
print(f" Score: {result.score:.4f}")
async def example_distance_metrics() -> None:
"""The example of different distance metrics."""
print("\n" + "=" * 60)
print("Test 4: Different Distance Metrics")
print("=" * 60)
metrics = ["COSINE", "L2", "IP"]
for metric in metrics:
print(f"\n--- Testing {metric} metric ---")
collection_name = f"ob_{metric}_collection"
store = _create_store(
collection_name=collection_name,
distance=metric,
)
store.get_client().drop_collection(collection_name)
docs = [
Document(
metadata=DocMetadata(
content=TextBlock(
type="text",
text=f"Test doc for {metric}",
),
doc_id=f"doc_{metric}_1",
chunk_id=0,
total_chunks=1,
),
embedding=[0.1, 0.2, 0.3, 0.4],
),
]
await store.add(docs)
results = await store.search(
query_embedding=[0.1, 0.2, 0.3, 0.4],
limit=1,
)
print(f"{metric} metric: Score = {results[0].score:.4f}")
async def main() -> None:
"""Run all example."""
print("\n" + "=" * 60)
print("OceanBaseStore Comprehensive Test Suite")
print("=" * 60)
try:
await example_basic_operations()
await example_filter_search()
await example_multiple_chunks()
await example_distance_metrics()
print("\n" + "=" * 60)
print("✓ All tests completed successfully!")
print("=" * 60)
except Exception as e:
print(f"\n✗ Test failed with error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(main())