跳到内容

语义流

语义流把一条模型回复表示为可增量消费的事件序列。普通文本以增量发送;组件和布局以完整快照发送;消息结束时会给出最终的 portableText

MFUI 使用 Server-Sent Events 作为传输格式。事件名写在 event 字段里,事件载荷是 data 字段中的 JSON。

事件模型

一个最小完整响应形如:

txt
event: message.start
data: {"id":"msg_1"}

event: text.delta
data: {"partId":"txt_1","text":"Here is the plan."}

event: message.end
data: {"id":"msg_1","portableText":"Here is the plan."}

客户端读取后会得到带 type 的语义事件对象,例如 type: 'text.delta'

事件类型

事件含义
message.start开始一条消息,并给出消息 id。
text.delta向某个文本 part 追加文本;同一个 partId 的增量会合并到同一个文本 part。
component.snapshot发送已校验、已投影的完整组件快照。
layout.snapshot发送已校验、已投影的完整布局快照。
message.end结束消息,并给出最终 portableText
error报告语义流处理错误。

流式策略

文本可以按增量发送,因为普通文本不需要等完整结构闭合。

组件以快照发送。模型必须先输出完整组件 spec,服务端才能校验 schema、渲染投影,并发出 component.snapshot

布局也以快照发送。对于 mfui.columns,服务端会先校验布局结构,再校验和投影其中的组件 cell,最后发出 layout.snapshot

服务端生成

提供商适配器会读取上游模型流,并返回 MFUI SSE 响应:

ts
import { createMFUIResponse } from '@mfui/openai-compatible';

const response = createMFUIResponse(upstream, mfui);

模型可以输出普通文本和 <mfui> block。适配器会把普通文本转换成 text.delta,并在解析出完整 <mfui> block 后发出 component.snapshotlayout.snapshot

客户端读取

如果只需要最终消息,使用 readMFUIMessage()

ts
import { readMFUIMessage } from '@mfui/client';

const assistantMessage = await readMFUIMessage(response);

如果需要流式 UI,使用 streamMFUIMessage() 消费渐进消息快照:

ts
import { streamMFUIMessage } from '@mfui/client';

for await (const message of streamMFUIMessage(response)) {
  render(message);
}

高级用法

大多数应用只需要适配器和客户端读取 API。自定义传输或高级流处理可以使用更底层的原语:

API用途
createMFUIStreamWriter手动写出 MFUI 语义 SSE。
createMFUIBlockParser从文本流中解析普通文本和 <mfui> block。
readSemanticStream把 MFUI SSE 读取为语义事件。
createMessageAccumulator把语义事件累积成投影消息快照。