开场:当传统API开发遇到会“自己思考”的接口
如果你是个写了三五年业务代码的Web开发者,最近一定被AI Agent刷屏了。GitHub趋势榜上全是Agent项目,招聘JD里开始出现“Agent开发经验”,连面试官都开始问“你怎么理解Function Calling”。说实话,两年前我听到这个概念的第一反应是:这不就是让模型调API吗?有什么好吹的。但真的动手把第一个带多步交互的Agent跑通之后,我才意识到事情没那么简单——Function Calling确实是在调API,但它是让模型自己决定“调哪个API、按什么顺序调、参数从哪里来”,这就完全是另一套思维方式了。
这篇文章就是写给Web开发者的实战笔记。我会从零拆解Function Calling的运行逻辑,用真实代码演示如何搭建一个能完成多步任务的Agent,重点讲提示词怎么优化才能让模型“听话”,以及那些官方文档里不会写的坑——比如模型突然不调工具了、参数传错了它还在硬编、死循环怎么防。内容面向有基本HTTP/API经验的开发者,即使你完全没碰过大模型,只要懂JSON、懂请求响应,就能跟上节奏。
1. 为什么Web开发者要关注Function Calling,它到底解决了什么问题
1.1 先搞懂大模型为什么“不会算账”
用过ChatGPT的人都有体会:你问它“23乘以47等于多少”,它可能在大部分时候答对,但一旦数字变大、步骤变多,它就很容易一本正经地胡说八道。原因在于,大模型的本质是“下一个词预测器”,它不是在计算,而是在根据海量训练数据推测“这道题最可能的答案是什么”。它没有真正的计算器,也没有执行环境。
所以你会发现一个很尴尬的局面:模型能写出漂亮的代码片段,但你不能让它直接操作你的数据库;它知道天气App应该返回什么格式,但它不知道北京的实时温度。这就是大模型的能力边界——它擅长“理解”和“生成”,不擅长“计算”和“执行”。
这几个月的AI Agent热潮,本质上就是在解决这个边界问题。Agent不是一个神秘的系统,它的核心思路特别朴素:让大模型只负责“思考”和“决策”,把“执行”外包给外部工具。模型说“我要查一下上海的天气”,然后代码帮它真实地调一次天气API,把结果再喂回给模型。模型再基于真实数据继续推理和回答。这一步一思考、一步一执行的循环,就是Agent最底层的形态。
1.2 Function Calling在中间扮演什么角色
很多人刚接触Function Calling会有一个误解:以为它是让模型变得更强的魔法。其实Function Calling只是一个协议——约定好了模型输出什么格式的结构化数据,由你的代码去解析、执行、返回结果。
用Web开发的老话来说,这就像是前端表单和后端接口之间的契约。你给大模型一份“接口文档”(tools参数),大模型看完后回你一个“我要调用哪个接口、传什么参数”的JSON,你的后端代码负责真正执行这个请求。区别在于:传统接口的调用方是有明确业务逻辑的前端,而Function Calling的调用方是一个能理解自然语言的模型,它会根据用户的不同表达,动态决定调哪个函数。
我打个比方你就明白了。传统API开发里,前端页面是个固定的表单,用户点击按钮就触发固定请求。而Function Calling相当于你做了一个智能中台,用户说一句“帮我看看明天去杭州合适吗”,这个中台自己去查天气API、查高铁票API、查酒店API,然后把结果汇总成建议。中台本身不生产数据,但它知道该问谁要数据,以及按什么顺序问。
对于Web开发者来说,这是个巨大的机会:**
- 你不需要学习复杂的机器学习算法,不需要微调模型。
- 你只需要把你熟悉的API按照模型能理解的JSON Schema格式描述清楚。
- 你现有的后端服务、数据库、第三方接口,都可以无缝变成Agent的“手和脚”。
这也是我强烈建议Web开发者从Function Calling切入Agent的原因——它把你已有的技能直接迁移到了AI时代,而不是让你从零开始。
2. 搭建第一个Function Calling Agent:从选模型到完整代码
2.1 模型选型与API协议准备
动手之前先解决选型问题。市面上支持Function Calling的模型不少,OpenAI系的GPT-4o、GPT-4o-mini,Anthropic的Claude系列,Google的Gemini,国内的通义千问、DeepSeek、智谱GLM都支持。对于学习目的,我建议选性价比高的模型,比如GPT-4o-mini或者DeepSeek,因为Agent的调试过程往往会产生大量请求,烧钱不是目的,跑通逻辑才是。
所有主流模型的Function Calling API都有一个共同的抽象逻辑,只是字段名略有不同:
- 你发送请求时带上用户消息(user message)和可用工具定义(tools)。
- 模型判断需要调用工具时,在响应中返回一个tool_calls字段,里面是提议的工具名称和参数。
- 你的代码执行工具,把工具的执行结果作为一个新角色(通常是tool角色)的消息追加到对话里,再次请求模型。
- 模型看到工具返回的真实数据后,生成最终回复,或者继续发起下一轮工具调用。
这里我强烈建议你先用OpenAI兼容协议来学习,因为目前它几乎成了行业标准接口。你甚至不需要OpenAI官方的SDK,直接用HTTP请求或者Python的openai库就能完成实验。下面的示例都基于这个协议。
2.2 从零写一个能查天气、算计算器的Agent
直接上代码。我先用一个最小可运行的Python示例,展示单轮Function Calling的完整流程。这个Agent具备两个能力:查实时天气和做数学运算。
import json from openai import OpenAI client = OpenAI() # 根据你的服务商配置 base_url 和 api_key # 1. 定义工具清单——这是给模型看的“接口文档” tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的实时天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,例如:北京、上海" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "calculate", "description": "进行数学运算,支持加、减、乘、除", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "数学表达式,例如:12*7+5" } }, "required": ["expression"] } } } ] # 2. 这是你的“工具实现”,也就是真正执行的那段代码 def execute_tool(name: str, arguments: dict) -> str: if name == "get_weather": # 这里接真实的天气API,这里用模拟数据代替 return json.dumps({"city": arguments["city"], "temperature": 26, "condition": "晴"}) elif name == "calculate": # 注意:eval有安全风险,生产环境请用表达式解析库 result = eval(arguments["expression"]) return json.dumps({"result": result}) else: return json.dumps({"error": "未知工具"}) # 3. 单轮对话:让模型决定是否调用工具 messages = [ {"role": "system", "content": "你是一个智能助手,可以调用工具来回答用户问题。"}, {"role": "user", "content": "北京的天气怎么样?如果气温超过25度,顺便帮我算一下32*18"} ] response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools, tool_choice="auto" # 让模型自己决定要不要调、调哪个 ) # 4. 根据模型返回结果,判断是否需要执行工具 msg = response.choices[0].message if msg.tool_calls: print("模型发起工具调用:", msg.tool_calls[0].function.name) tool_result = execute_tool( msg.tool_calls[0].function.name, json.loads(msg.tool_calls[0].function.arguments) ) # 把工具结果追加到消息历史中 messages.append(msg) # 先把完整的助手消息加入历史 messages.append({ "role": "tool", "tool_call_id": msg.tool_calls[0].id, "content": tool_result }) # 再次请求模型 second_response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools ) print("最终回复:", second_response.choices[0].message.content) else: print("直接回复:", msg.content)看到这里,你可能会嘀咕:这不就是一个if-else分发器吗?确实,最小实现就是这么朴素。但请注意两个关键细节:
第一,工具描述(description)是写给模型看的,不是写给用户看的。它写得越清晰,模型选错工具的概率越低。这跟写API文档时把功能讲清楚是一个道理,只是你的读者变成了模型。
第二,整个调用过程是无状态的。模型记不住上一次对话的状态,你需要把所有的历史消息都带在请求里。上面的代码里,我把助手消息和工具结果都append到messages里,这一步是必须的——如果丢了,模型就不知道刚才自己调了什么工具、拿到了什么结果。
2.3 为什么说tools的JSON Schema就是Agent的“技术债”
写工具定义的时候,Web开发者最常见的错误是把它当成给自己看的后端接口定义来写,从接口命名到参数说明都非常随意。但你要明白,这份JSON Schema是模型唯一能看到的“文档”,模型对工具的理解完全取决于这里面的描述。
我举个例子:假设你有一个发送短信的工具,如果你把description写成“发送短信”,模型在用户说“帮我把验证码发给这个手机号”的时候可能并不会调用它。但如果写成“发送短信到指定手机号,message参数为短信内容,仅用于给用户发送验证码”,模型的调用准确率会明显提高。原因很简单——模型是通过文本相似度来匹配用户意图和工具描述的,描述越具体、越贴近用户的自然表达,匹配越精准。
另外,参数名和类型定义也直接影响模型输出。比如你定义手机号字段时用了phone_number,模型能理解;但如果你用了一堆没有语义的缩写,比如pn,模型猜错的概率就大增。参数必填项务必标清楚,否则模型会自作主张跳过它认为“不重要”的参数。
这块做得好不好,决定了你后面所有提示词优化的空间。工具定义本身就是一种“提示词”,而且是结构性最强的提示词。后文我还会专门讲怎么优化描述。
3. 多步交互的实现逻辑与提示词优化实战
3.1 多步Agent的“思考-行动-观察”循环
上面单轮示例只能实现“用户一问,模型一调,Agent一答”的简单场景。真实的Agent诉求远不止此:用户可能说“帮我安排好这周末去上海的高铁票”,这时候Agent可能需要先查高铁班次、再选推荐班次、再查酒店、最后生成行程单。整个过程中涉及多次工具调用,且后一次调用依赖前一次的结果。
这个循环在技术上有个经典名字:ReAct(Reason + Act)。翻译成大白话就是:
- 模型阅读当前对话,推理出下一步需要的工具(Reason)。
- 模型输出工具调用请求,代码执行工具(Act)。
- 工具执行结果返回给模型,模型观察结果,决定是否继续(Observation)。
- 如果任务没结束,回到第1步;否则生成最终回复。
实现多步循环的代码并不复杂,核心就是加一个while循环,同时设置最大轮数上限:
def run_agent(user_input, max_iterations=5): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ] for i in range(max_iterations): response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools ) msg = response.choices[0].message if not msg.tool_calls: return msg.content # 没有工具调用,任务结束,返回最终回答 messages.append(msg) # 记录模型的决定 for tool_call in msg.tool_calls: result = execute_tool(tool_call.function.name, json.loads(tool_call.function.arguments)) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result }) print(f"第{i+1}轮:调用了{tool_call.function.name},结果{result}") return "已达最大迭代次数,任务终止"这个循环看起来简单,但真正决定Agent质量的不在循环本身,而在两处:一个是系统提示词(System Prompt)怎么写,另一个是每轮工具结果和上下文怎么组织。接下来重点讲这两个。
3.2 系统提示词的设计:给Agent立规矩
写系统提示词这事,很多从传统开发转过来的人特别不习惯。传统开发的代码是确定性的逻辑,而提示词是概率性的引导。你需要用文字清晰表达:Agent的角色是什么、行为边界在哪里、如何处理异常情况。
我自己的系统提示词模板长期迭代下来,沉淀了一套比较稳定的版本,分享出来给你参考:
你是“智能生活助手”,一个通过调用工具解决用户实际问题的AI助理。 你拥有以下能力: - 查询天气、计算数学公式(使用工具) - 回答通用知识问题(直接回答,无需调用工具) 工作时必须遵守以下规则: 1. 先判断用户意图。如果用户请求涉及实时数据或精确计算,必须调用对应工具,严禁依靠训练数据中的记忆作答。 2. 调用工具前,先向用户简述你将要执行的操作。 3. 工具结果返回后,用通俗易懂的语言向用户解释结果,必要时给出建议。 4. 如果工具调用失败或返回异常,告诉用户暂时无法处理,并提出替代方案,不要编造结果。 5. 多步骤任务要逐步推进,每完成一步向用户汇报进度,全部完成后输出完整结论。 6. 禁止执行超出工具能力范围的操作。如果用户请求无法满足,直接说明原因。有个很微妙的点是第1条。很多新手Agent最常见的问题就是模型“偷懒”——用户问天气,它凭记忆回答“北京夏天很热”,而不是去调工具。把这条规则写得既严格又带说明,能显著降低这种偷懒调用的概率。
还有一个易被忽略的细节:系统提示词也占用模型的上下文长度。别写成一部长篇小说,控制在几百字以内,规则分条目,关键信息前置。你的工具描述、历史消息、工具结果都需要空间,系统提示词太长会挤占这些内容的注意力。
3.3 工具描述该怎么写:让模型一次选对
工具描述是我做提示词优化时投入产出比最高的部分。一个写得好与坏的工具描述,可能让模型调用的准确率相差30%以上。这里我把打磨工具描述的checklist列出来:
- 描述包含动词+对象+场景。比如“get_user_score”不要只写“获取用户分数”,写成“根据用户ID获取用户在指定比赛中的积分分数,用户可能称为积分、得分、分数”就清晰很多。
- 明确参数边界和取值来源。如果参数是可枚举的,在description里列出所有允许值;如果参数值来自上一轮对话或其它工具结果,要写清楚来源。比如“dept_id参数必须来自get_departments工具返回结果,用户提到的部门名需要先转换为部门ID”。
- 在description里给出一个示例用法。模型非常擅长模仿示例,一个短小的“例如:get_weather(city=‘北京市’)”比大段参数说明更有效。
- 相似工具之间要刻意制造区分度。如果你的Agent同时有“获取当日订单”和“获取历史订单”两个工具,描述里一定要明确时间边界,否则模型很容易混淆。
- 表达负面规则。比如“此工具只能用于查询已支付的订单,未支付订单请使用get_pending_orders”。
补充一个我踩过的坑:工具描述里有些词看起来没问题,但在模型的语义空间里会和其它词混淆。比如有个工具“send_notification”我原本写的是“推送通知给用户”,结果模型在用户说“发个短信提醒我”的时候总是调错,把“推送通知”和“短信”当成一回事了。后来我把描述改为“应用内发送系统通知(App Push),不是短信、不是邮件”,错误率立刻降下来了。写工具描述时,多想想“用户换一种说法,模型还认不认识这个意思”。
3.4 多轮上下文管理:避免Agent“失忆”和“串台”
多步交互还有一个容易翻车的细节:上下文管理。模型对前面的对话是“一念之间”的记忆,每一轮都在读全部历史做预测。问题有几个层面:
第一是信息过载。比如第1步查天气返回了一大段JSON,第2步模型需要做算数,这段JSON又占空间又干扰决策。解决方案是控制工具返回结果的长度——能精简的字段就精简,能只返回结论就只返回结论。比如“resolve_arithmetic”工具返回的不是“表达式值为108”,而是直接“108”,减少模型二次解析的负担。
第二是多次工具调用的结果混淆。我一开始写多步循环时,把每轮的所有工具结果都全部塞进消息历史。当工具数量多了之后,模型经常出现“张冠李戴”,把上一个工具的结果当作当前工具的结果来推理。后来我养成了一个习惯:每轮工具调用前,输出一个简短的执行摘要,把关键信息从大JSON里提炼出来重新组织。比如“上一轮查询到杭州市气温32度,适合出行,接下来需要查杭州东站到上海虹桥的高铁票”。
第三是冗余消息累计。对于长流程,你不需要把每一轮的所有中间结果都永久保留。有些框架的做法是定期做“记忆压缩”,把已经完成步骤的原始JSON替换成一句摘要。这个优化在早期不太必要,但当你的Agent流程超过5步、每步返回几百字JSON时,它就成了刚需。
4. 一个完整的多步交互实战:旅行行程规划Agent
4.1 需求拆解与工具组合设计
前面讲了一堆理论,现在来一个完整的实战项目。目标:做一个“周末旅行规划Agent”,用户只需说“帮我规划一下这周末从北京去杭州的两日游”,Agent自主完成以下步骤:
- 查询北京周六的天气,评估是否适合出行。
- 查询杭州周六到周日的天气,判断天气对行程的影响。
- 查询北京到杭州的高铁班次,挑选合适时间的车次。
- 查询杭州当地的热门景点,生成两天行程。
- 计算预估交通费用和基础门票费用。
这个任务需要4个工具:get_weather、query_train、get_hot_attractions、calculate。工具定义的关键部分如下:
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市在指定日期的天气情况,返回温度和天气状况。用户可能说‘天气’、‘气温’、‘会不会下雨’", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,例如北京、杭州"}, "date": {"type": "string", "description": "查询日期,格式YYYY-MM-DD,不提供时默认今天"} }, "required": ["city"] } } }, { "type": "function", "function": { "name": "query_train", "description": "查询两个城市之间某一天的高铁班次,返回出发时间、到达时间、二等座票价。注意:日期必须是中国标准日期", "parameters": { "type": "object", "properties": { "from_city": {"type": "string", "description": "出发城市"}, "to_city": {"type": "string", "description": "到达城市"}, "date": {"type": "string", "description": "乘车日期,格式YYYY-MM-DD"} }, "required": ["from_city", "to_city", "date"] } } }, # get_hot_attractions 和 calculate 定义略 ]需要特别注意的是query_train的date参数。模型在处理“这周末”这种模糊时间词时,常常不转换成具体日期就直接调用工具。我见过很多次模型直接传“date: 这周末”的情况,然后工具解析失败。这个问题的解决办法除了在工具描述里反复强调“必须是YYYY-MM-DD格式”,更稳妥的做法是在代码层面做一层日期转换——在发送给模型前,先让另一个工具或代码逻辑把用户输入里的相对时间解析成绝对日期。这是工程上“不要把鸡蛋放在模型一个篮子里”的思路。
4.2 一步步跟踪Agent的执行过程
实际跑起来之后,我记录了Agent的一次完整执行过程,非常有意思。
第一轮:用户说“帮我规划一下这周末从北京去杭州的两日游”。模型没有急着调工具,而是先回复“我来帮你规划,先确认一下天气和交通信息”,然后发起了两个工具调用:get_weather(city=“北京”, date=“本周末”)和get_weather(city=“杭州”, date=“本周末”)。注意,我并没有在工具描述里明确“本周末”需要转换,但模型在这里返回的date参数格式不规范。
幸好我在execute_tool里做了容错处理,把相对时间翻译成了具体日期:周六和周日。执行结果返回了北京周六晴23度、杭州周六周日多云26到30度。
第二轮:模型看到天气结果后,调用query_train(from_city=“北京”, to_city=“杭州”, date=“2026-04-18”)。我设计的工具逻辑里,默认接受date参数为空时自动取周六。返回结果模拟了几趟车次,模型选择了一趟G字头高铁,上午8点出发、中午11点30到达,并把车次信息展示给用户。
第三轮:模型接着调用get_hot_attractions(city=“杭州”),拿到了西湖、灵隐寺、西溪湿地等景点列表。这一轮模型没有立即返回最终计划,而是继续调用calculate。
第四轮:calculate(expression=“553*2+1200”),这其实是模型在算”两人往返高铁费+门票基础费用“。这里有个有趣的现象:模型把估算逻辑内化成了数学表达式,这比让它用自然语言计算靠谱得多。
第五轮:所有信息齐了,模型组织了一份完整的行程规划,包含每天的时间安排、景点路线、天气提醒、总预算估算,并在结尾处加了一句“我是基于实时天气和高铁数据做的规划,如果需要我帮你预订,可以告诉我”。
整个流程跑完只用了40多秒,五轮交互,一次都没走错。你能明显感觉到,模型不是在背答案,而是真的在“按流程办事”。这种体验,传统的规则代码很难实现——因为你没法预先把所有用户表达方式都match到固定流程,而模型天然能理解语义差异。
4.3 多步交互的异常处理与兜底策略
上面的执行过程是理想情况。实际运行中,多步交互的异常简直防不胜防。我总结了几类高发问题:
- 模型跳过必要步骤:比如用户明明要两日游,模型查完天气直接给建议,压根没查高铁。这种情况系统提示词里的“多步骤任务要逐步推进”约束只能降低概率,不能完全杜绝。兜底方案是代码层面做流程校验,比如检查最终回复中是否包含车次、是否包含景点,如果缺失就强制再开一轮让模型补充。
- 工具调用参数错误但没有报错:模型把城市参数传成了“北京市”,你的代码做了模糊匹配没报错,但结果可能是错误的;更隐蔽的是模型把两个相似参数传反了,比如from_city和to_city对调。这类问题没有银弹,只能在工具函数内部做参数合理性校验,比如from和to不能相同、日期必须在当前日期之后等,校验不过就抛错误信息给模型,让它自查重试。
- 工具结果导致模型“编造”:工具返回的数据如果字段名含义不清,比如返回了一个“score”字段但模型不知道它代表好评率还是热度,就可能误解。工具返回的内容也应该做“结构化+可解释化”处理,必要时附带一个简短说明字段。
我的经验是:多步Agent的健壮性不是靠模型,而是靠工程兜底。模型负责“聪明”,代码负责“稳定”,两者缺一不可。Web开发者在这一点上有天然的优势,因为传统开发中对异常处理、参数校验的理解可以直接迁移过来。
5. 常见问题与排查技巧实录
5.1 工具调用潜规则:模型不调工具怎么办
这是被问得最多的一个问题:“我的工具明明定义好了,模型就是不调用,直接编了个答案给我”。排查方向按优先级排序:
- 检查工具描述是否清晰。这是第一大嫌疑。如果你的description写得含糊,让模型觉得“直接答也行”,它就会偷懒。把description改成“必须调用此工具才能获取答案”的语气,会有明显改善。
- 检查请求里是否正确传递了tools参数。很多人在多轮对话的后续请求里忘了带tools,模型马上就变回“纯对话模式”。
- 检查tool_choice参数。如果你希望模型强制调用某个工具,可以设置tool_choice={"type": "function", "function": {"name": "xxx"}}。但这个选项适合已明确知道下一步该调什么工具的场景,日常对话还是用“auto”更灵活。
- 升级模型或换同类型不同厂商的模型。不同模型对Function Calling的支持质量差异很大,有的模型对复杂多参工具定义就是“理解能力有限”。
- 核对上下文长度。如果消息历史过长,模型的注意力会被稀释,可能“忘记”工具的存在。考虑截断早期消息或做摘要压缩。
5.2 模型“幻觉式”调用:调了一个不存在的工具
模型返回的tool_calls里出现了你没有定义过的工具名,或者参数集合完全不符合定义。这通常不是模型的恶意行为,而是它在长文本生成时的“幻觉”。这类问题高发于工具数量多、功能重叠的场景。
我在排查时最常用的办法是加一个工具白名单校验。模型返回tool_calls之后,代码先校验工具名是否在已注册列表里,参数是否符合JSON Schema,不符合就直接构造一个错误结果返回给模型:“调用失败:工具名不存在或参数不合法”。模型看到错误信息后通常会自动纠正,发起新的正确调用。
这个白名单校验的成本极低,但对稳定性的提升非常大。很多生产级框架会在底层默认加上这一层防护,你如果自己手写Agent,一定不要省。
5.3 死循环与任务不收敛
Agent最常见的“翻车现场”就是陷入循环:调A工具 -> 返回结果 -> 调B工具 -> 返回结果 -> 又调A工具……反复横跳几十轮,既消耗token又不出结果。我的经验是三个防线:
第一,设置最大迭代次数上限,比如5到8轮,超出后强制终止并让模型输出“当前获取的信息已经足够,我基于现有结果给出建议”。
第二,在系统提示词里明确终止条件。比如写“当工具返回结果已经能回答用户问题时,立即输出最终回答,不要再调用其它无关工具”。
第三,在代码里检测重复调用。如果同一工具在最近N轮里被调用超过X次,主动中断并向模型注入一条提示:“你陷入了重复调用,请根据已有信息直接回答用户”。
第一道防线是硬性的,必须加;第二、三道防线的效果取决于模型的理解能力和你的提示词水平。三层叠加能让大多数循环问题被拦截住。
5.4 常用排查工具与方法
这里分享一套我在调试Agent时常用的“望闻问切”流程:
- 打印每一轮消息的完整JSON。不要嫌日志长,Agent的bug往往隐藏在某一条消息的角色或格式错误里。重点检查tool_call_id是否一一对应、does消息的role是否和消息类型匹配。
- 用单步调试法定位问题。如果5步流程在第3步出错,就别再从头跑整个Agent了。直接把前2步的messages固定下来,单独调试第3步的请求和响应。
- 构建回归测试集。准备10到20条典型的用户输入,每次改动提示词或工具定义后,批量跑一遍,对比前后输出的正确率。这听起来土,但确实是防止“改一个描述结果弄坏了另一个场景”的最有效手段。
- 善用模型的响应日志。OpenAI的API响应里会带usage信息,看token消耗能帮你判断是否发生了无意义的重复调用。
6. AI Agent开发的技术生态与进阶方向
6.1 从手写循环到框架:LangGraph、AutoGen等该怎么选
如果你只是学习或者做小Demo,手写上面的循环足够。但到了生产环境,手写Agent会出现大量重复工作:并发工具调用、状态持久化、人机交互确认、错误恢复机制、可视化追踪……这些需求催生了一批Agent开发框架。
- LangGraph:把Agent流程定义成一张图,节点是“工具调用”或“人机交互”,边是条件跳转。适合复杂流程编排,对熟悉状态机的后端开发者比较友好,但需要花时间理解它的事件循环和状态管理模型。
- AutoGen:微软出的多Agent对话框架,擅长“多个Agent互相协作”的场景。你可以定义一个用户Agent、一个助理Agent、一个审查Agent,它们之间通过消息对话协作。优点是理念先进,缺点是调试复杂度成倍上升。
- 字节的Coze / Dify:降低门槛的工作流平台,适合快速验证和内部工具开发。如果你不想写大量代码,可以先把流程在这些平台上拖出来看看效果,再决定是否用代码实现。
我给Web开发者的建议是:先手写一两遍循环,彻底理解Agent的运行逻辑,然后再上框架。直接上手框架容易被抽象概念绕晕,出了问题也难排查——因为你不知道底层发生了什么。
6.2 与Web技术栈的深度结合:Next.js、Spring Boot与Agent
现在很多Web开发者关心的是:Agent怎么和自己的Web应用集成。实际上,Agent服务完全可以当做一个常规的“智能后端服务”来设计。你的前端发送用户消息到后端,后端调用大模型API,执行工具调用,最终把结果返回前端。整个过程对前端完全透明。
我在实践中发现几个集成要点:
- 用流式输出提升体验。Agent的多轮工具调用会产生延迟,如果前端干等着,用户会觉得卡顿。解决方案是用SSE(Server-Sent Events)把“正在调用天气工具…”、“正在查询高铁…”这些中间状态实时推给前端,让用户看到进度。这也是为什么很多聊天式Agent的UI上有“打字机”效果的原因。
- 在后端维护会话状态。每个用户应该有自己的对话历史和工具调用上下文。你可以用Redis存每个会话的messages列表,也可以在数据库里按会话ID存JSON。
- 把工具调用设计成可观测的操作。对Web开发者来说,这就像给接口加日志和监控。Agent每次调用工具、每次生成回复,都应该有记录。这样无论是排查问题还是做费用优化,都有据可查。
如果你在Spring Boot项目里集成,底层的HTTP调用逻辑和你调任何第三方API没有本质区别。用RestTemplate或WebClient请求大模型接口,解析JSON,执行本地服务逻辑,再返回响应。如果你在Next.js项目里集成,可以把Agent逻辑封装成Server Action或API Route,前端组件用SWR或React Query管理流式响应。
6.3 给Web开发者的Agent学习路线图
最后聊一下学习路径,这是我带过好几个转AI方向的Web开发者后总结出来的顺序:
- 先把Function Calling的API协议吃透。找一家模型服务商,用Python或Node.js写一个最小调用例程,理解消息、工具、响应三者的关系。这一周左右就能上手。
- 实现一个单工具Agent,再实现多工具Agent。重点练习工具描述优化——尝试用不同的描述词,观察模型调用效果的变化,逐步建立对“模型怎么理解描述”的直觉。
- 实现一个多步交互场景。选一个你熟悉的Web业务,比如订单售后、客服问答、数据报表查询,把它拆成多步流程,用Agent串联起来。
- 学习主流框架的核心概念。这时候再看LangGraph的图模型、AutoGen的多Agent协作,就会觉得很多设计“你早该想到”。
- 关注人机协同机制。生产级Agent一定要有“人工确认”环节。比如Agent准备执行扣款操作前,应该返回一个确认请求给用户,用户点击确认后再继续。这个机制的设计非常考验工程能力。
我对2026年的Agent趋势判断比较乐观:随着多模态模型成熟,Agent能处理的场景会越来越复杂,但底层的Function Calling协议和“思考-行动-观察”循环不会变。Web开发者现在入场,学的每一个原理和踩的每一个坑,都还能在未来很长一段时间内复用。
说到这儿,我把这几年摸索下来的核心心得再拿出来晒一晒:Agent开发最难的从来不是写代码,而是“理解模型的行为模式”。模型是个概率系统,它会偷懒、会幻觉、会钻提示词的空子。你得像带新人一样,给它清晰的边界、明确的反馈、兜底的机制,它才能稳定地把活干好。这对习惯了确定性逻辑的Web开发者来说,是一个不小的思维转变,但一旦适应了,你会发现构建那些“几乎什么都能干”的应用,真的很有成就感。