--- title: Narada Env emoji: 🧬 colorFrom: blue colorTo: green sdk: docker app_port: 7860 pinned: false --- # Narada β€” Rare Disease Diagnosis RL Environment **Meta Γ— PyTorch OpenEnv Hackathon Γ— Scaler School of Technology β€” Grand Finale** **Live environment:** [krishvenky-narada-env.hf.space](https://huggingface.co/spaces/KrishVenky/narada-env) Β· **Blog:** [Blog.md](https://huggingface.co/spaces/KrishVenky/narada-env/blob/main/Blog.md) Β· **Colab training notebook:** [Open in Colab](https://colab.research.google.com/drive/15tPrE95ASXcBA2zKImWgmRoFM0OozPQm?usp=sharing) --- ## Results GRPO training on Qwen3-1.7B (LoRA rank 16, 200 steps curriculum) vs zero-shot baseline: | Task | Baseline | After GRPO | Gain | |---|---|---|---| | monogenic | 0.4955 | **0.572** | +15.4% | | oligogenic | 0.4955 | **0.561** | +13.2% | | phenotype_mismatch | 0.4955 | **0.552** | +11.4% | | **Average** | 0.4955 | **0.562** | **+13.3%** | ![Before/After GRPO](results/before_after.png) *Zero-shot baseline vs. trained agent across all three task tiers* ![Reward Curve](results/reward_curve.png) *Mean reward across 200 training steps (curriculum order). Shaded region = reward_std. Dotted line = zero-shot baseline.* ![Loss Curve](results/loss_curve.png) *Policy loss across curriculum. Phase boundaries shown as dashed vertical lines.* ![Reward Std](results/reward_std.png) *reward_std > 0 throughout confirms GRPO received real gradient signal β€” model never collapsed to uniform reward.* --- ## The Problem Most rare disease patients wait **4–7 years** for a correct diagnosis ([Lancet 2024](https://www.thelancet.com/journals/langlo/article/PIIS2214-109X(24)00056-1/fulltext), [Nature 2024](https://www.nature.com/articles/s41431-024-01604-z)). The delay is systemic, not a simple workflow gap: - Rare diseases are **clinically heterogeneous** β€” many present with non-specific symptoms common across hundreds of conditions ([PMC 2024](https://pmc.ncbi.nlm.nih.gov/articles/PMC11323401/)) - **60% of patients are initially misdiagnosed** with either a different physical illness or a psychological condition; patients consult 8+ specialists (EU) or 17+ specialists (US) before a correct diagnosis ([EURORDIS survey](https://www.eurordis.org/survey-reveals-lengthy-diagnostic-delays/)) - **"Medical ping-pong"** β€” patients are passed between specialists with poor cross-service communication ([NHS Genomics Education](https://www.genomicseducation.hee.nhs.uk/genotes/knowledge-hub/the-diagnostic-odyssey-in-rare-disease/)) - **95% of rare diseases have no approved treatment**, and poor clinician/patient awareness of symptom profiles compounds the problem ([Lancet 2024](https://www.thelancet.com/journals/langlo/article/PIIS2214-109X(24)00056-1/fulltext)) ClinVar contains 2M+ catalogued genetic variants; HPO maps 15,000+ diseases to phenotypic signatures. The bottleneck is **reasoning under uncertainty** β€” cross-referencing patient symptoms against thousands of candidate variants while resisting high-salience but causally irrelevant signals. This is exactly where current LLMs fail: they follow pathogenicity scores, not causal chains. **Narada's scope:** The full diagnostic odyssey involves referral pathways, specialist availability, and fragmented care across institutions. Narada targets the **variant prioritization substage** β€” given a patient's phenotype profile and a shortlist of candidate genomic variants, which variant is causally responsible? This substage has a ground-truth signal (ClinVar), a tractable action space (graph traversal), and measurable improvement via RL β€” making it ideal for methodology proof-of-concept work. --- ## What Narada Is A **reinforcement learning environment** where an LLM agent navigates a 55,000-node gene-disease knowledge graph built from real ClinVar and HPO data. The agent must diagnose a rare disease patient by reasoning through phenotype β†’ disease β†’ gene β†’ variant chains. **Three task tiers, increasing difficulty:** | Task | Description | Key Challenge | |---|---|---| | `monogenic` | Single causal gene, 3–4 phenotypes | Basic directional reasoning | | `oligogenic` | 2 causal genes (one variant each), 5–7 phenotypes | Multi-objective tracking across long trajectory | | `phenotype_mismatch` | Cardiac patient + BRCA1 frameshift decoy | Causal discipline β€” resist the highest-salience wrong signal | **Three-agent system:** - **Detective (Qwen3-1.7B, trainable)** β€” navigates the graph, flags the causal variant - **Overseer** β€” local heuristic (no LLM) that scores trajectory quality: penalises hallucinated hops, rewards touching the causal gene, and scales with a concise trail. Added only to *successful* terminal rewards. - **Adversary** *(planned)* β€” curriculum case generation targeting Detective failure patterns; reliable adversarial curriculum from agent error logs is an open research problem, deferred to future work --- ## Proof-of-Concept Framing This project uses Qwen3-1.7B as the Detective agent. At this scale, the honest goal is **methodology proof-of-concept**: can GRPO training on a verifiable graph-navigation task move the needle even on a constrained model? Measurable improvement from a low baseline is a legitimate research contribution. The environment and training pipeline are designed to generalize to larger models β€” the same loop applies to Qwen2.5-72B with zero code changes. --- ## Graph Built at runtime from: - `data/hp.obo` β€” 19,389 HPO phenotype terms - `data/clinvar_pathogenic.tsv` β€” 92,000 high-confidence pathogenic variants (GRCh38, criteria provided/expert panel, deduplicated) **Graph stats:** - 55,000+ nodes (phenotype, disease, gene, variant, pathway) - 70,000+ edge pairs - 3,268 genes represented --- ## Action Space | Action | Effect | Reward | |---|---|---| | `hop(node_id)` | Move to connected node | +0.15 relevant / βˆ’0.05 irrelevant | | `flag_causal(variant_id)` | Declare diagnosis; oligogenic cases allow multiple flags | +1.0 correct terminal, βˆ’0.5 wrong | | `backtrack()` | Return to previous node | +0.05 after wrong direction | | `request_lab(test)` | Get additional phenotype data | βˆ’0.10 penalty | | `summarise_trail()` | Compressed visit summary | 0.0 (neutral) | Signed raw rewards are mapped into OpenEnv's required score interval `(0.01, 0.99)`. This preserves penalties while keeping the validator-compatible output range. --- ## Running Locally ```bash # 1. Clone git clone https://github.com/KrishVenky/Narada-Env.git cd Narada-Env # 2. Create virtualenv (Python 3.10+) python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 3. Install pip install -r requirements.txt # 4. Set up environment variables cp .env.example .env # Edit .env and fill in your API keys # 5. Generate filtered ClinVar (one-time, ~3 min) python scripts/filter_clinvar.py # 6. Start server PYTHONPATH=src/envs uvicorn narada.server.app:app --port 7860 # 7. Open browser β†’ http://localhost:7860 ``` --- ## Contributing Pull requests welcome. To add a new task tier, reward component, or agent variant: 1. Fork the repo and create a branch off `main` 2. Environment logic lives in [src/envs/narada/](src/envs/narada/) β€” `environment.py` for reward design, `case_generator.py` for case sampling, `graph.py` for the knowledge graph 3. Add your changes and run the OpenEnv validator before opening a PR: ```bash openenv validate http://localhost:7860 ``` 4. Update `ARCHITECTURE.md` if you change reward values, node types, or the session protocol **Key files:** | File | Purpose | |---|---| | `src/envs/narada/server/environment.py` | Core RL loop, reward computation | | `src/envs/narada/server/app.py` | FastAPI entry point (WebSocket + HTTP debug) | | `src/envs/narada/graph.py` | Knowledge graph build + singleton | | `src/envs/narada/case_generator.py` | Patient case generation per tier | | `src/envs/narada/models.py` | Pydantic schemas (action/observation/state) | | `src/envs/narada/client.py` | Python WebSocket client for agents | | `training/narada_grpo.ipynb` | GRPO training notebook (Colab) | | `inference.py` | Benchmark script (Groq or HF backend) | --- ## OpenEnv Validation ```bash openenv validate https://krishvenky-narada-env.hf.space ``` All scores strictly in `(0.01, 0.99)`. `[END]` line always includes `score=` field. --- ## Baseline Benchmark Zero-shot evaluation via `inference.py` (Groq backend, no fine-tuning). Two single-episode samples per model show the core problem: zero-shot LLMs are **inconsistent** and frequently collapse into `summarise_trail` loops instead of navigating the graph. > Note: the exact scores below are reference runs from before reward hardening. Re-run `inference.py` before final submission so the README plots and tables match the current reward mapping. ### llama-3.3-70b-versatile (reference runs) | Task | Run 1 Score | Run 2 Score | Notes | |---|---|---|---| | `monogenic` | **0.990** | 0.433 | Run 1: solved in 4 steps. Run 2: hit summarise_trail loop after step 3 | | `oligogenic` | 0.500 | 0.240 | Run 1: WS disconnect mid-episode. Run 2: full summarise_trail timeout | | `phenotype_mismatch` | 0.060 | 0.225 | Run 1: looped on wrong gene 9Γ— before giving up. Run 2: pure timeout | ### Multi-model comparison (zero-shot, all 3 tasks, single run each) | Model | monogenic | oligogenic | phenotype_mismatch | Behavior | |---|---|---|---|---| | `llama-3.3-70b-versatile` | **0.990** | 0.500 | 0.060 | Hops graph; inconsistent | | `llama-3.1-8b-instant` | 0.310 | 0.425 | 0.310 | Hops but flags wrong variant | | `mixtral-8x7b-32768` | 0.233 | 0.220 | 0.225 | Full summarise_trail timeout | | `gemma2-9b-it` | 0.233 | 0.220 | 0.225 | Full summarise_trail timeout | The pattern is clear: large frontier models (llama-3.3-70b) occasionally navigate the graph correctly but are inconsistent (0.990 vs 0.433 on the same task across runs). Mid-size models (llama-3.1-8b) attempt navigation but misfire on the final flag. Smaller models (mixtral, gemma2) collapse entirely to the `summarise_trail` loop and never issue a `flag_causal`. Fine-tuning on the graph-navigation reward signal is intended to make correct phenotype β†’ gene β†’ variant chaining the default, not a lucky outcome that only large models achieve occasionally. **Target post-GRPO (Qwen3-1.7B):** consistent flag accuracy > 50% on monogenic, causal path coverage > 60%. > To switch backends: set `GROQ_API_KEY` for Groq, or `HF_TOKEN` for HF Inference Router. The script auto-detects which to use. --- ## Training Training notebook: `training/narada_grpo.ipynb` (Colab, Unsloth + HF TRL GRPO) Base model: `Qwen/Qwen3-1.7B` β€” fits on a free T4 (4-bit quantised, 17.4M trainable LoRA params) Tasks trained in curriculum order: `monogenic β†’ oligogenic β†’ phenotype_mismatch` ### Architecture **Multi-step outcome GRPO** β€” the model generates a complete 3–5 action *diagnostic plan* per prompt. The full plan is executed in the environment and the terminal reward (correct flag / wrong flag / timeout) becomes the training signal. This gives 10Γ— more reward variance than single-step GRPO: | Approach | Typical reward range | reward_std | Learning | |---|---|---|---| | 1-step (old) | 0.47–0.56 | ~0.03 | Slow | | Multi-step plan | 0.28–0.99 | ~0.20–0.35 | Strong | **Async-parallel reward** β€” all G=8 completions are evaluated concurrently via `asyncio.gather`. Each completion's plan runs as an independent WebSocket session. Total overhead β‰ˆ one episode round-trip, not 8Γ—. **Curriculum learning** β€” monogenic cases establish the basic hopβ†’flag behaviour before oligogenic (multi-objective) and phenotype_mismatch (decoy resistance) add harder constraints. **Milestone reward** β€” environment gives a +0.10 bonus the first time the agent visits the actual causal gene node. Creates a two-stage reward landscape: find the gene (+0.10) β†’ flag the variant (+1.0). ### Key training config | Param | Value | Why | |---|---|---| | `num_generations` | 8 | More completions per prompt β†’ higher reward_std | | `temperature` | 1.1 | Forces diverse hop targets within a group | | `max_completion_length` | 800 | Fits 4–5 JSON action blocks | | `N_SEEDS_PER_TASK` | 40 | 120 total prompts (was 60) β€” more case diversity | | `LR` | 5e-6 | Conservative to avoid catastrophic forgetting on 1.7B | --- ## Multi-Model Benchmark Run zero-shot comparison across sub-5B models (all runnable on T4): ```bash HF_TOKEN=hf_... python benchmark.py --n_seeds 3 ``` Models compared: Qwen3-0.6B, Qwen3-1.7B, Qwen3-4B, Llama-3.2-3B, Phi-3.5-mini Results are saved to `benchmark_results.json` after each run. --- ## Graph Export (Neo4j) Export the 55K-node knowledge graph to Cypher for Neo4j Aura (free tier) or Desktop: ```bash PYTHONPATH=src/envs python scripts/export_neo4j.py # Generates neo4j_nodes.cypher + neo4j_rels.cypher # Import into Neo4j Browser, then: # MATCH (n:NaradaNode) RETURN n LIMIT 100 ``` The live environment also exposes a subgraph JSON endpoint for D3.js visualization: ``` GET /graph/subgraph?node_id=GENE:BRCA2&depth=2 ```