架构剖析:TinyRobot Bubble 渲染器、状态管理与工具调用机制
TinyRobot Bubble 的核心不是"一个组件",而是一套渲染引擎------它通过可插拔的渲染器体系、精细的状态管理和完整的工具调用支持,构建了高度灵活的消息渲染架构。
本文将从架构视角剖析这三个核心机制,帮助你理解 Bubble 的设计哲学,并在实际项目中自如地扩展它。
一、渲染器架构
1.1 双层渲染器设计
Bubble 的渲染器分为两层:
| 层级 | 职责 | 对应接口 |
|---|---|---|
| Box 渲染器 | 渲染消息的外层容器------背景、圆角、阴影、布局 | BubbleBoxRendererProps |
| Content 渲染器 | 渲染消息的具体内容------文本、图片、Markdown、代码 | BubbleContentRendererProps |
这种双层设计的好处是:样式和内容完全解耦。
你可以单独替换 Box 渲染器改变气泡外观,而不影响内容渲染;也可以单独替换 Content 渲染器改变内容展示方式,而不影响气泡样式。
1.2 渲染器匹配机制
渲染器不是固定分配的,而是通过匹配规则动态选择:
markdown
1. 按 priority 排序所有匹配规则
2. 依次执行 find 函数
3. 第一个返回 true 的规则 → 使用其对应的渲染器
4. 没有匹配到任何规则 → 使用 fallback 渲染器
优先级常量:
| 常量 | 值 | 适用场景 |
|---|---|---|
BubbleRendererMatchPriority.LOADING |
-1 | 加载状态渲染器 |
BubbleRendererMatchPriority.NORMAL |
0 | 普通渲染器 |
BubbleRendererMatchPriority.CONTENT |
10 | 基于 content 判断 |
BubbleRendererMatchPriority.ROLE |
20 | 基于 role 判断 |
数值越小优先级越高。这意味着:加载状态检查总是最先执行,然后才是内容类型判断,最后是角色判断。
1.3 配置层级
渲染器配置支持三个层级,优先级从高到低:
| 层级 | 配置方式 | 作用范围 |
|---|---|---|
| Prop 级别 | Bubble 的 fallbackBoxRenderer / fallbackContentRenderer |
当前组件 |
| Provider 级别 | BubbleProvider 的 boxRendererMatches / contentRendererMatches |
整个组件树 |
| Default 级别 | 内置默认渲染器和匹配规则 | 全局兜底 |
最佳实践:
- 全局统一配置 → Provider 级别
- 个别消息特殊处理 → Prop 级别
- 大多数场景 → Default 级别就够了
1.4 内置渲染器一览
typescript
// 通过 BubbleRenderers 访问所有内置渲染器
import { BubbleRenderers } from '@opentiny/tiny-robot'
BubbleRenderers.Box // 默认 Box 渲染器
BubbleRenderers.Text // 文本内容渲染器(默认 Content 渲染器)
BubbleRenderers.Image // 图片渲染器
BubbleRenderers.Markdown // Markdown 渲染器
BubbleRenderers.Loading // 加载状态渲染器
BubbleRenderers.Reasoning // 推理内容渲染器
BubbleRenderers.Tool // 单个工具调用渲染器
BubbleRenderers.Tools // 工具调用列表渲染器
BubbleRenderers.ToolRole // 工具角色消息渲染器
1.5 实现自定义渲染器
Content 渲染器
vue
<!-- CustomRenderer.vue -->
<template>
<div class="custom-content">
{{ message.content }}
</div>
</template>
<script setup lang="ts">
import type { BubbleContentRendererProps } from '@opentiny/tiny-robot'
defineProps<BubbleContentRendererProps>()
</script>
使用 useMessageContent 获取正确内容:
typescript
import { useMessageContent } from '@opentiny/tiny-robot'
const props = defineProps<BubbleContentRendererProps>()
const { content, contentText } = useMessageContent(props)
// content: 当前 contentIndex 对应的 ChatMessageContentItem
// contentText: content 的文本摘要
Box 渲染器
vue
<script setup lang="ts">
import type { BubbleBoxRendererProps } from '@opentiny/tiny-robot'
defineProps<BubbleBoxRendererProps>()
</script>
<template>
<div class="custom-box" :data-placement="placement" :data-shape="shape">
<slot />
</div>
</template>
1.6 通过 BubbleProvider 配置全局渲染器
vue
<template>
<tr-bubble-provider
:box-renderer-matches="boxMatches"
:content-renderer-matches="contentMatches"
>
<tr-bubble-list :messages="messages" :role-configs="roles" />
</tr-bubble-provider>
</template>
<script setup lang="ts">
import { markRaw } from 'vue'
import { BubbleRendererMatchPriority, TrBubbleProvider } from '@opentiny/tiny-robot'
const boxMatches = [
{
find: (messages) => messages[0]?.content?.includes('VIP'),
renderer: markRaw(TransparentBoxRenderer),
priority: BubbleRendererMatchPriority.NORMAL,
},
]
const contentMatches = [
{
find: (message) => message.content?.includes('VIP'),
renderer: markRaw(CustomContentRenderer),
priority: BubbleRendererMatchPriority.NORMAL,
},
]
</script>
关键提醒 :使用
markRaw包装渲染器组件,避免 Vue 的响应式处理。
二、状态管理机制
2.1 state 属性设计
Bubble 消息的 state 属性专门用于存储 UI 相关数据,不会影响消息内容:
typescript
interface BubbleMessage<S extends Record<string, unknown> = Record<string, unknown>> {
role?: string
content?: ChatMessageContent
state?: S // UI 状态数据,不影响 content
}
设计原则 :为了不修改源数据的内部内容和结构,所有 UI 相关的状态都应该放在 state 中,而不是修改 content。
2.2 state-change 事件
当状态变化时,Bubble 触发 state-change 事件:
typescript
interface StateChangePayload {
key: string // 状态键名
value: unknown // 状态值
messageIndex: number // 消息索引
contentIndex: number // 内容索引
}
实战示例------管理工具调用的展开/收起状态:
vue
<template>
<tr-bubble
:content="message.content"
:tool_calls="toolCalls"
:state="state"
@state-change="handleStateChange"
/>
</template>
<script setup lang="ts">
const state = ref({
toolCall: {
call_0: { status: 'running', open: true },
call_1: { status: 'success', open: false },
},
})
const handleStateChange = (payload) => {
if (payload.key === 'toolCall') {
state.value.toolCall = payload.value
}
}
</script>
2.3 BubbleProvider 的全局 store
BubbleProvider 还提供了 store 属性,用于在 BubbleList 和 Bubble 之间共享全局数据:
vue
<tr-bubble-provider :store="{ theme: 'dark', userSettings: {} }">
<tr-bubble-list :messages="messages" />
</tr-bubble-provider>
三、工具调用机制
3.1 消息层面的工具调用
Bubble 直接支持 OpenAI 风格的 tool_calls 字段:
typescript
interface ToolCall {
id: string
type: 'function' | string
function: {
name: string
arguments: string
}
}
消息携带 tool_calls 时,Bubble 会自动匹配 Tool/Tools 渲染器:
vue
<tr-bubble
content="我来帮您查询天气。"
:tool_calls="[
{ id: 'call_0', type: 'function', function: { name: 'get_weather', arguments: '{"city":"北京"}' } },
{ id: 'call_1', type: 'function', function: { name: 'get_weather', arguments: '{"city":"上海"}' } },
]"
:state="{ toolCall: { call_0: { status: 'running', open: true } } }"
/>
3.2 工具调用状态
state.toolCall[id].status 支持四种值:
| 状态 | 说明 | 渲染效果 |
|---|---|---|
running |
执行中 | 旋转加载图标 |
success |
成功 | 绿色勾选图标 |
failed |
失败 | 红色错误图标 |
cancelled |
取消 | 灰色取消图标 |
state.toolCall[id].open 控制工具调用详情面板的展开/收起。
3.3 Kit 层的 toolPlugin
渲染器负责"展示",而 Kit 层的 toolPlugin 负责"执行":
typescript
import { toolPlugin, useMessage } from '@opentiny/tiny-robot-kit'
useMessage({
responseProvider,
plugins: [
toolPlugin({
getTools: async () => tools, // 向 API 注入工具列表
callTool: async (toolCall) => { // 执行工具调用
// 执行逻辑...
return result
},
onToolCallStart: (toolCall, ctx) => {
// 工具开始执行时回调
},
onToolCallEnd: (toolCall, ctx) => {
// ctx.status: 'success' | 'failed' | 'cancelled'
},
}),
],
})
3.4 工具调用流程
用户提问 → API 返回 assistant 消息(含 tool_calls)
→ toolPlugin 解析 tool_calls
→ 为每个 tool_call 创建 tool 消息
→ callTool 执行 → 结果写入 tool 消息
→ 自动发起下一轮请求(带 tool 消息)
→ API 返回最终 assistant 回复
3.5 推理内容渲染
Bubble 还支持 reasoning_content 字段,配合 Thinking 渲染器展示 AI 的思考过程:
vue
<tr-bubble
content="最终回复内容"
reasoning_content="这是我的思考过程..."
:state="{ thinking: { open: true } }"
/>
在 Kit 层,thinkingPlugin 自动管理思考状态的展开/收起:
- 思考进行中 → 自动展开
- 思考结束 → 自动收起
四、架构总结
Bubble 的渲染架构遵循三大设计原则:
- 可插拔渲染器:Box 和 Content 双层分离,匹配规则动态选择,三层配置梯度
- 状态隔离 :UI 数据放在
state,不修改源数据内容 - 组件与 Kit 解耦:Bubble 只管渲染,useMessage + toolPlugin 只管逻辑
这种架构让 Bubble 既保持了组件的简洁性,又具备了强大的扩展能力。下一篇文章我们将深入底层,剖析 Bubble 消息气泡组件的核心技术原理。
TinyRobot 官网 :https://opentiny/tiny-robot GitHub 仓库 :github.com/opentiny/ti...