> 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/zhi-neng-ti/streaming.md).

# 流式传输

实时向您的用户展示 AI 响应。

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

<figure><img src="https://content.gitbook.com/content/GHx4l2LknIex7vFIUg1R/blobs/IYATPZdfnIJTqksj9NAy/ChatGPT-stream.gif" alt=""><figcaption></figcaption></figure>

### Agent

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

调用 `events()` 返回的 agent handler 上的方法后，你会得到一个 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 支持将工具与函数调用结合流式响应一起使用。你可以自由地为 Agents 提供工具，它们会在流的中间自动处理，以继续生成最终响应。

当 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 地址是什么？")
    );

// 遍历 chunks
foreach ($handler->events() as $chunk) {
    if ($chunk instanceof ToolCallChunk) {
        // 输出正在进行的工具调用
        echo "\n- Calling tool: ".$chunk->tool->getName();
        echo "\n- Input: ".json_encode($chunk->tool->getInputs());
        continue;
    }
    
    if ($chunk instanceof ToolResultChunk) {
        echo "\n- Tool ".$chunk->tool->getName()." completed";
        echo "\n- Result: ".$chunk->tool->getResult();
        continue;
    }
    
    // 处理 TextChunk 和 ReasoningChunk
    echo $chunk->content;
}

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

### 获取最终结果

当模型完成流式输出后，你可以获取最终的 `AssistantMessage` 实例，使用 `getMessage()` 工作流 handler 上的方法：

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

// 遍历 chunks
foreach ($handler->events() as $chunk) {
    // ...
}

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

### 监控与调试

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

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

## 流适配器

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

流适配器会将 Neuron 的内部流式事件（文本分块、工具调用、推理步骤）转换为特定前端协议事件，例如 AG-UI。

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

<figure><img src="https://99736354-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGHx4l2LknIex7vFIUg1R%2Fuploads%2F0Vv4CSAloxu40vUoZddx%2Fstreaming-adapter.png?alt=media&amp;token=eb8846c3-eef5-415a-bb2e-108f1ecd0cf2" alt=""><figcaption></figcaption></figure>

你只需向 `events()` 方法提供一个适配器实例，该 agent handler 用于流式输出 LLM 响应。

### 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）并不只是打开一个连接。它会发送一个 POST 请求，带有一个名为 `RunAgentInput`的 JSON 请求体，其中包含对话内容和当前运行的标识符：

```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 handler 的 events() 方法接受一个 StreamAdapterInterface 实例。因此你可以自由地用自定义实现来实现此接口，并将其传递给 handler。接口如下所示：

```php
interface StreamAdapterInterface
{
    /**
     * 将 Neuron chunk 转换为特定协议输出。
     *
     * @param object $chunk 任何 Neuron chunk（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;
}
```

你总能从内置实现中获得灵感。
