ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Agent记忆分层架构:文件、数据库、RAG到知识编译的工程实践

2026/9/26 23:21:17 拓冰建站 浏览量
Agent记忆分层架构:文件、数据库、RAG到知识编译的工程实践 开发过Agent的兄弟们应该都有同感模型本身没有记忆会话一关它刚才还记得的用户偏好、业务参数和之前聊过的上下文全都会消失。标题里“Agent记忆与知识库”这六个字恰好点出了一个最扎心的问题——我们在用人类的标准期待模型却给它配了一个只有短期工作记忆的脑回路。文件、数据库、RAG、知识编译这四个层次并不是要你全都要而是给你一套组合思路先把Agent的“记性”分成不同类型再为每种记忆选择最合适的存储方案。这篇内容适合正在做Agent项目、被上下文窗口和长期记忆折磨的开发者也适合那些刚接触知识库、想知道RAG和数据库到底怎么配合的产品技术人员。我会从最原始的文件层讲起一直讲到知识编译这个很多人还没注意到的跃迁最后给出一套我实际跑通的分层记忆架构以及具体的目录、表结构和调用顺序让这篇文章不只是理论而是可以直接“抄作业”的工程笔记。1. 先想清楚一个问题Agent“记不住”到底缺什么大部分Agent开发者的第一反应是“把上下文窗口调大”。说实话我自己也走过这个弯路一开始做客服型Agent想着把claude或gpt的context window拉到最大把所有历史记录全塞进去。结果模型还没开始回答光token费用就把我吓住了而且上下文一长老模型会间歇性“忘记”早些时候的信息有时候还会被一段无关的闲扯带偏。1.1 上下文窗口只能是工作台不是仓库把上下文窗口比作你的办公桌特别合适。桌子上摊开的资料越多你找文件越慢还会被无关信息干扰。真正靠谱的做法是桌子上只放当前这一步需要的东西其余全部收进抽屉、档案柜和数据库里。Agent的内心状态同样如此——对话历史是短期工作集回答完一个问题就可以归档真正需要长期保留的是用户偏好、业务规则、关键事件这些相对稳定的信息。我见过不少项目用“全量对话当作记忆”这种设计的问题在于对话史是噪声密集型数据里面有大量“嗯嗯”、“好的”、“麻烦你看一下”之类的填充语。你把它塞给模型等于让模型在一堆咳嗽声里找音乐。记忆系统首先要做的是“信息筛选”而不是“信息存储”。1.2 把记忆分成四层之后很多争论就消失了我习惯把Agent记忆分成四个层次对应不同的更新频率和访问方式记忆层人类类比实现方式典型数据更新频次上下文窗口桌面便签纸Prompt拼接当前会话、当前任务毫秒级文件层手写笔记本Markdown/JSON文件长期偏好、会议纪要、决策记录低数据库层档案橱柜SQLite/MySQL/PostgreSQL订单状态、用户余额、实体关系中高RAG层图书馆检索向量库全文检索引擎产品文档、帮助手册、非结构化语料低知识编译层教科书与操作手册规则库、小型模型、决策树高频问法、业务逻辑、标准流程低但编译成本高很多争议比如“该用向量库还是数据库”、“是不是应该把所有东西都放进RAG知识库”本质上是没分清自己存的是哪种记忆。偏好型内容用文件最快频繁变动的结构化事实用数据库最稳海量非结构化资料用RAG最划算高频且固定的问答模式用知识编译最省token。后面的章节就是逐层拆解每层我会给出设计思路、踩坑记录和一段能上手的代码。2. 文件级记忆跑通单机Agent最稳妥的起点很多团队一上来就搞全套向量数据库和知识库流水线结果项目凉在没有内容可存。文件系统虽然听起来“土”但它其实是Agent记忆最基础、最可审计、也最容易被人类干预的一层。文件在磁盘上任何文本编辑器都能打开Git可以追踪版本出问题可以人工修改甚至可以直接让非技术的业务人员用记事本维护部分知识。2.1 为什么 Obsidian 式知识库在 Agent 开发里突然流行这两年Obsidian知识库搭建的话题热得发烫连带着“LLM wiki知识库”也成为搜索高频词。观察下来大家并不是真的在乎Obsidian这个工具而是开始意识到知识库的底层格式应当是纯文本标记而不是某个私有系统的二进制格式。Obsidian用Markdown存所有笔记可以被LLM直接读取、解析、切片和补全。Agent不需要专门做接口适配读一个MD文件就跟读普通文本一样。我个人的经验是为Agent准备一套“可被人类维护的文件记忆”时最好的做法就是规定一个根目录、几种子目录、以及每个文件的YAML头。你甚至可以把这个目录直接挂到网盘或Git仓库里实现同步和备份。热搜词里专门有一条“obsidian知识库搭建”说明大家对“个人知识库怎么组织”是有真实诉求的。Agent的长期记忆完全可以复用个人的知识管理方法不必另起炉灶。2.2 一套可落地的文件目录设计我自己在多个Agent项目里用过的结构是这样agent_memory/ ├── profiles/user_id.md # 用户画像与偏好 ├── notes/date-topic.md # 临时笔记和决策记录 ├── facts/domain.yaml # 业务事实表如退款规则、渠道代号 ├── skills/skill_name.md # 操作流程、提示词模板 └── inbox/ # 不确定类型的数据先扔这里这套结构的核心思路是“按写入目的分流”用户画像写入profiles每次对话发现的偏好都追加到这里有业务节点产生了临时结论写进notes确定性的业务事实放进facts里的YAML文件因为YAML适合表达字段和值比自由文本更容易被解析skills目录存放那些在特定场景下才能触发的操作手册。给Agent写文件记忆的Python代码可以很朴素import os from datetime import datetime MEMORY_ROOT ./agent_memory def remember_user_preference(user_id: str, key: str, value: str): path f{MEMORY_ROOT}/profiles/{user_id}.md os.makedirs(os.path.dirname(path), exist_okTrue) with open(path, a, encodingutf-8) as f: f.write(f- {key}: {value}\n)注意这里用的是追加写入不是整文件覆盖。原因很简单Agent并发的写操作如果没有加锁整文件重写很容易丢数据追加一条记录原子性更高。后面读到用户画像的时候再让LLM从整份文件里提取当前有效信息即可。2.3 实操踩坑文件加载的“解析损耗”比想象中高文件层最大的坑不是存不进去而是读到模型面前时文件体积失控。假设你每天给Agent写十几条笔记一个月就是几百条全拼进Prompt会让模型在“噪音”里遨游。后来我把读取逻辑从“加载全部文件”改成“先列目录按关键词和日期过滤只加载最近N个文件”效果立竿见影。另一个很隐蔽的坑是编码。Windows机器上记事本保存文件默认可能是GBK编码Python在Linux服务器上读出来就是乱码。如果你在跨平台跑Agent所有文件读写最好统一UTF-8并且写代码时显式传递encodingutf-8不要用隐式默认编码。此外文件修改没有事务概念不要依赖文件系统做高频并发读写——那是下一层数据库的事情。3. 数据库级记忆当事实需要被频繁读写和共享文件再好也有天花板它不适合高并发更新不适合结构化查询更不适合多Agent实例共享。当你的Agent需要频繁查询“这个用户的会员等级是多少”、“上一笔订单的退款状态走到哪一步了”或者多个服务节点同时读写同一份记忆时就轮到数据库上场了。3.1 什么数据该进数据库而不是文件一句话结论凡是“需要查询条件”和“需要把状态作为当前值”的记忆都该进数据库。比如用户当前状态订阅中、免费试用、已过期业务流程位置OrderIdxxxstepwaiting_for_payment实体关系用户A是客服、用户B是购货方A向B发起退款这些数据的特点是字段固定、状态会变、需要按条件检索。你要是把它们放在文件里每次都只能全量加载再人工判断写多了就乱套。有热搜词提到“数据库同步软件”、“dbx数据库工具”也说明大家在实际Agent部署中已经遇到了多端同步和日常管理的痛点。3.2 记忆表到底怎么设计才不后悔先泼一盆冷水不要把全对话历史塞进一张大表。数据库层应该存“提炼后的事实”不是原始聊天记录。原始记录属于文件层或者日志系统数据库只保存模型从对话中提取出来的结构化信息。我建议的第一张表是agent_memory它是最通用的记忆表CREATE TABLE agent_memory ( id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id TEXT NOT NULL, memory_type TEXT NOT NULL, content TEXT NOT NULL, importance REAL DEFAULT 0.5, metadata TEXT, created_at TEXT DEFAULT (datetime(now)), updated_at TEXT DEFAULT (datetime(now)) ); CREATE INDEX idx_agent_type ON agent_memory(agent_id, memory_type);memory_type可以填preference、fact、decision、todo等metadata存JSON字符串比如来源会话ID、关联的实体ID。如果你追求更高查询效率再加一张entities表保存用户、商品、工单等实体然后在agent_memory里加entity_id字段做外键关联。很多项目的Agent记忆查询慢原因不是数据库不行而是没建索引。上面这条SQL里的idx_agent_type就是给“按agent和类型查记忆”这种高频查询准备的。没有索引时SQLite要全表扫描有索引后即使几十万条记录毫秒级也能完成。3.3 数据库连接和清理被大多数示例忽略的细节热搜词里有一堆“mysql的数据库连接池”、“数据库同步工具”相关的词说明大家在数据库接入这块踩过不少坑。Agent不同于普通Web应用它经常会以“循环多轮”的方式触发查询如果每次触发都新建数据库连接连接开销会占掉一大部分延迟。我在自己的Agent项目里会做一件非常简单的事用SQLite的时候把连接做成模块级复用用MySQL或PostgreSQL的时候直接用一个连接池。连接池大小不用大5到10个就够。还有一个容易忽略的参数是timeoutSQLite默认busy timeout很短多个Agent线程同时写数据时容易报“database is locked”。我习惯在连接时设置PRAGMA busy_timeout 5000稳了很多。数据库层的清理策略也要提前想好。记忆表不是无限膨胀的垃圾桶。我建议按importance和created_at联合做保留策略高重要度的数据一年中重要度的数据三个月低重要度的数据只留一个月。清理任务可以在每天低峰期跑一次SQL一条DELETE FROM agent_memory WHERE ...就够了。4. RAG级记忆语义检索的甜区与陷阱当知识库里的资料多到无法预定义字段时数据库就派不上用场了——你不可能给每一篇产品文档设计一张表。这时候要引入RAGRetrieval-Augmented Generation检索增强生成把非结构化文档切片、向量化再让Agent在回答前先做一次“语义检索”。热搜词里“rag知识库”、“rag项目”、“本地知识库搭建”的搜索量一直不低但认真做完效果好的其实不多。4.1 RAG解决的真正问题RAG解决的是“模型不知道但文档里有”的知识问题。模型训练数据有截止日期企业内部资料它更是一无所知。RAG通过检索召回相关片段再把片段塞进上下文让模型基于资料回答。这套机制的单次效果通常是不错的麻烦的是怎么做“对”。很多人误以为RAG就是“文档丢进向量库就完事”结果召回结果乱七八糟。我见过最多的现象是检索出来的片段和用户问题关键词相关但语义不相关或者排序靠前的内容来自一篇过时文档。要查的原因往往不是嵌入模型差而是切分和元数据过滤没做好。4.2 实操分块、向量化、混合检索的调参顺序先聊聊分块。分块的大小直接影响召回质量切得太碎单块信息不完整模型拼不出全貌切得太大块之间互相污染还浪费上下文长度。我的经验是中文场景先按500到800字符作为目标块大小加上50到100字符的重叠。强行追求完美切分器不如先把重叠加好否则长文档很容易在标题和正文中间断掉。再聊混合检索。纯向量检索有个硬伤专有名词、型号、编号的召回并不理想因为向量空间里相近不等于字面相似。我会在索引里同时挂BM25全文检索然后把向量得分和BM25得分做加权融合。混合检索听起来复杂其实实现起来也就几十行代码却能显著解决“型号A123和型号B456都有123但语义完全不同”这类问题。一个非常常见的检索配置大概长这样def retrieve(query: str, top_k: int 5): embed_query embed_model.encode(query) vector_hits vector_store.similarity_search_by_vector(embed_query, ktop_k) keyword_hits bm25_index.search(query, ktop_k) merged merge_by_reciprocal_rank(vector_hits, keyword_hits) return filter_by_metadata(merged, source_typeofficial_docs, date_after2024-01-01)注意最后一步的filter_by_metadata这一步极关键。RAG检索出来的片段如果能把来源、产品线、文档版本、日期当过滤条件召回质量会提升一大截。很多人把元数据当成仓库里不用的标签实际上它是检索的“安全带”。4.3 Agentic RAG让检索从一次查询变成一段推理固定“一次query一次检索”的模式面对复杂问题时经常不够用。比如用户问“我的退款什么时候到账”你至少需要先知道订单号再查退款规则再查当前账户状态——这是一个多跳检索。Agentic RAG的思路就是让Agent把“检索”当工具来调用它先拆解问题决定查什么看结果够不够不够就再查一次最后综合信息回答。我在一个工单系统里试过普通RAG和Agentic RAG的对比。普通RAG针对“退款流程是什么”这类单点问题很好但碰到“我这个订单要退款但我已经收到货了怎么办”这种多步骤问题普通RAG常会漏掉“退货地址”和“物流拦截”这两个关键信息。Agentic RAG需要更多token和构建决策逻辑但它能把问题拆成“订单状态→退款规则→实际可执行动作”三步回答完整度明显更高。5. 知识编译把“查来的知识”变成“会用知识”“知识编译”这四个字早期人工智能理论里就有指的是把声明性知识转换成可直接执行的程序化知识。放在Agent场景里我更喜欢叫它“预计算”与其每次回答时都去翻文档、检索知识库不如提前把高频知识点编制成规则、决策表或者模板让Agent在回答时直接命中规则又快又稳。5.1 知识编译的本质是“预计算”RAG是运行时检索知识编译是编译时预计算。两者的目标相同但延迟、成本和稳定性不同对比维度RAG知识编译使用方式每次回答前检索提前编译为规则/模板延迟中高依赖向量库查询极低直接查表或匹配Token消耗每次都要携带检索片段只需命中规则ID和参数准确性受切分和排序波动影响命中规则后非常稳定更新方式重传文档即可必须重新编译和验证适用数据海量、冷门、长尾知识高频、稳定、重复的知识举一个我亲身经历的例子。一套售后Agent知识库里有十几篇关于退款和售后的文档RAG平均回答延迟1.8秒而且每到新模型版本上线同样的问题偶尔会答得前后不一。后来我把其中“售后标准流程”这部分单独拿出来做知识编译人工和模型一起把文档整理成二十多条决策规则Agent识别到用户意图是“退款”后直接按规则查状态、给出对应动作不再走RAG。那部分问题的回答延迟降到0.3秒准确率从91%升到98%。5.2 一个可以照做的编译流程知识编译听起来有点玄学但落地流程非常固定筛选语料从线上日志里拉出用户提问Top50对照文档找出哪些问题反复出现且答案稳定。生成规则稿用LLM辅助把这些问答转成“条件→动作”的结构化规则。条件字段是订单状态、用户等级、发货状态、是否已签收等动作字段是返回话术模板或调用API。人工审查给业务人员看一遍规则确认没有把“仅限某些地区”这类边界条件丢在编译过程之外。存入规则库把规则存成JSON、YAML或代码文件。Agent在对话管线最前面查询规则命中就直接用不命中再走RAG。定期重编译每周或每月跑一次离线任务结合新增日志重新生成规则覆盖之前没预料到的分支。规则文件的样子不需要花哨关键是结构稳定。下面是一个简化的示例[ { intent: 退款, condition: { order_status: 已签收, return_flag: false }, response: 您的订单已签收需要先申请退货并填写退货原因。退货审核通过后系统会在48小时内退回原支付账户。, action: call_api:create_return_order } ]这种规则的好处是快、可解释、方便做回归测试。模型在面对规则命中场景时只需要执行模板不需要自行“临时创作”。临时创作是最容易引发幻觉的环节能避免就尽量避免。5.3 Ontology RAG带来的额外加成热搜词里“ontology rag”和“weknora知识库”这些词让我看到越来越多人在关注“带本体的知识库”。简单来说Ontology RAG在做检索之外还维护了一张概念关系图比如“退货单属于订单”、“退款属于售后”“维修单不是退货单”。关系图让Agent在检索时能沿着关系路径做约束而不是纯粹靠向量相似度硬碰。知识编译和一个轻量本体模型是绝配编译规则时把概念关系也一并编进去Agent拿到用户问题能先做一步“实体消歧”。现实中用户经常会用口语说“我要退东西”老式RAG可能检索不到“退货”相关片段但Ontology RAG通过同义词展开和关系推断能把它映射到“退货流程”上。再把这层映射编译进规则库效果会稳定很多。6. 一套我实际跑通的四层记忆架构前面四层拆开看各有用途真正考验人的是怎么把它们组装起来。以下是我在多个项目里验证过的一套四层记忆架构适用于大多数单机、中小规模的Agent应用如果你要上生产和多机部署只需在这套基础上的调度层和存储层做替换。6.1 整体数据流写优先落在文件读优先走规则写路径和读路径要分开设计。写路径上Agent发现新信息后先做一次分类是结构性事实如“用户等级是白银”就写数据库是自由文本偏好如“用户不喜欢太长的回复”就追加到文件是业务文档资料就交给索引管线切块进RAG。读路径上优先顺序相反先查知识编译规则再查数据库最后才查RAG。规则命中率高到足以覆盖80%的常见问题因此RAG的负载会小很多。这个“写分门别类读先规则后检索”的顺序是整篇架构里最重要的一个习惯。很多人败就败在把RAG当成万能入口所有问题都扔给向量库。当你把高频问题编译成规则后你会惊讶地发现真正需要去文档里翻的内容远比想象中少。6.2 配置示例和调用顺序我会用一个YAML配置文件来声明这套记忆架构memory: file: root: ./agent_memory profile_dir: profiles notes_dir: notes database: type: sqlite path: ./agent_memory.db busy_timeout_ms: 5000 rag: embed_model: local_embedding_v3 chunk_size: 700 chunk_overlap: 80 retrieval: mode: hybrid top_k: 5 metadata_filters: - source_type - updated_at compile: rule_file: ./rules/compiled_rules.json rebuild_schedule: weekly运行时Agent的回答管线大致是这样解析用户输入识别意图和实体。查知识编译规则库命中就走规则直接返回。未命中则查数据库按用户ID和实体ID取结构化事实。如果数据库无结果或问题仍不明确走RAG检索。将规则结果、数据库事实和RAG检索片段集合起来交给LLM生成回答。这五步不是每次都要走到底。我实测下来一个老问题命中规则的概率大约是60%剩下的问题里数据库能解决20%RAG真正上场的机会只有20%。RAG负载低了成本自然就降下来了。6.3 效果对比与更长期的维护建议我做过一组简单压测同样10个高频问题分别用“全量RAG”和“规则数据库RAG闭环”两套方案跑。全量RAG平均单次延迟1.6秒token消耗约1200闭环方案平均延迟0.5秒token消耗约600。答案的一致性也明显更好同一条问题重复问三次闭环方案的三次回答几乎完全一致全量RAG偶尔会出现表述跑偏。长期维护上我有几条很实在的建议知识编译要定期重跑不能一劳永逸。业务规则会变文档会更新每周用线上日志对比规则命中率命中率掉下来就说明规则需要扩容了。RAG的索引要主动淘汰过期文档。不要只往里塞东西一定得有“失效时间”机制否则旧文档会持续污染检索结果。文件层不是廉价垃圾桶。放在文件里的记忆也要有结构、有命名规范最好让人类可以快速浏览。无结构的自由文本只会成为下一年的RAG垃圾。数据库里的记忆字段建议加一个“来源会话ID”。一旦Agent答错方便回溯是哪个环节把错误信息写进了记忆。最后再分享一个小技巧给每个记忆条目和RAG片段都打上“可信度”标签。规则库是人类审核过的可信度最高数据库事实是模型抽取后人工复核过的其次RAG检索结果是最低等级的。模型生成回答时如果多个来源出现冲突优先采用高可信度来源。这样一套简单的分级能救回不少因为记忆冲突导致的翻车现场。