流式传输

延续 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 SDKuseChat 钩子实现 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 演示

下一步

  • 审阅更改指南:让用户在更改应用之前预览并批准更改。
  • AI Caret:在 AI 正在插入内容的位置显示光标。