本文回答什么问题:AgentRunner.run() 内部怎么循环?_request_model怎么调 LLM?_execute_tools怎么跑工具?迭代上限 / 流式 / 推理块怎么协调?目标读者:LLM Agent 开发者 / Provider 适配者预计阅读时间:16 分钟源码版本:GitHub HKUDS/nanobotmain 分支主线代码(仓库相对路径)AgentRunner(nanobot/agent/runner.py,约 1505 行)是 nanobot 模型层多循环——把单次 LLM 调用扩展为多轮 LLM 工具执行的迭代过程。1. 整体定位:为什么 AgentRunner 单独成模块如果让 AgentLoop 直接调 LLM,AgentLoop 就要处理 200 轮迭代 工具调用 流式——膨胀到 5000 行。AgentRunner 抽出模型层关注,AgentLoop 只负责调度。核心要点速查(建议收藏)核心文件:nanobot/agent/runner.py(约 1505 行)主入口:AgentRunner.run(spec, hook)跑单回合(可能 200 轮)3 个核心子方法:_request_model(messages)/_execute_tools(tool_calls)/_finalize(result)迭代上限:默认 200 次(Config.max_iterations)4 类 stop_reason:tool_calls/completed/max_iterations/error2. AgentRunner.run() 主循环asyncdefrun(self,spec:AgentRunSpec,hook:AgentTurnHook|NoneNone)-AgentRunResult:messageslist(spec.messages)outbound:list[OutboundMessage][]iteration0whileiterationspec.max_iterations:iteration1# 1. 调 LLMresponseawaitself._request_model(messages)outbound.extend(response.stream_events)# 流式片段# 2. 决定是否继续ifresponse.stop_reasoncompleted:# 已生成最终回复,退出循环outbound.append(self._finalize(response))returnAgentRunResult(messagesmessages,outbound_messagesoutbound,iterationiteration)ifresponse.stop_reasontool_calls:# 3. 跑工具tool_resultsawaitself._execute_tools(response.tool_calls)messages.extend(tool_results)continue# 下一轮# 4. 达到 max_iterationsreturnAgentRunResult(messagesmessages,outbound_messagesoutbound,iterationiteration,stop_reasonmax_iterations)3. 3 个核心子方法3.1_request_model(messages)→ LLMResponseasyncdef_request_model(self,messages:list[Message])-LLMResponse:responseawaitself._provider.chat(messagesmessages,toolsself._spec.tools.to_openai_schema(),# 工具清单转 OpenAI 格式streamTrue,# 流式)# 流式累积contenttool_calls:list[ToolCallRequest][]stream_events:list[OutboundEvent][]asyncforchunkinresponse:ifchunk.typecontent_delta:contentchunk.text stream_events.append(StreamDeltaEvent(contentchunk.text))elifchunk.typetool_call_delta:tool_calls.append(chunk.tool_call)elifchunk.typereasoning_delta:stream_events.append(StreamDeltaEvent(contentchunk.text,kindreasoning))returnLLMResponse(contentcontent,tool_callstool_calls,stream_eventsstream_events,stop_reasontool_callsiftool_callselsecompleted,)关键:所有 8 个 Provider 都通过LLMProvider.chat()统一接口(详见第 17 章)流式片段直接转OutboundEvent让 AgentLoop 投递给通道工具调用转ToolCallRequest(详见第 17 章)3.2_execute_tools(tool_calls)→ list[ToolResult]asyncdef_execute_tools(self,tool_calls:list[ToolCallRequest])-list[Message]:results:list[Message][]forcallintool_calls:toolself._spec.tools.get(call.name)iftoolisNone:results.append(Message(roletool,namecall.name,contentfError: tool {call.name} not found))continue# scope 检查(workspace / network / pairing,详见第 29 章)ifnotself._tool_scope.allows(tool,call):results.append(Message(roletool,namecall.name,contentError: permission denied))continue# 调工具resultawaittool.execute(**call.arguments)results.append(Message(roletool,namecall.name,contentresult.content))returnresults关键:工具调用按call.name查 ToolRegistryscope 校验失败的工具返回错误消息(不抛异常)工具结果作为roletool消息反馈给 LLM3.3_finalize(response)→ OutboundMessagedef_finalize(self,response:LLMResponse)-OutboundMessage:returnOutboundMessage(channelself._spec.channel,chat_idself._spec.chat_id,contentresponse.content,eventNone,# 纯文本)4. 4 类 stop_reasonstop_reason含义后续动作tool_callsLLM 想调工具_execute_tools→ 下一轮completedLLM 已生成最终回复退出循环max_iterations达到 200 轮强制结束(返回当前结果)errorLLM 调用失败返回错误消息(详见 §5)5. 错误处理asyncdef_request_model(self,messages):try:responseawaitself._provider.chat(...)exceptProviderErrorase:# fallback provider(详见第 20 章)ifself._fallback:responseawaitself._fallback.chat(...)else:returnLLMResponse(contentf⚠{e},stop_reasonerror)returnresponse3 层容错:单 Provider 失败 → FallbackProvider 兜底Fallback 失败 → 返回错误消息给通道AgentLoop 收到错误消息 → 通知用户6. 实战:增加轮次上限# config.yamlmaxIterations:50# 默认 200,降为 50# Pythonfromnanobot.configimportload_config configload_config()print(config.max_iterations)# 507. 常见问题 / 避坑Q:LLM 流式输出什么时候结束?A:Provider 的stream返回的 chunk 序列结束 →_request_model完成。AgentLoop 把累积的stream_events投递给通道渲染。Q:max_iterations太小会怎样?A:LLM 多轮任务未完成就强制结束,可能产生半成品回复。建议设 50-200。Q:工具调用超时怎么办?A:每个 Tool.execute 有自己的timeout(默认 60s);超时会抛ToolTimeoutError,被_execute_tools捕获后转roletool错误消息。8. 小结主循环:while iteration max_iterations: _request_model → 决定 stop_reason → _execute_tools / 退出关键模块:3 个核心子方法:_request_model/_execute_tools/_finalize设计要点:4 类 stop_reason:tool_calls/completed/max_iterations/error常见坑:3 层容错:Provider 错误 → FallbackProvider → 返回错误消息本文要点速查run()主循环默认 200 轮,见 §23 个核心子方法见 §34 类 stop_reason见 §4下一步:第 12 章《ContextBuilder 系统 Prompt》—— 阶段 ③ 的build_messages()详细展开按角色推荐LLM Agent 开发者:必读(模型层核心)LLM Provider 适配者:必读(Provider 接口契约)系统架构师:选读(知道 AgentRunner.run 即可)聊天通道开发者:选读Tool / MCP 工具开发者:选读(知道 Tool 怎么被调用即可)下一步第 12 章《ContextBuilder 系统 Prompt》——_request_model之前的 messages 怎么构造(主题群Agent 核心,第 3 周)第 17 章《LLMProvider 抽象》——chat()接口完整契约(主题群LLM Provider,第 4 周)第 20 章《FallbackProvider 兜底》——_request_model错误的兜底路由(主题群LLM Provider,第 4 周)tags:#nanobot#AI Agent#LLM#Python#源码解析#AgentRunner#工具调用