Metabase Embedding SDK 消息类型详解:MetabotAgentTextMessage 结构、判别联合与源码实现
【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase
导读
MetabotAgentTextMessage是 Metabase Embedding SDK 中由 AI 助手(Metabot)返回给宿主应用的一种纯文本消息类型,它描述了对话中"Agent 回复"这条消息的完整数据结构(消息 ID、正文、角色标识与消息种类)。本文以该类型定义为主线,结合其所属的MetabotMessage判别联合(discriminated union)、相邻的MetabotAgentChartMessage图表消息、useMetabot对话 Hook 以及frontend/src/embedding-sdk-bundle/types/metabot.ts与enterprise/frontend/src/embedding-sdk-ee/metabot/hooks/use-metabot.tsx的真实实现,帮助你彻底掌握如何在嵌入应用里识别、渲染并区分 Agent 的文本回复,构建出类型安全的自定义 AI 问答界面。
MetabotAgentTextMessage:一段最小的 TypeScript 类型定义
关联文档 MetabotAgentTextMessage.md 给出的核心类型定义非常精炼,完整内容如下:
type MetabotAgentTextMessage = { id: string; message: string; role: "agent"; type: "text"; };这是一条带字面量标记的消息类型。四个字段各司其职:
| 属性 | 类型 | 语义说明 |
|---|---|---|
id | string | 消息的唯一标识,可用于retryMessage(messageId)重试定位等场景 |
message | string | Agent 回复的文本正文,即要展示给终端用户的自然语言内容 |
role | "agent" | 消息发言方标识,固定为字面量"agent",表示消息来自 AI 助手 |
type | "text" | 消息种类判别符,固定为字面量"text",表示这是一条纯文本消息 |
其中role与type之所以使用字面量类型(literal type)而非宽泛的string,是为了让 TypeScript 能够在联合类型上进行判别收窄(type narrowing):只要检查type === "text"且role === "agent",编译器即可推断出这条消息的完整结构。
在消息类型体系中的位置:一条完整的判别联合
MetabotAgentTextMessage并不是孤立存在的,它是 SDK 对话消息类型树中的一个叶子节点。结合关联的 MetabotMessage.md 与 MetabotAgentMessage.md,可以还原出完整的类型层级:
// 对话中出现的所有消息(用户消息 + Agent 消息) type MetabotMessage = MetabotUserTextMessage | MetabotAgentMessage; // Agent 产生的消息(文本回复 + 图表回复) type MetabotAgentMessage = MetabotAgentTextMessage | MetabotAgentChartMessage;展开后,MetabotMessage实际包含三种具体形态:
- 用户文本消息
MetabotUserTextMessage(见 MetabotUserTextMessage.md):{ id: string; message: string; role: "user"; type: "text" },由用户发出; - Agent 文本消息
MetabotAgentTextMessage:本文主角,由 Agent 发出,role为"agent"、type为"text"; - Agent 图表消息
MetabotAgentChartMessage(见 MetabotAgentChartMessage.md):{ Chart: ComponentType<MetabotChartProps>; id: string; questionPath: string; role: "agent"; type: "chart" },Agent 直接产出一张图表。
三种形态通过type字段构成可判别的联合。这条定义在开源仓库的源码中有完全一致的对应实现,见 frontend/src/embedding-sdk-bundle/types/metabot.ts:
// User messages export type MetabotUserTextMessage = { id: string; role: "user"; type: "text"; message: string; }; // Agent messages export type MetabotAgentTextMessage = { id: string; role: "agent"; type: "text"; message: string; }; export type MetabotAgentChartMessage = { id: string; role: "agent"; type: "chart"; /** URL path to the question, e.g. `/question#<base64>` */ questionPath: string; /** A pre-wired React component that renders the chart. */ Chart: React.ComponentType<MetabotChartProps>; }; export type MetabotAgentMessage = | MetabotAgentTextMessage | MetabotAgentChartMessage; export type MetabotMessage = MetabotUserTextMessage | MetabotAgentMessage;值得注意的是,源码注释明确说明 SDK 只对外暴露type === "text"消息与generated_entity图表卡片这两类公开消息;内部还存在tool_call、action、data_part(如code_edit、transform_suggestion、todo_list、adhoc_viz、static_viz、state)等调试或内部形态,但这些都不会通过 SDK 输入路径产生,仅用于产品内其他界面。这解释了为什么公开类型体系中只保留文本与图表两种 Agent 消息。
从内部消息到公开类型:mapMessage 的映射逻辑
MetabotAgentTextMessage并不是后端直接下发的原始结构,而是由 SDK 层从内部消息部件(message part)映射而来。映射逻辑位于 enterprise/frontend/src/embedding-sdk-ee/metabot/hooks/use-metabot.tsx 的mapMessage函数中:
const mapMessage = ( message: PublicChatMessage, cache: Map<string, ReturnType<typeof createChartComponent>>, authConfig: MetabaseAuthConfig | undefined, ): MetabotMessage => match(message) .with( { role: "user", type: "text" }, ({ id, message }) => ({ id, role: "user", type: "text", message }) as const, ) .with( { role: "agent", type: "text" }, ({ id, message }) => ({ id, role: "agent", type: "text", message }) as const, ) .with( { role: "agent", type: "data_part", part: { type: "data-generated_entity", data: { type: "card" } }, }, ({ id, part }) => { const questionPath = Urls.generatedCard(part.data); const Chart = authConfig ? getCachedChartComponent(questionPath, cache, authConfig) : FallbackChartComponent; return { id, role: "agent", type: "chart", questionPath, Chart, } as const; }, ) .exhaustive();从这段实现可以看出:
- 当内部部件匹配
{ role: "agent", type: "text" }时,直接原样映射为公开的MetabotAgentTextMessage,保留id与message; - 当内部部件是
data_part且数据为generated_entity卡片时,会被转换成语义完全不同的MetabotAgentChartMessage:借助Urls.generatedCard(part.data)生成questionPath(形如/question#<base64>的 URL),并缓存创建出一个预先接线的 React 图表组件Chart; - 使用
ts-pattern的match+exhaustive()保证所有公开消息形态都被穷尽处理,新增类型时编译期即可发现遗漏分支。
因此,MetabotAgentTextMessage是对话流中"Agent 用自然语言回答问题"这一场景的标准载体,而图表消息则对应"Agent 直接给出可视化结果"。
消费入口:useMetabot 与 UseMetabotResult
MetabotAgentTextMessage通常不是单独使用的,而是通过useMetabotHook 从对话状态中读取。关联文档 useMetabot.md 给出其签名与基本用法:
function useMetabot(): UseMetabotResult | null;useMetabot返回 Metabot 对话 API;在 SDK bundle 加载完成、<MetabaseProvider>挂载其内部订阅器之前返回null,因此使用前必须做空值守卫,例如:
const metabot = useMetabot(); if (!metabot) { return <Spinner />; } metabot.submitMessage("Show me orders");返回对象UseMetabotResult(见 UseMetabotResult.md)中包含对话消息数组与一系列操作函数:
| 属性 | 类型 | 说明 |
|---|---|---|
messages | MetabotMessage[] | 对话中的全部消息,图表消息包含Chart属性 |
errorMessages | MetabotErrorMessage[] | 会话级错误,不挂在单条消息上 |
submitMessage | (message: string) => Promise<void> | 向对话提交一条新消息 |
retryMessage | (messageId: string) => Promise<void> | 回退到messageId之前的用户消息并重新提交,丢弃该 Agent 消息及其之后的内容 |
cancelRequest | () => void | 取消当前进行中的请求 |
resetConversation | () => void | 清空所有消息、重新开始 |
isProcessing | boolean | 从提交消息到响应完成(含成功、失败、取消)期间为true |
contextWindowPercentUsage | number | 对话占用的模型上下文窗口比例,取值 0–100 |
isContextWindowFull | boolean | 对话是否已耗尽整个上下文窗口 |
CurrentChart | ComponentType<MetabotChartProps> \| null | 绑定到 Agent 最新产出图表的预接线组件,未产出图表时为null |
messages数组正是MetabotAgentTextMessage出现的场所。结合 use-metabot.tsx 的实现,messages由内部agent.messages展开所有 parts、过滤出公开部件并逐条mapMessage得到;同时每个 turn 只保留最后一张图表(getFinalChartMessageIdsPerTurn),因为 Agent 在流式输出过程中可能发出多张中间图表。
在 Hook 内部,submitMessage调用的是agent.submitInput(message, { preventOpenSidebar: true })——即通过 SDK 提交消息时不会弹出产品内边栏,保证行为完全由宿主应用控制;resetConversation在清空对话的同时还会清空图表组件缓存;errorMessages来自内部消息状态status.type === "errored"的展示信息,类型为MetabotErrorMessage({ message: string; type: "message" | "alert" | "locked" },"alert"会以警告图标与错误色渲染,"message"以纯文本渲染)。
实战:如何在自定义聊天界面中渲染并区分 Agent 文本消息
借助判别联合与字面量类型,可以在渲染层用极少的代码安全区分消息形态。以下示例展示了如何基于type字段收窄联合类型并分别渲染文本气泡与图表卡片:
import { useMetabot } from "@metabase/embedding-sdk-react"; import type { MetabotMessage } from "@metabase/embedding-sdk-react"; function ChatThread() { const metabot = useMetabot(); if (!metabot) { return <Spinner />; // SDK 尚未就绪时的守卫 } return ( <div> {metabot.messages.map((msg) => ( <MessageBubble key={msg.id} message={msg} /> ))} </div> ); } function MessageBubble({ message }: { message: MetabotMessage }) { // 判别收窄:按 type 区分三种消息形态 if (message.type === "text" && message.role === "agent") { // MetabotAgentTextMessage:渲染 Agent 的文本回复 return <div className="agent-bubble">{message.message}</div>; } if (message.type === "text" && message.role === "user") { // MetabotUserTextMessage:渲染用户提问 return <div className="user-bubble">{message.message}</div>; } // MetabotAgentChartMessage:渲染 Agent 生成的图表 return <message.Chart />; }这段代码体现的关键实践:
- 用
type字段收窄:联合类型下的msg.type只有"text"与"chart"两种取值,再配合role即可把"text"分支进一步细分为用户消息与 Agent 消息,TypeScript 会为每个分支补全正确的字段类型,无需任何类型断言; - 图表消息直接渲染组件:
message.Chart是预接线组件,可直接以 JSX 形式渲染。Chart 组件内部按drills属性决定使用静态问题(StaticQuestionInternal)还是可交互问题(InteractiveQuestionInternal),drills={false}(默认)渲染静态图表,drills={true}渲染带下钻交互的图表,对应类型 MetabotChartProps.md 中Omit<StaticQuestionProps, ...>与Omit<InteractiveQuestionProps, ...>的联合; - 守卫
null:useMetabot返回null时先渲染加载占位,避免在订阅器未挂载时访问未就绪的对话状态。
除消息渲染外,还可以组合UseMetabotResult的其他能力实现完整对话交互:用submitMessage发送用户输入、用isProcessing显示输入中的加载态、用isContextWindowFull提示上下文已满并引导用户resetConversation、用retryMessage(msg.id)实现单条回复的重试(该函数会回退到目标 Agent 消息之前的用户消息并重新提交,目标消息及其之后的内容会被丢弃)。
高级话题:上下文窗口、错误与会话状态
理解MetabotAgentTextMessage所处的运行时环境有助于正确设计 UI:
- 上下文窗口管理:
contextWindowPercentUsage表示当前对话占用的模型上下文比例(0–100)。当isContextWindowFull为true时,对话已耗尽上下文,继续提问可能无法获得有效回答,应提示用户开启新会话(调用resetConversation)。注意所有useMetabot实例在同一个应用内共享对话状态(都读取同一份 Redux 状态),因此在多个组件中挂载 Hook 不会产生独立的会话。 - 会话级错误模型:
errorMessages是会话级的,不附着在单条消息上。它来自内部消息的errored状态,类型为 MetabotErrorMessage.md 中定义的"message" | "alert" | "locked"三态;"alert"用于需要醒目警告的错误,"message"用于普通文本提示。 - 重试语义:
retryMessage(messageId)的messageId应传入 Agent 消息的id(即MetabotAgentTextMessage.id)。它会把会话回滚到该 Agent 消息之前的用户消息并重新提交,属于"整轮回退重试"而非单条替换。 - 企业版能力:
useMetabot的完整实现挂载在METABOT_SDK_EE_PLUGIN插件上(见 use-metabot.tsx),源码位于enterprise/目录,说明 Metabot 对话属于 Metabase 企业版/嵌入能力,开源(OSS)版本中该插件未激活时useMetabot不提供完整功能。
如果不想完全自绘聊天界面,也可以直接使用 SDK 现成的 MetabotQuestion 组件——它接收 MetabotQuestionProps 并渲染一个完整的 metabot 问题界面,支持layout("auto"/"sidebar"/"stacked",其中"auto"在移动端使用stacked、大屏使用sidebar)、isSaveEnabled(是否显示保存按钮)、targetCollection(保存到指定集合,隐藏保存弹窗的集合选择器)等配置;而useMetabot面向的是需要完全自定义界面的场景,MetabotAgentTextMessage正是这种场景下处理 Agent 文本回复的类型基石。
小结
MetabotAgentTextMessage看似只是一个四字段的小类型,却是整个 Metabot 对话消息体系的关键一环:它以role: "agent"与type: "text"两个字面量参与MetabotMessage判别联合,让 TypeScript 在渲染层能安全地收窄消息形态;它由 use-metabot.tsx 中的mapMessage从内部消息部件映射而来,与图表消息MetabotAgentChartMessage共同构成 Agent 的两类回复。掌握它的结构、在联合类型中的位置以及useMetabot/UseMetabotResult的消费方式,即可在嵌入应用中构建类型安全的自定义 AI 聊天体验。
【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考