记忆、知识与可靠性外围
30 秒导读: 前五章讲的是 PraisonAI 怎么"想"和"跑"——Agent 单体、工具、LLM 层、多智能体编排、 确定性工作流。这一章讲的是托住它们的支撑层:让 agent 记得住(记忆)、查得到(知识/RAG)、 跑得稳(可靠性/安全)。这三组子系统不在主执行线上,而是被主链路"按需取用"的公共货架。 本章是导航式讲解——告诉你每个子系统解决什么问题、入口符号叫什么、在哪个文件,你可以按需下钻源码。
本章与兄弟章互补、不重复它们的内容:
- index —— PraisonAI 总览与阅读地图。
- 01-agent-chat-loop —— Agent 单体与 chat 主循环(本章的记忆/快照/doom-loop 都挂在这个 Agent 上)。
- 02-tools-and-mcp —— 工具系统(本章的 sandbox/policy/approval 是工具执行的安全闸)。
- 03-llm-layer —— LLM 层(guardrail 校验的是 LLM 的输出)。
- 04-multi-agent-orchestration / 05-agentflow-deterministic —— 主执行/编排线。
源码位置:全部在
src/praisonai-agents/praisonaiagents/下。下文所有file:line均相对该目录 (例:memory/memory.py:38)。
1. 这是什么(零基础也能懂)
一句话定义: 这一章讲 PraisonAI 里让 agent"脑能记、事能查、险能拦"的三组基础设施。
先用一个类比建立直觉。 把一个裸 LLM 想成一个很聪明、但有严重健忘症、还被关在小黑屋里的人。 主链路负责让它"开口说话",而本章这三层负责补它天生缺的能力:
| 它缺什么 | 这一层补什么 | 对应目录 |
|---|---|---|
| 记性(说完就忘,跨轮/跨会话记不住) | 记忆:短期滑动上下文 + 长期可检索存储 + 质量打分决定"什么值得长期记" | memory/ |
| 查资料的能力(不知道你私有文档里写了啥) | 知识 / RAG:把大文档切块、嵌入、按相似度召回、附引用喂回模型 | knowledge/ + rag/ |
| 危险动作的刹车与"卡死自救" | 可靠性 / 安全:输出校验、死循环检测、快照回滚、代码沙箱、策略与人工审批 | guardrails/ escalation/ snapshot/ checkpoints/ sandbox/ policy/ approval/ telemetry/ |
它们各自能做什么:
- 记忆:把一次对话里"值得留下的"信息按质量分数筛进长期库,下次任务开始时自动召回相关片段拼进上下文; 后端可换(内存 / SQLite / Mongo / mem0 / chroma),甚至支持图记忆(Neo4j/Memgraph)。
- 知识 / RAG:把任意文件读入→切块→嵌入→索引,查询时召回 + 重排 + 生成带引用编号的答案。
- 可靠性:LLM 输出过一道 guardrail 校验;检测"反复做同一件事"的 doom loop 并触发恢复;
agent 改文件前打快照,可
undo/redo/diff;要执行的代码先过静态危险扫描 + 沙箱隔离; 高危工具调用被 policy 拦下、走 approval 人工审批。
一句话直觉: 记忆是"上下文当内存、数据库当磁盘";RAG 是"给 agent 配了个可搜索、会标出处的图书馆"; 可靠性层是"在流水线上装了校验器、断路器、后悔药和安全阀"。
本节不出现底层代码。目标:完全不懂的人读完知道"这三层是干嘛的"。
2. 顶层全景(它大概怎么转)
怎么读这张图: 中间竖线是 Agent 主执行线(第 1 章),两侧/下方是本章三组支撑货架; 箭头表示"主线在某个时机取用某个货架"。三组彼此独立,可单独开关。
┌───────────────────────────────┐
│ Agent 主执行线 (ch.01) │
│ 收到任务 → 想 → 调工具 → 回答 │
└───┬───────────┬───────────┬────┘
①任务开始:召回记忆 │ │ │ ③改文件/调工具前后:安全闸
⑤任务结束:写回记忆 │ │ ②需要外部知识
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────────────────────────┐
│ 记忆 memory │ │ 知识/RAG │ │ 可靠性 / 安全外围 │
│ 短期 + 长期 │ │ knowledge│ │ guardrail 校输出 │
│ 质量打分筛选 │ │ + rag │ │ doom-loop 断路器 │
│ 多后端适配 │ │ 切块/召回 │ │ snapshot/checkpoint 后悔药 │
│ │ │ 重排/引用 │ │ sandbox/policy/approval 闸 │
└──────┬───────┘ └────┬─────┘ └───────────┬──────────────┘
│ 换后端 │ 换向量库 │
▼ ▼ ▼
in-mem/SQLite/ InMemoryVector/ 静态扫描 + 沙箱 +
Mongo/mem0/chroma 外部向量库 策略 + 人工审批
各货架一句话职责与入口:
| 货架 | 干什么 | 入口符号 | 文件 |
|---|---|---|---|
| 记忆 | 短/长期分层存取 + 质量打分 + 上下文拼装 | Memory | memory/memory.py:38 |
| 记忆后端 | 把存储抽象成可注册的适配器 | register_memory_adapter | memory/adapters/registry.py:44 |
| 知识 | 文件→切块→嵌入→索引→检索的门面 | Knowledge | knowledge/knowledge.py:53 |
| RAG | 检索 + 生成带引用的答案 | RAG / RAGConfig | rag/pipeline.py:77 / rag/models.py:188 |
| 输出校验 | 对 LLM 输出做 guardrail 判定 | GuardrailResult / LLMGuardrail | guardrails/guardrail_result.py:13 |
| 断路器 | 检测死循环并给恢复动作 | DoomLoopDetector / _is_doom_loop | escalation/doom_loop.py:87 / agent/agent.py:3070 |
| 后悔药 | 文件快照的 undo/redo/diff | undo/redo/diff | agent/agent.py:2935 |
| 沙箱 | 代码静态危险扫描 + 隔离执行 | check_code_safety / SandboxManager | sandbox/security.py:82 / sandbox/manager.py:23 |
| 策略/审批 | 高危工具拦截 + 人工审批 | PolicyEngine / ApprovalRegistry | policy/engine.py:17 / approval/registry.py:81 |
| 遥测 | 匿名使用统计 | MinimalTelemetry | telemetry/telemetry.py:78 |
3. 记 忆:记得住
这节讲什么: 一次对话里哪些信息值得留下、怎么打分、存哪、下次怎么召回。核心入口是一个 "单文件记忆管理器"类:
# 真实:memory/memory.py:38
class Memory(SearchMixin, MemoryCoreMixin):
"""A single-file memory manager covering STM/LTM/entity/user/quality-score/graph ..."""
Memory 用多重继承拼装:自身文件 memory.py 定义主实现,SearchMixin(memory/search.py:20)和
MemoryCoreMixin(memory/core.py:20)是为 向后兼容而拆出的同名方法。注意 MRO:Memory 自己定义的
store_short_term/compute_quality_score 等会覆盖 mixin 版本,所以读主逻辑看 memory.py 即可,
mixin 是历史分解的产物。
3.1 短期 vs 长期分层
- 短期记忆(STM):一次会话内的临时上下文,写入
store_short_term(memory/memory.py:509), 按search_short_term(memory/memory.py:581)召回。 - 长期记忆(LTM):跨会话的持久知识,
store_long_term(memory/memory.py:770)/search_long_term(memory/memory.py:907)。 - 另有两类专用记忆:实体记忆(命名实体的结构化信息)与用户记忆(每个用户的偏好/历史),
在类 docstring(
memory/memory.py:39-47)里列出。
这条分层的价值:不是所有东西都值得进长期库——STM 便宜可丢,LTM 要挑过。挑选的依据就是"质量分"。
3.2 质量打分:决定"什么值得长期记"
要解决的小问题: 记忆库会被垃圾撑爆。需要一个 0–1 的分数,给每条记忆定"含金量", 低于阈值的不进长期库、检索时也过滤掉。
打分公式(简化直觉,非源码):
# 示意,非源码 —— 四个子指标的加权平均
score = (completeness * 0.25 # 信息完整吗
+ relevance * 0.25 # 和任务相关吗
+ clarity * 0.25 # 表述清楚吗
+ accuracy * 0.25) # 准确吗
真实实现: compute_quality_score(memory/memory.py:471)就是这个加权平均,默认四项各 0.25、
结果 round(total, 3)。写入前由 _process_quality_metrics(memory/memory.py:1742)把分数塞进
metadata;若外部评估器直接给了 evaluator_quality,则跳过子指标直接用它。
分数怎么起作用: 检索时 search_short_term/search_long_term 接受 min_quality 和
relevance_cutoff 两个门槛(memory/memory.py:585-586、911-912),低于门槛的记录被过滤——
质量分在读这一侧发挥闸门作用。
3.3 上下文拼装:召回后怎么喂回 agent
一次任务开始时,build_context_for_task(memory/memory.py:1542)把短期 + 长期 + 实体 + 用户
四路记忆合并、去重、清洗成一段文本块,拼进 agent 的上下文。参数 include_in_output 控制这段记忆
是否真进输出(默认仅在 debug 日志开启时包含),避免污染正式回答。
3.4 多后端适配:换存储不改上层
要解决的小问题: 有人只想要内存里跑,有人要 SQLite 单机持久,有人要 Mongo/云向量,
有人要接 mem0 或 chroma。上层 Memory 不该被绑死在某个存储上。
做法是"注册表 + 适配器"。后端各实现统一协议 MemoryProtocol(memory/protocols.py),
通过注册表登记后按名字取用:
| 后端 | 注册方式 | 位置 |
|---|---|---|
| in_memory | register_memory_adapter("in_memory", InMemoryAdapter) | memory/adapters/__init__.py:37 |
| sqlite | register_memory_adapter("sqlite", SqliteMemoryAdapter) | memory/adapters/__init__.py:36 |
| mem0 | register_memory_factory("mem0", ...) | memory/adapters/__init__.py:40 |
| chroma | register_memory_factory("chroma", ...) | memory/adapters/__init__.py:41 |
| mongodb | register_memory_factory("mongodb", ...) | memory/adapters/__init__.py:42 |
注册表本体 MemoryAdapterRegistry(memory/adapters/registry.py:15)提供
register_memory_adapter(:44)、register_memory_factory(:49)、get_memory_adapter(:54)。
"适配器"直接给类、"工厂"给构造函数(mem0/chroma/mongo 这类需要外部连接的用工厂延迟构造)。
自动记忆抽取: memory/__init__.py 里以懒加载方式导出 AutoMemory / AutoMemoryExtractor
(memory/__init__.py:85,实现在 memory/auto_memory.py)——从对话中自动抽取值得记的条目,
省去手工调用 store_*。
诚实边界: 图记忆(Neo4j/Memgraph)在
Memory的 config 里以graph_store声明 (memory/memory.py:70-77),但走的是 mem0 通道,本章不下钻其实现。
4. 知识与 RAG:查得到
这节讲什么: 把你的私有文档变成 agent 能搜的知识库。PraisonAI 把这件事拆成两层:
knowledge/(索引与检索的积木)和 rag/(把检索结果组织成带引用的答案的管线)。
4.1 knowledge:文件到可检索索引
门面类 Knowledge(knowledge/knowledge.py:53)对外只暴露三个动词:
| 动作 | 方法 | 位置 |
|---|---|---|
| 把文件加入知识库 | add | knowledge/knowledge.py:410 |
| 存一段内容 | store | knowledge/knowledge.py:305 |
| 按查询检索 | search | knowledge/knowledge.py:347 |
底下的处理流水线(数据流向,从左到右):
文件 ──▶ 切块 ──▶ 嵌入 ──▶ 向量库 ──▶ 检索 ──▶ 重排 ──▶ 命中片段
chunking (embed) vector_store retrieval rerankers
流水线各节点的入口符号(都在 knowledge/ 下):
| 环节 | 关键符号 | 文件 |
|---|---|---|
| 切块 | Chunking / chunk | chunking.py:5 / :194 |
| 语料统计与索引结果 | CorpusStats / IndexResult / IgnoreMatcher | indexing.py:42 / :139 / :179 |
| 增量索引追踪(文件哈希/mtime) | FileTracker | indexing.py:326 |
| 向量存储 | VectorStoreProtocol / VectorStoreRegistry / InMemoryVectorStore | vector_store.py:57 / :164 / :239 |
| 检索策略 | RetrievalStrategy / RetrieverRegistry / RetrievalResult | retrieval.py:18 / :97 / :27 |
| 查询引擎 | SimpleQueryEngine / SubQuestionEngine | query_engine.py:210 / :248 |
| 重排 | RerankerRegistry / SimpleReranker / RerankResult | rerankers.py:79 / :126 / :18 |
设计暗线:向量库、检索器、查询引擎、重排器全走"协议 + 注册表"(每个文件都有
XxxProtocol + XxxRegistry),和记忆的适配器思路一致——都是"接口固定、实现可插拔"。
SubQuestionEngine(query_engine.py:248)值得注意:它能把一个复杂问题拆成子问题分别检索再合并。
indexing.py 里的四个类各管一段:
CorpusStats(:42)记语料统计、IndexResult(:139) 记一次索引的结果、IgnoreMatcher(:179)管忽略规则,而FileTracker(:326)按文件哈希 + mtime 做增量索引追踪(状态持久化到 JSON,跨会话复用),避免每次全量重建。四者并列、各司其职。
4.2 rag:把检索结果变成带引用的答案
knowledge/ 给的是"召回哪些片段",rag/ 负责"用这些片段生成答 案、并标清每句话的出处"。
- 管线入口
RAG(rag/pipeline.py:77):串起检索→压缩→生成的完整 RAG 流程 (同目录还有retriever.py/compressor.py/summarizer.py/budget.py等分步实现)。 - 核心数据模型(
rag/models.py):Citation(:21)——一条引用(出处 + 片段),是"可追溯"的最小单元。ContextPack(:78)——喂给 LLM 的上下文包,内含一组Citation。RAGResult(:131)——最终答案 + 其引用列表。RAGConfig(:188)——RAG 行为配置。
- 检索行为配置
RetrievalConfig(rag/retrieval_config.py:28):用RetrievalPolicy(:13) 决定何时检索、CitationsMode(:20,默认APPEND)决定引用怎么附到答案上。
一句话记住 knowledge 与 rag 的分工: knowledge = "图书馆的检索台"(找到书页); rag = "研究助理"(读完书页写出带脚注的答复)。
5. 可靠性与安全外围:跑得稳
这节讲什么: 让 agent 少犯错、犯了错能回头、危险动作有闸门。这里的子系统各管一个失败模式, 下面按"输出对不对 → 会不会卡死 → 改坏了能不能回滚 → 执行安不安全 → 该不该放行"的顺序过一遍。