You need to enable JavaScript to run this app.
文档中心
实时音视频

实时音视频

复制全文
下载 pdf
开发指南
函数调用 Function Calling
复制全文
下载 pdf
函数调用 Function Calling
Function Calling 能使 AI 识别用户说话中的特定意图,并调用您已实现的外部函数,以完成特定任务。例如:查询实时信息(如天气)、操作业务系统、控制 App 硬件功能(播放音乐、关灯、调整音量)等。​
使用限制​
模型要求:火山方舟模型或第三方大模型,且需具备 Function Calling 能力。​
注意​
  • 支持的方舟模型,请参见模型列表。不建议使用 doubao-seed-1.6-thinking,该模型会强制开启思考模式且不可关闭,可能造成较高时延。​
  • Doubao 1.5 及之前系列的模型,不建议同时使用视觉理解和 Function Calling。​
  • 如需保留 Function Calling 历史,当前仅火山方舟模型、已开启视觉理解的 OpenAI-like 模型,以及支持 OpenAI-like tool_calls / tool_call_id 消息格式的第三方模型支持。不支持的模型即使配置 LLMConfig.EnableToolCallHistory,也不会保留工具调用历史。​
工作流程​
Function Calling 的工作流程涉及以下几个核心角色:​
  • LLM:负责理解用户意图并决策调用哪个函数。​
  • 系统服务:作为技术底座,负责接收您的请求、调用 LLM,并将 LLM 的决策(如函数调用指令)通过回调传递给您的应用程序。​
  • 您的应用程序:负责实现和执行具体的本地函数(如查询天气)。​
整个流程的核心是 “LLM 决策,您的代码执行”。具体实现过程如下:​
​Image​​
  1. 准备函数并声明:​
  • 在客户端应用程序中,定义好具体的本地函数。调用 StartVoiceChat 接口时,向模型声明您已定义的函数。​
  1. 决策与执行:​
  1. 用户向 AI 提出需要外部能力才能完成的请求(例如:“帮我查询上海的天气”)。​
  1. 大模型识别用户意图,决策需要调用哪个函数,并生成相应的调用指令。​
  1. 系统会通过回调下发指令:​
  1. 先发送 “函数调用通知”:提前告知您有函数即将被调用。收到 “调用通知” 后,您可以立即请求播放一段安抚语(如 “正在查询,请稍候”),以优化用户等待体验。​
  1. 再发送 “函数调用指令”:告知需要执行的函数与入参(如 location: "上海")。​
  1. 您的应用程序根据 “调用指令” 执行本地函数,获取执行结果并回传给系统。​
  1. AI 回复:​
  • 系统在收到上报的函数执行结果后如何向用户播报,会根据您选择的回传方式进行处理:​
  • 方式 1(直接播放):​
  • 将函数执行结果直接通过 TTS 转化为语音播放。适用于结果无需润色、或回复内容较长的场景。​
  • 方式 2(经 LLM 处理后播放):​
  • 将函数执行结果返回给 LLM,由模型进行总结、润色后再通过 TTS 播放。适用于需要对回复进行二次处理的场景。​
准备工作​
准备函数​
您需要在业务应用程序中,实现需要 AI 调用的具体函数的逻辑。Function Calling 的作用是让模型 “决策” 调用哪个函数并传入什么参数,而函数的执行则完全由您的程序负责。​
例如,如果您想实现天气查询,您需要自己实现一个能查询并返回天气信息的函数。​
确定实现方式​
客户端和服务端可以同时配置,两者回调相互独立。您可以根据实际需求,选择在任一端处理回调逻辑,并忽略另一端即可。​
​
​
对比项​
服务端实现​
客户端实现​
适用场景​
  • 函数部署在独立的服务器上。​
  • 需要统一管理和验证函数调用的场景。​
  • 函数在客户端本地,如控制 App 功能、访问设备硬件。​
  • 希望避免搭建额外后端服务的场景。​
开发要求​
提供一个公网可访问的 URL 地址,用于接收回调数据。​
在客户端代码中监听 RTC SDK 的回调,并按照文档解析 / 构造特定的二进制数据包。​
通信方式​
系统通过公网 HTTP POST 请求下发指令到您提供的 URL。​
系统通过 RTC 的数据通道将指令发送给您的客户端 App。​
​
若为第三方大模型​
如果您使用第三方 LLM 接入,无论流式或非流式,最终响应必须包含 tool_calls 字段,且 finish_reason 必须设为 "tool_calls"。​
流式响应示例:​
​
{​
"choices": [{​
"index": 0,​
"delta": {​
"role": "assistant",​
"tool_calls": [{​
"id": "call_unique_id_123",​
"type": "function",​
"function": {​
"name": "make_phone_call",​
"arguments": "{\"phone_number\": \"13800138000\"}"​
}​
}]​
},​
"finish_reason": "tool_calls"​
}]​
}​
​
通过服务端实现​
步骤 1:声明函数并配置回调​
在调用 StartVoiceChat 接口时,需提供以下配置:​
  • LLMConfig.Tools:声明您需要模型调用的所有函数。请确保此处声明的每一个函数,您都已在自己的业务代码中实现了对应的逻辑。其中,parameters 需严格遵守JSON Schema规范,详细可参见工具函数参数构造规范。​
  • LLMConfig.EnableToolCallHistory:可选。设为 true 后,若某轮触发 Function Calling,系统会将该轮的工具调用指令和工具返回结果随该轮历史一起保留,便于模型基于此前工具调用结果继续对话;默认值为 false。​
  • FunctionCallingConfig:​
  • ServerMessageUrl:您的后端服务地址,用于接收函数调用指令。必须是公网可访问的 URL,且能正确处理无 Content-Type 的 HTTP POST 请求。​
