ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

拆解Agent内核:源码背后的五层架构与工程实践

拆解Agent内核:源码背后的五层架构与工程实践 把 DeepSeek-Honeycomb 这样的 Agent 源码打开时很多人第一反应是找“内核”文件。以为找到了核心循环就算看懂了整个项目。但真正阅读过几份 Agent 源码之后你会发现“内核”不是一个文件不是一个大类也不是一段几百行的主循环。它是一种关于状态流转、上下文组织、工具接入、失败恢复和人类干预的设计思路。这个判断会影响你读源码的方式也会影响你写 Agent 的方式。如果你只是照着某个框架的模板写了一个 Agent demo可能很难体会。但当你开始面对多工具协作、长任务执行、上下文膨胀、模型输出不稳定、工具调用失败这些真实问题时内核设计的好坏会直接决定项目能不能继续往前走。这篇文章不打算逐行注释某一个开源项目而是从“内核到底要解决哪些问题”出发拆解 Agent 底层架构的通用设计逻辑。如果你正在读 DeepSeek-Honeycomb 这类源码或者正准备从零搭一个 Agent内容会更有参考价值。1. 为什么“内核”是理解 Agent 项目的钥匙1.1 Agent 内核不是 Agent 框架也不是 Agent 应用很多资料会把“内核”“框架”“应用”混在一起讲。实际阅读源码时这是三种完全不同的东西。Agent 框架是使用工具集比如怎么加载大模型、怎么调用 API、怎么组织项目目录。它可以被替换也可以被增强。Agent 应用是面向具体场景的产物比如一个客服助手、一个代码审查机器人、一个数据分析智能体。它包含业务规则、提示词、领域知识、外部系统对接。而 Agent 内核夹在框架和应用之间负责的是 Agent 最本质的问题一个任务进来之后系统如何理解任务、如何规划、如何调用工具、如何根据新信息修正行动、如何判断任务已经完成。如果把 Agent 类比成一个团队框架是办公室的桌椅和电脑应用是团队要做的具体业务内核则是团队内部的协作机制。没有协作机制工位再舒服也做不出稳定的输出。DeepSeek-Honeycomb 这类源码之所以值得拆不是因为它的模型部分多特别而是因为它的内核设计关系到 Agent 能不能在真实环境里稳定工作。1.2 一个典型的 Agent 运行闭环几乎所有 Agent 内核都围绕同一个闭环展开任务输入、信息整理、模型推理、行动选择、工具执行、结果回填、判断是否继续。这个闭环听起来简单但源码实现时会出现大量细节问题。比如模型返回的是一段包含多个步骤的计划还是一次只返回一个行动工具执行结果太长是直接塞进上下文还是先压缩、提取、存储一次循环中模型只调用一个工具还是允许并行调用多个工具如果工具报错是结束任务、重试还是换一种策略这些细节最终都会沉淀在“内核”里。所以读源码时不要急着看某个具体函数。先试着回答一个问题这个项目的 Agent 循环到底在哪一层启动在哪一层终止找到这个主循环再往里看理解速度会快很多。1.3 不懂内核时加功能为什么很难很多初学者做 Agent 时习惯在一个 Python 文件里写死流程先调大模型再解析结果再执行函数再调大模型。当时没问题但一旦要加一个新的工具类型或者要支持多轮工具调用代码就开始混乱。问题不在代码质量而在设计边界不清晰。工具注册、上下文组装、模型调用、结果处理、状态记录、异常恢复这些事情全部耦合在同一个函数里。看起来是“内聚”实际上是内核没有独立出来。真正值得参考的做法是这些职责各占一层每层只做一件事层与层之间用数据对象或接口契约通信。DeepSeek-Honeycomb 如果延续了主流 Agent 项目的分层思路它的源码里应该能明确看到这样的结构划分。2. 用“五层内核模型”拆解 DeepSeek-Honeycomb 这类源码“内核”听起来像是很复杂的东西但拆开之后通常是五层输入层、决策层、行动层、记忆层、控制层。每层解决一类问题并且彼此之间有清晰的契约。我们不妨用这个模型来理解和拆解源码。2.1 输入层任务解析与上下文组装输入层不是简单地把用户字符串传给大模型。它要做三件事任务理解、上下文组装、约束注入。任务理解包括用户输入的意图识别、必要参数的抽取、任务类型判断。上下文组装包括把历史对话、外部查询结果、知识库文档、工具输出按照模型要求的格式组织起来。约束注入则包括系统提示词、输出格式要求、禁止项、可用工具列表等。这个阶段最容易出现的问题是上下文太长。很多 Agent 项目一开始只接一个模型 API对上下文长度不敏感。但一旦加入多个工具和长文档输入层如果没有做裁剪、摘要、优先级排序模型很可能会忽略关键指令或者直接产生格式错误。读源码时可以重点看输入层的“构造器”或“上下文管理器”。它负责的往往不是简单的字符串拼接而是一套上下文预算和内容压缩机制。2.2 决策层模型调用与推理循环决策层是 Agent 内核的“大脑”。它负责调用模型把输入层组装好的上下文交给大模型拿到输出再解析输出。这里有一个关键设计模型输出是结构化指令还是自由文本在多数 Agent 源码中模型输出会被要求使用某种格式比如 JSON或者按特定标记包裹。决策层需要一个可靠的解析器能够容忍模型偶尔的多余解释或格式漂移。决策层还包括“思维链”或“规划”的处理。一次复杂任务往往需要多步决策每一轮决策都要基于上一轮工具执行的结果。内核需要把多轮决策组织成一个循环而不是每一次都从零开始。因此决策层通常会暴露类似plan()、step()、act()的方法。从工程实践看这里的难点不是调用模型而是如何判断模型输出是否有效。如果模型输出了工具名但参数不完整怎么办如果模型输出了多个步骤但步骤之间有依赖冲突怎么办决策层要有校验和回退机制。2.3 行动层工具注册、调用、结果回填行动层负责把模型决策转成真实动作。这套动作可能是调用本地函数、发起 HTTP 请求、执行代码、操作文件也可能是调用另一个 Agent。行动层的核心是“工具协议”。每个工具需要有名称、描述、参数 JSON Schema、执行函数、返回格式。内核通过工具注册表维护这些信息模型根据工具描述选择要调用的工具行动层根据模型输出执行对应的函数。源码中通常会有类似ToolRegistry、ToolExecutor、FunctionCaller这类类。质量好的行动层会做几件事校验参数类型和必填项设置调用超时捕获异常并返回标准错误对象控制并发数量对敏感操作增加二次确认。结果回填也很重要。工具执行结果返回后不能直接丢进下一轮上下文至少要做一个大小评估。如果结果很长要么截断要么让模型先做摘要要么存储后只返回引用。这直接影响到后续决策的稳定性。2.4 记忆层短期记忆、长期记忆、工作记忆记忆层是 Agent 内核最容易设计模糊的部分。很多项目把记忆简单理解成“把历史消息都放在上下文里”但这完全不够。建议把记忆分成三类短期记忆当前任务内所有对话和中间结果。它服务于当前推理过程。工作记忆当前任务的关键变量和中间状态。比如“用户目标是写一篇报告”“已经收集了三个数据源”“还缺一张图表”这些信息必须随时可读。长期记忆从过去任务中抽取的常识、偏好、历史结论、知识库摘要。它不总是进上下文只在需要时被检索出来。读源码时可以关注记忆层是“全量存储”还是“结构化管理”。如果只是往列表里 append 消息那它其实是提示词管理不是记忆管理。真正的记忆层会设计存储结构、索引、检索策略以及摘要算法。在 DeepSeek-Honeycomb 这类项目中如果它的目标是对标生产级 Agent记忆层一定要考虑上下文预算。否则任务一旦超过几十轮模型就开始丢信息结果质量断崖式下降。2.5 控制层循环终止、异常恢复、人类干预控制层是很多人最容易忽略的部分但真实项目里最重要的往往是它。控制层负责回答几个问题循环何时终止模型连续失败/重试时怎么办工具执行超时后要不要继续用户能否在中途打断或修正如果任务执行到一半怎么保存状态、之后恢复一个简单的 Agent 循环可能用“最大步数”和“模型说完成”两个条件来终止。但生产环境里模型可能连续三次输出同一个无效计划工具可能因为外部服务不稳定一直失败任务可能需要用户补充信息。这些都需要控制层做策略管理。比较好的设计是把控制策略外置使用配置或策略类而不是硬编码在循环中。例如允许设置最大重试次数、最大执行时间、关键节点需要人工审批、失败时回退到默认方案等。这也是一个判断源码成熟度的好角度如果控制层只有一两个if这个内核大概率是教学性质如果一个源码里出现了RetryPolicy、MaxIterations、HumanInTheLoop这样的抽象说明设计者考虑过真实运行问题。3. 从源码看内核设计最容易踩的五个坑拆过几份 Agent 源码再看自己写的代码会发现很多问题不是模型能力造成的而是内核结构造成的。这里列五个最常见的问题后面对照源码或重构时可以自查。3.1 把工具调用写死在业务逻辑里有些项目判断工具调用不是靠协议而是直接解析模型文本里的某个关键词再if判断要执行哪个函数。比如“如果模型输出包含‘搜索’就执行 search()”。这个方案在 demo 里能跑但加新工具时内核代码会被频繁改动。稍微正规一点的做法是建立工具注册表用 schema 描述工具能力。模型看到的是“工具描述列表”内核根据模型返回的tool_name查表执行。工具写死还带来一个隐藏问题无法做权限控制和安全检查。真实项目里不同用户可能对同一工具拥有不同权限。工具注册表天然方便做权限检查而 if-else 结构会越写越乱。3.2 上下文无限增长导致模型指令遵循能力下降另一个常见问题是把所有历史步骤和工具输出一轮一轮拼进上下文。模型确实支持长上下文但不代表长上下文中的信息都能被有效利用。当上下文超过一定长度之后模型对早期指令的遵循能力会下降对中间细节的保留也会出现偏差。很多 Agent 执行到后半程就开始“忘记”用户原始要求就是因为上下文太乱。解决思路不是换一个更大的模型窗口而是对上下文做分层管理。比如每一轮只保留上一次决策和当前最新状态工具输出先经过摘要压缩长期内容放到检索器里按需提取。DeepSeek-Honeycomb 这类项目如果采用 Honeycomb 的“蜂巢”隐喻往往意味着它更重视信息的结构化存储和多格分区而不是单一长文本流。3.3 循环终止条件只靠模型自觉只靠模型输出“任务完成”来终止循环会让 Agent 在两类场景下出问题模型为了完成默认目标在用户没有确认的情况下就结束任务模型陷入重复调用同一个工具形成死循环。内核需要显式定义终止条件至少要组合以下几项模型显式输出结束标记达到最大轮次或最大时间用户主动中断工具返回致命错误关键决策需要人工审批审批通过后进入下一阶段或终止。这些条件最好集中在一个控制器里管理而不是散布在循环各处。3.4 异常处理只有 try-except没有状态机当你让 Agent 连续执行 20 步任务其中第 13 步失败怎么处理简单做法是捕获异常后把错误信息返回给模型让它尝试修复。这种思路本身没问题但还不够。如果失败发生在工具调用之前、调用中、结果解析中处理方式完全不同。只用 try-except很难区分失败的阶段也没有办法决定重试、跳过、回退还是终止。建议把 Agent 每一步的运行状态建模成状态机比如PLANNING、ACTING、OBSERVING、FAILED、TERMINATED。状态机让错误恢复路径可预测也让日志跟踪更清晰。3.5 记忆和存储没有区分“读频率”和“用途”很多人把记忆简单理解成“把内容存到数据库里”。但记忆的价值不是存储而是检索。一个 Agent 可能记住上千条信息但每一轮决策真正需要的往往只有几条。如果不按用途区分记忆并给记忆做索引和检索策略记忆越多模型反而越困惑。好的记忆层会把高频状态比如用户目标、当前进度、已获得结论放在显眼位置把低频背景知识放到检索库中按需调用。我在实际阅读源码时会先看记忆数据结构的定义。如果它只有 list 和 dict那它还需要往工程化方向走如果它出现了类似MemoryBank、VectorIndex、SummaryCache这样的抽象说明作者认真考虑过长任务场景。4. 如果让我重写一个 Agent 内核我会按这个顺序来很多人问Agent 项目到底该从哪开始写我的建议是不要一开始就设计非常宏大的内核。先做最小闭环然后一层一层加固。下面是一个实践过的顺序也可以当作阅读源码的线索。4.1 先跑通最小循环LLM 一个工具 一个终止条件最小循环是内核的骨架不需要任何花哨设计。实现一个函数run(task)函数里做三件事把任务和工具描述拼成 prompt交给模型解析模型输出如果选择调用工具就执行它把结果拼回去如果模型输出结束标记则返回结果。正常运行之后先做一次“故意破坏”让工具返回一个异常看系统是否能继续。这个阶段的重点是验证基本闭环有效而不是追求设计完整性。这个阶段不需要工具注册表不需要记忆层更不需要状态机。写得很烂也没关系因为它是用来暴露问题的。4.2 再加状态管理会话、任务、事件最小循环跑通后马上会遇到一个问题多轮对话时历史消息怎么组织多个任务并行时不同任务的状态怎么隔离这一步建议引入三个概念Session一次完整的用户交互可能包含多个任务Task一个具体目标拥有自己的上下文和状态Event循环中的每一步记录包括模型输入、模型输出、工具调用、结果、错误。状态管理不需要一开始就上数据库内存里的结构体足够。但它必须让开发者能回答三个问题当前任务到了哪一步下一步要做什么如果现在崩溃已经完成的工作有哪些4.3 再抽象工具协议JSON Schema 注册表写第二个工具时就会明显感觉到不能再用 if-else。这一步需要把每个工具抽象成统一格式namedescriptionparametersJSON Schemahandlerenabledtimeout内核通过工具注册表暴露工具列表模型根据列表选择工具内核通过查找表执行。这个设计的好处是新增工具时“内核”代码一行都不用改。工具返回结果也要规范化。比如结构化为{ success: boolean, output: any, error?: string }。这样后续做异常处理、日志记录、结果压缩都能统一处理。4.4 再加记忆与检索入参、出参、摘要当任务步骤超过十轮纯历史消息列表就开始不够用。建议在每一步的工具调用前后分别记录“入参摘要”“出参摘要”“模型决策摘要”。摘要可以是大模型生成的简短文本目的是保留关键信息控制上下文长度。同时把长文本结果存到外部存储并建立索引。决策时模型看到的是摘要需要详细数据时再通过检索工具获取。这里要区分“任务内记忆”和“跨任务记忆”。前者在一个任务生命周期内有效后者服务于后续任务的个性化推理。不要一上来就做复杂的向量库先用关键词和结构化查询都能覆盖很多场景。4.5 最后做可观测性日志、追踪、评估前面几层都具备之后才轮到可观测性。因为在没有完整状态模型之前日志很难打得规范。有了 Task、Event、State 之后日志就有了明确的“维度”。需要打日志的关键点至少有这些每一轮的输入上下文长度模型返回的原始输出工具名、参数、耗时、返回结果摘要异常类型和重试策略状态流转路径从哪个状态到哪个状态最终终止原因。这些日志不仅用于排查问题也是做评估数据集的基础。没有日志的 Agent 内核就像没有仪表盘的飞机能飞起来但不知道什么时候会掉下来。在阅读 DeepSeek-Honeycomb 或类似源码时你可以用上面的顺序反过来验证它是否先具备最小循环然后状态管理、工具协议、记忆层、可观测性一层层补全大多数成熟源码都遵循相似的演进路径只是类名和文件结构不同。5. 源码阅读与调试的实操建议5.1 确定入口从 main、run、agent 目录找线索拿到一份 Agent 源码第一件事不是打开 README而是先找入口文件。通常入口文件名是main.py、run.py、cli.py或者某个agent.py里的run()方法。先看入口再看它调用了哪些对象迅速画出“入口 → 内核循环 → 工具调用”的调用链。画调用链时可以用一个简单方法打印堆栈。在入口处加一个traceback.print_stack()让它把主要调用路径打出来。无论项目多复杂这个操作能帮你快速找到主循环。5.2 用“关键断点”观察 Agent 状态流转设置几个关键断点会比从头到尾读代码更高效第一处模型输出结果返回后。观察原始输出格式、长度、是否可解析。第二处工具执行前。观察工具名、参数校验结果。第三处工具执行后。观察返回结果和错误。第四处状态判断处。观察这一轮循环结束后是继续还是终止。这四处断点能覆盖大多数问题。如果工具调用失败能在第二、第三处发现问题如果模型输出格式混乱在第一处就能看到如果 Agent 不终止在第四处看条件判断逻辑。5.3 一次工具调用失败的排查路径工具调用失败是 Agent 开发中最常见的问题也是最能体现内核设计水平的问题。可以按下面的链路来排查先看错误日志是“未注册的工具”“参数校验失败”“执行超时”“外部服务报错”中的哪一种再看模型输出模型是否真的输出了正确的工具名和参数格式再看工具注册表该工具是否在前一轮模型可选列表里再看权限校验用户/Agent 是否有权调用这个工具再看外部服务是服务本身挂了还是 Agent 传错了参数最后看异常处理内核有没有把错误封装成标准对象并能够回传给模型进行修复。这个顺序看起来简单但实际处理时很多人会直接跳到第 5 步去查外部服务。结果浪费大量时间后发现是模型输出中把参数名拼错了或者工具根本没注册。5.4 如何避免“改一行代码跑三小时”的调试循环Agent 项目的调试和传统后端不一样因为它每次调试都会调模型耗时和费用都更高。所以调试前一定要加缓存。对模型响应做一层缓存只要输入完全一致就返回上一次结果。这样你在改工具参数、调格式解析时不需要反复烧模型 API。缓存层并不难写可以用哈希 key 做内存缓存或磁盘缓存。同时建议把每次运行的数据都落盘输入、输出、工具结果、错误信息。这样下次调试时可以基于上一次数据做复现而不是重新从模型调用开始。6. 判断一个 Agent 内核设计好坏的 6 个问题读源码不是看它多完整而是看它留给“扩展”和“维护”的余地有多大。这里有一套问题适合用来快速判断任何 Agent 项目内核的水准。6.1 如果换一个大模型要改多少代码一个设计良好的内核模型调用部分应该是可插拔的。切换模型时你只需要改一个 Provider 适配器而不需要改 Agent 循环。如果源码里模型调用逻辑和任务决策逻辑写在一起换模型就会变得非常痛苦。你不仅要改 API 地址和密钥还要重新处理不同的响应格式。所以看到源码中有ModelProvider或LLMClient独立抽象是一个加分项。6.2 如果加一个新工具要动内核吗好的内核加新工具时只需要在注册表里加一条配置并实现对应函数。工具描述、参数 schema、权限控制都在外部提供。如果加工具需要修改 Agent 主循环或决策层说明工具协议抽象得不够。现实项目里工具会越来越多如果每加一个工具都要碰内核很快整个项目就会变得没人敢改。6.3 任务中断后怎么恢复真实任务不总是线性的。Agent 可能在执行到一半时遇到超时、用户取消、服务重启。内核是否支持任务状态持久化是否能在恢复后接着上次进度继续跑这是生产环境和 demo 的重要区别。如果源码里有 TaskState、Checkpoint、Snapshot 这类概念说明它考虑过恢复问题。如果只靠内存变量保存状态那一旦进程退出任务就丢了。6.4 上下文不够时策略是什么任何一个 Agent 内核都会面对上下文长度限制。好的内核会提前设计策略而不是等超限时报错。至少应该有四种策略摘要压缩把早期步骤压缩成概述裁剪详情移除不重要的工具中间输出检索召回把关键信息放到外部存储中需要时再取回分段处理把一个大任务拆成多个小任务每个任务独立上下文。如果源码里一个都没有那它只能处理简单的一次性任务不适合长程任务。6.5 人类能不能在关键节点介入没有一个 Agent 内核能保证所有决策都是正确的。所以在高风险任务里人类介入能力非常重要。好的内核会在某些工具调用前设置审批钩子或者允许用户随时发送一条消息打断 Agent 当前执行方向。DeepSeek-Honeycomb 这类项目如果强调“可控性”人类介入机制一定是重点。你可以在源码里搜索HumanInTheLoop、approval、interrupt这些关键词快速判断它在这个维度上的投入程度。6.6 能不能稳定复现、可观测最后一个问题听起来不性感但最决定长期维护。同样的输入这次跑成功、下次跑失败是不是因为模型温度随机性Agent 的每一步有没有 trace id工具调用的耗时和 token 消耗有没有记录如果源码没有任何 trace 或 eval 机制你很难评估一次改动是变好了还是变坏了。哪怕功能再多最终也只能靠“看起来跑通了”来交付这在生产环境里是高风险的事。7. 与其背源码不如先建立自己的内核设计清单回到文章开头的问题为什么会觉得 Agent 源码难读因为你潜意识里在找一份“标准答案”希望读完源码就掌握了唯一正确的 Agent 架构。但实际上Agent 内核设计没有一个放之四海皆准的模板它主要由任务复杂度、模型能力、工具生态、使用场景四者动态平衡。我在读完几份开源 Agent 项目后最大的收获并不是记住了某一个类名而是在自己脑子里形成了一份清单输入怎么组装模型怎么调用工具怎么注册错误怎么恢复记忆怎么检索任务怎么终止日志怎么记录。每次看到一份新源码我都会拿着这份清单往上面套很快就知道这个项目的架构重心和短板在哪里。如果你准备开始 Agent 开发我建议你也建立一份这样的清单。可以从一个最小循环开始然后一个坑一个坑地踩再把自己的处理方案沉淀成清单。等到你再看 DeepSeek-Honeycomb 这类标着“内核”的源码时关注的就不是它每一行代码写了什么而是它如何用代码回应那些常见的 Agent 工程问题。最值得长期关注的不是某一次任务的成败而是你处理这些任务时沉淀下来的判断框架。这比任何一份源码都更接近“内核”的本质。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进