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

Function Tools

函数工具

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

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

工作原理

  1. 使用架构定义工具(工具接受哪些参数)
  2. 提供 endpoint 配置(ThunderPhone 在哪里调用您的 API)——或者不设置,以便在您的组织 Webhook 上接收工具调用
  3. 在通话期间,AI 会根据对话决定何时使用工具
  4. ThunderPhone 使用工具参数调用您的端点
  5. 您的 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-SignatureContent-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-SHA256
  • X-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 工具对空字节字符串进行签名
Python
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}
Node.js
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 会在调用工具前向用户询问必填信息。


相关内容