AI智能体从零到可用:大模型工具调用与自动化流程实战
1. 智能体到底是什么——先搞清楚概念再动手最近两年“AI智能体”几乎成了行业里最热的词各种消息满天飞有人说它是下一代的软件形态有人说不过是套了个壳的聊天机器人。我自己从最早接触大模型接口到后来沉迷调各种自动化工作流再到真正把一个能自主完成任务的智能体跑起来中间踩了不少坑也绕了不少弯路。这个系列文章就是想把我摸索出来的经验整理成一条相对顺的路径第一篇先解决“智能体到底是个什么东西”以及“怎么从零搭一个最小可用版本”。先给个直白的定义智能体就是让大模型不只是“动嘴”还能“动手”的一套系统。普通聊天机器人你问一句它答一句最多帮你编个文案、翻译段话智能体不一样它会自己拆解任务、调用工具、查看结果、再决定下一步做什么。你可以把它理解成一个干活不太熟练但学习能力很强的实习生你说“帮我整理一下这个项目里所有未归档的文件”他不会反问你要步骤清单而是自己去翻目录、识别文件类型、该归类归档、做完之后跟你汇报结果。这个思路其实并不新鲜十几年前的软件自动化和脚本编程就能干这类事但关键区别在于以前是你把步骤一步步写死告诉机器“先做A再做B遇到C就跳去D”智能体是只给它一个目标和一堆可用工具由大模型自己“临场发挥”去规划路径。好处是非标准流程也能处理坏处是它会犯错、会想歪你必须有兜底机制。现在业界对智能体的定义五花八门有人强调“自主决策”有人强调“多轮推理”有人强调“能调用外部API”。我的理解更朴素一些智能体 大模型大脑 规划循环思考 工具集手脚 记忆工作经验。缺了任何一块它都容易沦为普通的聊天框或者死板的脚本。这第一篇我会从最核心的循环开始讲起也就是“模型如何决定该调用哪个工具、如何读懂工具返回的结果、如何修正自己的行为”。再结合一个实际跑过的场景——让智能体去查天气并给出出行建议——把整个搭建过程拆开揉碎给你看。这个例子虽然简单但五脏俱全它包含了你后面做复杂智能体需要的所有关键机制工具定义、意图识别、参数抽取、结果解析、兜底策略。如果你是个完全没有接触过大模型开发的新手这篇也完全能跟上因为我会把涉及的概念都解释清楚如果你已经拿大模型接口写过一些脚本那这篇里的架构设计思路和踩坑记录应该能帮你少走不少弯路。后续系列文章还会讲多智能体协作、复杂记忆体系、更丰富的工具生态但第一脚如果踩不稳后面全是空中楼阁。2. 搭建前的全局思路——两条路线怎么选很多教程一上来就扔代码看完了你可能会跑通一个demo但换个场景就不知道怎么改了。我先把搭建智能体的宏观思路讲清楚你理解了整体框架后面填充细节容易得多。2.1 先搞清楚你缺的是什么搭智能体之前你要先花时间想清楚一个问题你想要的到底是“智能”还是“自动化”这两个词经常被混着说但做出来的东西完全不是一码事。如果你只是想“每天定时拉取某几个网页的标题总结成日报发给我”那这是个纯粹的自动化问题写个定时脚本就搞定了压根不需要智能体用了反而是杀鸡用牛刀。真正的智能体适合什么场景呢是那种“任务本身有不确定性需要根据中间结果动态调整”的场景。我用一个例子说明假设你要做一个竞品分析助手如果只是“抓取对手官网更新并总结”脚本就够了但如果你让它“持续监控竞品动态发现重要更新时结合自家产品卖点生成应对建议”这里就需要它自己判断什么算“重要更新”、什么信息值得扩展分析、分析到什么深度才适合写进报告。这就是智能体该出现的边界。所以我给你的第一句经验是先列一张清单把“固定步骤”和“需要决策”的任务分开。只有后者才值得用智能体框架来做否则你只是给自己找麻烦。2.2 两条主流搭建路线目前市面上搭智能体主要有两条路线各有各的优劣我建议你根据团队技术栈和业务复杂度来选。路线一可视化编排平台。近几年几个大厂都做了这类产品核心玩法是用积木一样的图形节点把“输入→大模型判断→调用工具→返回结果”串起来。优点是上手极快、不用写太多代码、调试的时候看得见流程走到哪个节点了。缺点是灵活性天花板比较低一旦你想要复杂的循环逻辑、多层嵌套调用拖拽画布会迅速变成蜘蛛网维护成本直接爆炸。路线二代码框架自建。用代码来定义工具、控制对话循环、管理记忆状态。目前比较主流的选择是以某个开源大模型家族为核心的生态配合Python的异步框架自己组织逻辑。优点是完全可控、可以构建极其复杂的推理循环缺点是需要更多的工程功底。但说句公道话一个中等复杂度的智能体核心编排代码可能也就两三百行没有你想的那么可怕。我的个人建议是如果你是为了尝鲜或者做一个简单的内部工具用可视化平台体验一下没问题但如果你打算认真做产品直接走代码路线别犹豫。原因有三第一代码可控性强线上出bug了你能改可视化平台出问题你只能干瞪眼。第二逻辑复杂到一定程度文字代码反而比图形化流程图更好维护。第三代码模式方便接入自己的测试体系能跑回归、能写断言这在大模型应用里尤其重要因为你模型的行为本身就不稳定没有一个自动化的验证机制产品上线等于裸奔。2.3 最小系统应该包含哪几块不管哪条路线一个能用的智能体必须具备这样几个能力模块主体调度模块负责驱动大模型“思考”把用户请求交给模型拿到模型的响应并解析。工具注册模块把外部API、函数、数据库查询封装成统一格式让模型知道“有哪些工具可以用、各自什么参数”。执行模块根据模型的输出去真实调用对应的工具并把结果返回给模型。状态管理模块记录当前对话历史、中间状态、递归深度、已尝试的步骤防止模型陷入死循环或者忘记上下文。记忆模块短期记忆靠上下文窗口长期记忆靠外部存储向量数据库、键值存储等。我在第一篇里先只做前四个模块记忆模块放到后面讲因为基础循环跑通之前谈记忆都是空中楼阁。就像你教实习生干活先教他怎么拆任务、怎么用工具再谈怎么记笔记、怎么积累经验顺序不能乱。3. 手把手搭一个最小可用智能体这一节是我最推荐你实际动手跟练的部分。我会以“查天气然后给出行建议”这个场景为例把从设计到跑通的全路径走一遍。3.1 先设计工具别急着写循环很多人犯的一个错误是上手就写大模型的调用循环工具函数拖到后面再补。我的习惯正好相反先把所有要用的工具函数写好测好最后再接模型。因为工具是你和模型之间的“公共语言”工具的边界理清楚了模型的规划才会准。这个场景里我需要两个工具一个是城市代码查询工具因为大多数天气API都要求传城市编码直接传中文名往往会失败第二个是天气查询工具传入城市代码和日期范围返回结构化天气数据。为什么要拆成两个工具因为模型先得把用户说的“北京”转换成合法参数“101010100”然后才能查天气。这个拆法也是后面排查问题的关键如果天气查询报参数错误你能明确知道是转换环节出错了还是查询环节出错了。工具的统一格式我用的是JSON描述法这是目前大模型工具调用最兼容的做法。每个工具包含name工具名供模型选择使用description这个工具是干嘛的、什么时候用、什么时候别用parameters参数列表每个参数的类型、是否必填、取值范围注意这个描述不是给人看的是给模型看的关键说明书。模型决定什么时候调用哪个工具、传什么参数完全依赖这里描述得清不清楚。比如城市代码这个工具如果你只写“查询城市编码”模型很可能在用户说“随便找个城市”的时候不知道怎么办如果你补一句“如果用户没有明确指定城市返回一个默认城市的编码”模型就会按你的规则走。两个工具写完之后我建议你直接用测试用例把函数调一遍确认入参出参符合预期。千万别图省事跳过这块后面模型一旦开始乱调工具你能确认“工具本身没问题”会省下大量排查时间。3.2 核心循环思考→行动→观察接下来是整个智能体最核心的部分循环机制。这段逻辑不复杂但对稳定性的影响最大我给你看一下核心过程。第一步把用户的请求和工具的描述列表一起发给大模型。注意这里不是只发用户的原话而是要让模型知道“你现在是个助手你手头有这么几个工具可以用各有各的约束”。模型看到这些之后会有两种可能的回复一种是直接生成最终答案意味着它觉得不需要调用工具或者光凭自己知识就能回答另一种是返回一个函数调用请求比如“我要调用城市编码查询工具参数是北京”。如果是第一种就直接把模型的回复展示给用户循环结束。如果是第二种你需要解析出它想调用哪个工具、参数是什么然后去真实执行那个工具再把工具返回的结构化数据包装成一条“观察结果”的消息连同之前的历史一起再次发给模型让它看到工具的结果、继续思考。这里有一个关键设计为什么每次调用都要把完整对话历史带回去因为模型本身没有记忆每次请求都是独立的。你必须在历史中保留“用户说了什么→你调了什么工具→工具返回了什么→你下一步打算干什么”模型才能在这个上下文中持续推进任务。这一步是新手最常踩的坑第二次调用的时候只传了新增加的那条消息把前面的历史丢了结果模型完全失忆开始胡言乱语。我在实际测试中发现这个循环能不能跑得顺畅很大程度取决于你对模型返回格式的解析能力。早期的开放性模型的输出是偏自由文本的你说“请你输出JSON格式的调用指令”它偶尔会输出一段带解释的文字再附带一个JSON。这就导致你没法用固定代码去解析必须让解析逻辑足够健壮。现在很多模型都支持原生函数调用功能生成的输出会有一个专门的结构化字段直接把解析难度降了一个量级。我建议你选型的时候把这个能力作为硬性指标不支持原生函数调用的模型代价是你后面得天天和解析逻辑搏斗那个体验真的糟糕。3.3 参数怎么定从零开始的一套可用配置写代码之前先放一组我实测比较稳的模型参数。这些参数非常重要直接关系到智能体的行为质量我逐个解释一下我为什么这么设。温度这个参数控制的是输出的随机性。数值越高越有创造力但越容易胡说数值越低越保守但显得笨。我的经验是在智能体场景里温度要调得偏低一般设到0.2以下。为什么因为工具调用场景你希望模型稳定输出不需要它发挥想象力。想象你让实习生去归档文件你希望他每次对待一个文件都能做出一样的判断而不是今天觉得这是重要文件、明天觉得可以扔掉。这种场景要的是稳定和准确。上下文长度这一项也很关键。它决定了模型能“看到”多少历史内容。查天气这个场景不太用的上大长上下文但如果是处理长文档或长对话就不一样了。大模型的费用通常是按输入输出token数计费的上下文越长单次调用的成本越高。不要把长度设置成你的上限够用就好。还有一个容易忽略的参数是“最多循环轮次”。这是我在实战中加上的保险丝因为模型有时候会陷入死循环调了一个工具看到结果不满意再调一个结果还是不满意如此反复十几次。如果没有一个上限一次请求能把你的预算烧穿。我自己通常设置最大循环次数为6到8次达到阈值后强制让模型停止并给用户一个交代“我在尝试了XX次之后仍未能完整解决目前进展是XXX可能的原因包括XXX建议你提供更多信息。”3.4 实操一个完整代码早就该跑通的场景我直接给你一个我目前项目里用的简化版智能体核心代码附上逐段注释。这套代码我跑过很多次配合上几节的配置在简单场景下成功率相当可观。import json from typing import Any, Callable class SimpleAgent: def __init__(self, model_api, max_rounds6, temperature0.2): self.model_api model_api # 你对接的大模型 API 封装 self.max_rounds max_rounds self.temperature temperature self.tool_registry {} # 存储所有注册的工具 def register_tool(self, name: str, desc: str, params: dict, handler: Callable): self.tool_registry[name] { description: desc, parameters: params, handler: handler, } def _build_system_message(self): tools_desc [] for name, tool in self.tool_registry.items(): tools_desc.append({ type: function, function: { name: name, description: tool[description], parameters: tool[parameters], } }) system_msg 你是一个智能助理请根据用户问题判断是否调用工具。 return {role: system, content: system_msg, tools: tools_desc} def run(self, user_input: str, verboseFalse): history [self._build_system_message()] history.append({role: user, content: user_input}) for round_idx in range(self.max_rounds): response self.model_api.chat( messageshistory, temperatureself.temperature, ) msg response[choices][0][message] history.append(msg) if !msg.get(tool_calls): return msg[content] for call in msg[tool_calls]: fn_name call.function.name fn_args call.function.arguments if fn_name not in self.tool_registry: result {error: f未知工具 {fn_name}} else: result self.tool_registry[fn_name][handler](**fn_args) history.append({ role: tool, tool_call_id: call.id, content: json.dumps(result, ensure_asciiFalse) }) if verbose: print(f[Round {round_idx}] 调用 {fn_name} 参数{fn_args}) return 已达最大尝试次数请补充信息后重试。这个类做完之后使用方式就非常简单了注册两个工具然后调用 run 方法。整体代码量不大但已经具备了一个最小可用智能体的核心能力会思考、会调用工具、会观察结果、有安全阀。我在实际跑这套代码时注意到几个影响精神状态的细节。第一是函数的实际返回值里别塞长篇大论的文本最好是紧凑的结构化JSON计算token更省。第二是给模型看的工具描述要像一个优秀的产品文档说明是什么、什么时候用、什么时候别用、参数边界是什么。我见过很多人工具描述写得模棱两可导致模型在边界情况乱来这全都是可预防的问题。第三是务必处理“模型编造工具”的情况这时候兜底逻辑一定要有。4. 为什么说工具调用是智能体的“手脚连接器”写完代码只是第一步你真正需要理解的是工具调用这套机制的内在逻辑。理解了它你才具备给智能体“长新手脚”的能力。4.1 模型天生不知道你的数据库在哪大模型的知识来自它的训练数据它可能知道“北京是中国的首都”但它一定不知道你公司的订单表存在哪个数据库里、查询接口的地址是什么。你要让智能体完成真实业务动作就必须让模型具备一种能力在合适的时机说出“我不知道这个但我知道去哪里查”。工具调用协议本质上就是在模型的知识边界和外部真实世界之间架一座桥。模型遇到自己不清楚的信息时不再强行编造一个答案而是输出一个标准化的“我要召唤外援”的指令。这是智能体和普通聊天机器人最本质的分界点。我之前做过一个内部知识库问答智能体聊到某员工入职时间时模型自己不知道但它通过工具调用查询了内部通讯录API并拿到了准确答案。整个过程在用户看来只是“我问了一个问题它一秒给了正确答案”好像很平常但背后是它正确地选择了工具、提取了查询参数、解析了结果。这个能力我用了大半天才调通调通的那一刻我还挺有成就感的。4.2 工具描述写得好不好直接决定智能体的下限工具描述这个环节我反复强调因为它实在太容易被低估了。很多人给工具写的描述只有一句话诸如“查询用户余额”“获取天气信息”然后把所有希望寄托在模型有读心术上。结果就是模型在模糊场景下的表现惨不忍睹。我来对比一下两种描述方式的差异。假如你的工具是“查询用户余额”如果描述只写了这一句模型面对一个用户说“我充值的钱去哪了”时它可能会纠结要不要调用这个工具——用户没明说“查余额”但是语境其实是这个意思。如果你把描述扩写成这样“查询用户当前账户余额。当用户咨询到账情况、充值记录、异常扣款等话题时使用。注意本工具仅查询余额不包含交易流水查询。”模型的行为准确度会明显上一个大台阶。描述工具本质上是给模型业务指引是在定义功能边界。“什么时候使用”和“什么时候不要用”这两块都要写清楚因为它们本质上一样重要。只告诉模型什么时候用它就不知道越界行为是被禁止的。我在一次训练智能体的实践中把工具列表里6个工具的description全部重新写了一遍没有改任何代码逻辑结果测试集通过率从六成出头提升到了接近九成。从这件事之后我再也没偷懒过写工具描述这件事。你把这个习惯养成后面的智能体做起来会高效很多。4.3 参数校验和容错预案不能省工具执行的异常处理是另一个新手重灾区。你想想这个场景天气查询工具要求传日期参数模型生成的是“昨天”而不是具体的“2024-06-15”。你运行工具的时候会直接报错——当然能发现这种粗粒度的问题——但如果你传的是“2024-02-30”呢很多API不会提前校验它会把请求发出去然后等一个不太友好的报错回来。我的建议是每个工具要有一层输入校验和安全兜底。日期参数就做日期格式解析并补默认值有边界要求的参数就加范围判断查不到结果时返回一个明确结构比如“未找到数据”而不是抛一个异常。因为你要记住这些工具的调用方是一个大模型它的输出天生带有不确定性你的代码要从“假设输入合法”改成“假设输入必然有不合法的情况我要做好防御”。防御到底是具体怎么做的我举我们的实际操作经验在工具函数入口先加一层简短的输入清洗参数明显不合法时不是直接报错而是返回一段能让模型理解的纠偏消息——比如“日期参数格式无效请使用YYYY-MM-DD格式重新提交”。因为工具返回的内容模型会“读”你给它可操作的纠偏信息它能修正自己。这个在实战里远比带脆弱的重试逻辑更管用。5. 踩过的坑和排查心法——谁还没被模型气过几回调试智能体和传统程序完全不一样。传统代码逻辑是确定的错了就是错的你总能找到那个出错的位置。智能体的每一轮输出都有随机性同样的输入两次跑了两个结果调试难度成倍增加。我在刚开始鼓捣智能体那会儿几乎每天都要对着日志叹气。这里我把自己反复踩的坑一次性列出来每条都是真金白银买来的教训。5.1 问题一它就是不调用工具怎么办最常见的问题是模型明明应该调用工具但它偏要硬答。一个用户问“北京的天气怎么样”模型回答“北京的天气一般来说……”然后开始用训练数据里的过期知识编造天气。这个时候你的智能体退化成了聊天机器人因为你有工具但它不用。这个问题的原因通常有三个。第一工具描述没有写清楚使用时机。如果描述里只写“查询指定城市天气”模型可能把“查询”理解成一种被动能力而不是“当用户询问任何实时天气时必须调用”。第二系统提示词里没有强调“实时信息必须通过工具获取”。模型不知道它自己的知识有延迟你必须明确告诉它“你的知识存在截止日期所有涉及天气、股票、新闻、价格的信息都必须调用工具不能凭记忆回答。”第三模型能力本身太弱原生工具调用能力不行。排查思路是这样的先在测试里直接发一句“调用天气工具查北京今天的天气”如果这都触发不了关键机制那是你工具注册写错了如果能触发再换成自然一点的用户说法逐级提升难度。这个过程能帮你定位是“不会用工具”还是“不知道该什么时候用工具”。5.2 问题二输出格式老翻车JSON解析失败在调用不支持原生工具函数调用的模型时你会让模型“用如下JSON格式输出调用指令”这就会遇到格式问题。模型偶尔会在JSON外面套一层markdown标记或者在前面加一句解释性文字或者输出不完整的JSON片段这时候你的解析代码就原地爆炸了。我解决办法有两层。第一是模型层优先选择支持原生函数调用的模型让格式输出由模型内部机制保证稳定度高很多。第二是代码层写解析逻辑的时候多做好兜底和修正——剥离掉markdown代码块标记遇到脏字符做处理实在解析失败就重试一次。这里给你一个我常用的预处理小技巧先用正则把json和标记剥掉再尝试解析。如果解析失败代码里确认字符串里包含“{”和“最后出现的}”切出中间部分再解析一次。这二段式解析看着简单实战中能救回相当一部分格式翻车事故。5.3 问题三上下文爆炸跑着跑着就乱了智能体如果在循环里调用多轮工具历史记录会积累得很快。每一轮的思考、调用申请、工具结果都要完完整整地留着跑个五六轮之后你光看历史消息的多轮对话就能吓一跳。上下文一旦超出模型的窗口限制你只能做截断而如果截断策略太粗暴直接把最早的对话截掉模型就会忘掉用户的初始需求后续输出质量直线下降。我目前的经验是按优先级做压缩优先保留用户初始描述和最新的几轮思考中间的工具调用细节做摘要。具体做法很简单在历史增长到一定长度时让模型自己把前面的内容summary成一段话然后继续跑下去。牺牲一些细节保住任务主线。这也解释了为什么下文记忆那块我刻意排到了后面真正的长期记忆需要一套独立的存储体系上下文截断摘要只是权宜之计。5.4 问题四工具结果不理想模型开始无限重试还有一类问题是模型发现工具返回结果不太对劲后像钻牛角尖一样反复重试。查天气查不到它就换个日期再查、换个城市再查同一个API连打八九次。这不仅费钱费时还会给业务系统造成不必要的压力。控制手段就是我前面提到的设置最大循环轮次上限。同时要在系统提示词里明确写一行字“当调用工具出现连续失败时停止尝试向用户解释问题并请其提供额外信息。”这两条配合使用能把失控行为限制在可控范围内。5.5 排查工具箱日志是智能体的半条命传统程序你可以随时打断点调试但模型每轮行为不完全可控你没法严格按照传统方式推进整个调试流程。所以日志的设计就显得格外重要了。我的习惯是在智能体代码里加详细日志每轮循环记录发了哪些消息、模型回复了什么、选择了哪个工具、传了什么参数、工具返回了什么、下一步怎么决定。日志越详细排查速度越快。很多智能体开发的问题盯着日志看个十分钟基本能锁定问题出在哪一个环节。6. 一些没地方归类的实操心得但很重要最后分享几个散装经验它们不属于某个步骤但实际用到的时候能救不少急。先给智能体一个“人设”和“行为准则”。不要一上来就聊功能要像带新人一样先说清楚它的身份“你是某团队的研究助理你需要基于接下来的工具结果完成总结并在输出时注明结果来源。”这能让它在所有工具都不适用的时候更得体地表达自己无法处理某类问题而不是用一句“我做不到”敷衍用户。工具开始时越少越好。新手总想一次性挂上十几二十个工具觉得功能越全越厉害。但模型面对的工具列表越长选错工具的概率越高。我建议第一版只挂三到五个强相关的工具跑通基础流程再逐步增加工具模块。工具的个数和成功率在我这边的实测数据是不成比例的负相关你要对模型的选择能力有个合理预期。接口和密钥的管理要用心。智能体要调用的真实插件多了之后涉及密钥和权限管理这里千万不要图省事硬编码在代码里或者直接扔进对话历史。一个很好的习惯是把所有工具的参数配置都放到独立的环境变量或配置服务里绝不让模型直接接触到密钥。我见过有人为了让模型“灵活”把数据库连接串写进工具描述里这个操作等于把家门的钥匙交给了一个说话不太靠谱的人保管风险极高。评估比开发更重要。这句话我是在踩了很多次坑之后才真正认可的。传统软件开发你会测试但智能体测试的性质完全不同因为模型的行为具有概率性。同一个测试用例跑三次也许结果各不相同。所以你需要建立一个测试集和评估流程定好二十到五十条有代表性的测试用例每次改完代码都统一跑一遍用通过率来衡量你的改动到底是在变好还是在变坏。市面上已经有一些辅助做评测的工具可以自动化这类验证但第一原则你就记住一句没有评测的迭代等于闭眼开车。这也算是一点遗憾——很多团队这个环节起步得太晚总以为结果看起来不错就够了结果产品一旦上线用户输入一变得复杂立刻露馅。我自己在折腾智能体的这一路最深的一个体悟是它更像是在培养一个实习生而不是在写一段程序。你得给它清晰的边界感、合适的工具、足够的反馈信息还得在教育成本和不信任之间不断找平衡。这篇先讲到这里后续的系列文里还会继续展开更深的记忆管理、多工具复杂协作、多智能体协同方向每一块都有大量值得写的内容。如果你按照这篇文章的思路完整跑通了一个最小智能体后面这些方向你已经有了一个相当扎实的地基。