说明​
对于嵌入式/IoT 等场景,如需在 FC 回调中识别对应的会话或设备,可将业务标识(如 TaskId、DeviceId)作为 query 参数附加在 ServerMessageUrl 中,例如:"ServerMessageUrl": "https://your-server.com/fc_callback?taskId=yourTaskId&deviceId=xxx",服务端直接从请求 URL 的 query 参数中读取即可。​
  • ServerMessageSignature: 自定义认证密钥。此字符串会在回调请求中原样返回,供您在服务端进行来源合法性校验。​
以下示例展示了如何声明一个名为 get_current_weather 的天气查询函数,并配置回调接收地址。完整参数参见接口文档 StartVoiceChat(2025-06-01)。​
​
// 声明您需要调用的所有函数(确保所有函数均已在业务代码中实现)​
{​
"LLMConfig": {​
"Tools": [​
{​
"type": "function",​
"function": {​
"name": "get_current_weather",​
"description": "获取一个或多个指定城市的实时天气信息",​
"parameters": {​
"type": "object",​
"properties": {​
"location": {​
"type": "string",​
"description": "需要查询天气的城市名,例如:'上海市'"​
},​
"unit": {​
"type": "string",​
"enum": ["celsius", "fahrenheit"],​
"description": "温度单位,枚举值必须为数组格式"​
}​
},​
"required": ["location"]​
}​
}​
}​
],​
"EnableParallelToolCalls": true, // 可选,设为 true 时支持模型在一轮对话中同时触发多个工具调用(并行工具调用);默认为 false,仅执行第一个被触发的工具​
"EnableToolCallHistory": true // 可选,设为 true 时工具调用指令和工具返回结果会随触发工具调用的轮次一起写入后续请求;默认为 false​
},​
"FunctionCallingConfig": {​
"ServerMessageUrl": "https://your-backend.com/api/fc_callback", ​
"ServerMessageSignature": "your_custom_signature"​
}​
}​
​
步骤 2:处理回调消息​
当 AI 识别到工具调用意图时,您配置服务器 URL 地址会先后收到 2 条跟 FC 相关的消息,您需要通过判断消息体的 Type 字段来区分并获取这两种消息。​
  1. (可选)播放安抚语。​
  • 函数执行需要一定时间。在收到函数调用通知后,AI 回复前,可插播安抚语以提升用户体验。​
  1. 获取并解析函数调用通知。​
  • 当 AI 识别到工具调用意图时,你会先收到函数调用通知消息(Type 为 information)。通知消息示例如下:​
  • ​
    {​
    "Message": "{\"event_type\":\"function_calling\",\"function\":\"get_current_weather\",\"tool_call_id\":\"call_yx8****\",\"response_id\":\"021765**********e711d\"}",​
    "Signature": "test_fc_***", //即你在步骤1中配置的 ServerMessageSignature​
    "Type": "information", //固定值,表示这是一个函数调用通知​
    "RoomID": "ChatRoom01", //通话房间 ID​
    "TaskID": "73bc1a7e-73ef-470f-91****",​
    "TaskType": "voiceChat", //固定值,表示 AI 对话任务​
    "AppId": "661e****543cf" // 应用的 AppId​
    }​
    ​
  • Message 是一个转义 JSON 字符串,使用前需要解析。具体内容如下:​
  • ​
    字段名​
    类型​
    描述​
    event_type​
    String​
    固定为 function_calling,表示触发了 Function Calling 函数调用。​
    function​
    String​
    触发的函数名称。​
    tool_call_id​
    String​
    本次 Function Calling 任务 ID。​
    response_id​
    String​
    触发本次 Function Calling 任务返回的响应 ID。如本次任务发生错误,可用于故障排查。​
    ​
  1. 播放安抚语。​
  • 调用 UpdateVoiceChat 接口播放安抚语,以提升用户体验。​
如果安抚语还未播放完,函数结果就已返回,系统会等待安抚语结束后再播报结果。​
  • ​
    {​
    "AppId": "661e****543cf",​
    "RoomId": "ChatRoom01",​
    "TaskId": "task1",​
    "Command": "ExternalTextToSpeech", // 固定为 ExternalTextToSpeech​
    "Message": "正在为您查询,请耐心等待。", // 内容结尾必须有标点​
    "InterruptMode": 3 // 固定为 3,安抚语播放优先级较低,会被后续的函数结果打断。​
    }​
    ​
  1. 获取并解析函数调用指令。​
  • 紧接着您会收到函数调用指令消息(Type 为 tool_calls), 您需要先解析外层 Message 字段,再解析内层 arguments 字段,来获取最终的函数名称和执行参数。​
  • 消息示例如下:​
  • ​
    {​
    "Message": "[{\"id\":\"call_yx8****\",\"type\":\"function\",\"function\":{\"name\":\"get_current_weather\",\"arguments\":\"{\\\"location\\\":\\\"上海\\\",\\\"unit\\\":\\\"摄氏度\\\"}\"}}]",​
    "Signature": "cn_test_***", //即你在步骤1中配置的 ServerMessageSignature​
    "Type": "tool_calls", //固定值,表示此次消息为函数指令消息。​
    "RoomID": "ChatRoom01", //通话房间 ID​
    "TaskID": "73bc1a7e-73ef-470f-91****", // 仅用于标识单次 FC 任务,便于内部问题排查,不可用于 UpdateVoiceChat​
    "TaskType": "voiceChat", //固定值,表示 AI 对话任务​
    "AppId": "661e****543cf" // 应用的 AppId​
    }​
    ​
  • Message 字段是一个转义 JSON 字符串,包含内容如下:​
  • ​
  • ​
    字段名​
    类型​
    描述​
    id​
    String​
    本次 Function Calling 任务 ID,与返回的函数调用通知信息中的 message.tool_call_id 的值一致。​
    type​
    String​
    Function Calling 所调用的工具类型,固定为 function,表示函数调用。​
    function​
    Object​
    调用函数详情。格式示例:​
    arguments 是一个 JSON 字符串,您需要进行二次解析才能获取参数对象。​
    ​
    {​
    "name": "get_current_weather", //函数名称​
    "arguments": {"location":"上海","unit":"摄氏度"} // 函数入参​
    }​
    ​
    ​
