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

资讯详情

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

LLM工具调用实战:从函数调用到智能体决策的架构演进与实现

LLM工具调用实战:从函数调用到智能体决策的架构演进与实现 1. 项目概述从函数调用到智能体决策的跃迁最近和几个做AI应用落地的朋友聊天大家普遍有个感受单纯靠大语言模型LLM生成文本已经很难做出真正解决实际问题的产品了。无论是处理实时数据、调用外部API还是执行复杂的工作流LLM本身就像一个知识渊博但“手无缚鸡之力”的顾问。它知道天气查询需要调用API也知道数据分析要用Python但它自己动不了手。这就是“LLM Tool Use”大模型工具使用技术要解决的核心问题——赋予LLM“动手能力”让它从单纯的文本生成器进化成能自主使用工具完成任务的操作者。这个领域现在火得一塌糊涂从OpenAI的Function Calling到各种AI Agent框架本质上都在做同一件事教会LLM如何根据你的指令选择合适的工具Tool生成正确的调用参数并理解返回结果。听起来简单但这里面门道很深。它不仅仅是技术实现更是一种全新的交互范式和智能构建思路。今天我就结合自己趟过的坑和项目经验来深度拆解一下LLM Tool Use从最基础的函数调用原理到复杂的智能体Agent运行时Runtime设计希望能帮你理清脉络少走弯路。2. 核心概念与架构演进2.1 从“函数”到“工具”思维的转变在传统编程中函数Function是封装好的、确定性的代码块。你输入参数它返回结果逻辑是固定的。但在LLM的语境下“工具”Tool的概念更宽泛。一个工具可以是一个API接口如查询天气、发送邮件、一个数据库查询操作、一段可执行的代码如Python脚本甚至是一个包含多步骤的子工作流。为什么是“工具”而不是“函数”这背后是范式的转变。函数调用要求调用者完全知晓其签名函数名、参数类型、返回值。而LLM使用工具是基于对自然语言指令的理解和工具的“描述”来进行的。LLM不需要事先“知道”工具的内部实现它只需要知道这个工具是“干什么的”功能描述以及“怎么用”输入输出格式。这种基于描述的、声明式的交互方式是构建灵活、可扩展AI系统的关键。2.2 核心组件拆解Tool, Agent, Runtime一个典型的LLM Tool Use系统通常包含以下几个核心层理解了它们就理解了整个架构工具层Tool Layer这是最底层由一个个具体的工具定义构成。每个工具都需要被“包装”提供机器可读的描述通常包括名称name唯一标识符。描述description用自然语言清晰说明这个工具的功能、适用场景和限制。这是给LLM看的“说明书”描述质量直接决定LLM能否正确使用它。例如“获取当前天气”就比“weather_api”好得多。参数模式parameters schema严格定义输入参数的格式和类型。目前JSON Schema是事实上的标准因为它结构清晰、表达能力强且被广泛支持。执行器executor真正执行工具调用的代码逻辑。智能体层Agent Layer这是系统的“大脑”。它接收用户指令结合上下文历史对话、当前状态决定下一步该做什么。核心决策循环通常是“思考-行动-观察”思考Reasoning分析当前目标和状态决定是否需要使用工具以及使用哪个工具。行动Acting根据思考结果要么直接生成回复给用户要么生成一个格式正确的工具调用请求。观察Observing接收工具执行后的返回结果将其作为新的上下文信息进入下一轮循环。复杂的Agent可能具备规划Planning、反思Reflection等高级能力用于处理多步骤任务或从错误中学习。运行时层Runtime / Harness这是连接大脑Agent和手脚Tool的“神经系统”和“骨架”。它不负责具体的推理逻辑那是LLM和Agent的事而是提供一套可靠的基础设施来支撑整个流程的运转。很多人容易混淆Agent和Runtime其实Runtime是更底层的基础设施。一个成熟的Runtime通常负责工具管理注册、发现、加载工具。对话/状态管理维护与用户交互的会话历史和环境状态。流程编排驱动“思考-行动-观察”循环的执行处理工具调用的输入输出。安全性、验证与监控校验LLM输出的工具调用参数是否符合Schema防止非法调用监控耗时和错误。错误处理与重试当工具调用失败或LLM输出格式错误时提供降级或重试策略。像LangChain、LangGraph这些框架以及Dify、Flowise这类低代码平台它们提供的核心价值之一就是一个功能丰富的Runtime。而“Harness”这个词在一些语境下特指包裹在Agent核心逻辑之外的、更轻量或更专注的基础设施层。2.3 技术栈全景图根据你的需求和技术偏好可以选择不同的技术组合来搭建这套系统基础协议层JSON Schema工具描述、OpenAI Function Calling/ReAct格式LLM与Runtime的交互协议。核心框架/运行时LangChain生态丰富组件多、LangGraph专注于多Agent工作流、LlamaIndex强于RAG也支持Agent、Semantic Kernel微软系与.NET集成好以及新兴的CrewAI、AutoGen等。LLM提供商OpenAI GPT系列、Anthropic Claude、Google Gemini、开源模型Llama、Qwen等通过Ollama、vLLM部署。部署与编排FastAPI构建工具服务端、Docker容器化、Kubernetes集群管理。3. 核心实现细节与实操要点3.1 如何定义一个好的“工具”工具定义是地基地基不牢地动山摇。这里有几个关键原则1. 描述Description要精准且富含信息差描述“搜索工具”。LLM根本不知道这是搜网页、搜数据库还是搜本地文件。好描述“使用谷歌搜索引擎在互联网上查询信息。当你需要获取最新的、未被包含在训练数据中的事件、事实或新闻时请使用此工具。输入应为明确的搜索查询词。”技巧在描述中隐含使用场景和限制。例如一个计算器工具可以描述为“用于执行基础算术运算加、减、乘、除。仅支持数字输入不支持变量或代数表达式。”2. 参数模式JSON Schema要严格且友好严格性利用JSON Schema的type,required,enum,pattern等属性对输入进行强约束。例如日期参数可以规定格式“pattern”: “^\\d{4}-\\d{2}-\\d{2}$”。友好性为每个参数提供description字段。这同样是给LLM看的提示。例如对于city参数描述可以写“城市名称请使用完整的、广为接受的名称如‘北京市’而非‘北京’或‘BJ’”。示例Example在Schema中或工具描述后附上一个完整的调用示例能极大提高LLM生成正确格式的几率。{ “name”: “get_current_weather”, “description”: “获取指定城市当前的天气情况包括温度、天气状况和湿度。”, “parameters”: { “type”: “object”, “properties”: { “location”: { “type”: “string”, “description”: “城市和国家的名称例如 ‘London, UK’。” }, “unit”: { “type”: “string”, “enum”: [“celsius”, “fahrenheit”], “description”: “温度单位默认为 ‘celsius’。” } }, “required”: [“location”] } }3. 工具粒度要适中避免“上帝工具”不要设计一个万能工具比如do_everything(action, params)。这会让LLM难以理解也破坏了封装性。遵循单一职责原则一个工具只做一件事并把它做好。send_email,query_database,format_document都是好例子。考虑可组合性细粒度的工具更容易被组合起来完成复杂任务。Runtime或上层Agent负责编排它们。3.2 Runtime的关键设计模式Runtime是系统的引擎其设计直接影响稳定性、性能和开发体验。1. 工具调用的执行与验证流程一个健壮的调用流程应该是这样的用户输入 - Agent/LLM 思考 - 生成工具调用请求含参数- Runtime 拦截 - 参数验证校验JSON Schema- 执行工具 - 捕获结果/异常 - 格式化结果 - 返回给Agent/LLM作为观察 - 下一轮思考关键点Runtime必须在执行前进行参数验证。绝不能盲目信任LLM的输出。这是防止无效调用、安全攻击如注入攻击的第一道防线。可以参考OWASP Top 10 for LLM中关于“提示词注入”和“不安全的插件设计”的警示。2. 状态管理Agent在处理多轮对话或多步骤任务时需要记住上下文。Runtime需要维护一个“状态”State对象。这个状态通常包括对话历史用户和AI的往来消息。中间结果之前工具调用的输出。任务目标用户最初请求的解析结果。自定义变量任何对任务有用的信息。 状态的管理可以是简单的内存字典也可以是更复杂的、支持持久化的存储如Redis、数据库。3. 流式Streaming与异步支持对于耗时较长的工具调用如训练模型、处理大文件Runtime需要支持异步操作避免阻塞主线程。同时将工具执行进度或LLM的思考过程以流式Server-Sent Events方式返回给前端能极大提升用户体验。4. 错误处理与韧性LLM和外部工具都不百分百可靠。Runtime必须优雅地处理各种错误LLM输出格式错误无法解析为工具调用。策略提示LLM重新生成或降级为普通对话。工具执行失败网络超时、API限流、内部错误。策略重试需有退避策略、切换备用工具、向用户和LLM报告清晰的错误信息。LLM陷入循环反复调用同一个工具或无意义循环。策略设置最大循环次数、超时机制或引入“人类审核”断点。3.3 Agent的推理逻辑设计这是智能的体现。最简单的Agent是“零样本”Zero-shotReAct模式依靠LLM的指令跟随能力。但对于复杂任务我们需要更高级的设计。1. 规划-执行-反思Plan-Act-Reflect循环规划在开始行动前让LLM先制定一个分步计划。例如“要回答‘公司上季度营收情况’我需要1. 从CRM获取客户列表2. 从财务系统查询这些客户的交易记录3. 汇总计算营收。” 这能提高行动的目的性。执行按照计划逐步调用工具。反思在每个步骤或任务结束后让LLM评估结果是否达到预期计划是否需要调整。这赋予了Agent从错误中学习和动态调整的能力。2. 多Agent协作对于极其复杂的任务可以引入角色化的多个Agent协同工作。例如规划Agent负责拆解任务制定高层计划。研究Agent负责搜索、收集信息。写作Agent负责整合信息生成报告。审核Agent负责检查报告的质量和准确性。 Runtime如LangGraph负责定义这些Agent之间的交互流程和数据传递。3. 长期记忆与知识管理要让Agent在多次会话中保持连贯性或拥有专属知识需要引入记忆机制。短期记忆即当前对话的上下文窗口。长期记忆通过向量数据库如Chroma, Weaviate存储和检索过往的重要交互信息。这通常与RAG检索增强生成技术结合。技能记忆将成功解决某类问题的工具使用序列保存为“技能”或“工作流”下次遇到类似问题直接调用提升效率。4. 实战构建一个简单的天气查询AI助手让我们用一个具体例子串联以上概念。我们将构建一个能理解“北京和上海哪里更热”这种比较性问题的AI助手。4.1 步骤一定义工具我们只需要一个工具get_weather。但它的描述要精心设计。# 工具定义示例 (使用类似LangChain的格式) weather_tool { “name”: “get_weather”, “description”: “获取指定城市当前的温度摄氏度和天气状况如晴、雨、多云。**当用户的问题涉及比较两个或多个地方的天气时你需要分别调用此工具获取每个地方的数据。**”, “args_schema”: WeatherArgs # 一个Pydantic模型定义了location参数 } class WeatherArgs(BaseModel): location: str Field(..., description“城市名称请使用‘北京市’、‘上海市’这样的完整中文名称。”)4.2 步骤二设置Runtime与Agent我们使用一个简单的Runtime逻辑伪代码import json from your_llm_client import call_llm from your_tool_executor import execute_tool class SimpleAgentRuntime: def __init__(self, tools, system_prompt): self.tools tools self.system_prompt system_prompt self.conversation_history [] def run(self, user_input): # 1. 将用户输入加入历史 self.conversation_history.append({“role”: “user”, “content”: user_input}) # 2. 构建给LLM的提示包含系统指令、历史、工具描述 full_prompt self._construct_prompt() llm_response call_llm(full_prompt) # 3. 解析LLM响应 if self._is_tool_call(llm_response): tool_name, tool_args self._parse_tool_call(llm_response) # 4. 验证并执行工具 if tool_name in self.tools: result execute_tool(tool_name, tool_args) # 5. 将结果格式化为观察信息加入历史 observation f“工具 {tool_name} 返回结果{result}” self.conversation_history.append({“role”: “assistant”, “content”: observation}) # 6. 进入下一轮循环让LLM基于结果继续思考 return self.run(“”) # 传入空输入让LLM继续 else: return “错误请求了不存在的工具。” else: # LLM直接生成了最终回答 final_answer llm_response[‘content’] self.conversation_history.append({“role”: “assistant”, “content”: final_answer}) return final_answer def _construct_prompt(self): # 这里需要构建一个包含工具描述的提示词格式可能遵循ReAct或Function Calling tools_desc json.dumps([{“name”: t[‘name’], “description”: t[‘description’], “args”: t[‘args_schema’].schema()} for t in self.tools]) prompt f“{self.system_prompt}\n\n你可以使用的工具{tools_desc}\n\n对话历史{self.conversation_history}\n\n请根据最新用户请求决定是直接回复还是调用工具。若调用工具请严格按指定JSON格式输出。” return prompt4.3 步骤三系统提示词工程系统提示词是Agent的“人格”和“行为准则”。对于我们的天气助手可以这样写你是一个专业的天气分析助手。你的核心能力是使用工具获取精确的天气数据来回答问题。 请遵循以下规则 1. 当用户询问单个地点天气时直接调用工具获取并总结。 2. 当用户比较多个地点天气时**务必为每个地点分别调用一次天气工具**收集所有数据后再进行对比分析。 3. 只使用提供给你的工具不要编造数据。 4. 工具调用参数必须严格符合JSON Schema要求。 5. 最终回答应友好、清晰并引用数据支撑你的结论。4.4 步骤四运行与迭代将用户问题“北京和上海哪里更热”输入系统。Runtime将问题、历史、工具描述和系统提示组合发给LLM。LLM如GPT-4理解到这是一个比较问题需要两个数据点。它生成第一个工具调用{“name”: “get_weather”, “args”: {“location”: “北京市”}}。Runtime验证参数并执行获取北京天气如25°C晴。Runtime将结果作为观察加入历史再次调用LLM。此时历史中包含了第一次调用的结果。LLM看到已有北京数据但问题还未解答上海数据缺失于是生成第二个工具调用{“name”: “get_weather”, “args”: {“location”: “上海市”}}。Runtime执行获取上海天气如28°C多云。Runtime再次将结果加入历史并调用LLM。LLM此时拥有了全部数据它不再调用工具而是生成最终答案“根据实时数据北京25°C晴上海28°C多云。因此上海目前比北京更热一些。”5. 常见问题、挑战与优化策略在实际开发和运营中你会遇到各种各样的问题。下面是我总结的一些典型挑战和应对思路。5.1 LLM不按预期调用工具现象LLM忽略工具描述直接生成文本回答如“我可以帮你查天气但需要你告诉我城市名”或者调用错误的工具。排查与解决检查工具描述描述是否足够清晰、无歧义是否明确写出了使用场景用更直接的语言重写描述。强化系统提示在系统提示中明确指令“你必须使用提供的工具来获取信息严禁凭空猜测或编造。”调整提示格式尝试不同的工具描述格式。OpenAI的Function Calling、ReAct的Thought/Action/Observation格式或者简单的JSON指令对不同模型的适配性不同。示例学习Few-shot在系统提示或历史中提供几个正确调用工具的对话示例让LLM模仿。模型能力某些较小的开源模型工具调用能力较弱。如果任务关键考虑升级到能力更强的模型如GPT-4、Claude 3。5.2 工具调用参数格式错误现象LLM决定调用工具但生成的参数JSON格式错误、缺少必填字段、或字段类型不匹配。排查与解决Runtime前置校验这是必须的。在Runtime层收到LLM的调用请求后第一时间用JSON Schema验证器进行严格校验。不通过则立即拒绝并向LLM返回清晰的错误信息如“参数‘city’是必填项且必须为字符串类型”让其重试。简化Schema如果某个参数LLM总是填错考虑是否Schema设计得太复杂能否拆分成更简单的结构提供默认值和枚举对于有常见取值的字段使用enum列出选项或提供合理的default值降低LLM出错的概率。5.3 处理复杂、多步骤任务效率低下现象Agent在完成一个需要多个工具、多次调用的任务时步骤冗长耗时久甚至中途迷失方向。排查与解决引入规划阶段在任务开始前让LLM先输出一个步骤计划。Runtime可以解析这个计划并依次推进避免LLM在每一步都重新思考全局。设计复合工具/工作流将频繁连续使用的几个工具打包成一个更高级的“复合工具”或“工作流”。例如一个“生成季度报告”工具内部封装了查询数据、分析、生成图表、写入文档等多个步骤。这样LLM只需调用一次。设置超时和最大步数防止Agent陷入死循环。例如最多允许20个推理步骤超过则强制终止并提示用户。利用LangGraph等框架对于有复杂状态转移和分支的任务使用专门的工作流框架来图形化地定义和管理流程比纯靠LLM推理更可控、高效。5.4 安全性问题风险用户通过精心构造的输入提示词注入诱导LLM调用危险工具如删除文件、发送恶意邮件或工具本身存在漏洞。防护策略工具权限隔离对工具进行分级。核心、危险的操作如写数据库、发邮件需要更高权限可以在Runtime层通过用户身份或会话上下文来控制是否可用。输入净化与校验对所有从LLM生成并传递给工具的参数进行严格的清洗和校验防止SQL注入、命令注入等。人工审核环节对于高风险操作设计“人工确认”环节。Runtime在接收到此类工具调用时暂停执行将请求提交给人工审核批准后再继续。遵循安全最佳实践密切关注像OWASP Top 10 for LLM这样的安全指南将其中关于不安全插件设计、过度依赖LLM生成内容等风险纳入考量。5.5 成本与性能优化挑战每次工具调用都意味着一次LLM API请求对于复杂任务token消耗和API成本增长很快。同时串行调用工具导致总耗时很长。优化策略缓存对工具结果进行缓存。例如天气数据在短时间内不会变化相同的查询可以直接返回缓存结果无需重复调用真实API和LLM。并行化当多个工具调用之间没有依赖关系时Runtime应支持并行执行。例如在比较多个城市天气时可以同时发起所有查询而不是一个个等。选择性价比模型在Agent的不同环节使用不同能力的模型。例如规划任务用能力强的模型如GPT-4简单的工具选择和信息提取用成本更低的模型如GPT-3.5-Turbo或开源小模型。精简上下文定期清理或总结过长的对话历史只保留对当前任务最关键的信息减少无效token消耗。从简单的函数调用到复杂的智能体系统LLM Tool Use 技术正在快速演进。它的核心价值在于将LLM的世界知识、推理能力与外部工具的确切性、实时性结合起来创造出真正能“做事”的AI应用。实现一个可用的原型不难但要构建一个稳定、可靠、安全且高效的生产级系统需要我们在工具设计、Runtime架构、Agent逻辑和安全防护等多个层面进行深思熟虑和精细打磨。希望这篇深度解析能为你点亮一盏灯在实际开发中多测试、多迭代从简单的场景开始逐步构建起属于你自己的智能体生态。
返回列表