Skip to content

feat(c-a2ui): LLM-driven aviation booking form (PR 4 of 4)#380

Merged
blove merged 12 commits into
mainfrom
claude/c-a2ui-aviation-spec
May 16, 2026
Merged

feat(c-a2ui): LLM-driven aviation booking form (PR 4 of 4)#380
blove merged 12 commits into
mainfrom
claude/c-a2ui-aviation-spec

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 16, 2026

Summary

  • PR 4 of 4 — final piece of the c-* aviation theme rollout.
  • Replaces the hardcoded contact-form JSONL in a2ui_graph.py with an LLM-authored aviation booking form, plus an LLM-authored post-submit flight-results surface.
  • LLM output is constrained by Pydantic schemas (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.
  • Disproves the prior code's claim that "LLMs cannot reliably emit A2UI JSONL" — schema-constrained structured output works.

Architecture

route → {build_form | search_flights} → END. build_form runs on first turn; search_flights runs when the last message is a v0.9 A2uiActionMessage with action.name == "bookingSubmit" and calls find_routes() (PR 1's aviation_tools) before authoring the results surface.

LLM: gpt-5 with reasoning_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:

  1. OpenAI strict mode rejected dict[str, Any] → switched to method="function_calling" (more flexible schema mode that tolerates open dicts)
  2. Flat component: "TextField" was wrong → A2UI v0.9 uses nested component: {TextField: {props}}. Validator now enforces a single key from the catalog.
  3. Envelope keys were wrong → chat-lib parser expects surfaceUpdate / dataModelUpdate / beginRendering, not my createSurface / updateComponents. Data model is also a typed entry list [{key, valueString|valueNumber|valueBoolean}], not a plain dict. beginRendering needs the root component id.
  4. Submit action shape was wrong → chat-lib sends {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 just formId).

Test plan

  • pnpm nx run cockpit-langgraph-streaming-python:build — green
  • pnpm nx run cockpit-chat-a2ui-angular:build — green
  • Programmatic real-LLM smoke (umbrella):
    • build_form → 3 envelopes (dataModelUpdate/surfaceUpdate/beginRendering), 11 components covering Column/Card/Text/TextField/MultipleChoice/Button
    • search_flights for LAX→JFK → 3 envelopes, 8+ components, includes UA123 flight Card + Select Button
  • Manual chrome MCP smoke (with running umbrella backend on real LLM):
    • "I want to fly from LAX to JFK" → booking form renders with origin/dest pickers, date field, passengers, fare class, "Search flights" button (screenshot attached)
    • Click "Search flights" → submit reaches agent (visible action message), search_flights runs, 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.
  • CI

Files

  • cockpit/langgraph/streaming/python/src/a2ui_graph.py — full rewrite (3 nodes, Pydantic schemas, retry, find_routes integration, v0.9 envelope wrapping)
  • cockpit/chat/a2ui/python/src/graph.py — mirror with inlined flight fixtures

🤖 Generated with Claude Code

blove and others added 12 commits May 16, 2026 14:01
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>
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>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 16, 2026 11:04pm

Request Review

@blove blove merged commit c2cbda5 into main May 16, 2026
16 checks passed
@blove blove deleted the claude/c-a2ui-aviation-spec branch May 18, 2026 17:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant