feat(ai): shift name extraction to LLM payload piggybacking to improve contextual accuracy
This commit is contained in:
@@ -1,373 +0,0 @@
|
|||||||
# MVP Contact Center (KZ) - Sequential 4-Stage Implementation
|
|
||||||
|
|
||||||
This repository bootstraps the agreed MVP implementation for the contact center platform.
|
|
||||||
|
|
||||||
## What is implemented
|
|
||||||
|
|
||||||
- Stage 1 foundation artifacts
|
|
||||||
- API gateway skeleton
|
|
||||||
- local RBAC auth service
|
|
||||||
- users, health, audit contracts
|
|
||||||
- Kubernetes + Helm + CI skeleton
|
|
||||||
- observability baseline docs and manifests
|
|
||||||
- Stage 2 operator core APIs
|
|
||||||
- customers
|
|
||||||
- interactions and escalation
|
|
||||||
- queue/routing rules
|
|
||||||
- voice events adapter (Asterisk-facing)
|
|
||||||
- Telegram webhook adapter
|
|
||||||
- Webchat intake adapter
|
|
||||||
- Stage 3 supervisor/reporting/KB-lite APIs
|
|
||||||
- knowledge base categories/articles/search
|
|
||||||
- KPI aggregation (SL, ASA, AHT, Abandon, FCR)
|
|
||||||
- supervisor realtime endpoints
|
|
||||||
- Stage 4 pilot hardening package
|
|
||||||
- gate checklists
|
|
||||||
- UAT package and templates
|
|
||||||
- runbooks for backup/restore and pilot operations
|
|
||||||
- Wave 2 Track 1-2 baseline
|
|
||||||
- signed bearer-token auth + optional Keycloak OIDC login
|
|
||||||
- first additional digital channels: Webchat and Email
|
|
||||||
- Wave 2 Track 4 baseline
|
|
||||||
- recording-service for managed local voice recordings
|
|
||||||
- supervisor playback/download/archive flow
|
|
||||||
- Wave 2 Track 5 baseline
|
|
||||||
- ivr-service for DTMF voice flows and IVR runtime sessions
|
|
||||||
- IVR-aware route preview and admin-side IVR management
|
|
||||||
- Wave 2 Track 6 baseline
|
|
||||||
- expanded KPI catalog with channel-aware metrics and coverage endpoint
|
|
||||||
- reporting export now includes channel and agent dimensions
|
|
||||||
- Wave 2 Track 7 baseline
|
|
||||||
- production-like Helm scale profile for K8s on-prem
|
|
||||||
- Postgres-first DB pooling and formal load/hardening validation scripts
|
|
||||||
- Wave 2 Track 8 baseline
|
|
||||||
- RabbitMQ-backed event bus with transactional outbox
|
|
||||||
- first consumers wired for audit and reporting
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
- Microservices (FastAPI), each service isolated in `services/*`
|
|
||||||
- API contracts in `contracts/openapi`
|
|
||||||
- Event schemas in `contracts/events`
|
|
||||||
- Deployment:
|
|
||||||
- Local compose: `deployment/docker-compose.yml`
|
|
||||||
- Kubernetes manifests: `deployment/kubernetes`
|
|
||||||
- Helm chart: `deployment/helm`
|
|
||||||
- GitLab registry server deploy: `deployment/docker-compose.server.registry.yml`
|
|
||||||
- Service state persistence:
|
|
||||||
- SQL storage via `DATABASE_URL` for all services (SQLite by default, PostgreSQL supported)
|
|
||||||
- production-like K8s scale validation is PostgreSQL-only
|
|
||||||
|
|
||||||
## Quick start (local)
|
|
||||||
|
|
||||||
1. Install dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Run tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pytest -q
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Apply DB migrations
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python scripts/migrate_core_db.py
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Start individual services (example)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uvicorn services.auth_service.app:app --reload --port 8001
|
|
||||||
uvicorn services.interaction_service.app:app --reload --port 8004
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Start local gateway
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uvicorn gateway.app:app --reload --port 8080
|
|
||||||
```
|
|
||||||
|
|
||||||
Operator UI is available at:
|
|
||||||
|
|
||||||
```text
|
|
||||||
http://localhost:8080/operator
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional dedicated Wave 2 shells:
|
|
||||||
|
|
||||||
```text
|
|
||||||
http://localhost:8080/supervisor
|
|
||||||
http://localhost:8080/admin
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Optional helper scripts (PowerShell)
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\run_all_local.ps1
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\stop_all_local.ps1
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\prepare_demo.ps1
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\backup_data.ps1
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\restore_data.ps1 -BackupZip e:\Zhan\backups\mvp_cc_data_YYYYMMDD_HHMMSS.zip
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\clean_workspace.ps1
|
|
||||||
python scripts\local_stack.py status
|
|
||||||
python scripts\demo_seed.py
|
|
||||||
python scripts\oidc_smoke.py --base-url http://localhost:8080
|
|
||||||
python scripts\load_test.py --base-url http://localhost:8080 --profile baseline_100_100
|
|
||||||
python scripts\track7_check.py --namespace mvp-cc --report-dir .artifacts\track7\<timestamp>
|
|
||||||
python scripts\postgres_dev_preflight.py --env-file .env.postgres.local.template
|
|
||||||
python scripts\postgres_dev_preflight.py --env-file .env.postgres.local.template --base-url http://127.0.0.1:8080
|
|
||||||
python scripts\live_smoke_gate12.py
|
|
||||||
python scripts\live_smoke_gate12.py --database-url postgresql://mvp:mvp@localhost:5432/mvpcc
|
|
||||||
python scripts\track9_preflight.py --base-url http://127.0.0.1:8080 --check-sftp
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\start_track9_qa.ps1
|
|
||||||
python scripts\asterisk_lab_smoke.py --base-url http://127.0.0.1:8080 --database-url <database-url>
|
|
||||||
python scripts\track9_check.py --base-url http://127.0.0.1:8080 --database-url <database-url> --require-recording
|
|
||||||
python scripts\track9_collect_evidence.py --base-url http://127.0.0.1:8080 --database-url <database-url> --run-checks --require-recording
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\track9_2_cutover.ps1 -KubeContext <context> -Namespace <namespace> -Release <release> -GatewayBaseUrl http://<gateway-host> -DatabaseUrl postgresql://<...> -ImageTag <image-tag> -AmiHost <asterisk-host> -AmiUser <ami-user> -AmiSecret <ami-secret> -SftpHost <asterisk-host> -SftpUser <sftp-user> -SftpPassword <sftp-password> -QueueId <queue-id> -AppTokenSecret <app-token-secret>
|
|
||||||
python scripts\gate3_check.py --auto-start
|
|
||||||
python scripts\gate4_check.py
|
|
||||||
python scripts\uat_preflight.py --auto-start
|
|
||||||
python scripts\uat_dry_run.py --auto-start --update-defect-register
|
|
||||||
python scripts\uat_manual_prepare.py --environment-url http://<pilot-gateway>:8080
|
|
||||||
python scripts\finalize_mvp_pilot.py --session-dir docs/uat/evidence/manual_<session-id> --dry-run
|
|
||||||
python scripts\finalize_mvp_pilot.py --session-dir docs/uat/evidence/manual_<session-id>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Demo-ready local run
|
|
||||||
|
|
||||||
Use one command to start the stack, run smoke checks, and preload demo data:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\prepare_demo.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
This starts every service in the background without opening extra PowerShell windows, writes logs to `.local_stack\logs`, and seeds:
|
|
||||||
|
|
||||||
- a demo customer
|
|
||||||
- a closed escalated voice interaction
|
|
||||||
- an active follow-up interaction
|
|
||||||
- voice + Telegram integration events
|
|
||||||
- one `recording.ready` event imported into managed storage
|
|
||||||
- one IVR flow with a completed demo session and route preview
|
|
||||||
- KB demo article
|
|
||||||
- supervisor snapshot
|
|
||||||
- KPI sample rows
|
|
||||||
|
|
||||||
Stop the stack with:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\stop_all_local.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Local runtime state under `.data*`, `.local_stack`, and `.artifacts` is disposable.
|
|
||||||
To reset the local workspace back to a clean baseline, run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
powershell -ExecutionPolicy Bypass -File scripts\clean_workspace.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Relevant PostgreSQL cutover runbooks:
|
|
||||||
|
|
||||||
- [local-run.md](/e:/Zhan/docs/runbooks/local-run.md)
|
|
||||||
- [postgres-dev-cutover.md](/e:/Zhan/docs/runbooks/postgres-dev-cutover.md)
|
|
||||||
- [postgres-server-docker.md](/e:/Zhan/docs/runbooks/postgres-server-docker.md)
|
|
||||||
- [postgres-server-parallel-stack.md](/e:/Zhan/docs/runbooks/postgres-server-parallel-stack.md)
|
|
||||||
- [deployment-onprem.md](/e:/Zhan/docs/runbooks/deployment-onprem.md)
|
|
||||||
|
|
||||||
## Wave 2 enterprise auth baseline (Track 1)
|
|
||||||
|
|
||||||
Track 1 introduces signed application bearer tokens and optional Keycloak-backed OIDC login.
|
|
||||||
|
|
||||||
Default local/demo mode:
|
|
||||||
|
|
||||||
- `OIDC_ENABLED=0`
|
|
||||||
- `ALLOW_LEGACY_HEADER_AUTH=1`
|
|
||||||
|
|
||||||
Enterprise-shaped mode:
|
|
||||||
|
|
||||||
- `OIDC_ENABLED=1`
|
|
||||||
- `ALLOW_LEGACY_HEADER_AUTH=0`
|
|
||||||
- set `APP_TOKEN_SECRET`
|
|
||||||
- set `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_REDIRECT_URI`
|
|
||||||
|
|
||||||
Quick checks:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python scripts\migrate_core_db.py
|
|
||||||
python scripts\oidc_smoke.py --base-url http://localhost:8080
|
|
||||||
python scripts\oidc_smoke.py --base-url http://localhost:8080 --require-enabled
|
|
||||||
```
|
|
||||||
|
|
||||||
Detailed rollout guidance:
|
|
||||||
|
|
||||||
- `docs/runbooks/keycloak-oidc.md`
|
|
||||||
- `docs/runbooks/deployment-onprem.md`
|
|
||||||
|
|
||||||
## Wave 2 Track 3 dedicated shells
|
|
||||||
|
|
||||||
Track 3 starts moving critical workflows out of the single operator shell:
|
|
||||||
|
|
||||||
- `/supervisor` for realtime, KPI, agent status, and queue snapshot controls
|
|
||||||
- `/admin` for users, queues, and routing previews
|
|
||||||
|
|
||||||
## Wave 2 Track 4 recordings
|
|
||||||
|
|
||||||
Track 4 introduces the first voice recording lifecycle:
|
|
||||||
|
|
||||||
- `recording-service` stores metadata for voice recordings
|
|
||||||
- audio files are copied into managed local storage
|
|
||||||
- `/supervisor` can import, preview, download, and archive recordings
|
|
||||||
|
|
||||||
Operational runbook:
|
|
||||||
|
|
||||||
- `docs/runbooks/recordings-local.md`
|
|
||||||
|
|
||||||
## Wave 2 Track 5 IVR
|
|
||||||
|
|
||||||
Track 5 introduces the first IVR runtime for voice:
|
|
||||||
|
|
||||||
- `ivr-service` stores IVR flow configs and runtime sessions
|
|
||||||
- DTMF steps can complete a session into an `ivr.completed` event
|
|
||||||
- `/admin` can create flows, start sessions, send digits, and preview routing with `ivr_session_id`
|
|
||||||
|
|
||||||
Operational runbook:
|
|
||||||
|
|
||||||
- `docs/runbooks/ivr-local.md`
|
|
||||||
|
|
||||||
## Wave 2 Track 6 KPI Expansion
|
|
||||||
|
|
||||||
Track 6 expands reporting beyond the MVP core subset:
|
|
||||||
|
|
||||||
- `reporting-service` now stores `channel` and `agent_id`
|
|
||||||
- `/reports/kpi` adds expanded metrics and channel breakdowns
|
|
||||||
- `/reports/coverage` exposes the implemented KPI catalog
|
|
||||||
|
|
||||||
Reference:
|
|
||||||
|
|
||||||
- `docs/kpi/definitions.md`
|
|
||||||
|
|
||||||
## Wave 2 Track 7 Scale-Up and Hardening
|
|
||||||
|
|
||||||
Track 7 adds the first production-shaped scale profile:
|
|
||||||
|
|
||||||
- Helm is now the canonical path for K8s scale validation
|
|
||||||
- `DATABASE_URL` is required for the `scale500` profile
|
|
||||||
- runtime DB pooling is configurable with:
|
|
||||||
- `DB_POOL_SIZE`
|
|
||||||
- `DB_MAX_OVERFLOW`
|
|
||||||
- `DB_POOL_TIMEOUT_SECONDS`
|
|
||||||
- `DB_POOL_RECYCLE_SECONDS`
|
|
||||||
- `scripts/load_test.py` now runs mixed load profiles and writes evidence to `.artifacts/track7/*`
|
|
||||||
- `scripts/track7_check.py` validates the load report plus K8s pod/HPA health
|
|
||||||
|
|
||||||
Operational runbook:
|
|
||||||
|
|
||||||
- `docs/runbooks/track7-scale-validation.md`
|
|
||||||
|
|
||||||
## Wave 2 Track 8 Event Bus
|
|
||||||
|
|
||||||
Track 8 adds the first asynchronous integration backbone:
|
|
||||||
|
|
||||||
- `event_outbox` / `event_inbox` tables for reliable publication and idempotent consumption
|
|
||||||
- `event-bus-service` for outbox dispatch and operational retry
|
|
||||||
- RabbitMQ as the first canonical message bus
|
|
||||||
- first consumers:
|
|
||||||
- `audit-service`
|
|
||||||
- `reporting-service`
|
|
||||||
|
|
||||||
Operational runbook:
|
|
||||||
|
|
||||||
- `docs/runbooks/event-bus-local.md`
|
|
||||||
|
|
||||||
## Wave 2 Track 9 Asterisk Lab Integration
|
|
||||||
|
|
||||||
Track 9 adds the first real telephony bridge toward Asterisk:
|
|
||||||
|
|
||||||
- `asterisk-bridge-service` listens to AMI `UserEvent` messages with the `MVPCC` prefix
|
|
||||||
- Asterisk `QueueCode` values are mapped to platform `queue_id` values through config
|
|
||||||
- `MVPCCCallStarted` creates a real `voice` interaction and forwards `call.started`
|
|
||||||
- `MVPCCCallEnded` forwards `call.ended` and keeps the interaction linked
|
|
||||||
- `MVPCCRecordingReady` can fetch a recording from a Linux VM over `SFTP` and upload it into `recording-service`
|
|
||||||
- bridge internal auth mode is configurable:
|
|
||||||
- `ASTERISK_BRIDGE_AUTH_MODE=legacy_headers` (Track 9 QA baseline)
|
|
||||||
- `ASTERISK_BRIDGE_AUTH_MODE=bearer_first|bearer` (Track 9.1 hardening)
|
|
||||||
- strict service-token hardening adds subject allowlists:
|
|
||||||
- `VOICE_ADAPTER_TRUSTED_SERVICE_SUBJECTS`
|
|
||||||
- `RECORDING_IMPORT_TRUSTED_SERVICE_SUBJECTS`
|
|
||||||
- `RECORDING_IMPORT_ALLOW_ADMIN=0` in strict mode
|
|
||||||
|
|
||||||
Repository assets for the Linux VM lab:
|
|
||||||
|
|
||||||
- `deployment/asterisk/pjsip.conf`
|
|
||||||
- `deployment/asterisk/extensions.conf`
|
|
||||||
- `deployment/asterisk/manager.conf`
|
|
||||||
|
|
||||||
Operational runbook:
|
|
||||||
|
|
||||||
- `docs/runbooks/asterisk-lab-linux-vm.md`
|
|
||||||
- `docs/runbooks/.env.production.checklist.md`
|
|
||||||
- `deployment/helm/values.track9-strict.yaml` (strict bridge auth overlay)
|
|
||||||
- `docs/runbooks/track9-2-production-cutover.md` (controlled production cutover)
|
|
||||||
|
|
||||||
Track 9 QA config artifacts:
|
|
||||||
|
|
||||||
- `.env.production.template`
|
|
||||||
- `docs/acceptance/track9/track9-acceptance.md`
|
|
||||||
|
|
||||||
## Wave 2 Track 11 Live Operator Voice Control
|
|
||||||
|
|
||||||
Track 11 extends the Asterisk bridge with operator call control:
|
|
||||||
|
|
||||||
- new bridge routes:
|
|
||||||
- `/asterisk/live-calls`
|
|
||||||
- `/asterisk/live-calls/{call_id}/claim`
|
|
||||||
- `/asterisk/live-calls/{call_id}/hangup`
|
|
||||||
- `/asterisk/live-calls/{call_id}/blind-transfer`
|
|
||||||
- `/asterisk/live-calls/{call_id}/actions`
|
|
||||||
- operator shell gets a `Живые звонки` block with claim/hangup/blind-transfer controls
|
|
||||||
- own-call RBAC guard for operator role
|
|
||||||
- additive voice events:
|
|
||||||
- `call.connected`
|
|
||||||
- `call.transferred`
|
|
||||||
|
|
||||||
Runbook and acceptance templates:
|
|
||||||
|
|
||||||
- `docs/runbooks/track11-live-operator-call-control.md`
|
|
||||||
- `docs/acceptance/track11/README.md`
|
|
||||||
- `docs/acceptance/track11/track11-acceptance.template.md`
|
|
||||||
|
|
||||||
## PostgreSQL run (docker-compose)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd deployment
|
|
||||||
docker compose up -d postgres
|
|
||||||
cd ..
|
|
||||||
python scripts/migrate_core_db.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Shared Docker host PostgreSQL for call-center
|
|
||||||
|
|
||||||
When the server already hosts other Docker projects and the running `call-center` stack is still SQLite-backed, use the isolated server-side PostgreSQL asset instead of reusing an existing database:
|
|
||||||
|
|
||||||
- compose: `deployment/docker-compose.postgres.server.yml`
|
|
||||||
- template: `.env.postgres.server.template`
|
|
||||||
- runbook: `docs/runbooks/postgres-server-docker.md`
|
|
||||||
|
|
||||||
To bring up a second PostgreSQL-backed application stack in parallel on the same host:
|
|
||||||
|
|
||||||
- compose: `deployment/docker-compose.parallel.server.yml`
|
|
||||||
- template: `.env.postgres.parallel.server.template`
|
|
||||||
- runbook: `docs/runbooks/postgres-server-parallel-stack.md`
|
|
||||||
|
|
||||||
## Stage-by-stage execution
|
|
||||||
|
|
||||||
- Stage 1: see `docs/roadmap/01-foundation.md`
|
|
||||||
- Stage 2: see `docs/roadmap/02-operator-core.md`
|
|
||||||
- Stage 3: see `docs/roadmap/03-supervisor-reporting-kb.md`
|
|
||||||
- Stage 4: see `docs/roadmap/04-pilot-hardening.md`
|
|
||||||
|
|
||||||
Every stage has a gate checklist in `docs/gates`.
|
|
||||||
@@ -2402,6 +2402,7 @@ def _sanitize_decision(raw: dict[str, Any], *, fallback_language: str) -> dict[s
|
|||||||
"language": str(raw.get("language") or fallback_language or "ru"),
|
"language": str(raw.get("language") or fallback_language or "ru"),
|
||||||
"intent": str(raw.get("intent") or "unknown"),
|
"intent": str(raw.get("intent") or "unknown"),
|
||||||
"reply_text": str(raw.get("reply_text") or "").strip(),
|
"reply_text": str(raw.get("reply_text") or "").strip(),
|
||||||
|
"extracted_name": str(raw.get("extracted_name") or "").strip() or None,
|
||||||
"confidence": float(raw.get("confidence") or 0.0),
|
"confidence": float(raw.get("confidence") or 0.0),
|
||||||
"needs_handoff": bool(raw.get("needs_handoff")),
|
"needs_handoff": bool(raw.get("needs_handoff")),
|
||||||
"handoff_reason": str(raw.get("handoff_reason") or "").strip() or None,
|
"handoff_reason": str(raw.get("handoff_reason") or "").strip() or None,
|
||||||
|
|||||||
@@ -75,6 +75,6 @@ def operator_system_prompt(*, language: str, channel_label: str, is_voice: bool)
|
|||||||
"If the customer explicitly asks for a live operator, if the request is sensitive, or if the case is blocked, "
|
"If the customer explicitly asks for a live operator, if the request is sensitive, or if the case is blocked, "
|
||||||
"set needs_handoff=true. "
|
"set needs_handoff=true. "
|
||||||
f"{delivery_hint} "
|
f"{delivery_hint} "
|
||||||
"Return only a JSON object with keys: language, intent, reply_text, confidence, needs_handoff, "
|
"Return only a JSON object with keys: language, intent, reply_text, extracted_name, confidence, needs_handoff, "
|
||||||
"handoff_reason, case_action, kb_refs. case_action must be one of none, close, escalate, keep_open."
|
"handoff_reason, case_action, kb_refs. case_action must be one of none, close, escalate, keep_open."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1115,14 +1115,23 @@ def _voice_llm_prompt_messages(
|
|||||||
"kb_results": kb_context,
|
"kb_results": kb_context,
|
||||||
"history": history,
|
"history": history,
|
||||||
}
|
}
|
||||||
|
system_prompt = persona.operator_system_prompt(
|
||||||
|
language=language,
|
||||||
|
channel_label="Voice",
|
||||||
|
is_voice=True,
|
||||||
|
)
|
||||||
|
if name_status in ("name_not_obtained", "name_followup_required"):
|
||||||
|
system_prompt += (
|
||||||
|
" The user's name is not yet obtained. If the user explicitly provided their name in this turn, "
|
||||||
|
"extract it into the `extracted_name` JSON field. If they did not provide a name or just stated a problem "
|
||||||
|
"(e.g., 'У меня не работает интернет'), set `extracted_name` to null and politely ask for their name "
|
||||||
|
"in your `reply_text` before assisting."
|
||||||
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": persona.operator_system_prompt(
|
"content": system_prompt,
|
||||||
language=language,
|
|
||||||
channel_label="Voice",
|
|
||||||
is_voice=True,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{"role": "user", "content": json.dumps(payload, ensure_ascii=False)},
|
{"role": "user", "content": json.dumps(payload, ensure_ascii=False)},
|
||||||
]
|
]
|
||||||
@@ -1951,6 +1960,33 @@ def turn_voice_session(session_id: str, payload: VoiceAITurnIn) -> VoiceAITurnDe
|
|||||||
customer_name_value=name_update["value"],
|
customer_name_value=name_update["value"],
|
||||||
customer_name_status=name_update["status"],
|
customer_name_status=name_update["status"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if decision.get("extracted_name") and name_update["status"] not in ("name_obtained", "name_obtained_previously"):
|
||||||
|
name_update["status"] = "name_obtained"
|
||||||
|
name_update["value"] = str(decision["extracted_name"]).strip()
|
||||||
|
name_update["source"] = "llm_extraction"
|
||||||
|
name_update["resolved_at"] = now
|
||||||
|
name_update["inline_followup"] = False
|
||||||
|
|
||||||
|
finalized_name = _finalize_customer_name(
|
||||||
|
session,
|
||||||
|
customer=customer,
|
||||||
|
customer_id=customer_id,
|
||||||
|
call_id=payload.call_id,
|
||||||
|
final_name=name_update["value"],
|
||||||
|
resolved_at=now,
|
||||||
|
)
|
||||||
|
if finalized_name:
|
||||||
|
name_update["value"] = finalized_name
|
||||||
|
_persist_voice_name_state(
|
||||||
|
session,
|
||||||
|
voice_session=voice_session,
|
||||||
|
status=name_update["status"],
|
||||||
|
value=name_update["value"],
|
||||||
|
source=name_update["source"],
|
||||||
|
resolved_at=name_update["resolved_at"],
|
||||||
|
)
|
||||||
|
|
||||||
decision_metadata = _voice_name_metadata(
|
decision_metadata = _voice_name_metadata(
|
||||||
language=voice_session.voice_start_language or ai_session.language or "ru",
|
language=voice_session.voice_start_language or ai_session.language or "ru",
|
||||||
customer_id=customer_id,
|
customer_id=customer_id,
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import urllib.request
|
||||||
|
import json
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'http://92.38.48.166:8080/proxy/auth/auth/login',
|
||||||
|
data=json.dumps({'username': 'admin', 'password': 'admin123'}).encode('utf-8'),
|
||||||
|
headers={'Content-Type': 'application/json'}
|
||||||
|
)
|
||||||
|
token = json.loads(urllib.request.urlopen(req).read().decode()).get('access_token')
|
||||||
|
headers = {'Authorization': f'Bearer {token}'}
|
||||||
|
|
||||||
|
req = urllib.request.Request('http://92.38.48.166:8080/proxy/asterisk-bridge/asterisk/events?limit=250', headers=headers)
|
||||||
|
events = json.loads(urllib.request.urlopen(req).read().decode())
|
||||||
|
cids = []
|
||||||
|
for e in events:
|
||||||
|
if e['call_id'] not in cids: cids.append(e['call_id'])
|
||||||
|
|
||||||
|
for cid in cids[:1]:
|
||||||
|
summ_req = urllib.request.Request(f'http://92.38.48.166:8080/proxy/asterisk-bridge/asterisk/live-calls/{cid}/ai-summary', headers=headers)
|
||||||
|
summ = json.loads(urllib.request.urlopen(summ_req).read().decode())
|
||||||
|
print('Call ID:', cid)
|
||||||
|
print('Voice Session ID:', summ.get('voice_session_id'))
|
||||||
|
for t in summ.get('interaction_transcript', []):
|
||||||
|
print(f"[{t.get('sequence_no', '')}] {t['speaker']}: {t['text']}")
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
Reference in New Issue
Block a user