From e4056c15f67b76d62ea639fcfe2aca77b661a90a Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Wed, 1 Apr 2026 22:32:34 +0000 Subject: [PATCH 1/6] docs: add Agent SDK development guide for other agent languages --- agent_sdks/agent_sdk_guide.md | 260 ++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 agent_sdks/agent_sdk_guide.md diff --git a/agent_sdks/agent_sdk_guide.md b/agent_sdks/agent_sdk_guide.md new file mode 100644 index 000000000..03bbed87f --- /dev/null +++ b/agent_sdks/agent_sdk_guide.md @@ -0,0 +1,260 @@ +# Agent SDK Development Guide + +This document describes the architecture of an A2UI Agent SDK. The design separates concerns into distinct layers to follow a similar structure for consistency across languages, providing a streamlined developer experience for building AI agents that generate rich UI. + +The **Agent SDK** is responsible for **capability management, prompt engineering, and A2UI payload validation**. It enables the LLM to understand what UI it can build and ensures that what it produces is valid. + +## 1. Unified Architecture Overview + +The A2UI Agent SDK architecture has a well-defined data flow that bridges language-agnostic schema specifications with LLM inputs and outputs. + +1. **Define Capabilities**: The SDK loads component schemas (usually from bundled package resources) and organizes them into **Catalogs**. +2. **Generate Prompts**: The SDK uses these catalogs to generate system instructions, automatically injecting the relevant JSON Schema and few-shot examples into the LLM's prompt. +3. **Streaming Parsing**: Support parsing the LLM's output *as it streams*, yielding partial or complete UI messages progressively. +4. **Validate Output**: When the LLM generates a response, the SDK parses it, extracts the A2UI JSON, and validates it against the schema. +5. **Serialize & Send**: The validated JSON is wrapped in a standard transport envelope (e.g., Agent-to-Agent/A2A DataPart) and streamed to the client. + +--- + +## 2. The Core Interfaces + +At the heart of the A2UI Agent SDK are four key interfaces that manage schemas and validate output. + +### `CatalogConfig` + +Defines the metadata for a component catalog. It points to where the schemas and examples live on disk. + +```python +class CatalogConfig: + name: str + schema_path: str + examples_path: Optional[str] = None +``` + +### `A2uiCatalog` + +Represents a processed catalog. It provides methods for validation and LLM instruction rendering. + +```python +class A2uiCatalog: + name: str + validator: A2uiValidator + + def render_instructions(self, options: InstructionOptions) -> str: + """ + Generates a string representation of the catalog (schemas and examples) + suitable for inclusion in an LLM system prompt. + """ + ... +``` + +### `InferenceStrategy` + +The abstract base interface for assembling system prompts for the LLM. It defines how to combine role descriptions, workflow descriptions, and UI descriptions into a single prompt. + +```python +class InferenceStrategy(ABC): + @abstractmethod + def generate_system_prompt( + self, + role_description: str, + workflow_description: str = "", + ui_description: str = "", + client_ui_capabilities: Optional[dict[str, Any]] = None, + allowed_components: Optional[list[str]] = None, + allowed_messages: Optional[list[str]] = None, + include_schema: bool = False, + include_examples: bool = False, + validate_examples: bool = False, + ) -> str: + """ + Generates a system prompt for the LLM. + """ + ... +``` + +#### Standard Implementations + +* **`A2uiSchemaManager`**: Generates prompts by dynamically loading and organizing Component Schemas and examples from catalogs. +* **`A2uiTemplateManager`**: Generates prompts using predefined UI templates or static structures. + +### `A2uiValidator` & `PayloadFixer` + +The safety net of the SDK. + +* **`PayloadFixer`**: Attempts to fix common LLM formatting errors (like trailing commas, missing quotes, or unterminated brackets) before structural parsing. +* **`A2uiValidator`**: Performs deep semantic and integrity validation beyond standard JSON Schema checks. + +#### Standard Validator Checks: + +1. **JSON Schema Validation**: Verifies that the payload adheres to the A2UI JSON Schema. +2. **Component Integrity**: Ensures all component IDs are unique and that a valid `root` component exists if required. +3. **Topology & Reachability**: Detects circular references (including self-references) and orphaned components (all components must be reachable from the root). +4. **Recursion Depth Limits**: Enforces limits on nesting depth (e.g., 50 levels) and specific limits for function calls (e.g., 5 levels) to prevent stack overflows on the client. +5. **Path Syntax Validation**: Validates JSON Pointer syntax for data binding paths. + +--- + +## 3. Schema Management & Loading + +The SDK does not define component schemas programmatically in code. Instead, it **loads basic catalog JSON Schema definitions packed into the SDK resources** at runtime. Porting the SDK to a new language requires implementing a resource loader and a schema parser for that language's ecosystem (e.g., using `Pydantic` in Python or `kotlinx.serialization` in Kotlin). + +Loading from the workspace's `specification/` directory is supported but should be treated as a **fallback for local development**. + +### Implementation Principles + +1. **Freestanding Catalogs**: Catalogs should be freestanding. They should define their own types or reference relative paths within the same directory tree. +2. **Version Awareness**: The schema manager must respect the A2UI protocol version (e.g., `v0.9`). If an agent requests `v0.8` schema, it should serve the `v0.8` definitions. +3. **Resource Bundling**: Standard schemas should be bundled with the SDK artifact. Use language-standard utilities to read from package resources (e.g., Python's `importlib.resources`). Fall back to scanning the local `/specification` filesystem path *only* if resource loading fails or if explicitly configured for development. + +--- + +## 4. Prompt Engineering & Examples + +The primary value of the Agent SDK is making it easy to create **dynamic, token-efficient system prompts**. + +### `generateSystemPrompt` Requirements + +When generating prompts, the SDK should allow developers to: + +1. **Prune Schemas**: If an agent only needs a subset of components (e.g., only `Text` and `Button`), the SDK should prune the schema to save tokens. +2. **Inject Few-Shot Examples**: Few-shot examples are critical for LLM accuracy. The SDK should load these from example files (e.g., `examples/` directory in the catalog) and format them correctly using standard A2UI tags. +3. **Standard Envelopes**: The prompt must instruct the LLM to wrap its A2UI output in standard tags to enable deterministic parsing. + +**Standard Prompt Tags:** +``` +CONVERSTIONAL TEXT RESPONSE + +[{ + "surfaceUpdate": { ... } +}] + +``` + +--- + +## 5. The Streaming Parser + +The `A2uiStreamParser` is a state-machine-based parser designed to parse A2UI JSON *incrementally* as it arrives from the LLM stream. It enables yielding partial UI updates to the client for progressive rendering. + +### 1. High-Level Usage + +The parser is designed to be fed chunks of text (e.g., from an LLM stream) and returns complete or partial `ResponsePart` objects. + +```python +parser = A2uiStreamParser(catalog=my_catalog) + +for chunk in llm_stream: + parts = parser.process_chunk(chunk) + for part in parts: + if part.a2ui_json: + # Send UI update to client + send_to_client(part.a2ui_json) + if part.text: + # Stream conversational text to user + stream_text(part.text) +``` + +### 2. Internal Mechanics + +The parser uses a single-pass character scanner to track state and extract JSON fragments. + +#### State Tracking (The Brace Stack) +The parser tracks curly braces `{}` and brackets `[]` using a stack. +* **Outside Tags**: It ignores characters until it sees ``. +* **Inside Tags**: It pushes index and brace type onto the stack. When it sees a matching close brace, it pops. +* **Object Completion**: When the stack becomes empty (single top-level object) or when a specific object (like a component) finishes, it attempts to parse it. + +#### JSON Fixing (The Healer) +If a chunk ends in the middle of a JSON object, the parser uses a `_fix_json` utility to make it valid JSON. +* **Closing Quotes**: If a string value is cut off, it closes it (only for safe keys like labels, not for structural IDs or URLs to prevent breakage). +* **Closing Braces**: It appends missing `}` and `]` to balance the stack. +* **Trailing Commas**: It removes trailing commas. + +#### Component Sniffing +To enable fast UI updates, the parser "sniffs" for components *before* the outer JSON message is complete. +* If it sees a JSON object with `{ "id": "...", "component": "..." }`, it validates it early. +* If valid, it yields it immediately and caches it. The client can render this component while the rest of the stream arrives. + +--- + +## 6. Parsing, Fixing, & Validation + +LLMs are prone to syntax errors or schema violations. The SDK must handle these gracefully. + +### `parseResponse` Flow + +1. **Tag Detection**: Locate `` and `` tags in the raw text. +2. **Extraction**: Extract the substring between the tags. +3. **Pre-processing (Fixers)**: Run standard fixers (e.g., removing trailing commas, fixing unquoted keys, correcting simple JSON structural errors). +4. **JSON Validation**: Validate the cleaned JSON string against the target catalog schema using a standard JSON Schema validator for your language. +5. **Error Reporting**: If validation fails and cannot be fixed, the SDK should throw a structured error or fallback gracefully (e.g., yielding an error part to the client). + +--- + +## 7. Transport & A2A Integration + +Once validated, the A2UI payload must be transmitted over the network. In typical Agent-to-App (A2A) topologies, these are wrapped as **DataParts**. + +### Standards for Transport + +1. **MIME Type**: Mark A2UI JSON payloads with `application/json+a2ui`. This tells the frontend renderer (e.g., the browser or mobile app) how to interpret the stream. +2. **Standard Helpers**: Provide a `createA2uiPart` helper to automate this wrapping process. +3. **Yielding Strategy**: Support both complete objects (when the LLM finishes speaking) and incremental streaming parser yielding (for partial JSON display). + +--- + +## 8. The Basic Catalog Standard + +The SDK should provide an out-of-the-box configuration for the **A2UI Basic Catalog** (Button, Text, Row, Column, etc.). This ensures that "Hello, World" agents can be built without defining custom schemas. + +* In Python, this is provided by `BasicCatalog.get_config()`. +* Your language SDK should provide a similar singleton or preset that points to the standard basic catalog files in the `specification` folder. + +--- + +## 9. Agent Framework Integration (Tooling) + +While an SDK can be standalone, it is most useful when it integrates with popular agent frameworks (like Python's ADK). The SDK should provide standard adapters to connect A2UI capabilities with the framework's tool and event systems. + +### 1. The Toolset & Tools + +Provide a standard toolset (often called `SendA2uiToClientToolset`) that exposes tools to the LLM for sending rich UI. + +* **Dynamic Providers**: The toolset should accept providers (callables or futures) to let the tool determine at runtime if A2UI is enabled, and which catalog/examples to use for the current session. +* **The UI Tool**: The actual tool exposed to the LLM (e.g., `send_a2ui_json_to_client`). It should validate the LLM's JSON arguments against the schema *before* returning success to the framework. + +### 2. Part Converters + +A Part Converter translates the LLM's output (either tool calls or text tags) into standard transport Parts (like A2A DataParts). + +* **Tool-to-Part**: When the LLM calls the UI tool, the converter intercepts the success response (which contains the validated JSON) and wraps it into an A2UI Part. +* **Text-to-Part**: When the LLM outputs text with standard delimiters (e.g., ``), the converter runs the text through the parser and emits A2UI Parts. + +### 3. Event Converters + +An Event Converter intercepts the agent framework's event stream and applies the Part Converter. This ensures that validation and extraction happen seamlessly in the background without modifying the core agent logic. + +--- + +## 10. Contributor Implementation Guide + +If you are tasked with porting the `agent_sdk` to a new language (e.g., C++ or Kotlin), follow this strict, phased sequence: + +### Step 1: Core Foundation (Non-UI) +Implement `CatalogConfig`, `A2uiCatalog`, and an `InferenceStrategy` (like `A2uiSchemaManager`). Ensure you can load a JSON file and print its schema. + +### Step 2: Prompt Generation +Implement `generateSystemPrompt`. Verify that it outputs valid Markdown with embedded JSON schemas and examples. + +### Step 3: Parsing & Validation +Implement `parseResponse` and validation. Hook up a standard JSON Schema validator for your language. Write unit tests to check if it rejects bad JSON and accepts good JSON. + +### Step 4: Transport (A2A) +Create the helper utilities to wrap JSON in transport Parts (if needed for your ecosystem). + +### Step 5: Sample Applications +Create a simple sample (like a command-line agent or local server) to verify that the SDK works end-to-end. Refer to the reference Python samples (e.g., `samples/agent/adk/contact_lookup`) for inspiration. + +> [!IMPORTANT] +> Keep the SDK idiomatic to your language. Don't force Python-isms if it doesn't make sense (e.g., use builder patterns in Java/Kotlin or macros in C++ if they are more ergonomic). From cfd6764e6564fd4fd4667433c600e42862aec1b4 Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Wed, 1 Apr 2026 22:35:22 +0000 Subject: [PATCH 2/6] docs: add cross-language feature synchronization guidelines to agent SDK guide --- agent_sdks/agent_sdk_guide.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/agent_sdks/agent_sdk_guide.md b/agent_sdks/agent_sdk_guide.md index 03bbed87f..aad0f21c7 100644 --- a/agent_sdks/agent_sdk_guide.md +++ b/agent_sdks/agent_sdk_guide.md @@ -258,3 +258,16 @@ Create a simple sample (like a command-line agent or local server) to verify tha > [!IMPORTANT] > Keep the SDK idiomatic to your language. Don't force Python-isms if it doesn't make sense (e.g., use builder patterns in Java/Kotlin or macros in C++ if they are more ergonomic). + +--- + +## 11. Cross-Language Feature Synchronization + +The A2UI Agent SDK is a multi-language ecosystem. While features may be implemented in one language first (e.g., Python), we strive for consistency across all supported languages (Kotlin, C++, etc.). To maintain this consistency, we follow a strict synchronization process: + +### Synchronization Process: + +1. **Lead Implementation**: A feature can be developed and merged in one language first (often Python as the reference). +2. **File Sync Issues**: The author or reviewer of the feature **must file issues** for the equivalent feature requests in all other supported languages to ensure they are tracked. +3. **Cross-Referencing**: Link these new issues back to the original Pull Request or issue for context and reference. +4. **Consistency Over Clones**: While implementations should be idiomatic to the target language, they must follow the same architectural patterns (Inference Strategies, Validators, Streaming Parsers) and protocol standards defined in this guide. From a85be6e982494184c706ac85f406b594861873e0 Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Wed, 1 Apr 2026 22:42:31 +0000 Subject: [PATCH 3/6] Address review comments --- agent_sdks/agent_sdk_guide.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/agent_sdks/agent_sdk_guide.md b/agent_sdks/agent_sdk_guide.md index aad0f21c7..0f3581d42 100644 --- a/agent_sdks/agent_sdk_guide.md +++ b/agent_sdks/agent_sdk_guide.md @@ -22,12 +22,12 @@ At the heart of the A2UI Agent SDK are four key interfaces that manage schemas a ### `CatalogConfig` -Defines the metadata for a component catalog. It points to where the schemas and examples live on disk. +Defines the metadata for a component catalog. It uses a **Provider** to load the schema and points to optional examples. ```python class CatalogConfig: name: str - schema_path: str + provider: A2uiCatalogProvider examples_path: Optional[str] = None ``` @@ -40,7 +40,7 @@ class A2uiCatalog: name: str validator: A2uiValidator - def render_instructions(self, options: InstructionOptions) -> str: + def render_as_llm_instructions(self, options: InstructionOptions) -> str: """ Generates a string representation of the catalog (schemas and examples) suitable for inclusion in an LLM system prompt. @@ -123,7 +123,7 @@ When generating prompts, the SDK should allow developers to: **Standard Prompt Tags:** ``` -CONVERSTIONAL TEXT RESPONSE +CONVERSATIONAL TEXT RESPONSE [{ "surfaceUpdate": { ... } @@ -242,7 +242,7 @@ An Event Converter intercepts the agent framework's event stream and applies the If you are tasked with porting the `agent_sdk` to a new language (e.g., C++ or Kotlin), follow this strict, phased sequence: ### Step 1: Core Foundation (Non-UI) -Implement `CatalogConfig`, `A2uiCatalog`, and an `InferenceStrategy` (like `A2uiSchemaManager`). Ensure you can load a JSON file and print its schema. +Implement `CatalogConfig` (and its `Provider`), `A2uiCatalog`, and an `InferenceStrategy` (like `A2uiSchemaManager`). Ensure you can load a JSON file via a provider and print its schema. ### Step 2: Prompt Generation Implement `generateSystemPrompt`. Verify that it outputs valid Markdown with embedded JSON schemas and examples. From 32b7ec17e8b56078fa2774f7d7d41b0e98548a2a Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Wed, 1 Apr 2026 22:49:39 +0000 Subject: [PATCH 4/6] docs: update A2uiStreamParser documentation to reflect transition from state-machine to regex-based parsing --- agent_sdks/agent_sdk_guide.md | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/agent_sdks/agent_sdk_guide.md b/agent_sdks/agent_sdk_guide.md index 0f3581d42..aa5242a5c 100644 --- a/agent_sdks/agent_sdk_guide.md +++ b/agent_sdks/agent_sdk_guide.md @@ -135,7 +135,7 @@ CONVERSATIONAL TEXT RESPONSE ## 5. The Streaming Parser -The `A2uiStreamParser` is a state-machine-based parser designed to parse A2UI JSON *incrementally* as it arrives from the LLM stream. It enables yielding partial UI updates to the client for progressive rendering. +The `A2uiStreamParser` uses **regex-based block parsing** to find and extract A2UI JSON payloads from the LLM's text output stream. It buffers incoming chunks and yields standard part representations when a complete block is detected. ### 1. High-Level Usage @@ -157,24 +157,21 @@ for chunk in llm_stream: ### 2. Internal Mechanics -The parser uses a single-pass character scanner to track state and extract JSON fragments. +The parser buffers text and uses regex to extract content between tags. -#### State Tracking (The Brace Stack) -The parser tracks curly braces `{}` and brackets `[]` using a stack. -* **Outside Tags**: It ignores characters until it sees ``. -* **Inside Tags**: It pushes index and brace type onto the stack. When it sees a matching close brace, it pops. -* **Object Completion**: When the stack becomes empty (single top-level object) or when a specific object (like a component) finishes, it attempts to parse it. +#### Chunk Buffering +Incoming text chunks are appended to an internal buffer. The parser passes through conversational text until it detects the `` opening tag. -#### JSON Fixing (The Healer) -If a chunk ends in the middle of a JSON object, the parser uses a `_fix_json` utility to make it valid JSON. -* **Closing Quotes**: If a string value is cut off, it closes it (only for safe keys like labels, not for structural IDs or URLs to prevent breakage). -* **Closing Braces**: It appends missing `}` and `]` to balance the stack. -* **Trailing Commas**: It removes trailing commas. +#### Regex Block Extraction +Once both the opening and closing tags are found in the buffer, the parser uses a regex pattern (e.g., `(.*?)` with `re.DOTALL`) to extract the raw JSON string. +* It yields any text preceding the tag as standard conversational text. +* It yields the JSON content as an A2UI JSON part. -#### Component Sniffing -To enable fast UI updates, the parser "sniffs" for components *before* the outer JSON message is complete. -* If it sees a JSON object with `{ "id": "...", "component": "..." }`, it validates it early. -* If valid, it yields it immediately and caches it. The client can render this component while the rest of the stream arrives. +#### Sanitization & Cleanup +Before parsing the JSON, it sanitizes the string to remove any unexpected markdown code block delimiters (e.g., ` ```json `) that the LLM might have inadvertently wrapped around the JSON inside the A2UI tags. + +#### Multi-Block Support +The parser searches for all occurrences of the tags in the buffer and splits the content into alternating text parts and A2UI JSON parts, clearing processed blocks from the buffer. --- From b0303e0757c27bcfd14612c892c79f9f10f24af9 Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Wed, 1 Apr 2026 22:52:50 +0000 Subject: [PATCH 5/6] docs: add installation instructions and guide for incremental streaming with A2uiStreamParser --- agent_sdks/python/agent_development.md | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/agent_sdks/python/agent_development.md b/agent_sdks/python/agent_development.md index d03f6a76d..85ca2504f 100644 --- a/agent_sdks/python/agent_development.md +++ b/agent_sdks/python/agent_development.md @@ -15,6 +15,10 @@ The `agent_sdk` revolves around three main classes: * **`A2uiSchemaManager`**: The central coordinator that loads catalogs, manages versioning, and generates system prompts. +## Prerequisites + +- Install the SDK: `pip install a2ui-agent-sdk` + ## Generating A2UI Messages ### Step 1: Set up the Schema Manager @@ -170,6 +174,40 @@ yield { } ``` +#### 4e. Incremental Streaming (Advanced) + +For sub-second UI updates, you can parse the LLM stream *incrementally* using `A2uiStreamParser`. This parser uses a state-machine to sniff characters and yield components *before* the entire JSON block is complete. It automatically "heals" partial JSON (closing quotes and braces) to ensure valid payloads are yielded early. + +```python +from a2ui.core.parser.streaming import A2uiStreamParser +from a2ui.a2a import create_a2ui_part + +# Initialize the stream parser with your catalog +parser = A2uiStreamParser(catalog=schema_manager.get_selected_catalog()) + +# Inside your LLM stream loop: +for chunk in llm_response_stream: + # Process each text chunk as it arrives + response_parts = parser.process_chunk(chunk.text) + + for part in response_parts: + if part.a2ui_json: + # Yield partial UI updates immediately + yield { + "is_task_complete": False, + "parts": [create_a2ui_part(p) for p in part.a2ui_json] + } + if part.text: + # Yield conversational text + yield { + "is_task_complete": False, + "parts": [DataPart(text=part.text, mime_type="text/plain")] + } +``` + +> [!TIP] +> `A2uiStreamParser` performs content-based change detection to ensure components are only re-yielded if their content changes, minimizing bandwidth usage. + ## Use Cases ### 1. Simple Agents with Static Schemas From 1cecad183c2414f04480115f095c1b66c1733084 Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Wed, 1 Apr 2026 23:49:31 +0000 Subject: [PATCH 6/6] docs: update agent development guide for A2UI v0.9, version negotiation, and incremental streaming workflows --- agent_sdks/python/agent_development.md | 156 +++++++++++++++++-------- 1 file changed, 106 insertions(+), 50 deletions(-) diff --git a/agent_sdks/python/agent_development.md b/agent_sdks/python/agent_development.md index 85ca2504f..47e872f43 100644 --- a/agent_sdks/python/agent_development.md +++ b/agent_sdks/python/agent_development.md @@ -27,23 +27,25 @@ The first step in any A2UI-enabled agent is initializing the `A2uiSchemaManager`. ```python -from a2ui.core.schema.constants import VERSION_0_8 +from a2ui.core.schema.constants import VERSION_0_9 from a2ui.core.schema.manager import A2uiSchemaManager, CatalogConfig from a2ui.basic_catalog.provider import BasicCatalog +# Define your catalogs (basic or bring your own) with optional examples +basic_catalog_config = BasicCatalog.get_config( + version=VERSION_0_9, + examples_path="examples" +) +my_catalog_config = CatalogConfig.from_path( + name="my_custom_catalog", + catalog_path="path/to/catalog.json", + examples_path="path/to/examples" +) + +# Initialize the schema manager with your catalogs schema_manager = A2uiSchemaManager( - version=VERSION_0_8, - catalogs=[ - BasicCatalog.get_config( - version=VERSION_0_8, - examples_path="examples" - ), - CatalogConfig.from_path( - name="my_custom_catalog", - catalog_path="path/to/catalog.json", - examples_path="path/to/examples" - ), - ], + version=VERSION_0_9, + catalogs=[basic_catalog_config, my_catalog_config], ) ``` @@ -56,6 +58,9 @@ Notes: - If you have a modular catalog that references other catalogs, refer to [Freestanding Catalogs](../../../docs/catalogs.md#freestanding-catalogs) for more information. +- You can define multiple `A2uiSchemaManager` instances (one for each protocol version) + and select the active one at runtime based on the client request. + See [Multiple Version Support](#3-multiple-version-support) for more details. ### Step 2: Generate System Prompt @@ -71,8 +76,9 @@ instruction = schema_manager.generate_system_prompt( ui_description="Use the following components...", include_schema=True, # Injects the raw JSON schema include_examples=True, # Injects few-shot examples - allowed_components=["Heading", "Text", "Button"] # Optional: prune schema to save tokens + allowed_components=["Heading", "Text", "Button"], + allowed_messages=["CreateSurfaceMessage", "UpdateSurfaceMessage"], ) ``` @@ -121,73 +127,86 @@ agent_executor = MyAgentExecutor( ) ``` -#### 4b. Parse, Validate, and Fix LLM Output +#### 4b. A2UI Extension Version Negotiation -To ensure reliability, always validate the LLM's JSON output before returning -it. The SDK's `A2uiCatalog` provides a validator that checks the payload against -the A2UI schema. If the payload is invalid, the validator will attempt to fix -it. +Before processing a request, negotiate which version of the A2UI extension to activate based on the client request and your agent's advertised capabilities. ```python -from a2ui.core.parser.parser import parse_response +from a2ui.a2a import try_activate_a2ui_extension + +# In your request handler: +activated_version = try_activate_a2ui_extension(context, agent_card) + +if activated_version: + # Use the activated version to route requests to the schema manager + schema_manager = schema_managers[activated_version] + selected_catalog = schema_manager.get_selected_catalog(context) +``` -# Get the catalog for the current request -selected_catalog = schema_manager.get_selected_catalog() +#### 4c. Select a Parsing Strategy -# Parse the LLM's response into parts with simple fixers like removing trailing commas -response_parts = parse_response(text) +Depending on your latency requirements, choose between waiting for the full response or parsing text chunks incrementally. + +##### Option A: Full-Response Parsing + +Use this approach if you wait for the LLM to finish its entire response before processing and sending UI to the client. It is simpler to implement. + +**1. Parse, Validate, and Fix** + +Validate the LLM's JSON output before returning it. The SDK's `A2uiCatalog` validates the payload and attempts to fix simple errors (e.g., trailing commas). + +```python +from a2ui.core.parser.parser import parse_response + +# Parse the full response into parts +response_parts = parse_response(full_text) for part in response_parts: if part.a2ui_json: - # Validate the JSON part against the schema + # Validate against schema selected_catalog.validator.validate(part.a2ui_json) ``` -#### 4c. Stream the A2UI Payload - -After parsing and validating the A2UI JSON payloads, wrap them in an A2A -DataPart and stream them to the client. +**2. Stream the A2UI Payload** -To ensure the A2UI Renderers on the frontend recognize the data, add -`{"mimeType": "application/json+a2ui"}` to the DataPart's metadata. +Wrap the validated payloads in an A2A `DataPart` with the correct MIME type (`application/json+a2ui`) and stream it. -**Recommendation:** Use the [create_a2ui_part](src/a2ui/a2a.py) helper method to -convert A2UI JSON payloads into an A2A DataPart. +**Recommendation:** Use the `create_a2ui_part` helper. -#### 4d. Complete Agent Output Structure +**3. Complete Agent Output Structure (Helper)** -The most efficient way to generate structured agent output is to use the -`parse_response_to_parts` helper. It handles splitting the text, extracting A2UI -JSON, optional validation, and wrapping everything into A2A `Part` objects. +The `parse_response_to_parts` helper is the most efficient way to split text, extract JSON, validate, and wrap into A2A `Part` objects in one go. ```python from a2ui.a2a import parse_response_to_parts -from a2ui.core.schema.constants import A2UI_OPEN_TAG, A2UI_CLOSE_TAG - -# Inside your agent's stream method: -final_response_content = f"{text_segment}\n{A2UI_OPEN_TAG}\n{json_payload}\n{A2UI_CLOSE_TAG}" yield { "is_task_complete": True, - "parts": parse_response_to_parts(final_response_content, - fallback_text="OK."), + "parts": parse_response_to_parts(full_text), } ``` -#### 4e. Incremental Streaming (Advanced) +##### Option B: Incremental Streaming Parsing (Advanced) + +Use this approach for sub-second UI updates. The `A2uiStreamParser` **automatically parses, validates, and fixes (heals)** the JSON payload chunks *incrementally* as they arrive from the LLM stream. It yields valid UI messages *before* the entire JSON block is complete by automatically closing open quotes and braces. -For sub-second UI updates, you can parse the LLM stream *incrementally* using `A2uiStreamParser`. This parser uses a state-machine to sniff characters and yield components *before* the entire JSON block is complete. It automatically "heals" partial JSON (closing quotes and braces) to ensure valid payloads are yielded early. +> [!IMPORTANT] +> **Prerequisite**: To use incremental streaming, your agent executor must support streaming mode. In ADK, enable this using `RunConfig`: +> ```python +> run_config=run_config.RunConfig( +> streaming_mode=run_config.StreamingMode.SSE +> ) +> ``` ```python from a2ui.core.parser.streaming import A2uiStreamParser from a2ui.a2a import create_a2ui_part -# Initialize the stream parser with your catalog -parser = A2uiStreamParser(catalog=schema_manager.get_selected_catalog()) +parser = A2uiStreamParser(catalog=selected_catalog) # Inside your LLM stream loop: for chunk in llm_response_stream: - # Process each text chunk as it arrives + # Process text chunks as they arrive response_parts = parser.process_chunk(chunk.text) for part in response_parts: @@ -311,7 +330,42 @@ When the LLM calls the UI tool, the toolset uses the dynamic catalog to: 3. **Validate Payloads**: Validate the LLM's generated JSON against the specific `A2uiCatalog` object's validator. -### 3. Orchestration and Delegation + +### 3. Multiple Version Support + +To support multiple protocol versions (e.g., v0.8 and v0.9), pre-configure `A2uiSchemaManager` and `LlmAgent` instances for each version during your agent's initialization. At runtime, use `try_activate_a2ui_extension` to negotiate the version and select the pre-configured schema manager or runner. + +```python +# During Initialization (Setup mapping for each supported version) +schema_managers = { + VERSION_0_8: A2uiSchemaManager( + version=VERSION_0_8, + catalogs=[...], + ), + VERSION_0_9: A2uiSchemaManager( + version=VERSION_0_9, + catalogs=[...], + ), +} +ui_runners = { + VERSION_0_8: build_runner(build_agent(schema_managers[VERSION_0_8])), + VERSION_0_9: build_runner(build_agent(schema_managers[VERSION_0_9])), +} + +# Runtime Stream Handling (Select based on negotiation) +version = try_activate_a2ui_extension(context, self.agent_card) + +if version: + # Select the pre-configured agent runner and schema manager + runner = ui_runners[version] + schema_manager = schema_managers[version] +else: + # Fallback to standard text agent runner + runner = text_runner + schema_manager = None +``` + +### 4. Orchestration and Delegation Orchestrator agents delegate work to sub-agents. They often need to propagate UI capabilities and handle cross-agent UI state. @@ -336,7 +390,9 @@ agent_card = AgentCard( capabilities=AgentCapabilities( extensions=[ get_a2ui_agent_extension( - supported_catalog_ids=list(supported_catalog_ids)) + version=VERSION_0_9, # Specify the version to advertise + supported_catalog_ids=list(supported_catalog_ids), + ) ] ) )