RGB Surface Analyzer
Version: 1.0
Date: 2026-07-21
Status: ✅ Production-Ready
Last Updated: 2026-08-08
Author: KavAI Backend Team
This page describes DataADK’s shipped analyzer path, which remains the production implementation. A platform-level successor exists but DataADK has not been ported to it (owner decision 2026-08-08 — DataADK is not modified; the port is deferred):
- The provider-neutral contract surface is
IMAGE_ANALYSIS_RESULT(CONTRACT-CDC-001 v2.4 §7.8, proposed);IMAGE_WITH_ANNOTATIONSremains this analyzer’s KavApps-compatible emission during the transition. - The analyzer’s prompt, vocabulary, cap, and pixel→normalized safety net are carried verbatim by
kavai-image-skills(image.surface-condition@1.0.0), which acquires media through the hostresolve_mediacapability instead of the JWT/SAS/fetch pipeline described below. - The
surface-analysiscertification capability row skips for engines that do not declare it and blocks for engines that do. The declaring set is inai/src/kavai/systems/e2e_registry.json, pinned bytest_the_engines_that_declare_the_capability— not restated here, because a prose list of adopters is wrong as soon as one more adopts.
Proposal: docs/proposals/20260808_uuid_only_image_analysis_skills.md · plan: docs/plans/20260808_image_skills_certification_execution.md.
Overview
rgb_surface_analyzer is a ADK LlmAgent wrapped as AgentTool and wired to the database_specialist agent. It is designed to perform visual surface anonlay detection on full-resolution RGB images. It receives up to multi_image_cap (default: 5) images in a single Gemini Vision call and returns structured, per-image surface-condition detections (currently limitted to stains and corrosion) with normalized bounding box coordinates.
Role and Position
- Role: LLM agent wrapped and wired as an AgentTool (using
google.adk.tools.agent_tool.AgentTool). - Position: It is owned and called by the
database_specialistexecutor agent as a tool. - Caller:
database_specialistinvokes it conditionally whenresolver_classifier.query_type == "surface_analysis". - Callee: It does not call any other agents.
Tools
- Tools bound to it: None.
rgb_surface_analyzerhas no tools bound to it.
LLM Context Design
This section details every item that ends up in the actual request sent to the LLM, categorized by its lifecycle and mutability.
Context Components
- Instruction Text (System Instruction) [Static / Dynamic · session (via template substitution)]
- The core instruction text defines the agent’s role, persona, input contract, output contract, coordinate system, self-check rules, confidence guidelines, error handling, few-shot examples, and critical output rules.
- It is loaded from
adk_agents_config.yamland processed bydynamic_instruction_providerinfactory.py. - It contains placeholders that are substituted at runtime:
{multi_image_cap}[Static]: Replaced withstr(load_multi_image_cap())(loaded fromadk_agents_config.yamlat startup). It is inserted exactly 3 times in the instruction text.{surface_analysis_vocabulary}[Static]: Replaced with the allowed condition labels (e.g.,"stain","corrosion") loaded fromadk_agents_config.yamlat startup. It is inserted exactly 2 times in the instruction text.
- There are no session-specific placeholders
- Input Arguments (JSON) [Dynamic · per-turn]
- ADK serializes the calling agent’s arguments as JSON in the first text part of
llm_request.contentswhen the agent is invoked as an AgentTool withinput_schema=RGBSurfaceAnalyzerInput. - The schema contains:
image_ids:Optional[List[str]](defaultNone) — list of image UUIDs to analyze.image_id:Optional[str](defaultNone) — deprecated single-image alias; thebefore_modelcallback normalizes this toimage_ids = [image_id]when present.dataset_slug:str(default"") — dataset slug for RLS-scoped SAS URL minting.
- This is recomputed every turn based on the executor’s findings.
- ADK serializes the calling agent’s arguments as JSON in the first text part of
- Labeled Image Parts (
Part.from_bytes) [Callback-mutated / Dynamic · per-turn]- The
before_modelcallback (rgb_surface_analyzer_before_model) fetches the image bytes concurrently and attaches them tollm_request.contents. - Each image is preceded by a text label part:
"Image N (id: <uuid>):"followed by the binary image part (Part.from_bytes). - This is recomputed every turn based on the resolved image IDs.
- The
- Output Schema Formatting Instructions [ADK-injected / default]
- ADK automatically appends formatting instructions to the request or configures the model’s response schema to enforce JSON output conforming to
RGBSurfaceAnalyzerResponse.
- ADK automatically appends formatting instructions to the request or configures the model’s response schema to enforce JSON output conforming to
Explicit Callouts
- include_contents: Set to
"none"inadk_agents_config.yaml. This means that conversation history is not pulled into the request. The agent only sees the current turn’s input (the JSON arguments and the attached images). It does not see previous turns’ conversation history, tool calls, or responses. This is a critical design choice to keep the context window clean and focused purely on the vision analysis task. - global_instruction: Not applicable, as
rgb_surface_analyzeris a sub-agent (AgentTool) and not the root agent.
State I/O
Reads
temp:jwt_token(fallback JWT token used bybefore_modelcallback to mint Azure SAS URLs when the request-scoped contextvar is empty; written bydatabase_specialist_before_tool)temp:rgb_image_dims_<image_id>(cached image dimensions read inafter_modelto normalize pixel coordinates)
Writes
temp:rgb_image_dims_<image_id>(cached image dimensions extracted via PIL inbefore_model)rgb_surface_analyzer_input_error(diagnostic error message written bybefore_modelif input validation or image fetch fails completely)rgb_surface_analyzer(the final structuredRGBSurfaceAnalyzerResponsewritten automatically by ADK’soutput_keymechanism)
Scope and Persistence
- Scope: Session-scoped.
rgb_surface_analyzerhas noafter_agent_callbackregistered. Thergb_surface_analyzeroutput key is written directly toctx.stateby ADK’s ownoutput_keymechanism — not by a manualpersist_session_statecall. Persistence of the parent session (including this key) is handled bydatabase_specialist_after_agent. - Temp Prefixes: Ephemeral keys starting with
temp:(liketemp:jwt_tokenandtemp:rgb_image_dims_<image_id>) or__(double underscore) are used in callbacks but are stripped before persistence tosession.dbto prevent leaking transient state or secrets. - Session State: Standard keys (
rgb_surface_analyzer,rgb_surface_analyzer_input_error) are persisted tosession.dband are cleared at the start of each turn by_apply_per_turn_resetinagent.pyto prevent stale data bleed across turns.
Output Schema
The agent’s output is strictly constrained to the RGBSurfaceAnalyzerResponse Pydantic schema (defined in sensor_analysis_models.py).
RGBSurfaceAnalyzerResponse Schema Fields
RGBSurfaceAnalyzerResponseresults:List[PerImageResult](one entry per analyzed image).
PerImageResultimage_id:str(UUID of the analyzed image)image_analyzed:bool(Trueon success,Falseon fetch/decode failure)bounding_regions:List[Detection](detected anomalies)overall_assessment:str(plain-text summary)error:Optional[str](diagnostic message on failure)
Detectioncondition_type:str(must be insurface_analysis_vocabulary, e.g.,"stain","corrosion")confidence:float([0.0, 1.0])x,y,width,height:float(normalized coordinates in[0.0, 1.0])description:Optional[str](max 200 chars)
Note: ADK passes the schema to Gemini’s constrained decoding API, forcing the model to output valid JSON matching the schema. To prevent crashes, the schema uses extra="ignore" (never extra="forbid") and avoids typing.Union, typing.Literal, or Dict[str, Any].
Callbacks
This section documents the ADK callbacks for rgb_surface_analyzer.
| Callback | Used? | Behavior |
|---|---|---|
before_agent |
❌ | N/A |
before_model |
✅ | rgb_surface_analyzer_before_model |
before_tool |
❌ | N/A (no tools) |
after_tool |
❌ | N/A (no tools) |
after_model |
✅ | rgb_surface_analyzer_after_model |
after_agent |
❌ | N/A |
on_model_error |
❌ | Intentionally not registered. rgb_surface_analyzer runs inside an isolated ADK AgentTool runner; error handling is delegated to the parent database_specialist pipeline. |
rgb_surface_analyzer_before_model
Resolves input args, caps at _MULTI_IMAGE_CAP, reads JWT, mints Azure SAS URLs, fetches image bytes concurrently, caches image dimensions, and attaches labeled Part.from_bytes to llm_request.contents.
- Argument Resolution: Resolves input args (
image_ids,dataset_slug) fromllm_request.contentsand caps the list at_MULTI_IMAGE_CAP(default: 5). - JWT and SAS Minting: Reads the JWT token from the request-scoped contextvar or
state["temp:jwt_token"]fallback, and concurrently mints Azure SAS URLs (TTL: 600s). - Image Fetch and Dimension Caching: Fetches image bytes using
httpx.AsyncClientwith exponential backoff (max 2 retries, 30s timeout). Decodes image bytes using PIL to extract and cache dimensions instate["temp:rgb_image_dims_<image_id>"]. - Request Mutation: Attaches labeled text parts (
"Image N (id: <uuid>):") and binary image parts (Part.from_bytes) tollm_request.contents. ReturnsNoneto proceed with the mutated request.
rgb_surface_analyzer_after_model
Intercepts the raw response, parses the JSON payload, checks for pixel-valued coordinates, normalizes them using cached image dimensions, clamps them to [0.0, 1.0], and re-serializes the response.
- Coordinate Normalization: Scans for pixel-valued coordinates (any coordinate > 1.0). If found, looks up cached image dimensions from
state["temp:rgb_image_dims_<image_id>"]and normalizes them (x / width,y / height, etc.) clamping to[0.0, 1.0]. - Failure Handling: If dimensions are missing, marks the result as failed (
image_analyzed=False,bounding_regions=[],errorpopulated) to prevent meaningless clamped coordinates. - Response Mutation: Re-serializes the JSON and returns the mutated
llm_responseso ADK re-runs validation. If no rewrite is needed, it returnsNone(pass-through).
Planner
- Planner usage: None.
rgb_surface_analyzeris a standardLlmAgentand does not use a planner.
ADK Version Verified Against
- Verified against
google_adkversion 1.26.0.
Generation Config
| Parameter | Value | Source / Notes |
|---|---|---|
| Model | gemini-3.5-flash |
Pinned in adk_agents_config.yaml |
| Temperature | Default (not set) | Inherits Gemini API default |
| Max Tokens | Default (not set) | Inherits Gemini API default |
| Safety Settings | Default (not set) | Inherits Gemini API default |
| Stop Sequences | Default (not set) | Inherits Gemini API default |
| Retry Options | 3 attempts, 1.0s initial delay, 16.0s max delay, exp base 2, retry on [408, 429, 500, 502, 503, 504] |
Configured at SDK level via Gemini model object in factory.py |
Open Questions / Unverified Items
- Gemini API Bug: The workaround of using
Part.from_bytesinstead ofPart.from_uriis due to an open Gemini API bug wherePart.from_urireturns 403 on Azure SAS URLs. This should be reverted toPart.from_urionce Google resolves the bug to reduce memory overhead and latency. - Coordinate Normalization Fallback: If Gemini fails to echo the
image_idin its response, the after-model callback cannot look up the cached dimensions for that image, resulting in a hard failure for that image result. This is a known limitation of relying on the model to echo identifiers.