ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Wave Terminal 中的 AI SDK UIMessage 类型体系:从泛型定义到消息渲染的完整实践

Wave Terminal 中的 AI SDK UIMessage 类型体系:从泛型定义到消息渲染的完整实践 Wave Terminal 中的 AI SDK UIMessage 类型体系从泛型定义到消息渲染的完整实践【免费下载链接】wavetermAn open-source, AI-integrated, cross-platform terminal for seamless workflows项目地址: https://gitcode.com/GitHub_Trending/wa/wavetermUIMessage是 Vercel AI SDK 中用于描述应用状态的消息模型它承载完整的消息历史、元数据、数据部件data parts与工具调用上下文是前端渲染useChat会话消息的唯一事实来源source of truth。本文以 aisdk-uimessage-type.md 为骨架结合 Wave TerminalAI 集成跨平台终端的 Wave AI 面板源码系统讲解UIMessage的三个泛型参数、全部UIMessagePart类型定义、如何构造自定义类型的UIMessage以及这些类型如何在前端渲染、工具审批、流式更新中被真实消费。读完本文你将能够为任意 AI 聊天应用设计出类型安全的UIMessage模型并理解 AI SDK 流式协议与UIMessage部件之间的映射关系。一、UIMessage 与 ModelMessage两种消息模型的职责划分在 AI SDK 中UIMessage与ModelMessage是两套面向不同消费者的消息模型ModelMessage表示传给模型的状态或上下文是模型推理的输入例如转换为 OpenAI Chat Completions、Anthropic Messages 格式的历史消息。UIMessage表示应用的完整状态包含渲染 UI 所需的全部信息——消息 ID、角色、元数据以及结构化的parts数组。它同时被客户端功能重试、编辑、分支、工具审批等所消费。Wave Terminal 的实现恰好体现了这一分工Go 后端在 uctypes.go 中定义了与 AI SDK 对齐的UIMessage/UIMessagePartJSON 结构UseChatRequest.Messages、UIChat.Messages均以[]UIMessage传输并将这些消息转换为各提供商Anthropic / OpenAI / Gemini的GenAIMessage原生格式而前端 aitypes.ts 直接从ai包导入UIMessage作为 Wave AI 面板的消息基类。前后端通过同一套 JSON 字段契约如toolCallId、input、output、state、providerMetadata保持类型一致。二、类型安全UIMessage 的三个泛型参数UIMessage被设计为完全类型安全接受三个泛型参数METADATA消息级自定义元数据类型用于附加额外信息默认unknown。DATA_PARTS自定义数据部件data part类型用于结构化数据组件默认UIDataTypes。TOOLS工具定义集合用于类型安全的工具交互默认UITools。泛型参数的约束关系体现在接口签名上interface UIMessageMETADATA unknown, DATA_PARTS extends UIDataTypes UIDataTypes, TOOLS extends UITools UITools { id: string; role: system | user | assistant; metadata?: METADATA; parts: ArrayUIMessagePartDATA_PARTS, TOOLS; }从声明可以看出DATA_PARTS与TOOLS会沿着parts向下传播到每一个UIMessagePart因此一旦你在应用层定义了自定义数据类型和工具集合parts数组中的data-*与tool-*部件就能获得精确到字段级别的类型推导而不是退化为any。实战构造你自己的 UIMessage 类型文档给出如下示例用于创建带自定义元数据、数据部件与工具集合的UIMessageimport { InferUITools, ToolSet, UIMessage, tool } from ai; import z from zod; const metadataSchema z.object({ someMetadata: z.string().datetime(), }); type MyMetadata z.infertypeof metadataSchema; const dataPartSchema z.object({ someDataPart: z.object({}), anotherDataPart: z.object({}), }); type MyDataPart z.infertypeof dataPartSchema; const tools { someTool: tool({}), } satisfies ToolSet; type MyTools InferUIToolstypeof tools; export type MyUIMessage UIMessageMyMetadata, MyDataPart, MyTools;要点拆解元数据与数据部件用zodschema 定义后通过z.infer提取为 TypeScript 类型保证运行时校验与静态类型同源tool({})定义工具satisfies ToolSet让对象字面量保持精确类型而非被拓宽InferUIToolstypeof tools从ToolSet推导出 UI 侧工具类型含input/output结构最终UIMessageMyMetadata, MyDataPart, MyTools即为整个应用共享的消息类型。Wave Terminal 的真实泛型实例Wave Terminal 在 aitypes.ts 中定义了自己的数据类型并实例化import { ChatRequestOptions, FileUIPart, UIMessage, UIMessagePart } from ai; type WaveUIDataTypes { userfile: { filename: string; size: number; mimetype: string; previewurl?: string; }; tooluse: { toolcallid: string; toolname: string; tooldesc: string; status: pending | error | completed; runts?: number; errormessage?: string; approval?: needs-approval | user-approved | user-denied | auto-approved | timeout; blockid?: string; writebackupfilename?: string; inputfilename?: string; }; toolprogress: { toolcallid: string; toolname: string; statuslines: string[]; }; }; export type WaveUIMessage UIMessageunknown, WaveUIDataTypes, any; export type WaveUIMessagePart UIMessagePartWaveUIDataTypes, any;这里WaveUIDataTypes就是第二个泛型参数DATA_PARTS的实例——它声明了三种data-*部件data-userfile用户上传文件、data-tooluse工具调用状态与审批信息、data-toolprogress工具执行进度。这些类型与 Go 后端的UIMessageDataUserFile、UIMessageDataToolUse、UIMessageDataToolProgress一一对应代码注释明确标注when updating this struct, also modify frontend/app/aipanel/aitypes.ts实现了跨语言的双向契约同步。三、UIMessage 接口核心字段UIMessage接口本身十分精简四个字段各司其职字段类型说明idstring消息的唯一标识用于 React key、重试、编辑与分支定位rolesystem \| user \| assistant消息角色决定渲染方向与样式metadata?METADATA消息级自定义元数据可选partsArrayUIMessagePartDATA_PARTS, TOOLS消息的全部结构化内容是 UI 渲染的主要依据在 aipanelmessages.tsx 中可以看到role与id的典型用法messages.map((message) AIMessage key{message.id} ... /)并用message.role assistant判断是否为最后一条流式消息system角色则用于注入系统提示参见 usechat-backend-design.md 中initialMessages注入系统上下文的示例。四、UIMessagePart八种部件类型全解析parts数组是UIMessage的灵魂。AI SDK 用**可判别联合类型discriminated union**将不同内容形态统一在UIMessagePart之下每种部件通过type字段区分。下面逐一展开文档给出的全部类型。1. TextUIPart — 文本部件type TextUIPart { type: text; text: string; state?: streaming | done; };state字段标注文本块是否仍在流式传输流式过程中为streaming完成收到text-end后为done。前端可据此决定是否启用不完整 Markdown 增量解析。2. ReasoningUIPart — 推理部件type ReasoningUIPart { type: reasoning; text: string; state?: streaming | done; providerMetadata?: Recordstring, any; };推理部件承载思考模型thinking model的推理过程文本providerMetadata可透传提供商额外信息如 token 用量、推理模式等。Wave Terminal 在 aimessage.tsx 的getThinkingMessage中判断lastPart?.type reasoning在流式期间展示 AI is thinking... 并滚动显示推理文本一旦text部件出现即切换为正文展示——这正是state与部件顺序共同驱动 UI 状态机的实例。3. ToolUIPart — 工具调用部件工具部件是状态机最复杂的部件其type为模板字面量类型tool-${NAME}即工具名someTool对应tool-someTool并且按state划分四个互斥子状态type ToolUIPartTOOLS extends UITools UITools ValueOf{ [NAME in keyof TOOLS string]: { type: tool-${NAME}; toolCallId: string; } ( | { state: input-streaming; input: DeepPartialTOOLS[NAME][input] | undefined; providerExecuted?: boolean; output?: never; errorText?: never; } | { state: input-available; input: TOOLS[NAME][input]; providerExecuted?: boolean; output?: never; errorText?: never; } | { state: output-available; input: TOOLS[NAME][input]; output: TOOLS[NAME][output]; errorText?: never; providerExecuted?: boolean; } | { state: output-error; input: TOOLS[NAME][input]; output?: never; errorText: string; providerExecuted?: boolean; } ); };四个状态对应工具调用的生命周期input-streaming模型正在生成工具入参input为DeepPartial允许部分字段input-available入参生成完毕可执行工具output-available工具执行完成携带outputoutput-error工具执行失败携带errorText。providerExecuted?: boolean用于标识该工具是否已由模型提供商侧执行。Wave Terminal 的 aimessage.tsx 中isDisplayPart只把state input-available的tool-*部件视为可显示内容流式生成入参期间input-streaming不打断正文展示。4. SourceUrlUIPart — 来源 URL 部件type SourceUrlUIPart { type: source-url; sourceId: string; url: string; title?: string; providerMetadata?: Recordstring, any; };用于标注回答引用的外部 URL联网搜索或引用来源sourceId用于去重与关联。5. SourceDocumentUIPart — 来源文档部件type SourceDocumentUIPart { type: source-document; sourceId: string; mediaType: string; title: string; filename?: string; providerMetadata?: Recordstring, any; };用于引用文档类来源mediaType标识文档 MIME 类型。6. FileUIPart — 文件部件type FileUIPart { type: file; mediaType: string; // IANA media type filename?: string; url: string; // 托管文件 URL 或 Data URL };url既可以是远端托管地址也可以是data:Data URL前端本地读取文件时常用。Wave Terminal 前端在 ai-utils.ts 中通过createDataUrl(file)将File转为 Data URL并定义了文件类型白名单图片、PDF、文本/代码文件与体积上限文本 200KB、PDF 5MB、图片 10MB超出时给出错误提示。7. DataUIPart — 自定义数据部件type DataUIPartDATA_TYPES extends UIDataTypes ValueOf{ [NAME in keyof DATA_TYPES string]: { type: data-${NAME}; id?: string; data: DATA_TYPES[NAME]; }; };data-*是 AI SDK 提供的扩展点任何自定义结构化数据都可以通过data-${NAME}成为消息的一部分前端按名处理。本文第二节中WaveUIDataTypes的userfile、tooluse、toolprogress即为三个实例。以data-tooluse为例Go 侧 uctypes.go 定义了其数据结构type UIMessageDataToolUse struct { ToolCallId string json:toolcallid ToolName string json:toolname ToolDesc string json:tooldesc Status string json:status // pending | error | completed RunTs int64 json:runts,omitempty ErrorMessage string json:errormessage,omitempty Approval string json:approval,omitempty // needs-approval | user-approved | ... BlockId string json:blockid,omitempty WriteBackupFileName string json:writebackupfilename,omitempty InputFileName string json:inputfilename,omitempty }IsApproved()方法定义Approval || user-approved || auto-approved视为已批准。前端 aitooluse.tsx 则消费这些字段渲染✓/✗/•状态图标、展示 Approve / Deny 审批按钮、为写文件工具提供 Revert File 备份还原与 Show Diff 差异查看入口甚至通过blockid在终端中高亮相关 Block——充分体现data-*部件承载客户端专属功能状态的设计意图。8. StepStartUIPart — 步骤边界部件type StepStartUIPart { type: step-start; };标记一个 step后端一次 LLM API 调用的开始与流式协议中的start-step/finish-step事件配套用于区分多步拼接的 assistant 消息。五、流式协议如何落地为 UIMessageUIMessage是静态形态而其内容来自流式协议。配套文档 aisdk-streaming.md 描述了 AI SDK 基于 SSE 的数据流协议自定义后端需设置x-vercel-ai-ui-message-stream: v1头。两类文档的映射关系如下流式事件SSEdata:落地的 UIMessage 部件text-start/text-delta/text-endTextUIPartstate先streaming后donereasoning-start/reasoning-delta/reasoning-endReasoningUIPartsource-urlSourceUrlUIPartsource-documentSourceDocumentUIPartfileFileUIPartdata-*DataUIParttool-input-start/tool-input-delta/tool-input-available/tool-output-availableToolUIPart状态机流转start-step/finish-stepStepStartUIPart与 step 边界error以errorText形式附加到消息finish/[DONE]消息完成标记Wave Terminal 的 Go 后端在 uctypes.go 中定义了与之一一对应的UseChatStreamPart结构text-start、text-delta、tool-input-available、tool-output-available、finish-step、finish等注释中完整列举了协议支持的 type 常量。流式部件在 aimessage.tsx 中被消费text部件流式期间交给WaveStreamdown做增量 Markdown 解析parseIncompleteMarkdown{isStreaming}消息渲染完成且非流式时显示反馈按钮AIFeedbackButtons。这一流式协议 → UIMessage 部件 → React 渲染的链路正是 AI SDK 聊天架构的标准范式。六、在应用中完整落地从类型定义到渲染管线综合以上内容在 Wave Terminal或其他 AI 应用中落地UIMessage类型体系的完整链路为定义类型用zod定义METADATA与DATA_PARTS用tool()定义TOOLS组合成应用级MyUIMessage见第二节示例传输契约前端UIMessage的 JSON 形态与后端结构体对齐如uctypes.go的UIMessage/UIMessagePart保证消息持久化UIChat.Messages与跨请求传输一致流式接收useChat通过 SSE 协议aisdk-streaming.md增量构建消息的parts由DATA_PARTS与TOOLS泛型保证每个部件的类型安全按类型渲染渲染器对parts做判别式分发——text走 Markdown 渲染、reasoning走思考展示、tool-*与data-tooluse/data-toolprogress走工具卡片含审批、进度、diff 等交互、data-userfile走文件缩略图列表见 aimessage.tsx 的AIMessagePart、UserMessageFiles、AIToolUseGroup客户端功能基于id实现重试/编辑基于state实现流式状态展示基于data-*的approval字段实现工具审批交互。usechat-backend-design.md 还展示了更宏观的视角Wave Terminal 的目标架构是前端useChat→ HTTP/SSE → Go 后端 → AI 提供商前端由单个useChathook 管理全部消息状态后端解析配置后流式返回标准 AI SDK 格式——而UIMessage正是这条链路上前后端共享的消息唯一事实来源也是上述全部能力得以类型安全地串联起来的基础。总结UIMessage通过三个泛型参数METADATA、DATA_PARTS、TOOLS将元数据、自定义数据部件与工具类型注入到统一的消息结构中八种UIMessagePart文本、推理、工具、来源 URL、来源文档、文件、自定义数据、步骤边界以可判别联合覆盖了现代 AI 聊天的全部内容形态。Wave Terminal 的实践表明前端UIMessage类型aitypes.ts与 Go 后端结构体uctypes.go双端对齐、渲染组件aimessage.tsx、aitooluse.tsx按部件判别分发、流式协议aisdk-streaming.md增量驱动状态机三者共同构成了可复用、可扩展、类型安全的 AI 聊天消息体系。【免费下载链接】wavetermAn open-source, AI-integrated, cross-platform terminal for seamless workflows项目地址: https://gitcode.com/GitHub_Trending/wa/waveterm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表