流式传输
延续 AI 代理聊天机器人指南
本指南是对AI 代理聊天机器人指南的延续。请先阅读该指南。
启用 AI 工具包的工具流式传输功能,在 AI 生成内容的同时实时更新文档。
查看GitHub 上的源代码。
关键更改
要在我们之前构建的AI 代理聊天机器人中添加流式传输,我们需要用 streamTool 方法替换 executeTool 方法。
首先,在工具调用流式传输期间,每当接收到新的流式部分时,您可以反复调用 streamTool,这将增量更新文档。
const aiToolkit = getAiToolkit(editor)
const result = aiToolkit.streamTool({
toolCallId: 'call_123',
toolName,
// 内容仍在流式传输,因此传递部分 JSON 对象
input,
// 此参数表示工具流式传输尚未完成
hasFinished: false,
})然后,当工具调用完成时,再次调用 streamTool 方法并传入 hasFinished: true,表示工具调用流式传输已结束。这将用最终内容更新文档。
const result = aiToolkit.streamTool({
toolCallId: 'call_123',
toolName,
// 流式传输完成,因此可以传递完整的 JSON 对象
input,
// 此参数表示工具流式传输已完成
hasFinished: true,
})要在我们之前构建的AI 代理聊天机器人中实现此流程,请按照以下步骤:
1. 处理流式更新
添加一个 useEffect 钩子,在工具调用进行中处理流式更新。在此钩子内,每次接收到新的流式部分时,都会调用 streamTool。
// 在工具流式传输进行时,我们需要随着工具输入的变化更新文档
useEffect(() => {
if (!editor) return
// 找到最后一条消息
const lastMessage = messages[messages.length - 1]
if (!lastMessage) return
// 找到 AI 刚调用的最后一个工具
const toolCallParts = lastMessage.parts.filter((p) => p.type.startsWith('tool-')) ?? []
const lastToolCall = toolCallParts[toolCallParts.length - 1]
if (!lastToolCall) return
// 获取工具调用数据
interface ToolStreamingPart {
input: unknown
state: string
toolCallId: string
type: string
}
const part = lastToolCall as ToolStreamingPart
if (!(part.state === 'input-streaming')) return
const toolName = part.type.replace('tool-', '')
// 在流式传输过程中将工具调用应用到文档
const toolkit = getAiToolkit(editor)
toolkit.streamTool({
toolCallId: part.toolCallId,
toolName,
input: part.input,
// 此参数表示工具流式传输尚未完成
hasFinished: false,
})
}, [addtoolOutput, editor, messages])2. 处理流式完成
在我们的演示中,使用来自 Vercel AI SDK 的 useChat 钩子实现 AI 代理聊天机器人。该钩子包含一个 onToolCall 事件处理器,当工具调用流式传输完成时触发。
在此处理器中,我们调用 streamTool 并传入 hasFinished: true,表示工具调用流式传输已完成。
const { messages, sendMessage, addtoolOutput } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
async onToolCall({ toolCall }) {
if (!editor) return
const { toolName, input, toolCallId } = toolCall
// 使用 AI 工具包来流式传输工具
const toolkit = getAiToolkit(editor)
const result = toolkit.streamTool({
toolCallId,
toolName,
input,
// 此参数表示工具流式传输已完成
hasFinished: true,
})
addtoolOutput({ tool: toolName, toolCallId, output: result.output })
},
})在所有 streamTool 调用中传递相同属性
应当在所有 streamTool 调用中传递相同的属性值。例如,在第一次对 streamTool 方法的调用中,如果向 reviewOptions 参数传递了 {mode: 'preview'},那么在随后的所有 streamTool 调用中也应传递相同的值({mode: 'preview'})。
完整实现
以下是包含工具流式传输的完整更新组件:
'use client'
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai'
import { useChat } from '@ai-sdk/react'
import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { useEffect, useState } from 'react'
import { AiToolkit, getAiToolkit } from '@tiptap-pro/ai-toolkit'
export default function Page() {
const editor = useEditor({
immediatelyRender: false,
extensions: [StarterKit, AiToolkit],
content: `<h1>AI 代理演示</h1><p>请让 AI 改进这段内容。</p>`,
})
const { messages, sendMessage, addtoolOutput } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
async onToolCall({ toolCall }) {
if (!editor) return
const { toolName, input, toolCallId } = toolCall
// 当工具流式传输完成时,我们需要将工具调用应用到文档
// 使用 AI 工具包执行工具
const toolkit = getAiToolkit(editor)
const result = toolkit.streamTool({
toolCallId,
toolName,
input,
// 此参数表示工具流式传输已完成
hasFinished: true,
})
addtoolOutput({ tool: toolName, toolCallId, output: result.output })
},
})
const [input, setInput] = useState(
'在文档末尾插入一个包含 10 段关于 Tiptap 的长故事',
)
// 在工具流式传输进行时,我们需要随着工具输入的变化更新文档
useEffect(() => {
if (!editor) return
// 找到最后一条消息
const lastMessage = messages[messages.length - 1]
if (!lastMessage) return
// 找到 AI 刚调用的最后一个工具
const toolCallParts = lastMessage.parts.filter((p) => p.type.startsWith('tool-')) ?? []
const lastToolCall = toolCallParts[toolCallParts.length - 1]
if (!lastToolCall) return
// 获取工具调用数据
interface ToolStreamingPart {
input: unknown
state: string
toolCallId: string
type: string
}
const part = lastToolCall as ToolStreamingPart
if (!(part.state === 'input-streaming')) return
const toolName = part.type.replace('tool-', '')
// 在流式传输过程中将工具调用应用到文档
const toolkit = getAiToolkit(editor)
toolkit.streamTool({
toolCallId: part.toolCallId,
toolName,
input: part.input,
// 此参数表示工具流式传输尚未完成
hasFinished: false,
})
}, [addtoolOutput, editor, messages])
if (!editor) return null
return (
<div>
<EditorContent editor={editor} />
{messages?.map((message) => (
<div key={message.id} style={{ whiteSpace: 'pre-wrap' }}>
<strong>{message.role}</strong>
<br />
{message.parts
.filter((p) => p.type === 'text')
.map((p) => p.text)
.join('\n')}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault()
sendMessage({ text: input })
setInput('')
}}
>
<input value={input} onChange={(e) => setInput(e.target.value)} />
</form>
</div>
)
}最终效果
借助工具流式传输,用户可以实时看到 AI 生成内容的变化。试试看:
查看GitHub 上的源代码。
下一步
您可以添加 AiCaret 扩展,以显示一个光标,指示 AI 在流式传输期间正在何处插入内容。这会为用户提供实时的视觉反馈。
import { AiCaret, AiToolkit, getAiToolkit } from '@tiptap-pro/ai-toolkit'
const editor = useEditor({
extensions: [StarterKit, AiToolkit, AiCaret],
})有关配置选项和 CSS 样式,请参阅 AI Caret 指南。
显示审阅界面
在审阅界面中显示更改,以便用户接受或拒绝这些更改。
有两种实现方式:
- 使用 Tracked Changes 扩展来渲染审阅界面。更改会作为文档的一部分保留,并对其他用户可见。
- AI Toolkit 建议:一种基于装饰的界面,具有临时性,仅对当前文档用户可见。
通过设置 reviewOptions 参数来配置审阅界面。每次调用 streamTool 方法时,此参数都应保持相同的值。
toolkit.streamTool({
// ... 其他选项
// 使用 Tracked Changes 显示审阅界面
reviewOptions: { mode: 'trackedChanges' },
// 使用 AI Toolkit 建议显示审阅界面
reviewOptions: { mode: 'review' },
})要了解更多关于审阅界面的信息,请参阅审阅更改指南。
有关如何将 AI Toolkit 流式传输与审阅界面结合使用的示例,请参阅 AI Toolkit 演示。