Files
jobtrackingapp/docs/architecture/ai-career-assistant.md
T
cesnimda 5eb9b3cb96 feat(ai): enforce local-first routing
Keep external providers behind server consent, task, and prompt-cost gates while persisting actual provider provenance.
2026-08-09 12:30:11 +02:00

92 lines
5.4 KiB
Markdown

# AI Career Assistant (Phase 5)
> Phase 5 (2026-07-18). The per-application AI Workspace: modules, prompt flow, provider abstraction,
> history model, extension points. Companion to `cv-builder.md`, `career-profile-model.md`, and
> `MASTER_IMPLEMENTATION_GUIDE.md` (AI philosophy). Verified against code + a running container.
## Principle
AI **assists** the application workflow — it never replaces the user. Every module is **suggestion
only**: it returns markdown the user reviews and copies. Nothing is applied automatically to the master
profile, a CV variant, or the application. Every prompt carries the guardrail *"preserve every factual
claim — never invent employers, titles, dates, qualifications, or metrics."* Job tracking stays the
core product; the assistant improves the *application* it hangs off.
## Where it lives
Each job application gains an **AI Workspace** tab (`AiWorkspacePanel`) in the job details dialog. It
is the central place for all AI on that application. Pre-existing per-tab AI (candidate-fit, focus-plan,
interview-prep) is untouched — the workspace is the unified, history-backed home for the Phase 5 modules.
## Modules
`AiWorkspaceService.Modules`, all via `ISummarizerService.SummarizeSectionAsync`:
| Module | Produces |
|---|---|
| `job-analysis` | Company/role/skills/tech/experience/education/soft-skills/responsibilities/salary/benefits/work-model/visa/language + summary + likely interview topics + confidence |
| `career-match` | Match % + reasoning, strengths, weaknesses, missing skills, most-relevant experience, suggested improvements |
| `cover-letter` | A tailored letter in one of 6 tones (professional, friendly, short, detailed, modern, traditional) |
| `interview` | Company research, likely/behavioural/technical questions, STAR answer outlines, prep checklist |
| `application-review` | Overall strength (rating), missing info, weak areas, ATS issues, grammar/clarity, formatting |
Tailored CV is **not** re-implemented here — it is the Phase 4 CV Builder (`CvVariant` linked to the
job application). The workspace links to it rather than duplicating it.
## Prompt flow
```
job (JobApplication + Company) ─┐
├─► AiWorkspaceService builds { instruction + guardrail, source }
master profile text ────────────┘ │
ISummarizerService.SummarizeSectionAsync ──► ai-service (active provider)
AiInteraction (append-only history) ──► markdown suggestion to the UI
```
`source` = the job context (`BuildJobContext`) plus the user's master profile text
(`ApplicationUser.ProfileCvText`). No profile fields are written; the text is read-only input.
## History model
`AiInteraction` (`JobTrackerApi/Models/AiInteraction.cs`) is **append-only** — one row per generation, never
overwritten. This is deliberately distinct from `AiWorkspaceNote` (a one-row-per-type *cache* for
candidate-fit/focus-plan). History gives the user restore/reuse (re-surface a past result), compare
(view two side by side), copy, and delete. `ResultJson` is `{ text, meta? }`; `Provider` records which
provider produced it. Each row also stores input/output character counts and a conservative estimated
token count (characters ÷ 4); the estimate is provider-neutral because the sidecar currently returns
text rather than provider billing metadata. Tenant-scoped (owner query filter), cascades with the application.
API (`AiWorkspaceController`, `/api/jobapplications/{id}/ai`): `GET modules` (+ current provider),
`POST generate`, `GET history?module=`, `DELETE history/{id}`. `GET /api/ai/usage` returns current-month
and all-time totals; the workspace displays the monthly calls and estimated tokens.
## Provider abstraction
Generation goes through `ISummarizerService` to the ai-service. Ollama is primary; `AI_PROVIDER` names
only the optional external fallback candidate. Fallback is sequential and requires administrator
enablement, task approval, live Pro/user consent and the prompt cost/privacy ceiling. Each
`AiInteraction` records the provider returned by the sidecar, plus bounded model/route metadata in
`ResultJson.meta`; configuration alone is not treated as proof that a provider executed.
Per-request user-selectable providers remain intentionally unsupported. The server-side privacy
policy selects a route, not the browser, and provider credentials remain deployment-only.
## Extension points
- **New module**: add a key to `AiWorkspaceService.Modules` + a prompt builder + a `switch` arm. No
new storage, controller, or UI wiring — the panel enumerates modules from `AI_MODULES`.
- **New cover-letter tone**: add to `CoverLetterModes` + `ModeGuidance`.
- **Structured (JSON) results**: swap a module's prompt for JSON and parse into `ResultJson.meta`; the
UI already renders `result.text` as markdown and can read `meta`.
- **Provider policy**: add task types to the explicit server-side allowlist only after their payload,
accounting and production checks pass; do not add browser provider overrides.
## Security
Suggestion only; never overwrites user content; everything requires the user to copy it in. The
ai-service stays backend-only (`ai_internal` network, `X-Ai-Service-Token`) — see `current.md` §16.
`Markdown` renders React nodes (no `dangerouslySetInnerHTML`), so AI output has no HTML-injection path.