You need to enable JavaScript to run this app.
导航
Function Calling 函数调用最佳实践
最近更新时间:2025.05.26 17:19:29首次发布时间:2025.05.26 17:19:29
我的收藏
有用
有用
无用
无用

核心机制

工具定义最佳实践

Tools 格式说明文档请参见Tools 格式说明文档

  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),复杂场景可使用微调能力提升准确率。

流式输出适配

从 Doubao-1.5 系列模型开始支持流式输出,逐步获取工具调用信息,提升响应效率。
详见Function Calling 流式输出适配

def function_calling_stream():
    completion = client.chat.completions.create(
        model="doubao-1.5-pro-32k",
        messages=messages,
        tools=tools,
        stream=True
    )
    for chunk in completion:
        if chunk.choices[0].delta.tool_calls:
            tool_call = chunk.choices[0].delta.tool_calls[0]
            print(f"工具名称:{tool_call.function.name},参数:{tool_call.function.arguments}")

function_calling_stream()

多轮工具调用

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

示例流程

  1. 用户提问:“查询北京的天气,并将结果发送给张三”。
  2. 第一轮:模型调用 get_current_weather 工具获取北京天气。
  3. 第二轮:模型调用 send_message 工具将天气结果发送给张三。
  4. 第三轮:模型总结任务完成情况,返回最终回复。

代码示例

说明

多轮Function Calling:指用户query需要多次调用工具和大模型才能完成的情况, 是多轮对话的子集。

调用Response细节图:
Image

Golang
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "os"
    "strings"

    "github.com/volcengine/volcengine-go-sdk/service/arkruntime"
    "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
    "github.com/volcengine/volcengine-go-sdk/volcengine"
)

func main() {
    client := arkruntime.NewClientWithApiKey(
       os.Getenv("ARK_API_KEY"),
       arkruntime.WithBaseUrl("${BASE_URL}"),
    )

    fmt.Println("----- function call mulstiple rounds request -----")
    ctx := context.Background()
    // Step 1: send the conversation and available functions to the model
    req := model.CreateChatCompletionRequest{
       Model: "${Model_ID}",
       Messages: []*model.ChatCompletionMessage{
          {
             Role: model.ChatMessageRoleSystem,
             Content: &model.ChatCompletionMessageContent{
                StringValue: volcengine.String("你是豆包,是由字节跳动开发的 AI 人工智能助手"),
             },
          },
          {
             Role: model.ChatMessageRoleUser,
             Content: &model.ChatCompletionMessageContent{
                StringValue: volcengine.String("上海的天气怎么样?"),
             },
          },
       },
       Tools: []*model.Tool{
          {
             Type: model.ToolTypeFunction,
             Function: &model.FunctionDefinition{
                Name:        "get_current_weather",
                Description: "Get the current weather in a given location",
                Parameters: map[string]interface{}{
                   "type": "object",
                   "properties": map[string]interface{}{
                      "location": map[string]interface{}{
                         "type":        "string",
                         "description": "The city and state, e.g. Beijing",
                      },
                      "unit": map[string]interface{}{
                         "type":        "string",
                         "description": "枚举值有celsius、fahrenheit",
                      },
                   },
                   "required": []string{
                      "location",
                   },
                },
             },
          },
       },
    }
    resp, err := client.CreateChatCompletion(ctx, req)
    if err != nil {
       fmt.Printf("chat error: %v\n", err)
       return
    }
    // extend conversation with assistant's reply
    req.Messages = append(req.Messages, &resp.Choices[0].Message)
    
    // Step 2: check if the model wanted to call a function.
    // The model can choose to call one or more functions; if so,
    // the content will be a stringified JSON object adhering to
    // your custom schema (note: the model may hallucinate parameters).
    for _, toolCall := range resp.Choices[0].Message.ToolCalls {
       fmt.Println("calling function")
       fmt.Println("    id:", toolCall.ID)
       fmt.Println("    name:", toolCall.Function.Name)
       fmt.Println("    argument:", toolCall.Function.Arguments)
       functionResponse, err := CallAvailableFunctions(toolCall.Function.Name, toolCall.Function.Arguments)
       if err != nil {
          functionResponse = err.Error()
       }
       // extend conversation with function response
       req.Messages = append(req.Messages,
          &model.ChatCompletionMessage{
             Role:       model.ChatMessageRoleTool,
             ToolCallID: toolCall.ID,
             Content: &model.ChatCompletionMessageContent{
                StringValue: &functionResponse,
             },
          },
       )
    }
    // get a new response from the model where it can see the function response
    secondResp, err := client.CreateChatCompletion(ctx, req)
    if err != nil {
       fmt.Printf("second chat error: %v\n", err)
       return
    }
    fmt.Println("conversation", MustMarshal(req.Messages))
    fmt.Println("new message", MustMarshal(secondResp.Choices[0].Message))
}
func CallAvailableFunctions(name, arguments string) (string, error) {
    if name == "get_current_weather" {
       params := struct {
          Location string `json:"location"`
          Unit     string `json:"unit"`
       }{}
       if err := json.Unmarshal([]byte(arguments), &params); err != nil {
          return "", fmt.Errorf("failed to parse function call name=%s arguments=%s", name, arguments)
       }
       return GetCurrentWeather(params.Location, params.Unit), nil
    } else {
       return "", fmt.Errorf("got unavailable function name=%s arguments=%s", name, arguments)
    }
}

