"""Builds the planner LLM prompt from question + catalog. Renders the catalog into a compact textual form that fits the LLM context window. For users with ≤50 tables the full catalog goes in verbatim. """ from __future__ import annotations from ...catalog.models import Catalog from ...catalog.render import render_source def render_catalog(catalog: Catalog) -> str: """Render every Source in the catalog as text. One blank line between sources.""" if not catalog.sources: return "(catalog is empty — the user has not registered any structured data yet)" return "\n\n".join(render_source(s) for s in catalog.sources) def build_planner_prompt( question: str, catalog: Catalog, previous_error: str | None = None, ) -> str: """Return the human-message content for the planner LLM. Composed of three sections in order: 1. The user's question. 2. The user's full catalog (rendered). 3. (optional) The previous attempt's error, on retry. The system prompt (`config/prompts/query_planner.md`) is loaded separately by `QueryPlannerService`. """ sections = [ f"# Question\n\n{question}", f"# Catalog\n\n{render_catalog(catalog)}", ] if previous_error: sections.append( "# Previous attempt failed validation\n\n" f"{previous_error}\n\n" "Emit a corrected IR. Do not repeat the same mistake." ) return "\n\n".join(sections)