Function Calling 功能允许智能体识别用户话语中的特定意图,并调用外部函数以完成大模型无法独立完成的任务,例如查询实时信息(如天气)、控制设备(如调节音量)等。本教程将以实现一个“调节音量”的功能为例,指导客户端开发者为硬件对话智能体集成 Function Calling 功能。
Function Calling 功能适用于以下大模型(LLM):
一次完整的 Function Calling 交互包含以下阶段:
adjust_volume 函数),然后向智能体中添加该函数的描述。adjust_volume 函数。{ "action": "increase", "step": 10 })的指令。adjust_volume("increase", 10) 函数,并获取到返回值,例如 "当前音量已调至 50%"。在客户端实现 Function Calling,开发者的主要工作是监听并解析服务端下发的函数调用指令,执行本地函数,并将结果返回给服务端。
您可以通过低负载(WebSocket)或高质量(RTC)两种方案来实现,这两种方案在指令下发流程、消息数据格式和函数结果处理方式上存在差异,具体如下表所示。下文将分别介绍两种方案的实现细节。
对比项 | 低负载方案 (WebSocket) | 高质量方案 (RTC) |
|---|---|---|
指令下发流程 | 分两步完成:先发送不含参数的通知消息,再发送包含完整参数的指令消息。 | 直接发送包含函数名和参数的完整指令消息。 |
消息数据格式 | 标准 JSON 格式。 | 自定义二进制格式,头部 8 字节为消息类型(4 字节)和消息长度(4 字节,大端序),JSON Payload 部分为标准 JSON 字符串。 |
整个实现过程主要依赖以下个客户端 API:
on_volc_message_data(接收指令)
这是一个回调函数。当服务端下发工具调用指令时,SDK 会通过此回调将指令内容传递给客户端。
void (*on_volc_message_data)(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr, void* user_data);
volc_send_message(发送消息)
这是一个主动调用接口。客户端通过此接口,可以向服务端发送自定义消息,例如返回函数执行结果。
int volc_send_message(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr);
volc_send_text_to_agent(发送文本,仅限高质量方案)
这是一个主动调用接口。客户端通过此接口,可以向服务端直接发送文本消息,例如播报安抚语的 TTS 请求。
您已经在客户端程序中集成了硬件对话智能体。
更多信息,请参见官方 SDK 集成指引和通过标准协议自定义接入。
在开始具体的编码工作前,您需要完成以下通用的准备环节。
您需要在客户端应用程序中,预先定义好希望智能体调用的本地函数。
在本文的示例中,我们假设您已定义了 adjust_volume(const char* action, int step) 函数,它能根据传入的指令调节设备音量。
无论是创建新智能体还是编辑现有智能体,您都需要在控制台中为设备所关联的智能体开启 Function Calling 功能,并对 adjust_volume 函数进行详细描述。
登录硬件对话智能体控制台。
在左侧导航栏,单击 智能体管理。
找到您的产品或设备当前关联的智能体,单击智能体卡片上的 编辑。
在左侧导航栏,单击 高级配置。
找到 Function Calling 区域,创建一个工具。
配置项 | 说明 |
|---|---|
工具名称 | 设置工具的唯一标识符,用于程序调用。 |
工具描述 | 详细说明该工具的功能、使用场景和作用,以便 LLM 能够理解并正确调用。 |
工具类型 | 定义工具的参数类型。可选项:string、number、boolean、integer、object、array。 |
工具参数 | 定义工具的参数结构,包含 参数名称、参数类型、参数描述、枚举、必填 信息。 |
低负载方案通过 WebSocket 实现。当识别到工具调用意图时,服务端会先发送一条不含参数的“通知”消息,再发送一条包含完整参数的“指令”消息。
您需要在 on_volc_message_data 回调中,通过判断消息的 type 字段来区分并处理两种不同的消息。
接收工具调用通知消息。
当 LLM 识别出工具调用意图后,您会先收到一条 type 为 conversation.item.created 的通知消息,其中 item.type 为 function_call。收到此消息后,您可以选择性地播报安抚语(见步骤 2(可选):播报安抚语)。
以下是 on_volc_message_data 回调中 data_ptr 参数的示例数据:
{ "event_id": "event_KJIMveRvR", "type": "conversation.item.created", "item": { "type": "function_call", "status": "in_progress", "call_id": "call_fluqrfiiea80klxscs9gf8ut", "name": "adjust_volume", "object": "realtime.item" } }
接收工具调用指令消息。
随后,您会收到一条 type 为 response.function_call_arguments.done 的指令消息。收到此消息后,您必须解析其 name 字段获取函数名,解析 arguments 字段获取函数参数,并执行相应的本地函数。
以下是 on_volc_message_data 回调中 data_ptr 参数的示例数据:
{ "event_id": "event_ssSMDeRvR", "type": "response.function_call_arguments.done", "response_id": "resp_round_1", "item_id": "", "output_index": 0, "call_id": "call_fluqrfiiea80klxscs9gf8ut", "arguments": "{\"action\": \"increase\", \"step\": 10}", "name": "adjust_volume" }
arguments字段的值是一个字符串,而不是一个 JSON 对象。您在客户端解析时,需要先获取这个字符串,然后再对这个字符串本身进行一次 JSON 解析,才能得到内部的参数(如action)。
由于函数可能耗费较长时间,为了避免用户在等待过程中感到焦虑或不舒适,您可以在收到步骤 1 的通知消息后,调用 volc_send_message 接口,发送一个 conversation.item.create 事件来播报安抚语。通过播放安抚语,您可以安抚用户的情绪,提升用户体验。
说明
若在播放安抚语过程中收到返回的 Function Calling 最终答复,会在安抚语播放完毕后,继续播放 Function Calling 最终答复。
以下是 volc_send_message 请求中 data_ptr 参数的示例数据:
{ "event_id": "event_XLLz0egvR", "type": "conversation.item.create", "item": { "type": "message", "role": "user", "content": [ { "type": "input_tts", "text": "好的,正在调节音量" } ], "interrupt_mode": 2 } }
interrupt_mode用于控制文本播报内容的优先级。取值:
1:高优先级。智能体会终止当前交互,直接播放传入的文本内容。2:中优先级。智能体会在当前交互结束后,播放传入的文本内容。3:低优先级。如果此时智能体正在交互,智能体会直直接丢弃传入的文本内容。如果未在交互,智能体会播放传入的文本内容。
函数执行完成后,您需要调用 volc_send_message 接口,通过 conversation.item.create 事件将执行结果返回给服务端。
方式 1:直接播报(不经 LLM 处理)
如果函数返回的结果是一句完整的话(如“音量已调大”),您可以将其包装成 input_tts 类型的消息发送给服务端进行 TTS 处理,处理结果由客户端直接播报。这种方式适用于回复无需润色或其他二次处理、回复的句子较长等场景。
以下是 volc_send_message 请求中 data_ptr 参数的示例数据:
{ "event_id": "event_XLLz0egvR", "type": "conversation.item.create", "item": { "type": "message", "role": "user", "content": [ { "type": "input_tts", "text": "音量已调大" } ], "interrupt_mode": 1 } }
方式 2:经 LLM 处理后播报
如果希望通过 LLM 对函数返回的结果进行总结、润色,您可将其包装成 input_text 类型的消息发送回服务端。服务端会先通过 LLM 生成更自然流畅的回复(如“音量已调至 50%”)再进行 TTS 处理,处理结果返回给客户端进行播报。
以下是 volc_send_message 请求中 data_ptr 参数的示例数据:
{ "event_id": "event_XLLz0egvR", "type": "conversation.item.create", "item": { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "当前音量 50%" } ], "interrupt_mode": 1 } }
完成上述步骤后,用户会收到智能体根据函数执行结果生成的音频回复消息。
高质量方案通过 RTC SDK 实现。当识别到工具调用意图时,服务端会将函数名和参数合并在一条消息中,一次性发送给客户端。
所有 RTC 的自定义消息(data_ptr)都采用的是“8 字节头部 + JSON Payload”的二进制格式。8 字节头部的具体结构为:
头部之后是 JSON 文本内容(即 JSON Payload)。下文将提供 C 语言代码示例,演示如何解析和构造自定义消息。
您需要在 on_volc_message_data 回调中,通过判断消息头部的 4 字节标识符来确定是否为工具调用指令。工具调用指令的标识符为 tool。
对于识别出的工具调用指令,您可以选择直接执行函数,或者先播放安抚语(见步骤 2(可选):播报安抚语),再执行函数。
说明
建议采用异步方式执行函数,避免阻塞回调线程,影响其他功能的正常运行。
JSON Payload 示例(消息头部的 4 字节标识符为 tool)
{ "subscriber_user_id" : "", "tool_calls" : [ { "function" : { "arguments" : "{\\"action\\": \\"increase\\", \\"step\\": 10}", "name" : "adjust_volume" }, "id" : "call_py400kek0e3pczrqdxgnb3lo", "type" : "function" } ] }
C 语言代码示例 - 解析工具调用指令
static void __on_volc_message_data(volc_engine_t handle, const void *message, size_t size, volc_message_info_t *info_ptr, void *user_data){ cJSON *tool_obj_arr = cJSON_GetObjectItem(root, "tool_calls"); cJSON *obji = NULL; cJSON_ArrayForEach(obji, tool_obj_arr) { cJSON *id_obj = cJSON_GetObjectItem(obji, "id"); cJSON *function_obj = cJSON_GetObjectItem(obji, "function"); if (id_obj && function_obj) { cJSON *arguments_obj = cJSON_GetObjectItem(function_obj, "arguments"); cJSON *name_obj = cJSON_GetObjectItem(function_obj, "name"); char *arguments_json_str = cJSON_GetStringValue(arguments_obj); const char *func_name = cJSON_GetStringValue(name_obj); const char *func_id = cJSON_GetStringValue(id_obj); if (strcmp(func_name, "adjust_volume") == 0 && arguments_json_str) { // 调节音量并返回 } } static char message_buffer[4096]; if (size > 8 && size < 4096) { memcpy(message_buffer, message, size); message_buffer[size] = 0; message_buffer[size + 1] = 0; cJSON *root = cJSON_Parse(message_buffer + 8); if (root != NULL) { if (message_buffer[0] == 't' && message_buffer[1] == 'o' && message_buffer[2] == 'o' && message_buffer[3] == 'l') { // 生产代码中需要异步,避免回调线程阻塞 __on_function_calling_message_received(root); } } } }
在收到指令并开始执行耗时操作前,您可以主动调用 volc_send_text_to_agent 接口,发送一条 TTS 消息来播报安抚语。通过播放安抚语,您可以安抚用户的情绪,提升用户体验。
说明
若在播放安抚语过程中收到返回的 Function Calling 最终答复,会在安抚语播放完毕后,继续播放 Function Calling 最终答复。
C 语言代码示例 - 发送安抚语
// 场景:在执行耗时操作(如搜索音乐、联网查询)前,先安抚用户等待 volc_send_text_to_agent( engine, "好的,正在调节音量", // 安抚语内容 VOLC_AGENT_TYPE_TTS, // 指定 TTS 进行语音播报 2 // 中优先级,智能体在当前交互结束后,播放传入的文本内容 );
函数执行完成后,您可以通过两种方式将执行结果返回给服务端,一种是直接播报(不经过 LLM 处理),一种是经 LLM 处理后播报。
方式 1:直接播报(不经 LLM 处理)
适用于回复无需润色或其他二次处理、回复的句子较长等场景。
与播放安抚语类似,您可以主动调用 volc_send_text_to_agent 接口,发送一条 TTS 消息来播报函数返回结果。
C 语言代码示例 - 直接播报
volc_send_text_to_agent( engine, "音量已调大", // 执行结果文本 VOLC_AGENT_TYPE_TTS, // 指定 TTS 进行语音播报 2 // 中优先级,智能体在当前交互结束后,播放传入的文本内容 );
方式 2:经 LLM 处理后播报
适用于需要通过 LLM 对函数返回的结果进行总结、润色,生成更自然流畅的回复。
您需要调用 volc_send_message 接口,将执行结果返回给服务端。
JSON Payload 示例(消息头部的 4 字节标识符为 func)
{ "ToolCallID":"call_py400kek0e3pczrqdxgnb3lo", "Content":"当前音量 50%" }
C 语言代码示例 - LLM 处理后播报
cJSON *fc_obj = cJSON_CreateObject(); cJSON_AddStringToObject(fc_obj, "ToolCallID", func_id); cJSON_AddStringToObject(fc_obj, "Content", "当前音量 50%"); char *json_string = cJSON_Print(fc_obj); static char fc_message_buffer[256] = {'f', 'u', 'n', 'c'}; int json_str_len = strlen(json_string); fc_message_buffer[4] = (json_str_len >> 24) & 0xff; fc_message_buffer[5] = (json_str_len >> 16) & 0xff; fc_message_buffer[6] = (json_str_len >> 8) & 0xff; fc_message_buffer[7] = (json_str_len >> 0) & 0xff; memcpy(fc_message_buffer + 8, json_string, json_str_len); cJSON_Delete(fc_obj); volc_message_info_t info = {0}; info.is_binary = 1; volc_send_message(conv_service.engine, fc_message_buffer, json_str_len + 8, &info);
完成上述步骤后,用户会收到智能体根据函数执行结果生成的音频回复消息。
您可以通过设计系统提示词(Prompt),为大模型设定明确的规则,以限制函数调用的触发时机,避免在非必要的情况下执行函数调用,从而节约成本和系统资源。
示例:
为避免 LLM 对用户的日常闲聊(如“你叫什么名字?”)也尝试调用函数,您可以为智能体设置如下 Prompt:
你是一个智能助理,负责控制智能家居设备和查询信息。 你只能在收到以下意图时才能调用对应的 Function Call: 1. 当用户明确表示要“调节音量”时,调用 `adjust_volume` 函数。 2. 当用户明确表示要“调节屏幕亮度”时,调用 `adjust_brightness` 函数。 3. 当用户查询“天气”、“时间”或“新闻”时,调用 `search_online_info` 函数。 对于其他所有与上述意图无关的用户指令或闲聊,你都不能调用任何 Function Call,并应像一个普通 AI 助手一样直接回答。
对于执行耗时较长(如网络请求)的函数,等待过程可能会让用户感到焦虑。因此,在调用这类函数前,主动播报一句安抚语(如“请稍等,正在为您查询”)是提升用户体验的关键。
建议使用时机:
说明
根据经验,对于任何预计执行时间可能超过 2 秒的函数,都建议您在调用前先播报安抚语。