You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
12 KiB
Markdown
245 lines
12 KiB
Markdown
---
|
|
title: "Chapter 10: A2UI Protocol (Streaming UI Components)"
|
|
---
|
|
|
|
The goal of this chapter is to implement the A2UI protocol, rendering Agent output as streaming UI components.
|
|
|
|
## Important Note: The Scope of A2UI
|
|
|
|
A2UI is not part of the Eino framework itself — it is a business-layer UI protocol/rendering solution. This chapter integrates A2UI into the Agent built step by step in previous chapters, in order to provide a complete end-to-end, production-ready example: from model calls, tool calls, and workflow orchestration, all the way to presenting results in a more user-friendly UI format.
|
|
|
|
In real business scenarios, you are free to choose different UI forms based on your product needs, such as:
|
|
|
|
- Web / App: Custom components, tables, cards, charts, etc.
|
|
- IM/office suites: Message cards, interactive forms
|
|
- Command line: Plain text or TUI (Terminal UI)
|
|
|
|
Eino focuses on "composable intelligent execution and orchestration capabilities". As for "how to present it to users", that's a business-layer concern that can be freely extended.
|
|
|
|
## Code Location
|
|
|
|
- Entry code: [main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/main.go)
|
|
- Agent construction: [agent.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/agent.go)
|
|
- Server routing: [server/server.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/server/server.go)
|
|
- A2UI subset implementation: [a2ui/types.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/types.go)
|
|
- A2UI event stream conversion: [a2ui/streamer.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/streamer.go)
|
|
- Frontend page: [static/index.html](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/static/index.html)
|
|
|
|
## Prerequisites
|
|
|
|
Same as Chapter 1: you need to configure an available ChatModel (OpenAI or Ark).
|
|
|
|
## Running
|
|
|
|
In the `quickstart/chatwitheino` directory, run:
|
|
|
|
```bash
|
|
go run .
|
|
```
|
|
|
|
Output example:
|
|
|
|
```text
|
|
starting server on http://localhost:8080
|
|
```
|
|
|
|
### (Optional) Enable Chapter 9 Skills
|
|
|
|
The final Web version uses Agent construction logic aligned with Chapter 9: when `EINO_EXT_SKILLS_DIR` points to a valid skills directory, the `skill` middleware is automatically registered, enabling the model to call the `skill` tool on demand to load `eino-guide` / `eino-component` / `eino-compose` / `eino-agent`.
|
|
|
|
```bash
|
|
go run ./scripts/sync_eino_ext_skills.go -src /path/to/eino-ext -dest ./skills/eino-ext -clean
|
|
EINO_EXT_SKILLS_DIR="$(pwd)/skills/eino-ext" go run .
|
|
```
|
|
|
|
## From Text to UI: Why We Need A2UI
|
|
|
|
In the first eight chapters, the Agent only output text, but modern AI applications need richer interactions.
|
|
|
|
**Limitations of plain text output:**
|
|
- Cannot display structured data (tables, lists, cards, etc.)
|
|
- Cannot update in real time (progress bars, status changes, etc.)
|
|
- Cannot embed interactive elements (buttons, forms, links, etc.)
|
|
- Cannot support multimedia (images, video, audio, etc.)
|
|
|
|
**The role of A2UI:**
|
|
- **A2UI is the Agent-to-UI protocol**: Defines how Agent output maps to UI components
|
|
- **A2UI supports streaming rendering**: Components can update in real time without waiting for the complete response
|
|
- **A2UI is declarative**: The Agent only needs to declare "what to display", and the UI handles rendering
|
|
|
|
**Simple analogy:**
|
|
- **Plain text output** = "terminal command line" (can only display text)
|
|
- **A2UI** = "web application" (can display any UI component)
|
|
|
|
## Key Concepts
|
|
|
|
### A2UI v0.8 Subset (Scope of This Example)
|
|
|
|
This quickstart does not implement a "complete A2UI standard library". Instead, it implements a **subset of A2UI v0.8**: the goal is to push Agent event streams to the browser as a stable, incrementally renderable UI component tree.
|
|
|
|
The currently implemented A2UI message types and component types are defined in [a2ui/types.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/types.go).
|
|
|
|
### A2UI Messages: BeginRendering / SurfaceUpdate / DataModelUpdate / InterruptRequest
|
|
|
|
Each SSE line (`data: {...}`) carries one A2UI Message. A Message is an "envelope structure" where only one field is present at a time:
|
|
|
|
**Key code snippet (Note: this is a simplified code snippet that cannot be run directly. For the complete code, please refer to** [a2ui/types.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/types.go)**)**:
|
|
|
|
```go
|
|
type Message struct {
|
|
BeginRendering *BeginRenderingMsg
|
|
SurfaceUpdate *SurfaceUpdateMsg
|
|
DataModelUpdate *DataModelUpdateMsg
|
|
DeleteSurface *DeleteSurfaceMsg
|
|
InterruptRequest *InterruptRequestMsg
|
|
}
|
|
```
|
|
|
|
Where:
|
|
|
|
- `BeginRendering`: Tells the frontend to "start rendering a surface (session)" and specifies the root node ID
|
|
- `SurfaceUpdate`: Adds/updates a batch of components (components form a tree, referencing each other by `id`)
|
|
- `DataModelUpdate`: Updates data bindings (used to incrementally update streaming text to a Text component)
|
|
- `InterruptRequest`: When the Agent triggers an interrupt (e.g., approval), notifies the frontend to display an approve/reject entry
|
|
|
|
### A2UI Components: Text / Column / Card / Row
|
|
|
|
This example implements only 4 UI component types (see [a2ui/types.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/types.go)):
|
|
|
|
- `Text`: Text rendering (supports `usageHint` to distinguish caption/body/title); when `dataKey` is present, text comes from `DataModelUpdate`
|
|
- `Column` / `Row`: Layout (children are lists of component IDs)
|
|
- `Card`: Card container (children are lists of component IDs)
|
|
|
|
## A2UI Implementation: Converting AgentEvent to A2UI SSE
|
|
|
|
The core pipeline of the final Web version is:
|
|
|
|
- The backend runs the Agent, producing `*adk.AsyncIterator[*adk.AgentEvent]`
|
|
- The event stream is converted to A2UI JSONL/SSE stream output for the browser (see [a2ui/streamer.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/a2ui/streamer.go))
|
|
- The frontend parses `data:` lines from SSE and renders the component tree (see [static/index.html](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/static/index.html))
|
|
|
|
### Server Routes (High Level)
|
|
|
|
Key endpoints related to A2UI (see [server/server.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/server/server.go)):
|
|
|
|
- `GET /`: Returns the frontend page `static/index.html`
|
|
- `POST /sessions/:id/chat`: Returns an SSE stream (A2UI messages), rendering Agent results to the UI as they're generated
|
|
- `GET /sessions/:id/render`: Returns JSONL (A2UI messages), used for "replaying history when selecting a session"
|
|
- `POST /sessions/:id/approve`: Handles interrupt approval/rejection and continues returning the SSE stream
|
|
|
|
### Event Stream Conversion (High Level)
|
|
|
|
The server passes the `Runner.Run(...)` event stream to `a2ui.StreamToWriter(...)`, which handles:
|
|
|
|
- Splitting user/assistant/tool output
|
|
- Rendering tool calls / tool results as "chip cards"
|
|
- Converting the assistant's streaming tokens into `DataModelUpdate`, enabling "render as you generate"
|
|
- Sending `InterruptRequest` when an interrupt is encountered, and pausing to wait for human approval
|
|
|
|
## Frontend Integration: fetch + SSE (Not WebSocket)
|
|
|
|
- The frontend initiates a request via `fetch('/sessions/:id/chat')`, then reads streaming bytes from `res.body`, splits by line, and parses the JSON from `data: {...}` lines (see [static/index.html](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/static/index.html)).
|
|
|
|
**Key code snippet (Note: this is a simplified code snippet that cannot be run directly. For the complete code, please refer to** [static/index.html](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/static/index.html)**)**:
|
|
|
|
```javascript
|
|
const res = await fetch(`/sessions/${id}/chat`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({message}),
|
|
});
|
|
|
|
const reader = res.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = '';
|
|
while (true) {
|
|
const {done, value} = await reader.read();
|
|
if (done) break;
|
|
buffer += decoder.decode(value, {stream: true});
|
|
const lines = buffer.split('\n');
|
|
buffer = lines.pop();
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (trimmed.startsWith('data:')) {
|
|
const jsonStr = trimmed.slice(5).trimStart();
|
|
processA2UIMessage(JSON.parse(jsonStr));
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## A2UI Streaming Rendering Flow (Overview)
|
|
|
|
```
|
|
+------------------------------------------+
|
|
| User: Analyze this file |
|
|
+------------------------------------------+
|
|
|
|
|
+------------------------+
|
|
| Agent starts |
|
|
| processing |
|
|
| A2UI: AddText |
|
|
| "Analyzing..." |
|
|
+------------------------+
|
|
|
|
|
+------------------------+
|
|
| Call Tool |
|
|
| A2UI: AddProgress |
|
|
| Progress: 0% |
|
|
+------------------------+
|
|
|
|
|
+------------------------+
|
|
| Tool executing |
|
|
| A2UI: UpdateProgress |
|
|
| Progress: 50% |
|
|
+------------------------+
|
|
|
|
|
+------------------------+
|
|
| Tool complete |
|
|
| A2UI: tool result |
|
|
+------------------------+
|
|
|
|
|
+------------------------+
|
|
| Display result |
|
|
| A2UI: DataModelUpdate |
|
|
| (streaming assistant |
|
|
| update) |
|
|
+------------------------+
|
|
```
|
|
|
|
## Chapter Summary
|
|
|
|
- **A2UI**: The Agent-to-UI protocol, defining how Agent output maps to UI components
|
|
- **Subset implementation**: This example only implements Text/Column/Card/Row and data binding
|
|
- **Streaming output**: The backend pushes A2UI JSONL via SSE, and the frontend incrementally renders the component tree
|
|
- **Events to UI**: Converts `AgentEvent` into visualized output for tool calls / tool results / assistant streams
|
|
|
|
## Series Conclusion: The Complete Vision for This Quickstart Agent
|
|
|
|
By this chapter, we've used a fully runnable Agent to tie together Eino's core capabilities. You can think of it as an extensible "end-to-end Agent application skeleton":
|
|
|
|
- **Runtime**: Runner drives execution, with support for streaming output and event models
|
|
- **Tool layer**: Filesystem / Shell and other Tool capabilities, with safe tool error handling
|
|
- **Middleware**: Pluggable middleware/handlers for cross-cutting concerns like error handling, retries, approvals, etc.
|
|
- **Observability**: Callbacks/trace capabilities connect key call chains, facilitating debugging and production monitoring
|
|
- **Human-agent collaboration**: Interrupt/resume + checkpoint support for approvals, parameter completion, branch selection, and other interactive flows
|
|
- **Deterministic orchestration**: compose (graph/chain/workflow) organizes complex business flows into maintainable, reusable execution graphs
|
|
- **Business delivery**: UI integrations like A2UI are a business-layer choice, used to present Agent capabilities in the appropriate product form to users
|
|
|
|
You can gradually replace/extend any part of this skeleton — model, tools, storage, workflows, frontend rendering protocol — without starting from scratch.
|
|
|
|
## Further Thinking
|
|
|
|
**Other component types:**
|
|
- Chart components (line charts, bar charts, pie charts)
|
|
- Map components
|
|
- Timeline components
|
|
- Tree components
|
|
- Tab components
|
|
|
|
**Advanced features:**
|
|
- Component interaction (click, drag, input)
|
|
- Conditional rendering
|
|
- Component animations
|
|
- Responsive layout
|