📝 Markdown as Protocol for Agentic UI

来源: Fabian Kuebler Blog | 2025-12-15 | 评分: ⭐⭐⭐⭐⭐

核心创意: 用 Markdown 作为协议,同时传输文本、代码、数据三种内容。流式执行 + mount() 原语,让 AI 代理动态生成 React UI。

🎯 核心理念

Eric Schmidt 预测: "用户界面将在很大程度上消失",AI 代理将按需动态生成 UI。本文探索了这一愿景的实现方式。

📐 协议设计: 三种 Block

Block 类型 语法 用途
Text **plain markdown** 流式输出给用户
Code fence ```tsx agent.run 在服务器持久上下文中执行
Data fence ```json agent.data => "id" 流式传输数据到 UI

🔄 反馈循环

console.log = agent 与自己的对话方式
  • LLM 生成带代码块的 Markdown
  • 文本流给用户,代码在服务器增量执行
  • console.* 输出和异常作为新 turn 反馈给 LLM
  • 无输出/异常 → 完成,等待新用户查询

⚡ 流式执行

希望语句在生成时就执行,不等完整响应。问题: 没有运行时支持这种流式执行。

解决方案: 构建 bun-streaming-exec,使用 vm.Script + 创意包装。

🖥️ Agentic UI: mount() 原语

```tsx agent.run
mount({
  ui: () => Hello from the agent!
});
```

LLM 生成代码 → 服务器执行 → mount() 序列化 React 组件 → 客户端渲染

🔀 四种数据流模式

1. Client → Server (表单)

```tsx agent.run
const form = mount({
  outputSchema: z.object({ name: z.string() }),
  ui: ({ output }) => (
    <Box>
      <TextField {...output.name} />
      <Button {...output}>Submit</Button>
    </Box>
  )
});
const { name } = await form.result; // 阻塞直到提交
console.log("user:responded", name);
```

2. Server → Client (实时更新)

```tsx agent.run
const data = new Data({ progress: 0 });
mount({
  data,
  ui: ({ data }) => <LinearProgress value={data.progress} />
});
data.progress = 40; // UI 立即更新
```

Data 对象是代理,检测变更 → 序列化为 patch → WebSocket 发送到客户端

3. LLM → Client (流式数据)

```tsx agent.run
const movies = new StreamedData("movies-list");
mount({
  streamedData: movies,
  ui: ({ streamedData }) => <Card>{streamedData?.map(...)}</Card>
});
```

```json agent.data => "movies-list"
[
  { "name": "Blade Runner", "rating": 4.5 }
]
```

4. Client → Server (回调)

```tsx agent.run
const onRefresh = async () => {
  data.messages = await loadMessages();
};
mount({
  callbacks: { onRefresh },
  ui: ({ callbacks }) => <RefreshButton onClick={callbacks.onRefresh} />
});
```

🕳️ Slots 机制

UI 复杂时,用户需等待更久。用 slot 机制: 先挂载骨架,逐步注入重内容。

```tsx agent.run
const shell = mount({
  ui: () => (
    <Card>
      <Slot name="stats" fallback={<Skeleton />} />
      <Slot name="blockers" fallback={<Skeleton />} />
    </Card>
  )
});
shell.mountSlot("stats", ({ data }) => <StatsRow />);
```

🔒 关于安全

Claude Code 和 ChatGPT Code Interpreter 已在执行 LLM 生成的代码。沙箱、权限、静态分析在开发中。Prompt injection 是所有 agent 架构的共同难题。本文探索的是假设安全合理解决后的上层建筑。

💡 为什么有效

每个设计选择都为一个目标优化: LLM 人体工程学
  • Markdown + code fences: LLMs 训练时见过数十亿 Markdown
  • 语法自然可扩展: 加新 fence header 即可
  • console.log 反馈: 代理最熟悉的调试方式

📚 相关标签

Agentic UI Markdown React Protocol Streaming


← 返回首页