AI-Assistant Module
Location: apps/api/src/modules/ai-assistant/
Aggregate roots: AssistantSession, AssistantTurn, AssistantSuggestion
The AI-assistant context implements the 5-question reflection flow that discovers a user's interests. It uses either a scripted prompt system (default, no API key needed) or Anthropic Claude Sonnet via @ai-sdk/anthropic when ANTHROPIC_API_KEY is set. After the session completes, the mapInterests endpoint produces AssistantSuggestion records the user can apply to their profile.
Prisma Models
| Model | Table | Notes |
|---|---|---|
| AssistantSession | assistant_sessions | One session per reflection run |
| AssistantTurn | assistant_turns | 5 turns per session (step 1..5) |
| AssistantSuggestion | assistant_suggestions | Interest suggestions derived from session |
AssistantSession Fields
| Field | Type | Notes |
|---|---|---|
| id | UUID PK | |
| userId | UUID FK | |
| status | String | in_progress | completed | abandoned |
| topic | String? | Starting topic (interest slug or free-form) |
| summary | String? | AI-generated summary |
| startedAt | DateTime | |
| completedAt | DateTime? |
AssistantTurn Fields
| Field | Type | Notes |
|---|---|---|
| step | Int | 1..5 |
| prompt | String | Assistant question |
| answer | String? | User reply (null until answered) |
| answeredAt | DateTime? |
Unique: (sessionId, step)
AssistantSuggestion Fields
| Field | Type | Notes |
|---|---|---|
| interestId | UUID FK | Suggested interest |
| confidence | Float | 0..1 from model or keyword score |
| depth | Int (1..5) | Suggested engagement depth |
| reason | String? (280) | Model reasoning |
| applied | Boolean | True once user accepted → UserInterest upserted |
Key Methods
| Method | Description |
|---|---|
start(userId, topic) | Create session, seed first prompt, emit ai.session.started |
findSession(userId, sessionId) | Fetch session with all turns |
listMine(userId, limit) | List user's sessions newest-first |
answer(sessionId, userId, answer) | Submit answer, generate next prompt, advance step |
complete(sessionId, userId) | Mark session completed, emit ai.session.completed |
mapInterests(sessionId, userId) | Analyse turns → create AssistantSuggestion rows |
applySuggestions(sessionId, userId, ids) | Upsert UserInterest for accepted suggestions |
HTTP Endpoints
All under /api/v1/ai/. Require JwtAuthGuard.
| Method | Path | Description |
|---|---|---|
| POST | /ai/sessions | Start a new session { topic? } |
| GET | /ai/sessions | List my sessions |
| GET | /ai/sessions/:id | Get session with turns |
| POST | /ai/sessions/:id/answer | Submit answer to current turn { answer } |
| POST | /ai/sessions/:id/complete | Mark session completed |
| POST | /ai/sessions/:id/map-interests | Run interest mapping → returns suggestions |
| POST | /ai/sessions/:id/apply-suggestions | Apply selected suggestions { ids: string[] } |
| POST | /ai/enhance-post | AI content enhancement { title, body, interestSlug? } |
Events Emitted
| Event | Payload | Consumed by |
|---|---|---|
ai.session.started | { sessionId, userId, topic } | (observability) |
ai.turn.answered | { sessionId, userId, step } | (observability) |
ai.session.completed | { userId, sessionId } | Notifications |
Two Backends
| Mode | Condition | Behaviour |
|---|---|---|
| Scripted | No ANTHROPIC_API_KEY | Predefined question templates from scripted-questions.ts, keyword-based interest matching |
| Anthropic | ANTHROPIC_API_KEY present | Claude Sonnet 4.6 via Vercel AI SDK, streaming responses, semantic interest inference |
The scripted fallback uses renderPrompt(step, topic, priorAnswers) to produce contextually aware questions without any LLM cost.
Interest Mapping (mapInterests)
- Concatenate all 5 turn answers into a single transcript.
- Either: send to Claude with the interest catalog as context (Anthropic mode), or run keyword overlap scoring (scripted mode).
- Create
AssistantSuggestionrows withconfidence,depth, andreason. - Return suggestions grouped by confidence tier.
Apply Suggestions
- User selects suggestion IDs to accept.
- For each accepted ID:
upsert UserInterest { depth, expertise }. - Mark suggestion
applied = true. - Emit
interests.user_selectedso Reputation seeds +5 per top-level interest.