1. 先搞清楚Agent和普通聊天机器人之间的那道界线
1.1 聊天补全和智能体的本质差异:不只会说,还会做
如果你用过市面上任何一款大模型聊天产品,会发现一个共性现象:你让它"帮我把上周的会议纪要找出来,整理成要点发到群里",它多半会回复你一大段文字,告诉你"可以这样做、应该那样做",然后……就没有然后了。它不会真的去翻你的会议记录,不会真的调起某个发送接口,更不会在操作完成后给你一个结果确认。
这个场景非常精准地划分了两个物种:左边是对话式AI(LLM Chat),右边是智能体(AI Agent)。前者只有"嘴",后者有"嘴、手、脚、甚至一套完整的工作流程"。我在最开始接触这个领域时,也误以为"给模型加个System Prompt,让它扮演个助手"就是Agent了,直到我自己动手搭了一个能真正调外部工具的Agent,才发现中间的差距不是一星半点。
一句话总结:Agent = 大模型 + 工具调用 + 循环决策 + 记忆管理。大模型在里面不是负责"输出最终答案",而是负责"思考下一步该干什么"。
1.2 ReAct循环:Agent的工作模式就是"想一步,做一步,看一步"
我先讲一个很多人容易绕晕的概念:ReAct。
ReAct这个词拆开是Reason(推理)和Act(行动)。大模型在接到一个复杂任务时,如果让它一次性输出最终结果,它大概率会在中间某一步编造一个看似合理但实际错误的信息——这就是业内常说的"幻觉"。而ReAct的思路是:不要求模型一步到位,而是让它一边推理、一边行动、一边观察结果,形成一个闭环。
流程是这样的:
- 模型基于当前状态,推理出"我下一步该做什么"。
- 模型输出一个工具调用指令(比如"查询日程表")。
- 程序(我们写的胶水代码)真正执行这个调用。
- 调用结果作为文本/结构化数据返回给模型。
- 模型基于这个新观察到的结果,再次推理下一步动作。
这个循环不断重复,直到模型认为任务已完成,或者达到我们设置的最大轮数。
我用一个生活化的类比:让一个实习生去安排一场会议。他不会一次性把整场会议的所有事项都列完,而是先看日历,发现周五下午有空档,就去订会议室,订完之后再通知参会人,收到反馈之后再调整时间。每一步都是"看一步走一步"。Agent也是这样。
1.3 先做个判断题:你的需求真的需要Agent吗
在正式开始搭建之前,我建议你先做一个成本评估。因为Agent不是银弹,很多场景用传统编程方式反而更稳。
我会用这三条标准来判断:
- 任务是否需要调用外部系统或数据?如果需要查询数据库、调用API、操作文件、发消息,Agent有价值;如果只是纯知识问答,直接调大模型聊天接口就够了。
- 任务是否存在不确定性路径?用户输入不同,需要走的流程分支也不同,传统硬编码很难提前穷举,此时Agent的"动态决策"优势才能发挥出来。
- 容错成本是否可控?Agent在执行过程可能调用错工具、传错参数,如果你的业务场景不允许任何错误(比如直接涉及资金交易),那现阶段建议人在环里(Human-in-the-loop)做审批,而不是纯自动。
我的建议是:当你判断一个任务满足上面第一、第二条,且第三条可控,才值得上Agent。这也解释了为什么很多人在尝试Agent后会觉得"不如硬编码稳定"——很可能是场景选错了。
2. Agent的四个积木块:大脑、手脚、记忆、调度
2.1 大脑:模型选择要关心的三件事
搭建Agent时,模型的选型比普通聊天应用要苛刻得多。因为模型不只是回答问题,它还要做决策、生成结构化指令、理解工具返回结果。我认为有三个硬指标要重点看。
第一个是指令遵循能力。Agent的运转高度依赖模型理解我们写的System Prompt,比如"如果用户没给时间参数,就调用get_current_time先获取当前时间"。如果模型在复杂上下文里遵循不了这些规则,整个Agent就会开始胡来。
第二个是结构化输出能力。Agent的核心机制是让模型输出一段"工具调用指令",而不是普通文本。模型需要稳定地输出工具名和参数JSON,一次都不出差错。有些模型在聊天测试里表现不错,但一到Function Calling场景就会漏参数、格式错乱,这种就不能用在Agent里。
第三个是上下文长度和价格。Agent是多轮循环的,每一轮都要把之前的对话历史和工具结果重新发给模型,token消耗远高于单次对话。上下文窗口太小的模型会在第三四轮时"失忆",价格太高的模型会让你的账单很快失控。
我个人的起步建议:优先选那些在Function Calling能力上有专门评测、且上下文窗口在32K以上的模型,同时准备一个便宜的小模型做摘要和路由,典型的大小模型搭配策略。
2.2 手脚:工具就是模型和外部世界之间的桥
工具(Tool)是Agent区别于聊天机器人的关键。你可以把工具理解成"给模型准备的、遵循特定格式说明的函数"。模型自己不会执行代码,它只会输出"我想调用xxx工具,参数是yyy",真正执行代码的是我们写在Agent框架里的函数。
设计工具时,最核心的不是实现函数本身——函数谁都会写——而是让模型知道这个工具是干什么的、什么时候该用、参数怎么填。这三样信息在代码里对应工具的描述(description)、参数声明(parameters)、以及我们精心设计的函数逻辑。
举个例子:一个"获取用户日程"的工具,它的描述可以是"查询某日期范围内的日程安排。当用户询问今天/明天/本周安排时调用此工具"。这里的关键词是"当……时调用",它给了模型一个明确的调用条件。很多新手最容易犯的错就是把描述写得含糊不清,比如"日程相关功能",模型根本不知道何时该触发。
2.3 记忆:上下文窗口和真正的记忆管理
记忆问题在Agent里比想象中更早出现。我用一个简单的数学题说明:假设你的模型上下文窗口是64K tokens,Agent每跑一轮需要消耗大约5K tokens来做推理输出,加上工具调用结果、积累的对话历史,大概10轮之后窗口就快满了。
而且这里有个容易被忽略的点:模型上下文窗口越大,并不代表你应该塞得越满。当上下文填充到80%以上时,模型对中间部分的关注度会明显下降,甚至出现"中间丢失"现象——它只记得开头和结尾的消息,把中间的工具调用结果忘得一干二净。
所以Agent的记忆管理,本质上是在做"有限注意力资源的分配":
- 短期记忆:当前任务循环内的消息记录,通常完整保留,但要设上限。
- 长期记忆:超出窗口后,把早期内容做摘要压缩,或者抽取出关键实体(用户偏好、任务状态)存入外部存储(向量数据库或普通数据库),后续按需检索回来。
2.4 调度:控制流决定了Agent是"聪明干活"还是"瞎忙活"
调度是大部分人忽略、但Agent行为质量最受影响的一环。所谓调度,就是"循环什么时候继续、什么时候停、出现错误怎么办"。
一个小实验能立刻让你体会到调度的重要性:同一个Agent、同一批工具、同一个任务,如果把最大循环轮数从5改成20,模型会在任务已完成之后继续"表演",额外调用一堆没意义的工具。因为模型天然倾向于"多做事"来讨好用户,如果没有强力的终止条件,它就会做过犹不及。
调度设计里要明确这几件事:
- 终止条件:模型认为任务完成(不再输出工具调用指令),或达到最大轮数,或出现不可恢复错误。
- 重试策略:工具调用失败、参数校验不通过时,是直接报错终止,还是把错误信息反馈给模型让它自行修正。
- 人机交接点:哪些环节必须停下来等用户确认,哪些可以自动执行。
3. 动手前的选择和准备:直接调API,还是引入编排框架
3.1 一条简单标准:最小成本起步
现在市面上已经有不少Agent编排框架,很多教程直接让你pip装一个库然后跑Demo。我自己的看法是:第一次搭建Agent,不要一上来就用重型框架,先用最少的胶水代码手动实现一个最小循环。
原因很朴素——框架会帮你屏蔽掉很多底层细节,但当你遇到问题要调试时,你连"问题出在哪一层"都说不清楚。手动实现一遍ReAct循环之后,再去用框架,你会觉得一切都是透明的:原来这个回调是在工具执行后触发的,原来这个参数是用来控制上下文裁剪的。
所以本文的路线是:先用一个虚构的日程管理场景,手动写一个可运行的最小Agent;在这个基础上,我再告诉你哪些地方可以用框架替换、替换后能省什么事。
3.2 项目结构和环境准备
我习惯用一个很简单的项目结构:
agent_demo/ ├── main.py # 入口,跑起Agent循环 ├── tools.py # 工具函数集合 ├── config.py # 模型配置、最大轮数等参数 ├── prompts.py # 系统提示词 └── logs/ └── trace.log # 每次执行的完整轨迹环境方面,只需要一个Python 3.10+环境,以及能调用大模型API的SDK包。我建议用虚拟环境隔离依赖:
mkdir agent_demo cd agent_demo python -m venv .venv source .venv/bin/activate # Windows下执行 .venv\Scripts\activate pip install <你的模型SDK包>关于模型服务商的SDK,各家大同小异,核心都是"发送消息列表、可能附带工具列表、返回模型输出"。我在示例里用一个抽象的model_client对象,你可以在应用时替换成实际引入的SDK。
3.3 建立一个最小可运行的骨架
先把骨架写出来,后面再逐层填充。这里我给出最小化的主循环代码,刻意保持朴素:
# main.py from tools import SCHEDULE_TOOLS from config import MAX_ROUNDS, SYSTEM_PROMPT import json def run_agent(user_input: str): messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.append({"role": "user", "content": user_input}) for round_idx in range(MAX_ROUNDS): print(f"--- Round {round_idx + 1} ---") # 调用模型,带上工具定义 response = model_client.chat( messages=messages, tools=SCHEDULE_TOOLS, # 工具声明,符合模型服务商的Tool Schema格式 ) message = response["choices"][0]["message"] messages.append({"role": "assistant", "content": message["content"]}) # 如果模型没有发起工具调用,说明任务收尾,直接退出 tool_calls = message.get("tool_calls") if not tool_calls: print("Agent 判断任务已完成,最终回复:", message["content"]) break # 逐个执行工具调用 for call in tool_calls: tool_name = call["function"]["name"] tool_args = json.loads(call["function"]["arguments"]) result = dispatch_tool(tool_name, tool_args) # 把工具执行结果作为 tool 角色的消息发回模型 messages.append({ "role": "tool", "tool_call_id": call["id"], "content": json.dumps(result, ensure_ascii=False), }) if __name__ == "__main__": run_agent("帮我查一下明天上午有没有空闲时间")只要模型支持工具调用(Function Calling)接口,这段骨架已经能跑通一个最简单的Agent了。注意model_client.chat里的tools参数,它在模型眼里不是普通的代码,而是一组"按键说明书"。
4. 亲手定义第一个工具:让Agent从"会聊天"变成"会干活"
4.1 工具函数的选择:挑一个真正有用的交互触点
我选择做一个日程管理Agent,工具清单如下:
get_current_time:获取当前日期时间,这是日程场景的必需品,因为模型不知道自己"现在"是什么时候。query_schedule(date):查询某天的日程安排。add_schedule(date, time, event):在指定时间添加一条日程。
这三个工具覆盖了"查时间、读数据、写数据"三种类型,足够演示Agent的核心动作。
先写普通函数部分,这部分是最简单的:
# tools.py from datetime import datetime schedule_db = {} def get_current_time(): return {"current_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")} def query_schedule(date: str): items = schedule_db.get(date, []) if not items: return {"date": date, "message": "当天暂无日程安排"} return {"date": date, "schedule": items} def add_schedule(date: str, time: str, event: str): if date not in schedule_db: schedule_db[date] = [] schedule_db[date].append({"time": time, "event": event}) return {"status": "success", "message": f"已添加日程:{date} {time} {event}"}4.2 写好工具的描述和参数声明,这才是Agent质量的胜负手
函数本身写好了,但模型并不知道这些函数存在的意义。我们需要给每个函数配一份"元数据",这份元数据直接决定了模型在什么时机调用哪个函数。
每个工具声明通常包含三部分:type(function)、function.name(名称)、function.description(描述)、function.parameters(参数JSON Schema)。下面这段是query_schedule的完整声明:
query_schedule_schema = { "type": "function", "function": { "name": "query_schedule", "description": ( "查询某一天的日程安排。" "当用户询问某天有什么安排、某个时间段是否空闲、有没有会议时,调用此工具。" "date参数必须是YYYY-MM-DD格式。" ), "parameters": { "type": "object", "properties": { "date": {"type": "string", "description": "要查询的日期,格式YYYY-MM-DD"} }, "required": ["date"] } } }这里有一个新手最容易忽略的细节:描述里的触发条件写得越具体,模型误调用的概率越低。你可以把描述理解成"给模型看的说明书",它需要明确三件事:这个工具能做什么、什么时候该用、什么时候不该用。比如query_schedule的描述里写"当用户询问……时调用",这比干巴巴写"查询日程"要好得多。
另一个细节是date参数的格式说明。模型可能会把用户说的"明天"直接当作字符串传给工具,最常见的结果就是工具查了一个非法日期。我们在描述里明确格式,同时让Agent在调用前先通过get_current_time算出确切日期,双管齐下。
4.3 工具返回值的格式约定,以及"错误要反馈不要吞掉"
工具执行完,它返回的数据会被拼接进后续的消息里发给模型。这个返回值的格式有一个原则:必须是模型能够轻松解析的结构化文本。
我的习惯是这样:
- 正常结果返回JSON对象,如
{"date": "2025-06-20", "schedule": [{"time": "10:00", "event": "项目评审"}]}。 - 查询为空时,不要返回
None或空列表,而是明确说"当天暂无日程安排"。为什么?因为模型看到空列表会困惑,而看到一句明确的话就知道"没有安排"这个状态。 - 出错时(参数异常、外部接口超时),把错误信息转换成字符串返回,让模型能看到"发生了什么错",它能基于错误自行调整策略。
我见过太多人忽略第3点。比如add_schedule因为日期解析失败抛了个异常,外层代码直接把异常往上抛,Agent循环直接崩溃。正确的做法是把异常捕获住,然后作为工具调用结果返回给模型:“调用add_schedule失败,原因:date格式不正确。”模型看到后会尝试修正参数再调一次。
5. 把循环编出来:ReAct执行链路与终止条件
5.1 主循环的完整实现:从架子到真循环
前面给出的骨架代码还不够完整,因为它把dispatch_tool写成了一个假定存在的函数。现在我们把它补齐,并且加上一个关键能力:多个工具调用结果按顺序依次注入。
在真实场景里,模型有时候会一口气请求多个工具(比如先查当前时间,再查日程)。不过对于刚起步的Agent,我建议你先把最大并行调用数限制为1,让模型一次只做一件事。这样轨迹更清晰,出错了也更容易定位。
# main.py import json from tools import ( get_current_time, query_schedule, add_schedule, get_current_time_schema, query_schedule_schema, add_schedule_schema, ) ALL_TOOLS = [ get_current_time_schema, query_schedule_schema, add_schedule_schema, ] TOOL_MAP = { "get_current_time": get_current_time, "query_schedule": query_schedule, "add_schedule": add_schedule, } def dispatch_tool(tool_name: str, tool_args: dict): try: func = TOOL_MAP[tool_name] return func(**tool_args) except Exception as exc: return {"error": f"工具执行失败:{exc}"}对于工具执行结果转成消息回传模型这一步,不同的模型SDK格式略有差异,但都是role: "tool"加上tool_call_id以及内容。这里有一个我自己走过的坑要提醒你:工具返回内容不要直接塞一个巨大的Python对象,一定要json.dumps成字符串。模型服务商对非字符串内容往往有隐式转换,但你无法控制它转换的格式,显式序列化才是可控的。
5.2 最大轮数与终止条件的设计:防"表演型勤奋"
终止条件这里我再展开说。一个Agent在以下三种情况下会结束循环:
- 模型不再产生工具调用。这是最理想的完成状态。
- 达到最大轮数上限。这是安全兜底。
- 工具调用连续失败超过阈值。避免模型在同一个错误上反复重试。
最大轮数我建议先从5开始。你会发现一个有趣的现象:在任务已经可以完成时,模型有时还会尝试"补充做一些事"。这是模型在对话历史里看到自己"一直在行动",形成了一种惯性。针对这种表演型勤奋,有两个办法:一是把SOC(Stop On Completion)信号放在系统提示词里,明确告诉模型"一旦获得足够信息完成任务,立即输出最终回答,不要做额外动作";二是降低最大轮数,逼模型在有限的行动次数里做出最优决策。
以下是带终止判断的完整循环:
MAX_ROUNDS = 5 def run_agent(user_input: str): messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.append({"role": "user", "content": user_input}) for round_idx in range(MAX_ROUNDS): response = model_client.chat(messages=messages, tools=ALL_TOOLS) message = response["choices"][0]["message"] messages.append({"role": "assistant", "content": message.get("content", "")}) tool_calls = message.get("tool_calls") if not tool_calls: print("最终答复:", message["content"]) return if len(tool_calls) > 1: print("警告:多个工具调用,本次只执行第一个") call = tool_calls[0] tool_name = call["function"]["name"] tool_args = json.loads(call["function"]["arguments"]) result = dispatch_tool(tool_name, tool_args) print(f"Round {round_idx+1}: 调用 {tool_name}, 参数 {tool_args}") print(f"结果: {result}") messages.append({ "role": "tool", "tool_call_id": call["id"], "content": json.dumps(result, ensure_ascii=False), }) print("已达最大轮数,强制停止。")5.3 上下文太长怎么办:两条腿走路的裁剪方案
第3.3节里我提到每轮循环都会往messages里追加消息。当循环轮数上升,消息列表膨胀得非常快。我的经验是在第四轮左右就开始干预。
第一梯队的做法是滑动窗口:始终保留系统提示词、最初的用户问题、最近两轮的所有消息,中间的中间过程全部丢弃。这个方案实现最简单,适合Demo和工具调用轮次少的场景。
第二梯队是摘要压缩:用一个便宜的小模型,把中间轮的对话历史总结成一段"到目前为止Agent已经完成的事情"的摘要,插入到系统提示词后面。这个方案信息保留更好,代价是多了一次摘要的模型调用。
我在实际项目里的习惯是两者结合:滑动窗口保底,超过一定轮数再触发摘要。具体阈值需要根据你的模型上下文窗口和单轮token消耗来实测,我通常以"总消息数超过25条"作为触发摘要的信号。
6. 调试Agent的经验之谈:你不是在改Bug,是在调行为
6.1 五种常见的故障模式,以及它们为什么必然发生
Agent的调试思路和传统代码很不一样。传统代码是确定性的:输入相同,输出就相同。Agent是非确定性的:同样的输入,模型可能做出不同的决策。所以调试Agent时,你要接受一个事实——你在"驯化"一个高度随机的系统,而不是在修一个线性Bug。
我把自己踩过最深的五种故障模式整理成一张表:
| 故障模式 | 典型表现 | 根因 |
|---|---|---|
| 工具调用幻觉 | 调用了根本不存在的工具名 | 工具描述冲突或模型对工具边界理解差 |
| 参数幻觉 | 日期传成"明天"、名字传成用户原话 | 参数描述不严格、缺少格式约束 |
| 死循环 | 同一个工具反复调用、结果一样 | 缺少连续失败检测,终止条件太宽松 |
| 答非所问 | 明明可以查询却泛泛而谈 | 系统提示词没有强调"必须调用工具获取事实" |
| 上下文漂移 | 做着做着忘了初始需求 | 没有在每轮保持用户目标在上下文里 |
这里的根因有一个共性:问题往往不是模型"不够聪明",而是我们给模型的信息(工具描述、提示词、外围防护)不够清晰。
6.2 日志和轨迹回放:Agent调试的两件套
传统程序调试你可以在代码里打断点、看堆栈,Agent调试的核心手段则是完整记录轨迹并回放。
我给自己的Agent加了很简单的日志机制:把每次模型请求的完整消息数组、模型返回的原始消息、工具调用结果,全部追加到一个JSON Lines文件里。这个日志我看的不是单个字段,而是整个思考轨迹:
- 模型是在哪一步决定调用
query_schedule的? - 工具返回结果后,模型是如何理解这个结果的?
- 是哪一轮之后模型开始偏离主线的?
没有轨迹回放,你在Agent调试上基本就是盲人摸象。我在每轮调试时都会问自己三个问题:它为什么要调用这个工具?它拿到结果后为什么这么理解?我的工具描述和提示词里有什么信息误导了它?
6.3 提示词战略:把规则写进系统提示词,而不是寄希望于模型悟性
我在系统提示词里一定会放三类内容,这几乎成了我的标配模板:
第一类,角色与目标声明。例如"你是一个日程管理助手。你的目标是在最短的行动次数内,帮用户查询和安排日程。你必须通过调用工具来获取事实,不要编造日程信息。"
第二类,行动边界。例如"你只能使用提供的工具。当用户需求不明确时(如缺少日期),先调用get_current_time获取当前时间,再询问用户或基于合理推断处理。"
第三类,终止与输出规范。例如"当你已经完成用户需求时,直接给出简洁的中文回答,不要再调用任何工具。"
很多新手把提示词写得很长很绕,结果模型反而抓不住重点。我自己的体会是:用短句,列规则,每条规则对应一个具体行为。与其写"请进行合理的日程管理",不如写"日期格式一律是YYYY-MM-DD,如果用户说"明天",先调用get_current_time推算日期,再查询"。
6.4 实测一个完整对话的痕迹
为了让你更直观地理解上面整套机制是怎么协作的,我贴一段我在本地模拟的完整轨迹(已做简化):
用户:明天下午四点有什么安排?
- Round 1:模型调
get_current_time,得到当前时间。这是正确的第一步,因为"明天"是相对时间,模型必须知道今天几号。 - Round 2:模型调
query_schedule,参数传了明天的日期。工具返回"暂无安排"。 - Round 3:模型没有继续调工具,直接回复"明天下午四点暂无日程安排。"
这个轨迹看起来平平无奇,但它每一步都在正确的位置上:先解决时间基准,再按格式查询,最后确认无任务就收尾。假如Round 2的参数传成了"明天"而不是具体日期,日志里立刻能看到,我会去检查query_schedule的参数描述是否写清楚了格式要求。
7. 从Demo到能用:让Agent真正稳定下来的进阶手段
7.1 给Agent装上"长期记忆":不只是把对话塞进窗口
Demo里的Agent每跑一次都是"失忆"状态,下次启动一无所知。真实的Agent需要记忆层。我常用的方案是:
- 在
add_schedule写入时,同时把日程数据落到SQLite或普通数据库。 - 每次启动Agent时,把用户的基本偏好(如常用会议时长、常用提醒时间)从数据库读取出来,拼进系统提示词。
- 对于历史对话,用摘要模型定期压缩,摘要存入数据库,需要时再取出来注入上下文。
这套机制的成本不高,但对体验的提升是巨大的。Agent第一次被问"我上次让你加的日程确认了吗"时,能正确回答——那一刻你会觉得之前的投入都值了。
7.2 从单循环到多角色协作:当任务太复杂时的破局思路
单个Agent能解决的任务有一个隐形的复杂度天花板。任务步骤一多、依赖一深,单Agent就会在超长上下文里迷失。
这时候有两个选择:一是把任务拆成多个子任务,用"规划Agent"负责拆解并分配;二是用多个专用Agent协作,每个Agent只负责自己擅长的一段流程。我个人的实践建议是:不要让只有三五个工具的Agent硬扛复杂任务,优先把工具的职责划分清楚。比如日程管理里,"查天气再决定今天是否适合户外会议"这种需求,我会直接增加一个天气工具,而不是让模型去猜。
7.3 没有评测集,就不要说自己的Agent好用
最后这一点最关键,也是我在项目从Demo走向真实环境过程中体会最深的事:Agent是随机的,所以你必须用数据来评估好坏,而不是凭感觉。
我会准备一组覆盖典型场景的测试输入,每条输入都配上期望的行为描述:
| 测试输入 | 期望行为 |
|---|---|
| "明天下午有空吗" | 先查当前时间,再查明天日程 |
| "帮我约周五上午十点和张总开会" | 日期解析正确,调用add_schedule |
| "我这周忙碌吗" | 查询多天日程并给出汇总判断 |
每次改动系统提示词或工具描述后,我都跑一遍这组用例,看成功率是升还是降。没有这套回归测试,你做的任何提示词优化都可能是"修好了这个case、弄坏了另外三个"。
把我个人最后一点体会放在这里:Agent真正难的从来不是"让模型输出一段JSON",而是设计一套让不确定的模型在确定的工作流里可靠工作的机制。工具定义、终止条件、记忆策略、错误恢复——这些外围工程才是Agent能不能落地的关键。你一旦手动走过一遍最小循环,再去理解市面上任何Agent框架的配置项,都会觉得非常通透。这就是从0到1的价值。