100 lines
3.6 KiB
Markdown
100 lines
3.6 KiB
Markdown
# 04 — AI fallback after FAQ miss
|
|||
|
|
|
||
|
|
**Agent:** 4/10 (`support-auto`)
|
||
|
|
**Status:** Implemented
|
||
|
|
**Coordinates with:** [02-contract.md](./02-contract.md), [01-inventory.md](./01-inventory.md), agent 3 `MatchAutoReply`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Goal
|
||
|
|
|
||
|
|
When Stage A FAQ/template match fails or confidence is below threshold, call the **admin-configured platform AI** (`ai_roles.support`) to draft or auto-send a labeled reply. On AI failure → leave the ticket **claimable** for humans.
|
||
|
|
|
||
|
|
No parallel BYOK secret store — reuse `platformsettings.AIRoleSupport` / `aiprovider.ResolveCompleterForRole(..., RoleSupport)`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
MaybeAutoReplyOnCreate (after ticket Create)
|
||
|
|
├─ [sync] MatchAutoReply ≥ threshold → PostMatchedAutoReply (KB/template)
|
||
|
|
└─ miss / low conf / FAQ off
|
||
|
|
└─ EnqueueAIFallback → support_auto_jobs (async)
|
||
|
|
└─ worker ProcessPendingAutoJobs
|
||
|
|
└─ TryAutoReplyLLM → CompleterSupportAI.RunAutoReply
|
||
|
|
├─ draft (default) → internal system note (AI-assisted label)
|
||
|
|
├─ auto_send → public system message + footer
|
||
|
|
└─ fail / low conf / handoff → auto_reply_disabled + handed_off
|
||
|
|
```
|
||
|
|
|
||
|
|
HTTP create **never awaits** the LLM. If migration `034` is missing, enqueue falls back to sync-with-timeout (`AutoReplyTimeout` = 25s).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Settings (`support_auto_config`)
|
||
|
|
|
||
|
|
| Key | Default | Role |
|
||
|
|
|-----|---------|------|
|
||
|
|
| `enabled` | false | Master switch |
|
||
|
|
| `ai_enabled` | false | Stage B |
|
||
|
|
| `ai_delivery` | `draft` | `draft` \| `auto_send` |
|
||
|
|
| `ai_confidence_threshold` | 0.65 | Min model confidence |
|
||
|
|
| `ai_use_global_support_role` | true | Use admin `ai_roles.support` |
|
||
|
|
| `ai_model_override` / `ai_base_url_override` | empty | Same API key; optional endpoint tweak |
|
||
|
|
|
||
|
|
Also requires `/admin/settings` → AI roles → **support** enabled with key + model.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Symbols
|
||
|
|
|
||
|
|
| Symbol | Path |
|
||
|
|
|--------|------|
|
||
|
|
| `TryAutoReplyLLM` | `apps/api/internal/support/ai_auto_reply.go` |
|
||
|
|
| `CompleterSupportAI` | `apps/api/internal/support/ai_fallback.go` |
|
||
|
|
| `EnqueueAIFallback` / `ProcessPendingAutoJobs` | `apps/api/internal/support/auto_jobs.go` |
|
||
|
|
| `MaybeAutoReplyOnCreate` (miss → enqueue) | `apps/api/internal/support/match_auto_reply.go` |
|
||
|
|
| `BuildAutoReplyMessages` | `apps/api/internal/support/auto_prompt.go` |
|
||
|
|
| Migration | `apps/api/sql/schema/034_support_auto_jobs.sql` |
|
||
|
|
|
||
|
|
Wiring: `httpapi.NewServer` and `cmd/worker` set `Support.SupportAI = NewCompleterSupportAI(aiSvc)`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Security
|
||
|
|
|
||
|
|
- Tenant isolation: ticket load checks `company_id`; prompts never mix tenants.
|
||
|
|
- Ticket text wrapped as untrusted; secrets redacted before prompt/logs.
|
||
|
|
- Rate limits: 10 AI jobs/company/hour, 30/platform/minute → handoff.
|
||
|
|
- Logs: `ticket_id`, `company_id`, `delivery`, `confidence` — no bodies/keys/emails.
|
||
|
|
- Docs Ask stays rule-based (never `AIRoleSupport`).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Labels
|
||
|
|
|
||
|
|
- Public AI reply footer: “AI-assisted reply…”
|
||
|
|
- Draft prefix: `[AI draft — not visible to customer]`
|
||
|
|
- Handoff internal note: “Needs human review…”
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Tests
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
cd apps/api
|
||
|
|
go test ./internal/support/ -count=1 -run "TestTryAutoReplyLLM|TestCompleterSupportAI|TestParseAIAssist|TestAIRate|TestBuildAutoReply|TestLabelAI"
|
||
|
|
```
|
||
|
|
|
||
|
|
Mock Completer covers draft JSON parse, low-confidence handoff, and redaction — no live OpenAI calls.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Manual check
|
||
|
|
|
||
|
|
1. Enable `support_auto.enabled` + `ai_enabled`, set `ai_delivery=draft` or `auto_send`.
|
||
|
|
2. Configure AI role **support** in admin settings.
|
||
|
|
3. Create a ticket that does **not** match FAQ → job row → draft/send or handoff.
|
||
|
|
4. Confirm ticket stays unassigned/claimable on AI failure.
|