mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
feat(memory): add ContextChecker component for context size management (#144)
* feat(memory): add ContextChecker component for context size management * refactor(memory): restructure file-based memory tools and update imports * docs(readme): update documentation with detailed architecture and components * docs(readme): update Chinese documentation with enhanced memory management diagrams * refactor(cookbook): move cookbook files to test directory and clean up docs * docs(readme): update link path for old version documentation * docs(readme): update documentation with improved architecture diagrams and component details * docs(readme): update documentation with improved clarity and structure * refactor(docs): update in-memory memory documentation * docs(readme): add experiment reproduction link to quickstart guide
This commit is contained in:
parent
dcf97dc77f
commit
d0c9d89092
107 changed files with 1922 additions and 1301 deletions
571
README.md
571
README.md
|
|
@ -12,7 +12,7 @@
|
|||
<p align="center">
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-black" alt="License"></a>
|
||||
<a href="./README.md"><img src="https://img.shields.io/badge/English-Click-yellow" alt="English"></a>
|
||||
<a href="./README_ZH.md"><img src="https://img.shields.io/badge/简体中文-点击查看-orange" alt="简体中文"></a>
|
||||
<a href="./README_ZH.md"><img src="https://img.shields.io/badge/Simplified%20Chinese-Click-orange" alt="Simplified Chinese"></a>
|
||||
<a href="https://github.com/agentscope-ai/ReMe"><img src="https://img.shields.io/github/stars/agentscope-ai/ReMe?style=social" alt="GitHub Stars"></a>
|
||||
</p>
|
||||
|
||||
|
|
@ -20,66 +20,65 @@
|
|||
<strong>A 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 the older version, please refer to the [0.2.x documentation](docs/README_0_2_x_ZH.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 designed for **AI agents**, providing 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).
|
||||
|
||||
ReMe gives agents **real memory** — old conversations are automatically condensed, important information is persisted,
|
||||
and the next conversation can recall it automatically.
|
||||
It tackles two core problems of agent memory: **limited context window** (early information is truncated or lost in long
|
||||
conversations) and **stateless sessions** (new sessions cannot inherit history and always start from scratch).
|
||||
|
||||
ReMe gives agents **real memory** — old conversations are automatically compacted, important information is persistently
|
||||
stored, and relevant context is automatically recalled in future interactions.
|
||||
|
||||
---
|
||||
|
||||
## 📁 File-Based Memory System (ReMeLight)
|
||||
## 📁 File-based memory system (ReMeLight)
|
||||
|
||||
> 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) implements long-term memory and context management by inheriting
|
||||
Treat **memory as files** — readable, editable, and copyable.
|
||||
[CoPaw](https://github.com/agentscope-ai/CoPaw) integrates long-term memory and context management by inheriting from
|
||||
`ReMeLight`.
|
||||
|
||||
| Traditional Memory Systems | File-Based ReMe |
|
||||
|----------------------------|--------------------|
|
||||
| 🗄️ Database storage | 📝 Markdown files |
|
||||
| 🔒 Opaque | 👀 Read anytime |
|
||||
| ❌ Hard to modify | ✏️ Edit directly |
|
||||
| 🚫 Hard to migrate | 📦 Copy to migrate |
|
||||
| Traditional memory system | File-based ReMe |
|
||||
|---------------------------|----------------------|
|
||||
| 🗄️ Database storage | 📝 Markdown files |
|
||||
| 🔒 Opaque | 👀 Always readable |
|
||||
| ❌ Hard to modify | ✏️ Directly editable |
|
||||
| 🚫 Hard to migrate | 📦 Copy to migrate |
|
||||
|
||||
```
|
||||
working_dir/
|
||||
├── MEMORY.md # Long-term memory: user preferences, project config, etc.
|
||||
├── MEMORY.md # Long-term memory: persistent info such as user preferences
|
||||
├── 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 journal: automatically written after each conversation
|
||||
└── tool_result/ # Cache for long tool outputs (auto-managed, expired entries auto-cleaned)
|
||||
└── <uuid>.txt
|
||||
```
|
||||
|
||||
### Core Capabilities
|
||||
### Core capabilities
|
||||
|
||||
[ReMeLight](reme/reme_light.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 the file-based memory system. It provides full 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/compactor.py) — ReActAgent generates structured context checkpoint |
|
||||
| `summary_memory` | 📝 Write important memory to files | [Summarizer](reme/memory/file_based/summarizer.py) — ReActAgent + file tools (read / write / edit) |
|
||||
| `compact_tool_result` | ✂️ Compact oversized tool output | [ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) — Truncate and save to `tool_result/`, keep file reference in message |
|
||||
| `pre_reasoning_hook` | 🔄 Pre-reasoning hook | Auto compact tool results + generate summary + async trigger memory summarization task |
|
||||
| `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 summary and state serialization (static method) |
|
||||
| Method | Function | Key components |
|
||||
|------------------------|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `check_context` | 📊 Check context size | [ContextChecker](reme/memory/file_based/component/context_checker.py) — checks whether context exceeds thresholds and splits messages |
|
||||
| `compact_memory` | 📦 Compact history into summary | [Compactor](reme/memory/file_based/component/compactor.py) — ReActAgent that generates structured context summaries |
|
||||
| `summary_memory` | 📝 Persist important memory to files | [Summarizer](reme/memory/file_based/component/summarizer.py) — ReActAgent + file tools (`read` / `write` / `edit`) |
|
||||
| `compact_tool_result` | ✂️ Compact long tool outputs | [ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) — truncates long tool outputs and stores them in `tool_result/` while keeping file references in messages |
|
||||
| `memory_search` | 🔍 Semantic memory search | [MemorySearch](reme/memory/file_based/tools/memory_search.py) — hybrid retrieval with vectors + BM25 |
|
||||
| `ReMeInMemoryMemory` | 🗂️ In-session memory class | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — token-aware memory management with summary compression and state serialization |
|
||||
| `pre_reasoning_hook` | 🔄 Pre-reasoning hook | `compact_tool_result` + `check_context` + `compact_memory` + `summary_memory` (async) |
|
||||
| `start` | 🚀 Start memory system | Initialize file storage, file watcher, and embedding cache; clean up expired tool result files |
|
||||
| `close` | 📕 Shutdown and cleanup | Clean up tool result files, stop file watcher, and persist embedding cache |
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Quick Start
|
||||
### 🚀 Quick start
|
||||
|
||||
#### Installation
|
||||
|
||||
|
|
@ -87,23 +86,22 @@ capabilities for AI Agents:
|
|||
pip install -e ".[light]"
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
#### Environment variables
|
||||
|
||||
`ReMeLight` environment variables configure Embedding and storage backend
|
||||
`ReMeLight` uses environment variables to configure the embedding model and storage backends:
|
||||
|
||||
| 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 (Optional) | `sk-xxx` |
|
||||
| `EMBEDDING_BASE_URL` | Embedding base URL (Optional) | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
| 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 (optional) | `sk-xxx` |
|
||||
| `EMBEDDING_BASE_URL` | Embedding base URL (optional) | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
||||
|
||||
#### Python Usage
|
||||
#### Python usage
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agentscope.message import Msg
|
||||
from reme.reme_light import ReMeLight
|
||||
|
||||
|
||||
|
|
@ -116,24 +114,24 @@ async def main():
|
|||
)
|
||||
await reme.start()
|
||||
|
||||
messages = [...] # Conversation message list
|
||||
messages = [...] # List of conversation messages
|
||||
|
||||
# 1. Compact oversized tool outputs (prevent tool results from overflowing context)
|
||||
# 1. Compact long tool outputs (prevent tool results from blowing up context)
|
||||
messages = await reme.compact_tool_result(messages)
|
||||
|
||||
# 2. Compact history to structured summary (can pass previous summary for incremental update)
|
||||
# 2. Compact conversation history into a structured summary
|
||||
summary = await reme.compact_memory(
|
||||
messages=messages,
|
||||
previous_summary="",
|
||||
max_input_length=128000, # Model context window (tokens)
|
||||
compact_ratio=0.7, # Trigger compaction when reaching max_input_length * 0.7
|
||||
language="zh", # Summary language (zh / "")
|
||||
compact_ratio=0.7, # Trigger compaction when exceeding max_input_length * 0.7
|
||||
language="zh", # Summary language (e.g., "zh" / "")
|
||||
)
|
||||
|
||||
# 3. Submit async summary task in background (non-blocking, writes to memory/YYYY-MM-DD.md)
|
||||
# 3. Submit summary task asynchronously (non-blocking, writes to memory/YYYY-MM-DD.md)
|
||||
reme.add_async_summary_task(messages=messages)
|
||||
|
||||
# 4. Pre-reasoning hook (auto compact tool results + generate summary)
|
||||
# 4. Pre-reasoning hook (auto compact tool results + generate summaries)
|
||||
processed_messages, compressed_summary = await reme.pre_reasoning_hook(
|
||||
messages=messages,
|
||||
system_prompt="You are a helpful AI assistant.",
|
||||
|
|
@ -145,22 +143,23 @@ async def main():
|
|||
tool_result_compact_keep_n=3,
|
||||
)
|
||||
|
||||
# 5. Semantic memory search (Vector + BM25 hybrid retrieval)
|
||||
# 5. Semantic memory search (vector + BM25 hybrid retrieval)
|
||||
result = await reme.memory_search(query="Python version preference", max_results=5)
|
||||
|
||||
# 6. Get in-memory instance (static method, manages single conversation context)
|
||||
memory = ReMeLight.get_in_memory_memory()
|
||||
# 6. Create in-session memory instance (manages context for one conversation)
|
||||
from reme.memory.file_based.reme_in_memory_memory import ReMeInMemoryMemory
|
||||
memory = ReMeInMemoryMemory()
|
||||
for msg in messages:
|
||||
await memory.add(msg)
|
||||
token_stats = await memory.estimate_tokens(max_input_length=128000)
|
||||
print(f"Current context usage: {token_stats['context_usage_ratio']:.1f}%")
|
||||
print(f"Message tokens: {token_stats['messages_tokens']}")
|
||||
print(f"Message token count: {token_stats['messages_tokens']}")
|
||||
print(f"Estimated total tokens: {token_stats['estimated_tokens']}")
|
||||
|
||||
# 7. Wait for background tasks before closing
|
||||
# 7. Wait for background summary tasks to complete before shutdown
|
||||
summary_result = await reme.await_summary_tasks()
|
||||
|
||||
# Close ReMeLight
|
||||
# Shutdown ReMeLight
|
||||
await reme.close()
|
||||
|
||||
|
||||
|
|
@ -168,173 +167,226 @@ if __name__ == "__main__":
|
|||
asyncio.run(main())
|
||||
```
|
||||
|
||||
> 📂 Full example code: [test_reme_light.py](tests/light/test_reme_light.py)
|
||||
> 📋 Example output: [test_reme_light.log](tests/light/test_reme_light_log.txt) (223,838 tokens → 1,105 tokens, 99.5%
|
||||
> compression ratio)
|
||||
> 📂 Full example: [test_reme_light.py](tests/light/test_reme_light.py)
|
||||
> 📋 Sample run log: [test_reme_light_log.txt](tests/light/test_reme_light_log.txt) (223,838 tokens → 1,105 tokens, 99.5%
|
||||
> compression)
|
||||
|
||||
### File-Based ReMeLight Memory System Architecture
|
||||
### Architecture of the file-based ReMeLight memory system
|
||||
|
||||
[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py)
|
||||
inherits `ReMeLight` and integrates memory capabilities into the Agent reasoning flow:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
CoPaw["CoPaw MemoryManager<br>(inherits ReMeLight)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook]
|
||||
CoPaw --> ReMeLight[ReMeLight]
|
||||
Hook -->|exceeds threshold| ReMeLight
|
||||
ReMeLight --> CompactMemory[compact_memory<br>History compaction]
|
||||
ReMeLight --> SummaryMemory[summary_memory<br>Write memory to files]
|
||||
ReMeLight --> CompactToolResult[compact_tool_result<br>Oversized tool output compaction]
|
||||
ReMeLight --> MemSearch[memory_search<br>Semantic search]
|
||||
ReMeLight --> InMemory[get_in_memory_memory<br>ReMeInMemoryMemory]
|
||||
CompactMemory --> Compactor[Compactor<br>ReActAgent]
|
||||
SummaryMemory --> Summarizer[Summarizer<br>ReActAgent + file tools]
|
||||
CompactToolResult --> ToolResultCompactor[ToolResultCompactor<br>Truncate + save to file]
|
||||
Summarizer --> FileIO[FileIO<br>read / 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]
|
||||
MemSearch --> FileStore
|
||||
```
|
||||
|
||||
### Context Compaction Mechanism
|
||||
|
||||
#### Context Compaction
|
||||
|
||||
[Compactor](reme/memory/file_based/compactor.py) uses ReActAgent to compact 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, etc. |
|
||||
|
||||
Supports **incremental updates**: when `previous_summary` is passed, automatically merges new conversation with old
|
||||
summary, preserving historical progress.
|
||||
|
||||
#### Tool Result Compaction
|
||||
|
||||
[ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) solves context overflow caused by oversized tool
|
||||
outputs (e.g., browser use):
|
||||
inherits
|
||||
`ReMeLight` and integrates its memory capabilities into the agent reasoning loop:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[tool_result message] --> B{Content length > threshold?}
|
||||
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`.
|
||||
|
||||
### Memory Summary: ReAct + File Tools
|
||||
|
||||
[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
|
||||
A[Receive conversation] --> B{Think: What's worth recording?}
|
||||
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?}
|
||||
F -->|Yes| B
|
||||
F -->|No| G[Done]
|
||||
```
|
||||
|
||||
[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 |
|
||||
|
||||
### In-Memory Session Management
|
||||
|
||||
[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) |
|
||||
| `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**:
|
||||
|
||||
| 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 |
|
||||
|
||||
**Fusion**: Both retrieval paths are weighted and summed (vector 0.7 + BM25 0.3), so both natural-language queries and
|
||||
exact lookups get reliable results.
|
||||
|
||||
```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]
|
||||
Agent[Agent] -->|Before each reasoning step| Hook[pre_reasoning_hook]
|
||||
Hook --> TC[compact_tool_result<br>Compact tool outputs]
|
||||
TC --> CC[check_context<br>Token counting]
|
||||
CC -->|Exceeds limit| CM[compact_memory<br>Generate summary]
|
||||
CC -->|Exceeds limit| SM[summary_memory<br>Async persistence]
|
||||
SM -->|ReAct + FileIO| Files[memory/*.md]
|
||||
Agent -->|Explicit call| Search[memory_search<br>Vector+BM25]
|
||||
Agent -->|In-session| InMem[ReMeInMemoryMemory<br>Token-aware memory]
|
||||
Files -.->|FileWatcher| Store[(FileStore<br>Vector+FTS index)]
|
||||
Search --> Store
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Vector-Based Memory System
|
||||
#### 1. `check_context` — context checking
|
||||
|
||||
[ReMe Vector Based](reme/reme.py) is the core class for the vector-based memory system, supporting unified management of
|
||||
three memory types:
|
||||
[ContextChecker](reme/memory/file_based/component/context_checker.py) uses token counting to determine whether the
|
||||
context exceeds thresholds and automatically splits messages into a "to compact" group and a "to keep" group.
|
||||
|
||||
| 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 |
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install -U reme-ai
|
||||
```mermaid
|
||||
graph LR
|
||||
M[messages] --> H[AsMsgHandler<br>Token counting]
|
||||
H --> C{total > threshold?}
|
||||
C -->|No| K[Return all messages]
|
||||
C -->|Yes| S[Keep from tail<br>reserve tokens]
|
||||
S --> CP[messages_to_compact<br>Earlier messages]
|
||||
S --> KP[messages_to_keep<br>Recent messages]
|
||||
S --> V{is_valid<br>Tool calls aligned?}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
- **Core logic**: keep `reserve` tokens from the tail; mark the rest as messages to compact.
|
||||
- **Integrity guarantee**: preserves complete user-assistant turns and tool_use/tool_result pairs without splitting
|
||||
them.
|
||||
|
||||
API keys are set via environment variables; you can put them in a `.env` file in the project root:
|
||||
---
|
||||
|
||||
| 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` |
|
||||
#### 2. `compact_memory` — conversation compaction
|
||||
|
||||
### Python Usage
|
||||
[Compactor](reme/memory/file_based/component/compactor.py) uses a ReActAgent to compact conversation history into a *
|
||||
*structured context summary**.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
M[messages] --> H[AsMsgHandler<br>format_msgs_to_str]
|
||||
H --> A[ReActAgent<br>reme_compactor]
|
||||
P[previous_summary] -->|Incremental update| A
|
||||
A --> S[Structured summary<br>Goal/Progress/Decisions...]
|
||||
```
|
||||
|
||||
**Summary structure** (context checkpoints):
|
||||
|
||||
| Field | Description |
|
||||
|-----------------------|------------------------------------------------------------------------|
|
||||
| `## Goal` | User goals |
|
||||
| `## Constraints` | Constraints and preferences |
|
||||
| `## Progress` | Task progress |
|
||||
| `## Key Decisions` | Key decisions |
|
||||
| `## Next Steps` | Next step plans |
|
||||
| `## Critical Context` | Critical data such as file paths, function names, error messages, etc. |
|
||||
|
||||
- **Incremental updates**: when `previous_summary` is provided, new conversations are merged into the existing summary.
|
||||
|
||||
---
|
||||
|
||||
#### 3. `summary_memory` — persistent memory
|
||||
|
||||
[Summarizer](reme/memory/file_based/component/summarizer.py) uses a **ReAct + file tools** pattern so that the AI can
|
||||
decide what to write and where to write it.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
M[messages] --> A[ReActAgent<br>reme_summarizer]
|
||||
A -->|read| R[Read memory/YYYY-MM-DD.md]
|
||||
R --> T{Reason: how to merge?}
|
||||
T -->|write| W[Overwrite]
|
||||
T -->|edit| E[Edit in place]
|
||||
W --> F[memory/YYYY-MM-DD.md]
|
||||
E --> F
|
||||
```
|
||||
|
||||
**File tools** ([FileIO](reme/memory/file_based/tools/file_io.py)):
|
||||
|
||||
| Tool | Function |
|
||||
|---------|-----------------------|
|
||||
| `read` | Read file content |
|
||||
| `write` | Overwrite file |
|
||||
| `edit` | Find-and-replace edit |
|
||||
|
||||
---
|
||||
|
||||
#### 4. `compact_tool_result` — tool result compaction
|
||||
|
||||
[ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) addresses the problem of long tool
|
||||
outputs bloating the context.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
M[messages] --> L{Iterate tool_result<br>len > threshold?}
|
||||
L -->|No| K[Keep as-is]
|
||||
L -->|Yes| T[truncate_text<br>Truncate to threshold]
|
||||
T --> S[Write full content<br>tool_result/uuid.txt]
|
||||
S --> R[Append file path reference<br>to message]
|
||||
R --> C[cleanup_expired_files<br>Delete expired files]
|
||||
```
|
||||
|
||||
- **Auto cleanup**: expired files (older than `retention_days`) are deleted automatically during `start` / `close` /
|
||||
`compact_tool_result`.
|
||||
|
||||
---
|
||||
|
||||
#### 5. `memory_search` — memory retrieval
|
||||
|
||||
[MemorySearch](reme/memory/file_based/tools/memory_search.py) provides **vector + BM25 hybrid retrieval**.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Q[query] --> E[Embedding<br>Vectorization]
|
||||
E --> V[vector_search<br>Semantic similarity]
|
||||
Q --> B[BM25<br>Keyword matching]
|
||||
V -->|" weight: 0.7 "| M[Deduplicate + weighted merge]
|
||||
B -->|" weight: 0.3 "| M
|
||||
M --> F[min_score filter]
|
||||
F --> R[Top-N results]
|
||||
```
|
||||
|
||||
- **Fusion mechanism**: vector weight 0.7 + BM25 weight 0.3 — balancing semantic similarity and exact matches.
|
||||
|
||||
---
|
||||
|
||||
#### 6. `ReMeInMemoryMemory` — in-session memory
|
||||
|
||||
[ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) extends AgentScope's `InMemoryMemory` to provide
|
||||
token-aware memory management.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
C[content] --> G[get_memory<br>exclude_mark=COMPRESSED]
|
||||
G --> F[Filter out compressed messages]
|
||||
F --> P{prepend_summary?}
|
||||
P -->|Yes| S[Prepend previous summary]
|
||||
S --> O[Output messages]
|
||||
P -->|No| O
|
||||
```
|
||||
|
||||
| Function | Description |
|
||||
|----------------------------------|---------------------------------------------------|
|
||||
| `get_memory` | Filter messages by mark and auto-append summary |
|
||||
| `estimate_tokens` | Estimate token usage of the context |
|
||||
| `state_dict` / `load_state_dict` | Serialize/deserialize state (session persistence) |
|
||||
|
||||
---
|
||||
|
||||
#### 7. `pre_reasoning_hook` — pre-reasoning processing
|
||||
|
||||
This is a unified entry point that wires all the above components together and automatically manages context before each
|
||||
reasoning step.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
M[messages] --> TC[compact_tool_result<br>Compact long tool outputs]
|
||||
TC --> CC[check_context<br>Compute remaining space]
|
||||
CC --> D{messages_to_compact<br>Non-empty?}
|
||||
D -->|No| K[Return original messages + summary]
|
||||
D -->|Yes| V{is_valid?}
|
||||
V -->|No| K
|
||||
V -->|Yes| CM[compact_memory<br>Sync summary generation]
|
||||
V -->|Yes| SM[add_async_summary_task<br>Async persistence]
|
||||
CM --> R[Return messages_to_keep + new summary]
|
||||
```
|
||||
|
||||
**Execution flow**:
|
||||
|
||||
1. `compact_tool_result` — compact long tool outputs.
|
||||
2. `check_context` — check whether the context exceeds limits.
|
||||
3. `compact_memory` — generate compact summary (sync).
|
||||
4. `summary_memory` — persist memory (async in the background).
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Vector-based memory system
|
||||
|
||||
[ReMe Vector Based](reme/reme.py) is the core class for the vector-based memory system. It manages three types of
|
||||
memories:
|
||||
|
||||
| Memory type | Use case |
|
||||
|-----------------------|-------------------------------------------------------------------|
|
||||
| **Personal memory** | Records user preferences and habits |
|
||||
| **Procedural memory** | Records task execution experience and patterns of success/failure |
|
||||
| **Tool memory** | Records tool usage experience and parameter tuning |
|
||||
|
||||
### Core capabilities
|
||||
|
||||
| Method | Function | Description |
|
||||
|--------------------|--------------|-------------------------------------------------------------|
|
||||
| `summarize_memory` | 🧠 Summarize | Automatically extract and store memories from conversations |
|
||||
| `retrieve_memory` | 🔍 Retrieve | Retrieve related memories based on a query |
|
||||
| `add_memory` | ➕ Add | Manually add memories into the vector store |
|
||||
| `get_memory` | 📖 Get | Get a single memory by ID |
|
||||
| `update_memory` | ✏️ Update | Update existing memory content or metadata |
|
||||
| `delete_memory` | 🗑️ Delete | Delete a specific memory |
|
||||
| `list_memory` | 📋 List | List memories with filtering and sorting |
|
||||
|
||||
### Installation and environment variables
|
||||
|
||||
Installation and environment configuration are the same as [ReMeLight](#installation).
|
||||
API keys are configured via environment variables and can be stored in a `.env` file at the project root.
|
||||
|
||||
### Python usage
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
|
@ -363,34 +415,34 @@ 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": "Sure, I'll help you with that.", "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 (automatically extract user preferences, task experience, etc.)
|
||||
result = await reme.summarize_memory(
|
||||
messages=messages,
|
||||
user_name="alice", # Personal memory
|
||||
# task_name="code_writing", # Task memory
|
||||
# task_name="code_writing", # Procedural memory
|
||||
)
|
||||
print(f"Summarize result: {result}")
|
||||
print(f"Summary result: {result}")
|
||||
|
||||
# 2. Retrieve relevant memory
|
||||
# 2. Retrieve related memories
|
||||
memories = await reme.retrieve_memory(
|
||||
query="Python programming",
|
||||
user_name="alice",
|
||||
# task_name="code_writing",
|
||||
)
|
||||
print(f"Retrieve result: {memories}")
|
||||
print(f"Retrieved memories: {memories}")
|
||||
|
||||
# 3. Manually add memory
|
||||
# 3. Manually add a memory
|
||||
memory_node = await reme.add_memory(
|
||||
memory_content="User prefers concise code style",
|
||||
memory_content="The user prefers concise code style.",
|
||||
user_name="alice",
|
||||
)
|
||||
print(f"Added memory: {memory_node}")
|
||||
memory_id = memory_node.memory_id
|
||||
|
||||
# 4. Get single memory by ID
|
||||
# 4. Get a single memory by ID
|
||||
fetched_memory = await reme.get_memory(memory_id=memory_id)
|
||||
print(f"Fetched memory: {fetched_memory}")
|
||||
|
||||
|
|
@ -398,11 +450,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="The user prefers concise code with comments.",
|
||||
)
|
||||
print(f"Updated memory: {updated_memory}")
|
||||
|
||||
# 6. List all memories for user (with filtering and sorting)
|
||||
# 6. List all memories for the user (supports filtering and sorting)
|
||||
all_memories = await reme.list_memory(
|
||||
user_name="alice",
|
||||
limit=10,
|
||||
|
|
@ -411,11 +463,11 @@ async def main():
|
|||
)
|
||||
print(f"User memory list: {all_memories}")
|
||||
|
||||
# 7. Delete specified memory
|
||||
# 7. Delete a specific memory
|
||||
await reme.delete_memory(memory_id=memory_id)
|
||||
print(f"Deleted memory: {memory_id}")
|
||||
|
||||
# 8. Delete all memories (use with caution)
|
||||
# 8. Delete all memories (use with care)
|
||||
# await reme.delete_all()
|
||||
|
||||
await reme.close()
|
||||
|
|
@ -425,21 +477,21 @@ if __name__ == "__main__":
|
|||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Technical Architecture
|
||||
### Technical architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
graph LR
|
||||
User[User / Agent] --> ReMe[Vector Based ReMe]
|
||||
ReMe --> Summarize[Memory Summarize]
|
||||
ReMe --> Retrieve[Memory Retrieve]
|
||||
ReMe --> CRUD[CRUD]
|
||||
ReMe --> Summarize[Summarize memories]
|
||||
ReMe --> Retrieve[Retrieve memories]
|
||||
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
|
||||
|
|
@ -447,17 +499,53 @@ graph TB
|
|||
ToolRet --> VectorStore
|
||||
```
|
||||
|
||||
## ⭐ Community & Support
|
||||
### Experimental results
|
||||
|
||||
- **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.
|
||||
Coming soon...
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Procedural memory paper
|
||||
|
||||
> Our procedural (task) memory paper is available on [arXiv](https://arxiv.org/abs/2512.10696).
|
||||
|
||||
### 🌍 [Appworld benchmark](benchmark/appworld/quickstart.md)
|
||||
|
||||
We evaluate ReMe on the Appworld environment using Qwen3-8B (non-thinking mode):
|
||||
|
||||
| Method | Avg@4 | Pass@4 |
|
||||
|----------|---------------------|---------------------|
|
||||
| w/o ReMe | 0.1497 | 0.3285 |
|
||||
| w/ ReMe | 0.1706 **(+2.09%)** | 0.3631 **(+3.46%)** |
|
||||
|
||||
Pass@K measures the probability that at least one of K generated candidates successfully completes the task (score=1).
|
||||
The current experiments use an internal AppWorld environment, which may differ slightly from the public version.
|
||||
|
||||
For more details on how to reproduce the experiments, see [quickstart.md](benchmark/appworld/quickstart.md).
|
||||
|
||||
### 🔧 [BFCL-V3 benchmark](benchmark/bfcl/quickstart.md)
|
||||
|
||||
We evaluate ReMe on the BFCL-V3 multi-turn-base task (random split 50 train / 150 val) using Qwen3-8B (thinking mode):
|
||||
|
||||
| Method | Avg@4 | Pass@4 |
|
||||
|----------|---------------------|---------------------|
|
||||
| w/o ReMe | 0.4033 | 0.5955 |
|
||||
| w/ ReMe | 0.4450 **(+4.17%)** | 0.6577 **(+6.22%)** |
|
||||
|
||||
For more details on how to reproduce the experiments, see [quickstart.md](benchmark/bfcl/quickstart.md).
|
||||
|
||||
|
||||
## ⭐ Community & support
|
||||
|
||||
- **Star & Watch**: Starring helps more agent developers discover ReMe; Watching keeps you up to date with new releases
|
||||
and features.
|
||||
- **Share your results**: Share how ReMe empowers your agents in Issues or Discussions — we are happy to showcase great
|
||||
community use cases.
|
||||
- **Need a new feature?** Open a feature request; we’ll evolve ReMe together with the community.
|
||||
- **Code contributions**: All forms of contributions are welcome. Please see
|
||||
the [contribution guide](docs/contribution.md).
|
||||
- **Acknowledgements**: We thank excellent open-source projects such as OpenClaw, Mem0, MemU, and CoPaw for their
|
||||
inspiration and support.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -476,10 +564,11 @@ 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 [LICENSE](./LICENSE) for details.
|
||||
|
||||
---
|
||||
|
||||
## 📈 Star History
|
||||
## 📈 Star history
|
||||
|
||||
[](https://www.star-history.com/#agentscope-ai/ReMe&Date)
|
||||
|
||||
|
|
|
|||
343
README_ZH.md
343
README_ZH.md
|
|
@ -38,7 +38,7 @@ ReMe 让智能体拥有**真正的记忆力**——旧对话自动浓缩,重
|
|||
> 记忆即文件,文件即记忆
|
||||
|
||||
将**记忆视为文件**——可读、可编辑、可复制。
|
||||
[CoPaw](https://github.com/agentscope-ai/CoPaw)通过继承 `ReMeLight` 实现了长期记忆和上下文的管理。
|
||||
[CoPaw](https://github.com/agentscope-ai/CoPaw) 通过继承 `ReMeLight` 实现了长期记忆和上下文的管理。
|
||||
|
||||
| 传统记忆系统 | File Based ReMe |
|
||||
|-----------|-----------------|
|
||||
|
|
@ -49,9 +49,9 @@ ReMe 让智能体拥有**真正的记忆力**——旧对话自动浓缩,重
|
|||
|
||||
```
|
||||
working_dir/
|
||||
├── MEMORY.md # 长期记忆:用户偏好、项目配置等持久信息
|
||||
├── MEMORY.md # 长期记忆:用户偏好等持久信息
|
||||
├── memory/
|
||||
│ └── YYYY-MM-DD.md # 每日摘要日志:对话结束后自动写入
|
||||
│ └── YYYY-MM-DD.md # 每日日记:对话结束后自动写入
|
||||
└── tool_result/ # 超长工具输出缓存(自动管理,超期自动清理)
|
||||
└── <uuid>.txt
|
||||
```
|
||||
|
|
@ -60,17 +60,17 @@ working_dir/
|
|||
|
||||
[ReMeLight](reme/reme_light.py) 是该记忆系统的核心类,为 AI Agent 提供完整的记忆管理能力:
|
||||
|
||||
| 方法 | 功能 | 关键组件 |
|
||||
|------------------------|--------------|----------------------------------------------------------------------------------------------------------|
|
||||
| `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/`,消息中保留文件引用 |
|
||||
| `pre_reasoning_hook` | 🔄 推理前预处理钩子 | 自动压缩工具结果 + 生成摘要 + 异步触发记忆总结任务 |
|
||||
| `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 感知的内存管理,支持压缩摘要和状态序列化(静态方法) |
|
||||
|
||||
| 方法 | 功能 | 关键组件 |
|
||||
|------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||
| `check_context` | 📊 检查上下文大小 | [ContextChecker](reme/memory/file_based/component/context_checker.py) — 检查上下文是否超出阈值并拆分Message |
|
||||
| `compact_memory` | 📦 压缩历史对话为摘要 | [Compactor](reme/memory/file_based/component/compactor.py) — ReActAgent 生成结构化上下文摘要 |
|
||||
| `summary_memory` | 📝 将重要记忆写入文件 | [Summarizer](reme/memory/file_based/component/summarizer.py) — ReActAgent + 文件工具(read / write / edit) |
|
||||
| `compact_tool_result` | ✂️ 压缩超长工具输出 | [ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) — 截断超长的工具调用结果并转存到 `tool_result/`,消息中保留文件引用 |
|
||||
| `memory_search` | 🔍 语义搜索记忆 | [MemorySearch](reme/memory/file_based/tools/memory_search.py) — 向量 + BM25 混合检索 |
|
||||
| `ReMeInMemoryMemory` | 🗂️ 会话内存类 | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — Token 感知的内存管理,支持压缩摘要和状态序列化 |
|
||||
| `pre_reasoning_hook` | 🔄 推理前预处理钩子 | compact_tool_result + check_context + compact_memory + summary_memory(async) |
|
||||
| `start` | 🚀 启动记忆系统 | 初始化文件存储、文件监控、Embedding 缓存;清理过期工具结果文件 |
|
||||
| `close` | 📕 关闭并清理 | 清理工具结果文件、停止文件监控、保存 Embedding 缓存 |·
|
||||
---
|
||||
|
||||
### 🚀 快速开始
|
||||
|
|
@ -85,14 +85,14 @@ pip install -e ".[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` |
|
||||
| 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` |
|
||||
|
||||
#### Python使用
|
||||
#### Python 使用
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
|
@ -141,8 +141,9 @@ async def main():
|
|||
# 5. 语义搜索记忆(向量 + BM25 混合检索)
|
||||
result = await reme.memory_search(query="Python 版本偏好", max_results=5)
|
||||
|
||||
# 6. 获取会话内存实例(静态方法,管理单次对话的上下文)
|
||||
memory = ReMeLight.get_in_memory_memory()
|
||||
# 6. 创建会话内存实例(管理单次对话的上下文)
|
||||
from reme.memory.file_based.reme_in_memory_memory import ReMeInMemoryMemory
|
||||
memory = ReMeInMemoryMemory()
|
||||
for msg in messages:
|
||||
await memory.add(msg)
|
||||
token_stats = await memory.estimate_tokens(max_input_length=128000)
|
||||
|
|
@ -162,7 +163,7 @@ if __name__ == "__main__":
|
|||
```
|
||||
|
||||
> 📂 完整示例代码:[test_reme_light.py](tests/light/test_reme_light.py)
|
||||
> 📋 运行结果示例:[test_reme_light.log](tests/light/test_reme_light_log.txt)(223,838 tokens → 1,105 tokens,压缩率 99.5%)
|
||||
> 📋 运行结果示例:[test_reme_light_log.txt](tests/light/test_reme_light_log.txt)(223,838 tokens → 1,105 tokens,压缩率99.5%)
|
||||
|
||||
### 基于文件的 ReMeLight 记忆系统架构
|
||||
|
||||
|
|
@ -170,125 +171,188 @@ if __name__ == "__main__":
|
|||
`ReMeLight`,将记忆能力集成到 Agent 推理流程中:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
CoPaw["CoPaw MemoryManager<br>(继承 ReMeLight)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook]
|
||||
CoPaw --> ReMeLight[ReMeLight]
|
||||
Hook -->|超出阈值| ReMeLight
|
||||
ReMeLight --> CompactMemory[compact_memory<br>历史对话压缩]
|
||||
ReMeLight --> SummaryMemory[summary_memory<br>记忆写入文件]
|
||||
ReMeLight --> CompactToolResult[compact_tool_result<br>超长工具输出压缩]
|
||||
ReMeLight --> MemSearch[memory_search<br>语义搜索]
|
||||
ReMeLight --> InMemory[get_in_memory_memory<br>ReMeInMemoryMemory]
|
||||
CompactMemory --> Compactor[Compactor<br>ReActAgent]
|
||||
SummaryMemory --> Summarizer[Summarizer<br>ReActAgent + 文件工具]
|
||||
CompactToolResult --> ToolResultCompactor[ToolResultCompactor<br>截断 + 转存文件]
|
||||
Summarizer --> FileIO[FileIO<br>read / write / edit]
|
||||
FileIO --> MemoryFiles[memory/YYYY-MM-DD.md]
|
||||
ToolResultCompactor --> ToolResultFiles[tool_result/*.txt]
|
||||
MemoryFiles -.->|文件变更| FileWatcher[异步文件监控]
|
||||
FileWatcher -->|更新索引| FileStore[本地数据库]
|
||||
MemSearch --> FileStore
|
||||
graph LR
|
||||
Agent[Agent] -->|每轮推理前| Hook[pre_reasoning_hook]
|
||||
Hook --> TC[compact_tool_result<br>压缩工具输出]
|
||||
TC --> CC[check_context<br>Token 计数]
|
||||
CC -->|超限| CM[compact_memory<br>生成摘要]
|
||||
CC -->|超限| SM[summary_memory<br>异步持久化]
|
||||
SM -->|ReAct + FileIO| Files[memory/*.md]
|
||||
Agent -->|主动调用| Search[memory_search<br>向量+BM25]
|
||||
Agent -->|会话内存| InMem[ReMeInMemoryMemory<br>Token感知内存]
|
||||
Files -.->|FileWatcher| Store[(FileStore<br>向量+FTS索引)]
|
||||
Search --> Store
|
||||
```
|
||||
|
||||
### 上下文压缩机制
|
||||
---
|
||||
|
||||
#### 上下文压缩
|
||||
#### 1. check_context — 上下文检查
|
||||
|
||||
[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)导致上下文膨胀的问题:
|
||||
[ContextChecker](reme/memory/file_based/component/context_checker.py) 基于 Token 计数判断上下文是否超限,自动拆分为「待压缩」和「保留」两组消息。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[tool_result 消息] --> B{内容长度 > threshold?}
|
||||
B -->|否| C[保留原样]
|
||||
B -->|是| D[截断到 threshold 字符]
|
||||
D --> E[完整内容写入 tool_result/uuid.txt]
|
||||
E --> F[消息中追加文件引用路径]
|
||||
M[messages] --> H[AsMsgHandler<br>Token 计数]
|
||||
H --> C{total > threshold?}
|
||||
C -->|否| K[返回全部消息]
|
||||
C -->|是| S[从尾部向前保留<br>reserve tokens]
|
||||
S --> CP[messages_to_compact<br>早期消息]
|
||||
S --> KP[messages_to_keep<br>近期消息]
|
||||
S --> V{is_valid<br>工具调用对齐?}
|
||||
```
|
||||
|
||||
过期文件(超过 `retention_days`)在 `start` / `close` / `compact_tool_result` 时自动清理。
|
||||
- **核心逻辑**:从尾部向前保留 `reserve` tokens,超出部分标记为待压缩
|
||||
- **完整性保证**:不拆分 user-assistant 对话对,不拆分 tool_use/tool_result 配对
|
||||
|
||||
### 记忆总结:ReAct + 文件工具
|
||||
---
|
||||
|
||||
[Summarizer](reme/memory/file_based/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪:
|
||||
#### 2. compact_memory — 对话压缩
|
||||
|
||||
[Compactor](reme/memory/file_based/component/compactor.py) 使用 ReActAgent 将历史对话压缩为**结构化上下文摘要**。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[接收对话] --> B{思考: 有什么值得记录?}
|
||||
B --> C[行动: read memory/YYYY-MM-DD.md]
|
||||
C --> D{思考: 如何与现有内容合并?}
|
||||
D --> E[行动: edit 更新文件]
|
||||
E --> F{思考: 还有遗漏吗?}
|
||||
F -->|是| B
|
||||
F -->|否| G[完成]
|
||||
M[messages] --> H[AsMsgHandler<br>format_msgs_to_str]
|
||||
H --> A[ReActAgent<br>reme_compactor]
|
||||
P[previous_summary] -->|增量更新| A
|
||||
A --> S[结构化摘要<br>Goal/Progress/Decisions...]
|
||||
```
|
||||
|
||||
[FileIO](reme/memory/file_based/file_io.py) 提供文件操作工具集:
|
||||
**摘要结构**(上下文检查点):
|
||||
|
||||
| 工具 | 功能 | 使用场景 |
|
||||
|---------|---------------|---------------|
|
||||
| `read` | 读取文件内容(支持行范围) | 查看现有记忆,避免重复写入 |
|
||||
| `write` | 覆盖写入文件 | 创建新记忆文件或大幅重构 |
|
||||
| `edit` | 精确匹配后替换 | 追加新内容或修改特定段落 |
|
||||
| 字段 | 说明 |
|
||||
|-----------------------|--------------------|
|
||||
| `## Goal` | 用户目标 |
|
||||
| `## Constraints` | 约束和偏好 |
|
||||
| `## Progress` | 任务进展 |
|
||||
| `## Key Decisions` | 关键决策 |
|
||||
| `## Next Steps` | 下一步计划 |
|
||||
| `## Critical Context` | 文件路径、函数名、错误信息等关键数据 |
|
||||
|
||||
### 会话内存管理
|
||||
- **增量更新**:传入 `previous_summary` 时,自动将新对话与旧摘要合并
|
||||
|
||||
[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` | 获取已压缩的摘要内容 |
|
||||
#### 3. summary_memory — 记忆持久化
|
||||
|
||||
### 记忆检索
|
||||
|
||||
[MemorySearch](reme/memory/tools/chunk/memory_search.py) 提供**向量 + BM25 混合检索**能力:
|
||||
|
||||
| 检索方式 | 优势 | 劣势 |
|
||||
|-------------|-----------------|----------------|
|
||||
| **向量语义** | 捕捉意义相近但措辞不同的内容 | 对精确 token 匹配较弱 |
|
||||
| **BM25 全文** | 精确 token 命中效果极佳 | 无法理解同义词和改写 |
|
||||
|
||||
**融合机制**:两路召回后按权重加权求和(向量 0.7 + BM25 0.3),自然语言与精确查找均可命中。
|
||||
[Summarizer](reme/memory/file_based/component/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Q[搜索查询] --> V[向量搜索 × 0.7]
|
||||
Q --> B[BM25 × 0.3]
|
||||
V --> M[去重 + 加权融合]
|
||||
B --> M
|
||||
M --> R[Top-N 结果]
|
||||
M[messages] --> A[ReActAgent<br>reme_summarizer]
|
||||
A -->|read| R[读取 memory/YYYY-MM-DD.md]
|
||||
R --> T{思考: 如何合并?}
|
||||
T -->|write| W[覆盖写入]
|
||||
T -->|edit| E[精确替换]
|
||||
W --> F[memory/YYYY-MM-DD.md]
|
||||
E --> F
|
||||
```
|
||||
|
||||
**文件工具**([FileIO](reme/memory/file_based/tools/file_io.py)):
|
||||
|
||||
| 工具 | 功能 |
|
||||
|---------|---------|
|
||||
| `read` | 读取文件内容 |
|
||||
| `write` | 覆盖写入文件 |
|
||||
| `edit` | 精确匹配后替换 |
|
||||
|
||||
---
|
||||
|
||||
#### 4. compact_tool_result — 工具结果压缩
|
||||
|
||||
[ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) 解决工具输出过长导致上下文膨胀的问题。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
M[messages] --> L{遍历 tool_result<br>len > threshold?}
|
||||
L -->|否| K[保留原样]
|
||||
L -->|是| T[truncate_text<br>截断到 threshold]
|
||||
T --> S[完整内容写入<br>tool_result/uuid.txt]
|
||||
S --> R[消息追加文件路径引用]
|
||||
R --> C[cleanup_expired_files<br>清理过期文件]
|
||||
```
|
||||
|
||||
- **自动清理**:过期文件(超过 `retention_days`)在 `start`/`close`/`compact_tool_result` 时自动删除
|
||||
|
||||
---
|
||||
|
||||
#### 5. memory_search — 记忆检索
|
||||
|
||||
[MemorySearch](reme/memory/file_based/tools/memory_search.py) 提供**向量 + BM25 混合检索**能力。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Q[query] --> E[Embedding<br>向量化]
|
||||
E --> V[vector_search<br>语义相似]
|
||||
Q --> B[BM25<br>关键词匹配]
|
||||
V -->|" weight: 0.7 "| M[去重 + 加权融合]
|
||||
B -->|" weight: 0.3 "| M
|
||||
M --> F[min_score 过滤]
|
||||
F --> R[Top-N 结果]
|
||||
```
|
||||
|
||||
- **融合机制**:向量权重 0.7 + BM25 权重 0.3,兼顾语义相似和精确匹配
|
||||
|
||||
---
|
||||
|
||||
#### 6. ReMeInMemoryMemory — 会话内存
|
||||
|
||||
[ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) 扩展 AgentScope 的 `InMemoryMemory`,提供 Token
|
||||
感知的内存管理。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
C[content] --> G[get_memory<br>exclude_mark=COMPRESSED]
|
||||
G --> F[排除已压缩消息]
|
||||
F --> P{prepend_summary?}
|
||||
P -->|是| S[头部插入 previous-summary]
|
||||
S --> O[输出 messages]
|
||||
P -->|否| O
|
||||
```
|
||||
|
||||
| 功能 | 说明 |
|
||||
|----------------------------------|-------------------|
|
||||
| `get_memory` | 按标记过滤,自动追加压缩摘要 |
|
||||
| `estimate_tokens` | 估算上下文 Token 用量 |
|
||||
| `state_dict` / `load_state_dict` | 状态序列化/反序列化(会话持久化) |
|
||||
|
||||
---
|
||||
|
||||
#### 7. pre_reasoning_hook — 推理前预处理
|
||||
|
||||
整合上述组件的统一入口,在每轮推理前自动管理上下文。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
M[messages] --> TC[compact_tool_result<br>压缩超长工具输出]
|
||||
TC --> CC[check_context<br>计算剩余空间]
|
||||
CC --> D{messages_to_compact<br>非空?}
|
||||
D -->|否| K[返回原消息 + 原摘要]
|
||||
D -->|是| V{is_valid?}
|
||||
V -->|否| K
|
||||
V -->|是| CM[compact_memory<br>同步生成摘要]
|
||||
V -->|是| SM[add_async_summary_task<br>异步持久化]
|
||||
CM --> R[返回 messages_to_keep + 新摘要]
|
||||
```
|
||||
|
||||
**执行流程**:
|
||||
|
||||
1. `compact_tool_result` — 压缩超长工具输出
|
||||
2. `check_context` — 检查上下文是否超限
|
||||
3. `compact_memory` — 生成压缩摘要(同步)
|
||||
4. `summary_memory` — 持久化记忆(异步后台)
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ 基于向量库的记忆系统
|
||||
|
||||
[ReMe Vector Based](reme/reme.py) 是基于向量库的记忆系统核心类,支持三种记忆类型的统一管理:
|
||||
|
||||
| 记忆类型 | 用途 | 使用场景 |
|
||||
|--------------|------------------|-------------|
|
||||
| **个人记忆** | 记录用户偏好、习惯 | `user_name` |
|
||||
| **任务/程序性记忆** | 记录任务执行经验、成功/失败模式 | `task_name` |
|
||||
| **工具记忆** | 记录工具使用经验、参数优化 | `tool_name` |
|
||||
| 记忆类型 | 用途 |
|
||||
|--------------|------------------|
|
||||
| **个人记忆** | 记录用户偏好、习惯 |
|
||||
| **任务/程序性记忆** | 记录任务执行经验、成功/失败模式 |
|
||||
| **工具记忆** | 记录工具使用经验、参数优化 |
|
||||
|
||||
### 核心能力
|
||||
|
||||
|
|
@ -302,24 +366,11 @@ M --> R[Top-N 结果]
|
|||
| `delete_memory` | 🗑️ 删除记忆 | 删除指定记忆 |
|
||||
| `list_memory` | 📋 列出记忆 | 列出某类记忆,支持过滤和排序 |
|
||||
|
||||
### 安装
|
||||
### 安装与环境变量
|
||||
|
||||
```bash
|
||||
pip install -U reme-ai
|
||||
```
|
||||
安装和环境变量配置与 [ReMeLight 一致](#安装),通过环境变量设置 API 密钥,可写在项目根目录的 `.env` 文件中。
|
||||
|
||||
### 环境变量
|
||||
|
||||
API 密钥通过环境变量设置,可写在项目根目录的 `.env` 文件中:
|
||||
|
||||
| 环境变量 | 说明 | 示例 |
|
||||
|----------------------|--------------------------|-----------------------------------------------------|
|
||||
| `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 使用
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
|
@ -413,7 +464,7 @@ if __name__ == "__main__":
|
|||
### 技术架构
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
graph LR
|
||||
User[用户 / Agent] --> ReMe[Vector Based ReMe]
|
||||
ReMe --> Summarize[记忆总结]
|
||||
ReMe --> Retrieve[记忆检索]
|
||||
|
|
@ -432,6 +483,42 @@ graph TB
|
|||
ToolRet --> VectorStore
|
||||
```
|
||||
|
||||
### 实验效果
|
||||
|
||||
Coming soon...
|
||||
|
||||
---
|
||||
|
||||
## 🧪 程序化记忆论文
|
||||
|
||||
> 我们的程序性(任务)记忆论文已在 [arXiv](https://arxiv.org/abs/2512.10696) 发布
|
||||
|
||||
### 🌍 [Appworld 实验](benchmark/appworld/quickstart.md)
|
||||
|
||||
我们在 Appworld 环境上使用 Qwen3-8B(非思考模式)进行评测:
|
||||
|
||||
| 方法 | Avg@4 | Pass@4 |
|
||||
|---------|---------------------|---------------------|
|
||||
| 无 ReMe | 0.1497 | 0.3285 |
|
||||
| 使用 ReMe | 0.1706 **(+2.09%)** | 0.3631 **(+3.46%)** |
|
||||
|
||||
Pass@K 衡量在生成 K 个候选中,至少一个成功完成任务(score=1)的概率。
|
||||
当前实验使用的是内部 AppWorld 环境,可能与对外版本存在轻微差异。
|
||||
|
||||
关于如何复现实验的更多细节,见 [quickstart.md](benchmark/appworld/quickstart.md)
|
||||
|
||||
### 🔧 [BFCL-V3 实验](benchmark/bfcl/quickstart.md)
|
||||
|
||||
我们在 BFCL-V3 multi-turn-base 任务(随机划分 50 train / 150 val)上,使用 Qwen3-8B(思考模式)进行评测:
|
||||
|
||||
| 方法 | Avg@4 | Pass@4 |
|
||||
|---------|---------------------|---------------------|
|
||||
| 无 ReMe | 0.4033 | 0.5955 |
|
||||
| 使用 ReMe | 0.4450 **(+4.17%)** | 0.6577 **(+6.22%)** |
|
||||
|
||||
关于如何复现实验的更多细节,见 [quickstart.md](benchmark/bfcl/quickstart.md)
|
||||
|
||||
|
||||
## ⭐ 社区与支持
|
||||
|
||||
- **Star 与 Watch**:Star 可让更多智能体开发者发现 ReMe;Watch 可助你第一时间获知新版本与特性。
|
||||
|
|
|
|||
5
benchmark/halumem/eval_scripts.sh
Executable file
5
benchmark/halumem/eval_scripts.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
clear && python benchmark/halumem/eval_reme.py \
|
||||
--data_path /Users/yuli/workspace/HaluMem/data/HaluMem-Medium.jsonl \
|
||||
--reme_model_name qwen3.5-plus \
|
||||
--batch_size 10000 \
|
||||
--algo_version default
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
|
||||
# TODO
|
||||
- [] halumem bench开发
|
||||
- [] default版本开发,for cli版本体验
|
||||
- [] cli开发
|
||||
- [] locomo bench开发
|
||||
- [] task memory迁移
|
||||
- [] mcp开发
|
||||
- [] reme外层接口完善
|
||||
- [] reme2 readme完善
|
||||
- [] 看日志,看要这个default版本怎么优化。
|
||||
- [] 学习Clawdbot记忆系统
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
from loguru import logger
|
||||
|
||||
用英文注释,完善module/class/function docstring,要一句话简洁,不要变更代码逻辑,符合pep和pylint规范,使用list而不是typing.List/Dict,不使用typing.Union
|
||||
|
||||
看看代码有什么问题
|
||||
用英文注释,完善module/class/function docstring,要一句话简洁,代码要简洁,符合pep和pylint规范,使用list而不是typing.List,不使用typing.Union
|
||||
C0114: Missing module docstring (missing-module-docstring)
|
||||
C0115: Missing class docstring (missing-class-docstring)
|
||||
C0116: Missing function or method docstring (missing-function-docstring)
|
||||
done: { for f in ./*.py; do [[ "$f" != "./__init__.py" ]] && grep -v '^[[:space:]]*#' "$f"; done; } | pbcopy
|
||||
|
||||
然后是一个完整的tests,但是不要用其他的包,只是test开头的函数或者类,要求from loguru import logger
|
||||
写一个测试文件,不要使用pytest,普通的test,要求英文注释
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Future Work
|
||||
|
||||
- [ ] P0 ReMe documentation style migration: Recommend using the same doc and jupyter structure as Agentscope Runtime @jiaji
|
||||
- [ ] P0 ReMe integration with agentscope Personal/Task/Tool @jinli
|
||||
- [ ] P0 ReMe sample library examples [show case](https://github.com/agentscope-ai/agentscope-samples/tree/main/functionality/long_term_memory_mem0) @jinli
|
||||
- [ ] P0 Decouple flowllm dependencies @jinli
|
||||
- [ ] P0 ReMe support for import, improve code documentation @jinli
|
||||
- [ ] P1 ReMe integration with asio tool_memory @jinli
|
||||
- [ ] P2 ReMe integration with agentscope-Runtime tool_memory @jinli
|
||||
|
||||
- [ ] P0 Task Memory Research Paper @zhoyin
|
||||
|
||||
- [ ] P1 Context interface definition @jinli
|
||||
|
||||
- [ ] P2 Database layer interface unification @jinli
|
||||
- [ ] P2 Automatic Tool Exploration Mode @wangcan
|
||||
- [ ] P2 Mem-Agent Exploration @weikang
|
||||
- [ ] P2 Desktop Pet Personal Assistant
|
||||
|
|
@ -1,735 +0,0 @@
|
|||
# ReMeV2 深度设计文档:渐进式 Agentic Memory 方案
|
||||
|
||||
## 一、 背景与现状分析
|
||||
|
||||
### 1.1 当前面临的挑战
|
||||
|
||||
* **外功修炼(接口易用性)**:现有的 `server-client` 模式对新手开发者不够友好,集成成本高,需要更直观、纯 Pythonic 的调用方式。
|
||||
* **内功修炼(架构深度)**:受 `skills` 和 `agentic memory` 启发,现有的存储检索较为机械。我们需要一种基于**渐进式检索(Progressive Retrieval)**与**渐进式总结(Progressive Summarization)**的智能体记忆方案。
|
||||
|
||||
### 1.2 核心目标
|
||||
|
||||
1. **极简开发体验**:开发者友好,全异步接口,支持本地直接运行与 CLI 体验。
|
||||
2. **认知架构升级**:引入 渐进式检索 & 渐进式总结 的 Agentic 模式,融合多种记忆,让记忆的存取具备“思考”过程。
|
||||
3. **生态融合**:原生支持 AgentScope、LangChain 等主流框架。
|
||||
|
||||
---
|
||||
|
||||
## 二、 竞品调研与启示
|
||||
|
||||
### 2.1 主流竞品深度对比
|
||||
|
||||
| 产品 | 设计哲学 | 核心优势 | 局限性 |
|
||||
|-------------|----------|---------------------------------------------|-------------------|
|
||||
| **mem0** | 智能便签本 | 原子事实提取,极高 Token 效率。 | 缺乏对复杂逻辑链条的支持。 |
|
||||
| **Letta** | 带硬盘的 CPU | 模拟计算机三级存储(Core/Recall/Archival),Agent 自主控存。 | 状态机管理相对复杂。 |
|
||||
| **MIRIX** | 认知架构图谱 | 实体-关系双引擎,支持记忆“进化”与“固化”。 | 侧重研究,落地集成门槛较高。 |
|
||||
| **LangMem** | 用户档案系统 | 异步 Compaction(压缩),Schema 驱动,强一致性。 | 偏向 SaaS 应用,灵活性略逊。 |
|
||||
|
||||
### 2.2 mem0
|
||||
- https://github.com/mem0ai/mem0
|
||||
- https://docs.mem0.ai/core-concepts/memory-operations/add
|
||||
- https://docs.mem0.ai/core-concepts/memory-operations/search
|
||||
- https://docs.mem0.ai/core-concepts/memory-operations/update
|
||||
- https://docs.mem0.ai/core-concepts/memory-operations/delete
|
||||
|
||||
#### 2.2.1 API Reference
|
||||
| 接口名称 | 核心输入参数 (Inputs) | 核心输出 (Outputs) | 背后逻辑 (Internal Logic) |
|
||||
| --- | --- | --- | --- |
|
||||
| **Add** | `messages` (文本/对话), `user_id`, `metadata` | `id`, `event` (ADD/UPDATE), `data` | **提取与合并**:LLM 提取事实,自动去重并更新已有记忆,而非简单堆叠。 |
|
||||
| **Search** | `query` (自然语言), `filters`, `limit` | `id`, `memory` (事实文本), `score`, `metadata` | **语义检索**:基于向量相似度查找最相关的“原子事实”,支持多维过滤。 |
|
||||
| **Update** | `memory_id` (必填), `data` (新内容) | 操作状态 (Success/Fail) | **手动干预**:允许开发者对特定的事实进行精确修正。 |
|
||||
| **Delete** | `memory_id` 或 `user_id` (清空) | 操作状态 (Success/Fail) | **遗忘机制**:物理删除或逻辑移除不再需要的信息。 |
|
||||
|
||||
#### 2.2.2 Tech Strategy & Benefits
|
||||
| 维度 | 技术方案 (Technical Solution) | 核心优势 (Key Advantages) |
|
||||
| --- | --- | --- |
|
||||
| **存储架构** | **混合存储**:向量数据库 (Vector) + 图数据库 (Graph) + 关系型元数据。 | **多维关联**:不仅能搜到相似内容,还能理解实体间的逻辑关系(如“父子”、“因果”)。 |
|
||||
| **数据处理** | **原子化事实提取**:利用 LLM 将长篇对话压缩为简短的 Fact。 | **极高 Token 效率**:注入 Prompt 的内容更精炼,减少 90% 以上的冗余信息,大幅降本。 |
|
||||
| **管理层级** | **多级联动**:User (长期) Agent (专业) Session (短期)。 | **个性化定制**:实现跨会话的“长效记忆”,AI 能记住用户一个月前说过的偏好。 |
|
||||
| **冲突处理** | **自适应更新算法**:新信息进入时自动比对旧记忆。 | **数据一致性**:自动处理矛盾信息(如用户更换了住址),确保记忆库始终是“最新真理”。 |
|
||||
| **兼容性** | **解耦设计**:支持多种 Embedding 模型与向量数据库后端。 | **快速集成**:几行代码即可为现有 LLM 应用增加记忆层,适配各种生产环境。 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Letta
|
||||
- https://github.com/letta-ai/letta
|
||||
- https://docs.letta.com/guides/agents/archival-memory/
|
||||
- https://docs.letta.com/guides/agents/archival-search/
|
||||
|
||||
#### 2.3.1 存储架构层级 (Memory Tiering)
|
||||
|
||||
Letta 将记忆分为三个物理/逻辑层,模拟计算机的存储架构:
|
||||
|
||||
| 记忆层级 | 存储介质 | 访问方式 | 核心作用 |
|
||||
| --- | --- | --- | --- |
|
||||
| **Core Memory** | **上下文窗口 (Prompt)** | 直接读写 | **即时意识**:包含 `Persona`(AI 设定)和 `Human`(用户信息)。Agent 随时可见,响应最快。 |
|
||||
| **Recall Memory** | **关系型数据库 (SQL)** | 分页检索 | **短期/历史回顾**:存储完整的对话流(Messages)。用于回答“你刚才说了什么”。 |
|
||||
| **Archival Memory** | **向量数据库 (Vector)** | 语义搜索 | **长期知识库**:存储海量事实或文档。Agent 通过工具自主检索或存入。 |
|
||||
|
||||
#### 2.3.2 核心操作接口 (API & Tool Reference)
|
||||
|
||||
在 Letta 中,记忆的操作通常封装为 **Tools**,由 Agent 根据推理需求主动调用。
|
||||
|
||||
| 接口/工具名称 | 输入参数 (Inputs) | 核心输出 (Outputs) | 背后逻辑 (Internal Logic) |
|
||||
| --- | --- | --- | --- |
|
||||
| **`core_memory_update`** | `section`, `new_content` | 更新后的段落内容 | **原子替换**:直接修改 System Prompt 中的特定块(如:更新用户的职业或 AI 的性格偏好)。 |
|
||||
| **`archival_memory_insert`** | `content` (字符串) | 写入状态/ID | **知识沉淀**:将当前对话中的重要信息或外部文件片段“持久化”到向量数据库。 |
|
||||
| **`archival_memory_search`** | `query`, `page` | 匹配的文本块列表 | **主动 RAG**:Agent 意识到知识不足时,自主发起向量检索,并将结果拉入临时上下文。 |
|
||||
| **`conversation_search`** | `query`, `start_date` | 历史消息记录 | **全文检索**:在 Recall Memory 中根据关键词或时间戳查找历史对话详情。 |
|
||||
| **`send_message`** | `message`, `agent_id` | 响应流/状态更新 | **状态循环**:这是主入口,触发 Agent 的“思考-行动-观察”循环,自动处理内存同步。 |
|
||||
|
||||
#### 2.3.3 技术策略与核心优势 (Tech Strategy & Benefits)
|
||||
|
||||
| 维度 | 技术方案 (Technical Solution) | 核心优势 (Key Advantages) |
|
||||
| --- | --- | --- |
|
||||
| **状态持久化** | **Agent State Snapshot**:将 Agent 的所有内存、工具定义和历史记录打包存入数据库。 | **无限存续**:Agent 不再是无状态的 API 调用。重启服务器后,Agent 依然记得所有细节。 |
|
||||
| **自主演进** | **Self-Editing Loop**:Agent 拥有修改自己 Core Memory 的权限(通过函数调用)。 | **认知闭环**:AI 能在交流中发现矛盾并自我更正,例如发现用户搬家后自动更新 `Human` 模块。 |
|
||||
| **算力调度** | **OOC (Out-of-Context) 管理**:当对话过长,系统自动将旧消息从 Core 移入 Recall。 | **突破 Context 限制**:在 8k 窗口的模型上也能处理相当于 1M 窗口的逻辑量,且成本更低。 |
|
||||
| **多代理协同** | **Letta Server 中控**:统一管理多个 Agent 的状态机与资源访问权限。 | **企业级扩展**:支持创建 Agent 团队,每个 Agent 拥有独立的记忆空间但可共享 Archival 库。 |
|
||||
| **解耦灵活性** | **Provider Agnostic**:后端支持 Postgres/Chroma,前端支持 OpenAI/Anthropic/Local LLMs。 | **无缝迁移**:不绑定特定模型,开发者可以根据成本或能力随时更换底座。 |
|
||||
|
||||
#### 2.3.4 与 mem0 的深度对比
|
||||
|
||||
* **设计哲学**:
|
||||
* **mem0** 像是一个**“智能记事本”**,它在后台默默地帮你总结事实。
|
||||
* **Letta** 像是一个**“带硬盘的 CPU”**,它把记忆管理完全交给了 Agent 自己的逻辑推理。
|
||||
|
||||
|
||||
* **交互模式**:
|
||||
* **mem0** 通常是外部干预(Add/Search)。
|
||||
* **Letta** 强调 **Agentic Control**(Agent 意识到需要搜索时才去搜索),这种模式更接近人类的思维过程。
|
||||
|
||||
---
|
||||
|
||||
### 2.4 MIRIX
|
||||
- https://github.com/Mirix-AI/MIRIX
|
||||
- https://docs.mirix.io/
|
||||
|
||||
#### 2.4.1 API Reference
|
||||
|
||||
| 接口名称 | 核心输入参数 (Inputs) | 核心输出 (Outputs) | 背后逻辑 (Internal Logic) |
|
||||
| --- | --- | --- | --- |
|
||||
| **Add** | `content` (观察/对话), `agent_id`, `context_type` (如任务/闲聊) | `memory_id`, `graph_nodes`, `status` | **实体建模**:不只是提取事实,而是将信息拆解为实体(Entities)与关系(Relations),并挂载到智能体的知识图谱中。 |
|
||||
| **Query** | `query` (意图), `scope` (全局/局部), `top_k` | `retrieved_memories`, `relation_paths`, `score` | **混合检索**:结合向量(Vector)的语义相关性和图(Graph)的拓扑连接性,寻找具有逻辑深度背景的记忆。 |
|
||||
| **Evolve** | `target_memories` (可选), `agent_id` | `optimized_structure`, `merged_nodes` | **记忆固化/压缩**:模仿人类大脑的“睡眠”机制,自动合并碎片化记忆,将短期经验转化为长期的结构化知识。 |
|
||||
| **Observe** | `interaction_stream`, `feedback` | `insights`, `priority_update` | **实时学习**:根据用户反馈或环境变化,动态调整记忆的权重(Importance)和置信度。 |
|
||||
|
||||
#### 2.4.2 Tech Strategy & Benefits
|
||||
|
||||
| 维度 | 技术方案 (Technical Solution) | 核心优势 (Key Advantages) |
|
||||
| --- | --- | --- |
|
||||
| **存储架构** | **语义-关系双引擎**:向量索引(Vector Index)+ 属性图(Property Graph)。 | **深度上下文**:不仅知道“是什么”,还能通过图路径推理出“为什么”,有效解决 LLM 幻觉问题。 |
|
||||
| **记忆层级** | **三层架构**:感知记忆 (Perception) -> 语义记忆 (Semantic) -> 经验记忆 (Episodic)。 | **任务适应性**:不同任务自动匹配不同的记忆深度,短期任务关注细节,长期任务关注模式。 |
|
||||
| **演化机制** | **自主固化 (Self-Consolidation)**:通过 LLM 定期对冗余、矛盾信息进行清洗和逻辑抽象。 | **永久生命力**:解决随时间推移记忆库膨胀导致的检索噪声,确保记忆库“越用越聪明”。 |
|
||||
| **推理增强** | **基于记忆的 RAG+**:在检索到的事实基础上,额外提供关联的逻辑链条(Logic Chains)。 | **辅助决策**:为 Agent 提供决策支撑,使其在处理复杂流程时具备类似“长期经验值”的直觉。 |
|
||||
| **多代理协同** | **内存共享协议**:支持 Agent 之间的记忆交换与知识同步。 | **群体智能**:多个 Agent 可以共享同一套底层知识体系,同时保留各自的私有工作记忆。 |
|
||||
|
||||
#### 2.4.3 与 mem0 的主要区别
|
||||
|
||||
* **Mem0** 侧重于**个性化偏好存储**(Personalization),核心是记住“用户喜欢什么”。
|
||||
* **MIRIX** 侧重于**智能体认知架构**(Agent Cognition),核心是让 Agent 具备类似人类的“知识归纳”和“逻辑推理”记忆能力。
|
||||
|
||||
---
|
||||
|
||||
### 2.5 LangMem
|
||||
- https://github.com/langchain-ai/langmem
|
||||
- https://langchain-ai.github.io/langmem/
|
||||
|
||||
#### 2.5.1 API Reference
|
||||
|
||||
| 接口名称 | 核心输入参数 (Inputs) | 核心输出 (Outputs) | 背后逻辑 (Internal Logic) |
|
||||
| --- | --- | --- | --- |
|
||||
| **Add Messages** | `thread_id`, `messages` (List), `user_id` | 操作确认 / 任务 ID | **流式注入**:将原始对话追加到指定的 Thread。LangMem 会自动关联用户上下文,准备进行后续的异步处理。 |
|
||||
| **Query Memory** | `user_id`, `query` (语义描述), `namespace` | 结构化记忆对象 (JSON / Text) | **多维检索**:不仅支持向量相似度搜索,还能根据定义的 Schema 返回结构化的用户画像或知识状态。 |
|
||||
| **Trigger Logic** | `thread_id`, `memory_type` | 更新后的 Memory State | **异步固化**:后台启动 LLM 任务,将长篇对话“压缩”并“提取”到长期存储中。支持自定义提取逻辑(如更新用户信息)。 |
|
||||
| **Manage State** | `user_id`, `patch_data` (增量更新) | 成功/失败 状态 | **精确受控**:开发者可以直接修改持久化的状态(State),支持类似于 Git 的状态管理。 |
|
||||
|
||||
#### 2.5.2 Tech Strategy & Benefits
|
||||
|
||||
| 维度 | 技术方案 (Technical Solution) | 核心优势 (Key Advantages) |
|
||||
| --- | --- | --- |
|
||||
| **存储架构** | **Stateful Persistence**:基于关系型数据库 (Postgres) + 向量索引。 | **强一致性**:利用数据库事务确保记忆更新的可靠性,支持复杂的结构化查询与过滤。 |
|
||||
| **数据处理** | **异步化 Compaction (压缩)**:在对话间隙通过后台 Worker 提取知识。 | **无感延迟**:核心对话流程不被记忆提取阻塞,通过定时或事件驱动完成“记忆固化”,优化用户体验。 |
|
||||
| **管理层级** | **Thread -> User -> Organization**:三层级联记忆。 | **上下文隔离**:完美适配 SaaS 应用场景,既能记住单次对话(Thread),也能沉淀用户习惯(User)。 |
|
||||
| **逻辑引擎** | **Schema-Driven (模式驱动)**:允许定义 JSON Schema 来规范记忆内容。 | **高度可预测**:输出不再是散乱的句子,而是结构化的字段,方便下游程序直接调用逻辑(如自动填充表单)。 |
|
||||
| **集成生态** | **LangGraph 原生集成**:作为 Checkpointer 或存储节点直接接入。 | **生态协同**:如果你已经在用 LangChain,LangMem 可以无缝接管状态流转,无需重写底层存储逻辑。 |
|
||||
|
||||
#### 2.5.3 与 mem0 的核心差异
|
||||
|
||||
* **mem0** 像是一个**“便签本”**:它擅长从每一句话里抠出零散的事实(如“我喜欢吃苹果”),然后把它们存成一条条语义片段。
|
||||
* **LangMem** 像是一个**“用户档案系统”**:它更擅长分析一整段对话,然后更新一个复杂的 JSON 档案(如更新用户的偏好模型、性格标签、历史任务状态)。
|
||||
|
||||
---
|
||||
|
||||
## 三、 ReMeV2 API 接口设计
|
||||
|
||||
### 3.1 Long-Term Memory (长期记忆)
|
||||
|
||||
#### 3.1.1 Basic Usage (基础用法)
|
||||
|
||||
The most straightforward way to use ReMe for long-term memory management. Supports basic summary and retrieval operations.
|
||||
|
||||
```python
|
||||
import os
|
||||
from reme_ai import ReMe
|
||||
|
||||
os.environ["REME_LLM_API_KEY"] = "sk-..."
|
||||
os.environ["REME_LLM_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
os.environ["REME_EMBEDDING_API_KEY"] = "sk-..."
|
||||
os.environ["REME_EMBEDDING_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
memory = ReMe(
|
||||
memory_space="remy", # workspace identifier
|
||||
llm={"backend": "openai", "model": "qwen-plus", "temperature": 0.6},
|
||||
embedding={"backend": "openai", "model": "text-embedding-v4", "dimension": 1024},
|
||||
vector_store={"backend": "local_file"}, # supported: local_file, chromadb, qdrant, etc.
|
||||
)
|
||||
|
||||
# Summarize conversation into memory
|
||||
result = await memory.summary(
|
||||
messages=[
|
||||
{"role": "user", "content": "I'm travelling to SF"},
|
||||
{"role": "assistant", "content": "That's great to hear!"}
|
||||
],
|
||||
user_id="Alice",
|
||||
# memory_type="auto" # default: auto (auto, personal, procedural, tool)
|
||||
)
|
||||
|
||||
# Retrieve relevant memories
|
||||
memories = await memory.retrieve(
|
||||
query="what is your travel plan?",
|
||||
limit=3,
|
||||
user_id="Alice",
|
||||
# memory_type="auto" # default: auto
|
||||
)
|
||||
memories_str = "\n".join(f"- {m['memory']}" for m in memories["results"])
|
||||
print(memories_str)
|
||||
```
|
||||
|
||||
#### 3.1.2 CLI Chat Application (命令行聊天应用)
|
||||
|
||||
A complete example demonstrating how to build a memory-enhanced chatbot with CLI interface.
|
||||
|
||||
```python
|
||||
import os
|
||||
from reme_ai import ReMe
|
||||
from openai import OpenAI
|
||||
|
||||
os.environ["REME_LLM_API_KEY"] = "sk-..."
|
||||
os.environ["REME_LLM_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
os.environ["REME_EMBEDDING_API_KEY"] = "sk-..."
|
||||
os.environ["REME_EMBEDDING_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
memory = ReMe(
|
||||
memory_space="remy",
|
||||
llm={"backend": "openai", "model": "qwen-plus", "temperature": 0.6},
|
||||
embedding={"backend": "openai", "model": "text-embedding-v4", "dimension": 1024},
|
||||
vector_store={"backend": "local_file"},
|
||||
)
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "sk-..."
|
||||
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
openai_client = OpenAI()
|
||||
|
||||
def chat_with_memories(
|
||||
query: str,
|
||||
history_messages: list[dict],
|
||||
user_name: str = "",
|
||||
start_summary_size: int = 2,
|
||||
keep_size: int = 0
|
||||
) -> str:
|
||||
# Retrieve relevant memories for the query
|
||||
memories = memory.retrieve(query=query, user_id=user_name, limit=3)
|
||||
|
||||
# Build system prompt with memories
|
||||
system_prompt = (
|
||||
"You are a helpful AI named `Remy`. Use the user memories to answer the question. "
|
||||
"If you don't know the answer, just say you don't know. Don't try to make up an answer.\n"
|
||||
)
|
||||
if memories:
|
||||
memories_str = "\n".join(f"- {m['memory']}" for m in memories["results"])
|
||||
system_prompt += f"User Memories:\n{memories_str}\n"
|
||||
|
||||
# Generate response
|
||||
system_message = {"role": "system", "content": system_prompt}
|
||||
history_messages.append({"role": "user", "content": query})
|
||||
response = openai_client.chat.completions.create(
|
||||
model="qwen-plus",
|
||||
messages=[system_message] + history_messages
|
||||
)
|
||||
history_messages.append({"role": "assistant", "content": response.choices[0].message.content})
|
||||
|
||||
# Summarize history when it gets too long
|
||||
if len(history_messages) >= start_summary_size:
|
||||
memory.summary(history_messages[:-keep_size], user_id=user_name)
|
||||
print("Current memories: " + memory.list_memories(user_id=user_name))
|
||||
history_messages = history_messages[-keep_size:]
|
||||
|
||||
return history_messages[-1]["content"]
|
||||
|
||||
def main():
|
||||
user_name = input("Enter your name: ").strip()
|
||||
print("Chat with Remy (type 'exit' to quit)")
|
||||
|
||||
messages = []
|
||||
while True:
|
||||
user_input = input(f"{user_name}: ").strip()
|
||||
if user_input.lower() == 'exit':
|
||||
print("Goodbye!")
|
||||
break
|
||||
|
||||
print(f"Remy: {chat_with_memories(user_input, messages, user_name)}")
|
||||
|
||||
# Cleanup
|
||||
memory.delete_all_memories(user_id=user_name)
|
||||
print("All memories deleted")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
#### 3.1.3 Advanced Usage (高级用法)
|
||||
|
||||
For advanced users who want to customize retriever and summarizer behavior with Agentic mode.
|
||||
|
||||
```python
|
||||
import os
|
||||
from reme_ai import ReMe
|
||||
from reme_ai.retriever import AgenticRetriever
|
||||
from reme_ai.summarizer import AgenticSummarizer
|
||||
from reme_ai.tools import ATool, BTool, CTool
|
||||
|
||||
os.environ["REME_LLM_API_KEY"] = "sk-..."
|
||||
os.environ["REME_LLM_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
os.environ["REME_EMBEDDING_API_KEY"] = "sk-..."
|
||||
os.environ["REME_EMBEDDING_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
memory = ReMe(
|
||||
memory_space="remy",
|
||||
llm={"backend": "openai", "model": "qwen-plus", "temperature": 0.6},
|
||||
embedding={"backend": "openai", "model": "text-embedding-v4", "dimension": 1024},
|
||||
vector_store={"backend": "local_file"},
|
||||
use_agentic_mode=True,
|
||||
)
|
||||
|
||||
# Customize retriever and summarizer with custom tools and prompts
|
||||
memory.set_retriever(
|
||||
AgenticRetriever(tools=[ATool(), BTool(), CTool()]),
|
||||
system_prompt="Custom retrieval instructions..."
|
||||
)
|
||||
memory.set_summarizer(
|
||||
AgenticSummarizer(tools=[ATool(), BTool(), CTool()])
|
||||
)
|
||||
|
||||
# Use the customized memory system
|
||||
result = memory.summary(
|
||||
messages=[
|
||||
{"role": "user", "content": "I'm travelling to SF"},
|
||||
{"role": "assistant", "content": "That's great to hear!"}
|
||||
],
|
||||
user_id="Alice",
|
||||
memory_type="auto", # auto, personal, procedural, tool
|
||||
)
|
||||
|
||||
memories = memory.retrieve(
|
||||
query="what is your travel plan?",
|
||||
limit=3,
|
||||
user_id="Alice",
|
||||
memory_type="auto",
|
||||
)
|
||||
memories_str = "\n".join(f"- {m['memory']}" for m in memories["results"])
|
||||
print(memories_str)
|
||||
```
|
||||
|
||||
### 3.2 Short-Term Memory (短期记忆)
|
||||
|
||||
#### 3.2.1 Basic Usage (基础用法)
|
||||
|
||||
Context offload/reload API for managing short-term conversational memory within a session.
|
||||
|
||||
```python
|
||||
import os
|
||||
from reme_ai import ReMe
|
||||
|
||||
os.environ["REME_LLM_API_KEY"] = "sk-..."
|
||||
os.environ["REME_LLM_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
os.environ["REME_EMBEDDING_API_KEY"] = "sk-..."
|
||||
os.environ["REME_EMBEDDING_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
memory = ReMe(
|
||||
memory_space="remy",
|
||||
llm={"backend": "openai", "model": "qwen-plus", "temperature": 0.6},
|
||||
embedding={"backend": "openai", "model": "text-embedding-v4", "dimension": 1024},
|
||||
vector_store={"backend": "local_file"},
|
||||
)
|
||||
|
||||
# Offload context when conversation gets too long
|
||||
result = memory.offload_context(
|
||||
messages=[
|
||||
{"role": "user", "content": "I'm travelling to SF"},
|
||||
{"role": "assistant", "content": "That's great to hear!"}
|
||||
],
|
||||
)
|
||||
|
||||
# Reload relevant context when needed
|
||||
memories = memory.reload_context(
|
||||
query="what is your travel plan?",
|
||||
limit=3,
|
||||
)
|
||||
memories_str = "\n".join(f"- {m['memory']}" for m in memories["results"])
|
||||
print(memories_str)
|
||||
```
|
||||
|
||||
### 3.3 Framework Integration (框架集成)
|
||||
|
||||
#### 3.3.1 Integration with AgentScope
|
||||
|
||||
Integration example for AgentScope ReActAgent with long-term memory support.
|
||||
|
||||
```python
|
||||
# TODO: Provide AgentScope integration example
|
||||
```
|
||||
|
||||
#### 3.3.2 Integration with LangChain
|
||||
|
||||
Integration example for LangChain agents with ReMe memory layer.
|
||||
|
||||
```python
|
||||
# TODO: Provide LangChain integration example
|
||||
```
|
||||
|
||||
### 3.4 OpenAI Compatible Interface
|
||||
|
||||
OpenAI-compatible API interface for seamless integration with existing OpenAI-based applications.
|
||||
|
||||
```python
|
||||
# TODO: Research and implement OpenAI-compatible interface
|
||||
# - Support for threads and assistants API
|
||||
# - Compatible with OpenAI SDK
|
||||
# - Support for streaming responses
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 四、核心方案设计
|
||||
|
||||
### 4.1 设计概述
|
||||
|
||||
ReMeV2 采用简洁的架构设计,核心理念为:**ReMeV2 = Tool(s) + Agent(s)**
|
||||
|
||||
- **Tool层**:提供原子化的记忆操作能力,包括增删改查、检索、元数据管理等基础操作
|
||||
- **Agent层**:基于Tool层构建的智能代理,负责复杂的记忆管理逻辑,如分类总结、渐进式检索等
|
||||
- **Runtime层**:内部调度机制,协调Tool和Agent的交互流程
|
||||
|
||||
### 4.2 Tool层设计
|
||||
|
||||
Tool层提供装饰器形式的记忆操作工具,每个工具类通过 `@tool` 装饰器注册,明确定义初始化参数和调用参数。
|
||||
|
||||
#### 4.2.1 基类:BaseMemoryToolOp
|
||||
|
||||
**初始化参数:**
|
||||
- `enable_multiple` (bool): Enable multi-item operation mode. Default: `True`
|
||||
- `enable_thinking_params` (bool): Include thinking parameter in tool schema for model reasoning. Default: `False`
|
||||
- `memory_metadata_dir` (str): Directory path for storing memory metadata. Default: `"./memory_metadata"`
|
||||
|
||||
#### 4.2.2 Tool操作列表
|
||||
|
||||
以下是所有Tool操作的完整定义,包括继承关系、初始化参数和调用参数:
|
||||
|
||||
| Tool类 | 继承自 | 初始化参数(除基类外) | Tool Call参数(单项模式) | Tool Call参数(多项模式) |
|
||||
|----------------------------|------------------|------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
||||
| **AddMemoryOp** | BaseMemoryToolOp | `add_when_to_use` (bool, 默认: False)<br>`add_metadata` (bool, 默认: True) | `when_to_use` (str, 可选)<br>`memory_content` (str, 必需)<br>`metadata` (dict, 可选) | `memories` (array, 必需):<br> - `when_to_use` (str, 可选)<br> - `memory_content` (str, 必需)<br> - `metadata` (dict, 可选) |
|
||||
| **UpdateMemoryOp** | BaseMemoryToolOp | 无 | `memory_id` (str, 必需)<br>`memory_content` (str, 必需)<br>`metadata` (dict, 可选) | `memories` (array, 必需):<br> - `memory_id` (str, 必需)<br> - `memory_content` (str, 必需)<br> - `metadata` (dict, 可选) |
|
||||
| **DeleteMemoryOp** | BaseMemoryToolOp | 无 | `memory_id` (str, 必需) | `memory_ids` (array[str], 必需) |
|
||||
| **VectorRetrieveMemoryOp** | BaseMemoryToolOp | `enable_summary_memory` (bool, 默认: False)<br>`add_memory_type_target` (bool, 默认: False)<br>`top_k` (int, 默认: 20) | `query` (str, 必需)<br>`memory_type` (str, 可选, 枚举: [identity, personal, procedural])<br>`memory_target` (str, 可选) | `query_items` (array, 必需):<br> - `query` (str, 必需)<br> - `memory_type` (str, 可选)<br> - `memory_target` (str, 可选) |
|
||||
| **AddMetaMemoryOp** | BaseMemoryToolOp | 无 | `memory_type` (str, 必需, 枚举: [personal, procedural])<br>`memory_target` (str, 必需) | `meta_memories` (array, 必需):<br> - `memory_type` (str, 必需)<br> - `memory_target` (str, 必需) |
|
||||
| **ReadMetaMemoryOp** | BaseMemoryToolOp | `enable_tool_memory` (bool, 默认: False)<br>`enable_identity_memory` (bool, 默认: False) | 无(无输入schema) | N/A (enable_multiple=False) |
|
||||
| **AddHistoryMemoryOp** | BaseMemoryToolOp | 无 | `messages` (array[object], 必需) | N/A (enable_multiple=False) |
|
||||
| **ReadHistoryMemoryOp** | BaseMemoryToolOp | 无 | `memory_id` (str, 必需) | `memory_ids` (array[str], 必需) |
|
||||
| **AddSummaryMemoryOp** | AddMemoryOp | 无(继承自AddMemoryOp) | `summary_memory` (str, 必需)<br>`metadata` (dict, 可选) | N/A (enable_multiple=False) |
|
||||
| **ReadIdentityMemoryOp** | BaseMemoryToolOp | 无 | 无(无输入schema) | N/A (enable_multiple=False) |
|
||||
| **UpdateIdentityMemoryOp** | BaseMemoryToolOp | 无 | `identity_memory` (str, 必需) | N/A (enable_multiple=False) |
|
||||
| **ThinkToolOp** | BaseAsyncToolOp | `add_output_reflection` (bool, 默认: False) | `reflection` (str, 必需) | N/A |
|
||||
| **HandsOffOp** | BaseMemoryToolOp | 无 | `memory_type` (str, 必需, 枚举: [identity, personal, procedural, tool])<br>`memory_target` (str, 必需) | `memory_tasks` (array, 必需):<br> - `memory_type` (str, 必需)<br> - `memory_target` (str, 必需) |
|
||||
|
||||
### 4.3 Agent层设计
|
||||
|
||||
#### 4.3.1 基类:BaseMemoryAgentOp
|
||||
|
||||
Agent层构建在Tool层之上,封装复杂的记忆管理逻辑。每个Agent通过组合多个Tool实现特定的记忆管理任务。
|
||||
|
||||
#### 4.3.2 Agent操作列表
|
||||
|
||||
以下是所有Agent操作的完整定义,包括初始化参数、调用参数和可用工具:
|
||||
|
||||
| Agent类 | 继承自 | 初始化参数(基类外) | Tool Call参数 | 可用工具 |
|
||||
|--------------------------------|-------------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
|
||||
| **PersonalSummaryAgentV1Op** | BaseMemoryAgentOp | None | `workspace_id` (str, required)<br>`memory_target` (str, required)<br>`query` (str, optional)<br>`messages` (array, optional)<br>`ref_memory_id` (str, required) | add_memory<br>update_memory<br>delete_memory<br>vector_retrieve_memory |
|
||||
| **ProceduralSummaryAgentV1Op** | BaseMemoryAgentOp | None | `workspace_id` (str, required)<br>`memory_target` (str, required)<br>`query` (str, optional)<br>`messages` (array, optional)<br>`ref_memory_id` (str, required) | add_memory<br>update_memory<br>delete_memory<br>vector_retrieve_memory |
|
||||
| **ToolSummaryAgentV1Op** | BaseMemoryAgentOp | None | `workspace_id` (str, required)<br>`memory_target` (str, required)<br>`query` (str, optional)<br>`messages` (array, optional)<br>`ref_memory_id` (str, required) | add_memory<br>update_memory<br>vector_retrieve_memory |
|
||||
| **IdentitySummaryAgentV1Op** | BaseMemoryAgentOp | None | `workspace_id` (str, required)<br>`query` (str, optional)<br>`messages` (array, optional) | read_identity_memory<br>update_identity_memory |
|
||||
| **ReMeSummaryAgentV1Op** | BaseMemoryAgentOp | `enable_tool_memory` (bool, 默认: True)<br>`enable_identity_memory` (bool, 默认: True) | `workspace_id` (str, required)<br>`query` (str, optional)<br>`messages` (array, optional) | add_meta_memory<br>add_summary_memory<br>hands_off<br>(内部调用: add_history_memory, read_identity_memory, read_meta_memory) |
|
||||
| **ReMeRetrieveAgentV1Op** | BaseMemoryAgentOp | `enable_tool_memory` (bool, 默认: True) | `workspace_id` (str, required)<br>`query` (str, optional)<br>`messages` (array, optional) | vector_retrieve_memory<br>read_history_memory<br>(内部调用: read_meta_memory) |
|
||||
| **ReMyAgentV1Op** | BaseMemoryAgentOp | `enable_tool_memory` (bool, 默认: True)<br>`enable_identity_memory` (bool, 默认: True) | `workspace_id` (str, required)<br>`query` (str, optional)<br>`messages` (array, optional) | vector_retrieve_memory<br>read_history_memory<br>(内部调用: read_identity_memory, read_meta_memory) |
|
||||
|
||||
### 4.4 Runtime层设计(内部实现)
|
||||
|
||||
Runtime层负责协调Tool和Agent的调用流程,实现记忆的渐进式处理。
|
||||
|
||||
#### 4.4.1 渐进式总结流程(Summary)
|
||||
|
||||
总结流程采用分层处理策略,首先保存历史对话,读取元信息,然后由主Agent协调多个专用Agent完成分类总结。
|
||||
|
||||
**流程结构:**
|
||||
|
||||
```python
|
||||
# Step 1: Save conversation history
|
||||
AddHistoryMemoryOp()
|
||||
|
||||
# Step 2: Load meta information (memory types and targets)
|
||||
ReadMetaMemoryOp()
|
||||
|
||||
# Step 3: Progressive summarization with delegation
|
||||
ReMeSummaryAgentV1Op(tools=[
|
||||
# Add meta memory entries for new memory types/targets
|
||||
AddMetaMemoryOp(list(memory_type, memory_target)),
|
||||
|
||||
# Add general summary memory as fallback
|
||||
AddSummaryMemoryOp(summary_memory),
|
||||
|
||||
# Delegate to specialized summary agents
|
||||
HandsOffOp(list(memory_type, memory_target), agents=[
|
||||
PersonalSummaryAgentV1Op, # Summarize personal memories
|
||||
ProceduralSummaryAgentV1Op, # Summarize procedural memories
|
||||
ToolSummaryAgentV1Op, # Summarize tool-related memories
|
||||
IdentitySummaryAgentV1Op # Update identity memory
|
||||
]),
|
||||
])
|
||||
|
||||
# Specialized agents and their available tools
|
||||
PersonalSummaryAgentV1Op(tools=[AddMemoryOp, UpdateMemoryOp, DeleteMemoryOp, VectorRetrieveMemoryOp])
|
||||
ProceduralSummaryAgentV1Op(tools=[AddMemoryOp, UpdateMemoryOp, DeleteMemoryOp, VectorRetrieveMemoryOp])
|
||||
ToolSummaryAgentV1Op(tools=[AddMemoryOp, UpdateMemoryOp, VectorRetrieveMemoryOp])
|
||||
IdentitySummaryAgentV1Op(tools=[ReadIdentityMemoryOp, UpdateIdentityMemoryOp])
|
||||
```
|
||||
|
||||
#### 4.4.2 渐进式检索流程(Retrieve)
|
||||
|
||||
检索流程采用三层检索策略,类似于技能系统的加载机制,逐层加载和过滤记忆。
|
||||
|
||||
**流程结构:**
|
||||
|
||||
```python
|
||||
# Progressive retrieval with three layers
|
||||
ReMeRetrieveAgentV1Op(tools=[
|
||||
# Layer 0: Load meta memory (all available memory types and targets)
|
||||
ReadMetaMemoryOp(),
|
||||
# Output format example:
|
||||
# - personal(jinli): Information about Jinli's personal life and preferences
|
||||
# - personal(jiaji): Information about Jiaji's background and interests
|
||||
# - personal(jinli&jiaji): Shared memories between Jinli and Jiaji
|
||||
# - procedural(appworld): Procedural knowledge for AppWorld tasks
|
||||
# - procedural(bfcl-v3): Procedural knowledge for BFCL-v3 benchmark
|
||||
# - tool(tool_guidelines): Guidelines for tool usage
|
||||
# - identity(self): Agent's self-identity information
|
||||
|
||||
# Layer 1+2: Vector-based retrieval on structured memories
|
||||
VectorRetrieveMemoryOp(list(memory_type, memory_target, query)),
|
||||
|
||||
# Layer 3: Load full conversation history for specific memory
|
||||
ReadHistoryMemoryOp(ref_memory_id),
|
||||
])
|
||||
```
|
||||
|
||||
**与技能系统的类比:**
|
||||
|
||||
```python
|
||||
# Skill system hierarchy (for reference)
|
||||
load_meta_skills # Load skill metadata
|
||||
load_skills # Load skill implementations
|
||||
load_reference_skills # Load detailed skill documentation
|
||||
execute_shell # Execute actual commands
|
||||
```
|
||||
|
||||
## 五、扩展设计与实验方向
|
||||
|
||||
### 5.1 Summary Memory机制
|
||||
|
||||
Summary Memory作为通用维度的记忆类型,提供兜底的原始对话索引能力。
|
||||
|
||||
**工作流程示例:**
|
||||
|
||||
```txt
|
||||
Step 1: Progressive summarization across sessions
|
||||
session1: List[Message] -> session2: List[Message] -> session3: List[Message] -> ...
|
||||
summary ✓ (always) ✓ (always) ✓ (always)
|
||||
personal ✗ ✗ ✓ (when applicable)
|
||||
procedural ✗ ✓ (when applicable) ✗
|
||||
|
||||
Step 2: Retrieval with fallback strategy
|
||||
vector_retrieve_memory(query, memory_type="personal", memory_target="jinli")
|
||||
-> Search in memory_type: ["personal", "summary"] # Fallback to summary if personal not found
|
||||
```
|
||||
|
||||
**设计优势:**
|
||||
1. Provides a universal dimension for memory extraction across all memory types
|
||||
2. Ensures fallback indexing of original conversations when specific meta memory is not available
|
||||
3. Maintains conversation context even when specialized memory extraction fails
|
||||
|
||||
### 5.2 Thinking参数实验
|
||||
|
||||
探索不同的模型推理能力增强方案,受AgentScope和Claude启发。
|
||||
|
||||
#### 5.2.1 Thinking参数设计
|
||||
|
||||
```python
|
||||
async def record_to_memory(
|
||||
self,
|
||||
thinking: str,
|
||||
content: list[str],
|
||||
**kwargs: Any,
|
||||
) -> ToolResponse:
|
||||
"""Use this function to record important information that you may
|
||||
need later. The target content should be specific and concise, e.g.
|
||||
who, when, where, do what, why, how, etc.
|
||||
|
||||
Args:
|
||||
thinking (`str`):
|
||||
Your thinking and reasoning about what to record
|
||||
content (`list[str]`):
|
||||
The content to remember, which is a list of strings.
|
||||
"""
|
||||
```
|
||||
|
||||
#### 5.2.2 实验对比方案
|
||||
|
||||
| 方案类型 | 说明 | 灵感来源 |
|
||||
|-------------------------------|--------------------------------------------|----------------|
|
||||
| Thinking Model | Native reasoning-capable models (e.g., o1) | OpenAI |
|
||||
| Instruct Model | Standard instruction-following models | Baseline |
|
||||
| Instruct Model + Thinking Params | Add thinking parameter to tool schema | AgentScope |
|
||||
| Instruct Model + Thinking Tool | Dedicated thinking tool for explicit reasoning | Claude |
|
||||
|
||||
### 5.3 多项操作模式实验
|
||||
|
||||
对比单次调用和批量调用的性能与准确性差异。
|
||||
|
||||
**两种模式对比:**
|
||||
|
||||
| 模式 | Tool调用方式 | Model调用次数 | 优势 | 劣势 |
|
||||
|--------------|----------------------------|---------------|------------------------------|--------------------------|
|
||||
| 单项模式 | Single-item per call | Multiple | Fine-grained control | Higher latency, more tokens |
|
||||
| 多项模式 | Batch multiple items | Single | Lower latency, fewer tokens | Potential batch errors |
|
||||
|
||||
**实验目标:**
|
||||
- Evaluate accuracy: single vs. batch operations
|
||||
- Measure latency and token efficiency
|
||||
- Identify optimal use cases for each mode
|
||||
|
||||
### 5.4 多版本与扩展性
|
||||
|
||||
支持从基类继承实现自定义Agent,便于团队协作和功能迭代。
|
||||
|
||||
**扩展示例:**
|
||||
|
||||
```python
|
||||
# Version 2 implementations by different team members
|
||||
PersonalSummaryAgentV2Op / PersonalRetrieveAgentV2Op # @weikang
|
||||
ProceduralSummaryAgentV2Op / ProceduralRetrieveAgentV2Op # @zouyin
|
||||
|
||||
# Inherit from BaseMemoryAgentOp
|
||||
class PersonalSummaryAgentV2Op(BaseMemoryAgentOp):
|
||||
"""Enhanced personal memory summarization with improved algorithms"""
|
||||
pass
|
||||
```
|
||||
|
||||
### 5.5 文件系统集成(未来方向)
|
||||
|
||||
探索将文件操作能力集成到记忆系统中,支持基于文件的记忆管理。
|
||||
|
||||
**挑战与考虑:**
|
||||
|
||||
1. **操作适配性**:Current operations (retrieve/add/update/delete) need adaptation for file-based storage
|
||||
2. **工具选择**:Consider file operation tools: `grep`, `glob`, `ls`, `read_file`, `write_file`, `edit_file`
|
||||
3. **模型能力**:Base models have limited file operation capabilities; `qwen3-code` shows better performance
|
||||
|
||||
**潜在架构:**
|
||||
|
||||
```python
|
||||
# File-based memory operations
|
||||
FileMemoryOp(tools=[
|
||||
grep, # Search within files
|
||||
glob, # File pattern matching
|
||||
ls, # List directory contents
|
||||
read_file, # Read file contents
|
||||
write_file, # Write new memory files
|
||||
edit_file, # Update existing memory files
|
||||
])
|
||||
```
|
||||
|
||||
### 5.6 自我修改上下文
|
||||
|
||||
支持Agent动态修改自身的上下文状态,实现自适应记忆管理。
|
||||
|
||||
**实现方式:**
|
||||
|
||||
1. **Summary Agent 主动修改**:
|
||||
- `add_meta_memory` directly modifies agent context
|
||||
- Updates available memory types and targets during execution
|
||||
|
||||
2. **ReMy Agent 被动修改**:
|
||||
- Retrieves `identity_memory` at each interaction
|
||||
- Dynamically updates self-state based on retrieved identity
|
||||
- Enables adaptive behavior based on accumulated identity knowledge
|
||||
|
||||
## ReMe V2 开发路线图与实施计划
|
||||
|
||||
### 技术改造阶段
|
||||
1. **代码整合与兼容**:合并flowllm中reme必要的代码,保留现在server-client的依赖,兼容现在各个仓库的依赖代码
|
||||
2. **核心接口重构**:新的ReMe接口设计,支持summary,retrieve,context_offload, context_reload 4个核心接口
|
||||
3. **Agentic算法升级**:新的agentic算法方案开发
|
||||
|
||||
### 评估验证阶段
|
||||
4. **Benchmark测试**
|
||||
- halumem
|
||||
- locomo
|
||||
- longmemevel
|
||||
- personal-v2 ?
|
||||
- appworld/bfcl-v3
|
||||
|
||||
### 发布推广阶段
|
||||
5. **技术报告**撰写与发布
|
||||
6. **生态更新**:更新各个仓库的依赖代码
|
||||
- agentscope
|
||||
- agentscope-runtime
|
||||
- evotraders
|
||||
- alias(tool-memory)
|
||||
- agentscope-java
|
||||
- AgentEvolver
|
||||
- cookbook: reme procedural memory paper
|
||||
- tool-memory-upgrade(将要合并)
|
||||
|
||||
**里程碑目标**:春节前完成小版本发布
|
||||
|
||||
---
|
||||
|
||||
## ReMe V2 核心竞争优势
|
||||
|
||||
### 1. 渐进式 Agentic Memory 架构【核心创新】
|
||||
融合了多种记忆的渐进式agentic方案,实现从短期到长期记忆的智能化演进
|
||||
|
||||
### 2. 全生命周期记忆管理
|
||||
同时支持长期记忆(Long-term Memory)和短期记忆(Working Memory),完整覆盖Agent认知周期
|
||||
|
||||
### 3. 模型
|
||||
提供开源小模型
|
||||
|
||||
### 4. 开发者友好生态
|
||||
1. **简洁接口**:提供简洁的接口设计,全异步接口
|
||||
2. **即开即用**:提供CLI工具,开箱即用的体验
|
||||
3. **生态融合**:提供和AgentScope、LangChain无缝集成的方案
|
||||
4. **高度可扩展**:支持Agentic算法的二次开发与定制
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
1. 如何更好的注册class
|
||||
2. op的返回,使用return 还是 self.output
|
||||
3. 如何把agent的东西放出来
|
||||
|
|
@ -2,6 +2,4 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"""memory"""
|
||||
|
||||
from . import file_based
|
||||
from . import tools
|
||||
from . import vector_tools
|
||||
from . import vector_based
|
||||
|
||||
__all__ = [
|
||||
"file_based",
|
||||
"tools",
|
||||
"vector_tools",
|
||||
"vector_based",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,18 +9,21 @@ Components:
|
|||
- 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
|
||||
- ContextChecker: Checks context size and splits messages for compaction
|
||||
"""
|
||||
|
||||
from .as_msg_handler import AsMsgHandler
|
||||
from .reme_in_memory_memory import ReMeInMemoryMemory
|
||||
from .component.compactor import Compactor
|
||||
from .component.context_checker import ContextChecker
|
||||
from .component.summarizer import Summarizer
|
||||
from .component.tool_result_compactor import ToolResultCompactor
|
||||
from .reme_in_memory_memory import ReMeInMemoryMemory
|
||||
|
||||
__all__ = [
|
||||
"AsMsgHandler",
|
||||
"ReMeInMemoryMemory",
|
||||
"Summarizer",
|
||||
"Compactor",
|
||||
"ContextChecker",
|
||||
"ToolResultCompactor",
|
||||
]
|
||||
|
|
|
|||
97
reme/memory/file_based/component/context_checker.py
Normal file
97
reme/memory/file_based/component/context_checker.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
"""ContextChecker module for checking context size and splitting messages."""
|
||||
|
||||
from agentscope.message import Msg
|
||||
from agentscope.token import HuggingFaceTokenCounter
|
||||
|
||||
from ..as_msg_handler import AsMsgHandler
|
||||
from ....core.op import BaseOp
|
||||
from ....core.utils import get_std_logger
|
||||
|
||||
logger = get_std_logger()
|
||||
|
||||
|
||||
class ContextChecker(BaseOp):
|
||||
"""
|
||||
ContextChecker class for checking context size and splitting messages.
|
||||
|
||||
This class analyzes conversation messages to determine if the context
|
||||
exceeds the specified token threshold and splits messages into two groups:
|
||||
those that should be compacted and those to keep in context.
|
||||
|
||||
Attributes:
|
||||
memory_compact_threshold (int): Token count threshold for triggering compaction.
|
||||
memory_compact_reserve (int): Token count to reserve for recent messages.
|
||||
msg_handler (AsMsgHandler): Handler for message processing and token counting.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
memory_compact_threshold: int,
|
||||
memory_compact_reserve: int = 10000,
|
||||
token_counter: HuggingFaceTokenCounter | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Initialize the ContextChecker.
|
||||
|
||||
Args:
|
||||
memory_compact_threshold (int): Token count threshold for triggering
|
||||
compaction. Messages exceeding this threshold will be split.
|
||||
memory_compact_reserve (int): Token count to reserve for recent messages
|
||||
to keep in context. Defaults to 10000 tokens.
|
||||
token_counter (HuggingFaceTokenCounter | None): Token counter for
|
||||
measuring content length. If None, a default counter will be used.
|
||||
**kwargs: Additional keyword arguments passed to BaseOp.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.memory_compact_threshold: int = memory_compact_threshold
|
||||
self.memory_compact_reserve: int = memory_compact_reserve
|
||||
assert self.memory_compact_threshold > self.memory_compact_reserve
|
||||
|
||||
self.msg_handler = AsMsgHandler(token_counter=token_counter)
|
||||
|
||||
async def execute(self) -> tuple[list[Msg], list[Msg], bool]:
|
||||
"""
|
||||
Execute context check and split messages.
|
||||
|
||||
Retrieves messages from context and checks if they exceed the token
|
||||
threshold. If so, splits them into messages to compact and messages
|
||||
to keep.
|
||||
|
||||
Context Parameters:
|
||||
messages (list[Msg]): List of conversation messages to check.
|
||||
Retrieved from self.context.get("messages", []).
|
||||
|
||||
Returns:
|
||||
tuple[list[Msg], list[Msg], bool]: A tuple containing:
|
||||
- messages_to_compact (list[Msg]): Older messages that should
|
||||
be compacted/summarized.
|
||||
- messages_to_keep (list[Msg]): Recent messages to keep in context.
|
||||
- is_valid (bool): True if the split is valid (tool calls aligned),
|
||||
False if splitting would break conversation integrity.
|
||||
|
||||
Note:
|
||||
- Returns ([], messages, True) if no compaction is needed.
|
||||
- Ensures conversation pairs (user-assistant) are not split.
|
||||
- is_valid=False indicates tool_use and tool_result are misaligned.
|
||||
"""
|
||||
messages: list[Msg] = self.context.get("messages", [])
|
||||
|
||||
if not messages:
|
||||
logger.info("ContextChecker: No messages to check.")
|
||||
return [], [], True
|
||||
|
||||
messages_to_compact, messages_to_keep, is_valid = self.msg_handler.context_check(
|
||||
messages=messages,
|
||||
memory_compact_threshold=self.memory_compact_threshold,
|
||||
memory_compact_reserve=self.memory_compact_reserve,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"ContextChecker Result: "
|
||||
f"to_compact={len(messages_to_compact)}, "
|
||||
f"to_keep={len(messages_to_keep)}, "
|
||||
f"is_valid={is_valid}",
|
||||
)
|
||||
|
||||
return messages_to_compact, messages_to_keep, is_valid
|
||||
13
reme/memory/file_based/tools/__init__.py
Normal file
13
reme/memory/file_based/tools/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"""File-based memory tool implementations."""
|
||||
|
||||
from .file_io import FileIO
|
||||
from .memory_get import MemoryGet
|
||||
from .memory_search import MemorySearch
|
||||
from .shell import Shell
|
||||
|
||||
__all__ = [
|
||||
"FileIO",
|
||||
"MemoryGet",
|
||||
"MemorySearch",
|
||||
"Shell",
|
||||
]
|
||||
|
|
@ -7,6 +7,8 @@ from typing import Optional
|
|||
from agentscope.message import TextBlock
|
||||
from agentscope.tool import ToolResponse
|
||||
|
||||
from .utils import DEFAULT_MAX_BYTES, read_file_safe, truncate_output
|
||||
|
||||
|
||||
class FileIO:
|
||||
"""File I/O operations with a configurable working directory."""
|
||||
|
|
@ -78,57 +80,64 @@ class FileIO:
|
|||
)
|
||||
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
all_lines = f.readlines()
|
||||
content = read_file_safe(file_path)
|
||||
all_lines = content.split("\n")
|
||||
total = len(all_lines)
|
||||
|
||||
range_requested = start_line is not None or end_line is not None
|
||||
# Determine read range
|
||||
s = max(1, start_line if start_line is not None else 1)
|
||||
e = min(total, end_line if end_line is not None else total)
|
||||
|
||||
if range_requested:
|
||||
total = len(all_lines)
|
||||
s = max(1, start_line if start_line is not None else 1)
|
||||
e = min(total, end_line if end_line is not None else total)
|
||||
|
||||
if s > total:
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=(f"Error: start_line {s} exceeds file length " f"({total} lines) in {file_path}."),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
if s > e:
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=(f"Error: start_line ({s}) is greater than " f"end_line ({e}) in {file_path}."),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
selected = all_lines[s - 1 : e]
|
||||
content = "".join(selected)
|
||||
header = f"{file_path} (lines {s}-{e} of {total})\n"
|
||||
if s > total:
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=header + content,
|
||||
text=f"Error: start_line {s} exceeds file length ({total} lines).",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
if s > e:
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=f"Error: start_line ({s}) > end_line ({e}).",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Extract selected lines
|
||||
selected_content = "\n".join(all_lines[s - 1 : e])
|
||||
|
||||
# Apply smart truncation (keep head for file reading)
|
||||
truncated, was_truncated, output_lines, reason = truncate_output(selected_content, keep="head")
|
||||
|
||||
# Build response with truncation hints
|
||||
if was_truncated:
|
||||
end_display = s + output_lines - 1
|
||||
next_line = end_display + 1
|
||||
if reason == "lines":
|
||||
hint = f"\n\n[Lines {s}-{end_display} of {total}. Use start_line={next_line} to continue.]"
|
||||
else:
|
||||
hint = (
|
||||
f"\n\n[Lines {s}-{end_display} of {total} ({DEFAULT_MAX_BYTES // 1024}KB limit). "
|
||||
f"Use start_line={next_line} to continue.]"
|
||||
)
|
||||
text = truncated + hint
|
||||
elif e < total:
|
||||
remaining = total - e
|
||||
text = (
|
||||
f"{file_path} (lines {s}-{e} of {total})\n{truncated}\n\n[{remaining} more lines. "
|
||||
f"Use start_line={e + 1} to continue.]"
|
||||
)
|
||||
else:
|
||||
content = "".join(all_lines)
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=content,
|
||||
),
|
||||
],
|
||||
)
|
||||
text = truncated
|
||||
|
||||
return ToolResponse(
|
||||
content=[TextBlock(type="text", text=text)],
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return ToolResponse(
|
||||
229
reme/memory/file_based/tools/shell.py
Normal file
229
reme/memory/file_based/tools/shell.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa: E501
|
||||
# pylint: disable=line-too-long
|
||||
"""The shell command tool."""
|
||||
|
||||
import asyncio
|
||||
import locale
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from agentscope.message import TextBlock
|
||||
from agentscope.tool import ToolResponse
|
||||
|
||||
from .utils import truncate_shell_output
|
||||
|
||||
|
||||
def _execute_subprocess_sync(
|
||||
cmd: str,
|
||||
cwd: str,
|
||||
timeout: int,
|
||||
) -> tuple[int, str, str]:
|
||||
"""Execute subprocess synchronously in a thread.
|
||||
|
||||
This function runs in a separate thread to avoid Windows asyncio
|
||||
subprocess limitations.
|
||||
|
||||
Args:
|
||||
cmd (`str`):
|
||||
The shell command to execute.
|
||||
cwd (`str`):
|
||||
The working directory for the command execution.
|
||||
timeout (`int`):
|
||||
The maximum time (in seconds) allowed for the command to run.
|
||||
|
||||
Returns:
|
||||
`tuple[int, str, str]`:
|
||||
A tuple containing the return code, standard output, and
|
||||
standard error of the executed command. If timeout occurs, the
|
||||
return code will be -1 and stderr will contain timeout information.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=cwd,
|
||||
timeout=timeout,
|
||||
encoding=locale.getpreferredencoding(False) or "utf-8",
|
||||
errors="replace",
|
||||
check=True,
|
||||
)
|
||||
return (
|
||||
result.returncode,
|
||||
result.stdout.strip("\n"),
|
||||
result.stderr.strip("\n"),
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
return (
|
||||
-1,
|
||||
"",
|
||||
f"Command execution exceeded the timeout of {timeout} seconds.",
|
||||
)
|
||||
except Exception as e:
|
||||
return -1, "", str(e)
|
||||
|
||||
|
||||
class Shell:
|
||||
"""Shell command execution with a configurable working directory."""
|
||||
|
||||
def __init__(self, working_dir: str | Path):
|
||||
"""Initialize Shell with a working directory.
|
||||
|
||||
Args:
|
||||
working_dir (`str | Path`):
|
||||
The working directory for command execution.
|
||||
"""
|
||||
self.working_dir = Path(working_dir)
|
||||
|
||||
# pylint: disable=too-many-branches, too-many-statements
|
||||
async def execute_shell_command(
|
||||
self,
|
||||
command: str,
|
||||
timeout: int = 60,
|
||||
) -> ToolResponse:
|
||||
"""Execute given command and return the return code, standard output and
|
||||
error within <returncode></returncode>, <stdout></stdout> and
|
||||
<stderr></stderr> tags.
|
||||
|
||||
Args:
|
||||
command (`str`):
|
||||
The shell command to execute.
|
||||
timeout (`int`, defaults to `60`):
|
||||
The maximum time (in seconds) allowed for the command to run.
|
||||
Default is 60 seconds.
|
||||
|
||||
Returns:
|
||||
`ToolResponse`:
|
||||
The tool response containing the return code, standard output, and
|
||||
standard error of the executed command. If timeout occurs, the
|
||||
return code will be -1 and stderr will contain timeout information.
|
||||
"""
|
||||
|
||||
cmd = (command or "").strip()
|
||||
|
||||
# Set working directory
|
||||
working_dir = self.working_dir
|
||||
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
# Windows: use thread pool to avoid asyncio subprocess limitations
|
||||
returncode, stdout_str, stderr_str = await asyncio.to_thread(
|
||||
_execute_subprocess_sync,
|
||||
cmd,
|
||||
str(working_dir),
|
||||
timeout,
|
||||
)
|
||||
else:
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
bufsize=0,
|
||||
cwd=str(working_dir),
|
||||
)
|
||||
|
||||
try:
|
||||
# Apply timeout to communicate directly; wait()+communicate()
|
||||
# can hang if descendants keep stdout/stderr pipes open.
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
proc.communicate(),
|
||||
timeout=timeout,
|
||||
)
|
||||
encoding = locale.getpreferredencoding(False) or "utf-8"
|
||||
stdout_str = stdout.decode(encoding, errors="replace").strip(
|
||||
"\n",
|
||||
)
|
||||
stderr_str = stderr.decode(encoding, errors="replace").strip(
|
||||
"\n",
|
||||
)
|
||||
returncode = proc.returncode
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
# Handle timeout
|
||||
stderr_suffix = (
|
||||
f"⚠️ TimeoutError: The command execution exceeded "
|
||||
f"the timeout of {timeout} seconds. "
|
||||
f"Please consider increasing the timeout value if this command "
|
||||
f"requires more time to complete."
|
||||
)
|
||||
returncode = -1
|
||||
try:
|
||||
proc.terminate()
|
||||
# Wait a bit for graceful termination
|
||||
try:
|
||||
await asyncio.wait_for(proc.wait(), timeout=1)
|
||||
except asyncio.TimeoutError:
|
||||
# Force kill if graceful termination fails
|
||||
proc.kill()
|
||||
await proc.wait()
|
||||
|
||||
# Avoid hanging forever while draining pipes after timeout.
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
proc.communicate(),
|
||||
timeout=1,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
stdout, stderr = b"", b""
|
||||
encoding = locale.getpreferredencoding(False) or "utf-8"
|
||||
stdout_str = stdout.decode(
|
||||
encoding,
|
||||
errors="replace",
|
||||
).strip(
|
||||
"\n",
|
||||
)
|
||||
stderr_str = stderr.decode(
|
||||
encoding,
|
||||
errors="replace",
|
||||
).strip(
|
||||
"\n",
|
||||
)
|
||||
if stderr_str:
|
||||
stderr_str += f"\n{stderr_suffix}"
|
||||
else:
|
||||
stderr_str = stderr_suffix
|
||||
except ProcessLookupError:
|
||||
stdout_str = ""
|
||||
stderr_str = stderr_suffix
|
||||
|
||||
# Apply output truncation
|
||||
stdout_str = truncate_shell_output(stdout_str)
|
||||
stderr_str = truncate_shell_output(stderr_str)
|
||||
|
||||
# Format the response in a human-friendly way
|
||||
if returncode == 0:
|
||||
# Success case: just show the output
|
||||
if stdout_str:
|
||||
response_text = stdout_str
|
||||
else:
|
||||
response_text = "Command executed successfully (no output)."
|
||||
else:
|
||||
# Error case: show detailed information
|
||||
response_parts = [f"Command failed with exit code {returncode}."]
|
||||
if stdout_str:
|
||||
response_parts.append(f"\n[stdout]\n{stdout_str}")
|
||||
if stderr_str:
|
||||
response_parts.append(f"\n[stderr]\n{stderr_str}")
|
||||
response_text = "".join(response_parts)
|
||||
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=response_text,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return ToolResponse(
|
||||
content=[
|
||||
TextBlock(
|
||||
type="text",
|
||||
text=f"Error: Shell command execution failed due to \n{e}",
|
||||
),
|
||||
],
|
||||
)
|
||||
112
reme/memory/file_based/tools/utils.py
Normal file
112
reme/memory/file_based/tools/utils.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"""Shared utilities for file and shell tools."""
|
||||
|
||||
# Default truncation limits
|
||||
DEFAULT_MAX_LINES = 1000
|
||||
DEFAULT_MAX_BYTES = 30 * 1024 # 30KB
|
||||
|
||||
|
||||
def truncate_output(
|
||||
text: str,
|
||||
max_lines: int = DEFAULT_MAX_LINES,
|
||||
max_bytes: int = DEFAULT_MAX_BYTES,
|
||||
keep: str = "head",
|
||||
) -> tuple[str, bool, int, str]:
|
||||
"""Smart truncation for large content.
|
||||
|
||||
Args:
|
||||
text: Text content to truncate.
|
||||
max_lines: Maximum number of lines.
|
||||
max_bytes: Maximum size in bytes.
|
||||
keep: Which part to keep - "head" (first lines) or "tail" (last lines).
|
||||
|
||||
Returns:
|
||||
(truncated_content, was_truncated, output_line_count, truncate_reason)
|
||||
"""
|
||||
if not text:
|
||||
return text, False, 0, ""
|
||||
|
||||
lines = text.split("\n")
|
||||
total_lines = len(lines)
|
||||
|
||||
# No truncation needed
|
||||
if total_lines <= max_lines and len(text.encode("utf-8")) <= max_bytes:
|
||||
return text, False, total_lines, ""
|
||||
|
||||
# Apply line limit
|
||||
if total_lines > max_lines:
|
||||
if keep == "tail":
|
||||
lines = lines[-max_lines:]
|
||||
else:
|
||||
lines = lines[:max_lines]
|
||||
reason = "lines"
|
||||
else:
|
||||
reason = ""
|
||||
|
||||
# Apply byte limit
|
||||
if len("\n".join(lines).encode("utf-8")) > max_bytes:
|
||||
if keep == "tail":
|
||||
while lines and len("\n".join(lines).encode("utf-8")) > max_bytes:
|
||||
lines.pop(0)
|
||||
else:
|
||||
truncated = []
|
||||
current_bytes = 0
|
||||
for line in lines:
|
||||
line_bytes = len(line.encode("utf-8")) + 1
|
||||
if current_bytes + line_bytes > max_bytes:
|
||||
break
|
||||
truncated.append(line)
|
||||
current_bytes += line_bytes
|
||||
lines = truncated
|
||||
reason = "bytes"
|
||||
|
||||
return "\n".join(lines), True, len(lines), reason
|
||||
|
||||
|
||||
def truncate_shell_output(text: str) -> str:
|
||||
"""Truncate shell output to last N lines or M bytes, with truncation notice.
|
||||
|
||||
Args:
|
||||
text: The output text to truncate.
|
||||
|
||||
Returns:
|
||||
Truncated text with notice if truncated.
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
try:
|
||||
total_lines = len(text.split("\n"))
|
||||
truncated, was_truncated, output_lines, reason = truncate_output(text, keep="tail")
|
||||
|
||||
if not was_truncated:
|
||||
return text
|
||||
|
||||
start_line = total_lines - output_lines + 1
|
||||
if reason == "lines":
|
||||
notice = f"\n\n[Output truncated: showing lines {start_line}-{total_lines} of {total_lines} total]"
|
||||
else:
|
||||
notice = (
|
||||
f"\n\n[Output truncated: showing lines {start_line}-{total_lines} of {total_lines} "
|
||||
f"({DEFAULT_MAX_BYTES // 1024}KB limit)]"
|
||||
)
|
||||
|
||||
return truncated + notice
|
||||
except Exception:
|
||||
return text
|
||||
|
||||
|
||||
def read_file_safe(file_path: str) -> str:
|
||||
"""Read file with Unicode error handling.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file.
|
||||
|
||||
Returns:
|
||||
File content as string.
|
||||
"""
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except UnicodeDecodeError:
|
||||
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
return f.read()
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
"""File-based memory tool implementations."""
|
||||
|
||||
from .file_io import FileIO
|
||||
|
||||
__all__ = [
|
||||
"FileIO",
|
||||
]
|
||||
|
|
@ -3,8 +3,6 @@
|
|||
from .base_memory_tool import BaseMemoryTool
|
||||
|
||||
# chunk tools
|
||||
from .chunk.memory_get import MemoryGet
|
||||
from .chunk.memory_search import MemorySearch
|
||||
from .delegate_task import DelegateTask
|
||||
|
||||
# history tools
|
||||
|
|
@ -36,9 +34,6 @@ __all__ = [
|
|||
# base
|
||||
"BaseMemoryTool",
|
||||
"DelegateTask",
|
||||
# chunk tools
|
||||
"MemoryGet",
|
||||
"MemorySearch",
|
||||
# history tools
|
||||
"AddHistory",
|
||||
"ReadHistory",
|
||||
|
|
@ -7,7 +7,7 @@ from .config import ReMeConfigParser
|
|||
from .core import Application
|
||||
from .core.enumeration import MemoryType, Role
|
||||
from .core.schema import Message, MemoryNode
|
||||
from .memory.tools import (
|
||||
from .memory.vector_tools import (
|
||||
AddDraftAndRetrieveSimilarMemory,
|
||||
AddHistory,
|
||||
AddMemory,
|
||||
|
|
@ -17,8 +17,8 @@ from .memory.tools import (
|
|||
RetrieveMemory,
|
||||
UpdateProfilesV1,
|
||||
)
|
||||
from .memory.tools.profiles.profile_handler import ProfileHandler
|
||||
from .memory.tools.record.memory_handler import MemoryHandler
|
||||
from .memory.vector_tools.profiles.profile_handler import ProfileHandler
|
||||
from .memory.vector_tools.record.memory_handler import MemoryHandler
|
||||
from .memory.vector_based import (
|
||||
BaseMemoryAgent,
|
||||
PersonalRetriever,
|
||||
|
|
@ -185,7 +185,7 @@ class ReMe(Application):
|
|||
format_messages.append(message)
|
||||
|
||||
if version == "default":
|
||||
personal_summarizer_tools = [
|
||||
personal_summarizer_tools: list = [
|
||||
AddDraftAndRetrieveSimilarMemory(
|
||||
enable_thinking_params=enable_thinking_params,
|
||||
enable_memory_target=False,
|
||||
|
|
|
|||
|
|
@ -26,15 +26,45 @@ from agentscope.tool import Toolkit, ToolResponse
|
|||
from .config import ReMeConfigParser
|
||||
from .core import Application
|
||||
from .core.utils import get_hf_token_counter, get_std_logger
|
||||
from .memory.file_based import Compactor, Summarizer, ToolResultCompactor, ReMeInMemoryMemory, AsMsgHandler
|
||||
from .memory.tools import MemorySearch
|
||||
from .memory.tools.file import FileIO
|
||||
from .memory.file_based import (
|
||||
Compactor,
|
||||
ContextChecker,
|
||||
Summarizer,
|
||||
ToolResultCompactor,
|
||||
ReMeInMemoryMemory,
|
||||
AsMsgHandler,
|
||||
)
|
||||
from .memory.file_based import MemorySearch
|
||||
from .memory.file_based.tools import FileIO
|
||||
|
||||
logger = get_std_logger()
|
||||
|
||||
|
||||
class ReMeLight(Application):
|
||||
"""ReMe Light Application Class"""
|
||||
"""
|
||||
ReMe Light Application Class.
|
||||
|
||||
A lightweight memory-enabled application that provides semantic search,
|
||||
memory compaction, summarization, and tool result management capabilities.
|
||||
Built on top of the core Application framework with integrated vector store
|
||||
and file-based memory management.
|
||||
|
||||
This class is designed for applications requiring:
|
||||
- Long conversation memory management with automatic compaction
|
||||
- Semantic search over stored memories using hybrid vector/text search
|
||||
- Background summarization of conversation history
|
||||
- Automatic cleanup of expired tool results
|
||||
|
||||
Attributes:
|
||||
working_path (Path): Absolute path to the working directory.
|
||||
memory_path (Path): Path to the memory storage directory.
|
||||
tool_result_path (Path): Path to the tool result storage directory.
|
||||
vector_weight (float): Weight for vector search in hybrid search (0-1).
|
||||
candidate_multiplier (float): Multiplier for candidate retrieval count.
|
||||
tool_result_threshold (int): Character threshold for tool result compaction.
|
||||
retention_days (int): Number of days to retain tool result files.
|
||||
summary_tasks (list[asyncio.Task]): List of active background summary tasks.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -51,6 +81,47 @@ class ReMeLight(Application):
|
|||
tool_result_threshold: int = 1000,
|
||||
retention_days: int = 7,
|
||||
):
|
||||
"""
|
||||
Initialize the ReMeLight application.
|
||||
|
||||
Sets up the working directory structure, configures API connections,
|
||||
and initializes memory management components.
|
||||
|
||||
Args:
|
||||
working_dir (str): Base directory for all application data storage.
|
||||
Defaults to ".reme". Will be created if it doesn't exist.
|
||||
llm_api_key (str | None): API key for the language model service.
|
||||
If None, will attempt to use environment variables.
|
||||
llm_base_url (str | None): Base URL for the language model API endpoint.
|
||||
If None, will use the default endpoint.
|
||||
embedding_api_key (str | None): API key for the embedding model service.
|
||||
If None, will attempt to use environment variables.
|
||||
embedding_base_url (str | None): Base URL for the embedding API endpoint.
|
||||
If None, will use the default endpoint.
|
||||
default_as_llm_config (dict | None): Default configuration dictionary
|
||||
for AgentScope language model. Overrides default settings.
|
||||
default_embedding_model_config (dict | None): Default configuration
|
||||
dictionary for the embedding model.
|
||||
default_file_store_config (dict | None): Default configuration
|
||||
dictionary for the file storage backend.
|
||||
vector_weight (float): Weight assigned to vector similarity search
|
||||
in hybrid search operations. Range [0.0, 1.0], default 0.7.
|
||||
Higher values prioritize semantic similarity over keyword matching.
|
||||
candidate_multiplier (float): Multiplier applied to max_results when
|
||||
retrieving candidates for re-ranking. Default 3.0 means 3x more
|
||||
candidates are retrieved than the final result count.
|
||||
tool_result_threshold (int): Character count threshold for tool result
|
||||
compaction. Results exceeding this length will be truncated and
|
||||
saved to files. Default 1000 characters.
|
||||
retention_days (int): Number of days to retain tool result files
|
||||
before automatic cleanup. Default 7 days.
|
||||
|
||||
Note:
|
||||
The following directory structure will be created:
|
||||
- {working_dir}/ - Root working directory
|
||||
- {working_dir}/memory/ - Memory storage files
|
||||
- {working_dir}/tool_result/ - Compacted tool result files
|
||||
"""
|
||||
# Initialize working directory structure
|
||||
self.working_path = Path(working_dir).absolute()
|
||||
self.working_path.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -129,18 +200,64 @@ class ReMeLight(Application):
|
|||
return 0
|
||||
|
||||
async def start(self):
|
||||
"""Start the application lifecycle."""
|
||||
"""
|
||||
Start the application lifecycle.
|
||||
|
||||
Initializes all application components by calling the parent class start
|
||||
method, then performs initial cleanup of expired tool result files.
|
||||
|
||||
Returns:
|
||||
The result from the parent Application.start() method.
|
||||
|
||||
Note:
|
||||
This method should be called before using any other application
|
||||
functionality. It ensures all services are properly initialized.
|
||||
"""
|
||||
result = await super().start()
|
||||
# Perform initial cleanup of any expired tool result files
|
||||
self._cleanup_tool_results()
|
||||
return result
|
||||
|
||||
async def close(self) -> bool:
|
||||
"""Close the application and perform cleanup."""
|
||||
"""
|
||||
Close the application and perform cleanup.
|
||||
|
||||
Performs final cleanup of expired tool result files and then shuts down
|
||||
all application components by calling the parent class close method.
|
||||
|
||||
Returns:
|
||||
bool: True if the application was closed successfully, False otherwise.
|
||||
|
||||
Note:
|
||||
This method should be called when the application is no longer needed
|
||||
to ensure proper resource cleanup and data persistence.
|
||||
"""
|
||||
# Final cleanup of expired tool result files before shutdown
|
||||
self._cleanup_tool_results()
|
||||
return await super().close()
|
||||
|
||||
async def compact_tool_result(self, messages: list[Msg]) -> list[Msg]:
|
||||
"""Compact tool results by truncating large outputs and saving full content to files."""
|
||||
"""
|
||||
Compact tool results by truncating large outputs and saving full content to files.
|
||||
|
||||
This method processes a list of messages containing tool results and compacts
|
||||
any that exceed the configured threshold. Large tool outputs are truncated
|
||||
in the message while the full content is saved to separate files for later
|
||||
retrieval if needed.
|
||||
|
||||
Args:
|
||||
messages (list[Msg]): List of messages potentially containing tool results
|
||||
that may need compaction.
|
||||
|
||||
Returns:
|
||||
list[Msg]: The processed list of messages with large tool results compacted.
|
||||
If an error occurs, returns the original unmodified messages.
|
||||
|
||||
Note:
|
||||
- Tool results shorter than tool_result_threshold are left unchanged
|
||||
- Full content of truncated results is saved to tool_result_path
|
||||
- Expired files are automatically cleaned up during this operation
|
||||
"""
|
||||
try:
|
||||
# Create compactor with instance configuration
|
||||
compactor = ToolResultCompactor(
|
||||
|
|
@ -162,6 +279,61 @@ class ReMeLight(Application):
|
|||
logger.exception(f"Error compacting tool results: {e}")
|
||||
return messages
|
||||
|
||||
async def check_context(
|
||||
self,
|
||||
messages: list[Msg],
|
||||
memory_compact_threshold: int,
|
||||
memory_compact_reserve: int = 10000,
|
||||
token_counter: HuggingFaceTokenCounter | None = None,
|
||||
) -> tuple[list[Msg], list[Msg], bool]:
|
||||
"""
|
||||
Check context size and determine if compaction is needed.
|
||||
|
||||
Analyzes the provided messages to determine if they exceed the configured
|
||||
token threshold and splits them into two groups: messages that should be
|
||||
compacted and messages to keep in context.
|
||||
|
||||
Args:
|
||||
messages (list[Msg]): List of messages to check for context overflow.
|
||||
memory_compact_threshold (int): Token count threshold for triggering
|
||||
compaction. Messages exceeding this threshold will be split.
|
||||
memory_compact_reserve (int): Token count to reserve for recent messages
|
||||
to keep in context. Defaults to 10000 tokens.
|
||||
token_counter (HuggingFaceTokenCounter | None): Token counter for
|
||||
measuring message length. If None, uses default HuggingFace counter.
|
||||
|
||||
Returns:
|
||||
tuple[list[Msg], list[Msg], bool]: A tuple containing:
|
||||
- messages_to_compact (list[Msg]): Older messages that should
|
||||
be compacted/summarized.
|
||||
- messages_to_keep (list[Msg]): Recent messages to keep in context.
|
||||
- is_valid (bool): True if the split is valid (tool calls aligned),
|
||||
False if splitting would break conversation integrity.
|
||||
|
||||
Note:
|
||||
- Returns ([], messages, True) if no compaction is needed.
|
||||
- Ensures conversation pairs (user-assistant) are not split.
|
||||
- is_valid=False indicates tool_use and tool_result are misaligned.
|
||||
"""
|
||||
try:
|
||||
if token_counter is None:
|
||||
token_counter = get_hf_token_counter()
|
||||
|
||||
checker = ContextChecker(
|
||||
memory_compact_threshold=memory_compact_threshold,
|
||||
memory_compact_reserve=memory_compact_reserve,
|
||||
token_counter=token_counter,
|
||||
)
|
||||
|
||||
return await checker.call(
|
||||
messages=messages,
|
||||
service_context=self.service_context,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error checking context: {e}")
|
||||
return [], messages, False
|
||||
|
||||
async def compact_memory(
|
||||
self,
|
||||
messages: list[Msg],
|
||||
|
|
@ -173,7 +345,34 @@ class ReMeLight(Application):
|
|||
compact_ratio: float = 0.7,
|
||||
previous_summary: str = "",
|
||||
) -> str:
|
||||
"""Compact a list of messages into a condensed summary."""
|
||||
"""
|
||||
Compact a list of messages into a condensed summary.
|
||||
|
||||
Uses the configured language model to generate a concise summary of the
|
||||
provided messages. This is useful for reducing context window usage while
|
||||
preserving important information from the conversation history.
|
||||
|
||||
Args:
|
||||
messages (list[Msg]): List of messages to be compacted into a summary.
|
||||
as_llm (str | ChatModelBase): Language model identifier or instance
|
||||
to use for summarization. Defaults to "default".
|
||||
as_llm_formatter (str | FormatterBase): Formatter for the language model.
|
||||
Defaults to "default".
|
||||
token_counter (HuggingFaceTokenCounter | None): Token counter for
|
||||
measuring message length. If None, uses default HuggingFace counter.
|
||||
language (str): Language for the summary output. "zh" for Chinese,
|
||||
any other value for English. Defaults to "zh".
|
||||
max_input_length (float): Maximum input length in tokens for the model.
|
||||
Defaults to 128K tokens.
|
||||
compact_ratio (float): Ratio used to calculate compaction threshold.
|
||||
Defaults to 0.7.
|
||||
previous_summary (str): Previous summary to incorporate into the new
|
||||
summary for continuity. Defaults to empty string.
|
||||
|
||||
Returns:
|
||||
str: The condensed summary of the messages, or an empty string if
|
||||
an error occurred during compaction.
|
||||
"""
|
||||
try:
|
||||
if token_counter is None:
|
||||
token_counter = get_hf_token_counter()
|
||||
|
|
@ -208,7 +407,37 @@ class ReMeLight(Application):
|
|||
max_input_length: float = 128 * 1024,
|
||||
compact_ratio: float = 0.7,
|
||||
) -> str:
|
||||
"""Generate a comprehensive summary of the given messages."""
|
||||
"""
|
||||
Generate a comprehensive summary of the given messages.
|
||||
|
||||
Creates a detailed summary of the conversation history and persists it
|
||||
to the memory directory as structured files. Unlike compact_memory, this
|
||||
method produces more detailed summaries suitable for long-term storage.
|
||||
|
||||
Args:
|
||||
messages (list[Msg]): List of messages to summarize.
|
||||
as_llm (str | ChatModelBase): Language model identifier or instance
|
||||
for summarization. Defaults to "default".
|
||||
as_llm_formatter (str | FormatterBase): Formatter for the language model.
|
||||
Defaults to "default".
|
||||
token_counter (HuggingFaceTokenCounter | None): Token counter for
|
||||
measuring message length. If None, uses default HuggingFace counter.
|
||||
toolkit (Toolkit | None): Toolkit with file operations for persisting
|
||||
summaries. If None, creates a default toolkit with read/write/edit.
|
||||
language (str): Language for the summary output. "zh" for Chinese,
|
||||
any other value for English. Defaults to "zh".
|
||||
max_input_length (float): Maximum input length in tokens.
|
||||
Defaults to 128K tokens.
|
||||
compact_ratio (float): Ratio used to calculate compaction threshold.
|
||||
Defaults to 0.7.
|
||||
|
||||
Returns:
|
||||
str: The generated summary text, or an empty string if an error occurred.
|
||||
|
||||
Note:
|
||||
This method may write summary files to the memory_path directory
|
||||
using the provided or default toolkit.
|
||||
"""
|
||||
try:
|
||||
if token_counter is None:
|
||||
token_counter = get_hf_token_counter()
|
||||
|
|
@ -238,7 +467,30 @@ class ReMeLight(Application):
|
|||
return ""
|
||||
|
||||
def add_async_summary_task(self, messages: list[Msg], **kwargs):
|
||||
"""Add an asynchronous summary task for the given messages."""
|
||||
"""
|
||||
Add an asynchronous summary task for the given messages.
|
||||
|
||||
Creates a background task to generate a summary of the provided messages
|
||||
without blocking the main execution flow. Completed tasks are automatically
|
||||
cleaned up from the task list.
|
||||
|
||||
Args:
|
||||
messages (list[Msg]): List of messages to be summarized asynchronously.
|
||||
**kwargs: Additional keyword arguments passed to summary_memory().
|
||||
Supported arguments include:
|
||||
- as_llm: Language model identifier or instance
|
||||
- as_llm_formatter: Formatter for the language model
|
||||
- token_counter: Token counter instance
|
||||
- toolkit: Toolkit for file operations
|
||||
- language: Output language ("zh" or other)
|
||||
- max_input_length: Maximum input token length
|
||||
- compact_ratio: Compaction threshold ratio
|
||||
|
||||
Note:
|
||||
- Completed/failed/cancelled tasks are cleaned up before adding new ones
|
||||
- Task results and errors are logged automatically
|
||||
- Use await_summary_tasks() to wait for all pending tasks to complete
|
||||
"""
|
||||
remaining_tasks = []
|
||||
for task in self.summary_tasks:
|
||||
if task.done():
|
||||
|
|
@ -274,7 +526,49 @@ class ReMeLight(Application):
|
|||
enable_tool_result_compact: bool = True,
|
||||
tool_result_compact_keep_n: int = 3,
|
||||
) -> tuple[list[Msg], str]:
|
||||
"""Hook called before reasoning."""
|
||||
"""
|
||||
Hook called before reasoning to manage memory and context.
|
||||
|
||||
This method is designed to be called before each reasoning step to ensure
|
||||
the conversation context fits within model limits. It performs tool result
|
||||
compaction, checks context size, and triggers memory compaction if needed.
|
||||
|
||||
Args:
|
||||
messages (list[Msg]): Current conversation messages to be processed.
|
||||
system_prompt (str): System prompt that will be included in the context.
|
||||
Used to calculate available space. Defaults to empty string.
|
||||
compressed_summary (str): Existing compressed summary from previous
|
||||
compactions. Defaults to empty string.
|
||||
as_llm (str | ChatModelBase): Language model for compaction operations.
|
||||
Defaults to "default".
|
||||
as_llm_formatter (str | FormatterBase): Formatter for the language model.
|
||||
Defaults to "default".
|
||||
token_counter (HuggingFaceTokenCounter | None): Token counter for
|
||||
measuring content length. If None, uses default counter.
|
||||
toolkit (Toolkit | None): Toolkit for file operations in summarization.
|
||||
Defaults to None.
|
||||
language (str): Language for generated summaries. Defaults to "zh".
|
||||
max_input_length (float): Maximum context window size in tokens.
|
||||
Defaults to 128K tokens.
|
||||
compact_ratio (float): Ratio for calculating compaction threshold.
|
||||
Defaults to 0.7.
|
||||
memory_compact_reserve (int): Token count to reserve for new responses.
|
||||
Defaults to 10000 tokens.
|
||||
enable_tool_result_compact (bool): Whether to compact tool results.
|
||||
Defaults to True.
|
||||
tool_result_compact_keep_n (int): Number of recent messages to exclude
|
||||
from tool result compaction. Defaults to 3.
|
||||
|
||||
Returns:
|
||||
tuple[list[Msg], str]: A tuple containing:
|
||||
- list[Msg]: Messages to keep in context (may be reduced)
|
||||
- str: Updated compressed summary incorporating compacted messages
|
||||
|
||||
Note:
|
||||
- Automatically triggers background summarization for compacted messages
|
||||
- Tool results in recent messages (keep_n) are not compacted
|
||||
- Returns original messages unchanged if no compaction is needed
|
||||
"""
|
||||
if token_counter is None:
|
||||
token_counter = get_hf_token_counter()
|
||||
|
||||
|
|
@ -328,7 +622,25 @@ class ReMeLight(Application):
|
|||
return messages_to_keep, compressed_summary
|
||||
|
||||
async def await_summary_tasks(self) -> str:
|
||||
"""Wait for all background summary tasks to complete and collect results."""
|
||||
"""
|
||||
Wait for all background summary tasks to complete and collect results.
|
||||
|
||||
Blocks until all pending summary tasks in the task list have completed,
|
||||
cancelled, or failed. Collects status information from each task and
|
||||
clears the task list after processing.
|
||||
|
||||
Returns:
|
||||
str: A concatenated string of status messages for all tasks, including:
|
||||
- Completion confirmations with results
|
||||
- Cancellation notices
|
||||
- Error messages for failed tasks
|
||||
|
||||
Note:
|
||||
- This method will block if any tasks are still running
|
||||
- All tasks are removed from summary_tasks after this call
|
||||
- Task exceptions are logged but do not raise to the caller
|
||||
- Use this before application shutdown to ensure all summaries complete
|
||||
"""
|
||||
result = ""
|
||||
for task in self.summary_tasks:
|
||||
if task.done():
|
||||
|
|
@ -369,26 +681,19 @@ class ReMeLight(Application):
|
|||
|
||||
async def memory_search(self, query: str, max_results: int = 5, min_score: float = 0.1) -> ToolResponse:
|
||||
"""
|
||||
Perform semantic memory search using vector and full-text search.
|
||||
|
||||
This method searches the memory store for content relevant to the given query
|
||||
using a hybrid approach combining vector similarity search and full-text search.
|
||||
Results are ranked by relevance and filtered by the minimum score threshold.
|
||||
Mandatory recall step: semantically search MEMORY.md + memory/*.md
|
||||
(and optional session transcripts) before answering questions about
|
||||
prior work, decisions, dates, people, preferences, or todos; returns
|
||||
top snippets with path + lines.
|
||||
|
||||
Args:
|
||||
query (str): The search query string. Must not be empty.
|
||||
max_results (int): Maximum number of results to return (1-100, default: 5)
|
||||
min_score (float): Minimum relevance score threshold (0.001-0.999, default: 0.1)
|
||||
query (str): The semantic search query to find relevant memory snippets.
|
||||
max_results (int): Maximum number of search results to return (optional), default 5.
|
||||
min_score (float): Minimum similarity score threshold for results (optional), default 0.1.
|
||||
|
||||
Returns:
|
||||
ToolResponse: A ToolResponse containing the search results as text,
|
||||
or an error message if the query is empty
|
||||
|
||||
Note:
|
||||
- Vector search weight is controlled by self.vector_weight
|
||||
- Candidate retrieval uses self.candidate_multiplier for broader search
|
||||
- Parameters are validated and clamped to valid ranges
|
||||
- Requires vector search to be enabled via embedding configuration
|
||||
or an error message if the query is empty.
|
||||
"""
|
||||
# Validate query parameter
|
||||
if not query:
|
||||
|
|
@ -452,7 +757,25 @@ class ReMeLight(Application):
|
|||
|
||||
@staticmethod
|
||||
def get_in_memory_memory(token_counter: HuggingFaceTokenCounter | None = None):
|
||||
"""Create and return an in-memory memory instance."""
|
||||
"""
|
||||
Create and return an in-memory memory instance.
|
||||
|
||||
Factory method to create a ReMeInMemoryMemory instance configured with
|
||||
the specified token counter. This memory instance stores data in RAM
|
||||
without persistence, suitable for temporary or session-based storage.
|
||||
|
||||
Args:
|
||||
token_counter (HuggingFaceTokenCounter | None): Token counter for
|
||||
measuring content length in the memory. If None, creates a
|
||||
default HuggingFace token counter.
|
||||
|
||||
Returns:
|
||||
ReMeInMemoryMemory: A new in-memory memory instance ready for use.
|
||||
|
||||
Example:
|
||||
>>> memory = ReMeLight.get_in_memory_memory()
|
||||
>>> # Use memory for temporary storage during a session
|
||||
"""
|
||||
if token_counter is None:
|
||||
token_counter = get_hf_token_counter()
|
||||
|
||||
|
|
|
|||
|
|
@ -96,10 +96,7 @@ class AppworldReactAgent:
|
|||
|
||||
def prompt_messages(self, run_id, task_index, previous_memories: None, world: AppWorld):
|
||||
app_descriptions = json.dumps(
|
||||
[
|
||||
{"name": k, "description": v}
|
||||
for (k, v) in world.task.app_descriptions.items()
|
||||
],
|
||||
[{"name": k, "description": v} for (k, v) in world.task.app_descriptions.items()],
|
||||
indent=1,
|
||||
)
|
||||
dictionary = {"supervisor": world.task.supervisor, "app_descriptions": app_descriptions}
|
||||
|
|
@ -112,7 +109,12 @@ class AppworldReactAgent:
|
|||
self.retrieved_memory_list[run_id][task_index] = response["metadata"]["memory_list"]
|
||||
task_memory = response["answer"]
|
||||
logger.info(f"loaded task_memory: {task_memory}")
|
||||
query = "Task:\n" + query + "\n\nSome Related Experience to help you to complete the task:\n" + re.sub(r'(?i)\bMemory\s*(\d+)\s*[:]', r'Experience \1:', task_memory)
|
||||
query = (
|
||||
"Task:\n"
|
||||
+ query
|
||||
+ "\n\nSome Related Experience to help you to complete the task:\n"
|
||||
+ re.sub(r"(?i)\bMemory\s*(\d+)\s*[:]", r"Experience \1:", task_memory)
|
||||
)
|
||||
else:
|
||||
formatted_memories = []
|
||||
for i, memory in enumerate(previous_memories, 1):
|
||||
|
|
@ -120,14 +122,18 @@ class AppworldReactAgent:
|
|||
memory_content = memory["content"]
|
||||
memory_text = f"Experience {i}:\n When to use: {condition}\n Content: {memory_content}\n"
|
||||
formatted_memories.append(memory_text)
|
||||
query = "Task:\n" + query + "\n\nSome Related Experience to help you to complete the task:\n" + "\n".join(formatted_memories)
|
||||
query = (
|
||||
"Task:\n"
|
||||
+ query
|
||||
+ "\n\nSome Related Experience to help you to complete the task:\n"
|
||||
+ "\n".join(formatted_memories)
|
||||
)
|
||||
messages = [
|
||||
{"role": "system", "content": sys_prompt},
|
||||
{"role": "user", "content": query}
|
||||
{"role": "user", "content": query},
|
||||
]
|
||||
self.history[run_id][task_index] = messages
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_reward(world) -> float:
|
||||
tracker = world.evaluate()
|
||||
|
|
@ -136,7 +142,9 @@ class AppworldReactAgent:
|
|||
return num_passes / (num_passes + num_failures)
|
||||
|
||||
def extract_code_and_fix_content(
|
||||
self, text: str, ignore_multiple_calls=True
|
||||
self,
|
||||
text: str,
|
||||
ignore_multiple_calls=True,
|
||||
) -> tuple[str, str]:
|
||||
full_code_regex = r"```python\n(.*?)```"
|
||||
partial_code_regex = r".*```python\n(.*)"
|
||||
|
|
@ -154,7 +162,9 @@ class AppworldReactAgent:
|
|||
match_end = re_match.end()
|
||||
# check for partial code match at end (no terminating ```) following the last match
|
||||
partial_match = re.match(
|
||||
partial_code_regex, original_text[match_end:], flags=re.DOTALL
|
||||
partial_code_regex,
|
||||
original_text[match_end:],
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
if partial_match:
|
||||
output_code += partial_match.group(1).strip()
|
||||
|
|
@ -180,7 +190,12 @@ class AppworldReactAgent:
|
|||
before_score = self.get_reward(world)
|
||||
for i in range(self.max_interactions):
|
||||
if i == 0:
|
||||
self.prompt_messages(run_id=run_id, task_index=task_index, previous_memories=previous_memories, world=world)
|
||||
self.prompt_messages(
|
||||
run_id=run_id,
|
||||
task_index=task_index,
|
||||
previous_memories=previous_memories,
|
||||
world=world,
|
||||
)
|
||||
code_msg = self.call_llm(self.history[run_id][task_index])
|
||||
code, text = self.extract_code_and_fix_content(code_msg)
|
||||
self.history[run_id][task_index].append({"role": "assistant", "content": code})
|
||||
|
|
@ -189,7 +204,9 @@ class AppworldReactAgent:
|
|||
# if len(output) > self.max_response_size:
|
||||
# # logger.warning(f"output exceed max size={len(output)}")
|
||||
# output = output[: self.max_response_size]
|
||||
self.history[run_id][task_index].append({"role": "user", "content": "Output:\n```\n" + output + "```\n\n"})
|
||||
self.history[run_id][task_index].append(
|
||||
{"role": "user", "content": "Output:\n```\n" + output + "```\n\n"},
|
||||
)
|
||||
|
||||
if world.task_completed():
|
||||
break
|
||||
|
|
@ -199,7 +216,9 @@ class AppworldReactAgent:
|
|||
|
||||
if self.use_memory:
|
||||
if self.use_memory_addition:
|
||||
new_traj_list = [self.get_traj_from_task_history(task_id, self.history[run_id][task_index], after_score)]
|
||||
new_traj_list = [
|
||||
self.get_traj_from_task_history(task_id, self.history[run_id][task_index], after_score),
|
||||
]
|
||||
previous_memories = self.add_memory(new_traj_list)
|
||||
if after_score != 1:
|
||||
self.delete_memory_by_ids([mem["memory_id"] for mem in previous_memories])
|
||||
|
|
@ -209,7 +228,7 @@ class AppworldReactAgent:
|
|||
self.update_memory_information(self.retrieved_memory_list[run_id][task_index], update_utility)
|
||||
|
||||
counter += 1
|
||||
if self.use_memory_deletion: # and counter % self.delete_freq == 0:
|
||||
if self.use_memory_deletion: # and counter % self.delete_freq == 0:
|
||||
self.delete_memory()
|
||||
|
||||
t_result = {
|
||||
|
|
@ -261,7 +280,7 @@ class AppworldReactAgent:
|
|||
return {
|
||||
"task_id": task_id,
|
||||
"messages": task_history,
|
||||
"score": reward
|
||||
"score": reward,
|
||||
}
|
||||
|
||||
def add_memory(self, trajectories):
|
||||
|
|
@ -290,8 +309,8 @@ class AppworldReactAgent:
|
|||
json={
|
||||
"workspace_id": self.memory_workspace_id,
|
||||
"action": "delete_ids",
|
||||
"memory_ids": memory_ids
|
||||
}
|
||||
"memory_ids": memory_ids,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
|
@ -318,6 +337,7 @@ class AppworldReactAgent:
|
|||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def main():
|
||||
dataset_name = "train"
|
||||
task_ids = load_task_ids(dataset_name)
|
||||
|
|
@ -90,7 +90,7 @@ def run_agent(
|
|||
utility_threshold: float = 0.5,
|
||||
workspace_id: str = "appworld_v1",
|
||||
api_url: str = "http://0.0.0.0:8002/",
|
||||
batch_size: int = 4
|
||||
batch_size: int = 4,
|
||||
):
|
||||
experiment_name = dataset_name + "_" + experiment_suffix
|
||||
path: Path = Path(f"./exp_result/{model_name}")
|
||||
|
|
@ -125,7 +125,7 @@ def run_agent(
|
|||
future_list: list = []
|
||||
for i, task_id in enumerate(batch_task_ids):
|
||||
actor = AppworldReactAgent.remote(
|
||||
index=start_idx+i,
|
||||
index=start_idx + i,
|
||||
model_name=model_name,
|
||||
task_ids=[task_id],
|
||||
experiment_name=experiment_name,
|
||||
|
|
@ -193,9 +193,10 @@ def run_agent(
|
|||
result.append(task_results)
|
||||
dump_file()
|
||||
|
||||
|
||||
def main():
|
||||
max_workers = 8
|
||||
num_runs = 1 # Number of runs
|
||||
num_runs = 1 # Number of runs
|
||||
batch_size = 8 # Number of concurrent tasks per batch
|
||||
|
||||
num_trials = 2
|
||||
|
|
@ -206,7 +207,6 @@ def main():
|
|||
workspace_id = "appworld"
|
||||
api_url = "http://0.0.0.0:8002/"
|
||||
|
||||
|
||||
# Clean up workspace before starting
|
||||
logger.info("Deleting workspace...")
|
||||
delete_workspace(workspace_id=workspace_id, api_url=api_url)
|
||||
|
|
@ -216,7 +216,6 @@ def main():
|
|||
logger.info("Start load experiments to build task memories")
|
||||
load_memory(workspace_id=workspace_id, api_url=api_url)
|
||||
|
||||
|
||||
for i in range(num_runs):
|
||||
run_agent(
|
||||
model_name=model_name,
|
||||
|
|
@ -232,8 +231,9 @@ def main():
|
|||
utility_threshold=0.5,
|
||||
workspace_id=workspace_id,
|
||||
api_url=api_url,
|
||||
batch_size=batch_size
|
||||
batch_size=batch_size,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -195,7 +195,7 @@ class BFCLAgent:
|
|||
|
||||
# Extract memory list from response
|
||||
memory_list = result.get("metadata", {}).get("memory_list", [])
|
||||
logger.info(f'add new memories: {memory_list}')
|
||||
logger.info(f"add new memories: {memory_list}")
|
||||
return memory_list
|
||||
|
||||
def delete_memory_by_ids(self, memory_ids):
|
||||
|
|
@ -204,8 +204,8 @@ class BFCLAgent:
|
|||
json={
|
||||
"workspace_id": self.memory_workspace_id,
|
||||
"action": "delete_ids",
|
||||
"memory_ids": memory_ids
|
||||
}
|
||||
"memory_ids": memory_ids,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
|
@ -647,7 +647,9 @@ class BFCLAgent:
|
|||
reward = self.get_reward(run_id, task_index)
|
||||
if self.use_memory:
|
||||
if self.use_memory_addition: # selectively add memories when succeed
|
||||
new_traj_list = [self.get_traj_from_task_history(task_id, self.history[run_id][task_index], reward)]
|
||||
new_traj_list = [
|
||||
self.get_traj_from_task_history(task_id, self.history[run_id][task_index], reward),
|
||||
]
|
||||
previous_memories = self.add_memory(new_traj_list)
|
||||
if reward != 1:
|
||||
self.delete_memory_by_ids([mem["memory_id"] for mem in previous_memories])
|
||||
|
|
@ -91,7 +91,7 @@ def main():
|
|||
num_runs = 1
|
||||
|
||||
num_trials = 2
|
||||
model_name="qwen3-8b"
|
||||
model_name = "qwen3-8b"
|
||||
use_memory = False
|
||||
use_memory_addition = False
|
||||
use_memory_deletion = False
|
||||
|
|
@ -28,9 +28,11 @@ class ReactAgent:
|
|||
rather than on complex agent logic.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
model_name="",
|
||||
max_steps: int = 50):
|
||||
def __init__(
|
||||
self,
|
||||
model_name="",
|
||||
max_steps: int = 50,
|
||||
):
|
||||
|
||||
# You can replace this with your own LLM wrapper if needed.
|
||||
self.llm = OpenAICompatibleLLM(model_name=model_name)
|
||||
|
|
@ -65,10 +67,16 @@ class ReactAgent:
|
|||
|
||||
# Prepare all available tools from the MCP server.
|
||||
tool_dict: Dict[str, ToolCall] = {}
|
||||
async with FastMcpClient("reme_mcp_server", {
|
||||
"type": "sse",
|
||||
"url": "http://0.0.0.0:8002/sse",
|
||||
}) as mcp_client, HttpClient(base_url="http://localhost:8003") as http_client:
|
||||
async with (
|
||||
FastMcpClient(
|
||||
"reme_mcp_server",
|
||||
{
|
||||
"type": "sse",
|
||||
"url": "http://0.0.0.0:8002/sse",
|
||||
},
|
||||
) as mcp_client,
|
||||
HttpClient(base_url="http://localhost:8003") as http_client,
|
||||
):
|
||||
tool_calls = await mcp_client.list_tool_calls()
|
||||
|
||||
for tool_call in tool_calls:
|
||||
|
|
@ -87,25 +95,30 @@ class ReactAgent:
|
|||
# - compress long histories,
|
||||
# - offload detailed context into working memory storage,
|
||||
# - keep the recent message(s) for short-term reasoning.
|
||||
result = await http_client.execute_flow("summary_working_memory",
|
||||
messages=[x.simple_dump() for x 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")
|
||||
result = await http_client.execute_flow(
|
||||
"summary_working_memory",
|
||||
messages=[x.simple_dump() for x 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",
|
||||
)
|
||||
|
||||
# Convert the API result back into `Message` objects for the LLM.
|
||||
messages = [Message(**x) for x in result.answer]
|
||||
|
||||
# Ask the LLM what to do next.
|
||||
# You can plug in your own tool-calling strategy here.
|
||||
assistant_message: Message = await self.llm.achat(messages=messages, tools=[
|
||||
tool_dict["grep_working_memory"],
|
||||
tool_dict["read_working_memory"],
|
||||
])
|
||||
assistant_message: Message = await self.llm.achat(
|
||||
messages=messages,
|
||||
tools=[
|
||||
tool_dict["grep_working_memory"],
|
||||
tool_dict["read_working_memory"],
|
||||
],
|
||||
)
|
||||
|
||||
messages.append(assistant_message)
|
||||
|
||||
|
|
@ -118,20 +131,25 @@ class ReactAgent:
|
|||
logger.exception(f"unknown tool_call.name={tool_call.name}")
|
||||
continue
|
||||
|
||||
logger.info(f"round{i + 1}.{j} submit tool_calls={tool_call.name} "
|
||||
f"argument={tool_call.argument_dict}")
|
||||
logger.info(
|
||||
f"round{i + 1}.{j} submit tool_calls={tool_call.name} " f"argument={tool_call.argument_dict}",
|
||||
)
|
||||
|
||||
# Execute the tool via MCP and parse the result.
|
||||
result = await mcp_client.call_tool(tool_call.name,
|
||||
arguments=tool_call.argument_dict,
|
||||
parse_result=True)
|
||||
result = await mcp_client.call_tool(
|
||||
tool_call.name,
|
||||
arguments=tool_call.argument_dict,
|
||||
parse_result=True,
|
||||
)
|
||||
|
||||
# Attach the tool result as a TOOL-role message so the LLM
|
||||
# can see and reason about it in the next step.
|
||||
messages.append(Message(
|
||||
role=Role.TOOL,
|
||||
tool_call_id=tool_call.id,
|
||||
content=result,
|
||||
))
|
||||
messages.append(
|
||||
Message(
|
||||
role=Role.TOOL,
|
||||
tool_call_id=tool_call.id,
|
||||
content=result,
|
||||
),
|
||||
)
|
||||
|
||||
return messages
|
||||
|
|
@ -108,7 +108,7 @@ async def main():
|
|||
logger.info(
|
||||
f"origin_token_count: {origin_token_count} "
|
||||
f"after_token_count: {after_token_count} "
|
||||
f"compress_ratio={after_token_count / origin_token_count:.2f}"
|
||||
f"compress_ratio={after_token_count / origin_token_count:.2f}",
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue