Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a458056
refactor: restructure a2ui package layout for unified inference formats
gspencergoog Jul 14, 2026
a586f1f
refactor(sdk): add deprecated redirects for legacy schema validator a…
gspencergoog Jul 14, 2026
2c89ff9
docs(sdk): add docstrings and module documentation to unified-inferen…
gspencergoog Jul 14, 2026
7574be8
refactor(sdk): expose prompt_generator property on InferenceFormat an…
gspencergoog Jul 14, 2026
e12c3a3
Fix Formatting
gspencergoog Jul 14, 2026
27bb306
fix(sdk): restore backward compatibility of parse_response_to_parts w…
gspencergoog Jul 14, 2026
358c24d
deprecate(sdk): mark legacy parse_response function as deprecated
gspencergoog Jul 14, 2026
8cdc29f
refactor(sdk): introduce type-safe parse_content_to_parts and depreca…
gspencergoog Jul 14, 2026
e148e62
refactor(sdk): hide TransportPromptGenerator from package exports
gspencergoog Jul 14, 2026
b709c17
refactor(sdk): rename A2uiStreamParser to TransportStreamParser
gspencergoog Jul 14, 2026
6f40a90
refactor(samples): update sample agents to use TransportStreamParser
gspencergoog Jul 14, 2026
a44bb7e
feat(sdk): add has_format_content to Parser and use it in part converter
gspencergoog Jul 15, 2026
2dbdff1
feat(sdk): support complete flag on has_format_content and deprecate …
gspencergoog Jul 15, 2026
a22b726
refactor(sdk): use has_format_content for TextPart conversion in part…
gspencergoog Jul 15, 2026
a1dfec3
style(sdk): run ruff linter check and auto-fix formatting and unused …
gspencergoog Jul 15, 2026
b50d380
test(sdk): restore env var fallback gating tests in test_v10_validato…
gspencergoog Jul 15, 2026
2365406
feat(sdk): eliminate global environment variable checks for experimen…
gspencergoog Jul 15, 2026
27dffa7
feat(sdk): remove import gate and remaining os.environ references to …
gspencergoog Jul 15, 2026
962011a
refactor(sdk): extract PromptGenerator into prompt package and add de…
gspencergoog Jul 15, 2026
a83a633
style(adk): remove dead empty TYPE_CHECKING block in send_a2ui_to_cli…
gspencergoog Jul 15, 2026
4f32e68
refactor: remove unused legacy prompt_generator and strategies/schema…
gspencergoog Jul 15, 2026
063257a
Merge branch 'main' into unified-inference-formats-foundation
gspencergoog Jul 15, 2026
938192a
Merge branch 'main' into unified-inference-formats-foundation
gspencergoog Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion agent_sdks/python/a2ui_agent/agent_development.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ The first step in any A2UI-enabled agent is initializing the

