LLM工具调用实战:从函数调用到智能体决策的架构演进与实现
2026/9/9 3:31:28 网站建设 项目流程

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系统通常包含以下几个核心层,理解了它们,就理解了整个架构:

  1. 工具层(Tool Layer):这是最底层,由一个个具体的工具定义构成。每个工具都需要被“包装”,提供机器可读的描述,通常包括:

    • 名称(name):唯一标识符。
    • 描述(description):用自然语言清晰说明这个工具的功能、适用场景和限制。这是给LLM看的“说明书”,描述质量直接决定LLM能否正确使用它。例如,“获取当前天气”就比“weather_api”好得多。
    • 参数模式(parameters schema):严格定义输入参数的格式和类型。目前JSON Schema是事实上的标准,因为它结构清晰、表达能力强,且被广泛支持。
    • 执行器(executor):真正执行工具调用的代码逻辑。
  2. 智能体层(Agent Layer):这是系统的“大脑”。它接收用户指令,结合上下文(历史对话、当前状态),决定下一步该做什么。核心决策循环通常是“思考-行动-观察”:

    • 思考(Reasoning):分析当前目标和状态,决定是否需要使用工具,以及使用哪个工具。
    • 行动(Acting):根据思考结果,要么直接生成回复给用户,要么生成一个格式正确的工具调用请求。
    • 观察(Observing):接收工具执行后的返回结果,将其作为新的上下文信息,进入下一轮循环。
    • 复杂的Agent可能具备规划(Planning)、反思(Reflection)等高级能力,用于处理多步骤任务或从错误中学习。
  3. 运行时层(Runtime / Harness):这是连接大脑(Agent)和手脚(Tool)的“神经系统”和“骨架”。它不负责具体的推理逻辑(那是LLM和Agent的事),而是提供一套可靠的基础设施来支撑整个流程的运转。很多人容易混淆Agent和Runtime,其实Runtime是更底层的基础设施。一个成熟的Runtime通常负责:

    • 工具管理:注册、发现、加载工具。
    • 对话/状态管理:维护与用户交互的会话历史和环境状态。
    • 流程编排:驱动“思考-行动-观察”循环的执行,处理工具调用的输入输出。
    • 安全性、验证与监控:校验LLM输出的工具调用参数是否符合Schema,防止非法调用;监控耗时和错误。
    • 错误处理与重试:当工具调用失败或LLM输出格式错误时,提供降级或重试策略。

    LangChainLangGraph这些框架,以及DifyFlowise这类低代码平台,它们提供的核心价值之一就是一个功能丰富的Runtime。而“Harness”这个词,在一些语境下特指包裹在Agent核心逻辑之外的、更轻量或更专注的基础设施层。

2.3 技术栈全景图

根据你的需求和技术偏好,可以选择不同的技术组合来搭建这套系统:

  • 基础协议层JSON Schema(工具描述)、OpenAI Function Calling/ReAct格式(LLM与Runtime的交互协议)。
  • 核心框架/运行时LangChain(生态丰富,组件多)、LangGraph(专注于多Agent工作流)、LlamaIndex(强于RAG,也支持Agent)、Semantic Kernel(微软系,与.NET集成好),以及新兴的CrewAIAutoGen等。
  • 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-shot)ReAct模式,依靠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 prompt

4.3 步骤三:系统提示词工程

系统提示词是Agent的“人格”和“行为准则”。对于我们的天气助手,可以这样写:

你是一个专业的天气分析助手。你的核心能力是使用工具获取精确的天气数据来回答问题。 请遵循以下规则: 1. 当用户询问单个地点天气时,直接调用工具获取并总结。 2. 当用户比较多个地点天气时,**务必为每个地点分别调用一次天气工具**,收集所有数据后再进行对比分析。 3. 只使用提供给你的工具,不要编造数据。 4. 工具调用参数必须严格符合JSON Schema要求。 5. 最终回答应友好、清晰,并引用数据支撑你的结论。

4.4 步骤四:运行与迭代

将用户问题“北京和上海哪里更热?”输入系统。

  1. Runtime将问题、历史、工具描述和系统提示组合发给LLM。
  2. LLM(如GPT-4)理解到这是一个比较问题,需要两个数据点。它生成第一个工具调用:{“name”: “get_weather”, “args”: {“location”: “北京市”}}
  3. Runtime验证参数并执行,获取北京天气(如25°C,晴)。
  4. Runtime将结果作为观察加入历史,再次调用LLM。此时历史中包含了第一次调用的结果。
  5. LLM看到已有北京数据,但问题还未解答(上海数据缺失),于是生成第二个工具调用:{“name”: “get_weather”, “args”: {“location”: “上海市”}}
  6. Runtime执行,获取上海天气(如28°C,多云)。
  7. Runtime再次将结果加入历史并调用LLM。
  8. LLM此时拥有了全部数据,它不再调用工具,而是生成最终答案:“根据实时数据,北京25°C(晴),上海28°C(多云)。因此上海目前比北京更热一些。”

5. 常见问题、挑战与优化策略

在实际开发和运营中,你会遇到各种各样的问题。下面是我总结的一些典型挑战和应对思路。

