Open in
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
- Užsiprenumeruojate
telephony.incoming(telefono) arbaweb.incoming(valdiklio) įvykį. Abu yra blokuojantys žiniatinklio kabliukai: ThunderPhone laukia iki 10 sekundžių jūsų atsakymo prieš tęsdamas skambutį. - ThunderPhone siunčia jums
{call_id, from_number, to_number}(valdiklio sesijose vietoje numerių pateikiami valdikliui būdingi laukai — žr. užklausos schemą). - Jūsų serveris atsako agento konfigūracija (raginimu, balsu, produktu, įrankiais). ThunderPhone naudoja tą konfigūraciją skambučio metu.
- 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į.
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 ...
passimport 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:
| Laukas | Tipas | Aprašymas |
|---|---|---|
prompt | eilutė (privaloma) | Sistemos raginimas agentui |
voice | eilutė (privaloma) | Balso ID iš GET /v1/voices |
product | eilutė | Numatytoji reikšmė yra spark |
background_track | eilutė | null | Aplinkos garso ID |
acknowledgement_prompt_mode | eilutė | auto arba manual (tik Storm su patvirtinimu) |
acknowledgement_prompt | eilutė | Privaloma, kai režimas yra manual |
tools | masyvas | Į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
Tikslios užklausų ir atsakymų schemos, įskaitant kiekvieną konfigūracijos raktą.
Vieną kartą teisingai nustatykite HMAC; naudokite visur.
Derinkite dinaminį maršrutizavimą su agentui skirtais įrankiais.
Pakartotiniai bandymai, tvarka, laiko limitai.