Report Generator
Overview
report_generator is the third and final agent in the SequentialAgent pipeline (query_resolution). It acts as the “FINAL AUTHORITY” and data presentation specialist for the Kavion AI Assistant. Its sole responsibility is to synthesize retrieved database records, metadata, and analytical results into a clear, structured, and highly readable Markdown report. It operates under a strict “NO ANALYSIS ALLOWED” mandate, presenting facts, counts, and sample images exactly as retrieved without drawing subjective conclusions, making recommendations, or performing causal reasoning.
Role and Position
- Role: Root-level sub-agent within the
SequentialAgentpipeline (query_resolution). - Position: Third and final agent in the pipeline. Runs unconditionally on every turn.
- Caller: Called by the
SequentialAgentpipeline (query_resolution) when the pipeline is executed. - Callee: None. It does not call other agents.
- Input: Reads consolidated state from
ctx.state(includingresolver_classifier,database_specialist, andrgb_surface_analyzeroutputs). - Output: Its output is written to
ctx.state["report_generator"]and processed by thereport_generator_after_agentcallback, which assembles the final FAT schema payload (StructuredCrewResponse) and writes it back toctx.state["report_generator"].
Tools
- Tools: None. No tools are bound to
report_generator. It is a pure data presentation and synthesis agent.
LLM Context Design
This section details every item that ends up in the actual request sent to the LLM, categorized by its lifecycle and source.
Context Lifecycle Categorization
- Static (fixed at code-authoring time, identical every call):
- Base Instruction Template: The core instruction text defined in
adk_agents_config.yaml(excluding placeholders). - Few-Shot Examples: The 8 few-shot examples embedded directly in the instruction text (Examples 1 to 8).
- Base Instruction Template: The core instruction text defined in
- Dynamic · session (varies by session/user state but stable within a session):
{active_filters}: Sourced fromglobal.active_filtersin session state. (Inserted exactly 1 time in the instruction template).{resolver_classifier}: Sourced fromresolver_classifierin session state. (Inserted exactly 1 time in the instruction template).{database_specialist}: Sourced fromdatabase_specialistin session state. (Inserted exactly 1 time in the instruction template).{multi_image_cap}: Maximum images per surface analysis turn (configured viamulti_image_capinadk_agents_config.yaml). (Inserted exactly 18 times in the instruction template).
- Dynamic · per-turn (recomputed every turn):
- User Message: The current turn’s raw user query.
- ADK-injected / default (added automatically by the ADK framework itself):
- Conversation History: Pulled in automatically by ADK because
include_contents: defaultis set. This includes user messages, assistant responses, tool calls, and tool responses from previous turns in the session. - Output Schema Formatting Instructions: Automatically appended by ADK to the end of the prompt because
output_schema: ReporterSynthesisSchemais set. This instructs the model to output JSON matching the Pydantic schema.
- Conversation History: Pulled in automatically by ADK because
- Callback-mutated (present, changed, or removed because a callback touched it):
- None. No
before_agentorbefore_modelcallbacks are registered forreport_generatorthat mutate the request.
- None. No
History Inclusion (include_contents)
- Setting:
include_contents: default - Behavior: Instructs the ADK framework to automatically include the conversation history (user messages, assistant responses, tool calls, and tool responses) in the LLM request. The history is formatted as a sequence of turns.
Instruction Template and Placeholders
The instruction template is defined in src/kavai/config/adk_agents_config.yaml. The factory (factory.py) substitutes placeholders into the instruction text at agent build time:
| Placeholder | Source | What it injects | Insertion Count |
|---|---|---|---|
{active_filters} |
Session state (factory.py) |
JSON representation of active filters | 1 |
{resolver_classifier} |
Session state (factory.py) |
JSON representation of classifier output | 1 |
{database_specialist} |
Session state (factory.py) |
JSON representation of database specialist output | 1 |
{multi_image_cap} |
adk_agents_config.yaml |
Maximum images per surface analysis turn (configured via multi_image_cap) |
18 |
Ultra-Lean Schema V3 Reporting Contract
The agent operates under the Ultra-Lean Schema V3 reporting contract. It is strictly mandated to output ONLY the markdown_report field within a JSON object matching the ReporterSynthesisSchema.
- No Hand-Authored UI Tags: The agent MUST NOT output any UI tags (such as
[[image-gallery:...]]or[[surface-analysis:...]]). These are injected deterministically in Python by thereport_generator_after_agentcallback using thesmart_router.pydispatch table. - No Raw UUIDs: The agent MUST NOT output raw database UUIDs in the report text. It should use filenames or human-readable identifiers.
- No Findings Objects: The agent MUST NOT output a separate
findingslist or object. All findings must be integrated directly into the narrative Markdown report. - No Top-Level Title: The first non-blank line of the Markdown report MUST NOT begin with a single
#(use##or lower).
State I/O
Reads
ctx.state["resolver_classifier"](to readis_in_scope,is_ambiguous,query_type,target_image_ids,resolved_query)ctx.state["database_specialist"](to readretrieved_rows,errors,is_complete,agent_result, andrgb_surface_analyzer.resultsfor surface analysis turns)ctx.state["global"]["active_filters"](to readactive_filtersfor multi-turn context retention)ctx.state["temp:real_execute_sql"](to read the real SQL rows captured bydatabase_specialist_after_tool)
Writes
ctx.state["report_generator"]: Overwritten by thereport_generator_after_agentcallback with the assembled FAT payload (StructuredCrewResponse).
Scope and Persistence
- Scope: Session-scoped. All state is written to
ctx.stateand manually persisted tosession.dbviapersist_session_statein theafter_agentcallback. - Temp Prefixes: Ephemeral keys starting with
temp:(liketemp:real_execute_sqlortemp:_llm_last_invocation_id) are used in callbacks but are stripped before persistence tosession.dbto prevent leaking transient state or secrets.
Output Schema
- Schema:
ReporterSynthesisSchema(defined insrc/kavai/foundation_services/utility/models/responses/structured.py). - How it changes the request:
- ADK automatically appends formatting instructions to the end of the prompt, forcing the model to output a JSON object matching the schema.
- The schema is wrapped using
make_gemini_compatible_model(which stripsadditionalPropertiesfrom the JSON schema to prevent Gemini constrained decoding crashes). - Since
report_generatordoes not call any tools, there is no interaction between the output schema and tool calling.
ReporterSynthesisSchema Schema Fields
| Field | Type | Default | Description |
|---|---|---|---|
markdown_report |
str |
N/A (Required) | The complete narrative response/report in Markdown. |
Callbacks
| Callback | Used? | Behavior |
|---|---|---|
before_agent |
❌ | N/A |
before_model |
❌ | N/A |
before_tool |
❌ | N/A (no tools) |
after_tool |
❌ | N/A (no tools) |
after_model |
❌ | N/A |
after_agent |
✅ | report_generator_after_agent |
on_model_error |
✅ | on_model_error_callback |
report_generator_after_agent
Runs after the agent emits its response. Parses the JSON output, validates it against the Pydantic schema, and writes to session state.
- Robust Parsing: Implements a 3-tier parsing strategy:
- Tier 1: Strict JSON parse.
- Tier 2: Markdown-fenced block extraction (
```json). - Tier 3: Greedy regex fallback (matches
(\{.*\})viare.search).
- Context and Specialist Data Retrieval:
- Reads
database_specialistoutput fromctx.state["database_specialist"]. - Reads the real
execute_sqlrows captured bydatabase_specialist_after_toolfromctx.state["temp:real_execute_sql"](falls back todatabase_specialist.retrieved_rowsif no SQL was executed or in unit tests). - Reads
anomaliesandis_completefromdatabase_specialist.
- Reads
- Smart Routing:
- Invokes
smart_route(ctx.state, raw_rows)fromsmart_router.pyto determine the UI layout (markdown_reportorgeneral) and the UI tag to append (e.g.,[[image-gallery:slug]],[[gas-readings:slug]],[[dataset-list:all]],[[surface-analysis:slug]]). - If layout is
markdown_report, the main chat bubble text is set to a generic status message:"Analysis complete. See the detailed report in the side panel.", and the full markdown report (with the appended UI tag) is written to themarkdown_reportfield. - If layout is
general, the main chat bubble text is set to the raw markdown report, andmarkdown_reportis set toNone(no side-panel).
- Invokes
- Analysis Turn Handling:
- Detects if the turn contains sensor-analysis output via
has_analysis_output(ctx.state). - If it is an analysis turn, it builds
fat_analysis_resultsusingbuild_analysis_results_from_state(ctx.state). - Populates single-image fields (
analysis_targetandbounding_regions) from the first result for backward compatibility. - Builds
fat_imageslist:- If actual image records are present in
raw_rows, it uses them. - Else if
fat_analysis_resultsis present, it builds fat image rows using_build_fat_image_row(img_id, target, analysis_dict). - Else if
fat_analysis_targetis present, it builds a single fat image row.
- If actual image records are present in
- If it is NOT an analysis turn,
fat_imagesis set toraw_rowsif layout isimage_galleryormarkdown_report, else[].
- Detects if the turn contains sensor-analysis output via
- Final Payload Assembly (FAT Schema):
- Assembles the final
StructuredCrewResponsedict:is_complete:is_completetext:final_textresponse_type:final_layoutimages:fat_imagesimage_list:[]datasets:raw_rows if final_layout == "dataset_list" else []gas_readings:raw_rows if final_layout == "gas_readings" else []findings:[]markdown_report:final_markdowncomponents:[]metadata:{"dataset_slug": active_slug}anomalies:anomaliesresults:{}bounding_regions:fat_bounding_regionsanalysis_target:fat_analysis_targetanalysis_results:fat_analysis_results
- Assembles the final
- State Override:
- Overwrites
ctx.state["report_generator"]with the assembled FAT payload.
- Overwrites
on_model_error_callback
Handles raised Gemini exceptions (network, auth, quota, 5xx) after SDK-level retries are exhausted.
- Invocation Reset: Resets retry and model-swap counters at the start of each new pipeline invocation using a temporary invocation ID (
temp:_llm_last_invocation_id). - App-Level Retries: If the error is retryable, it increments the retry counter and directly retries the request with the same model (up to 2 retries).
- Model Swap: If retries are exhausted or the error is non-retryable but swap-eligible, it swaps the model to the next one in the fallback chain:
gemini-3.1-flash-lite(default) →gemini-3.5-flash(first fallback) →gemini-2.5-pro(last resort). - Graceful Degradation: If all models are exhausted or a terminal error occurs, it returns a graceful
LlmResponsewith a user-friendly error message, swallowing the exception.
Planner
- Planner: None.
report_generatordoes not use a planner.
ADK Version Verified Against
- Verified against:
google-adk >= 1.26.0(as specified inpyproject.toml).
Generation Config
| Parameter | Value | Source / Notes |
|---|---|---|
| Model | gemini-3.1-flash-lite |
Pinned in adk_agents_config.yaml |
| Temperature | Default (1.0) | Left to ADK/Gemini defaults |
| Max Output Tokens | Default | Left to ADK/Gemini defaults |
| Safety Settings | Default | Left to ADK/Gemini defaults |
| Stop Sequences | None | Left to ADK/Gemini defaults |
Open Questions / Unverified Items
- Unverified items:
- The exact token limit or window size for
include_contents: defaultin ADK 1.26.0+ is not explicitly documented in the codebase, but it is assumed to be managed dynamically by the ADK framework. - Whether the ADK framework performs any prompt-level optimization or compression on the conversation history before sending it to the model.
- The exact token limit or window size for