// GetCurrentWeather get the current weather in a given location.
// Example dummy function hard coded to return the same weather.
// In production, this could be your backend API or an external API
func GetCurrentWeather(location, unit string) string {
    if unit == "" {
       unit = "celsius"
    }
    switch strings.ToLower(location) {
    case "beijing":
       return `{"location": "Beijing", "temperature": "10", "unit": unit}`
    case "北京":
       return `{"location": "Beijing", "temperature": "10", "unit": unit}`
    case "shanghai":
       return `{"location": "Shanghai", "temperature": "23", "unit": unit})`
    case "上海":
       return `{"location": "Shanghai", "temperature": "23", "unit": unit})`
    default:
       return fmt.Sprintf(`{"location": %s, "temperature": "unknown"}`, location)
    }
}
func MustMarshal(v interface{}) string {
    b, _ := json.Marshal(v)
    return string(b)
}

Python
from volcenginesdkarkruntime import Ark
import time
client = Ark(
    base_url="${BASE_URL}",
)

print("----- function call mulstiple rounds request -----")
messages = [
    {
        "role": "system",
        "content": "你是豆包,是由字节跳动开发的 AI 人工智能助手",
    },
    {
        "role": "user",
        "content": "北京今天的天气",
    },
]
req = {
    "model": "${YOUR_ENDPOINT_ID}",
    "messages": messages,
    "temperature": 0.8,
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "MusicPlayer",
                "description": """歌曲查询Plugin,当用户需要搜索某个歌手或者歌曲时使用此plugin,给定歌手,歌名等特征返回相关音乐。\n 例子1:query=想听孙燕姿的遇见, 输出{"artist":"孙燕姿","song_name":"遇见","description":""}""",
                "parameters": {
                    "properties": {
                        "artist": {"description": "表示歌手名字", "type": "string"},
                        "description": {
                            "description": "表示描述信息",
                            "type": "string",
                        },
                        "song_name": {
                            "description": "表示歌曲名字",
                            "type": "string",
                        },
                    },
                    "required": [],
                    "type": "object",
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "地理位置,比如北京市",
                        },
                        "unit": {"type": "string", "description": "枚举值 [摄氏度,华氏度]"},
                    },
                    "required": ["location"],
                },
            },
        },
    ],
}

ts = time.time()
completion = client.chat.completions.create(**req)
if completion.choices[0].message.tool_calls:
    print(
        f"Bot [{time.time() - ts:.3f} s][Use FC]: ",
        completion.choices[0].message.tool_calls[0],
    )
    # ========== 补充函数调用的结果 =========
    req["messages"].extend(
        [
            completion.choices[0].message.dict(),
             {
                "role": "tool",
                "tool_call_id": completion.choices[0].message.tool_calls[0].id,
                "content": "北京天气晴,24~30度",  # 根据实际调用函数结果填写,最好用自然语言。
                "name": completion.choices[0].message.tool_calls[0].function.name,
            },
        ]
    )
    # 再请求一次模型,获得总结。 如不需要,也可以省略
    ts = time.time()
    completion = client.chat.completions.create(**req)
    print(
        f"Bot [{time.time() - ts:.3f} s][FC Summary]: ",
        completion.choices[0].message.content,
    )

Java
package com.volcengine.ark.runtime;

import com.volcengine.ark.runtime.model.completion.chat.*;
import com.volcengine.ark.runtime.service.ArkService;
import okhttp3.ConnectionPool;
import okhttp3.Dispatcher;

