跳转到内容

使用 Generative UI

当你希望 agent 把声明式 UI 组件发送给前端,而不是只返回文本时,使用本页。

Awaken 当前支持两种集成方式:

  • 通过 A2uiPluginrender_a2ui 工具发送 A2UI 消息,本页下方主要覆盖这条路径。
  • 通过 run_streaming_subagent() 流式输出子 agent 结果;awaken-ext-generative-uijson-renderopenui features 提供 JSON Render 和 OpenUI Lang 预设。
  • 已有可运行的 runtime
  • 对 A2UI:前端能够消费 A2UI 消息,并已注册组件目录(catalog)
  • 对 JSON Render 或 OpenUI Lang:前端能够消费流式 tool output,例如 ai-sdk-starteropenui-chat 示例
[dependencies]
awaken = { git = "https://github.com/AwakenWorks/awaken" }
tokio = { version = "1", features = ["full"] }
serde_json = "1"
  1. 注册 A2UI 插件:
use std::sync::Arc;
use awaken::engine::GenaiExecutor;
use awaken::ext_generative_ui::A2uiPlugin;
use awaken::registry_spec::ModelSpec;
use awaken::registry_spec::AgentSpec;
use awaken::{AgentRuntimeBuilder, Plugin};
let plugin = A2uiPlugin::with_catalog_id("my-catalog");
let mut agent_spec = AgentSpec::new("ui-agent")
.with_model_id("gpt-4o-mini")
.with_system_prompt("Render structured UI when visual output helps.")
.with_hook_filter("generative-ui");
agent_spec.plugin_ids.push("generative-ui".into());
let runtime = AgentRuntimeBuilder::new()
.with_provider("openai", Arc::new(GenaiExecutor::new()))
.with_model(ModelSpec::new("gpt-4o-mini", "openai", "gpt-4o-mini"))
.with_agent_spec(agent_spec)
.with_plugin("generative-ui", Arc::new(plugin) as Arc<dyn Plugin>)
.build()
.expect("failed to build runtime");

插件会注册一个 render_a2ui 工具,LLM 通过它把 A2UI 消息发给前端。 plugin_ids 负责加载插件,with_hook_filter("generative-ui") 在同一 agent 加载多个插件时保留 A2UI 的 prompt 注入 hook。

  1. 理解 A2UI v0.8 消息类型:
消息类型作用
surfaceUpdate定义或更新组件树
dataModelUpdate写入或更新数据模型
beginRendering指定根组件并开始渲染
deleteSurface删除 surface

新 surface 的常见顺序是:先发 surfaceUpdate,需要数据时再发 dataModelUpdate,最后用 beginRendering 指定 root。

  1. 定义组件树:
let message = serde_json::json!({
"surfaceUpdate": {
"surfaceId": "order-form-1",
"components": [
{
"id": "root",
"component": { "Card": { "child": "title" } }
},
{
"id": "title",
"component": {
"Text": { "text": { "literalString": "New Order" } }
}
}
]
}
});

组件列表是扁平的。每个组件都要有 idcomponentcomponent 必须是 只包含一个 v0.8 组件类型的对象,例如 { "Text": {...} }。父子关系通过组件 属性里的 childchildren.explicitList 指向其他组件 ID。

  1. 写入数据模型:
let message = serde_json::json!({
"dataModelUpdate": {
"surfaceId": "order-form-1",
"path": "/order",
"contents": [
{ "key": "customer", "valueString": "" },
{ "key": "quantity", "valueNumber": 1.0 }
]
}
});

contents 是 key/value 数组,支持 valueStringvalueNumbervalueBooleanvalueMap

  1. 开始渲染:
let message = serde_json::json!({
"beginRendering": {
"surfaceId": "order-form-1",
"root": "root"
}
});
  1. 删除 surface:
let message = serde_json::json!({
"deleteSurface": {
"surfaceId": "order-form-1"
}
});
  1. 一次 tool call 可以携带多条消息:

render_a2ui 接收 messages 数组,因此可以在一次调用中同时更新组件树并开始渲染:

let args = serde_json::json!({
"messages": [
{ "surfaceUpdate": {
"surfaceId": "s1",
"components": [
{ "id": "root", "component": { "Text": {
"text": { "literalString": "Hello" }
}}}
]
}},
{ "beginRendering": { "surfaceId": "s1", "root": "root" }}
]
});
  1. 自定义插件指令:
let plugin = A2uiPlugin::with_catalog_and_examples(
"my-catalog",
"Example: create a card with a title and a button..."
);
let plugin = A2uiPlugin::with_custom_instructions(
"You can render UI by calling render_a2ui...".to_string()
);
let agent_spec = agent_spec.with_section("generative-ui", serde_json::json!({
"catalog_id": "my-catalog",
"examples": "Example: render a compact order summary."
}));

generative-ui section 与 admin console 页面保存的是同一份配置,可覆盖 catalog_id、追加 examples,或用 instructions 完整替换默认指令。

  1. 注册插件后,给 agent 一个”请以可视化方式展示内容”的提示
  2. 确认 agent 调用了 render_a2ui
  3. AgentEvent::ToolCallDone 上的 tool result 只是一个小确认:{"rendered": true, "count": N}(N 是 LLM 这次提交的 A2UI 消息数量)
  4. 真正的 UI markup 在 tool call args 上,不是 result —— 前端从 AgentEvent::ToolCallReadyname = "render_a2ui"arguments = { ..A2UI 消息.. })里读出来渲染
  5. 前端上应看到对应 surface 和组件
错误原因修复
缺少 A2UI 消息键tool 调用格式不对surfaceUpdatedataModelUpdatebeginRenderingdeleteSurface{"messages": [...]}
messages array must not be empty消息数组为空至少传一条 A2UI 消息
surfaceUpdate.components is required没有组件列表提供非空 components
component payload 数量不为 1component 不是 { "Text": {...} } 这类结构每个组件只放一个 v0.8 组件类型
dataModelUpdate.contents must not be empty数据模型更新为空添加带 key 和 value 字段的条目
beginRendering.root is required未指定根组件root 指向已存在的组件 ID
LLM 不调用工具插件未加载或 hook 被过滤plugin_ids 中加入 "generative-ui",使用 hook filter 时再加 with_hook_filter("generative-ui")
  • examples/examples/generative_ui.rs — 使用 OpenUI Lang 输出的流式子 agent 管线
  • crates/awaken-ext-generative-ui/src/a2ui/tests.rs
  • crates/awaken-ext-generative-ui/src/a2ui/mod.rs
  • crates/awaken-ext-generative-ui/src/a2ui/plugin.rs
  • crates/awaken-ext-generative-ui/src/a2ui/tool.rs
  • crates/awaken-ext-generative-ui/src/a2ui/types.rs
  • crates/awaken-ext-generative-ui/src/a2ui/validation.rs
  • crates/awaken-ext-generative-ui/src/json_render.rs
  • crates/awaken-ext-generative-ui/src/openui.rs