Skip to content

Commit 9ca0bf6

Browse files
committed
feature: 引入 advanced_memory 组件
feature: 增加advanced memory的示例 Update .env feature: 第一版可用advanced memory Preserve default Runner post-turn behavior feature: 优化session memory实现逻辑,加入清理机制 增加coordination的test脚本 feature: 增加记忆新鲜度机制 feature: 增加环境变量设置,用户可以在.env中设置模型窗口大小 feature: 为advanced_memory增加preload功能
1 parent 7f45fdd commit 9ca0bf6

48 files changed

Lines changed: 8677 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[flake8]
22
max-line-length = 120
3-
ignore = E402, W503
3+
ignore = E402, W503, W504
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Set TRPC_AGENT_API_KEY、TRPC_AGENT_BASE_URL、TRPC_AGENT_MODEL_NAME
2+
TRPC_AGENT_API_KEY=
3+
TRPC_AGENT_BASE_URL=
4+
TRPC_AGENT_MODEL_NAME=
5+
# Optional: enable token-based context budgeting for Advanced Memory.
6+
# Set both model limits to enable token-based context budgeting.
7+
TRPC_AGENT_MODEL_CONTEXT_WINDOW_TOKENS=
8+
TRPC_AGENT_MAX_OUTPUT_TOKENS=
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# Advanced Memory
2+
3+
## Advanced Memory 简介
4+
5+
`Advanced Memory` 是一套面向 Agent 的本地化记忆与上下文管理机制,重点增强
6+
Agent 在长期信息沉淀和超长对话处理方面的能力:
7+
8+
- **本地化持久存储**:记忆和上下文数据以本地文件形式持久化,存储位置、数据边界
9+
和组织方式清晰可控,适合本地开发、调试、迁移和审计。
10+
- **更强的长期记忆能力**:支持将对话中的稳定事实、用户偏好和重要经验主动沉淀为
11+
可组织、可更新、可跨 Session 使用的长期记忆,而不是简单堆积历史消息。
12+
- **分层记忆管理**:分别管理原始对话、Session 级记忆和跨 Session 长期记忆,让不同
13+
类型的信息以合适的粒度参与后续推理。
14+
- **上下文管理**:根据上下文规模、信息类型和使用情况,对历史消息、工具结果及记忆
15+
内容进行统一治理,在保留关键信息的同时控制模型输入规模。
16+
- **上下文压缩**:支持对历史上下文和工具结果进行渐进式裁剪、压缩和摘要,降低长
17+
对话导致的上下文膨胀以及超出模型窗口限制的风险。
18+
- **结构化记忆提取**:从持续增长的对话中提取结构化信息,形成更稳定、更易维护的
19+
Session Memory,提升后续对话对历史信息的利用效率。
20+
21+
本示例演示如何使用 `AdvancedMemorySessionService`。它把 Session 持久化和
22+
Advanced Memory 上下文管理整合到一个 SessionService 中,用户不需要显式调用
23+
`setup_advanced_memory()`,也不需要再创建 `InMemorySessionService`
24+
25+
## 示例流程
26+
27+
脚本使用同一个 Runner 执行多个 Session:
28+
29+
1. `session-1` 连续输入多轮 Python 开发偏好。
30+
2. 低阈值配置会触发 session memory 提取,并写入 `session_memory.md`
31+
3. `session-1` 请求总结已经学习到的开发偏好。
32+
4. `session-2` 查询长期记忆,验证不同 Session 共享同一个 `MEMORY/`
33+
34+
## 使用方式
35+
36+
```python
37+
from pathlib import Path
38+
39+
from trpc_agent_sdk.memory import AdvancedMemoryConfig
40+
from trpc_agent_sdk.sessions import AdvancedMemorySessionService
41+
from trpc_agent_sdk.runners import Runner
42+
43+
session_service = AdvancedMemorySessionService(
44+
config=AdvancedMemoryConfig(
45+
root_dir=Path(__file__).resolve().parent,
46+
)
47+
)
48+
49+
runner = Runner(
50+
app_name="advanced_memory_demo",
51+
agent=agent,
52+
session_service=session_service,
53+
)
54+
```
55+
56+
`Runner` 检测到 `AdvancedMemorySessionService` 后会自动完成 Advanced Memory
57+
绑定,包括:
58+
59+
- transcript 持久化
60+
- session memory 提取
61+
- 长期记忆 tools:`save_memory``read_memory``list_memory_index`
62+
- `HistorySnip`
63+
- `Microcompact`
64+
- `AutoCompact`
65+
- `ToolResultBudget`
66+
67+
`AdvancedMemoryConfig` 默认已经启用这些能力。示例中额外降低了
68+
session-memory 的阈值,只是为了用较少的对话轮数演示提取流程;生产环境可以
69+
删除这些阈值配置,使用默认值。
70+
71+
## 数据目录
72+
73+
运行后,数据默认写入当前示例目录:
74+
75+
```text
76+
MEMORY/
77+
├── MEMORY.md
78+
└── *.md # 长期记忆详情
79+
80+
SESSION/
81+
├── _state.json # app/user 级 state
82+
├── session-1/
83+
│ ├── session.json # Session 元数据和 session state
84+
│ ├── transcript.jsonl # 原始 Events 和 checkpoint
85+
│ ├── session_memory.md # 结构化 Session 记忆
86+
│ └── tool-results/ # 超大工具结果
87+
└── session-2/
88+
├── session.json
89+
├── transcript.jsonl
90+
└── session_memory.md
91+
```
92+
93+
其中:
94+
95+
- `session.json` 保存 Session 元数据和状态,不保存完整 Events。
96+
- `transcript.jsonl` 是追加写入的原始事件日志,可用于恢复 Session。
97+
- `session_memory.md` 是根据 transcript 提取的结构化摘要。
98+
- `MEMORY/` 保存跨 Session 使用的长期记忆。
99+
100+
## 运行
101+
102+
先在本目录创建 `.env`,然后填写模型配置:
103+
104+
```bash
105+
cd examples/memory_service_with_advanced_memory
106+
python3 run_agent.py
107+
```
108+
109+
需要的环境变量:
110+
111+
- `TRPC_AGENT_API_KEY`
112+
- `TRPC_AGENT_BASE_URL`
113+
- `TRPC_AGENT_MODEL_NAME`
114+
- `TRPC_AGENT_MODEL_CONTEXT_WINDOW_TOKENS`(可选,模型总上下文窗口大小,单位为 token)
115+
- `TRPC_AGENT_MAX_OUTPUT_TOKENS`(可选,模型最大输出窗口大小,单位为 token)
116+
117+
`.env` 中留空的变量不会覆盖默认值;如果同时在 Python 中传入
118+
`model_context_window_tokens``max_output_tokens`,Python 显式配置优先。
119+
120+
如果配置了模型上下文窗口,Advanced Memory 会用
121+
`TRPC_AGENT_MODEL_CONTEXT_WINDOW_TOKENS - TRPC_AGENT_MAX_OUTPUT_TOKENS`
122+
作为可用于输入内容的窗口;两个变量都留空时使用字符数阈值。
123+
124+
## `AdvancedMemoryConfig` 配置项
125+
126+
下面列出当前所有可直接传入 `AdvancedMemoryConfig` 的配置项。**没有特殊需求时,
127+
只设置 `root_dir` 即可**;示例中的值均为默认值。
128+
129+
```python
130+
session_service = AdvancedMemorySessionService(
131+
config=AdvancedMemoryConfig(
132+
root_dir=Path(__file__).resolve().parent, # 当前示例目录
133+
# root_dir=Path(
134+
# "/data/workspace/trpc-agent-python/examples/memory_service_with_advanced_memory"
135+
# ),
136+
# Optional
137+
enabled=True, # 总开关和存储路径
138+
memory_dir_name="MEMORY", # 长期记忆目录
139+
session_dir_name="SESSION", # Session 数据目录
140+
memory_index_name="MEMORY.md", # 长期记忆索引文件
141+
transcript_name="transcript.jsonl", # transcript 文件
142+
session_memory_name="session_memory.md", # Session 摘要文件
143+
encoding="utf-8", # 文件编码
144+
transcript_fsync=False, # transcript 写入后是否 fsync
145+
146+
# 长期记忆
147+
memory_index_max_lines=200, # 注入 prompt 的索引最大行数
148+
memory_index_max_bytes=25_000, # 注入 prompt 的索引最大字节数
149+
long_term_memory_injection_enabled=True, # 是否注入 MEMORY.md
150+
151+
# 工具结果
152+
tool_result_max_chars=50_000, # 单个工具结果最大字符数
153+
tool_results_per_message_max_chars=200_000, # 单条消息工具结果总上限
154+
tool_result_preview_chars=2_000, # 超限结果的预览字符数
155+
156+
# HistorySnip
157+
history_snip_enabled=True, # 是否压缩过长历史
158+
history_snip_trigger_chars=600_000, # 触发阈值
159+
history_snip_target_chars=400_000, # 压缩目标
160+
history_snip_keep_recent=5, # 保留最近的完整消息数
161+
history_snip_tool_names=( # 可处理的工具名称
162+
"Read", "Bash", "Grep", "Glob",
163+
"WebSearch", "WebFetch", "Edit", "Write",
164+
),
165+
166+
# Token 上下文预算
167+
# 这两个值也可以通过 .env 配置;显式传参优先于环境变量。
168+
# model_context_window_tokens=131072, # 显式设置后覆盖环境变量
169+
# max_output_tokens=8192, # 显式设置后覆盖环境变量
170+
# 如果省略这两行,则分别读取 .env;未配置时默认 None 和 0。
171+
token_warning_ratio=0.85, # 告警比例
172+
token_autocompact_ratio=0.90, # 自动压缩比例
173+
token_blocking_ratio=0.95, # 阻止继续增加上下文的比例
174+
token_estimator=None, # 可选:自定义 token 估算器
175+
context_window_resolver=None, # 可选:自定义窗口解析器
176+
177+
# Session Memory
178+
session_memory_enabled=True, # 是否启用 Session 摘要
179+
session_memory_initial_chars=40_000, # 首次提取字符阈值
180+
session_memory_update_chars=20_000, # 后续更新字符阈值
181+
session_memory_initial_tokens=10_000, # 首次提取 token 阈值
182+
session_memory_update_tokens=5_000, # 后续更新 token 阈值
183+
session_memory_tool_calls_between_updates=3, # 两次更新间的工具调用数
184+
session_memory_prompt_max_chars=200_000, # 摘要请求最大字符数
185+
session_memory_request_overhead_tokens=2_048, # 请求预留 token
186+
session_memory_section_max_chars=8_000, # 单个摘要 section 最大字符数
187+
session_memory_total_max_chars=54_000, # 摘要总最大字符数
188+
session_memory_wait_timeout_seconds=15.0, # 等待摘要 Agent 的超时时间
189+
190+
# AutoCompact
191+
autocompact_enabled=True, # 是否启用自动压缩
192+
autocompact_trigger_chars=700_000, # 触发阈值
193+
autocompact_target_chars=350_000, # 压缩目标
194+
autocompact_blocking_chars=780_000, # 阻止继续增加上下文的阈值
195+
autocompact_keep_recent_contents=8, # 保留最近内容数
196+
autocompact_max_failures=3, # 最大连续失败次数
197+
autocompact_summary_input_max_chars=600_000, # 摘要 Agent 输入上限
198+
autocompact_summary_retries=3, # 摘要 Agent 重试次数
199+
200+
# Microcompact
201+
microcompact_enabled=True, # 是否启用工具结果微压缩
202+
microcompact_gap_seconds=3_600.0, # 工具结果时间间隔阈值
203+
microcompact_trigger_count=20, # 触发工具结果数量
204+
microcompact_keep_recent=5, # 保留最近工具结果数
205+
microcompact_tool_names=( # 可处理的工具名称
206+
"Read", "Bash", "Grep", "Glob",
207+
"WebSearch", "WebFetch", "Edit", "Write",
208+
),
209+
210+
# Advanced Memory preload
211+
preload_memory_enabled=False, # 是否自动预加载相关 topic
212+
preload_memory_max_topics=5, # 一次最多加载的 topic 数
213+
preload_memory_max_chars=50_000, # 预加载内容总字符上限
214+
preload_memory_candidate_limit=200, # 筛选模型的候选 topic 数
215+
),
216+
)
217+
```
218+
219+
`preload_memory_model` 不是 `AdvancedMemoryConfig` 字段,而是
220+
`AdvancedMemorySessionService` 的可选参数,用于指定轻量筛选模型:
221+
222+
```python
223+
session_service = AdvancedMemorySessionService(
224+
config=AdvancedMemoryConfig(preload_memory_enabled=True),
225+
preload_memory_model=small_model, # 不传时复用主 Agent 的模型
226+
)
227+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Agent package for the Advanced Memory example."""
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Agent definition for the Advanced Memory example."""
2+
3+
from trpc_agent_sdk.agents import LlmAgent
4+
from trpc_agent_sdk.models import OpenAIModel
5+
6+
from .config import get_model_config
7+
from .prompts import INSTRUCTION
8+
9+
10+
def create_agent() -> LlmAgent:
11+
"""Create an agent; Advanced Memory tools are installed by run_agent.py."""
12+
api_key, base_url, model_name = get_model_config()
13+
return LlmAgent(
14+
name="advanced_memory_assistant",
15+
description="A minimal Advanced Memory demonstration assistant",
16+
model=OpenAIModel(
17+
model_name=model_name,
18+
api_key=api_key,
19+
base_url=base_url,
20+
),
21+
instruction=INSTRUCTION,
22+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Model configuration for the example."""
2+
3+
import os
4+
5+
6+
def get_model_config() -> tuple[str, str, str]:
7+
"""Read the model configuration from the environment."""
8+
api_key = os.getenv("TRPC_AGENT_API_KEY", "")
9+
base_url = os.getenv("TRPC_AGENT_BASE_URL", "")
10+
model_name = os.getenv("TRPC_AGENT_MODEL_NAME", "")
11+
if not api_key or not base_url or not model_name:
12+
raise ValueError("TRPC_AGENT_API_KEY, TRPC_AGENT_BASE_URL, and "
13+
"TRPC_AGENT_MODEL_NAME must be set")
14+
return api_key, base_url, model_name
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Prompt for the example agent."""
2+
3+
INSTRUCTION = """You are a helpful assistant demonstrating Advanced Memory.
4+
5+
When the user asks you to remember a durable personal preference or fact, use
6+
save_memory. When the user asks what you remember, use list_memory_index first
7+
and read_memory for the relevant file. Always answer using the tool result.
8+
"""
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env python3
2+
"""Run the two-session Advanced Memory demonstration."""
3+
4+
import asyncio
5+
from pathlib import Path
6+
7+
from dotenv import load_dotenv
8+
from trpc_agent_sdk.memory import AdvancedMemoryConfig
9+
from trpc_agent_sdk.sessions import AdvancedMemorySessionService
10+
from trpc_agent_sdk.sessions import SessionServiceConfig
11+
from trpc_agent_sdk.types import Content
12+
from trpc_agent_sdk.types import Part
13+
14+
from agent.agent import create_agent
15+
16+
load_dotenv()
17+
18+
19+
def create_session_service() -> AdvancedMemorySessionService:
20+
"""Create the persistent Advanced Memory session service."""
21+
return AdvancedMemorySessionService(
22+
config=AdvancedMemoryConfig(root_dir=Path(__file__).resolve().parent),
23+
session_config=SessionServiceConfig(ttl=SessionServiceConfig.create_ttl_config(
24+
ttl_seconds=60,
25+
cleanup_interval_seconds=5,
26+
)),
27+
)
28+
29+
30+
async def run_turn(runner, *, user_id: str, session_id: str, prompt: str) -> None:
31+
"""Run one turn and print tool activity and the final response."""
32+
print(f"\n👤 [{session_id}] {prompt}")
33+
content = Content(parts=[Part.from_text(text=prompt)])
34+
async for event in runner.run_async(
35+
user_id=user_id,
36+
session_id=session_id,
37+
new_message=content,
38+
):
39+
if not event.content or not event.content.parts:
40+
continue
41+
for part in event.content.parts:
42+
if part.function_call:
43+
print(f"🔧 {part.function_call.name}({part.function_call.args})")
44+
elif part.function_response:
45+
print(f"📊 {part.function_response.response}")
46+
elif part.text and not part.thought and not event.partial:
47+
print(f"🤖 {part.text}")
48+
49+
50+
async def main() -> None:
51+
"""Run two independent sessions sharing Advanced Memory."""
52+
agent = create_agent()
53+
session_service = create_session_service()
54+
55+
from trpc_agent_sdk.runners import Runner
56+
runner = Runner(
57+
app_name="advanced_memory_demo",
58+
agent=agent,
59+
session_service=session_service,
60+
)
61+
try:
62+
session_one_prompts = [
63+
("Please remember that my favorite programming language is Python. "
64+
"Save this as a user preference."),
65+
"I use Python mainly for backend services and data processing.",
66+
"I prefer typed Python code with clear dataclasses and small modules.",
67+
"For testing Python code, I usually prefer pytest and focused unit tests.",
68+
"When documenting projects, I prefer concise examples with runnable commands.",
69+
]
70+
for prompt in session_one_prompts:
71+
await run_turn(
72+
runner,
73+
user_id="demo-user",
74+
session_id="session-1",
75+
prompt=prompt,
76+
)
77+
78+
await run_turn(
79+
runner,
80+
user_id="demo-user",
81+
session_id="session-1",
82+
prompt="Summarize what you learned about my Python development preferences.",
83+
)
84+
85+
await run_turn(
86+
runner,
87+
user_id="demo-user",
88+
session_id="session-2",
89+
prompt="What do you remember about my favorite programming language?",
90+
)
91+
92+
print("\n⏳ Waiting 1 minutes for the session TTL cleanup...")
93+
await asyncio.sleep(125)
94+
print("🧹 Expired Advanced Memory sessions should now be removed.")
95+
finally:
96+
await runner.close()
97+
98+
99+
if __name__ == "__main__":
100+
asyncio.run(main())

0 commit comments

Comments
 (0)