```python
from a2ui.schema.constants import VERSION_0_9
from a2ui.schema.manager import A2uiSchemaManager, CatalogConfig
from a2ui.strategies.schema import A2uiSchemaManager
from a2ui.schema.catalog import CatalogConfig
from a2ui.basic_catalog.provider import BasicCatalog

# Define your catalogs (basic or bring your own) with optional examples
Expand Down
67 changes: 56 additions & 11 deletions agent_sdks/python/a2ui_agent/src/a2ui/a2a/parts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@

import logging
from typing import Any, Optional, List, AsyncIterable, TYPE_CHECKING
from a2ui.parser.parser import Parser

if TYPE_CHECKING:
from a2ui.parser.streaming import A2uiStreamParser
from a2ui.inference_formats.transport.streaming import TransportStreamParser
from a2a.types import (
Part,
DataPart,
Expand Down Expand Up @@ -85,36 +86,80 @@ def get_a2ui_datapart(part: Part) -> Optional[DataPart]:
return None


def parse_response_to_parts(
def parse_content_to_parts(
content: str,
validator: Optional[Any] = None,
parser: Parser,
fallback_text: Optional[str] = None,
version: Optional[str] = None,
) -> List[Part]:
"""Helper to parse LLM response content into A2A Parts, with optional validation.
"""Helper to parse LLM response content into A2A Parts using a Parser instance.

Args:
content: The LLM response content, potentially containing A2UI delimiters.
validator: Optional validator to run against extracted JSON payloads.
parser: The Parser instance used to extract and compile format parts.
fallback_text: Optional text to return if no parts are successfully created.
version: Optional version string.

Returns:
A list of A2A Part objects (TextPart and/or DataPart).
"""
from a2ui.parser.parser import parse_response
parts = []
try:
response_parts = parser.parse_response(content)

for part in response_parts:
if part.text:
parts.append(Part(root=TextPart(text=part.text)))

if part.a2ui_json:
json_data = part.a2ui_json
if isinstance(json_data, list):
for message in json_data:
parts.append(create_a2ui_part(message, version=version))
else:
parts.append(create_a2ui_part(json_data, version=version))

except Exception as e:
logger.warning(f"Failed to parse A2UI response: {e}")

if not parts and fallback_text:
parts.append(Part(root=TextPart(text=fallback_text)))

return parts


def parse_response_to_parts(
content: str,
validator: Optional[Any] = None,
fallback_text: Optional[str] = None,
version: Optional[str] = None,
) -> List[Part]:
"""Deprecated compatibility wrapper around parse_response_to_parts.

Please use parse_content_to_parts instead, providing a Parser instance.
"""
import warnings

warnings.warn(
"parse_response_to_parts is deprecated. Please use parse_content_to_parts(...) "
"providing a Parser instance instead.",
DeprecationWarning,
stacklevel=2,
)

from a2ui.parser.parser import parse_response as legacy_parse_response

parts = []
try:
response_parts = parse_response(content)
response_parts = legacy_parse_response(content)

for part in response_parts:
if part.text:
parts.append(Part(root=TextPart(text=part.text)))

if part.a2ui_json:
json_data = part.a2ui_json
if validator:
if validator is not None:
validator.validate(json_data)

if isinstance(json_data, list):
Expand All @@ -124,7 +169,7 @@ def parse_response_to_parts(
parts.append(create_a2ui_part(json_data, version=version))

except Exception as e:
logger.warning(f"Failed to parse or validate A2UI response: {e}")
logger.warning(f"Failed to parse legacy A2UI response: {e}")

if not parts and fallback_text:
parts.append(Part(root=TextPart(text=fallback_text)))
Expand All @@ -133,14 +178,14 @@ def parse_response_to_parts(


async def stream_response_to_parts(
parser: "A2uiStreamParser",
parser: "TransportStreamParser",
token_stream: AsyncIterable[str],
version: Optional[str] = None,
) -> AsyncIterable[Part]:
"""Helper to parse a stream of LLM tokens into A2A Parts incrementally.

Args:
parser: A2uiStreamParser instance to process the stream.
parser: TransportStreamParser instance to process the stream.
token_stream: An async iterable of strings (tokens).
version: Optional version string.

Expand Down
21 changes: 13 additions & 8 deletions agent_sdks/python/a2ui_agent/src/a2ui/adk/a2a/part_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@


from a2a import types as a2a_types
from a2ui.a2a.parts import create_a2ui_part, parse_response_to_parts
from a2ui.parser.parser import has_a2ui_parts
from a2ui.a2a.parts import create_a2ui_part, parse_content_to_parts
from a2ui.parser.parser import Parser
from a2ui.inference_formats.transport.parser import TransportParser
from a2ui.schema import constants
from a2ui.schema.catalog import A2uiCatalog
from google.adk.a2a.converters import part_converter
Expand Down Expand Up @@ -69,11 +70,15 @@ def __init__(
bypass_tool_check: bool = False,
fallback_text: Optional[str] = None,
version: str = constants.VERSION_0_8,
parser: Optional[Parser] = None,
):
self._catalog = a2ui_catalog
self._bypass_tool_check = bypass_tool_check
self._fallback_text = fallback_text
self._version = version
self._parser = parser or TransportParser(
a2ui_catalog, validator=a2ui_catalog.validator
)

def convert(self, part: genai_types.Part) -> list[a2a_types.Part]:
"""Converts a GenAI part to A2A parts, with A2UI validation.
Expand Down Expand Up @@ -117,10 +122,10 @@ def convert(self, part: genai_types.Part) -> list[a2a_types.Part]:

if function_response.response:
result = function_response.response.get("result")
if isinstance(result, str) and has_a2ui_parts(result):
return parse_response_to_parts(
if isinstance(result, str) and self._parser.has_format_content(result):
return parse_content_to_parts(
result,
validator=self._catalog.validator,
parser=self._parser,
fallback_text=self._fallback_text,
version=self._version,
)
Expand All @@ -133,10 +138,10 @@ def convert(self, part: genai_types.Part) -> list[a2a_types.Part]:

# 3. Handle Text-based A2UI (TextPart)
if text := part.text:
if has_a2ui_parts(text):
return parse_response_to_parts(
if self._parser.has_format_content(text):
return parse_content_to_parts(
text,
validator=self._catalog.validator,
parser=self._parser,
fallback_text=self._fallback_text,
version=self._version,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,9 @@
A2UI_DELETE_SURFACE_KEY,
A2UI_ACTIONS_KEY,
A2UI_ERROR_KEY,
A2UI_CLIENT_DATA_MODEL_KEY,
A2UI_CLIENT_DATA_MODEL_SURFACES_KEY,
)
from a2ui.a2a.parts import is_a2ui_part
from a2a.server.events import Event as A2AEvent
from a2a.types import Part, DataPart


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,26 +96,18 @@ async def get_examples(ctx: ReadonlyContext) -> str:
from google.adk.utils.feature_decorator import experimental
from google.genai import types as genai_types

from a2ui.adk.a2a.event_converter import A2uiEventConverter
from a2ui.adk.a2a.part_converter import A2uiPartConverter
from a2ui.parser.payload_fixer import parse_and_fix
from a2ui.schema import catalog
from a2ui.core import A2uiValidationError

from a2ui.schema import constants
from a2ui.schema.catalog import A2uiCatalog
from a2ui.schema.constants import (
A2UI_SCHEMA_BLOCK_END,
A2UI_SCHEMA_BLOCK_START,
A2UI_TOOL_ERROR_KEY,
A2UI_TOOL_NAME,
A2UI_VALIDATED_JSON_KEY,
)

if TYPE_CHECKING:
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events.event import Event

logger = logging.getLogger(__name__)

A2uiEnabledProvider: TypeAlias = Callable[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ def _compile_value(self, val: Any, is_action: bool = False) -> Any:
v, is_action
)

fn_schema = self.helper.functions[fn_name]
self.helper.functions[fn_name]

if is_action:
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import html
import json
import re
from typing import Any, Dict, List, Optional, Union
from typing import Any, Optional, Union
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog
from a2ui.experimental.express.schema_helper import CatalogSchemaHelper
Expand Down Expand Up @@ -254,7 +254,6 @@ def _render_component(
if k not in ["id", "component"] and k not in all_props:
all_props.append(k)

has_text_content = False
text_content = ""

for prop_name in all_props:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
including array literals, object literals, and path bindings prefixed with '$'.
"""

from typing import Any, Dict, List, Union
from typing import Any, Dict, List
from a2ui.core.basic_catalog.expression_parser import ExpressionParser, Scanner


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""Parser utilities to extract and compile A2UI Elemental HTML from LLM responses."""

import re
from typing import Any, Dict, List, Optional, Union
from typing import Any, List, Union
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog
from a2ui.parser.response_part import ResponsePart
Expand Down Expand Up @@ -94,7 +94,7 @@ def parse_elemental_response(
a2ui_json=[compiled_json],
)
)
except Exception as e:
except Exception:
# Graceful fallback: treat malformed/unparseable blocks as plain text
fallback_text = html_content
full_text = f"{text_part}\n{fallback_text}" if text_part else fallback_text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import json
import re
from typing import Any, Dict, List, Optional, Union
from typing import Any, Union
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog
from a2ui.experimental.express.schema_helper import CatalogSchemaHelper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@
into standard A2UI v1.0 wire JSON messages and vice-versa.
"""

import os

if os.environ.get("A2UI_EXPRESS_ENABLED", "").lower() not in ("true", "1", "yes"):
raise ImportError(
"A2UI Express is an experimental proposal extension and is disabled by default."
" To enable it, set the environment variable A2UI_EXPRESS_ENABLED=true."
)

from .compiler import ExpressCompiler
from .decompiler import ExpressDecompiler
from .prompt_generator import ExpressPromptGenerator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
The grammar for A2UI Express is defined in Express.g4.
"""

import re
from typing import Any, Dict, Optional, Union
from typing import Any, Optional, Union
from antlr4 import InputStream, CommonTokenStream
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
tailored for prompt tokens compression.
"""

from typing import Any, Dict, Optional, Union
from typing import Any, Union
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog
from .schema_helper import CatalogSchemaHelper
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Generated from Express.g4 by ANTLR 4.13.2
from antlr4 import *
from io import StringIO
import sys
if sys.version_info[1] > 5:
from typing import TextIO
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Generated from Express.g4 by ANTLR 4.13.2
# encoding: utf-8
from antlr4 import *
from io import StringIO
import sys
if sys.version_info[1] > 5:
from typing import TextIO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""Parser utilities to extract and compile A2UI Express DSL from LLM responses."""

import re
from typing import Any, Dict, List, Optional, Union
from typing import Any, List, Union
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog
from a2ui.parser.response_part import ResponsePart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import json
import re
from typing import Any, Dict, Optional, Union
from typing import Any, Optional, Union
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog
from .decompiler import ExpressDecompiler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
signatures, and requirements directly from standard catalog JSON schemas.
"""

import json
from typing import Any, Dict, Optional, Union
from typing import Any, Optional, Union
from a2ui.core.catalog import Catalog
from a2ui.schema.catalog import A2uiCatalog

Expand Down
Loading
Loading