源文件:chapter3/contextual-retrieval-for-user-memory/README.md 实验 3-12:利用上下文感知检索增强用户记忆 将上下文感知检索技术应用于用户记忆的构建,是解决传统对话历史分块所面临的核心痛点,并迈向更高层次记忆能力的关键。本项目实现了一个双层记忆系统,结合了: 上下文感知检索(Contextual RAG):对话历史的精准检索 高级 JSON 卡片(Advanced JSON Cards):结构化的核心事实存储 🆕 最新更新 LLM-Based Memory Card Generation 自动提取:使用 LLM 从对话中智能提取结构化记忆卡片 完整结构:每张卡片包含 backstory、person、relationship
源文件:chapter3/contextual-retrieval-for-user-memory/README.md
将上下文感知检索技术应用于用户记忆的构建,是解决传统对话历史分块所面临的核心痛点,并迈向更高层次记忆能力的关键。本项目实现了一个双层记忆系统,结合了:
传统的对话分块会丢失上下文信息。例如,一段孤立的对话片段"好的,就订这个吧"本身毫无信息量。只有知道上文是在讨论"从上海到西雅图的、价格为500美元的单程机票",这段对话才有意义。
本系统在索引对话历史之前,增加了关键的"上下文生成"步骤:
Advanced JSON Cards(常驻记忆)
Contextual RAG(按需检索)
系统现在能够从对话中智能提取结构化记忆卡片:
# 自动从对话生成记忆卡片 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 # 依赖项
pip install -r requirements.txt
创建 .env 文件:
# LLM Provider Configuration MOONSHOT_API_KEY=your_api_key_here ARK_API_KEY=your_api_key_here SILICONFLOW_API_KEY=your_api_key_here OPENAI_API_KEY=your_api_key_here # Default Provider LLM_PROVIDER=kimi # Options: kimi, doubao, siliconflow, openai # Model Settings LLM_MODEL=kimi-k3 # 或其他模型
cd ../retrieval-pipeline python api_server.py
本实验的核心论点是:在把对话记忆块送入嵌入/索引前,先为每块生成一段『上下文前缀』,能提升脱离上下文的孤立片段(如『好的,就订这个吧』)的召回。 --mode compare 提供一个完全离线、无需任何 API Key 或检索服务的受控对照实验来量化这一点。
它用同一份上下文,分别度量『不拼接(plain)』与『拼接后再索引(contextual)』两种方式的召回,变量只有『索引文本是否含上下文前缀』,因此结果直接反映上下文化本身的贡献。检索采用确定性的 BM25 词法检索(纯 Python、无第三方依赖)作为神经嵌入的离线代理;对照数据集见 memory_qa_eval.json(受控教学集,可用 --dataset 替换)。
# 打印对比指标表(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。
# 交互式界面(默认) 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... ============================================================
当用户询问"为我一月的东京之行,还有什么要准备的吗?"时:
事实回顾:Agent 首先审视 Advanced JSON Cards 中的内容
关联与推理:通过对比核心事实
细节验证:启动 RAG 检索
主动服务:结合两种记忆
自动评估:LLM Judge 评估答案
MIT License
This experiment now supports a universal OpenRouter fallback for its chat LLM.
MOONSHOT_API_KEY / KIMI_API_KEY / OPENAI_API_KEY / DOUBAO_API_KEY …) is present, behavior is unchanged.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.Add OPENROUTER_API_KEY=... to your .env (see env.example) to enable it.