更新时间:2025-09-15 GMT+08:00
分享

Tool Calling

什么是Tool Calling

工具调用(Tool Calling,Function Calling,FC)是一种将大模型与外部工具和 API 相连的关键功能,作为自然语言与信息接口之间的“翻译官”,它能够将用户的自然语言请求智能地转化为对特定工具或 API 的调用,从而高效满足用户的特定需求。

工作原理:开发者通过自然语言向模型描述工具的功能和定义,模型在对话过程中自主判断是否需要调用工具。当需要调用时,模型会返回符合要求的工具函数及入参,开发者负责实际调用工具并将结果回填给模型,模型再根据结果进行总结或继续规划子任务。

实现原理

FC特性实现主要包括前处理(serving_chat模块)、后处理模块(tool parser)以及chat_template,前处理模块通过chat_template对用户prompt进行渲染,后处理模块对模型输出进行解析。整体流程如下图所示:

  1. 检测到用户输入中传入工具定义,并开启了auto tool choice选项;
  2. 应用chat template渲染用户输入,将工具信息和用户prompt构造到模型输入中;
  3. 模型执行推理并完成基础的后处理流程;
  4. tool parser对模型输出进行解析,如果模型输出中包含工具调用信息,tool parser会将工具调用信息解析到用户响应中的tool_calls字段中。

规格说明

表1 支持模型选型

模型

优势

说明

Qwen3-32B/Qwen3-30B-A3B/Qwen3-235B-A22B

平衡性能与资源消耗,在 MMLU(通用知识)、C-Eval(中文评测)上表现优秀。

-

1. 上表中的模型均经过验证,其他发布模型支持能力与社区保持一致;

2. FC不会增强模型能力,模型推理能力影响FC性能和精度。

tool choice支持模式

tool choice目前只支持auto(自动识别)和named(指定函数调用)两种模式,不支持required(必选函数调用)模式,详情如下。

模式

支持情况

说明

示例

auto(推荐)

模型自动识别是否需要进行工具调用

"tool_choice": "auto"

named

指定函数调用

