pediatric-ai-scribe-v3/docs/CLINICAL_ASSISTANT.md

120 lines
6 KiB
Markdown

# Clinical Assistant
The Clinical Assistant is a retrieval-grounded assistant for pediatric clinical reference questions. It is not the same as the app's note-generation/HPI workflow.
## Responsibilities
| Component | Responsibility |
|---|---|
| Browser UI | question input, source display, markdown/citation rendering, export |
| Ped-AI backend | settings, MCP search call, answer prompt construction, model call |
| MCP server | Nextcloud access, indexing, vector search, rerank, source metadata |
| LiteLLM | model routing and provider abstraction |
## Request Flow
```txt
User asks a question
-> browser posts to Ped-AI
-> Ped-AI calls MCP `nc_semantic_search`
-> MCP returns source excerpts and metadata
-> Ped-AI builds an answer prompt with source constraints
-> LiteLLM model returns answer text
-> browser renders answer and source cards
```
## Source Rules
- Prefer MCP `file_path` basename for displayed source titles when present.
- Do not relabel one source as another requested source.
- If the user names a source and retrieval does not return it, say that before using other sources.
- Use citations only for returned source numbers.
- Unknown citation numbers should remain plain text instead of being guessed.
## Table And Markdown Rendering
LLM output is not guaranteed to be valid markdown. The browser renderer defensively handles common problems:
- adjacent citation clusters,
- missing closing bracket in narrow citation cases,
- smashed bullet lists,
- inline headings,
- malformed pipe tables,
- bare source numbers in source/citation table columns,
- orphan markdown emphasis markers,
- code blocks that must not be modified.
Renderer fixes must be narrow. Do not add broad repairs that turn arbitrary clinical numbers into citations.
## Image Routing
Table lookup requests should stay in retrieval flow.
Examples that should use retrieval:
```txt
show me the table
show me Table 13.1
summarize the developmental table
```
Explicit visual creation/display requests can use image flow.
Examples:
```txt
create an infographic
generate a diagram
show me the image/figure
```
## Caching Policy
Clinical answer response caching is intentionally disabled. Redis can support prompt suggestions and operational metadata, but final answers should be generated from current retrieval context.
## Image Attachments
Users can attach up to 4 images (PNG, JPEG, WebP) to an outgoing clinical question. Attachments **ride the outgoing question only for inference** and **persist with the saved chat** once the question is sent:
- They are validated client-side and authoritatively on the server (MIME allowlist, canonical base64, ≤ 5 MiB per image, ≤ 4 images, ≤ 10 MiB decoded total). Invalid input is rejected with 400 before any retrieval or provider call.
- They are sent **only** with the outgoing clinical question for inference. Attaching images never disables retrieval: RAG/includeContext runs exactly as without images.
- The conversation budget counts text only: images are excluded from the UTF-16 code-unit count. The server still validates every request.
- Once sent, the message's attachments are stored in the saved chat payload (same bounded limits, re-validated on every save) and restored as thumbnails on load.
- Only OpenAI-compatible providers (LiteLLM, OpenRouter, Azure) receive them as multimodal content parts (`text` + `image_url` data URIs) on the latest user message; the system/retrieval/history structure is unchanged. Legacy direct adapters (Bedrock/Vertex) refuse with a clear 400 before contacting the provider.
- Attachments clear on a successful send and on New chat; a rejected send keeps them for correction.
## Autosave, titles and saved-chat updates
After each completed assistant turn (and on any change to the conversation), the chat is autosaved with an 800 ms debounce to `POST /api/clinical-assistant/chats`. New chats get a title derived from the first user message (first 60 characters); later saves include the chat `id` and update the same row in place. Failures surface once per change and never block chat flow; oversized saves keep the 8 MiB / 400 / 413 semantics and are retried only on the next change, never truncated. The raw transcript stays canonical. The generated sidebar image and per-message image jobs persist with the chat again.
## Translation
Every message offers Translate with a target-language picker and a provider choice. The default provider is the local LibreTranslate container (`LIBRETRANSLATE_URL`, default `http://libretranslate:5000`); DeepL is an admin-configured alternative (`DEEPL_API_KEY` + `DEEPL_API_BASE`, default `https://api.deepl.com/v2`, `api-free.deepl.com` also allowed). The admin default lives in `clinical_assistant.translate_provider` (libretranslate|deepl). Responses are cached per provider+message+lang; transient provider failures fall back to the other configured provider once; validation failures never fall back. No patient data leaves the local network unless the admin explicitly configures DeepL.
## Settings
Important settings include:
| Setting | Purpose |
|---|---|
| `clinical_assistant.chat_model` | Chat model used for answers |
| `clinical_assistant.image_model` | Image model used for explicit image generation |
| `clinical_assistant.search_limit` | Number of MCP results requested |
| `clinical_assistant.context_chars` | Context characters requested from MCP |
| `clinical_assistant.system_behavior` | Admin-editable assistant behavior guidance |
| `clinical_assistant.translate_provider` | Default translation provider: `libretranslate` or `deepl` |
## Testing Priorities
Add or update tests when changing:
- citation rendering,
- source title cleanup,
- named-source provenance behavior,
- table rendering and table copy/CSV actions,
- image intent routing,
- image attachment validation, multimodal payload shape and saved-chat roundtrips,
- autosave debounce, title derivation and saved-chat updates,
- translation validation, caching and provider fallback,
- MCP result normalization,
- model discovery or settings behavior.