尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

大模型工具调用实战:从JSON Schema到Agent工程化架构

大模型工具调用实战:从JSON Schema到Agent工程化架构 最近在折腾本地大模型发现一个挺有意思的现象很多朋友把模型跑起来问它“现在几点”它能给你编一个时间让它算个“1234乘以5678”它能给你一个看起来很像那么回事的答案但仔细一算十有八九是错的。模型很努力但它就像一位被关在图书馆里的博学者知识渊博口若悬河却没法伸手去碰一下墙上的挂钟也没法拿起旁边的计算器。这其实就是当前大模型的一个核心瓶颈它擅长理解和生成语言但缺乏与真实世界“交互”和“执行”的能力。它知道“查时间”这个概念但没有调用系统API的“手”它理解算术逻辑但计算过程依赖其内部并不总是可靠的参数推理。“Agent”智能体和“Tool Use”工具调用要解决的正是这个“从动嘴到动手”的问题。它不是让模型变得更“聪明”而是赋予它使用现有工具的能力将模型的“思考”与外部工具的“执行”无缝衔接。上篇我们聊了Agent的基础理念和为什么需要工具调用。今天这篇我们深入“动手”环节聚焦于如何具体实现工具调用尤其是那个看似简单却至关重要的技术桥梁JSON Schema。我会带你从“模型说人话”到“系统能听懂”的完整链路走一遍不止于跑通Demo更会探讨如何设计一个健壮、可维护的工具调用层这是将AI助手从玩具变为生产力工具的关键一步。1. 工具调用不只是“能调用”更是“调用对”和“管理好”工具调用的核心思想很直观当模型遇到一个自己无法直接完成的任务如实时信息获取、复杂计算、操作外部系统时它不再硬扛或瞎编而是生成一个结构化的请求由系统去调用对应的工具函数、API、脚本等执行并将结果返回给模型由模型整合进最终回复。这个过程听起来简单但落地时至少面临三层挑战描述层如何让模型准确理解每个工具是干什么的、需要什么输入这就是JSON Schema的用武之地。决策层面对多个可用工具模型如何选择最合适的那一个执行与安全层如何安全、可靠地执行调用如何处理错误、超时如何管理工具的生命周期和权限很多初期的Agent项目只解决了第一层的“连通”问题用一个简单的if-else或规则匹配就实现了调用但在复杂度和可靠性上很快会遇到瓶颈。我们需要的是一套可扩展、可描述、可管控的机制。1.1 从自然语言到结构化请求Function Calling 的精髓各大模型平台如OpenAI、Anthropic、DeepSeek等普遍支持的Function Calling或Tool Calling功能是当前实现工具调用的主流协议。其工作流可以概括为以下几步定义工具开发者预先定义好工具列表每个工具包含名称、描述和参数模式通常用JSON Schema描述。模型决策将用户查询和工具列表一起发给大模型。模型判断是否需要调用工具以及调用哪个工具并生成一个符合参数模式的调用请求。系统执行你的应用程序收到这个结构化请求解析出要调用的函数名和参数在安全沙箱或受控环境中执行真正的函数。结果回传将函数执行的结果或错误信息再次发送给大模型由它生成面向用户的自然语言回复。这个流程的关键在于步骤2模型输出的不再是一段自由文本而是一个严格遵循预定格式如JSON的结构化数据。这实现了从非结构化语言到结构化指令的可靠转换。1.2 JSON Schema让模型和系统说同一种“结构语言”为什么是JSON Schema因为它是一种强大且通用的标准用于描述JSON数据的结构和约束。在工具调用中它扮演了“工具说明书”的角色。一个简单的工具定义可能长这样以OpenAI格式为例{ type: function, function: { name: get_current_weather, description: 获取指定城市的当前天气情况, parameters: { type: object, properties: { location: { type: string, description: 城市名称例如北京上海 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位, default: celsius } }, required: [location] } } }这份“说明书”告诉模型name工具叫什么用于系统匹配。description这个工具是干什么的。这是最重要的部分模型主要靠它来决定是否调用此工具。描述要清晰、具体包含典型用例。parameters调用时需要提供什么。它用JSON Schema定义了参数的类型、描述、是否必填、枚举值、默认值等。当用户说“上海天气怎么样”时模型结合工具描述就能生成{ name: get_current_weather, arguments: {\location\: \上海\, \unit\: \celsius\} }写好description的秘诀不要只写“获取天气”要写成“获取指定城市当前的温度、天气状况晴、雨等、湿度和风速等信息”。越能触发模型对应用场景的联想它判断得就越准。2. 动手实践构建一个具备工具调用能力的迷你AI助手理论说再多不如动手搭一个。我们以Python为例使用流行的litellm库它统一了多个大模型的调用接口来构建一个能查时间和做计算的助手。这里我们假设使用一个支持工具调用的本地模型如Qwen2.5-Coder-7B-Instruct或云端API。2.1 第一步定义你的工具集首先我们创建两个简单的工具函数并为其生成JSON Schema描述。在实际项目中这些工具可以是任何东西数据库查询、调用第三方API、执行系统命令、操作文件等。import json from datetime import datetime import math # 1. 实际的工具函数 def get_current_time(timezone: str Asia/Shanghai) - str: 获取指定时区的当前日期和时间。 Args: timezone: 时区字符串例如 Asia/Shanghai, America/New_York。默认为上海时间。 Returns: 格式化后的日期时间字符串。 # 简化处理实际应用中应使用pytz或zoneinfo处理时区 if timezone ! Asia/Shanghai: return f[模拟] 当前不支持时区 {timezone} 默认返回北京时间。 now datetime.now() return now.strftime(%Y-%m-%d %H:%M:%S (北京时间)) def calculate_expression(expression: str) - str: 计算一个数学表达式的结果。支持加减乘除、乘方和常见函数。 Args: expression: 数学表达式字符串例如 3 5 * 2, sqrt(16)。 Returns: 计算结果字符串或错误信息。 # 警告使用eval有安全风险仅用于演示。生产环境必须使用安全评估器如ast.literal_eval配合限制或专用库。 try: # 为安全起见这里进行极简的过滤和映射切勿用于生产 expression expression.replace(^, **) # 限制可用的数学函数 allowed_names {k: v for k, v in math.__dict__.items() if not k.startswith(_)} allowed_names[abs] abs result eval(expression, {__builtins__: {}}, allowed_names) return str(result) except Exception as e: return f计算错误: {e} # 2. 构建工具描述列表 (JSON Schema) tools [ { type: function, function: { name: get_current_time, description: 当用户询问当前时间、日期、几点钟时调用此函数。可以指定时区如‘纽约时间’。, parameters: { type: object, properties: { timezone: { type: string, description: 时区名称例如 Asia/Shanghai, America/New_York。如果用户未指定可默认为 Asia/Shanghai。, default: Asia/Shanghai } }, required: [] } } }, { type: function, function: { name: calculate_expression, description: 当用户需要进行数学计算、算术、解方程或询问数值结果时调用此函数。例如‘3加5等于几’、‘计算圆的面积’、‘1234乘以5678’。, parameters: { type: object, properties: { expression: { type: string, description: 需要计算的数学表达式。请将用户问题转化为标准的数学表达式例如将‘三加五’转化为‘35’将‘二的平方’转化为‘2**2’。 } }, required: [expression] } } } ] # 工具名称到实际函数的映射 tool_function_map { get_current_time: get_current_time, calculate_expression: calculate_expression, }关键点描述(description)是灵魂get_current_time的描述明确了调用时机问时间、日期、几点钟calculate_expression则涵盖了计算、算术、解方程等多种相关场景。参数设计要合理timezone给了默认值非必填更符合对话场景。expression是必填项。安全安全安全calculate_expression中的eval是极度危险的这里仅为演示。生产环境中必须使用安全的数学表达式解析库如numexpr、asteval或严格的白名单过滤机制。2.2 第二步与大模型交互触发工具调用接下来我们设置与大模型的对话并将定义好的工具列表传给它。import litellm from litellm import completion # 配置litellm这里以OpenAI格式的本地模型为例需自行部署 # 实际使用时请替换为你的模型服务地址和API Key litellm.set_verbose True # 开启详细日志便于调试 def chat_with_ai(user_query, conversation_history[]): 与AI对话支持工具调用。 # 1. 准备消息历史 messages conversation_history [{role: user, content: user_query}] # 2. 调用模型传入工具定义 try: response completion( modelopenai/your-local-model-endpoint, # 替换为你的模型端点 messagesmessages, toolstools, # 关键将工具列表传给模型 tool_choiceauto, # 让模型自行决定是否调用工具 api_basehttp://localhost:8000/v1, # 本地模型API地址 api_keyyour-api-key-if-required, ) except Exception as e: return f调用模型出错: {e}, conversation_history # 3. 解析模型响应 response_message response.choices[0].message tool_calls response_message.tool_calls # 4. 处理工具调用 if tool_calls: print(f模型决定调用工具: {tool_calls}) # 将模型的工具调用请求添加到消息历史 messages.append(response_message) # 遍历所有被调用的工具模型可能同时调用多个 for tool_call in tool_calls: func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) # 查找并执行对应的工具函数 if func_name in tool_function_map: func tool_function_map[func_name] try: # 执行工具 tool_result func(**func_args) print(f工具 {func_name} 执行结果: {tool_result}) except Exception as e: tool_result f工具执行出错: {e} # 将工具执行结果作为一条新消息追加到历史 messages.append({ role: tool, tool_call_id: tool_call.id, content: str(tool_result), # 结果需要是字符串 name: func_name }) else: # 处理未知工具 messages.append({ role: tool, tool_call_id: tool_call.id, content: f错误未知工具 {func_name}, name: func_name }) # 5. 将工具执行结果返回给模型让它生成最终回复 second_response completion( modelopenai/your-local-model-endpoint, messagesmessages, toolstools, # 第二次调用通常不需要再传tools但某些实现需要 api_basehttp://localhost:8000/v1, api_keyyour-api-key-if-required, ) final_message second_response.choices[0].message.content # 更新对话历史包含工具调用和最终回复 new_history messages [{role: assistant, content: final_message}] return final_message, new_history else: # 模型没有调用工具直接返回文本回复 final_message response_message.content new_history messages [{role: assistant, content: final_message}] return final_message, new_history # 模拟对话 if __name__ __main__: history [] queries [ 现在几点了, 帮我算一下 1234 乘以 5678 等于多少, 纽约现在是什么时间, 计算 sin(30度) 的值。 ] for query in queries: print(f\n用户: {query}) reply, history chat_with_ai(query, history) print(f助手: {reply})执行流程解析用户输入“现在几点了”。模型收到消息和工具列表发现get_current_time的描述匹配决定调用它。模型生成一个结构化的工具调用请求tool_calls。我们的程序捕获到这个请求解析出函数名和参数timezone使用默认值。程序在安全环境下执行真正的get_current_time函数得到当前时间字符串。程序将执行结果以特定格式role: tool追加到对话历史中。程序将包含工具结果的新历史再次发送给模型。模型看到时间结果生成最终的自然语言回复“现在是2023-10-27 14:30:00 (北京时间)”。程序将最终回复返回给用户。2.3 第三步处理复杂情况与错误上面的流程是理想路径。实际开发中必须考虑以下情况模型不调用工具用户的问题可能不需要工具或者模型判断失误。我们的代码中if tool_calls分支处理了无调用的情况。模型调用错误工具可能因为工具描述不清。需要在工具结果中返回清晰错误让模型在下一轮纠正。工具执行失败网络超时、API错误、参数无效等。必须在try...except中捕获异常并将友好的错误信息返回给模型。多轮对话中的工具调用对话历史需要完整保存模型消息、工具调用请求和工具执行结果以保持上下文连贯。并行工具调用一些高级模型支持在一个回合内调用多个工具。我们的代码通过for tool_call in tool_calls进行了简单支持。3. 超越基础构建健壮Agent工具层的核心考量让一个工具调用跑通Demo可能只需要一个下午。但要让一个Agent系统在生产环境中稳定、可靠、安全地运行就需要在架构和设计上多下功夫。3.1 工具的设计与管理哲学不要把所有功能都塞进一个工具。好的工具设计遵循“单一职责”和“高内聚”原则。粒度适中一个工具做好一件事。get_weather比get_weather_and_news_and_stock要好。细粒度的工具更易复用、测试和描述。描述驱动模型完全依赖你的文字描述来理解工具。用自然语言清晰地写出在什么场景下When、为了什么目的Why、使用这个工具What、需要提供什么Input、会得到什么Output。版本化与生命周期当工具更新如API变更时需要有版本管理。旧版Agent可能还在使用旧版工具描述需要考虑兼容性或迁移策略。一个建议的工具注册与管理模式class ToolRegistry: def __init__(self): self._tools {} # name - {func: callable, schema: dict} def register(self, name: str, func: callable, schema: dict): 注册一个工具 self._tools[name] {func: func, schema: schema} def get_tool_schemas(self): 获取所有工具的JSON Schema列表用于发送给模型 return [{type: function, function: info[schema]} for info in self._tools.values()] def execute(self, tool_name: str, arguments: dict): 安全地执行一个工具 if tool_name not in self._tools: raise ValueError(f未知工具: {tool_name}) tool_info self._tools[tool_name] # 在这里可以添加参数验证、权限检查、限流、审计日志等 return tool_info[func](**arguments) # 使用示例 registry ToolRegistry() registry.register(get_current_time, get_current_time, get_current_time_schema) registry.register(calculate, calculate_expression, calculate_schema) # 当模型返回工具调用时 tool_name get_current_time tool_args {timezone: Asia/Shanghai} result registry.execute(tool_name, tool_args)3.2 安全与沙箱给工具的“手”戴上手套允许模型调用外部工具等于打开了潘多拉魔盒。必须实施严格的安全控制。输入验证与净化对模型传入的参数进行严格检查。类型、范围、枚举值、字符串长度、正则匹配等。防止注入攻击。权限控制不是所有工具对所有用户或所有会话开放。需要建立基于角色、上下文或会话的权限模型。资源限制限制工具的执行时间、内存使用、网络请求次数和频率。防止无限循环或资源耗尽攻击。沙箱环境对于执行代码如eval、访问文件系统或网络的工具尽可能在隔离的沙箱环境如Docker容器、安全进程中运行。审计与日志详细记录每一次工具调用谁会话/用户、何时、调用什么工具、参数是什么、结果是什么、耗时多长。这是排查问题和安全审计的基础。3.3 错误处理与用户体验工具调用可能失败模型也可能基于错误结果生成误导性回复。需要有韧性的错误处理流程。工具执行失败应返回结构化的错误信息给模型例如{error: true, code: NETWORK_ERROR, message: 天气服务暂时不可用}而不是一个Python异常栈。模型可以学习解释这些错误并生成友好的用户提示。模型生成无效调用如果模型生成的参数不符合JSON Schema不应该直接崩溃。可以尝试修复或返回一个要求模型重试的指令。超时与重试为工具调用设置合理的超时。对于暂时性错误如网络抖动可以实现指数退避的重试机制。用户反馈循环当最终回复明显基于错误工具结果时应提供渠道让用户标记“结果不正确”这些反馈可以用来优化工具描述或模型微调。4. 工程化路径从单次调用到可编排的Agent工作流当工具数量增多、任务变复杂时简单的“一问一调”模式就不够用了。我们需要Agent框架和工作流编排。4.1 Agent框架的价值LangChain、LlamaIndex、Semantic Kernel等框架提供了更高层次的抽象标准化工具接口统一不同来源工具函数、API、数据库的接入方式。内置工具提供大量开箱即用的工具网络搜索、维基百科、计算器等。记忆管理帮助Agent记住对话历史、工具调用结果等上下文。规划与决策支持多步规划ReAct模式让Agent能“先思考再行动再观察”完成复杂任务。多Agent协作定义多个具有不同专长和工具的Agent让它们通过协作解决问题。使用框架可以避免重复造轮子快速搭建原型。但也要理解其背后的原理避免成为“调参侠”。4.2 工作流编排当任务需要多个步骤查天气可能只需要一步。但“帮我订一张下周五从北京飞往上海的最便宜机票并选一个靠窗的座位”这种任务就需要分解为多个步骤搜索航班、比价、选择航班、填写乘客信息、选座、支付确认。每个步骤可能调用不同的工具并且后续步骤依赖前序步骤的结果。这就是工作流编排要解决的问题。你可以使用像Prefect、Airflow这样的通用工作流引擎或者LangGraphLangChain的图编排组件这样的AI专用框架来定义和执行为Agent设计的有状态、有条件的工作流。# 伪代码展示工作流概念 def book_cheapest_flight_workflow(user_request): # 1. 解析用户意图可能由模型完成 intent parse_intent(user_request) # 2. 搜索航班 flights search_flights(intent.departure, intent.destination, intent.date) # 3. 找到最便宜的 cheapest_flight find_cheapest(flights) # 4. 检查座位图 seat_map get_seat_map(cheapest_flight.id) # 5. 选择靠窗座位 window_seat pick_window_seat(seat_map) # 6. 确认预订可能需要用户二次确认 confirmation confirm_booking(cheapest_flight, window_seat, intent.passenger) return confirmation在这个工作流中每个步骤都可能是一个工具调用也可能由模型决策触发。编排引擎负责管理步骤间的依赖、错误处理、状态持久化和重试。4.3 评估与迭代你的Agent真的在变好吗搭建好Agent后需要一套评估体系来衡量其表现工具选择准确率模型在需要时是否调用了正确的工具参数填充正确率生成的参数是否完整、准确任务完成率最终是否解决了用户的问题人工评分随机抽样让人来评价回复的质量。基于评估结果你可以优化工具描述如果模型频繁误调用某个工具检查其描述是否含糊不清。增加示例在系统提示词或Few-Shot示例中提供更多工具调用的正确范例。微调模型如果使用的是可微调模型可以收集高质量的“用户查询-正确工具调用”数据对进行微调让模型更擅长使用你的特定工具集。5. 总结从“会说话”到“能办事”的桥梁给大模型加上工具调用能力本质上是扩展其能力边界而不是改变其核心。模型依然是那个擅长理解和生成语言的“大脑”而工具则是它可灵活使用的“四肢”和“感官”。实现这一点的关键技术桥梁是结构化的工具描述JSON Schema和标准化的调用协议Function Calling。它们将非结构化的语言指令转化为结构化的、机器可可靠执行的命令。从实践角度看成功的工具调用系统需要三层构建清晰的定义层用精准的自然语言描述每个工具的用途、输入和输出。可靠的执行层安全、稳健地执行工具并妥善处理所有边界情况和错误。灵活的编排层将单个工具调用组合成复杂的工作流以完成多步骤任务。起步时可以从一两个核心工具开始专注于把“描述-决策-执行-回复”的闭环跑通、跑稳。然后再逐步丰富工具集引入权限、审计、编排等高级特性。记住一个能可靠调用计算器和查询时间的小助手其价值远胜过一个功能列表华丽但动不动就报错或胡言乱语的复杂系统。最终当我们把工具调用、记忆管理、规划能力结合起来一个真正的、能自主完成复杂任务的AI Agent才成为可能。而这一切都始于今天这看似简单的一步教会模型如何正确地伸出它的“手”。
返回列表