1. 从一堆名词说起:AI Agent到底在串什么
第一次看到“AI Agent”这个词的人,十有八九会经历三个阶段:先是觉得高大上,然后被一堆新名词砸晕,最后发现每个字都认识但连起来不知道在说什么。我刚开始接触的时候也一样,Prompt、Function Calling、RAG、ReAct、Memory、Planning、Tool Use、MCP、Multi-Agent……这些词单独拎出来好像都能说两句,但真要串起来讲清楚“一个Agent到底是怎么跑起来的”,脑子里就成了一锅粥。
这篇东西就是来解决这个问题的。我不打算写成教科书,也不打算堆砌概念定义,而是按照一个Agent从接收任务到完成任务的真实流程,把涉及到的核心概念一个一个串起来讲。你可以把它理解成一次“概念串烧”——每根签子上都是一个关键知识点,串在一起就是一条完整的链路。适合谁看?如果你已经用过对话式AI,想进一步搞清楚Agent和普通聊天机器人的区别,或者你正在做AI应用开发,需要把各个模块拼装成一个能自主干活的系统,那这篇内容应该能帮你省下不少翻文档的时间。
核心关键词会自然分布在后面各个章节里:AI Agent、大语言模型、工具调用、记忆机制、任务规划、多智能体协作。我不会刻意去解释每个词的字面意思,而是把它们放到具体的运行场景里,让你看到它们各自解决什么问题、彼此之间怎么配合。
2. 先搞清楚一个根本问题:Agent和聊天机器人差在哪
2.1 从“你问我答”到“你交代我搞定”
普通聊天机器人的工作模式很简单:你发一句话,它回一句话。整个过程是单轮的、被动的、无状态的。你问“今天天气怎么样”,它告诉你“晴转多云”;你再问“那明天呢”,它其实并不知道“明天”指的是天气,除非你把上下文一起塞给它。它的本质是一个映射函数:输入文本,输出文本。
Agent完全不是这个逻辑。你给Agent一个目标,比如“帮我查一下最近三个月AI Agent领域的融资情况,整理成表格,然后发到我邮箱”。它不会直接给你一段文字就完事,而是会自己拆解任务:先搜索相关信息,再提取关键数据,然后整理成结构化格式,最后调用邮件接口发送。整个过程它自己决定先做什么、后做什么、用什么工具做。
这个区别用一句话概括就是:聊天机器人是“对话接口”,Agent是“任务执行器”。前者等着你喂指令,后者拿到目标后自己找路径。
2.2 一个Agent的最小构成要素
把Agent拆开来看,不管多复杂的系统,核心组件就那么几个:
- 大脑(大语言模型):负责理解任务、推理决策、生成内容。它是整个Agent的“CPU”,所有判断都从这里出。
- 手脚(工具调用):负责与外部世界交互。搜索、计算、读写文件、调API,都是工具。没有工具的Agent就像没有手脚的人,只能动嘴皮子。
- 记忆(短期+长期):短期记忆保存当前对话的上下文,长期记忆保存跨会话的知识和经验。没有记忆的Agent每次都是从零开始。
- 规划(任务拆解):把一个大目标拆成可执行的小步骤,并决定执行顺序。这是Agent最像“智能”的地方。
- 执行循环(观察-思考-行动):Agent不是一步到位,而是循环执行:观察当前状态,思考下一步,执行动作,再观察结果,直到任务完成。
这五个要素缺一不可。少了大脑就是普通脚本,少了工具就是聊天机器人,少了记忆就是金鱼,少了规划就是无头苍蝇,少了执行循环就是一次性问答。
2.3 为什么现在Agent突然火了
其实Agent的概念不新,早在上世纪就有“智能体”的研究。但为什么这两年才真正落地?核心原因有三个:
第一,大语言模型的能力跨过了阈值。以前的模型理解不了复杂指令,更别说自主规划了。现在的大模型在推理、代码生成、指令遵循方面已经足够支撑一个Agent的基本运转。
第二,工具生态成熟了。搜索API、代码执行环境、各类SaaS服务的接口都很完善,Agent可以方便地调用外部能力。以前你想让Agent查个数据,得自己爬网页;现在一个API调用就搞定。
第三,成本降下来了。推理成本在过去两年下降了一个数量级,让多轮循环调用变得经济可行。一个Agent完成一个任务可能要调用模型十几次甚至几十次,成本太高的话根本跑不起来。
这三个条件同时满足,Agent才从论文里走进了实际项目。
3. 核心概念串烧:一个Agent的完整运行链路
3.1 第一串:Prompt Engineering——怎么跟模型说清楚你要什么
Prompt是Agent的起点。你给Agent的任务描述、系统提示词、工具说明,全都是Prompt的一部分。很多人觉得Prompt就是“跟AI说话”,但在Agent场景下,Prompt的设计直接决定了Agent能不能正确工作。
系统提示词(System Prompt)是Agent的“人格设定”和“操作手册”。它需要包含几个关键信息:Agent的角色定位、可用工具列表、输出格式要求、行为约束。比如一个搜索Agent的系统提示词可能是这样的:
你是一个信息检索助手。你可以使用以下工具: - search(query): 搜索互联网获取信息 - extract(url): 提取指定网页的正文内容 工作流程: 1. 分析用户的问题,确定需要搜索的关键词 2. 调用search工具获取相关结果 3. 从结果中选择最相关的链接,调用extract获取详细内容 4. 综合信息给出回答 注意:如果搜索结果不充分,可以换关键词重新搜索,最多尝试3次。这段提示词看起来简单,但每一句都有讲究。“最多尝试3次”是防止Agent陷入无限循环,“从结果中选择最相关的”是引导它做筛选而不是全盘照搬。这些细节不写清楚,Agent的行为就会很随机。
实操心得:系统提示词不要写太长。我见过有人写了三千字的系统提示词,结果模型反而抓不住重点。核心约束控制在500字以内,把最关键的行为规则放在开头和结尾,中间部分模型容易忽略。
3.2 第二串:Function Calling——Agent怎么调用外部工具
Function Calling是Agent从“动嘴”到“动手”的关键机制。它的原理不复杂:你在调用模型API的时候,除了传对话内容,还传一个工具列表。模型在生成回复时,如果判断需要调用某个工具,就会输出一个结构化的调用请求,而不是普通文本。
举个例子,你问Agent“北京现在气温多少度”,模型不会直接编一个温度给你,而是输出:
{ "tool": "get_weather", "parameters": { "city": "北京", "unit": "celsius" } }你的程序收到这个请求后,去调用真实的天气API,拿到结果再传回给模型,模型再根据结果生成最终回复。整个过程模型只负责“决定调用什么工具、传什么参数”,实际执行是你的代码干的。
这个机制解决了大模型的两个硬伤:一是知识截止问题,模型训练数据有截止日期,实时信息它不知道;二是计算能力问题,模型做数学运算容易出错,但调用计算器工具就稳了。
工具描述怎么写很关键。模型是根据你提供的工具名称、参数说明、功能描述来判断该不该调用的。描述写得太模糊,模型就不知道该用哪个工具;参数定义不清楚,模型传的参数就可能格式错误。
{ "name": "search_flights", "description": "搜索指定日期和路线的航班信息", "parameters": { "type": "object", "properties": { "origin": { "type": "string", "description": "出发城市,中文城市名" }, "destination": { "type": "string", "description": "到达城市,中文城市名" }, "date": { "type": "string", "description": "出发日期,格式YYYY-MM-DD" } }, "required": ["origin", "destination", "date"] } }注意事项:工具数量不要太多。我实测下来,单次传给模型的工具超过15个,模型的选择准确率就会明显下降。如果确实需要很多工具,可以分组,让Agent先选择工具类别,再在类别内选择具体工具。
3.3 第三串:ReAct——Agent的思考与行动循环
ReAct是Reasoning and Acting的缩写,是目前Agent最主流的执行框架。它的核心思想是让模型在每一步都先“想一想”再“做一做”,然后把做的结果作为下一步思考的依据。
一个典型的ReAct循环长这样:
用户任务:帮我查一下某公司最新季度的营收数据 思考1:我需要搜索这家公司的财报信息 行动1:search("某公司 2024年Q3 财报 营收") 观察1:搜索结果返回了三条链接,第一条是财报PDF,第二条是新闻分析,第三条是股吧讨论 思考2:财报PDF最权威,我需要提取它的内容 行动2:extract("财报PDF链接") 观察2:提取到了营收数据:本季度营收XX亿元,同比增长X% 思考3:我已经拿到了核心数据,可以给出回答了 行动3:生成最终回复这个循环看起来简单,但实际运行时有几个坑。第一个坑是思考步骤可能跑偏。模型有时候会陷入过度思考,一个简单问题想五六步还没动手。解决办法是在系统提示词里加约束,比如“如果已经获取到足够信息,立即给出回答,不要继续搜索”。
第二个坑是观察结果太长。如果工具返回的内容有几千字,全部塞回给模型会占用大量token,而且模型可能抓不住重点。通常的做法是在工具层面做截断或摘要,只返回最相关的部分。
第三个坑是循环终止条件不明确。如果不设置最大循环次数,Agent可能一直搜下去。一般设置5到10次循环上限,超过就强制输出当前结果。
3.4 第四串:Memory——Agent怎么记住事情
Memory分两层:短期记忆和长期记忆。
短期记忆就是当前对话的上下文窗口。Agent执行任务过程中的每一步思考、行动、观察,都保存在对话历史里。模型每次调用时都会带上完整的历史,这样它才知道自己刚才做了什么、现在处于哪一步。但上下文窗口有长度限制,任务步骤多了之后,早期内容会被挤掉。解决办法是做上下文压缩:把已经完成的步骤总结成简短摘要,只保留关键信息。
长期记忆是跨会话的。比如一个客服Agent,它需要记住每个用户的历史问题和偏好。这通常用向量数据库来实现:把重要信息转成向量存起来,下次遇到相关问题时检索出来。RAG(检索增强生成)就是长期记忆的典型实现方式。
# 长期记忆的简化实现思路 def save_memory(content, metadata): vector = embed(content) # 把文本转成向量 vector_db.insert(vector, content, metadata) def recall_memory(query, top_k=3): query_vector = embed(query) results = vector_db.search(query_vector, top_k) return [r.content for r in results]实操心得:不是所有信息都值得存长期记忆。我一般只存三类:用户的明确偏好、任务执行的成功经验、反复出现的错误模式。存太多噪音反而会干扰检索效果。
3.5 第五串:Planning——Agent怎么拆解复杂任务
Planning是Agent最核心的能力之一。面对“帮我策划一场线上活动”这种模糊任务,Agent需要自己拆出子任务:确定活动目标、选择活动形式、制定时间表、准备物料清单、设计推广方案。
目前主流的规划方式有三种:
第一种是线性规划。模型一次性把所有步骤列出来,然后按顺序执行。适合步骤明确、依赖关系简单的任务。缺点是如果中间某步失败,后面的步骤可能全部作废。
第二种是动态规划。模型只规划下一步,执行完根据结果再规划下一步。灵活性强,但容易“走一步看一步”,缺乏全局视角。
第三种是分层规划。先制定高层目标,再把每个目标拆成具体步骤。结合了前两者的优点,但实现复杂度最高。
实际项目中,我通常用动态规划为主、线性规划为辅的混合策略:先让模型列出一个大致步骤框架,然后每一步执行时再根据实际情况动态调整。
任务:分析某产品的用户评价 高层规划: 1. 收集评价数据 2. 清洗和预处理 3. 情感分析 4. 提取关键问题 5. 生成分析报告 执行时动态调整: - 第1步发现数据量太大,改为采样分析 - 第3步发现情感分析工具对反讽识别不准,增加人工规则补充3.6 第六串:Multi-Agent——多个Agent怎么协作
单个Agent的能力有上限。当任务复杂到需要多种专业技能时,Multi-Agent架构就派上用场了。它的思路很简单:让不同的Agent各司其职,通过协作完成大任务。
常见的协作模式有三种:
主管模式:一个主管Agent负责拆解任务和分配工作,多个执行Agent各自完成子任务,最后主管汇总结果。适合任务边界清晰的场景。
辩论模式:多个Agent对同一问题给出不同方案,互相批评和改进,最终达成共识。适合需要多角度分析的决策场景。
流水线模式:每个Agent负责流程中的一个环节,前一个的输出是后一个的输入。适合有明确阶段划分的任务。
主管Agent:收到任务“写一份行业分析报告” ├─ 分配給搜索Agent:收集行业数据和新闻 ├─ 分配給分析Agent:对数据进行统计和趋势分析 ├─ 分配給写作Agent:根据分析结果撰写报告 └─ 主管Agent:审核并整合最终输出Multi-Agent的坑在于通信成本。Agent之间传递信息需要消耗token,如果协作轮次太多,成本会急剧上升。另外,Agent之间可能产生误解,一个Agent的输出另一个Agent理解错了,整个流程就跑偏了。所以实际项目中,Agent之间的接口要定义得非常明确,最好用结构化格式而不是自然语言传递信息。
4. 工具选型:搭一个Agent需要哪些基础设施
4.1 模型选择:不是越贵越好
选模型是搭Agent的第一步。很多人一上来就选最强的模型,但实际上不同环节对模型能力的要求不一样。
| 环节 | 能力要求 | 推荐策略 |
|---|---|---|
| 任务规划 | 强推理能力 | 用最强模型 |
| 工具调用 | 指令遵循能力 | 中等模型即可 |
| 内容生成 | 语言表达能力 | 中等偏上模型 |
| 结果审核 | 判断能力 | 中等模型即可 |
我的做法是混合使用:规划环节用最强模型,执行环节用性价比高的模型,审核环节用中等模型。这样整体成本能降一半以上,效果损失很小。
4.2 框架选择:别重复造轮子
目前主流的Agent开发框架有好几个,各有侧重。选框架主要看三点:你的任务复杂度、团队技术栈、是否需要快速原型。
如果只是做个简单Demo,用轻量级框架就够了,几十行代码就能跑起来。如果要上生产环境,需要考虑状态管理、错误重试、日志追踪、并发控制这些工程问题,就得选更成熟的框架。
注意事项:框架只是脚手架,核心还是你对Agent运行逻辑的理解。我见过有人换了三个框架都没跑通,最后发现是系统提示词写错了。先把原理搞清楚,再选工具。
4.3 向量数据库:长期记忆的载体
长期记忆需要向量数据库来支撑。选型时主要考虑四个维度:检索速度、存储成本、易用性、扩展性。
小规模应用(百万级向量以下),用轻量级方案就够了,部署简单,不需要额外运维。大规模应用(千万级以上),需要考虑分布式方案,但运维复杂度也会上去。
实际选型时,我建议先用最简单的方案跑通流程,等数据量真的上来了再考虑迁移。过早优化是万恶之源。
5. 实操过程:从零搭一个能干活的小Agent
5.1 环境准备与基础配置
先明确目标:搭一个能搜索信息、整理内容、输出报告的Agent。不需要多复杂,但要把核心链路跑通。
基础环境很简单:Python环境、一个大模型的API Key、一个搜索工具的API Key。依赖装最基础的几个包就行,不用一上来就搞一堆框架。
pip install openai requests核心配置就三个:模型接口地址、API Key、默认模型名称。把这些放在环境变量里,不要硬编码在代码里。
5.2 定义工具函数
工具函数是Agent的手脚。先定义两个最基础的:搜索和网页内容提取。
import requests def search(query: str, max_results: int = 5) -> list: """搜索互联网,返回结果列表""" # 这里用某搜索API做示例,实际替换成你用的服务 response = requests.get( "https://api.example.com/search", params={"q": query, "limit": max_results}, headers={"Authorization": f"Bearer {SEARCH_API_KEY}"} ) results = response.json().get("results", []) return [ {"title": r["title"], "url": r["url"], "snippet": r["snippet"]} for r in results ] def extract_content(url: str, max_length: int = 3000) -> str: """提取网页正文内容,做长度截断""" response = requests.get(url, timeout=10) # 简化处理:实际项目中需要用解析库提取正文 content = response.text[:max_length] return content这两个函数看起来简单,但有几个细节要注意。搜索函数返回的是结构化列表而不是纯文本,这样模型更容易理解。提取函数做了长度截断,防止超长内容撑爆上下文窗口。
5.3 构建Agent主循环
主循环是Agent的心脏。它的逻辑是:调用模型→判断是否需要工具→执行工具→把结果传回模型→重复直到任务完成。
def run_agent(task: str, max_steps: int = 8): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": task} ] for step in range(max_steps): response = call_llm(messages, tools=TOOL_DEFINITIONS) # 如果模型没有调用工具,说明它认为可以给出最终答案了 if not response.tool_calls: return response.content # 执行模型请求的工具调用 for tool_call in response.tool_calls: tool_name = tool_call.function.name tool_args = json.loads(tool_call.function.arguments) if tool_name == "search": result = search(**tool_args) elif tool_name == "extract_content": result = extract_content(**tool_args) else: result = f"未知工具:{tool_name}" # 把工具执行结果追加到对话历史 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) return "达到最大步数限制,任务未完成"这段代码是整个Agent的核心。max_steps是安全阀,防止无限循环。每次工具执行的结果都追加到messages里,模型下一轮就能看到。注意工具返回的结果要转成JSON字符串,保持格式统一。
5.4 系统提示词的设计
系统提示词决定了Agent的行为模式。下面是我实际用的一套模板,你可以根据自己的场景调整:
你是一个信息研究助手,擅长搜索、整理和分析信息。 ## 可用工具 - search(query): 搜索互联网,返回标题、链接和摘要 - extract_content(url): 提取指定网页的正文内容 ## 工作流程 1. 理解用户的信息需求 2. 设计搜索关键词,调用search工具 3. 从搜索结果中选择最相关的链接,调用extract_content获取详细内容 4. 如果信息不足,换关键词重新搜索(最多3轮) 5. 综合所有信息,给出结构化的回答 ## 输出要求 - 回答要基于搜索到的实际内容,不要编造 - 如果信息有冲突,指出冲突并给出判断 - 最终回答用Markdown格式,包含小标题和要点列表 ## 约束 - 每次搜索后要评估结果相关性,不相关就换关键词 - 不要重复搜索相同的关键词 - 如果3轮搜索后仍信息不足,如实告知用户这套提示词的关键在于:工作流程给了模型明确的步骤指引,输出要求约束了回答质量,约束条件防止了常见错误。
5.5 跑通第一个任务
环境搭好之后,用一个真实任务测试一下。比如:
result = run_agent("帮我了解一下AI Agent领域最近有哪些重要的技术进展") print(result)运行过程中,你可以打印每一步的思考、行动和观察,方便调试。我通常会在主循环里加日志:
print(f"--- Step {step + 1} ---") print(f"模型输出:{response.content[:200]}") if response.tool_calls: for tc in response.tool_calls: print(f"调用工具:{tc.function.name}({tc.function.arguments})")跑通之后你会发现,Agent的行为和纯聊天完全不同。它会自己决定搜什么、看什么、什么时候停。这种“自主性”就是Agent的核心价值。
6. 常见问题与排查技巧实录
6.1 Agent不调用工具怎么办
这是最常见的问题。模型收到任务后直接编了一段回答,根本没有调用搜索工具。原因通常有三个:
第一,系统提示词没有明确要求使用工具。解决办法是在提示词里加一句“你必须先搜索再回答,不要依赖已有知识”。
第二,工具描述不够清晰。模型不知道这个工具能干什么,自然就不会用。把工具描述写具体,说明什么场景下应该使用。
第三,模型本身的能力问题。有些小模型对Function Calling的支持不好,换个模型试试。
6.2 工具调用参数错误怎么排查
模型传的参数格式不对,比如日期格式写成“2024年1月1日”而不是“2024-01-01”。解决办法是在参数描述里写清楚格式要求,并在代码里做参数校验和自动修正。
def validate_date(date_str): """尝试把各种日期格式统一成YYYY-MM-DD""" import re # 匹配 2024年1月1日 或 2024/1/1 等格式 match = re.match(r"(\d{4})[年/\-](\d{1,2})[月/\-](\d{1,2})", date_str) if match: return f"{match.group(1)}-{int(match.group(2)):02d}-{int(match.group(3)):02d}" return date_str6.3 Agent陷入循环怎么破
Agent反复搜索同一个关键词,或者在不同工具之间来回跳转。这是循环终止条件不明确导致的。解决办法有三个:设置最大步数限制、在提示词里加“不要重复相同操作”的约束、检测重复动作并强制中断。
seen_actions = set() # 在执行工具前检查 action_key = f"{tool_name}:{json.dumps(tool_args, sort_keys=True)}" if action_key in seen_actions: messages.append({ "role": "system", "content": "你刚刚已经执行过相同的操作,请换一个策略或给出当前结果。" }) continue seen_actions.add(action_key)6.4 输出质量不稳定怎么优化
同样的任务,有时候Agent输出很好,有时候一塌糊涂。这种不稳定性通常来自模型推理的随机性。可以通过降低temperature参数来减少随机性,但完全消除是不可能的。
更有效的办法是加一道审核环节:让另一个模型调用对Agent的输出做质量检查,不合格就重新生成。这增加了成本,但对质量要求高的场景很值得。
| 常见问题 | 根本原因 | 解决思路 |
|---|---|---|
| 不调用工具 | 提示词约束不足 | 明确要求先搜索再回答 |
| 参数格式错误 | 参数描述不清晰 | 写清格式要求+代码校验 |
| 陷入循环 | 终止条件不明确 | 最大步数+重复检测 |
| 输出不稳定 | 模型随机性 | 降低temperature+审核环节 |
| 上下文超长 | 历史信息太多 | 压缩摘要+截断工具返回 |
实操心得:调试Agent最有效的方法是打印完整的对话历史。每一步模型看到了什么、输出了什么、工具返回了什么,全部打出来。90%的问题看一眼完整日志就能定位。
7. 进阶方向:从能跑到好用还差什么
7.1 错误恢复与重试策略
生产环境的Agent必须能处理各种异常:API超时、返回格式错误、工具执行失败。基本的做法是加try-catch和重试,但更关键的是让Agent知道“刚才那步失败了,需要换个方式”。
def safe_tool_call(tool_name, tool_args, max_retries=2): for attempt in range(max_retries + 1): try: result = execute_tool(tool_name, tool_args) return {"success": True, "data": result} except Exception as e: if attempt == max_retries: return {"success": False, "error": str(e)} time.sleep(1 * (attempt + 1)) # 退避重试工具返回的错误信息也要传给模型,让它知道发生了什么。比如“搜索API返回429,请求过于频繁”,模型看到后可能会决定等待或换一个搜索词。
7.2 成本控制与性能优化
Agent的成本主要来自模型调用。一个任务调用十几次模型很常见,如果每次都用最强模型,成本会很高。优化策略包括:简单判断用便宜模型、缓存重复的工具调用结果、压缩上下文减少token消耗。
缓存特别有效。很多任务会重复搜索相同的内容,把搜索结果缓存起来,第二次直接读缓存,既省钱又快。
7.3 从单Agent到多Agent的演进路径
不要一上来就搞多Agent。先用单Agent跑通核心流程,等遇到明确的能力瓶颈时再拆分。拆分的时机通常是:单Agent的工具数量超过15个、系统提示词超过1000字、或者任务需要明显不同的专业技能。
拆分时先从主管模式开始:一个协调Agent加两三个执行Agent。跑通之后再考虑更复杂的协作模式。每增加一个Agent,系统的复杂度和调试难度都会上一个台阶。
8. 我踩过的几个坑和对应的解法
第一个坑是过度设计。刚开始做Agent的时候,我总想把所有可能的情况都考虑到,系统提示词写了2000字,工具定义了20个。结果模型反而不知道该干什么,经常选错工具或者忽略关键约束。后来砍到500字提示词、8个工具,效果反而好了。Agent的设计哲学是“够用就好”,留出空间让模型自己判断。
第二个坑是忽略工具返回的格式。我一开始让搜索工具返回纯文本,结果模型经常把搜索结果和自己的知识混淆。后来改成返回结构化的JSON,每个结果有明确的title、url、snippet字段,模型引用信息时就准确多了。工具返回的格式越结构化,模型的理解就越准确。
第三个坑是不做超时控制。有一次Agent调用一个外部API,那个API挂了,请求一直不返回,整个Agent就卡在那里。后来所有工具调用都加了超时,超时就返回错误信息让模型决定下一步。生产环境里,任何外部调用都必须有超时和降级方案。
第四个坑是忽视日志。早期调试全靠print,后来任务复杂了,print出来的信息根本不够用。建议从一开始就用结构化日志,记录每一步的输入输出、耗时、token消耗。出了问题查日志,比重新跑一遍快得多。
这套东西跑下来,最大的体会是:Agent的难点不在模型,而在工程。模型能力已经足够强了,真正花时间的是工具设计、错误处理、提示词调优这些“脏活累活”。但正是这些细节,决定了一个Agent是Demo还是产品。