编码母题 / P0 / AI 全栈与 Agent 工程 / 01

智能体流式事件协议与状态归并器

先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。

重要程度高价值难度中等预计完成35–45 分钟

难度按 LeetCode 简单/中等/困难三档估计;工程题并非 LeetCode 官方题目。用时指独立编码并检查主要边界的参考时间,因经验而异。

题目是什么

按 runId、sequence 和事件类型把 Agent 流式事件归并成前端状态。重复序号忽略,跳号报错,工具调用只能按合法状态转移。

输入输出示例

收到 sequence=1 后直接收到 3,应报告 gap,不能把 token 拼入错误状态。

练习起手式与验证

复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。

/*
 * 题目:智能体流式事件协议与状态归并器
 * 要求:按 runId、sequence 和事件类型把 Agent 流式事件归并成前端状态。
 * 重复序号忽略,跳号报错,工具调用只能按合法状态转移。
 * 示例:收到 sequence=1 后直接收到 3,应报告 gap,不能把 token
 * 拼入错误状态。
 */
function initialAgentState(runId) {}
function reduceAgentEvent(state, event) {}

const events = [
  { runId: "r", sequence: 1, type: "run_started" },
  { runId: "r", sequence: 2, type: "token_delta", delta: "你" },
  {
    runId: "r",
    sequence: 3,
    type: "tool_started",
    toolCallId: "t",
    name: "search",
  },
  {
    runId: "r",
    sequence: 4,
    type: "tool_completed",
    toolCallId: "t",
    output: [],
  },
  { runId: "r", sequence: 5, type: "run_completed" },
];
const state = events.reduce(reduceAgentEvent, initialAgentState("r"));
console.log(state.status); // 预期:"completed"
console.log(state.text); // 预期:"你"
console.log(state.tools.t.status); // 预期:"completed"

解题思路

展开思路、不变量与常见边界

关键切入点:把事件作为可重放事实日志,Reducer 只接受严格连续序号并保持纯函数。

核心模型:服务端事件是事实日志,前端状态是可重放投影。只接受严格连续序号; Tool 以 id 索引并校验合法转移;终态拒绝后续新事件。Reducer 不做网络和时间副作用。 迁移:刷新后重放、断线续传、Agent 时间线和多端同步。

参考代码与验证

“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。

// 关键机制:把事件作为可重放事实日志,Reducer 只接受严格连续序号并保持纯函数。
function initialAgentState(runId) {
  return {
    runId,
    status: "idle",
    lastSequence: 0,
    text: "",
    tools: {},
    error: null,
  };
}
function reduceAgentEvent(state, event) {
  if (event.runId !== state.runId) throw new Error("runId mismatch");
  if (event.sequence <= state.lastSequence) return state;
  if (event.sequence !== state.lastSequence + 1)
    throw new Error("event sequence gap");
  if (["completed", "failed"].includes(state.status))
    throw new Error("event after terminal state");
  const next = {
    ...state,
    lastSequence: event.sequence,
    tools: { ...state.tools },
  };
  if (event.type === "run_started") {
    if (state.status !== "idle") throw new Error("run already started");
    next.status = "running";
  } else if (event.type === "token_delta") {
    if (state.status !== "running") throw new Error("run not active");
    next.text += event.delta;
  } else if (event.type === "tool_started") {
    if (next.tools[event.toolCallId]) throw new Error("duplicate tool call");
    next.tools[event.toolCallId] = { status: "running", name: event.name };
  } else if (event.type === "tool_completed") {
    const tool = next.tools[event.toolCallId];
    if (!tool || tool.status !== "running") throw new Error("tool not running");
    next.tools[event.toolCallId] = {
      ...tool,
      status: "completed",
      output: event.output,
    };
  } else if (event.type === "run_completed") next.status = "completed";
  else if (event.type === "run_failed") {
    next.status = "failed";
    next.error = event.error;
  } else throw new Error(`unknown event: ${event.type}`);
  return next;
}

// 三组以上可直接运行的验证。
const events = [
  { runId: "r", sequence: 1, type: "run_started" },
  { runId: "r", sequence: 2, type: "token_delta", delta: "你" },
  {
    runId: "r",
    sequence: 3,
    type: "tool_started",
    toolCallId: "t",
    name: "search",
  },
  {
    runId: "r",
    sequence: 4,
    type: "tool_completed",
    toolCallId: "t",
    output: [],
  },
  { runId: "r", sequence: 5, type: "run_completed" },
];
const state = events.reduce(reduceAgentEvent, initialAgentState("r"));
console.log(state.status); // 预期:"completed"
console.log(state.text); // 预期:"你"
console.log(state.tools.t.status); // 预期:"completed"
console.log(reduceAgentEvent(state, events[4])); // 预期:state
console.log((() => { try { (() =>
    reduceAgentEvent(initialAgentState("r"), {
      runId: "r",
      sequence: 2,
      type: "run_started",
    }))(); return false; } catch { return true; } })()); // 预期:true(抛出异常)

复杂度分析

时间复杂度:O(T+L)/事件。复制 T 个工具状态;token 追加可能复制已有 L 长度文本。

空间复杂度:O(T+L)/状态。下一状态保存工具表和累计文本。

下载原始 JS 文件