Skip to content
签到签到

进阶第 1 章:生产级心智模型

业务问题

一个 demo 只要能调用模型,但生产级 AI 编码助手 / 客服 Agent 必须处理长时间任务、工具调用、用户取消、网络失败和会话恢复。

交互图解:Agent 流式 UI 生命周期 · 查看图解导读

核心原理

不要把 Agent 看成一次 fetch,而要看成一个带生命周期和失败分类的异步状态机。

一个 run 至少经历:

text
idle
-> submitting
-> streaming
-> tool-running
-> completed
   | error
   | aborted
   | blocked

消息、工具、turn 和 session 各自有自己的状态。

消息状态

  • queued
  • streaming
  • tool-calling
  • completed
  • aborted
  • error

工具状态

  • pending
  • asking-approval
  • running
  • succeeded
  • failed
  • cancelled

Turn 与 Session

Turn 描述一轮从输入到结束的工作,Session 描述整个会话。一次 turn 可能因为工具调用包含多个模型请求。

真实项目中的映射

AI 编码助手 / 客服 Agent 的界面不能只有“回答中”。它需要:

  • 展示模型是否正在思考。
  • 展示正在调用哪个工具。
  • 展示文件变更、命令输出和搜索来源。
  • 让用户批准或拒绝高风险操作。
  • 在断线后恢复到准确状态。

DeepSeek Harness 对应实现

DeepSeek Harness 明确区分:

  • AgentStatusidlerunning
  • durable session event,例如 turn/startstep/starttool/result
  • live extension point,例如 agent/pre-steptools/pre-execute

这意味着前端不应该自己凭空发明一套状态,而应把服务端事件映射成 UI 状态。

进一步阅读:

  • packages/core/agent/src/runtime-types.ts
  • packages/core/session/src/types.ts

代码示例

ts
type AssistantMessageState =
  | { kind: 'queued' }
  | { kind: 'streaming'; text: string }
  | { kind: 'tool-calling'; toolName: string }
  | { kind: 'completed'; text: string }
  | { kind: 'aborted'; text: string }
  | { kind: 'error'; code: string; message: string }

function canSubmit(state: AssistantMessageState): boolean {
  return state.kind === 'completed'
    || state.kind === 'aborted'
    || state.kind === 'error'
}

使用 discriminated union 比一堆布尔值更容易维护。

面试追问

问:Agent 前端为什么不能用普通 loading 状态?

回答要点:

  • Agent 会经历流式、工具调用、批准、取消和错误。
  • 普通 loading 只能表达“是否结束”,不能表达中间过程。

问:消息状态和工具状态为什么要分开?

回答要点:

  • 一条 assistant 消息可能包含多个工具调用。
  • 工具可以独立 pending、running、success 或 failed。
  • 分开建模才能准确展示任务进度。

问:服务端事件和前端 UI 状态有什么关系?

回答要点:

  • 服务端事件是事实来源。
  • 前端把事件投影为 UI 节点。
  • UI 不应自己生成与事件无关的假状态。

实践任务

为一个简化 AI 客服 Agent 定义 SessionStateTurnStateMessageStateToolState,并写出状态转换表。

验收标准

  • 能列举至少六种 assistant 消息状态。
  • 能说明 turn 和 session 的区别。
  • 能把服务端事件映射为前端状态。