🎁 100 free AI Toolkit licenses – apply by August 15.Learn more

流式传输

来自 AI 代理聊天机器人指南的延续

本指南接续 AI 代理聊天机器人 指南。请先阅读它。

启用 AI 工具包的工具流式传输功能,在 AI 生成内容的同时实时更新文档。

查看GitHub 上的源代码

关键更改

要将我们在上一份指南中构建的 AI 代理聊天机器人添加流式传输功能,我们需要将 executeTool 方法替换为 streamTool 方法。

首先,在工具调用流式传输期间,每当接收到新的流式部分时,您可以反复调用 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'})。

Complete Implementation

Here is the complete updated component including tool streaming:

'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/client-ai-toolkit'

export default function Page() {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [StarterKit, AiToolkit],
    content: `<h1>AI Agent Demo</h1><p>Please ask AI to improve this content.</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

      // When tool streaming is complete, we need to apply the tool call to the document
      // Use the AI toolkit to execute the tool
      const toolkit = getAiToolkit(editor)
      const result = toolkit.streamTool({
        toolCallId,
        toolName,
        input,
        // This parameter indicates that tool streaming has finished
        hasFinished: true,
      })

      addtoolOutput({ tool: toolName, toolCallId, output: result.output })
    },
  })

  const [input, setInput] = useState(
    'Insert a long story about Tiptap with 10 paragraphs at the end of the document',
  )

  // While tool streaming is in progress, we need to update the document as the tool input changes
  useEffect(() => {
    if (!editor) return

    // Find the last message
    const lastMessage = messages[messages.length - 1]
    if (!lastMessage) return

    // Find the last tool just called by AI
    const toolCallParts = lastMessage.parts.filter((p) => p.type.startsWith('tool-')) ?? []
    const lastToolCall = toolCallParts[toolCallParts.length - 1]
    if (!lastToolCall) return

    // Get the tool call data
    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-', '')

    // Apply the tool call to the document during streaming
    const toolkit = getAiToolkit(editor)
    toolkit.streamTool({
      toolCallId: part.toolCallId,
      toolName,
      input: part.input,
      // This parameter indicates that tool streaming has not yet finished
      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/client-ai-toolkit'

const editor = useEditor({
  extensions: [StarterKit, AiToolkit, AiCaret],
})

请参阅 AI Caret 指南 了解配置选项和 CSS 样式。

显示审阅界面

在审阅界面中显示更改,以便用户接受或拒绝这些更改。

有两种实现方式:

  • 使用 Tracked Changes 扩展来渲染审阅界面。更改会作为文档的一部分保留,并对其他用户可见。
  • AI Toolkit 建议:一种基于装饰的界面,具有临时性,仅对当前文档用户可见。

通过设置 reviewOptions 参数来配置审阅界面。每次调用 streamTool 方法时,此参数都应保持相同的值。

toolkit.streamTool({
  // ... 其他选项

  // 使用 Tracked Changes 显示审阅界面
  reviewOptions: { mode: 'trackedChanges' },

  // 使用 AI Toolkit 建议显示审阅界面
  reviewOptions: { mode: 'review' },
})

请参阅 审阅更改 指南,了解有关审阅界面的更多信息。

有关如何将 AI Toolkit 流式传输与审阅界面结合使用的示例,请参阅 AI Toolkit 演示

下一步