news 2026/4/25 3:05:23

Tool Use vs Function Calling:LLM工具调用架构深度对比与工程选型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Tool Use vs Function Calling:LLM工具调用架构深度对比与工程选型

"Tool Use"和"Function Calling"在大模型圈经常被混用,但它们代表了不同的设计哲学。本文深入对比两种架构,帮你在构建 AI Agent 时做出正确的工程选择。

一、概念厘清:两者的本质区别### 1.1 Function Calling(函数调用)Function Calling 是 OpenAI 在 GPT-4 API 中引入的机制,核心特征:-结构化输出:LLM 输出特定的 JSON 格式,指定要调用的函数名和参数-单轮完成:一次 API 调用中,模型决定调用什么函数、传什么参数-外部执行:函数的实际执行由调用方(你的代码)完成,结果再传回模型python# Function Calling 示例import openaitools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的当前天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} }, "required": ["city"] } } }]response = openai.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "北京今天天气怎么样?"}], tools=tools, tool_choice="auto")# 模型返回工具调用请求tool_call = response.choices[0].message.tool_calls[0]print(tool_call.function.name) # "get_weather"print(tool_call.function.arguments) # '{"city": "北京", "unit": "celsius"}'### 1.2 Tool Use(工具使用)Tool Use 是 Anthropic 在 Claude API 中的对应实现,设计理念有所不同:-更强的语义解耦:工具描述更自然语言化,减少对 JSON Schema 的依赖-并行工具调用:原生支持在单次响应中请求多个工具调用-强调安全性:在设计层面更注重对工具使用的安全控制pythonimport anthropicclient = anthropic.Anthropic()tools = [ { "name": "get_weather", "description": "获取指定城市的实时天气信息,包括温度、湿度和天气状况", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称(中文或英文)" } }, "required": ["city"] } }]response = client.messages.create( model="claude-opus-4-5", max_tokens=1024, tools=tools, messages=[{"role": "user", "content": "北京和上海今天哪个城市更热?"}])# Claude 可能同时请求两个城市的天气(并行工具调用)for content in response.content: if content.type == "tool_use": print(f"工具: {content.name}, 参数: {content.input}")—## 二、关键差异深度对比### 2.1 并行工具调用能力python# OpenAI 并行工具调用(GPT-4o 支持)response = openai.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "比较北京和上海的天气"}], tools=tools, parallel_tool_calls=True # 显式开启并行)# 可能返回多个工具调用for tool_call in response.choices[0].message.tool_calls: print(tool_call.function.name, tool_call.function.arguments)# 输出:# get_weather {"city": "北京"}# get_weather {"city": "上海"}# Claude 默认支持并行(无需特殊配置)# 模型自动判断哪些工具可以并行调用### 2.2 工具描述质量的影响python# 差的工具描述(函数式思维)bad_tool = { "name": "search", "description": "search(query: str) -> list", "parameters": { "type": "object", "properties": { "query": {"type": "string"} } }}# 好的工具描述(语义化思维)good_tool = { "name": "web_search", "description": """搜索互联网获取最新信息。 适用场景: - 需要获取实时信息(新闻、股价、天气) - 需要查找具体事实但不确定准确性 - 用户询问近期事件 不适用场景: - 模型已知的通用知识 - 需要深度分析而非信息查找的任务 最佳实践:查询应简洁明确,避免超长查询语句。""", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索查询词,应简洁明确。例如:'2026年诺贝尔物理学奖得主'" }, "max_results": { "type": "integer", "description": "返回结果数量,默认5,最多20", "default": 5 } }, "required": ["query"] }}### 2.3 错误处理机制python# 工具调用错误处理的标准模式async def execute_with_error_handling(tool_call, tool_registry): tool_name = tool_call.function.name try: args = json.loads(tool_call.function.arguments) except json.JSONDecodeError as e: # 参数解析失败 return { "tool_call_id": tool_call.id, "role": "tool", "content": f"参数解析失败:{e}。请检查参数格式。" } if tool_name not in tool_registry: return { "tool_call_id": tool_call.id, "role": "tool", "content": f"工具 '{tool_name}' 不存在。可用工具:{list(tool_registry.keys())}" } try: result = await tool_registry[tool_name](**args) return { "tool_call_id": tool_call.id, "role": "tool", "content": json.dumps(result, ensure_ascii=False) } except TypeError as e: # 参数类型错误 return { "tool_call_id": tool_call.id, "role": "tool", "content": f"参数错误:{e}。请提供正确的参数类型。" } except Exception as e: # 工具执行失败 return { "tool_call_id": tool_call.id, "role": "tool", "content": f"工具执行失败:{str(e)}" }—## 三、统一抽象层设计### 3.1 跨模型的工具调用适配器实际工程中,我们往往需要同时支持多个模型。设计一个统一的抽象层:pythonfrom abc import ABC, abstractmethodfrom dataclasses import dataclassfrom typing import List, Dict, Any, Optional, Callableimport json@dataclassclass ToolDefinition: """跨平台的工具定义格式""" name: str description: str parameters: Dict[str, Any] # JSON Schema 格式 handler: Callable # 实际执行函数@dataclassclass ToolCallRequest: """工具调用请求""" call_id: str tool_name: str arguments: Dict[str, Any]class UnifiedToolCallAdapter(ABC): """跨模型工具调用适配器基类""" @abstractmethod def format_tools(self, tools: List[ToolDefinition]) -> List[Dict]: """将统一工具定义转换为平台特定格式""" pass @abstractmethod def parse_tool_calls(self, response) -> List[ToolCallRequest]: """从模型响应中解析工具调用请求""" pass @abstractmethod def format_tool_results(self, results: List[Dict]) -> List[Dict]: """将工具结果格式化为平台特定的消息格式""" passclass OpenAIToolAdapter(UnifiedToolCallAdapter): """OpenAI Function Calling 适配器""" def format_tools(self, tools: List[ToolDefinition]) -> List[Dict]: return [ { "type": "function", "function": { "name": t.name, "description": t.description, "parameters": t.parameters } } for t in tools ] def parse_tool_calls(self, response) -> List[ToolCallRequest]: message = response.choices[0].message if not message.tool_calls: return [] return [ ToolCallRequest( call_id=tc.id, tool_name=tc.function.name, arguments=json.loads(tc.function.arguments) ) for tc in message.tool_calls ] def format_tool_results(self, results: List[Dict]) -> List[Dict]: return [ { "role": "tool", "tool_call_id": r["call_id"], "content": json.dumps(r["result"], ensure_ascii=False) } for r in results ]class AnthropicToolAdapter(UnifiedToolCallAdapter): """Anthropic Tool Use 适配器""" def format_tools(self, tools: List[ToolDefinition]) -> List[Dict]: return [ { "name": t.name, "description": t.description, "input_schema": t.parameters } for t in tools ] def parse_tool_calls(self, response) -> List[ToolCallRequest]: tool_calls = [] for content in response.content: if content.type == "tool_use": tool_calls.append(ToolCallRequest( call_id=content.id, tool_name=content.name, arguments=content.input )) return tool_calls def format_tool_results(self, results: List[Dict]) -> List[Dict]: return [ { "type": "tool_result", "tool_use_id": r["call_id"], "content": json.dumps(r["result"], ensure_ascii=False) } for r in results ]### 3.2 完整的 Agent 执行循环pythonclass ToolCallingAgent: """ 通用工具调用 Agent,支持 OpenAI 和 Anthropic """ def __init__( self, model_client, adapter: UnifiedToolCallAdapter, tools: List[ToolDefinition], max_iterations: int = 10 ): self.client = model_client self.adapter = adapter self.tools = {t.name: t for t in tools} self.formatted_tools = adapter.format_tools(tools) self.max_iterations = max_iterations async def run(self, user_message: str) -> str: messages = [{"role": "user", "content": user_message}] for iteration in range(self.max_iterations): # 调用模型 response = await self._call_model(messages) # 解析工具调用请求 tool_calls = self.adapter.parse_tool_calls(response) if not tool_calls: # 没有工具调用,提取最终答案 return self._extract_final_answer(response) # 并行执行所有工具调用 tool_results = await asyncio.gather(*[ self._execute_tool(tc) for tc in tool_calls ]) # 将工具结果添加到消息历史 assistant_message = self._extract_assistant_message(response) messages.append(assistant_message) formatted_results = self.adapter.format_tool_results(tool_results) messages.extend(formatted_results) return "已达到最大迭代次数,任务可能未完成。" async def _execute_tool(self, tool_call: ToolCallRequest) -> Dict: tool = self.tools.get(tool_call.tool_name) if not tool: return {"call_id": tool_call.call_id, "result": {"error": f"工具不存在: {tool_call.tool_name}"}} try: if asyncio.iscoroutinefunction(tool.handler): result = await tool.handler(**tool_call.arguments) else: result = tool.handler(**tool_call.arguments) return {"call_id": tool_call.call_id, "result": result} except Exception as e: return {"call_id": tool_call.call_id, "result": {"error": str(e)}}—## 四、工程选型指南### 4.1 选型决策矩阵| 场景 | 推荐方案 | 理由 ||------|----------|------|| 纯 OpenAI 生态 | Function Calling | 原生支持,文档完善 || 纯 Anthropic 生态 | Tool Use | 原生支持,并行调用自然 || 多模型混用 | 统一抽象层 | 灵活切换,避免锁定 || 需要严格 Schema 验证 | Function Calling | JSON Schema 支持更完善 || 复杂多步骤 Agent | 任意(配合框架) | 取决于框架支持 || 开源模型部署 | 取决于模型 | Qwen/LLaMA 支持 FC 格式 |### 4.2 常见踩坑与解决方案python# 坑 1:工具参数中使用 Python 保留字段名# 错误bad_params = { "type": "object", "properties": { "type": {"type": "string"}, # "type" 与 JSON Schema 保留字冲突 }}# 解决:避免使用 "type", "$schema" 等 JSON Schema 保留字# 坑 2:工具结果过长导致上下文超限def truncate_tool_result(result: str, max_tokens: int = 2000) -> str: """截断过长的工具结果""" # 粗略估算:1 token ≈ 4 字符(英文)/ 2 字符(中文) max_chars = max_tokens * 3 if len(result) > max_chars: return result[:max_chars] + f"\n...[结果已截断,原始长度 {len(result)} 字符]" return result# 坑 3:工具调用死循环class LoopDetector: def __init__(self, max_same_calls: int = 3): self.call_history = [] self.max_same = max_same_calls def check_loop(self, tool_name: str, arguments: Dict) -> bool: call_key = f"{tool_name}:{json.dumps(arguments, sort_keys=True)}" self.call_history.append(call_key) # 如果最近 N 次调用都是同一个工具+参数,视为死循环 recent = self.call_history[-self.max_same:] if len(recent) == self.max_same and len(set(recent)) == 1: return True # 检测到死循环 return False—## 五、最佳实践总结1.工具描述是最重要的工程:详细、准确的工具描述比调用架构本身更重要,花 80% 的时间在工具描述上2.默认启用并行调用:现代 LLM 都支持并行工具调用,利用这一特性可大幅降低 Agent 延迟3.构建统一抽象层:避免被单一模型厂商锁定,提前设计跨模型的工具调用抽象4.工具数量控制在 20 个以内:工具太多会降低模型的选择准确率,复杂场景用工具路由而非堆砌工具5.完善的错误处理:工具执行失败要给模型清晰的错误信息,让模型能够自我纠正Function Calling 和 Tool Use 在本质上解决同一个问题,差异主要在 API 风格上。选择哪种方案,最终取决于你使用的模型和生态系统。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 2:56:07

Real-Anime-Z实战教程:用real-anime-z_21生成赛博朋克风格角色

Real-Anime-Z实战教程:用real-anime-z_21生成赛博朋克风格角色 1. 项目介绍 Real-Anime-Z是一款基于Stable Diffusion技术的写实向动漫风格大模型,由Devilworld团队开发。它巧妙融合了写实与动漫两种风格,创造出独特的2.5D视觉效果——在保…

作者头像 李华
网站建设 2026/4/25 2:55:24

机器学习中的连续概率分布应用与实践

1. 连续概率分布在机器学习中的重要性作为一名从业多年的数据科学家,我深刻理解连续概率分布在实际机器学习项目中的核心地位。这些分布不仅构成了统计学习的基础框架,更是我们理解和处理现实世界数据的关键工具。连续概率分布描述的是连续随机变量的概率…

作者头像 李华