函数工具
为您的 AI 智能体配备可在对话过程中调用外部 API 的函数工具——获取客户数据、预约、更新记录——并使用类型化参数。
函数工具允许您的 AI 智能体在通话期间调用外部 API。您可以使用它们查找客户数据、检查可用性、预约或执行您的后端支持的任何操作。
工作原理
- 使用架构定义工具(工具接受哪些参数)
- 提供
endpoint配置(ThunderPhone 在哪里调用您的 API)——或者不设置,以便在您的组织 Webhook 上接收工具调用 - 在通话期间,AI 会根据对话决定何时使用工具
- ThunderPhone 使用工具参数调用您的端点
- 您的 API 响应会反馈给 AI,以继续对话
工具架构
每个工具都遵循以下结构:
{
"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 | 数字 | 否 | 最大执行时间,单位为秒(默认值:20,最大值:180) |
函数定义
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | 字符串 | 是 | 工具的唯一标识符 |
description | 字符串 | 是 | 向 AI 说明何时使用此工具 |
parameters | 对象 | 是 | 工具参数的 JSON Schema |
端点配置
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
url | 字符串 | 是 | 您的 API 端点 URL |
method | 字符串 | 否 | HTTP 方法(默认值:POST) |
headers | 对象 | 否 | 要包含的自定义标头 |
两种调用路径
您的服务器收到哪种请求,取决于工具是否具有
endpoint:
具有 endpoint 的工具 | 不具有 endpoint 的工具 | |
|---|---|---|
| 请求发送位置 | 直接发送到 endpoint.url | 您组织的旧版 Webhook URL |
| 请求体 | 裸工具参数 | telephony.tool / web.tool 封装 |
| 标头 | 您的 endpoint.headers + X-ThunderPhone-Call-ID + X-ThunderPhone-Signature | Content-Type + 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: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key您的 endpoint.headers 中的自定义标头始终会原样包含,此外还会包含两个 ThunderPhone 命名空间标头:
X-ThunderPhone-Signature—— 使用您的 组织 Webhook 密钥 作为密钥,对精确的请求正文 字节计算的 HMAC-SHA256X-ThunderPhone-Call-ID—— 当前通话 ID
除非您的 endpoint.headers 对其进行覆盖,否则会设置 Content-Type: application/json
——自定义 Content-Type 优先。
请求正文
对于 POST / PUT / PATCH,正文仅包含工具
参数(不含包装层),并以规范格式序列化(键排序、分隔符紧凑):
{"date":"2025-01-02","service":"consultation"}对于 GET / DELETE,参数将作为查询参数
发送,正文为空——签名随后基于空
字节字符串计算。请参阅
验证 Webhook 签名。
响应
返回包含工具结果的 JSON 响应:
{
"available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
"timezone": "America/Los_Angeles"
}响应会经过格式化并提供给 AI,以继续
对话。非 JSON 响应会包装为 {"data": "<text>"};
超时和连接失败会作为错误报告给 AI,因此
智能体可以致歉并继续处理,而不会停滞。
Webhook 模式分派
没有 endpoint 的工具会作为已签名的 telephony.tool(电话通话)或 web.tool
(网页通话)请求,分派到您组织的旧版
Webhook URL。与执行后发送到 Webhook 端点的审计通知
不同,此请求就是
执行本身——您的 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 相同:
- 对精确的请求正文字节执行 HMAC-SHA256(规范化 JSON——键已排序,无额外空白)
- 使用您组织的 Webhook 密钥
GET/DELETE工具对空字节字符串进行签名
import hmac
import hashlib
def verify_tool_call(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
@app.post("/appointments/search")
async def search_appointments(request: Request):
body = await request.body()
signature = request.headers.get("X-ThunderPhone-Signature", "")
if not verify_tool_call(body, signature, WEBHOOK_SECRET):
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}app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-thunderphone-signature'] || '';
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (!signature ||
signature.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
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 });
});完整示例——包括空正文情况和未设置密钥时的注意事项——请参阅验证 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 会在调用工具前向用户询问必填信息。