
作者:逆境不可逃
技术永无止境
希望我的内容可以帮助到你!!!!!
大家吼 ! 我是 逆境不可逃 今天给大家带来文章《Hello-Agents 第二部分-第八章总结:记忆与检索》.
Hello-Agents 官方地址: datawhalechina/hello-agents: 📚 《从零开始构建智能体》------从零开始的智能体原理与实践教程
本章的核心目标是给 HelloAgents 增加两类能力:记忆系统 Memory System 和检索增强生成 RAG。前者让 Agent 能保存、检索、整合和遗忘交互经验;后者让 Agent 能从外部知识库获取最新、专业、可溯源的信息。两者都遵循第 7 章的工具化思想:不为每种能力新建 Agent 类,而是把能力封装成标准工具 MemoryTool 和 RAGTool,再通过 ToolRegistry 挂载到 Agent 上。
配套代码建议按下面顺序阅读:
| 文件 | 关注点 |
|---|---|
01_MemoryTool_Basic_Operations.py |
MemoryTool 的 add/search/summary/stats/forget/consolidate 基础操作 |
02_MemoryTool_Architecture.py |
MemoryTool 与 MemoryManager 的分层架构 |
03_WorkingMemory_Implementation.py |
工作记忆的容量、TTL、混合检索和性能特征 |
04_RAGTool_MarkItDown_Pipeline.py |
MarkItDown 文档转换、Markdown 分块、嵌入流程 |
05_RAGTool_Advanced_Search.py |
基础检索、MQE、HyDE、组合高级检索与性能对比 |
06_Memory_Consolidation_Demo.py |
记忆整合条件、整合流程、生命周期管理 |
07_RAGTool_Intelligent_QA.py |
RAG 智能问答、上下文构建、引用和答案质量 |
08_Agent_Tool_Integration.py |
MemoryTool 与 RAGTool 在 Agent 中协同工作 |
09_Memory_Types_Deep_Dive.py |
四类记忆的差异、用法和协作方式 |
10_RAG_Pipeline_Complete.py |
从文档摄取到智能问答的完整 RAG 管道 |
11_Q&A_Assistant.py |
基于 Gradio 的智能文档问答助手案例 |
1. 为什么 Agent 需要记忆与 RAG
大语言模型本身通常是无状态的。每一次 API 调用都只看当前输入和显式传入的上下文,不会天然记住之前发生的事情。这会造成四类问题:
- 上下文丢失:长对话中,早期信息会因上下文窗口限制被挤出。
- 个性化缺失:模型无法稳定记住用户偏好、身份、习惯和长期目标。
- 学习能力受限:不能从过往成功或失败经验中积累改进。
- 一致性问题:多轮对话中容易出现前后回答不一致。
RAG 解决的是另一类问题:模型的内置知识是静态的、有限的,且容易出现事实幻觉。通过先检索外部知识,再把检索结果作为上下文交给模型,RAG 可以提升时效性、专业性、可解释性和可信度。
因此,本章的设计可以概括为:
MemoryTool:面向 "历史交互、用户状态、经验和偏好" 的长期能力。RAGTool:面向 "外部文档、知识库、专业资料" 的检索增强能力。- 两者协同:RAG 找知识,Memory 记历程;RAG 回答当下问题,Memory 支撑长期个性化。
2. 总体架构
2.1 记忆系统架构
记忆系统采用分层设计,重点是把 "统一管理" 和 "不同类型记忆的专业化实现" 分开。
HelloAgents 记忆系统
├── 基础设施层
│ ├── MemoryManager:统一调度和协调
│ ├── MemoryItem:标准化记忆项
│ ├── MemoryConfig:系统参数配置
│ └── BaseMemory:记忆类型基类
├── 记忆类型层
│ ├── WorkingMemory:工作记忆,短期上下文,TTL 管理
│ ├── EpisodicMemory:情景记忆,具体事件和时间序列
│ ├── SemanticMemory:语义记忆,抽象知识和图谱关系
│ └── PerceptualMemory:感知记忆,多模态数据
├── 存储后端层
│ ├── QdrantVectorStore:向量检索
│ ├── Neo4jGraphStore:知识图谱
│ └── SQLiteDocumentStore:结构化持久化
└── 嵌入服务层
├── DashScopeEmbedding:云端嵌入
├── LocalTransformerEmbedding:本地嵌入
└── TFIDFEmbedding:轻量兜底
这个结构的好处是职责清晰:MemoryTool 面向 Agent 调用,MemoryManager 负责编排,具体记忆类型负责自己的存储、检索和评分。
2.2 RAG 系统架构
RAG 被设计成一条 pipeline,而不是一个庞大的 Agent:
用户层:RAGTool 统一接口
↓
应用层:智能问答、搜索、知识库管理
↓
处理层:文档解析、Markdown 分块、向量化
↓
存储层:向量数据库、文档存储
↓
基础层:嵌入模型、LLM、数据库连接
RAG 的主要工程链路是:
任意格式文档
→ MarkItDown 转换为 Markdown
→ 标题层次和段落语义分割
→ token 控制与 chunk overlap
→ 文本预处理和嵌入
→ 写入 Qdrant
→ 检索、构建上下文、LLM 生成答案
3. MemoryTool:让 Agent 拥有记忆
3.1 记忆生命周期
本章借鉴认知科学,把记忆过程拆成五步:
- 编码
Encoding:把感知到的信息转成可保存的结构。 - 存储
Storage:写入对应类型的记忆模块。 - 检索
Retrieval:根据查询找回相关记忆。 - 整合
Consolidation:把重要短期记忆提升为长期记忆。 - 遗忘
Forgetting:删除不重要、过期或超容量的信息。
这五步对应到工具层,就是 add/search/summary/stats/forget/consolidate 等操作。
3.2 基础用法
对应代码:01_MemoryTool_Basic_Operations.py
from hello_agents.tools import MemoryTool
memory_tool = MemoryTool(
user_id="demo_user",
memory_types=["working", "episodic", "semantic", "perceptual"],
)
memory_tool.run({
"action": "add",
"content": "正在学习 HelloAgents 框架的记忆系统",
"memory_type": "working",
"importance": 0.7,
"task_type": "learning",
})
memory_tool.run({
"action": "search",
"query": "记忆系统",
"limit": 3,
})
memory_tool.run({"action": "summary", "limit": 5})
memory_tool.run({"action": "stats"})
MemoryTool 的统一入口是 execute(action, **kwargs) 或脚本中常用的 run({"action": ...})。它支持的主要动作如下:
| 动作 | 作用 | 常用参数 |
|---|---|---|
add |
添加记忆 | content, memory_type, importance, metadata |
search |
搜索记忆 | query, limit, memory_type/memory_types, min_importance |
summary |
获取记忆摘要 | limit |
stats |
获取统计信息 | 无 |
update |
更新记忆 | memory_id, 新内容或元数据 |
remove |
删除指定记忆 | memory_id |
forget |
按策略遗忘记忆 | strategy, threshold, max_age_days |
consolidate |
短期记忆转长期记忆 | from_type, to_type, importance_threshold |
clear_all |
清空记忆 | 谨慎使用 |
3.3 add:记忆编码
add 不只是保存一段文本,还会补齐会话 ID、时间戳、重要性、多模态信息和业务元数据。importance 默认 0.5,范围通常是 0 到 1,用来影响检索排序、整合和遗忘。
# 工作记忆:当前任务上下文
memory_tool.execute(
"add",
content="用户刚才问了关于 Python 函数的问题",
memory_type="working",
importance=0.6,
)
# 情景记忆:具体事件
memory_tool.execute(
"add",
content="用户完成了第一个 Python 项目",
memory_type="episodic",
importance=0.8,
event_type="milestone",
)
# 语义记忆:抽象知识
memory_tool.execute(
"add",
content="Python 是解释型、面向对象的编程语言",
memory_type="semantic",
importance=0.9,
knowledge_type="factual",
)
# 感知记忆:多模态信息
memory_tool.execute(
"add",
content="用户上传了一张 Python 代码截图",
memory_type="perceptual",
importance=0.7,
modality="image",
file_path="./uploads/code_screenshot.png",
)
3.4 search:记忆检索
搜索支持按单一记忆类型或多个记忆类型过滤,也支持最低重要性阈值。
# 全局搜索
memory_tool.execute("search", query="Python 编程", limit=5)
# 指定类型
memory_tool.execute(
"search",
query="学习进度",
memory_type="episodic",
limit=3,
)
# 多类型 + 重要性过滤
memory_tool.execute(
"search",
query="函数定义",
memory_types=["semantic", "episodic"],
min_importance=0.5,
)
检索的重点不是 "找到包含关键词的文本",而是把语义相似度、时间近因性、重要性、结构过滤等因素组合起来排序。
3.5 forget:选择性遗忘
遗忘机制模拟人类选择性遗忘,避免记忆无限增长。基础策略有三类:
# 删除重要性低于阈值的记忆
memory_tool.execute("forget", strategy="importance_based", threshold=0.2)
# 删除超过指定天数的记忆
memory_tool.execute("forget", strategy="time_based", max_age_days=30)
# 容量接近上限时删除低价值记忆
memory_tool.execute("forget", strategy="capacity_based", threshold=0.3)
需要注意:生产系统中的 "遗忘" 不仅是节省空间,也涉及隐私、合规和数据生命周期管理。
3.6 consolidate:记忆整合
consolidate 把重要的短期记忆转为长期记忆。默认思路是把重要性超过阈值的 working 记忆提升为 episodic,也可以把情景记忆进一步抽象成语义记忆。
# 重要工作记忆转为情景记忆
memory_tool.execute(
"consolidate",
from_type="working",
to_type="episodic",
importance_threshold=0.7,
)
# 重要情景记忆抽象为语义记忆
memory_tool.execute(
"consolidate",
from_type="episodic",
to_type="semantic",
importance_threshold=0.8,
)
整合的判断不能只看重要性。更合理的触发条件还应考虑:是否被多次访问、是否与长期目标相关、是否被用户显式要求记住、是否跨会话仍有价值、是否包含结构化知识。
3.7 MemoryManager:工具与底层记忆的分工
对应代码:02_MemoryTool_Architecture.py
MemoryTool 负责统一接口、参数规范化、会话元数据和结果格式化;MemoryManager 负责调用不同记忆类型的实现。这个分层避免让工具层直接关心 SQLite、Qdrant、Neo4j 或 embedding 细节。
memory_tool = MemoryTool(
user_id="demo_user",
memory_types=["working", "semantic"],
)
只启用部分记忆类型是一个实用设计。例如本地轻量 demo 可以只开 working 和 semantic;多模态应用再启用 perceptual;需要长期事件回顾时启用 episodic。
4. 四种记忆类型
对应代码:03_WorkingMemory_Implementation.py、06_Memory_Consolidation_Demo.py、09_Memory_Types_Deep_Dive.py
| 类型 | 存什么 | 存储方式 | 检索重点 | 典型场景 |
|---|---|---|---|---|
工作记忆 working |
当前会话、临时任务上下文 | 内存 + TTL + 容量限制 | 快速访问、时间衰减、关键词 / TF-IDF 混合 | 当前问题、刚刚上传的信息、短期状态 |
情景记忆 episodic |
具体事件、交互经历、学习历程 | SQLite + Qdrant | 语义相似 + 时间近因性 + 会话过滤 | "上次我问了什么""我什么时候完成了任务" |
语义记忆 semantic |
概念、事实、规则、长期知识 | Qdrant + Neo4j | 向量语义 + 图关系推理 | 用户偏好、领域概念、知识关联 |
感知记忆 perceptual |
图像、音频、文档等多模态感知数据 | 分模态向量集合 | 同模态 / 跨模态检索 + 时间近因性 | 截图、语音、图片、PDF 特征 |
4.1 工作记忆
工作记忆强调速度和短期性,默认容量有限,带 TTL 自动清理。它适合保存当前任务状态,不适合保存长期知识。
评分思路:
base_relevance = TF-IDF/向量相似度 * 0.7 + 关键词匹配 * 0.3
final_score = base_relevance * 时间衰减 * (0.8 + importance * 0.4)
如果向量检索不可用,系统可以退回关键词匹配,保证基础可用性。
4.2 情景记忆
情景记忆保存具体事件,重点是 "发生了什么、何时发生、在哪个会话中发生"。它适合回顾学习历程、任务里程碑、用户历史操作。
评分公式:
score = (向量相似度 * 0.8 + 时间近因性 * 0.2) * (0.8 + importance * 0.4)
情景记忆强调时间近因性,是因为事件天然与时间顺序有关。用户问 "最近一次""上次""今天做了什么" 时,时间因素直接影响答案质量。
4.3 语义记忆
语义记忆保存抽象知识、实体、概念和关系。它的关键不是 "什么时候发生",而是 "概念之间如何关联"。因此它使用向量检索加图检索的混合策略。
评分公式:
score = (向量相似度 * 0.7 + 图相似度 * 0.3) * (0.8 + importance * 0.4)
图检索权重虽然只有 0.3,但非常关键。它能支持多跳关系、路径查询、实体关联等纯向量检索不擅长的问题。例如 "张三学习了哪些与 RAG 相关的概念" 不仅需要匹配文本,还需要沿着 "用户 - 学习 - 概念 - 技术领域" 的关系找答案。
4.4 感知记忆
感知记忆支持文本、图像、音频等模态。它通常为不同模态使用独立向量集合,避免不同编码器维度不一致。
评分公式:
score = (向量相似度 * 0.8 + 时间近因性 * 0.2) * (0.8 + importance * 0.4)
时间近因性通常采用指数衰减。这样可以让最近上传的截图、音频或文档在检索中更靠前,但又不会完全抹掉旧信息的价值。
5. RAGTool:知识检索增强
对应代码:04_RAGTool_MarkItDown_Pipeline.py、05_RAGTool_Advanced_Search.py、07_RAGTool_Intelligent_QA.py、10_RAG_Pipeline_Complete.py
5.1 RAG 的基本流程
RAG 分为两个阶段:
- 数据准备阶段:提取外部文档内容,切分成片段,向量化后写入数据库。
- 应用阶段:接收用户问题,检索相关片段,构建上下文,让 LLM 基于证据回答。
基础用法:
from hello_agents.tools import RAGTool
rag_tool = RAGTool(
knowledge_base_path="./knowledge_base",
collection_name="test_collection",
rag_namespace="test",
)
rag_tool.execute(
"add_text",
text="RAG 是结合信息检索和文本生成的 AI 技术。",
document_id="rag_concept",
)
rag_tool.execute(
"search",
query="RAG 的作用是什么",
limit=3,
min_score=0.1,
)
rag_tool.execute("stats")
在完整应用里,更常用的是 add_document 和 ask:
rag_tool.run({
"action": "add_document",
"file_path": "./docs/tutorial.pdf",
"chunk_size": 1000,
"chunk_overlap": 200,
})
rag_tool.run({
"action": "ask",
"question": "文档中如何解释 RAG?",
"limit": 5,
"enable_advanced_search": True,
"enable_mqe": True,
"enable_hyde": True,
})
5.2 文档转换:MarkItDown
RAGTool 使用 MarkItDown 把多种文件统一转成 Markdown。这样后续处理可以只面对一种结构化文本格式。
支持的类型包括:
- PDF、Word、Excel、PowerPoint
- 图片 OCR
- 音频转录
- TXT、CSV、JSON、XML、HTML
- Python、JavaScript 等代码文件
统一成 Markdown 的价值在于:标题层次、段落、列表和代码块可以成为分块依据,而不是粗暴按字符数切割。
5.3 智能分块
本章的分块不是简单固定长度切片,而是结构感知的流程:
Markdown 文本
→ 标题层次解析
→ 段落语义分割
→ token 计算
→ chunk_size 控制
→ chunk_overlap 保持连续性
→ 准备嵌入
关键点:
- 标题路径
heading_path可作为元数据保存,帮助回答时定位上下文。 - 分块不能太大,否则检索粒度粗、上下文噪声多。
- 分块不能太小,否则语义不完整,容易丢失上下文。
chunk_overlap用于保留跨块信息,但过大又会增加重复内容和存储成本。- 中英文混合文本需要特殊 token 估算,不能只按英文空格切词。
5.4 嵌入模型与向量存储
本章给出三类嵌入方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 百炼 API / DashScope | 语义质量较好、维护成本低 | 需要网络和 API 成本 | 生产应用、中文语义要求高 |
| 本地 Transformer | 可离线、数据不出本地 | 依赖模型部署和算力 | 私有化部署、内网环境 |
| TF-IDF | 简单、快、无模型依赖 | 语义能力弱,只擅长字面匹配 | 兜底方案、小规模 demo |
向量数据库使用 Qdrant,关键是通过 rag_namespace 做命名空间隔离,并将 memory_type=rag_chunk、is_rag_data=True、data_source=rag_pipeline 等元数据写入向量,方便过滤检索范围。
5.5 高级检索:MQE 与 HyDE
基础检索常见问题是 "用户问法" 和 "文档写法" 不一致。第 8 章实现了两种补强策略。
MQE Multi-Query Expansion:让 LLM 把原始问题扩展成多个语义等价或互补的问题,再并行检索并合并结果。
适合:
- 用户提问模糊。
- 同一概念有多种叫法。
- 需要提高召回率。
HyDE Hypothetical Document Embeddings:先让 LLM 生成一段 "假设答案",再用这段答案去检索真实文档。它的核心是 "用答案形态去匹配答案型文档",缓解问题句和文档陈述句之间的语义鸿沟。
适合:
- 专业领域问题。
- 问题很抽象,直接检索命中率低。
- 文档内容更像解释性段落,而不是问句。
组合策略:
原始查询
→ MQE 生成多个扩展查询
→ HyDE 生成假设答案文档
→ 对每个查询执行向量检索
→ 扩大候选池
→ 按 memory_id 去重
→ 按最高分排序
→ 返回 top-k
工程取舍:
- 普通查询:优先开启 MQE。
- 专业复杂查询:MQE + HyDE 同时开启。
- 性能敏感场景:只用基础检索或只开 MQE。
- 高准确问答:检索后可加重排序、引用来源和答案质量评估。
6. Memory 与 RAG 的协同
对应代码:08_Agent_Tool_Integration.py
MemoryTool 和 RAGTool 都是标准工具,可以挂到同一个 Agent:
from hello_agents import SimpleAgent, HelloAgentsLLM, ToolRegistry
from hello_agents.tools import MemoryTool, RAGTool
agent = SimpleAgent(
name="智能助手",
llm=HelloAgentsLLM(),
system_prompt="你是一个有记忆和知识检索能力的 AI 助手",
)
registry = ToolRegistry()
registry.register_tool(MemoryTool(user_id="user123"))
registry.register_tool(RAGTool(knowledge_base_path="./knowledge_base"))
agent.tool_registry = registry
两者协作的典型模式:
- 学习新资料:RAG 存文档,Memory 记录 "用户加载了什么资料"。
- 提问回答:RAG 检索资料生成答案,Memory 记录问题、学习轨迹和重要结论。
- 复盘学习:Memory 回顾用户历史问题和笔记,RAG 补充知识细节。
- 制定计划:RAG 提供领域知识,Memory 提供用户进度和偏好。
7. 智能文档问答助手案例
对应代码:11_Q&A_Assistant.py
第 8.4 节把 MemoryTool 和 RAGTool 组合成一个 Gradio Web 应用,核心类是 PDFLearningAssistant。它的职责不是重新实现底层检索或记忆,而是把工具编排成一个学习闭环。
核心结构:
class PDFLearningAssistant:
def __init__(self, user_id="default_user"):
self.user_id = user_id
self.session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
self.memory_tool = MemoryTool(user_id=user_id)
self.rag_tool = RAGTool(rag_namespace=f"pdf_{user_id}")
self.stats = {
"session_start": datetime.now(),
"documents_loaded": 0,
"questions_asked": 0,
"concepts_learned": 0,
}
关键方法:
| 方法 | RAGTool 作用 | MemoryTool 作用 |
|---|---|---|
load_document(pdf_path) |
add_document 处理 PDF、分块、入库 |
记录 "加载文档" 事件到情景记忆 |
ask(question) |
ask 执行高级检索和问答 |
工作记忆记录当前问题,情景记忆记录问答事件 |
add_note(content, concept) |
不直接用 RAG | 保存学习笔记到语义记忆 |
recall(query) |
不直接用 RAG | 搜索历史学习记忆 |
get_stats() |
可读取 RAG 统计 | 汇总学习过程指标 |
generate_report() |
读取知识库状态 | 读取记忆摘要,生成学习报告 |
这个案例的关键不是 UI,而是数据流:
上传 PDF
→ RAGTool 转换、分块、向量化、入库
→ MemoryTool 记录加载事件
→ 用户提问
→ RAGTool 执行 MQE/HyDE/向量检索/答案生成
→ MemoryTool 记录问题和学习事件
→ 用户写笔记
→ MemoryTool 写入语义记忆
→ 统计和报告
8. 实践注意事项
- 不要把所有信息都塞进工作记忆。工作记忆应该短小、即时、可丢弃。
- 情景记忆适合事件,不适合抽象规则。比如 "用户今天问了 RAG" 是情景,"用户偏好中文解释" 更像语义。
- 语义记忆要注意实体关系质量。自动抽取会出错,生产中应加入人工校验或置信度机制。
- RAG 的效果高度依赖分块和嵌入模型。模型再强,分块糟糕也会检索失败。
- MQE 和 HyDE 会增加 LLM 调用成本。只有在召回不足或问题复杂时才值得开启。
- 多用户应用必须做数据隔离。至少需要
user_id、session_id、rag_namespace和数据库层面的过滤。 - 遗忘敏感信息时,不能只删业务表。向量库、图数据库、缓存、日志、备份都要纳入删除范围。
9. 习题解析
题 1:四种记忆类型、评分机制与健康助手设计
问题一:为什么情景记忆更强调时间近因性,而语义记忆更强调图检索?
情景记忆保存的是事件。事件的价值往往和时间顺序强相关,例如 "上次对话""最近一次学习""昨天上传的文档"。因此它的评分把时间近因性纳入权重:
情景记忆 = (向量相似度 * 0.8 + 时间近因性 * 0.2) * 重要性权重
语义记忆保存的是概念、规则和知识关系。知识不一定因为 "新" 就更正确,真正重要的是概念之间的关联。例如 "RAG 和向量数据库的关系""用户偏好与推荐策略的关系"。因此语义记忆更强调图检索:
语义记忆 = (向量相似度 * 0.7 + 图相似度 * 0.3) * 重要性权重
问题二:个人健康管理助手如何组合四种记忆?
| 记忆类型 | 健康助手中的用途 | 示例 |
|---|---|---|
| 工作记忆 | 当前对话状态、当天临时数据 | "用户刚输入今天午餐:米饭、鸡胸肉、沙拉" |
| 情景记忆 | 带时间戳的健康事件 | "2026-05-19 晚上跑步 5 公里,睡眠 7 小时" |
| 语义记忆 | 长期健康偏好、规则和知识 | "用户乳糖不耐受""减脂期每日蛋白目标 120g" |
| 感知记忆 | 手环数据、体重秤图片、餐盘照片 | 食物照片估算热量,睡眠曲线截图,运动轨迹图 |
推荐流程:
用户上传饮食/运动/睡眠数据
→ 工作记忆保存当前输入
→ 情景记忆记录每天事件
→ 语义记忆维护长期偏好、禁忌、目标
→ 感知记忆处理图片、音频、设备截图
→ 定期整合:把连续多天模式抽象为长期健康建议
问题三:重要工作记忆何时整合为长期记忆?
可设计自动触发条件:
- 重要性超过阈值,如
importance >= 0.75。 - 被用户明确要求 "记住"。
- 在多个会话中反复出现。
- 与长期目标相关,如健康、学习、项目计划。
- 被频繁检索或引用。
- 包含稳定偏好或关键事实,而不是一次性临时状态。
示例策略:
def should_consolidate(memory):
return (
memory.importance >= 0.75
or memory.metadata.get("user_explicit_save") is True
or memory.metadata.get("access_count", 0) >= 3
or memory.metadata.get("goal_related") is True
)
题 2:RAG 分块、MQE/HyDE 对比与嵌入模型选型
问题一:无明确标题结构的文档如何优化分块?
小说、法律条文、聊天记录等文档不一定有 Markdown 标题。此时不能只依赖 # / ## / ###,应改为 "语义边界 + 长度约束" 的混合分块。
可用边界:
- 段落边界:空行、自然段。
- 句子边界:句号、问号、分号、编号。
- 结构边界:法律条文的 "第 x 条"、合同的 "甲方 / 乙方"、小说章节中的场景转换。
- 语义边界:相邻段落 embedding 相似度明显下降。
- 长度边界:达到
chunk_size后优先在最近的自然边界切分。
算法草案:
def semantic_chunk(paragraphs, embed, max_tokens=800, overlap_tokens=120, drop_threshold=0.55):
chunks = []
current = []
current_tokens = 0
prev_vec = None
for para in paragraphs:
vec = embed(para)
tokens = approx_token_len(para)
semantic_break = False
if prev_vec is not None:
semantic_break = cosine(prev_vec, vec) < drop_threshold
too_long = current_tokens + tokens > max_tokens
if current and (semantic_break or too_long):
chunks.append("\n\n".join(current))
current = keep_tail_by_tokens(current, overlap_tokens)
current_tokens = sum(approx_token_len(x) for x in current)
current.append(para)
current_tokens += tokens
prev_vec = vec
if current:
chunks.append("\n\n".join(current))
return chunks
问题二:基础检索、MQE、HyDE 的效果差异。
以 "技术文档问答" 为例:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基础向量检索 | 快、成本低、稳定 | 用户问法和文档写法差异大时召回不足 | 明确术语查询,如 "Transformer 注意力机制" |
| MQE | 扩展多种问法,提高召回 | 多一次 LLM 调用,可能引入偏题扩展 | 模糊问题,如 "怎么提升模型效果" |
| HyDE | 用假设答案贴近文档语义,适合专业问答 | 假设答案可能带偏检索,成本更高 | 专业复杂问题,如 "为什么注意力机制适合长距离依赖" |
经验结论:
- 查定义、查术语:基础检索通常够用。
- 用户表达不清:优先 MQE。
- 问题和答案语义形态差异明显:用 HyDE。
- 高质量问答:MQE + HyDE + 重排序 + 引用来源。
问题三:三种嵌入方案选型。
| 维度 | 百炼 API | 本地 Transformer | TF-IDF |
|---|---|---|---|
| 准确性 | 通常较高,中文语义较强 | 取决于模型,合适模型效果好 | 语义弱,偏关键词 |
| 速度 | 网络延迟影响明显 | 本地推理稳定,可批处理 | 很快 |
| 成本 | 有 API 成本 | 有机器和部署成本 | 几乎无成本 |
| 离线 | 不适合 | 适合 | 适合 |
| 维护 | 低 | 中等,需要模型管理 | 低 |
选型建议:
- 生产中文知识库:优先百炼 API 或同等级云端 embedding。
- 私有化 / 敏感数据:本地 Transformer。
- Demo、兜底、关键词强匹配:TF-IDF。
- 最稳妥的工程方案:统一 embedding 接口,按可用性降级:云端 API → 本地模型 → TF-IDF。
题 3:智能遗忘、记忆归档与敏感信息删除
问题一:设计智能遗忘策略。
基础遗忘只看重要性、时间或容量,容易误删 "旧但关键" 的记忆。更合理的是综合评分:
retention_score =
importance * 0.40
+ normalized_access_frequency * 0.25
+ recency_score * 0.20
+ goal_relevance * 0.10
+ user_pin * 0.05
当 retention_score 低于阈值时进入遗忘候选;如果只是长期不用但仍可能有价值,应归档而不是直接删除。
示例实现思路:
def retention_score(memory, now):
age_days = (now - memory.timestamp).days
recency = math.exp(-0.05 * age_days)
access = min(memory.metadata.get("access_count", 0) / 10, 1.0)
goal = 1.0 if memory.metadata.get("goal_related") else 0.0
pinned = 1.0 if memory.metadata.get("pinned") else 0.0
return (
memory.importance * 0.40
+ access * 0.25
+ recency * 0.20
+ goal * 0.10
+ pinned * 0.05
)
问题二:记忆归档机制如何设计?
归档不是删除,而是冷热分层:
- 热存储:工作记忆、近期情景记忆、高频语义记忆,保留在内存、Qdrant、Neo4j 活跃集合中。
- 温存储:低频但仍可能检索的长期记忆,降低检索优先级。
- 冷存储:长期不用但有价值的记忆,转移到压缩文件、对象存储、归档表或低成本数据库。
与四类记忆集成:
| 记忆类型 | 归档策略 |
|---|---|
| 工作记忆 | 一般不归档,过期后整合或删除 |
| 情景记忆 | 超过时间窗口后归档原始事件,保留摘要和索引 |
| 语义记忆 | 保留核心实体关系,归档低置信或低访问知识 |
| 感知记忆 | 原始大文件进冷存储,保留缩略图、向量和元数据 |
恢复机制:
用户查询
→ 热存储无结果或低置信
→ 搜索归档索引
→ 命中后恢复到温/热存储
→ 更新 access_count 和 last_accessed
问题三:敏感信息删除是否只删数据库即可?
不够。敏感数据可能存在多个副本:
- SQLite 文档表。
- Qdrant 向量与 payload。
- Neo4j 实体和关系。
- embedding 缓存。
- LLM 请求日志。
- 应用日志。
- 备份和归档文件。
彻底删除方案:
- 为每条记忆维护全局
memory_id和数据血缘索引。 - 删除 SQLite 原文记录。
- 删除 Qdrant 中对应向量和 payload。
- 删除 Neo4j 中由该记忆创建的实体关系;若实体被其他记忆引用,则只删除该来源边。
- 清理 embedding 缓存、检索缓存和报告文件。
- 对日志做脱敏,避免记录原始敏感文本。
- 对备份执行到期删除或加密擦除策略。
- 生成删除审计记录,但审计记录不能包含敏感原文。
题 4:智能学习助手中的 RAG 与 Memory 协同
问题一:什么时候优先 RAG,什么时候优先 Memory?
优先 RAG:
- 用户问文档内容、专业知识、外部事实。
- 问题需要引用来源。
- 问题涉及最新资料或上传文件。
- 用户问 "文档里怎么说""这篇 PDF 的结论是什么"。
优先 Memory:
- 用户问自己的历史行为、学习进度、偏好。
- 问题和会话上下文强相关。
- 用户问 "我之前问过什么""我学到哪里了"。
- 需要个性化建议。
智能路由机制可以先做意图分类,再决定检索路径:
def route_query(question):
if mentions_document(question) or asks_external_knowledge(question):
return "rag"
if asks_user_history(question) or asks_preference(question):
return "memory"
if asks_personalized_recommendation(question):
return "hybrid"
return "hybrid"
更稳的方案是并行检索后再融合:
问题
→ 意图识别
→ RAG 检索外部知识
→ Memory 检索用户状态
→ 按问题类型加权
→ 构建最终上下文
→ LLM 生成回答
问题二:如何扩展学习报告?
当前 generate_report() 主要统计文档数、提问数、笔记数和记忆摘要。更智能的报告应包含:
- 学习轨迹:按时间整理加载文档、提问、笔记、复习记录。
- 知识点覆盖:从语义记忆中抽取概念,统计学习频次。
- 知识盲点:识别多次提问但低置信回答、重复搜索、错误笔记或没有形成笔记的主题。
- 学习节奏:分析会话时长、问题密度、复习间隔。
- 推荐内容:用 RAG 搜索相邻主题,用 Memory 匹配用户目标。
- 行动建议:输出下一步学习计划。
需要用到的能力:
| 能力 | 用途 |
|---|---|
| 情景记忆 | 还原学习时间线 |
| 语义记忆 | 抽取概念掌握情况 |
| 工作记忆 | 汇总当前会话任务 |
| RAG 检索 | 推荐相关文档片段 |
| MQE/HyDE | 对薄弱知识点扩展检索 |
问题三:多用户 Web 服务如何隔离数据?
Qdrant 隔离方案:
- 简单方案:同一 collection,payload 中写入
user_id、rag_namespace,每次检索加过滤条件。 - 强隔离方案:按租户或用户创建 collection,例如
rag_user_123。 - 折中方案:按组织 / 租户 collection,用户级 payload 过滤。
Neo4j 隔离方案:
- 所有节点和边写入
user_id或tenant_id。 - 查询时强制加用户过滤。
- 对高隔离要求的租户使用独立 database 或独立实例。
- 关系边也要带来源记忆 ID,避免跨用户共享关系污染。
性能优化:
- 为
user_id、rag_namespace、memory_type建过滤索引。 - 用户集合过多时避免每人一个 collection,可按租户合并。
- 热门知识库可共享只读索引,用户私有记忆单独存储。
- 检索时先过滤命名空间,再做向量相似度。
- 缓存高频查询,但缓存键必须包含用户和命名空间。
题 5:语义记忆、知识图谱质量与图检索价值
问题一:自动实体关系抽取的准确性如何评估?
自动抽取会受文本质量、语言歧义、分词、实体边界和关系表达影响。常见错误包括:
- 实体边界错误:把 "向量数据库 Qdrant" 拆成错误实体。
- 实体类型错误:把产品名识别成人名或组织。
- 关系方向错误:把 "A 依赖 B" 抽成 "B 依赖 A"。
- 隐含关系缺失:文本没有显式动词时漏掉关系。
- 同义实体未合并:
RAG、检索增强生成、Retrieval-Augmented Generation被当成三个概念。
质量评估机制:
抽取结果
→ 实体置信度评分
→ 关系置信度评分
→ 与 schema/ontology 校验
→ 同义词归一化
→ 冲突检测
→ 抽样人工审核
→ 低置信结果进入待确认区
可量化指标:
- 实体 precision /recall。
- 关系 precision /recall。
- 同义实体合并准确率。
- 图中孤立节点比例。
- 关系冲突数量。
- 人工审核通过率。
问题二:设计一个利用 Neo4j 多跳关系的查询场景。
场景:学习助手回答 "我学习 RAG 还缺哪些前置知识?"
图谱结构:
(User)-[:LEARNED]->(Concept)
(Concept)-[:REQUIRES]->(Prerequisite)
(Concept)-[:RELATED_TO]->(Concept)
(Document)-[:COVERS]->(Concept)
查询逻辑:
MATCH (u:User {id: $user_id})-[:LEARNED]->(c:Concept {name: "RAG"})
MATCH (c)-[:REQUIRES*1..2]->(pre:Concept)
WHERE NOT (u)-[:LEARNED]->(pre)
RETURN pre.name, pre.importance
ORDER BY pre.importance DESC
纯向量检索只能找 "和 RAG 语义相似的文本",但很难稳定回答 "缺哪些前置知识"。图查询可以沿着明确关系做推理。
问题三:混合检索相比纯向量检索在哪些查询中提升明显?
图检索提升明显的查询类型:
- 关系型查询:谁依赖谁、谁属于谁、谁影响谁。
- 多跳推理:A 的前置知识的相关应用是什么。
- 路径查找:从 "Python 基础" 到 "RAG 应用" 需要经过哪些知识点。
- 约束查询:找出用户学过但未复习、且与当前目标相关的概念。
- 实体消歧:区分同名实体或缩写。
例子:
查询:我已经学过 embedding 和 Qdrant,下一步学习 RAG 还缺什么?
纯向量检索:可能返回几段 RAG 介绍文本。
图检索:可以根据 prerequisites 找出 chunking、retrieval、prompt construction、reranking 等缺口。
混合检索:先用图找知识缺口,再用向量找每个缺口的解释文档。
结论:向量检索适合 "语义相似",图检索适合 "结构关系",混合策略适合需要既理解语义又利用知识结构的场景。
