feat(c-a2ui): LLM-driven aviation booking form (PR 4 of 4)#380
Merged
Conversation
Final PR of the c-* aviation rollout. Replaces the hardcoded contact form with an LLM-authored booking form that emits valid A2UI JSONL via .with_structured_output() + Pydantic schemas. Post-submit emits a SECOND LLM-authored surface listing matching flights from find_routes(). Retry policy: 2 retries on validation failure, then hardcoded sentinel fallback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
11 tasks: Pydantic schemas + envelope helper, LLM with structured output + retry, build_form / search_flights / route nodes, 3-node graph compile, real-LLM smoke, standalone mirror with inlined flight fixtures, build verification, REQUIRED iterative chrome MCP smoke, PR open. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ported by strict mode)
The chat-lib's unwrapComponentDef() expects {id, component: {<Name>:
{<props>}}} (single-key inner dict) — NOT my prior flat {id, component:
"Name", ...props}. Unknown component types silently fall through to a
stub Text and render nothing visible.
Also corrected:
- ChoicePicker → MultipleChoice (catalog name)
- NumberField → TextField with textFieldType="number"
- DatePicker → TextField with textFieldType="date"
- Card takes single `child` (not children list) — wrap multi-field forms
in a Card→Column→[fields...]
- Button needs `child` pointing at a Text component (label) + action
- children format is {explicitList: [...]}
- MultipleChoice uses `selections` (plural) + options=[{label,value}]
Updated:
- A2uiComponent schema → component: dict[str, dict[str, Any]] with
field_validator enforcing single key from ALLOWED_COMPONENTS frozenset
- Build_form / search_flights system prompts: complete v0.9 component
cheatsheet + exact id-tree spec + per-component shape examples
- Both sentinel forms rewritten in nested format
Mirrored to standalone (cockpit/chat/a2ui/python/src/graph.py).
Verified end-to-end: build_form produces 11 components in nested format;
search_flights for LAX→JFK produces 8 components including UA123 flight
Card + Select Button.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…odelUpdate/beginRendering)
…in submit context
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
a2ui_graph.pywith an LLM-authored aviation booking form, plus an LLM-authored post-submit flight-results surface.A2uiComponent+BookingFormSpec/FlightResultsSpec) via.with_structured_output(..., method="function_calling"). On validation failure: 2 retries with the error re-injected, then a hardcoded sentinel form so the demo doesn't crash.Architecture
route → {build_form | search_flights} → END.build_formruns on first turn;search_flightsruns when the last message is a v0.9A2uiActionMessagewithaction.name == "bookingSubmit"and callsfind_routes()(PR 1'saviation_tools) before authoring the results surface.LLM:
gpt-5withreasoning_effort="low"(matches PR #372's tool-discipline pattern).Iterative fixes during live chrome MCP verification
The plan was right on architecture but needed four progressive fixes to get end-to-end rendering, all discovered via chrome MCP smoke + reading
libs/chat/src/lib/a2ui/source:dict[str, Any]→ switched tomethod="function_calling"(more flexible schema mode that tolerates open dicts)component: "TextField"was wrong → A2UI v0.9 uses nestedcomponent: {TextField: {props}}. Validator now enforces a single key from the catalog.surfaceUpdate/dataModelUpdate/beginRendering, not mycreateSurface/updateComponents. Data model is also a typed entry list[{key, valueString|valueNumber|valueBoolean}], not a plain dict.beginRenderingneeds the root component id.{version:"v0.9", action:{name, surfaceId, context:{...}}}, not my{type:"a2ui_event", context:{...}}. Also: Button's action context now includes{key, value:{path:"/origin"}}entries so the agent receives the user's form data resolved at click time (not justformId).Test plan
pnpm nx run cockpit-langgraph-streaming-python:build— greenpnpm nx run cockpit-chat-a2ui-angular:build— greenbuild_form→ 3 envelopes (dataModelUpdate/surfaceUpdate/beginRendering), 11 components covering Column/Card/Text/TextField/MultipleChoice/Buttonsearch_flightsfor LAX→JFK → 3 envelopes, 8+ components, includes UA123 flight Card + Select Buttonsearch_flightsruns, results surface mounts. With empty origin/dest in the submit context: empty "No flights found / Modify search" Card. With explicit LAX→JFK submit (via curl): UA123 + Select Button card.Files
cockpit/langgraph/streaming/python/src/a2ui_graph.py— full rewrite (3 nodes, Pydantic schemas, retry,find_routesintegration, v0.9 envelope wrapping)cockpit/chat/a2ui/python/src/graph.py— mirror with inlined flight fixtures🤖 Generated with Claude Code