Hydra AI(Tambo)组件体系详解:Generative 生成式组件与 Interactable 可交互组件实战指南
【免费下载链接】hydra-aiGenerative UI SDK for React项目地址: https://gitcode.com/GitHub_Trending/hy/hydra-ai
本文是一份围绕 Tambo(Hydra AI 的 Generative UI SDK for React)组件体系的实战技术指南。它系统讲解两种核心组件类型——由 AI 按需动态创建的Generative Components(生成式组件)与预先放置在 UI 中、由 AI 观察和更新的Interactable Components(可交互组件),覆盖注册方式、propsSchema配置、ComponentRenderer渲染、withTamboInteractable双向更新等完整链路。读完本文,你将能够在自己的 React 应用中接入@tambo-ai/react,正确注册组件、渲染 AI 生成结果,并让 AI 通过自然语言直接操作你预先摆放的界面组件。
本文以技能参考文档 components.md 为主体,并结合仓库内 react-sdk 的源码实现与测试用例,对每一处 API 的底层机制进行溯源验证。
组件体系概览:两种互补的组件类型
Tambo 的组件体系只包含两种组件类型,两者分工明确、互为补充:
| 维度 | Generative(生成式组件) | Interactable(可交互组件) |
|---|---|---|
| 创建方式 | AI 按需动态创建 | 开发者预先放置在 UI 中 |
| 渲染时机 | 单次渲染,随 AI 回复出现 | 会话期间持续存在 |
| Props 来源 | AI 生成一次 | AI 可持续更新 props |
| 典型场景 | 聊天回复、仪表盘图表 | 设置面板、表单、任务看板 |
一句话概括:Generative 组件是 AI "说出来的界面",Interactable 组件是 AI "能动手操作的界面"。前者回答"AI 如何根据用户请求现造一个 UI",后者回答"AI 如何接管你页面里已经存在的 UI 并实时更新它"。
快速开始
无论使用哪种类型,第一步都是把组件注册到TamboProvider上。以下是最小可用示例(摘自参考文档的 Quick Start):
// Generative: AI creates when needed const components: TamboComponent[] = [ { name: "WeatherCard", component: WeatherCard, description: "Shows weather. Use when user asks about weather.", propsSchema: z.object({ city: z.string(), temp: z.number() }), }, ]; <TamboProvider components={components}> <App /> </TamboProvider>;这里的核心约定是:components数组中的每一项都是一个TamboComponent,包含name(AI 引用的标识)、component(实际的 React 组件)、description(告诉 AI 何时使用它)、propsSchema(AI 生成 props 的契约)。
从源码看,TamboComponent类型定义在 component-metadata.ts,它继承自@tambo-ai/client的基类并用ComponentType<any>覆盖了 React 相关的字段。源码注释特别强调:component字段必须传组件本身,而不是组件实例:
const MyComponent = () => { return <div>My Component</div>; }; // 正确:传 Component 本身 const components = [MyComponent];TamboProvider内部会把components交给TamboRegistryProvider处理。查看 tambo-registry-provider.tsx 的registerComponent实现可知:每个组件注册时会被校验(validateAndPrepareComponent)、规范化 props schema,并存入以name为键的ComponentRegistry中;若传入同名组件且开启覆盖警告,会输出overwriting component <name>的 console 警告。也就是说,name必须全局唯一,否则后面的注册会覆盖前面的。
Generative Components:让 AI 按需生成界面
注册生成式组件
生成式组件的完整注册示例(参考文档核心示例):
import { TamboProvider, TamboComponent } from "@tambo-ai/react"; import { z } from "zod"; const WeatherCardSchema = z.object({ city: z.string().describe("City name"), temperature: z.number().describe("Temperature in Celsius"), condition: z.string().describe("Weather condition"), }); const components: TamboComponent[] = [ { name: "WeatherCard", component: WeatherCard, description: "Displays weather for a city. Use when user asks about weather.", propsSchema: WeatherCardSchema, }, ]; <TamboProvider apiKey={apiKey} components={components}> <App /> </TamboProvider>;TamboProvider在这里同时接收了apiKey与components。apiKey对应NEXT_PUBLIC_TAMBO_API_KEY(Next.js)或VITE_TAMBO_API_KEY(Vite)等环境变量(详见技能主文档 SKILL.md 的 Environment Variables 小节)。
propsSchema:AI 生成 props 的"合同"
propsSchema是生成式组件的核心配置,有两个关键约束(参考文档 Key Points):
- 必须是 Zod object,并且每个字段都要调用
.describe()。.describe()写入的字符串就是 AI 理解该字段含义的唯一依据,例如z.string().describe("City name")让 AI 知道city应该填城市名而不是国家名。 - description 字段告诉 AI 何时使用该组件。它相当于组件的"触发条件说明",写得越清晰,AI 在用户消息中匹配到正确组件的概率越高。
使用 ComponentRenderer 渲染 AI 生成的组件
组件注册到 Provider 后,AI 回复中的"组件块"需要通过ComponentRenderer渲染到消息列表里。参考文档给出了完整示例:
import { ComponentRenderer } from "@tambo-ai/react"; function Message({ message, threadId, }: { message: TamboThreadMessage; threadId: string; }) { return ( <div> {message.content.map((block) => { switch (block.type) { case "text": return <p key={`${message.id}:text`}>{block.text}</p>; case "component": return ( <ComponentRenderer key={block.id} content={block} threadId={threadId} messageId={message.id} /> ); default: return null; } })} </div> ); }消息的content数组中的每个 block 有两种类型:text(纯文本)与component(组件块)。对component块,交给ComponentRenderer处理即可。注意key={block.id}至关重要——ComponentRenderer依靠稳定的 key 在 React 调和(reconciliation)过程中保持组件实例不被销毁。
源码视角:ComponentRenderer 内部做了什么
从 v1-component-renderer.tsx 的实现可以看到完整的渲染链路:
- 从注册表查找组件:通过
getComponentFromRegistry(content.name, registry.componentList)按名称查找已注册组件。若找不到,registry.ts 会抛出Tambo tried to use Component ${name}, but it was not found错误。 - 解析 props(支持流式):使用
partial-json库的parse解析 props。这一步正是流式渲染的关键——AI 回复尚未结束时 props 可能是残缺的 JSON 片段,partial-json能宽容地解析这些半成品数据,让组件在流式输出过程中就能逐步渲染出来。 - 校验 props:如果组件带 schema(标准 Schema 兼容),会用
~standard.validate校验解析后的 props。校验失败时不会阻断渲染,只是打印 warning 并按原始 props 渲染,保证 UI 始终有内容呈现。 - 包一层组件上下文:用
ComponentContentProvider包裹渲染结果,注入componentId、threadId、messageId、componentName,使得组件内部可以通过useTamboComponentState等 Hook 访问组件上下文。 - 失败兜底:任何异常都会被捕获并记录详细错误上下文(threadId、messageId、componentName、streamingState、props),渲染返回
null或传入的fallback。
生成式组件关键要点(Key Points)
- propsSchema:Zod 对象,每个字段用
.describe()描述含义; - description:用自然语言告诉 AI 何时使用该组件;
- Streaming(流式):流式渲染时 props 一开始是
undefined的,因此 props 要么声明为 optional,要么在组件内做优雅降级处理(如默认值); - 类型安全:用
z.infer<typeof Schema>生成 TypeScript props 类型,让 AI 生成的 props 与组件签名对齐。
type WeatherCardProps = z.infer<typeof WeatherCardSchema>; // { city: string; temperature: number; condition: string }Interactable Components:让 AI 操作你预先放置的 UI
与生成式组件不同,可交互组件由你直接放进页面,AI 可以"看见"它的当前 props,并通过自然语言指令更新它们。
使用 withTamboInteractable 包装组件
参考文档的 Note 示例:
import { withTamboInteractable } from "@tambo-ai/react"; import { z } from "zod"; const NoteSchema = z.object({ title: z.string().describe("Note title"), content: z.string().describe("Note content"), color: z.enum(["white", "yellow", "blue"]).optional(), }); function Note({ title, content, color = "white" }: Props) { return ( <div style={{ backgroundColor: color }}> <h3>{title}</h3> <p>{content}</p> </div> ); } export const InteractableNote = withTamboInteractable(Note, { componentName: "Note", description: "A note with editable title, content, and color", propsSchema: NoteSchema, });withTamboInteractable接收两个参数:原始组件 + 配置对象InteractableConfig。从 with-tambo-interactable.tsx 的源码可见,配置对象支持四个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
componentName | string | 组件在 Tambo 中的标识名称 |
description | string | 组件用途描述,LLM 依据它理解如何与该组件交互 |
propsSchema? | SupportedSchema<Props> | 可选的 props schema,提供后 AI 更新 props 时会按它校验 |
stateSchema? | SupportedSchema<State> | 可选的 state schema,提供后 state 更新会按它校验 |
此外还有一组可选的回调/注入 props(WithTamboInteractableProps,见 with-tambo-interactable.tsx):
interactableId?:可手动指定的实例 ID,不传则自动生成唯一 ID;onInteractableReady?: (id: string) => void:组件注册完成回调;onPropsUpdate?: (newProps) => void:Tambo 通过工具调用更新 props 后的回调。
可交互组件的工作原理(How It Works)
参考文档给出了四条核心机制,逐一结合源码展开:
1. Auto-registration:挂载即注册
withTamboInteractable生成的包装组件在useEffect挂载阶段调用addInteractableComponent()完成注册(with-tambo-interactable.tsx)。注册时会在componentName后追加一个随机后缀生成唯一 ID(如Note-a1b2c3),实现"同一组件多个实例互不干扰"。卸载时自动调用removeInteractableComponent清理,同时注销其关联工具。
2. Context sending:当前 props 自动暴露给 AI
注册时组件的当前 props 会被存储进TamboInteractableProvider的组件列表。AI 可通过全局工具get_all_interactable_components与get_interactable_component_by_id实时读取所有可交互组件及其 props(工具实现见 tambo-interactable-provider.tsx)。同时,props 变化时会通过updateInteractableComponentProps同步,包装组件里会用JSON.stringify对比上一次序列化结果,避免无意义的重复同步(with-tambo-interactable.tsx)。
3. Tool registration:更新工具自动注册
这是"AI 能操作 UI"的底层支撑。TamboInteractableProvider为每个可交互组件自动注册两个工具(tambo-interactable-provider.tsx):
update_component_props_<id>:按propsSchema生成 JSON Schema 并转为**部分可选(partial)**结构,AI 只需传入想修改的字段即可局部更新;update_component_state_<id>:同理更新组件 state,支持stateSchema校验。
两个工具默认都带tamboStreamableHint: true注解,因此 props/state 更新可以实时流式呈现。若想关闭某个组件工具流的实时流式,可在annotations中设置{ tamboStreamableHint: false }(见 InteractableConfig.annotations)。TamboInteractableProvider还维护了一个工具归属映射,卸载组件时会连带注销它名下的所有工具。
4. Bidirectional:用户编辑与 AI 更新双向打通
- 用户 → AI:用户在界面上修改组件,props 变化自动同步给 AI(供后续指令参考);
- AI → 用户:AI 调用
update_component_props_<id>工具,updateInteractableComponentProps对 props 做浅比较后执行部分合并更新(tambo-interactable-provider.tsx),组件随即以新 props 重渲染。
在可交互组件内部管理状态:useTamboComponentState
如果组件内部有"本地状态"希望同时被 AI 感知和修改(例如便签的isPinned),可以配合useTamboComponentStateHook。从 use-tambo-v1-component-state.ts 的类型签名可见其用法与useState几乎一致,但返回三元组:
const [count, setCount, { isPending, error, flush }] = useTamboComponentState( "count", 0, // 初始值 500, // 可选防抖毫秒数,默认 500 );该 Hook 支持三种模式(源码注释明确说明):
- Rendered components(生成式组件内):与后端做双向状态同步;
- Interactable components(可交互组件内):通过 interactable provider 同步 state(即走
update_component_state_<id>工具链路); - No context yet:在上下文尚未就绪(如首帧渲染)时退化为纯
useState,无副作用。
何时使用哪种组件
参考文档末尾的对照表是选型决策的核心依据:
| Generative | Interactable |
|---|---|
| AI 按需创建 | 你预先放置在 UI 中 |
| 一次性渲染 | 会话期间持续存在 |
| Props 只生成一次 | AI 可以持续更新 props |
| 适用:聊天回复、仪表盘 | 适用:设置、表单、任务看板 |
选型建议:如果这个 UI 是"AI 回答的一部分",回答完就翻篇——选 Generative;如果这个 UI 是"你应用里本来就有的功能界面,希望用户能用自然语言指挥 AI 去操作它"——选 Interactable。两者的结合点在于:可交互组件被 AI 渲染在消息里时,同样能享受ComponentRenderer+ComponentContentProvider的上下文注入,这正是"生成后仍可被操作"的进阶玩法。
配套参考
- 技能主流程:building-with-tambo/SKILL.md(安装、Provider 接线、Chat UI 布局选型的完整 Step-by-Step)
- 组件渲染细节:component-rendering.md(流式 props、loading 状态、持久化状态)
- 组件渲染器源码:v1-component-renderer.tsx 及测试 v1-component-renderer.test.tsx
- 可交互 HOC 源码:with-tambo-interactable.tsx 及测试 with-tambo-interactable.test.tsx
- 可交互 Provider 源码:tambo-interactable-provider.tsx
- 组件状态 Hook 源码:use-tambo-v1-component-state.ts
【免费下载链接】hydra-aiGenerative UI SDK for React项目地址: https://gitcode.com/GitHub_Trending/hy/hydra-ai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考