Function Calling 与工具使用开发指南
原创
灵阙教研团队
A 推荐 入门 |
约 5 分钟阅读
更新于 2026-02-27 AI 导读
Function Calling 与工具使用开发指南 Maurice | 灵阙学院 2026-02-27 什么是 Function Calling Function Calling 让大模型不再只是输出文本,而是能够结构化地调用外部工具。模型根据用户请求决定调用哪个函数、传入什么参数,开发者执行函数后将结果返回模型,模型再基于结果生成最终回答。...
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 设计原则
- 函数名:动词+名词,清晰表达意图(
get_weather而非weather) - 描述:告诉模型"什么时候应该调用这个工具",这比参数定义更重要
- 参数:用 enum 约束可选值,required 标记必填项,default 减少调用歧义
- 粒度:一个工具做一件事;宁可多几个单一工具,也不要一个万能工具
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 |
安全考量
- 输入验证:永远不要直接将模型生成的参数传入危险操作(SQL、shell 命令)
- 权限控制:工具分级(只读/读写/危险),危险工具需要人工确认
- 速率限制:防止模型陷入循环调用耗尽 API 配额
- 审计日志:记录每次工具调用的输入输出,用于事后追溯
- 沙盒执行:代码执行类工具必须在隔离环境中运行
- 输出过滤:工具返回的数据可能包含敏感信息,需脱敏后再传给模型
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