---
title: "流式传输"
description: "为您的 AI 代理聊天机器人添加实时流式传输，实时查看变化。"
canonical_url: "https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/agents/streaming"
---

# 流式传输

为您的 AI 代理聊天机器人添加实时流式传输，实时查看变化。

> **延续 AI 代理聊天机器人指南:**
>
> 本指南是对[AI 代理聊天机器人指南](https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/agents/ai-agent-chatbot.md)的延续。请先阅读该指南。

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

> **Interactive demo:** [tool streaming](https://ai-toolkit-demos.vercel.app/tool-streaming)

查看[GitHub 上的源代码](https://github.com/ueberdosis/ai-toolkit-demos)。

## 关键更改

要在我们之前构建的[AI 代理聊天机器人](https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/agents/ai-agent-chatbot.md)中添加流式传输，我们需要用 `streamTool` 方法替换 `executeTool` 方法。

首先，在工具调用流式传输期间，每当接收到新的流式部分时，您可以反复调用 `streamTool`，这将增量更新文档。

```tsx
const aiToolkit = getAiToolkit(editor)

const result = aiToolkit.streamTool({
  toolCallId: 'call_123',
  toolName,
  // 内容仍在流式传输，因此传递部分 JSON 对象
  input,
  // 此参数表示工具流式传输尚未完成
  hasFinished: false,
})
```

然后，当工具调用完成时，再次调用 `streamTool` 方法并传入 `hasFinished: true`，表示工具调用流式传输已结束。这将用最终内容更新文档。

```tsx
const result = aiToolkit.streamTool({
  toolCallId: 'call_123',
  toolName,
  // 流式传输完成，因此可以传递完整的 JSON 对象
  input,
  // 此参数表示工具流式传输已完成
  hasFinished: true,
})
```

要在我们之前构建的[AI 代理聊天机器人](https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/agents/ai-agent-chatbot.md)中实现此流程，请按照以下步骤：

### 1. 处理流式更新

添加一个 `useEffect` 钩子，在工具调用进行中处理流式更新。在此钩子内，每次接收到新的流式部分时，都会调用 `streamTool`。

```tsx
// 在工具流式传输进行时，我们需要随着工具输入的变化更新文档
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](https://ai-sdk.vercel.app/) 的 `useChat` 钩子实现 AI 代理聊天机器人。该钩子包含一个 `onToolCall` 事件处理器，当工具调用流式传输完成时触发。

在此处理器中，我们调用 `streamTool` 并传入 `hasFinished: true`，表示工具调用流式传输已完成。

```tsx
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'}`）。

## 完整实现

以下是包含工具流式传输的完整更新组件：

```tsx
'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 生成内容的变化。试试看：

> **Interactive demo:** [tool streaming](https://ai-toolkit-demos.vercel.app/tool-streaming)

查看[GitHub 上的源代码](https://github.com/ueberdosis/ai-toolkit-demos)。

## 下一步

您可以添加 `AiCaret` 扩展，以显示一个光标，指示 AI 在流式传输期间正在何处插入内容。这会为用户提供实时的视觉反馈。

```tsx
import { AiCaret, AiToolkit, getAiToolkit } from '@tiptap-pro/ai-toolkit'

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

有关配置选项和 CSS 样式，请参阅 [AI Caret 指南](https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/advanced-guides/ai-caret.md)。

## 显示审阅界面

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

有两种实现方式：

- 使用 [**Tracked Changes**](https://tiptap.zhcndoc.com/tracked-changes/getting-started/overview.md) 扩展来渲染审阅界面。更改会作为文档的一部分保留，并对其他用户可见。
- **AI Toolkit 建议**：一种基于装饰的界面，具有临时性，仅对当前文档用户可见。

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

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

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

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

要了解更多关于审阅界面的信息，请参阅[审阅更改](https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/agents/review-changes.md)指南。

有关如何将 AI Toolkit 流式传输与审阅界面结合使用的示例，请参阅 [AI Toolkit 演示](https://github.com/ueberdosis/ai-toolkit-demos)。

## 下一步

- [审阅更改指南](https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/agents/review-changes.md)：让用户在更改应用之前预览并批准更改。
- [AI Caret](https://tiptap.zhcndoc.com/content-ai/capabilities/ai-toolkit/advanced-guides/ai-caret.md)：在 AI 正在插入内容的位置显示光标。
