Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/mkdocs/en/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,9 @@ python3 run_agent.py
[TencentDB Agent Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory)
V3 gateway. After each completed turn, the framework incrementally sends new
text events to `/v3/conversation/add`. An Agent equipped with
`load_memory_tool` searches extracted L1 atomic memories through
`/v3/atomic/search`.
`load_memory_tool` recalls L1 atomic memories, L2 scenario navigation, and L3
core memory in parallel. If all three layers have no usable content, the
service searches raw L0 conversations through `/v3/conversation/search`.

```python
from trpc_agent_sdk.memory.tencentdb_memory_service import (
Expand Down Expand Up @@ -630,6 +631,11 @@ Operational notes:

- L1 extraction is asynchronous, so a memory may not be searchable immediately
after a successful write.
- Recall requests `/v3/atomic/search`, `/v3/scenario/ls`, and `/v3/core/read`
concurrently. A failure in one layer does not discard results from the other
layers.
- L0 conversation search is used only when L1, L2, and L3 all have no usable
content.
- Successfully accepted event IDs are checkpointed in process. Delivery is
at-least-once across restarts.
- TencentDB Agent Memory controls retention; framework TTL settings do not
Expand Down
8 changes: 6 additions & 2 deletions docs/mkdocs/zh/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,9 @@ python3 run_agent.py
`TencentDBMemoryService` 用于对接
[TencentDB Agent Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory)
V3 网关。每轮对话结束后,框架会将新增文本事件增量写入
`/v3/conversation/add`;配置了 `load_memory_tool` 的 Agent 会通过
`/v3/atomic/search` 检索异步提取出的 L1 原子记忆。
`/v3/conversation/add`;配置了 `load_memory_tool` 的 Agent 会并行召回
L1 原子记忆、L2 场景导航和 L3 核心记忆。三层均无可用内容时,再通过
`/v3/conversation/search` 搜索 L0 原始对话。

```python
from trpc_agent_sdk.memory.tencentdb_memory_service import (
Expand Down Expand Up @@ -581,6 +582,9 @@ memory_service = TencentDBMemoryService(
使用时需要注意:

- L1 记忆提取是异步的,写入成功后不保证立即可以搜索到。
- 召回请求并行访问 `/v3/atomic/search`、`/v3/scenario/ls` 和
`/v3/core/read`;单层失败不会丢弃其他层的有效结果。
- 只有 L1、L2、L3 均无可用内容时才回退搜索 L0。
- 进程内会记录已成功写入的事件 ID;进程重启后采用至少一次投递语义。
- 记忆保留策略由 TencentDB Agent Memory 管理,框架 TTL 配置不适用于该服务。
- 使用前需要部署 V3 网关和记忆提取流水线。
Expand Down
16 changes: 9 additions & 7 deletions examples/memory_service_with_tencentdb/.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ TRPC_AGENT_API_KEY=your-llm-api-key
TRPC_AGENT_BASE_URL=https://your-llm-endpoint
TRPC_AGENT_MODEL_NAME=your-model-name

# TencentDB Agent Memory V3 gateway
TENCENTDB_MEMORY_ENDPOINT=http://127.0.0.1:8420
TENCENTDB_MEMORY_API_KEY=local
# TencentDB Agent Memory cloud values from the verified L1 demo.
# Do not commit a real API key; provide it locally before running.
TENCENTDB_MEMORY_ENDPOINT=https://memory.ap-beijing.tencenttdai.com
TENCENTDB_MEMORY_API_KEY=your-memory-api-key
TENCENTDB_MEMORY_SERVICE_ID=your-memory-service-id
TENCENTDB_MEMORY_TEAM_ID=your-team-id
TENCENTDB_MEMORY_AGENT_ID=weather-assistant
# This Agent/User scope already produced "favorite color is blue" as L1.
TENCENTDB_MEMORY_AGENT_ID=your-agent-id
TENCENTDB_MEMORY_USER_ID=usr-your-user-id

# L1 extraction is asynchronous. Increase this for slower deployments.
TENCENTDB_MEMORY_PIPELINE_WAIT_SECONDS=5
MEMORY_PROMPT_MODE=chat
# L1 extraction is asynchronous. This delay is an allowance, not a readiness guarantee.
TENCENTDB_MEMORY_PIPELINE_WAIT_SECONDS=60
49 changes: 27 additions & 22 deletions examples/memory_service_with_tencentdb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

本示例演示如何通过 `TencentDBMemoryService` 将 tRPC-Agent 接入
[TencentDB Agent Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory),
在一个 Session 中写入用户偏好,并在另一个 Session 中召回服务端提取的 L1 长期记忆。
在一个 Session 中写入带唯一验证标识的办公场景信息,并在另一个 Session 中召回
服务端提取的长期记忆。

## 工作流程

Expand All @@ -15,8 +16,8 @@
第二轮对话
-> Agent 调用 load_memory
-> TencentDBMemoryService.search_memory()
-> POST /v3/atomic/search 搜索 L1 记忆
-> L1 无结果时,POST /v3/conversation/search 搜索 L0 原始对话
-> 并行读取 L1 /v3/atomic/search、L2 /v3/scenario/ls 和 L3 /v3/core/read
-> L1/L2/L3 均无可用内容时,POST /v3/conversation/search 搜索 L0 原始对话
```

写入记忆由框架自动完成,不需要给 Agent 添加 `save_memory` 工具。Agent 只需通过
Expand All @@ -32,7 +33,7 @@
> - `chat`:个人偏好、用户画像、对话经历、教学和通用助手场景。
> - `code`:项目事实、工程任务、技术决策、SOP 和团队协作场景。
>
> 本示例写入“喜欢的颜色”。本地部署需要在 Memory Core 服务端选择 `chat`
> 本示例写入工程任务和发版评审信息。本地部署需要在 Memory Core 服务端选择 `code`
> 模式。腾讯云托管实例的提取策略由产品服务端管理,本客户端不会读取或发送
> `MEMORY_PROMPT_MODE`。

Expand Down Expand Up @@ -78,11 +79,11 @@ cd TencentDB-Agent-Memory/deploy/global-images
cp .env.example .env
```

本示例保存的是“喜欢的颜色”这类个人偏好,服务端必须使用 `chat` 提取模式。在
本示例保存的是工程任务和发版评审信息,服务端应使用 `code` 提取模式。在
`TencentDB-Agent-Memory/deploy/global-images/.env` 中确认:

```dotenv
MEMORY_PROMPT_MODE=chat
MEMORY_PROMPT_MODE=code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: 本变更将示例内容从"喜欢的颜色"(chat 模式)改为"工程任务和发版评审"并要求服务端使用 MEMORY_PROMPT_MODE=code(第 86 行),但 README 故障排查段落仍残留旧结论:日志显示 promptMode=code 且提取数为 0 时"设置 MEMORY_PROMPT_MODE=chat 并重新执行"(第 312 行),与示例新要求直接矛盾。

触发条件: 用户按新要求以 code 模式部署后遇到 L1 提取为空,按 README 指示切换为 chat 模式——与新示例内容(工程任务)所需模式相反,提取依然为空或错误提取。

实际影响: 故障排查指引引导用户做出与示例要求矛盾的配置,误导排查方向;同类残留还出现在第 199 行引用块("MEMORY_PROMPT_MODE=chat 应配置在服务端")。

修正方向: 将故障排查段和相关引用同步为 code 模式语义(例如"确认服务端已使用 code 模式而非 chat")。

```

然后运行启动脚本。脚本会交互式要求填写 Memory 和 Proxy 使用的 LLM 地址、
Expand Down Expand Up @@ -259,28 +260,31 @@ python3 examples/memory_service_with_tencentdb/run_agent.py

示例执行以下流程:

1. `session-write` 告诉 Agent:“My favorite color is blue.”
2. 第一轮结束后,Runner 自动将新增事件写入 L0。
3. Memory Core 异步将该偏好提取为 L1 记忆。
4. 等待提取完成后,`session-recall` 在另一个 Session 中询问喜欢的颜色。
5. Agent 调用 `load_memory`,跨 Session 搜索 `alice` 的长期记忆。
1. 为本次运行生成唯一的 `验证项目-<run-id>` 标识。
2. 写入 Agent 不加载历史记忆,只发送本轮办公场景对话并由 Runner 写入 L0。
3. Memory Core 异步提取 L1/L2/L3。
4. 等待配置的提取时间后,通过公开 `search_memory()` 输出各层召回结果。
5. 召回 Agent 在新的 Session 中根据唯一标识询问评审会最终安排。

## 测试结果

一次成功运行的关键输出如下,模型的具体措辞可能不同:

```text
User (session-write): My favorite color is blue. Please remember it.
Assistant: ... I've noted that your favorite color is blue.
TencentDB verification marker: 验证项目-1234abcd
User (office-write-1234abcd): 验证项目-1234abcd 的 Q4 发版评审会原定于...
Waiting <N>s for asynchronous memory extraction...

User (session-recall): What is my favorite color?
Assistant: Your favorite color is blue!
TencentDB recall verification summary: layers=L1,L2,L3, memories=<N>,
current_run_l1_found=True, current_run_final_found=True

User (office-recall-1234abcd): 验证项目-1234abcd 的 Q4 发版评审会最终安排...
Assistant: 11 月 6 日上午 10 点,会议室 3B。
```

实际写入发生在对话结束后的 Runner 阶段,对 Agent 是透明的。第二个 Session 能
回答 `blue`,说明记忆检索链路可用;但由于当前实现会在 L1 无结果时搜索 L0 原始
对话,仅凭回答正确不能证明 L1 已成功提取。
`current_run_final_found=True` 表示包含本次唯一标识的 L1 已提取出最终改期信息,
不会因为历史记录中恰好存在相同日期和会议室而误判。若 L1、L2、L3 均无结果,
服务仍会搜索 L0 原始对话作为兜底。

## 故障排查

Expand Down Expand Up @@ -314,8 +318,7 @@ l1-empty reason=empty_scenes

1. Gateway 地址、实例 ID、Team ID 和 Agent ID 来自同一个实例。
2. `run_agent.py` 的 `user_id` 与查询时使用的业务用户一致。
3. 当前实例的服务端提取策略适合测试内容。默认编码场景可能不会沉淀“喜欢的颜色”
这类个人偏好。
3. 当前实例的服务端提取策略适合测试内容。本示例属于工程协作场景。
4. 已等待足够时间;L0 写入成功不代表 L1 已经生成。

如果仍然无法生成或检索 L1,请参考腾讯云官方文档检查服务端配置:
Expand Down Expand Up @@ -352,8 +355,10 @@ POST /v3/atomic/search status=200

- `service_id/team_id/agent_id/user_id` 共同构成记忆隔离边界。
- `session_id` 仅在写入时发送;搜索时不限定 Session,因此支持跨 Session 召回。
- `/v3/atomic/search` 是 L1 语义检索接口;如果 L1 没有命中,当前实现会使用
`/v3/conversation/search` 对 L0 原始对话做语义兜底。
- 召回时会并行读取 L1 `/v3/atomic/search`、L2 `/v3/scenario/ls` 和 L3
`/v3/core/read`;只要任意一层有可用内容就返回组合结果。
- L1/L2/L3 均无可用内容时,使用 `/v3/conversation/search` 对 L0 原始对话
做语义兜底。
- 服务只发送当前进程中尚未成功写入的事件。
- 进程重启后采用至少一次投递语义,因为 V3 写入接口没有调用方提供的幂等键。
- L1 提取是异步的,写入成功不代表记忆可以立即搜索。
Expand Down
9 changes: 5 additions & 4 deletions examples/memory_service_with_tencentdb/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from .config import get_model_config


def create_agent() -> LlmAgent:
"""Create an assistant that can recall cross-session memory."""
def create_agent(*, recall_enabled: bool = True) -> LlmAgent:
"""Create an assistant, optionally with cross-session recall."""
api_key, base_url, model_name = get_model_config()
return LlmAgent(
name="memory_assistant",
Expand All @@ -24,8 +24,9 @@ def create_agent() -> LlmAgent:
base_url=base_url,
),
instruction=("Use load_memory before answering questions about information the "
"user may have shared in earlier conversations."),
tools=[load_memory_tool],
"user may have shared in earlier conversations."
if recall_enabled else "Acknowledge the user's new information without recalling prior memory."),
tools=[load_memory_tool] if recall_enabled else [],
)


