Pi Monorepo Stream Event Flow 深度分析

本文档深入分析 agent.tsagent-loop.tsopenai-completions.ts 三个核心文件中异步迭代器 Stream 的完整时序,以及所有 Event 的生产消费流程。


概览:三层 Stream 架构

css 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Stream 三层架构                                       │
│                                                                             │
│  Layer 3: Agent._runLoop()                                                  │
│           consumes EventStream<AgentEvent, AgentMessage[]>                  │
│           ┌──────────────────────────────────────────────────────────┐      │
│           │  for await (const event of stream) {                     │      │
│           │    // 更新内部状态,转发给 subscribers                      │      │
│           │  }                                                       │      │
│           └──────────────────────────────────────────────────────────┘      │
│                              ▲ consumes                                     │
│                              │                                              │
│  Layer 2: agentLoop() / runLoop()                                           │
│           produces EventStream<AgentEvent, AgentMessage[]>                  │
│           ┌──────────────────────────────────────────────────────────┐      │
│           │  stream.push({ type: "agent_start" })                    │      │
│           │  stream.push({ type: "turn_start" })                     │      │
│           │  await streamAssistantResponse()  ← 调用 Layer 1         │      │
│           │  await executeToolCalls()                                │      │
│           │  stream.push({ type: "agent_end", messages })            │      │
│           │  stream.end(newMessages)                                 │      │
│           └──────────────────────────────────────────────────────────┘      │
│                              ▲ consumes                                     │
│                              │                                              │
│  Layer 1: streamOpenAICompletions()                                         │
│           produces AssistantMessageEventStream                              │
│           ┌──────────────────────────────────────────────────────────┐      │
│           │  stream.push({ type: "start", partial })                 │      │
│           │  for await (const chunk of openaiStream) {               │      │
│           │    stream.push({ type: "text_delta", ... })              │      │
│           │    stream.push({ type: "thinking_delta", ... })          │      │
│           │    stream.push({ type: "toolcall_delta", ... })          │      │
│           │  }                                                       │      │
│           │  stream.push({ type: "done", message })                  │      │
│           │  stream.end()                                            │      │
│           └──────────────────────────────────────────────────────────┘      │
│                              ▲ consumes                                     │
│                              │                                              │
│  Layer 0: OpenAI SDK                                                        │
│           produces AsyncIterable<ChatCompletionChunk>                       │
│           ┌──────────────────────────────────────────────────────────┐      │
│           │  for await (const chunk of openaiStream) { ... }         │      │
│           └──────────────────────────────────────────────────────────┘      │
└─────────────────────────────────────────────────────────────────────────────┘

Layer 1: OpenAI Completions Provider

1.1 AssistantMessageEventStream 创建

typescript 复制代码
// packages/ai/src/providers/openai-completions.ts:60-65
export const streamOpenAICompletions: StreamFunction<...> = (model, context, options) => {
  const stream = new AssistantMessageEventStream();  // 创建空流
  
  // 异步立即返回,内部 IIFE 填充事件
  (async () => {
    // ... 事件生产逻辑
  })();
  
  return stream;  // 调用者立即拿到 stream 引用
};

关键设计 :Provider 函数同步返回 AssistantMessageEventStream,实际的事件推送在异步 IIFE 中进行。调用者通过 for await 消费时,会自动等待事件到达。

1.2 OpenAI SDK 层迭代

typescript 复制代码
// packages/ai/src/providers/openai-completions.ts:94
const openaiStream = await client.chat.completions.create(params, { signal: options?.signal });

openaiStream 是 OpenAI SDK 返回的 AsyncIterable<ChatCompletionChunk>,代表 HTTP SSE 连接。

typescript 复制代码
// packages/ai/src/providers/openai-completions.ts:129-266
for await (const chunk of openaiStream) {
  // chunk 结构:
  // {
  //   id: "chatcmpl-xxx",
  //   choices: [{
  //     delta: { content?: string, tool_calls?: [...] },
  //     finish_reason: "stop" | "tool_calls" | null
  //   }],
  //   usage: { prompt_tokens, completion_tokens, ... }
  // }
}

1.3 事件类型与推送时序