"tool_choice": {"type": "function", "function": {"name": "get_weather"}

required

×

提示模型必须进行函数调用

"tool_choice": "required"

支持处理模式

模式

支持情况

说明

流式输出适配

支持流式输出,逐步获取工具调用信息,提升响应效率。

多轮工具调用

当用户需求需要多次调用工具时,维护对话历史上下文,逐轮处理工具调用和结果回填。

特性兼容性

Function call与vllm已有关键特性兼容性如下表。

特性

兼容

说明

兼容报错行为

Reasoning Outputs

支持思维链模型输出深度思考内容,Function call场景下关闭深度思考内容输出,可以提高Function call运行效率,同时避免某些模型reasoning_content中的异常返回影响结果解析。

Deepseek R1:不支持关闭思维链

Qwen3:支持关闭思维链

-

Automatic Prefix Caching

-

-

Chunked Prefill

-

-

投机推理

-

-

图模式

-

-

上述内容为auto模式下的兼容性说明,named模式下与Guided decoding的特性兼容性一致,详见社区特性兼容矩阵。

基本使用流程

  1. 环境准备

    在启用工具调用的情况下启动服务器。此示例使用Qwen3模型,因此我们需要使用vLLM示例目录中的hermes工具调用聊天模板:

    python -m vllm.entrypoints.openai.api_server \
    --model Qwen3/ \
    --chat-template=tool_chat_template_hermes.v1.jinja \
    --enable-auto-tool-choice \
    --tool-call-parser=hermes

  2. 定义工具

    FC通过json格式定义 tools 字段的方式向模型提供可用工具。tools 包含工具名称、描述、参数定义等信息。具体参考 :工具参数构造规范

    定义工具函数

    def get_weather(location: str, unit: str):
        return f"Getting the weather for {location} in {unit}..."
    • 假设在代码中定义了一个名为 get_weather 的工具函数,用于获取指定地点的天气信息。
      • location:表示天气查询的地点,必选。
      • unit:表示函数返回结果的温度单位,可选。
    • 注意:此用例只是模拟的天气查询场景,实际应用中需要调用真实的天气查询 API。

    定义 Tools

    "tools": [{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather in a given location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string", "description": "City and state, e.g., 'San Francisco, CA'"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
                },
                "required": ["location"]
            }
        }
    }]
    • tools 列表类型,其中每个元素代表一个可调用的工具函数。这里定义了一个名为 get_weather 的工具。
    • type:工具的类型,这里是固定值 function,表示这是一个可调用工具函数。
    • function:函数对象,用于定义工具函数的详细信息,如名称、描述和参数。
      • name:函数名称,在这里为 get_weather
      • description:函数描述,解释该函数应用信息。
      • parameters:函数所需参数,这里是一个对象,包含 locationunit 两个属性。
        • location:地点的位置信息,参数类型为string。
        • unit:温度单位,参数类型为枚举string,候选值为 celsiusfahrenheit
        • required:指定必需的参数,这里 location是必需的。

  3. 发起请求

    在请求中提供用户问题和工具,模型会根据问题自动识别并返回需要调用的工具及参数。

    from openai import OpenAI
    import json
    client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
    //用户问题 
    messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}]
    tools = [
        {
            //参见步骤1中定义的tools
        }
    ]
    //发起模型请求
    response = client.chat.completions.create(
        model=client.models.list().data[0].id,
        messages=messages ,
        tools=tools,
        tool_choice="auto"
    )

  4. 调用外部工具

    根据模型返回的工具和参数信息,调用对应的外部工具或 API,获取工具的实际执行结果。

    # 解析模型返回的工具调和参数信息
    tool_call = response.choices[0].message.tool_calls[0].function
    # 工具名称
    tool_name = tool_call.function.name
    #根据工具名称执行对应工具
    if tool_name == "get_weather":
        # 提取的用户参数
        arguments = json.loads(tool_call.function.arguments)
        # 调用工具
        tool_result = get_weather(**arguments)
    • 从模型返回的 tool_calls 中获取模型调用的工具列表。
    • 根据工具名称执行对应工具。如当工具名称是 get_weather 时,则提取用户参数并调用 get_weather 函数获取工具执行结果。

  5. 回填执行结果并生成最终回复

    将工具执行结果以 role=tool 的消息形式回填给模型,模型根据结果生成最终回复。

    messages.append(completion.choices[0].message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": tool_result
    })
    # 调用模型生成最终回复
    final_response = client.chat.completions.create(
        model=client.models.list().data[0].id,
        messages=messages ,
        tools=tools,
        tool_choice="auto"
    )
    print(final_response.choices[0].message.content)

完整代码示例

from openai import OpenAI
import json
client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
def get_weather(location: str, unit: str):
    return f"Getting the weather for {location} in {unit}..."
tool_functions = {"get_weather": get_weather}
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather in a given location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "City and state, e.g., 'San Francisco, CA'"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["location", "unit"]
        }
    }
}]
response = client.chat.completions.create(
    model=client.models.list().data[0].id,
    messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
    tools=tools,
    tool_choice="auto"
)
tool_call = response.choices[0].message.tool_calls[0].function
print(f"Function called: {tool_call.name}")
print(f"Arguments: {tool_call.arguments}")
print(f"Result: {get_weather(**json.loads(tool_call.arguments))}")

Example output:

Function called: get_weather
Arguments: {"location": "San Francisco, CA", "unit": "fahrenheit"}
Result: Getting the weather for San Francisco, CA in fahrenheit...

Prompt最佳实践

Function call的性能很大程度上依赖于所使用模型本身的推理能力,用户需要通过合理的任务定义prompt配合清晰明确的工具函数定义实现高效的工具调用,使用时应该遵从以下原则:

  1. 任务定义简单直接,避免输入和任务无关的多余信息,避免造成对模型的信息干扰;
  2. 模型推理具有随机性,能用代码完成的任务就不要调用大模型;

以下是一些典型的错误示例和修正示范:

类别

问题

错误示例

改正后示例

函数定义

函数命名不规范,功能描述不清晰

