diff --git a/docs/ops-docs-ped-ai-and-milvus.md b/docs/ops-docs-ped-ai-and-milvus.md new file mode 100644 index 00000000..32de62c2 --- /dev/null +++ b/docs/ops-docs-ped-ai-and-milvus.md @@ -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 +```