Skip to content
Charles Shao
Go back

AI Agent 是什么:拆解模型、工具、记忆与调度循环

–views

这是“手搓 Agent”系列架构实践的开篇。后续文章里,我们会彻底抛开重型框架的层层封装,以一条贯穿始终的订房业务线(“西安 10 月 1 日住两晚,下雨订 suite,否则订 standard”)为锚点,把 Agent 底层的流转逻辑逐层扒干净。为了避免后续概念在工程语境里打架,本文先在架构层面对核心组件做一次严谨的定名与拆解。

相比于裸调 API 的普通聊天,Agent 的核心差异在于对外部状态的改写能力。普通对话场景下,当用户抛出“西安 10 月 1 日该订哪家”时,模型不过是按部就班地产出一份步骤说明,全程没有任何副作用(Side Effect)落地;至于后续是否真的去预订、预订出错如何兜底,一概由人类开发者接管。而切到 Agent 架构,一旦你下达“西安 10 月 1 日住两晚,下雨订 suite”的目标,它就会自行调度天气查询与酒店搜索,甚至在经过 Human-in-the-loop 拦截确认后完成最终的扣款落盘。这种差异的本质,并不在于模型本身更聪明,而在于系统是否为模型装配了**工具调用(Tool Use)**的能力,以及底层是否架起了一台支持其自主推演下一步动作的调度引擎。

为了统一技术语境,下文涉及的核心术语将严格遵循以下工程定义:

术语架构定义
模型核心推理引擎(LLM)。在循环中负责输出状态预测,即决定下一步调用哪个工具,并组装对应的参数(Payload)。
工具本地代码中可被执行的函数实例。未注册进路由分发清单的,模型一概无感知且无法触达。
记忆独立于调度循环之外的状态字典,用于持久化目标上下文与已推演出的中间结论。每次交互前会被打平并回灌至 system prompt 中。
循环核心调度状态机(Loop)。其生命周期为:请求模型 → 捕获 tool_calls 并执行 → 结果回灌 → 再次请求模型,直至输出终答或触发异常终止兜底。
回灌将工具执行的返回值或异常堆栈,以 role=tool 的角色属性结构化拼装,并透传打平至 messages 数组中的操作。
终答当模型判定任务闭环、不再吐出 tool_calls 时,所直接响应的纯文本内容(content)。
终止强行打破调度循环,停止后续路由与执行。通常由安全阈值(如最大轮数限制、上下文膨胀触发的 Token 截断)触发。
run一次完整的任务生命周期。指代目标指令进入调度循环,直至触发任意出口(终答或终止)的完整流转过程。

核心判断准则:普通聊天只能按指令产出步骤;而 Agent 则是依赖自身循环机制完成工具调度与上下文回灌,直至吐出终答或被强行终止。

Table of contents

Open Table of contents

一、四大组件:缺一即退化为“瞎聊”

剥离了框架的黑盒后,这四大组件在底层缺一不可,任何一环的缺失都会让整个状态机流转退化为单调的一问一答:

组件缺失系统降级表现
无模型整个工程失去推理中枢,没有任何组件能够根据当前上下文决定下一个该调度的工具。
无工具哪怕接入了最顶级的模型,它也只能在纯文本层面输出“建议您前往官网预订”,无法对外部系统产生任何副作用。
无记忆丧失状态持久化能力,导致每一步的上下文都在重置——比如上一轮辛苦搜到的 hotel_id,在下一轮请求中会直接丢失。
无循环请求链路退化为单次 HTTP Call,收到响应后立刻结束,既不会触发本地执行引擎,也不存在状态回灌与多步推演。

最棘手的问题在于,循环(Loop)本身才是划分 Agent 与普通对话的真正分水岭。普通对话是线性、单次的 Request-Response 模式;而 Agent 的底层是一个具备**出口(Exit Nodes)**的并发调度过程。一旦进入循环,要么因为不再有 tool_calls 而顺利抛出终答,要么因为触碰轮数上限或累计 Token 超限,被系统强行终止。

