进阶第 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 明确区分:
AgentStatus的idle与running。- durable session event,例如
turn/start、step/start、tool/result。 - live extension point,例如
agent/pre-step、tools/pre-execute。
这意味着前端不应该自己凭空发明一套状态,而应把服务端事件映射成 UI 状态。
进一步阅读:
packages/core/agent/src/runtime-types.tspackages/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 定义 SessionState、TurnState、MessageState 和 ToolState,并写出状态转换表。
验收标准
- 能列举至少六种 assistant 消息状态。
- 能说明 turn 和 session 的区别。
- 能把服务端事件映射为前端状态。