# Function Call
#### 什么是Function Call
工具调用（Tool Calling，Function Call，FC）是一种将大模型与外部工具和API相连的关键功能，作为自然语言与信息接口之间的"翻译官"，它能够将用户的自然语言请求智能地转化为对特定工具或API的调用，从而高效满足用户的特定需求。
**工作原理**：开发者通过自然语言向模型描述工具的功能和定义，模型在对话过程中自主判断是否需要调用工具。当需要调用时，模型会返回符合要求的工具函数及入参，开发者负责实际调用工具并将结果回填给模型，模型再根据结果进行总结或继续规划子任务。
#### 实现原理
图1Function Call实现原理   
![](https://support.huaweicloud.com/model-call-maas/figure/zh-cn_image_0000002531121328.png "点击放大")
1. 分析用户输入并判断是否调用工具。
2. 确定调用工具，根据用户输入确定调用工具及其参数。
3. 进行工具调用并获取工具调用结果。
4. 循环分析用户输入和工具结果判断是否需要再次调用工具，确定调用重复[2]、[3]。
5. 整合所有结果生成最终回答。
 
#### 支持模型
当前支持Function Call的模型请参见[文本生成](https://support.huaweicloud.com/model-list-maas/model_list_0001.html#section1)支持能力。
#### 规格说明
**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已有关键特性兼容性如下表。
| 特性                 | 兼容  | 说明                                                                                                                                                                      | 兼容报错行为 |
|:---|:---|:---|:---|
| Reasoning Outputs | √ | 支持思维链模型输出深度思考内容，Function Call场景下关闭深度思考内容输出，可以提高Function Call运行效率，同时避免某些模型reasoning_content中的异常返回影响结果解析。 Qwen3：支持关闭思维链。 | -        |
   
#### 基本使用流程
1. 定义工具 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：函数所需参数，这里是一个对象，包含location 和unit两个属性。
       - location：地点的位置信息，参数类型为string。
       
       - unit：温度单位，参数类型为枚举string，候选值为celsius或fahrenheit。
       
       - required：指定必须的参数，这里location是必须的。
        
      
    
2. 发起请求 在请求中提供用户问题和工具，模型会根据问题自动识别并返回需要调用的工具及参数。
   ```
   from openai import OpenAI
   import httpx
   import json
   client = OpenAI(base_url="https://api.modelarts-maas.com/openai/v1", api_key="MAAS_API_KEY", http_client=httpx.Client(verify=False))  # 把MAAS_API_KEY替换成已获取的API Key
   messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}]
   tools = [
       {
           #参见步骤1中定义的tools
       }
   ]
   response = client.chat.completions.create(
       
       model="deepseek-v4-flash",
       messages=messages ,
       tools=tools,
       tool_choice="auto"
   )
   ```
   
   
3. 调用外部工具 根据模型返回的工具和参数信息，调用对应的外部工具或API，获取工具的实际执行结果。
   ```
   # 解析模型返回的工具调和参数信息
   tool_call = response.choices[0].message.tool_calls[0].function
   # 工具名称
   tool_name = tool_call.name
   #根据工具名称执行对应工具
   if tool_name == "get_weather":
       # 提取的用户参数
       arguments = json.loads(tool_call.arguments)
       # 调用工具
       tool_result = get_weather(**arguments)
   ```
   - 从模型返回的*tool_calls*中获取模型调用的工具列表。
   
   - 根据工具名称执行对应工具。如当工具名称是*get_weather* 时，则提取用户参数并调用*get_weather*函数获取工具执行结果。
    
4. 回填执行结果并生成最终回复
   将工具执行结果以*role=tool* 的消息形式回填给模型，模型根据结果生成最终回复。
   ```
   messages.append(completion.choices[0].message)
   messages.append({
       "role": "tool",
       "tool_call_id": tool_call.id,
       "content": tool_result,
       "name": tool_name
   })
   # 调用模型生成最终回复
   final_response = client.chat.completions.create(
       model="deepseek-v4-flash",
       messages=messages ,
       tools=tools,
       tool_choice="auto"
   )
   print(final_response.choices[0].message.content)
   ```
   
**完整代码示例**
```
from openai import OpenAI
import httpx
import json
api_key = "MAAS_API_KEY"  # 把MAAS_API_KEY替换成已获取的API Key
client = OpenAI(base_url="https://api.modelarts-maas.com/openai/v1", api_key=api_key, http_client=httpx.Client(verify=False))
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="deepseek-v4-flash",
    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为用户创建工作项…… ```               |
| prompt | 任务定义包含模糊信息，模型无法区分   | System prompt: ``` 可以通过ID查找用户，并获得用户的任务ID ``` 此处两个ID无法明确区分，导致模型可能混用。                                                                       | System prompt: ``` 每个用户具有唯一的用户ID；每个工作项有对应的工作项ID。可以通过用户ID查找用户信息，并获取用户的所有工作项ID ``` |
| prompt | 任务定义与工具函数无法match       | 查询天气工具函数入参要求传入查询地点和指定日期。 prompt: ``` 查询北京的天气 ``` 任务定义缺失日期信息，会导致模型无法调用函数或者输入日期为缺省值导致调用结果异常。 | prompt: ``` 查询北京2025年7月30日的天气 ```                                               |
| prompt | 格式冲突                  | system prompt定义的某种返回格式与Function Call要求的格式冲突，导致工具函数调用失败。                                                                                                                                | 去除导致函数调用异常的格式内容。                                                                  |
   
#### 异常处理
**JSON格式容错机制**
对于轻微不合法的JSON格式，可尝试使用json-repair库进行容错修复。
```
import json_repair
 
invalid_json = '{"location": "北京", "unit": "摄氏度"}'
valid_json = json_repair.loads(invalid_json)
```
**工具调用异常**
因prompt输入和工具函数定义导致模型无法正确调用工具函数的场景，建议按照最佳实践部分的内容进行prompt调优和函数定义优化。
**模型返回异常**
Function Call功能依赖模型能力实现，因大模型幻觉等原因，可能出现模型返回结果不符合预期，导致一定的工具调用失败概率。
如果某场景下调用失败率较低，可以通过设置重试机制进行可靠性加固。
#### 常见问题
- **使用Function Call功能时，只调用1个函数，为什么会出现返回多个函数调用的情况？**
  模型自主判断调用函数次数，通常与prompt语义不明确或工具函数定义模糊有关，建议参考最佳实践部分内容优化prompt输入和工具函数定义。
  
- **使用Function Call功能时，配置max_tokens后为什么无法解析tool_calls？**
  max_tokens配置过小可能会导致模型输出截断，无法正常解析出tool_calls内容，需要合理配置max_tokens大小。
  
- **如何判断模型是否需要调用函数** **？**
  模型会根据用户问题结合工具定义和tool_choice字段自主判断，若返回结果中包含tool_calls字段，则表示需要调用工具。
  
- **使用Function Call功能时，如果在请求中的System Prompt设置了输出格式，为什么有时会输出特殊标签token，导致无法解析tool_calls？**
  这一般是由于System Prompt设置的格式未遵循模型所要求的格式规范。建议参考相关最佳实践，对prompt进行优化调整。
  
 
