Download apps/api/mbf/graph/agent.py from Sandy-sys/finai: direct link, hf CLI and curl.
- Browser
- Download file 18.8 kB
-
https://huggingface.co/spaces/Sandy-sys/finai/resolve/main/apps/api/mbf/graph/agent.py
- Command line
-
hf download hf://spaces/Sandy-sys/finai/apps/api/mbf/graph/agent.py
-
curl -L -o agent.py https://huggingface.co/spaces/Sandy-sys/finai/resolve/main/apps/api/mbf/graph/agent.py
18.8 kB
| """Agent orchestrator: routes question β multi-stage pipeline β UIPlan. | |
| Pipeline stages activate different slices of the 30-key pool, so a single | |
| question distributes load across 8-12 distinct keys in parallel: | |
| 1. intent β LIGHT (17-19) | |
| 2. retrieve β RETRIEVER (11-16) β GraphRAG-lite schema rerank | |
| 3. plan β SQL (7-10) | |
| 4. critic β CRITIC (1-6) β runs concurrently with primary exec | |
| 4b.verify β VERIFIER (20-23) β second-opinion on result quality | |
| 5. insights β SUMMARY (11-16) β fan-out, 1 LLM call per result set | |
| 6. narrative β NARRATIVE (7-10) | |
| 7. followup β FOLLOWUP (24-26) β smart follow-up generation | |
| Each stage is resilient: any failure degrades to a heuristic and the pipeline | |
| continues. | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import time | |
| from dataclasses import dataclass, field | |
| from typing import Any, Awaitable, Callable | |
| # Type for stage event emitter: async function(event_dict) -> None | |
| EmitFn = Callable[[dict[str, Any]], Awaitable[None]] | |
| from ..keys.pool import get_pool | |
| from ..retrieval.duck import run_sql | |
| from ..retrieval.sql_safety_duck import validate_duckdb_sql | |
| from ..ui_plan.schema import UIPlan | |
| from .dynamic import ( | |
| ResultSet, | |
| _pick_table, | |
| assemble_plan, | |
| dynamic_offline_plan, | |
| heuristic_secondaries, | |
| ) | |
| from .pipeline import ( | |
| classify_intent, | |
| critique_sql, | |
| exec_narrative, | |
| generate_followups, | |
| per_result_insights, | |
| plan_sqls, | |
| retrieve_tables, | |
| verify_sql, | |
| ) | |
| class AgentState: | |
| question: str | |
| intent: str = "exploratory" | |
| sql: str | None = None | |
| rows: list[dict[str, Any]] = field(default_factory=list) | |
| plan: UIPlan | None = None | |
| trace: list[dict[str, Any]] = field(default_factory=list) | |
| mode: str = "offline" | |
| keys_used: list[str] = field(default_factory=list) | |
| confidence: float = 0.7 | |
| followups: list[str] = field(default_factory=list) | |
| def _safe_run(sql: str) -> tuple[str, list[dict[str, Any]], str | None]: | |
| chk = validate_duckdb_sql(sql) | |
| if not chk.ok: | |
| return sql, [], chk.reason | |
| res = run_sql(chk.rewritten) | |
| return chk.rewritten, res.rows, res.error | |
| def _track(state: AgentState, kid: str | None) -> None: | |
| if kid and kid not in state.keys_used: | |
| state.keys_used.append(kid) | |
| async def _noop_emit(_evt: dict[str, Any]) -> None: | |
| return None | |
| # --------------------------------------------------------------------------- # | |
| # Result-quality helpers # | |
| # --------------------------------------------------------------------------- # | |
| def _is_all_null_or_zero(rows: list[dict[str, Any]]) -> bool: | |
| """True when every numeric value in every row is None or 0, | |
| meaning the result set carries no useful information. | |
| Non-numeric columns (labels, ids) are ignored.""" | |
| if not rows: | |
| return True | |
| found_any_numeric = False | |
| for r in rows: | |
| for v in r.values(): | |
| if v is None: | |
| continue | |
| if isinstance(v, (int, float)): | |
| found_any_numeric = True | |
| if v != 0: | |
| return False | |
| # strings that look like "0" or "0.00" β common from VARCHARs | |
| elif isinstance(v, str): | |
| try: | |
| fv = float(v) | |
| found_any_numeric = True | |
| if fv != 0: | |
| return False | |
| except (ValueError, OverflowError): | |
| pass # genuine text label β skip | |
| # If there were no numeric columns at all, treat the result as meaningful | |
| # (it's probably a label/lookup query). | |
| return found_any_numeric | |
| def _filter_meaningful(results: list[ResultSet]) -> list[ResultSet]: | |
| """Drop result sets whose numeric data is entirely null/zero.""" | |
| return [r for r in results if not _is_all_null_or_zero(r.rows)] | |
| def _preview_rows(rows: list[dict[str, Any]], n: int = 3) -> list[dict[str, Any]]: | |
| out = [] | |
| for r in rows[:n]: | |
| item = {} | |
| for k, v in r.items(): | |
| if isinstance(v, (int, float)): | |
| item[k] = v | |
| elif v is None: | |
| item[k] = None | |
| else: | |
| s = str(v) | |
| item[k] = s if len(s) <= 60 else s[:57] + "β¦" | |
| out.append(item) | |
| return out | |
| async def _exec_secondaries_parallel( | |
| question: str, table: str, specs: list[dict[str, Any]] | |
| ) -> list[ResultSet]: | |
| """Run secondary SQLs concurrently; each DuckDB call is short & cheap.""" | |
| def _job(spec: dict[str, Any]) -> ResultSet | None: | |
| sq = (spec.get("sql") or "").strip() | |
| st = spec.get("title") or "Companion view" | |
| if not sq: | |
| return None | |
| rewritten, rows, err = _safe_run(sq) | |
| if err or not rows or _is_all_null_or_zero(rows): | |
| return None | |
| return ResultSet(title=st, sql=rewritten, rows=rows, table=table) | |
| loop = asyncio.get_running_loop() | |
| tasks = [loop.run_in_executor(None, _job, s) for s in specs[:3]] | |
| out = await asyncio.gather(*tasks, return_exceptions=True) | |
| return [r for r in out if isinstance(r, ResultSet)] | |
| async def _run_llm_path(state: AgentState, emit: EmitFn = _noop_emit) -> AgentState: | |
| q = state.question | |
| t0 = time.perf_counter() | |
| def _ms() -> int: | |
| return int((time.perf_counter() - t0) * 1000) | |
| # Stages 1 + 2 run in parallel β intent and retrieval are independent. | |
| await emit({"stage": "intent", "status": "running", "label": "Classifying intent", | |
| "role": "LIGHT", "ms": _ms()}) | |
| await emit({"stage": "retrieve", "status": "running", | |
| "label": "Retrieving relevant tables (GraphRAG-lite)", | |
| "role": "RETRIEVER", "ms": _ms()}) | |
| (intent, k_intent), (tables, k_retr) = await asyncio.gather( | |
| classify_intent(q), retrieve_tables(q, k=3), | |
| ) | |
| state.intent = intent | |
| _track(state, k_intent) | |
| _track(state, k_retr) | |
| state.trace.append({"stage": "intent", "value": intent, "key": k_intent}) | |
| state.trace.append({"stage": "retrieve", "tables": tables, "key": k_retr}) | |
| await emit({"stage": "intent", "status": "done", "key": k_intent, | |
| "value": intent, "ms": _ms()}) | |
| await emit({"stage": "retrieve", "status": "done", "key": k_retr, | |
| "tables": tables, "ms": _ms()}) | |
| if not tables: | |
| tables = [_pick_table(q)] | |
| # Stage 3 β SQL plan | |
| await emit({"stage": "plan", "status": "running", | |
| "label": "Generating SQL plan (primary + secondaries)", | |
| "role": "SQL", "ms": _ms()}) | |
| plan_data, k_plan = await plan_sqls(q, tables, intent) | |
| _track(state, k_plan) | |
| state.trace.append({"stage": "plan", "key": k_plan, | |
| "has_primary": bool(plan_data.get("primary"))}) | |
| table = plan_data.get("table") or tables[0] | |
| primary = plan_data.get("primary") or {} | |
| secondaries = plan_data.get("secondaries") or [] | |
| p_sql = (primary.get("sql") or "").strip() | |
| p_title = primary.get("title") or "Result" | |
| await emit({"stage": "plan", "status": "done", "key": k_plan, | |
| "table": table, "primary_title": p_title, | |
| "primary_sql": p_sql, | |
| "secondary_count": len(secondaries), | |
| "secondary_titles": [s.get("title") for s in secondaries[:3]], | |
| "ms": _ms()}) | |
| # Stage 4 β SQL critic runs CONCURRENTLY with primary execution. | |
| primary_result: ResultSet | None = None | |
| if p_sql: | |
| await emit({"stage": "critic", "status": "running", | |
| "label": "Auditing primary SQL", "role": "CRITIC", "ms": _ms()}) | |
| await emit({"stage": "exec_primary", "status": "running", | |
| "label": "Executing primary query on DuckDB", | |
| "role": "DUCKDB", "ms": _ms()}) | |
| async def _run_primary() -> tuple[str, list[dict], str | None]: | |
| loop = asyncio.get_running_loop() | |
| return await loop.run_in_executor(None, _safe_run, p_sql) | |
| critic_fut = asyncio.create_task(critique_sql(q, table, p_sql)) | |
| exec_fut = asyncio.create_task(_run_primary()) | |
| (rewritten, rows, err), (fix, k_crit) = await asyncio.gather(exec_fut, critic_fut) | |
| _track(state, k_crit) | |
| state.trace.append({"stage": "critic", "key": k_crit, | |
| "applied": bool(fix) and bool(err)}) | |
| # Apply critic fix if execution failed OR if results are all-null/zero | |
| all_null = (not err and rows and _is_all_null_or_zero(rows)) | |
| await emit({"stage": "exec_primary", "status": "done", | |
| "row_count": len(rows), "error": err, | |
| "all_null": bool(all_null), "ms": _ms()}) | |
| await emit({"stage": "critic", "status": "done", "key": k_crit, | |
| "fix_provided": bool(fix), | |
| "will_apply": bool(fix) and (bool(err) or bool(all_null)), | |
| "ms": _ms()}) | |
| if (err or all_null) and fix: | |
| await emit({"stage": "critic_retry", "status": "running", | |
| "label": "Re-running corrected SQL", "ms": _ms()}) | |
| rewritten, rows, err = _safe_run(fix) | |
| state.trace.append({"stage": "critic_retry", "error": err, | |
| "fix_len": len(fix)}) | |
| await emit({"stage": "critic_retry", "status": "done", | |
| "row_count": len(rows), "error": err, "ms": _ms()}) | |
| state.sql = rewritten | |
| state.rows = rows | |
| if not err and rows: | |
| primary_result = ResultSet(title=p_title, sql=rewritten, | |
| rows=rows, table=table) | |
| # Stage 4b β SQL verifier (second opinion, runs concurrently w/ secondaries) | |
| verify_task = None | |
| if primary_result and primary_result.rows: | |
| await emit({"stage": "verify", "status": "running", | |
| "label": "Verifying result quality (second opinion)", | |
| "role": "VERIFIER", "ms": _ms()}) | |
| verify_task = asyncio.create_task( | |
| verify_sql(q, table, primary_result.sql, primary_result.rows) | |
| ) | |
| # Stage 3b β secondaries fan-out in parallel DuckDB executions | |
| results: list[ResultSet] = [] | |
| if primary_result: | |
| results.append(primary_result) | |
| if secondaries: | |
| await emit({"stage": "secondaries", "status": "running", | |
| "label": f"Running {min(len(secondaries),3)} companion queries in parallel", | |
| "role": "DUCKDB", "ms": _ms()}) | |
| sec_results = await _exec_secondaries_parallel(q, table, secondaries) | |
| results.extend(sec_results) | |
| if secondaries: | |
| await emit({"stage": "secondaries", "status": "done", | |
| "succeeded": len(sec_results), | |
| "attempted": min(len(secondaries), 3), | |
| "ms": _ms()}) | |
| # Collect verifier result (ran concurrently with secondaries) | |
| if verify_task: | |
| try: | |
| v_fix, v_conf, k_ver = await verify_task | |
| _track(state, k_ver) | |
| state.confidence = v_conf | |
| state.trace.append({"stage": "verify", "key": k_ver, | |
| "confidence": v_conf, | |
| "fix_provided": bool(v_fix)}) | |
| await emit({"stage": "verify", "status": "done", "key": k_ver, | |
| "confidence": v_conf, | |
| "fix_provided": bool(v_fix), "ms": _ms()}) | |
| # If verifier says results are wrong and provides a fix, re-run | |
| if v_fix and v_conf < 0.5: | |
| await emit({"stage": "verify_retry", "status": "running", | |
| "label": "Re-running verifier-corrected SQL", | |
| "ms": _ms()}) | |
| rewritten, rows, err = _safe_run(v_fix) | |
| if not err and rows and not _is_all_null_or_zero(rows): | |
| state.sql = rewritten | |
| state.rows = rows | |
| results[0] = ResultSet(title=p_title, sql=rewritten, | |
| rows=rows, table=table) | |
| state.confidence = max(v_conf + 0.2, 0.6) | |
| state.trace.append({"stage": "verify_retry", "error": err, | |
| "row_count": len(rows)}) | |
| await emit({"stage": "verify_retry", "status": "done", | |
| "row_count": len(rows), "error": err, "ms": _ms()}) | |
| except Exception: # noqa: BLE001 | |
| state.confidence = 0.5 | |
| # top-up with heuristic companion views if still sparse | |
| if len(results) < 3: | |
| await emit({"stage": "heuristic_topup", "status": "running", | |
| "label": "Adding heuristic companion views", "ms": _ms()}) | |
| added = 0 | |
| for ti, sq in heuristic_secondaries(q, table): | |
| if any(r.sql.strip() == sq.strip() for r in results): | |
| continue | |
| rewritten, rows, err = _safe_run(sq) | |
| if err or not rows or _is_all_null_or_zero(rows): | |
| continue | |
| results.append(ResultSet(title=ti, sql=rewritten, rows=rows, | |
| table=table)) | |
| added += 1 | |
| if len(results) >= 4: | |
| break | |
| await emit({"stage": "heuristic_topup", "status": "done", | |
| "added": added, "ms": _ms()}) | |
| # Drop result sets whose numeric data is entirely null/zero | |
| results = _filter_meaningful(results) | |
| if not results: | |
| state.trace.append({"stage": "llm_empty_fallback"}) | |
| state.plan = dynamic_offline_plan(q) | |
| await emit({"stage": "fallback", "status": "done", | |
| "label": "No results β using offline dynamic plan", "ms": _ms()}) | |
| return state | |
| # Emit a preview of result sets so the UI can show data right away | |
| await emit({"stage": "result_sets", "status": "done", | |
| "count": len(results), | |
| "previews": [ | |
| {"title": r.title, "rows": _preview_rows(r.rows), | |
| "row_count": len(r.rows), | |
| "columns": list(r.rows[0].keys()) if r.rows else []} | |
| for r in results | |
| ], | |
| "ms": _ms()}) | |
| # Stages 5 + 6 + 7 in parallel β insights, narrative, AND follow-ups | |
| await emit({"stage": "insights", "status": "running", | |
| "label": f"Generating per-result insights ({len(results)} parallel)", | |
| "role": "SUMMARY", "ms": _ms()}) | |
| await emit({"stage": "narrative", "status": "running", | |
| "label": "Drafting executive narrative", | |
| "role": "NARRATIVE", "ms": _ms()}) | |
| await emit({"stage": "followups", "status": "running", | |
| "label": "Generating smart follow-up questions", | |
| "role": "FOLLOWUP", "ms": _ms()}) | |
| (insights_pair, narrative_pair, followup_pair) = await asyncio.gather( | |
| per_result_insights(q, [(r.title, r.rows) for r in results]), | |
| exec_narrative(q, [(r.title, r.rows) for r in results]), | |
| generate_followups(q, [(r.title, r.rows) for r in results], intent), | |
| ) | |
| insights, k_insights = insights_pair | |
| narrative, k_nar = narrative_pair | |
| followups, k_fol = followup_pair | |
| for k in k_insights: | |
| _track(state, k) | |
| _track(state, k_nar) | |
| _track(state, k_fol) | |
| state.followups = followups | |
| state.trace.append({"stage": "insights", "count": len(insights), | |
| "keys": k_insights}) | |
| state.trace.append({"stage": "narrative", "key": k_nar}) | |
| state.trace.append({"stage": "followups", "key": k_fol, | |
| "count": len(followups)}) | |
| await emit({"stage": "insights", "status": "done", "keys": k_insights, | |
| "count": len([i for i in insights if i]), "ms": _ms()}) | |
| await emit({"stage": "narrative", "status": "done", "key": k_nar, | |
| "preview": (narrative or "")[:200], "ms": _ms()}) | |
| await emit({"stage": "followups", "status": "done", "key": k_fol, | |
| "count": len(followups), "ms": _ms()}) | |
| await emit({"stage": "synth", "status": "running", | |
| "label": "Assembling adaptive UI plan", "ms": _ms()}) | |
| plan = assemble_plan(q, results, intent=intent) | |
| if narrative: | |
| plan.narrative = narrative | |
| # Attach per-slice insights as the `why` on each data-table component | |
| if insights: | |
| tbl_comps = [c for c in plan.components if c.type == "data-table"] | |
| for comp, ins in zip(tbl_comps, insights): | |
| if ins: | |
| comp.why = ins | |
| state.plan = plan | |
| state.trace.append({ | |
| "stage": "synth", | |
| "components": len(plan.components), | |
| "result_sets": len(results), | |
| "keys_used": state.keys_used, | |
| }) | |
| await emit({"stage": "synth", "status": "done", | |
| "components": len(plan.components), | |
| "result_sets": len(results), | |
| "keys_used": state.keys_used, "ms": _ms()}) | |
| return state | |
| async def run_agent(question: str, emit: EmitFn = _noop_emit) -> AgentState: | |
| state = AgentState(question=question) | |
| if not get_pool().available(): | |
| state.mode = "offline" | |
| plan = dynamic_offline_plan(question) | |
| state.plan = plan | |
| state.intent = "descriptive" | |
| for c in plan.citations: | |
| if c.snippet: | |
| state.sql = c.snippet | |
| break | |
| state.trace.append({"stage": "offline_dynamic", | |
| "components": len(plan.components), | |
| "result_sets": len(plan.citations)}) | |
| await emit({"stage": "offline", "status": "done", | |
| "label": "No keys available β offline plan", | |
| "components": len(plan.components)}) | |
| return state | |
| state.mode = "groq" | |
| try: | |
| return await _run_llm_path(state, emit=emit) | |
| except Exception as e: # noqa: BLE001 | |
| state.trace.append({"stage": "llm_exception", "error": str(e)[:200]}) | |
| state.plan = dynamic_offline_plan(question) | |
| state.intent = "descriptive" | |
| await emit({"stage": "exception", "status": "error", | |
| "error": str(e)[:200]}) | |
| return state | |