flyquery's agent + pipeline prompts live as YAML files under
src/flyquery/resources/prompts/.
This is byte-equivalent to the conventions in flycanon and flyradar
so the three services share one mental model for prompt tuning.
- Operators tune prompts without a Python redeploy. A devops engineer can edit a YAML file in a hotfix branch, run the smoke suite, and ship.
- CI diffs prompts independently of code. Reviewing a single YAML change is much faster than rereading a Python module to find the multi-paragraph triple-quoted string.
- Long instructions stay out of code files. YAML files have no
line-length ceiling, so multi-paragraph prompts don't run into
ruff E501warnings or get abbreviated to satisfy them.
src/flyquery/resources/prompts/
├── grounding.yaml # GroundingAgent
├── generation.yaml # GenerationAgent
├── critic.yaml # CriticAgent
├── explainer.yaml # ExplainerAgent
├── describe.yaml # DescribeAgent
├── relation_proposer.yaml # RelationProposerAgent
├── rename_detection.yaml # RenameDetectionAgent
└── column_name_proposer.yaml # ColumnNameProposerAgent
Each file declares (matching flycanon + flyradar):
name: flyquery/<agent_name>
version: "1.0.0"
description: >-
One-paragraph human description of what this agent does.
# Variables consumed by the user template (when present).
system: |
System / instructions prompt -- passed to pydantic-ai's
``Agent.instructions``.
user: | # optional
Jinja2-templated user prompt:
{{ question }}
{% for h in hits -%}
...
{% endfor %}src/flyquery/core/agents/prompt_loader.py
ships a 90-line loader that:
- Reads the YAML file from the package resources.
- Compiles the
usertemplate through Jinja2 withStrictUndefined(missing variables raise at the call site, not silently corrupt the prompt). - Exposes
.instructions(thesystemblock) +.render(**vars)(returns(instructions, user_prompt)tuple).
from flyquery.core.agents.prompt_loader import load_prompt
prompt = load_prompt("grounding")
instructions, user = prompt.render(question="...", inv_tables=[...], ...)Each build_<agent>_agent constructor loads its YAML and passes
prompt.instructions to build_agent -- there is no fallback
to inline Python strings.
- Edit the YAML file.
- Bump
version(semver) -- shows up in observability traces. - Run the unit tests:
The grounding-anti-hallucination test catches accidental deletion of the Hard Rules.
uv run pytest tests/unit/test_prompt_loader.py - Run the e2e demo (
scripts/nl_query_demo.py) to verify the real LLM still produces grounded answers.
tests/unit/test_prompt_loader.py pins:
- The loader resolves every shipped prompt name.
rendersubstitutes Jinja2 variables.grounding.yamlretains the hard rules againstbalance_sheet/income_statement/cash_flow/financials.*hallucinations.
If a prompt edit removes a rule, the test fails before the change reaches main.