test / docs /API.md
Gazia Aziz
Backend API documentation
702c725
|
Raw History Blame Contribute Delete
6.81 kB
# ALAS Backend API
FastAPI backend for the ALAS workplace soft-skills coaching app (`main.py`). It drives an LLM-played character through a scripted scenario, extracts live coaching notes as the conversation progresses, and produces a rubric-based assessment on demand.
- **Base URL (local):** `http://localhost:8000`
- **Run:** `uv run uvicorn main:app --reload --port 8000`
- **Interactive docs:** `/docs` (Swagger UI) and `/redoc`, auto-generated by FastAPI
- **Content type:** `application/json` for all requests/responses except `/chats/{chat_id}/stream`, which returns `text/event-stream`
## Session model
State is kept **in memory** (`store: dict[chat_id -> session]`) β€” there is no database, and all sessions are lost on server restart. A session holds:
| Field | Description |
|---|---|
| `agent` | `CharacterAgent` instance (LangChain-backed) that plays the AI role and holds conversation history |
| `scenario_id` | The scenario this chat was started with |
| `messages` | Ordered list of `{role, text, turn}` β€” the full transcript |
| `notes` | Accumulated coaching notes, appended asynchronously |
| `pending_turns` | Trainee turn numbers whose note extraction is still running in the background |
A session must be created via `POST /chats` before any of the `/chats/{chat_id}/...` endpoints can be used, and every endpoint that takes a `chat_id` returns **404** if it doesn't exist (see [Errors](#errors)).
## Endpoints
### `GET /scenarios`
All available scenarios, for a scenario picker UI.
**Response 200** β€” `ScenariosResponse`
```json
{
"scenarios": [
{
"id": "onboarding_report",
"title": "string",
"skill_area": "string",
"ai_name": "string",
"ai_role": "string",
"learner_role": "string",
"learning_goal": "string",
"has_autoplay": true
}
]
}
```
Known scenario ids in this repo: `onboarding_report` (default), `coaching_employee`, `guest_price_complaint`.
---
### `POST /chats`
Start a new chat session for a scenario. Returns a fresh `chat_id` and any scripted opening message from the AI character.
**Request body** β€” `NewChatRequest` (optional; defaults to `onboarding_report`)
```json
{ "scenario_id": "onboarding_report" }
```
**Response 201** β€” `NewChatResponse`
```json
{
"chat_id": "uuid",
"scenario_id": "onboarding_report",
"messages": [
{ "role": "manager", "text": "string", "turn": 0 }
]
}
```
`messages` is empty if the scenario doesn't open with an AI line (`opens == "learner"`).
**Errors**
- `400` β€” unknown `scenario_id`
---
### `GET /chats/{chat_id}`
Retrieve the full message history for a session.
**Response 200** β€” `ChatHistoryResponse`
```json
{
"chat_id": "uuid",
"messages": [
{ "role": "trainee", "text": "string", "turn": 1 },
{ "role": "manager", "text": "string", "turn": 1 }
]
}
```
**Errors**
- `404` β€” unknown `chat_id`
---
### `POST /chats/{chat_id}/message`
Send a trainee message and get the full AI reply back in one blocking response. Note extraction for this turn is kicked off as a FastAPI background task (non-blocking).
**Request body** β€” `ChatRequest`
```json
{ "message": "string" }
```
**Response 200** β€” `ChatResponse`
```json
{
"chat_id": "uuid",
"reply": "string",
"turn": 1,
"messages": [ { "role": "trainee|manager", "text": "string", "turn": 1 } ]
}
```
**Errors**
- `404` β€” unknown `chat_id`
---
### `POST /chats/{chat_id}/stream`
Same as `/message`, but streams the AI reply token-by-token over **Server-Sent Events**.
**Request body** β€” `ChatRequest` (same as above)
**Response 200** β€” `text/event-stream`
```
data: Hel
data: lo
data: !
data: [DONE]
```
Each `data:` line is a raw token chunk (not JSON β€” concatenate to reassemble the reply). The stream always ends with a `data: [DONE]` sentinel. Note extraction is scheduled on a background thread once streaming completes and is *not* reflected in this response β€” poll `GET /chats/{chat_id}/notes` and watch `pending_turns`.
**Errors**
- `404` β€” unknown `chat_id`
---
### `GET /skills`
The rubric's skill list, in display order. Same set for every scenario β€” lets a UI render empty sections before any notes exist.
**Response 200** β€” `SkillsResponse`
```json
{ "skills": [ { "slug": "string", "name": "string", "order": 0 } ] }
```
---
### `GET /chats/{chat_id}/notes`
Flat, insertion-ordered list of coaching notes extracted so far. Grouping by skill is left to the caller.
**Response 200** β€” `NotesResponse`
```json
{
"chat_id": "uuid",
"notes": [
{ "skill": "string", "note": "string", "evidence": "string", "turn": 1 }
],
"pending_turns": [2]
}
```
`pending_turns` lists trainee turns whose extraction is still running in the background β€” poll this endpoint until a turn drops out of the list to know its notes (possibly none) are final.
**Errors**
- `404` β€” unknown `chat_id`
---
### `POST /chats/{chat_id}/assessment`
Score the conversation so far against the rubric's skills. The result is **cached on the session** β€” repeat calls for the same `chat_id` return the same assessment without re-invoking the LLM.
**Response 200** β€” `Assessment`
```json
{
"overall_summary": "string",
"grades": [
{
"skill": "string",
"level": "Low | Medium | High",
"reasoning": "string",
"evidence_turns": [1, 3]
}
],
"key_moment": "string",
"coaching_suggestion": "string",
"readiness_score": 7
}
```
**Errors**
- `400` β€” no messages yet to assess, or no rubric skills configured for the scenario
- `404` β€” unknown `chat_id`
---
### `DELETE /chats/{chat_id}`
Drop a session and free its in-memory state.
**Response** β€” `204 No Content`
**Errors**
- `404` β€” unknown `chat_id`
---
### `GET /health`
Liveness/readiness probe.
**Response 200**
```json
{ "status": "ok", "active_sessions": 3 }
```
## Errors
All error responses use FastAPI's default shape:
```json
{ "detail": "chat_id '...' not found" }
```
| Status | Meaning |
|---|---|
| 400 | Bad request β€” unknown `scenario_id`, empty conversation on assessment, or no rubric configured |
| 404 | `chat_id` not found (session expired, deleted, or server restarted) |
| 422 | Request body failed Pydantic validation (e.g. missing `message` field) |
## Typical flow
1. `GET /scenarios` β†’ let the user pick a scenario
2. `POST /chats` β†’ get a `chat_id`
3. Repeat: `POST /chats/{chat_id}/message` (or `/stream`) for each trainee turn
4. `GET /chats/{chat_id}/notes` β†’ poll for live coaching notes, checking `pending_turns`
5. `POST /chats/{chat_id}/assessment` β†’ final rubric-based score once the conversation is done
6. `DELETE /chats/{chat_id}` β†’ clean up when finished
A minimal reference client for this exact flow lives in [api_client.py](api_client.py).