import java.util.*;
import java.util.concurrent.TimeUnit;

public class FunctionCallChatCompletionsExample {
    static String apiKey = System.getenv("ARK_API_KEY");
    static ConnectionPool connectionPool = new ConnectionPool(5, 1, TimeUnit.SECONDS);
    static Dispatcher dispatcher = new Dispatcher();
    static ArkService service = ArkService.builder().dispatcher(dispatcher).connectionPool(connectionPool).baseUrl("${BASE_URL}").apiKey(apiKey).build();

    public static void main(String[] args) {
        System.out.println("\n----- function call mulstiple rounds request -----");
        final List<ChatMessage> messages = new ArrayList<>();
        final ChatMessage userMessage = ChatMessage.builder().role(ChatMessageRole.USER).content("北京今天天气如何?").build();
        messages.add(userMessage);

        final List<ChatTool> tools = Arrays.asList(
                new ChatTool(
                        "function",
                        new ChatFunction.Builder()
                                .name("get_current_weather")
                                .description("获取给定地点的天气")
                                .parameters(new Weather(
                                        "object",
                                        new HashMap<String, Object>() {{
                                            put("location", new HashMap<String, String>() {{
                                                put("type", "string");
                                                put("description", "T地点的位置信息,比如北京");
                                            }});
                                            put("unit", new HashMap<String, Object>() {{
                                                put("type", "string");
                                                put("description", "枚举值有celsius、fahrenheit");
                                            }});
                                        }},
                                        Collections.singletonList("location")
                                ))
                                .build()
                )
        );

        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                .model("${YOUR_ENDPOINT_ID}")
                .messages(messages)
                .tools(tools)
                .build();

        ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0);
        messages.add(choice.getMessage());
        choice.getMessage().getToolCalls().forEach(
                toolCall -> {
                messages.add(ChatMessage.builder().role(ChatMessageRole.TOOL).toolCallId(toolCall.getId()).content("北京天气晴,24~30度").name(toolCall.getFunction().getName()).build());
        });
        ChatCompletionRequest chatCompletionRequest2 = ChatCompletionRequest.builder()
                .model("${YOUR_ENDPOINT_ID}")
                .messages(messages)
                .build();

        service.createChatCompletion(chatCompletionRequest2).getChoices().forEach(System.out::println);

        // shutdown service
        service.shutdownExecutor();
    }

    public static class Weather {
        public String type;
        public Map<String, Object> properties;
        public List<String> required;

        public Weather(String type, Map<String, Object> properties, List<String> required) {
            this.type = type;
            this.properties = properties;
            this.required = required;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public Map<String, Object> getProperties() {
            return properties;
        }

        public void setProperties(Map<String, Object> properties) {
            this.properties = properties;
        }

        public List<String> getRequired() {
            return required;
        }

        public void setRequired(List<String> required) {
            this.required = required;
        }
    }

}

响应示例

========== Round 1 ==========
user: 先查询北京的天气,如果是晴天微信发给Alan,否则发给Peter...

assistant [FC Response]:
name=GetCurrentWeather, args={"location": "\u5317\u4eac"} 
[elpase=2.607 s]
========== Round 2 ==========
tool: 北京今天20~24度,天气:阵雨。...

assistant [FC Response]:
name=SendMessage, args={"content": "\u4eca\u5929\u5317\u4eac\u7684\u5929\u6c14", "receiver": "Peter"} 
[elpase=3.492 s]
========== Round 3 ==========
tool: 成功发送微信消息至Peter...

assistant [Final Answer]:
好的,请问还有什么可以帮助您? 
[elpase=0.659 s]

配置与优化

工具调用异常处理

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

import json_repair

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

需求澄清

需求澄清(确认需求),不依赖与FC,可独立使用。
可在 System prompt 中加入:

如果用户没有提供足够的信息来调用函数,请继续提问以确保收集到了足够的信息。
在调用函数之前,你必须总结用户的描述并向用户提供总结,询问他们是否需要进行任何修改。
......

在函数的 description 中加入:

函数参数除了提取a和b, 还应要求用户提供c、d、e、f和其他相关细节。

或在系统提示中加入参数校验逻辑,当模型生成的参数缺失时,引导模型重新生成完整的参数。

如果用户提供的信息缺少工具所需要的必填参数,你需要进一步追问让用户提供更多信息。

实施指南

