Contact center
CallAgent e și un PBX, și un contact center uman, nu doar o platformă de voice AI, așa că un apel AI poate fi escaladat la o persoană fără să iasă din sistem. Operatorul primește transcrierea, identitatea apelantului și motivul pentru care asistentul a renunțat, iar apelantul nu o ia de la capăt.
Folosește-l când apelurile au uneori nevoie de un om. Dacă nu au niciodată, sari pagina.
Piesele
| Concept | Ce este |
|---|---|
| Extensie | Un număr intern: un operator, un ring group, un IVR sau o căsuță vocală. Dispozitivele SIP se înregistrează pe ea. |
| Coadă | Apelanți care așteaptă, cu o strategie de distribuție, un SLA, anunțuri și overflow. |
| Operator | O persoană cu competențe, limbi, o stare și o sesiune. |
| Competență | O aptitudine cu un nivel de proficiență, folosită la rutare. |
| Ring group | Mai mulți operatori sunați simultan sau în secvență. |
| IVR | Un graf de noduri publicat: prompt-uri, ramuri pe cifre, capturare de variabile, webhook-uri. |
| Condiție de timp | Rutează după orar săptămânal, sărbători și o suprascriere manuală. |
| Disposition | Rezultatul pe care îl notează operatorul în wrap-up. |
| Cameră de conferință | Protejată cu PIN, opțional înregistrată, cu moderator. |
| Căsuță vocală | Mesaje de întâmpinare, transcriere, notificare, retenție. |
Cozi
Fiecare setare, cu valoarea implicită
| Câmp | Implicit | Interval |
|---|---|---|
strategy | fifo | cele nouă de mai jos |
priority | 100 | 0–1000 |
max_wait_seconds | 300 | 0–7200 |
max_waiting_calls | 100 | 1–10000 |
offer_timeout_seconds | 20 | 5–120 |
retry_delay_seconds | 5 | |
max_offers_per_call | 10 | 1–50, și limitat la 25 în momentul ofertei |
avoid_recent_rejector | true | |
wrap_up_seconds | 30 | 0–900 |
service_level_seconds | 20 | 1–600 |
service_level_target_percent | 80 | 1–100 |
announce_position / announce_hold_time | false | |
announce_interval_seconds | 30 | 5–600 |
agent_announce_queue_name | true | |
callback_enabled | false | |
callback_offer_after_seconds | 60 | |
callback_expires_minutes | 240 | |
overflow_when_no_agents | true | |
overflow_after_seconds, overflow_when_waiting_over | null | |
overflow_destination_type / _id / _value | null | cele zece tipuri de destinație |
voicemail_fallback_enabled, ai_fallback_enabled, human_fallback_enabled | false | |
recording_policy | inherit | inherit · disabled · mono · dual_channel |
require_disposition | true | |
skill_strict | false | |
required_language | null | |
is_active | true |
avoid_recent_rejector e o setare mică ce contează: un operator care tocmai a refuzat un apel nu ar trebui să îl primească imediat înapoi.
Callback-ul e opțiunea umană. În loc să ții pe cineva douăsprezece minute, coada îi oferă să îl sune înapoi și îi păstrează locul.
O coadă cu apelanți în așteptare nu poate fi ștearsă — asta e un 409, nu un cascade.
Cele nouă strategii de distribuție
Fiecare strategie sortează operatorii eligibili și oferă apelul primului. penalty al membrului cozii e prima cheie de sortare în toate nouă, deci nivelurile de penalty câștigă mereu înainte să se aplice regula proprie a strategiei în interiorul unui nivel.
strategy | Sortează după |
|---|---|
fifo | De cât timp e operatorul disponibil. Cel mai mult, primul. |
longest_idle | Când a răspuns ultima dată la un apel. Cine nu a răspuns niciodată se sortează primul. |
round_robin | Când i s-a oferit ultima dată un apel. Cursorul de rotație e o coloană în baza de date, deci supraviețuiește unui restart. |
least_calls | Cele mai puține apeluri răspunse în coada asta, apoi cel mai mult de la ultimul răspuns. |
skill_based | Scorul de potrivire a competențelor, descrescător, după ce sunt excluși operatorii care nu îndeplinesc o cerință obligatorie. |
priority | Prioritatea configurată a operatorului în coadă, cea mai mare prima, cu cel mai inactiv ca departajare în interiorul unui nivel. |
vip | Un apelant VIP (is_vip, sau priority > 0) primește operatorii cu prioritatea cea mai mare. Toți ceilalți primesc ordinea nivelurilor inversată, ca operatorii seniori să rămână liberi pentru VIP-uri. |
language_match | Operatorii care vorbesc limba apelantului primii. Cei care nu o vorbesc sunt excluși doar când skill_strict e pornit; altfel se sortează la coadă. |
custom | O specificație declarativă de sortare, în custom_rules. |
custom_rules nu e intenționat un motor de expresii — fără eval, fără webhook, fără nimic pe calea critică ce ar putea bloca un apel:
{
"require": { "min_priority": 5 },
"order": [
{ "by": "skill_score", "dir": "desc" },
{ "by": "idle_seconds", "dir": "desc" }
]
}by acceptă penalty, priority, calls_answered, last_answered, last_offered, idle_seconds, skill_score. Un order gol cade pe penalty, apoi pe cel mai mult de la ultimul răspuns.
Cine e eligibil, de fapt
Înainte să ruleze vreo strategie, un operator trebuie să fie is_active, can_receive_inbound, în starea available și să aibă un heartbeat viu. Starea și prezența sunt separate: un browser care a murit lasă starea pe available, iar verificarea de heartbeat îl exclude din rutare înainte ca reaper-ul să apuce să îl delogheze.
Selecția ține un lock de cache per coadă, iar revendicarea în sine e un compare-and-set: UPDATE operators SET state='ringing' WHERE id = ? AND state='available'. Un singur rând actualizat înseamnă că ai câștigat cursa. Doi workeri nu pot oferi același apel aceluiași operator.
Competențe
O competență are o category (general, language, product, tier, certification), un language_tag opțional și un max_proficiency (implicit 5). Un operator o deține la o proficiency (implicit 3), cu un certified_at și un expires_at opționale — o certificare expirată nu mai contează.
O coadă cere o competență cu minimum_proficiency (implicit 1), is_mandatory (implicit true) și weight (implicit 1).
Potrivirea eșuează închis: un operator căruia îi lipsește o cerință obligatorie e exclus complet, nu punctat mic. Scorul e o sumă ponderată de proficiențe normalizate — fiecare proficiență se împarte întâi la max_proficiency al competenței ei, deci 3-din-5 și 6-din-10 se compară la fel — plus un bonus fix de 2.0 pentru cunoașterea limbii apelantului.
„Vorbește limba" se verifică în trei locuri, în ordine: array-ul languages al operatorului, scalarul lui language, apoi orice competență de categorie language deținută la proficiență peste zero.
Operatori
Stări
Zece stări: offline, available, busy, ringing, on_call, wrap_up, break, training, meeting, custom_pause.
Un operator poate alege șase dintre ele: offline, available, break, training, meeting, custom_pause. ringing, on_call și wrap_up sunt setate de motor și nu pot fi alese.
Tranzițiile sunt un graf strict. O singură clasă — mașina de stări a operatorului — are voie să scrie operators.state, iar o tranziție invalidă aruncă, în loc să fie mușamalizată. Fiecare tranziție acceptată închide rândul deschis dintr-un registru de stări doar cu inserări și deschide unul nou, ceea ce face ca ocuparea și aderența să poată fi reconstruite ulterior, nu doar crezute pe baza unui contor viu.
Prezență
Prezența e un heartbeat, nu o stare. Browserul bate din 15 în 15 secunde implicit; după 60 de secunde operatorul e învechit și exclus din rutare; reaper-ul îl deloghează forțat mai târziu.
POST /api/v1/contact-center/operators/{operator}/login
POST /api/v1/contact-center/operators/{operator}/heartbeat
POST /api/v1/contact-center/operators/{operator}/state
POST /api/v1/contact-center/operators/{operator}/logoutO ofertă la care nu se răspunde
offer_expires_at e setat la acum plus no_answer_timeout_seconds al operatorului, sau offer_timeout_seconds al cozii, sau 20. Un tick programat (php artisan callagent:contact-center:tick) găsește ofertele expirate și le respinge ca no_answer.
Operatorul se întoarce la available — în afară de cazul în care asta a fost a auto_pause_after_missed-a ratare consecutivă (implicit 3), caz în care e împins în custom_pause cu motivul auto_pause_missed_calls. Ratarea apelurilor în tăcere e modul de eșec pe care îl previne asta.
Wrap-up
wrap_up_until e un termen limită, nu un contor invers, și stă pe server. Un contor ținut de browser înseamnă că un operator se întoarce de la masă și încă „finalizează" un apel de acum o oră.
POST /api/v1/contact-center/operators/{operator}/wrap-upDurata e wrap_up_seconds al cozii, altfel al operatorului, altfel 30.
Încheierea wrap-up-ului scrie o disposition: disposition_code, outcome, note, tags, first_contact_resolution, quality_score, wrap_up_seconds scurse și un callback_scheduled_for / callback_number opționale. E refuzată dacă coada are require_disposition (implicit) și nu s-a dat niciuna, sau dacă disposition-ul ales cere o notă și nu s-a dat niciuna.
extend împinge termenul mai departe. Când un termen expiră fără disposition pe o coadă care cere una, operatorul e parcat în custom_pause cu motivul disposition_required, nu eliberat — un disposition sărit devine vizibil, în loc să lipsească în tăcere.
Transferuri și predare
Mecanismul, și de ce apelantul nu e resunat
Asta e partea pe care oamenii o greșesc când o construiesc singuri, deci merită să fim exacți.
- Asistentul apelează tool-ul
transferCall. Gateway-ul scoate apelantul din hold, mută apelul înTransferringși trimite un artefacttransfer_requestcătre control plane. Gateway-ul nu ține nicio credențială de telefonie și nu sună niciodată pe nimeni. - Control plane-ul potrivește tipul de destinație.
queue,number,sipșiextensionse dispecerizează.assistante refuzat aici — aia e o schimbare de persona în același apel, nu un transfer către un om. - Pentru o coadă, rulează două jumătăți independente, raportate separat: evidența (
queued) și media (bridged). - Jumătatea de media nu inițiază niciodată un apel nou către apelant:
- Pe un apel aflat deja pe switch-ul nostru, apelantul e pur și simplu pus în hold.
- Pe un apel Twilio, SID-ul apelului existent e redirecționat pe loc către un TwiML nou, care pune apelantul într-o conferință denumită. Legul operatorului e inițiat separat și adăugat în aceeași conferință. Un test verifică faptul că request-ul către Twilio merge la
/Calls/{același provider_call_id}.json, cu unUrlnou. - Unde e activată calea cu leg de participant în gateway, browserul operatorului primește un al doilea leg pe aceeași sesiune de media vie și nu se inițiază niciun apel de carrier.
- Odată conectat, se atașează briefing-ul — transcriere, rezumat, intenție, sentiment, rezultate de tools, context CRM, istoricul apelantului, acțiune recomandată, toate înghețate la intrarea în coadă — și se scrie un rând de cusătură
handoffîn transcriere.
Predarea de la operator la operator are aceeași proprietate: colegul e sunat în conferința existentă a apelantului, și abia după ce e înăuntru se eliberează și se închide legul operatorului care pleacă. Făcut în ordine inversă, apelantul cade.
Moduri
Opt: blind, warm, assistant_warm, dynamic, sip, phone, queue, on_hold_specialist. Consultative: warm, assistant_warm, on_hold_specialist.
assistant_warm diferă de warm doar prin faptul că briefing-ul generat de AI e rostit specialistului înainte de conectare, în loc ca un om să explice pe viu.
Limite care merită știute înainte să îți proiectezi soluția în jurul lor
- Transferul warm funcționează doar pe apeluri purtate de FreeSWITCH. Secvența hold → originate → park → bridge folosește comenzi FreeSWITCH și nu are echivalent Twilio implementat.
- Pe calea de conferință Twilio, o consultare privată nu există. Ambii operatori sunt în conferință cu apelantul. Produsul o spune, în loc să simuleze o intimitate pe care nu o poate livra.
- Un apel Twilio nu poate ajunge la o extensie internă simplă. Transferul e refuzat cu
extension_not_reachable_from_carrier, în afară de cazul în care extensia are o destinație de redirecționare permanentă. - Destinațiile
dynamicdin tool-ul asistentului sunt refuzate (dynamic_expression_unresolved). Se rezolvă doar pe calea inițiată de operator sau de supervizor.
Cum declari tool-ul
name: escalateToBilling
type: transferCall
description: Predă apelantul unui specialist de facturare.
destinations:
- type: queue
queueId: 01JQ…
priority: 10
message: Te pun în legătură cu facturarea. O clipă.
plan:
mode: warm_with_summary
summaryPrompt: Rezumă în două propoziții problema apelantului, pentru operator.
timeoutSeconds: 60GET /contact-center/queue-calls/{queueCall}/briefing e ce citește ecranul operatorului.
Un transferCall către o coadă mută apelul din assistant_speaking în human_queue și apoi, când răspunde un operator, în human_connected. Astea sunt stări de apel reale, pe aceeași înregistrare — legul AI și cel uman împart un singur apel.
Supervizori
POST /api/v1/contact-center/supervisor/calls/{call}/listen
POST /api/v1/contact-center/supervisor/calls/{call}/whisper
POST /api/v1/contact-center/supervisor/calls/{call}/barge
POST /api/v1/contact-center/supervisor/calls/{call}/end
POST /api/v1/contact-center/supervisor/operators/{operator}/force-state
POST /api/v1/contact-center/supervisor/operators/{operator}/force-logout
POST /api/v1/contact-center/supervisor/queue-calls/{queueCall}/moveBody: { "channel_uuid": "…" }, opțional.
| Acțiune | Cine aude | Flag-urile de eavesdrop FreeSWITCH |
|---|---|---|
| Listen | Nimeni în afară de supervizor | niciun leg conectat |
| Whisper | Doar operatorul | legul A conectat |
| Barge | Toată lumea | ambele leg-uri conectate |
Fiecare acțiune trece printr-o singură cale, cu poartă și cu audit. Verificarea de permisiune rulează prima, iar un rând de audit se scrie la succes, la refuz și la excepție — deci o încercare nu e niciodată invizibilă, indiferent cum s-a terminat.
Ce e dovedit și ce nu
Poarta de permisiuni, urma de audit și end sunt dovedite. end deține tranziția de stare a apelului, deci e neambiguu.
Listen, whisper și barge nu sunt dovedite că mișcă audio. Calea eavesdrop din FreeSWITCH nu are niciun test cu switch-ul pornit — fișierul de test o spune într-un comentariu. Calea mai nouă, cu leg de participant în gateway, e testată pe un gateway HTTP fals, nu pe unul care rulează, și e oprită implicit (contact_center.media.gateway_leg_enabled). Vezi Ce nu este construit.
Statistici
Oferite, răspunse, abandonate, service level, timp mediu până la răspuns, timp mediu de tratare, cea mai lungă așteptare, ocuparea operatorilor — calculate per coadă și per interval, și afișate live pe wallboard.
GET /api/v1/contact-center/queues/overview
GET /api/v1/contact-center/queues/{queue}/statistics
GET /api/v1/contact-center/queues/{queue}/history
GET /api/v1/contact-center/operators/{operator}/statisticsoverview întoarce tot setul deodată, și el e cel pe care îl citește wallboard-ul.
Telefonul web al operatorului
Neconstruit
Softphone-ul din browser (module.web_phone) e specificat, arhitectura e decisă — refolosește același transport WebRTC ca Web SDK-ul — și nu e construit. Operatorii înregistrează un dispozitiv SIP pe extensia lor.
API
GET/POST /api/v1/contact-center/queues
GET/PATCH/DELETE /api/v1/contact-center/queues/{queue}
GET /api/v1/contact-center/queues/overview
GET /api/v1/contact-center/queues/{queue}/statistics
GET /api/v1/contact-center/queues/{queue}/history
POST/DELETE /api/v1/contact-center/queues/{queue}/members[/{operator}]
POST /api/v1/contact-center/queues/{queue}/members/{operator}/pause
GET/POST /api/v1/contact-center/operators
POST /api/v1/contact-center/operators/{operator}/login|logout|heartbeat|state|wrap-up
POST /api/v1/contact-center/operators/{operator}/calls/{queueCall}/accept|reject
GET /api/v1/contact-center/operators/{operator}/statistics
GET /api/v1/contact-center/queue-calls/{queueCall}/briefing
POST /api/v1/contact-center/calls/{call}/transfer
GET /api/v1/contact-center/transfers/{transfer}
POST /api/v1/contact-center/transfers/{transfer}/brief|complete|cancel
GET/POST /api/v1/contact-center/extensions | skills | dispositions | ring-groups | ivrsDetalii complete: cozi, operatori, supervizor, transferuri, extensii, catalog.
Mai departe
- Apeluri și artefacte — o singură înregistrare acoperă legul AI și pe cel uman.
- Trunking SIP — înregistrarea dispozitivelor operatorilor.