01. Agent 大脑:主循环是怎么转起来的
01. Agent 大脑主循环是怎么转起来的从零到一实现一个 AI Agent 框架 · 第一篇1. 为什么需要 Agent Loop先看一个直接的对比。普通 LLM 调用用户帮我查一下 AAPL 的股价然后写个分析报告 ↓ LLM抱歉我无法实时查股价但我可以告诉你...卡住了。模型知道数据截止日期拒绝回答实时问题。加了 Agent Loop 之后用户帮我查一下 AAPL 的股价然后写个分析报告 ↓ LLM[思考] 我需要查股价 → 调用 get_stock_price(AAPL) ↓ 获取到结果$178.23 ↓ LLM[思考] 数据拿到了写分析报告... ↓ 回复用户完整的分析区别在哪后者会自主决定我还需要做什么然后去做再把结果纳入思考。这个思考→行动→观察→继续思考的循环就是Agent Loop智能体主循环。没有循环的 LLM 调用 高级计算器。有循环才叫 Agent。2. 从零开始最小 Agent Loop先别看 axon 的代码我们自己推一遍——一个 Agent Loop 最少需要什么调用工具直接回复用户输入LLM 思考解析意图执行工具反馈结果返回结果整个循环就四个节点LLM 思考 → 解析意图 → 执行工具 → 反馈结果 → 继续思考。直到 LLM 觉得够了直接回复。# 最小 Agent Loop伪代码defagent_loop(prompt):messages[{role:user,content:prompt}]whileTrue:# 1. 思考让 LLM 决定下一步做什么responsellm.chat(messages)# 2. 解析LLM 想调用工具还是直接回复actionparse_action(response)ifaction.typereply:# 直接回复用户结束循环returnaction.contentelifaction.typecall_tool:# 3. 执行工具调用resultexecute_tool(action.tool_name,action.args)# 4. 把结果放回上下文继续循环messages.append(response)messages.append({role:tool,content:result})核心就 4 步步骤做什么为什么需要思考LLM 基于当前上下文决定下一步Agent 的大脑解析从 LLM 输出中提取意图把自然语言转成可执行指令执行调用工具/函数Agent 的手脚反馈把结果放回上下文让 LLM 看到它做了什么这就构成了一个闭环。就这么简单。当然这个最小实现有很多问题。看看下面的场景用户请写一篇 10000 字的分析报告 LLM好我先查数据... → 调用 search_web(AAPL financials) → 调用 search_web(AAPL competition) → 调用 search_web(AAPL risks) → ...无限调用下去坏了它停不下来。这就是工程要解决的问题。3. 工程演进循环需要解决哪些问题从最小实现到可用的 Agent Loop要处理 4 个核心问题。3.1 停不下来怎么办LLM 没有内置的差不多了的判断力。你需要强制限制max_turns 20 # 最多 20 轮工具调用 max_tokens 4096 # 最多消耗 4096 tokens max_time 120 # 最多运行 120 秒任何一个超了强制结束循环。设计原则Agent Loop 必须有硬边界。LLM 负责做什么你负责不能做什么。3.2 上下文越来越长怎么办每轮循环都把新结果塞进 messages对话会无限膨胀。两个问题成本爆炸LLM 按 token 计费越长越贵质量下降中间细节淹没关键信息模型迷失在中间常见策略策略做法代价滑动窗口只保留最近 N 轮丢失早期上下文摘要压缩把旧对话压缩成摘要信息有损关键信息提取只保留工具调用结果需要设计提取规则Axon 的做法是分层压缩先试无损裁剪不行就摘要再不行就丢弃非关键内容。3.3 工具调用怎么注入LLM 怎么知道它能调用哪些工具靠 System Prompt。# System Prompt 里的一段 你是一个 AI 助手可以使用以下工具 - get_stock_price(symbol): 获取股票当前价格 - search_web(query): 搜索互联网 - send_email(to, subject, body): 发送邮件 当你想调用工具时请严格按以下格式输出 function_call {name: get_stock_price, arguments: {symbol: AAPL}} /function_call但更好的做法是让 LLM 原生支持工具调用Function Calling。OpenAI、Claude、Gemini 都支持结构化工具输出不需要自己解析文本。3.4 出错了怎么办工具调用可能失败网络超时、参数错误、权限不足。LLM: → 调用 delete_database() 系统权限不足操作被拒绝 ↓ LLM: 哦那我... → 调用 delete_database() // 又试了一次Agent 可能不断重试同一个失败操作。你需要错误信息清晰返回让 LLM 理解为什么失败重试限制同一个工具连续失败 N 次后跳过用户确认危险操作先问用户4. 代码解剖Axon 的 Agent 循环现在来看 Axon 的实际代码。核心文件是src/agent.ts。先看整体结构未超限超限是否是否LLM 主动停止未停止用户输入1. 初始化上下文加载系统提示词2. 进入循环检查 max_turns2.1 调用 LLM强制结束2.2 解析响应有工具调用执行工具权限检查结果放回上下文有回复文本追加到消息列表2.3 检查终止条件返回最终回复2.4 上下文压缩完成现在看关键代码。我简化并注释了核心逻辑// src/agent.ts核心循环部分简化版asyncfunctionprocessMessage(input:string):Promisestring{// 1. 初始化上下文包含系统提示词和历史消息constcontextinitializeContext(input);// 2. 循环计数letturn0;constMAX_TURNS25;while(turnMAX_TURNS){turn;// 2.1 调用 LLMconstresponseawaitllm.complete(context.messages);// 2.2 检查 LLM 是否主动停止if(response.stopReasonend_turn){break;}// 2.3 处理工具调用for(consttoolCallofresponse.toolCalls){// 权限检查后续文章详讲awaitcheckPermission(toolCall);// 执行工具constresultawaitexecuteToolCall(toolCall);// 把结果放回上下文context.addToolResult(toolCall.id,result);}// 2.4 如果 LLM 有文本回复追加到消息列表if(response.text){context.addAssistantMessage(response.text);}// 2.5 上下文压缩如果太长if(context.tokensTHRESHOLD){context.compress();}}// 3. 组装最终回复returncontext.getFinalResponse();}几个关键设计点值得注意① 循环边界是硬性的MAX_TURNS 25是硬编码上限。不管 LLM 多想继续25 轮必须停。这个数字来自实践大多数任务 5-10 轮内完成25 是安全余量。② 工具调用是批量的注意代码里是for (const toolCall of response.toolCalls)—— LLM 一次可以请求多个并行工具调用。Axon 的处理方式是逐个执行因为有些工具有副作用如发送邮件和删除文件不该并行。③ 上下文压缩是触发式的不是每轮都压缩只在 token 数超过阈值时才触发。这里用了惰性策略不提前优化等问题出现了再处理。5. 动手实验跑一个自己的 Agent Loop理论讲完了动手跑一个。准备工作你需要一个 LLM API KeyOpenAI / Anthropic / 任意兼容的 API。最小实现# 最小 Agent LoopPython OpenAI SDKimportjsonfromopenaiimportOpenAI clientOpenAI(api_keyyour-key)# 定义工具tools[{type:function,function:{name:get_weather,description:获取城市天气,parameters:{type:object,properties:{city:{type:string}},required:[city]}}}]defexecute_tool(name,args):实际执行工具ifnameget_weather:# 模拟天气查询returnf{args[city]}的天气晴25°Creturnf未知工具{name}defagent_loop(user_input):messages[{role:user,content:user_input}]max_turns10turn0whileturnmax_turns:turn1print(f\n--- 第{turn}轮 ---)responseclient.chat.completions.create(modelgpt-4o,messagesmessages,toolstools,tool_choiceauto)msgresponse.choices[0].messageifnotmsg.tool_calls:# LLM 直接回复结束returnmsg.content messages.append(msg)fortool_callinmsg.tool_calls:nametool_call.function.name argsjson.loads(tool_call.function.arguments)print(f→ 调用工具{name}({args}))resultexecute_tool(name,args)messages.append({role:tool,tool_call_id:tool_call.id,content:result})return已达最大轮数强制结束。# 跑起来resultagent_loop(北京的天气怎么样适合散步吗)print(f\n最终回复{result})把上面代码保存为agent_loop.py填入你的 API Key然后运行python agent_loop.py你会看到 LLM 先调工具拿数据再基于数据生成回复——这就是一个完整的 Agent Loop。实验一下改几个参数看看行为变化把max_turns改成 1→ LLM 没机会调工具就被强制结束给一个复杂任务“帮我规划一次旅行查 3 个城市的天气推荐最佳目的地”让工具返回错误把execute_tool的返回值改成error: 服务不可用看看 LLM 怎么应对下一篇预告让 Agent 有手有脚 —— 工具系统的设计与演化当 LLM 想调工具时工具从哪来参数怎么校验结果怎么返回下一篇从零搭建一个完整的工具系统。