最简智能体Pi Agent核心架构拆解:从最小Agent闭环到工程实践
Agent框架层出不穷的这几年有一个现象很值得注意真正让开发者卡住的往往不是模型能力不够强也不是工具数量不够多而是框架本身越来越像一个黑盒。你照着文档把代码跑起来了但一旦出现问题你根本不知道它在哪一步做了什么决策也不知道该从哪里下手排查。最近频繁出现在技术社区热搜里的 Pi Agent之所以能在众多智能体工具里被反复讨论并不是因为它的功能列表比别人长而是因为“最简”这个定位恰好戳中了大家已经厌倦重框架、黑盒框架的节点。本文不会把 Pi Agent 的安装文档再复述一遍而是想借这个选题把智能体背后真正绕不开的核心架构拆开讲清楚。文章会做三件事第一分析“最简智能体”这个说法背后的架构设计逻辑第二给出一个不依赖任何重框架、只用少量代码就能跑通的最小 Agent 核心循环示例第三把新手接入时最常踩的坑和工程化建议整理出来。读完你会得到一张判断地图知道什么样的复杂是必要的什么样的复杂其实可以砍掉。如果你正在学习 Agent 开发或者刚接触 Pi Agent、想理解它和 Dify、Coze、Hermes 这类智能体平台的差异这篇文章会比较适合你。它不是一份官方文档的搬运而是一份架构视角的导读和实践参考。1. 这篇文章真正要解决的问题先说一个我观察到的现象。在很多 Agent 相关社群里新手的提问往往不是“Agent 能做什么”而是“我该从哪里开始”。框架文档动辄几十页概念图一张比一张复杂有 Orchestrator、Planner、Memory、Tool Use、Multi-Agent 协作……看起来很高大上但真正上手时很多人卡在了同一个地方不知道一个最小的 Agent 应该长什么样。你可以把这个问题理解成学做饭。给你一本几百页的米其林菜谱你反而不知道今晚该吃什么但如果先学会一道番茄炒蛋你至少有了一个可以下厨的起点。Agent 开发也是同样的道理很多框架把“什么都能做”写在了简介里却没有告诉你“一个能跑起来的 Agent 核心循环”只需要哪几个组件。Pi Agent 之所以值得专门聊是因为它的定位和很多框架相反。它的关键词是“最简”也就是说它在架构上刻意做减法只保留让 Agent 能够完成一次任务闭环的必要组件。这里的“必要”不是随便拍脑袋定出来的而是来自工程层面的取舍Agent 的本质是一个循环不是一堆模块的堆叠。理解了这一点你再去看任何智能体框架都会轻松很多。无论是 Dify 这种图形化平台还是 Coze 这种在线搭建工具它们的底层逻辑都离不开“模型调用、工具调用、结果反馈、再决策”这个循环。差别只在于谁把循环封装得更深谁把循环暴露得更清晰。这篇文章要解决的问题就是帮你把循环看清楚。当你看懂了一个最小 Agent 的运转方式再去评估 Pi Agent、Hermes、Opencode、Codex 这些工具各自的侧重点就不会再被宣传话术带偏。2. 核心设计思想“最简”不是功能少而是闭环短很多人第一次听说“最简智能体”时会下意识把它理解成“功能残缺的智能体”。这是一个很大的误解。Pi Agent 所代表的“最简”并不是砍掉功能而是压缩从输入到输出的决策链路让每一个环节都足够透明。我们对比一下两种设计思路设计思路典型特征调试体验适用阶段功能堆叠型框架组件齐全、配置项多、调度复杂出问题时很难定位是哪一层的问题团队成熟、需求明确、需要统一规范闭环最短型框架核心循环精简、组件少、路径短每一步都可观测、可控个人开发、快速验证、学习原理传统的智能体框架往往会把规划、执行、记忆、工具管理拆成独立模块再通过消息队列或事件总线把这些模块串起来。架构图确实漂亮但代价是引入大量间接层。一个请求进来先经过规划器再进入任务队列然后由执行器调用工具结果再回传给记忆模块最后重新生成下一轮计划。只要中间有一个环节状态同步出问题整个链路就会变得很难排查。Pi Agent 这类“最简”设计的思路是把重心放回到LLM 自身的推理能力上。它不预设复杂的任务编排而是通过一个明确的循环让模型在每一步都自己决定下一个动作。这其实有点像经典 ReAct 模式的工程化改良也就是在“思考 - 行动 - 观察”之间建立一个封闭循环。一个最简 Agent 的核心闭环只需要三样东西一个可调用的 LLM负责理解和决策一组工具让 Agent 能对外部环境产生影响一个循环控制结构负责把模型输出解析成动作再把动作结果反馈给模型。少了任何一样Agent 都无法完成一个完整的任务闭环。这就是“最小可运行集合”的概念它和数学里的“基”很相似不要求元素最多只要求不可或缺。这个设计还有一个额外的好处可解释性。因为闭环短你很容易在每一步打印出模型到底看到了什么、决定做什么、执行结果如何。这种透明性在调试阶段尤其宝贵尤其是当你使用的模型在复杂任务上表现不稳定时能看到完整的决策链比任何日志系统都管用。3. 核心架构拆解控制层、工具层、记忆层如果要把 Pi Agent 这类最简智能体的架构画成一张图核心只有三层。我把每一层都讲清楚并说明这层的职责边界以及新手最容易误解的地方。3.1 控制层Agent 的“大脑”控制层解决的核心问题是下一步该做什么。在传统程序里控制流由开发者写死if-else 或者状态机决定程序走向。在 Agent 里控制流的决策权交给了大模型。每一次循环控制层都会把当前的目标、已有信息和可用的工具列表发给模型让模型输出下一步动作。这块需要特别注意的是控制层并不负责“执行”工具它只负责“决定”调用哪个工具、传入什么参数。如果把执行也塞进控制层你会发现代码很快变成一团乱麻。一个典型的最简控制循环代码如下# agent_minimal.py # 最简 Agent 核心循环思考 - 行动 - 观察 import json import os import urllib.request def call_llm(messages, tools): 调用兼容 OpenAI 协议的 LLM 接口。 如果使用本地模型或代理服务请自行替换 base_url 和 api_key。 api_key os.getenv(LLM_API_KEY, EMPTY) base_url os.getenv(LLM_BASE_URL, http://localhost:8000/v1) model os.getenv(LLM_MODEL, qwen2.5:7b) url base_url.rstrip(/) /chat/completions payload { model: model, messages: messages, tools: tools, tool_choice: auto, } req urllib.request.Request( url, datajson.dumps(payload).encode(utf-8), headers{Content-Type: application/json, Authorization: fBearer {api_key}}, methodPOST, ) with urllib.request.urlopen(req, timeout60) as resp: result json.loads(resp.read().decode(utf-8)) return result[choices][0][message]这段代码里的call_llm是整个控制层的中枢。它干的事情很简单把当前对话消息和可用工具列表发给模型然后让模型返回一个响应。响应里可能是纯文本回复也可能包含工具调用请求。控制层拿到响应之后再决定下一步是继续调用工具还是结束循环。3.2 工具层Agent 的“手脚”控制层负责决策工具层负责执行。工具层把外部能力封装成统一接口让模型可以通过结构化参数来调用。这里的核心设计原则是工具签名越简单越好。一个函数能被 Agent 正确调用前提是它的入参和出参都是清晰的 JSON 格式。如果你把一个复杂的类方法直接暴露给 Agent模型经常会在参数格式上出错。更推荐的做法是用独立的函数做一层薄封装把复杂逻辑藏在函数内部。下面是一个最小工具层的示例包含两个工具一个是查询“当前时间”一个是计算器。其中get_tools_schema返回给模型看的工具描述run_tool是实际的执行入口。# tool_layer.py # 工具层定义 Agent 可调用的工具函数以及给 LLM 看的工具描述 schema import datetime import json def get_current_time(): 返回当前系统时间用于测试 Agent 的工具调用能力。 return {current_time: datetime.datetime.now().isoformat()} def calculator(expression): 一个简单的计算器只支持加减乘除不要在生产环境直接执行任意字符串表达式。 allowed set(0123456789-*/(). ) if any(c not in allowed for c in expression): raise ValueError(表达式包含非法字符) # 在受限字符集下执行且仅用于示例 result eval(expression, {__builtins__: {}}, {}) return {result: result} def get_tools_schema(): 返回工具描述让 LLM 知道有哪些工具可用、参数长什么样。 return [ { type: function, function: { name: get_current_time, description: 获取当前系统时间, parameters: { type: object, properties: {}, }, }, }, { type: function, function: { name: calculator, description: 计算简单数学表达式例如 (12)*3, parameters: { type: object, properties: { expression: { type: string, description: 数学表达式字符串, } }, required: [expression], }, }, }, ] def run_tool(name, arguments): 根据模型返回的工具名称和参数分发到具体的执行函数。 if name get_current_time: return get_current_time() if name calculator: args json.loads(arguments) return calculator(args[expression]) raise ValueError(f未知工具: {name})工具层写起来不难真正难的是调参和边界控制。模型并不总是能猜对你的参数类型所以工具函数内部一定要做校验不要把未经检查的输入直接丢给底层系统。上面示例里的calculator就做了一个字符白名单校验这是工具层最基本的自我防护。3.3 记忆层Agent 的“上下文”记忆层在最小 Agent 里往往是最容易被忽视却又最容易决定成败的部分。它的职责是维护对话历史让模型知道“我们已经聊过什么、做过什么”。在最简架构里你不需要引入向量数据库也不需要复杂的知识库管理。只需要一个 list把用户输入、模型思考、工具执行结果按顺序追加进去。每一轮循环都把完整的 messages 列表发给模型模型就能基于最新状态做下一步决策。# memory_layer.py # 记忆层简化版用一个 list 维护对话上下文 def init_messages(system_prompt): return [{role: system, content: system_prompt}] def add_user_message(messages, content): messages.append({role: user, content: content}) return messages def add_assistant_message(messages, content): messages.append({role: assistant, content: content}) return messages def add_tool_result(messages, tool_call_id, content): messages.append({role: tool, tool_call_id: tool_call_id, content: content}) return messages def trim_messages(messages, max_len20): 简单粗暴的上下文截断策略只保留系统提示和最近 max_len 条消息。 生产环境建议用 token 数做精确控制。 if len(messages) max_len: return messages return [messages[0]] messages[-max_len 1:]这里的trim_messages其实已经引出了一个工程问题上下文长度总会耗尽。不同的模型上下文长度不一样如果你的任务是长流程任务一旦超过模型的 context window最远端的信息就会被截断导致 Agent“失忆”。面向生产环境时记忆层的设计会复杂很多比如用向量库存历史、用摘要压缩早期对话。但在理解核心架构阶段先用 list 跑通最重要。3.4 三层如何协作三层写完之后协作方式就是一个 while 循环。控制层决定调用工具时把工具名和参数传给工具层工具层执行完把结果通过工具消息追加进记忆层控制层再带着新的记忆去问模型。如此往复直到模型不再请求调用工具直接输出最终答案。这个协作模型是理解所有 Agent 框架的钥匙。你去看 Dify 的工作流编排、Coze 的 Bot 搭建本质上都是在用图形化方式控制这个循环只不过把每一层都封装成了可视化的节点。4. 最小可运行示例用标准库实现 Agent 核心循环理解了三个层级之后接下来我们把它们组装成一个真正能跑的 Agent。为了照顾到不同读者的环境我尽量少引入第三方依赖直接用 Python 标准库urllib调用兼容 OpenAI 协议的接口。这样你无论使用云端的模型服务还是本地部署的模型都能按同样的方式对接。4.1 环境准备建议环境如下版本以你本地实际项目为准Python 3.9 及以上一个可用的 LLM API兼容 OpenAI 的/v1/chat/completions接口即可环境变量LLM_BASE_URL、LLM_API_KEY、LLM_MODEL如果你使用的是本地模型服务比如 Ollama 或 vLLM 启动的 OpenAI 兼容服务通常会得到类似http://localhost:8000/v1的地址。如果你用的是云端服务请把对应的 Base URL 和 API Key 填入环境变量。权限和密钥请妥善管理不要在代码里硬编码。export LLM_BASE_URLhttp://localhost:8000/v1 export LLM_API_KEYEMPTY export LLM_MODELqwen2.5:7b4.2 组装 Agent 主循环下面是一个不依赖第三方库的最小 Agent 实现文件。我把它命名为minimal_agent.py代码中包含了完整的循环控制逻辑。# minimal_agent.py # 最简 Agent 核心循环思考 - 行动 - 观察 import json import os import urllib.request # ---------- 控制层 ---------- def call_llm(messages, tools): api_key os.getenv(LLM_API_KEY, EMPTY) base_url os.getenv(LLM_BASE_URL, http://localhost:8000/v1) model os.getenv(LLM_MODEL, qwen2.5:7b) url base_url.rstrip(/) /chat/completions payload { model: model, messages: messages, tools: tools, tool_choice: auto, } req urllib.request.Request( url, datajson.dumps(payload).encode(utf-8), headers{Content-Type: application/json, Authorization: fBearer {api_key}}, methodPOST, ) with urllib.request.urlopen(req, timeout60) as resp: result json.loads(resp.read().decode(utf-8)) return result[choices][0][message] # ---------- 工具层 ---------- def get_current_time(): import datetime return {current_time: datetime.datetime.now().isoformat()} def calculator(expression): allowed set(0123456789-*/(). ) if any(c not in allowed for c in expression): raise ValueError(表达式包含非法字符) result eval(expression, {__builtins__: {}}, {}) return {result: result} def get_tools_schema(): return [ { type: function, function: { name: get_current_time, description: 获取当前系统时间, parameters: {type: object, properties: {}}, }, }, { type: function, function: { name: calculator, description: 计算简单数学表达式例如 (12)*3, parameters: { type: object, properties: { expression: {type: string, description: 数学表达式字符串} }, required: [expression], }, }, }, ] def run_tool(name, arguments): if name get_current_time: return get_current_time() if name calculator: args json.loads(arguments) return calculator(args[expression]) raise ValueError(f未知工具: {name}) # ---------- 记忆层 ---------- def init_messages(system_prompt): return [{role: system, content: system_prompt}] def trim_messages(messages, max_len10): if len(messages) max_len: return messages return [messages[0]] messages[-max_len 1:] # ---------- Agent 主循环 ---------- def agent_run(user_query, max_steps5): system_prompt 你是一个最简智能体。在合适的场景下请优先使用工具来回答用户问题。 messages init_messages(system_prompt) messages.append({role: user, content: user_query}) tools get_tools_schema() for step in range(max_steps): print(f\n Step {step 1} ) response call_llm(messages, tools) if response.get(tool_calls): for tool_call in response[tool_calls]: fn_name tool_call[function][name] fn_args tool_call[function][arguments] print(f[Action] 调用工具: {fn_name}, 参数: {fn_args}) # 将模型请求追加到上下文 messages.append({ role: assistant, content: response.get(content) or , tool_calls: response[tool_calls], }) # 执行工具 observation run_tool(fn_name, fn_args) print(f[Observation] 执行结果: {observation}) messages.append({ role: tool, tool_call_id: tool_call[id], content: json.dumps(observation, ensure_asciiFalse), }) messages trim_messages(messages) continue # 没有 tool_calls说明模型已经生成最终答案 final_answer response.get(content) or print(f[Final Answer] {final_answer}) return final_answer return 达到最大步数Agent 循环结束。 if __name__ __main__: query input(请输入你的问题: ) agent_run(query)4.3 代码的关键点解释这段代码虽然不到 120 行但已经完整包含了一个 Agent 的核心机制。几个关键点值得单独解释第一call_llm里设置了tool_choice: auto。这意味着模型可以自行决定这次该回复普通文本还是应该调用工具。如果你希望模型每次都必须调用工具可以改成required但日常场景下auto更灵活。第二循环跳出条件只有一个模型返回的响应里没有tool_calls字段。换句话说Agent 的结束条件不是由代码写死的而是由模型自主决定的。如果模型觉得不需要工具就能回答它会直接输出文字如果模型觉得需要多次调用工具它会在一次循环后继续发起下一次调用。第三trim_messages在每个步骤之后被调用目的是控制上下文长度。这里的max_len写的是 10你可以根据模型上下文窗口大小进行调整。要注意的是截断策略不能粗暴地把所有消息都砍掉至少需要保留系统提示和当前正在处理的那一轮工具调用记录。5. 运行结果与效果验证完成代码后在终端执行下面的命令python minimal_agent.py程序会提示你输入问题。我们分别测试两个场景。5.1 测试场景一纯文本回答输入什么是智能体预期输出模型直接返回一段解释不调用任何工具循环在第一轮就结束。因为模型认为回答问题不需要工具所以在tool_calls为空的情况下直接输出最终答案。5.2 测试场景二工具调用输入现在几点了顺便帮我算一下 (128)*3 等于多少。预期输出大致如下 Step 1 [Action] 调用工具: get_current_time, 参数: {} [Observation] 执行结果: {current_time: 2026-01-01T10:00:00.123456} [Action] 调用工具: calculator, 参数: {expression: (128)*3} [Observation] 执行结果: {result: 60} Step 2 [Final Answer] 当前时间是 2026-01-01 10:00:00计算结果为 60。这里需要注意的是模型可能会在第一步只调用一个工具把另一个工具调用放到第二步这取决于模型自身的决策。AI 的行为不完全确定只要最终能给出正确答案流程就是成功的。5.3 如何判断成功与失败判断成功的标准有三个Agent 能够根据问题内容自主决定是否调用工具工具调用的参数能被run_tool正确解析并执行工具的返回结果被成功追加到上下文模型最终利用这个结果生成答案。如果运行失败第一步先看终端有没有打印异常信息。最常见的失败原因是网络连接不上模型服务其次是 API Key 错误再其次是模型不支持 tools 接口。关于这些问题下一节会给出更细的排查方向。6. 常见问题与排查思路以下是我认为实践中最常见的问题整理成表格方便查阅。问题现象可能原因排查方式解决方案请求 LLM 超时或连接失败网络不通或 BASE_URL 配置错误先用 curl 测试接口连通性检查网络和 URL确认地址末尾包含/v1返回 401 认证失败API Key 错误或未设置打印环境变量确认是否存在重新配置LLM_API_KEY环境变量模型不返回tool_calls模型本身不支持 Function Call / Tools 接口查看模型文档确认是否兼容 OpenAI tools 协议更换支持 tools 的模型或升级模型版本工具执行报未知工具模型幻觉生成了不存在的工具名打印messages查看模型输出在工具 schema 中加重描述降低幻觉概率工具参数解析失败模型返回的 JSON 参数不合法打印原始arguments字符串在run_tool里做容错必要时用正则提取参数Agent 一直不停调用工具循环缺少终止条件或任务本身模糊检查max_steps是否生效强制设置最大步数并在第 n 步返回当前结果上下文长度超限工具调用轮数太多或历史消息太长观察报错信息是否提到 max tokens调小max_len或用摘要压缩历史计算器工具执行了危险代码eval使用不当检查是否对表达式做了字符白名单校验生产环境不要用 eval改用安全的 AST 求值方案这里的第 6 条尤其要说一下。Agent 的工具调用并不保证永远按照预期发展模型可能因为任务描述不清晰而反复调用同一个工具。工程上最稳妥的做法永远是设置最大迭代次数也就是代码里的max_steps。宁可让 Agent 提前结束也不要让它陷入无限循环。另外calculator中的eval仅用于教学演示。生产环境如果要做公式计算建议使用ast模块解析表达式或者直接使用专门的表达式求值库。7. 从最小架构到完整工程工程化建议当你能跑通上面这个最小 Agent 循环以后下一步是把它放到真实项目里。很多开发者在这里会发现跑通 demo 很容易上了生产却一堆问题。这里有几个工程化建议我认为优先级是最高的。7.1 给工具加权限边界工具层最容易被忽略的是权限控制。当你把 Agent 接入数据库、文件系统或第三方 API 时务必要给每个工具明确标注权限范围。建议先问自己几个问题这个工具能被未登录用户调用吗工具的参数是否会被外部输入控制工具的返回结果是否包含敏感数据一个通用的做法是工具白名单机制。在工具层维护一个字典只允许调用预先注册过的函数任何动态导入或者反射调用的方式都应该被禁止。# tool_registry.py # 工具注册表推荐在工程化阶段使用显式注册方式 TOOL_REGISTRY { get_current_time: { handler: get_current_time, description: 获取当前系统时间, required_roles: [user, admin], enable_audit: True, }, calculator: { handler: calculator, description: 计算简单数学表达式, required_roles: [user], enable_audit: False, }, } def execute_tool(name, arguments, user_roleuser): if name not in TOOL_REGISTRY: raise ValueError(f工具不存在或未注册: {name}) tool TOOL_REGISTRY[name] if user_role not in tool[required_roles]: raise PermissionError(f当前角色无权限调用工具: {name}) if tool.get(enable_audit): # 生产环境应写入审计日志 print(f[AUDIT] user{user_role} tool{name} args{arguments}) return tool[handler](**arguments)这样的注册表结构比直接写 if-else 分发更清晰也为后续接入配置中心和权限系统预留了位置。7.2 循环里加日志和追踪最小示例里我用了print来打印关键信息。生产环境建议把这些输出统一接入日志系统关键节点打上 trace_id这样一次 Agent 执行的全链路都可以被追溯。日志至少要包含这几个信息模型输入的消息列表可脱敏模型返回的工具调用请求工具执行结果每一步消耗的 token 数和耗时最终退出原因正常完成还是达到最大步数。有了这些日志你才能回答最基本的运维问题一次用户请求Agent 到底做了几次工具调用每一步花了多少钱和时间。Copy 到表格里就是日志类型关键字段用途请求日志trace_id、模型名、输入 token 数成本统计和延迟分析工具日志工具名、参数、返回状态工具正确性检查循环日志step 序号、决策内容定位逻辑错误终止日志退出原因、总耗时判断是否需要调整 max_steps7.3 上下文管理不能只靠截断上文的trim_messages是最粗暴的截断方式生产环境很快会遇到问题。比如一个长任务前面几步已经完成了关键信息提取如果直接截掉Agent 后面就失去了判断依据。更稳妥的做法是分层记忆短期记忆最近几轮的工具调用和模型输出原样保留工作记忆当前任务的中间结论每次工具返回后做一次摘要长期记忆跨会话的知识存放在外部的向量数据库或普通数据库里。一般情况下小项目的 Agent 只需要做好短期记忆和工作记忆就够了。只有当你的 Agent 需要处理跨会话、跨用户的历史信息时才需要引入向量数据库。不要一上来就上向量数据库这是很多项目过度设计的典型例子。7.4 为 Agent 增加人工确认机制在自动化任务中Agent 调用了破坏性工具比如删除文件、清空数据库、发送邮件一旦决策失误后果会比较麻烦。生产环境建议在工具层增加人工确认回调机制。当 Agent 请求调用高风险工具时系统先挂起执行返回一个确认链接给用户用户点击同意后再真正执行。这个机制的实现并不复杂在工具注册表里给每个工具增加need_confirm字段即可。重点是要在架构层面预留这个能力而不是等出事之后才补丁式地加。8. 你还需要知道的Pi Agent 与几个常见框架的定位差异聊完最小架构我们再看回 Pi Agent 在整个智能体工具生态里的位置。最近围绕它的讨论大多是“Pi Agent 和 Opencode、Codex 哪个好用”“Pi Agent 和 Hermes 怎么选”这类问题。这类问题其实没有一个放之四海而皆准的答案但我们可以从架构定位上做一些判断。有一个很值得留意的现象Pi Agent 的热搜词里除了“安装”“官网”之外出现频率很高的还有“编码 Skill”“Agent 开发”“框架对比”。这说明它的核心受众主要是工程师关注的是能不能用更轻的配置方式完成编码类自动化任务。它和 Dify、Coze 这类面向业务人员的可视化搭建平台定位不同也和 Hermes、Opencode 这类同样面向开发者的 Agent 工具存在差异化。具体选型时可以从四个方面去对比闭环透明度工具是否让你看清每一步决策Pi Agent 的“最简”定位通常意味着更好的可观测性默认能力 vs 扩展成本框架开箱自带的功能越多你在自定义时的自由度往往越低Skill 机制Pi Agent 的编码 Skill 意味着可以针对特定任务类型比如代码生成、代码审查做定向优化多智能体协作如果你需要多个 Agent 分工协作那么单 Agent 的最简设计是否仍然适用需要仔细评估。我的建议是不要以“哪个工具最强”作为选型依据而是以“哪个工具的闭环最短、最适合我的任务”为依据。工具只是把架构思想工程化了真正决定你项目天花板的是你对核心循环的理解深度。9. 总结与下一步实践方向这篇内容从架构角度拆解了最简智能体 Pi Agent 的核心思路也给出了一个不依赖重框架的最小 Agent 实现。你现在应该能回答这几个问题了一个 Agent 的最小可运行闭环需要哪几个组件控制层、工具层、记忆层各自负责什么工具调用循环里的结束条件是什么在工程化阶段需要补上哪些能力。下一步的实践路径我建议按顺序做三件事第一把上面的minimal_agent.py跑通分别测试纯文本回答和工具调用两个场景。第二给代码增加一个新的工具函数比如获取天气、查数据库等重点关注模型的参数生成能力。如果模型频繁传错参数多试几次调整工具描述里的 description往往比改代码更有效。第三给自己设定一个稍微复杂一点的任务比如“帮我读取某个目录下所有 Python 文件统计每个文件的行数并按行数排序输出”。这个任务要求 Agent 多次调用工具并整合结果是检验循环控制逻辑的好练习。把最小可运行闭环跑通之后你再去对比 Dify 这类平台是如何封装工作流的或者看 Pi Agent、Hermes 这类工具是如何做 Skill 编排的都会有完全不同的理解深度。尤其是当你在项目里遇到 Agent 行为不符合预期时基于这套闭环思维你能更快定位是模型问题、工具问题还是上下文管理的问题。