左侧为单次问答模型:User 输入需求,Assistant 输出步骤清单后链路即刻结束,没有产生任何状态变更。右侧为 Agent 的四大核心组件联动:模型负责吐出下一步调用的工具及参数(未注册的工具模型无法触达);记忆模块持久化核心目标与结论,并逐轮打平至 System Prompt;循环引擎负责执行、结果回灌并再次发起请求;最终在无 tool_calls 时抛出终答,或在触及 Token 阈值及轮数上限时触发终止。

需要再次强调的是,在这个流转体系里,模型始终只负责一件事——预测“我现在需要调用哪个工具”。至于如何安全执行、如何做数据校验、如何回灌异常堆栈、如何锁死出口,这些全部是由开发者手搓的硬核代码来兜底的。

二、状态机流转:一次“订房”的底层切面

为了把抽象的调度循环落到具体的工程切面上,我们来看目标为“西安 10 月 1 日住两晚,先查当天天气,下雨订 suite,否则订 standard”的请求,其一次完整的 run 在底层究竟是如何流转的:

  1. 首次循环:模型决定调用查询天气的工具,代码层捕获 tool_calls 并执行,将天气结果回灌进 messages,同时把“西安 10 月 1 日下雨”这一核心结论落盘到记忆中。
  2. 二次循环:模型根据回灌的上下文决定调用搜酒店工具,代码层再次执行并回灌。此时严格校验提取出的 hotel_id 必须合法,且必须来自本次搜索结果。
  3. 触达出口:模型判断前置条件已满足,不再输出 tool_calls,转而输出终答并请求用户确认(写操作必须经过确认)。在未获得显式确认前,系统处于挂起状态,拒绝向外发送任何真实的预订请求。
  4. 状态唤醒:用户回复 user: 确认,上下文重新进入调度循环,代码层执行 book_hotel,最终落盘成功并返回最终状态,循环正式结束。

试想一下,如果没有接入工具,它只能“口嗨”建议你订 suite;如果没有外置记忆模块,上一轮搜出的唯一 ID 在下一轮上下文截断后根本对不上;如果没有调度循环,系统在查完天气那一刻就直接结束了,根本走不到预订逻辑这一步。

三、剥离滤镜:实战中容易踩坑的认知误区

在实际动手造轮子之前,我们需要先剔除市面上泛滥的营销话术,回归工程本质:

四、架构自测:你的代码真的是 Agent 吗?

与其被各种概念绕晕,不如用以下三个核心特征,对自己手搓的代码做一次架构审查:

  1. 输入驱动形式:你喂给系统的,是抽象的业务目标(Goal-driven),还是事无巨细、一步一步硬编码的流水线指令?
  2. 路由分发决策:下一步到底走哪条分支、调哪个函数,是由模型在运行时动态推演的,还是在代码里写死的 if-else 分支?
  3. 系统副作用:这套逻辑是否具备向外改写外部状态(如落盘数据、发起真实扣款、提交 PR)的能力,而不仅仅是产出一堆 Markdown 文本?

如果这三个维度的答案都偏向于前者,那么恭喜你,你已经构建了一个纯正的 Agent 雏形。下一篇我们将直接深入代码层面,把核心调度循环彻彻底底地手写出来:看着模型如何吐出工具请求,本地代码如何执行并透传回灌,以及那三个决定生死的出口,到底是怎么被代码锁死的。

一句话总结

剥离黑盒,Agent 的底层无非是模型、工具、记忆、循环四大组件的拼图。模型仅仅扮演一个负责说“我想调哪个工具”的预测节点;而真正的执行落地、状态回灌与出口控制,全仰仗于你亲手糊出的每一行硬核代码。


–views
Share this post on:

Previous Post
从零手搓 Agent:带出口的 while 调度循环
Next Post
Go 日志设计笔记:从照搬 logback 到删掉一半设计