mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
Dev/readme (#137)
* refactor(memory): rename copaw to reme and update module structure * chore(release): bump version to 0.3.0.6b1 * refactor(docs): update README and ReMeLight implementation * refactor(reme): rename ReMeCopaw to ReMeLight and remove CLI module * docs(readme): update Chinese documentation with enhanced structure and content * docs(readme): update Chinese documentation for context compression * docs(readme): update context compression section header * docs(readme): update documentation with installation and usage guide * docs(readme): update Chinese documentation table * chore(deps): update dependency extras configuration * chore(test): remove deprecated test files for message operations * docs(readme): update documentation with ReMeLight implementation changes
This commit is contained in:
parent
3a4c1cae93
commit
9e2e98ef40
47 changed files with 745 additions and 1480 deletions
476
README.md
476
README.md
|
|
@ -17,144 +17,59 @@
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>A memory management toolkit for AI agents — Remember Me, Refine Me.</strong><br>
|
||||
<strong>Memory Management Toolkit for AI Agents, Remember Me, Refine Me.</strong><br>
|
||||
</p>
|
||||
|
||||
> For legacy versions, see [0.2.x Documentation](docs/README_0_2_x.md)
|
||||
> For legacy versions, please refer to [0.2.x Documentation](docs/README_0_2_x.md)
|
||||
|
||||
---
|
||||
|
||||
🧠 ReMe is a **memory management framework** built for **AI agents**, offering both **file-based** and **vector-based**
|
||||
memory systems.
|
||||
🧠 ReMe is a memory management framework built specifically for **AI Agents**, offering both file-based and vector-based memory systems.
|
||||
|
||||
It addresses two core problems of agent memory: **limited context windows** (early information gets truncated or lost
|
||||
during
|
||||
long conversations) and **stateless sessions** (new conversations cannot inherit history and always start from scratch).
|
||||
It addresses two core memory challenges for agents: **Limited context window** (early information gets truncated or lost in long conversations), and **Stateless sessions** (new conversations cannot inherit history, starting from scratch every time).
|
||||
|
||||
ReMe gives agents **real memory** — old conversations are automatically condensed, important information is persisted,
|
||||
and the next conversation can recall it automatically.
|
||||
ReMe gives agents **true memory capability** — old conversations are automatically condensed, important information is persistently stored, and relevant context is automatically recalled in future conversations.
|
||||
|
||||
---
|
||||
|
||||
## 📁 File-Based CoPaw Memory System
|
||||
## 📁 File-Based Memory System
|
||||
|
||||
> Memory as files, files as memory
|
||||
> Memory as Files, Files as Memory
|
||||
|
||||
Treat **memory as files** — readable, editable, and portable. [CoPaw](https://github.com/agentscope-ai/CoPaw)
|
||||
integrates this memory system
|
||||
through [MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py),
|
||||
which inherits `ReMeCopaw` and exposes memory management capabilities.
|
||||
Treat **memory as files** — readable, editable, and copyable. [CoPaw](https://github.com/agentscope-ai/CoPaw)'s memory system inherits from `ReMeLight`, implementing memory management capabilities.
|
||||
|
||||
| Traditional Memory Systems | File-Based ReMe |
|
||||
|----------------------------|--------------------|
|
||||
| 🗄️ Database storage | 📝 Markdown files |
|
||||
| 🔒 Opaque | 👀 Read anytime |
|
||||
| ❌ Hard to modify | ✏️ Edit directly |
|
||||
| Traditional Memory System | File Based ReMe |
|
||||
|---------------------------|-------------------|
|
||||
| 🗄️ Database storage | 📝 Markdown files |
|
||||
| 🔒 Invisible | 👀 Always readable |
|
||||
| ❌ Hard to modify | ✏️ Direct editing |
|
||||
| 🚫 Hard to migrate | 📦 Copy to migrate |
|
||||
|
||||
```
|
||||
working_dir/
|
||||
├── MEMORY.md # Long-term memory: user preferences, project config, etc.
|
||||
├── MEMORY.md # Long-term memory: user preferences, project configs, etc.
|
||||
├── memory/
|
||||
│ └── YYYY-MM-DD.md # Daily summary logs: written automatically after conversation ends
|
||||
└── tool_result/ # Cache for oversized tool outputs (auto-managed, auto-cleaned when expired)
|
||||
│ └── YYYY-MM-DD.md # Daily summary logs: auto-written after conversations
|
||||
└── tool_result/ # Long tool output cache (auto-managed, auto-cleanup on expiry)
|
||||
└── <uuid>.txt
|
||||
```
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
[ReMeCopaw](reme/reme_copaw.py) is the core class of this memory system, providing complete memory management
|
||||
capabilities for AI Agents:
|
||||
[ReMeLight](reme/reme_light.py) is the core class of this memory system, providing complete memory management capabilities for AI Agents:
|
||||
|
||||
| Method | Function | Key Components |
|
||||
|--------------------------|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `start` | 🚀 Start memory system | Initialize file store, file watcher, Embedding cache; clean up expired tool result files |
|
||||
| `close` | 📕 Close and clean up | Clean tool result files, stop file watcher, save Embedding cache |
|
||||
| `compact_memory` | 📦 Compact history to summary | [Compactor](reme/memory/file_based_copaw/compactor.py) — ReActAgent generates structured context checkpoint |
|
||||
| `summary_memory` | 📝 Write important memory to files | [Summarizer](reme/memory/file_based_copaw/summarizer.py) — ReActAgent + file tools (read / write / edit) |
|
||||
| `compact_tool_result` | ✂️ Compact oversized tool output | [ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) — Truncate and save to `tool_result/`, keep file reference in message |
|
||||
| `add_async_summary_task` | ⚡ Submit background summary task | `asyncio.create_task`, summary doesn't block main conversation flow |
|
||||
| `await_summary_tasks` | ⏳ Wait for background tasks | Collect results from all background summary tasks, call before closing to ensure writes complete |
|
||||
| `memory_search` | 🔍 Semantic memory search | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — Vector + BM25 hybrid retrieval |
|
||||
| `get_in_memory_memory` | 🗂️ Create in-memory instance | [CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) — Token-aware memory management, supports compression summary and state serialization |
|
||||
| `update_params` | ⚙️ Update runtime parameters | Adjust `max_input_length`, `memory_compact_ratio`, `language` at runtime |
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Vector-Based ReMe
|
||||
|
||||
[ReMe Vector Based](reme/reme.py) is the core class for the vector-based memory system, supporting unified management of
|
||||
three memory types:
|
||||
|
||||
| Memory Type | Purpose | Usage Context |
|
||||
|------------------------------|-----------------------------------------------------|---------------|
|
||||
| **Personal memory** | User preferences, habits | `user_name` |
|
||||
| **Task / procedural memory** | Task execution experience, success/failure patterns | `task_name` |
|
||||
| **Tool memory** | Tool usage experience, parameter tuning | `tool_name` |
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
| Method | Function | Description |
|
||||
|--------------------|---------------------|-----------------------------------------------------------|
|
||||
| `summarize_memory` | 🧠 Summarize memory | Automatically extract and store memory from conversations |
|
||||
| `retrieve_memory` | 🔍 Retrieve memory | Retrieve relevant memory by query |
|
||||
| `add_memory` | ➕ Add memory | Manually add memory to vector store |
|
||||
| `get_memory` | 📖 Get memory | Fetch a single memory by ID |
|
||||
| `update_memory` | ✏️ Update memory | Update content or metadata of existing memory |
|
||||
| `delete_memory` | 🗑️ Delete memory | Delete specified memory |
|
||||
| `list_memory` | 📋 List memory | List memories with filtering and sorting |
|
||||
|
||||
---
|
||||
|
||||
## 💻 ReMeCli: Terminal Assistant with File-Based Memory
|
||||
|
||||
<table border="0" cellspacing="0" cellpadding="0" style="border: none;">
|
||||
<tr style="border: none;">
|
||||
<td width="10%" style="border: none; vertical-align: middle; text-align: center;">
|
||||
<strong>马<br>上<br>有<br>钱</strong>
|
||||
</td>
|
||||
<td width="80%" style="border: none;">
|
||||
<video src="https://github.com/user-attachments/assets/d731ae5c-80eb-498b-a22c-8ab2b9169f87" autoplay muted loop controls></video>
|
||||
</td>
|
||||
<td width="10%" style="border: none; vertical-align: middle; text-align: center;">
|
||||
<strong>马<br>到<br>成<br>功</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### When Is Memory Written?
|
||||
|
||||
| Scenario | Written to | Trigger |
|
||||
|---------------------------------------------|------------------------|------------------------------------|
|
||||
| Auto-compact when context is too long | `memory/YYYY-MM-DD.md` | Automatic in background |
|
||||
| User runs `/compact` | `memory/YYYY-MM-DD.md` | Manual compact + background save |
|
||||
| User runs `/new` | `memory/YYYY-MM-DD.md` | New conversation + background save |
|
||||
| User says "remember this" | `MEMORY.md` or log | Agent writes via `write` tool |
|
||||
| Agent finds important decisions/preferences | `MEMORY.md` | Agent writes proactively |
|
||||
|
||||
### Memory Retrieval Tools
|
||||
|
||||
| Method | Tool | When to use | Example |
|
||||
|-----------------|-----------------|----------------------------------|---------------------------------------|
|
||||
| Semantic search | `memory_search` | Unsure where it is, fuzzy lookup | "Earlier discussion about deployment" |
|
||||
| Direct read | `read` | Know the date or file | Read `memory/2025-02-13.md` |
|
||||
|
||||
Search uses **vector + BM25 hybrid retrieval** (vector weight 0.7, BM25 weight 0.3), so queries using both natural
|
||||
language and exact
|
||||
keywords can match.
|
||||
|
||||
### Built-in Tools
|
||||
|
||||
| Tool | Function | Details |
|
||||
|-----------------|----------------|------------------------------------------------------------|
|
||||
| `memory_search` | Search memory | Vector + BM25 hybrid search over MEMORY.md and memory/*.md |
|
||||
| `bash` | Run commands | Execute bash commands with timeout and output truncation |
|
||||
| `ls` | List directory | Show directory structure |
|
||||
| `read` | Read file | Text and images supported, with segmented reading |
|
||||
| `edit` | Edit file | Replace after exact text match |
|
||||
| `write` | Write file | Create or overwrite, auto-create directories |
|
||||
| `execute_code` | Run Python | Execute code snippets |
|
||||
| `web_search` | Web search | Search via Tavily |
|
||||
| Method | Function | Key Components |
|
||||
|---------------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------|
|
||||
| `start` | 🚀 Start memory system | Initialize file store, file watcher, embedding cache; cleanup expired tool result files |
|
||||
| `close` | 📕 Close and cleanup | Cleanup tool result files, stop file watcher, save embedding cache |
|
||||
| `compact_memory` | 📦 Compress history to summary | [Compactor](reme/memory/file_based/compactor.py) — ReActAgent generates structured context checkpoints |
|
||||
| `summary_memory` | 📝 Write important memories to files | [Summarizer](reme/memory/file_based/summarizer.py) — ReActAgent + file tools (read / write / edit) |
|
||||
| `compact_tool_result` | ✂️ Compress long tool outputs | [ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) — Truncate and save to `tool_result/`, keep file reference in message |
|
||||
| `add_async_summary_task` | ⚡ Submit background summary task | `asyncio.create_task`, summary doesn't block main conversation flow |
|
||||
| `await_summary_tasks` | ⏳ Wait for background tasks | Collect results from all background summary tasks, call before closing to ensure writes complete |
|
||||
| `memory_search` | 🔍 Semantic memory search | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — Vector + BM25 hybrid retrieval |
|
||||
| `get_in_memory_memory` | 🗂️ Create in-memory instance | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — Token-aware memory management, supports compression summaries and state serialization |
|
||||
| `update_params` | ⚙️ Dynamically update runtime params | Runtime adjustment of `max_input_length`, `memory_compact_ratio`, `language` |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -163,131 +78,66 @@ keywords can match.
|
|||
### Installation
|
||||
|
||||
```bash
|
||||
pip install -U reme-ai
|
||||
pip install -U reme-ai[as]
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
API keys are set via environment variables; you can put them in a `.env` file in the project root:
|
||||
`ReMeLight` environment variables configure embedding and storage backends
|
||||
|
||||
| Variable | Description | Example |
|
||||
|---------------------------|----------------------------------|-----------------------------------------------------|
|
||||
| `REME_LLM_API_KEY` | LLM API key | `sk-xxx` |
|
||||
| `REME_LLM_BASE_URL` | LLM base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `REME_EMBEDDING_API_KEY` | Embedding API key | `sk-xxx` |
|
||||
| `REME_EMBEDDING_BASE_URL` | Embedding base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `TAVILY_API_KEY` | Tavily search API key (optional) | `tvly-xxx` |
|
||||
|
||||
### Using ReMeCli
|
||||
|
||||
#### Start ReMeCli
|
||||
|
||||
```bash
|
||||
remecli config=cli
|
||||
```
|
||||
|
||||
#### ReMeCli System Commands
|
||||
|
||||
> Year of the Horse easter egg: `/horse` — fireworks, galloping animation, and random horse-year blessings.
|
||||
|
||||
Commands starting with `/` control session state:
|
||||
|
||||
| Command | Description | Waits for response |
|
||||
|------------|--------------------------------------------------------------------|--------------------|
|
||||
| `/compact` | Manually compact current conversation and save to long-term memory | Yes |
|
||||
| `/new` | Start new conversation; history saved to long-term memory | No |
|
||||
| `/clear` | Clear everything, **without saving** | No |
|
||||
| `/history` | View uncompressed messages in current conversation | No |
|
||||
| `/help` | Show command list | No |
|
||||
| `/exit` | Exit | No |
|
||||
|
||||
**Difference between the three commands**
|
||||
|
||||
| Command | Compact summary | Long-term memory | Message history |
|
||||
|------------|-----------------|------------------|-----------------|
|
||||
| `/compact` | New summary | Saved | Keep recent |
|
||||
| `/new` | Cleared | Saved | Cleared |
|
||||
| `/clear` | Cleared | Not saved | Cleared |
|
||||
|
||||
> `/clear` permanently deletes; nothing is persisted anywhere.
|
||||
|
||||
### Using the ReMe Package
|
||||
|
||||
#### File-Based ReMe (CoPaw Memory System)
|
||||
|
||||
`ReMeCopaw` receives AgentScope components like `ChatModelBase`, `Formatter`, `Toolkit`, and configures Embedding and
|
||||
storage backend via environment variables:
|
||||
|
||||
| Environment Variable | Description | Default |
|
||||
|----------------------------|-----------------------------------------------|-----------------------------------------------------|
|
||||
| `EMBEDDING_API_KEY` | Embedding service API Key | `""` (vector search disabled if not configured) |
|
||||
| `EMBEDDING_BASE_URL` | Embedding service Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `EMBEDDING_MODEL_NAME` | Embedding model name | `""` |
|
||||
| `EMBEDDING_DIMENSIONS` | Vector dimensions | `1024` |
|
||||
| `EMBEDDING_CACHE_ENABLED` | Whether to enable Embedding cache | `true` |
|
||||
| `EMBEDDING_MAX_CACHE_SIZE` | Maximum cache entries | `2000` |
|
||||
| `FTS_ENABLED` | Whether to enable full-text search (BM25) | `true` |
|
||||
| `MEMORY_STORE_BACKEND` | Storage backend (`auto` / `chroma` / `local`) | `auto` (local on Windows, chroma on others) |
|
||||
| Environment Variable | Description | Default |
|
||||
|-----------------------------|--------------------------------------|-----------------------------------------------------|
|
||||
| `EMBEDDING_API_KEY` | Embedding service API Key | `""` |
|
||||
| `EMBEDDING_BASE_URL` | Embedding service Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `EMBEDDING_MODEL_NAME` | Embedding model name | `""` |
|
||||
| `EMBEDDING_DIMENSIONS` | Vector dimensions | `1024` |
|
||||
| `EMBEDDING_CACHE_ENABLED` | Enable embedding cache | `true` |
|
||||
| `EMBEDDING_MAX_CACHE_SIZE` | Maximum cache entries | `2000` |
|
||||
| `FTS_ENABLED` | Enable full-text search (BM25) | `true` |
|
||||
| `MEMORY_STORE_BACKEND` | Storage backend (`auto` / `chroma` / `local`) | `auto` (local on Windows, chroma otherwise) |
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agentscope.formatter import ClaudeFormatter
|
||||
from agentscope.model import get_model
|
||||
from agentscope.token import HuggingFaceTokenCounter
|
||||
from agentscope.tool import Toolkit
|
||||
|
||||
from reme.reme_copaw import ReMeCopaw
|
||||
|
||||
from agentscope.message import Msg
|
||||
from reme.reme_light import ReMeLight
|
||||
|
||||
async def main():
|
||||
# Prepare AgentScope core components
|
||||
chat_model = get_model(config={"backend": "openai", "model_name": "qwen3.5-plus"})
|
||||
formatter = ClaudeFormatter()
|
||||
token_counter = HuggingFaceTokenCounter()
|
||||
toolkit = Toolkit() # Can register additional tools
|
||||
|
||||
# Initialize ReMeCopaw
|
||||
reme = ReMeCopaw(
|
||||
reme = ReMeLight(
|
||||
working_dir=".reme", # Memory file storage directory
|
||||
chat_model=chat_model,
|
||||
formatter=formatter,
|
||||
token_counter=token_counter,
|
||||
toolkit=toolkit,
|
||||
max_input_length=128000, # Model context window (tokens)
|
||||
memory_compact_ratio=0.7, # Trigger compaction when reaching max_input_length * 0.7
|
||||
memory_compact_ratio=0.7, # Trigger compression at max_input_length * 0.7
|
||||
language="zh", # Summary language (zh / "")
|
||||
tool_result_threshold=1000, # Auto-save tool outputs exceeding this character count
|
||||
retention_days=7, # tool_result/ file retention days
|
||||
)
|
||||
await reme.start()
|
||||
|
||||
messages = [...] # list[Msg], conversation history
|
||||
messages = [...]
|
||||
|
||||
# 1. Compact oversized tool outputs (prevent tool results from overflowing context)
|
||||
# 1. Compress long tool outputs (prevent tool results from bloating context)
|
||||
messages = await reme.compact_tool_result(messages)
|
||||
|
||||
# 2. Compact history to structured summary (trigger: context approaching limit)
|
||||
summary = await reme.compact_memory(
|
||||
messages=messages,
|
||||
previous_summary="", # Can pass previous summary for incremental update
|
||||
)
|
||||
print(f"Compact summary:\n{summary}")
|
||||
# 2. Compress conversation history to structured summary (triggered when context approaches limit), pass previous summary for incremental updates
|
||||
summary = await reme.compact_memory(messages=messages, previous_summary="")
|
||||
|
||||
# 3. Submit async summary task in background (non-blocking, writes to memory/YYYY-MM-DD.md)
|
||||
reme.add_async_summary_task(messages=messages)
|
||||
|
||||
# 4. Semantic memory search (Vector + BM25 hybrid retrieval)
|
||||
result = await reme.memory_search(query="Python version preference", max_results=5)
|
||||
print(f"Search results: {result}")
|
||||
|
||||
# 5. Get in-memory instance (CoPawInMemoryMemory, manages single conversation context)
|
||||
# 5. Get in-memory instance (ReMeInMemoryMemory, manages single conversation context) AgentScope InMemoryMemory
|
||||
memory = reme.get_in_memory_memory()
|
||||
token_stats = await memory.estimate_tokens()
|
||||
print(f"Current context usage: {token_stats['context_usage_ratio']:.1f}%")
|
||||
print(f"Message tokens: {token_stats['messages_tokens']}")
|
||||
print(f"Estimated total tokens: {token_stats['estimated_tokens']}")
|
||||
|
||||
# 6. Wait for background tasks before closing
|
||||
await reme.await_summary_tasks()
|
||||
summary_result = await reme.await_summary_tasks()
|
||||
|
||||
# Close ReMeLight
|
||||
await reme.close()
|
||||
|
||||
|
||||
|
|
@ -297,6 +147,28 @@ if __name__ == "__main__":
|
|||
|
||||
#### Vector-Based ReMe
|
||||
|
||||
## 🗃️ Vector-Based ReMe
|
||||
|
||||
[ReMe Vector Based](reme/reme.py) is the core class of the vector-based memory system, supporting unified management of three memory types:
|
||||
|
||||
| Memory Type | Purpose | Use Case |
|
||||
|----------------------|--------------------------------------|--------------|
|
||||
| **Personal Memory** | Record user preferences, habits | `user_name` |
|
||||
| **Task/Procedural Memory** | Record task execution experience, success/failure patterns | `task_name` |
|
||||
| **Tool Memory** | Record tool usage experience, parameter optimization | `tool_name` |
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
| Method | Function | Description |
|
||||
|---------------------|------------------|------------------------------------|
|
||||
| `summarize_memory` | 🧠 Memory Summary | Auto-extract and store memories from conversations |
|
||||
| `retrieve_memory` | 🔍 Memory Retrieval | Retrieve relevant memories based on query |
|
||||
| `add_memory` | ➕ Add Memory | Manually add memory to vector store |
|
||||
| `get_memory` | 📖 Get Memory | Get single memory by ID |
|
||||
| `update_memory` | ✏️ Update Memory | Update existing memory content or metadata |
|
||||
| `delete_memory` | 🗑️ Delete Memory | Delete specified memory |
|
||||
| `list_memory` | 📋 List Memories | List memories by type, supports filtering and sorting |
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from reme import ReMe
|
||||
|
|
@ -323,24 +195,24 @@ async def main():
|
|||
|
||||
messages = [
|
||||
{"role": "user", "content": "Help me write a Python script", "time_created": "2026-02-28 10:00:00"},
|
||||
{"role": "assistant", "content": "Sure, I'll help you write it", "time_created": "2026-02-28 10:00:05"},
|
||||
{"role": "assistant", "content": "OK, let me help you", "time_created": "2026-02-28 10:00:05"},
|
||||
]
|
||||
|
||||
# 1. Summarize memory from conversation (auto-extract user preferences, task experience, etc.)
|
||||
# 1. Summarize memories from conversation (auto-extract user preferences, task experience, etc.)
|
||||
result = await reme.summarize_memory(
|
||||
messages=messages,
|
||||
user_name="alice", # Personal memory
|
||||
# task_name="code_writing", # Task memory
|
||||
)
|
||||
print(f"Summarize result: {result}")
|
||||
print(f"Summary result: {result}")
|
||||
|
||||
# 2. Retrieve relevant memory
|
||||
# 2. Retrieve relevant memories
|
||||
memories = await reme.retrieve_memory(
|
||||
query="Python programming",
|
||||
user_name="alice",
|
||||
# task_name="code_writing",
|
||||
)
|
||||
print(f"Retrieve result: {memories}")
|
||||
print(f"Retrieval result: {memories}")
|
||||
|
||||
# 3. Manually add memory
|
||||
memory_node = await reme.add_memory(
|
||||
|
|
@ -358,11 +230,11 @@ async def main():
|
|||
updated_memory = await reme.update_memory(
|
||||
memory_id=memory_id,
|
||||
user_name="alice",
|
||||
memory_content="User prefers concise, well-commented code style",
|
||||
memory_content="User prefers concise code style with comments",
|
||||
)
|
||||
print(f"Updated memory: {updated_memory}")
|
||||
|
||||
# 6. List all memories for user (with filtering and sorting)
|
||||
# 6. List all user memories (supports filtering and sorting)
|
||||
all_memories = await reme.list_memory(
|
||||
user_name="alice",
|
||||
limit=10,
|
||||
|
|
@ -385,91 +257,81 @@ if __name__ == "__main__":
|
|||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ Technical Architecture
|
||||
|
||||
### File-Based CoPaw Memory System Architecture
|
||||
### File-Based ReMeLight Memory System Architecture
|
||||
|
||||
[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py)
|
||||
inherits
|
||||
`ReMeCopaw` and integrates memory capabilities into the Agent reasoning flow:
|
||||
[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) inherits from `ReMeLight`, integrating memory capabilities into the Agent reasoning flow:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
CoPaw["CoPaw MemoryManager\n(inherits ReMeCopaw)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook]
|
||||
CoPaw --> ReMeCopaw[ReMeCopaw]
|
||||
Hook -->|exceeds threshold| ReMeCopaw
|
||||
ReMeCopaw --> CompactMemory[compact_memory\nHistory compaction]
|
||||
ReMeCopaw --> SummaryMemory[summary_memory\nWrite memory to files]
|
||||
ReMeCopaw --> CompactToolResult[compact_tool_result\nOversized tool output compaction]
|
||||
ReMeCopaw --> MemSearch[memory_search\nSemantic search]
|
||||
ReMeCopaw --> InMemory[get_in_memory_memory\nCoPawInMemoryMemory]
|
||||
CoPaw["CoPaw MemoryManager\n(inherits ReMeLight)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook]
|
||||
CoPaw --> ReMeLight[ReMeLight]
|
||||
Hook -->|exceeds threshold| ReMeLight
|
||||
ReMeLight --> CompactMemory[compact_memory\nHistory Compression]
|
||||
ReMeLight --> SummaryMemory[summary_memory\nWrite Memory to Files]
|
||||
ReMeLight --> CompactToolResult[compact_tool_result\nLong Tool Output Compression]
|
||||
ReMeLight --> MemSearch[memory_search\nSemantic Search]
|
||||
ReMeLight --> InMemory[get_in_memory_memory\nReMeInMemoryMemory]
|
||||
CompactMemory --> Compactor[Compactor\nReActAgent]
|
||||
SummaryMemory --> Summarizer[Summarizer\nReActAgent + file tools]
|
||||
CompactToolResult --> ToolResultCompactor[ToolResultCompactor\nTruncate + save to file]
|
||||
SummaryMemory --> Summarizer[Summarizer\nReActAgent + File Tools]
|
||||
CompactToolResult --> ToolResultCompactor[ToolResultCompactor\nTruncate + Save to File]
|
||||
Summarizer --> FileIO[FileIO\nread / write / edit]
|
||||
FileIO --> MemoryFiles[memory/YYYY-MM-DD.md]
|
||||
ToolResultCompactor --> ToolResultFiles[tool_result/*.txt]
|
||||
MemoryFiles -.->|File change| FileWatcher[Async File Watcher]
|
||||
FileWatcher -->|Update index| FileStore[Local DB]
|
||||
MemoryFiles -.->|file changes| FileWatcher[Async File Watcher]
|
||||
FileWatcher -->|update index| FileStore[Local Database]
|
||||
MemSearch --> FileStore
|
||||
```
|
||||
|
||||
#### Auto-Compaction Trigger Flow
|
||||
#### Auto-Compression Trigger Flow
|
||||
|
||||
`MemoryCompactionHook` checks context token usage before each reasoning step, automatically triggering compaction when
|
||||
threshold is exceeded:
|
||||
`MemoryCompactionHook` checks context token usage before each reasoning step, automatically triggering compression when threshold is exceeded:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[pre_reasoning] --> B{Token exceeds threshold?}
|
||||
B -->|No| Z[Continue reasoning]
|
||||
B -->|Yes| C[compact_tool_result\nCompact oversized tool outputs in recent messages]
|
||||
B -->|Yes| C[compact_tool_result\nCompress long tool outputs in recent messages]
|
||||
C --> D[compact_memory\nGenerate structured context checkpoint]
|
||||
D --> E[Mark old messages as COMPRESSED]
|
||||
E --> F[add_async_summary_task\nBackground write to memory file]
|
||||
E --> F[add_async_summary_task\nBackground write to memory files]
|
||||
F --> Z
|
||||
```
|
||||
|
||||
#### Context Compaction Summary Format
|
||||
#### Context Compression Summary Format
|
||||
|
||||
[Compactor](reme/memory/file_based_copaw/compactor.py) uses ReActAgent to compact history into structured **context
|
||||
checkpoints**:
|
||||
[Compactor](reme/memory/file_based/compactor.py) uses ReActAgent to compress conversation history into structured **context checkpoints**:
|
||||
|
||||
| Field | Description |
|
||||
|-----------------------|--------------------------------------------------|
|
||||
| `## Goal` | 🎯 User's objectives (can be multiple) |
|
||||
| `## Constraints` | ⚙️ Constraints and preferences mentioned by user |
|
||||
| `## Progress` | 📈 Completed / in progress / blocked tasks |
|
||||
| `## Key Decisions` | 🔑 Decisions made with brief reasons |
|
||||
| `## Next Steps` | 🗺️ Next action plan (ordered list) |
|
||||
| `## Critical Context` | 📌 File paths, function names, error messages |
|
||||
| Field | Description |
|
||||
|------------------------|----------------------------------------------|
|
||||
| `## Goal` | 🎯 Goals the user wants to accomplish (can be multiple) |
|
||||
| `## Constraints` | ⚙️ Constraints and preferences mentioned by user |
|
||||
| `## Progress` | 📈 Completed / In-progress / Blocked tasks |
|
||||
| `## Key Decisions` | 🔑 Decisions made with brief rationale |
|
||||
| `## Next Steps` | 🗺️ Next action plan (ordered list) |
|
||||
| `## Critical Context` | 📌 Key data like file paths, function names, error messages |
|
||||
|
||||
Supports **incremental updates**: when `previous_summary` is passed, automatically merges new conversation with old
|
||||
summary, preserving historical progress.
|
||||
Supports **incremental updates**: When `previous_summary` is provided, new conversation is automatically merged with old summary, preserving historical progress.
|
||||
|
||||
#### Tool Result Compaction
|
||||
#### Tool Result Compression
|
||||
|
||||
[ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) solves context overflow caused by oversized
|
||||
tool outputs:
|
||||
[ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) solves the problem of context bloat caused by overly long tool outputs:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[tool_result message] --> B{Content length > threshold?}
|
||||
B -->|No| C[Keep as-is]
|
||||
B -->|No| C[Keep as is]
|
||||
B -->|Yes| D[Truncate to threshold characters]
|
||||
D --> E[Write full content to tool_result/uuid.txt]
|
||||
E --> F[Append file reference path to message]
|
||||
```
|
||||
|
||||
Expired files (exceeding `retention_days`) are automatically cleaned up during `start` / `close` /
|
||||
`compact_tool_result`.
|
||||
Expired files (exceeding `retention_days`) are automatically cleaned up during `start` / `close` / `compact_tool_result`.
|
||||
|
||||
#### Memory Summary: ReAct + File Tools
|
||||
#### Memory Summarization: ReAct + File Tools
|
||||
|
||||
[Summarizer](reme/memory/file_based_copaw/summarizer.py) uses the **ReAct + file tools** pattern, letting AI
|
||||
autonomously decide what to write and where:
|
||||
[Summarizer](reme/memory/file_based/summarizer.py) uses the **ReAct + File Tools** pattern, letting AI autonomously decide what to write and where:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
|
|
@ -477,68 +339,86 @@ graph LR
|
|||
B --> C[Act: read memory/YYYY-MM-DD.md]
|
||||
C --> D{Think: How to merge with existing content?}
|
||||
D --> E[Act: edit to update file]
|
||||
E --> F{Think: Anything missing?}
|
||||
E --> F{Think: Anything missed?}
|
||||
F -->|Yes| B
|
||||
F -->|No| G[Done]
|
||||
F -->|No| G[Complete]
|
||||
```
|
||||
|
||||
[FileIO](reme/memory/file_based_copaw/file_io.py) provides file operation tools:
|
||||
[FileIO](reme/memory/file_based/file_io.py) provides file operation tools:
|
||||
|
||||
| Tool | Function | Use case |
|
||||
|---------|--------------------------------|-----------------------------------------|
|
||||
| `read` | Read file content (line range) | View existing memory, avoid duplicates |
|
||||
| `write` | Overwrite file | Create new memory file or major rewrite |
|
||||
| `edit` | Replace after exact match | Append or modify specific sections |
|
||||
| Tool | Function | Use Case |
|
||||
|---------|-------------------------------|-----------------------------------|
|
||||
| `read` | Read file content (supports line ranges) | View existing memories, avoid duplicate writes |
|
||||
| `write` | Overwrite file | Create new memory files or major restructuring |
|
||||
| `edit` | Replace after exact match | Append new content or modify specific sections |
|
||||
|
||||
#### In-Memory Session Management
|
||||
#### In-Memory Management
|
||||
|
||||
[CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) extends AgentScope's `InMemoryMemory`:
|
||||
[ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) extends AgentScope's `InMemoryMemory`:
|
||||
|
||||
| Feature | Description |
|
||||
|----------------------------------|---------------------------------------------------------------------|
|
||||
| `get_memory` | Filter messages by mark, auto-prepend compression summary |
|
||||
| `estimate_tokens` | Precisely estimate current context token usage and ratio |
|
||||
| `get_history_str` | Generate human-readable conversation summary (with token stats) |
|
||||
| `state_dict` / `load_state_dict` | Support state serialization / deserialization (session persistence) |
|
||||
| Feature | Description |
|
||||
|-----------------------------------|------------------------------------------|
|
||||
| `get_memory` | Filter messages by tag, auto-prepend compression summary at head |
|
||||
| `estimate_tokens` | Precisely estimate current context token usage and utilization |
|
||||
| `get_history_str` | Generate human-readable conversation history summary (with token stats) |
|
||||
| `state_dict` / `load_state_dict` | Support state serialization / deserialization (session persistence) |
|
||||
| `mark_messages_compressed` | Mark messages as compressed state |
|
||||
| `get_compressed_summary` | Get compressed summary content |
|
||||
|
||||
#### Memory Retrieval
|
||||
|
||||
[MemorySearch](reme/memory/tools/chunk/memory_search.py) provides **vector + BM25 hybrid retrieval**:
|
||||
[MemorySearch](reme/memory/tools/chunk/memory_search.py) provides **Vector + BM25 hybrid retrieval** capabilities:
|
||||
|
||||
| Retrieval | Strength | Weakness |
|
||||
|---------------------|-------------------------------------------------|----------------------------------------|
|
||||
| **Vector semantic** | Captures similar meaning with different wording | Weaker on exact token match |
|
||||
| **BM25 full-text** | Strong exact token match | No synonym or paraphrase understanding |
|
||||
| Retrieval Method | Advantage | Disadvantage |
|
||||
|------------------|----------------------------------------|----------------------------------|
|
||||
| **Vector Semantic** | Captures semantically similar but differently worded content | Weak on exact token matching |
|
||||
| **BM25 Full-text** | Excellent for exact token hits | Cannot understand synonyms and paraphrases |
|
||||
|
||||
**Fusion**: Both retrieval paths are used; results are combined by weighted sum (vector 0.7 + BM25 0.3), so both
|
||||
natural-language queries and exact lookups get reliable results.
|
||||
**Fusion Mechanism**: After dual-path recall, weighted sum is applied (Vector 0.7 + BM25 0.3), enabling both natural language and exact lookups to hit.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Q[Search query] --> V[Vector search × 0.7]
|
||||
Q --> B[BM25 × 0.3]
|
||||
V --> M[Dedupe + weighted merge]
|
||||
B --> M
|
||||
M --> R[Top-N results]
|
||||
Q[Search Query] --> V[Vector Search × 0.7]
|
||||
Q --> B[BM25 × 0.3]
|
||||
V --> M[Dedupe + Weighted Fusion]
|
||||
B --> M
|
||||
M --> R[Top-N Results]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Vector-Based ReMe Core Architecture
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install -U reme-ai
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
API keys are set via environment variables, can be written in `.env` file in project root:
|
||||
|
||||
| Environment Variable | Description | Example |
|
||||
|----------------------------|--------------------------|-----------------------------------------------------|
|
||||
| `REME_LLM_API_KEY` | LLM API Key | `sk-xxx` |
|
||||
| `REME_LLM_BASE_URL` | LLM Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `REME_EMBEDDING_API_KEY` | Embedding API Key | `sk-xxx` |
|
||||
| `REME_EMBEDDING_BASE_URL` | Embedding Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
User[User / Agent] --> ReMe[Vector Based ReMe]
|
||||
ReMe --> Summarize[Memory Summarize]
|
||||
ReMe --> Retrieve[Memory Retrieve]
|
||||
ReMe --> CRUD[CRUD]
|
||||
ReMe --> Summarize[Memory Summarization]
|
||||
ReMe --> Retrieve[Memory Retrieval]
|
||||
ReMe --> CRUD[CRUD Operations]
|
||||
Summarize --> PersonalSum[PersonalSummarizer]
|
||||
Summarize --> ProceduralSum[ProceduralSummarizer]
|
||||
Summarize --> ToolSum[ToolSummarizer]
|
||||
Retrieve --> PersonalRet[PersonalRetriever]
|
||||
Retrieve --> ProceduralRet[ProceduralRetriever]
|
||||
Retrieve --> ToolRet[ToolRetriever]
|
||||
PersonalSum --> VectorStore[Vector DB]
|
||||
PersonalSum --> VectorStore[Vector Database]
|
||||
ProceduralSum --> VectorStore
|
||||
ToolSum --> VectorStore
|
||||
PersonalRet --> VectorStore
|
||||
|
|
@ -546,19 +426,13 @@ graph TB
|
|||
ToolRet --> VectorStore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⭐ Community & Support
|
||||
|
||||
- **Star & Watch**: Star helps more agent developers discover ReMe; Watch keeps you updated on new releases and
|
||||
features.
|
||||
- **Share your work**: In Issues or Discussions, share what ReMe unlocks for your agents — we’re happy to highlight
|
||||
great community examples.
|
||||
- **Need a new feature?** Open a Feature Request; we’ll iterate with the community.
|
||||
- **Code contributions**: All forms of code contribution are welcome. See
|
||||
the [Contribution Guide](docs/contribution.md).
|
||||
- **Acknowledgments**: Thanks to OpenClaw, Mem0, MemU, CoPaw, and other open-source projects for inspiration and
|
||||
support.
|
||||
- **Star & Watch**: Star helps more agent developers discover ReMe; Watch keeps you informed about new releases and features.
|
||||
- **Share Your Work**: Share what ReMe unlocked for your agent in Issues or Discussions — we'd love to showcase community achievements.
|
||||
- **Need a Feature?** Submit a Feature Request, and we'll work with the community to improve.
|
||||
- **Code Contributions**: All forms of code contributions are welcome, please see the [Contribution Guide](docs/contribution.md).
|
||||
- **Acknowledgments**: Thanks to OpenClaw, Mem0, MemU, CoPaw, and other excellent open-source projects for their inspiration and help.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -577,7 +451,7 @@ graph TB
|
|||
|
||||
## ⚖️ License
|
||||
|
||||
This project is open source under the Apache License 2.0. See the [LICENSE](./LICENSE) file for details.
|
||||
This project is open-sourced under the Apache License 2.0, see the [LICENSE](./LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
539
README_ZH.md
539
README_ZH.md
|
|
@ -33,13 +33,12 @@ ReMe 让智能体拥有**真正的记忆力**——旧对话自动浓缩,重
|
|||
|
||||
---
|
||||
|
||||
## 📁 基于文件的 CoPaw 记忆系统
|
||||
## 📁 基于文件的记忆系统
|
||||
|
||||
> 记忆即文件,文件即记忆
|
||||
|
||||
将**记忆视为文件**——可读、可编辑、可复制。[CoPaw](https://github.com/agentscope-ai/CoPaw)
|
||||
通过 [MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py)
|
||||
集成此记忆系统,继承 `ReMeCopaw` 并对外暴露记忆管理能力。
|
||||
将**记忆视为文件**——可读、可编辑、可复制。
|
||||
[CoPaw](https://github.com/agentscope-ai/CoPaw)通过继承 `ReMeLight` 实现了长期记忆和上下文的管理。
|
||||
|
||||
| 传统记忆系统 | File Based ReMe |
|
||||
|-----------|-----------------|
|
||||
|
|
@ -59,22 +58,209 @@ working_dir/
|
|||
|
||||
### 核心能力
|
||||
|
||||
[ReMeCopaw](reme/reme_copaw.py) 是该记忆系统的核心类,为 AI Agent 提供完整的记忆管理能力:
|
||||
[ReMeLight](reme/reme_light.py) 是该记忆系统的核心类,为 AI Agent 提供完整的记忆管理能力:
|
||||
|
||||
| 方法 | 功能 | 关键组件 |
|
||||
|--------------------------|--------------|----------------------------------------------------------------------------------------------------------------|
|
||||
| `start` | 🚀 启动记忆系统 | 初始化文件存储、文件监控、Embedding 缓存;清理过期工具结果文件 |
|
||||
| `close` | 📕 关闭并清理 | 清理工具结果文件、停止文件监控、保存 Embedding 缓存 |
|
||||
| `compact_memory` | 📦 压缩历史对话为摘要 | [Compactor](reme/memory/file_based_copaw/compactor.py) — ReActAgent 生成结构化上下文检查点 |
|
||||
| `summary_memory` | 📝 将重要记忆写入文件 | [Summarizer](reme/memory/file_based_copaw/summarizer.py) — ReActAgent + 文件工具(read / write / edit) |
|
||||
| `compact_tool_result` | ✂️ 压缩超长工具输出 | [ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) — 截断并转存到 `tool_result/`,消息中保留文件引用 |
|
||||
| `add_async_summary_task` | ⚡ 提交后台摘要任务 | `asyncio.create_task`,摘要不阻塞主对话流程 |
|
||||
| `await_summary_tasks` | ⏳ 等待后台任务完成 | 收集所有后台摘要任务的结果,关闭前调用确保写入完成 |
|
||||
| `memory_search` | 🔍 语义搜索记忆 | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — 向量 + BM25 混合检索 |
|
||||
| `get_in_memory_memory` | 🗂️ 创建会话内存实例 | [CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) — Token 感知的内存管理,支持压缩摘要和状态序列化 |
|
||||
| `update_params` | ⚙️ 动态更新运行时参数 | 运行时调整 `max_input_length`、`memory_compact_ratio`、`language` |
|
||||
| 方法 | 功能 | 关键组件 |
|
||||
|--------------------------|--------------|----------------------------------------------------------------------------------------------------------|
|
||||
| `start` | 🚀 启动记忆系统 | 初始化文件存储、文件监控、Embedding 缓存;清理过期工具结果文件 |
|
||||
| `close` | 📕 关闭并清理 | 清理工具结果文件、停止文件监控、保存 Embedding 缓存 |
|
||||
| `compact_memory` | 📦 压缩历史对话为摘要 | [Compactor](reme/memory/file_based/compactor.py) — ReActAgent 生成结构化上下文检查点 |
|
||||
| `summary_memory` | 📝 将重要记忆写入文件 | [Summarizer](reme/memory/file_based/summarizer.py) — ReActAgent + 文件工具(read / write / edit) |
|
||||
| `compact_tool_result` | ✂️ 压缩超长工具输出 | [ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) — 截断并转存到 `tool_result/`,消息中保留文件引用 | |
|
||||
| `memory_search` | 🔍 语义搜索记忆 | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — 向量 + BM25 混合检索 |
|
||||
| `get_in_memory_memory` | 🗂️ 创建会话内存实例 | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — Token 感知的内存管理,支持压缩摘要和状态序列化 |
|
||||
|
||||
## 🗃️ 基于向量库的 ReMe
|
||||
---
|
||||
|
||||
### 🚀 快速开始
|
||||
|
||||
#### 安装
|
||||
|
||||
```bash
|
||||
pip install -U reme-ai[light]
|
||||
```
|
||||
|
||||
#### 环境变量
|
||||
|
||||
`ReMeLight` 环境变量配置 Embedding 和存储后端
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------------------|--------------------|-----------------------------------------------------|
|
||||
| `LLM_API_KEY` | LLM API key | `sk-xxx` |
|
||||
| `LLM_BASE_URL` | LLM base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `EMBEDDING_API_KEY` | Embedding API key | `sk-xxx` |
|
||||
| `EMBEDDING_BASE_URL` | Embedding base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `LLM_MODEL_NAME` | LLM model name | `qwen3.5-plus` |
|
||||
|
||||
#### Python使用
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agentscope.message import Msg
|
||||
from reme.reme_light import ReMeLight
|
||||
|
||||
|
||||
async def main():
|
||||
reme = ReMeLight(
|
||||
working_dir=".reme", # 记忆文件存储目录
|
||||
max_input_length=128000, # 模型上下文窗口(tokens)
|
||||
memory_compact_ratio=0.7, # 达到 max_input_length * 0.7 时触发压缩
|
||||
language="zh", # 摘要语言(zh / "")
|
||||
tool_result_threshold=1000, # 超过此字符数的工具输出自动转存
|
||||
retention_days=7, # tool_result/ 文件保留天数
|
||||
)
|
||||
await reme.start()
|
||||
|
||||
messages = [...]
|
||||
|
||||
# 1. 压缩超长工具输出(防止工具结果撑爆上下文)
|
||||
messages = await reme.compact_tool_result(messages)
|
||||
|
||||
# 2. 将历史对话压缩为结构化摘要(触发时机:上下文接近上限),可传入上轮摘要,实现增量更新
|
||||
summary = await reme.compact_memory(messages=messages, previous_summary="")
|
||||
|
||||
# 3. 后台异步提交摘要任务(不阻塞对话,摘要写入 memory/YYYY-MM-DD.md)
|
||||
reme.add_async_summary_task(messages=messages)
|
||||
|
||||
# 4. 语义搜索记忆(向量 + BM25 混合检索)
|
||||
result = await reme.memory_search(query="Python 版本偏好", max_results=5)
|
||||
|
||||
# 5. 获取会话内存实例(ReMeInMemoryMemory,管理单次对话的上下文)AgentScope InMemoryMemory
|
||||
memory = reme.get_in_memory_memory()
|
||||
token_stats = await memory.estimate_tokens()
|
||||
print(f"当前上下文使用率: {token_stats['context_usage_ratio']:.1f}%")
|
||||
print(f"消息 Token 数: {token_stats['messages_tokens']}")
|
||||
print(f"预估总 Token 数: {token_stats['estimated_tokens']}")
|
||||
|
||||
# 6. 关闭前等待后台任务完成
|
||||
summary_result = await reme.await_summary_tasks()
|
||||
|
||||
# 关闭 ReMeLight
|
||||
await reme.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### 基于文件的 ReMeLight 记忆系统架构
|
||||
|
||||
[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) 继承
|
||||
`ReMeLight`,将记忆能力集成到 Agent 推理流程中:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
CoPaw["CoPaw MemoryManager\n(继承 ReMeLight)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook]
|
||||
CoPaw --> ReMeLight[ReMeLight]
|
||||
Hook -->|超出阈值| ReMeLight
|
||||
ReMeLight --> CompactMemory[compact_memory\n历史对话压缩]
|
||||
ReMeLight --> SummaryMemory[summary_memory\n记忆写入文件]
|
||||
ReMeLight --> CompactToolResult[compact_tool_result\n超长工具输出压缩]
|
||||
ReMeLight --> MemSearch[memory_search\n语义搜索]
|
||||
ReMeLight --> InMemory[get_in_memory_memory\nReMeInMemoryMemory]
|
||||
CompactMemory --> Compactor[Compactor\nReActAgent]
|
||||
SummaryMemory --> Summarizer[Summarizer\nReActAgent + 文件工具]
|
||||
CompactToolResult --> ToolResultCompactor[ToolResultCompactor\n截断 + 转存文件]
|
||||
Summarizer --> FileIO[FileIO\nread / write / edit]
|
||||
FileIO --> MemoryFiles[memory/YYYY-MM-DD.md]
|
||||
ToolResultCompactor --> ToolResultFiles[tool_result/*.txt]
|
||||
MemoryFiles -.->|文件变更| FileWatcher[异步文件监控]
|
||||
FileWatcher -->|更新索引| FileStore[本地数据库]
|
||||
MemSearch --> FileStore
|
||||
```
|
||||
|
||||
### 上下文压缩机制
|
||||
|
||||
#### 上下文压缩
|
||||
|
||||
[Compactor](reme/memory/file_based/compactor.py) 使用 ReActAgent 将历史对话压缩为结构化的**上下文检查点**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|-----------------------|-----------------------|
|
||||
| `## Goal` | 🎯 用户要完成的目标(可多项) |
|
||||
| `## Constraints` | ⚙️ 用户提到的约束和偏好 |
|
||||
| `## Progress` | 📈 已完成 / 进行中 / 阻塞的任务 |
|
||||
| `## Key Decisions` | 🔑 做出的决策及简短理由 |
|
||||
| `## Next Steps` | 🗺️ 下一步行动计划(有序列表) |
|
||||
| `## Critical Context` | 📌 文件路径、函数名、错误信息等关键数据 |
|
||||
|
||||
支持**增量更新**:传入 `previous_summary` 时,自动将新对话与旧摘要合并,保留历史进展。
|
||||
|
||||
#### 工具结果压缩
|
||||
|
||||
[ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) 解决工具输出过长(比如 browser use)导致上下文膨胀的问题:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[tool_result 消息] --> B{内容长度 > threshold?}
|
||||
B -->|否| C[保留原样]
|
||||
B -->|是| D[截断到 threshold 字符]
|
||||
D --> E[完整内容写入 tool_result/uuid.txt]
|
||||
E --> F[消息中追加文件引用路径]
|
||||
```
|
||||
|
||||
过期文件(超过 `retention_days`)在 `start` / `close` / `compact_tool_result` 时自动清理。
|
||||
|
||||
### 记忆总结:ReAct + 文件工具
|
||||
|
||||
[Summarizer](reme/memory/file_based/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[接收对话] --> B{思考: 有什么值得记录?}
|
||||
B --> C[行动: read memory/YYYY-MM-DD.md]
|
||||
C --> D{思考: 如何与现有内容合并?}
|
||||
D --> E[行动: edit 更新文件]
|
||||
E --> F{思考: 还有遗漏吗?}
|
||||
F -->|是| B
|
||||
F -->|否| G[完成]
|
||||
```
|
||||
|
||||
[FileIO](reme/memory/file_based/file_io.py) 提供文件操作工具集:
|
||||
|
||||
| 工具 | 功能 | 使用场景 |
|
||||
|---------|---------------|---------------|
|
||||
| `read` | 读取文件内容(支持行范围) | 查看现有记忆,避免重复写入 |
|
||||
| `write` | 覆盖写入文件 | 创建新记忆文件或大幅重构 |
|
||||
| `edit` | 精确匹配后替换 | 追加新内容或修改特定段落 |
|
||||
|
||||
### 会话内存管理
|
||||
|
||||
[ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) 扩展了 AgentScope 的 `InMemoryMemory`:
|
||||
|
||||
| 功能 | 说明 |
|
||||
|----------------------------------|---------------------------|
|
||||
| `get_memory` | 按标记过滤消息,自动在头部追加压缩摘要 |
|
||||
| `estimate_tokens` | 精确估算当前上下文 Token 用量及使用率 |
|
||||
| `get_history_str` | 生成人类可读的对话历史摘要(含 Token 统计) |
|
||||
| `state_dict` / `load_state_dict` | 支持状态序列化 / 反序列化(会话持久化) |
|
||||
| `mark_messages_compressed` | 标记消息为已压缩状态 |
|
||||
| `get_compressed_summary` | 获取已压缩的摘要内容 |
|
||||
|
||||
### 记忆检索
|
||||
|
||||
[MemorySearch](reme/memory/tools/chunk/memory_search.py) 提供**向量 + BM25 混合检索**能力:
|
||||
|
||||
| 检索方式 | 优势 | 劣势 |
|
||||
|-------------|-----------------|----------------|
|
||||
| **向量语义** | 捕捉意义相近但措辞不同的内容 | 对精确 token 匹配较弱 |
|
||||
| **BM25 全文** | 精确 token 命中效果极佳 | 无法理解同义词和改写 |
|
||||
|
||||
**融合机制**:两路召回后按权重加权求和(向量 0.7 + BM25 0.3),自然语言与精确查找均可命中。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Q[搜索查询] --> V[向量搜索 × 0.7]
|
||||
Q --> B[BM25 × 0.3]
|
||||
V --> M[去重 + 加权融合]
|
||||
B --> M
|
||||
M --> R[Top-N 结果]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ 基于向量库的记忆系统
|
||||
|
||||
[ReMe Vector Based](reme/reme.py) 是基于向量库的记忆系统核心类,支持三种记忆类型的统一管理:
|
||||
|
||||
|
|
@ -96,60 +282,6 @@ working_dir/
|
|||
| `delete_memory` | 🗑️ 删除记忆 | 删除指定记忆 |
|
||||
| `list_memory` | 📋 列出记忆 | 列出某类记忆,支持过滤和排序 |
|
||||
|
||||
---
|
||||
|
||||
## 💻 ReMeCli:基于文件记忆的终端助手
|
||||
|
||||
<table border="0" cellspacing="0" cellpadding="0" style="border: none;">
|
||||
<tr style="border: none;">
|
||||
<td width="10%" style="border: none; vertical-align: middle; text-align: center;">
|
||||
<strong>马<br>上<br>有<br>钱</strong>
|
||||
</td>
|
||||
<td width="80%" style="border: none;">
|
||||
<video src="https://github.com/user-attachments/assets/befa7e40-63ba-4db2-8251-516024616e00" autoplay muted loop controls></video>
|
||||
</td>
|
||||
<td width="10%" style="border: none; vertical-align: middle; text-align: center;">
|
||||
<strong>马<br>到<br>成<br>功</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 什么时候会写记忆?
|
||||
|
||||
| 场景 | 写到哪 | 怎么触发 |
|
||||
|------------------|------------------------|----------------------|
|
||||
| 上下文超长自动压缩 | `memory/YYYY-MM-DD.md` | 后台自动 |
|
||||
| 用户执行 `/compact` | `memory/YYYY-MM-DD.md` | 手动压缩 + 后台保存 |
|
||||
| 用户执行 `/new` | `memory/YYYY-MM-DD.md` | 新对话 + 后台保存 |
|
||||
| 用户说"记住这个" | `MEMORY.md` 或日志 | Agent 用 `write` 工具写入 |
|
||||
| Agent 发现了重要决策/偏好 | `MEMORY.md` | Agent 主动写 |
|
||||
|
||||
### 记忆检索工具
|
||||
|
||||
| 方式 | 工具 | 什么时候用 | 举例 |
|
||||
|------|-----------------|------------|--------------------------|
|
||||
| 语义搜索 | `memory_search` | 不确定记在哪,模糊找 | "之前关于部署的讨论" |
|
||||
| 直接读 | `read` | 知道是哪天、哪个文件 | 读 `memory/2025-02-13.md` |
|
||||
|
||||
搜索用的是**向量 + BM25 混合检索**(向量权重 0.7,BM25 权重 0.3),无论自然语言还是精确关键词都能命中。
|
||||
|
||||
### 内置工具
|
||||
|
||||
| 工具 | 功能 | 细节 |
|
||||
|-----------------|----------|----------------------------------------|
|
||||
| `memory_search` | 搜记忆 | MEMORY.md 和 memory/*.md 里做向量+BM25 混合检索 |
|
||||
| `bash` | 跑命令 | 执行 bash 命令,有超时和输出截断 |
|
||||
| `ls` | 看目录 | 列目录结构 |
|
||||
| `read` | 读文件 | 文本和图片都行,支持分段读 |
|
||||
| `edit` | 改文件 | 精确匹配文本后替换 |
|
||||
| `write` | 写文件 | 创建或覆盖,自动建目录 |
|
||||
| `execute_code` | 跑 Python | 运行代码片段 |
|
||||
| `web_search` | 联网搜索 | 通过 Tavily |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
|
|
@ -160,134 +292,17 @@ pip install -U reme-ai
|
|||
|
||||
API 密钥通过环境变量设置,可写在项目根目录的 `.env` 文件中:
|
||||
|
||||
| 环境变量 | 说明 | 示例 |
|
||||
|---------------------------|-----------------------|-----------------------------------------------------|
|
||||
| `REME_LLM_API_KEY` | LLM 的 API Key | `sk-xxx` |
|
||||
| `REME_LLM_BASE_URL` | LLM 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `REME_EMBEDDING_API_KEY` | Embedding 的 API Key | `sk-xxx` |
|
||||
| `REME_EMBEDDING_BASE_URL` | Embedding 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `TAVILY_API_KEY` | Tavily 搜索 API Key(可选) | `tvly-xxx` |
|
||||
|
||||
### 使用 ReMeCli
|
||||
|
||||
#### 启动 ReMeCli
|
||||
|
||||
```bash
|
||||
remecli config=cli
|
||||
```
|
||||
|
||||
#### ReMeCli 系统命令
|
||||
|
||||
> 马年彩蛋:`/horse` 触发——烟花、奔马动画和随机马年祝福。
|
||||
|
||||
对话里输入 `/` 开头的命令控制状态:
|
||||
|
||||
| 命令 | 说明 | 需等待响应 |
|
||||
|------------|---------------------|-------|
|
||||
| `/compact` | 手动压缩当前对话,同时后台存到长期记忆 | 是 |
|
||||
| `/new` | 开始新对话,历史后台保存到长期记忆 | 否 |
|
||||
| `/clear` | 清空一切,**不保存** | 否 |
|
||||
| `/history` | 看当前对话里未压缩的消息 | 否 |
|
||||
| `/help` | 看命令列表 | 否 |
|
||||
| `/exit` | 退出 | 否 |
|
||||
|
||||
**三个命令的区别**
|
||||
|
||||
| 命令 | 压缩摘要 | 长期记忆 | 消息历史 |
|
||||
|------------|-------|------|-------|
|
||||
| `/compact` | 生成新摘要 | 保存 | 保留最近的 |
|
||||
| `/new` | 清空 | 保存 | 清空 |
|
||||
| `/clear` | 清空 | 不保存 | 清空 |
|
||||
|
||||
> `/clear` 是真删,删了就没了,不会存到任何地方。
|
||||
|
||||
### 使用 ReMe Package
|
||||
|
||||
#### 基于文件的 ReMe(CoPaw的记忆系统)
|
||||
|
||||
`ReMeCopaw` 接收 AgentScope 的 `ChatModelBase`、`Formatter`、`Toolkit` 等组件,通过环境变量配置 Embedding 和存储后端:
|
||||
|
||||
| 环境变量 | 说明 | 默认值 |
|
||||
|----------------------------|-----------------------------------|-----------------------------------------------------|
|
||||
| `EMBEDDING_API_KEY` | Embedding 服务 API Key | `""`(未配置则禁用向量搜索) |
|
||||
| `EMBEDDING_BASE_URL` | Embedding 服务 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `EMBEDDING_MODEL_NAME` | Embedding 模型名称 | `""` |
|
||||
| `EMBEDDING_DIMENSIONS` | 向量维度 | `1024` |
|
||||
| `EMBEDDING_CACHE_ENABLED` | 是否启用 Embedding 缓存 | `true` |
|
||||
| `EMBEDDING_MAX_CACHE_SIZE` | 最大缓存条数 | `2000` |
|
||||
| `FTS_ENABLED` | 是否启用全文搜索(BM25) | `true` |
|
||||
| `MEMORY_STORE_BACKEND` | 存储后端(`auto` / `chroma` / `local`) | `auto`(Windows 用 local,其他用 chroma) |
|
||||
| 环境变量 | 说明 | 示例 |
|
||||
|-----------------|----------------------|-----------------------------------------------------|
|
||||
| `LLM_API_KEY` | LLM 的 API Key | `sk-xxx` |
|
||||
| `LLM_BASE_URL` | LLM 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| `EMBEDDING_API_KEY` | Embedding 的 API Key | `sk-xxx` |
|
||||
| `EMBEDDING_BASE_URL` | Embedding 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
|
||||
### Python使用
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agentscope.formatter import ClaudeFormatter
|
||||
from agentscope.model import get_model
|
||||
from agentscope.token import HuggingFaceTokenCounter
|
||||
from agentscope.tool import Toolkit
|
||||
|
||||
from reme.reme_copaw import ReMeCopaw
|
||||
|
||||
|
||||
async def main():
|
||||
# 准备 AgentScope 核心组件
|
||||
chat_model = get_model(config={"backend": "openai", "model_name": "qwen3.5-plus"})
|
||||
formatter = ClaudeFormatter()
|
||||
token_counter = HuggingFaceTokenCounter()
|
||||
toolkit = Toolkit() # 可注册额外工具
|
||||
|
||||
# 初始化 ReMeCopaw
|
||||
reme = ReMeCopaw(
|
||||
working_dir=".reme", # 记忆文件存储目录
|
||||
chat_model=chat_model,
|
||||
formatter=formatter,
|
||||
token_counter=token_counter,
|
||||
toolkit=toolkit,
|
||||
max_input_length=128000, # 模型上下文窗口(tokens)
|
||||
memory_compact_ratio=0.7, # 达到 max_input_length * 0.7 时触发压缩
|
||||
language="zh", # 摘要语言(zh / "")
|
||||
tool_result_threshold=1000, # 超过此字符数的工具输出自动转存
|
||||
retention_days=7, # tool_result/ 文件保留天数
|
||||
)
|
||||
await reme.start()
|
||||
|
||||
messages = [...] # list[Msg],对话历史
|
||||
|
||||
# 1. 压缩超长工具输出(防止工具结果撑爆上下文)
|
||||
messages = await reme.compact_tool_result(messages)
|
||||
|
||||
# 2. 将历史对话压缩为结构化摘要(触发时机:上下文接近上限)
|
||||
summary = await reme.compact_memory(
|
||||
messages=messages,
|
||||
previous_summary="", # 可传入上轮摘要,实现增量更新
|
||||
)
|
||||
print(f"压缩摘要:\n{summary}")
|
||||
|
||||
# 3. 后台异步提交摘要任务(不阻塞对话,摘要写入 memory/YYYY-MM-DD.md)
|
||||
reme.add_async_summary_task(messages=messages)
|
||||
|
||||
# 4. 语义搜索记忆(向量 + BM25 混合检索)
|
||||
result = await reme.memory_search(query="Python 版本偏好", max_results=5)
|
||||
print(f"搜索结果: {result}")
|
||||
|
||||
# 5. 获取会话内存实例(CoPawInMemoryMemory,管理单次对话的上下文)
|
||||
memory = reme.get_in_memory_memory()
|
||||
token_stats = await memory.estimate_tokens()
|
||||
print(f"当前上下文使用率: {token_stats['context_usage_ratio']:.1f}%")
|
||||
|
||||
# 6. 关闭前等待后台任务完成
|
||||
await reme.await_summary_tasks()
|
||||
await reme.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
#### 基于向量库的 ReMe
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from reme import ReMe
|
||||
|
||||
|
||||
|
|
@ -374,137 +389,7 @@ if __name__ == "__main__":
|
|||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 🏛️ 技术架构
|
||||
|
||||
### 基于文件的 CoPaw 记忆系统架构
|
||||
|
||||
[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) 继承
|
||||
`ReMeCopaw`,将记忆能力集成到 Agent 推理流程中:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
CoPaw["CoPaw MemoryManager\n(继承 ReMeCopaw)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook]
|
||||
CoPaw --> ReMeCopaw[ReMeCopaw]
|
||||
Hook -->|超出阈值| ReMeCopaw
|
||||
ReMeCopaw --> CompactMemory[compact_memory\n历史对话压缩]
|
||||
ReMeCopaw --> SummaryMemory[summary_memory\n记忆写入文件]
|
||||
ReMeCopaw --> CompactToolResult[compact_tool_result\n超长工具输出压缩]
|
||||
ReMeCopaw --> MemSearch[memory_search\n语义搜索]
|
||||
ReMeCopaw --> InMemory[get_in_memory_memory\nCoPawInMemoryMemory]
|
||||
CompactMemory --> Compactor[Compactor\nReActAgent]
|
||||
SummaryMemory --> Summarizer[Summarizer\nReActAgent + 文件工具]
|
||||
CompactToolResult --> ToolResultCompactor[ToolResultCompactor\n截断 + 转存文件]
|
||||
Summarizer --> FileIO[FileIO\nread / write / edit]
|
||||
FileIO --> MemoryFiles[memory/YYYY-MM-DD.md]
|
||||
ToolResultCompactor --> ToolResultFiles[tool_result/*.txt]
|
||||
MemoryFiles -.->|文件变更| FileWatcher[异步文件监控]
|
||||
FileWatcher -->|更新索引| FileStore[本地数据库]
|
||||
MemSearch --> FileStore
|
||||
```
|
||||
|
||||
#### 自动压缩触发流程
|
||||
|
||||
`MemoryCompactionHook` 在每次推理前检查上下文 Token 用量,超过阈值时自动触发压缩:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[pre_reasoning] --> B{Token 超过阈值?}
|
||||
B -->|否| Z[继续推理]
|
||||
B -->|是| C[compact_tool_result\n压缩最近消息中的超长工具输出]
|
||||
C --> D[compact_memory\n生成结构化上下文检查点]
|
||||
D --> E[标记旧消息为 COMPRESSED]
|
||||
E --> F[add_async_summary_task\n后台写入 memory 文件]
|
||||
F --> Z
|
||||
```
|
||||
|
||||
#### 上下文压缩摘要格式
|
||||
|
||||
[Compactor](reme/memory/file_based_copaw/compactor.py) 使用 ReActAgent 将历史对话压缩为结构化的**上下文检查点**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|-----------------------|-----------------------|
|
||||
| `## Goal` | 🎯 用户要完成的目标(可多项) |
|
||||
| `## Constraints` | ⚙️ 用户提到的约束和偏好 |
|
||||
| `## Progress` | 📈 已完成 / 进行中 / 阻塞的任务 |
|
||||
| `## Key Decisions` | 🔑 做出的决策及简短理由 |
|
||||
| `## Next Steps` | 🗺️ 下一步行动计划(有序列表) |
|
||||
| `## Critical Context` | 📌 文件路径、函数名、错误信息等关键数据 |
|
||||
|
||||
支持**增量更新**:传入 `previous_summary` 时,自动将新对话与旧摘要合并,保留历史进展。
|
||||
|
||||
#### 工具结果压缩
|
||||
|
||||
[ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) 解决工具输出过长导致上下文膨胀的问题:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[tool_result 消息] --> B{内容长度 > threshold?}
|
||||
B -->|否| C[保留原样]
|
||||
B -->|是| D[截断到 threshold 字符]
|
||||
D --> E[完整内容写入 tool_result/uuid.txt]
|
||||
E --> F[消息中追加文件引用路径]
|
||||
```
|
||||
|
||||
过期文件(超过 `retention_days`)在 `start` / `close` / `compact_tool_result` 时自动清理。
|
||||
|
||||
#### 记忆总结:ReAct + 文件工具
|
||||
|
||||
[Summarizer](reme/memory/file_based_copaw/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[接收对话] --> B{思考: 有什么值得记录?}
|
||||
B --> C[行动: read memory/YYYY-MM-DD.md]
|
||||
C --> D{思考: 如何与现有内容合并?}
|
||||
D --> E[行动: edit 更新文件]
|
||||
E --> F{思考: 还有遗漏吗?}
|
||||
F -->|是| B
|
||||
F -->|否| G[完成]
|
||||
```
|
||||
|
||||
[FileIO](reme/memory/file_based_copaw/file_io.py) 提供文件操作工具集:
|
||||
|
||||
| 工具 | 功能 | 使用场景 |
|
||||
|---------|---------------|---------------|
|
||||
| `read` | 读取文件内容(支持行范围) | 查看现有记忆,避免重复写入 |
|
||||
| `write` | 覆盖写入文件 | 创建新记忆文件或大幅重构 |
|
||||
| `edit` | 精确匹配后替换 | 追加新内容或修改特定段落 |
|
||||
|
||||
#### 会话内存管理
|
||||
|
||||
[CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) 扩展了 AgentScope 的 `InMemoryMemory`:
|
||||
|
||||
| 功能 | 说明 |
|
||||
|----------------------------------|---------------------------|
|
||||
| `get_memory` | 按标记过滤消息,自动在头部追加压缩摘要 |
|
||||
| `estimate_tokens` | 精确估算当前上下文 Token 用量及使用率 |
|
||||
| `get_history_str` | 生成人类可读的对话历史摘要(含 Token 统计) |
|
||||
| `state_dict` / `load_state_dict` | 支持状态序列化 / 反序列化(会话持久化) |
|
||||
|
||||
#### 记忆检索
|
||||
|
||||
[MemorySearch](reme/memory/tools/chunk/memory_search.py) 提供**向量 + BM25 混合检索**能力:
|
||||
|
||||
| 检索方式 | 优势 | 劣势 |
|
||||
|-------------|-----------------|----------------|
|
||||
| **向量语义** | 捕捉意义相近但措辞不同的内容 | 对精确 token 匹配较弱 |
|
||||
| **BM25 全文** | 精确 token 命中效果极佳 | 无法理解同义词和改写 |
|
||||
|
||||
**融合机制**:两路召回后按权重加权求和(向量 0.7 + BM25 0.3),自然语言与精确查找均可命中。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Q[搜索查询] --> V[向量搜索 × 0.7]
|
||||
Q --> B[BM25 × 0.3]
|
||||
V --> M[去重 + 加权融合]
|
||||
B --> M
|
||||
M --> R[Top-N 结果]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 基于向量库的 ReMe 核心架构
|
||||
|
||||
### 技术架构
|
||||
```mermaid
|
||||
graph TB
|
||||
User[用户 / Agent] --> ReMe[Vector Based ReMe]
|
||||
|
|
|
|||
14
example.env
14
example.env
|
|
@ -1,11 +1,7 @@
|
|||
FLOW_EMBEDDING_API_KEY=sk-xxxx
|
||||
FLOW_EMBEDDING_BASE_URL=https://xxxx/v1
|
||||
FLOW_LLM_API_KEY=sk-xxxx
|
||||
FLOW_LLM_BASE_URL=https://xxxx/v1
|
||||
|
||||
REME_LLM_API_KEY=sk-xxxx
|
||||
REME_LLM_BASE_URL=https://xxxx/v1
|
||||
REME_EMBEDDING_API_KEY=sk-xxxx
|
||||
REME_EMBEDDING_BASE_URL=https://xxxx/v1
|
||||
LLM_API_KEY=sk-xxxx
|
||||
LLM_BASE_URL=https://xxxx/v1
|
||||
EMBEDDING_API_KEY=sk-xxxx
|
||||
EMBEDDING_BASE_URL=https://xxxx/v1
|
||||
LLM_MODEL_NAME=qwen3.5-plus
|
||||
|
||||
TAVILY_API_KEY=xxxx
|
||||
|
|
|
|||
|
|
@ -77,11 +77,11 @@ dev = [
|
|||
]
|
||||
|
||||
full = [
|
||||
"reme_ai[dev,ray]",
|
||||
"reme_ai[dev,ray,light]",
|
||||
]
|
||||
|
||||
as = [
|
||||
"agentscope",
|
||||
light = [
|
||||
"agentscope==1.0.16.dev0",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ from . import core
|
|||
from . import extension
|
||||
from . import memory
|
||||
from .reme import ReMe
|
||||
from .reme_cli import ReMeCli
|
||||
|
||||
__version__ = "0.3.0.5"
|
||||
__version__ = "0.3.0.6b1"
|
||||
|
||||
__all__ = [
|
||||
"config",
|
||||
|
|
@ -15,7 +14,6 @@ __all__ = [
|
|||
"extension",
|
||||
"memory",
|
||||
"ReMe",
|
||||
"ReMeCli",
|
||||
]
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -50,12 +50,12 @@ class BaseLLM(ABC):
|
|||
@property
|
||||
def api_key(self) -> str | None:
|
||||
"""Get API key from environment variable."""
|
||||
return os.getenv("REME_LLM_API_KEY") or self._api_key
|
||||
return os.getenv("LLM_API_KEY") or self._api_key
|
||||
|
||||
@property
|
||||
def base_url(self) -> str | None:
|
||||
"""Get base URL from environment variable."""
|
||||
return os.getenv("REME_LLM_BASE_URL") or self._base_url
|
||||
return os.getenv("LLM_BASE_URL") or self._base_url
|
||||
|
||||
@staticmethod
|
||||
def _accumulate_tool_call_chunk(tool_call, ret_tools: list[ToolCall]):
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ class ServiceContext(BaseDict):
|
|||
load_env()
|
||||
|
||||
# Update common environment variables for LLM and embedding services.
|
||||
self.update_env("REME_LLM_API_KEY", llm_api_key)
|
||||
self.update_env("REME_LLM_BASE_URL", llm_base_url)
|
||||
self.update_env("REME_EMBEDDING_API_KEY", embedding_api_key)
|
||||
self.update_env("REME_EMBEDDING_BASE_URL", embedding_base_url)
|
||||
self.update_env("LLM_API_KEY", llm_api_key)
|
||||
self.update_env("LLM_BASE_URL", llm_base_url)
|
||||
self.update_env("EMBEDDING_API_KEY", embedding_api_key)
|
||||
self.update_env("EMBEDDING_BASE_URL", embedding_base_url)
|
||||
|
||||
if service_config is None:
|
||||
parser_class = parser if parser is not None else PydanticConfigParser
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"""memory"""
|
||||
|
||||
from . import cli
|
||||
from . import file_based
|
||||
from . import tools
|
||||
from . import vector_based
|
||||
|
||||
__all__ = [
|
||||
"cli",
|
||||
"file_based",
|
||||
"tools",
|
||||
"vector_based",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"""File-based CoPaw Memory Module.
|
||||
"""File-based Memory Module.
|
||||
|
||||
This module provides memory management components for CoPaw (Cooperative Paw) agents,
|
||||
including memory formatting, compaction, summarization, and file I/O operations.
|
||||
|
||||
Components:
|
||||
- MemoryFormatter: Converts message lists to formatted strings with token limiting
|
||||
- CoPawInMemoryMemory: Extended InMemoryMemory with bugfixes and summary support
|
||||
- ReMeInMemoryMemory: Extended InMemoryMemory with bugfixes and summary support
|
||||
- Summarizer: Generates memory summaries using LLM
|
||||
- Compactor: Compacts memory content to reduce token usage
|
||||
- ToolResultCompactor: Truncates large tool results and saves full content to files
|
||||
|
|
@ -14,18 +14,20 @@ Components:
|
|||
|
||||
from . import utils
|
||||
from .compactor import Compactor
|
||||
from .copaw_in_memory_memory import CoPawInMemoryMemory
|
||||
from .file_io import FileIO
|
||||
from .memory_formatter import MemoryFormatter
|
||||
from .reme_chat_formatter import ReMeChatFormatter
|
||||
from .reme_in_memory_memory import ReMeInMemoryMemory
|
||||
from .summarizer import Summarizer
|
||||
from .tool_result_compactor import ToolResultCompactor
|
||||
|
||||
__all__ = [
|
||||
"MemoryFormatter",
|
||||
"CoPawInMemoryMemory",
|
||||
"ReMeInMemoryMemory",
|
||||
"Summarizer",
|
||||
"Compactor",
|
||||
"ToolResultCompactor",
|
||||
"FileIO",
|
||||
"utils",
|
||||
"ReMeChatFormatter",
|
||||
]
|
||||
29
reme/memory/file_based/reme_chat_formatter.py
Normal file
29
reme/memory/file_based/reme_chat_formatter.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""ReMe chat formatter."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agentscope.formatter import OpenAIChatFormatter
|
||||
from agentscope.token import HuggingFaceTokenCounter
|
||||
|
||||
from .utils import _extract_text_from_messages
|
||||
|
||||
|
||||
class ReMeChatFormatter(OpenAIChatFormatter):
|
||||
"""ReMe chat formatter class."""
|
||||
|
||||
async def _count(self, msgs: list[dict[str, Any]]) -> int | None:
|
||||
"""Count the number of tokens in the input messages. If token counter
|
||||
is not provided, `None` will be returned.
|
||||
|
||||
Args:
|
||||
msgs (`list[Msg]`):
|
||||
The input messages to count tokens for.
|
||||
"""
|
||||
if self.token_counter is None:
|
||||
return None
|
||||
|
||||
assert isinstance(self.token_counter, HuggingFaceTokenCounter)
|
||||
text = _extract_text_from_messages(msgs)
|
||||
token_ids = self.token_counter.tokenizer.encode(text)
|
||||
token_count = len(token_ids)
|
||||
return token_count
|
||||
|
|
@ -13,7 +13,7 @@ from .utils import safe_count_message_tokens, safe_count_str_tokens, _get_block_
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CoPawInMemoryMemory(InMemoryMemory):
|
||||
class ReMeInMemoryMemory(InMemoryMemory):
|
||||
"""Extended InMemoryMemory with bugfixes and summary support."""
|
||||
|
||||
def __init__(
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
"""Utility functions for working with text."""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from agentscope.token import HuggingFaceTokenCounter
|
||||
|
||||
|
|
@ -229,3 +230,42 @@ def _get_block_tokens( # pylint: disable=too-many-return-statements
|
|||
return 0, ""
|
||||
|
||||
return 0, ""
|
||||
|
||||
|
||||
_token_counter = None
|
||||
|
||||
|
||||
def get_token_counter():
|
||||
"""Get or initialize the global token counter instance.
|
||||
|
||||
Returns:
|
||||
TokenCounterBase: The token counter instance for Qwen models.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If token counter initialization fails.
|
||||
"""
|
||||
global _token_counter
|
||||
if _token_counter is None:
|
||||
# Use Qwen tokenizer for DashScope models
|
||||
# Qwen3 series uses the same tokenizer as Qwen2.5
|
||||
|
||||
# Try local tokenizer first, fall back to online if not found
|
||||
local_tokenizer_path = Path(__file__).parent.parent.parent / "tokenizer"
|
||||
|
||||
if local_tokenizer_path.exists() and (local_tokenizer_path / "tokenizer.json").exists():
|
||||
tokenizer_path = str(local_tokenizer_path)
|
||||
logger.info(f"Using local Qwen tokenizer from {tokenizer_path}")
|
||||
else:
|
||||
tokenizer_path = "Qwen/Qwen2.5-7B-Instruct"
|
||||
logger.info(
|
||||
"Local tokenizer not found, downloading from HuggingFace",
|
||||
)
|
||||
|
||||
_token_counter = HuggingFaceTokenCounter(
|
||||
pretrained_model_name_or_path=tokenizer_path,
|
||||
use_mirror=True, # Use HF mirror for users in China
|
||||
use_fast=True,
|
||||
trust_remote_code=True,
|
||||
)
|
||||
logger.debug("Token counter initialized with Qwen tokenizer")
|
||||
return _token_counter
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
ReMe Copaw Application Module
|
||||
ReMe Light Application Module
|
||||
|
||||
This module provides the ReMeCopaw class, a specialized application built on top of
|
||||
This module provides the ReMeLight class, a specialized application built on top of
|
||||
ReMe's core Application framework. It integrates memory management capabilities
|
||||
including memory compaction, summarization, tool result management, and semantic
|
||||
memory search functionality.
|
||||
|
|
@ -22,22 +22,23 @@ from pathlib import Path
|
|||
|
||||
from agentscope.formatter import FormatterBase
|
||||
from agentscope.message import Msg, TextBlock
|
||||
from agentscope.model import ChatModelBase
|
||||
from agentscope.model import ChatModelBase, OpenAIChatModel
|
||||
from agentscope.token import HuggingFaceTokenCounter
|
||||
from agentscope.tool import Toolkit, ToolResponse
|
||||
|
||||
from .config import ReMeConfigParser
|
||||
from .core import Application
|
||||
from .memory.file_based_copaw import Compactor, Summarizer, ToolResultCompactor, CoPawInMemoryMemory
|
||||
from .memory.file_based import Compactor, Summarizer, ToolResultCompactor, ReMeInMemoryMemory, ReMeChatFormatter
|
||||
from .memory.file_based.utils import get_token_counter
|
||||
from .memory.tools import MemorySearch
|
||||
from .core.utils import load_env
|
||||
|
||||
# Module-level logger for tracking application events and errors
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ReMeCopaw(Application):
|
||||
class ReMeLight(Application):
|
||||
"""
|
||||
ReMe Copaw Application Class
|
||||
ReMe Light Application Class
|
||||
|
||||
A specialized application class that extends ReMe's core Application framework
|
||||
with advanced memory management capabilities. This class is designed to handle
|
||||
|
|
@ -64,13 +65,17 @@ class ReMeCopaw(Application):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
working_dir: str,
|
||||
chat_model: ChatModelBase,
|
||||
formatter: FormatterBase,
|
||||
token_counter: HuggingFaceTokenCounter,
|
||||
toolkit: Toolkit,
|
||||
max_input_length: int,
|
||||
memory_compact_ratio: float,
|
||||
working_dir: str = ".reme",
|
||||
llm_api_key: str | None = None,
|
||||
llm_base_url: str | None = None,
|
||||
embedding_api_key: str | None = None,
|
||||
embedding_base_url: str | None = None,
|
||||
chat_model: ChatModelBase | None = None,
|
||||
formatter: FormatterBase | None = None,
|
||||
token_counter: HuggingFaceTokenCounter | None = None,
|
||||
toolkit: Toolkit | None = None,
|
||||
max_input_length: int = 128000,
|
||||
memory_compact_ratio: float = 0.7,
|
||||
language: str = "zh",
|
||||
vector_weight: float = 0.7,
|
||||
candidate_multiplier: float = 3.0,
|
||||
|
|
@ -90,23 +95,11 @@ class ReMeCopaw(Application):
|
|||
self.tool_result_path = self.working_path / "tool_result"
|
||||
self.tool_result_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Store references to core components
|
||||
self.chat_model: ChatModelBase = chat_model
|
||||
self.formatter: FormatterBase = formatter
|
||||
self.token_counter: HuggingFaceTokenCounter = token_counter
|
||||
self.toolkit: Toolkit = toolkit
|
||||
|
||||
# Initialize runtime parameters (will be updated via update_params)
|
||||
self.max_input_length: int = 0
|
||||
self.memory_compact_threshold: int = 0
|
||||
self.language: str = ""
|
||||
|
||||
# Store configuration parameters
|
||||
self.vector_weight: float = vector_weight
|
||||
self.candidate_multiplier: float = candidate_multiplier
|
||||
self.tool_result_threshold: int = tool_result_threshold
|
||||
self.retention_days: int = retention_days
|
||||
|
||||
# Apply initial parameter configuration
|
||||
self.update_params(
|
||||
max_input_length=max_input_length,
|
||||
|
|
@ -114,18 +107,21 @@ class ReMeCopaw(Application):
|
|||
language=language,
|
||||
)
|
||||
|
||||
# Retrieve embedding configuration from environment variables
|
||||
# These settings control the vector search capabilities
|
||||
(
|
||||
embedding_api_key,
|
||||
embedding_base_url,
|
||||
embedding_model_name,
|
||||
embedding_dimensions,
|
||||
embedding_cache_enabled,
|
||||
embedding_max_cache_size,
|
||||
embedding_max_input_length,
|
||||
embedding_max_batch_size,
|
||||
) = self.get_emb_envs()
|
||||
# Store configuration parameters
|
||||
self.vector_weight: float = vector_weight
|
||||
self.candidate_multiplier: float = candidate_multiplier
|
||||
self.tool_result_threshold: int = tool_result_threshold
|
||||
self.retention_days: int = retention_days
|
||||
|
||||
load_env()
|
||||
|
||||
llm_model_name = self._safe_str("LLM_MODEL_NAME", "")
|
||||
embedding_model_name = self._safe_str("EMBEDDING_MODEL_NAME", "")
|
||||
embedding_dimensions = self._safe_int("EMBEDDING_DIMENSIONS", 1024)
|
||||
embedding_cache_enabled = self._safe_str("EMBEDDING_CACHE_ENABLED", "true").lower() == "true"
|
||||
embedding_max_cache_size = self._safe_int("EMBEDDING_MAX_CACHE_SIZE", 2000)
|
||||
embedding_max_input_length = self._safe_int("EMBEDDING_MAX_INPUT_LENGTH", 8192)
|
||||
embedding_max_batch_size = self._safe_int("EMBEDDING_MAX_BATCH_SIZE", 10)
|
||||
|
||||
# Determine if vector search should be enabled based on configuration
|
||||
# Vector search requires either an API key or a local model name
|
||||
|
|
@ -149,13 +145,14 @@ class ReMeCopaw(Application):
|
|||
else:
|
||||
memory_backend = memory_store_backend
|
||||
|
||||
# Initialize the parent Application class with configuration
|
||||
# Initialize the parent Application class with comprehensive configuration
|
||||
super().__init__(
|
||||
llm_api_key=llm_api_key,
|
||||
llm_base_url=llm_base_url,
|
||||
embedding_api_key=embedding_api_key,
|
||||
embedding_base_url=embedding_base_url,
|
||||
working_dir=str(self.working_path),
|
||||
config_path="copaw",
|
||||
config_path="light",
|
||||
enable_logo=False,
|
||||
log_to_console=False,
|
||||
parser=ReMeConfigParser,
|
||||
|
|
@ -183,6 +180,27 @@ class ReMeCopaw(Application):
|
|||
},
|
||||
)
|
||||
|
||||
if chat_model is not None:
|
||||
self.chat_model: ChatModelBase = chat_model
|
||||
else:
|
||||
# add more params later
|
||||
self.chat_model = OpenAIChatModel(
|
||||
api_key=os.environ["LLM_API_KEY"],
|
||||
client_kwargs={"base_url": os.environ["LLM_BASE_URL"]},
|
||||
model_name=llm_model_name,
|
||||
)
|
||||
|
||||
if token_counter is not None:
|
||||
self.token_counter: HuggingFaceTokenCounter = token_counter
|
||||
else:
|
||||
self.token_counter = get_token_counter()
|
||||
|
||||
if formatter is not None:
|
||||
self.formatter: FormatterBase = formatter
|
||||
else:
|
||||
self.formatter = ReMeChatFormatter(token_counter=self.token_counter)
|
||||
self.toolkit: Toolkit | None = toolkit
|
||||
|
||||
# Initialize list to track background summarization tasks
|
||||
self.summary_tasks: list[asyncio.Task] = []
|
||||
|
||||
|
|
@ -264,55 +282,6 @@ class ReMeCopaw(Application):
|
|||
logger.warning(f"Invalid int value '{value}' for key '{key}', using default {default}")
|
||||
return default
|
||||
|
||||
def get_emb_envs(self):
|
||||
"""
|
||||
Retrieve all embedding-related configuration from environment variables.
|
||||
|
||||
This method collects all settings needed for the embedding service,
|
||||
including API credentials, model configuration, and caching parameters.
|
||||
|
||||
Environment Variables:
|
||||
EMBEDDING_API_KEY: API key for the embedding service
|
||||
EMBEDDING_BASE_URL: Base URL for the embedding API (default: dashscope)
|
||||
EMBEDDING_MODEL_NAME: Name of the embedding model to use
|
||||
EMBEDDING_DIMENSIONS: Vector dimensions (default: 1024)
|
||||
EMBEDDING_CACHE_ENABLED: Whether to enable caching (default: true)
|
||||
EMBEDDING_MAX_CACHE_SIZE: Maximum cache entries (default: 2000)
|
||||
EMBEDDING_MAX_INPUT_LENGTH: Max input text length (default: 8192)
|
||||
EMBEDDING_MAX_BATCH_SIZE: Max batch size for requests (default: 10)
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing all embedding configuration values in order:
|
||||
(api_key, base_url, model_name, dimensions, cache_enabled,
|
||||
max_cache_size, max_input_length, max_batch_size)
|
||||
"""
|
||||
# API authentication and endpoint configuration
|
||||
embedding_api_key = self._safe_str("EMBEDDING_API_KEY", "")
|
||||
embedding_base_url = self._safe_str("EMBEDDING_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
|
||||
embedding_model_name = self._safe_str("EMBEDDING_MODEL_NAME", "")
|
||||
|
||||
# Model and vector configuration
|
||||
embedding_dimensions = self._safe_int("EMBEDDING_DIMENSIONS", 1024)
|
||||
|
||||
# Caching configuration for performance optimization
|
||||
embedding_cache_enabled = self._safe_str("EMBEDDING_CACHE_ENABLED", "true").lower() == "true"
|
||||
embedding_max_cache_size = self._safe_int("EMBEDDING_MAX_CACHE_SIZE", 2000)
|
||||
|
||||
# Input processing limits
|
||||
embedding_max_input_length = self._safe_int("EMBEDDING_MAX_INPUT_LENGTH", 8192)
|
||||
embedding_max_batch_size = self._safe_int("EMBEDDING_MAX_BATCH_SIZE", 10)
|
||||
|
||||
return (
|
||||
embedding_api_key,
|
||||
embedding_base_url,
|
||||
embedding_model_name,
|
||||
embedding_dimensions,
|
||||
embedding_cache_enabled,
|
||||
embedding_max_cache_size,
|
||||
embedding_max_input_length,
|
||||
embedding_max_batch_size,
|
||||
)
|
||||
|
||||
def _cleanup_tool_results(self) -> int:
|
||||
"""
|
||||
Clean up expired tool result files from the tool result directory.
|
||||
|
|
@ -681,13 +650,13 @@ class ReMeCopaw(Application):
|
|||
"""
|
||||
Create and return an in-memory memory instance.
|
||||
|
||||
This method instantiates a CoPawInMemoryMemory object configured with
|
||||
This method instantiates a ReMeInMemoryMemory object configured with
|
||||
the current application's token counter, formatter, and input length limits.
|
||||
The in-memory memory provides fast, temporary storage for conversation
|
||||
context without persistence.
|
||||
|
||||
Returns:
|
||||
CoPawInMemoryMemory: A configured in-memory memory instance ready
|
||||
ReMeInMemoryMemory: A configured in-memory memory instance ready
|
||||
for storing and retrieving conversation messages
|
||||
|
||||
Note:
|
||||
|
|
@ -695,7 +664,7 @@ class ReMeCopaw(Application):
|
|||
- Useful for managing conversation context within a single session
|
||||
- Shares the same token counter and formatter as the main application
|
||||
"""
|
||||
return CoPawInMemoryMemory(
|
||||
return ReMeInMemoryMemory(
|
||||
token_counter=self.token_counter,
|
||||
formatter=self.formatter,
|
||||
max_input_length=self.max_input_length,
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
"""Test script for AgenticRetrieveOp.
|
||||
|
||||
This script provides a simple end-to-end test case for AgenticRetrieveOp.
|
||||
It can be run directly with: python test_agentic_retrieve_op.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from flowllm.core.enumeration import Role
|
||||
from flowllm.core.schema import Message, ToolCall
|
||||
from loguru import logger
|
||||
|
||||
from reme_ai.agent.react.agentic_retrieve_op import AgenticRetrieveOp
|
||||
from reme_ai.main import ReMeApp
|
||||
|
||||
|
||||
async def test_agentic_retrieve_basic():
|
||||
"""Basic test for AgenticRetrieveOp with a short conversation history."""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Test: AgenticRetrieveOp basic behavior")
|
||||
logger.info("=" * 60)
|
||||
|
||||
tool_call_id = "call_6596dafa2a6a46f7a217da"
|
||||
f = open("README.md", encoding="utf-8")
|
||||
readme_content = f.read()
|
||||
f.close()
|
||||
|
||||
messages = [
|
||||
Message(
|
||||
role=Role.SYSTEM,
|
||||
content=(
|
||||
"You are a helpful assistant. "
|
||||
"请先使用`Grep`匹配关键词或者正则表达式所在行数,然后通过`ReadFile`读取位置附近的代码。"
|
||||
"如果没有找到匹配项,永远不要放弃尝试,尝试其他的参数,比如只搜索部分关键词。"
|
||||
"`Grep`之后通过 `ReadFile` 命令,你可以从指定偏移位置`offset`+长度`limit`开始查看内容,不要超过100行。"
|
||||
"如果当前内容不足,`ReadFile` 命令也可以不断尝试不同的`offset`和`limit`参数"
|
||||
),
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="搜索下reme项目的的README内容",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="",
|
||||
tool_calls=[
|
||||
ToolCall(
|
||||
**{
|
||||
"index": 0,
|
||||
"id": tool_call_id,
|
||||
"function": {
|
||||
"arguments": '{"query": "readme"}',
|
||||
"name": "web_search",
|
||||
},
|
||||
"type": "function",
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content=readme_content * 4,
|
||||
tool_call_id=tool_call_id,
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="根据readme回答task memory在appworld的效果是多少,需要具体的数值",
|
||||
),
|
||||
]
|
||||
|
||||
# llm = "qwen3_coder_plus"
|
||||
llm = "qwen3_30b_instruct"
|
||||
# llm = "qwen3_30b_thinking"
|
||||
# llm = "qwen3_coder_30b_instruct"
|
||||
# llm = "qwen3_max_instruct"
|
||||
op = AgenticRetrieveOp(llm=llm)
|
||||
|
||||
await op.async_call(
|
||||
messages=[m.model_dump() for m in messages],
|
||||
working_summary_mode="auto",
|
||||
compact_ratio_threshold=0.75,
|
||||
max_total_tokens=20000,
|
||||
max_tool_message_tokens=2000,
|
||||
group_token_threshold=None,
|
||||
keep_recent_count=1,
|
||||
store_dir="./test_working_memory",
|
||||
chat_id="c123",
|
||||
)
|
||||
|
||||
answer = op.context.response.answer
|
||||
messages = op.context.response.metadata["messages"]
|
||||
logger.info(f"✓ AgenticRetrieveOp result answer: {answer}")
|
||||
logger.info(f"✓ AgenticRetrieveOp result messages: {json.dumps(messages, ensure_ascii=False, indent=2)}")
|
||||
logger.info(f" Success: {op.context.response.success}")
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""Entry point for running AgenticRetrieveOp test."""
|
||||
async with ReMeApp():
|
||||
logger.info("=" * 80)
|
||||
logger.info("Testing AgenticRetrieveOp - ReAct Retrieval Workflow")
|
||||
logger.info("=" * 80)
|
||||
|
||||
await test_agentic_retrieve_basic()
|
||||
|
||||
logger.info("\n" + "=" * 80)
|
||||
logger.info("All AgenticRetrieveOp tests completed!")
|
||||
logger.info("=" * 80)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(async_main())
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
"""Test script for MessageCompactOp.
|
||||
|
||||
This script provides test cases for MessageCompactOp class.
|
||||
It can be run directly with: python test_context_compact_op.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from flowllm.core.enumeration import Role
|
||||
from flowllm.core.schema import Message
|
||||
|
||||
from reme_ai.main import ReMeApp
|
||||
from reme_ai.retrieve.working import BatchWriteFileOp
|
||||
from reme_ai.summary.working import MessageCompactOp
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""Test function for MessageCompactOp."""
|
||||
async with ReMeApp():
|
||||
# Create test messages with system, user, assistant, tool sequence
|
||||
messages = [
|
||||
Message(role=Role.SYSTEM, content="You are a helpful assistant."),
|
||||
Message(role=Role.USER, content="What is the weather today?"),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check the weather for you.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="A" * 5000, # Large tool message that should be compacted
|
||||
tool_call_id="call_001",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="Let me also check the forecast.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="B" * 5000, # Another large tool message
|
||||
tool_call_id="call_002",
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="What about tomorrow?",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check tomorrow's weather.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="C" * 5000, # Third large tool message
|
||||
tool_call_id="call_003",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="Recent result", # Recent tool message (should be kept)
|
||||
tool_call_id="call_004",
|
||||
),
|
||||
]
|
||||
|
||||
# Create op with lower thresholds for testing
|
||||
op = MessageCompactOp() >> BatchWriteFileOp()
|
||||
|
||||
# Execute the compaction
|
||||
await op.async_call(
|
||||
messages=[m.model_dump() for m in messages],
|
||||
max_total_tokens=1000, # Low threshold to trigger compaction
|
||||
max_tool_message_tokens=100, # Low threshold to compact tool messages
|
||||
preview_char_length=50, # Keep 50 chars in preview
|
||||
keep_recent_count=1, # Keep 1 recent tool message
|
||||
store_dir="./test_compact_storage",
|
||||
)
|
||||
|
||||
# Print results
|
||||
result = op.context.response.answer
|
||||
print(f"Context compaction result: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(async_main())
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
"""
|
||||
Test script for MessageCompressOp.
|
||||
|
||||
This script demonstrates how to use the message compression operation to reduce
|
||||
token usage in conversation histories using language models.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from reme_ai.main import ReMeApp
|
||||
from reme_ai.summary.working import MessageCompressOp
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to test MessageCompressOp."""
|
||||
|
||||
async with ReMeApp():
|
||||
logger.info("=" * 80)
|
||||
logger.info("Testing MessageCompressOp - LLM-based Context Compression")
|
||||
logger.info("=" * 80)
|
||||
|
||||
# Create a mock conversation with multiple messages
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful AI assistant specialized in software development.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I need help building a REST API in Python. I want to use FastAPI.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Great choice! FastAPI is an excellent framework for building REST APIs. "
|
||||
"It's fast, modern, and has automatic API documentation. To get started, you'll need "
|
||||
"to install FastAPI and uvicorn. Would you like me to guide you through setting up "
|
||||
"your first endpoint?",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Yes please. I want to create a user management API with CRUD operations.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Perfect! For a user management API, I recommend this structure:\n"
|
||||
"1. Define a User model using Pydantic\n"
|
||||
"2. Create POST /users endpoint for creating users\n"
|
||||
"3. Create GET /users and GET /users/{id} for reading\n"
|
||||
"4. Create PUT /users/{id} for updates\n"
|
||||
"5. Create DELETE /users/{id} for deletion\n"
|
||||
"We'll also need a database. Would you prefer SQLite, PostgreSQL, or MongoDB?",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Let's use PostgreSQL. Also, I need JWT authentication.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Excellent. PostgreSQL is a robust choice. For JWT authentication, we'll use "
|
||||
"python-jose library. Here's what we'll implement:\n"
|
||||
"1. User registration endpoint\n"
|
||||
"2. Login endpoint that returns JWT token\n"
|
||||
"3. Protected endpoints that require valid JWT\n"
|
||||
"4. Password hashing using bcrypt\n"
|
||||
"Let me show you the code for the User model first.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Before we proceed, I also need rate limiting and input validation.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Good thinking! For rate limiting, we can use slowapi library which integrates "
|
||||
"well with FastAPI. For input validation, Pydantic (which FastAPI uses) handles most of it, "
|
||||
"but we can add custom validators. I'll also add request validation middleware. "
|
||||
"Let's start implementing all of this step by step.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I need to build a distributed task queue system in Python that can handle millions of tasks"
|
||||
" per day. It needs to be horizontally scalable and fault-tolerant.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "That's a challenging but exciting project! For a high-throughput distributed task queue, "
|
||||
"I recommend a architecture with:\n\n1. **Message Broker**: Redis or RabbitMQ for task "
|
||||
"distribution\n2. **Task Workers**: Multiple worker processes across multiple machines\n3."
|
||||
" **Result Backend**: Redis or PostgreSQL for storing task results\n4. **Monitoring**:"
|
||||
" Prometheus + Grafana for metrics\n5. **API Layer**: FastAPI for task submission and "
|
||||
"status queries\n\nFor the core library, we can build on top of Celery or create a custo"
|
||||
"m solution. Would you like me to design the system architecture first, or do you have pr"
|
||||
"eferences for specific technologies?",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I want to build this from scratch without Celery. I need custom features like task"
|
||||
" priorities, retry policies with exponential backoff, and task dependencies. Also,"
|
||||
" I need it to support both synchronous and asynchronous task execution patterns.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Excellent! Building from scratch gives us full control. Let me design a comprehensive "
|
||||
"system architecture:\n\n**Core Components:**\n1. **Task Router**: Distributes tasks based"
|
||||
" on priority queues\n2. **Worker Manager**: Handles worker lifecycle and load balancing\n3."
|
||||
" **Retry Engine**: Implements exponential backoff with jitter\n4. **Dependency Graph**:"
|
||||
" Manages task dependencies using topological sorting\n5. **State Manager**: Tracks task "
|
||||
"states (pending, running, completed, failed)\n6. **Metrics Collector**: Real-time "
|
||||
"performance metrics\n\n**Data Structures:**\n- Priority queues using Redis Sorted Sets\n-"
|
||||
" Task metadata in PostgreSQL with JSONB columns\n- Distributed locks with Redis Redlock"
|
||||
" algorithm\n\n**Features:**\n- At-least-once delivery guarantee\n- Dead letter queue for "
|
||||
"failed tasks\n- Rate limiting per task type\n- Circuit breaker pattern for failing "
|
||||
"services\n\nWould you like me to start with the core task model and priority queue "
|
||||
"implementation?",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "That sounds good. But I also need WebSocket support for real-time task status updates, a "
|
||||
"web dashboard for monitoring, and the ability to dynamically scale workers based on queue"
|
||||
" depth. Plus, I need task deduplication and exactly-once processing semantics.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Perfect! Let me enhance the architecture with these advanced requirements:\n\n**Real-time "
|
||||
"Features:**\n1. **WebSocket Manager**: Using Python's websocket-server with Redis pub/sub "
|
||||
"for multi-instance communication\n2. **Event Stream**: Task state changes published to Re"
|
||||
"dis streams\n3. **Client SDK**: JavaScript/TypeScript library for real-time subscriptio"
|
||||
"ns\n\n**Auto-scaling Architecture:**\n1. **Metrics Aggregator**: Collects queue depth, "
|
||||
"processing latency, error rates\n2. **Scaler Service**: Kubernetes HPA integration or cu"
|
||||
"stom Docker Swarm scaling\n3. **Predictive Scaling**: ML model to predict load based on hi"
|
||||
"storical patterns\n\n**Exactly-once Processing:**\n1. **Idempotency Keys**: Each task has "
|
||||
"UUID-based idempotency key\n2. **Deduplication Store**: Redis with TTL for processed task "
|
||||
"IDs\n3. **Transactional Outbox**: PostgreSQL outbox pattern for reliable event publishing"
|
||||
"\n4. **Two-phase Commit**: For distributed task processing\n\n**Dashboard Features:**\n- "
|
||||
"Real-time queue metrics with D3.js visualizations\n- Task timeline view with dependency g"
|
||||
"raphs\n- Worker health monitoring with automatic restart\n- Alert system for SLA violati"
|
||||
"ons\n\n**Additional Components:**\n- Task scheduler for delayed tasks using Redis keys w"
|
||||
"ith TTL\n- Batch processing support for bulk operations\n- Multi-tenant isolation with na"
|
||||
"mespace support\n\nThis is getting quite comprehensive! Should I start with the core tas"
|
||||
"k model and database schema, or would you prefer to begin with the WebSocket real-time up"
|
||||
"date system?",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "This is getting complex. I also need multi-region support with eventual consistency, the "
|
||||
"ability to pause/resume task processing by task type, and A/B testing capabilities for "
|
||||
"different worker implementations. Also, I need comprehensive audit logging and GDPR comp"
|
||||
"liance features.",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Absolutely! This is evolving into an enterprise-grade system. Let me architect the compl"
|
||||
"ete solution:\n\n**Multi-region Architecture:**\n1. **Global Load Balancer**: GeoDNS with"
|
||||
" health checks\n2. **Cross-region Replication**: Redis Cluster with active-active setup "
|
||||
"using CRDTs\n3. **Conflict Resolution**: Vector clocks for task ordering, last-writer-win"
|
||||
"s for metadata\n4. **Region-aware Routing**: Route tasks to workers in same region when p"
|
||||
"ossible\n5. **Failover Mechanism**: Automatic region failover with 30-second RTO\n\n**Adv"
|
||||
"anced Control Features:**\n1. **Task Type Governance**: \n - Pause/resume via Redis fe"
|
||||
"ature flags with immediate propagation\n - Rate limits per task type with burst capaci"
|
||||
"ty\n - Resource quotas (CPU/memory) per task category\n2. **A/B Testing Framework**:\n"
|
||||
" - Task routing based on consistent hashing of task ID\n - Variant assignment with s"
|
||||
"tickiness\n - Statistical significance tracking for performance metrics\n - Automati"
|
||||
"c winner selection based on success rate and latency\n\n**Compliance & Audit:**\n1. **A"
|
||||
"udit Trail**:\n - Immutable task history in PostgreSQL with row-level security\n -"
|
||||
" Change data capture (CDC) using Debezium\n - Cryptographic signing of audit logs\n "
|
||||
" - 7-year retention policy with automated archival to S3\n2. **GDPR Compliance**:\n "
|
||||
" - Right to be forgotten: Cascade delete with verification\n - Data portability: JSO"
|
||||
"N export of all user tasks\n - Consent management: Task processing consent tracking"
|
||||
"\n - Data anonymization: PII encryption with rotating keys\n\n**Enhanced Monitoring:*"
|
||||
"*\n1. **Distributed Tracing**: OpenTelemetry integration with Jaeger\n2. **Performance "
|
||||
"Profiling**: Py-spy continuous profiling\n3. **Cost Analytics**: Per-task-type resource"
|
||||
" consumption tracking\n4. **SLA Monitoring**: 99.9% task processing latency targets\n\n"
|
||||
"**Security Features:**\n1. **Zero-trust Architecture**: mTLS between all services\n2. **"
|
||||
"Secrets Management**: HashiCorp Vault integration\n3. **Network Segmentation**: VPC is"
|
||||
"olation with private subnets\n4. **Encryption**: AES-256 at rest, TLS 1.3 in transit\n"
|
||||
"\n**Operational Excellence:**\n1. **Blue-green Deployments**: Zero-downtime updates\n2."
|
||||
" **Canary Releases**: Gradual rollout of worker changes\n3. **Chaos Engineering**: Rand"
|
||||
"om worker termination testing\n4. **Capacity Planning**: Automated infrastructure provi"
|
||||
"sioning\n\nThis is becoming a mission-critical system! Would you like me to start with "
|
||||
"the database schema and core task lifecycle management, or should we begin with the mul"
|
||||
"ti-region replication strategy? I can also create a detailed implementation roadmap wi"
|
||||
"th phases if you prefer.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "How do I reverse a string in Python?",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "You can reverse a string in Python using slicing: `reversed_string = original_string[::"
|
||||
"-1]`. The `[::-1]` slice notation means start at the end of the string and end at posit"
|
||||
"ion 0, moving with the step -1.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the difference between a list and a tuple?",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Lists are mutable (you can change their content after creation) and use square bracket"
|
||||
"s `[]`. Tuples are immutable and use parentheses `()`. Lists are better for collectio"
|
||||
"ns that need to change, while tuples are faster and can be used as dictionary keys.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "How do I handle exceptions in Python?",
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Use try-except blocks: `try:` followed by your risky code, then `except SpecificError"
|
||||
":` to catch particular exceptions. Always catch specific exceptions rather than the g"
|
||||
"eneric `Exception` when possible. You can also use `finally:` for cleanup code that ru"
|
||||
"ns regardless.",
|
||||
},
|
||||
]
|
||||
|
||||
# Test 1: Messages below threshold (should not compress)
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Test 1: Messages below threshold (should skip compression)")
|
||||
logger.info("=" * 60)
|
||||
|
||||
compress_op1 = MessageCompressOp()
|
||||
|
||||
await compress_op1.async_call(
|
||||
messages=messages,
|
||||
max_total_tokens=50000, # High threshold, won't trigger
|
||||
keep_recent_count=2,
|
||||
)
|
||||
|
||||
result_messages1 = compress_op1.context.response.answer
|
||||
logger.info(f"✓ Result: {len(result_messages1)} messages (unchanged)")
|
||||
|
||||
# Test 2: Messages above threshold (should compress)
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Test 2: Messages above threshold (should compress)")
|
||||
logger.info("=" * 60)
|
||||
|
||||
compress_op2 = MessageCompressOp()
|
||||
|
||||
await compress_op2.async_call(
|
||||
messages=messages,
|
||||
max_total_tokens=2000, # Low threshold, will trigger
|
||||
keep_recent_count=2,
|
||||
compress_system_message=False,
|
||||
)
|
||||
|
||||
result_messages2 = compress_op2.context.response.answer
|
||||
logger.info(f"✓ Result: {len(result_messages2)} messages (compressed)")
|
||||
|
||||
# Display compression results
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Compression Result Details:")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Original messages: {len(messages)}")
|
||||
logger.info(f"Compressed messages: {len(result_messages2)}")
|
||||
|
||||
# Test 3: Messages above threshold (should compress)
|
||||
logger.info("\n" + "=!" * 30)
|
||||
logger.info("Test 3: Messages above micro threshold (should compress)")
|
||||
logger.info("=!" * 30)
|
||||
|
||||
compress_op2 = MessageCompressOp()
|
||||
|
||||
await compress_op2.async_call(
|
||||
messages=messages,
|
||||
max_total_tokens=2000, # Low threshold, will trigger
|
||||
keep_recent_count=2,
|
||||
compress_system_message=False, # Don't compress system messages
|
||||
group_token_threshold=1500,
|
||||
)
|
||||
|
||||
result_messages2 = compress_op2.context.response.answer
|
||||
logger.info(f"✓ Result: {len(result_messages2)} messages (compressed)")
|
||||
|
||||
# Display compression results
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Compression Result Details:")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Original messages: {len(messages)}")
|
||||
logger.info(f"Compressed messages: {len(result_messages2)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
"""Test script for MessageOffloadOp.
|
||||
|
||||
This script provides test cases for MessageOffloadOp class.
|
||||
It can be run directly with: python test_context_offload_op.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from flowllm.core.enumeration import Role
|
||||
from flowllm.core.schema import Message
|
||||
from loguru import logger
|
||||
|
||||
from reme_ai.enumeration import WorkingSummaryMode
|
||||
from reme_ai.main import ReMeApp
|
||||
from reme_ai.retrieve.working import BatchWriteFileOp
|
||||
from reme_ai.summary.working import MessageOffloadOp
|
||||
|
||||
|
||||
async def test_compact_mode():
|
||||
"""Test COMPACT mode - Only apply compaction with MessageOffloadOp."""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Test: COMPACT mode - Only apply compaction")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Create test messages with system, user, assistant, tool sequence
|
||||
messages = [
|
||||
Message(role=Role.SYSTEM, content="You are a helpful assistant."),
|
||||
Message(role=Role.USER, content="What is the weather today?"),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check the weather for you.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="A" * 5000, # Large tool message that should be compacted
|
||||
tool_call_id="call_001",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="Let me also check the forecast.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="B" * 5000, # Another large tool message
|
||||
tool_call_id="call_002",
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="What about tomorrow?",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check tomorrow's weather.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="C" * 5000, # Third large tool message
|
||||
tool_call_id="call_003",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="Recent result", # Recent tool message (should be kept)
|
||||
tool_call_id="call_004",
|
||||
),
|
||||
]
|
||||
|
||||
op = MessageOffloadOp() >> BatchWriteFileOp()
|
||||
|
||||
await op.async_call(
|
||||
messages=[m.model_dump() for m in messages],
|
||||
context_manage_mode=WorkingSummaryMode.COMPACT,
|
||||
max_total_tokens=1000, # Low threshold to trigger compaction
|
||||
max_tool_message_tokens=100, # Low threshold to compact tool messages
|
||||
preview_char_length=50, # Keep 50 chars in preview
|
||||
keep_recent_count=1, # Keep 1 recent tool message
|
||||
store_dir="./test_compact_storage",
|
||||
)
|
||||
|
||||
result = op.context.response.answer
|
||||
logger.info(f"✓ COMPACT mode result: {len(result)} messages")
|
||||
logger.info(f" Success: {op.context.response.success}")
|
||||
|
||||
|
||||
async def test_compress_mode():
|
||||
"""Test COMPRESS mode - Only apply compression with MessageOffloadOp."""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Test: COMPRESS mode - Only apply compression")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Create test messages with system, user, assistant, tool sequence
|
||||
messages = [
|
||||
Message(role=Role.SYSTEM, content="You are a helpful assistant."),
|
||||
Message(role=Role.USER, content="What is the weather today?"),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check the weather for you.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="A" * 5000, # Large tool message that should be compacted
|
||||
tool_call_id="call_001",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="Let me also check the forecast.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="B" * 5000, # Another large tool message
|
||||
tool_call_id="call_002",
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="What about tomorrow?",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check tomorrow's weather.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="C" * 5000, # Third large tool message
|
||||
tool_call_id="call_003",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="Recent result", # Recent tool message (should be kept)
|
||||
tool_call_id="call_004",
|
||||
),
|
||||
]
|
||||
|
||||
op = MessageOffloadOp() >> BatchWriteFileOp()
|
||||
|
||||
await op.async_call(
|
||||
messages=[m.model_dump() for m in messages],
|
||||
context_manage_mode=WorkingSummaryMode.COMPRESS,
|
||||
max_total_tokens=2000, # Low threshold to trigger compression
|
||||
keep_recent_count=2,
|
||||
store_dir="./test_compact_storage",
|
||||
)
|
||||
|
||||
result = op.context.response.answer
|
||||
logger.info(f"✓ COMPRESS mode result: {len(result)} messages")
|
||||
logger.info(f" Success: {op.context.response.success}")
|
||||
|
||||
|
||||
async def test_auto_mode():
|
||||
"""Test AUTO mode - Apply compaction first, then compression if needed using MessageOffloadOp."""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("Test: AUTO mode - Apply compaction first, then compression if needed")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Create messages with extensive user content to ensure compact ratio exceeds threshold
|
||||
auto_messages = [
|
||||
Message(role=Role.SYSTEM, content="You are a helpful assistant."),
|
||||
Message(role=Role.USER, content="What is the weather today?"),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check the weather for you.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="A" * 5000, # Large tool message that should be compacted
|
||||
tool_call_id="call_001",
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="I need detailed information about the weather forecast for the next week. "
|
||||
"Please provide temperature, humidity, wind speed, and precipitation chances for each day. "
|
||||
"Also, I want to know about any weather warnings or advisories. "
|
||||
"This is very important for my travel planning." * 50, # Long user message
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll gather comprehensive weather information for you. Let me check multiple sources." * 3,
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="B" * 5000, # Another large tool message
|
||||
tool_call_id="call_002",
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="Can you also provide information about air quality, UV index, and sunrise/sunset times? "
|
||||
"I'm planning outdoor activities and need to know the best times to be outside. "
|
||||
"Also, please include historical weather data for comparison." * 4, # More long user content
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="Absolutely! I'll get all that information for you including air quality metrics and UV data." * 2,
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="C" * 5000, # Third large tool message
|
||||
tool_call_id="call_003",
|
||||
),
|
||||
Message(
|
||||
role=Role.USER,
|
||||
content="What about tomorrow?",
|
||||
),
|
||||
Message(
|
||||
role=Role.ASSISTANT,
|
||||
content="I'll check tomorrow's weather.",
|
||||
),
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
content="Recent result", # Recent tool message (should be kept)
|
||||
tool_call_id="call_004",
|
||||
),
|
||||
]
|
||||
|
||||
op = MessageOffloadOp() >> BatchWriteFileOp()
|
||||
|
||||
await op.async_call(
|
||||
messages=[m.model_dump() for m in auto_messages],
|
||||
context_manage_mode=WorkingSummaryMode.AUTO,
|
||||
compact_ratio_threshold=0.2, # Low threshold, should trigger compression after compact
|
||||
max_total_tokens=1000,
|
||||
max_tool_message_tokens=100,
|
||||
preview_char_length=50,
|
||||
keep_recent_count=1,
|
||||
store_dir="./test_compact_storage",
|
||||
)
|
||||
|
||||
result = op.context.response.answer
|
||||
logger.info(f"✓ AUTO mode result: {len(result)} messages")
|
||||
logger.info(f" Success: {op.context.response.success}")
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""Test function for MessageOffloadOp."""
|
||||
async with ReMeApp():
|
||||
logger.info("=" * 80)
|
||||
logger.info("Testing MessageOffloadOp - Context Management Orchestration")
|
||||
logger.info("=" * 80)
|
||||
|
||||
await test_compact_mode()
|
||||
await test_compress_mode()
|
||||
await test_auto_mode()
|
||||
|
||||
logger.info("\n" + "=" * 80)
|
||||
logger.info("All tests completed!")
|
||||
logger.info("=" * 80)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(async_main())
|
||||
|
|
@ -10,7 +10,7 @@ from test_utils import (
|
|||
get_formatter,
|
||||
get_token_counter,
|
||||
)
|
||||
from reme.memory.file_based_copaw import Compactor
|
||||
from reme.memory.file_based import Compactor
|
||||
|
||||
# 配置日志输出到控制台
|
||||
logging.basicConfig(
|
||||
|
|
@ -7,7 +7,7 @@ import logging
|
|||
from agentscope.message import Msg
|
||||
|
||||
from test_utils import get_token_counter
|
||||
from reme.memory.file_based_copaw import MemoryFormatter
|
||||
from reme.memory.file_based import MemoryFormatter
|
||||
|
||||
# 配置日志输出到控制台
|
||||
logging.basicConfig(
|
||||
198
tests/light/test_reme_light.py
Normal file
198
tests/light/test_reme_light.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
"""测试 ReMeLight"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from agentscope.message import Msg
|
||||
from reme.reme_light import ReMeLight
|
||||
|
||||
|
||||
# ==================== 消息创建辅助函数 ====================
|
||||
def create_user_msg(content: str) -> Msg:
|
||||
"""创建用户消息"""
|
||||
return Msg(name="user", role="user", content=content)
|
||||
|
||||
|
||||
def create_assistant_msg(content: str) -> Msg:
|
||||
"""创建助手消息"""
|
||||
return Msg(name="assistant", role="assistant", content=content)
|
||||
|
||||
|
||||
def create_tool_use_msg(tool_id: str, tool_name: str, tool_input: dict) -> Msg:
|
||||
"""创建工具调用消息"""
|
||||
return Msg(
|
||||
name="assistant",
|
||||
role="assistant",
|
||||
content=[
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": tool_id,
|
||||
"name": tool_name,
|
||||
"input": tool_input,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def create_tool_result_msg(tool_id: str, tool_name: str, output: str) -> Msg:
|
||||
"""创建工具结果消息"""
|
||||
return Msg(
|
||||
name="tool",
|
||||
role="user",
|
||||
content=[
|
||||
{
|
||||
"type": "tool_result",
|
||||
"id": tool_id,
|
||||
"name": tool_name,
|
||||
"output": output,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def create_thinking_msg(thinking_content: str) -> Msg:
|
||||
"""创建思考消息"""
|
||||
return Msg(
|
||||
name="assistant",
|
||||
role="assistant",
|
||||
content=[
|
||||
{
|
||||
"type": "thinking",
|
||||
"text": thinking_content,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# ==================== 构建模拟对话历史 ====================
|
||||
def build_sample_messages() -> list[Msg]:
|
||||
"""构建一段包含多种消息类型的模拟对话"""
|
||||
messages = [
|
||||
# 用户询问 Python 版本
|
||||
create_user_msg("我想设置一个 Python 开发环境,你有什么建议?"),
|
||||
# 助手思考
|
||||
create_thinking_msg("用户想要搭建 Python 开发环境,我需要了解他的需求和偏好..."),
|
||||
# 助手回复
|
||||
create_assistant_msg(
|
||||
"好的!我建议使用 Python 3.11 或 3.12 版本,它们性能更好且功能丰富。"
|
||||
"你希望用于什么类型的开发?Web、数据科学还是其他?",
|
||||
),
|
||||
# 用户提供更多信息
|
||||
create_user_msg("主要是做 Web 开发,使用 FastAPI 框架。另外我喜欢用 pyenv 管理版本。"),
|
||||
# 助手调用工具查询
|
||||
create_tool_use_msg(
|
||||
tool_id="call_001",
|
||||
tool_name="search_web",
|
||||
tool_input={"query": "FastAPI Python version compatibility 2024"},
|
||||
),
|
||||
# 工具返回结果(模拟较长的输出)
|
||||
create_tool_result_msg(
|
||||
tool_id="call_001",
|
||||
tool_name="search_web",
|
||||
output=(
|
||||
"FastAPI 官方推荐使用 Python 3.8+ 版本,但 3.11/3.12 性能最佳。\n"
|
||||
"主要依赖:\n"
|
||||
"- Starlette: ASGI 框架\n"
|
||||
"- Pydantic v2: 数据验证\n"
|
||||
"- Uvicorn: ASGI 服务器\n"
|
||||
"最新版本 FastAPI 0.109+ 完全支持 Python 3.12。\n"
|
||||
"建议搭配 uv 或 pip-tools 进行依赖管理。"
|
||||
),
|
||||
),
|
||||
# 助手总结建议
|
||||
create_assistant_msg(
|
||||
"根据查询结果,我的建议是:\n"
|
||||
"1. **Python 版本**: 使用 Python 3.11 或 3.12(通过 pyenv 安装)\n"
|
||||
"2. **框架**: FastAPI 0.109+ 完全兼容这些版本\n"
|
||||
"3. **依赖管理**: 推荐使用 uv(更快)或 pip-tools\n"
|
||||
"4. **ASGI 服务器**: Uvicorn 配合 gunicorn 用于生产环境\n\n"
|
||||
"需要我帮你生成一个项目模板吗?",
|
||||
),
|
||||
# 用户确认偏好
|
||||
create_user_msg("好的,我决定用 Python 3.12 + FastAPI + uv。请记住我的这些偏好。"),
|
||||
# 助手确认
|
||||
create_assistant_msg(
|
||||
"已记录你的开发偏好:\n"
|
||||
"- Python 版本: 3.12 (通过 pyenv 管理)\n"
|
||||
"- Web 框架: FastAPI\n"
|
||||
"- 包管理器: uv\n"
|
||||
"以后有相关问题我会参考这些偏好给你建议!",
|
||||
),
|
||||
]
|
||||
return messages
|
||||
|
||||
|
||||
# ==================== 主测试流程 ====================
|
||||
async def main():
|
||||
"""ReMeLight 主测试流程,演示完整的记忆管理功能。"""
|
||||
# 初始化 ReMeLight
|
||||
reme = ReMeLight(
|
||||
working_dir=".reme", # 记忆文件存储目录
|
||||
max_input_length=128000, # 模型上下文窗口(tokens)
|
||||
memory_compact_ratio=0.7, # 达到 max_input_length * 0.7 时触发压缩
|
||||
language="zh", # 摘要语言(zh / "")
|
||||
tool_result_threshold=1000, # 超过此字符数的工具输出自动转存
|
||||
retention_days=7, # tool_result/ 文件保留天数
|
||||
)
|
||||
await reme.start()
|
||||
print("=" * 60)
|
||||
print("ReMeLight 已启动")
|
||||
print("=" * 60)
|
||||
|
||||
# 构建模拟对话历史
|
||||
messages = build_sample_messages()
|
||||
print(f"\n[原始消息数量]: {len(messages)} 条")
|
||||
|
||||
# 1. 压缩超长工具输出(防止工具结果撑爆上下文)
|
||||
print("\n" + "-" * 40)
|
||||
print("[步骤 1] 压缩超长工具输出...")
|
||||
messages = await reme.compact_tool_result(messages)
|
||||
print(f"处理后消息数量: {len(messages)} 条")
|
||||
|
||||
# 2. 将历史对话压缩为结构化摘要(触发时机:上下文接近上限)
|
||||
print("\n" + "-" * 40)
|
||||
print("[步骤 2] 生成结构化压缩摘要...")
|
||||
summary = await reme.compact_memory(
|
||||
messages=messages,
|
||||
previous_summary="", # 可传入上轮摘要,实现增量更新
|
||||
)
|
||||
print(f"压缩摘要:\n{summary[:500]}..." if len(summary) > 500 else f"压缩摘要:\n{summary}")
|
||||
|
||||
# 3. 后台异步提交摘要任务(不阻塞对话,摘要写入 memory/YYYY-MM-DD.md)
|
||||
print("\n" + "-" * 40)
|
||||
print("[步骤 3] 提交后台异步摘要任务...")
|
||||
reme.add_async_summary_task(messages=messages)
|
||||
print("异步任务已提交")
|
||||
|
||||
# 4. 语义搜索记忆(向量 + BM25 混合检索)
|
||||
print("\n" + "-" * 40)
|
||||
print("[步骤 4] 语义搜索记忆...")
|
||||
result = await reme.memory_search(query="Python 版本偏好", max_results=5)
|
||||
print(f"搜索结果: {result}")
|
||||
|
||||
# 5. 获取会话内存实例(ReMeInMemoryMemory,管理单次对话的上下文)
|
||||
print("\n" + "-" * 40)
|
||||
print("[步骤 5] 获取会话内存实例并估算 Token 使用...")
|
||||
memory = reme.get_in_memory_memory()
|
||||
# 将消息添加到内存中以便估算
|
||||
for msg in messages:
|
||||
await memory.add(msg)
|
||||
token_stats = await memory.estimate_tokens()
|
||||
print(f"当前上下文使用率: {token_stats['context_usage_ratio']:.1f}%")
|
||||
print(f"消息 Token 数: {token_stats['messages_tokens']}")
|
||||
print(f"预估总 Token 数: {token_stats['estimated_tokens']}")
|
||||
|
||||
# 6. 关闭前等待后台任务完成
|
||||
print("\n" + "-" * 40)
|
||||
print("[步骤 6] 等待后台任务完成...")
|
||||
summary_result = await reme.await_summary_tasks()
|
||||
print(f"后台摘要任务完成,结果长度: {len(summary_result)} 字符")
|
||||
|
||||
# 关闭 ReMeLight
|
||||
await reme.close()
|
||||
print("\n" + "=" * 60)
|
||||
print("ReMeLight 已关闭")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -13,7 +13,7 @@ from test_utils import (
|
|||
get_formatter,
|
||||
get_token_counter,
|
||||
)
|
||||
from reme.memory.file_based_copaw import Summarizer
|
||||
from reme.memory.file_based import Summarizer
|
||||
|
||||
# 配置日志输出到控制台
|
||||
logging.basicConfig(
|
||||
|
|
@ -7,8 +7,8 @@ from pathlib import Path
|
|||
|
||||
from agentscope.message import Msg
|
||||
|
||||
from reme.memory.file_based_copaw.tool_result_compactor import ToolResultCompactor
|
||||
from reme.memory.file_based_copaw.utils import TRUNCATION_MARKER_START
|
||||
from reme.memory.file_based.tool_result_compactor import ToolResultCompactor
|
||||
from reme.memory.file_based.utils import TRUNCATION_MARKER_START
|
||||
|
||||
|
||||
def create_tool_result_msg(output: str | list, tool_name: str = "test_tool") -> Msg:
|
||||
|
|
@ -64,7 +64,7 @@ def get_formatter():
|
|||
"""Get formatter instance."""
|
||||
from agentscope.formatter import OpenAIChatFormatter
|
||||
from agentscope.token import HuggingFaceTokenCounter
|
||||
from reme.memory.file_based_copaw.utils import _extract_text_from_messages
|
||||
from reme.memory.file_based.utils import _extract_text_from_messages
|
||||
|
||||
class ReMeChatFormatter(OpenAIChatFormatter):
|
||||
"""ReMe chat formatter class."""
|
||||
Loading…
Add table
Reference in a new issue