recon + sqlglot validator + drill_down package; guard ReAct dimension picks against candidate list
This commit is contained in:
@@ -5,6 +5,10 @@ transition; the FastAPI SSE endpoint subscribes and forwards.
|
||||
|
||||
Event payloads are constructed via the module-level factory functions below
|
||||
so the dict shape lives in one place and is grep-able.
|
||||
|
||||
Many event types carry an `analysis` field that names the active Analysis
|
||||
(read from `current_analysis` ContextVar). The UI uses it to group
|
||||
sub-events under the right Analysis node without inferring from time order.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -14,7 +18,7 @@ import json
|
||||
from typing import Any
|
||||
|
||||
from api.analyses.types import Finding
|
||||
from api.runtime.context import current_run_id
|
||||
from api.runtime.context import current_analysis, current_run_id
|
||||
|
||||
# run_id -> queue of events. Events are plain dicts.
|
||||
_queues: dict[str, asyncio.Queue[dict[str, Any] | None]] = {}
|
||||
@@ -80,6 +84,8 @@ async def stream(run_id: str):
|
||||
# ── Event factories ──
|
||||
# Each returns the dict payload to pass into `publish()` /
|
||||
# `publish_current()`. Names mirror the `type` field for grep-ability.
|
||||
# Factories that fire from inside an Analysis pick up the analysis name from
|
||||
# the `current_analysis` ContextVar so callers don't have to pass it.
|
||||
|
||||
def run_start(question: str) -> dict[str, Any]:
|
||||
return {"type": "run_start", "question": question}
|
||||
@@ -101,33 +107,66 @@ def node_update(node: str, update: Any) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def analysis_start(name: str, args: dict[str, Any], why: str) -> dict[str, Any]:
|
||||
return {"type": "analysis_start", "analysis": name, "args": args, "why": why}
|
||||
def analysis_start(name: str, args: dict[str, Any], why: str, *,
|
||||
step_id: str | None = None) -> dict[str, Any]:
|
||||
return {"type": "analysis_start", "analysis": name, "step_id": step_id,
|
||||
"args": args, "why": why}
|
||||
|
||||
|
||||
def analysis_end(name: str, finding: Finding) -> dict[str, Any]:
|
||||
def analysis_end(name: str, finding: Finding, *,
|
||||
step_id: str | None = None) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "analysis_end",
|
||||
"analysis": name,
|
||||
"step_id": step_id,
|
||||
"summary": finding.summary,
|
||||
"error": finding.error,
|
||||
"row_count": len(finding.rows),
|
||||
}
|
||||
|
||||
|
||||
def analysis_fallback(*, primary: str, fallback: str, reason: str,
|
||||
step_id: str | None = None) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "analysis_fallback",
|
||||
"primary": primary,
|
||||
"fallback": fallback,
|
||||
"reason": reason,
|
||||
"step_id": step_id,
|
||||
}
|
||||
|
||||
|
||||
def synth_start() -> dict[str, Any]:
|
||||
return {"type": "synth_start"}
|
||||
|
||||
|
||||
def tool_call_start(tool: str, *, input: Any = None) -> dict[str, Any]:
|
||||
return {"type": "tool_call_start", "tool": tool, "input": input}
|
||||
return {
|
||||
"type": "tool_call_start",
|
||||
"tool": tool,
|
||||
"analysis": current_analysis.get(),
|
||||
"input": input,
|
||||
}
|
||||
|
||||
|
||||
def tool_call_end(tool: str, *, output: Any = None, error: str | None = None) -> dict[str, Any]:
|
||||
return {"type": "tool_call_end", "tool": tool, "output": output, "error": error}
|
||||
def tool_call_end(tool: str, *, output: Any = None,
|
||||
error: str | None = None) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "tool_call_end",
|
||||
"tool": tool,
|
||||
"analysis": current_analysis.get(),
|
||||
"output": output,
|
||||
"error": error,
|
||||
}
|
||||
|
||||
|
||||
def llm_call(label: str, *, system_len: int, user_len: int) -> dict[str, Any]:
|
||||
"""Lightweight breadcrumb for an LLM call. Doesn't carry the prompt
|
||||
contents (those go through Langfuse) — just the fact and rough size."""
|
||||
return {"type": "llm_call", "label": label, "system_chars": system_len, "user_chars": user_len}
|
||||
return {
|
||||
"type": "llm_call",
|
||||
"label": label,
|
||||
"analysis": current_analysis.get(),
|
||||
"system_chars": system_len,
|
||||
"user_chars": user_len,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user