{
    "type": "function",
    "function": {
        "name": "func1",
        "description": "功能函数"
    }
}
{
    "type": "function",
    "function": {
        "name": "CreateTask",
        "description": "当需要为用户新建工作项时,此工具将创建工作项,并返回工作项ID"
    }
}

参数定义

格式定义冗余,包含重复信息

{
    "time": {
        "type": "object",
        "description": "城市",
        "properties": {
            "city": {
                "description": "城市"
            }
        }
    }
}
{
    "time": {
        "type": "string",
        "description": "城市"
    }
}

包含非必要入参(包含固定值等)

{
    "time": {
        "type": "object",
        "description": "城市",
        "properties": {
            "city": {
                "description": "固定传杭州即可"
            }
        }
    }
}

如果函数入参非必要,比如为固定值。这种情况应该移除相关入参,直接由代码处理。

prompt

任务描述复杂,导致无意义增加工具调用轮次

System prompt:

你正在与用户Marvin沟通,你需要先查询用户ID,再通过ID为用户创建工作项……

System prompt:

你正在与用户Marvin(ID=123)沟通,你可以通过用户ID为用户创建工作项……

任务定义包含模糊信息,模型无法区分

System prompt:

可以通过ID查找用户,并获得用户的任务ID

此处两个ID无法明确区分,导致模型可能混用。

System prompt:

每个用户具有唯一的用户ID;每个工作项有对应的工作项ID。可以通过用户ID查找用户信息,并获取用户的所有工作项ID

任务定义与工具函数无法match

查询天气工具函数入参要求传入查询地点和指定日期。

prompt:

查询北京的天气

任务定义缺失日期信息,会导致模型无法调用函数或者输入日期为缺省值导致调用结果异常。

prompt:

查询北京2025年7月30日的天气

格式冲突

system prompt定义的某种返回格式与Function call要求的格式冲突,导致工具函数调用失败。

去除导致函数调用异常的格式内容。

工具函数定义最佳实践

为确保模型能正确调用工具功能,工具函数定义需遵循 JSON Schema 标准,按照如下规范构造 tools 对象。

tools 整体结构

"tools": [
    {
        "type": "function",
        "function": {
            "name": "...", // 函数名称(小写+下划线)
            "description": "...", // 函数描述
            "parameters": { ...
            } // 函数参数(JSON Schema格式)
        }
    }
]
  • type:工具的类型,这里是固定值 function,表示这是一个可调用工具函数。
  • function:函数对象,用于定义工具函数的名称、描述和参数等详细配置。

字段解释

function

字段

类型

是否必填

说明

name

string

函数名称,唯一标识,建议使用小写加下划线

description

string

函数描述,解释函数应用信息

parameters

object

函数所需参数,需遵循 JSON Schema 格式

parameters

parameters 需为遵循JSON Schema格式的对象:

"parameters": {
    {
        "type": "object",
        "properties": {
            "name": {
                "type": "string | number | boolean | object | array | integer",
                "description": "属性说明"
            }
        },
        "required": [
            "必填参数"
        ]
    }
}
  • type:必须为"object"
  • properties:列出支持的所有属性名及其类型;
    • name:参数名,须为英文字符串,且不能重复;
    • type:需遵循JSON Schema规范,支持类型包括string、number、boolean、integer、object、array;
    • required:指定函数中必填的参数名;
    • 其它参数根据type类型不同稍有区别,配置方式请参见下表。

type类型

示例

string、integer、number、boolean

object (对象)

  • description:说明
  • properties: 描述对象涉及属性
  • required :描述必填属性
  • 示例1: 查询学生个人信息
    "parameters": {
        "type": "object",
        "description": "学生信息",
        "properties": {
            "age": {
                "type": "integer",
                "description": "年龄"
            },
            "gender": {
                "type": "string",
                "description": "性别"
            },
            "married": {
                "type": "boolean",
                "description": "是否党员"
            }
        },
        "required": [
            "age"
        ],
    }

