Function Calling 与工具使用开发指南

Maurice | 灵阙学院 2026-02-27

什么是 Function Calling

Function Calling 让大模型不再只是输出文本,而是能够结构化地调用外部工具。模型根据用户请求决定调用哪个函数、传入什么参数,开发者执行函数后将结果返回模型,模型再基于结果生成最终回答。

┌────────────────────────────────────────────────────────────┐
│                Function Calling 流程                       │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  User: "北京今天天气怎么样?"                               │
│       │                                                    │
│       v                                                    │
│  LLM: 判断需要调用 get_weather(city="北京")                 │
│       │                                                    │
│       v                                                    │
│  App:  执行 get_weather("北京") --> {"temp": 5, ...}       │
│       │                                                    │
│       v                                                    │
│  LLM: "北京今天气温 5 度C,多云,建议穿厚外套。"              │
│                                                            │
└────────────────────────────────────────────────────────────┘

三大平台 API 对比

特性 OpenAI Anthropic (Claude) Google (Gemini)
参数名 tools tools tools
格式 JSON Schema JSON Schema JSON Schema (兼容)
并行调用 支持 支持 支持
强制调用 tool_choice: {function:{name:...}} tool_choice: {type:tool,name:...} tool_config
最大工具数 128 64 128
Strict Mode strict: true 默认较严格 --

工具 Schema 设计原则

  1. 函数名:动词+名词,清晰表达意图(get_weather 而非 weather
  2. 描述:告诉模型"什么时候应该调用这个工具",这比参数定义更重要
  3. 参数:用 enum 约束可选值,required 标记必填项,default 减少调用歧义
  4. 粒度:一个工具做一件事;宁可多几个单一工具,也不要一个万能工具

Schema 示例(核心工具)

tools = [
    {
        "type": "function",
        "function": {
            "name": "query_tax_policy",
            "description": "查询中国税收政策法规。当用户询问税率、免税条件、申报规则时调用。",
            "strict": True,
            "parameters": {
                "type": "object",
                "properties": {
                    "tax_type": {
                        "type": "string",
                        "enum": ["增值税", "企业所得税", "个人所得税", "印花税", "消费税"],
                    },
                    "query": {"type": "string", "description": "具体查询内容"},
                    "year": {"type": "integer", "default": 2026},
                },
                "required": ["tax_type", "query"],
                "additionalProperties": False,
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "calculate_tax",
            "description": "计算应缴税额。当用户提供金额并要求计算税费时调用。",
            "strict": True,
            "parameters": {
                "type": "object",
                "properties": {
                    "tax_type": {"type": "string", "enum": ["增值税", "企业所得税", "个人所得税"]},
                    "amount": {"type": "number", "description": "金额(元)"},
                    "rate": {"type": "number", "description": "税率(小数形式,如0.13)"},
                    "deductions": {"type": "number", "default": 0},
                },
                "required": ["tax_type", "amount", "rate"],
                "additionalProperties": False,
            },
        },
    },
]

完整调用流程(OpenAI)

from openai import OpenAI
import json

client = OpenAI()

TOOL_MAP = {
    "query_tax_policy": lambda **kw: {"policy": f"{kw.get('year',2026)}年{kw['tax_type']}相关政策..."},
    "calculate_tax": lambda **kw: {"tax_due": round((kw["amount"] - kw.get("deductions", 0)) * kw["rate"], 2)},
}

def run_agent(user_message: str) -> str:
    messages = [
        {"role": "system", "content": "你是税务合规助手。使用工具回答用户问题。"},
        {"role": "user", "content": user_message},
    ]

    response = client.chat.completions.create(
        model="gpt-4o", messages=messages, tools=tools, tool_choice="auto",
    )
    assistant_msg = response.choices[0].message
    messages.append(assistant_msg)

    if not assistant_msg.tool_calls:
        return assistant_msg.content

    # 执行所有工具调用(支持并行)
    for tool_call in assistant_msg.tool_calls:
        fn_name = tool_call.function.name
        fn_args = json.loads(tool_call.function.arguments)
        result = TOOL_MAP[fn_name](**fn_args)
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result, ensure_ascii=False),
        })

    # 第二轮:模型基于工具结果生成最终回答
    final = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
    return final.choices[0].message.content

