| import html |
| import json |
| import os |
| import re |
| import tempfile |
| import time |
| import urllib.error |
| import urllib.request |
| import uuid |
| from dataclasses import dataclass, field |
| from datetime import datetime, timezone |
| from typing import Any |
|
|
| import gradio as gr |
| from huggingface_hub import InferenceClient |
|
|
|
|
| APP_TITLE = "Glass-Box Agent" |
| APP_TAGLINE = "A tiny ReAct agent whose trace is a living, forkable tree." |
| MAX_STEPS = 6 |
| DEFAULT_MODEL_ID = os.getenv("GLASS_BOX_MODEL_ID", "Qwen/Qwen2.5-7B-Instruct") |
| BACKEND_OFFLINE = "Offline heuristic" |
| BACKEND_HF = "HF Inference small model" |
| BACKEND_MODAL = "Modal served model" |
| MODAL_APP_NAME = os.getenv("GLASS_BOX_MODAL_APP", "glass-box-agent-modal") |
| MODAL_MODEL_FUNCTION = os.getenv("GLASS_BOX_MODAL_MODEL_FUNCTION", "reason_next") |
| MODAL_TRACE_FUNCTION = os.getenv("GLASS_BOX_MODAL_TRACE_FUNCTION", "store_trace") |
| MODAL_MODEL_URL = os.getenv("GLASS_BOX_MODAL_MODEL_URL", "") |
| MODAL_TRACE_URL = os.getenv("GLASS_BOX_MODAL_TRACE_URL", "") |
| MODAL_TUNE_FUNCTION = os.getenv("GLASS_BOX_MODAL_TUNE_FUNCTION", "start_rl_tune") |
| MODAL_TUNE_URL = os.getenv("GLASS_BOX_MODAL_TUNE_URL", "") |
| MODAL_TRACE_ENABLED = bool(MODAL_TRACE_URL or os.getenv("GLASS_BOX_MODAL_TRACE_ENABLED")) |
| START_TASK = "Task intake" |
| START_RESEARCH = "Research / evidence" |
| START_PROTOTYPE = "Prototype sketch" |
| START_CRITIQUE = "Critique dead end" |
| START_VALIDATE = "Validation pass" |
| START_SYNTHESIS = "Final synthesis" |
| START_MODES = [ |
| START_TASK, |
| START_RESEARCH, |
| START_PROTOTYPE, |
| START_CRITIQUE, |
| START_VALIDATE, |
| START_SYNTHESIS, |
| ] |
| LABELS = [ |
| "useful", |
| "weak", |
| "wrong", |
| "unsafe", |
| "redundant", |
| "dead_end", |
| "backtrack_good", |
| "fork_winner", |
| "fork_loser", |
| "delightful", |
| ] |
| DATASET_EXAMPLES = [ |
| { |
| "name": "Transparent research agent", |
| "task": "Compare two designs for a transparent small-model research agent and choose the one users can trust.", |
| "start_phase": START_RESEARCH, |
| "steps": 4, |
| "feedback_note": "The preferred branch cites visible evidence and is easier to audit.", |
| }, |
| { |
| "name": "Hackathon demo critique", |
| "task": "Improve a hackathon demo so judges can feel the agent's reasoning, not just read a trace.", |
| "start_phase": START_CRITIQUE, |
| "steps": 5, |
| "feedback_note": "The preferred branch makes the fork/retry moment more immediate.", |
| }, |
| { |
| "name": "Small model math workflow", |
| "task": "Plan a small-model assistant that calculates totals, exposes mistakes, and lets users retry weak steps.", |
| "start_phase": START_TASK, |
| "steps": 4, |
| "feedback_note": "The preferred branch separates computation from explanation.", |
| }, |
| { |
| "name": "RL feedback product loop", |
| "task": "Design a product loop where human trace labels become finetuning and RL preference data.", |
| "start_phase": START_PROTOTYPE, |
| "steps": 5, |
| "feedback_note": "The preferred branch turns user edits into reusable training data.", |
| }, |
| ] |
| DATASET_NAMES = [example["name"] for example in DATASET_EXAMPLES] |
|
|
|
|
| @dataclass |
| class TraceNode: |
| id: str |
| parent: str | None |
| kind: str |
| title: str |
| body: str |
| status: str = "done" |
| depth: int = 0 |
| timeline: str = "main" |
| created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) |
| meta: dict[str, Any] = field(default_factory=dict) |
|
|
|
|
| def new_state() -> dict[str, Any]: |
| root = TraceNode( |
| id="root", |
| parent=None, |
| kind="task", |
| title="Waiting for task", |
| body="Enter a task and press Run. Every thought, tool call, observation, and fork will appear here.", |
| status="idle", |
| depth=0, |
| ) |
| return { |
| "nodes": [root.__dict__], |
| "selected_id": "root", |
| "active_tip": "root", |
| "timeline_count": 1, |
| "transcript": [], |
| "task": "", |
| "model_note": "offline heuristic ReAct engine", |
| "marked_ids": [], |
| "feedback": [], |
| "trace_id": f"trace-{uuid.uuid4().hex[:10]}", |
| "modal_store": {}, |
| "action_log": "Select a node from the trace table, then retry, replay, or label it.", |
| } |
|
|
|
|
| def reset_state(task: str, model_note: str | None = None) -> dict[str, Any]: |
| task = task.strip() or "Plan a delightful tiny AI demo for the hackathon." |
| root = TraceNode( |
| id="root", |
| parent=None, |
| kind="task", |
| title="Task", |
| body=task, |
| status="running", |
| depth=0, |
| meta={"task": task}, |
| ) |
| return { |
| "nodes": [root.__dict__], |
| "selected_id": "root", |
| "active_tip": "root", |
| "timeline_count": 1, |
| "transcript": [f"Task: {task}"], |
| "task": task, |
| "model_note": model_note or "offline heuristic ReAct engine", |
| "marked_ids": [], |
| "feedback": [], |
| "trace_id": f"trace-{uuid.uuid4().hex[:10]}", |
| "modal_store": {}, |
| "action_log": "Trace started. Select any node to operate on it.", |
| } |
|
|
|
|
| def node_by_id(state: dict[str, Any], node_id: str) -> dict[str, Any] | None: |
| return next((node for node in state["nodes"] if node["id"] == node_id), None) |
|
|
|
|
| def children_by_parent(nodes: list[dict[str, Any]]) -> dict[str | None, list[dict[str, Any]]]: |
| children: dict[str | None, list[dict[str, Any]]] = {} |
| for node in nodes: |
| children.setdefault(node["parent"], []).append(node) |
| return children |
|
|
|
|
| def descendant_nodes(state: dict[str, Any], node_id: str) -> list[dict[str, Any]]: |
| children = children_by_parent(state.get("nodes", [])) |
| descendants: list[dict[str, Any]] = [] |
|
|
| def walk(parent: str) -> None: |
| for child in children.get(parent, []): |
| descendants.append(child) |
| walk(child["id"]) |
|
|
| walk(node_id) |
| return descendants |
|
|
|
|
| def upstream_nodes(state: dict[str, Any], node_id: str) -> list[dict[str, Any]]: |
| nodes: list[dict[str, Any]] = [] |
| current = node_by_id(state, node_id) |
| while current: |
| nodes.append(current) |
| parent = current.get("parent") |
| current = node_by_id(state, parent) if parent else None |
| return list(reversed(nodes)) |
|
|
|
|
| def node_labels(node: dict[str, Any]) -> list[str]: |
| labels = node.get("meta", {}).get("labels", []) |
| return labels if isinstance(labels, list) else [] |
|
|
|
|
| def mark_node(state: dict[str, Any], node_id: str, label: str) -> str: |
| node = node_by_id(state, node_id) |
| if not node: |
| return "No node selected." |
| node.setdefault("meta", {}) |
| labels = node_labels(node) |
| if label and label not in labels: |
| labels.append(label) |
| node["meta"]["labels"] = labels |
| marked = state.setdefault("marked_ids", []) |
| if node_id not in marked: |
| marked.append(node_id) |
| state["selected_id"] = node_id |
| return f"Marked {node['title']} as {label}." |
|
|
|
|
| def mark_summary(state: dict[str, Any]) -> str: |
| marked = state.get("marked_ids", []) |
| if not marked: |
| return "No marked nodes yet." |
| lines = [] |
| for node_id in marked: |
| node = node_by_id(state, node_id) |
| if node: |
| labels = ", ".join(node_labels(node)) or "marked" |
| lines.append(f"{node['title']} ({node['kind']}): {labels}") |
| return "\n".join(lines) or "No marked nodes yet." |
|
|
|
|
| def dataset_example(name: str) -> dict[str, Any]: |
| return next((example for example in DATASET_EXAMPLES if example["name"] == name), DATASET_EXAMPLES[0]) |
|
|
|
|
| def load_dataset_example(name: str): |
| example = dataset_example(name) |
| return ( |
| example["task"], |
| int(example["steps"]), |
| example["start_phase"], |
| example["feedback_note"], |
| f"Loaded dataset example: {example['name']}. Press Run trace to inspect it.", |
| ) |
|
|
|
|
| def dataset_result_rows(results: list[dict[str, Any]]) -> list[list[str]]: |
| return [ |
| [ |
| item.get("name", ""), |
| str(item.get("nodes", 0)), |
| str(item.get("preference_pairs", 0)), |
| item.get("selected_label", ""), |
| item.get("trace_id", ""), |
| ] |
| for item in results |
| ] |
|
|
|
|
| def node_training_view(node: dict[str, Any] | None) -> dict[str, Any]: |
| if not node: |
| return {} |
| return { |
| "id": node.get("id"), |
| "kind": node.get("kind"), |
| "title": node.get("title"), |
| "body": node.get("body"), |
| "timeline": node.get("timeline", "main"), |
| "labels": node_labels(node), |
| "meta": node.get("meta", {}), |
| } |
|
|
|
|
| def preference_context(state: dict[str, Any], node_id: str) -> str: |
| upstream = upstream_nodes(state, node_id) |
| lines = [f"Task: {state.get('task') or 'Untitled task'}"] |
| for node in upstream[-5:]: |
| lines.append(f"{node.get('kind', 'node')}: {node.get('title', '')} - {node.get('body', '')}") |
| return "\n".join(lines) |
|
|
|
|
| def comparison_node(state: dict[str, Any], selected_id: str) -> dict[str, Any] | None: |
| children = children_by_parent(state.get("nodes", [])) |
| for node in reversed(upstream_nodes(state, selected_id)): |
| parent = node.get("parent") |
| siblings = [child for child in children.get(parent, []) if child.get("id") != node.get("id")] |
| if siblings: |
| different_timeline = [ |
| child for child in siblings if child.get("timeline", "main") != node.get("timeline", "main") |
| ] |
| return (different_timeline or siblings)[-1] |
| return None |
|
|
|
|
| def feedback_rows(state: dict[str, Any] | None) -> list[list[str]]: |
| rows = [] |
| for item in (state or {}).get("feedback", []): |
| rows.append( |
| [ |
| item.get("id", ""), |
| item.get("chosen", {}).get("title", ""), |
| item.get("rejected", {}).get("title", ""), |
| item.get("reason", ""), |
| item.get("created_at", ""), |
| ] |
| ) |
| return rows |
|
|
|
|
| def record_preference_pair( |
| state: dict[str, Any], |
| chosen: dict[str, Any], |
| rejected: dict[str, Any], |
| reason: str, |
| ) -> str: |
| reason = reason.strip() or "Human preferred this branch after inspecting the trace." |
| mark_node(state, chosen["id"], "fork_winner") |
| mark_node(state, rejected["id"], "fork_loser") |
| pair = { |
| "id": f"pref-{uuid.uuid4().hex[:8]}", |
| "created_at": datetime.now(timezone.utc).isoformat(), |
| "source": "human_trace_preference", |
| "reason": reason, |
| "task": state.get("task", ""), |
| "context": preference_context(state, chosen["id"]), |
| "chosen": node_training_view(chosen), |
| "rejected": node_training_view(rejected), |
| "dpo": { |
| "prompt": preference_context(state, chosen["id"]), |
| "chosen": f"{chosen.get('title', '')}: {chosen.get('body', '')}", |
| "rejected": f"{rejected.get('title', '')}: {rejected.get('body', '')}", |
| }, |
| } |
| state.setdefault("feedback", []).append(pair) |
| state["selected_id"] = chosen["id"] |
| return f"Recorded preference: {chosen['title']} over {rejected['title']}." |
|
|
|
|
| def add_node( |
| state: dict[str, Any], |
| parent: str, |
| kind: str, |
| title: str, |
| body: str, |
| status: str = "done", |
| timeline: str = "main", |
| meta: dict[str, Any] | None = None, |
| ) -> str: |
| parent_node = node_by_id(state, parent) |
| depth = int(parent_node["depth"]) + 1 if parent_node else 0 |
| node_id = f"{kind[:1]}-{uuid.uuid4().hex[:7]}" |
| node = TraceNode( |
| id=node_id, |
| parent=parent, |
| kind=kind, |
| title=title, |
| body=body, |
| status=status, |
| depth=depth, |
| timeline=timeline, |
| meta=meta or {}, |
| ) |
| state["nodes"].append(node.__dict__) |
| state["active_tip"] = node_id |
| state["selected_id"] = node_id |
| return node_id |
|
|
|
|
| def classify_task(task: str) -> dict[str, Any]: |
| lower = task.lower() |
| wants_math = bool(re.search(r"\b(sum|calculate|math|count|average|total|percent|number)\b", lower)) |
| wants_research = bool(re.search(r"\b(compare|research|find|source|which|why|best|evidence)\b", lower)) |
| wants_build = bool(re.search(r"\b(build|make|design|prototype|app|demo|ship)\b", lower)) |
| wants_plan = bool(re.search(r"\b(plan|strategy|steps|roadmap|schedule)\b", lower)) |
| return { |
| "math": wants_math, |
| "research": wants_research, |
| "build": wants_build, |
| "plan": wants_plan, |
| "risk": "high" if any(word in lower for word in ["medical", "legal", "finance", "safety"]) else "normal", |
| } |
|
|
|
|
| def tiny_reasoner(task: str, step: int, memory: list[str]) -> dict[str, str]: |
| traits = classify_task(task) |
| if step == 1: |
| return { |
| "thought": "I should make the task concrete before using tools. A small agent wins by narrowing scope early.", |
| "tool": "task_decomposer", |
| "input": task, |
| } |
| if step == 2 and traits["research"]: |
| return { |
| "thought": "This has a research flavor, so I will gather candidate evidence instead of hallucinating a confident answer.", |
| "tool": "mini_search", |
| "input": extract_keywords(task), |
| } |
| if step == 2 and traits["math"]: |
| return { |
| "thought": "There may be arithmetic hidden in the task. I will isolate computable numbers before I decide.", |
| "tool": "calculator", |
| "input": task, |
| } |
| if step == 3 and traits["build"]: |
| return { |
| "thought": "The useful move is to turn the idea into a buildable interface with one memorable interaction.", |
| "tool": "prototype_sketcher", |
| "input": task, |
| } |
| if step == 3: |
| return { |
| "thought": "I should test whether my current path is too generic and create a more opinionated alternative.", |
| "tool": "critique", |
| "input": " ".join(memory[-3:]), |
| } |
| if step == 4 and traits["plan"]: |
| return { |
| "thought": "The task asks for action, so I will convert the findings into a sequence with a visible finish line.", |
| "tool": "planner", |
| "input": task, |
| } |
| if step == 5: |
| return { |
| "thought": "I should compress the trace into a final answer while preserving uncertainty and next actions.", |
| "tool": "synthesizer", |
| "input": " ".join(memory[-5:]), |
| } |
| return { |
| "thought": "I have enough signal. One final consistency check should catch weak claims before the answer.", |
| "tool": "validator", |
| "input": " ".join(memory[-5:]), |
| } |
|
|
|
|
| def hf_reasoner(task: str, step: int, memory: list[str], model_id: str) -> dict[str, str]: |
| model_id = model_id.strip() or DEFAULT_MODEL_ID |
| system = ( |
| "You are the planning core of Glass-Box Agent, a small transparent ReAct agent. " |
| "Return only compact JSON with keys thought, tool, and input. " |
| "Allowed tools: task_decomposer, mini_search, calculator, prototype_sketcher, critique, planner, synthesizer, validator." |
| ) |
| user = { |
| "task": task, |
| "step": step, |
| "memory": memory[-5:], |
| "instruction": "Choose the next useful ReAct thought and tool call. Keep thought under 24 words.", |
| } |
| client = InferenceClient(token=os.getenv("HF_TOKEN") or None) |
| response = client.chat_completion( |
| model=model_id, |
| messages=[ |
| {"role": "system", "content": system}, |
| {"role": "user", "content": json.dumps(user)}, |
| ], |
| max_tokens=180, |
| temperature=0.3, |
| ) |
| content = response.choices[0].message.content |
| match = re.search(r"\{.*\}", content or "", flags=re.DOTALL) |
| payload = json.loads(match.group(0) if match else content) |
| thought = str(payload.get("thought", "")).strip() |
| tool = str(payload.get("tool", "validator")).strip() |
| tool_input = str(payload.get("input", task)).strip() |
| allowed = { |
| "task_decomposer", |
| "mini_search", |
| "calculator", |
| "prototype_sketcher", |
| "critique", |
| "planner", |
| "synthesizer", |
| "validator", |
| } |
| if tool not in allowed: |
| tool = "validator" |
| if not thought: |
| thought = "I should validate the current branch before trusting it." |
| if not tool_input: |
| tool_input = task |
| return {"thought": thought, "tool": tool, "input": tool_input} |
|
|
|
|
| def choose_reasoner(task: str, step: int, memory: list[str], backend: str, model_id: str) -> tuple[dict[str, str], str]: |
| if backend == BACKEND_HF: |
| try: |
| return hf_reasoner(task, step, memory, model_id), f"HF Inference model: {model_id.strip() or DEFAULT_MODEL_ID}" |
| except Exception as exc: |
| fallback = tiny_reasoner(task, step, memory) |
| fallback["thought"] = f"{fallback['thought']} (HF backend fell back: {type(exc).__name__})" |
| return fallback, f"HF backend requested but fell back to offline heuristic: {type(exc).__name__}" |
| if backend == BACKEND_MODAL: |
| try: |
| return modal_reasoner(task, step, memory, model_id), f"Modal served model: {model_id.strip() or DEFAULT_MODEL_ID}" |
| except Exception as exc: |
| fallback = tiny_reasoner(task, step, memory) |
| fallback["thought"] = f"{fallback['thought']} (Modal backend fell back: {type(exc).__name__})" |
| return fallback, f"Modal backend requested but fell back to offline heuristic: {type(exc).__name__}" |
| return tiny_reasoner(task, step, memory), "offline heuristic ReAct engine" |
|
|
|
|
| def modal_reasoner(task: str, step: int, memory: list[str], model_id: str) -> dict[str, str]: |
| payload = { |
| "task": task, |
| "step": step, |
| "memory": memory[-5:], |
| "model_id": model_id.strip() or DEFAULT_MODEL_ID, |
| } |
| response = call_modal_json(MODAL_MODEL_URL, MODAL_MODEL_FUNCTION, payload) |
| decision = response.get("decision", response) |
| thought = str(decision.get("thought", "")).strip() |
| tool = str(decision.get("tool", "validator")).strip() |
| tool_input = str(decision.get("input", task)).strip() |
| if not thought: |
| thought = "I should validate this branch before trusting it." |
| if not tool_input: |
| tool_input = task |
| return {"thought": thought, "tool": tool, "input": tool_input} |
|
|
|
|
| def call_modal_json(url: str, function_name: str, payload: dict[str, Any]) -> dict[str, Any]: |
| if url: |
| request = urllib.request.Request( |
| url, |
| data=json.dumps(payload).encode("utf-8"), |
| headers={"Content-Type": "application/json"}, |
| method="POST", |
| ) |
| try: |
| with urllib.request.urlopen(request, timeout=30) as response: |
| return json.loads(response.read().decode("utf-8")) |
| except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as exc: |
| raise RuntimeError(f"Modal web endpoint failed: {type(exc).__name__}") from exc |
|
|
| try: |
| import modal |
| except ImportError as exc: |
| raise RuntimeError("Modal SDK is not installed and no Modal endpoint URL is configured.") from exc |
|
|
| try: |
| function = modal.Function.from_name(MODAL_APP_NAME, function_name) |
| result = function.remote(payload) |
| except Exception as exc: |
| raise RuntimeError(f"Modal function lookup failed: {type(exc).__name__}") from exc |
| return result if isinstance(result, dict) else {"result": result} |
|
|
|
|
| def trace_payload(state: dict[str, Any], artifact: str = "trace", event: str = "snapshot") -> dict[str, Any]: |
| return { |
| "app": APP_TITLE, |
| "artifact": artifact, |
| "event": event, |
| "trace_id": state.get("trace_id") or f"trace-{uuid.uuid4().hex[:10]}", |
| "exported_at": datetime.now(timezone.utc).isoformat(), |
| "model_note": state.get("model_note"), |
| "task": state.get("task"), |
| "nodes": state.get("nodes", []), |
| "feedback": state.get("feedback", []), |
| "transcript": state.get("transcript", []), |
| } |
|
|
|
|
| def rl_payload(state: dict[str, Any]) -> dict[str, Any]: |
| return { |
| "app": APP_TITLE, |
| "artifact": "rl_feedback_dataset", |
| "trace_id": state.get("trace_id") or f"trace-{uuid.uuid4().hex[:10]}", |
| "exported_at": datetime.now(timezone.utc).isoformat(), |
| "task": state.get("task", ""), |
| "model_note": state.get("model_note", ""), |
| "preference_pairs": state.get("feedback", []), |
| "supervised_labels": [ |
| { |
| "node": node_training_view(node), |
| "labels": node_labels(node), |
| } |
| for node in state.get("nodes", []) |
| if node_labels(node) |
| ], |
| "notes": [ |
| "preference_pairs are shaped for DPO/RLHF: prompt, chosen, rejected.", |
| "supervised_labels can seed easy finetuning or reward-model heuristics.", |
| ], |
| } |
|
|
|
|
| def persist_trace_to_modal(state: dict[str, Any], event: str) -> dict[str, Any]: |
| if not MODAL_TRACE_ENABLED: |
| return {"enabled": False} |
| payload = trace_payload(state, event=event) |
| result = call_modal_json(MODAL_TRACE_URL, MODAL_TRACE_FUNCTION, payload) |
| state["modal_store"] = { |
| "last_event": event, |
| "stored_at": datetime.now(timezone.utc).isoformat(), |
| "result": result, |
| } |
| return result |
|
|
|
|
| def persist_trace_safely(state: dict[str, Any], event: str) -> None: |
| try: |
| persist_trace_to_modal(state, event) |
| except Exception as exc: |
| state["modal_store"] = { |
| "last_event": event, |
| "error": type(exc).__name__, |
| "stored_at": datetime.now(timezone.utc).isoformat(), |
| } |
|
|
|
|
| def extract_keywords(text: str) -> str: |
| words = re.findall(r"[A-Za-z][A-Za-z0-9+-]{2,}", text.lower()) |
| stop = {"the", "and", "for", "with", "that", "this", "from", "into", "about", "while", "should"} |
| return ", ".join(dict.fromkeys(word for word in words if word not in stop))[:180] or "small model agent" |
|
|
|
|
| def run_tool(tool: str, tool_input: str, task: str) -> str: |
| if tool == "task_decomposer": |
| return ( |
| "Subgoals: identify the user-facing promise; choose the smallest useful tool loop; " |
| "surface uncertainty; produce a trace humans can inspect." |
| ) |
| if tool == "mini_search": |
| return ( |
| f"Mock search notes for '{tool_input}': use primary sources, cite constraints, and separate observed facts " |
| "from model inference. In the hackathon, this trace can be published as the Open Trace artifact." |
| ) |
| if tool == "calculator": |
| nums = [float(match) for match in re.findall(r"-?\d+(?:\.\d+)?", tool_input)] |
| if not nums: |
| return "No explicit numbers found. I should avoid pretending there was arithmetic." |
| return f"Numbers found: {nums}. Sum={sum(nums):g}; mean={sum(nums) / len(nums):g}; count={len(nums)}." |
| if tool == "prototype_sketcher": |
| return ( |
| "Interface sketch: left panel for task controls, center tree for trace, right inspector for node details, " |
| "bottom drawer for exported JSON and replay." |
| ) |
| if tool == "critique": |
| return ( |
| "Critique: this could become another opaque chatbot unless the fork action is immediate, visual, and useful." |
| ) |
| if tool == "planner": |
| return ( |
| "Plan: run a short trace, inspect a weak node, fork it with a different assumption, compare final branches." |
| ) |
| if tool == "synthesizer": |
| return ( |
| "Synthesis: answer from the strongest branch, show discarded branches, and let the user fork any questionable step." |
| ) |
| if tool == "validator": |
| return "Validation: every final claim should point back to a trace node, tool observation, or explicit assumption." |
| return "Tool unavailable." |
|
|
|
|
| def make_branch_hint(step: int, thought: str) -> str: |
| if step == 2: |
| return "Alternate timeline: skip research and prototype immediately." |
| if "generic" in thought: |
| return "Alternate timeline: make it stranger and more demo-first." |
| return "Alternate timeline: question the previous assumption before moving on." |
|
|
|
|
| def seed_start_phase(state: dict[str, Any], start_phase: str) -> tuple[str, list[str], int]: |
| if start_phase == START_TASK: |
| return "root", [], 1 |
|
|
| state["transcript"].append(f"Start from: {start_phase}") |
| memory: list[str] = [] |
| parent = "root" |
| step = 1 |
|
|
| if start_phase in {START_RESEARCH, START_PROTOTYPE, START_CRITIQUE, START_VALIDATE, START_SYNTHESIS}: |
| thought = add_node( |
| state, |
| parent, |
| "thought", |
| "Seed: frame task", |
| "Assume the task has already been scoped; begin from the selected phase.", |
| status="done", |
| meta={"seed": True, "start_phase": start_phase}, |
| ) |
| tool = add_node( |
| state, |
| thought, |
| "tool", |
| "task_decomposer", |
| state["task"], |
| status="done", |
| meta={"seed": True}, |
| ) |
| parent = add_node( |
| state, |
| tool, |
| "observation", |
| "Seed observation", |
| "Working context: user wants a transparent, forkable small-agent trace.", |
| status="done", |
| meta={"seed": True}, |
| ) |
| memory.append("Working context: user wants a transparent, forkable small-agent trace.") |
| step = 2 |
|
|
| if start_phase in {START_PROTOTYPE, START_CRITIQUE, START_VALIDATE, START_SYNTHESIS}: |
| thought = add_node( |
| state, |
| parent, |
| "thought", |
| "Seed: evidence branch", |
| "Assume the relevant constraints are known and move toward interface decisions.", |
| status="done", |
| meta={"seed": True, "start_phase": start_phase}, |
| ) |
| tool = add_node( |
| state, |
| thought, |
| "tool", |
| "mini_search", |
| extract_keywords(state["task"]), |
| status="done", |
| meta={"seed": True}, |
| ) |
| parent = add_node( |
| state, |
| tool, |
| "observation", |
| "Evidence snapshot", |
| "Constraint snapshot: <=32B model, visible trace, forkable branches, exportable artifact.", |
| status="done", |
| meta={"seed": True}, |
| ) |
| memory.append("Constraint snapshot: <=32B model, visible trace, forkable branches, exportable artifact.") |
| step = 3 |
|
|
| if start_phase in {START_CRITIQUE, START_VALIDATE, START_SYNTHESIS}: |
| thought = add_node( |
| state, |
| parent, |
| "thought", |
| "Seed: prototype branch", |
| "Assume the first interface draft exists and inspect where it can fail.", |
| status="done", |
| meta={"seed": True, "start_phase": start_phase}, |
| ) |
| tool = add_node( |
| state, |
| thought, |
| "tool", |
| "prototype_sketcher", |
| state["task"], |
| status="done", |
| meta={"seed": True}, |
| ) |
| parent = add_node( |
| state, |
| tool, |
| "observation", |
| "Prototype snapshot", |
| "Workspace layout: command rail, trace tree, node inspector, exportable artifact.", |
| status="done", |
| meta={"seed": True}, |
| ) |
| memory.append("Workspace layout: command rail, trace tree, node inspector, exportable artifact.") |
| step = 4 |
|
|
| if start_phase in {START_CRITIQUE, START_VALIDATE, START_SYNTHESIS}: |
| checkpoint = add_node( |
| state, |
| parent, |
| "checkpoint", |
| "Seed checkpoint", |
| "Repeat from here: this prototype point can be retried, labeled, or replayed downstream.", |
| status="ready", |
| meta={"seed": True, "start_phase": start_phase}, |
| ) |
| add_node( |
| state, |
| checkpoint, |
| "backtrack", |
| "Ready to replay", |
| "Use this checkpoint to retry the branch or replay downstream decisions.", |
| status="done", |
| meta={"seed": True, "returns_to": parent}, |
| ) |
| memory.append("Checkpoint created: prototype branch can be retried or replayed.") |
| step = 5 |
|
|
| if start_phase == START_SYNTHESIS: |
| thought = add_node( |
| state, |
| parent, |
| "thought", |
| "Seed: validation passed", |
| "Assume major risks are checked and move directly to a final synthesis branch.", |
| status="done", |
| meta={"seed": True, "start_phase": start_phase}, |
| ) |
| tool = add_node( |
| state, |
| thought, |
| "tool", |
| "validator", |
| "seeded validation context", |
| status="done", |
| meta={"seed": True}, |
| ) |
| parent = add_node( |
| state, |
| tool, |
| "observation", |
| "Validation snapshot", |
| "Every claim should connect to a visible trace node, tool output, or explicit assumption.", |
| status="done", |
| meta={"seed": True}, |
| ) |
| memory.append("Every claim should connect to a visible trace node, tool output, or explicit assumption.") |
| step = 5 |
|
|
| state["active_tip"] = parent |
| state["selected_id"] = parent |
| return parent, memory, step |
|
|
|
|
| def render_tree(state: dict[str, Any]) -> str: |
| visual_canvas = render_visual_fork_canvas(state) |
| nodes = state["nodes"] |
| selected_id = state.get("selected_id", "root") |
| children = children_by_parent(nodes) |
|
|
| def render_node(node: dict[str, Any]) -> str: |
| status = html.escape(node["status"]) |
| kind = html.escape(node["kind"]) |
| selected = " selected" if node["id"] == selected_id else "" |
| marked = " marked" if node["id"] in state.get("marked_ids", []) else "" |
| title = html.escape(node["title"]) |
| body = html.escape(node["body"]) |
| timeline = html.escape(node.get("timeline", "main")) |
| branch = " branch" if timeline != "main" else "" |
| labels = node_labels(node) |
| label_html = "".join(f'<span class="node-label">{html.escape(label)}</span>' for label in labels) |
| child_html = "".join(render_node(child) for child in children.get(node["id"], [])) |
| return f""" |
| <li> |
| <button class="trace-node {kind} {status}{selected}{branch}{marked}" data-node-id="{html.escape(node['id'])}"> |
| <span class="node-top"> |
| <span class="kind">{kind}</span> |
| <span class="timeline">{timeline}</span> |
| </span> |
| <strong>{title}</strong> |
| {f'<span class="node-labels">{label_html}</span>' if labels else ''} |
| <span class="body">{body}</span> |
| </button> |
| {f'<ul>{child_html}</ul>' if child_html else ''} |
| </li> |
| """ |
|
|
| return f""" |
| <div class="tree-shell"> |
| <div class="tree-header"> |
| <div> |
| <h2>{APP_TITLE}</h2> |
| <p>{APP_TAGLINE}</p> |
| </div> |
| <div class="badge">Open trace ready</div> |
| </div> |
| {visual_canvas} |
| <div class="tree-divider"> |
| <span>Server trace mirror</span> |
| </div> |
| <ol class="trace-tree">{render_node(nodes[0])}</ol> |
| </div> |
| """ |
|
|
|
|
| def render_visual_fork_canvas(state: dict[str, Any]) -> str: |
| node_count = len(state.get("nodes", [])) |
| selected = html.escape(state.get("selected_id", "root")) |
| modal_trace_url = html.escape(MODAL_TRACE_URL) |
| trace_id = html.escape(state.get("trace_id", "visual-trace")) |
| return f""" |
| <div class="visual-fork-controls" data-selected-node="{selected}" data-node-count="{node_count}" data-trace-id="{trace_id}" data-modal-trace-url="{modal_trace_url}"> |
| <div> |
| <h3>Trace canvas</h3> |
| <p>Click a node to select it on the server. Right-click for server-backed retry, replay, and labels. Drag sibling nodes only rearranges the visual view.</p> |
| </div> |
| <input class="visual-fork-prompt" value="Try the opposite assumption from this node." aria-label="Visual fork prompt"> |
| <button class="visual-fork-button" type="button">Retry selected branch</button> |
| <button class="visual-export-button" type="button">Download visual trace</button> |
| </div> |
| <div class="visual-action-bar" aria-label="Selected node actions"> |
| <button class="visual-server-button primary" type="button" data-action="retry">Retry</button> |
| <button class="visual-server-button" type="button" data-action="replay">Replay</button> |
| <button class="visual-server-button" type="button" data-action="mark_weak">Weak</button> |
| <button class="visual-server-button" type="button" data-action="mark_useful">Useful</button> |
| <button class="visual-server-button positive" type="button" data-action="prefer">Prefer</button> |
| <button class="visual-server-button" type="button" data-action="reject">Reject</button> |
| </div> |
| <div class="visual-fork-inspector"> |
| <strong>Selected visual node</strong> |
| <span class="visual-selected-title">Click a node to select it.</span> |
| </div> |
| """ |
|
|
|
|
| def render_inspector(state: dict[str, Any]) -> str: |
| selected = node_by_id(state, state.get("selected_id", "root")) or state["nodes"][0] |
| meta = json.dumps(selected.get("meta", {}), indent=2) |
| labels = node_labels(selected) |
| label_html = "".join(f'<span class="node-label">{html.escape(label)}</span>' for label in labels) |
| descendants = descendant_nodes(state, selected["id"]) |
| return f""" |
| <div class="inspector"> |
| <div class="inspector-kicker">{html.escape(selected['kind'])} / {html.escape(selected['status'])}</div> |
| <h3>{html.escape(selected['title'])}</h3> |
| {f'<div class="node-labels inspector-labels">{label_html}</div>' if labels else ''} |
| <p>{html.escape(selected['body'])}</p> |
| <dl> |
| <dt>Node</dt><dd>{html.escape(selected['id'])}</dd> |
| <dt>Parent</dt><dd>{html.escape(str(selected['parent']))}</dd> |
| <dt>Timeline</dt><dd>{html.escape(selected.get('timeline', 'main'))}</dd> |
| <dt>Downstream</dt><dd>{len(descendants)} node(s)</dd> |
| </dl> |
| <details> |
| <summary>Metadata</summary> |
| <pre>{html.escape(meta)}</pre> |
| </details> |
| </div> |
| """ |
|
|
|
|
| def render_transcript(state: dict[str, Any]) -> str: |
| lines = state.get("transcript", []) |
| if not lines: |
| return "No run yet." |
| return "\n".join(lines[-30:]) |
|
|
|
|
| def trace_rows(state: dict[str, Any]) -> list[list[str]]: |
| rows = [] |
| for node in state.get("nodes", []): |
| indent = " " * int(node.get("depth", 0)) |
| rows.append( |
| [ |
| node["id"], |
| f"{indent}{node['title']}", |
| node["kind"], |
| node.get("timeline", "main"), |
| node["status"], |
| ", ".join(node_labels(node)), |
| str(node.get("parent")), |
| ] |
| ) |
| return rows |
|
|
|
|
| def outputs_for(state: dict[str, Any], event: str = "snapshot"): |
| persist_trace_safely(state, event) |
| return render_tree(state), trace_rows(state), render_inspector(state), render_transcript(state), state |
|
|
|
|
| def outputs_for_training(state: dict[str, Any], event: str = "snapshot"): |
| return (*outputs_for(state, event), feedback_rows(state)) |
|
|
|
|
| def run_agent(task: str, steps: int, start_phase: str, backend: str, model_id: str, state: dict[str, Any] | None): |
| if backend == BACKEND_HF: |
| model_label = f"HF Inference model: {model_id.strip() or DEFAULT_MODEL_ID}" |
| elif backend == BACKEND_MODAL: |
| model_label = f"Modal served model: {model_id.strip() or DEFAULT_MODEL_ID}" |
| else: |
| model_label = "offline heuristic ReAct engine" |
| state = reset_state(task, model_label) |
| parent, memory, start_step = seed_start_phase(state, start_phase) |
| yield outputs_for_training(state) |
| stop_step = min(int(steps), MAX_STEPS) |
| if start_phase == START_SYNTHESIS: |
| stop_step = max(stop_step, 5) |
| for step in range(start_step, stop_step + 1): |
| decision, model_note = choose_reasoner(state["task"], step, memory, backend, model_id) |
| state["model_note"] = model_note |
| thought_id = add_node( |
| state, |
| parent, |
| "thought", |
| f"Thought {step}", |
| decision["thought"], |
| status="running", |
| meta={"step": step, "backend": backend, "model_id": model_id.strip() or None}, |
| ) |
| state["transcript"].append(f"Thought {step}: {decision['thought']}") |
| yield outputs_for_training(state) |
| time.sleep(0.25) |
|
|
| tool_id = add_node( |
| state, |
| thought_id, |
| "tool", |
| decision["tool"], |
| decision["input"], |
| status="running", |
| meta={"step": step, "tool": decision["tool"], "backend": backend}, |
| ) |
| state["transcript"].append(f"Tool call: {decision['tool']}({decision['input']})") |
| yield outputs_for_training(state) |
| time.sleep(0.25) |
|
|
| observation = run_tool(decision["tool"], decision["input"], state["task"]) |
| obs_id = add_node( |
| state, |
| tool_id, |
| "observation", |
| f"Observation {step}", |
| observation, |
| status="done", |
| meta={"step": step}, |
| ) |
| node_by_id(state, thought_id)["status"] = "done" |
| node_by_id(state, tool_id)["status"] = "done" |
| memory.append(observation) |
| state["transcript"].append(f"Observation {step}: {observation}") |
| parent = obs_id |
| yield outputs_for_training(state) |
|
|
| if step == 3: |
| checkpoint_id = add_node( |
| state, |
| obs_id, |
| "checkpoint", |
| "Repeat from here", |
| "This is a replay checkpoint. Label this node, retry the branch, or re-execute downstream.", |
| status="ready", |
| meta={"step": step, "checkpoint": obs_id, "operation": "repeat_from_here"}, |
| ) |
| add_node( |
| state, |
| checkpoint_id, |
| "backtrack", |
| "Replay downstream", |
| "Use the checkpoint above to rerun the following nodes with a different assumption.", |
| status="done", |
| meta={"step": step, "returns_to": obs_id}, |
| ) |
| state["active_tip"] = parent |
| state["selected_id"] = parent |
| state["transcript"].append("Checkpoint: repeat from here, retry the branch, or replay downstream.") |
| yield outputs_for_training(state) |
|
|
| if step in {2, 3}: |
| branch_id = add_node( |
| state, |
| obs_id, |
| "fork", |
| f"Possible fork after step {step}", |
| make_branch_hint(step, decision["thought"]), |
| status="available", |
| timeline=f"branch-{state['timeline_count']}", |
| meta={"fork_from": obs_id, "suggested_by": "agent"}, |
| ) |
| state["timeline_count"] += 1 |
| state["active_tip"] = parent |
| state["selected_id"] = branch_id |
| state["transcript"].append(f"Fork suggested from {obs_id}: {make_branch_hint(step, decision['thought'])}") |
| yield outputs_for_training(state) |
|
|
| add_node( |
| state, |
| parent, |
| "answer", |
| "Final answer", |
| "The best branch is the one whose claims are backed by visible observations. Click any node to inspect it, then fork the trace to test a different assumption.", |
| status="done", |
| meta={"trace_nodes": len(state["nodes"]), "start_phase": start_phase}, |
| ) |
| node_by_id(state, "root")["status"] = "done" |
| state["transcript"].append("Final: trace complete. Export JSON or fork a node to continue.") |
| yield outputs_for_training(state) |
|
|
|
|
| def build_dataset_trace(example: dict[str, Any], backend: str, model_id: str) -> dict[str, Any]: |
| model_label = "offline heuristic ReAct engine" |
| if backend == BACKEND_HF: |
| model_label = f"HF Inference model: {model_id.strip() or DEFAULT_MODEL_ID}" |
| elif backend == BACKEND_MODAL: |
| model_label = f"Modal served model: {model_id.strip() or DEFAULT_MODEL_ID}" |
| state = reset_state(example["task"], model_label) |
| parent, memory, start_step = seed_start_phase(state, example["start_phase"]) |
| stop_step = min(int(example["steps"]), MAX_STEPS) |
| first_branch_id = None |
| for step in range(start_step, stop_step + 1): |
| decision, model_note = choose_reasoner(state["task"], step, memory, backend, model_id) |
| state["model_note"] = model_note |
| thought_id = add_node( |
| state, |
| parent, |
| "thought", |
| f"Dataset thought {step}", |
| decision["thought"], |
| status="done", |
| meta={"step": step, "dataset": example["name"]}, |
| ) |
| tool_id = add_node( |
| state, |
| thought_id, |
| "tool", |
| decision["tool"], |
| decision["input"], |
| status="done", |
| meta={"step": step, "dataset": example["name"]}, |
| ) |
| observation = run_tool(decision["tool"], decision["input"], state["task"]) |
| parent = add_node( |
| state, |
| tool_id, |
| "observation", |
| f"Dataset observation {step}", |
| observation, |
| status="done", |
| meta={"step": step, "dataset": example["name"]}, |
| ) |
| memory.append(observation) |
| if step in {2, 3} and not first_branch_id: |
| first_branch_id = add_node( |
| state, |
| parent, |
| "fork", |
| "Dataset alternate branch", |
| make_branch_hint(step, decision["thought"]), |
| status="available", |
| timeline=f"branch-{state['timeline_count']}", |
| meta={"dataset": example["name"], "fork_from": parent}, |
| ) |
| state["timeline_count"] += 1 |
| if not first_branch_id: |
| first_branch_id = add_node( |
| state, |
| parent, |
| "fork", |
| "Dataset baseline branch", |
| "Baseline continuation: proceed without challenging the current assumption.", |
| status="available", |
| timeline=f"branch-{state['timeline_count']}", |
| meta={"dataset": example["name"], "fallback_comparison": True, "fork_from": parent}, |
| ) |
| state["timeline_count"] += 1 |
| answer_id = add_node( |
| state, |
| parent, |
| "answer", |
| "Dataset answer", |
| "Prefer the branch whose claims are easiest to inspect, label, and retry.", |
| status="done", |
| meta={"dataset": example["name"]}, |
| ) |
| node_by_id(state, "root")["status"] = "done" |
| if first_branch_id: |
| state["selected_id"] = answer_id |
| rejected = node_by_id(state, first_branch_id) |
| chosen = node_by_id(state, answer_id) |
| if chosen and rejected: |
| record_preference_pair(state, chosen, rejected, example["feedback_note"]) |
| persist_trace_safely(state, "dataset_eval") |
| return state |
|
|
|
|
| def run_dataset_eval(backend: str, model_id: str): |
| results = [] |
| traces = [] |
| for example in DATASET_EXAMPLES: |
| state = build_dataset_trace(example, backend, model_id) |
| pairs = state.get("feedback", []) |
| traces.append(trace_payload(state, artifact="dataset_trace", event="dataset_eval")) |
| results.append( |
| { |
| "name": example["name"], |
| "task": example["task"], |
| "trace_id": state.get("trace_id", ""), |
| "nodes": len(state.get("nodes", [])), |
| "preference_pairs": len(pairs), |
| "selected_label": "fork_winner" if pairs else "none", |
| } |
| ) |
| payload = { |
| "app": APP_TITLE, |
| "artifact": "mini_dataset_eval", |
| "exported_at": datetime.now(timezone.utc).isoformat(), |
| "backend": backend, |
| "model_id": model_id.strip() or DEFAULT_MODEL_ID, |
| "results": results, |
| "traces": traces, |
| } |
| path = os.path.join(tempfile.gettempdir(), f"glass-box-mini-dataset-{uuid.uuid4().hex[:8]}.json") |
| with open(path, "w", encoding="utf-8") as handle: |
| json.dump(payload, handle, indent=2) |
| summary = f"Ran {len(results)} dataset examples and collected {sum(item['preference_pairs'] for item in results)} preference pair(s)." |
| return dataset_result_rows(results), path, summary |
|
|
|
|
| def inspect_node(selected_id: str, state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| if selected_id and node_by_id(state, selected_id): |
| state["selected_id"] = selected_id |
| return render_tree(state), trace_rows(state), render_inspector(state), state |
|
|
|
|
| def select_trace_row(state: dict[str, Any] | None, evt: gr.SelectData): |
| if not state: |
| state = new_state() |
| index = evt.index |
| row_index = index[0] if isinstance(index, (list, tuple)) else index |
| try: |
| selected_id = trace_rows(state)[int(row_index)][0] |
| except (TypeError, ValueError, IndexError): |
| selected_id = state.get("selected_id", "root") |
| state["selected_id"] = selected_id |
| selected = node_by_id(state, selected_id) |
| if selected: |
| state["action_log"] = f"Selected {selected['title']} ({selected['kind']}). Downstream nodes: {len(descendant_nodes(state, selected_id))}." |
| return render_tree(state), trace_rows(state), render_inspector(state), state.get("action_log", ""), state |
|
|
|
|
| def visual_server_action( |
| action: str, |
| node_id: str, |
| prompt: str, |
| backend: str, |
| model_id: str, |
| state: dict[str, Any] | None, |
| ): |
| if not state: |
| state = new_state() |
| action = (action or "select").strip() |
| node_id = (node_id or state.get("selected_id", "root")).strip() |
| if node_by_id(state, node_id): |
| state["selected_id"] = node_id |
| selected = node_by_id(state, state.get("selected_id", "root")) or state["nodes"][0] |
|
|
| if action == "retry": |
| return (*retry_selected_branch(prompt, backend, model_id, state), feedback_rows(state)) |
| if action == "replay": |
| return (*replay_downstream(prompt, state), feedback_rows(state)) |
| if action == "prefer": |
| return prefer_selected(prompt or "This branch is more grounded and useful.", state) |
| if action == "reject": |
| return reject_selected(prompt or "This branch is less useful than the comparison branch.", state) |
| if action == "mark_useful": |
| state["action_log"] = mark_node(state, selected["id"], "useful") |
| state["transcript"].append(state["action_log"]) |
| return (*operation_outputs(state, event="visual_mark_useful"), feedback_rows(state)) |
| if action == "mark_weak": |
| state["action_log"] = mark_node(state, selected["id"], "weak") |
| state["transcript"].append(state["action_log"]) |
| return (*operation_outputs(state, event="visual_mark_weak"), feedback_rows(state)) |
| if action == "annotate": |
| note = (prompt or "Retry from here.").strip() |
| selected.setdefault("meta", {})["annotation"] = note |
| state["action_log"] = f"Annotated {selected['title']}: {note}" |
| state["transcript"].append(state["action_log"]) |
| return (*operation_outputs(state, event="visual_annotate"), feedback_rows(state)) |
|
|
| state["action_log"] = f"Selected {selected['title']} ({selected['kind']}). Downstream nodes: {len(descendant_nodes(state, selected['id']))}." |
| state["transcript"].append(state["action_log"]) |
| return (*operation_outputs(state, event="visual_select"), feedback_rows(state)) |
|
|
|
|
| def operation_outputs(state: dict[str, Any], trace_file: str | None = None, event: str = "operation"): |
| persist_trace_safely(state, event) |
| return render_tree(state), trace_rows(state), render_inspector(state), render_transcript(state), state.get("action_log", ""), state, trace_file |
|
|
|
|
| def feedback_outputs(state: dict[str, Any], trace_file: str | None = None, event: str = "feedback"): |
| return (*operation_outputs(state, trace_file, event), feedback_rows(state)) |
|
|
|
|
| def mark_selected(label: str, state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| selected_id = state.get("selected_id", "root") |
| state["action_log"] = mark_node(state, selected_id, label) |
| state["transcript"].append(state["action_log"]) |
| return operation_outputs(state) |
|
|
|
|
| def clear_marks(state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| for node in state.get("nodes", []): |
| node.get("meta", {}).pop("labels", None) |
| state["marked_ids"] = [] |
| state["action_log"] = "Cleared all node marks." |
| state["transcript"].append(state["action_log"]) |
| return operation_outputs(state) |
|
|
|
|
| def retry_selected_branch(instruction: str, backend: str, model_id: str, state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| selected_id = state.get("selected_id", "root") |
| selected = node_by_id(state, selected_id) or state["nodes"][0] |
| instruction = instruction.strip() or "Retry this branch with a clearer assumption." |
| memory = [node["body"] for node in upstream_nodes(state, selected["id"]) if node["kind"] in {"observation", "thought"}] |
| decision, model_note = choose_reasoner( |
| f"{state.get('task', '')}\nRetry instruction: {instruction}\nSelected node: {selected['title']} - {selected['body']}", |
| min(int(selected.get("meta", {}).get("step", 3)) + 1, MAX_STEPS), |
| memory, |
| backend, |
| model_id, |
| ) |
| state["model_note"] = model_note |
| timeline = f"retry-{state['timeline_count']}" |
| state["timeline_count"] += 1 |
| retry_id = add_node( |
| state, |
| selected["id"], |
| "thought", |
| "Retry branch", |
| decision["thought"], |
| status="done", |
| timeline=timeline, |
| meta={"operation": "retry_branch", "instruction": instruction, "retried_from": selected["id"]}, |
| ) |
| tool_id = add_node( |
| state, |
| retry_id, |
| "tool", |
| decision["tool"], |
| decision["input"], |
| status="done", |
| timeline=timeline, |
| meta={"operation": "retry_branch", "tool": decision["tool"]}, |
| ) |
| observation = run_tool(decision["tool"], decision["input"], state.get("task", "")) |
| obs_id = add_node( |
| state, |
| tool_id, |
| "observation", |
| "Retry observation", |
| observation, |
| status="done", |
| timeline=timeline, |
| meta={"operation": "retry_branch", "retried_from": selected["id"]}, |
| ) |
| state["selected_id"] = obs_id |
| state["active_tip"] = obs_id |
| state["action_log"] = f"Retried branch from {selected['title']} into {timeline}." |
| state["transcript"].append(state["action_log"]) |
| return operation_outputs(state, export_trace_file(state)) |
|
|
|
|
| def replay_downstream(instruction: str, state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| selected_id = state.get("selected_id", "root") |
| selected = node_by_id(state, selected_id) or state["nodes"][0] |
| descendants = descendant_nodes(state, selected["id"]) |
| if not descendants: |
| for marked_id in reversed(state.get("marked_ids", [])): |
| marked = node_by_id(state, marked_id) |
| marked_descendants = descendant_nodes(state, marked_id) if marked else [] |
| if marked and marked_descendants: |
| selected = marked |
| descendants = marked_descendants |
| selected_id = marked_id |
| state["selected_id"] = marked_id |
| break |
| if not descendants: |
| state["action_log"] = f"{selected['title']} has no downstream nodes to re-execute." |
| state["transcript"].append(state["action_log"]) |
| return operation_outputs(state) |
| instruction = instruction.strip() or "Replay downstream with the selected node as the checkpoint." |
| timeline = f"replay-{state['timeline_count']}" |
| state["timeline_count"] += 1 |
| replay_root = add_node( |
| state, |
| selected["id"], |
| "thought", |
| "Re-execute downstream", |
| instruction, |
| status="done", |
| timeline=timeline, |
| meta={"operation": "replay_downstream", "from": selected["id"], "source_descendants": len(descendants)}, |
| ) |
| parent = replay_root |
| replayed = 0 |
| for original in descendants[:10]: |
| if original["kind"] in {"fork", "answer"}: |
| continue |
| body = original["body"] |
| if original["kind"] == "tool": |
| body = original["body"] |
| elif original["kind"] == "observation": |
| body = f"Replayed observation: {original['body']}" |
| else: |
| body = f"Replayed from {original['title']}: {original['body']}" |
| parent = add_node( |
| state, |
| parent, |
| original["kind"], |
| f"Replay: {original['title']}", |
| body, |
| status="done", |
| timeline=timeline, |
| meta={"operation": "replay_downstream", "source_node": original["id"]}, |
| ) |
| replayed += 1 |
| state["selected_id"] = parent |
| state["active_tip"] = parent |
| state["action_log"] = f"Re-executed {replayed} downstream node(s) from {selected['title']} into {timeline}." |
| state["transcript"].append(state["action_log"]) |
| return operation_outputs(state, export_trace_file(state)) |
|
|
|
|
| def summarize_marked(state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| state["action_log"] = mark_summary(state) |
| return operation_outputs(state) |
|
|
|
|
| def prefer_selected(reason: str, state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| selected_id = state.get("selected_id", "root") |
| selected = node_by_id(state, selected_id) or state["nodes"][0] |
| rejected = comparison_node(state, selected["id"]) |
| if not rejected: |
| state["action_log"] = "No sibling branch found to compare against. Fork or retry a node first, then record a preference." |
| state["transcript"].append(state["action_log"]) |
| return feedback_outputs(state) |
| state["action_log"] = record_preference_pair(state, selected, rejected, reason) |
| state["transcript"].append(state["action_log"]) |
| return feedback_outputs(state, export_rl_file(state)) |
|
|
|
|
| def reject_selected(reason: str, state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| selected_id = state.get("selected_id", "root") |
| rejected = node_by_id(state, selected_id) or state["nodes"][0] |
| chosen = comparison_node(state, rejected["id"]) |
| if not chosen: |
| state["action_log"] = "No sibling branch found to promote over the selected node. Fork or retry a node first." |
| state["transcript"].append(state["action_log"]) |
| return feedback_outputs(state) |
| state["action_log"] = record_preference_pair(state, chosen, rejected, reason) |
| state["transcript"].append(state["action_log"]) |
| return feedback_outputs(state, export_rl_file(state)) |
|
|
|
|
| def fork_selected(fork_prompt: str, state: dict[str, Any] | None): |
| if not state: |
| state = new_state() |
| selected_id = state.get("selected_id", "root") |
| selected = node_by_id(state, selected_id) or state["nodes"][0] |
| fork_prompt = fork_prompt.strip() or "Try the opposite assumption and continue from here." |
| timeline = f"branch-{state['timeline_count']}" |
| state["timeline_count"] += 1 |
| thought_id = add_node( |
| state, |
| selected["id"], |
| "thought", |
| "Human fork", |
| fork_prompt, |
| status="done", |
| timeline=timeline, |
| meta={"forked_from": selected["id"], "created_by": "human"}, |
| ) |
| tool_id = add_node( |
| state, |
| thought_id, |
| "tool", |
| "counterfactual_runner", |
| fork_prompt, |
| status="done", |
| timeline=timeline, |
| meta={"tool": "counterfactual_runner"}, |
| ) |
| obs = ( |
| "Counterfactual result: this branch changes the working assumption. Compare it with sibling nodes before trusting the final answer." |
| ) |
| add_node( |
| state, |
| tool_id, |
| "observation", |
| "Fork observation", |
| obs, |
| status="done", |
| timeline=timeline, |
| meta={"forked_from": selected["id"]}, |
| ) |
| state["transcript"].append(f"Human fork from {selected['id']}: {fork_prompt}") |
| return (*outputs_for(state), export_trace_file(state)) |
|
|
|
|
| def export_trace_file(state: dict[str, Any] | None): |
| state = state or new_state() |
| payload = trace_payload(state, event="export_trace") |
| path = os.path.join(tempfile.gettempdir(), f"glass-box-trace-{uuid.uuid4().hex[:8]}.json") |
| with open(path, "w", encoding="utf-8") as handle: |
| json.dump(payload, handle, indent=2) |
| persist_trace_safely(state, "export_trace") |
| return path |
|
|
|
|
| def export_only(state: dict[str, Any] | None): |
| return export_trace_file(state) |
|
|
|
|
| def export_rl_file(state: dict[str, Any] | None): |
| state = state or new_state() |
| payload = rl_payload(state) |
| path = os.path.join(tempfile.gettempdir(), f"glass-box-rl-feedback-{uuid.uuid4().hex[:8]}.json") |
| with open(path, "w", encoding="utf-8") as handle: |
| json.dump(payload, handle, indent=2) |
| persist_trace_safely(state, "export_rl_data") |
| return path |
|
|
|
|
| def export_rl_dataset(state: dict[str, Any] | None): |
| state = state or new_state() |
| state["action_log"] = f"Exported {len(state.get('feedback', []))} preference pair(s) for RL finetuning." |
| return state["action_log"], state, export_rl_file(state), feedback_rows(state) |
|
|
|
|
| def start_modal_tuning(base_model: str, state: dict[str, Any] | None): |
| state = state or new_state() |
| payload = { |
| "base_model": base_model.strip() or DEFAULT_MODEL_ID, |
| "trace_id": state.get("trace_id"), |
| "dataset": rl_payload(state), |
| } |
| try: |
| result = call_modal_json(MODAL_TUNE_URL, MODAL_TUNE_FUNCTION, payload) |
| checkpoint = result.get("checkpoint") or result.get("checkpoint_id") or result.get("result") or "Modal tuning job started" |
| state.setdefault("modal_store", {})["tuning"] = result |
| state["action_log"] = f"Started Modal RL tuning. Checkpoint: {checkpoint}" |
| state["transcript"].append(state["action_log"]) |
| except Exception as exc: |
| state["action_log"] = f"Modal tuning not started: {type(exc).__name__}. Export RL data and deploy modal_service.py first." |
| state["transcript"].append(state["action_log"]) |
| return state["action_log"], state, feedback_rows(state) |
|
|
|
|
| CSS = """ |
| :root { |
| --ink: #1f2937; |
| --muted: #6b7280; |
| --paper: #f7f8fb; |
| --panel: #ffffff; |
| --panel-2: #f2f4f8; |
| --line: #d9dee7; |
| --line-strong: #c3cad6; |
| --accent: #0f766e; |
| --accent-2: #c2410c; |
| --blue: #315fbd; |
| --gold: #a16207; |
| --danger: #b4232f; |
| --violet: #6d4aff; |
| } |
| html, |
| body { |
| height: auto !important; |
| min-height: 100% !important; |
| overflow-y: auto !important; |
| } |
| .gradio-container { |
| background: var(--paper); |
| color: var(--ink); |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; |
| min-height: 100vh !important; |
| overflow: visible !important; |
| } |
| .gradio-container .main { |
| background: linear-gradient(180deg, #fbfcff 0%, var(--paper) 45%, #eef2f7 100%); |
| overflow: visible !important; |
| } |
| .gradio-container label, |
| .gradio-container .block-info { |
| color: var(--muted) !important; |
| font-size: 12px !important; |
| } |
| .gradio-container textarea, |
| .gradio-container input, |
| .gradio-container select { |
| background: #ffffff !important; |
| border-color: var(--line) !important; |
| color: var(--ink) !important; |
| } |
| .gradio-container .block { |
| background: transparent !important; |
| } |
| .gradio-container .form, |
| .gradio-container .panel, |
| .gradio-container .wrap { |
| border-color: var(--line) !important; |
| } |
| .gradio-container .prose h1 { |
| color: var(--ink); |
| font-size: 24px; |
| letter-spacing: 0; |
| margin-bottom: 2px; |
| } |
| .gradio-container .prose p { |
| color: var(--muted); |
| } |
| .command-rail { |
| background: linear-gradient(180deg, #ffffff, #f6f8fb); |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| box-shadow: 0 10px 30px rgba(15, 23, 42, 0.05); |
| padding: 14px !important; |
| position: relative; |
| } |
| .command-rail .prose h3 { |
| color: #334155; |
| font-size: 13px; |
| letter-spacing: 0; |
| margin: 8px 0 6px; |
| text-transform: uppercase; |
| } |
| .command-rail .prose:first-child h3 { |
| margin-top: 0; |
| } |
| .command-rail .block { |
| border-radius: 8px !important; |
| } |
| .command-rail .form, |
| .command-rail .panel, |
| .command-rail .wrap { |
| background: transparent !important; |
| border-color: transparent !important; |
| box-shadow: none !important; |
| } |
| .command-rail [data-testid="block-info"] { |
| background: transparent !important; |
| color: #64748b !important; |
| display: block !important; |
| font-size: 11px !important; |
| font-weight: 700 !important; |
| letter-spacing: 0 !important; |
| margin-bottom: 5px !important; |
| padding: 0 !important; |
| text-transform: uppercase !important; |
| } |
| .command-rail textarea, |
| .command-rail input, |
| .command-rail select { |
| border-radius: 8px !important; |
| box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04) !important; |
| } |
| .command-rail textarea:focus, |
| .command-rail input:focus, |
| .command-rail select:focus { |
| border-color: #7fc6ba !important; |
| box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.12) !important; |
| } |
| .task-box textarea { |
| min-height: 104px !important; |
| } |
| .depth-slider button[aria-label="Reset to default value"] { |
| display: none !important; |
| } |
| .depth-slider input[type="number"] { |
| max-width: 72px !important; |
| } |
| .run-row, |
| .action-row, |
| .label-row, |
| .compact-actions { |
| align-items: end; |
| gap: 10px !important; |
| } |
| .run-row > *, |
| .action-row > *, |
| .compact-actions > * { |
| min-width: 0 !important; |
| } |
| .label-row > :first-child { |
| flex: 1 1 auto !important; |
| } |
| .label-row > :last-child { |
| flex: 0 0 92px !important; |
| } |
| .advanced-setup { |
| background: #f8fafc !important; |
| border: 1px solid var(--line) !important; |
| border-radius: 8px !important; |
| margin: 8px 0 14px !important; |
| overflow: hidden; |
| } |
| .training-signal { |
| background: #fffdf7 !important; |
| border: 1px solid #ead7a4 !important; |
| border-radius: 8px !important; |
| margin: 10px 0 12px !important; |
| overflow: hidden; |
| } |
| .dataset-trial { |
| background: #f7fbff !important; |
| border: 1px solid #c8d7ff !important; |
| border-radius: 8px !important; |
| margin: 10px 0 14px !important; |
| overflow: hidden; |
| } |
| .dataset-trial summary { |
| color: #315fbd !important; |
| font-weight: 700 !important; |
| } |
| .dataset-table { |
| max-height: 190px; |
| overflow: auto; |
| } |
| .training-signal summary { |
| color: #6f4e00 !important; |
| font-weight: 700 !important; |
| } |
| .feedback-table { |
| max-height: 180px; |
| overflow: auto; |
| } |
| .advanced-setup summary { |
| color: #334155 !important; |
| font-weight: 700 !important; |
| } |
| .command-rail .radio, |
| .command-rail .checkbox-group { |
| gap: 8px !important; |
| } |
| .command-rail button { |
| min-height: 38px !important; |
| } |
| .action-log textarea { |
| background: #f8fafc !important; |
| color: #475569 !important; |
| font-size: 12px !important; |
| } |
| .trace-artifact { |
| opacity: 0.86; |
| } |
| #run-btn, #fork-btn, #export-btn, #retry-btn, #replay-btn, #mark-btn, #summary-btn, #clear-btn, #prefer-btn, #reject-btn, #export-rl-btn, #tune-btn, #load-dataset-btn, #run-dataset-btn { |
| border-radius: 6px; |
| } |
| #run-btn, #retry-btn, #prefer-btn, #run-dataset-btn { |
| background: linear-gradient(180deg, #18a999, #0f766e) !important; |
| border: 1px solid #0f766e !important; |
| color: white !important; |
| font-weight: 700 !important; |
| } |
| #run-btn { |
| min-height: 46px !important; |
| width: 100% !important; |
| } |
| #fork-btn, #export-btn, #replay-btn, #mark-btn, #summary-btn, #clear-btn, #reject-btn, #export-rl-btn, #tune-btn, #load-dataset-btn { |
| background: #ffffff !important; |
| border: 1px solid var(--line-strong) !important; |
| color: var(--ink) !important; |
| } |
| #mark-btn { |
| background: #eefaf7 !important; |
| border-color: #b9e2d9 !important; |
| color: #0f5f57 !important; |
| font-weight: 700 !important; |
| } |
| .tree-shell { |
| background: linear-gradient(180deg, #ffffff, #f8fafc); |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| min-height: 620px; |
| padding: 18px; |
| overflow: auto; |
| } |
| .tree-header { |
| align-items: start; |
| border-bottom: 1px solid var(--line); |
| display: flex; |
| justify-content: space-between; |
| gap: 16px; |
| margin-bottom: 18px; |
| padding-bottom: 14px; |
| } |
| .tree-header h2 { |
| font-size: 24px; |
| margin: 0 0 4px; |
| color: var(--ink); |
| } |
| .tree-header p { |
| color: var(--muted); |
| margin: 0; |
| } |
| .visual-fork-controls { |
| align-items: center; |
| background: #fbfcfe; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| display: grid; |
| gap: 10px; |
| grid-template-columns: minmax(220px, 1fr) minmax(220px, 320px) auto auto; |
| margin-bottom: 10px; |
| padding: 12px; |
| } |
| .visual-fork-controls h3 { |
| color: var(--ink); |
| font-size: 16px; |
| margin: 0 0 4px; |
| } |
| .visual-fork-controls p { |
| color: var(--muted); |
| margin: 0; |
| } |
| .visual-fork-controls input { |
| background: #ffffff; |
| border: 1px solid var(--line-strong); |
| border-radius: 6px; |
| color: var(--ink); |
| font: inherit; |
| min-width: 0; |
| padding: 9px 10px; |
| } |
| .visual-fork-controls button { |
| background: #ffffff; |
| border: 1px solid var(--line-strong); |
| border-radius: 6px; |
| color: var(--ink); |
| cursor: pointer; |
| font: inherit; |
| padding: 9px 10px; |
| } |
| .visual-fork-button { |
| background: #ffffff !important; |
| border-color: #94a3b8 !important; |
| color: var(--ink) !important; |
| font-weight: 700 !important; |
| } |
| .visual-action-bar { |
| align-items: center; |
| background: #ffffff; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| margin-bottom: 10px; |
| padding: 8px; |
| } |
| .visual-server-button { |
| background: #ffffff; |
| border: 1px solid var(--line-strong); |
| border-radius: 6px; |
| color: var(--ink); |
| cursor: pointer; |
| font: inherit; |
| font-weight: 700; |
| min-height: 34px; |
| padding: 7px 11px; |
| } |
| .visual-server-button:hover { |
| background: #f8fafc; |
| } |
| .visual-server-button.primary, |
| .visual-server-button.positive { |
| background: linear-gradient(180deg, #18a999, #0f766e); |
| border-color: #0f766e; |
| color: #ffffff; |
| } |
| .visual-fork-inspector { |
| background: #eefaf7; |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| color: var(--muted); |
| display: flex; |
| gap: 10px; |
| margin-bottom: 14px; |
| padding: 10px 12px; |
| } |
| .visual-fork-inspector strong { |
| color: var(--accent); |
| } |
| .tree-divider { |
| align-items: center; |
| color: var(--muted); |
| display: flex; |
| font-size: 12px; |
| gap: 10px; |
| margin: 10px 0 14px; |
| text-transform: uppercase; |
| } |
| .tree-divider:before, |
| .tree-divider:after { |
| background: var(--line); |
| content: ""; |
| flex: 1; |
| height: 1px; |
| } |
| .badge { |
| background: #e7f7f3; |
| border: 1px solid #a9ded2; |
| border-radius: 999px; |
| color: #0f5f57; |
| font-size: 13px; |
| padding: 6px 10px; |
| white-space: nowrap; |
| } |
| .trace-tree, .trace-tree ol, .trace-tree ul { |
| list-style: none; |
| margin: 0; |
| padding-left: 24px; |
| position: relative; |
| } |
| .trace-tree { |
| padding-left: 0; |
| } |
| .trace-tree li { |
| margin: 8px 0; |
| position: relative; |
| } |
| .trace-tree li:before { |
| border-left: 1px solid var(--line); |
| content: ""; |
| height: calc(100% + 8px); |
| left: -14px; |
| position: absolute; |
| top: -8px; |
| } |
| .trace-tree li:after { |
| border-top: 1px solid var(--line); |
| content: ""; |
| left: -14px; |
| position: absolute; |
| top: 34px; |
| width: 14px; |
| } |
| .trace-tree > li:before, .trace-tree > li:after { |
| display: none; |
| } |
| .trace-node { |
| background: #ffffff; |
| border: 1px solid var(--line-strong); |
| border-left: 5px solid var(--muted); |
| border-radius: 8px; |
| box-shadow: 0 1px 1px rgba(15, 23, 42, 0.04); |
| color: var(--ink); |
| cursor: pointer; |
| display: block; |
| font: inherit; |
| max-width: 680px; |
| min-width: 260px; |
| padding: 10px 12px; |
| text-align: left; |
| transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease; |
| } |
| .trace-node:hover { |
| box-shadow: 0 10px 24px rgba(15, 23, 42, 0.09); |
| transform: translateY(-1px); |
| } |
| .trace-node:active { |
| cursor: grabbing; |
| } |
| .trace-node.selected { |
| border-color: var(--accent); |
| box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.16); |
| } |
| .trace-node.dragging { |
| opacity: 0.58; |
| transform: scale(0.99); |
| } |
| .trace-node.drop-target { |
| box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.18); |
| } |
| .trace-node.marked { |
| background: #f8fbff; |
| border-color: #9ab3dd; |
| } |
| .trace-node.annotated { |
| background: #fffdf4; |
| } |
| .trace-node.thought { border-left-color: var(--blue); } |
| .trace-node.tool { border-left-color: var(--gold); } |
| .trace-node.observation { border-left-color: var(--accent); } |
| .trace-node.checkpoint { border-left-color: #2563eb; } |
| .trace-node.fork { border-left-color: var(--accent-2); } |
| .trace-node.dead_end { border-left-color: var(--danger); } |
| .trace-node.backtrack { border-left-color: #6f5f16; } |
| .trace-node.answer { border-left-color: var(--violet); } |
| .trace-node.branch { |
| background: #fff7ed; |
| } |
| .node-top { |
| align-items: center; |
| display: flex; |
| gap: 8px; |
| justify-content: space-between; |
| margin-bottom: 6px; |
| } |
| .kind, .timeline { |
| color: var(--muted); |
| font-size: 11px; |
| letter-spacing: 0; |
| text-transform: uppercase; |
| } |
| .trace-node strong { |
| display: block; |
| font-size: 15px; |
| margin-bottom: 4px; |
| } |
| .node-labels { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 5px; |
| margin: 0 0 6px; |
| } |
| .node-label { |
| background: #edf2ff; |
| border: 1px solid #c8d7ff; |
| border-radius: 999px; |
| color: #315fbd; |
| display: inline-flex; |
| font-size: 11px; |
| line-height: 1; |
| padding: 4px 7px; |
| } |
| .inspector-labels { |
| margin: 0 0 10px; |
| } |
| .trace-node .body { |
| color: #374151; |
| display: block; |
| font-size: 13px; |
| line-height: 1.35; |
| } |
| .node-annotation { |
| background: #fffbeb; |
| border: 1px solid #fde68a; |
| border-radius: 6px; |
| color: #713f12; |
| display: block; |
| font-size: 12px; |
| line-height: 1.35; |
| margin-top: 8px; |
| padding: 7px 8px; |
| } |
| .trace-context-menu { |
| background: #ffffff; |
| border: 1px solid var(--line-strong); |
| border-radius: 8px; |
| box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16); |
| color: var(--ink); |
| display: none; |
| min-width: 230px; |
| padding: 6px; |
| position: fixed; |
| z-index: 9999; |
| } |
| .trace-context-menu.open { |
| display: block; |
| } |
| .trace-context-menu button { |
| align-items: center; |
| background: transparent; |
| border: 0; |
| border-radius: 6px; |
| color: var(--ink); |
| cursor: pointer; |
| display: flex; |
| font: inherit; |
| gap: 8px; |
| justify-content: space-between; |
| padding: 8px 9px; |
| text-align: left; |
| width: 100%; |
| } |
| .trace-context-menu button:hover { |
| background: #f1f5f9; |
| } |
| .trace-context-menu .danger { |
| color: #b91c1c; |
| } |
| .inspector { |
| background: linear-gradient(180deg, #ffffff, #f8fafc); |
| border: 1px solid var(--line); |
| border-radius: 8px; |
| padding: 16px; |
| } |
| .inspector-kicker { |
| color: var(--accent); |
| font-size: 12px; |
| font-weight: 700; |
| text-transform: uppercase; |
| } |
| .inspector h3 { |
| color: var(--ink); |
| font-size: 20px; |
| margin: 6px 0 8px; |
| } |
| .inspector p { |
| color: #374151; |
| } |
| .inspector dl { |
| display: grid; |
| grid-template-columns: 76px 1fr; |
| gap: 6px 12px; |
| } |
| .inspector dt { |
| color: var(--muted); |
| font-size: 12px; |
| } |
| .inspector dd { |
| font-size: 12px; |
| margin: 0; |
| overflow-wrap: anywhere; |
| } |
| .inspector pre { |
| background: #f3f5f8; |
| color: #1f2937; |
| border: 1px solid var(--line); |
| border-radius: 6px; |
| overflow: auto; |
| padding: 10px; |
| } |
| .gradio-container table { |
| background: var(--panel) !important; |
| color: var(--ink) !important; |
| } |
| .gradio-container th { |
| background: #f3f5f8 !important; |
| color: var(--muted) !important; |
| } |
| .gradio-container td { |
| background: var(--panel) !important; |
| border-color: var(--line) !important; |
| color: var(--ink) !important; |
| } |
| .gradio-container [data-testid="block-label"] { |
| color: var(--muted) !important; |
| } |
| .server-bridge, |
| #visual-action-btn { |
| display: none !important; |
| } |
| @media (max-width: 760px) { |
| .visual-fork-controls { |
| grid-template-columns: 1fr; |
| } |
| .tree-header { |
| display: block; |
| } |
| .badge { |
| display: inline-block; |
| margin-top: 10px; |
| } |
| .trace-node { |
| min-width: 220px; |
| } |
| } |
| """ |
|
|
|
|
| JS = """ |
| (() => { |
| const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (ch) => ({ |
| "&": "&", |
| "<": "<", |
| ">": ">", |
| '"': """, |
| "'": "'" |
| }[ch])); |
| |
| let draggedItem = null; |
| let pointerDrag = null; |
| let suppressClickNode = null; |
| |
| const selectedNode = (shell) => shell.querySelector(".trace-node.selected") || shell.querySelector(".trace-node"); |
| const shellForNode = (node) => node?.closest(".tree-shell"); |
| const bridgeInput = (id) => document.querySelector(`#${id} textarea, #${id} input`); |
| const setBridgeValue = (id, value) => { |
| const input = bridgeInput(id); |
| if (!input) return false; |
| const setter = Object.getOwnPropertyDescriptor(input.constructor.prototype, "value")?.set; |
| if (setter) setter.call(input, value); |
| else input.value = value; |
| input.dispatchEvent(new Event("input", { bubbles: true })); |
| input.dispatchEvent(new Event("change", { bubbles: true })); |
| return true; |
| }; |
| |
| const triggerServerAction = (shell, action, node) => { |
| if (!shell || !node) return false; |
| let prompt = shell.querySelector(".visual-fork-prompt")?.value?.trim() || "Retry this branch with a clearer assumption."; |
| if (action === "prefer") prompt = "This branch is more grounded and useful."; |
| if (action === "reject") prompt = "This branch is less useful than the comparison branch."; |
| const ok = [ |
| setBridgeValue("visual-action-kind", action), |
| setBridgeValue("visual-action-node", node.dataset.nodeId || ""), |
| setBridgeValue("visual-action-prompt", prompt) |
| ].every(Boolean); |
| const button = document.querySelector("#visual-action-btn"); |
| if (!ok || !button) return false; |
| button.click(); |
| return true; |
| }; |
| |
| const updateInspector = (shell, node) => { |
| const inspector = shell.querySelector(".visual-fork-inspector"); |
| const controls = shell.querySelector(".visual-fork-controls"); |
| if (!inspector || !controls || !node) return; |
| controls.dataset.selectedNode = node.dataset.nodeId || ""; |
| const kind = node.querySelector(".kind")?.textContent?.trim() || "node"; |
| const timeline = node.querySelector(".timeline")?.textContent?.trim() || "main"; |
| const title = node.querySelector("strong")?.textContent?.trim() || "Untitled node"; |
| const body = node.querySelector(".body")?.textContent?.trim() || ""; |
| inspector.innerHTML = ` |
| <strong>Selected visual node</strong> |
| <span class="visual-selected-title">${esc(title)} (${esc(kind)} / ${esc(timeline)})</span> |
| <span>${esc(body).slice(0, 180)}</span> |
| `; |
| }; |
| |
| const selectVisualNode = (shell, node, sync = true) => { |
| if (!shell || !node) return; |
| shell.querySelectorAll(".trace-node.selected").forEach((selected) => selected.classList.remove("selected")); |
| node.classList.add("selected"); |
| updateInspector(shell, node); |
| if (sync) triggerServerAction(shell, "select", node); |
| }; |
| |
| const ensureLabelRack = (node) => { |
| let rack = node.querySelector(".node-labels"); |
| if (rack) return rack; |
| rack = document.createElement("span"); |
| rack.className = "node-labels"; |
| const title = node.querySelector("strong"); |
| if (title?.nextSibling) { |
| node.insertBefore(rack, title.nextSibling); |
| } else { |
| node.appendChild(rack); |
| } |
| return rack; |
| }; |
| |
| const addVisualLabel = (node, label) => { |
| const rack = ensureLabelRack(node); |
| const exists = [...rack.querySelectorAll(".node-label")].some((item) => item.textContent.trim() === label); |
| if (!exists) { |
| const pill = document.createElement("span"); |
| pill.className = "node-label"; |
| pill.textContent = label; |
| rack.appendChild(pill); |
| } |
| node.classList.add("marked"); |
| persistVisualTrace(shellForNode(node), `label:${label}`); |
| }; |
| |
| const annotateVisualNode = (node, text) => { |
| const note = document.createElement("span"); |
| note.className = "node-annotation"; |
| note.textContent = text; |
| node.appendChild(note); |
| node.classList.add("annotated"); |
| addVisualLabel(node, "annotated"); |
| persistVisualTrace(shellForNode(node), "annotate"); |
| }; |
| |
| const moveVisualNode = (shell, node, direction) => { |
| const item = node.closest("li"); |
| if (!item) return; |
| if (direction === "up" && item.previousElementSibling) { |
| item.parentElement.insertBefore(item, item.previousElementSibling); |
| } |
| if (direction === "down" && item.nextElementSibling) { |
| item.parentElement.insertBefore(item.nextElementSibling, item); |
| } |
| selectVisualNode(shell, node, false); |
| persistVisualTrace(shell, `move:${direction}`); |
| }; |
| |
| const removeVisualNode = (shell, node) => { |
| if (node.dataset.nodeId === "root") { |
| annotateVisualNode(node, "Root stays put; remove a branch node instead."); |
| return; |
| } |
| const item = node.closest("li"); |
| const parentNode = item?.parentElement?.closest("li")?.querySelector(":scope > .trace-node"); |
| if (!item) return; |
| item.remove(); |
| selectVisualNode(shell, parentNode || selectedNode(shell)); |
| persistVisualTrace(shell, "remove"); |
| }; |
| |
| const siblingDrop = (shell, targetNode) => { |
| const targetItem = targetNode?.closest("li"); |
| if (!draggedItem || !targetItem || draggedItem === targetItem || draggedItem.parentElement !== targetItem.parentElement) { |
| return false; |
| } |
| targetItem.parentElement.insertBefore(draggedItem, targetItem.nextElementSibling); |
| const movedNode = draggedItem.querySelector(":scope > .trace-node"); |
| selectVisualNode(shell, movedNode); |
| persistVisualTrace(shell, "drag-reorder"); |
| return true; |
| }; |
| |
| const contextMenu = () => { |
| let menu = document.querySelector(".trace-context-menu"); |
| if (menu) return menu; |
| menu = document.createElement("div"); |
| menu.className = "trace-context-menu"; |
| document.body.appendChild(menu); |
| return menu; |
| }; |
| |
| const closeContextMenu = () => { |
| document.querySelector(".trace-context-menu")?.classList.remove("open"); |
| }; |
| |
| const menuButton = (label, action, danger = false) => { |
| const button = document.createElement("button"); |
| button.type = "button"; |
| button.textContent = label; |
| if (danger) button.className = "danger"; |
| button.onclick = (event) => { |
| event.preventDefault(); |
| action(); |
| closeContextMenu(); |
| }; |
| return button; |
| }; |
| |
| const openContextMenu = (shell, node, x, y) => { |
| selectVisualNode(shell, node, false); |
| const menu = contextMenu(); |
| menu.innerHTML = ""; |
| menu.appendChild(menuButton("Select on server", () => triggerServerAction(shell, "select", node))); |
| menu.appendChild(menuButton("Retry branch on server", () => triggerServerAction(shell, "retry", node))); |
| menu.appendChild(menuButton("Replay downstream on server", () => triggerServerAction(shell, "replay", node))); |
| menu.appendChild(menuButton("Annotate on server", () => triggerServerAction(shell, "annotate", node))); |
| menu.appendChild(menuButton("Mark useful on server", () => triggerServerAction(shell, "mark_useful", node))); |
| menu.appendChild(menuButton("Mark weak on server", () => triggerServerAction(shell, "mark_weak", node))); |
| menu.appendChild(menuButton("Prefer on server", () => triggerServerAction(shell, "prefer", node))); |
| menu.appendChild(menuButton("Reject on server", () => triggerServerAction(shell, "reject", node))); |
| menu.appendChild(menuButton("Move up (visual only)", () => moveVisualNode(shell, node, "up"))); |
| menu.appendChild(menuButton("Move down (visual only)", () => moveVisualNode(shell, node, "down"))); |
| menu.appendChild(menuButton("Remove visual node only", () => removeVisualNode(shell, node), true)); |
| const width = 240; |
| const height = 330; |
| menu.style.left = `${Math.max(8, Math.min(x, window.innerWidth - width))}px`; |
| menu.style.top = `${Math.max(8, Math.min(y, window.innerHeight - height))}px`; |
| menu.classList.add("open"); |
| }; |
| |
| const bindTree = (shell) => { |
| shell.querySelectorAll(".trace-node").forEach((node) => { |
| node.dataset.visualBound = "true"; |
| node.onclick = (event) => { |
| event.preventDefault(); |
| if (suppressClickNode === node) { |
| suppressClickNode = null; |
| return; |
| } |
| selectVisualNode(shell, node); |
| }; |
| node.oncontextmenu = (event) => { |
| event.preventDefault(); |
| openContextMenu(shell, node, event.clientX, event.clientY); |
| }; |
| node.draggable = true; |
| node.ondragstart = (event) => { |
| draggedItem = node.closest("li"); |
| node.classList.add("dragging"); |
| event.dataTransfer.effectAllowed = "move"; |
| event.dataTransfer.setData("text/plain", node.dataset.nodeId || ""); |
| }; |
| node.ondragend = () => { |
| node.classList.remove("dragging"); |
| shell.querySelectorAll(".drop-target").forEach((target) => target.classList.remove("drop-target")); |
| draggedItem = null; |
| }; |
| node.ondragover = (event) => { |
| if (!draggedItem) return; |
| const targetItem = node.closest("li"); |
| if (targetItem && draggedItem.parentElement === targetItem.parentElement && draggedItem !== targetItem) { |
| event.preventDefault(); |
| node.classList.add("drop-target"); |
| } |
| }; |
| node.ondragleave = () => node.classList.remove("drop-target"); |
| node.ondrop = (event) => { |
| const targetItem = node.closest("li"); |
| if (!draggedItem || !targetItem || draggedItem === targetItem || draggedItem.parentElement !== targetItem.parentElement) return; |
| event.preventDefault(); |
| node.classList.remove("drop-target"); |
| siblingDrop(shell, node); |
| }; |
| node.onpointerdown = (event) => { |
| if (event.button !== 0) return; |
| pointerDrag = { |
| shell, |
| node, |
| item: node.closest("li"), |
| startX: event.clientX, |
| startY: event.clientY, |
| active: false |
| }; |
| draggedItem = pointerDrag.item; |
| node.setPointerCapture?.(event.pointerId); |
| }; |
| }); |
| updateInspector(shell, selectedNode(shell)); |
| }; |
| |
| const branchButton = (kind, title, body, timeline, nodeId) => ` |
| <button class="trace-node ${kind} done branch" data-node-id="${esc(nodeId)}"> |
| <span class="node-top"> |
| <span class="kind">${esc(kind)}</span> |
| <span class="timeline">${esc(timeline)}</span> |
| </span> |
| <strong>${esc(title)}</strong> |
| <span class="body">${esc(body)}</span> |
| </button> |
| `; |
| |
| const forkVisualNode = (shell) => { |
| const parent = selectedNode(shell); |
| if (!parent) return; |
| const prompt = shell.querySelector(".visual-fork-prompt")?.value?.trim() || "Try the opposite assumption from this node."; |
| const controls = shell.querySelector(".visual-fork-controls"); |
| const count = Number(controls?.dataset.nodeCount || "0") + 1; |
| if (controls) controls.dataset.nodeCount = String(count + 2); |
| const timeline = `visual-${count}`; |
| const thoughtId = `visual-thought-${Date.now()}`; |
| const obsId = `visual-observation-${Date.now()}`; |
| const parentLi = parent.closest("li"); |
| let childList = parentLi.querySelector(":scope > ul"); |
| if (!childList) { |
| childList = document.createElement("ul"); |
| parentLi.appendChild(childList); |
| } |
| const thoughtLi = document.createElement("li"); |
| thoughtLi.innerHTML = ` |
| ${branchButton("thought", "Visual fork", prompt, timeline, thoughtId)} |
| <ul> |
| <li> |
| ${branchButton("observation", "Alternate timeline result", "This branch was created by clicking a visual trace node. Compare it with sibling branches before trusting the answer.", timeline, obsId)} |
| </li> |
| </ul> |
| `; |
| childList.appendChild(thoughtLi); |
| shell.querySelectorAll(".trace-node.selected").forEach((selected) => selected.classList.remove("selected")); |
| const observation = thoughtLi.querySelector(`[data-node-id="${obsId}"]`); |
| observation.classList.add("selected"); |
| bindTree(shell); |
| updateInspector(shell, observation); |
| persistVisualTrace(shell, "visual-fork"); |
| }; |
| |
| const visualTracePayload = (shell, event = "visual-snapshot") => { |
| const controls = shell.querySelector(".visual-fork-controls"); |
| const nodes = [...shell.querySelectorAll(".trace-node")].map((node) => ({ |
| id: node.dataset.nodeId || "", |
| kind: node.querySelector(".kind")?.textContent?.trim() || "", |
| timeline: node.querySelector(".timeline")?.textContent?.trim() || "", |
| title: node.querySelector("strong")?.textContent?.trim() || "", |
| body: node.querySelector(".body")?.textContent?.trim() || "", |
| labels: [...node.querySelectorAll(".node-label")].map((label) => label.textContent.trim()), |
| annotations: [...node.querySelectorAll(".node-annotation")].map((note) => note.textContent.trim()), |
| selected: node.classList.contains("selected") |
| })); |
| return { |
| app: "Glass-Box Agent", |
| artifact: "visual_trace", |
| source: "visual_dom_fork_canvas", |
| event, |
| trace_id: controls?.dataset.traceId || "visual-trace", |
| exported_at: new Date().toISOString(), |
| nodes |
| }; |
| }; |
| |
| const persistVisualTrace = (shell, event = "visual-snapshot") => { |
| if (!shell) return; |
| const endpoint = shell.querySelector(".visual-fork-controls")?.dataset.modalTraceUrl; |
| if (!endpoint) return; |
| fetch(endpoint, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(visualTracePayload(shell, event)), |
| keepalive: true |
| }).catch(() => {}); |
| }; |
| |
| const downloadVisualTrace = (shell) => { |
| const payload = visualTracePayload(shell, "download_visual_trace"); |
| const blob = new Blob([JSON.stringify({ |
| ...payload |
| }, null, 2)], { type: "application/json" }); |
| const url = URL.createObjectURL(blob); |
| const link = document.createElement("a"); |
| link.href = url; |
| link.download = `glass-box-visual-trace-${Date.now()}.json`; |
| link.click(); |
| URL.revokeObjectURL(url); |
| }; |
| |
| const bindShell = (shell) => { |
| bindTree(shell); |
| const fork = shell.querySelector(".visual-fork-button"); |
| if (fork) { |
| fork.dataset.visualBound = "true"; |
| fork.onclick = () => triggerServerAction(shell, "retry", selectedNode(shell)); |
| } |
| shell.querySelectorAll(".visual-server-button").forEach((button) => { |
| button.onclick = () => triggerServerAction(shell, button.dataset.action || "select", selectedNode(shell)); |
| }); |
| const download = shell.querySelector(".visual-export-button"); |
| if (download) { |
| download.dataset.visualBound = "true"; |
| download.onclick = () => downloadVisualTrace(shell); |
| } |
| }; |
| |
| document.addEventListener("click", (event) => { |
| if (!event.target.closest(".trace-context-menu")) closeContextMenu(); |
| }); |
| document.addEventListener("keydown", (event) => { |
| if (event.key === "Escape") closeContextMenu(); |
| }); |
| document.addEventListener("pointermove", (event) => { |
| if (!pointerDrag) return; |
| const distance = Math.abs(event.clientX - pointerDrag.startX) + Math.abs(event.clientY - pointerDrag.startY); |
| if (distance < 12) return; |
| pointerDrag.active = true; |
| pointerDrag.node.classList.add("dragging"); |
| pointerDrag.shell.querySelectorAll(".drop-target").forEach((target) => target.classList.remove("drop-target")); |
| const targetNode = document.elementFromPoint(event.clientX, event.clientY)?.closest(".trace-node"); |
| const targetItem = targetNode?.closest("li"); |
| if (targetNode && targetItem && targetItem !== pointerDrag.item && targetItem.parentElement === pointerDrag.item.parentElement) { |
| targetNode.classList.add("drop-target"); |
| } |
| }); |
| document.addEventListener("pointerup", (event) => { |
| if (!pointerDrag) return; |
| const drag = pointerDrag; |
| pointerDrag = null; |
| drag.node.classList.remove("dragging"); |
| drag.shell.querySelectorAll(".drop-target").forEach((target) => target.classList.remove("drop-target")); |
| if (drag.active) { |
| suppressClickNode = drag.node; |
| const targetNode = document.elementFromPoint(event.clientX, event.clientY)?.closest(".trace-node"); |
| siblingDrop(drag.shell, targetNode); |
| } |
| draggedItem = null; |
| }); |
| |
| const bindAll = () => document.querySelectorAll(".tree-shell").forEach(bindShell); |
| bindAll(); |
| setInterval(bindAll, 500); |
| })(); |
| """ |
|
|
|
|
| with gr.Blocks(title=APP_TITLE) as demo: |
| state = gr.State(new_state()) |
|
|
| gr.Markdown( |
| f""" |
| # {APP_TITLE} |
| {APP_TAGLINE} Start at task intake, evidence, prototype, critique, validation, or synthesis. |
| """ |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(scale=3, elem_classes="command-rail"): |
| gr.Markdown("### Run") |
| task = gr.Textbox( |
| label="Task", |
| lines=4, |
| value="Design a delightful hackathon demo that proves small agents can be transparent, forkable, and useful.", |
| elem_classes="task-box", |
| ) |
| steps = gr.Slider(2, MAX_STEPS, value=5, step=1, label="Trace depth", elem_classes="depth-slider") |
| run_btn = gr.Button("Run trace", variant="primary", elem_id="run-btn") |
| with gr.Accordion("Advanced setup", open=False, elem_classes="advanced-setup"): |
| start_phase = gr.Dropdown( |
| START_MODES, |
| value=START_TASK, |
| label="Start point", |
| info="Seed the trace from a specific phase instead of always beginning at task intake.", |
| ) |
| backend = gr.Radio( |
| [BACKEND_OFFLINE, BACKEND_MODAL, BACKEND_HF], |
| value=BACKEND_OFFLINE, |
| label="Reasoner", |
| ) |
| model_id = gr.Textbox( |
| label="Small model", |
| value=DEFAULT_MODEL_ID, |
| lines=1, |
| ) |
| with gr.Accordion("Dataset trial", open=False, elem_classes="dataset-trial"): |
| dataset_choice = gr.Dropdown( |
| DATASET_NAMES, |
| value=DATASET_NAMES[0], |
| label="Example set", |
| ) |
| with gr.Row(elem_classes="compact-actions"): |
| load_dataset_btn = gr.Button("Load example", elem_id="load-dataset-btn") |
| run_dataset_btn = gr.Button("Run mini dataset", elem_id="run-dataset-btn") |
| dataset_table = gr.Dataframe( |
| headers=["example", "nodes", "pairs", "label", "trace"], |
| datatype=["str", "str", "str", "str", "str"], |
| value=[], |
| interactive=False, |
| label="Dataset results", |
| wrap=True, |
| elem_classes="dataset-table", |
| ) |
| dataset_file = gr.File(label="Dataset artifact") |
| gr.Markdown("### Advanced node controls") |
| operation_prompt = gr.Textbox( |
| label="Instruction", |
| value="Retry this branch with a clearer assumption.", |
| lines=2, |
| ) |
| with gr.Row(elem_classes="action-row"): |
| retry_btn = gr.Button("Retry branch", variant="primary", elem_id="retry-btn") |
| replay_btn = gr.Button("Replay downstream", elem_id="replay-btn") |
| with gr.Row(elem_classes="label-row"): |
| label_choice = gr.Dropdown(LABELS, value="weak", label="Label") |
| mark_btn = gr.Button("Apply", elem_id="mark-btn") |
| with gr.Row(elem_classes="compact-actions"): |
| summary_btn = gr.Button("Summarize", elem_id="summary-btn") |
| clear_marks_btn = gr.Button("Clear", elem_id="clear-btn") |
| with gr.Row(elem_classes="compact-actions"): |
| fork_btn = gr.Button("Fork", elem_id="fork-btn") |
| export_btn = gr.Button("Export JSON", elem_id="export-btn") |
| with gr.Accordion("Training signal", open=False, elem_classes="training-signal"): |
| feedback_reason = gr.Textbox( |
| label="Feedback note", |
| value="This branch is more grounded and useful.", |
| lines=2, |
| ) |
| with gr.Row(elem_classes="action-row"): |
| prefer_btn = gr.Button("Prefer selected", variant="primary", elem_id="prefer-btn") |
| reject_btn = gr.Button("Reject selected", elem_id="reject-btn") |
| export_rl_btn = gr.Button("Export RL data", elem_id="export-rl-btn") |
| tune_btn = gr.Button("RL tune on Modal", elem_id="tune-btn") |
| feedback_table = gr.Dataframe( |
| headers=["id", "chosen", "rejected", "reason", "created"], |
| datatype=["str", "str", "str", "str", "str"], |
| value=feedback_rows(new_state()), |
| interactive=False, |
| label="Preference pairs", |
| wrap=True, |
| elem_classes="feedback-table", |
| ) |
| action_log = gr.Textbox( |
| label="Action log", |
| value="Select a node from the trace table, then retry, replay, or label it.", |
| lines=4, |
| interactive=False, |
| elem_classes="action-log", |
| ) |
| trace_file = gr.File(label="Artifact", elem_classes="trace-artifact") |
| visual_action_kind = gr.Textbox(value="select", elem_id="visual-action-kind", elem_classes="server-bridge") |
| visual_action_node = gr.Textbox(value="root", elem_id="visual-action-node", elem_classes="server-bridge") |
| visual_action_prompt = gr.Textbox( |
| value="Retry this branch with a clearer assumption.", |
| elem_id="visual-action-prompt", |
| elem_classes="server-bridge", |
| ) |
| visual_action_btn = gr.Button("Run visual action", elem_id="visual-action-btn") |
| with gr.Column(scale=4): |
| tree = gr.HTML(render_tree(new_state()), label="Trace tree") |
| trace_table = gr.Dataframe( |
| headers=["id", "node", "kind", "timeline", "status", "labels", "parent"], |
| datatype=["str", "str", "str", "str", "str", "str", "str"], |
| value=trace_rows(new_state()), |
| interactive=False, |
| label="Click a trace node to inspect or operate on it", |
| wrap=True, |
| ) |
| with gr.Column(scale=2): |
| inspector = gr.HTML(render_inspector(new_state()), label="Inspector") |
| transcript = gr.Textbox(label="Readable trace log", lines=18, interactive=False) |
|
|
| run_btn.click( |
| run_agent, |
| inputs=[task, steps, start_phase, backend, model_id, state], |
| outputs=[tree, trace_table, inspector, transcript, state, feedback_table], |
| ) |
| load_dataset_btn.click( |
| load_dataset_example, |
| inputs=[dataset_choice], |
| outputs=[task, steps, start_phase, operation_prompt, action_log], |
| ) |
| run_dataset_btn.click( |
| run_dataset_eval, |
| inputs=[backend, model_id], |
| outputs=[dataset_table, dataset_file, action_log], |
| ) |
| trace_table.select( |
| select_trace_row, |
| inputs=[state], |
| outputs=[tree, trace_table, inspector, action_log, state], |
| ) |
| visual_action_btn.click( |
| visual_server_action, |
| inputs=[visual_action_kind, visual_action_node, visual_action_prompt, backend, model_id, state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file, feedback_table], |
| ) |
| retry_btn.click( |
| retry_selected_branch, |
| inputs=[operation_prompt, backend, model_id, state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file], |
| ) |
| replay_btn.click( |
| replay_downstream, |
| inputs=[operation_prompt, state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file], |
| ) |
| mark_btn.click( |
| mark_selected, |
| inputs=[label_choice, state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file], |
| ) |
| summary_btn.click( |
| summarize_marked, |
| inputs=[state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file], |
| ) |
| clear_marks_btn.click( |
| clear_marks, |
| inputs=[state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file], |
| ) |
| fork_btn.click( |
| fork_selected, |
| inputs=[operation_prompt, state], |
| outputs=[tree, trace_table, inspector, transcript, state, trace_file], |
| ) |
| export_btn.click(export_only, inputs=[state], outputs=[trace_file]) |
| prefer_btn.click( |
| prefer_selected, |
| inputs=[feedback_reason, state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file, feedback_table], |
| ) |
| reject_btn.click( |
| reject_selected, |
| inputs=[feedback_reason, state], |
| outputs=[tree, trace_table, inspector, transcript, action_log, state, trace_file, feedback_table], |
| ) |
| export_rl_btn.click( |
| export_rl_dataset, |
| inputs=[state], |
| outputs=[action_log, state, trace_file, feedback_table], |
| ) |
| tune_btn.click( |
| start_modal_tuning, |
| inputs=[model_id, state], |
| outputs=[action_log, state, feedback_table], |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch(css=CSS, head=f"<script>{JS}</script>", theme=gr.themes.Soft()) |
|
|