array (列表)

  • description:说明
  • "items": {"type": ITEM_TYPE} :用于表达数组元素的数据类型
  • 示例1:定义文本数组
    "parameters": {
        "type": "array",
        "description": "任意数量文本",
        "items": {
            "type": "string"
        }
    }
  • 示例2: 定义二维数组
    "parameters": {
        "type": "array",
        "description": "二维矩阵",
        "items": {
            "type": "array",
            "items": {
                "type": "number"
            }
        },
    }
  • 示例3: 通过array来实现多选数组
    "parameters": {
        "description": "爱好, 支持多选",
        "type": "array",
        "items": {
            "type": "string",
            "description": """枚举值有
            "游泳",
            "跑步",
            "爬山",
            "健身",
            "羽毛球",
            "篮球"。 """,
        },
    }

完整示例

"tools": [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather information for a specified location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City and state, e.g., 'San Francisco, CA'"
                    }
                },
                "required": [
                    "location"
                ]
            }
        }
    }
]

注意事项

  1. 大小写敏感:所有字段名、参数名严格区分大小写(建议统一用小写)。
  2. 中英文:字段名建议使用英文, description 中可以使用中文(如location 可以描述为“城市”)。
  3. Schema 合规性:定义方式必须遵循 JSON Schema ,可通过JSON Schema 校验工具验证。

最佳实践

  1. 工具描述核心准则
    • 详细阐述工具能力、参数含义与影响、适用场景(或禁用场景)、限制条件(如输入长度限制)。单工具描述建议3-4句。
    • 优先完善功能、参数等基础描述,示例仅作为补充(推理模型需谨慎添加)。
  2. 函数设计要点
    • 命名与参数:函数名直观(如parse_product_info),参数说明包含格式(如city: string)和业务含义(如“目标城市拼音全称”),明确输出定义(如“返回JSON格式天气数据”)。
    • 系统提示:通过提示指定调用条件(如“用户询问商品详情时触发get_product_detail”)。
    • 工程化设计
      • 最小惊讶原则:使用枚举类型(如StatusEnum)避免无效参数,确保逻辑直观。
      • 简洁明了原则:提示词需表述清晰,达到人类可凭直觉进行判断的效果。
    • 调用优化
      • 已知参数通过代码隐式传递(如submit_order无需重复声明user_id)。
      • 合并固定流程函数(如query_locationmark_location整合为query_and_mark_location)。
    • 数量与性能:控制函数数量≤20个,通过调试工具迭代函数模式(Schema),复杂场景可使用微调能力提升准确率。