5.1 LLM不按预期调用工具

  • 现象:LLM忽略工具描述,直接生成文本回答(如“我可以帮你查天气,但需要你告诉我城市名”),或者调用错误的工具。
  • 排查与解决
    1. 检查工具描述:描述是否足够清晰、无歧义?是否明确写出了使用场景?用更直接的语言重写描述。
    2. 强化系统提示:在系统提示中明确指令:“你必须使用提供的工具来获取信息,严禁凭空猜测或编造。
    3. 调整提示格式:尝试不同的工具描述格式。OpenAI的Function Calling、ReAct的Thought/Action/Observation格式,或者简单的JSON指令,对不同模型的适配性不同。
    4. 示例学习(Few-shot):在系统提示或历史中,提供几个正确调用工具的对话示例,让LLM模仿。
    5. 模型能力:某些较小的开源模型工具调用能力较弱。如果任务关键,考虑升级到能力更强的模型(如GPT-4、Claude 3)。

5.2 工具调用参数格式错误

  • 现象:LLM决定调用工具,但生成的参数JSON格式错误、缺少必填字段、或字段类型不匹配。
  • 排查与解决
    1. Runtime前置校验:这是必须的。在Runtime层,收到LLM的调用请求后,第一时间用JSON Schema验证器进行严格校验。不通过则立即拒绝,并向LLM返回清晰的错误信息(如“参数‘city’是必填项,且必须为字符串类型”),让其重试。
    2. 简化Schema:如果某个参数LLM总是填错,考虑是否Schema设计得太复杂?能否拆分成更简单的结构?
    3. 提供默认值和枚举:对于有常见取值的字段,使用enum列出选项,或提供合理的default值,降低LLM出错的概率。

5.3 处理复杂、多步骤任务效率低下

  • 现象:Agent在完成一个需要多个工具、多次调用的任务时,步骤冗长,耗时久,甚至中途迷失方向。
  • 排查与解决
    1. 引入规划阶段:在任务开始前,让LLM先输出一个步骤计划。Runtime可以解析这个计划,并依次推进,避免LLM在每一步都重新思考全局。
    2. 设计复合工具/工作流:将频繁连续使用的几个工具打包成一个更高级的“复合工具”或“工作流”。例如,一个“生成季度报告”工具,内部封装了查询数据、分析、生成图表、写入文档等多个步骤。这样LLM只需调用一次。
    3. 设置超时和最大步数:防止Agent陷入死循环。例如,最多允许20个推理步骤,超过则强制终止并提示用户。
    4. 利用LangGraph等框架:对于有复杂状态转移和分支的任务,使用专门的工作流框架来图形化地定义和管理流程,比纯靠LLM推理更可控、高效。

5.4 安全性问题

  • 风险:用户通过精心构造的输入(提示词注入),诱导LLM调用危险工具(如删除文件、发送恶意邮件);或工具本身存在漏洞。
  • 防护策略
    1. 工具权限隔离:对工具进行分级。核心、危险的操作(如写数据库、发邮件)需要更高权限,可以在Runtime层通过用户身份或会话上下文来控制是否可用。
    2. 输入净化与校验:对所有从LLM生成并传递给工具的参数进行严格的清洗和校验,防止SQL注入、命令注入等。
    3. 人工审核环节:对于高风险操作,设计“人工确认”环节。Runtime在接收到此类工具调用时,暂停执行,将请求提交给人工审核批准后再继续。
    4. 遵循安全最佳实践:密切关注像OWASP Top 10 for LLM这样的安全指南,将其中关于不安全插件设计、过度依赖LLM生成内容等风险纳入考量。

5.5 成本与性能优化

  • 挑战:每次工具调用都意味着一次LLM API请求,对于复杂任务,token消耗和API成本增长很快。同时,串行调用工具导致总耗时很长。
  • 优化策略
    1. 缓存:对工具结果进行缓存。例如,天气数据在短时间内不会变化,相同的查询可以直接返回缓存结果,无需重复调用真实API和LLM。
    2. 并行化:当多个工具调用之间没有依赖关系时,Runtime应支持并行执行。例如,在比较多个城市天气时,可以同时发起所有查询,而不是一个个等。
    3. 选择性价比模型:在Agent的不同环节使用不同能力的模型。例如,规划任务用能力强的模型(如GPT-4),简单的工具选择和信息提取用成本更低的模型(如GPT-3.5-Turbo或开源小模型)。
    4. 精简上下文:定期清理或总结过长的对话历史,只保留对当前任务最关键的信息,减少无效token消耗。

从简单的函数调用到复杂的智能体系统,LLM Tool Use 技术正在快速演进。它的核心价值在于将LLM的世界知识、推理能力与外部工具的确切性、实时性结合起来,创造出真正能“做事”的AI应用。实现一个可用的原型不难,但要构建一个稳定、可靠、安全且高效的生产级系统,需要我们在工具设计、Runtime架构、Agent逻辑和安全防护等多个层面进行深思熟虑和精细打磨。希望这篇深度解析能为你点亮一盏灯,在实际开发中,多测试、多迭代,从简单的场景开始,逐步构建起属于你自己的智能体生态。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询