Skip to content

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 ​

ConceptCe este
ExtensieUn 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.
OperatorO persoană cu competențe, limbi, o stare și o sesiune.
CompetențăO aptitudine cu un nivel de proficiență, folosită la rutare.
Ring groupMai mulți operatori sunați simultan sau în secvență.
IVRUn graf de noduri publicat: prompt-uri, ramuri pe cifre, capturare de variabile, webhook-uri.
Condiție de timpRutează după orar săptămânal, sărbători și o suprascriere manuală.
DispositionRezultatul 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âmpImplicitInterval
strategyfifocele nouă de mai jos
priority1000–1000
max_wait_seconds3000–7200
max_waiting_calls1001–10000
offer_timeout_seconds205–120
retry_delay_seconds5
max_offers_per_call101–50, și limitat la 25 în momentul ofertei
avoid_recent_rejectortrue
wrap_up_seconds300–900
service_level_seconds201–600
service_level_target_percent801–100
announce_position / announce_hold_timefalse
announce_interval_seconds305–600
agent_announce_queue_nametrue
callback_enabledfalse
callback_offer_after_seconds60
callback_expires_minutes240
overflow_when_no_agentstrue
overflow_after_seconds, overflow_when_waiting_overnull
overflow_destination_type / _id / _valuenullcele zece tipuri de destinație
voicemail_fallback_enabled, ai_fallback_enabled, human_fallback_enabledfalse
recording_policyinheritinherit · disabled · mono · dual_channel
require_dispositiontrue
skill_strictfalse
required_languagenull
is_activetrue

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.

strategySortează după
fifoDe cât timp e operatorul disponibil. Cel mai mult, primul.
longest_idleCând a răspuns ultima dată la un apel. Cine nu a răspuns niciodată se sortează primul.
round_robinCâ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_callsCele mai puține apeluri răspunse în coada asta, apoi cel mai mult de la ultimul răspuns.
skill_basedScorul de potrivire a competențelor, descrescător, după ce sunt excluși operatorii care nu îndeplinesc o cerință obligatorie.
priorityPrioritatea configurată a operatorului în coadă, cea mai mare prima, cu cel mai inactiv ca departajare în interiorul unui nivel.
vipUn 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_matchOperatorii 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ă.
customO 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:

json
{
  "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}/logout

O 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-up

Durata 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.

  1. Asistentul apelează tool-ul transferCall. Gateway-ul scoate apelantul din hold, mută apelul în Transferring și trimite un artefact transfer_request către control plane. Gateway-ul nu ține nicio credențială de telefonie și nu sună niciodată pe nimeni.
  2. Control plane-ul potrivește tipul de destinație. queue, number, sip și extension se dispecerizează. assistant e refuzat aici — aia e o schimbare de persona în același apel, nu un transfer către un om.
  3. Pentru o coadă, rulează două jumătăți independente, raportate separat: evidența (queued) și media (bridged).
  4. 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 un Url nou.
    • 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.
  5. 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 dynamic din tool-ul asistentului sunt refuzate (dynamic_expression_unresolved). Se rezolvă doar pe calea inițiată de operator sau de supervizor.

Cum declari tool-ul ​

yaml
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: 60

GET /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}/move

Body: { "channel_uuid": "…" }, opțional.

AcțiuneCine audeFlag-urile de eavesdrop FreeSWITCH
ListenNimeni în afară de supervizorniciun leg conectat
WhisperDoar operatorullegul A conectat
BargeToată lumeaambele 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}/statistics

overview î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 | ivrs

Detalii complete: cozi, operatori, supervizor, transferuri, extensii, catalog.

Mai departe ​

Scrisă după cod. Ce nu este încă implementat este marcat ca atare, niciodată descris ca funcțional.