AI Agent工具调用机制:从Function Calling到MCP

工具调用:Agent的双手 没有工具调用能力的LLM只是一个"会说话的大脑"。真正的Agent需要调用API、执行代码、读写文件——这些都需要工具调用机制。 演进历程 阶段一:Prompt工程时代(2022-2023) 最早的工具调用靠Prompt引导: 你可以使用以下工具: 1. search(query): 搜索网页 2. calculator(expr): 数学计算 如果需要使用工具,请输出以下格式: <tool>search("天气")</tool> 这种方式的问题: 格式不稳定,模型经常不遵守 参数提取容易出错 无法处理多步工具调用 阶段二:Function Calling时代(2023-2024) OpenAI在2023年6月推出Function Calling,将工具调用作为原生API能力: response = openai.chat.completions.create( model="gpt-4", messages=[{"role": "user", "content": "北京天气"}], tools=[{ "type": "function", "function": { "name": "get_weather", "description": "获取城市天气", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"] } } }] ) # response.choices[0].message.tool_calls 模型输出结构化的工具调用请求,API层解析后执行。 阶段三:标准化时代(2024-2026) MCP协议将工具调用标准化,实现"一次开发,处处可用"。 Function Calling的技术细节 工作流程 1. 用户消息 + 工具定义 → LLM 2. LLM输出 → tool_call(name, args) 3. 应用层执行工具 4. 工具结果 → LLM 5. LLM基于结果生成回答 关键参数 tools定义: { "type": "function", "function": { "name": "string", "description": "string", "parameters": { // JSON Schema格式 } } } tool_choice控制: auto:模型自主决定是否调用 none:禁止调用 required:必须调用 {"function": {"name": "xxx"}}:指定调用特定工具 并行调用 现代模型支持在单次响应中输出多个tool_call: ...

2026-07-16 · 2 min · 218 words · 硅基 AGI 探索者

Function Calling标准化演进:从OpenAI到MCP统一协议

工具调用:从实验性功能到标准基础设施 2023年OpenAI推出Function Calling时,它被视为一个便捷的实验性功能。到2026年,工具调用已成为大模型应用的标准基础设施——每个Agent都需要调用工具,而调用方式的标准化程度直接决定了开发效率。 各厂商方案对比 OpenAI Function Calling OpenAI的方案是最早的标准化工具调用格式: { "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} }, "required": ["city"] } } } ] } 模型响应包含工具调用: { "tool_calls": [ { "id": "call_abc123", "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" } } ] } 特点:参数以JSON字符串形式返回,需要二次解析。Parallel function calling支持一次调用多个工具。 Anthropic Tool Use Anthropic的格式与OpenAI类似但在细节上有差异: { "tools": [ { "name": "get_weather", "description": "获取指定城市的天气", "input_schema": { "type": "object", "properties": { "city": {"type": "string"} }, "required": ["city"] } } ] } 差异点: 用input_schema替代parameters 参数直接作为对象返回,不需要二次解析 工具调用结果用tool_result消息类型返回 Google Gemini Function Calling { "function_declarations": [ { "name": "get_weather", "description": "获取指定城市的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string"} } } } ] } 差异点:用function_declarations替代tools,响应格式也略有不同。 ...

2026-07-16 · 3 min · 505 words · 硅基 AGI 探索者
Function Calling最佳实践

Function Calling最佳实践

Function Calling的核心价值 Function Calling让LLM能够调用外部工具和API,从"对话助手"升级为"行动助手"。但实践中的挑战在于:如何让模型可靠地选择正确的工具、生成正确的参数、处理工具执行失败的情况。 工具定义最佳实践 清晰的工具描述 tools = [ { "type": "function", "function": { "name": "search_knowledge_base", "description": ( "搜索企业知识库中的文档。当用户询问公司政策、产品信息、" "技术文档等内部知识时使用此工具。\n" "注意:不要用于搜索公开互联网信息。" ), "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,使用自然语言描述要查找的内容" }, "department": { "type": "string", "enum": ["engineering", "sales", "hr", "finance"], "description": "限定搜索的部门范围,不指定则搜索全部" }, "limit": { "type": "integer", "description": "返回结果数量,默认5", "default": 5, "minimum": 1, "maximum": 20 } }, "required": ["query"] } } } ] 工具选择的引导 # 好的描述:明确什么场景用,什么场景不用 "description": "获取当前天气信息。当用户询问天气状况、温度、降水概率时使用。不要用于历史天气查询。" # 坏的描述:模糊不清 "description": "天气工具" # 模型不知道何时使用 参数验证 from pydantic import BaseModel, Field, validator class SearchParams(BaseModel): query: str = Field(..., min_length=2, max_length=500) department: str = Field("all", pattern="^(engineering|sales|hr|finance|all)$") limit: int = Field(5, ge=1, le=20) @validator('query') def sanitize_query(cls, v): # 移除潜在注入 v = v.replace('\n', ' ').replace('\r', ' ') return v.strip() def validate_tool_args(func_name, args): """验证工具参数""" schema_map = { "search_knowledge_base": SearchParams, } if func_name in schema_map: return schema_map[func_name](**args) return args 执行与错误处理 class ToolExecutor: def __init__(self, tools_dict, timeout=30): self.tools = tools_dict self.timeout = timeout async def execute(self, tool_name, arguments): # 验证工具存在 if tool_name not in self.tools: return {"error": f"Unknown tool: {tool_name}"} # 验证参数 try: validated = validate_tool_args(tool_name, arguments) except Exception as e: return {"error": f"Invalid arguments: {e}"} # 带超时执行 try: result = await asyncio.wait_for( self.tools[tool_name](**validated.dict()), timeout=self.timeout ) return {"result": result} except asyncio.TimeoutError: return {"error": f"Tool execution timed out after {self.timeout}s"} except Exception as e: return {"error": f"Tool execution failed: {str(e)}"} async def function_calling_loop(llm, messages, tools, executor, max_rounds=5): """Function Calling循环""" for round_idx in range(max_rounds): response = await llm.achat_completion( messages=messages, tools=tools, tool_choice="auto" ) msg = response.choices[0].message messages.append(msg) # 如果模型没有调用工具,返回最终回答 if not msg.tool_calls: return msg.content # 执行所有工具调用 for tool_call in msg.tool_calls: result = await executor.execute( tool_call.function.name, json.loads(tool_call.function.arguments) ) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) return "达到最大工具调用轮次限制。" 并行工具调用 async def parallel_tool_calls(llm, messages, tools, executor): """支持并行工具调用""" response = await llm.achat_completion( messages=messages, tools=tools, tool_choice="auto" ) msg = response.choices[0].message messages.append(msg) if msg.tool_calls: # 并行执行所有工具调用 tasks = [] for tool_call in msg.tool_calls: task = executor.execute( tool_call.function.name, json.loads(tool_call.function.arguments) ) tasks.append((tool_call.id, task)) results = await asyncio.gather(*[t for _, t in tasks]) for (tool_call_id, _), result in zip(tasks, results): messages.append({ "role": "tool", "tool_call_id": tool_call_id, "content": json.dumps(result, ensure_ascii=False) }) return messages 工具调用日志 import structlog logger = structlog.get_logger() class LoggingToolExecutor: def __init__(self, executor): self.executor = executor async def execute(self, tool_name, arguments): log = logger.bind(tool=tool_name) log.info("tool_call_start", args=arguments) start = time.time() result = await self.executor.execute(tool_name, arguments) duration = time.time() - start if "error" in result: log.error("tool_call_error", error=result["error"], duration_ms=duration*1000) else: log.info("tool_call_success", duration_ms=duration*1000) return result 结语 Function Calling的可靠性来自清晰的工具描述、严格的参数验证、完善的错误处理和详尽的日志记录。将这些实践标准化,可以显著提升LLM Agent在生产环境中的稳定性。 加入讨论 这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。 ...

2026-07-02 · 2 min · 407 words · 硅基 AGI 探索者
Agent工具选择架构

Agent工具选择架构:让AI自己决定用什么工具

引言 2026年,一个生产级Agent可能需要调用数百甚至上千个工具。从发邮件到查数据库,从写代码到部署服务,工具是Agent与物理世界交互的桥梁。但工具数量增长带来了一个棘手的问题:Agent如何从数百个工具中选择正确的那个? 这不是一个简单的匹配问题。用户说"帮我看看昨天那个数据",Agent需要理解"昨天"是哪天、“那个数据"指什么、应该从哪个数据源查、用什么查询语言。工具选择架构的设计直接决定了Agent的能力边界。 一、工具选择的挑战 1.1 规模挑战 当工具数量少于20个时,可以将所有工具描述放入LLM上下文,让模型直接选择。但当工具数量达到数百个时,这种方法不再可行: 上下文窗口被工具描述占满,留给推理的空间不足 工具间描述相似,LLM容易混淆 延迟增加,成本上升 1.2 语义挑战 用户意图与工具描述之间往往存在语义鸿沟。用户说"把这个发给老板”,Agent需要理解这是要发邮件,收件人是"老板",内容是"这个"(需要从上下文解析)。 1.3 组合挑战 有些任务需要组合多个工具。例如"帮我查一下竞品最近的价格变化并生成报告"需要:搜索竞品列表→查询各竞品价格→对比分析→生成报告文档。工具选择不仅要选对单个工具,还要规划正确的执行顺序。 二、工具选择架构分层 2.1 工具索引层 工具索引层负责工具的注册、描述和索引。每个工具的描述应包含: tool_name: "send_email" description: "发送电子邮件到指定收件人" when_to_use: | - 用户要求发送邮件时 - 需要将结果通过邮件分享时 when_not_to_use: | - 用户只是想保存内容(用save_file) - 用户想在聊天中直接展示(用display_result) parameters: - name: to type: string description: "收件人邮箱地址" required: true - name: subject type: string description: "邮件主题" required: true - name: body type: string description: "邮件正文" required: true examples: - input: "发邮件给john@example.com,主题是项目更新" output: "send_email(to='john@example.com', subject='项目更新', body='...')" cost: "low" latency: "medium" 2.2 工具路由层 工具路由层是架构的核心。当Agent接收到用户请求时,路由层负责从工具池中筛选出最相关的候选工具。 ...

2026-07-02 · 2 min · 274 words · 硅基 AGI 探索者
结构化输出技术

结构化输出技术:从JSON Mode到Function Calling

为什么结构化输出如此重要 在生产环境中,LLM的输出需要被程序解析和处理。非结构化的自然语言输出虽然灵活,但带来三个严重问题: 解析不可靠:正则提取容易遗漏边界情况 集成困难:下游系统需要稳定的接口契约 验证缺失:无法保证输出满足业务约束 2026年,结构化输出已从"nice to have"变为"must have"。所有主流模型都提供了原生结构化输出能力。 技术方案全景 方案对比 方案 原理 可靠性 性能 灵活性 适用场景 JSON Mode 模型内置JSON生成 95% 高 中 简单结构 Function Calling 函数签名约束 97% 高 高 API调用 Constrained Decoding 解码时约束 99% 中 最高 严格格式 Pydantic + LLM Schema验证+重试 90% 低 高 复杂校验 XML标签 标签结构化 85% 高 低 简单提取 JSON Mode 基本使用 import json from openai import OpenAI client = OpenAI() # OpenAI JSON Mode response = client.chat.completions.create( model="gpt-4o-2026", response_format={"type": "json_object"}, messages=[ { "role": "system", "content": """你是一个信息提取助手。 请将用户输入提取为JSON格式,包含以下字段: - name: 姓名 - age: 年龄(整数) - skills: 技能列表(字符串数组) - experience: 工作经验(整数,单位年) """ }, { "role": "user", "content": "张三,28岁,精通Python和JavaScript,有5年开发经验" } ] ) result = json.loads(response.choices[0].message.content) print(result) # 输出: {"name": "张三", "age": 28, "skills": ["Python", "JavaScript"], "experience": 5} JSON Schema约束 # 2026年最新:JSON Schema强化约束 response = client.chat.completions.create( model="gpt-4o-2026", response_format={ "type": "json_schema", "json_schema": { "name": "employee_info", "strict": True, "schema": { "type": "object", "properties": { "name": {"type": "string", "minLength": 1, "maxLength": 50}, "age": {"type": "integer", "minimum": 18, "maximum": 65}, "skills": { "type": "array", "items": {"type": "string"}, "minItems": 1, "maxItems": 20 }, "experience": {"type": "integer", "minimum": 0, "maximum": 50}, "level": { "type": "string", "enum": ["junior", "mid", "senior", "expert"] } }, "required": ["name", "age", "skills", "experience", "level"], "additionalProperties": False } } }, messages=[...] ) 各家模型JSON Mode对比 模型 JSON可靠性 Schema支持 性能影响 特殊限制 GPT-4o 95% 完整 <5% 需提示JSON关键词 Claude 4 93% XML标签 <3% 推荐XML格式 Gemini 2 94% 部分支持 <5% Qwen 3 92% 部分 <5% Llama 4 88% 不支持 <8% 需Few-shot Function Calling 基本架构 from dataclasses import dataclass from typing import Callable import inspect @dataclass class ToolDefinition: name: str description: str parameters: dict # JSON Schema class FunctionCallingSystem: """ 2026年Function Calling最佳实践 """ def __init__(self, model_client): self.model = model_client self.tools: dict[str, ToolDefinition] = {} self.handlers: dict[str, Callable] = {} def register_function(self, func: Callable, description: str): """注册可调用函数""" # 自动从函数签名生成Schema sig = inspect.signature(func) params = {} required = [] for name, param in sig.parameters.items(): param_type = param.annotation json_type = self._python_type_to_json(param_type) params[name] = { "type": json_type, "description": self._extract_param_doc(func, name) } if param.default == inspect.Parameter.empty: required.append(name) tool = ToolDefinition( name=func.__name__, description=description or func.__doc__, parameters={ "type": "object", "properties": params, "required": required } ) self.tools[func.__name__] = tool self.handlers[func.__name__] = func async def execute_with_functions(self, user_message: str) -> str: """带函数调用的对话""" messages = [{"role": "user", "content": user_message}] tools = [t.__dict__ for t in self.tools.values()] while True: response = await self.model.chat( messages=messages, tools=tools, tool_choice="auto" ) message = response.choices[0].message messages.append(message) if not message.tool_calls: # 模型没有调用工具,返回最终回复 return message.content # 执行函数调用 for tool_call in message.tool_calls: func_name = tool_call.function.name func_args = json.loads(tool_call.function.arguments) # 参数验证 validation = self._validate_arguments(func_name, func_args) if not validation["valid"]: result = f"参数错误: {validation['errors']}" else: # 执行函数 try: handler = self.handlers[func_name] result = await handler(**func_args) except Exception as e: result = f"执行错误: {str(e)}" # 将结果返回给模型 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) }) 实际应用示例 # 定义工具函数 @dataclass class SearchResult: title: str url: str snippet: str async def search_web(query: str, max_results: int = 5) -> list[dict]: """搜索网络内容 Args: query: 搜索关键词 max_results: 最大返回结果数(默认5) """ # 实际搜索逻辑 results = await search_engine.search(query, limit=max_results) return [{"title": r.title, "url": r.url, "snippet": r.snippet} for r in results] async def get_weather(city: str, unit: str = "celsius") -> dict: """获取指定城市的天气信息 Args: city: 城市名称 unit: 温度单位(celsius或fahrenheit) """ weather = await weather_api.get(city, unit) return { "city": city, "temperature": weather.temp, "condition": weather.condition, "humidity": weather.humidity } async def send_email(to: str, subject: str, body: str) -> dict: """发送邮件 Args: to: 收件人邮箱 subject: 邮件主题 body: 邮件正文 """ await email_service.send(to, subject, body) return {"status": "sent", "to": to} # 注册并使用 system = FunctionCallingSystem(model_client) system.register_function(search_web, "搜索网络获取最新信息") system.register_function(get_weather, "查询天气信息") system.register_function(send_email, "发送邮件") # 执行 response = await system.execute_with_functions( "帮我查一下北京今天的天气,然后把结果发邮件给 zhangsan@example.com" ) Constrained Decoding 原理 Constrained Decoding(约束解码)在生成过程中实时约束token选择,确保输出符合预定义的语法规则。 ...

2026-06-30 · 6 min · 1190 words · 硅基 AGI 探索者
function calling production

Function Calling 生产实践:从 Demo 到可靠工具调用

Demo 与生产的差距 Function Calling 的 demo 很简单:定义一个函数,LLM 返回参数,调用函数,返回结果。但在生产中,你需要处理: LLM 返回了不存在的函数 参数类型不对、必填字段缺失 函数调用超时 并行调用之间的依赖冲突 用户通过参数注入恶意命令 调用链过深导致上下文爆炸 Schema 设计:函数定义是契约 好的 Schema 长什么样 # ❌ 糟糕的 Schema: 模糊、无约束 BAD_SCHEMA = { "name": "search", "description": "搜索东西", "parameters": { "type": "object", "properties": { "query": {"type": "string"} } } } # ✅ 好的 Schema: 精确、有约束、有枚举 GOOD_SCHEMA = { "name": "search_product", "description": "在商品库中搜索商品。当用户询问有没有某种商品、价格、库存时使用此函数。", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,从用户意图中提取,去除无意义词" }, "category": { "type": "string", "enum": ["electronics", "clothing", "food", "books", "other"], "description": "商品类别" }, "max_price": { "type": "number", "minimum": 0, "description": "用户能接受的最高价格(元)" }, "sort_by": { "type": "string", "enum": ["price_asc", "price_desc", "relevance", "sales"], "default": "relevance" } }, "required": ["query"], "additionalProperties": False } } Schema 设计原则 原则 说明 示例 名字即文档 函数名要自解释 search_product > search description 要写触发条件 不是描述函数做什么,而是何时用 “当用户询问商品价格时使用” 枚举优于自由文本 能枚举就不要用 string category 用 enum 设默认值 减少LLM猜测 sort_by 默认 relevance 禁止额外属性 additionalProperties: false 防止 LLM 编造参数 范围约束 用 min/max 限制数值 max_price minimum: 0 参数验证:不要信任 LLM 的输出 import json from pydantic import BaseModel, Field, ValidationError from typing import Optional, Literal # 用 Pydantic 做二次验证 class SearchProductParams(BaseModel): query: str = Field(..., min_length=1, max_length=100) category: Optional[Literal["electronics", "clothing", "food", "books", "other"]] = None max_price: Optional[float] = Field(None, ge=0, le=1000000) sort_by: Literal["price_asc", "price_desc", "relevance", "sales"] = "relevance" class FunctionCallValidator: def __init__(self): self.schemas = { "search_product": SearchProductParams, } def validate(self, function_name: str, arguments: dict) -> tuple[bool, any]: schema_cls = self.schemas.get(function_name) if not schema_cls: return False, {"error": f"Unknown function: {function_name}"} try: validated = schema_cls(**arguments) return True, validated except ValidationError as e: return False, {"error": e.errors()} 处理 LLM 返回的常见问题 class RobustFunctionExecutor: async def execute(self, llm_response: dict) -> dict: tool_calls = llm_response.get("tool_calls", []) results = [] for call in tool_calls: name = call["function"]["name"] # 1. 函数是否存在? if name not in self.registry: results.append(self._error_result(call, f"未知函数: {name}")) continue # 2. 参数解析 try: args = json.loads(call["function"]["arguments"]) except json.JSONDecodeError: # LLM 返回了非法 JSON,尝试修复 args = self._repair_json(call["function"]["arguments"]) if args is None: results.append(self._error_result(call, "参数 JSON 解析失败")) continue # 3. 参数验证 ok, validated = self.validator.validate(name, args) if not ok: results.append(self._error_result(call, str(validated))) continue # 4. 执行(带超时) try: result = await asyncio.wait_for( self.registry[name](**validated.dict()), timeout=10 ) results.append({"tool_call_id": call["id"], "result": result}) except asyncio.TimeoutError: results.append(self._error_result(call, "函数执行超时")) except Exception as e: results.append(self._error_result(call, f"执行错误: {str(e)}")) return results def _repair_json(self, broken: str) -> dict | None: """尝试修复 LLM 输出的破损 JSON""" # 去除尾部逗号 fixed = broken.rstrip().rstrip(",") # 补全括号 open_braces = fixed.count("{") - fixed.count("}") open_brackets = fixed.count("[") - fixed.count("]") fixed += "}" * open_braces + "]" * open_brackets try: return json.loads(fixed) except: return None 错误恢复:让 LLM 从错误中学习 async def function_call_loop(messages: list, max_rounds: int = 5): """多轮函数调用循环,带错误恢复""" for round_num in range(max_rounds): response = await client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tool_definitions, tool_choice="auto" ) msg = response.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content # LLM 认为完成了 # 执行所有工具调用 for tool_call in msg.tool_calls: success, result = await executor.execute_single(tool_call) # 把结果(包括错误)反馈给 LLM messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) if not success: # 在错误信息中给出修复建议 messages.append({ "role": "system", "content": f"函数 {tool_call.function.name} 调用失败: {result['error']}。请修正参数后重试,或换一种方式回答。" }) return "达到最大调用轮数,请简化请求。" 并行调用与依赖管理 import asyncio from typing import Dict, List, Set class ParallelCallOrchestrator: def __init__(self): # 声明函数间的依赖关系 self.dependencies = { "get_order_detail": [], # 无依赖 "check_inventory": [], # 无依赖 "calculate_shipping": ["check_inventory"], # 依赖库存检查 "create_order": ["get_order_detail", "check_inventory"], } async def execute_parallel(self, tool_calls: list) -> list: # 构建依赖图 independent = [] dependent = {} for call in tool_calls: name = call["function"]["name"] deps = self.dependencies.get(name, []) if not deps: independent.append(call) else: dependent[call["id"]] = {"call": call, "deps": set(deps)} # 先执行无依赖的 results = {} tasks = [self._execute_and_store(call, results) for call in independent] await asyncio.gather(*tasks, return_exceptions=True) # 再执行有依赖的(拓扑排序) while dependent: ready = [ cid for cid, info in dependent.items() if info["deps"].issubset(set(results.keys())) ] if not ready: # 有循环依赖,强制执行剩余的 ready = list(dependent.keys()) tasks = [] for cid in ready: tasks.append(self._execute_and_store(dependent[cid]["call"], results)) del dependent[cid] await asyncio.gather(*tasks, return_exceptions=True) return list(results.values()) 安全沙箱:永远不要直接执行 LLM 生成的代码 import subprocess import tempfile import os class CodeExecutionSandbox: """安全执行 LLM 生成的代码""" def __init__(self): self.allowed_modules = {"math", "statistics", "json", "re"} self.timeout = 5 self.memory_limit = "256m" async def execute_python(self, code: str) -> dict: # 1. 静态检查 violations = self._static_check(code) if violations: return {"error": "安全检查失败", "violations": violations} # 2. 在 Docker 容器中执行 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write(code) f.flush() try: result = subprocess.run( ["docker", "run", "--rm", "--memory", self.memory_limit, "--cpus", "0.5", "--network", "none", # 无网络 "--read-only", # 只读文件系统 "--tmpfs", "/tmp:size=10m", # 临时目录 "-v", f"{f.name}:/app/code.py:ro", "python:3.12-slim", "python", "/app/code.py"], capture_output=True, timeout=self.timeout, text=True ) return { "stdout": result.stdout[:5000], # 截断 "stderr": result.stderr[:2000], "exit_code": result.returncode } except subprocess.TimeoutExpired: return {"error": "执行超时"} finally: os.unlink(f.name) def _static_check(self, code: str) -> list: violations = [] # 检查 import for line in code.split("\n"): if "import" in line: module = line.split("import")[-1].strip().split(".")[0] if module not in self.allowed_modules: violations.append(f"禁止导入模块: {module}") # 检查危险函数 dangerous = ["open(", "exec(", "eval(", "os.system", "subprocess", "__import__"] for d in dangerous: if d in code: violations.append(f"禁止使用: {d}") return violations 审计日志:记录每次调用 import logging from datetime import datetime logger = logging.getLogger("function_caller") class AuditLogger: def log_call(self, function_name: str, arguments: dict, result: dict, duration_ms: float, success: bool): logger.info(json.dumps({ "timestamp": datetime.utcnow().isoformat(), "function": function_name, "arguments": self._sanitize(arguments), # 脱敏 "result_size": len(str(result)), "duration_ms": duration_ms, "success": success, "trace_id": self._get_trace_id(), }, ensure_ascii=False)) def _sanitize(self, args: dict) -> dict: """脱敏处理""" sensitive_keys = {"password", "token", "api_key", "credit_card"} sanitized = {} for k, v in args.items(): if k.lower() in sensitive_keys: sanitized[k] = "***REDACTED***" else: sanitized[k] = v return sanitized 性能优化 函数定义缓存 # 工具定义不要每次请求都重新构建 from functools import lru_cache @lru_cache(maxsize=1) def get_tool_definitions(): return [ {"type": "function", "function": schema} for schema in load_all_schemas() ] 函数调用结果缓存 import hashlib from datetime import timedelta class ResultCache: def __init__(self, redis_client): self.redis = redis_client self.ttl = timedelta(minutes=10) async def get_or_execute(self, func_name: str, args: dict, executor): # 对幂等函数做缓存 cache_key = self._make_key(func_name, args) cached = await self.redis.get(cache_key) if cached: return json.loads(cached) result = await executor(func_name, args) await self.redis.setex( cache_key, int(self.ttl.total_seconds()), json.dumps(result, ensure_ascii=False) ) return result def _make_key(self, name: str, args: dict) -> str: arg_hash = hashlib.md5(json.dumps(args, sort_keys=True).encode()).hexdigest() return f"fc:{name}:{arg_hash}" 总结 Function Calling 在生产中可靠运行的关键:严格的 Schema 设计是契约,二次验证是防线,错误恢复是韧性,并行管理是效率,安全沙箱是底线,审计日志是追溯。把每个 LLM 返回的函数调用都当作不可信输入来处理,就能避免大部分生产事故。 加入讨论 这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。 ...

2026-06-25 · 5 min · 948 words · 硅基 AGI 探索者
agent tool use guide

Agent 工具使用指南:从 Function Calling 到自主工具发现

工具使用:Agent 的双手 没有工具的 LLM 是一个"只会说话的大脑"——它知道很多,但什么都做不了。工具使用让 Agent 从"问答机器"变成"行动者"。 演进路径 2023: Hard-coded Functions(硬编码函数) 2024: Function Calling(OpenAI 标准化) 2025: Tool Use API(Anthropic 扩展) 2026: MCP + 自主工具发现(工具市场) 第一阶段:Function Calling 基本用法 from openai import OpenAI client = OpenAI() tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如北京、上海" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位" } }, "required": ["city"] } } } ] response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "北京今天多少度?"}], tools=tools, ) # LLM 返回工具调用 tool_call = response.choices[0].message.tool_calls[0] # {"name": "get_weather", "arguments": {"city": "北京", "unit": "celsius"}} # 执行工具 result = get_weather(**json.loads(tool_call.function.arguments)) # 把结果返回给 LLM response = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "user", "content": "北京今天多少度?"}, response.choices[0].message, {"role": "tool", "tool_call_id": tool_call.id, "content": result} ], tools=tools, ) 多工具并行调用 # 现代 LLM 支持一轮调用多个工具 response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "北京和上海今天天气怎么样?"}], tools=tools, ) # 返回两个 tool_calls: get_weather(北京) + get_weather(上海) # 并行执行 results = await asyncio.gather(*[ execute_tool(tc) for tc in response.choices[0].message.tool_calls ]) 第二阶段:工具设计原则 原则一:描述要精确 # ❌ 差:描述模糊 { "name": "search", "description": "搜索内容" } # ✅ 好:描述精确 { "name": "search_web", "description": "在互联网上搜索给定查询,返回前10个结果的标题、URL和摘要。适用于查找最新信息、新闻、技术文档。", "parameters": { "properties": { "query": { "type": "string", "description": "搜索关键词,30字以内,去除无意义词" }, "time_range": { "type": "string", "enum": ["day", "week", "month", "year", "all"], "description": "时间范围过滤" } } } } 原则二:参数要简洁 # ❌ 差:参数太多 { "name": "send_email", "parameters": { "properties": { "to": {}, "cc": {}, "bcc": {}, "subject": {}, "body": {}, "attachments": {}, "priority": {}, "format": {}, "encoding": {}, "reply_to": {}, "sender_name": {} } } } # ✅ 好:只保留必要参数 { "name": "send_email", "parameters": { "properties": { "to": {"type": "string", "description": "收件人邮箱"}, "subject": {"type": "string", "description": "邮件主题"}, "body": {"type": "string", "description": "邮件正文"} }, "required": ["to", "subject", "body"] } } 原则三:错误信息要友好 async def execute_tool(tool_call): try: result = await tool_registry[tool_call.name](**tool_call.args) return {"status": "success", "data": result} except ValidationError as e: return { "status": "error", "error": f"参数错误:{e}", "hint": "请检查参数格式和必填项" } except Exception as e: return { "status": "error", "error": str(e), "hint": "如果重试仍失败,请尝试其他方法" } 第三阶段:MCP(Model Context Protocol) MCP 是 Anthropic 提出的工具标准协议,让工具可以跨模型使用: ...

2026-06-24 · 4 min · 675 words · 硅基 AGI 探索者
鲁ICP备2026018361号