Expand Down
118 changes: 102 additions & 16 deletions examples/memory_service_with_tencentdb/run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import os
import sys
from pathlib import Path
from uuid import uuid4

from dotenv import load_dotenv
from trpc_agent_sdk.context import AgentContext
Expand All @@ -26,6 +27,21 @@
load_dotenv()
sys.path.append(str(Path(__file__).parent))

_FINAL_DATE_MARKERS = ("11月6", "11/6", "2026-11-06")
_FINAL_ROOM_MARKER = "3b"


def _office_messages(run_marker: str) -> tuple[str, ...]:
return (
f"{run_marker} 的 Q4 发版评审会原定于 11 月 5 日下午 3 点,会议室 3A。",
"我这周负责修 pay-service 的订单超时 bug,已经在 TAPD 建了单 TAPD-88231。",
"以后给我写 commit message 都用英文,标题不超过 72 字符,正文用 bullet 列改动。",
"刚定位到根因是连接池 maxIdle 配成 2 太小,改成 20 后本地复现不出来了。",
f"{run_marker} 的评审会最终改到 11 月 6 日上午 10 点,会议室 3B;"
"原定的 11 月 5 日下午 3 点、会议室 3A 作废。",
"发版前必须跑一遍全量回归,这是我定的硬规矩,别跳过。",
)


