Skip to content
🔗 分享本题
查看我的学习进度 →

🧠 图解记忆:模型不直接查数据库或调 API——它先决定要不要做,再做就生成结构化调用,应用负责执行并把结果告诉模型。

一句话定义

Function Calling = 让 LLM 输出结构化的 JSON 来描述"调用哪个函数、带什么参数",从而打通大模型与现实世界的操作接口。

完整工作流(面试手撕级)

用户:"帮我查北京今天的天气"

[Step 1] 应用层声明可用函数(tool/function schema)
{
  "name": "get_weather",
  "description": "查询指定城市的实时天气",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {"type": "string", "description": "城市名称"},
      "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
    },
    "required": ["city"]
  }
}

[Step 2] 模型收到消息 + tool definitions,判断:
  - 是否需要调用工具? → ✅ 需要
  - 调用哪个函数?     → get_weather
  - 参数是什么?       → city="北京", unit="celsius"
  - 输出:
    {
      "message": null,
      "tool_calls": [
        {"id": "call_123", "type": "function",
         "function": {"name": "get_weather",
                      "arguments": "{\"city\": \"北京\", \"unit\": \"celsius\"}"}}
      ]
    }

[Step 3] 应用层接收 tool_calls,执行函数调用
result = get_weather(city="北京", unit="celsius")
# 返回: {"temperature": 22, "condition": "晴", "humidity": 45}

[Step 4] 应用将结果回传给模型
assistant_message = model.chat(messages + [{"role": "tool", "content": result_json}])

[Step 5] 模型根据工具结果生成最终自然语言回复
"北京今天晴,气温22°C,湿度45%,体感舒适哦!"

为什么重要?(三大理由,面试必答)

理由说明
1. 打破封闭边界模型只能"说"不能"做"——Function Calling 给它接入搜索、数据库、API 的手脚
2. 结构化交互相比让模型从自由文本中"提取"参数,JSON 结构化输出可靠性大幅优于 Prompt 工程
3. 动态扩展能力不用重新训练/微调模型就能增加新功能:加一个函数定义 = 多一项能力

两种调用模式对比

维度Autonomous(自动模式)Chained(链式模式)
工作方式模型一次性调用多个工具后直接出答案每调用一个工具后等待结果,再来一轮
适合场景多个独立查询并行执行需要前一步结果来决定下一步(顺序依赖)
延迟低(并行)较高(串行等待)
实现难度简单复杂(需管理对话状态机)

工程陷阱(面试高频追问)

陷阱解决方案
参数类型漂移模型可能输出字符串而非数字,需加类型转换层
幻觉参数模型编造不存在的字段名,必须配合 Schema Validation
函数命名不一致不同厂商 API 格式不同(OpenAI vs Anthropic vs OpenRouter),建议抽象一层统一适配
无限循环模型反复调用同一函数,需设置最大调用次数限制
权限控制不是所有函数都应暴露给模型——敏感操作(删除/转账)需鉴权层

代码示例(生产级 Function Calling 封装)

python
class SafeToolExecutor:
    def __init__(self, tools: list[dict]):
        self.tools = {t["name"]: t for t in tools}
        self.max_calls_per_turn = 5
    
    async def handle_tool_call(self, message: dict) -> list[dict]:
        """处理 tool_calls,安全执行并返回结果"""
        results = []
        call_count = 0
        
        for tc in message.get("tool_calls", []):
            if call_count >= self.max_calls_per_turn:
                break
            call_count += 1
            
            func_name = tc["function"]["name"]
            func_def = self.tools.get(func_name)
            
            if not func_def:
                results.append({"role": "tool", "tool_call_id": tc["id"],
                              "content": f"Error: function '{func_name}' not found"})
                continue
            
            try:
                args = json.loads(tc["function"]["arguments"])
                # 🔒 Schema Validation — 防止参数注入
                validated = validate_schema(args, func_def["parameters"])
                result = await execute_func_safely(func_name, validated)
                results.append({"role": "tool", "tool_call_id": tc["id"], "content": json.dumps(result)})
            except Exception as e:
                results.append({"role": "tool", "tool_call_id": tc["id"],
                              "content": f"Execution error: {str(e)}"})
        
        return results

面试话术:

"Function Calling 是让模型从'聊天机器人'变成'智能体'的关键桥接技术。它的工作流很清晰:声明工具 schema → 模型判断是否调用 → 生成结构化 JSON → 应用执行 → 结果回传 → 综合回答。核心优势是不需要微调模型就能扩展功能。但工程上有不少坑:参数类型漂移、无限循环、权限控制——我在项目里封装了一个 SafeToolExecutor,做了 Schema Validation、调用次数上限和错误降级。"