新业务接入

  1. 明确业务需求。
    1. 如果对Function Calling依赖较重(如流水线、插件较多)、需要串行调用等场景,建议用Function Calling分支模型。
    2. 对Function Calling依赖较轻、无串行调用,对模型通用能力更为依赖的业务,建议用pro即可。
  2. 准备评测集,在FC模型测试,看看准确率,以及业务预期上线准确率。
  3. 常规调优手段:
    1. Functions的params、description等字段准确填写。 System prompt中不用再重复介绍函数,(可选)可以描述在何种情况下调用某函数
    • 优化函数、参数的描述, 明确不同函数的边界情况; 避免歧义;增加示例等。
    • 对模型进行sft(建议至少50条数据,模型越多、参数越多、情况越多,则所需要的数据越多),见上文【精调功能】。
  4. 速度优化手段:
    • 对于简单无歧义的函数或参数,适当精简输入输出内容。
    • 对于system prompt、fc list固定的流量,可购买模型单元,并联系我们开启Cache。
    • 对于实时性要求较高的场景,可以人工拆分为2个阶段,1阶段调用LLM做函数选择,2阶段调用LLM做该函数内的参数提取。 可测试2个阶段分别用pro/lite的表现; 该方法可能会降低效果。

prompt最佳实践

原则: Treat LLM as a kid

  1. 能不用大模型完成的任务,就不要调用大模型,尽量代码完成。
  2. 和任务无关的信息,避免输入,避免信息干扰。

类别

问题

错误示例

改正后示例

函数

命名不规范、描述不规范

{
   "type": "function",
    "function": {
        "name": "GPT1",
        "description": "新建日程",
     }
}
{
   "type": "function",
    "function": {
        "name": "CreateEvent",
        "description": "当需要为用户新建日程时,此工具将创建日程,并返回日程ID",
     }
}

参数

避免不必要的复杂格式(或嵌套)

{
    "time": {
        "type": "object",
        "description": "事件时间",
        "properties": {
            "timestamp": {
                "description": "事件时间"
            }
        }
    }
}
{
    "time": {
        "type": "string",
        "description": "事件时间",
    }
}

避免固定值

{
    "time": {
        "type": "object",
        "description": "事件时间",
        "properties": {
            "timestamp": {
                "description": "固定传2024-01-01即可"
            }
        }
    }
}

既然参数值固定,删去该参数,由代码处理。

业务流程

尽量缩短LLM调用轮次

System prompt:

你正在与用户Alan沟通,你需要先查询用户ID,再通过ID创建日程……

System prompt:

你正在与用户Alan(ID=abc123)沟通,你可以通过ID创建日程……

歧义消解

System prompt:

可以通过ID查找用户,并获得用户的日程ID

这里两个ID未明确,模型可能会混用

System prompt:

每个用户具有唯一的用户ID;每个日程有对应的日程ID,两者独立的ID。
可以通过用户ID查找用户,并获得用户的所有日程ID

精调优化

如果 Function Calling 效果未达预期,可通过精调(SFT)提升模型表现。
详情请参见创建模型精调任务

精调场景

  • 提高工具选择的准确性,确保模型在合适的时机调用正确的工具。
  • 优化参数提取能力,使模型能够准确解析用户需求并生成正确的工具入参。
  • 改善结果总结质量,让模型能够更自然、准确地总结工具执行结果。

精调样本格式

{
  "messages": [
    {
      "role": "system",
      "content": "你是豆包AI助手"
    },
    {
      "role": "user",
      "content": "把北京的天气发给李四"
    },
    {
      "role": "assistant",
      "content": "",
      "tool_calls": [
        {
          "type": "function",
          "function": {
            "name": "get_current_weather",
            "arguments": "{\"location\": \"北京\"}"
          }
        }
      ],
      "loss_weight": 1.0
    },
    {
      "role": "tool",
      "content": "北京今天晴,25摄氏度"
    },
    {
      "role": "assistant",
      "content": "",
      "tool_calls": [
        {
          "type": "function",
          "function": {
            "name": "send_message",
            "arguments": "{\"receiver\": \"李四\", \"content\": \"北京今天晴,25摄氏度\"}"
          }
        }
      ],
      "loss_weight": 1.0
    },
    {
      "role": "tool",
      "content": "消息发送成功"
    },
    {
      "role": "assistant",
      "content": "已将北京的天气信息发送给李四"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_current_weather",
        "description": "获取指定地点的天气",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {"type": "string", "description": "地点的位置信息"}
          },
          "required": ["location"]
        }
      }
    }
  ]
}