| """Node: ≤2-sentence NL caption via mistral-large-latest (or any LLMProvider). |
| |
| Per arch v2 §3, this is the ONLY place the pipeline calls a non-coder LLM. |
| We call it last so a caption failure can never mask the structured answer — |
| on any error here we fall back to a deterministic Sentence built from the |
| result shape. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from collections.abc import Callable |
|
|
| from nl_sql.agent.prompts import load_prompt |
| from nl_sql.agent.state import PipelineState |
| from nl_sql.llm.providers.base import GenerateRequest, LLMProvider, ProviderError |
|
|
|
|
| def make_explain_trace_node( |
| provider: LLMProvider, |
| *, |
| max_tokens: int = 200, |
| temperature: float = 0.2, |
| preview_rows: int = 5, |
| ) -> Callable[[PipelineState], PipelineState]: |
| def node(state: PipelineState) -> PipelineState: |
| outcome = state.get("outcome") |
| question = state.get("question", "") |
| trace = list(state.get("trace") or []) |
|
|
| if outcome is None or outcome.result is None: |
| caption = state.get("error_message") or "no result available" |
| trace.append({"node": "explain_trace", "fallback": True}) |
| return {"caption": caption, "trace": trace} |
|
|
| result = outcome.result |
| preview = ", ".join(str(row) for row in result.rows[:preview_rows]) or "(none)" |
| prompt = load_prompt( |
| "explain", |
| question=question, |
| sql=outcome.sql, |
| columns=", ".join(result.columns), |
| row_count=result.row_count, |
| preview=preview, |
| ) |
| try: |
| response = provider.generate( |
| GenerateRequest(prompt=prompt, max_tokens=max_tokens, temperature=temperature) |
| ) |
| caption = (response.text or "").strip() |
| except ProviderError as exc: |
| caption = f"(caption unavailable: {exc})" |
| trace.append({"node": "explain_trace", "error": str(exc)}) |
| return {"caption": caption, "trace": trace} |
|
|
| trace.append( |
| { |
| "node": "explain_trace", |
| "model": response.model, |
| "input_tokens": response.input_tokens, |
| "output_tokens": response.output_tokens, |
| } |
| ) |
| return {"caption": caption, "trace": trace} |
|
|
| return node |
|
|