彻底搞懂大模型 Function Calling:核心原理与消息流转

Function Calling 的本质是一个多轮对话流程,让模型、代码和外部工具(如 API)协同工作。其核心工作流如下:

(1)定义工具:首先,在代码中以特定格式(通常是 JSON Schema)定义好可用的工具,包括工具的名称、功能描述、以及需要的参数。

(2)用户提问:用户发起一个需要调用工具才能回答的请求。

(3)模型决策:模型接收到请求后,分析用户的意图,并匹配最合适的工具。它不会直接回答,而是返回一个包含 tool_calls 的特殊响应。这个响应相当于一个指令:“请调用某某工具,并使用这些参数”。

(4)代码执行:应用接收到这个指令,解析出工具名称和参数,然后在代码层面实际执行这个工具(例如,调用一个真实的天气 API)。

(5)结果反馈:将工具的执行结果(例如,从 API 获取的真实天气数据)包装成一个 role 为 tool 的消息,再次发送给模型。

(6)最终生成:模型接收到工具的执行结果后,结合原始问题和工具返回的信息,生成最终的、自然的语言回答。

1. 核心认知

Function Calling 的本质是:大模型负责决策,本地程序负责执行。

大模型不会直接执行代码、不会联网、不会查数据库。它只输出结构化调用计划,真正执行工具的是你的本地程序。

完整闭环:

LLM 下发调用指令 → 本地执行工具 → 结果回填上下文 → LLM 整合输出答案

2. 四大核心消息类型

Function Calling 的消息流转主要由四种消息组成。

SystemMessage

作用是给大模型设定规则,包括:

  • 角色设定
  • 可用工具
  • 工具调用场景
  • 回答约束

它是大模型决策的依据。

HumanMessage

用户的原始问题,是触发工具调用的业务输入。

AIMessage

AIMessage 有两种形态。

普通问答形态

没有工具调用,直接返回自然语言答案。

AIMessage(content="北京今天天气晴朗。")
工具调用形态

模型不直接回答,而是返回 tool_calls,要求本地程序执行工具。

AIMessage(
    content="",
    tool_calls=[
        {
            "id": "call_001",
            "name": "get_weather",
            "args": {"city": "北京"}
        }
    ]
)

其中:

  • id:本次调用的唯一标识
  • name:工具名称
  • args:工具参数

ToolMessage

工具执行完成后,结果会被封装成 ToolMessage,回填到消息上下文中。

ToolMessage(
    content="北京今日晴,25℃",
    tool_call_id="call_001"
)

重点:ToolMessage 必须绑定正确的 tool_call_id。否则大模型无法判断这个结果对应哪一次工具调用。


3. 单轮 Function Calling 完整流程

以“今天北京天气怎么样?”为例。

第一步:组装初始消息

[
    SystemMessage(工具规则),
    HumanMessage(今天北京天气怎么样?)
]

第二步:大模型返回工具调用指令

大模型判断需要查询天气,返回带 tool_calls 的 AIMessage。

AIMessage(
    content="",
    tool_calls=[
        {
            "id": "call_001",
            "name": "get_weather",
            "args": {"city": "北京"}
        }
    ]
)

第三步:本地程序执行工具

本地代码负责:

  1. 解析工具名
  2. 解析参数
  3. 调用真实函数或接口
  4. 获取返回结果

大模型在这一步并没有执行代码。

第四步:回填 ToolMessage

工具结果被封装成 ToolMessage,并绑定调用 ID。

ToolMessage(
    content="北京今日晴,25℃",
    tool_call_id="call_001"
)

此时完整消息结构为:

SystemMessage
HumanMessage(用户提问)
AIMessage(工具调用指令)
ToolMessage(工具执行结果)

第五步:大模型生成最终答案

大模型读取工具结果后,不再返回 tool_calls,而是输出自然语言答案。

AIMessage(content="北京今天天气晴朗,气温25摄氏度。")

4. 多轮工具调用消息结构

有些问题需要连续调用多个工具。

例如:对比北京和上海的天气。

消息结构会变成:

SystemMessage
HumanMessage(对比北京上海天气)

AIMessage(调用 get_weather(北京))
ToolMessage(北京天气结果)

AIMessage(调用 get_weather(上海))
ToolMessage(上海天气结果)

AIMessage(对比总结答案)

规则很简单:

多轮调用 = 多组 AIMessage + ToolMessage 顺序叠加

5. 工具无限循环的三大原因

模型重复调用同一个工具,常见原因只有三个。

1. tool_call_id 不匹配

ToolMessage 的 tool_call_id 与 AIMessage 中的 id 不一致。

模型会认为工具没有正确执行,从而再次调用。

2. 工具返回异常

工具结果为空、报错、格式混乱,模型无法识别有效信息。

3. 系统提示约束不足

没有明确告诉模型:拿到工具结果后直接回答,不要重复调用。


6. 核心本质

Function Calling 不是大模型直接执行代码,而是一套消息交互协议。

它的核心逻辑是:

LLM 下发结构化任务 → 本地程序执行 → 结果回填上下文 → LLM 整合回答

7. 最简伪代码

messages = [SystemMessage(工具规则), HumanMessage(用户输入)]

while True:
    ai_msg = llm.invoke(messages)
    messages.append(ai_msg)

    if not ai_msg.tool_calls:
        break

    for call in ai_msg.tool_calls:
        res = run_tool(call.name, call.args)
        tool_msg = ToolMessage(content=res, tool_call_id=call.id)
        messages.append(tool_msg)

print(messages[-1].content)

结语

掌握 Function Calling,关键不是记住某个框架 API,而是理解消息如何变化。

核心重点:

  • 大模型只负责决策
  • 本地程序负责执行
  • AIMessage 下发工具调用
  • ToolMessage 回填结果
  • tool_call_id 必须一一对应
  • 每一次工具调用都由一组 AIMessage(tool_call) + ToolMessage 组成

更多推荐