narada-env / ARCHITECTURE.md
Jin2413's picture
upgrades for openenv validation
b654948
|
Raw
History Blame Contribute Delete
7.75 kB
# Narada β€” Architecture & Reward Design
## Overview
Narada is a multi-step reinforcement learning environment where an LLM agent plays a clinical geneticist navigating a gene-disease knowledge graph. The agent observes a patient's HPO phenotype terms and a pool of candidate variants, then navigates the graph to identify the causal variant(s).
---
## Knowledge Graph
Built at runtime from two public biomedical datasets:
| Source | File | Content |
|--------|------|---------|
| HPO | `data/hp.obo` | 19,389 phenotype terms + hierarchy |
| ClinVar | `data/clinvar_pathogenic.tsv` | 92,000 high-confidence pathogenic variants (GRCh38, criteria provided / expert panel, deduplicated by AlleleID) |
### Node types
| Type | ID format | Count | Meaning |
|------|-----------|-------|---------|
| `phenotype` | `HP:XXXXXXX` | ~2,400 | HPO terms (catalog + 5-level ancestors) |
| `disease` | `DIS:<slug>` | ~28,000 | Disease names from ClinVar PhenotypeList |
| `gene` | `GENE:<symbol>` | ~3,268 | Gene symbols |
| `variant` | `VAR:<AlleleID>` | ~43,634 | Individual ClinVar variants |
| `pathway` | `PATH:<name>` | ~14 | Coarse pathway group (cardiac, neurological, …) |
**Total: 55,201 nodes, 70,741 edge-pairs**
### Edge structure
All edges are undirected (bidirectional). Key connections:
```
phenotype ↔ phenotype (HPO parent-child hierarchy)
phenotype ↔ disease (catalog wiring + inverted word-index matching)
disease ↔ gene (via ClinVar PhenotypeList)
gene ↔ variant (one gene β†’ many variants)
gene ↔ pathway (PATHWAY_MAP classification)
```
### Graph build performance
The `_add_hpo_nodes()` method uses an O(N+M) inverted word index instead of O(NΓ—M) brute-force matching. Cold build time: ~2 seconds. Loaded once at server startup, shared across all sessions.
---
## Session Lifecycle
```
Client Server
β”‚ β”‚
β”œβ”€β”€β”€ WebSocket connect ─────────►│ NaradaEnvironment() created (per session)
β”‚ β”‚
β”œβ”€β”€β”€ {"type":"reset", ...} ─────►│ generate_case() β†’ PatientCase
│◄── {"type":"observation"} ────── _build_observation() β†’ StepResult(reward=0.0)
β”‚ β”‚
β”œβ”€β”€β”€ {"type":"step", action} ───►│ _dispatch_action() β†’ raw reward
│◄── {"type":"observation"} ────── reward mapped to (0.01, 0.99)
β”‚ (repeat) β”‚
β”‚ β”‚
β”œβ”€β”€β”€ flag_causal(VAR:xxxxx) ────►│ _compute_terminal_reward() + _overseer_score()
│◄── {"type":"observation", β”‚ done=True, final reward returned
β”‚ done:true, reward:X} ───────
β”‚ β”‚
└─── WebSocket close ───────────►│ session destroyed
```
**WORKERS=1** is enforced. All state is in-memory per WebSocket connection; no shared state between sessions.
---
## Task Tiers
### monogenic (easy)
- 1 causal gene, 3–4 HPO phenotypes, 5–8 candidate variants
- Max 15 steps
- Tests: basic phenotype β†’ disease β†’ gene β†’ variant chain reasoning
### oligogenic (medium)
- 2 causal genes, 5–7 HPO phenotypes, 10–15 candidates
- Max 25 steps
- Tests: multi-objective tracking β€” the agent must flag both contributing variants; correct intermediate flags are recorded and the episode continues until all are found or a wrong variant is flagged
### phenotype_mismatch (hard)
- 1 causal gene (cardiac/neurological)
- A high-pathogenicity BRCA1/BRCA2/TP53 frameshift variant in the candidate pool as a decoy
- Max 20 steps
- Tests: causal discipline β€” pathogenicity score alone is not sufficient; the variant must match the patient's phenotype
---
## Reward Design
### Step-level rewards
| Action | Reward | Condition |
|--------|--------|-----------|
| `hop` | +0.15 | Target node is on the causal path |
| `hop` | βˆ’0.05 | Target node is off-path |
| `hop` | βˆ’0.10 | Hallucinated hop (node exists but not connected) |
| `backtrack` | +0.05 | Previous node was off-path (recovering) |
| `backtrack` | βˆ’0.05 | Previous node was on-path (wrong direction) |
| `request_lab` | βˆ’0.10 | Always penalised |
| `summarise_trail` | 0.00 | Neutral |
| Per-step | βˆ’0.01 | Applied to every action (efficiency pressure) |
### Terminal rewards
| Outcome | Reward |
|---------|--------|
| Correct flag (monogenic/mismatch) | +1.0 |
| Correct flag + timing bonus (step < 10) | +1.2 |
| Progress per correct variant (oligogenic) | `0.5 / total` non-terminal |
| All oligogenic variants flagged | `(correct/total) Γ— 0.5` + timing bonus |
| Flagged decoy in mismatch task | βˆ’0.5 |
| Wrong flag | βˆ’0.5 |
| Timeout (no flag) | `-0.25 + min(0.2, trail_size / max_steps Γ— 0.25)` |
### Overseer score (additive, 0.0–0.3)
Added only to successful terminal rewards. Computed locally without an LLM call:
| Criterion | Effect |
|-----------|--------|
| Hallucinated hops | βˆ’0.05 each |
| Visited < 3 unique nodes | βˆ’0.10 |
| Visited causal gene node | +0.05 |
### Score mapping
Rewards are kept as signed raw values internally, then mapped to the **open interval (0.01, 0.99)** before returning to the client. This preserves the ordering between penalties, neutral moves, and successes while satisfying the OpenEnv validator. `math.isfinite()` guards against NaN/inf.
---
## Three-Agent System
### Detective (trainable)
- Qwen3-1.7B fine-tuned with GRPO
- Navigates the graph, flags the causal variant
- Trained via `training/narada_grpo.ipynb`
### Overseer (local)
- Heuristic, no LLM call: reads only the trail and hop counters, not the free-form `reasoning` string
- Penalises hallucinated hops and trivial exploration
- Adds 0.0–0.3 only to successful terminal rewards
### Adversary (exploratory, WIP)
- Intended to generate harder cases targeting Detective failure patterns
- Reliable curriculum generation from agent error logs is an open research problem
- Current implementation falls back to random seed selection
---
## Action Space
```python
class NaradaAction(BaseModel):
action_type: str # hop | flag_causal | backtrack | request_lab | summarise_trail
node_id: Optional[str] # required for hop
variant_id: Optional[str] # required for flag_causal (format: VAR:12345)
test_type: Optional[str] # for request_lab
reasoning: str # agent's stated rationale (logged by Overseer)
```
## Observation Space
```python
class NaradaObservation(BaseModel):
step: int
max_steps: int
task_type: str
current_node: GraphNode
trail: List[GraphNode] # last 10 visited nodes
patient_phenotypes: List[str] # HPO IDs
phenotype_names: List[str]
candidate_variants: List[Variant] # 5–15 variants
step_reward: float
cumulative_reward: float
done: bool
info: Dict[str, Any] # ground_truth_hint revealed on done=True
```
---
## OpenEnv Compliance
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/health` | GET | Liveness check |
| `/metadata` | GET | Environment name + description |
| `/schema` | GET | Pydantic JSON schemas for action/observation/state |
| `/mcp` | POST | JSON-RPC 2.0 tool discovery |
| `/reset` | POST | Start new episode |
| `/step` | POST | Take action |
| `/state` | GET | Current episode metadata |
| `/ws` | WebSocket | Primary transport (persistent session) |
Validation: `python -m openenv.cli validate --url https://krishvenky-narada-env.hf.space` β†’ **6/6 passed**