跳转至

Function Calling

一句话:Function Calling 是让 LLM 按预定义的"工具元数据"输出结构化调用指令、由外部代码真正执行工具的机制;把"调工具成功率做到 98%"靠的不是模型本身,而是异常重试、参数校验、超时、降级这套工程化。

概念

Function Calling(函数调用 / 工具调用) 的本质是:开发者把一组工具的元数据(名称、描述、参数 schema)作为上下文喂给 LLM,LLM 判断"该调哪个工具、用什么参数",并输出一段结构化的调用指令(通常是 JSON)。这段指令本身不执行任何操作——真正的执行发生在宿主程序里:宿主解析指令、调用真实函数、把返回值作为 Observation 喂回 LLM。

一次完整流程:

  1. 工具注册:声明工具元数据(name / description / parameters JSON Schema);
  2. 模型决策:LLM 根据用户意图,选择工具并生成参数;
  3. 宿主执行:宿主代码校验参数、调用真实函数;
  4. 结果回填:函数返回值作为 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% → 题库