Spaces:
Sleeping
Sleeping
| """Visual Modality Router -> decides WHAT kind of visual fits an explicit | |
| student visualize request. | |
| This is now only ever reached from an explicit student action (a /visualize | |
| command or a natural-language "show me a chart of X" request in Chat) -- | |
| Wiki's old click-to-offer path was removed entirely. Because the student has | |
| already asked for a visual, "decline" is not offered as an outcome: the | |
| router always picks exactly one of the 5 real modalities. It does NOT verify | |
| the source has ENOUGH data for a specific chart, nor does it pick a concrete | |
| engine/template -- that finer extract-and-verify-and-synthesize work belongs | |
| to each of the 5 downstream engines individually (mirroring the | |
| D3TemplateRouter.select() -> D3DataExtractor.fill() two-stage split in | |
| app/agents/d3/). | |
| Decision rule: classify primarily from what the student's own wording asks | |
| for (an explicit "chart"/"graph" -> graph, "animate"/"simulate" -> 2d_anim, | |
| "3D model" -> 3d, "formula"/"derive" -> formula, otherwise a conceptual point | |
| -> 2d_text), using the source material only as secondary supporting context | |
| -- never as a reason to refuse an outcome. Downstream engines independently | |
| decide whether to ground the result in the source, the web, or a clearly | |
| labeled illustrative synthesis; this router's only job is picking the shape. | |
| """ | |
| from __future__ import annotations | |
| from typing import List, Literal | |
| from pydantic import BaseModel | |
| from app.agents.cerebras_client import CerebrasClient | |
| _MAX_CHUNK_CHARS = 3000 # mirrors BrainAgent.extract_curriculum cap | |
| class ModalityDecision(BaseModel): | |
| modality: Literal["formula", "graph", "2d_text", "3d", "2d_anim"] | |
| reasoning: str | |
| _SYSTEM_PROMPT = """\ | |
| You are the Visualization Router for a student research assistant. The student | |
| has explicitly asked for a visual -- your only job is picking which of 5 shapes | |
| best fits their request. Declining is not an option; always pick exactly one. | |
| Classify primarily from the STUDENT'S REQUEST wording itself, using the source | |
| material only as secondary context (it may be sparse, unrelated, or absent -- | |
| that never changes which modality is the right shape for what they asked for): | |
| - formula: the student is asking for an equation, derivation, or formula. | |
| - graph: the student is asking for a chart, plot, graph, or a comparison/series | |
| of data (e.g. "bar chart of X", "plot Y over time"). | |
| - 2d_text: the student is asking for a definitional or conceptual explanation | |
| best served by prose plus at most one citation, not a diagram. | |
| - 3d: the student is asking for a spatial/structural object -- a molecule, an | |
| anatomical structure, a 3D geometric shape or mechanism. | |
| - 2d_anim: the student is asking for a 2D dynamic process, motion, or | |
| simulation -- a mechanism, a waveform, a state transition, a physical | |
| process with movement. | |
| If the request itself doesn't name a type explicitly, infer the best fit from | |
| what's actually being asked about (a described mechanism or motion -> 2d_anim, | |
| a described structure -> 3d, a described relationship worth charting -> graph, | |
| otherwise -> 2d_text). | |
| Output a single JSON object matching the schema. One sentence for reasoning.\ | |
| """ | |
| class VisualModalityRouter: | |
| def __init__(self) -> None: | |
| self._client = CerebrasClient() | |
| def classify( | |
| self, | |
| selection_text: str, | |
| card_markdown: str, | |
| chunks: List[dict], | |
| familiarity: str, | |
| ) -> ModalityDecision: | |
| chunk_text = "\n\n".join(c["text"] for c in chunks) | |
| if len(chunk_text) > _MAX_CHUNK_CHARS: | |
| chunk_text = chunk_text[:_MAX_CHUNK_CHARS] | |
| messages = [ | |
| {"role": "system", "content": _SYSTEM_PROMPT}, | |
| { | |
| "role": "user", | |
| "content": ( | |
| f"STUDENT REQUEST: {selection_text}\n" | |
| f"STUDENT LEVEL: {familiarity}\n\n" | |
| f"SOURCE MATERIAL (may be sparse or unrelated):\n{chunk_text}\n\n" | |
| f"WIKI CARD SUMMARY:\n{card_markdown[:800]}" | |
| ), | |
| }, | |
| ] | |
| return self._client.structured_complete(messages, ModalityDecision, reasoning_effort="medium") | |