步骤 3:执行外部函数​
获取调用指令信息后,您需要在自己的应用程序中实现:​
  1. 路由到函数:根据 function.name 的值(例如 "get_current_weather"),调用您在自己代码中已实现的相应函数。​
  1. 传递参数:将上一步解析出的 function.arguments 中的参数作为入参传递给您的函数。​
  1. 获取返回值:执行您的函数,并获取其返回结果,这个结果将在下一步中回传。​
步骤 4:回传函数结果​
调用 UpdateVoiceChat 接口将函数执行结果回传给系统,并由 AI 播放。​
完整参数及说明,参见对应的接口文档:UpdateVoiceChat(2025-06-01)、UpdateVoiceChat(2024-12-01)。​
​
方式一:传给 LLM 处理后再播放
方式二:直接播放(不经 LLM 处理)
​
将函数返回的原始数据传回给 LLM 处理后再播放。适用于需要总结、润色等二次处理场景。​
Message 中的 ToolCallID 的值必须与您在步骤 3:执行外部函数收到的 tool_calls 指令中的 id 字段值完全一致。​
​
{​
"AppId": "YOUR_AppId", // 与 StartVoiceChat 的一致​
"RoomId": "YOUR_RoomId", // 与 StartVoiceChat 的一致​
"TaskId": "YOUR_TaskId", // 与 StartVoiceChat 的一致​
"Command": "function", // 必须为 function​
"Message": "{\\\\"ToolCallID\\\\":\\\\"call_yx8****\\\\",\\\\"Content\\\\":\\\\"上海天气是晴天,25摄氏度\\\\"}" // 将函数结果作为 JSON 转义字符串传入​
}​
​
LLM 可能会生成回复:“上海今天天气不错,是个大晴天,温度是 25 摄氏度。”​
​
通过客户端实现​
通过客户端(如App、Web页面)处理 Function Calling 的通知和指令,并在本地执行函数后,将结果发送回系统。​
步骤 1:声明函数​
调用 StartVoiceChat 接口,配置 LLMConfig.Tools 字段来声明您要调用的函数(确保所有函数均已在业务代码中实现)。其中,parameters 需严格遵守JSON Schema规范,详细可参见工具函数参数构造规范。​
如需让模型在后续轮次继续参考此前的工具调用结果,可同时配置 LLMConfig.EnableToolCallHistory: true。该参数默认值为 false。​
以下示例展示了如何声明一个名为 get_current_weather 的天气查询函数。完整参数参见接口文档 StartVoiceChat(2025-06-01)。​
​
// 声明您需要调用的所有函数(确保所有函数均已在业务代码中实现)​
{​
"LLMConfig": {​
"Tools": [​
{​
"type": "function",​
"function": {​
"name": "get_current_weather",​
"description": "获取一个或多个指定城市的实时天气信息",​
"parameters": {​
"type": "object",​
"properties": {​
"location": {​
"type": "string",​
"description": "需要查询天气的城市名,例如:'上海市'"​
},​
"unit": {​
"type": "string",​
"enum": ["celsius", "fahrenheit"],​
"description": "温度单位,枚举值必须为数组格式"​
}​
},​
"required": ["location"]​
}​
}​
}​
],​
"EnableParallelToolCalls": true, // 可选,设为 true 时支持模型在一轮对话中同时触发多个工具调用(并行工具调用);默认为 false,仅执行第一个被触发的工具​
"EnableToolCallHistory": true // 可选,设为 true 时工具调用指令和工具返回结果会随触发工具调用的轮次一起写入后续请求;默认为 false​
}​
}​
​
步骤 2:监听回调并处理​
  1. 在客户端监听 onRoomBinaryMessageReceived 回调。​
  • 当 AI 识别到工具调用意图时,该回调会被先后触发两次,您会收到两条与 Function Calling 相关的二进制消息。您需要解析消息头部的 magic number 来识别并处理这两条消息。​
  1. (可选)播放安抚语。​
  • 函数执行需要一定时间。在收到函数调用通知后,AI 回复前,可插播安抚语以提升用户体验。​
  1. 获取函数调用通知。​
  • 当 AI 识别到工具调用意图时,你先会收到函数调用通知消息 message(magic number 为 info),该消息为二进制格式,收到消息后需解析该字段。message 格式和解析示例如下:​
  • ​Image​​
  • ​
  • ​
    字段名​
    类型​
    描述​
    magic number​
    String​
    消息格式,固定为 info,表示此次消息为通知函数被触发调用。​
    length​
    String​
    通知消息长度,单位为 bytes。存放方式为大端序。​
    info_message​
    String​
    函数调用通知的详细信息。内容如下:​
    ​
    {​
    "event_type": "function_calling", // 事件类型,固定为此值​
    "function": "get_current_weather", // 被触发的函数名,与您在 StartVoiceChat 中定义的名称一致​
    "tool_call_id": "call_yx8****", // 本次函数调用的唯一ID,后续回传结果时需要用到​
    "response_id": "resp_abcde" // 本次响应的唯一ID,可用于问题排查​
    }​
    ​
    ​
  • message 解析示例:​
  • ​
    C++
    Java
    TypeScript
    ​
    ​
    // 消息体 JSON 对应的结构体​
    struct FCInfoMessage {​
    std::string event_type;​
    std::string function;​
    std::string tool_call_id;​
    std::string response_id;​
    };​
    ​
    ​
    // 拆包函数,专门用于 "info" 消息:只做协议校验 + 提取 JSON 字符串​
    bool UnpackInfoMessage(const uint8_t *message, int size, std::string& json_payload) {​
    const int kHeaderSize = 8;​
    if (size < kHeaderSize) {​
    return false;​
    }​
    ​
    ​
    // 校验 magic number "info" (0x696E666F)​
    uint32_t magic = ntohl(*reinterpret_cast<const uint32_t*>(message));​
    if (magic != 0x696E666F) {​
    return false;​
    }​
    ​
    ​
    // 读取 length(大端)​
    uint32_t length = ntohl(*reinterpret_cast<const uint32_t*>(message + 4));​
    if (size - kHeaderSize != static_cast<int>(length)) {​
    return false;​
    }​
    ​
    ​
    if (length > 0) {​
    json_payload.assign(reinterpret_cast<const char*>(message) + kHeaderSize, length);​
    } else {​
    json_payload.clear();​
    }​
    return true;​
    }​
    ​
    ​
    // 对外的解析接口:解析出结构体 FCInfoMessage​
    bool ParseInfoMessage(const uint8_t* message, int size, FCInfoMessage& out_info) {​
    std::string json_str;​
    if (!UnpackInfoMessage(message, size, json_str)) {​
    return false;​
    }​
    ​
    ​
    // 解析 JSON,容错一点,解析失败直接返回 false​
    nlohmann::json json_data = nlohmann::json::parse(json_str, /*callback=*/nullptr, /*allow_exceptions=*/false);​
    if (json_data.is_discarded()) {​
    return false;​
    }​
    ​
    ​
    out_info.event_type = json_data.value("event_type", "");​
    out_info.function = json_data.value("function", "");​
    out_info.tool_call_id = json_data.value("tool_call_id", "");​
    out_info.response_id = json_data.value("response_id", "");​
    ​
    ​
    return true;​
    }​
    ​
    ​
    // 在 onRoomBinaryMessageReceived 回调中​
    void onRoomBinaryMessageReceived(const char* uid, int size, const uint8_t* message) {​
    FCInfoMessage info_msg;​
    if (!ParseInfoMessage(message, size, info_msg)) {​
    // 不是合法的 info 消息,直接忽略,可以继续检查是否为 tool 等其他消息​
    return;​
    }​
    ​
    // 解析成功!现在可以安全地使用 info_msg 里的字段了。​
    std::cout << "Function Call Notification for: " << info_msg.function << std::endl;​
    std::cout << "ToolCallID: " << info_msg.tool_call_id << std::endl;​
    ​
    // 可在这里添加自己的业务逻辑​
    // 例如收到函数调用通知后,调用 `sendUserBinaryMessage` 函数,让 AI 播放安抚语。​
    // 具体实现,参见下方文档。​
    }​
    ​
    ​
  1. (可选)播放安抚语。​
  • 函数执行需要一定时间。在收到函数调用通知后,可立即调用 sendUserBinaryMessage 接口发送一条控制消息,让 AI 播放安抚语。​
