
1. 学习 Agent 越往后越发现“记忆”不是功能而是架构决策我是在做系列笔记第三篇的时候踩到一个问题的。当时我搭的 Agent 已经能调用工具、能拆解任务、能在一次会话里完成多轮推理看起来什么都会。但换一个新会话来问它“你还记得我上次让你查的那几款数据库吗”它直接愣住了。它确实“读过”那部分内容但那些内容只存在于上一次对话的上下文窗口里会话一结束就全部清空。那一刻我突然意识到Agent 的能力上限根本不取决于模型多聪明而取决于它能记住多少、能调用多少外部信息。这也正是我把第四篇笔记的主题定在“用户记忆与知识库从四种存储格式到 RAG 检索”的原因。用户记忆和知识库是两种不同性质的信息载体。用户记忆回答的是“这个用户是谁、他偏好什么、我们之间发生过什么”知识库回答的是“我手上的资料里有哪些事实和内容可以用”。它们在 AI Agent 里的落点也不同用户记忆偏个性化、强更新、量不一定大知识库偏领域化、相对稳定、通常需要支持语义检索。本文这篇笔记不是泛泛讲概念我会从存储格式的选型逻辑讲起一直讲到知识库如何切片、如何向量化、如何做 RAG 检索再加上我实际调试中的失败记录。内容面向正在做 Agent、准备接知识库或者想给 Agent 增加记忆能力的开发者硬核居多但也尽量把原理讲到“小白能听懂”的程度。先说一个我的判断很多 Agent 项目做不好记忆不是因为模型不够强而是从一开始就没想清楚“记忆应该放在哪里”。所以这篇笔记的第一部分我们先不急着谈技术选型而是先把记忆本身拆开来看。1.1 上下文窗口不是记忆它只是一块临时工作台在 AI Agent 开发里最常见的误解就是把模型的 context window 当成记忆。大模型确实能在一段很长的上下文里保持连贯性但这个“连贯”有严格的边界一次会话、有限 token。一旦超出上下文窗口早期内容会被截断一旦会话关闭所有内容都不复存在。我习惯把 context window 类比成你的办公桌。桌面上能摊开很多资料方便你当下处理但办公桌不是仓库。真正的记忆系统应该是把需要长期保留的内容从桌面上归档到仓库里下次需要时再精确取回。同样AI Agent 里的记忆指的是可以跨会话持久化存储并在恰当时间被检索回来的信息。所以设计 Agent 的时候第一件事就是分清哪些内容只存在于本次会话工作区哪些内容要落入持久化存储。举个例子一个客服类的 Agent用户问“我的订单多久能到”这个查询上下文不需要长期保存但用户的姓名、所在城市、历史投诉记录、上次沟通过的结论这些都是长期记忆。如果一个 Agent 把这些全部塞进每次的上下文里很快就会被 token 撑爆如果不塞又无法回答问题。这就要靠记忆的分层来化解。1.2 我给 Agent 记忆做的三级分类用户画像、历史对话、业务资料我在实际设计记忆结构时会把需要“记住”的内容拆成三类每一类的存储方式、更新频率、检索方式都不一样。第一类是用户画像和偏好型记忆。包括用户的姓名、性别、年龄段、工作领域、偏好设定、常用术语、产品偏好甚至用户明确说过的“以后推荐内容不要包含短视频”这样的指令。这类记忆的特点是单条体积小、结构化程度高、更新不频繁、读取频繁。比如一个私人助手 Agent用户首次说过“我在上海做 UX 设计”后面每次对话都应当默认这个背景不需要再问一次。第二类是历史会话与关键决策型记忆。比如用户上次和你讨论了哪些话题、最终选型是什么、哪些方案被否定了、当时的理由是什么。这类记忆的特点是体积不断增长、存在明显的时序关系、有时候需要按语义去检索。用户可能隔了一个月问“上次我们说的那个低代码平台你帮我再看一眼”这里就不能简单地拉出最后一条聊天记录而要能从历史中找到“低代码平台”相关的上下文片段。第三类是业务知识和资料库型记忆。这块就是通常说的知识库常见来源有产品文档、培训手册、行业报告、历史工单、企业 wiki。这类内容不属于某个用户的隐私偏好而是 Agent 回答问题时的事实依据。它通常比较稳定但量往往很大需要支持“用户问一句自然语言Agent 能定位到文档中若干片段并组织答案”。把这三类分开是我后来做记忆系统最受益的一步。因为如果你把它们混在一起存比如把所有聊天记录和文档全部灌进同一个向量库不做任何标签和分区那检索时返回的片段会来自各个层面跟用户意图对不上是常态。分开之后每条数据就带上了它自己的“身份”召回时能精确限定范围。1.3 知识库本质上是可替换的外部记忆这也是 RAG 存在的理由我自己的理解是知识库也是一种记忆只不过它更像是“外部硬盘”而不是“大脑内部的神经元连接”。模型参数里其实也封装了一部分知识那是它预训练阶段学到的但那是静态的无法针对你的业务做即时更新也无法精确追溯来源。知识库存在的意义是把模型的知识与业务库的知识解耦让 Agent 在面对领域内问题时可以实时去查阅外部资料。这就是 RAG检索增强生成的底层逻辑。Agent 收到用户提问先从外部知识库中找到若干最相关的片段再把问题和片段一起交给模型让模型基于给定资料来生成回答。这样做有三个直接好处一是业务知识可以随时改文档来更新不用重新训练模型二是回答可以附上引用来源可追溯三是能明显减少模型凭记忆“胡编”的风险只要检索准确回答就更可控。而 RAG 的效果上限几乎完全取决于两件事知识库里资料的组织方式和检索质量。组织方式出问题检索结果就乱检索质量不达标再强的模型也会被噪音上下文带偏。这两种问题我都实际遇到过后面我会详细展开。如果你现在正准备给 Agent 加知识库我的建议是先读完全文再动手很多坑能提前避开。2. 四种存储格式逐个过一遍JSON、关系表、向量库、图库分别装什么接下来进入这节笔记的硬核部分。标题里写了“四种存储格式”我先说明白是哪四种JSON/文本文件这类轻量文件存储、关系型数据库、向量数据库、图数据库。它们不是竞争关系而是应对不同记忆类型的工具。我在给某一种记忆选择存储时会问自己三个问题这些数据需要按语义查找吗需要频繁更新吗需要在实体之间做多跳关系遍历吗答案不同落库选择就不同。2.1 JSON 与文件级存储适合固定的用户画像别存对话流水先看最简单的 JSON。很多 Agent 框架里有一个概念叫 memory file典型存在方式是一个 JSON 文件里面存一组结构化字段比如用户的基本信息、个性设定、偏好设置。不同框架的实现细节不完全一样但原理很接近Agent 启动时读取这个文件形成系统提示的一部分对话过程中如果用户提出了新的偏好设定Agent 会通过工具调用更新这个文件。JSON 存储的优势非常明显可读性好、实现起来几乎零成本、方便调试。你可以在不依赖任何数据库的情况下直接打开文件看内容看 Agent 到底记住了什么。对个人项目或者单用户场景用户画像这种本身就很适合用 JSON 存。下面给出一个最小的记忆文件示例{ user_id: u_2024001, profile: { name: 林一, city: 上海, job: 产品经理, industry: 企业服务, level: 老用户 }, preferences: { answer_style: 简洁干练少讲废话, topic_filters: [不做短视频推荐], tools_frequently_used: [sql, excel] }, updated_at: 2025-06-18T21:40:0008:00 }这样一份文件注入到 Agent 的 prompt 里比让用户每次重新介绍自己高效得多。但 JSON 这种格式有明确的边界它不适合存随时间持续增长、需要按语义查找的对话历史。原因很好理解。第一JSON 是按整体加载的如果对话历史有几万条记录全放里面一次读取就会把文件撑大每次注入都消耗大量 token第二JSON 无法做条件过滤取一段记录也要读全量性能会越来越差第三如果多个请求同时更新同一份文件容易出现写冲突。所以你如果用 JSON 存记忆我建议只装那些“一次性写入、低频更新、单条体积小”的用户画像数据并且保留 updated_at 这类时间戳字段便于理解记忆的新鲜度。一旦发现某个文件超过几百 KB 还在频繁读取就该考虑迁移到更合适的存储了。2.2 关系型数据库用户画像和高频业务记忆的稳定底座对需要支持多用户、有权限隔离、需要按字段条件查询的 Agent 来说关系型数据库是很自然的选择。拿 PostgreSQL 举例我们可以为记忆设计一张很直接的表CREATE TABLE user_memory ( id BIGSERIAL PRIMARY KEY, user_id VARCHAR(64) NOT NULL, memory_type VARCHAR(32) NOT NULL, -- profile / preference / history_summary / etc content TEXT NOT NULL, metadata JSONB DEFAULT {}, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX idx_user_memory_user_type ON user_memory (user_id, memory_type);这张表把用户画像、偏好规则、历史摘要都作为记录存起来每一条都带 user_id 做隔离。这样做的好处是支持百万级甚至更高的用户量更新某一项偏好时不需要重写整个用户文件而且可以用条件过滤精确地取数据。比如 Agent 收到用户说“以后我的邮件回复语气要更专业”你只需要 UPDATE 一条记录而不是把整份记忆重写一遍。很多知名的开源知识库项目比如 Dify 和 AnythingLLM看起来像是有自己独立的存储体系其实它们的底层配置数据、文档记录、会话记录依然依赖 PostgreSQL 这类关系型数据库。如果你深度使用过这些项目就会发现它们都会建议你提供数据库连接串。这也说明了一个趋势复杂的 Agent 场景不会只用单一存储关系表承担的是元数据和业务结构化数据向量承担的是语义检索数据。关系型数据库不仅仅适合存用户画像。如果你的业务场景要求“用户最近一次选择的模型”“用户知识库的权限名单”“历史会话的摘要条目”这类明确的最新状态或可枚举查询关系表都是更可靠的方案。有一点要特别提醒不要在一个 PG 表里塞 BLOB 类型的原始大文本然后每次全字段读取那样性能会很难看。把长内容单独放需要的时再取。2.3 向量数据库为“语义相似”而生的检索层但不是万能库向量数据库是 RAG 架构里的核心组件。它做的事情是把一段文本通过嵌入模型转换成一个高维数值向量然后存储起来查询时用同样的嵌入模型把问题变成向量再在库里做相似度计算召回最接近的片段。为什么需要它因为传统数据库是按字段精确匹配的它处理不了“用自然语言找相似含义”的问题。比如你的知识库里有一段话写的是“服务有效期为购买之日起 12 个月到期前 30 天可在控制台续费”用户问的是“我的会员还能用多久”SQL 的 LIKE 查询根本匹配不到“将会员”和“有效期”这两个不同表述但向量相似度可以做到。这就是语义检索的价值。常见的向量数据库包括 Qdrant、Milvus、Chroma、Weaviate以及 PostgreSQL 的 pgvector 扩展。如果是个人学习项目Chroma 或者 pgvector 都够用如果是生产并行度高的项目Milvus 和 Qdrant 会更合适。但我前面也说了向量数据库不是万能库它不适合做精细的事实同步和状态管理。假设用户在个人设置里改了自己的称呼这属于单条精确更新你不需要也没有必要把它变成向量再召回。更合理的做法是把用户最新状态放在缓存或关系库里向量库只存那些“真正需要靠语义捞回来”的历史记忆片段。这里有一条很重要的工程原则能用精确查询解决的就不要使用语义检索否则召回结果会不可控。使用向量数据库还有一个很多人忽略的细节就是metadata 过滤。你不能把不同用户的历史记忆全部向量化之后不做区分地放进同一个 collection否则用户 A 提问时很可能会召回用户 B 的私密内容。正确做法是给每条向量记录附上 user_id、timestamp、memory_type 之类的元数据查询时先做元数据过滤再做语义相似度检索。这是向量数据库落地的安全红线。2.4 图数据库当记忆需要“顺着关系走”的时候才值得上第四种格式是图数据库代表产品是 Neo4j。它的数据模型是节点和边特别适合表达实体之间的多跳关系。举个例子如果 Agent 服务的是一个知识管理场景用户有“项目 A”“项目 B”每个项目关联了不同的人员、技术栈、供应商人员之间还有协作关系这个时候如果想回答“哪些供应商同时服务于客户 X 和客户 Y 的项目”这类关系型问题图数据库会比向量库和关系库都更自然。但图数据库在大多数 Agent 项目里的优先级并没有那么高。因为它引入的复杂度较高查询语言 Cypher 也不是所有团队都熟悉。我自己的判断是如果应用的核心是“实体关系密度高、需要通过关系链进行推理”的知识图谱场景才值得引入图数据库如果只是把几份文档切片做问答那向量库就够了不需要为了“像图谱”而强行建模。一个比较现实的做法是复合使用向量库承担语义召回关系表承担状态和权限图数据库在业务出现明确的关系探索需求时再单独建设模块。对于刚开始学习 Agent 的开发者我的建议是先驾驭好 JSON/关系表 向量库的组合图数据库放在后面遇到真实场景时再学效果会更好。2.5 四种格式的选择参考表为了便于后面查我把四种格式的选择思路整理成下面的表。每次要“存一种记忆”的时候对照这张表就行。存储格式适合存放的记忆内容检索方式典型代表不适合的场景JSON / 文件单用户画像、简单偏好、Agent 配置整体加载Memory File 类机制高并发、持续增长、语义检索关系型数据库用户画像、历史摘要索引、权限信息、知识库元数据SQL 条件查询PostgreSQL、MySQL大段非结构化文本的语义检索向量数据库对话历史片段、文档切片、需要按语义召回的内容相似度检索 元数据过滤Qdrant、Milvus、pgvector精确状态更新、业务事务处理图数据库实体关系图谱、多跳关联查询图遍历Neo4j通用知识文档问答、低关系密度场景这张表不是死的。很多项目最终都会走向混合存储用户画像存关系表、细碎偏好用 JSON 缓存、历史对话按摘要进关系表同时切片进向量库、事实性资料统一进 RAG 知识库。理解了每种格式的边界再组合使用心里就有底了。3. 知识库接入实操从切片、向量化到检索的完整链路说完存储选型现在我们进入知识库的具体实现。不管你是从零写代码还是用 Dify、AnythingLLM、Coze 这类平台工具底层跑的都是同一条流水线源文档采集、清洗、切片、向量化、写入向量库、查询时做召回。很多人以为知识库就是“把 PDF 传进去就能用了”这是误解。传文件只是第一步切片和向量化的质量决定整个 RAG 系统的表现。3.1 为什么做检索增强而不是微调模型在动手前先回答一个绕不开的问题要给 Agent 注入业务能力为什么选 RAG 而不去做模型微调我的观点是两者解决的问题不同。微调适合改变模型的行为风格和固定输出格式比如让模型学会用某种语气、某种领域术语的说话习惯但如果要注入的是持续更新的业务事实比如产品手册、售后政策、最新报价微调就很不划算了。一是文档业务更新频繁每次更新都重新花钱训练不现实二是微调后的事实仍然可能出错模型会一本正经地编造不存在的条款三是微调结果不可解释无法告诉用户“这句话来自哪份文档”。反之RAG 的机制是外挂知识库。上线之后想改知识内容只需要替换对应的文档片段不需要动模型本身。这种解耦对业务非常重要。还有一个容易被忽略的点RAG 能提供引用来源。在企业场景里客服回答用户“根据某政策第几条”能不能点开溯源决定了这套系统能不能真正落地而微调模型很难做到这一点。3.2 切片策略粒度大小直接决定召回质量知识库处理的第一个关键动作是切片。切片就是把长文档拆成若干段。切片长度会直接影响召回效果切得太长一段内容包含多个主题向量化后语义被平均化查询召回时片段回答问题不精准还会浪费上下文 token切得太短单个片段信息量不足模型拿到片段后找不到完整上下文同样回答不好。常见的切片策略有几种我实际用下来各有适用场景。第一种是按固定长度切片比如每 512 个 token 一段适合内容结构比较简单、段落都不太长的文档。第二种是按文档结构切片比如优先保留 Markdown 的标题层级把同一个二级标题下面的所有内容作为一个大段如果太长再按小标题拆。这种方法对操作手册、API 文档这类层级清晰的文本效果很好。第三种是递归切分比如 LangChain 里的 RecursiveCharacterTextSplitter按分隔符优先级逐层尝试在尽量不破坏语义的前提下控制长度。这里特别想强调一个工程细节重叠overlap。切片时通常会给相邻片段之间设置一小段重叠区比如每段 500 token重叠 50 token。这样设计是为了避免一句话在切片边沿被拦腰斩断从而保证查询时能拿到完整的语义。我在实际测试中发现不设置重叠时知识的召回率会明显下降尤其遇到长句、引用格式的文本更是如此。建议你项目中至少要保留 5% 到 10% 的重叠比例具体数值根据文档的句子长度微调。还有一类更高级的做法叫父子切片。做法是生成两层索引父块是大段落子块是小片段。检索时先召回小片段的向量但最终把该小片段所属的父级段落提交给大模型。这解决了“片段虽准但上下文不够”的矛盾。可以把父块理解成章节把子块理解成章节里的一个重要句子查询先定位句子回答却带着整个章节的逻辑去生成。在我处理过的高质量长文知识库中父子切片的综合效果是最好的尤其文档的推理链路跨越多个自然段时。3.3 向量化操作细节选对嵌入模型保存元数据切片完成之后每一段文本会经过嵌入模型变成向量。嵌入模型的选择要结合你内容的主要语言。如果你的知识库以中文为主尤其建议选择对中文支持更好的模型比如目前常用的 bge-m3、m3e 系列以及各家云厂商提供的中文向量模型如果知识库本身就是英文技术文档用 OpenAI 的 text-embedding-3-small 这类通用模型也够用。我在对比测试中观察到中文长尾词、口语化表达在不同嵌入模型上的表现差距很大上线前建议用你自己的知识库做一组 mini 测试不要只看宣传指标。向量化还有几个容易踩的坑。第一模型输入通常有 token 限制切片总长度必须控制在限制以内所以前面切片参数不能拍脑袋定太大。第二调用嵌入接口时建议做批量处理而非逐条调用既能减少网络开销也能避免触发服务方限流。第三插入向量库的每条 record 都要带上必要的 metadata至少包括文档 ID、切片 ID、来源名称、用户 ID如果是用户私有知识库、更新时间这样后面才能做过滤。没有 metadata 的向量库就像一堆没有标签的档案后期想按来源筛选或者做权限隔离都无从下手。向量库写入完成之后知识库的索引工作就算告一段落。实际跑起来你会看到真正影响使用体验的还在于查询端的检索和重排逻辑。这里牵出 RAG 链路中最重要的一个判断拿什么标准决定“哪些片段是用户问题的答案依据”。如果只是单纯比对问题和所有切片的向量相似度取 TopK 直接拼给模型很多场景是不够的问题就出在语料的碎片化和向量相似度对“精确匹配”的天然弱势上。3.4 平台工具越方便越要理解背后的检索设置Dify、AnythingLLM、Coze 这类平台大大降低了知识库的接入门槛。尤其 Dify 的知识库流水线做得相当直观你上传文档后可以选择分段规则、索引方式、检索模式还提供召回测试工具。但我提醒一句用平台不等于不用理解原理。这些平台只是把链路封装了它内部的切分设置、检索模式选错依然会导致模型回答质量差。如果在 Dify 里搭建知识库我建议重点关注三个设置。一是分段设置默认分段规则很可能不适合你的文档要根据文档结构改成“按标题层级切分”或者更合适的规则。二是索引方式对于需要高质量回答的知识库建议开启高质量模式使用 Embedding 模型而非关键词索引。三是检索模式从“向量检索”切换成“混合检索”然后配合 Rerank 模型会明显更稳。不要怕 Retrival 测试麻烦把用户经常问的问题拿出来逐条跑召回测试比上线后再返工高效得多。4. RAG 检索不准我从召回和重排两个环节逐个排查知识库搭好之后最常收到的反馈是“文档我已经传进去了但 Agent 回答还是不对。”这里边有相当一部分问题出在检索环节而不是模型本身太笨。我把自己调 RAG 的过程拆成一条完整的排查链路希望能在这篇笔记里帮各位复现。你可以对照着检查用户提问之后系统到底检索出了什么片段片段是否真的跟问题相关这些片段被注入提示词后信息之间是否冲突回答最终有没有忠实于给定材料4.1 先定位问题出在哪一环不要一上来就怀疑模型有一次我需要让 Agent 查一份内部技术规范文档已经切好了向量库也写进去了但无论用户怎么问回答都差一截。比如用户问“服务超时时间默认是多少毫秒”Agent 给出的答案来自另一段讲客户端重试策略的内容虽然两个片段离得不远但答非所问。我当时的第一反应是换更强的模型结果换完之后问题依旧。后来我打开知识库的召回测试面板逐条查看查询召回的前几个片段才发现问题出在切分上原始文档里“服务超时”“重试策略”“连接池大小”这些不同主题的配置项在源文件里是逐条排列的我的语义切分器把它们整体切成了一大段导致一个向量包含了好几个配置项。查询“默认超时时间”时召回的片段虽然关联到“超时”两个字但实际文本同时包含大量重试策略细节模型看多了无关信息就被带跑偏了。从此我养成了一个习惯无论用什么 RAG 框架先做召回测试再讨论生成结果。Dify 的知识库里自带“召回测试”AnythingLLM 也提供对话前检索的可视化展示。如果发现召回的片段本身不相关那是检索和切片的问题如果召回片段已经足够相关但回答仍然不对这时才应该去检查提示词和模型参数。不要在还看不清召回结果的时候就去调 prompt那样只会越调越乱。4.2 召回层优化混合检索、查询改写与重排序在召回层面向量相似度检索不是万能的。它的弱点在于对专有名词、缩写、数字敏感。比如用户的问题里包含具体型号“XTR-330”但文档里全称写的是“XTR 330 工业控制器”两者语义接近向量可能会漏掉或匹配不准此时传统的关键词检索往往有奇效。所以业界的主流方案不是二选一而是混合检索。混合检索的做法很简单把向量召回 TopK 结果和 BM25 关键词召回 TopK 结果合并起来使用某种融合算法比如 RRFReciprocal Rank Fusion合并排序取综合最优的若干片段。这样做的好处是两条路互补向量负责理解语义关键词负责精确捕捉特殊符号和编号。尤其在技术文档、产品型号、法规条文这类充满精确实体词的知识库里混合检索几乎是必须配置。有的场景还需要额外的查询改写。用户提问的口语化程度很高比如“上次那个事情后来怎么样了”这种问题直接拿去向量检索效果基本上不会好。需要在检索之前让 Agent 结合会话上下文把问题改写成独立自足的描述比如“我们上次讨论的供应商合同审批流程后来怎么样了”。现在不少 Agent 框架都支持这种查询改写模块配合一个简单的大模型调用就能实现。改写消耗的那一点延迟在召回质量提升面前完全值得。召回合并之后还有一个值得加上的组件是重排序Rerank。先用较轻量的方式召回 20 到 50 个候选片段再用专门的 Rerank 模型做精细排序取前 3 到 5 个提交给大模型。Rerank 模型直接对“查询-文档片段”的匹配程度打分准确率通常比单纯向量相似度更可靠。中文场景里可以考虑使用 bge-reranker 系列也可以接入云厂商提供的 Rerank API。实际测试下来Rerank 能明显把“正确的答案片段”往前推它带来的提升往往比换更强的大模型更直接。4.3 索引层容易忽略的脏数据问题除了检索算法本身索引层的质量同样重要。你清洗过的文档、切好的切片是不是真的干净直接影响最终效果。这里的“脏数据”有三种情况比较典型。第一种是重复文本。同一份知识被上传了多次导致向量库里存在大量重复片段召回时把同样的内容重复占住几个位置挤压了其他有效片段的空间。处理方式是在写入前做去重可以用 MinHash 或 SimHash 做文本相似度去重也可以在写入时检查文档标题与内容哈希。第二种是坏切片。有些 PDF 转出来的文本段落错乱、或者大段代码和日志混在一起会被切出一些无意义的片段。需要在文档导入时增加清洗逻辑按源文件的实际情况过滤掉那些明显不属于文档正文的内容。第三种是缺少版本意识。文档更新后旧切片没有失效新旧内容同时存在于向量库里。当新旧信息矛盾时模型会优先采信哪个片段完全不可控。这时的对策是每条向量记录都带 version 或 updated_at 元数据查询时或写入时主动过滤掉旧版本。我对知识库项目的一个基本态度是先保证进库资料的干净再谈检索算法的优化。垃圾进、垃圾出这句话放到 RAG 链路上被展现得特别彻底。你可以在重排模型上花很多精力但如果切片里混着大段无关代码再好的重排也无法挽回。4.4 如何衡量一个知识库检索系统的效果说到评估这也是我在热词里看到很多人问的“RAG 知识库指标有哪些如何理解各指标”。我给自己的项目做评测时通常会把指标拆成检索层的指标和生成层的指标。检索层最常看的是召回率Recall和命中率Hit Rate。命中率是 TopK 里是否存在至少一条正确片段的比例可以理解为“系统有没有把正确答案相关的内容找出来”召回率更严格看的是正确答案片段有多大比例进入最终结果。重排的效果好不好可以直接看 MRRMean Reciprocal Rank即正确答案的排名位次的倒数均值MRR 越高说明正确答案排得越靠前也就越容易被模型提取到。我自己做回归测试时会先准备 50 到 100 组“问题-期望片段来源”的标注集跑一遍管线算出这几个指标用数字判断优化是否有提升。生成层指标则需要看回答的忠实度Faithfulness和答案相关性Answer Relevance。忠实度指的是生成内容是否严格基于召回片段有没有自己编造事实答案相关性是最终答案对用户问题的覆盖程度。市面上也有 RAGAS 这类开源评测框架能自动对这些维度打分但你不要完全相信自动打分器的结论关键用例最好还是人工看一下。我的体会是自动评测适合做回归和趋势监控人工评测适合做上线前的一次性把关二者配合使用效果最好。5. 落到一个 “用户画像 对话记忆 知识库” 的三层结构实例前面四节把存储和 RAG 拆开讲了一遍。这一节我想把它们整合到一个具体项目里做一个可以直接参考的原型设计。场景设定我是一个面向企业用户的 AI 助理用户每天会问产品问题、让助手帮忙查文档、提需求我需要让这个 Agent 做到三件事记住用户的身份和常用偏好能够回溯我和用户之间讨论过的历史结论能够在回答当前问题时引用公司最新版的产品文档。5.1 数据结构设计一张画像表 一张历史记忆表 一个知识库集合先看数据层。用户画像用关系表直接存结构化字段这是精确信息的主要来源。历史记忆则既存摘要又存向量。为什么不能只存摘要因为摘要压缩了太多细节当用户需要追问上次的某个细节时只有向量库能把最原始的表达捞出来。我建议的做法是关系表里存会话摘要同时把会话内容切段后写入向量库每段带上 user_id 和 session_id。知识库就更直接了一个独立的 Collection 专门装业务文档切片不区分用户但区分文档版本。代码层面的最小实现可以这样定义# 伪代码用于说明链路组合方式 from typing import List import json class AgentMemory: def __init__(self, db, vector_store, embedder): self.db db # 关系型数据库保存用户画像和历史摘要 self.vector_store vector_store # 向量库保存对话历史片段与知识库切片 self.embedder embedder # 嵌入模型 def get_user_profile(self, user_id: str) - dict: row self.db.query( SELECT profile FROM user_memory WHERE user_id ?, user_id ) return json.loads(row[profile]) if row else {} def retrieve_knowledge(self, query: str, top_k: int 3) - List[str]: # 从通用知识库 Collection 里召回并按元数据过滤版本 results self.vector_store.search( collectionproduct_docs, query_vectorself.embedder.encode(query), top_ktop_k, filter{doc_version: 2025.06} ) return [r[content] for r in results] def retrieve_history(self, user_id: str, query: str, top_k: int 2) - List[str]: results self.vector_store.search( collectionchat_history, query_vectorself.embedder.encode(query), top_ktop_k, filter{user_id: user_id} ) return [r[content] for r in results]看到没有关键点在于把两类 Collection 分开产品文档是公共知识不带 user_id 过滤对话历史是私有的必须带 user_id 过滤。这是一个安全性的分离绝对不能混。5.2 运行时的工作流程先生成提示词再检索最后带引用输出当用户发来一条消息时完整的处理流程是这样的。第一步从关系表取出用户画像组装成系统提示词的一部分。这里要注意控制提示词的长度画像信息本来就不长全部注入问题不大。第二步对用户输入做一次判断识别它到底属于“查资料型”还是“回顾历史型”还是两者兼有。比如“帮我对比一下我们选购的那款软件和企业版的功能差异”就需要同时检索知识库和该用户的历史会话。第三步执行混合检索知识库召回结果、用户历史召回结果、如果有必要的话再把用户最新的几条订单记录从关系库取出来。第四步对召回片段做重排把最相关的 3 到 5 个片段放进最终的上下文。第五步提示词里明确告诉模型“你在回答时只能依据给定的上下文片段如果答案不在上下文里就明确说不知道”。最后还要把知识来源附加在回答后面让用户知道结论来自哪份文档。我在学习过程中也总结了一个简化的判断口诀简单状态找关系库开放问答找知识库相似经历找历史向量库。当用户问到“我上次大概提了个什么需求”这种需要语义匹配历史的时候才值得去向量库里捞如果只需要知道“用户的套餐等级”这样的精确值直接查关系表就够了完全不必绕道向量检索。5.3 关于“要不要给每句话都做长期记忆”的取舍做多了之后我对记忆和知识库有了一个新的认知记忆也是有成本的。这里的成本不只是存储费用更重要的是每次请求都要从记忆候选里挑出真正有用的内容挑不好就会污染上下文。如果你想给 Agent 加长期记忆我的建议是不要什么都记只记那些极可能在未来被复用到的信息。例如用户主动表达的偏好、历史对话的阶段性结论、关键决策的理由而一句“今天天气不错”之类的寒暄该丢就丢。我自己现在常用的策略是会话进行中先在工作区短暂保存完整对话等到一个业务单元结束时做摘要抽取抽取出来的结论存进关系表或向量库对于用户明确纠正过的表述例如“我不喜欢这种详尽的回答风格”则作为高优先级偏好写入用户画像并且在系统提示词里固化。这种分频率的记忆写入方式既保证记忆不过期又不会因为每条消息都写库而造成性能浪费。另外值得留意的还有数据过期问题。用户画像不是永远不变的。用户说了一句“我现在开始做跨境电商了”之前在互联网行业的画像就需要更新。所以系统设计时要允许记忆被覆盖或者标记为过时可以采用时间戳加置信度的方式例如画像字段带 last_updated抓取检索结果时优先返回新的偏好。对知识库来说同样如此新版本的文档上线后旧版本就应该被下线不让 Agent 引用已经失效的资料。最后说一个我踩过很多次之后的体会加入用户记忆和知识库之后Agent 的调试复杂度会上一个台阶。以前你只需要调 prompt现在你要同时判断问题改写、检索召回、重排结果和上下文拼接每一个环节都可能出错。所以我的习惯是给所有关键环节都打印结构化的日志记录下“用户原始问题、改写后的问题、召回的 Top 片段、每一条片段的得分、重排后的排序、实际注入的上下文、最终回答”。排查问题的时候这些日志能让你少走很多弯路。在这个系列的学习过程里我明显感受到所谓 Agent 能力本质上是把记忆、知识这些碎片化模块按正确的方式组装起来而组装用的胶水就是你对自己数据模型的深入理解。