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

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

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