如果安抚语还未播放完,函数结果就已返回,系统会等待安抚语结束后再播报结果。​
  • userId:AI 的标识 ID(对应 AgentConfig.UserId)。​
  • buffer:传入安抚语内容。需遵循以下格式:​
  • ​Image​​
  • ​
  • ​
    参数名​
    类型​
    描述​
    magic number​
    String​
    消息格式标识符,当前场景消息格式固定为 ctrl,用于标识该消息为控制消息。​
    length​
    String​
    安抚语长度,单位为 bytes。存放方式为大端序。​
    control_message​
    String​
    安抚语配置信息,JSON 字符串格式,内容如下:​
    ​
    {​
    "Command": "ExternalTextToSpeech", // 指令类型,固定值,表示文本转语音​
    "Message": "请稍等,正在查询。", // 需要播放的安抚语文本​
    "InterruptMode": 3 // 必须为 3,表示如果此时 AI 正在交互,系统会直接丢弃传入的安抚语文本。如果未在交互,AI 才会将安抚语送入TTS 播放​
    }​
    ​
    ​
  • 安抚语传入示例:​
  • ​
    C++
    Java
    TypeScript
    ​
    ​
    // 发送文本转语音(TTS)指令,让 AI 播报指定的安抚语内容。​
    void sendTTSMessage(const std::string &uid, const std::string& content) {​
    nlohmann::json json_data;​
    json_data["Command"] = "ExternalTextToSpeech";​
    json_data["Message"] = content;​
    json_data["InterruptMode"] = 3; ​
    sendUserBinaryMessage(uid, json_data.dump());​
    }​
    ​
    void buildBinaryMessage(const std::string& magic_number, const std::string& message, size_t& binary_message_length, std::shared_ptr<uint8_t[]>& binary_message) {​
    auto magic_number_length = magic_number.size();​
    auto message_length = message.size();​
    ​
    binary_message_length = magic_number_length + 4 + message_length;​
    binary_message = std::shared_ptr<uint8_t[]>(new uint8_t[binary_message_length]);​
    std::memcpy(binary_message.get(), magic_number.data(), magic_number_length);​
    binary_message[magic_number_length] = static_cast<uint8_t>((message_length >> 24) & 0xFF);​
    binary_message[magic_number_length+1] = static_cast<uint8_t>((message_length >> 16) & 0xFF);​
    binary_message[magic_number_length+2] = static_cast<uint8_t>((message_length >> 8) & 0xFF);​
    binary_message[magic_number_length+3] = static_cast<uint8_t>(message_length & 0xFF);​
    std::memcpy(binary_message.get()+magic_number_length+4, message.data(), message_length);​
    }​
    ​
    int sendUserBinaryMessage(const std::string &uid, const std::string& message) {​
    if (rtcRoom_ != nullptr)​
    {​
    size_t length = 0;​
    std::shared_ptr<uint8_t[]> binary_message = nullptr;​
    buildBinaryMessage("ctrl", message, length, binary_message);​
    return rtcRoom_->sendUserBinaryMessage(uid.c_str(), static_cast<int>(length), binary_message.get());​
    }​
    return -1;​
    }​
    ​
    ​
  1. 获取函数调用指令。​
  • 收到调用通知后,您会收到函数调用指令消息(magic number 为 tool)。您需要解析 message,获取执行函数所需的具体参数。message 格式与解析示例如下:​
  • message 格式:​
  • ​Image​​
  • ​
  • ​
    字段名​
    类型​
    描述​
    magic number​
    String​
    消息格式,固定为 tool,表示此次消息为函数调用指令。​
    length​
    String​
    调用指令消息长度,单位为 bytes。存放方式为大端序。​
    tool_message​
    String​
    调用指令消息内容。格式如下:​
    ​
    {​
    "tool_calls": [​
    {​
    "id": "call_***45", // 调用 ID,后续响应需通过此 ID 关联回执​
    "type": "function", // 固定值,表示这是一次函数调用​
    "function": {​
    "name": "get_current_weather", // 要调用的函数名称​
    "arguments": "{\"location\":\"上海\"}" // 执行函数所需的参数,JSON 字符串(注意转义)​
    }​
    }​
    ]​
    }​
    ​
    ​
  • message 解析示例:​
  • ​
    C++
    Java
    TypeScript
    ​
    ​
    // 定义与JSON结构对应的C++结构体​
    struct FunctionCall {​
    std::string name;​
    std::string arguments; // 注意:这是一个字符串化的JSON​
    };​
    ​
    struct ToolCall {​
    std::string id;​
    std::string type;​
    FunctionCall function;​
    };​
    ​
    ​
    bool ParseToolMessage(const uint8_t* message, int size, std::vector<ToolCall>& out_calls) {​
    // 1. 二进制解包​
    const int kHeaderSize = 8;​
    if (size < kHeaderSize) return false;​
    ​
    uint32_t magic = ntohl(*reinterpret_cast<const uint32_t*>(message));​
    if (magic != 0x746F6F6C) return false; // "tool"​
    ​
    uint32_t length = ntohl(*reinterpret_cast<const uint32_t*>(message + 4));​
    if (size - kHeaderSize != static_cast<int>(length)) return false;​
    ​
    std::string json_str;​
    if (length > 0) {​
    json_str.assign(reinterpret_cast<const char*>(message) + kHeaderSize, length);​
    }​
    ​
    // 2. 解析JSON​
    nlohmann::json json_data = nlohmann::json::parse(json_str, nullptr, false);​
    if (json_data.is_discarded() || !json_data.contains("tool_calls") || !json_data["tool_calls"].is_array()) return false;​
    ​
    out_calls.clear();​
    ​
    // 3. 遍历并填充结构体​
    for (const auto& item : json_data["tool_calls"]) {​
    ToolCall tool_call;​
    tool_call.id = item.value("id", "");​
    tool_call.type = item.value("type", "");​
    ​
    if (item.contains("function") && item["function"].is_object()) {​
    const auto& func_json = item["function"];​
    tool_call.function.name = func_json.value("name", "");​
    tool_call.function.arguments = func_json.value("arguments", "{}");​
    }​
    out_calls.push_back(tool_call);​
    }​
    return true;​
    }​
    ​
    ​
