Metabase Embedding SDK 消息类型详解:MetabotAgentTextMessage 结构、判别联合与源码实现
2026/9/11 2:04:44 网站建设 项目流程

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.tsenterprise/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"; };

这是一条带字面量标记的消息类型。四个字段各司其职:

属性类型语义说明
idstring消息的唯一标识,可用于retryMessage(messageId)重试定位等场景
messagestringAgent 回复的文本正文,即要展示给终端用户的自然语言内容
role"agent"消息发言方标识,固定为字面量"agent",表示消息来自 AI 助手
type"text"消息种类判别符,固定为字面量"text",表示这是一条纯文本消息

其中roletype之所以使用字面量类型(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实际包含三种具体形态:

  1. 用户文本消息MetabotUserTextMessage(见 MetabotUserTextMessage.md):{ id: string; message: string; role: "user"; type: "text" },由用户发出;
  2. Agent 文本消息MetabotAgentTextMessage:本文主角,由 Agent 发出,role"agent"type"text"
  3. 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_callactiondata_part(如code_edittransform_suggestiontodo_listadhoc_vizstatic_vizstate)等调试或内部形态,但这些都不会通过 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,保留idmessage
  • 当内部部件是data_part且数据为generated_entity卡片时,会被转换成语义完全不同的MetabotAgentChartMessage:借助Urls.generatedCard(part.data)生成questionPath(形如/question#<base64>的 URL),并缓存创建出一个预先接线的 React 图表组件Chart
  • 使用ts-patternmatch+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)中包含对话消息数组与一系列操作函数:

属性类型说明
messagesMetabotMessage[]对话中的全部消息,图表消息包含Chart属性
errorMessagesMetabotErrorMessage[]会话级错误,不挂在单条消息上
submitMessage(message: string) => Promise<void>向对话提交一条新消息
retryMessage(messageId: string) => Promise<void>回退到messageId之前的用户消息并重新提交,丢弃该 Agent 消息及其之后的内容
cancelRequest() => void取消当前进行中的请求
resetConversation() => void清空所有消息、重新开始
isProcessingboolean从提交消息到响应完成(含成功、失败、取消)期间为true
contextWindowPercentUsagenumber对话占用的模型上下文窗口比例,取值 0–100
isContextWindowFullboolean对话是否已耗尽整个上下文窗口
CurrentChartComponentType<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 />; }

这段代码体现的关键实践:

  1. type字段收窄:联合类型下的msg.type只有"text""chart"两种取值,再配合role即可把"text"分支进一步细分为用户消息与 Agent 消息,TypeScript 会为每个分支补全正确的字段类型,无需任何类型断言;
  2. 图表消息直接渲染组件message.Chart是预接线组件,可直接以 JSX 形式渲染。Chart 组件内部按drills属性决定使用静态问题(StaticQuestionInternal)还是可交互问题(InteractiveQuestionInternal),drills={false}(默认)渲染静态图表,drills={true}渲染带下钻交互的图表,对应类型 MetabotChartProps.md 中Omit<StaticQuestionProps, ...>Omit<InteractiveQuestionProps, ...>的联合;
  3. 守卫nulluseMetabot返回null时先渲染加载占位,避免在订阅器未挂载时访问未就绪的对话状态。

除消息渲染外,还可以组合UseMetabotResult的其他能力实现完整对话交互:用submitMessage发送用户输入、用isProcessing显示输入中的加载态、用isContextWindowFull提示上下文已满并引导用户resetConversation、用retryMessage(msg.id)实现单条回复的重试(该函数会回退到目标 Agent 消息之前的用户消息并重新提交,目标消息及其之后的内容会被丢弃)。

高级话题:上下文窗口、错误与会话状态

理解MetabotAgentTextMessage所处的运行时环境有助于正确设计 UI:

  • 上下文窗口管理contextWindowPercentUsage表示当前对话占用的模型上下文比例(0–100)。当isContextWindowFulltrue时,对话已耗尽上下文,继续提问可能无法获得有效回答,应提示用户开启新会话(调用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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询