ThunderPhone 2.0 jau čia.Viską atlikite savarankiškai – nuo 2 ct/min.Skaityti pranešimą

Developer cookbook

Dinaminė konfigūracija kiekvienam skambučiui

Kiekvienam įeinančiam skambučiui atskirai pasirinkite atsiliepiantį agentą arba pakeiskite jo raginimą ir nustatymus, remdamiesi pasirinkta logika jūsų valdomame webhook.

Pagal numatytuosius nustatymus kiekvienam telefono numeriui ir viešajam raktui priskiriamas statinis agentas. Kai reikia tinkinti kiekvienam skambintojui arba kiekvienam lankytojui — nukreipti VIP klientus, įtraukti prisijungusio naudotojo kontekstą, atlikti A/B raginimų bandymus — perjunkite į žiniatinklio kabliuko režimą ir leiskite spręsti savo serveriui.

Kaip tai veikia

  1. Užsiprenumeruojate telephony.incoming (telefono) arba web.incoming (valdiklio) įvykį. Abu yra blokuojantys žiniatinklio kabliukai: ThunderPhone laukia iki 10 sekundžių jūsų atsakymo prieš tęsdamas skambutį.
  2. ThunderPhone siunčia jums {call_id, from_number, to_number} (valdiklio sesijose vietoje numerių pateikiami valdikliui būdingi laukai — žr. užklausos schemą).
  3. Jūsų serveris atsako agento konfigūracija (raginimu, balsu, produktu, įrankiais). ThunderPhone naudoja tą konfigūraciją skambučio metu.
  4. Jei grąžinate {}, baigiasi laukimo laikas arba įvyksta klaida, kaip atsarginis variantas naudojamas statiškai priskirtas agentas. Saugus numatytasis nustatymas.

1. Sukonfigūruokite žiniatinklio kabliuko paskirties vietą

Telefono numeriams užsiprenumeruokite savo galinį tašką įvykiui telephony.incoming:

curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'

Atsakyme pateikiamas vienkartinis secret — išsaugokite jį; naudosite jį parašo tikrinimui.

Valdiklio sesijoms sukurkite viešąjį raktą su mode="webhook", kuriame įtraukta jūsų galinio taško URL:

curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'

Valdiklis siųs POST užklausą į šį URL kiekvienos sesijos pradžioje.

2. Įdiekite tvarkytuvę

Trys pagrindinės taisyklės:

  • Patikrinkite parašą kiekvienoje užklausoje (žr. Tikrinkite webhook parašus). Nepraleiskite to kūrimo aplinkoje – atlikite teisingai vieną kartą ir pakartotinai naudokite.
  • Atsakykite greitai. Dešimt sekundžių yra griežta riba, o kiekviena sekundė skambinančiajam yra tyla. Jei reikia, atlikite duomenų bazės paieškas, bet nekvieskite tolesnių LLM sinchroniškai – jei norite dinamiškai generuoti raginimus, iš anksto apskaičiuokite ir išsaugokite podėlyje.
  • Tvarkingai grįžkite prie numatytojo sprendimo. Bet kokia netikėta būsena turi grąžinti {}, kad statiškai priskirtas agentas apdorotų skambutį.
FastAPI
import hashlib
import hmac
import json
import os
 
from fastapi import FastAPI, HTTPException, Request
 
app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
 
def verify(body: bytes, sig: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig or "")
 
@app.post("/thunderphone/incoming")
async def incoming(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(401)
 
    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default
 
    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs
 
def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
Express
import crypto from "node:crypto";
import express from "express";
 
const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;
 
function verify(body, sig) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return sig &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
 
app.post(
  "/thunderphone/incoming",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
 
    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});
 
    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);

3. Atsakymo schema

Atsakymo turinys tiksliai atitinka įeinančio skambučio atsakymo schemą. Dažniausiai naudojami laukai:

LaukasTipasAprašymas
prompteilutė (privaloma)Sistemos raginimas agentui
voiceeilutė (privaloma)Balso ID iš GET /v1/voices
producteilutėNumatytoji reikšmė yra spark
background_trackeilutė | nullAplinkos garso ID
acknowledgement_prompt_modeeilutėauto arba manual (tik Storm su patvirtinimu)
acknowledgement_prompteilutėPrivaloma, kai režimas yra manual
toolsmasyvasĮterptos funkcijų įrankių schemos — žr. Funkcijų įrankiai

Išsaugokite agentą ir pateikite kintamuosius

Grąžinkite {"agent_id": 12, "variables": {"name": "Ada"}}, kad naudotumėte tos organizacijos išsaugotą agentą su skambučiui skirtais duomenimis. Jo raginime gali būti {{name}} arba {{name|Friend}}. Žiniatinklio kablio kintamieji perrašo užklausos lygio kintamuosius; null naudoja vietos ženklo numatytąją reikšmę arba tuščią tekstą, jei ji nepateikta. Galutinės reikšmės ir neišspręsti pavadinimai rodomi skambučio išsamioje informacijoje ir užbaigimo žiniatinklio kabliuose. Išsaugoto agento atsakymai priima tik agent_id ir variables. Jei yra prompt, atsakymas naudoja įterptąją konfigūraciją ir nepaiso agent_id (įskaitant null arba ne sveikojo skaičiaus metaduomenis); įterptasis raginimas vis tiek turi būti galiojantis. Įterptosios konfigūracijos atsakymai taip pat gali apimti variables. Išsaugoto agento atsakymai naudoja agente įdiegtą A/B paskirstymą tiek telefono, tiek valdiklio skambučiams, tada pateikia kintamuosius. Apribojimus ir sesijos API palaikymą žr. skambučio kintamieji. Blokavimo konfigūracija gaunama iš senstelėjusio telefono numerio / organizacijos URL arba žiniatinklio kablio režimo valdiklio rakto; galinio taško sistemos įeinantys įvykiai yra tik pranešimai.

Modeliai

Prisijungusio naudotojo kontekstas

Žiniatinklio kablio režimo valdikliuose lankytojo puslapis jau žino, kas jis yra. Iškvieskite savo žiniatinklio kablį su užklausos eilutės parametru, kurį valdiklio SDK persiunčia (?customer_id=123), ir suraskite klientą serverio pusėje.

A/B raginimų diegimas

Prieš kurdami tai patys, atkreipkite dėmesį, kad ThunderPhone turi vietinę eksperimentų funkciją (/dashboard/experiments ir agento kūrimo priemonės A/B skirtuką), kuri apibrėžia variantus, paskirsto srautą ir palygina kiekvieno varianto rezultatus — žiniatinklio kablio nereikia.

Jei vis tiek reikia valdymo žiniatinklio kablio pusėje: maišykite call_id → segmentas; pateikite raginimą A reikšmėms 0..49 ir raginimą B reikšmėms 50..99. Įrašykite pasirinktą segmentą savo DB ir vėliau susiekite jį su užbaigto skambučio įvertinimu.

Maršrutizavimas pagal laiką

Darbo valandos → „tiesioginio palaikymo“ agentas; ne darbo valandomis → „priimti žinutę“ agentas. Tvarkyklėje tiesiog perjunkite pagal new Date().getUTCHours().


Tolesni veiksmai