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

资讯详情

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

大语言模型工具调用实战:从原理到Agent构建指南

大语言模型工具调用实战:从原理到Agent构建指南 1. 项目概述从“递纸条”到“动手干活”最近在折腾大语言模型LLM应用开发的朋友估计都绕不开一个核心问题怎么让这个“聪明但手无缚鸡之力”的AI模型真正去“做”点事情比如你问它“今天上海天气怎么样”它可能给你编一段看似合理的描述但无法给你一个实时的、准确的天气预报。这就是LLM的局限性——它本质上是一个基于概率生成文本的模型它的“知识”和“能力”都凝固在训练数据截止的那个时间点无法感知实时世界也无法操作外部系统。于是“工具调用”这个概念就火了起来。你可以把它想象成让LLM学会“递纸条”。LLM作为大脑负责理解你的意图、规划步骤、生成指令但它自己不执行。它把写好的“纸条”一个结构化的请求递给一个专门的“跑腿小哥”一个函数或API由“跑腿小哥”去查询天气、发送邮件、操作数据库然后把结果“纸条”再递回给LLM大脑由大脑整理成最终答案告诉你。这个过程就是AI调用工具的核心。它让LLM从一个“聊天机器人”进化成了一个可以协调外部资源、完成复杂任务的“智能体”。围绕这个核心衍生出了Agent、Function Calling、Tool Calling等一系列技术和框架成为了当前AI应用落地的关键。2. 核心原理拆解LLM如何“理解”并“调用”工具要让LLM调用工具核心是解决两个问题意图识别和参数结构化。LLM生成的普通文本是自由、非结构化的但调用一个API或函数需要严格遵循其接口定义包括函数名和参数格式。这就需要一个“翻译”机制。2.1 从“描述”到“可执行指令”的转换目前主流的方法无论是OpenAI的Function Calling还是其他开源模型的Tool Calling其底层逻辑都高度相似。开发者首先需要向LLM“声明”或“描述”可用的工具。这通常通过一个JSON Schema模式来完成。例如我们定义一个查询天气的工具{ type: function, function: { name: get_current_weather, description: 获取指定城市的当前天气信息, parameters: { type: object, properties: { location: { type: string, description: 城市名称例如上海北京 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位 } }, required: [location] } } }这个描述文件就是给LLM的“工具说明书”。它告诉LLM有一个叫get_current_weather的工具。这个工具是干嘛的获取天气。调用它需要哪些参数location是必需的字符串unit是可选的枚举值。当用户提问“北京今天热吗”时结合这个工具描述LLM会进行推理“用户想知道天气我手头有天气工具。需要城市参数‘北京’温度单位用户没提我可以默认用‘celsius’摄氏度。” 然后LLM不再生成普通回答而是生成一个结构化的调用请求{ name: get_current_weather, arguments: { location: 北京, unit: celsius } }这个过程就是LLM将自然语言意图格式化为机器可读的指令。你的应用程序收到这个JSON后就可以解析它找到本地对应的get_current_weather函数传入参数执行获得真实的天气数据如{“temperature”: 28, “condition”: “sunny”}再将这个结果返回给LLM让它生成最终回复“北京今天天气晴朗气温28摄氏度比较热。”注意LLM本身并不“执行”工具它只负责生成调用的“指令”。执行发生在你的代码环境中。这确保了安全性因为工具的执行权限完全由你的应用程序控制。2.2 多工具协作与流程控制Agent的诞生当工具数量变多任务变复杂时简单的单次调用就不够了。例如用户说“帮我查一下上海明天的天气如果下雨就提醒我带伞并推荐一个室内活动。” 这涉及多个步骤和条件判断调用天气API查询上海明天天气。判断天气结果中是否包含“雨”。如果下雨调用“发送提醒”工具。同时调用“搜索本地信息”工具寻找室内活动推荐。这就需要引入**Agent智能体**的概念。Agent是一个更高层次的抽象它包含LLM大脑、工具集手脚、记忆对话历史、知识和决策逻辑规划器。LLM在Agent中扮演“规划者”和“调度者”的角色。一个典型的Agent工作流如使用LangChain或LangGraph构建如下规划LLM根据用户目标和可用工具决定下一步该做什么Plan。执行LLM选择最合适的工具生成结构化调用指令Act。观察获取工具执行的结果Observe。循环根据观察结果决定是继续调用下一个工具还是已经收集到足够信息可以生成最终答案Loop。这个过程会循环进行直到任务完成。框架如LangGraph通过有向图来显式定义这个循环和状态流转使得构建复杂的多步骤Agent变得更加清晰和可控。3. 实战构建从零搭建一个天气查询Agent理论说再多不如动手一试。我们以DeepSeek最新模型为例使用Python和简单的框架概念搭建一个具备天气查询和日期判断能力的智能助手。这里我们不会直接使用复杂的框架而是手动实现核心流程以便你彻底理解每一步。3.1 环境准备与工具定义首先确保你有Python环境并安装必要的库。我们主要需要requests来调用天气API以及openai兼容的库来调用DeepSeek这里假设使用openai库通过配置base_url指向DeepSeek API。pip install openai requests接下来定义我们的两个核心工具。我们将使用一个免费的天气API例如open-meteo和一个简单的日期判断函数。import requests import json from datetime import datetime # 工具1获取实时天气 def get_current_weather(location: str) - str: 根据城市名获取当前天气。 参数: location: 城市名如 Shanghai 返回: 描述天气的字符串。 # 这里使用open-meteo的免费API仅作示例 try: # 首先获取地理坐标简化处理实际应用需更健壮的地理编码 geo_url fhttps://geocoding-api.open-meteo.com/v1/search?name{location}count1 geo_resp requests.get(geo_url) geo_data geo_resp.json() if not geo_data.get(results): return f未找到城市 {location} 的地理信息。 lat geo_data[results][0][latitude] lon geo_data[results][0][longitude] # 获取天气数据 weather_url fhttps://api.open-meteo.com/v1/forecast?latitude{lat}longitude{lon}current_weathertrue weather_resp requests.get(weather_url) weather_data weather_resp.json() current weather_data[current_weather] temperature current[temperature] windspeed current[windspeed] weathercode current[weathercode] # 简单转换天气代码为描述 weather_map { 0: 晴朗, 1: 基本晴朗, 2: 局部多云, 3: 阴天, 45: 有雾, 48: 结冰雾, 51: 小雨, 61: 雨, 80: 阵雨, 95: 雷暴 } condition weather_map.get(weathercode, 未知) return f{location}当前天气{condition}气温 {temperature}°C风速 {windspeed} km/h。 except Exception as e: return f获取天气时出错{str(e)} # 工具2判断给定日期是否是周末 def is_weekend(date_str: str) - str: 判断输入的日期字符串是否是周末。 参数: date_str: 日期字符串格式应为 YYYY-MM-DD例如 2024-05-18 返回: 判断结果的字符串。 try: date_obj datetime.strptime(date_str, %Y-%m-%d) # weekday() 返回 0-60是周一6是周日。通常56为周末。 if date_obj.weekday() 5: return f{date_str} 是周末。 else: return f{date_str} 不是周末是工作日。 except ValueError: return f日期格式错误请使用 YYYY-MM-DD 格式例如 2024-05-18。 # 工具字典方便通过名称调用 TOOLS { get_current_weather: get_current_weather, is_weekend: is_weekend } # 工具描述用于提供给LLM TOOL_DESCRIPTIONS [ { type: function, function: { name: get_current_weather, description: 获取指定城市的当前天气情况包括温度、风速和天气状况。, parameters: { type: object, properties: { location: { type: string, description: 城市或地区的名称例如上海北京New York } }, required: [location] } } }, { type: function, function: { name: is_weekend, description: 判断给定的日期是否是周末。输入日期格式必须为 YYYY-MM-DD。, parameters: { type: object, properties: { date_str: { type: string, description: 日期字符串格式为 YYYY-MM-DD例如2024-05-18 } }, required: [date_str] } } } ]3.2 与LLM交互发起工具调用请求现在我们编写核心的对话循环。这个循环模拟了Agent的“思考-行动”过程。from openai import OpenAI import os # 配置DeepSeek API (请替换为你的实际API Key) client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), # 建议从环境变量读取 base_urlhttps://api.deepseek.com # DeepSeek API 端点 ) def run_conversation(user_input: str): 运行一次完整的对话轮次包括LLM推理和工具执行。 # 第一步将用户输入和工具描述发送给LLM询问它是否需要调用工具。 messages [ {role: system, content: 你是一个乐于助人的助手可以查询天气和判断日期。请根据用户问题决定是否需要调用工具。如果需要请严格按描述生成工具调用请求。}, {role: user, content: user_input} ] # 首次调用让LLM决定是否使用工具 response client.chat.completions.create( modeldeepseek-chat, # 或使用其他支持的模型 messagesmessages, toolsTOOL_DESCRIPTIONS, tool_choiceauto, # 让模型自动决定是否调用工具 ) response_message response.choices[0].message tool_calls response_message.tool_calls # 将LLM的回复添加到消息历史中 messages.append(response_message) # 第二步如果LLM决定调用工具则执行工具 if tool_calls: print(f检测到工具调用请求: {[tc.function.name for tc in tool_calls]}) for tool_call in tool_calls: function_name tool_call.function.name function_to_call TOOLS.get(function_name) if function_to_call: # 解析LLM生成的参数 function_args json.loads(tool_call.function.arguments) print(f执行工具 {function_name} 参数: {function_args}) # 执行工具函数 function_response function_to_call(**function_args) # 将工具执行结果作为一条新消息追加告知LLM messages.append({ role: tool, tool_call_id: tool_call.id, name: function_name, content: str(function_response), # 确保内容是字符串 }) else: print(f警告请求了未知工具 {function_name}) # 第三步将工具执行结果返回给LLM让它生成面向用户的最终回答 second_response client.chat.completions.create( modeldeepseek-chat, messagesmessages, # 此时messages包含了用户问题、LLM的工具调用请求、工具执行结果 ) final_reply second_response.choices[0].message.content return final_reply else: # 如果LLM没有调用工具直接返回它的回复 return response_message.content # 测试对话 if __name__ __main__: # 测试1简单天气查询 query1 上海现在天气怎么样 print(f用户: {query1}) answer1 run_conversation(query1) print(f助手: {answer1}\n) # 测试2结合日期判断的复杂查询 query2 如果2024-05-18是周末就告诉我北京的天气否则告诉我东京的天气。 print(f用户: {query2}) answer2 run_conversation(query2) print(f助手: {answer2})实操心得与避坑指南参数验证至关重要示例中工具函数内部有简单的错误处理但在生产环境中必须在执行工具前对LLM生成的参数进行严格验证。例如location参数是否为空date_str格式是否真的符合YYYY-MM-DDLLM可能会生成“明天”、“下周一”这样的相对日期这需要你在工具描述中明确禁止或者在代码层进行转换。工具描述的精确性工具的描述description和参数描述是LLM能否正确调用的关键。描述要清晰、无歧义。例如“获取天气”就不如“获取指定城市的当前天气情况”明确。模糊的描述会导致LLM误用工具。上下文长度管理当对话轮次增多特别是工具调用结果数据量大时很容易触及模型的上下文窗口限制如1048576 tokens。你需要设计策略来压缩或总结历史消息例如只保留最近几轮对话和关键的工具结果摘要。错误处理与重试网络请求如天气API可能失败。你的代码需要能捕获这些异常并以一种LLM能理解的方式如返回“工具调用失败网络超时”反馈给LLMLLM可能会尝试其他方案或告知用户。tool_choice参数的使用设置为“auto”时由模型决定是否调用工具。你也可以强制调用{“type”: “function”, “function”: {“name”: “xxx”}}或禁止调用“none”。这在构建确定性的工作流时很有用。4. 深入探讨工具调用的高级模式与架构选型当我们从简单的单次调用走向复杂的多步骤Agent时就需要更系统的架构。市面上主流的框架如LangChain、LangGraph、Dify、FastAPI自定义逻辑等各有侧重。4.1 两种主流架构模式对比特性基于链Chain的模式(如 LangChain Expression Language)基于图Graph的模式(如 LangGraph, Dify Workflow)核心思想将多个组件LLM、工具、检索器按顺序连接成一条“链”。将工作流定义为节点步骤和边条件流转组成的有向图。控制流主要是线性或分支结构相对简单。支持任意复杂的循环、条件分支、并行和汇聚表达能力极强。适用场景相对固定的任务流程如问答 - 检索 - 总结。需要动态规划、多轮交互、状态保持的复杂Agent如客服、游戏NPC、复杂数据分析。状态管理通常通过链的输入/输出字典传递。拥有明确的“状态”State对象在整个图运行过程中持久化和更新。调试难度较简单可以逐步查看每个环节的输出。较复杂需要理解图的结构和状态流转但可视化工具能极大帮助调试。典型框架LangChain LCEL, LlamaIndexLangGraph, Dify Workflow, Microsoft Autogen如何选择如果你的任务像“配方”一样步骤固定链就足够了它更轻量、直观。如果你的任务需要“思考-行动-观察-再思考”的循环或者有复杂的“如果...就...”逻辑图是更强大和自然的选择。例如一个旅行规划Agent需要反复查询机票、酒店、景点信息并比较图模型能清晰地表达这种循环和决策过程。4.2 使用LangGraph构建一个决策型Agent让我们用LangGraph的概念不依赖具体安装来设计一个更智能的“旅行顾问”Agent的思维流程。这个Agent的目标是根据用户预算和目的地推荐航班和酒店。节点1规划LLM分析用户请求“我要去上海预算5000元”决定需要调用哪些工具查询航班、查询酒店。节点2执行航班查询调用外部航班API获取价格列表。节点3执行酒店查询调用外部酒店API获取价格列表。节点4判断与推荐LLM接收航班和酒店结果。判断“总价是否在预算内”。如果是进入节点5生成推荐报告。如果否则返回节点1并携带信息“当前选择超预算”要求LLM重新规划例如建议调整日期或选择更经济的选项。节点5结束生成最终推荐结束流程。这个包含循环从节点4回到节点1的流程用链就很难优雅地实现而用图则可以清晰地定义出来。在LangGraph中你会定义一个State里面包含messages对话历史、budget、flight_results、hotel_results等。每个节点负责更新State的一部分而边上的条件函数conditional_edge根据State的内容决定下一步走向哪个节点。4.3 工具生态与扩展性一个强大的Agent系统其工具库的丰富程度决定了它的能力边界。除了自定义API还可以集成搜索引擎让Agent获取最新信息。代码解释器让Agent执行计算、数据分析、图表生成。数据库连接器让Agent查询和操作内部业务数据。硬件控制接口在机器人或物联网场景中让Agent控制物理设备。设计工具时要遵循单一职责和接口明确原则。一个工具只做一件事并且输入输出清晰。这能让LLM更容易理解和正确调用。5. 常见问题、挑战与优化策略在实际开发和部署中你会遇到各种各样的问题。下面是一些典型问题及其解决思路。5.1 工具调用失败排查清单问题现象可能原因排查步骤与解决方案LLM不调用工具1. 工具描述不清晰或与问题不匹配。2. 系统提示词System Prompt未引导其使用工具。3. 模型能力或版本不支持工具调用。1. 优化工具描述确保准确反映功能。2. 在System Prompt中明确指令如“你拥有以下工具请优先使用它们回答问题”。3. 确认模型是否支持tools参数如GPT-4, Claude, DeepSeek最新版本均支持。工具参数解析错误1. LLM生成的参数格式不符合JSON Schema要求。2. 参数值类型错误如需要数字却给了字符串。3. 参数值本身不合理如城市名不存在。1. 在代码中添加严格的JSON解析和参数验证对错误进行友好提示并反馈给LLM重试。2. 在工具描述中使用enum限定可选值用description详细说明格式。3. 在工具函数内部实现健壮的容错逻辑如城市名模糊匹配。工具执行超时或出错1. 依赖的外部API不稳定或不可用。2. 网络问题。3. 工具函数本身有Bug。1. 为外部API调用设置合理的超时如timeout10和重试机制。2. 使用try...except捕获所有异常并返回明确的错误信息给LLM。3. 对工具函数进行充分的单元测试。上下文溢出Context Overflow1. 多轮对话和大量工具结果导致token数超限。2. 工具返回的数据量过大如长篇网页内容。1. 实现“短期记忆”管理定期总结或丢弃早期对话。2. 让工具返回精简的数据或让LLM在调用工具前就指定需要哪些字段通过参数。3. 使用具有更长上下文窗口的模型。Agent陷入死循环1. 规划逻辑有缺陷导致在几个步骤间无限循环。2. 工具返回的结果始终无法满足结束条件。1. 在图模式中设置最大循环次数interrupt_before。2. 设计更明确的成功/失败终止条件。3. 增强LLM的规划能力或在System Prompt中给出更具体的步骤指引。5.2 性能与成本优化减少不必要的调用每次工具调用都意味着一次LLM推理和一次外部请求有延迟和成本。可以通过更精准的工具描述和更聪明的提示词工程让LLM在“需要时”才调用工具。并行调用工具如果多个工具调用之间没有依赖关系如同时查询航班和酒店应尽可能并行执行而不是串行这能大幅降低总延迟。LangGraph等框架支持这种并行节点。缓存工具结果对于相同参数的查询如“上海天气”在短时间内被多次问及可以将结果缓存一段时间避免重复调用外部API。选择性价比高的模型对于工具调用的“规划”步骤不一定非要使用最顶级、最昂贵的模型。可以尝试用较小、较快的模型如DeepSeek-V4-Flash进行工具调用决策用大模型进行最终答案的润色和总结。5.3 安全性与可靠性考量权限隔离这是最重要的原则。Agent只能调用你明确赋予它的工具。确保工具函数本身是安全的不会执行破坏性操作如rm -rf /。在服务器部署时Agent应运行在权限受限的沙箱或容器中。输入净化与验证对所有从LLM生成并传递给工具的参数进行严格的清洗和验证防止注入攻击。特别是当参数用于构造数据库查询或系统命令时。用户确认机制对于涉及敏感操作的工具如发送邮件、支付、修改数据应在执行前设计用户确认环节可以由Agent生成确认语句等待用户明确同意后再调用工具。可解释性与审计记录完整的Agent执行日志包括每一轮LLM的思考如果支持、工具调用请求、参数、执行结果。这对于调试、优化和事后审计至关重要。让LLM学会“递纸条”调用工具是将其从“知识库”转变为“行动者”的关键一跃。从理解工具调用的基本原理到动手搭建一个简单的Agent再到规划复杂的多步骤工作流每一步都充满了工程上的细节和挑战。核心在于清晰地定义工具、稳健地处理交互、并设计出符合业务逻辑的流程。随着工具生态的丰富和框架的成熟构建强大、可靠的AI智能体将变得越来越容易。
返回列表