def _required_env(name: str) -> str:
value = os.getenv(name, "").strip()
Expand Down Expand Up @@ -73,39 +89,109 @@ async def _run_turn(
print()


async def _print_recall_verification(
memory_service: TencentDBMemoryService,
*,
user_id: str,
run_marker: str,
recall_query: str,
) -> None:
"""Print the public recall result and its inferred memory layers."""
result = await memory_service.search_memory(
key=user_id,
query=recall_query,
limit=10,
)
found_layers: set[str] = set()
current_run_l1_found = False
current_run_final_found = False
normalized_marker = "".join(run_marker.split()).lower()
print("\nTencentDB recall verification:")
for index, memory in enumerate(result.memories):
if memory.author == "scenario":
layer = "L2"
elif memory.author == "core":
layer = "L3"
elif memory.author in {"user", "assistant", "message"}:
layer = "L0"
else:
layer = "L1"
found_layers.add(layer)
text = "".join(part.text or "" for part in memory.content.parts)
normalized = "".join(text.split()).lower()
belongs_to_current_run = layer == "L1" and normalized_marker in normalized
current_run_l1_found = current_run_l1_found or belongs_to_current_run
if (belongs_to_current_run and any(marker in normalized for marker in _FINAL_DATE_MARKERS)
and _FINAL_ROOM_MARKER in normalized):
current_run_final_found = True
preview = text if len(text) <= 500 else f"{text[:500]}...[truncated]"
print(f" [{index}] {layer}:{memory.author} {preview}")
layers = ",".join(sorted(found_layers)) if found_layers else "none"
print(
"TencentDB recall verification summary: "
f"layers={layers}, memories={len(result.memories)}, "
f"current_run_l1_found={current_run_l1_found}, "
f"current_run_final_found={current_run_final_found}", )


async def main() -> None:
"""Write a fact in one session and recall it from another."""
"""Write office facts, wait for extraction, then recall across sessions."""
from agent.agent import create_agent
from agent.agent import root_agent

memory_service = create_memory_service()
runner = Runner(
session_service = InMemorySessionService()
write_runner = Runner(
app_name="tencentdb_memory_demo",
agent=create_agent(recall_enabled=False),
session_service=session_service,
memory_service=memory_service,
close_session_service_on_close=False,
close_memory_service_on_close=False,
)
recall_runner = Runner(
app_name="tencentdb_memory_demo",
agent=root_agent,
session_service=InMemorySessionService(),
session_service=session_service,
memory_service=memory_service,
)
user_id = "alice"
user_id = _required_env("TENCENTDB_MEMORY_USER_ID")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: run_agent.py 将 user_id 从硬编码 "alice" 改为 _required_env("TENCENTDB_MEMORY_USER_ID"),但同变更只同步了 .env(该文件并非 README 指示复制模板),README 配置流程引用的 .env.local/.env.remote 模板以及正文第 157 行(仍写 user_id="alice")均未包含该必需变量。

触发条件: 用户按 README 第 4 节执行 cp examples/memory_service_with_tencentdb/.env.local .env 并照模板填写后运行 python3 run_agent.py——环境变量缺失,_required_env 在第 158 行直接抛 ValueError: TENCENTDB_MEMORY_USER_ID must be configured 退出。

实际影响: 示例按文档操作必然无法运行且无任何文档提示该新必需项,示例可用性被破坏。

修正方向: 在 .env.local、.env.remote 模板与 README 正文(包括第 157 行 "user_id=alice" 段落)中补充 TENCENTDB_MEMORY_USER_ID 的填写说明。

run_id = uuid4().hex[:8]
run_marker = f"验证项目-{run_id}"
recall_query = f"{run_marker} 的 Q4 发版评审会最终安排在什么时间和会议室?"
write_session_id = f"office-write-{run_id}"
recall_session_id = f"office-recall-{run_id}"
print(f"TencentDB verification marker: {run_marker}")

try:
await _run_turn(
runner,
user_id=user_id,
session_id="session-write",
text="My favorite color is blue. Please remember it.",
)

wait_seconds = float(os.getenv("TENCENTDB_MEMORY_PIPELINE_WAIT_SECONDS", "5"), )
for text in _office_messages(run_marker):
await _run_turn(
write_runner,
user_id=user_id,
session_id=write_session_id,
text=text,
)

wait_seconds = float(os.getenv("TENCENTDB_MEMORY_PIPELINE_WAIT_SECONDS", "10"), )
print(f"Waiting {wait_seconds:g}s for asynchronous memory extraction...", )
await asyncio.sleep(wait_seconds)

await _print_recall_verification(
memory_service,
user_id=user_id,
run_marker=run_marker,
recall_query=recall_query,
)

await _run_turn(
runner,
recall_runner,
user_id=user_id,
session_id="session-recall",
text="What is my favorite color?",
session_id=recall_session_id,
text=recall_query,
)
finally:
await runner.close()
await write_runner.close()
await recall_runner.close()


if __name__ == "__main__":
Expand Down
Loading
Loading