answer = run_agent("帮我算一下100万元收入按13%增值税要交多少税")
print(answer)

Claude 工具调用示例

import anthropic, json
client = anthropic.Anthropic()

claude_tools = [{
    "name": "calculate_tax",
    "description": "计算应缴税额",
    "input_schema": {
        "type": "object",
        "properties": {
            "tax_type": {"type": "string", "enum": ["增值税", "企业所得税", "个人所得税"]},
            "amount": {"type": "number"}, "rate": {"type": "number"},
        },
        "required": ["tax_type", "amount", "rate"],
    },
}]

response = client.messages.create(
    model="claude-sonnet-4-20250514", max_tokens=1024,
    tools=claude_tools,
    messages=[{"role": "user", "content": "100万按13%增值税算多少"}],
)

for block in response.content:
    if block.type == "tool_use":
        result = {"tax_due": block.input["amount"] * block.input["rate"]}
        follow_up = client.messages.create(
            model="claude-sonnet-4-20250514", max_tokens=1024, tools=claude_tools,
            messages=[
                {"role": "user", "content": "100万按13%增值税算多少"},
                {"role": "assistant", "content": response.content},
                {"role": "user", "content": [
                    {"type": "tool_result", "tool_use_id": block.id,
                     "content": json.dumps(result, ensure_ascii=False)}
                ]},
            ],
        )
        print(follow_up.content[0].text)

多工具编排模式

┌──────────────────────────────────────────────────┐
│           Multi-Tool Orchestration               │
├──────────────────────────────────────────────────┤
│                                                  │
│  Sequential:  Tool A --> Tool B --> Tool C       │
│               (上一步输出是下一步输入)              │
│                                                  │
│  Parallel:    Tool A ─┐                          │
│               Tool B ─┼──> Merge --> Answer      │
│               Tool C ─┘                          │
│                                                  │
│  Conditional: IF condition THEN Tool A           │
│               ELSE Tool B                        │
│                                                  │
│  Loop:        WHILE not satisfied DO Tool A      │
│               (Self-RAG / Agentic pattern)       │
│                                                  │
└──────────────────────────────────────────────────┘

错误处理最佳实践

错误类型 处理方式 返回给模型的信息
参数校验失败 拒绝执行,返回错误 {"error": "amount must be positive"}
外部 API 超时 重试 1-2 次后降级 {"error": "service temporarily unavailable"}
权限不足 拒绝执行 {"error": "permission denied"}
函数不存在 记录日志,安全拒绝 {"error": "unknown function"}
结果过大 截断或摘要 前 N 条 + "truncated": true

安全考量

  1. 输入验证:永远不要直接将模型生成的参数传入危险操作(SQL、shell 命令)
  2. 权限控制:工具分级(只读/读写/危险),危险工具需要人工确认
  3. 速率限制:防止模型陷入循环调用耗尽 API 配额
  4. 审计日志:记录每次工具调用的输入输出,用于事后追溯
  5. 沙盒执行:代码执行类工具必须在隔离环境中运行
  6. 输出过滤:工具返回的数据可能包含敏感信息,需脱敏后再传给模型
def safe_tool_call(fn_name: str, fn_args: dict, user_permissions: list) -> dict:
    if fn_name not in TOOL_MAP:
        return {"error": f"Unknown function: {fn_name}"}
    if TOOL_PERMISSIONS.get(fn_name, "admin") not in user_permissions:
        return {"error": f"Permission denied for {fn_name}"}
    sanitized_args = sanitize_inputs(fn_args)
    try:
        result = execute_with_timeout(TOOL_MAP[fn_name], sanitized_args, timeout=30)
    except TimeoutError:
        return {"error": "Function execution timed out"}
    return redact_sensitive_fields(result)

Maurice | maurice_wen@proton.me