Fix ai-service Dockerfile: bake in drug_entities.json, override its path

This commit is contained in:
2026-08-10 10:35:13 +07:00
parent a4b8e1c4db
commit 60b4397032
51 changed files with 4302 additions and 2087 deletions
+21 -12
View File
@@ -17,12 +17,12 @@ disclaimer.
| **auth-service** (NestJS) | Signup/login, password hashing, JWT issuance/refresh | Postgres (users); no dependency on other services |
| **user-service** (NestJS) | Profile data, preferences, account settings | Postgres (profiles), called by gateway |
| **chat-service** (NestJS) | Chat session lifecycle, message history persistence | Postgres (chat_sessions, chat_messages); calls ai-service per user message, persists both turns |
| **ai-service** (Python/FastAPI) | RAG orchestration: embed query → vector search in Qdrant → build grounded prompt → call OpenAI → return answer + citations | Qdrant (vector search), OpenAI API; stateless, does not own chat history |
| **ingestion** (Python, offline batch) | One-time/periodic job: parse PDF → monographs → chunks → embeddings → upsert to Qdrant | Qdrant (write), OpenAI embeddings API; runs as CLI/CI/k8s Job, never in the live request path |
| **ai-service** (Python/FastAPI) | RAG orchestration: understand query (LLM) → route to deterministic section/drug retrieval in Qdrant → generate + verify (LLM) → return answer + citations | Qdrant (payload-filtered retrieval), AWS Bedrock (Cohere embed-v4 for query embedding where used, Qwen3 via the Converse API for understanding/generation/entailment, Cohere rerank); conversation history is an in-process dict per `RagAgent`, not yet durable — see ADR 0008 |
| **ingestion** (Python, offline batch) | One-time/periodic job: parse PDF → monographs → chunks → embeddings → upsert to Qdrant | Qdrant (write), AWS Bedrock (`cohere.embed-v4:0`); runs as CLI/CI/k8s Job, never in the live request path |
| **web** (Next.js) | Chat UI, auth UI, citation/disclaimer rendering, session list | Calls api-gateway only |
**Sync vs async**: the live chat path (web → gateway → chat-service →
ai-service → Qdrant + OpenAI → back) is synchronous request/response.
ai-service → Qdrant + AWS Bedrock → back) is synchronous request/response.
Ingestion is fully decoupled, offline, batch — it populates Qdrant ahead of
time and is never triggered by a chat request, since parsing the 37MB PDF and
embedding thousands of chunks takes minutes. Internal protocol is REST/JSON
@@ -105,10 +105,12 @@ methodology, cross-tool comparison, and validation numbers.
header/footer-boilerplate leak into section text (98.4% of monographs
affected) must be fixed upstream before this design runs against real
data.
4. **Embedding + load**: OpenAI `text-embedding-3-small` in batches, upserted
into a versioned Qdrant collection (`drug_monographs_v1`) keyed by
`chunk_id` for idempotent re-runs; collection aliasing allows re-ingesting
with a changed chunking strategy without downtime.
4. **Embedding + load**: AWS Bedrock `cohere.embed-v4:0` in batches
(cached by `(model_id, input_kind, text_sha256)` so a reload needs no
repeat cloud calls), upserted into Qdrant collection `duocthu_v1`
(15,100 points, live) keyed by `uuid5(chunk_id)` for idempotent re-runs; a
`<collection>__manifest` sidecar records the corpus sha/model/dimensions
and `ai-service` refuses to start against a mismatched one (F-05).
5. **Batch job, not synchronous**: runs as a CLI command locally, and as a
Kubernetes `Job`/`CronJob` in production — never inside the ai-service
request path.
@@ -119,8 +121,15 @@ methodology, cross-tool comparison, and validation numbers.
context, never state a dosage/contraindication/interaction not present in
it, always append a disclaimer, and say "not found in the formulary"
rather than guess when retrieval is irrelevant.
- **Retrieval-confidence gate**: below a similarity threshold, skip the LLM
call entirely and return a canned "consult a professional" response.
- **Deterministic routing, not a similarity-confidence gate.** The live
path resolves drug + section by exact payload filter (`section_key`
routing moved contraindication hit@1 from 0.05 to 1.00 — similarity
ranking alone was not reliable enough to gate on). A quarantined table/
formula in the retrieved evidence, or missing page provenance, forces
`VERIFY_PDF`/abstain deterministically — never an LLM-reported confidence
score. Dense vector similarity search exists (`QdrantRetriever.search()`)
but is reachable only in the legacy no-generator-configured mode, not the
live agent path. See ADR 0008.
- **Citations from metadata, not LLM prose**: the `citations` list is built
directly from retrieved-chunk metadata, independent of what the LLM says,
so the frontend can always show verifiable sources.
@@ -136,9 +145,9 @@ methodology, cross-tool comparison, and validation numbers.
1. **Ingestion pipeline + populated, queryable vector DB.** Done when a CLI
run populates Qdrant and a test script retrieves the correct
drug/section chunk for a sample query — no API, no LLM call yet.
2. **ai-service (FastAPI) wrapping RAG + OpenAI.** Done when a `curl` to
`/query` returns a grounded answer with a traceable citation and an
always-present disclaimer.
2. **ai-service (FastAPI) wrapping RAG + AWS Bedrock.** Done when a `curl` to
`/v1/rag/query` returns a grounded answer with a traceable citation and an
always-present disclaimer. **Done** — live since 2026-08-05, see ADR 0008.
3. **auth/user/chat services + api-gateway.** Done when register → login →
chat message flows end-to-end through the gateway only, persisted in
Postgres.