a2ui-project / a2ui-project/a2ui
feat(python): Type-safe Python fluent builder API and AST deserialization architecture
- Vorherrschende Sprache
- TypeScript
- Sterne
- 16.4k
- Forks
- 1.3k
- Ø Merge
- 2 T. 13 Std.
- Gemergte PRs (30 T.)
- 134
Beschreibung
## Overview & Motivation
In A2UI, the wire protocol represents user interfaces as flat arrays of components where parent components reference children via string IDs. While this flat array structure is optimal for streaming transport, incremental patching, and client rendering engines, authoring it directly in Python (either by hand or through raw nested dictionaries) is cumbersome and error-prone.
This issue tracks the design and implementation of a type-safe **Python Fluent Builder API** for A2UI, built on top of **Pydantic v2**, with explicit architecture for supporting **future bidirectional AST deserialization**.
---
## Core Architectural Pillars
### 1. Fluent Authoring & Strict Validation with Pydantic v2
- **Strict typo detection at edit/runtime**: Component builder classes inherit from a Pydantic `BaseModel` configured with `extra="forbid"` and `validate_assignment=True`. Typos (e.g. `Button(lable="Save")`) trigger immediate validation errors in IDEs and during test execution.
- **Open enums for catalog evolution**: Enum properties are emitted as open unions (`Literal["primary", "secondary"] | str`), allowing clients and agents to accept newer variants introduced by upstream catalogs without failing validation.
- **Strongly-typed child slots**: Child slots are typed as `Slot` (`ComponentBuilderNode`) and `SlotList` (`Sequence[Slot]`), maintaining an object hierarchy rather than exposing raw string IDs to authors.
- **Deterministic ID allocation & flattening**: Calling `tree.to_components()` deterministically assigns scoped identifiers (`{prefix}__{comp}_{counter}`) while transforming the object hierarchy into wire-format component dictionaries.
### 2. Decoupling Component Trees from Surfaces
- **The Problem**: In client renderers, a `Surface` is a persistent rendering canvas identified by `surfaceId` that manages a live data model and event dispatching. In contrast, an agent-side builder constructs a component hierarchy (which may be a macro expansion, an incremental patch, or a new canvas). Conflating the two causes calling `.to_messages()` to unconditionally emit `createSurface`, resetting client state on incremental updates.
- **The Solution**:
- **`ComponentTree`**: Represents the in-memory hierarchy (root node, unlinked subtrees, metadata).
- **Explicit envelope helpers**:
- `card.to_components()`: Produces flat component dictionaries directly (ideal for macro expansion and direct embedding).
- `update_components(surface_id, root)`: Emits only `{"updateComponents": ...}` targeting an existing surface without resetting state.
- `create_surface(surface_id, root)`: Emits `{"createSurface": ...}` followed by `{"updateComponents": ...}` for brand-new layouts.
### 3. Forward Architecture: Future AST Deserialization (Phase 2)
The builder architecture is designed from the ground up to support bidirectional AST deserialization as an additive, non-breaking extension:
- **Single-pass contextual slot resolution**: Child `Slot` annotations will use Pydantic `WrapValidator` (`Annotated[ComponentBuilderNode, WrapValidator(_resolve_slot)]`). When deserializing wire JSON, the validator uses validation context (`info.context["components"]`) to resolve child IDs into concrete component instances in a single pass without intermediate dictionary munging.
- **Schema-aware slot resolution**: Distinguishes true child component slots from text literals whose value might happen to match a component ID.
- **Cycle & loop detection**: Tracks active branches (`info.context["_visited"]`) to guard against cyclic references or malformed wire payloads.
- **Catalog evolution & `UnknownComponent` fallback**: Unrecognized component types deserialize into an `UnknownComponent` node (`extra="allow"`), preserving all attributes and metadata during round-trips.
- **Strongly-typed unlinked subtrees (`tree.unlinked_roots`)**: If an unrecognized component acts as an intermediate container, its known children are preserved as typed models under `tree.unlinked_roots` rather than degrading into untyped dictionaries, allowing developers to inspect and mutate them with full IDE autocomplete.
---
## Phased Implementation Roadmap
- [x] **Phase 1: Pydantic foundation, fluent authoring, and serialization**
- Migrated `ComponentBuilderNode` and supporting types (`Action`, `DataBinding`, `FunctionCall`, etc.) to Pydantic v2.
- Implemented `ComponentTree`, `create_surface`, `update_components`, and `.to_components()`.
- Generated Pydantic builder models for the A2UI Basic Catalog (`a2ui.builder.catalogs.basic`).
- Implemented Dart CLI (`dart/a2ui_cli`) and TypeScript CLI (`@a2ui/cli`) to emit Pydantic builder modules from catalog JSON schemas.
- Created YAML conformance test suites for CLI code generation and macro execution.
- [ ] **Phase 2: Bidirectional AST Deserialization**
- Implement `WrapValidator` slot resolution.
- Implement `UnknownComponent` and catalog discriminated unions.
- Implement top-level `deserialize(payload) -> ComponentTree`.
- Add support for mutating and re-emitting deserialized trees and unlinked subtrees.
---
## Stacked Pull Requests
This architecture is implemented across a 5-layer stacked PR series:
1. **Layer 1: PR #2425 (`feat/cli-typescript-codegen`)** — `feat(python): python builder API base classes and basic catalog fluent builders` (Core Pydantic base classes, `ComponentTree`, envelope helpers, Basic Catalog builders)
2. **Layer 2: PR #2519 (`feat/python-agent-sdk-macros`)** — `feat(macros): add programmatic macro runtime and type coercion engine` (`@macro` decorator, runtime schema synthesis, `MacroParser`)
3. **Layer 3: PR #2520 (`feat/community-sample-macros-demo`)** — `feat(samples): add community macros demo server and interactive client` (End-to-end full-stack demo with FastAPI and React)
4. **Layer 4: PR #2521 (`feat/dart-cli-conformance`)** — `feat(cli): introduce Dart CLI code generator and conformance suite` (Dart CLI catalog analyzer, Python emitter, and YAML conformance harness)
5. **Layer 5: PR #2523 (`feat/typescript-cli-proposals`)** — `feat(cli,docs): add TypeScript CLI implementation and macro architecture proposals` (Architecture proposals and `@a2ui/cli` package)
---
## Attached Documentation
Detailed design proposals and developer guides from the repository are included in full in the comments below:
1. **Comment 1**: *Pydantic Models for A2UI Fluent Builders and AST Deserialization* (Full specification covering architecture, deserialization mechanics, trade-offs, and edge cases).
2. **Comment 2**: *A2UI Python Builder API Guide* (Developer-facing documentation and usage guide).
3. **Comment 3**: *Programmatic Macros and Type-Safe Catalog Builders* (Comprehensive proposal on code generation and the macro runtime).
Beitragsleitfaden
Bewertung
Dieses Issue wurde noch nicht bewertet.