MedicalRAG — developer guide¶
app/services/rag_engine.py implements MedicalRAG: indexing and search for the AI Medical Assistant knowledge base.
Repo: https://github.com/valera7623/AI-medical-assistant.
Enabling¶
from app.services.rag_engine import is_rag_enabled, rag_mode, get_medical_rag
is_rag_enabled() # RAG_ENABLED and MEDICAL_ASSISTANT_ENABLED
rag_mode() # local | external | hybrid (invalid → local)
Global singleton: get_medical_rag() (thread-safe lazy init).
Modes¶
RAG_MODE |
Chroma | External POST …/search |
Notes |
|---|---|---|---|
local |
yes | no | memory fallback if Chroma unavailable |
external |
skipped | yes | soft-fallback to local when empty |
hybrid |
yes | yes | merge_hits with RAG_HYBRID_LOCAL_WEIGHT |
Variables:
| Env | Purpose |
|---|---|
RAG_COLLECTION_NAME |
Collection name |
RAG_EXTERNAL_URL |
External API base (…/v1) |
RAG_EXTERNAL_API_KEY |
Bearer (optional) |
RAG_EXTERNAL_TIMEOUT_SECONDS |
HTTP timeout |
RAG_HYBRID_LOCAL_WEIGHT |
Local weight in hybrid (0–1) |
CHROMA_PERSIST_DIR |
Chroma persistence path |
Language filter¶
Only two lanes: en and ru.
from app.services.rag_engine import normalize_rag_language, VALID_RAG_LANGUAGES
normalize_rag_language("ru-RU") # → "ru"
normalize_rag_language("en_US") # → "en"
normalize_rag_language(None) # → DEFAULT_LANGUAGE normalized to en|ru
Search drops hits whose language does not match the request. UI and seed must set language explicitly.
Document shape¶
Normalized document:
{
"id": "guidelines:acs-en",
"title": "ACS — initial steps",
"category": "guidelines",
"language": "en",
"text": "...",
"metadata": {
"title": "...",
"category": "guidelines",
"language": "en",
"source_file": "guidelines.json"
}
}
Methods:
upsert_documents(docs) → int— write local (admin ingest also POSTs to external/ingestwhen configured);search(query, *, language, limit=5) → list[hit]— normalized hits withsimilarity,source.
Hit after _normalize_hit:
{
"text": "...",
"title": "...",
"category": "...",
"language": "en",
"similarity": 0.82,
"source": "local", # local | external | hybrid
"id": "..."
}
Hybrid merge¶
merge_hits(local, external, limit=5, local_weight=0.5):
- Scales similarity by local / external weights.
- Dedupes by
idor text hash. - Sorts by similarity descending.
Unit tests: tests/test_rag_engine.py.
Seed¶
Script scripts/seed_medical_knowledge.py:
# Inside aima-app container
docker compose exec app python scripts/seed_medical_knowledge.py --also-db
docker compose exec app python scripts/seed_medical_knowledge.py \
--from-url http://mock_rag:8090/v1/documents.json \
--api-key "$RAG_EXTERNAL_API_KEY" \
--skip-local-files
Sources:
app/data/medical_knowledge/guidelines.jsonapp/data/medical_knowledge/diseases.jsonapp/data/medical_knowledge/medications.json- optional JSON dump via
--from-url
Mock external API¶
scripts/mock_rag_server.py and Compose profile mock-rag (container aima-mock-rag):
docker compose --profile mock-rag up -d mock_rag
Endpoints: /health, POST /v1/search, POST /v1/ingest, GET /v1/documents.json.
For the app on the same network: RAG_EXTERNAL_URL=http://mock_rag:8090/v1.
Tutor integration¶
app/services/ai_tutor.py calls MedicalRAG when answering; routes:
| Path | Purpose |
|---|---|
POST /api/tutor/ask |
Answer + sources |
POST /api/tutor/knowledge/ingest |
Admin upsert |
GET /api/tutor/knowledge/status |
Config without secrets |
MEDICAL_ASSISTANT_ENABLED gates all learning APIs (/api/tutor, /api/cases, /api/exam, /api/progress).
Local stack¶
| Service | Port / name |
|---|---|
| App | http://localhost:8001 (APP_PORT) |
| Redis host | 6380 |
| Containers | aima-* |
| Mock RAG | 8090 (profile mock-rag) |
Extending¶
- Always set
languageon documents. - New external KBs must implement
/searchand optionally/ingest. - Do not commit secrets (
RAG_EXTERNAL_API_KEY) — only.env.example. - After changing
RAG_MODE, restart the app container and re-seed if needed.