> For the complete documentation index, see [llms.txt](https://docs.neuron-ai.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neuron-ai.dev/neuron-v3-zh/agent/streaming.md).

# 流式传输

流式传输使你能够在响应文本到达时就向用户展示分块内容，而不是傻傻地等待完整响应。你可以提供实时的 Agent 对话体验。

<figure><img src="/files/d61695d22f1797165ea6426a36e934e7d2b9cb11" alt=""><figcaption></figcaption></figure>

### Agent

要流式输出 AI 响应，你应该使用 `stream()` 方法，而不是 `chat()`。该方法会将 agent 工作流准备为使用 `StreamingNode` 而不是使用 `ChatNode`.

调用 `events()` 方法。在返回的 agent 处理器上，你会得到一个 PHP 生成器，可将流式内容作为可迭代对象来消费。

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;

$handler = MyAgent::make()->stream(new UserMessage('你好吗？'));

// 逐块实时打印响应
foreach ($handler->events() as $chunk) {
    echo $chunk->content;
}

// 我很好，谢谢！今天我能为你做些什么？
```

### 流式分块

当你处理 agent 的流式响应时，预计会收到三种类型的 chunk 对象：

* `TextChunk`：表示一段文本
* `ReasoningChunk`：包含模型推理摘要的分块（仅适用于推理模型）
* `ToolCallChunk`：表示 LLM 请求执行一个工具
* `ToolResultChunk`：包含工具执行结果

这些对象是在 agent 内部为完成任务而流转的底层消息与客户端侧为了解后台正在发生什么所需数据之间的一层抽象。

流的组成取决于你的 agent 实现。如果该 agent 没有附加任何工具，就不会收到 `ToolCallChunk` 或 `ToolResultChunk` 实例，因此你可以迭代输出流，并预期只会有文本和推理分块。

### 流式传输与工具

Neuron 支持将 Tools 和 Function calls 与流式响应结合使用。你可以自由为 Agents 提供 Tools，它们会在流的中间被自动处理，并继续生成最终响应。

当 agent 收到来自 LLM 的工具调用请求时，它会流式输出两种类型的 chunk： `ToolCallChunk`, `ToolResultChunk`.

这些类包含由 LLM 发起调用的工具实例，因此你可以向客户端展示有关 agent 正在做什么以回答用户提示的详细输出。

下面是一个如何处理这种场景的示例：

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Tools\Tool;

$handler = MyAgent::make()
    ->addTool(
        Tool::make(
            'get_server_configuration',
            '检索服务器网络配置'
         )->addProperty(...)->setCallable(...)
    )
    ->stream(
        new UserMessage("服务器的 IP 地址是什么？")
    );

// 迭代分块
foreach ($handler->events() as $chunk) {
    if ($chunk instanceof ToolCallChunk) {
        // 输出正在进行中的工具调用
        echo "\n- 正在调用工具：".$chunk->tool->getName();
        echo "\n- 输入：".json_encode($chunk->tool->getInputs());
        continue;
    }
    
    if ($chunk instanceof ToolResultChunk) {
        echo "\n- 工具 ".$chunk->tool->getName()." 已完成";
        echo "\n- 结果：".$chunk->tool->getResult();
        continue;
    }
    
    // 处理 TextChunk 和 ReasoningChunk
    echo $chunk->content;
}

// 让我来检索服务器配置。 
// - 正在调用工具：get_server_configuration
// - 工具 get_server_configuration 已完成
// - 服务器的 IP 地址是：192.168.0.10
```

### 获取最终结果

当模型完成流式输出后，你可以通过 `AssistantMessage` 实例与 `getMessage()` 方法从 workflow 处理器中获取最终

```php
$handler = MyAgent::make()->stream(...);

// 迭代分块
foreach ($handler->events() as $chunk) {
    // ...
}

$message = $handler->getMessage(); // 获取最终消息实例
echo $message->getContent();
```

### 监控与调试

使用 Neuron 构建的许多应用程序会包含多个步骤以及对 LLM 的多次调用。随着这些应用程序变得越来越复杂，能够检查你的代理系统内部到底发生了什么就变得至关重要。实现这一点的最佳方式是使用 [Inspector](https://inspector.dev/).

{% embed url="<https://docs.inspector.dev/guides/neuron-ai>" %}

## 流适配器

Neuron 的流适配器系统提供了一种灵活、与协议无关的方式，帮助你轻松将由 Neuron 驱动的 agent 集成到你的前端技术栈中。

流适配器充当 Neuron 内部流式事件（文本分块、工具调用、推理步骤）与特定前端协议（如 Vercel AI SDK 或 AG-UI）之间的转换器。

你也可以接入适配器，将流式数据发送到外部传输层，例如 [Pusher](https://pusher.com/)，如果你希望将由后台运行的 agent 输出内容流式传输到 UI。

这种架构使你能够在不修改核心 agent 逻辑的情况下，将 Neuron agents 无缝集成到各种前端框架中。适配器负责处理协议相关的问题，例如消息生命周期事件、事件格式化和 ID 跟踪，同时在所有提供方（Anthropic、OpenAI、Gemini、Ollama 等）之间保持一致的流式行为。该系统具有很强的可扩展性，你可以通过扩展 `SSEAdapter` 来实现流式数据转换，或者直接实现 `StreamAdapterInterface` 以满足自定义需求。

<figure><img src="/files/47675371437628d2207448d69c2aab3c41b6ba25" alt=""><figcaption></figcaption></figure>

你只需要向用于流式输出 LLM 响应的 agent 处理器的 `events()` 方法提供一个适配器实例即可。

### AG-UI 适配器

实现了 AG-UI 协议定义的基于事件的流式协议，用于实时 agent-前端交互。支持文本消息、工具调用、推理和生命周期事件。

更多信息请访问： <https://docs.ag-ui.com/concepts/events>

```php
use NeuronAI\Chat\Messages\Stream\Adapters\AGUIAdapter;

// 指示 agent
$handler = MyAgent::make()
    ->stream(
        new UserMessage('144 的平方根是多少？')
    );

// 将适配器实例提供给 events() 方法
$stream = $handler->events(new AGUIAdapter());

// 处理响应
foreach ($stream as $line) {
    echo $line;
}
```

#### 连接 AG-UI 前端

AG-UI 客户端（如 CopilotKit）不只是打开一个连接。它会发送一个带有 JSON 请求体的 POST 请求，称为 `RunAgentInput`，其中包含对话内容和当前运行的标识符：

```json
{
  "threadId": "thread_123",
  "runId": "run_456",
  "messages": [
    {
      "id": "msg_1",
      "role": "user",
      "content": "144 的平方根是多少？"
    }
  ],
  "tools": [],
  "state": {},
  "context": [],
  "forwardedProps": {}
}
```

你的端点应读取此负载，将消息映射为 Neuron 消息对象，并将 `threadId` 和 `runId` 传递给适配器构造函数。适配器会在 `RUN_STARTED` 和 `RUN_FINISHED` 事件中将它们回传，这样客户端就能将流与其请求的运行关联起来。如果你省略它们，适配器会生成自己的标识符（这对测试很有用，但真正的 AG-UI 前端期望收到自己的 ID）。

适配器还会通过 `getHeaders()` 方法提供 SSE 传输所需的 HTTP 头。记得发送这些头，并在每一行之后刷新输出，否则流可能会卡在 PHP 输出缓冲区或代理中。

下面是一个完整的端点示例：

```php
use NeuronAI\Chat\Messages\Stream\Adapters\AGUIAdapter;
use NeuronAI\Chat\Messages\UserMessage;

// 解析 AG-UI RunAgentInput 负载
$input = json_decode(file_get_contents('php://input'), true);

$messages = [];
foreach ($input['messages'] as $message) {
    if ($message['role'] === 'user') {
        $messages[] = new UserMessage($message['content']);
    }
}

// 将客户端的 thread 和 run 标识符回显到流中
$adapter = new AGUIAdapter(
    threadId: $input['threadId'],
    runId: $input['runId'],
);

// 发送协议所需的 SSE 头
foreach ($adapter->getHeaders() as $name => $value) {
    header("{$name}: {$value}");
}

$stream = MyAgent::make()->stream($messages)->events($adapter);

foreach ($stream as $line) {
    echo $line;
    flush();
}
```

#### 发出的事件

该适配器会将 Neuron 的流式分块转换为以下 AG-UI 事件：

| Neuron 分块         | AG-UI 事件                                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| 运行生命周期            | `RUN_STARTED`, `RUN_FINISHED`                                                                                       |
| `TextChunk`       | `TEXT_MESSAGE_START`, `TEXT_MESSAGE_CONTENT`, `TEXT_MESSAGE_END`                                                    |
| `ReasoningChunk`  | `REASONING_START`, `REASONING_MESSAGE_START`, `REASONING_MESSAGE_CONTENT`, `REASONING_MESSAGE_END`, `REASONING_END` |
| `ToolCallChunk`   | `TOOL_CALL_START`, `TOOL_CALL_ARGS`, `TOOL_CALL_END`                                                                |
| `ToolResultChunk` | `TOOL_CALL_RESULT`                                                                                                  |

附加到 Neuron agent 的工具会在服务器上执行。客户端会通过 `TOOL_CALL_*` 事件获知正在进行的执行，并在 `TOOL_CALL_RESULT` 事件中接收到工具输出，随后是 agent 的最终文本消息。前端定义的工具（列在 `tools` 字段中的 `RunAgentInput` （由客户端执行的工具）不会由该适配器处理。

该适配器不会发出 AG-UI 共享状态事件（`STATE_SNAPSHOT`, `STATE_DELTA`, `MESSAGES_SNAPSHOT`），因此无法通过此适配器使用 AG-UI 客户端的状态同步功能。

### Vercel AI SDK 适配器

适用于 Vercel AI SDK Data Stream Protocol 的适配器： <https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol>

```php
use NeuronAI\Chat\Messages\Stream\Adapters\VercelAIAdapter;

// 指示 agent
$handler = MyAgent::make()
    ->stream(
        new UserMessage('144 的平方根是多少？')
    );

// 将适配器实例提供给 events() 方法
$stream = $handler->events(new VercelAIAdapter());

// 处理响应
foreach ($stream as $line) {
    echo $line;
}
```

### 自定义适配器

agent 处理器的 events() 方法接受 StreamAdapterInterface 的一个实例。因此你可以自由地用自定义实现来实现该接口，并将其传递给处理器。接口如下：

```php
interface StreamAdapterInterface
{
    /**
     * 将一个 Neuron 分块转换为特定协议的输出。
     *
     * @param object $chunk 任意 Neuron 分块（TextChunk、ToolCallChunk 等）
     * @return iterable<string> 一个或多个输出行/消息
     */
    public function transform(object $chunk): iterable;

    /**
     * 获取该协议的 HTTP 头。
     *
     * @return array<string, string>
     */
    public function getHeaders(): array;

    /**
     * 协议初始化序列（可选）。
     *
     * @return iterable<string>
     */
    public function start(): iterable;

    /**
     * 协议终止序列（可选）。
     *
     * @return iterable<string>
     */
    public function end(): iterable;
}
```

你总是可以从内置实现中获得灵感。
