Open in
函数工具
为您的 AI 智能体提供可在对话过程中调用外部 API 的函数工具——获取客户数据、预约、更新记录——并使用类型化参数。
函数工具允许您的 AI 智能体在通话期间调用外部 API。您可以使用它们查询客户数据、检查可用性、预约服务,或执行后端支持的任何操作。
工作原理
- 使用架构定义工具(工具接受哪些参数)
- 提供
endpoint配置(ThunderPhone 在何处调用您的 API)——或者省略该配置,以便通过组织 webhook 接收工具调用 - 在通话期间,AI 会根据对话决定何时使用工具
- ThunderPhone 使用工具参数调用您的端点
- 您的 API 响应会返回给 AI,以继续对话
| 功能 | 运行位置 | 设置 |
|---|---|---|
| 内置工具 | ThunderPhone | 提示词指令;部分工具还需要智能体设置 |
| 应用连接 | ThunderPhone 和已连接的提供商 | 连接账户并附加已批准的操作 |
| API 连接和函数工具 | 您的 HTTP API | 定义端点和架构,或通过 webhook 接收函数调用 |
| MCP 服务器 | 远程 MCP 服务器 | 添加服务器、发现其工具并将其附加到智能体 |
工具架构
每个工具都遵循以下结构:
{
"type": "function",
"function": {
"name": "search_appointments",
"description": "Find available appointment slots for a given date",
"parameters": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "Date in YYYY-MM-DD format"
},
"service": {
"type": "string",
"description": "Type of service (e.g., 'consultation', 'follow-up')"
}
},
"required": ["date"]
}
},
"endpoint": {
"url": "https://api.example.com/appointments/search",
"method": "POST",
"headers": {
"X-Api-Key": "your-api-key"
}
},
"timeout": 120
}工具配置
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
timeout | number | 否 | 最大执行时间,单位为秒(默认值:20,最大值:180) |
函数定义
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
name | string | 是 | 工具的唯一标识符 |
description | string | 是 | 向 AI 说明何时使用此工具 |
parameters | object | 是 | 工具参数的 JSON Schema |
端点配置
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
url | string | 是 | 您的 API 端点 URL |
method | string | 否 | HTTP 方法(默认值:POST) |
headers | object | 否 | 要包含的自定义请求头 |
两种调用路径
您的服务器接收哪种请求取决于工具是否具有
endpoint:
包含 endpoint 的工具 | 不包含 endpoint 的工具 | |
|---|---|---|
| 请求发送至 | 直接发送至 endpoint.url | 您的组织的旧版 Webhook URL |
| 请求正文 | 所选请求格式(默认仅包含参数) | telephony.tool / web.tool 封装 |
| 请求头 | 您的 endpoint.headers + X-ThunderPhone-Call-ID + X-ThunderPhone-Signature-V2 + X-ThunderPhone-Signature | Content-Type + X-ThunderPhone-Signature-V2 + X-ThunderPhone-Signature |
| 签名密钥 | 组织 Webhook 密钥 | 组织 Webhook 密钥 |
两种路径都是阻塞式的——AI 会在句子中途等待
结果。默认超时为 20 秒;您可以设置工具顶层的
timeout 以允许更长的执行时间,最长可达平台规定的 180 秒
上限。请保持处理程序快速响应。您可以混合使用:
对于组织具有 webhook URL 的通话,具有 endpoint 的工具会被
直接调用,其余工具则回退至 webhook。
直接端点调用
当 AI 调用具有 endpoint 的工具时,ThunderPhone 会向您的 URL 发送
请求:
请求头
POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature-V2: t=1767225600,v1=5f2c...
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key您在 endpoint.headers 中配置的自定义请求头始终会原样包含,此外还会包含以下以 ThunderPhone 命名空间开头的请求头:
X-ThunderPhone-Signature-V2—— 基于方法、路径、查询参数和请求体计算的带时间戳 HMAC-SHA256,使用您的 组织 Webhook 密钥 作为密钥。请验证此签名并拒绝超过 5 分钟的请求;请参阅验证 Webhook 签名X-ThunderPhone-Signature—— 对完整请求体字节进行的旧版 HMAC-SHA256,使用相同密钥,仍会发送以兼容现有验证器X-ThunderPhone-Call-ID—— 当前通话 ID
除非您的 endpoint.headers 对其进行了覆盖,否则会设置
Content-Type: application/json —— 自定义 Content-Type 优先。
请求格式
每个 API 连接都有 request_format 设置。当导入的工具需要继续调用为该平台构建的端点时,请选择 vapi 或 retell。新的和现有的连接默认使用 standard。
对于 POST / PUT / PATCH,ThunderPhone 会以规范方式序列化所选封装格式(键已排序、分隔符紧凑)。两种签名都覆盖这些精确字节。
标准
standard 仅发送工具参数,不含任何包装。这是默认设置,并会逐字节保留 ThunderPhone 的原始请求正文:
{"date":"2025-01-02","service":"consultation"}Vapi
vapi 会按照 Vapi 文档中定义的格式发送 Vapi 的 tool-calls 服务器消息。call.id 是 ThunderPhone 通话 ID。工具调用 id 对每次调用都是唯一的,并会重复用于匹配端点的结果。该调用同时出现在 toolCallList 和 toolWithToolCallList 中,其中 function.arguments 为 JSON 对象:
{
"message": {
"type": "tool-calls",
"timestamp": 1725000000123,
"call": { "id": "987654321" },
"toolCallList": [
{
"id": "call_00112233445566778899aabbccddeeff",
"type": "function",
"function": {
"name": "search_appointments",
"arguments": { "date": "2025-01-02", "service": "consultation" }
}
}
],
"toolWithToolCallList": [
{
"type": "function",
"function": { "name": "search_appointments" },
"toolCall": {
"id": "call_00112233445566778899aabbccddeeff",
"type": "function",
"function": {
"name": "search_appointments",
"arguments": { "date": "2025-01-02", "service": "consultation" }
}
}
}
]
}
}不会发送 assistant、artifact 等仅适用于 Vapi 的字段,以及完整的 call 对象。
返回包含匹配工具调用 ID 的 Vapi 响应封装:
{
"results": [
{
"toolCallId": "call_00112233445566778899aabbccddeeff",
"result": "{\"available_slots\":[\"9:00 AM\",\"2:00 PM\"]}"
}
]
}ThunderPhone 会将未封装的 result 字符串提供给 AI。为兼容已返回 JSON 值的端点,它也接受 JSON 值。如果以 error 替代 result,该错误会作为工具错误返回给 AI。如果端点返回格式错误或不匹配的 Vapi 封装,ThunderPhone 会记录警告并回退到其标准响应解析,而不会导致通话失败。
Retell
retell 会发送 Retell 自定义函数文档中定义的请求:
{
"name": "search_appointments",
"call": { "call_id": "987654321" },
"args": { "date": "2025-01-02", "service": "consultation" }
}call 对象仅包含 call_id。ThunderPhone 不会包含在分派时无法如实提供的 Retell 专有字段,例如 agent_id、transcript、transcript_object、metadata 或动态变量。Retell 接受任何响应正文;ThunderPhone 会使用标准响应处理将其透传。
对于 GET / DELETE,无论 request_format 为何,参数都会作为查询参数发送,且正文为空。V2 签名覆盖实际发送的查询字符串;旧版签名基于空字节字符串计算。请参阅验证 Webhook 签名。
响应
返回包含工具结果的 JSON 响应:
{
"available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
"timezone": "America/Los_Angeles"
}响应会被格式化并提供给 AI 以继续
对话。非 JSON 响应会包装为 {"data": "<text>"};
超时和连接失败会作为错误报告给 AI,因此
智能体可以致歉并继续处理,而不会停滞。
网络钩子模式分发
不具有 endpoint 的工具会作为已签名的 telephony.tool(电话调用)或 web.tool
(网络调用)请求,分发到您组织的旧版
网络钩子 URL。与执行后发送到网络钩子端点的审计通知
不同,此请求就是
执行本身 —— 您的 HTTP 响应即为工具结果。
{
"type": "telephony.tool",
"data": {
"call_id": 987654321,
"tool_name": "search_appointments",
"arguments": { "date": "2026-04-21" },
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}web.tool 使用 origin_domain,而非 from_number /
to_number。以 JSON 格式返回工具结果——与直接端点调用使用相同的响应
契约。该请求使用组织的 webhook 密钥签名,并像其他所有 webhook 一样包含两个签名标头。
签名验证
直接工具调用的签名方式与 Webhook 相同,使用您组织的 Webhook 密钥:
X-ThunderPhone-Signature-V2对发送时的时间戳、方法、路径和查询参数,以及精确的请求正文字节进行签名。拒绝时间戳与您的时钟相差超过 5 分钟的请求。X-ThunderPhone-Signature(旧版)仅对请求正文字节进行签名;对于GET/DELETE,签名内容为空字节字符串。该标头仍会照常发送,未作更改。
SDK 辅助函数会校验 V2 标头,包括 5 分钟时间窗口:
import json
from fastapi import FastAPI, HTTPException, Request
from thunderphone import verify_signature
app = FastAPI()
@app.post("/appointments/search")
async def search_appointments(request: Request):
body = await request.body()
query = request.scope.get("query_string", b"").decode("latin-1")
path = request.url.path + (f"?{query}" if query else "")
if not verify_signature(
request.headers.get("X-ThunderPhone-Signature-V2"),
secret=WEBHOOK_SECRET,
method=request.method,
path=path,
body=body,
):
raise HTTPException(status_code=401)
data = json.loads(body)
date = data["date"]
# Look up availability
slots = await get_available_slots(date)
return {"available_slots": slots}import express from "express";
import { verifySignature } from "@thunderphone/sdk";
app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
const ok = verifySignature({
header: req.header('X-ThunderPhone-Signature-V2'),
secret: WEBHOOK_SECRET,
method: req.method,
path: req.originalUrl,
body: req.body,
});
if (!ok) return res.status(401).send('Invalid signature');
const { date, service } = JSON.parse(req.body);
// Look up availability
const slots = getAvailableSlots(date, service);
res.json({ available_slots: slots });
});完整示例——签名字符串、完整示例、无需 SDK 的代码、GET / DELETE 情况以及未设置密钥时的注意事项——请参阅验证 Webhook 签名。
示例:完整预约流程
以下是一组用于完整预约系统的工具:
{
"tools": [
{
"type": "function",
"function": {
"name": "search_appointments",
"description": "Find available appointment slots",
"parameters": {
"type": "object",
"properties": {
"date": { "type": "string", "description": "YYYY-MM-DD" },
"service": { "type": "string" }
},
"required": ["date"]
}
},
"endpoint": {
"url": "https://api.example.com/appointments/search",
"method": "POST",
"headers": { "X-Api-Key": "key" }
}
},
{
"type": "function",
"function": {
"name": "book_appointment",
"description": "Book an appointment at a specific time",
"parameters": {
"type": "object",
"properties": {
"date": { "type": "string", "description": "YYYY-MM-DD" },
"time": { "type": "string", "description": "HH:MM format" },
"customer_name": { "type": "string" },
"customer_phone": { "type": "string" }
},
"required": ["date", "time", "customer_name"]
}
},
"endpoint": {
"url": "https://api.example.com/appointments/book",
"method": "POST",
"headers": { "X-Api-Key": "key" }
}
},
{
"type": "function",
"function": {
"name": "cancel_appointment",
"description": "Cancel an existing appointment",
"parameters": {
"type": "object",
"properties": {
"confirmation_number": { "type": "string" }
},
"required": ["confirmation_number"]
}
},
"endpoint": {
"url": "https://api.example.com/appointments/cancel",
"method": "POST",
"headers": { "X-Api-Key": "key" }
}
}
]
}最佳实践
编写清晰的描述
description 字段可帮助 AI 理解何时使用该工具。请明确说明它的作用及适用场景。
妥善处理错误
返回 AI 能够理解的错误消息:使用 {"error": "No slots available for that date"},而非通用的 500 错误。
保持响应简洁
仅返回 AI 继续对话所需的信息。较大的负载会降低响应速度。
谨慎使用必填字段
仅在确有必要时将字段标记为 required。AI 会在调用工具前向用户询问必填信息。