from __future__ import annotations from typing import Literal, Optional, List, Dict, Any from pydantic import BaseModel, Field NodeStatus = Literal["ongoing", "completed"] class NodeData(BaseModel): id: str label: str description: str = "" status: NodeStatus = "ongoing" depth: int = 1 complexity: int = Field(3, ge=1, le=5, description="Conceptual density: 1=simple, 5=very complex") parent_id: Optional[str] = None children_ids: List[str] = Field(default_factory=list) # Which uploaded paper(s) this node draws from -> scopes its lessons/RAG. Empty = all. document_ids: List[str] = Field(default_factory=list) # Canonical page/bbox-grounded evidence behind this node. These IDs let # lessons, chat, graphs, and citations transfer the same source anchors. evidence_ids: List[str] = Field(default_factory=list) project_memory_ids: List[str] = Field(default_factory=list) # True when this node synthesizes overlapping treatments from 2+ source documents # (see BrainAgent.cleanup_curriculum) -> tutoring agents use this to avoid # conflating the papers' individual treatments when explaining the concept. is_merged: bool = Field(False) merge_summary: str = Field("", description="What's shared vs. distinct across source documents, when is_merged is True") # "exploration" = spawned by the GraphCuratorAgent from off-curriculum student # activity (see COMMIT_PROJECT) -> rendered as a distinct color from planned # curriculum nodes so the student can see what they discovered vs. what was planned. origin: Literal["curriculum", "exploration"] = "curriculum" # How the student's cross-session Cognee memory reshaped this node when the # curriculum was generated -> this is what makes memory LOAD-BEARING on the # graph's structure (not just soft prompt context). "new" = genuinely new # material; "review" = a light pass because prior sessions show it's largely # mastered; "scaffold" = inserted to shore up a gap memory surfaced. Brain Agent # sets it during generation from query_prior_knowledge (see BrainAgent). memory_tag: Literal["new", "review", "scaffold"] = "new" class KnowledgeEdge(BaseModel): id: str source: str target: str edge_type: Literal["prerequisite", "related", "contains"] = "prerequisite" class NodePatch(BaseModel): node_id: str status: Optional[NodeStatus] = None updated_label: Optional[str] = None updated_description: Optional[str] = None new_children: Optional[List[str]] = Field(None, description="IDs of newly generated sub-topics") class FormulaStep(BaseModel): latex: str explanation: str class FormulaContent(BaseModel): main_latex: str steps: List[FormulaStep] = Field(default_factory=list) class TextRefContent(BaseModel): body_markdown: str source_label: str = "" source_url: str = "" class HTML5VisualPayload(BaseModel): html_code: str = "" # iframe path: graph / 3d / 2d_anim / decline animation_type: Literal["formula", "graph", "2d_text", "3d", "2d_anim"] explanation: str = Field("", description="A 2-3 sentence explanation describing exactly what the visualization demonstrates, how to use the interactive elements/sliders, and how it connects to the study material.") formula: Optional[FormulaContent] = None text_ref: Optional[TextRefContent] = None # Honest transparency signal for the frontend: "paper" only when the engine # actually verified specific content against the uploaded source (a verbatim # anchor match, or -- for D3 charts, which have no anchor concept -- at least # one generated label/value found verbatim in the source chunks). "web" is # reserved for when a cited external source was used instead (TextRefEngine # already accepts web_results; not yet wired to a live caller). Anything else # is illustrative/from the model's own knowledge and must say so. source: Literal["paper", "web", "model_knowledge"] = "model_knowledge" # App-owned registered templates are deterministic HTML and must never enter # the generated-code sandbox repair loop. trusted_template: bool = False template_id: str = "" class ExternalAction(BaseModel): action_type: Literal["YOUTUBE_FETCH", "GENERATE_FLASHCARDS"] parameters: Dict[str, Any] class OrchestratorAction(BaseModel): intent: Literal["UPDATE_GRAPH", "GENERATE_VISUAL", "STREAM_CHAT", "TOOL_CALL"] chat_stream_response: str = "" graph_patches: Optional[List[NodePatch]] = None visual_payload: Optional[HTML5VisualPayload] = None tool_execution: Optional[ExternalAction] = None