AI 应用后端工程化:从原型到可交付系统 · 第 14 篇 · 第四章 · 会话与安全
将会话、消息、推理步骤和队列事件拆开建模,让流式输出、工具调用和失败恢复都有落点。
聊天界面最终显示一段回答,但后台可能经历了多轮模型请求、工具调用、重试和流式输出。如果数据库只保存用户问题与最终答案,刷新页面还能看到文字,却无法解释答案如何产生,也无法从中断位置恢复。
把对话看成事件流,可以为每一步找到稳定位置:会话定义边界,消息表达参与者内容,推理步骤连接模型动作,队列事件负责实时传输。
四类对象不要揉成一张表
flowchart TD
Conversation[会话] --> Message1[用户消息]
Conversation --> Message2[助手消息]
Message2 --> Invocation[一次模型调用]
Invocation --> ToolCall[工具调用]
ToolCall --> Observation[工具结果]
Invocation --> Token[文本增量事件]
Invocation --> Error[失败事件]
Token --> Stream[实时事件流]
Observation --> Stream
Error --> Stream
会话保存所有者、应用与标题;消息保存角色和稳定正文;调用记录保存模型、输入版本、用量和状态;工具步骤保存参数、结果与耗时。界面可以把它们投影成气泡和进度,数据库则保留可恢复事实。
事件要有类型和序号
把流式数据都当字符串会出现歧义:一段文本是最终答案、思考说明还是工具日志?事件应带明确类型、调用 ID 和递增序号。
{"seq": 17, "type": "assistant.delta", "message_id": "m9", "text": "根据查询结果"}
{"seq": 18, "type": "tool.started", "call_id": "c4", "tool": "weather.get_current"}
{"seq": 19, "type": "tool.finished", "call_id": "c4", "ok": true}
{"seq": 20, "type": "message.completed", "message_id": "m9"}
序号让断线重连可以从 last_event_id=18 继续,而不是重放全部内容。事件消费者也能检测丢失和乱序。跨进程时,内存队列只负责低延迟推送,持久记录才负责恢复。
Agent 队列管理什么
Agent 运行过程中,模型增量、工具开始、工具完成和错误会由不同组件产生。队列管理器把它们汇聚到一个按会话隔离的通道,并管理关闭、取消与背压。它不应该承担业务持久化,否则一个慢客户端可能阻塞数据库状态更新。
背压很重要。模型生成速度高于浏览器消费时,无界队列会占满内存。可以合并小文本增量、限制缓冲区,并在客户端断开后取消不再需要的任务。若任务需要后台继续,则应切换为持久任务,而不是假装连接仍在。
最终消息何时成立
常见错误是收到第一个文本片段就创建“成功消息”,后面工具失败或用户取消,数据库仍留下半句完成态。消息应先处于 streaming,只有收到明确结束事件后标记 completed;失败与取消保留已生成内容,但状态不能伪装成功。
工具结果可以很大,消息表只保存摘要和引用。原始结果进入独立存储并遵守访问权限。模型下一轮需要的协议消息,也不一定与人类界面展示完全相同,两种投影都应来自同一事实记录。
输出解析不是字符串切片
模型响应可能包含文本、结构化工具调用和供应商特有字段。用字符串正则提取“Action:”在演示里可用,格式稍变就失败。优先使用模型提供的结构化 parts,并在边界处归一化为内部事件。
归一化层应保留原始响应 ID 和未知字段摘要,方便供应商升级时排查;核心 Agent 只消费稳定事件,不到处写供应商分支。
一次难以恢复的失败
服务向浏览器发送了三十个 Token 后崩溃,数据库没有任何中间状态。用户刷新只看到自己的问题,再次发送会重复调用工具。如果工具是创建日程,就可能产生两条记录。
改进方案是先创建 invocation 和幂等 call ID,工具执行前写 started,成功后写 result,文本按批次或结束时持久化。恢复程序看到已成功的 call ID 可以复用结果,而不是再次产生副作用。
迁移到普通实时系统
订单履约、视频转码和部署流水线同样用事件表达阶段,再投影成 UI。练习可以模拟五种事件并让客户端在序号 3 后断开,重连后只补 4、5;随后重复投递 tool.finished,验证消费者按 event ID 去重。
五个提交勾勒出事件模型
69ba5e5:建立会话服务,先确定状态容器。1e5ddb5:调整输出解析,模型响应开始被归一化。a6ac9e4:加入会话、消息与推理模型,持久事实被拆开。c4f8c33:建立基础 Agent 与实体,执行获得独立抽象。54731dd:完善队列、函数调用与事件发布监听,实时投影连接到执行过程。
从会话服务到事件队列,系统逐渐学会记录“发生了什么”,而不只是“最后说了什么”。这是调试、恢复和审计的共同基础。