Function Calling¶
一句话:Function Calling 是让 LLM 按预定义的"工具元数据"输出结构化调用指令、由外部代码真正执行工具的机制;把"调工具成功率做到 98%"靠的不是模型本身,而是异常重试、参数校验、超时、降级这套工程化。
概念¶
Function Calling(函数调用 / 工具调用) 的本质是:开发者把一组工具的元数据(名称、描述、参数 schema)作为上下文喂给 LLM,LLM 判断"该调哪个工具、用什么参数",并输出一段结构化的调用指令(通常是 JSON)。这段指令本身不执行任何操作——真正的执行发生在宿主程序里:宿主解析指令、调用真实函数、把返回值作为 Observation 喂回 LLM。
一次完整流程:
- 工具注册:声明工具元数据(
name/description/parametersJSON Schema); - 模型决策:LLM 根据用户意图,选择工具并生成参数;
- 宿主执行:宿主代码校验参数、调用真实函数;
- 结果回填:函数返回值作为 Observation 回传 LLM,LLm 据此继续推理或给最终答案。
工具元数据的质量是成败关键:description 要说清"这个工具做什么、什么时候用",parameters 的 schema 要严格(类型、必填、枚举、范围),否则 LLM 会选错工具或生成非法参数。
原理¶
报告 §5.3 指出彭超案例实现了 "工具调用成功率 98%",并采用自定义注解扫描注册工具元数据。98% 不是模型白送的,而是工程化的结果——把模型当"会犯错的决策器",在执行链路上层层兜底:
- 参数校验:执行前用 JSON Schema 严格校验 LLM 产出,参数缺失/类型错/越界直接拦截,不让脏参数打到下游;
- 异常重试:工具调用失败(网络抖动、下游 5xx)按退避策略重试,区分可重试错误与不可重试错误;
- 超时控制:每个工具设独立超时,避免一个慢工具拖垮整个 Agent 响应(P95 < 800ms 的前提);
- 降级:重试仍失败时走降级路径(换备选工具、走规则兜底、转人工),不让链路整体失败。
// Spring AI 风格:动态工具注册 + 工程化执行(校验/重试/超时/降级)
/** 自定义注解:扫描注册工具元数据 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AgentTool {
String name();
String description();
}
public class ToolExecutor {
private final ToolRegistry registry; // 注解扫描产物
private final JsonSchemaValidator validator; // 参数校验
private final RetryTemplate retry; // 异常重试
public ToolResult invoke(ToolCall call) {
AgentTool tool = registry.find(call.getName());
if (tool == null) return ToolResult.fallback("工具不存在");
// 1. 参数校验:JSON Schema 严格校验 LLM 产出
if (!validator.validate(call.getArguments(), tool.schema())) {
return ToolResult.fallback("参数校验失败:" + validator.errors());
}
// 2. 超时 + 异常重试执行真实函数
try {
return retry.execute(ctx ->
CompletableFuture.supplyAsync(
() -> doInvoke(tool, call.getArguments()),
pool
).get(tool.timeoutMs(), TimeUnit.MILLISECONDS) // 独立超时
);
} catch (RetryExhausted e) {
// 3. 降级:备选工具 / 规则兜底 / 转人工
return fallbackChain.handle(call);
} catch (TimeoutException e) {
return fallbackChain.handle(call);
}
}
}
# LangChain 风格:结构化工具 + 工程化执行
import json
from tenacity import retry, stop_after_attempt, retry_if_exception_type
from pydantic import BaseModel, ValidationError
class QueryOrderArgs(BaseModel): # 参数 schema = 校验器
order_id: str
def query_order(order_id: str) -> dict:
"""根据订单号查询订单状态。当用户询问订单/物流进度时调用。
Args:
order_id: 订单编号,纯数字字符串
"""
... # 真实实现
@retry(stop=stop_after_attempt(3),
retry=retry_if_exception_type((TimeoutError, ConnectionError)))
def _invoke_with_timeout(tool, args, timeout_ms=1500):
# 超时控制:单工具独立超时
return future_with_timeout(tool.invoke, args, timeout_ms)
def execute_tool(name: str, raw_args: dict) -> dict:
tool = TOOL_MAP[name]
try:
# 1. 参数校验:pydantic 严格校验 LLM 产出
args = tool.arg_model(**raw_args)
except ValidationError as e:
# 参数非法:拦截,不让脏参数下游
return {"error": "bad_args", "detail": str(e)}
try:
# 2. 超时 + 重试
return _invoke_with_timeout(tool, args.model_dump())
except (RetryError, TimeoutError):
# 3. 降级:备选工具 / 规则兜底 / 转人工
return fallback_chain.handle(name, raw_args)
实战要点¶
- 元数据驱动选工具:工具
description写清"做什么、何时用",参数 schema 严格化——这是模型能选对的根本前提。报告里"自定义注解扫描注册工具元数据"正是让工具声明可维护、可扩展的工程手段。 - 校验在执行之前:永远不要把 LLM 原样产出的参数直接喂给下游。先 JSON Schema 校验,拦截非法参数,再执行。
- 可重试 vs 不可重试要区分:网络抖动、5xx 可重试;参数错误、业务逻辑错误不可重试——无脑重试会放大问题。
- 独立超时 + 整体超时双层:每个工具有独立超时(防慢工具),整个 Agent 链路有总超时(保 P95 延迟),二者配合。
- 降级链要有:工具彻底失败时,备选工具 → 规则引擎兜底 → 转人工,不能让用户体验直接崩掉。这与"99.99% 可用性"的多级降级一脉相承。
- 全链路 Trace 工具调用:选了哪个工具、参数是什么、校验/重试/超时/降级触发情况都要落 Trace,是 98% 这个数字可归因、可迭代的基础。
本节相关题目¶
| 难度 | 题目 | 链接 |
|---|---|---|
| 基础 | Function Calling 与 MCP 各自解决什么问题 | → 题库 |
| 进阶 | 工具调用成功率 80%→98% 的工程化改造 | → 题库 |
| 深度 | 工具层与双活/降级如何支撑 99.99% | → 题库 |