diff --git a/cockpit/chat/a2ui/angular/src/environments/environment.development.ts b/cockpit/chat/a2ui/angular/src/environments/environment.development.ts index f5c053266..d324f197b 100644 --- a/cockpit/chat/a2ui/angular/src/environments/environment.development.ts +++ b/cockpit/chat/a2ui/angular/src/environments/environment.development.ts @@ -1,5 +1,5 @@ export const environment = { production: false, langGraphApiUrl: 'http://localhost:4511/api', - a2uiAssistantId: 'c-a2ui', + a2uiAssistantId: 'a2ui_form', }; diff --git a/cockpit/chat/a2ui/angular/src/environments/environment.ts b/cockpit/chat/a2ui/angular/src/environments/environment.ts index 2919bf6d0..f23ee1ad2 100644 --- a/cockpit/chat/a2ui/angular/src/environments/environment.ts +++ b/cockpit/chat/a2ui/angular/src/environments/environment.ts @@ -1,5 +1,5 @@ export const environment = { production: true, langGraphApiUrl: '/api', - a2uiAssistantId: 'c-a2ui', + a2uiAssistantId: 'a2ui_form', }; diff --git a/cockpit/langgraph/streaming/python/src/a2ui_graph.py b/cockpit/langgraph/streaming/python/src/a2ui_graph.py index f66894a8e..2d3b0616c 100644 --- a/cockpit/langgraph/streaming/python/src/a2ui_graph.py +++ b/cockpit/langgraph/streaming/python/src/a2ui_graph.py @@ -1,8 +1,13 @@ """ A2UI Contact Form Graph -Demonstrates the A2UI (Agent-to-UI) protocol by streaming JSONL that -builds an interactive contact form on the Angular frontend. +Demonstrates the A2UI (Agent-to-UI) protocol by emitting hardcoded JSONL +that builds an interactive contact form on the Angular frontend. +Uses the v0.9 envelope format: {"createSurface": {...}}. + +The graph does NOT use an LLM for UI generation — A2UI JSONL requires +exact format adherence that LLMs cannot reliably provide. The LLM is +only used for conversational responses to form submission events. """ import json @@ -11,83 +16,83 @@ A2UI_PREFIX = "---a2ui_JSON---" +# v0.9 envelope format: each message is {"": {}} CONTACT_FORM_JSONL = A2UI_PREFIX + "\n" + "\n".join([ - json.dumps({"type": "createSurface", "surfaceId": "contact", "catalogId": "basic"}), - json.dumps({"type": "updateDataModel", "surfaceId": "contact", "value": { - "name": "", "email": "", "department": "Engineering", "consent": False, + json.dumps({"createSurface": { + "surfaceId": "contact", "catalogId": "basic", "sendDataModel": True, + }}), + json.dumps({"updateDataModel": { + "surfaceId": "contact", + "value": {"name": "", "email": "", "department": "Engineering", "consent": False}, + }}), + json.dumps({"updateComponents": { + "surfaceId": "contact", + "components": [ + {"id": "root", "component": "Column", "children": ["card"]}, + {"id": "card", "component": "Card", "title": "Contact Us", "children": [ + "name_field", "email_field", "dept_picker", "consent_check", "divider", "submit_btn", + ]}, + {"id": "name_field", "component": "TextField", + "label": "Name", "value": {"path": "/name"}, "placeholder": "Your full name", + "checks": [ + {"condition": {"call": "required", "args": {"value": {"path": "/name"}}}, + "message": "Name is required"}, + ]}, + {"id": "email_field", "component": "TextField", + "label": "Email", "value": {"path": "/email"}, "placeholder": "you@company.com", + "checks": [ + {"condition": {"call": "required", "args": {"value": {"path": "/email"}}}, + "message": "Email is required"}, + {"condition": {"call": "email", "args": {"value": {"path": "/email"}}}, + "message": "Must be a valid email address"}, + ]}, + {"id": "dept_picker", "component": "ChoicePicker", + "label": "Department", + "options": ["Engineering", "Sales", "Support", "Marketing"], + "selected": {"path": "/department"}}, + {"id": "consent_check", "component": "CheckBox", + "label": "I agree to be contacted", "checked": {"path": "/consent"}}, + {"id": "divider", "component": "Divider"}, + {"id": "submit_btn", "component": "Button", + "label": "Submit", + "checks": [ + {"condition": {"call": "and", "args": {"values": [ + {"call": "required", "args": {"value": {"path": "/name"}}}, + {"call": "email", "args": {"value": {"path": "/email"}}}, + {"path": "/consent"}, + ]}}, + "message": "Complete all required fields and agree to be contacted"}, + ], + "action": {"event": {"name": "formSubmit", "context": {"formId": "contact"}}}}, + ], }}), - json.dumps({"type": "updateComponents", "surfaceId": "contact", "components": [ - {"id": "root", "component": "Column", "children": ["card"]}, - {"id": "card", "component": "Card", "title": "Contact Us", "children": [ - "name_field", "email_field", "dept_picker", "consent_check", "divider", "submit_btn", - ]}, - {"id": "name_field", "component": "TextField", - "label": "Name", "value": {"path": "/name"}, "placeholder": "Your full name", - "_bindings": {"value": "/name"}, - "checks": [ - {"condition": {"call": "required", "args": {"value": {"path": "/name"}}}, - "message": "Name is required"}, - ]}, - {"id": "email_field", "component": "TextField", - "label": "Email", "value": {"path": "/email"}, "placeholder": "you@company.com", - "_bindings": {"value": "/email"}, - "checks": [ - {"condition": {"call": "required", "args": {"value": {"path": "/email"}}}, - "message": "Email is required"}, - {"condition": {"call": "email", "args": {"value": {"path": "/email"}}}, - "message": "Must be a valid email address"}, - ]}, - {"id": "dept_picker", "component": "ChoicePicker", - "label": "Department", - "options": ["Engineering", "Sales", "Support", "Marketing"], - "selected": {"path": "/department"}, - "_bindings": {"selected": "/department"}}, - {"id": "consent_check", "component": "CheckBox", - "label": "I agree to be contacted", "checked": {"path": "/consent"}, - "_bindings": {"checked": "/consent"}}, - {"id": "divider", "component": "Divider"}, - {"id": "submit_btn", "component": "Button", - "label": "Submit", - "checks": [ - {"condition": {"call": "and", "args": {"values": [ - {"call": "required", "args": {"value": {"path": "/name"}}}, - {"call": "email", "args": {"value": {"path": "/email"}}}, - {"path": "/consent"}, - ]}}, - "message": "Complete all required fields and agree to be contacted"}, - ], - "action": {"event": {"name": "formSubmit", "context": {"formId": "contact"}}}}, - ]}), ]) def build_a2ui_graph(): """ - Two-node graph: - - create_form: emits the A2UI contact form surface - - handle_event: responds to form submission events + Single-node graph: + - On first message: emits hardcoded A2UI JSONL for the contact form + - On a2ui_event messages: responds with a confirmation message """ async def create_form(state: MessagesState) -> dict: last = state["messages"][-1] - # If this is an a2ui_event, route to event handling + # Check if this is a form submission event from the A2UI surface try: payload = json.loads(last.content) if isinstance(payload, dict) and payload.get("type") == "a2ui_event": - return await handle_event(state, payload) + name = payload.get("context", {}).get("formId", "unknown") + return {"messages": [AIMessage( + content=f"Thanks for submitting the **{name}** form! We'll be in touch soon.", + )]} except (json.JSONDecodeError, AttributeError): pass - # First message — emit the contact form + # Any other message — emit the contact form return {"messages": [AIMessage(content=CONTACT_FORM_JSONL)]} - async def handle_event(state: MessagesState, payload: dict) -> dict: - name = payload.get("context", {}).get("formId", "unknown") - return {"messages": [AIMessage( - content=f"Thanks for submitting the **{name}** form! We'll be in touch soon.", - )]} - graph = StateGraph(MessagesState) graph.add_node("create_form", create_form) graph.set_entry_point("create_form") diff --git a/scripts/examples-middleware.ts b/scripts/examples-middleware.ts index dff0a9fd8..7a70ac4ae 100644 --- a/scripts/examples-middleware.ts +++ b/scripts/examples-middleware.ts @@ -60,18 +60,19 @@ const PATH_TO_KEY: Record = { 'deep-agents/memory': 'da-memory', 'deep-agents/skills': 'skills', 'deep-agents/sandboxes': 'sandboxes', - // Chat capabilities - 'chat/a2ui': 'c-a2ui', - 'chat/debug': 'c-debug', - 'chat/generative-ui': 'c-generative-ui', - 'chat/input': 'c-input', - 'chat/interrupts': 'c-interrupts', - 'chat/messages': 'c-messages', - 'chat/subagents': 'c-subagents', - 'chat/theming': 'c-theming', - 'chat/threads': 'c-threads', - 'chat/timeline': 'c-timeline', - 'chat/tool-calls': 'c-tool-calls', + // Chat capabilities — routed through the streaming deployment which has + // all chat graphs consolidated in its langgraph.json (PR #113). + 'chat/a2ui': 'streaming', + 'chat/debug': 'streaming', + 'chat/generative-ui': 'streaming', + 'chat/input': 'streaming', + 'chat/interrupts': 'streaming', + 'chat/messages': 'streaming', + 'chat/subagents': 'streaming', + 'chat/theming': 'streaming', + 'chat/threads': 'streaming', + 'chat/timeline': 'streaming', + 'chat/tool-calls': 'streaming', // Render capabilities 'render/computed-functions': 'r-computed-functions', 'render/element-rendering': 'r-element-rendering',