步骤 3:执行外部函数​
在成功解析 tool 消息后,您需要在自己的业务逻辑中实现:​
  1. 路由到函数:根据 function.name 的值(例如 "get_current_weather"),调用您在自己代码中已实现的相应函数。​
  1. 传递参数:将上一步解析出的 function.arguments 中的参数作为入参传递给您的函数。​
  1. 获取返回值:执行您的函数,并获取其返回结果,这个结果将在下一步中回传。​
注意​
function.arguments 字段本身是一个字符串,其内容是一个 JSON 对象。在调用您的本地函数之前,您必须对这个字符串再进行一次 JSON 解析,才能获得真正的参数。​
步骤 4:回传函数结果​
调用 sendUserBinaryMessage 接口将函数执行结果回传给系统,并由 AI 播放。​
方式 1:经 LLM 处理后播放​
将函数返回的原始数据传回给 LLM 处理后再播放。适用于需要总结、润色等二次处理场景。​
调用 sendUserBinaryMessage 接口,传入函数的执行结果交由 LLM 处理。​
  • userId:AI 的标识 ID(对应 AgentConfig.UserId)。​
  • buffer:​
  • 大小限制:该接口默认单次发送的二进制消息内容包大小为 1 KB,如需更大的包传递(最大不可超过 64 KB),需在控制台启用 RTS 高级功能。​
