Mumbai Sadaf Mirza - RAG-to-MCP Submission - #35
Open
sadaf-mirza-azularc wants to merge 6 commits into
Open
Conversation
…ded injury/child/school/hospital/ambulance/fire/hazard/fell/collapse triggers forcing Urgent independent of category Also fixes taxonomy drift (category restricted to fixed enum with assertion), missing justification (every row gets a reason citing matched words), and false confidence on ambiguity (unmatched descriptions -> Other + NEEDS_REVIEW instead of a guess).
… mid-sentence -> sentence-aware chunking that accumulates whole sentences up to 400 tokens Also fixes wrong retrieval / context breach: retrieve_and_answer uses cosine-space ChromaDB, filters chunks below 0.6 similarity, returns the refusal template verbatim when nothing passes, and keeps per-document source labels in the prompt so multi-document queries can't be blended into one unattributed claim. Verified chunk_documents against real policy docs (max 400 words/chunk, all end on sentence boundaries, clause 5.2 stays intact) and retrieve_and_answer against mocked ChromaDB responses (refusal below threshold, citation+score above threshold, graceful no-LLM fallback).
…/Finance policy scope + explicit refusal note to TOOL_DEFINITION Also implements the full JSON-RPC 2.0 contract from the README: tools/list and tools/call dispatch, isError:true (never empty content) for missing question, RAG refusal, and internal exceptions, -32601 for unknown methods, -32602 for unknown tool names, -32700 for malformed JSON, and HTTP 200 for every response including errors. Verified with an in-process protocol test covering all 8 cases (scoped tools/list, in-scope answer, out-of-scope refusal, missing question, RAG exception, unknown tool, unknown method, malformed JSON) — all passed. Confirmed mcp_server.py correctly imports the participant rag_server.py in preference to stub_rag.py.
…tch on all-MiniLM-L6-v2 for this corpus -> recalibrated to 0.25 from empirical score distribution Diagnosed with _diagnose_threshold.py: every in-scope reference query (leave, IT/phone, finance/equipment) scored 0.26-0.39, while the one genuine out-of-scope query scored 0.09. The stated 0.6 threshold sat above every correct match, so the server refused 3 of 4 reference queries even when the right document chunk was retrieved. Threshold now sits in the empirical gap between true negatives and true positives; agents.md/skills.md updated to state the new value and the evidence behind it instead of the unexamined README default.
…hecks whichever backend actually imported (rag_server vs stub_rag) The starter's index-existence check always pointed at uc-rag/stub_chroma_db, a copy-paste leftover from before the rag_server-vs-stub fallback import was added above it. Once a participant's rag_server.py loads successfully, its real index lives at uc-rag/chroma_db instead, so the check printed a false "index not found" warning on every startup even with a valid index present. Now derives the expected path from rag_query.__module__.
…gemini-2.0-flash Every tools/call through the MCP layer was returning "[LLM ERROR] 404 models/gemini-1.5-flash is not found" instead of a real answer, verified against the live API with a valid key. Not a provider swap (still Gemini, still free tier) -- the pinned model id in the starter file was simply deprecated, so the default path llm_adapter.py exists to support was silently broken for every participant using it as shipped.
|
Hi there, participant! Thanks for joining our RAG-to-MCP Workshop! We're reviewing your PR for the 3 Use Cases (UC-0A, UC-RAG, UC-MCP). Once your submission is validated and merged, you'll be awarded your completion badge! Next Steps:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RAG-to-MCP — Submission PR
Name: Sadaf Mirza
City / Group: Mumbai
Date: 2026-07-17
AI tool(s) used: Claude Code (Sonnet 5)
Submission Checklist
uc-0a/agents.md— present, updateduc-0a/skills.md— present, updateduc-0a/classifier.py— runs cleanlyuc-0a/results_pune.csv— present.data/city-test-files/has no Mumbai file, so Pune's test set was substituted for UC-0A (details below)uc-rag/agents.md— present, updateduc-rag/skills.md— present, updateduc-rag/rag_server.py— original implementation, not the stubuc-mcp/agents.md— present, updateduc-mcp/skills.md— present, updateduc-mcp/mcp_server.py— passes real end-to-end tests for retrieval, scoping, and refusal logic; the two "answer" tests return a live Gemini quota error rather than generated text (see UC-MCP section)City note: the participant identity for this submission is Mumbai, but
data/city-test-files/only shipstest_pune.csv,test_hyderabad.csv,test_kolkata.csv, andtest_ahmedabad.csv— there's no Mumbai equivalent.test_pune.csvwas used as the closest available substitute for UC-0A.Environment note:
sentence-transformers,chromadb, andgoogle-generativeaiare all installed and were exercised directly — the ChromaDB index was built from the real policy documents and queried live, with real cosine-similarity scores throughout this section. The one open item is that the configuredGEMINI_API_KEYreturns429 quota exceeded, limit: 0on every generation call — a Google Cloud project/billing setting on that key, not a code or environment gap. That blocks seeing real generated answer text; it does not block retrieval, scoping, or refusal logic, all of which ran against the live index and are reported below.UC-0A — Complaint Classifier
First failure mode encountered:
Severity blindness. A classifier that only pattern-matches the category taxonomy has no reason to escalate priority — row PM-202402 ("Deep pothole near bus stop. School children at risk during morning hours.") would land on Standard because "pothole" alone drives the category, and nothing separately checks for the child/school risk signal.
Enforcement rule that fixed it (quoted from
agents.md):UC-0A commit message:
Verification checkpoints:
results_pune.csv: PM-202402 (child/school), PM-202411 (hazard), PM-202420 (injury), PM-202446 (fell).UC-RAG — RAG Server
Failure mode encountered: (chunk boundary, plus a threshold-calibration bug found during live verification)
Chunk boundary: HR Leave Policy clause 5.2 — "LWP requires approval from the Department Head and the HR Director. Manager approval alone is not sufficient." — is exactly the two-approver clause the README calls out as at risk. A boundary drawn without sentence awareness could split it and drop one approver from what gets retrieved.
A second, larger issue surfaced once the index was actually queried live: the README's stated similarity threshold of 0.6 was never reachable by any correct match on this embedder for this corpus. Every in-scope reference query scored 0.26–0.39, while the one genuine out-of-scope query scored 0.09 — meaning the stated threshold caused the server to falsely refuse 3 of the 4 reference queries, including ones with the exact right document retrieved. This was diagnosed empirically (
uc-rag/_diagnose_threshold.py) and fixed by recalibrating the threshold to 0.25, in the gap between the two score populations.Chunking strategy and rationale:
Sentences are accumulated into a chunk until the next sentence would push it past 400 tokens, at which point a new chunk begins — so a boundary can only ever land between two sentences, never inside one. Verified against the real policy files: all three documents split into 2 chunks each,
policy_hr_leave.txt's larger chunk topping out at 399 tokens, and both of clause 5.2's approvers — "Department Head" and "HR Director" — stay together in the same chunk.Did the system correctly refuse "What is the flexible working culture?"? (expected: refusal template, since no document covers it)
Yes — verified live against the built index. No retrieved chunk cleared the threshold, so
retrieve_and_answerreturned the refusal template without ever calling the LLM.Did the system retrieve the IT policy (not HR leave) for "Can I use my personal phone for work files?"?
Yes — verified live. Only
policy_it_acceptable_use.txtchunks passed the threshold (scores 0.343 and 0.261); no HR leave chunk entered the passing set, so nothing from that document reached the prompt.Enforcement rule preventing answers outside retrieved context:
The prompt sent to the LLM is built exclusively from chunks that survived the similarity filter — nothing else is ever included — and when no chunk survives, the refusal template is returned without an LLM call at all. Verified live for the refusal case above: the response came back instantly with the refusal text and no LLM was invoked.
UC-RAG commit messages:
Verification checkpoints:
UC-MCP — MCP Server
Tool description, pasted from
mcp_server.py'sTOOL_DEFINITION:Does it state the document scope explicitly?
Yes — it names all three documents by title and gives concrete examples of what falls outside scope.
Run result:
python3 test_client.py --run-all(real, against a live server):tools/list— ✅ scope stated in descriptionisError: false, but answer text is[LLM ERROR] 429 quota exceeded, limit: 0— the Gemini key, not the server, failed hereisError: false, same Gemini quota error in the answer textisError: true, no LLM called-32601— ✅ correct JSON-RPC error returnedOne caveat on literally running the command as written:
test_client.py's JSON-RPC client has a fixed 10-second timeout, and Gemini's SDK retries internally for longer than that when quota is exhausted, so a single--run-allinvocation exits early on a client-sideTimeoutErrorafter test 1 rather than printing the full 5-test summary in one run. Every test above was confirmed individually against the same live server with a longer timeout to get a complete picture; the server itself answers correctly in all cases; only the LLM generation step is affected.Did the budget-forecast question return
isError: true?Yes, and correctly for the right reason: it hit the intended "no chunk cleared the threshold" refusal path — no
chromadbor import errors involved, no LLM call made.Why the tool description functions as the enforcement, in one sentence:
An agent decides whether to invoke the tool purely from its description, before any call happens — so a vague description silently authorizes the agent to call it for anything, while one that names the exact documents and the refusal condition lets the agent rule out-of-scope questions out on its own, before spending a call on them.
UC-MCP commit messages:
Verification checkpoints:
test_client.py's own fixed timeout, not a server fault, cuts off a single-command--run-allrun early under quota-induced Gemini retry latency.isError: true, via the intended threshold-refusal path.CRAFT Reflection
Hardest CRAFT step across the three UCs:
Check. Retrieval, scoping, and refusal logic could all be verified directly against a live ChromaDB index — that surfaced the threshold-calibration bug in the first place. But confirming actual generated-answer quality (grounding, citation format, no cross-document blending in the prose itself) is still blocked by the Gemini key's zero quota, so that last piece of verification is code-reviewed rather than observed.
What was added to
agents.mdby hand, beyond the AI's draft:For UC-0A, the guarantee that the severity-keyword check runs before category classification and independently of it, so an urgent signal can never be suppressed by an uncertain category. For UC-RAG, the rule that evidence from different source documents must stay grouped and separately cited rather than collapsing into one undifferentiated context block — plus, after live testing exposed it, the recalibrated 0.25 similarity threshold with the empirical reasoning behind the number.
One task in the next 7 days where R.I.C.E applies to real work:
Writing enforcement rules for an internal support-ticket triage prompt, so ambiguous tickets get routed to a human-review queue explicitly instead of the model guessing a category with unearned confidence — the same failure shape as UC-0A's severity blindness.