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