注意​
若在 SDK 运行期间开启了该功能,则必须重新初始化引擎,配置才能生效。​
  • 消息格式:​
  • ​Image​​
  • ​
  • ​
    参数名​
    类型​
    描述​
    magic number​
    String​
    消息格式,固定为 func。​
    length​
    String​
    函数调用的结果信息长度,单位为 bytes。存放方式为大端序。​
    Function_Response​
    String​
    函数调用的结果信息。​
    ​
  • 你可参看以下示例代码,回传函数调用的结果信息并播放:​
  • ​
    C++
    Java
    TypeScript
    ​
    ​
    // 依赖 nlohmann/json 库进行 JSON 操作​
    // https://github.com/nlohmann/json​
    #include "json.hpp"​
    ​
    // 为方便使用​
    using json = nlohmann::json;​
    ​
    // --- 配置项 ---​
    // 请将此处的 "RobotMan_" 替换为您为 AI 的实际 UserId​
    const std::string kAgentUserId = "RobotMan_";​
    ​
    ​
    // 发送二进制消息的函数​
    // 您需要根据实际的 SDK 接口进行适配​
    void sendUserBinaryMessage(const std::string& userId, const std::vector<uint8_t>& message);​
    ​
    ​
    /**​
    * @brief 将 JSON 字符串打包成 'func' 类型的 TLV 字节流​
    */​
    std::vector<uint8_t> stringToTLV(const std::string& content) {​
    const std::string type = "func";​
    size_t type_len = type.length();​
    size_t content_len = content.length();​
    size_t total_len = type_len + 4 + content_len;​
    ​
    std::vector<uint8_t> buffer(total_len);​
    ​
    // 1. 写入 Type ("func")​
    std::memcpy(buffer.data(), type.data(), type_len);​
    ​
    // 2. 写入 Length (大端序)​
    buffer[type_len + 0] = (content_len >> 24) & 0xFF;​
    buffer[type_len + 1] = (content_len >> 16) & 0xFF;​
    buffer[type_len + 2] = (content_len >> 8) & 0xFF;​
    buffer[type_len + 3] = content_len & 0xFF;​
    ​
    // 3. 写入 Value (JSON 字符串)​
    if (!content.empty()) {​
    std::memcpy(buffer.data() + type_len + 4, content.data(), content_len);​
    }​
    ​
    return buffer;​
    }​
    ​
    ​
    /**​
    * @brief 发送函数调用的响应​
    */​
    void SendFunctionResponse(const std::string& toolCallID, const std::string& content) {​
    json payload;​
    payload["ToolCallID"] = toolCallID;​
    payload["Content"] = content;​
    ​
    std::string jsonString = payload.dump();​
    ​
    std::vector<uint8_t> binaryMessage = stringToTLV(jsonString);​
    ​
    // 将二进制消息发送给指定的 AI​
    sendUserBinaryMessage(kAgentUserId, binaryMessage);​
    }​
    ​
    ​
方式 2:直接播放(不经 LLM 处理)​
将函数原始结果直接通过 TTS 播放。适用于返回结果无需处理或回复的句子较长需降低延迟等场景。​
调用 sendUserBinaryMessage 接口,设置以下参数传入函数调用的结果并播放:​
注意​
若回复的句子较长,建议先自行将长句切分为多个短句,再分次调用 sendUserBinaryMessage 接口,降低延时。​
  • 切句建议:首个分句按逗号(,)、分号(;)切分;后续分句按句号(。)、问号(?)等完整句子标点切分。避免切分数字中的小数点。​
  • 切句示例:今天的天气晴朗,、温度10-29摄氏度,北风三级。、未来七天,天气会保持晴朗。​
  • userId:AI 的标识 ID(对应 AgentConfig.UserId)。​
  • buffer:​
  • 大小限制:该接口默认单次发送的二进制消息内容包大小为 1 KB,如需更大的包传递(最大不可超过 64 KB),需在控制台启用 RTS 高级功能。​
注意​
若在 SDK 运行期间开启了该功能,则必须重新初始化引擎,配置才能生效。​
  • 消息格式:​
  • ​Image​​