请求实践

  1. 工具调用
    请求示例:
    {
        "model": "deepseek",
        "messages": [
            {
                "role": "user",
                "content": "获取北京天气"
            }
        ],
        "tools": [
            {
                "type": "function",
                "function": {
                    "name": "get_weather",
                    "description": "查询天气",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "location": {
                                "type": "string",
                                "description": "城市"
                            },
                            "unit": {
                                "type": "string",
                                "enum": [
                                    "celsius",
                                    "fahrenheit"
                                ]
                            }
                        },
                        "required": [
                            "location",
                            "unit"
                        ]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "send_email",
                    "description": "发送邮件",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "userInput": {
                                "type": "string",
                                "description": "邮件内容"
                            }
                        },
                        "required": [
                            "userInput"
                        ]
                    }
                }
            }
        ],
        "tool_choice": "auto",
        "temperature": 0,
        "stream": false
    }

    请求结果:

    {
        "id": "1530/chat-c7277abfbc724677a570c03c7541edd7",
        "object": "chat.completion",
        "created": 1753423691,
        "model": "deepseek",
        "choices": [
            {
                "index": 0,
                "message": {
                    "role": "assistant",
                    "content": null,
                    "reasoning_content": null,
                    "tool_calls": [
                        {
                            "id": "chatcmpl-tool-6714630cc3fc4551a156aa48715d5139",
                            "type": "function",
                            "function": {
                                "name": "get_weather",
                                "arguments": "{\"location\":\"北京\",\"unit\":\"celsius\"}"
                            }
                        }
                    ]
                },
                "logprobs": null,
                "finish_reason": "tool_calls",
                "stop_reason": null
            }
        ],
        "usage": {
            "prompt_tokens": 309,
            "total_tokens": 359,
            "completion_tokens": 50
        },
        "prompt_logprobs": null
    }
  2. 工具总结

    请求示例:

    {
        "model": "deepseek",
        "messages": [
                    {
                        "role": "user",
                        "content": "获取北京的天气"
                    },
                    {
                        "role": "assistant",
                        "tool_calls": [
                            {
                                "id": "chatcmpl-tool-fc6986a3dc014e80a5d3e091c60648d9",
                                "type": "function",
                                "function": {
                                    "name": "get_weather",
                                    "arguments": "{\"location\": \"Beijing\", \"unit\": \"celsius\"}"
                                }
                            }
                        ]},
                    {
                        "role": "tool",
                        "tool_call_id": "chatcmpl-tool-fc6986a3dc014e80a5d3e091c60648d9",
                        "content": "北京今天20到50度",
                        "name": "get_weather"
                     }
        ],
        "tools": [
            {
                "type": "function",
                "function": {
                    "name": "get_weather",
                    "description": "查询天气",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "location": {
                                "type": "string",
                                "description": "城市"
                            },
                            "unit": {
                                "type": "string",
                                "enum": [
                                    "celsius",
                                    "fahrenheit"
                                ]
                            }
                        },
                        "required": [
                            "location",
                            "unit"
                        ]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "send_email",
                    "description": "发送邮件",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "userInput": {
                                "type": "string",
                                "description": "邮件内容"
                            }
                        },
                        "required": [
                            "userInput"
                        ]
                    }
                }
            }
        ],
        "tool_choice": "auto",
        "temperature": 0,
        "stream": false
    }

    请求结果:

    {
        "id": "1530/chat-2966628beae0430b872b994f7ef0f9b4",
        "object": "chat.completion",
        "created": 1753423849,
        "model": "deepseek",
        "choices": [
            {
                "index": 0,
                "message": {
                    "role": "assistant",
                    "content": "北京今天的天气是20到50度。",
                    "reasoning_content": null,
                    "tool_calls": []
                },
                "logprobs": null,
                "finish_reason": "stop",
                "stop_reason": null
            }
        ],
        "usage": {
            "prompt_tokens": 387,
            "total_tokens": 398,
            "completion_tokens": 11
        },
        "prompt_logprobs": null
    }

异常处理

JSON 格式容错机制

对于轻微不合法的 JSON 格式,可尝试使用 json-repair 库进行容错修复。

import json_repair
 
invalid_json = '{"location": "北京", "unit": "摄氏度"}'
valid_json = json_repair.loads(invalid_json)

工具调用异常

因prompt输入和工具函数定义导致模型无法正确调用工具函数的场景,建议按照最佳实践部分的内容进行prompt调优和函数定义优化。

模型返回异常

Function call功能依赖模型能力实现,因大模型幻觉等原因,可能出现模型返回结果不符合预期,导致一定的工具调用失败概率。

如果某场景下调用失败率较低,可以通过设置重试机制进行可靠性加固。

Q&A

Q:Deepseek-R1-0528为什么在非流式处理模式下content中会返回<result>标签?

A:模型能力缺陷,模型自行输出标签,不影响Function call功能正常使用,用户无需关注。

Q:Deepseek-R1等思维链模型开启深度思考情况下,reasoning_content中为什么概率性出现</think>标签?

A:Deepseek-R1模型能力缺陷,存在概率性多输出</think>标签,导致思维链内容被截断。目前无法规避,建议增加重试。

Q:Deepseek-R1等思维链模型开启深度思考情况下,reasoning_content返回中为什么包含<tool_calls_begin>标签?

A:Funtion call预置提示词模版引入特殊字符,不影响功能正常使用,用户无需关注。

Q:使用Function call功能时,只调用1个函数,为什么会出现返回多个函数调用的情况?

A:模型自主判断调用函数次数,通常与prompt语义不明确或工具函数定义模糊有关,建议参考最佳实践部分内容优化prompt输入和工具函数定义。

相关文档