docs: Ops Docs page on how Ped-AI and the two Milvus servers work

Written in the operator-handbook style of the Ops Docs collective (title,
last-updated line, short prose, tables, a quick-checks block, no secrets).
Ready to paste into the collective, or to publish once writing to Nextcloud
is permitted — that call was blocked by my permission layer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
This commit is contained in:
Daniel 2026-09-10 18:37:41 +02:00
parent d5001ed0b8
commit db208031ae

View file

@ -0,0 +1,121 @@
# Ped AI And Milvus
Last updated: 2026-09-11
What Ped-AI is, which Milvus is which, and where each kind of data lives. Written
for the operator handbook, so it says where things are rather than how they are
coded.
## What Ped-AI is
A clinical documentation assistant. Two halves:
- **Workspace** — encounter notes, dictation, SOAP, well and sick visits, ED,
hospital course, calculators, Learning Hub. Each generates text with an LLM
through the LiteLLM gateway.
- **Clinical Assistant** — a chat that answers only from an indexed medical
library, with numbered citations back to the source documents.
It runs as one container, `pediatric-ai-scribe`, on `127.0.0.1:3552` behind
Caddy. Postgres and Redis are its own; everything else it talks to belongs to
another stack.
## The two Milvus servers
They are easy to confuse, and both listen on 19530. Connecting to the wrong one
succeeds at the network level and then fails authentication, because each has
its own users — so a mistake here looks like a password problem.
| Which | Container | Database | Collection | Holds |
|---|---|---|---|---|
| **Clinical** | `nextcloud-mcp-server-milvus-1` | `default` | `mcp_bge_m3_1024` | The medical library Ped-AI answers from. ~1.8M chunks. |
| **Personal assistant** | `personal-assistant-storage-milvus-basic-milvus-1` | `basic` | `personal_assistant_bge_m3_1024` | Your Nextcloud mail, notes, tables and files. |
Both embed with **bge-m3** at 1024 dimensions, which is what the collection names
now say. Neither is reachable from the internet: the personal one publishes no
ports at all, the clinical one is bound to `127.0.0.1`.
Repositories, renamed 2026-09-11 to match what they hold:
- `personal-assistant-storage-milvus` — was `ped-ai-storage`
- `clinical-storage-milvus` — was `nextcloud-mcp-server`, and runs no MCP server,
only etcd, MinIO and Milvus
Container and volume prefixes still read `ped-ai-storage_*` on anything not yet
migrated; the project name is pinned deliberately, because changing it makes
Compose create empty volumes instead of finding the existing ones.
## How Milvus stores its data
Milvus keeps **segment files in MinIO**, not on a local disk, and its metadata in
etcd. That matters: `COMMON_STORAGETYPE=local` writes segments relative to the
working directory, so recreating the container destroys them while etcd still
references them, and the collection then hangs at `Loading` forever. Each Milvus
has its own MinIO and its own etcd container.
Access is per-database and per-collection. Renaming a collection revokes its
grants, because a grant names the collection — after any rename, re-run the
bootstrap that provisions the roles.
## Where each kind of data lives
| Data | Where |
|---|---|
| Accounts, notes, encounters, Learning Hub content and quizzes | Postgres (`pedscribe`) |
| Learning Hub semantic search | `learning_content.embedding`, pgvector |
| Generated images | MinIO bucket `generated-images` |
| Voice recordings, kept 24 hours | MinIO bucket `audio-backups` |
| Medical library chunks | Clinical Milvus |
| Nextcloud mail, notes, tables, files | Personal assistant Milvus |
Quizzes are ordinary relational tables and use no vectors at all.
## How a question is answered
1. The browser posts to `/api/clinical-assistant/chat/stream`.
2. Ped-AI calls the MCP tool `clinical_semantic_search` on `mcp-server-mcp-1`.
3. That searches the clinical Milvus and returns passages with scores.
4. Ped-AI sends those passages to the chat model as the only permitted source.
5. The answer streams back with numbered citations; the Sources panel shows the
passages behind them.
If retrieval returns nothing, the assistant says so rather than answering from
the model's own knowledge.
## Recordings
Every recording is kept for 24 hours, whether or not its transcription
succeeded, gzipped and encrypted before storage. A recording holds a screen wake
lock while it runs, survives moving around the app, and is saved with the module
it came from if the session ends. Recordings can be downloaded from
Settings → Audio backups.
Transcription is a gateway model — currently `mistral-voxtral-mini-transcribe`.
The browser's own speech recognition is a separate, off-by-default setting; it
sends audio to the browser vendor and is not HIPAA-compliant.
## Indexing
| Index | Written by | Source |
|---|---|---|
| Clinical | `mcp-server-mcp-indexer-1` | The medical library. Text extraction via `unstructured`, PyMuPDF4LLM and Tesseract. |
| Personal assistant | `personal-assistant-storage-milvus-basic-indexer-1` | Nextcloud. Mail first, then notes, deck, tables, calendar, todos, collectives, cookbook, talk, contacts, and files. PDF text via PyMuPDF4LLM. |
The folder the personal indexer walks is `BASIC_INDEXING_DOCUMENTS_FOLDER`. It
takes one folder or several separated by commas, each walked recursively:
```
BASIC_INDEXING_DOCUMENTS_FOLDER=Personal assistant,Clinical Notes
```
A complete listing is the authority for deletion, so moving that setting to a
different folder removes the chunks of everything no longer under an indexed
root.
## Quick checks
```bash
curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3552/api/health
docker ps --format '{{.Names}} {{.Status}}' | grep -E 'pediatric-ai-scribe|milvus|indexer'
docker logs --since 10m personal-assistant-storage-milvus-basic-indexer-1 | grep -c Indexed
```