ThunderPhone 2.0 正式上线。全程自助,低至2美分/分钟。查看发布公告

Function Tools

函数工具

为您的 AI 智能体提供可在对话过程中调用外部 API 的函数工具——获取客户数据、预约、更新记录——并使用类型化参数。

函数工具允许您的 AI 智能体在通话期间调用外部 API。您可以使用它们查询客户数据、检查可用性、预约服务,或执行后端支持的任何操作。

工作原理

  1. 使用架构定义工具(工具接受哪些参数)
  2. 提供 endpoint 配置(ThunderPhone 在何处调用您的 API)——或者省略该配置,以便通过组织 webhook 接收工具调用
  3. 在通话期间,AI 会根据对话决定何时使用工具
  4. ThunderPhone 使用工具参数调用您的端点
  5. 您的 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
}

工具配置

字段类型必填描述
timeoutnumber否最大执行时间,单位为秒(默认值:20,最大值:180)

函数定义

字段类型必填描述
namestring是工具的唯一标识符
descriptionstring是向 AI 说明何时使用此工具
parametersobject是工具参数的 JSON Schema

端点配置

字段类型必填描述
urlstring是您的 API 端点 URL
methodstring否HTTP 方法(默认值:POST)
headersobject否要包含的自定义请求头

两种调用路径

您的服务器接收哪种请求取决于工具是否具有 endpoint:

包含 endpoint 的工具不包含 endpoint 的工具
请求发送至直接发送至 endpoint.url您的组织的旧版 Webhook URL
请求正文所选请求格式(默认仅包含参数)telephony.tool / web.tool 封装
请求头您的 endpoint.headers + X-ThunderPhone-Call-ID + X-ThunderPhone-Signature-V2 + X-ThunderPhone-SignatureContent-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 分钟时间窗口:

Python
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}
Node.js
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 会在调用工具前向用户询问必填信息。


相关内容