这一两年我最大的一个感受是:大模型的热闹劲儿过去了,真正静下心来研究怎么让它"干活"的人,开始吃到红利了。去年大家还在比谁的对话更流畅、更有"人味儿",今年风向明显变了——客户上来就问:"它能不能自己把活干了?"所谓"干活",不是让它写一首诗或者讲个笑话,是让它能调你的接口、查你的数据库、操作你的系统、跑完一整条业务流程。就在这个节骨眼上,千问这一系模型的能力升级,让我觉得机会真正来了。之前你可能觉得大模型就是个聪明点的聊天框,但当你发现它已经能看懂一份工具清单、自己决定调用哪个函数、把返回值消化成下一步动作的时候,你心里那个"这玩意儿能帮我省一个人"的念头,就开始压不住了。这篇文章我想用我做过的几个实际项目来聊聊:为什么说"能干活"和"会聊天"是两码事,怎么让千问这样的模型真正接进你的业务流程里,以及这一路上我踩过的那些坑。如果你正准备把一个"只会聊天"的机器人升级成"能办事"的助手,这篇文章应该能帮你省下几周的摸索时间。1. 从"对话"到"干活":大模型价值的临界点1.1 对话只是入口,执行才是价值先别急着聊技术,我们算一笔账。一个纯对话机器人,它的价值上限大概是"节省一点客服人力"或者"做个有趣的产品Demo"。但一个能执行任务的Agent,它的价值空间会大得多——它可以替你盯监控数据、自动生成报表、帮你把客户的消息分类并回复、甚至在无人值守的时候完成一整套操作。这两者的差距,本质上不是工程复杂度的问题,而是产品定位的分水岭。我做个项目时印象特别深。甲方最初的需求是"做个AI客服",我说没问题。后来聊深了才发现,他们真正的痛点根本不是"回答客户问题",而是每天有几百条售后消息需要分类、查订单、填工单、发通知——四步操作全是重复劳动。这哪是对话需求,这分明是自动化需求。等我把方案改成"AI+工具调用"的Agent架构时,对方的眼神都亮了。那一刻我确定了:纯对话是玩具,能干活才是工具。千问这个系列模型之所以在这个时间点值得关注,不光是它的语言理解能力又强了,而是它在"要不要调用工具、调哪个工具、参数怎么传"这件事上的判断越来越稳。这件事看着简单,但如果你在去年试过让某个大模型去调API,大概率会有种"教它用工具比自己做还累"的挫败感。模型不是乱调就是不调,参数传得七零八落。而现在的千问在这块的表现,基本到了"可以放手让它跑一跑"的程度。1.2 "会聊天"和"能干活"的底层差别大多数人以为"干活"就是"聊天"的延伸——你能理解了,难道还不能执行吗?实际上这是两条完全不同的技术路线。聊天模型的核心目标函数是"生成一个合理的回复",它只需要对语言负责。但"干活"的模型需要的是"生成一个正确的动作序列",它得对结果负责。语言通顺不等于步骤正确,这是本质区别。举个例子,用户说"帮我查一下昨天的销售额,顺便把异常波动的数据标出来",聊天模型会回复你一段话:"好的,昨天的销售额是X,异常波动的数据有Y",但如果这个模型要干活,它得经历这些步骤:识别出这是一个多步骤任务,拆成"查数据"和"做分析"两件事。知道"查数据"对应的是哪个数据接口,该传什么时间参数。把接口返回的原始数据消化掉,挑出"异常波动"的部分。生成一份标记好的结果,用约定的格式返回给上层系统。这个过程里,模型每一次"思考"都不是为了生成漂亮的文字,而是为了驱动一次真实世界的状态变化。所以你会发现,能干活的好模型和能聊天的好模型,评价标准完全不同。后者看流畅度,前者看重成功率、参数准确率、错误恢复能力。千问系列近几次更新里,我对工具调用的成功率提升感知特别明显,这也是我决定围绕它做深度集成的原因。1.3 为什么"干活"比"对话"更容易商业化纯对话产品很难收费,这是行业里的公开秘密。用户用完就走,觉得你不过是个高级点的搜索引擎。但"干活"不同,它是可以量化价值的。一个Agent跑通了一条原本需要人力操作五分钟的流程,省下的就是真金白银。我在和几个团队聊的时候发现一个规律:凡是说得清"这个Agent每天帮我处理多少单、省多少时间"的项目,推进得都特别快;凡是还停留在"我们想做个智能客服"的项目,基本都卡在需求评审阶段。因为前者是明确的降本增效,后者是模糊的锦上添花。千问开始具备稳定的干活能力之后,最直接的影响是:以前大模型只能当个"前端花瓶"的项目,现在可以把模型塞进业务流程的核心环节里了。这对做ToB解决方案的团队来说,是个实打实的机会窗口。2. 核心机制拆解:让千问学会"调用工具"2.1 Function Calling:模型与外部世界的接口要让大模型"干活",第一步不是写业务代码,是让模型具备"调用外部工具"的能力。这个能力在技术上有专门的称谓:Function Calling,也就是函数调用。打个比方,模型本来是一个只有嘴巴的专家,它再聪明也碰不到现实世界的任何东西。Function Calling就是给它装上手和脚,让它能通过一套标准协议去操作你提供的工具。具体原理是这样的:你在请求模型的时候,除了给它用户的问题,还额外给一份"工具清单"——每个工具包含名字、功能描述、参数定义。模型看完用户的问题,再比对这些工具描述,会决定这一步要不要调用工具、调用哪个、参数填什么。关键是,模型返回的内容不再是一段人话,而是一个结构化的调用指令,你的代码拿到这个指令后去真正执行,再把执行结果交还给模型,让它继续下一步处理。我最初上手的时候,以为这个功能很复杂,其实千问的API已经把这层封装得很好了。你只需要在请求里加上工具定义,模型就会在你需要的时候稳定返回工具调用请求。下面是一个最简化的示例,能帮你理解它的工作方式:from openai import OpenAI client = OpenAI( api_key="your-api-key", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) tools = [ { "type": "function", "function": { "name": "get_sales_data", "description": "获取指定日期的销售额数据", "parameters": { "type": "object", "properties": { "date": { "type": "string", "description": "日期,格式YYYY-MM-DD" } }, "required": ["date"] } } } ] response = client.chat.completions.create( model="qwen-plus", messages=[ {"role": "user", "content": "帮我查一下昨天(2025-01-15)的销售额"} ], tools=tools ) # 如果模型决定调用工具,它会在这里返回工具调用请求 print(response.choices[0].message.tool_calls)你看,模型不会直接回答"昨天的销售额是多少"这种它根本不知道的事,而是返回一个类似"我需要调用get_sales_data这个函数,参数date为2025-01-15"的结构化指令。你的代码拿到这个指令后,去数据库查询,把结果再塞回对话上下文里,模型就能基于真实数据继续做分析了。这就是一次完整的"干活"闭环。2.2 让模型准确选择工具的三个关键设计一开始我以为只要把工具定义传给了模型,它就会准确调用。实践之后才发现,模型选错工具、传错参数的频率,直接决定了整个系统的体验。后来我总结出三个关键设计,缺一个都不行。第一是工具名字和描述要像"写给外行人看"一样清晰。很多人写工具描述的时候想当然,觉得参数名叫"start_time"大家都能懂。但模型是真不知道你的业务里start_time到底是订单创建时间还是支付时间的。你得把描述写成这样:"查询订单的起始时间,格式为时间戳,单位毫秒,注意是订单支付完成的时间而非创建时间"。别怕啰嗦,描述越精准,模型的判断越准。第二是参数定义要尽量用枚举值约束。比如有个字段表示订单状态,你可以直接限定它只能传"pending"、"completed"、"cancelled"三个值。这样模型不会自作聪明传一个"已完成"或者"end"之类的值进来。代码里限制死取值范围,比事后清洗有效得多。第三是工具数量要克制。这是我踩过最大的坑。有一天我兴奋地给Agent接入了二十多个工具,心想这下它什么都能干了。结果模型开始频繁地在两个功能相似的函数之间反复横跳,像一个选择困难症患者。后来我做了几层合并,把工具收敛到八个左右,准确率一下就上来了。工具太多的时候,模型的信息检索负担太重。能用三个工具完成的事情,不要拆成十个。2.3 千问在这块的优势和它的"性格"说句公道话,市面上主流大模型我都试过Function Calling相关能力,千问在我这边的实测感受排在第一梯队。最明显的一点是,它对中文业务场景的理解有天然优势。比如你在工具描述里写"查询最近三天的销量,注意排除退款订单",一些国外模型会在"退款"这个业务语义上绕弯子,而千问基本能直接抓住重点。另外千问的参数提取能力强得有点超出预期。我给它一段用户原话,里面混杂着时间、地点、人物、金额这些信息,它能比较准确地抽取并映射到工具参数里。有一次用户说"帮我把上周三到周五的所有异常订单重新跑一遍分析,重点看退款率超过20%的店铺",模型能准确解析出时间范围、操作对象、过滤条件,一次性把参数填齐。这种"把口语翻译成结构化参数"的能力,就是Agent体验好不好的分水岭。不过它也有自己的"性格",就是偶尔会过度自信。举个例子,如果工具返回的数据有空值或者格式不对,它会倾向于"脑补"一个合理的值而不是如实告诉你"数据有问题"。所以我在系统设计里加了一层校验逻辑,比模型的判断优先,发现参数不合预期就打回重来,不能让它自作主张。3. 实操过程:搭一个能真正干活的千问Agent3.1 环境准备与基础接入好了,理论说完了,咱们直接上手。我假设你已经有一个千问的API Key,如果没有的话,先去对应平台申请一个,步骤很简单,拿到Key就能开始。我的建议是直接用OpenAI兼容模式接入,这样做的好处有两个:一是市面上大部分开源框架都兼容OpenAI的客户端协议,你可以直接复用;二是将来想切换到其他模型,代码改动成本很小。下面这个代码片段可以验证你的环境是否通:pip install openai然后跑一个最基础的测试,确认模型能正常响应:from openai import OpenAI client = OpenAI( api_key="your-api-key", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) response = client.chat.completions.create( model="qwen-plus", messages=[ {"role": "system", "content": "你是一个智能助手,能调用工具完成任务。"}, {"role": "user", "content": "你好,介绍一下你能做什么"} ] ) print(response.choices[0].message.content)这里有一点要特别提醒:base_url必须填对,很多新手在这里卡了半天,搞不清为什么一直报连接错误。如果是用国内的服务,region-endpoint和compatible-mode两个地址的含义不同,建议直接照官方文档用dashscope.aliyuncs.com/compatible-mode/v1这个兼容地址,省心。3.2 设计工具函数:从业务逻辑到模型可识别的Schema现在进入核心环节:设计工具函数。好的工具设计是Agent能用起来的前提,这一步做不好,后面全白搭。我的习惯是先列出业务上需要的能力清单,再逐条映射成工具定义。拿我最近做的一个"售后工单自动处理Agent"来举例。这个Agent要做的事情是:收到客户反馈后,自动判断问题类型、查询对应订单、检查售后政策、生成处理建议。我给它设计了四个工具:工具名功能说明关键参数查询订单信息根据订单号查询订单详情,含商品、金额、状态订单号(order_id)检查售后政策查询商品对应的退换货政策商品类目(category)、购买时长(days_since_purchase)查询客户历史工单查看该客户之前有没有类似的工单记录客户ID(customer_id)创建处理工单生成一条处理建议并写入工单系统订单号、处理类型、说明文本每个工具在代码里都是一个普通函数,但在传给模型时需要转换成Schema格式。下面这个是"查询订单信息"的Schema示例:tools = [ { "type": "function", "function": { "name": "query_order", "description": "根据订单号查询订单详情,包括商品信息、订单金额、物流状态、售后状态等", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,例如SO20250115001" } }, "required": ["order_id"] } } }, # 其他工具定义省略 ]写到这里你会发现,这个"工具定义"本质上就是一份给模型看的说明书。模型不执行这些函数,执行它们的是我们的代码,模型只负责根据用户需求"选择"正确的函数和参数。所以描述写得越具体,模型选择就越准确。这里有个小技巧:描述里带上业务关键词,比如"查询订单详情"和"根据订单号查询订单详情,包括商品信息、订单金额、物流状态、售后状态",后者明显更准确,因为模型能从中理解这个工具返回什么数据、适合处理什么问题。3.3 多轮工具调用与上下文管理一个任务往往不是调一次工具就能完成的。比如用户问"帮我查一下订单SO20250115001的售后进度,如果还没处理完就催一下",Agent需要先查询订单状态,再判断是否需要进入催办流程,可能还需要调用创建工单或者发通知的工具。这就是多轮工具调用。实现多轮调用的逻辑其实不复杂,是一个"循环":把用户问题发给模型,带上所有工具定义。如果模型返回了工具调用请求,就执行对应的函数。把函数执行结果以"工具消息"的形式返回给模型。重复第2步,直到模型不再请求调用工具,返回最终答案。在代码层面,关键点在于每次调用都要把之前的消息历史完整保留。我的做法是把每一轮的User消息、Assistant消息、Tool消息全部追加到一个列表里,完整的上下文信息都保留下来。这里有一个特别容易踩的坑:千问或者其他大模型对上下文长度有限制,如果你把整个对话历史无脑塞进去,很快会撞上长度上限。我的方案是做摘要压缩——当历史消息超过一定轮数时,把前面的对话交给模型生成一个摘要,用摘要替换旧消息。下面展示一下循环调用的核心逻辑:def run_agent(user_input, tools, tool_functions): messages = [{"role": "system", "content": "你是一个售后工单处理助理。"}] messages.append({"role": "user", "content": user_input}) for _ in range(5): # 设置最大迭代次数,防止死循环 response = client.chat.completions.create( model="qwen-plus", messages=messages, tools=tools ) message = response.choices[0].message if message.tool_calls: # 模型要求调用工具 messages.append(message) for tool_call in message.tool_calls: func_name = tool_call.function.name func_args = json.loads(tool_call.function.arguments) func = tool_functions[func_name] result = func(**func_args) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) else: # 模型不再调用工具,返回最终回答 return message.content return "处理超时,请稍后重试"注意我设了一个最大迭代次数(这里是5),这是非常重要的一步。如果不加这个限制,一旦模型陷入循环调工具的困境,你的请求就会一直空转,既浪费token又拖慢响应。实战中我会根据任务复杂度把上限设成5到10之间,基本够用了。3.4 一个完整的业务流程案例这里我用一个模拟场景串一遍整个流程。假设我们接到一个用户消息:"我上周买的东西坏了,订单号是SO20250110088,想退货,帮我看下能不能退。"第一步:模型看到"订单号"和"退货"两个关键信息,决定调用query_order工具,参数是{"order_id": "SO20250110088"}。第二步:你的代码执行query_order函数,从数据库查出这笔订单的信息,返回给模型:"订单状态:已签收;商品类目:数码配件;购买时间:2025-01-10;当前时间:2025-01-15。"第三步:模型看到已签收、购买时间为5天前,判断需要进一步确认退货政策。它继续调用check_return_policy工具,参数是{"category": "数码配件", "days_since_purchase": 5}。第四步:工具返回"数码配件支持7天无理由退货,用户购买5天,在政策范围内"。第五步:模型综合所有信息,决定调用create_ticket工具,生成一条退货处理工单,并返回给用户一段话:"您的订单支持退货,我已为您生成退货工单,工单编号TK20250115023,请在24小时内寄回商品。"整个过程中,模型的角色更像是一个"调度员":它每一步都在思考"现在我需要什么信息、哪个工具能提供这个信息、下一步应该做什么"。这就是干活和聊天的区别——聊天只需要最后一句话漂亮,干活看重的是每一步都准确。4. 常见问题与排查技巧实录4.1 模型死活不调用工具,怎么办这是最常见的问题。你明明把工具定义传过去了,模型却像没看见一样,直接凭自己的知识硬答。排查思路按顺序来:第一,检查工具定义的数量和复杂度。如果工具特别多,光看描述就有十几个,模型可能会不知所措。我的经验是超过十个工具时要考虑合并同类的。第二,检查消息格式。有些接口要求工具消息必须配套对应的工具调用ID,如果ID对不上,模型在后续轮次可能就无法正确理解工具上下文。我的做法是把每轮调用的tool_call_id存好,确保一条不缺。第三,给系统提示词加个强制引导。我在System Prompt里加了一句:"当需要查询实时数据或执行操作时,必须调用提供的工具,不要直接回答。"这句简单的约束能把工具调用率提升好几个百分点。第四,如果以上都排查了还是不行,注意是不是某些特殊请求里带了奇怪的思考链,干扰了模型。这时候可以简化用户输入,去掉多余修饰词再试一次。4.2 参数传递出错:模型总是不按套路传参另一个高频问题是模型生成的工具参数格式不对,比如缺字段、多字段、类型不匹配。这个问题的成因通常在Schema定义上。我排查逻辑是:先看参数的required字段,是否把所有必要的字段都列上了。列上之后,模型就应该稳定输出这些参数。再检查description里有没有说清楚参数值的格式。比如时间戳,你需要写清楚是"毫秒级Unix时间戳"还是"字符串YYYY-MM-DD",不然模型隔三差五传出个别的格式。如果模型确实频繁传错特定参数,还有一个笨但有效的办法:在函数内部做兜底转换。例如不管模型传的是"2025年1月15日"还是"2025-01-15"还是"2025/1/15",函数内部统一解析为标准日期。工具自己皮实一点,整体可靠性就上去了。4.3 多轮任务中模型"失忆"或者上下文混乱Agent干起活来一跑就是好几轮,上下文越长,出问题的概率越高。我遇到过模型的后续轮次把之前的工具执行结果理解错了,或者是把A订单的信息安到B订单上。这个问题的根源在于上下文里的信息太多了,模型抓不住重点。我的解决方案是在每次工具调用结果的文本里做"高亮处理"——不是真的高亮,而是在返回内容里把关键信息提取出来,用规整的格式重写一遍。比如原始工具返回的是一个大JSON,我不会直接把整个JSON喂给模型,而是写个小函数把关键字段抽出来,整理成"订单号:xxx,状态:已签收,金额:xx元"这样简洁明了的文本。模型处理这种结构化文本时,上下文理解会清晰很多。4.4 常用排查手段汇总现象可能原因解决方案模型不调用工具工具描述不清/工具过多/提示词缺少引导优化描述、合并工具、在System Prompt中加强制性语句参数传错格式Schema定义不完整/描述不具体完善required字段、细化description、在函数内部做兜底转换多轮信息混乱上下文过长/关键信息被淹没精简工具返回内容、按关键字段提取、用规整文本重写任务中途报错工具执行异常/网络超时增加异常捕获、设置重试机制、模拟数据兜底返回效果不稳定模型参数配置不合适把temperature调到0.3以下,必要时固定随机种子5. 实操心得:让Agent从"能用"到"好用"的三个进阶技巧5.1 每次调用都要"自证清白":记录决策轨迹Agent跑起来之后,最大的痛苦是出了问题不知道怎么排查。它自己调了什么工具、为什么调、返回了什么,如果这些没有记录,你只能看着最终结果发呆。我的做法是给Agent增加完整的日志系统,每一步操作都记录:模型的原始输出(包括思考过程里的工具调用指令)工具的名称、参数、返回值从调用到返回的耗时有了这些记录,调试体验会完全不一样。有一次我花了半天时间定位一个问题:Agent在处理用户订单时把一个老订单当成了当前订单。如果有调用轨迹,一眼就能看到模型传入的日期参数有问题,而不是在几万行日志里大海捞针。这个习惯一定从一开始就养成。5.2 给Agent配备"防呆机制":确认-重试-兜底让模型干活,永远不能100%信任它。我的工程准则是:高风险操作必须加确认环节,低置信度的结果必须留在系统边界内校验。具体来说,如果Agent要执行的是不可逆操作(比如删除数据、扣款、提交订单),我坚持让它在执行前输出一个"操作确认"步骤,由用户或者上级系统做二次确认。虽然这会让流程多一步,但换来的稳定性是值得的。另外,每次工具调用都应该包一层try...except,接口超时了就自动重试一次,重试失败就返回一个明确的错误信息给模型,让它决定怎么处理。5.3 用"测试剧本"替代手工试错最后一个小建议:不要靠手工聊天来测试Agent的好坏。应该准备一套固定的"测试剧本"——比如"正常路径"、"参数缺失路径"、"工具报错路径"、"上下文超长路径",每次代码有改动,就自动化跑一遍这些剧本,对比结果是否回归。我的测试剧本大概有二十多个场景,每次跑完能发现不少问题。这个投入非常值得,因为Agent的调用链复杂,手工测试往往只能覆盖到自己想得到的场景,而测试剧本能帮你覆盖到系统设计的全部边界。这一两年做Agent相关项目,我最大的体会是:大模型的能力天花板已经足够高了,真正的瓶颈往往在工程基础设施——工具的稳定性、上下文的管理、异常的处理。谁能把这些工程细节做好,谁就能把大模型的能力转化成实际的业务价值。从"会聊天"到"能干活",看起来只是产品形态的一小步,实际上是整个技术栈适配的一大步。千问这一步迈得颇为扎实,而作为开发者,我们要做的就是把油门踩下去,在模型和业务之间建起一座可靠的桥。别等"机会窗口"关闭了再动手,现在正是把模型从聊天框里解放出来、让它真正干活的好时候。
这一两年我最大的一个感受是:大模型的热闹劲儿过去了,真正静下心来研究怎么让它"干活"的人,开始吃到红利了。去年大家还在比谁的对话更流畅、更有"人味儿",今年风向明显变了——客户上来就问:"它能不能自己把活干了?"所谓"干活",不是让它写一首诗或者讲个笑话,是让它能调你的接口、查你的数据库、操作你的系统、跑完一整条业务流程。就在这个节骨眼上,千问这一系模型的能力升级,让我觉得机会真正来了。之前你可能觉得大模型就是个聪明点的聊天框,但当你发现它已经能看懂一份工具清单、自己决定调用哪个函数、把返回值消化成下一步动作的时候,你心里那个"这玩意儿能帮我省一个人"的念头,就开始压不住了。这篇文章我想用我做过的几个实际项目来聊聊:为什么说"能干活"和"会聊天"是两码事,怎么让千问这样的模型真正接进你的业务流程里,以及这一路上我踩过的那些坑。如果你正准备把一个"只会聊天"的机器人升级成"能办事"的助手,这篇文章应该能帮你省下几周的摸索时间。1. 从"对话"到"干活":大模型价值的临界点1.1 对话只是入口,执行才是价值先别急着聊技术,我们算一笔账。一个纯对话机器人,它的价值上限大概是"节省一点客服人力"或者"做个有趣的产品Demo"。但一个能执行任务的Agent,它的价值空间会大得多——它可以替你盯监控数据、自动生成报表、帮你把客户的消息分类并回复、甚至在无人值守的时候完成一整套操作。这两者的差距,本质上不是工程复杂度的问题,而是产品定位的分水岭。我做个项目时印象特别深。甲方最初的需求是"做个AI客服",我说没问题。后来聊深了才发现,他们真正的痛点根本不是"回答客户问题",而是每天有几百条售后消息需要分类、查订单、填工单、发通知——四步操作全是重复劳动。这哪是对话需求,这分明是自动化需求。等我把方案改成"AI+工具调用"的Agent架构时,对方的眼神都亮了。那一刻我确定了:纯对话是玩具,能干活才是工具。千问这个系列模型之所以在这个时间点值得关注,不光是它的语言理解能力又强了,而是它在"要不要调用工具、调哪个工具、参数怎么传"这件事上的判断越来越稳。这件事看着简单,但如果你在去年试过让某个大模型去调API,大概率会有种"教它用工具比自己做还累"的挫败感。模型不是乱调就是不调,参数传得七零八落。而现在的千问在这块的表现,基本到了"可以放手让它跑一跑"的程度。1.2 "会聊天"和"能干活"的底层差别大多数人以为"干活"就是"聊天"的延伸——你能理解了,难道还不能执行吗?实际上这是两条完全不同的技术路线。聊天模型的核心目标函数是"生成一个合理的回复",它只需要对语言负责。但"干活"的模型需要的是"生成一个正确的动作序列",它得对结果负责。语言通顺不等于步骤正确,这是本质区别。举个例子,用户说"帮我查一下昨天的销售额,顺便把异常波动的数据标出来",聊天模型会回复你一段话:"好的,昨天的销售额是X,异常波动的数据有Y",但如果这个模型要干活,它得经历这些步骤:识别出这是一个多步骤任务,拆成"查数据"和"做分析"两件事。知道"查数据"对应的是哪个数据接口,该传什么时间参数。把接口返回的原始数据消化掉,挑出"异常波动"的部分。生成一份标记好的结果,用约定的格式返回给上层系统。这个过程里,模型每一次"思考"都不是为了生成漂亮的文字,而是为了驱动一次真实世界的状态变化。所以你会发现,能干活的好模型和能聊天的好模型,评价标准完全不同。后者看流畅度,前者看重成功率、参数准确率、错误恢复能力。千问系列近几次更新里,我对工具调用的成功率提升感知特别明显,这也是我决定围绕它做深度集成的原因。1.3 为什么"干活"比"对话"更容易商业化纯对话产品很难收费,这是行业里的公开秘密。用户用完就走,觉得你不过是个高级点的搜索引擎。但"干活"不同,它是可以量化价值的。一个Agent跑通了一条原本需要人力操作五分钟的流程,省下的就是真金白银。我在和几个团队聊的时候发现一个规律:凡是说得清"这个Agent每天帮我处理多少单、省多少时间"的项目,推进得都特别快;凡是还停留在"我们想做个智能客服"的项目,基本都卡在需求评审阶段。因为前者是明确的降本增效,后者是模糊的锦上添花。千问开始具备稳定的干活能力之后,最直接的影响是:以前大模型只能当个"前端花瓶"的项目,现在可以把模型塞进业务流程的核心环节里了。这对做ToB解决方案的团队来说,是个实打实的机会窗口。2. 核心机制拆解:让千问学会"调用工具"2.1 Function Calling:模型与外部世界的接口要让大模型"干活",第一步不是写业务代码,是让模型具备"调用外部工具"的能力。这个能力在技术上有专门的称谓:Function Calling,也就是函数调用。打个比方,模型本来是一个只有嘴巴的专家,它再聪明也碰不到现实世界的任何东西。Function Calling就是给它装上手和脚,让它能通过一套标准协议去操作你提供的工具。具体原理是这样的:你在请求模型的时候,除了给它用户的问题,还额外给一份"工具清单"——每个工具包含名字、功能描述、参数定义。模型看完用户的问题,再比对这些工具描述,会决定这一步要不要调用工具、调用哪个、参数填什么。关键是,模型返回的内容不再是一段人话,而是一个结构化的调用指令,你的代码拿到这个指令后去真正执行,再把执行结果交还给模型,让它继续下一步处理。我最初上手的时候,以为这个功能很复杂,其实千问的API已经把这层封装得很好了。你只需要在请求里加上工具定义,模型就会在你需要的时候稳定返回工具调用请求。下面是一个最简化的示例,能帮你理解它的工作方式:from openai import OpenAI client = OpenAI( api_key="your-api-key", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) tools = [ { "type": "function", "function": { "name": "get_sales_data", "description": "获取指定日期的销售额数据", "parameters": { "type": "object", "properties": { "date": { "type": "string", "description": "日期,格式YYYY-MM-DD" } }, "required": ["date"] } } } ] response = client.chat.completions.create( model="qwen-plus", messages=[ {"role": "user", "content": "帮我查一下昨天(2025-01-15)的销售额"} ], tools=tools ) # 如果模型决定调用工具,它会在这里返回工具调用请求 print(response.choices[0].message.tool_calls)你看,模型不会直接回答"昨天的销售额是多少"这种它根本不知道的事,而是返回一个类似"我需要调用get_sales_data这个函数,参数date为2025-01-15"的结构化指令。你的代码拿到这个指令后,去数据库查询,把结果再塞回对话上下文里,模型就能基于真实数据继续做分析了。这就是一次完整的"干活"闭环。2.2 让模型准确选择工具的三个关键设计一开始我以为只要把工具定义传给了模型,它就会准确调用。实践之后才发现,模型选错工具、传错参数的频率,直接决定了整个系统的体验。后来我总结出三个关键设计,缺一个都不行。第一是工具名字和描述要像"写给外行人看"一样清晰。很多人写工具描述的时候想当然,觉得参数名叫"start_time"大家都能懂。但模型是真不知道你的业务里start_time到底是订单创建时间还是支付时间的。你得把描述写成这样:"查询订单的起始时间,格式为时间戳,单位毫秒,注意是订单支付完成的时间而非创建时间"。别怕啰嗦,描述越精准,模型的判断越准。第二是参数定义要尽量用枚举值约束。比如有个字段表示订单状态,你可以直接限定它只能传"pending"、"completed"、"cancelled"三个值。这样模型不会自作聪明传一个"已完成"或者"end"之类的值进来。代码里限制死取值范围,比事后清洗有效得多。第三是工具数量要克制。这是我踩过最大的坑。有一天我兴奋地给Agent接入了二十多个工具,心想这下它什么都能干了。结果模型开始频繁地在两个功能相似的函数之间反复横跳,像一个选择困难症患者。后来我做了几层合并,把工具收敛到八个左右,准确率一下就上来了。工具太多的时候,模型的信息检索负担太重。能用三个工具完成的事情,不要拆成十个。2.3 千问在这块的优势和它的"性格"说句公道话,市面上主流大模型我都试过Function Calling相关能力,千问在我这边的实测感受排在第一梯队。最明显的一点是,它对中文业务场景的理解有天然优势。比如你在工具描述里写"查询最近三天的销量,注意排除退款订单",一些国外模型会在"退款"这个业务语义上绕弯子,而千问基本能直接抓住重点。另外千问的参数提取能力强得有点超出预期。我给它一段用户原话,里面混杂着时间、地点、人物、金额这些信息,它能比较准确地抽取并映射到工具参数里。有一次用户说"帮我把上周三到周五的所有异常订单重新跑一遍分析,重点看退款率超过20%的店铺",模型能准确解析出时间范围、操作对象、过滤条件,一次性把参数填齐。这种"把口语翻译成结构化参数"的能力,就是Agent体验好不好的分水岭。不过它也有自己的"性格",就是偶尔会过度自信。举个例子,如果工具返回的数据有空值或者格式不对,它会倾向于"脑补"一个合理的值而不是如实告诉你"数据有问题"。所以我在系统设计里加了一层校验逻辑,比模型的判断优先,发现参数不合预期就打回重来,不能让它自作主张。3. 实操过程:搭一个能真正干活的千问Agent3.1 环境准备与基础接入好了,理论说完了,咱们直接上手。我假设你已经有一个千问的API Key,如果没有的话,先去对应平台申请一个,步骤很简单,拿到Key就能开始。我的建议是直接用OpenAI兼容模式接入,这样做的好处有两个:一是市面上大部分开源框架都兼容OpenAI的客户端协议,你可以直接复用;二是将来想切换到其他模型,代码改动成本很小。下面这个代码片段可以验证你的环境是否通:pip install openai然后跑一个最基础的测试,确认模型能正常响应:from openai import OpenAI client = OpenAI( api_key="your-api-key", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) response = client.chat.completions.create( model="qwen-plus", messages=[ {"role": "system", "content": "你是一个智能助手,能调用工具完成任务。"}, {"role": "user", "content": "你好,介绍一下你能做什么"} ] ) print(response.choices[0].message.content)这里有一点要特别提醒:base_url必须填对,很多新手在这里卡了半天,搞不清为什么一直报连接错误。如果是用国内的服务,region-endpoint和compatible-mode两个地址的含义不同,建议直接照官方文档用dashscope.aliyuncs.com/compatible-mode/v1这个兼容地址,省心。3.2 设计工具函数:从业务逻辑到模型可识别的Schema现在进入核心环节:设计工具函数。好的工具设计是Agent能用起来的前提,这一步做不好,后面全白搭。我的习惯是先列出业务上需要的能力清单,再逐条映射成工具定义。拿我最近做的一个"售后工单自动处理Agent"来举例。这个Agent要做的事情是:收到客户反馈后,自动判断问题类型、查询对应订单、检查售后政策、生成处理建议。我给它设计了四个工具:工具名功能说明关键参数查询订单信息根据订单号查询订单详情,含商品、金额、状态订单号(order_id)检查售后政策查询商品对应的退换货政策商品类目(category)、购买时长(days_since_purchase)查询客户历史工单查看该客户之前有没有类似的工单记录客户ID(customer_id)创建处理工单生成一条处理建议并写入工单系统订单号、处理类型、说明文本每个工具在代码里都是一个普通函数,但在传给模型时需要转换成Schema格式。下面这个是"查询订单信息"的Schema示例:tools = [ { "type": "function", "function": { "name": "query_order", "description": "根据订单号查询订单详情,包括商品信息、订单金额、物流状态、售后状态等", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,例如SO20250115001" } }, "required": ["order_id"] } } }, # 其他工具定义省略 ]写到这里你会发现,这个"工具定义"本质上就是一份给模型看的说明书。模型不执行这些函数,执行它们的是我们的代码,模型只负责根据用户需求"选择"正确的函数和参数。所以描述写得越具体,模型选择就越准确。这里有个小技巧:描述里带上业务关键词,比如"查询订单详情"和"根据订单号查询订单详情,包括商品信息、订单金额、物流状态、售后状态",后者明显更准确,因为模型能从中理解这个工具返回什么数据、适合处理什么问题。3.3 多轮工具调用与上下文管理一个任务往往不是调一次工具就能完成的。比如用户问"帮我查一下订单SO20250115001的售后进度,如果还没处理完就催一下",Agent需要先查询订单状态,再判断是否需要进入催办流程,可能还需要调用创建工单或者发通知的工具。这就是多轮工具调用。实现多轮调用的逻辑其实不复杂,是一个"循环":把用户问题发给模型,带上所有工具定义。如果模型返回了工具调用请求,就执行对应的函数。把函数执行结果以"工具消息"的形式返回给模型。重复第2步,直到模型不再请求调用工具,返回最终答案。在代码层面,关键点在于每次调用都要把之前的消息历史完整保留。我的做法是把每一轮的User消息、Assistant消息、Tool消息全部追加到一个列表里,完整的上下文信息都保留下来。这里有一个特别容易踩的坑:千问或者其他大模型对上下文长度有限制,如果你把整个对话历史无脑塞进去,很快会撞上长度上限。我的方案是做摘要压缩——当历史消息超过一定轮数时,把前面的对话交给模型生成一个摘要,用摘要替换旧消息。下面展示一下循环调用的核心逻辑:def run_agent(user_input, tools, tool_functions): messages = [{"role": "system", "content": "你是一个售后工单处理助理。"}] messages.append({"role": "user", "content": user_input}) for _ in range(5): # 设置最大迭代次数,防止死循环 response = client.chat.completions.create( model="qwen-plus", messages=messages, tools=tools ) message = response.choices[0].message if message.tool_calls: # 模型要求调用工具 messages.append(message) for tool_call in message.tool_calls: func_name = tool_call.function.name func_args = json.loads(tool_call.function.arguments) func = tool_functions[func_name] result = func(**func_args) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) else: # 模型不再调用工具,返回最终回答 return message.content return "处理超时,请稍后重试"注意我设了一个最大迭代次数(这里是5),这是非常重要的一步。如果不加这个限制,一旦模型陷入循环调工具的困境,你的请求就会一直空转,既浪费token又拖慢响应。实战中我会根据任务复杂度把上限设成5到10之间,基本够用了。3.4 一个完整的业务流程案例这里我用一个模拟场景串一遍整个流程。假设我们接到一个用户消息:"我上周买的东西坏了,订单号是SO20250110088,想退货,帮我看下能不能退。"第一步:模型看到"订单号"和"退货"两个关键信息,决定调用query_order工具,参数是{"order_id": "SO20250110088"}。第二步:你的代码执行query_order函数,从数据库查出这笔订单的信息,返回给模型:"订单状态:已签收;商品类目:数码配件;购买时间:2025-01-10;当前时间:2025-01-15。"第三步:模型看到已签收、购买时间为5天前,判断需要进一步确认退货政策。它继续调用check_return_policy工具,参数是{"category": "数码配件", "days_since_purchase": 5}。第四步:工具返回"数码配件支持7天无理由退货,用户购买5天,在政策范围内"。第五步:模型综合所有信息,决定调用create_ticket工具,生成一条退货处理工单,并返回给用户一段话:"您的订单支持退货,我已为您生成退货工单,工单编号TK20250115023,请在24小时内寄回商品。"整个过程中,模型的角色更像是一个"调度员":它每一步都在思考"现在我需要什么信息、哪个工具能提供这个信息、下一步应该做什么"。这就是干活和聊天的区别——聊天只需要最后一句话漂亮,干活看重的是每一步都准确。4. 常见问题与排查技巧实录4.1 模型死活不调用工具,怎么办这是最常见的问题。你明明把工具定义传过去了,模型却像没看见一样,直接凭自己的知识硬答。排查思路按顺序来:第一,检查工具定义的数量和复杂度。如果工具特别多,光看描述就有十几个,模型可能会不知所措。我的经验是超过十个工具时要考虑合并同类的。第二,检查消息格式。有些接口要求工具消息必须配套对应的工具调用ID,如果ID对不上,模型在后续轮次可能就无法正确理解工具上下文。我的做法是把每轮调用的tool_call_id存好,确保一条不缺。第三,给系统提示词加个强制引导。我在System Prompt里加了一句:"当需要查询实时数据或执行操作时,必须调用提供的工具,不要直接回答。"这句简单的约束能把工具调用率提升好几个百分点。第四,如果以上都排查了还是不行,注意是不是某些特殊请求里带了奇怪的思考链,干扰了模型。这时候可以简化用户输入,去掉多余修饰词再试一次。4.2 参数传递出错:模型总是不按套路传参另一个高频问题是模型生成的工具参数格式不对,比如缺字段、多字段、类型不匹配。这个问题的成因通常在Schema定义上。我排查逻辑是:先看参数的required字段,是否把所有必要的字段都列上了。列上之后,模型就应该稳定输出这些参数。再检查description里有没有说清楚参数值的格式。比如时间戳,你需要写清楚是"毫秒级Unix时间戳"还是"字符串YYYY-MM-DD",不然模型隔三差五传出个别的格式。如果模型确实频繁传错特定参数,还有一个笨但有效的办法:在函数内部做兜底转换。例如不管模型传的是"2025年1月15日"还是"2025-01-15"还是"2025/1/15",函数内部统一解析为标准日期。工具自己皮实一点,整体可靠性就上去了。4.3 多轮任务中模型"失忆"或者上下文混乱Agent干起活来一跑就是好几轮,上下文越长,出问题的概率越高。我遇到过模型的后续轮次把之前的工具执行结果理解错了,或者是把A订单的信息安到B订单上。这个问题的根源在于上下文里的信息太多了,模型抓不住重点。我的解决方案是在每次工具调用结果的文本里做"高亮处理"——不是真的高亮,而是在返回内容里把关键信息提取出来,用规整的格式重写一遍。比如原始工具返回的是一个大JSON,我不会直接把整个JSON喂给模型,而是写个小函数把关键字段抽出来,整理成"订单号:xxx,状态:已签收,金额:xx元"这样简洁明了的文本。模型处理这种结构化文本时,上下文理解会清晰很多。4.4 常用排查手段汇总现象可能原因解决方案模型不调用工具工具描述不清/工具过多/提示词缺少引导优化描述、合并工具、在System Prompt中加强制性语句参数传错格式Schema定义不完整/描述不具体完善required字段、细化description、在函数内部做兜底转换多轮信息混乱上下文过长/关键信息被淹没精简工具返回内容、按关键字段提取、用规整文本重写任务中途报错工具执行异常/网络超时增加异常捕获、设置重试机制、模拟数据兜底返回效果不稳定模型参数配置不合适把temperature调到0.3以下,必要时固定随机种子5. 实操心得:让Agent从"能用"到"好用"的三个进阶技巧5.1 每次调用都要"自证清白":记录决策轨迹Agent跑起来之后,最大的痛苦是出了问题不知道怎么排查。它自己调了什么工具、为什么调、返回了什么,如果这些没有记录,你只能看着最终结果发呆。我的做法是给Agent增加完整的日志系统,每一步操作都记录:模型的原始输出(包括思考过程里的工具调用指令)工具的名称、参数、返回值从调用到返回的耗时有了这些记录,调试体验会完全不一样。有一次我花了半天时间定位一个问题:Agent在处理用户订单时把一个老订单当成了当前订单。如果有调用轨迹,一眼就能看到模型传入的日期参数有问题,而不是在几万行日志里大海捞针。这个习惯一定从一开始就养成。5.2 给Agent配备"防呆机制":确认-重试-兜底让模型干活,永远不能100%信任它。我的工程准则是:高风险操作必须加确认环节,低置信度的结果必须留在系统边界内校验。具体来说,如果Agent要执行的是不可逆操作(比如删除数据、扣款、提交订单),我坚持让它在执行前输出一个"操作确认"步骤,由用户或者上级系统做二次确认。虽然这会让流程多一步,但换来的稳定性是值得的。另外,每次工具调用都应该包一层try...except,接口超时了就自动重试一次,重试失败就返回一个明确的错误信息给模型,让它决定怎么处理。5.3 用"测试剧本"替代手工试错最后一个小建议:不要靠手工聊天来测试Agent的好坏。应该准备一套固定的"测试剧本"——比如"正常路径"、"参数缺失路径"、"工具报错路径"、"上下文超长路径",每次代码有改动,就自动化跑一遍这些剧本,对比结果是否回归。我的测试剧本大概有二十多个场景,每次跑完能发现不少问题。这个投入非常值得,因为Agent的调用链复杂,手工测试往往只能覆盖到自己想得到的场景,而测试剧本能帮你覆盖到系统设计的全部边界。这一两年做Agent相关项目,我最大的体会是:大模型的能力天花板已经足够高了,真正的瓶颈往往在工程基础设施——工具的稳定性、上下文的管理、异常的处理。谁能把这些工程细节做好,谁就能把大模型的能力转化成实际的业务价值。从"会聊天"到"能干活",看起来只是产品形态的一小步,实际上是整个技术栈适配的一大步。千问这一步迈得颇为扎实,而作为开发者,我们要做的就是把油门踩下去,在模型和业务之间建起一座可靠的桥。别等"机会窗口"关闭了再动手,现在正是把模型从聊天框里解放出来、让它真正干活的好时候。