rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
a2ce565
Β·
1 Parent(s): 85a34b0

docs: ADR-025 (single-Chroma-collection rationale) + KI-018/019 backlog

Browse files

ADR-025 documents the architectural decision to keep one Chroma
collection partitioned by doc_type metadata rather than splitting into
4 collections (policies, regulatory, reviews, profiles). Records the
trade-off: joint retrieval simplicity wins over per-type independence,
with explicit revisit triggers for when to split later.

KI-018 β€” rag/policies.duckdb is a 132-byte empty stub from ADR-004's
hybrid design that was never populated. Marketplace reads JSON
directly instead. Options: (A) populate from data/policy_facts/*.json
for SQL filtering, (B) remove dead code. Backlog.

KI-019 β€” kb/policies/*.md (224 hand-written policy summaries) are NOT
embedded. Retrieval would benefit from natural-language summary chunks
for "what does X cover?" style questions. Backlog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

docs/40-evaluation/known-issues.md CHANGED
@@ -352,3 +352,49 @@ filter or boost by facet when intent is clear. Live count:
352
 
353
  Verified locally; pushed to HF Dataset; live HF Space picks up
354
  on next rebuild.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
  Verified locally; pushed to HF Dataset; live HF Space picks up
354
  on next rebuild.
355
+
356
+ ### KI-018 β€” `rag/policies.duckdb` stub is dead code (132 bytes, never populated)
357
+
358
+ **Severity:** P3
359
+ **Source:** `rag/policies.duckdb` is in the repo but empty (132-byte sqlite header only)
360
+ **Discovered:** Data architecture audit 2026-05-14
361
+
362
+ ADR-004 designed a hybrid structured (DuckDB) + vector (Chroma) split. The
363
+ DuckDB half was never populated; the marketplace UI ended up reading
364
+ `data/policy_facts/*.json` directly (one file per policy) at request time
365
+ via the `/api/policies/all` endpoint. The 132-byte file in the repo is
366
+ visually misleading β€” looks like a real store, isn't.
367
+
368
+ **Fix plan (two options):**
369
+ - (A) Populate it from `data/policy_facts/*.json` so the marketplace can
370
+ do SQL filters (sum-insured β‰₯ X AND room-rent-cap = no AND restoration
371
+ = unlimited). Worth ~30 min.
372
+ - (B) Remove the file + the `import duckdb` + the `policies.duckdb` ADR
373
+ reference + add a note in ADR-025 explaining why structured filtering
374
+ reads JSON directly. Cleaner architecturally.
375
+
376
+ **Recommendation:** (A) β€” once activated, the marketplace tab gets
377
+ proper SQL filtering that's faster than the current N-JSON-load pattern.
378
+
379
+ ### KI-019 β€” `kb/policies/*.md` not embedded (224 human-readable summaries left out of retrieval)
380
+
381
+ **Severity:** P3
382
+ **Source:** `kb/policies/<policy_id>.md` files exist (224 markdown summaries) but the ingest pipeline only reads from `rag/extracted/*.json`. The natural-language summary text is never embedded.
383
+ **Discovered:** Data architecture audit 2026-05-14
384
+
385
+ Each policy has a hand-written or auto-generated `kb/policies/<id>.md`
386
+ that summarizes the policy in 1-2 pages of natural English (e.g.
387
+ "Care Supreme covers in-patient hospitalization across India with a
388
+ 36-month PED waiting…"). These would be EXCELLENT retrieval targets
389
+ for "what does X cover?" style questions because they're already
390
+ phrased for human reading. Currently retrieval matches against raw
391
+ PDF-extracted prose which is denser + less semantic.
392
+
393
+ **Fix plan:** Add a kb-markdown ingestion path in `rag/ingest.py` that
394
+ walks `kb/policies/*.md`, splits at H2 boundaries (each H2 β‰ˆ one
395
+ schema-field discussion), embeds each section, and writes with
396
+ `doc_type="summary"` so retrieval can prefer summary chunks when the
397
+ intent is high-level Q&A.
398
+
399
+ Expected impact: better grounding for natural-language "tell me about X"
400
+ questions; cleaner citations (one summary chunk vs. 3-4 raw chunks).
docs/60-decisions/ADR-025-single-chroma-collection-with-metadata-partitioning.md ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ADR-025: Single Chroma collection with metadata partitioning (not 4 separate collections)
2
+
3
+ **Status:** Locked
4
+ **Date:** 2026-05-14
5
+
6
+ ## Context
7
+
8
+ The bot retrieves four logically distinct kinds of content at request time:
9
+
10
+ 1. **Policy chunks** (PDF wordings, brochures, CIS, prospectuses) β€” what the policy says
11
+ 2. **Regulatory chunks** (IRDAI master circulars, Insurance Act, etc.) β€” what the regulator mandates
12
+ 3. **Review chunks** (per-insurer claim metrics, aggregator ratings, Reddit/YouTube sentiment, news) β€” what users say
13
+ 4. **Profile chunks** (one chunk per active session, holding the user's age / dependents / income / conditions / budget) β€” what we know about THIS user
14
+
15
+ A reasonable mental model is "four separate databases". The literal Chroma equivalent would be four collections (`policies`, `regulatory`, `reviews`, `profiles`) queried separately, with results merged in the orchestrator.
16
+
17
+ ## Decision
18
+
19
+ **Keep ONE Chroma collection (`policies`) partitioned by a `doc_type` metadata field.** All four logical types co-exist in the same vector index. Retrieval is a single cosine-similarity call; the orchestrator applies metadata filters only when the user's intent is clearly type-specific (e.g., a regulatory-only question).
20
+
21
+ ```
22
+ collection: policies (Chroma; 7,366 chunks; BGE-small 384-d cosine)
23
+ doc_type=wordings (5,401 chunks) β€” policy wordings
24
+ doc_type=brochure ( 611 chunks)
25
+ doc_type=cis ( 303 chunks)
26
+ doc_type=prospectus ( 483 chunks)
27
+ doc_type=regulatory ( 498 chunks) β€” IRDAI + Insurance Act
28
+ doc_type=review ( 60 chunks) β€” 10 insurers Γ— 6 facets (KI-017)
29
+ doc_type=profile (variable) β€” one per active session_id
30
+ ```
31
+
32
+ ## Alternatives considered
33
+
34
+ | Approach | Why rejected |
35
+ |---|---|
36
+ | 4 separate collections (`policies`, `regulatory`, `reviews`, `profiles`) | Joint retrieval requires N queries + manual merge + N embedding-pool warmups. Loses the natural "user profile boosted in the same retrieval as the policy text" trick. ~2Γ— higher per-turn latency for marginal lifecycle benefit. |
37
+ | Per-insurer collections | Cross-insurer comparison (the killer feature) becomes N queries. No upside. |
38
+ | Per-doc_type with shared embedder | Same as 4-collection but with one embedder. Still N queries. |
39
+
40
+ ## Why one-collection wins for THIS bot
41
+
42
+ 1. **Joint retrieval is the headline RAG move.** The bot's most cited feature is "the brain sees policy text + regulatory mandate + user's own profile in the same context window". One Chroma query returns all three. Splitting would require an explicit fan-out + merge.
43
+
44
+ 2. **Profile-chunk boost is organic.** When `session_id` is passed to `retrieve()`, the chunk with `policy_id="profile_<session_id>"` is fetched separately and prepended to the top-k. This works cleanly because the profile lives in the same collection β€” it's a metadata lookup, not a cross-collection fetch.
45
+
46
+ 3. **Free-tier resource discipline.** Each Chroma collection in the deployment has its own HNSW index (~50 MB of RAM warmup per collection on first use). The HF Space free tier has 16 GB RAM total; 4 collections means ~200 MB of cold-start memory pressure for negligible UX gain.
47
+
48
+ 4. **Metadata partitioning is functionally equivalent.** When intent IS type-specific (e.g., the user asks about IRDAI), the orchestrator can apply `where={"doc_type": "regulatory"}` filters. The retrieval is single-query, same speed, same ergonomics as a separate-collection design β€” just with a 0.1ms metadata filter step.
49
+
50
+ ## Consequences
51
+
52
+ **Positive:**
53
+
54
+ - Joint retrieval stays cheap (one query, one warmup).
55
+ - Profile chunk boost is a simple Chroma `get(ids=[profile_<session>])` call.
56
+ - Single backup target (`rag/vectors/chroma.sqlite3` + HNSW binaries) β€” simpler dataset hygiene.
57
+ - Mental model: "one vector store, one schema, doc_type tells you what kind".
58
+
59
+ **Negative:**
60
+
61
+ - A bad embedding for one doc_type can pollute global recall on unrelated queries. We mitigate with retrieval-floor + faithfulness gates.
62
+ - We cannot iterate embedding models per type (e.g., a legal-domain embedder for regulatory only) without changing the architecture.
63
+
64
+ ## When we'd revisit
65
+
66
+ | Condition | Migration |
67
+ |---|---|
68
+ | Per-type embedding models become necessary | Split off the heaviest type (regulatory) into its own collection with its own embedder. |
69
+ | Multi-tenant deployment (profiles for thousands of concurrent users) | Move profiles to a separate `profiles` collection with TTL-based eviction policy. |
70
+ | Review content grows to >5,000 chunks | Move reviews to a dedicated collection so policy retrieval isn't diluted. |
71
+
72
+ ## Related KIs and ADRs
73
+
74
+ - **KI-017** β€” Enriched per-facet review chunks (10 β†’ 60). Validates that doc_type partitioning scales for richer content without requiring a separate collection.
75
+ - **KI-018** β€” Empty `rag/policies.duckdb` cleanup. The structured store from ADR-004 is currently dormant; structured filtering reads `data/policy_facts/*.json` directly.
76
+ - **ADR-004** β€” Hybrid structured + vector retrieval (the original design β€” DuckDB for filtering, Chroma for citing).
77
+ - **ADR-011** β€” Local BGE-small embeddings.