​
​
参数名​
类型​
描述​
magic number​
String​
消息格式标识符,当前场景消息格式固定为 ctrl,用于标识该消息为控制消息。​
length​
String​
函数调用的结果信息长度,单位为 bytes。存放方式为大端序。​
control_message​
String​
函数调用的结果配置信息。具体内容格式如下:​
​
{​
"Command": "ExternalTextToSpeech", // 固定值​
"Message": "这里是函数调用的结果信息", // 传入函数调用的结果​
"InterruptMode": 2 //必须为 2,直接播放函数结果,确保其不会被打断。​
}​
​
​
传入示例:​
​
C++
Java
TypeScript
​
​
// 发送文本转语音(TTS)指令,让 AI 播报函数调用的结果信息​
void sendTTSMessage(const std::string &uid, const std::string& content) {​
nlohmann::json json_data;​
json_data["Command"] = "ExternalTextToSpeech";​
json_data["Message"] = content;​
json_data["InterruptMode"] = 2; ​
sendUserBinaryMessage(uid, json_data.dump());​
}​
​
void buildBinaryMessage(const std::string& magic_number, const std::string& message, size_t& binary_message_length, std::shared_ptr<uint8_t[]>& binary_message) {​
auto magic_number_length = magic_number.size();​
auto message_length = message.size();​
​
binary_message_length = magic_number_length + 4 + message_length;​
binary_message = std::shared_ptr<uint8_t[]>(new uint8_t[binary_message_length]);​
std::memcpy(binary_message.get(), magic_number.data(), magic_number_length);​
binary_message[magic_number_length] = static_cast<uint8_t>((message_length >> 24) & 0xFF);​
binary_message[magic_number_length+1] = static_cast<uint8_t>((message_length >> 16) & 0xFF);​
binary_message[magic_number_length+2] = static_cast<uint8_t>((message_length >> 8) & 0xFF);​
binary_message[magic_number_length+3] = static_cast<uint8_t>(message_length & 0xFF);​
std::memcpy(binary_message.get()+magic_number_length+4, message.data(), message_length);​
}​
​
int sendUserBinaryMessage(const std::string &uid, const std::string& message) {​
if (rtcRoom_ != nullptr)​
{​
size_t length = 0;​
std::shared_ptr<uint8_t[]> binary_message = nullptr;​
buildBinaryMessage("ctrl", message, length, binary_message);​
return rtcRoom_->sendUserBinaryMessage(uid.c_str(), static_cast<int>(length), binary_message.get());​
}​
return -1;​
}​
​
​
进阶使用与实践​
多轮函数调用​
当用户的一句话包含多个有依赖关系的任务时(如“先查会议室,再导航”),大模型会与您的服务进行多轮、串行的交互来逐步完成任务。​
  • 典型示例​
  • 用户提问:“帮我查一下我下一场会议的会议室地点,然后导航到那里。”​
  • 第一轮(获取前提)​
  1. 模型:生成第一个指令,调用 get_next_meeting_info()。​
  1. 您的服务:执行后,必须立即将结果 {"room": "A 座 301 会议室"} 回传给模型。​
  • 第二轮(执行依赖动作)​
  • 3. 模型:收到第一轮的结果“A 座 301 会议室”后,基于此信息发出第二个指令,调用 start_navigation(destination="A 座 301 会议室")。​
  • 4. 您的服务:执行后,再次立即将结果 {"status": "导航已开启"} 回传给模型。​
  • 结束(总结回复)​
  • 模型:确认所有步骤完成,生成最终回复:“到 A 座 301 会议室的导航已经为您开启。”​
  • 实现方法​
  1. 启动会话:调用 StartVoiceChat,并在 Tools 参数中提供所有可能用到的函数。​
  1. 接收并执行指令:您的后端服务会收到一个 tool_calls 指令。​
  1. 执行函数:在您的代码中执行对应的函数。​
  1. 回填结果:将函数的执行结果通过 UpdateVoiceChat 接口回填给大模型服务。​
  1. 循环等待:大模型会根据您回填的结果,决定是生成下一个函数调用指令,还是生成最终的文本回复。您只需循环重复第 2-4 步,直到不再收到 tool_calls 指令为止。​
并行函数调用​
当用户的一句话包含多个互相独立的意图时(如“查天气和查股价”),您可以使用该功能,使大模型能够一次性返回多个函数调用指令,供您并发执行以提升效率。​
注意​
请确保你使用的模型支持该能力。对于方舟模型,仅 doubao-seed-1.6 系列模型支持该功能。​
  • 典型示例​
  • 用户提问:“今天天气怎么样?顺便帮我查一下 A 的股价。”​
  • 模型:识别出两个独立任务,一次性生成两个函数的调用指令:​
  • ​
    [​
    {"name": "get_current_weather", "arguments": {}},​
    {"name": "get_stock_price", "arguments": {"ticker": "A"}}​
    ]​
    ​
  • 您的服务:接收到指令列表后,可以并发执行 get_current_weather 和 get_stock_price 这两个函数。​
  • 实现方法​
  1. 启用并行调用:调用 StartVoiceChat 时,在 LLMConfig 中设置:​
  • EnableParallelToolCalls:设置为 true。​
  • Tools:提供所有可能用到的函数。​
  1. 接收并执行:您的后端服务会收到一个包含多个 tool_calls 对象的指令。遍历该指令列表,并发执行所有函数。​
  1. 回传结果:​
  • 交由 LLM 总结后统一回复:将所有函数的执行结果收集起来,通过一次 UpdateVoiceChat 调用(Command: "function")将结果数组一并回传给大模型。​
  • 直接播报单个结果:任何一个函数执行完毕,就立即调用 UpdateVoiceChat(Command: "ExternalTextToSpeech"),将该函数的结果直接通过 TTS 播放给用户。​
保留工具调用历史​
如果您的场景要求模型在后续轮次继续引用此前的工具执行结果,例如“继续基于刚才查到的天气安排行程”或“根据上一轮函数返回的订单信息继续退款”,可在 StartVoiceChat 请求中将 LLMConfig.EnableToolCallHistory 设置为 true。​
开启后,系统会把工具调用指令和工具返回结果作为所属对话轮次的一部分写入后续上下文,减少模型遗漏已执行结果的问题。若某轮未触发 Function Calling,该轮仍只包含用户提问和最终回复,不会额外产生工具调用历史。​
注意​
  • 该参数仅在已配置 LLMConfig.Tools 并实际触发 Function Calling 时生效。​
  • HistoryLength 仍按对话轮次计数。一次完成的用户提问及其最终回复计为一轮;若中间触发工具调用,工具调用指令和工具返回结果归入该轮,不额外增加 HistoryLength 轮数,并随该轮一起保留和逐出。​
