拆解 AI Agent 底层工作流程:Prompt 输入后模型输出 Tool Call,Runtime 执行后结果回填 Context,循环往复直到任务完成或触发终止条件。两种记忆(短期上下文 / 长期向量存储)和护栏机制是防止「无限循环」的关键工程点。
一个实际视角,看 AI agent 的运行时——在 prompt 和任务完成之间到底发生了什么。
几个月前,一家金融科技公司的 CTO 问我,为什么他的 agent 一直失败。不是失败在哪个具体任务上——那个他清楚。他想知道这东西内部是怎么运作的,细节到他能看出钱是从哪里漏掉的。我打开终端,给他看了一次运行记录:模型发出了一个 tool call,我们的运行时执行了它,结果返回到 context 中,模型又发出了下一个 call,这样走了九步。其中八步都没问题。一步是同一个网页搜索换了种说法重新查了一遍。
他看着日志,把大家心知肚明的话说出来了:"所以就是个循环,在猜测,然后把猜测记下来。"
他说得没错。AI agent,在底层,就是一个循环。其他所有东西——memory、tools、框架、编排——都是绕着这个循环搭建的脚手架。本文将逐步讲解每一步实际发生了什么:循环本身、tool-calling 协议、两种 memory,以及阻止循环失控的 guardrails。不是框架的营销话术,就是生产环境中工作的机制原貌。
剥掉所有包装,一个 agent 就是这样:
while goal_not_met and budget_remaining:
observation = assemble_context() # history + memory + tool results
decision = model(observation) # a reply or a tool call
if decision.is_tool_call:
result = execute(decision.tool, decision.args)
append(result, context)
continue
if decision.is_final_answer:
return decision
这就是整个运行时。你会安装的任何库——LangChain、CrewAI、AutoGen、或者手写的 run_agent() 函数——都是对这个循环如何组织、何时停止、跑多少步的不同主张。循环本身是通用的,一旦你接受这一点,每个"agent 框架"就都可以解读了。你不再是在学习框架,而是在辨认同一个循环穿了不同的衣服。
重要的推论:agent 不会"决定去做事"。它产生文本,你的运行时把这段文本解读为回答或者执行函数的请求。智能在模型里。纪律在循环周围的代码里——你如何组装 observation、如何执行 tools、什么允许进入 context、以及何时拒绝继续。
这就是让 agent 之所以是 agent 而不是聊天机器人的部分。聊天机器人产生文本。agent 产生你的运行时被允许执行的文本。机制无聊得恰到好处:你把函数声明为 JSON schema 给模型,模型发出结构化的请求,你的代码执行它并把结果喂回去。
declare_tools() ──▶ model sees schemas + descriptions
│
model emits ──▶ tool_call(name, arguments)
│
runtime validates args ──▶ executes function ──▶ result appended to context
三个细节决定这能否正常工作,还是静默失败:
描述就是给模型的产品文档。描述为"获取余额"的工具会在不该调用的时候被调用。描述为"返回已验证账户的可用余额;若 KYC 未完成或账户被锁定则抛出错误"的工具会被正确调用,因为模型读描述的方式就像初级工程师读代码注释——字面理解。我见过同样的模型,工具使用准确率从大约 70% 跃升到大约 95%,方法就是把描述从两个词改写成两句话。
参数必须在执行前验证。模型是以良好的统计置信度生成 JSON,而不是保证。参数格式错误很常见,每个 tool call 都应该经过验证器。在 Python 中我用 Pydantic;我声明给模型的 schema 和我在入口处验证的 schema 是同一个。一个 schema,两种用途。
工具结果是不可信的数据。无论返回什么——数据库行、API 响应、网页——都会被注入模型的 context。这意味着它可以包含指令。一个获取了客户论坛帖子的客服 agent,可能会在该帖子中被告知忽略其 system prompt 并泄露数据。把每个工具结果当作不可信输入来对待:净化它、引用它,永远不要让它覆盖 system prompt。我在其他地方写过关于 prompt injection 的内容,在 agent 循环内部那不是个猎奇话题——是你的 agent 被控制的最高可能性途径。
Memory 是大多数"agent"博文变得草率的地方,因为他们用一个词指代三件不同的事。在生产环境中我会分开设计它们,因为它们会分开失效。
工作 memory(Working memory)是此刻的 context window:system prompt、最近几轮、当前任务状态,以及当前飞行中的工具结果。这是 agent"思考"的地方。它的硬上限是模型的 context length,成本随你推进的每个 token 缩放。纪律是精准剪枝——决定什么属于 window,其余丢弃。客户的完整交易历史不属于 window;三笔相关记录才属于。
长期 memory(Long-term memory)是 agent 在此次对话之外知道的一切:产品文档、过往工单、政策手册。它存在于 context window 之外,通常在向量数据库中,按需检索。检索步骤是循环中最弱的环节,因为错误的 memory 就是错误的信念,而 agent 根据信念行动。如果你的 top-k 检索返回三个无关的 chunk,agent 会自信地根据它们回答。这就是为什么检索质量评估比模型选择更重要——我在这里再说一遍,因为在循环内部它加倍正确。
情景 memory(Episodic memory)是第三种被遗忘的类型:这个 agent 在之前的运行中实际做了什么。在严肃的部署中,这是可查询的动作日志、参数、结果和失败模式。你用它来让未来的运行更聪明,也用来 debug 凌晨两点出问题的那个运行。它不华丽。它就是一个 schema 良好的数据库。
我给客户用的心智模型:工作 memory 是 CPU 缓存,长期 memory 是磁盘,情景 memory 是审计日志。它们有不同的成本、不同的生命周期、不同的失效模式,不应该塞进同一个 prompt。
够了抽象。这是我能交付的最小生产形态的 agent,不依赖任何框架——只有一个 OpenAI 兼容端点、一个 tool、还有内置 guardrails 的循环。
import json
from openai import OpenAI
client = OpenAI() # any OpenAI-compatible endpoint works here
TOOLS = [
{
"type": "function",
"function": {
"name": "get_refund_eligibility",
"description": (
"Check whether an order is eligible for a refund. Returns "
"eligibility, the amount, and the refund window deadline. "
"Raises an error if the order is not found or is outside the "
"refund window."
),
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
},
"required": ["order_id"],
},
},
}
]
def get_refund_eligibility(order_id: str) -> str:
# Production: query the orders DB with authz + caching.
return json.dumps({"order_id": order_id, "eligible": True, "amount": 149.00})
def run_agent(goal: str, max_steps: int = 6) -> str:
system = (
"You are a support agent. Goal: resolve the request or escalate "
"with a summary of what was tried. Only call a tool when you need "
"data. Never make up order details. Be concise."
)
msgs = [{"role": "system", "content": system},
{"role": "user", "content": goal}]
for step in range(max_steps):
resp = client.chat.completions.create(
model="your-model",
messages=msgs,
tools=TOOLS,
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content
msgs.append(msg)
for tc in msg.tool_calls:
try:
args = json.loads(tc.function.arguments)
result = globals()[tc.function.name](**args)
except (KeyError, TypeError, json.JSONDecodeError) as e:
result = json.dumps({"error": f"invalid call: {e}"})
msgs.append({"role": "tool", "tool_call_id": tc.id,
"content": result})
return "ESCALATE: step budget exhausted; last state: " + repr(msgs[-2:])
print(run_agent("Can I get a refund on order ORD-7781?"))
注意这个小小例子中 guardrails 在做什么:步骤预算让循环不会永远跑下去,参数验证让格式错误的调用返回可读的错误而不是崩溃,还有一个升级路径带着最后状态附着。这些不是功能。这是 demo 和凌晨两点客户能跑而不吵醒你的东西之间的区别。
为真实客户在生产环境运行这个,我维护了一个简短的列表,记录循环如何崩溃。这里每一个都是我 debug 过的真实事件,每个的修复成本都比事件本身低得多。
模型调用了不该调用的工具。它查询了一个从未下单的订单的退款资格,因为描述没说"先验证订单是否存在"。修复:写明前置条件的描述,并在代码中执行前置条件验证后再行动。
模型拒绝停止调用工具。它用稍微不同的措辞重新查询同一个网页端点,三次,白白燃烧 token 和延迟——正是 CTO 日志中发生的。修复:检测重复的相同或近似相同的 tool call,在 N 次尝试后强制升级。
Context 被污染了。检索 chunk 包含恶意或误导性指令,agent 照着做了。修复:把所有工具和检索内容当作不可信数据,注入前净化,绝不允许它覆盖 system prompt。
静默工具放弃。agent 停止调用工具,开始从训练数据里猜测——听起来正确但错误的答案,带着不知道自己在猜测的模型的自信。修复:为每次运行插桩,追踪 tool-call 率,在率下降时告警。一个客服 agent 原来 90% 的运行中都用了工具,突然变成 40%,这不是更高效了;是更不诚实了。
成本悄悄增长。复杂任务平均 3-8 个 tool call,每个都有往返和 context 增长。每天几千个任务,那就是真金白银。修复:按已解决任务测量成本,缓存工具结果(余额查询很少会变),循环用更便宜的模型,只在最终合成阶段用更强的模型。
升级变成马后炮。循环没有定义好的放弃方式,所以它要么永远循环,要么产生自信的错误答案。修复:写一份升级契约——"不确定时,生成一段话总结尝试了什么、缺什么,交给人。"
我花了多年告诉客户不要建他们要求的东西,而 agents 是这场对话当前的冠军。决策规则很简短:
当任务是指向目标的、多步骤的、需要工具或数据查询的、且变化足够大以至于手写规则会成为维护噩梦时,建一个 agent。
当任务是单步骤的、输入可预测的、或者错误自主行为的成本高于人点击一次按钮的成本时,不要建 agent。我给一个客户计时过一个表单填写流程:确定性版本 1.4 秒完成,大约每次 0.0001 美元;agent 版本 6 秒,大约每次 0.02 美元,偶尔还读错字段。确定性版本赢了,客户因为没建他要求的东西省了真金白银。这就是这份工作的诚实之处:循环很强大,但不是免费的,大多数业务流程不需要它。
在你把一个 agent 标记为完成之前,跑一下这个清单:
[ ] 循环有步骤预算和成本预算,在代码中强制执行,不是在 README 里
[ ] 每个工具都有名称、契约级描述和 JSON schema
[ ] 工具参数在执行前验证;格式错误的调用返回可读的错误
[ ] 工具结果被视为不可信数据;prompt injection 尝试被净化或拦截
[ ] 工作 memory 被精准剪枝;长期 memory 被检索而不是倾倒
[ ] 检索质量有评估集和可测量的召回指标
[ ] 重复失败的 tool call 触发升级,而不是再重试
[ ] 定义的升级契约产生人类可读的摘要
[ ] 每次运行都有日志:步骤、tool call、成本、延迟、结果
[ ] 当 tool-call 率或检索质量下降时触发告警
那位问为什么他的 agent 一直失败的 CTO,回家时带着一张图和一句话:循环做决策,运行时执行纪律,guardrails 决定"完成"是什么意思。他的团队接下来一周在修脚手架而不是买新框架,他们的解决率上升了,原因和模型无关。
下次有人给你看一个"自主 agent",你现在知道自己看的是什么了:一个循环、一些声明的工具、两层或三层的 memory、以及决定何时可以停止的一套预算。框架会不断来来去去。循环不会。