css 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                OpenAI Completions Event 推送时序                              │
│                                                                             │
│  HTTP SSE 连接建立                                                          │
│       │                                                                     │
│       ▼                                                                     │
│  stream.push({ type: "start", partial: output })  ← 初始化空的 partial      │
│       │                                                                     │
│       ▼                                                                     │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ for await (chunk of openaiStream)                                    │   │
│  │                                                                      │   │
│  │   chunk.choices[0].delta.content 有内容?                             │   │
│  │     ├─ 是 → 新文本块或追加到当前块                                    │   │
│  │     │      ├─ 新块: push({ type: "text_start", contentIndex, partial })│  │
│  │     │      └─ 追加: push({ type: "text_delta", contentIndex, delta, partial })│
│  │     │                                                                │   │
│  │   chunk.choices[0].delta.reasoning_content 有内容?                   │   │
│  │     ├─ 是 → 新思维块或追加到当前块                                    │   │
│  │     │      ├─ 新块: push({ type: "thinking_start", ... })           │   │
│  │     │      └─ 追加: push({ type: "thinking_delta", ... })           │   │
│  │     │                                                                │   │
│  │   chunk.choices[0].delta.tool_calls 有内容?                         │   │
│  │     ├─ 是 → 新工具调用块或追加到当前块                                │   │
│  │     │      ├─ 新块: push({ type: "toolcall_start", ... })           │   │
│  │     │      └─ 追加: push({ type: "toolcall_delta", ... })           │   │
│  │     │                                                                │   │
│  │   chunk.usage 存在?                                                  │   │
│  │     └─ 是 → output.usage = parseChunkUsage(chunk.usage, model)      │   │
│  │                                                                      │   │
│  │   chunk.choices[0].finish_reason 存在?                               │   │
│  │     └─ 是 → output.stopReason = mapStopReason(finish_reason)        │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│       │                                                                     │
│       ▼                                                                     │
│  finishCurrentBlock(currentBlock)  ← 发送 *_end 事件                        │
│       │                                                                     │
│       ▼                                                                     │
│  stream.push({ type: "done", reason, message: output })                     │
│  stream.end()                                                               │
│                                                                             │
│  ─────────────────────────────────────────────────────────────────────────  │
│  异常路径:                                                                   │
│       │                                                                     │
│       ▼                                                                     │
│  stream.push({ type: "error", reason, error: output })                      │
│  stream.end()                                                               │
└─────────────────────────────────────────────────────────────────────────────┘

1.4 完整事件类型定义