提高 FC 触发准确性​
为避免大模型在不相关或意图模糊的场景下(尤其是受历史对话干扰时)误触发函数调用(Function Calling),您可以通过以下方式来提升其触发准确性。​
1. 高质量地声明工具​
通过 StartVoiceChat 接口的 LLMConfig.Tools 声明工具时,提供清晰、准确的工具声明:​
  • name :应使用清晰、符合语义的英文名(如 get_current_weather),而不是 tool_1 或 my_func。​
  • description:用一句话清晰地描述函数的核心用途。​
  • (不推荐) "description": "一个天气工具。"​
  • (推荐) "description": "获取一个指定城市的实时天气信息。"​
  • parameters:清晰说明参数的用途、格式要求,并提供示例。​
  • (不推荐) "description": "地点"​
  • (推荐) "description": "需要查询天气的城市名,例如:'上海市'。请不要包含省份信息。"​
2. 设计系统提示词​
通过系统提示词(LLMConfig.SystemMessages)来为模型指定函数调用条件,从而精准控制模型的工具调用行为。​
参考原则​
  • 意图匹配:仅当用户意图与工具功能明确匹配时才调用。​
  • 主动澄清:当用户意图模糊或缺少必要参数时,应通过提问来澄清,而不是猜测。​
  • 禁止幻觉:严禁调用未声明的工具或为参数捏造数值。​
提示词示例​
您可以参考以下模板,并根据您的业务场景进行调整:​
​
提示词示例
对应 SystemMessages 示例
​
​
## 工具调用规则​
你拥有以下工具,仅在准确识别到用户的意图与下方某个工具功能匹配时,才调用该工具:​
1. `adjust_volume`(调整音量):仅当用户表达了任何想改变设备音量时调用。例如,“我听不清了”、“声音太大了”、“帮我静音”、“调大音量”等。​
2. `get_current_weather`(查询天气):仅当用户表达了想了解气象信息时调用。例如,“明天杭州会下雨吗?”、“帮我查询上海的天气”、“外面冷不冷?”等。​
如果用户意图不匹配上述任何一个工具的触发条件,严禁调用任何工具请直接对话;如果用户意图模糊,请通过提问来澄清意图。​
​
​
3. 避免历史内容干扰​
如果您的业务场景无需参考历史对话,也可将 HistoryLength 设置为 0,以避免历史内容干扰。​
FAQ​
  • Q1:配置了 Function Calling 后,通过 updateVoiceChat/sendUserBinaryMessage 返回给 AI 的函数调用结果信息可以不读出来吗?​
  • 不可以。​
  • Q2:配置了 Function Calling 后,相关配置如何更新?​
  • 需要先调用 StopVoiceChat 接口停止 AI 对话任务,然后再调用 StartVoiceChat 接口重新配置 Function Calling 功能。​
  • **Q3:配置了 Tools,但客户端收不到 **** tool ** 消息?​
  1. 检查 StartVoiceChat 是否开启了 AgentConfig.EnableConversationStateCallback: true。​
  1. 确保自定义 LLM 返回的 JSON 包含 finish_reason: "tool_calls"。​
  1. sendUserBinaryMessage 单次发送的二进制消息内容包大小为 1 KB,如需更大的包传递(最大不可超过 64 KB),检查是否已在控制台启用 RTS 高级功能。​
  • Q4:客户端执行函数,服务端也想收到通知可以吗?​
  • 可以。在 StartVoiceChat 中同时配置 LLMConfig.Tools 和 FunctionCallingConfig.ServerMessageUrl。RTC 会同时向客户端下发二进制消息和向服务端发送 HTTP POST 回调。​
  • Q5:AI 为什么总是重复调用同一个函数?​
  • 通常是由于历史对话上下文干扰。请尝试:​
  1. 调小 HistoryLength。​
  1. 在函数执行结果回传后,通过 Prompt 明确告知 AI 该任务已完成。​
  • Q6:在多轮对话中 FC 被持续错误触发该如何处理?​
  • 若在长周期多轮对话中,遇到 FC 被持续错误触发的情况,可采取以下应对措施:​
  1. 在 tools 的 description 中明确工具触发的强边界(如:“仅在用户询问天气时调用 get_weather 函数”)。​
  1. 在 SystemMessages 中增加前置逻辑条件(如:“只有当用户明确提出 XXX 需求时,才调用工具”)。​
  1. 通过重置上下文(将 LLMConfig.HistoryLength 设为 0)切断历史干扰。​
  1. 过滤 UserPrompts,仅保留当前一轮的核心问询内容,防止历史信息影响判断。​
  • Q7:FC 中 TaskID、TaskId 和 ToolCallID 的区别是什么?​
  • 在 FC 完整流程中,存在三种不同的 ID,请勿混用:​
  • TaskID(UUID 格式,如 73bc1a7e-...):FC 回调请求体中的 TaskID 字段,仅用于标识单次 FC 任务,不可用于 UpdateVoiceChat。​
  • 会话侧 TaskId(用户自定义,如 task_001 / ChatTask01):您在 StartVoiceChat 中设置的任务 ID,UpdateVoiceChat 的 TaskId 参数必须使用此值。请从返回结果或对话状态变化回调中获取并缓存。​
  • ToolCallID(call_* 格式):FC 指令消息中的工具调用唯一 ID,回传函数消息结果时需使用此 ID。​
​
最近更新时间:2026.09.21 16:24:32
这个页面对您有帮助吗?
有用
有用
无用
无用