BeeWeave:基于图数据库与向量检索的Agent上下文持久化架构实践
1. 项目缘起为什么“留住上下文”成了Agent开发的痛点最近在捣鼓一个叫BeeWeave的Agent项目核心目标就一个把Agent用过的上下文给“留住”。这想法听起来简单但做过Agent开发的朋友尤其是跟大模型API打过交道的估计都懂我在说什么。你辛辛苦苦设计了一套复杂的提示词Prompt让Agent去调用工具、分析数据、生成结果一轮对话下来Agent可能已经处理了十几条甚至几十条消息形成了一个包含任务目标、中间思考、工具调用结果、用户反馈的完整“上下文”。但问题是当这轮对话结束或者你需要开启一个新任务时这个宝贵的上下文往往就“消失”了。这带来的麻烦是实实在在的。比如你让Agent帮你分析一份财报它先调用了搜索工具获取了公司背景又调用了数据提取工具从PDF里抽出了关键数字最后进行了一轮复杂的推理。整个过程可能花费了十几秒和不少Token。然后你接着问“那它明年的利润趋势会怎样” 理想情况下Agent应该能记住刚才分析过的所有数据直接基于已有信息进行预测。但现实是很多简单的Agent实现要么因为上下文窗口限制被无情截断要么就是设计上根本没考虑状态的持久化导致每次提问都像是“重启”了一个新的Agent它得从头开始搜索、提取、分析既浪费资源API调用次数、Token费用体验也极其割裂。这就是我启动BeeWeave项目的直接动机。我不想让Agent的“记忆”只存在于单次API调用的生命周期里。我希望它能像一个有经验的助手一样能把处理过的问题、用过的资料、得出的中间结论都攒下来形成一个不断积累的知识库或工作记忆在后续的交互中能被灵活地检索和复用。这不只是节省Token那么简单更是为了构建真正具有连贯性、能处理复杂多轮任务的智能体。2. 深入拆解Agent上下文到底包含什么在动手设计BeeWeave之前我们得先掰开揉碎看看一个Agent在一次任务执行中产生的“上下文”究竟由哪些部分组成。理解了这个才知道要“留住”什么以及怎么留。### 2.1 核心构成不止是聊天记录很多人一提到上下文就想到大模型API的messages数组里面一串user和assistant的对话。这没错但这只是最表层的一部分。对于一个功能完整的Agent其上下文是一个多层结构对话历史Conversation History最直观的部分即用户与Agent之间一来一往的文本消息。这是模型进行语言理解和生成的基础。系统指令与角色设定System Prompt Persona定义Agent身份、能力边界和行为准则的初始提示。这部分通常在一开始注入但严格来说也属于上下文的一部分因为它指导着整个对话的走向。工具调用与执行结果Tool Calls Results这是Agent上下文的“重头戏”。当Agent决定调用一个函数Function Call或工具Tool时它会在消息中插入一个特殊的tool_calls结构。随后系统需要执行这个工具并将执行结果可能是成功的数据也可能是错误信息以tool角色的消息形式追加回上下文。这个“调用-执行-返回”的循环及其产生的结果是Agent完成实际工作的核心证据包含了大量的结构化或半结构化数据。内部思考过程Chain-of-Thought, CoT许多先进的Agent框架会鼓励或要求模型输出其推理的中间步骤。这些步骤可能以特定的格式如“让我们一步步思考…”出现在助手的回复中。这些思考痕迹对于理解Agent的决策逻辑、以及在后续步骤中纠正或延续其思路至关重要。会话元数据Session Metadata例如本次会话的唯一ID、创建时间、用户标识、当前使用的模型名称、温度Temperature等参数设置。这些数据对于会话的管理、审计和重现很有帮助。### 2.2 上下文的动态性与关联性上下文不是静态的文本堆砌而是动态生长的。新的用户输入会触发新的工具调用工具返回的结果又会成为下一轮模型推理的依据。这就形成了一个数据流图。在BeeWeave的设计中我特别关注这种关联性。例如一个“获取天气”工具调用的结果可能会直接作为后续“推荐穿衣”工具调用的输入参数。留住上下文不仅仅是保存文本更是要保存这种调用之间的依赖关系和数据流向这样才能在后续任务中实现真正的“记忆”和“理解”。### 2.3 与“短期上下文”和“长期记忆”的区分这里需要澄清一个概念。在Agent架构讨论中常提到“短期上下文”和“长期记忆”。短期上下文通常指直接喂给大模型单次推理的、受限于其上下文窗口长度如128K、200K、1M的那部分信息。它的特点是“即用即抛”用于支撑模型做出当前这一次的决策或生成。长期记忆指被持久化存储到外部数据库向量数据库、图数据库、传统关系型数据库等中的信息供未来需要时通过检索Retrieval的方式重新加载到短期上下文中。BeeWeave要解决的“留住上下文”其目标更像是构建一个高效的长期记忆系统并且要能智能地决定将哪些历史上下文片段在何时、以何种方式重新注入到新的短期上下文中去。这远不是简单的聊天记录保存。3. BeeWeave的架构设计如何编织这张“记忆之网”BeeWeave这个名字意在“编织蜜蜂Bee”象征着Agent像蜜蜂一样辛勤工作而系统则负责将其采撷的“花粉”上下文编织成可持续利用的“蜂蜜”记忆与知识。整个架构围绕“上下文留存与复用”展开核心包含以下几个层次### 3.1 上下文捕获层Context Capture Layer这是最基础的一层负责在Agent运行的每一个环节无侵入地抓取所有上下文元素。我的实现方案是构建一个上下文总线Context Bus。拦截与标准化在Agent框架例如使用LangChain、LlamaIndex或自定义框架的处理流水线中插入钩子Hooks。无论是用户消息传入、模型调用发起、工具执行开始/结束还是最终回复生成每一个事件都会被总线捕获。结构化封装捕获到的原始数据可能是字典、JSON、字符串会被封装成统一的事件对象。每个事件对象包含event_id: 唯一事件标识。session_id: 所属会话标识。event_type: 如user_message,model_call,tool_invocation,tool_result,assistant_message,internal_thought。content: 事件的具体内容。timestamp: 发生时间。parent_event_ids: 指向触发本事件的上游事件ID列表。这至关重要用于构建事件之间的依赖图谱。例如一个tool_result事件的parent_event_ids必然包含对应的tool_invocation事件的ID。异步分发封装好的事件被异步推送到总线上。这样做的好处是不阻塞主Agent的执行流程保证响应速度。### 3.2 上下文存储层Context Storage Layer总线上的事件需要被持久化。这里没有采用简单的追加日志文件方式而是选择了更适合复杂查询和关联分析的数据库。存储选型图数据库优先。经过对比我选择了Neo4j社区版足够用作为主存储。为什么是图数据库因为Agent的上下文本质是一个有向无环图DAG。用户消息是根节点它可能触发模型思考节点思考节点又触发工具调用节点工具调用产生结果节点结果又触发新的思考或最终回复。用图来存储能天然地表达这种复杂的、网络化的关联关系后续做溯源、子图提取、影响分析都非常方便。节点Node代表一个上下文事件如Message,ToolCall,ToolResult。关系Relationship代表事件之间的流向如TRIGGERED_BY,RESULT_OF,PRECEDES。属性Property存储事件的具体内容、时间戳、元数据等。关系型数据库作为补充对于一些简单的、需要频繁聚合查询的元信息如会话列表、基础统计我同时用PostgreSQL存了一份。这是一种混合存储策略各取所长。向量化嵌入与存储所有文本内容用户消息、助手回复、工具结果中的文本都会通过一个嵌入模型如text-embedding-3-small转换为向量并存入向量数据库如ChromaDB或PgVector。这是为了实现基于语义的上下文检索。当未来需要寻找相关历史时我们不仅可以通过图关系查找还可以通过语义相似度查找。### 3.3 上下文索引与检索层Context Indexing Retrieval Layer存下来不是目的能用得上才是关键。这一层负责回答“当Agent处理新问题时应该把哪些旧上下文‘回忆’起来”多路检索策略会话内时序检索最简单的方式直接按时间顺序获取本次会话内的最近N条事件。适用于连续对话。图关系检索给定当前处理的事件节点在图数据库中沿着关系边向上游或下游探索获取与之直接相关的子图。例如当用户问“你刚才提到的那个数据是多少”系统可以定位到最近一次工具调用结果节点并将其完整调用链用户问题-模型思考-工具调用-工具结果检索出来。语义检索将用户的新问题编码成向量在向量数据库中搜索语义最相似的历史文本片段可以是某条消息、某个工具结果。这是解决“跨会话记忆”和“联想记忆”的关键。比如用户三周前问过“Python异步编程的最佳实践”今天问“asyncio怎么调试”即使不是同一次对话语义检索也能把历史相关内容找出来。混合检索结合以上多种方式并对检索结果进行去重、排序和融合。例如先通过图关系找到直接相关的上下文再通过语义检索补充一些背景知识最后按时间或相关性得分排序。检索结果的重构与压缩检索回来的可能是一堆分散的事件节点和文本片段不能直接扔给模型。需要将它们重构成模型能理解的格式通常是重新组织成messages数组的形式。这里面临上下文窗口的限制。如果检索到的内容太多就需要压缩。提取式压缩只保留最关键的事件节点和文本片段比如工具调用的最终结论而省略中间的详细步骤。摘要式压缩用一个更小的模型如Claude Haiku或大模型本身对一大段检索到的上下文进行摘要生成一个简短的背景说明。分层管理借鉴一些前沿思路如“上下文分层”将上下文分为“核心对话层”、“工具调用层”、“背景知识层”在不同轮次选择性注入。BeeWeave目前实现的是动态优先级策略优先注入与当前问题图关系最直接、语义最相关的内容如果还有空间再注入次相关的背景。4. 核心实现细节与踩坑实录把架构图变成代码过程中充满了“惊喜”。这里分享几个关键模块的实现细节和遇到的坑。### 4.1 上下文总线的实现与事件去重我最初用Python的asyncio.Queue实现了一个简单的事件总线。生产者在各个钩子函数里put事件消费者在一个独立线程中循环get并处理存储、索引。很快就遇到了第一个问题事件风暴。在一次复杂的Agent任务中一个工具调用可能触发多个状态更新事件开始、进度、成功/失败如果日志详细瞬间会产生大量事件导致队列积压存储层压力剧增甚至影响主线程性能。解决方案是引入事件聚合与节流定义事件优先级将事件分为高、中、低优先级。user_message和tool_result是高优先级必须立即处理internal_thought的中间步骤可能是低优先级。实现时间窗口聚合对于低优先级的、高频的同类事件如连续的思考步骤在一个短时间窗口如100毫秒内进行聚合只存储最后一个状态或一个聚合后的摘要。设置背压Backpressure机制当队列长度超过阈值时总线会通知生产者钩子函数暂缓发送低优先级事件或者直接丢弃一些可丢失的事件。另一个坑是事件去重。由于钩子可能被多次触发或者网络重试等原因同一个逻辑事件可能被捕获多次。我通过为每个事件生成一个确定性ID来解决hash(session_id event_type str(content) str(parent_event_ids))。在存入图数据库前先检查该ID是否已存在避免创建重复节点。### 4.2 图数据库建模的演进Neo4j的建模不是一蹴而就的。第一版设计非常简单(UserMessage)-[:TRIGGERED_BY]-(ModelCall) (ModelCall)-[:GENERATED]-(ToolCall) (ToolCall)-[:HAS_RESULT]-(ToolResult) (ToolResult)-[:INFORMS]-(AssistantMessage)这个模型能跑但很僵硬。当遇到多轮工具调用、嵌套思考ReAct模式时关系变得混乱。第二版设计引入了更通用的“事件”抽象和“流”的概念(Event {id, type, content, timestamp}) (Event)-[:NEXT {order: 1}]-(Event) // 表示时间顺序流 (Event)-[:CAUSED_BY]-(Event) // 表示逻辑因果关系 (Event)-[:REFERS_TO]-(Event) // 表示内容上的引用关系所有具体类型Message, ToolCall等都是Event节点的子标签并通过type属性区分。NEXT边构建了整个会话的时序链CAUSED_BY边构建了逻辑因果图REFERS_TO边可以表示“助手消息引用了工具结果X”。这个模型灵活得多可以表达复杂的交互模式。查询时可以用Cypher语句灵活地匹配路径例如找到导致某个工具结果的所有上游事件MATCH path(result:Event {type:ToolResult})-[:CAUSED_BY*]-(cause) RETURN path。### 4.3 语义检索的冷启动与更新问题向量检索听起来很美但实操有门槛。第一个问题是冷启动一个新建的BeeWeave系统向量数据库是空的语义检索毫无作用。为了快速让系统产生价值我实现了双写策略在将事件存入图数据库的同时如果事件包含文本内容就异步调用嵌入模型生成向量并存入向量库。这意味着系统刚启动时检索能力是逐渐增强的。第二个问题是更新与删除。如果一条历史消息被用户修正或标记为无效我们不仅要在图数据库里标记它还需要在向量数据库里删除或失效对应的向量条目。我采用“软删除”策略在向量存储中增加一个is_active字段检索时过滤掉不活跃的条目。同时定期运行清理任务将软删除的条目真正移除以节省空间和维护索引效率。### 4.4 上下文压缩与Claude Code的启发上下文窗口是宝贵的资源。当检索回大量历史而当前模型的窗口比如Claude 3.5 Sonnet的200K装不下时压缩是必须的。我尝试了几种方法基于重要性的过滤通过一些启发式规则判断事件的重要性。例如tool_result通常比internal_thought的中间步骤更重要包含具体数据、结论的文本比过程性描述更重要。可以给事件类型和内容关键词打分只保留高分片段。调用大模型进行摘要这是效果最好但成本也最高的方法。我专门对比了不同模型。像Claude 3 Haiku这样速度快、成本低的模型适合做这件事。网上有讨论claude code的上下文分层或压缩命令其核心思路就是让模型自己识别出上下文中的核心信息、代码块、关键结论并生成一个更简洁的版本。我在BeeWeave中实现了一个类似的压缩器将需要压缩的上下文段落交给Haiku提示词是“请将以下AI Agent与用户的对话历史及工具调用结果压缩成一个简洁的背景摘要保留所有事实性结论、关键数据和最终决定省略重复的思考和过程描述。”实测下来摘要压缩在保证关键信息不丢失的前提下通常能将文本长度减少60%-80%效果非常显著。当然这需要权衡延迟和成本。5. BeeWeave在实践中的应用场景与效果经过一段时间的开发和内部测试BeeWeave已经开始在一些场景中发挥作用验证了“留住上下文”的价值。### 5.1 场景一复杂任务的中断与续作这是最直接的应用。用户让Agent编写一个爬虫脚本中途因为网络问题或者用户自己有事离开了。几天后回来他只需要说“继续上次的爬虫任务”BeeWeave能通过会话ID或语义检索立刻找回完整的上下文之前已经写好的部分代码、讨论过的网站结构、遇到过的反爬虫问题及解决方案。Agent无需重新询问需求可以直接基于历史上下文继续工作极大地提升了复杂、长周期任务的体验。### 5.2 场景二跨会话的知识积累与复用市场部的同事A用Agent分析了上季度的社交媒体数据得出了几个关键结论。几周后同事B要开始策划新季度的活动他问Agent“我们上个季度在社交媒体上表现如何” 传统的Agent对此一无所知。但集成了BeeWeave后Agent可以通过语义检索找到同事A那次会话中产生的分析报告工具结果节点并将其作为背景信息注入当前对话。这样Agent就能给出有数据支撑的回答实现了组织内部知识的隐性传递和积累。### 5.3 场景三Agent行为的审计与调试在开发阶段当一个Agent表现不符合预期时调试是痛苦的。你只能看到输入和最终输出中间的“黑箱”过程难以洞察。BeeWeave保存的完整上下文图谱成为了强大的调试工具。开发者可以像查看调用链追踪Trace一样可视化地看到一次任务中所有的消息、思考、工具调用及结果精准定位问题出在哪一环是工具调用参数错了还是模型错误解读了工具返回的结果这大大降低了Agent的开发和调优成本。### 5.4 场景四构建“个性化”Agent通过长期积累一个用户与Agent的所有交互上下文可以训练一个轻量级的模型或构建一个用户画像来理解该用户的偏好、常用指令模式、知识盲区等。未来的Agent可以基于这些历史提供更个性化的服务。例如如果历史显示用户经常让Agent将输出总结为三点那么新任务中Agent可以主动询问“需要像往常一样为您总结成三个要点吗”6. 面临的挑战与未来演进方向当然BeeWeave还远非完美在实现过程中也暴露出不少挑战这也是后续迭代的重点。### 6.1 性能与成本平衡这是最现实的挑战。每一次交互都伴随大量的写入图数据库、向量数据库、读取检索和计算向量化、压缩。虽然大部分操作是异步的但对系统资源仍有要求。特别是向量化嵌入和调用大模型进行摘要会产生额外的API成本和延迟。未来的优化方向包括更智能的索引策略不是所有文本都需要向量化。也许可以先根据事件类型和简单规则过滤只对判定为“高信息密度”的内容进行向量化。缓存机制对频繁检索的上下文片段将其重构后的messages格式缓存起来避免每次检索都重新执行图查询和语义排序。边缘计算考虑使用更小的、可在本地运行的嵌入模型以减少对云端API的依赖。### 6.2 上下文的有效性管理与“记忆幻觉”不是所有历史上下文都是有益的。过时的、错误的、与当前任务无关的上下文如果被错误地检索并注入反而会干扰模型导致其输出质量下降甚至产生“记忆幻觉”——即模型基于错误的历史信息进行了自信的但错误的推理。如何评估上下文的有效性和相关性是一个难题。我目前正在探索的方法包括基于置信度的过滤为每个存储的上下文事件打上置信度标签置信度可能来源于工具执行的返回状态、用户的直接反馈如点赞/点踩、或多轮对话后模型的自我验证。时效性衰减为上下文引入“衰减因子”时间越久远其默认相关性得分越低除非被频繁检索或明确关联。相关性验证在将检索到的上下文注入前可以增加一个轻量级的验证步骤让一个小模型判断“这段历史与当前问题是否真的相关”。### 6.3 更复杂的检索与推理逻辑目前的检索策略还是比较基础的。未来希望引入更复杂的逻辑多跳检索用户问题可能不能直接匹配历史片段但通过中间桥梁可以找到。例如用户问“A项目的风险”历史中没有直接讨论但历史中讨论了“A项目依赖B技术”以及“B技术存在C漏洞”。系统需要能进行多跳推理将“B技术的C漏洞”这段历史检索出来。基于上下文的查询重写在检索前先用大模型对用户当前问题进行重写或扩展使其更贴合历史上下文的表述方式提高检索命中率。### 6.4 标准化与生态集成BeeWeave目前是一个相对独立的中间件。长远来看我希望它能成为Agent开发中处理上下文记忆的一个标准组件。这意味着需要提供更友好的API、定义更清晰的数据接口以便与主流的Agent框架LangChain, LlamaIndex, AutoGen等无缝集成。同时存储后端图数据库、向量数据库也应该支持可插拔让开发者可以根据自身基础设施灵活选择。写BeeWeave的过程是一个不断深化对Agent理解的过程。它让我意识到一个强大的Agent其核心能力可能不仅在于它调用工具的逻辑有多聪明更在于它如何有效地管理、利用自己“过去”的经验。留住上下文就是为Agent赋予连续性和成长性。这条路还很长但每一次让Agent“想起”过去的事情并做出更好决策时都让人觉得这些折腾是值得的。如果你也在做类似的事情或者对其中某个技术细节有疑问欢迎交流共同把这个“记忆之网”织得更牢、更智能。