typescript 复制代码
// packages/ai/src/types.ts - AssistantMessageEvent 联合类型
type AssistantMessageEvent =
  | { type: "start"; partial: AssistantMessage }
  | { type: "text_start"; contentIndex: number; partial: AssistantMessage }
  | { type: "text_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
  | { type: "text_end"; contentIndex: number; content: string; partial: AssistantMessage }
  | { type: "thinking_start"; contentIndex: number; partial: AssistantMessage }
  | { type: "thinking_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
  | { type: "thinking_end"; contentIndex: number; content: string; partial: AssistantMessage }
  | { type: "toolcall_start"; contentIndex: number; partial: AssistantMessage }
  | { type: "toolcall_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
  | { type: "toolcall_end"; contentIndex: number; toolCall: ToolCall; partial: AssistantMessage }
  | { type: "done"; reason: StopReason; message: AssistantMessage }
  | { type: "error"; reason: StopReason; error: AssistantMessage }

1.5 partial 对象的演进

typescript 复制代码
// 初始 partial (在 "start" 事件时创建)
const output: AssistantMessage = {
  role: "assistant",
  content: [],           // 空,后续事件会追加
  api: model.api,
  provider: model.provider,
  model: model.id,
  usage: { input: 0, output: 0, ... },
  stopReason: "stop",
  timestamp: Date.now(),
};

// 每个 delta 事件时,output.content 被原地修改
output.content.push({ type: "text", text: "" });  // text_start
output.content[0].text += delta;                   // text_delta
// ...以此类推

// 最终 message (在 "done" 事件时)
// output 已被完全填充

关键点partial 是同一个对象的引用,每次 push 时传递的是同一对象的快照引用。消费者看到的 partial 内容取决于消费时机。


Layer 2: Agent Loop

2.1 EventStream 创建

typescript 复制代码
// packages/agent/src/agent-loop.ts:94-99
function createAgentStream(): EventStream<AgentEvent, AgentMessage[]> {
  return new EventStream<AgentEvent, AgentMessage[]>(
    (event: AgentEvent) => event.type === "agent_end",  // 终止条件
    (event: AgentEvent) => (event.type === "agent_end" ? event.messages : []),  // 结果提取
  );
}

2.2 agentLoop 入口

typescript 复制代码
// packages/agent/src/agent-loop.ts:28-55
export function agentLoop(prompts, context, config, signal, streamFn) {
  const stream = createAgentStream();

  (async () => {
    const newMessages: AgentMessage[] = [...prompts];
    const currentContext: AgentContext = {
      ...context,
      messages: [...context.messages, ...prompts],  // 追加用户消息
    };

    // 发送会话启动事件
    stream.push({ type: "agent_start" });
    stream.push({ type: "turn_start" });
    
    // 为每个用户消息发送 message_start/end
    for (const prompt of prompts) {
      stream.push({ type: "message_start", message: prompt });
      stream.push({ type: "message_end", message: prompt });
    }

    // 进入主循环
    await runLoop(currentContext, newMessages, config, signal, stream, streamFn);
  })();

  return stream;  // 立即返回,异步填充
}

2.3 runLoop 双层循环

typescript 复制代码
// packages/agent/src/agent-loop.ts:104-198
async function runLoop(currentContext, newMessages, config, signal, stream, streamFn) {
  let firstTurn = true;
  let pendingMessages: AgentMessage[] = (await config.getSteeringMessages?.()) || [];

  // ========== 外层循环:处理 follow-up 消息 ==========
  while (true) {
    let hasMoreToolCalls = true;
    let steeringAfterTools: AgentMessage[] | null = null;

    // ========== 内层循环:处理工具调用 + steering 消息 ==========
    while (hasMoreToolCalls || pendingMessages.length > 0) {
      if (!firstTurn) {
        stream.push({ type: "turn_start" });
      } else {
        firstTurn = false;
      }

      // [1] 注入 pending 消息(steering 或 follow-up)
      if (pendingMessages.length > 0) {
        for (const message of pendingMessages) {
          stream.push({ type: "message_start", message });
          stream.push({ type: "message_end", message });
          currentContext.messages.push(message);
          newMessages.push(message);
        }
        pendingMessages = [];
      }

      // [2] 调用 LLM(关键!消费 Layer 1 的 Stream)
      const message = await streamAssistantResponse(currentContext, config, signal, stream, streamFn);
      newMessages.push(message);

      // [3] 检查错误/中止
      if (message.stopReason === "error" || message.stopReason === "aborted") {
        stream.push({ type: "turn_end", message, toolResults: [] });
        stream.push({ type: "agent_end", messages: newMessages });
        stream.end(newMessages);
        return;  // 提前退出
      }

      // [4] 检查工具调用
      const toolCalls = message.content.filter((c) => c.type === "toolCall");
      hasMoreToolCalls = toolCalls.length > 0;

      // [5] 执行工具调用
      const toolResults: ToolResultMessage[] = [];
      if (hasMoreToolCalls) {
        const toolExecution = await executeToolCalls(
          currentContext.tools, message, signal, stream, config.getSteeringMessages
        );
        toolResults.push(...toolExecution.toolResults);
        steeringAfterTools = toolExecution.steeringMessages ?? null;

        for (const result of toolResults) {
          currentContext.messages.push(result);
          newMessages.push(result);
        }
      }

      stream.push({ type: "turn_end", message, toolResults });

      // [6] 检查 steering 消息(用户中断)
      if (steeringAfterTools && steeringAfterTools.length > 0) {
        pendingMessages = steeringAfterTools;
        steeringAfterTools = null;
      } else {
        pendingMessages = (await config.getSteeringMessages?.()) || [];
      }
    }

    // [7] 检查 follow-up 消息(Agent 完成后注入)
    const followUpMessages = (await config.getFollowUpMessages?.()) || [];
    if (followUpMessages.length > 0) {
      pendingMessages = followUpMessages;
      continue;  // 继续外层循环
    }

    // 无更多消息,退出
    break;
  }

  stream.push({ type: "agent_end", messages: newMessages });
  stream.end(newMessages);
}

2.4 streamAssistantResponse:跨层消费

这是 Layer 1 → Layer 2 的桥梁:

typescript 复制代码
// packages/agent/src/agent-loop.ts:204-289
async function streamAssistantResponse(context, config, signal, stream, streamFn) {
  // [1] 可选:上下文变换(剪枝/注入外部知识)
  let messages = context.messages;
  if (config.transformContext) {
    messages = await config.transformContext(messages, signal);
  }

  // [2] 转换为 LLM 兼容格式
  const llmMessages = await config.convertToLlm(messages);

  // [3] 构建 LLM Context
  const llmContext: Context = {
    systemPrompt: context.systemPrompt,
    messages: llmMessages,
    tools: context.tools,
  };

  // [4] 获取 stream 函数(默认 streamSimple)
  const streamFunction = streamFn || streamSimple;

  // [5] 调用 Layer 1,获取 AssistantMessageEventStream
  const response = await streamFunction(config.model, llmContext, {
    ...config,
    apiKey: resolvedApiKey,
    signal,
  });

  let partialMessage: AssistantMessage | null = null;
  let addedPartial = false;

  // [6] ========== 消费 Layer 1 的 Stream ==========
  for await (const event of response) {
    switch (event.type) {
      case "start":
        partialMessage = event.partial;
        context.messages.push(partialMessage);  // 立即加入上下文!
        addedPartial = true;
        stream.push({ type: "message_start", message: { ...partialMessage } });
        break;

      case "text_start":
      case "text_delta":
      case "text_end":
      case "thinking_start":
      case "thinking_delta":
      case "thinking_end":
      case "toolcall_start":
      case "toolcall_delta":
      case "toolcall_end":
        if (partialMessage) {
          partialMessage = event.partial;
          // 原地更新上下文中的 partial message
          context.messages[context.messages.length - 1] = partialMessage;
          stream.push({
            type: "message_update",
            assistantMessageEvent: event,
            message: { ...partialMessage },
          });
        }
        break;

      case "done":
      case "error": {
        const finalMessage = await response.result();
        if (addedPartial) {
          context.messages[context.messages.length - 1] = finalMessage;
        } else {
          context.messages.push(finalMessage);
        }
        if (!addedPartial) {
          stream.push({ type: "message_start", message: { ...finalMessage } });
        }
        stream.push({ type: "message_end", message: finalMessage });
        return finalMessage;
      }
    }
  }

  return await response.result();
}

关键设计partial messagestart 事件时就立即加入 context.messages,后续 delta 事件直接原地更新。这确保了:

  1. 如果发生中断,上下文中已有部分响应
  2. 工具调用参数在流式过程中就可被其他代码访问

2.5 executeToolCalls:工具执行与中断检测

typescript 复制代码
// packages/agent/src/agent-loop.ts:294-378
async function executeToolCalls(tools, assistantMessage, signal, stream, getSteeringMessages) {
  const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
  const results: ToolResultMessage[] = [];
  let steeringMessages: AgentMessage[] | undefined;

  for (let index = 0; index < toolCalls.length; index++) {
    const toolCall = toolCalls[index];
    const tool = tools?.find((t) => t.name === toolCall.name);

    // 发送工具执行开始事件
    stream.push({
      type: "tool_execution_start",
      toolCallId: toolCall.id,
      toolName: toolCall.name,
      args: toolCall.arguments,
    });

    let result: AgentToolResult<any>;
    let isError = false;

    try {
      if (!tool) throw new Error(`Tool ${toolCall.name} not found`);

      // 参数验证
      const validatedArgs = validateToolArguments(tool, toolCall);

      // 执行工具,支持流式进度更新
      result = await tool.execute(toolCall.id, validatedArgs, signal, (partialResult) => {
        stream.push({
          type: "tool_execution_update",
          toolCallId: toolCall.id,
          toolName: toolCall.name,
          args: toolCall.arguments,
          partialResult,
        });
      });
    } catch (e) {
      result = { content: [{ type: "text", text: e.message }], details: {} };
      isError = true;
    }

    // 发送工具执行结束事件
    stream.push({
      type: "tool_execution_end",
      toolCallId: toolCall.id,
      toolName: toolCall.name,
      result,
      isError,
    });

    // 构建 ToolResultMessage
    const toolResultMessage: ToolResultMessage = {
      role: "toolResult",
      toolCallId: toolCall.id,
      toolName: toolCall.name,
      content: result.content,
      details: result.details,
      isError,
      timestamp: Date.now(),
    };

    results.push(toolResultMessage);
    stream.push({ type: "message_start", message: toolResultMessage });
    stream.push({ type: "message_end", message: toolResultMessage });

    // ========== 关键:检查 steering 消息 ==========
    if (getSteeringMessages) {
      const steering = await getSteeringMessages();
      if (steering.length > 0) {
        steeringMessages = steering;
        // 跳过剩余工具调用!
        const remainingCalls = toolCalls.slice(index + 1);
        for (const skipped of remainingCalls) {
          results.push(skipToolCall(skipped, stream));  // 发送 skip 事件
        }
        break;  // 跳出工具执行循环
      }
    }
  }

  return { toolResults: results, steeringMessages };
}

Layer 3: Agent Class

3.1 _runLoop:最终消费

typescript 复制代码
// packages/agent/src/agent.ts:413-560
private async _runLoop(messages?: AgentMessage[], options?) {
  // ...

  try {
    // 获取 Layer 2 的 Stream
    const stream = messages
      ? agentLoop(messages, context, config, this.abortController.signal, this.streamFn)
      : agentLoopContinue(context, config, this.abortController.signal, this.streamFn);

    // ========== 消费 Layer 2 的 Stream ==========
    for await (const event of stream) {
      // 更新内部状态
      switch (event.type) {
        case "message_start":
          partial = event.message;
          this._state.streamMessage = event.message;
          break;

        case "message_update":
          partial = event.message;
          this._state.streamMessage = event.message;
          break;

        case "message_end":
          partial = null;
          this._state.streamMessage = null;
          this.appendMessage(event.message);  // 持久化到历史
          break;

        case "tool_execution_start": {
          const s = new Set(this._state.pendingToolCalls);
          s.add(event.toolCallId);
          this._state.pendingToolCalls = s;
          break;
        }

        case "tool_execution_end": {
          const s = new Set(this._state.pendingToolCalls);
          s.delete(event.toolCallId);
          this._state.pendingToolCalls = s;
          break;
        }

        case "turn_end":
          if (event.message.role === "assistant" && (event.message as any).errorMessage) {
            this._state.error = (event.message as any).errorMessage;
          }
          break;

        case "agent_end":
          this._state.isStreaming = false;
          this._state.streamMessage = null;
          break;
      }

      // 转发给所有订阅者
      this.emit(event);
    }

    // 处理残留的 partial message
    if (partial && partial.role === "assistant" && partial.content.length > 0) {
      // ...
    }
  } catch (err: any) {
    // 错误处理:构造错误消息
    const errorMsg: AgentMessage = { /* ... */ };
    this.appendMessage(errorMsg);
    this._state.error = err?.message;
    this.emit({ type: "agent_end", messages: [errorMsg] });
  } finally {
    this._state.isStreaming = false;
    this._state.streamMessage = null;
    this._state.pendingToolCalls = new Set<string>();
    this.abortController = undefined;
    this.resolveRunningPrompt?.();
  }
}

3.2 subscribe:事件订阅

typescript 复制代码
// packages/agent/src/agent.ts:210-213
subscribe(fn: (e: AgentEvent) => void): () => void {
  this.listeners.add(fn);
  return () => this.listeners.delete(fn);  // 返回取消订阅函数
}

// packages/agent/src/agent.ts:563-566
private emit(e: AgentEvent) {
  for (const listener of this.listeners) {
    listener(e);
  }
}

完整时序图

yaml 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    完整 Event 时序图(单次 LLM 调用 + 工具执行)               │
│                                                                             │
│  时间轴 ──────────────────────────────────────────────────────────────────► │
│                                                                             │
│  Layer 2                          Layer 1                         Layer 0    │
│  agentLoop()                      streamOpenAICompletions()       OpenAI SDK │
│       │                                │                              │      │
│       │ push: agent_start              │                              │      │
│       │ push: turn_start               │                              │      │
│       │ push: message_start(user)      │                              │      │
│       │ push: message_end(user)        │                              │      │
│       │                                │                              │      │
│       │ ───streamAssistantResponse───► │                              │      │
│       │                                │ create: AssistantMsgStream   │      │
│       │                                │ ───await client.chat.create─►│      │
│       │                                │                              │      │
│       │                                │ ◄─── SSE stream established ─┤      │
│       │                                │                              │      │
│       │                                │ push: start                  │      │
│       │ ◄─── push: message_start ──────┤                              │      │
│       │                                │                              │      │
│       │                                │ ◄─── chunk: delta.content ───┤      │
│       │                                │ push: text_start             │      │
│       │ ◄─── push: message_update ─────┤ push: text_delta             │      │
│       │                                │                              │      │
│       │                                │ ◄─── chunk: delta.content ───┤      │
│       │ ◄─── push: message_update ─────┤ push: text_delta             │      │
│       │                                │                              │      │
│       │                                │ ◄─── chunk: tool_calls ──────┤      │
│       │                                │ push: toolcall_start         │      │
│       │ ◄─── push: message_update ─────┤ push: toolcall_delta         │      │
│       │                                │                              │      │
│       │                                │ ◄─── chunk: finish_reason ───┤      │
│       │                                │ push: done                   │      │
│       │ ◄─── push: message_end ────────┤                             │      │
│       │                                │ stream.end()                 │      │
│       │ ◄─── return finalMessage ──────┤                             │      │
│       │                                │                              │      │
│       │ push: turn_end                 │                              │      │
│       │                                │                              │      │
│       │ ───executeToolCalls──────────► │                              │      │
│       │                                │                              │      │
│       │ push: tool_execution_start     │                              │      │
│       │ push: tool_execution_update    │  (流式进度)                   │      │
│       │ push: tool_execution_end       │                              │      │
│       │ push: message_start(toolResult)│                              │      │
│       │ push: message_end(toolResult)  │                              │      │
│       │                                │                              │      │
│       │ ───streamAssistantResponse───► │  (第二轮 LLM 调用)            │      │
│       │ ...                            │ ...                          │      │
│       │                                │                              │      │
│       │ push: turn_end                 │                              │      │
│       │ push: agent_end                │                              │      │
│       │ stream.end()                   │                              │      │
│       │                                │                              │      │
└─────────────────────────────────────────────────────────────────────────────┘

AgentEvent 完整类型

typescript 复制代码
// packages/agent/src/types.ts
type AgentEvent =
  // 会话级别
  | { type: "agent_start" }
  | { type: "agent_end"; messages: AgentMessage[] }

  // Turn 级别(一次 LLM 调用)
  | { type: "turn_start" }
  | { type: "turn_end"; message: AssistantMessage; toolResults: ToolResultMessage[] }

  // 消息级别
  | { type: "message_start"; message: AgentMessage }
  | { type: "message_update"; assistantMessageEvent: AssistantMessageEvent; message: AgentMessage }
  | { type: "message_end"; message: AgentMessage }

  // 工具执行级别
  | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: Record<string, any> }
  | { type: "tool_execution_update"; toolCallId: string; toolName: string; args: Record<string, any>; partialResult: any }
  | { type: "tool_execution_end"; toolCallId: string; toolName: string; result: AgentToolResult<any>; isError: boolean }

EventStream 内部机制

队列与等待者数组

typescript 复制代码
// packages/ai/src/utils/event-stream.ts
export class EventStream<T, R = T> implements AsyncIterable<T> {
  private queue: T[] = [];                                    // 待消费事件
  private waiting: ((value: IteratorResult<T>) => void)[] = []; // 等待 Promise 的 resolve 函数
  private done = false;
  private finalResultPromise: Promise<R>;
  private resolveFinalResult!: (result: R) => void;
  private isComplete: (event: T) => boolean;
  private extractResult: (event: T) => R;

push 流程

typescript 复制代码
push(event: T): void {
  if (this.done) return;  // 已终止,忽略

  // 有等待者:直接唤醒
  if (this.waiting.length > 0) {
    const resolve = this.waiting.shift()!;
    resolve({ value: event, done: false });
  } else {
    // 无等待者:存入队列
    this.queue.push(event);
  }

  // 检查终止条件
  if (this.isComplete(event)) {
    this.done = true;
    this.resolveFinalResult(this.extractResult(event));
  }
}

AsyncIterator 实现

typescript 复制代码
[Symbol.asyncIterator](): AsyncIterator<T> {
  return {
    next: async (): Promise<IteratorResult<T>> => {
      // 情况一:队列有事件
      if (this.queue.length > 0) {
        return { value: this.queue.shift()!, done: false };
      }

      // 情况二:已终止
      if (this.done) {
        return { value: undefined as any, done: true };
      }

      // 情况三:等待事件
      return new Promise((resolve) => {
        this.waiting.push(resolve);  // 注册 resolve,等待 push 调用
      });
    },
  };
}

背压感知示意

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                         背压感知机制                                          │
│                                                                             │
│  场景 A:生产者快于消费者                                                      │
│                                                                             │
│    push(e1) → queue: [e1]                                                   │
│    push(e2) → queue: [e1, e2]                                               │
│    push(e3) → queue: [e1, e2, e3]                                           │
│    ...                                                                      │
│    consumer.next() → yield e1, queue: [e2, e3, ...]                         │
│                                                                             │
│  场景 B:消费者快于生产者                                                      │
│                                                                             │
│    consumer.next() → waiting: [resolve1]                                    │
│    push(e1) → 调用 resolve1({ value: e1, done: false })                      │
│    consumer.next() → waiting: [resolve2]                                    │
│    push(e2) → 调用 resolve2({ value: e2, done: false })                      │
│                                                                             │
│  场景 C:生产者完成                                                           │
│                                                                             │
│    push(done_event) → done = true, resolveFinalResult(result)               │
│    consumer.next() → { value: undefined, done: true }                       │
└─────────────────────────────────────────────────────────────────────────────┘

关键设计总结

1. 同步返回 + 异步填充

所有 Stream 创建函数都是同步返回 Stream 引用,实际事件在异步 IIFE 中推送:

typescript 复制代码
const stream = new AssistantMessageEventStream();
(async () => { /* 推送事件 */ })();
return stream;  // 立即返回

这允许调用者在事件开始推送前就持有 Stream 引用,通过 for await 进入等待状态。

2. Partial Message 原地更新

streamAssistantResponse 中:

typescript 复制代码
case "start":
  context.messages.push(event.partial);  // 加入上下文
  break;

case "text_delta":
  partialMessage = event.partial;
  context.messages[context.messages.length - 1] = partialMessage;  // 原地更新
  break;

这确保了即使发生中断,上下文中也保留了部分响应。

3. Steering 中断机制

typescript 复制代码
// executeToolCalls 中
if (getSteeringMessages) {
  const steering = await getSteeringMessages();
  if (steering.length > 0) {
    // 跳过剩余工具
    for (const skipped of remainingCalls) {
      results.push(skipToolCall(skipped, stream));
    }
    break;
  }
}

用户可以在工具执行期间注入"方向盘消息",立即中断当前执行流程。

4. 双层循环结构

markdown 复制代码
外层循环:处理 follow-up 消息(Agent 完成后继续)
  └─ 内层循环:处理工具调用 + steering 消息
       ├─ 调用 LLM
       ├─ 执行工具
       └─ 检查中断

这允许 Agent 在"完成"后接收新任务,实现长链 Agent 场景。

5. 事件转译层

Layer 1 Event Layer 2 Event
start message_start
*_delta message_update
done / error message_end

Layer 2 将底层的事件细节抽象为更高级的"消息生命周期"事件,简化了上层消费逻辑。

相关推荐
ChatInfo2 小时前
AI 写代码的时代,为什么动态语言开始显得更“便宜”了?
人工智能·web api
AI医影跨模态组学2 小时前
Ann Oncol(IF=65.4)广东省人民医院放射科刘再毅等团队:基于深度学习CT分类器与病理标志物增强II期结直肠癌风险分层以优化辅助治疗决策
人工智能·深度学习·论文·医学·医学影像
L-影2 小时前
下篇:tool的四大门派,以及它到底帮AI干了什么
人工智能·ai·tool
后端小肥肠3 小时前
一句话出流程图!我把 OpenClaw + Skill 做成了自动生成业务图的能力
人工智能·aigc
Ztopcloud极拓云视角3 小时前
Gemini 3.1 Pro vs GPT-5.4 Pro:API成本1/3、性能差多少?选型实测笔记
人工智能·笔记·gpt·ai·语言模型
阿里云大数据AI技术3 小时前
三行代码,百万图片秒变向量:基于MaxFrame 构建多模态数据处理管线
人工智能
码路高手3 小时前
Trae-Agent中的sandbox逻辑分析
人工智能·架构
咪的Coding3 小时前
为什么Claude Code这么强?我从泄漏的源码里挖到了核心秘密
人工智能·claude