Files
ai-agent-book/chapter3/contextual-retrieval-for-user-memory/README.md
T
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

581 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Experiment 3-11: Contextual Retrieval for User Memory / 实验 3-11:利用上下文感知检索增强用户记忆
> Companion material for *AI Agents in Depth*, Chapter 3 — dual-layer memory: Contextual RAG + Advanced JSON Cards.
> 配套《深入理解 AI Agent》第 3 章——双层记忆:上下文感知 RAG + Advanced JSON Cards。
← [Chapter 3 index / 返回第 3 章目录](../README.md)
---
## English
### Canonical live campaign
`python campaign.py` evaluates all 60 three-layer cases with a preregistered
plain/contextual/dual-layer ablation. A live ReAct planner's exact queries are
replayed across plain and contextual fixed-window indexes; the dual arm adds
the live Advanced JSON Card checkpoint from Experiment 3-1. Prefixes, cards,
raw chunks, trajectories, per-layer external-judge metrics, and credential-free
receipts are retained under `validation/`; `validation/latest.json` is the
canonical gate report.
### What this experiment is
Applying contextual retrieval to user memory addresses a core pain point of naive conversation chunking and steps toward higher-level memory. This project implements a dual-layer memory system:
1. **Contextual Retrieval (Contextual RAG)**: precise retrieval over conversation history
2. **Advanced JSON Cards**: structured storage of core facts
### Latest updates
#### LLM-Based Memory Card Generation
- **Auto extract**: LLM extracts structured memory cards from conversations
- **Full structure**: each card includes backstory, person, relationship, and related fields
- **Graceful fallback**: falls back to keyword extraction when the LLM is unavailable
#### LLM Judge Integration
- **Auto evaluation**: LLM Judge scores agent answers automatically
- **Dual path**: import evaluation module or call the API directly
- **Detailed feedback**: 01 score, pass/fail, and reasoning
#### Enhanced Debugging
- **Memory card dump**: prints full JSON of all memory cards during evaluation
- **Test case sorting**: test cases listed alphabetically by name
- **Eval transparency**: clearly shows whether LLM Judge is in use
### Core ideas
#### 1. Context-enriched conversation chunking
Traditional chunking drops context. An isolated fragment like “OK, book that one” means little until you know the prior turn discussed “a one-way Shanghai→Seattle flight for $500.”
Before indexing conversation history, the system adds a **context generation** step:
- Each conversation chunk gets an LLM-generated prefix summary with key background
- Context includes time, people, intent, and other cues
- Greatly improves retrieval accuracy and relevance
#### 2. Dual-layer memory
**Advanced JSON Cards (always-on memory)**
- Structured, summarized core facts
- Always fixed in the Agents context
- Metadata such as backstory (source) and relationship (related people)
- Example: “User Jessicas passport expires on 2025-02-18”
**Contextual RAG (on-demand retrieval)**
- Precise access to unstructured raw dialogue detail
- Quickly finds full context of a specific discussion
- Acts as “evidence” for decisions
#### 3. LLM-based memory extraction
The system can extract structured memory cards from dialogue:
```python
# Auto-generate memory cards from conversation
cards = indexer._generate_summary_cards(chunks, conversation_id)
# Example card:
{
"category": "financial",
"card_key": "bank_account_primary",
"backstory": "用户在开设账户时提供了银行信息",
"date_created": "2024-01-15 10:30:00",
"person": "John Smith (primary)",
"relationship": "primary account holder",
"bank_name": "Chase Bank",
"account_type": "checking",
"account_ending": "4567"
}
```
### Project structure
```
contextual-retrieval-for-user-memory/
├── contextual_chunking.py # Context-aware chunking
├── advanced_memory_manager.py # Advanced JSON card manager
├── contextual_indexer.py # Dual-layer memory indexer (incl. LLM extract)
├── contextual_agent.py # Agent combining dual-layer memory
├── contextual_evaluator.py # Evaluation (incl. LLM Judge)
├── contextual_compare.py # Offline compare: contextual vs plain recall (no API)
├── memory_qa_eval.json # Controlled memory Q&A set for offline compare
├── main.py # Main entry (argparse; --mode compare offline)
├── config.py # Config
├── chunker.py # Base chunker
├── tools.py # Agent tools
└── requirements.txt # Dependencies
```
### Install and configure
#### 1. Install dependencies
```bash
# From the repository root: use the shared Chapter 3 environment
uv sync --locked --python 3.12 --extra ch3
# Activate it before changing directories:
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell: .venv\Scripts\Activate.ps1
# Windows cmd: .venv\Scripts\activate.bat
# pip fallback when uv is not installed:
# python -m pip install -e ".[ch3]"
cd chapter3/contextual-retrieval-for-user-memory
# Single-project compatibility path, still supported during migration:
# python -m pip install -r requirements.txt
```
#### 2. Environment variables
Create a `.env` file:
```bash
# LLM Provider Configuration
MOONSHOT_API_KEY=your_api_key_here
ARK_API_KEY=your_api_key_here
SILICONFLOW_API_KEY=your_api_key_here
DASHSCOPE_API_KEY=your_dashscope_api_key_here
# DASHSCOPE_BASE_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1
OPENAI_API_KEY=your_api_key_here
# Default Provider
LLM_PROVIDER=kimi # Options: dashscope/qwen/bailian, kimi, doubao, siliconflow, openai
# Model Settings
LLM_MODEL=kimi-k3 # or another model
```
#### 3. Start the retrieval pipeline (for full e2e eval)
```bash
cd ../retrieval-pipeline
python api_server.py
```
### Usage
#### Offline compare: does contextualization help? (no API; recommended first)
Core claim: **before embedding/indexing memory chunks, generating a “context prefix” per chunk improves recall of out-of-context fragments** (e.g. “OK, book that one”). `--mode compare` is a **fully offline, no API key / no retrieval service** controlled experiment.
It measures “plain” (no prefix) vs “contextual” (prefix then index) on the same context; the only variable is whether the indexed text includes the context prefix. Retrieval is deterministic pure-Python BM25 as an offline stand-in for neural embeddings. Dataset: [`memory_qa_eval.json`](memory_qa_eval.json) (teaching set; override with `--dataset`).
```bash
# Print comparison metrics (Recall@1 / Recall@3 / MRR)
python main.py --mode compare
# Equivalent standalone script:
python contextual_compare.py
# Single-query plain vs contextual Top-K
python main.py --mode compare --query '我最后确认预订的那张机票是哪个航班?'
# Save full results (per-query ranks) to JSON
python main.py --mode compare --output results/compare.json
```
Measured output (12 memory chunks, 8 queries):
```
方法 Recall@1 Recall@3 MRR
--------------------------------------------------------------------
Plain(直接索引原始块) 0.625 1.000 0.792
Contextual(上下文化后索引) 0.750 1.000 0.875
--------------------------------------------------------------------
提升(Δ) +0.125 +0.000 +0.083
```
For queries like “is my Seattle hotel confirmed,” the gold chunk (“yes, book it”) ranks 3rd under Plain and 1st after contextualization—the prefix re-anchors an isolated confirmation to “Hyatt Seattle.”
> Note: this is an **offline lexical proxy** to show mechanism and directional gain without APIs. In production, prefixes are LLM-generated per chunk and indexed with neural embeddings + hybrid search (see `--mode evaluate` below), which needs an API key.
#### End-to-end evaluation (needs API / retrieval service)
```bash
# Interactive UI (default)
python main.py
# Evaluate a category (needs LLM + retrieval pipeline)
python main.py --mode evaluate --category layer1
# Ablate contextualization; set model and output
python main.py --mode evaluate --category layer1 --no-contextual --model gpt-5.6-luna --output results/plain_eval.json
```
Full CLI: `python main.py --help` (includes Chinese descriptions).
#### Interactive menu
Run `python main.py`:
```
Main Menu:
1. 🚀 Demo Mode (Quick Start)
2. 📚 Load & Index Conversations
3. 🎴 Manage Memory Cards
4. 🔍 Test Query
5. 📊 Evaluate All Test Cases (by Category) [LLM Judge]
6. 🎯 Evaluate Specific Test Case [LLM Judge]
7. 📈 Show Statistics
8. ⚙️ Configure Settings
0. Exit
```
#### Evaluation output example
```
============================================================
DEBUG: All Memory Cards in System
============================================================
[financial.bank_account_primary]
{
"backstory": "用户开设银行账户时提供的信息",
"date_created": "2024-06-12 14:30:00",
"person": "Michael James Robertson (primary)",
"relationship": "primary account holder",
"bank_name": "First National Bank",
"account_number": "4429853327",
"routing_number": "123006800"
}
Total Memory Cards: 5
============================================================
LLM Judge Evaluation Results
============================================================
Reward: 1.000/1.000
Passed: Yes
Reasoning: The agent correctly provided the checking account number...
============================================================
```
### Workflow example
When the user asks “Anything else to prepare for my January Tokyo trip?”:
1. **Fact review**: Agent inspects Advanced JSON Cards
- Finds “Tokyo trip” (departs Jan 25)
- Finds “passport” (expires Feb 18)
2. **Link and reason**: compares core facts
- Flags passport expiry near flight date
3. **Detail check**: starts RAG
- Searches dialogue chunks about passport / Tokyo flights
- Loads full original discussion
4. **Proactive service**: both memory layers
- Advises: “Your passport is about to expire; strongly consider expedited renewal.”
5. **Auto eval**: LLM Judge
- Score: 0.95/1.0
- Reason: correctly identified risk and gave appropriate advice
### References
- [Anthropic's Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval)
- [RAG survey](https://arxiv.org/abs/2005.11401)
- [Memory Systems in AI Agents](https://arxiv.org/abs/2203.14680)
- [LLM as Judge](https://arxiv.org/abs/2306.05685)
### License
MIT License
---
## 中文
### 本实验是什么
将上下文感知检索技术应用于用户记忆的构建,是解决传统对话历史分块所面临的核心痛点,并迈向更高层次记忆能力的关键。本项目实现了一个双层记忆系统,结合了:
1. **上下文感知检索(Contextual RAG**:对话历史的精准检索
2. **高级 JSON 卡片(Advanced JSON Cards**:结构化的核心事实存储
### 最新更新
#### LLM-Based Memory Card Generation
- **自动提取**:使用 LLM 从对话中智能提取结构化记忆卡片
- **完整结构**:每张卡片包含 backstory、person、relationship 等必要字段
- **智能降级**:当 LLM 不可用时自动降级到关键词提取
#### LLM Judge Integration
- **自动评估**:集成 LLM Judge 对 Agent 回答进行自动评分
- **双路径支持**:支持导入模块或直接 API 调用
- **详细反馈**:提供 0-1 分数、通过/失败状态和评估理由
#### Enhanced Debugging
- **内存卡片可视化**:评估时自动打印所有记忆卡片的完整 JSON
- **测试用例排序**:按名称字母顺序显示测试用例
- **评估透明度**:清晰显示 LLM Judge 使用状态
### 核心创新
#### 1. 上下文增强的对话分块
传统的对话分块会丢失上下文信息。例如,一段孤立的对话片段“好的,就订这个吧”本身毫无信息量。只有知道上文是在讨论“从上海到西雅图的、价格为500美元的单程机票”,这段对话才有意义。
本系统在索引对话历史之前,增加了关键的“上下文生成”步骤:
- 每个对话块都会调用 LLM 生成包含关键背景信息的前缀摘要
- 上下文包括时间、人物和意图等关键线索
- 极大提升了检索的准确性和相关性
#### 2. 双层记忆结构
**Advanced JSON Cards(常驻记忆)**
- 存储结构化的、总结性的核心事实
- 始终固定在 Agent 的上下文中
- 包含 backstory(信息来源)和 relationship(关联人员)等元数据
- 如:“用户 Jessica 的护照将于2025年2月18日过期”
**Contextual RAG(按需检索)**
- 提供对非结构化的原始对话细节的精准访问
- 快速找到具体讨论的完整上下文
- 作为决策的“证据”支持
#### 3. LLM-Based Memory Extraction
系统现在能够从对话中智能提取结构化记忆卡片:
```python
# 自动从对话生成记忆卡片
cards = indexer._generate_summary_cards(chunks, conversation_id)
# 生成的卡片示例:
{
"category": "financial",
"card_key": "bank_account_primary",
"backstory": "用户在开设账户时提供了银行信息",
"date_created": "2024-01-15 10:30:00",
"person": "John Smith (primary)",
"relationship": "primary account holder",
"bank_name": "Chase Bank",
"account_type": "checking",
"account_ending": "4567"
}
```
### 项目结构
```
contextual-retrieval-for-user-memory/
├── contextual_chunking.py # 上下文感知分块
├── advanced_memory_manager.py # 高级JSON卡片管理
├── contextual_indexer.py # 双层记忆索引器(含LLM提取)
├── contextual_agent.py # 结合双层记忆的Agent
├── contextual_evaluator.py # 评估框架(含LLM Judge
├── contextual_compare.py # 离线对比脚本:上下文化 vs 原始块的召回(无需 API)
├── memory_qa_eval.json # 离线对比用的受控记忆问答对照集
├── main.py # 主入口(argparse,含 --mode compare 离线对比)
├── config.py # 配置管理
├── chunker.py # 基础分块器
├── tools.py # Agent工具
└── requirements.txt # 依赖项
```
### 安装与配置
#### 1. 安装依赖
```bash
# 在仓库根目录使用统一的第 3 章环境
uv sync --locked --python 3.12 --extra ch3
# 切换目录前先激活环境:
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell.venv\Scripts\Activate.ps1
# Windows cmd.venv\Scripts\activate.bat
# 未安装 uv 时可用 pip 兜底:
# python -m pip install -e ".[ch3]"
cd chapter3/contextual-retrieval-for-user-memory
# 迁移期间仍支持单项目兼容路径:
# python -m pip install -r requirements.txt
```
#### 2. 配置环境变量
创建 `.env` 文件:
```bash
# LLM Provider Configuration
MOONSHOT_API_KEY=your_api_key_here
ARK_API_KEY=your_api_key_here
SILICONFLOW_API_KEY=your_api_key_here
DASHSCOPE_API_KEY=your_dashscope_api_key_here
# DASHSCOPE_BASE_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1
OPENAI_API_KEY=your_api_key_here
# Default Provider
LLM_PROVIDER=kimi # Options: dashscope/qwen/bailian, kimi, doubao, siliconflow, openai
# Model Settings
LLM_MODEL=kimi-k3 # 或其他模型
```
#### 3. 启动检索管道服务
```bash
cd ../retrieval-pipeline
python api_server.py
```
### 使用示例
#### 离线对比:上下文化到底有没有用?(无需 API,推荐先跑这个)
本实验的核心论点是:**在把对话记忆块送入嵌入/索引前,先为每块生成一段『上下文前缀』,能提升脱离上下文的孤立片段(如『好的,就订这个吧』)的召回。** `--mode compare` 提供一个**完全离线、无需任何 API Key 或检索服务**的受控对照实验来量化这一点。
它用同一份上下文,分别度量『不拼接(plain)』与『拼接后再索引(contextual)』两种方式的召回,变量只有『索引文本是否含上下文前缀』,因此结果直接反映上下文化本身的贡献。检索采用确定性的 BM25 词法检索(纯 Python、无第三方依赖)作为神经嵌入的离线代理;对照数据集见 [`memory_qa_eval.json`](memory_qa_eval.json)(受控教学集,可用 `--dataset` 替换)。
```bash
# 打印对比指标表(Recall@1 / Recall@3 / MRR
python main.py --mode compare
# 等价于直接运行独立脚本:
python contextual_compare.py
# 对单条查询做 plain vs contextual 的 Top-K 检索对比
python main.py --mode compare --query '我最后确认预订的那张机票是哪个航班?'
# 保存完整结果(含逐查询名次明细)到 JSON
python main.py --mode compare --output results/compare.json
```
实测输出(12 个记忆块、8 条查询的受控集):
```
方法 Recall@1 Recall@3 MRR
--------------------------------------------------------------------
Plain(直接索引原始块) 0.625 1.000 0.792
Contextual(上下文化后索引) 0.750 1.000 0.875
--------------------------------------------------------------------
提升(Δ) +0.125 +0.000 +0.083
```
其中『我订的西雅图酒店确认了吗』这类查询,gold 记忆块(『可以,帮我订下来』)在 Plain 下排名第 3、上下文化后升到第 1——正是上下文前缀把孤立确认片段重新锚定回了『西雅图凯悦酒店』的情境。
> 说明:这是一个**离线词法代理**,用于在无 API 环境下清晰地演示上下文化的机制与方向性收益。生产管线中,上下文前缀由 LLM 逐块生成、并用神经嵌入 + 混合检索索引(见下方 `--mode evaluate`),需要配置 API Key。
#### 端到端评估(需 API / 检索服务)
```bash
# 交互式界面(默认)
python main.py
# 评估特定分类(需 LLM 与检索管道服务)
python main.py --mode evaluate --category layer1
# 关闭上下文化做对照,并指定模型与输出
python main.py --mode evaluate --category layer1 --no-contextual --model gpt-5.6-luna --output results/plain_eval.json
```
完整命令行参数见 `python main.py --help`(含中文说明)。
#### 交互式测试界面
运行 `python main.py` 进入交互式界面:
```
Main Menu:
1. 🚀 Demo Mode (Quick Start)
2. 📚 Load & Index Conversations
3. 🎴 Manage Memory Cards
4. 🔍 Test Query
5. 📊 Evaluate All Test Cases (by Category) [LLM Judge]
6. 🎯 Evaluate Specific Test Case [LLM Judge]
7. 📈 Show Statistics
8. ⚙️ Configure Settings
0. Exit
```
#### 评估输出示例
```
============================================================
DEBUG: All Memory Cards in System
============================================================
[financial.bank_account_primary]
{
"backstory": "用户开设银行账户时提供的信息",
"date_created": "2024-06-12 14:30:00",
"person": "Michael James Robertson (primary)",
"relationship": "primary account holder",
"bank_name": "First National Bank",
"account_number": "4429853327",
"routing_number": "123006800"
}
Total Memory Cards: 5
============================================================
LLM Judge Evaluation Results
============================================================
Reward: 1.000/1.000
Passed: Yes
Reasoning: The agent correctly provided the checking account number...
============================================================
```
### 工作流程示例
当用户询问“为我一月的东京之行,还有什么要准备的吗?”时:
1. **事实回顾**Agent 首先审视 Advanced JSON Cards 中的内容
- 发现“东京之行”信息(1月25日出发)
- 发现“护照信息”(2月18日过期)
2. **关联与推理**:通过对比核心事实
- 识别出机票日期与护照过期日期接近的风险
3. **细节验证**:启动 RAG 检索
- 搜索与“护照”和“东京机票”相关的对话片段
- 获取原始讨论的所有细节
4. **主动服务**:结合两种记忆
- 给出关键建议:“您的护照即将过期,强烈建议您立即加急办理续签”
5. **自动评估**LLM Judge 评估答案
- 评分:0.95/1.0
- 理由:正确识别风险并给出适当建议
### 参考资料
- [Anthropic's Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval)
- [RAG 技术综述](https://arxiv.org/abs/2005.11401)
- [Memory Systems in AI Agents](https://arxiv.org/abs/2203.14680)
- [LLM as Judge](https://arxiv.org/abs/2306.05685)
### 许可证
MIT License
---
## Notes / 说明
### OpenRouter 通用回退 / Universal OpenRouter fallback
This experiment supports a **universal OpenRouter fallback** for its chat LLM.
- If the primary provider key (e.g. `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `OPENAI_API_KEY` / `DOUBAO_API_KEY` …) is present, behavior is unchanged.
- Else if `OPENROUTER_API_KEY` is set, the chat LLM is automatically routed through OpenRouter (`https://openrouter.ai/api/v1`). Model names are mapped automatically: `gpt-*`/`o1-*``openai/…`, `claude-*``anthropic/claude-opus-4.8`, `kimi-*``moonshotai/kimi-k2.6`, ids already containing `/` are kept as-is, and other provider-native ids (e.g. `doubao-*`) fall back to `openai/gpt-5.6-luna`. Set `OPENROUTER_MODEL` to force a specific OpenRouter model id.
- Else a clear error lists the accepted keys.
Add `OPENROUTER_API_KEY=...` to your `.env` (see `env.example`) to enable it.