对话 Agent 的会话表和消息表应该如何设计?
未标记
考查重点:会话归属、消息顺序与调用关联。会话表存用户与活跃状态,消息表按 conversation_id 关联并用 seq 保序;工具调用与结果靠调用标识配对
本题目录
参考答案
核心回答
可以先用会话表保存一次对话的元信息,消息表保存对话中的具体内容,两者是一对多关系。
- 会话表:
id、所属用户user_id、标题、会话状态、创建时间和最后活跃时间。 - 消息表:
id、conversation_id、会话内顺序号seq、角色role、内容content、生成状态,以及创建和更新时间。 - Agent扩展信息:按需要保存本次执行的
run_id、工具调用与结果的关联、模型和用量等;图片、文件可以用内容块和附件引用表示。
关键约束是消息必须归属有效会话,读取时校验用户访问范围,消息有稳定顺序,并能分清完整回复、生成中、失败和取消。普通线性对话先做好这两张表;编辑重生成、分支或复杂工具执行,再增加对应结构。
展开说明
1. 会话表描述“这段对话是谁的”
id 是会话主键,user_id 关联会话拥有者;标题方便列表展示;状态可表示正常、归档等产品生命周期。created_at 记录创建时间,last_active_at 支持按最近活动列出会话。若需要区分标题修改与新消息活动,也可以单独保留 updated_at。
只有系统支持多个 Agent、共享会话或多租户时,才进一步增加 Agent 配置关联、成员或租户等信息。字段来源应来自实际需求,不必默认每个聊天产品都有群聊和企业租户。
会话标识只是定位键,不是访问凭证。服务端仍应根据登录身份检查会话归属或成员权限,不能因为请求带了一个合法 ID 就返回内容。
2. 消息既要保存内容,也要保存语义
id 用于稳定定位一条消息;conversation_id 关联会话;seq 表示会话内的展示或追加顺序。role 表示用户、助手、工具结果等语义,内容则可用文本加结构化内容块保存。纯文本场景不必一开始就拆消息内容表,但结构应留得下图片、文件和工具调用。
助手输出可能包含文本和多个工具请求;工具结果需要关联到原调用,例如在支持该字段的适配层保存 tool_call_id。不能只留下“查询成功”这段文字,也不能靠返回先后来猜它属于哪个调用。
status 可以表示生成中、完成、失败或取消,必要时保存简短错误信息。模型、Token 用量、完成原因等可作为扩展元数据保存,实际值来自调用结果。run_id 用于关联一次执行,一次会话可以有多次执行;消息表不需要承担全部执行状态。
内部结构要和模型接口做适配。不同厂商可能使用消息角色、内容块或独立调用 item,不能把某一家协议的角色枚举当作所有模型的通用标准。需要回传的原始协议字段也应妥善保存,避免只存展示文本后无法继续工具交互。
3. 顺序、约束和查询要配套
消息主键唯一,conversation_id 可用外键保证引用有效,并给 (conversation_id, seq) 加唯一约束。会话历史按顺序号读取,分页时用顺序号作为游标;相应复合索引可以支持“查某会话的某段消息”。会话列表则按用户和最近活跃时间设计索引,具体是否被使用要看实际查询与执行计划。
时间戳可能相同,也可能受并发和不同服务时钟影响,所以不宜只靠 created_at 确定稳定顺序。顺序号可在事务中锁定会话计数或通过原子更新分配,不能在并发请求里各自执行 MAX(seq)+1 就直接插入。唯一约束负责兜住冲突,冲突后仍要有明确处理方式。
插入消息与更新会话活跃时间需要保持一致。客户端可能重复提交同一条用户消息,可以增加客户端消息标识并做适当唯一约束,实现重复请求去重;它和模型工具调用 ID、外部操作幂等键是不同概念。
4. 用一轮工具交互解释怎样落库
假设用户询问天气:先保存用户消息;模型提出天气工具调用时,保存带调用信息的助手消息;工具执行完成后保存关联的结果;最后保存助手回复。这些记录都属于同一会话,可通过 run_id 关联本次执行。
流式回复通常保持同一消息 ID,在生成过程中合并片段,按批次或最终结果更新内容和状态,不必每个 Token 都作为一条普通消息插入。若业务需要断线续传,可以另外记录有顺序的流式事件或阶段快照。生成中断时保留实际已有内容并标记取消或失败,避免把半段回复伪装成完整答案。
读取历史用于展示,和选择历史送给模型也要区分。界面可分页查看完整记录,模型上下文则按预算选择近期消息、摘要及必要原文;摘要保存覆盖到哪条消息,不能用一段摘要直接覆盖所有历史而失去定位。
常见追问
编辑问题、重新生成回复怎样设计?
如果需要保留多个版本,可以增加 parent_message_id、分支或版本信息,让新回复挂到相应用户消息上,并明确当前展示和继续对话使用哪条路径。父消息还应属于同一会话。没有这些需求时,线性顺序即可,不必先实现整棵消息树。
system消息需要每轮重复存一份吗?
不一定。应用指令可以由配置和版本管理,在执行记录中保存所用版本;确需重放时保存必要快照。客户端传入的普通文本不能因为声称自己是system,就获得系统指令的权限。
存下消息就能恢复Agent吗?
可以恢复对话展示及部分上下文,但完整续跑还可能需要当前节点、待执行调用、检查点和外部操作状态。消息历史与执行恢复各有职责,不能看到最后一条消息就盲目重做所有动作。
对应教程
- 记忆与对话历史:从按会话读历史、拼入提示、调用模型到写回消息,理解存储需要支撑什么读写路径。关系表的字段、约束和分页是这条主线的具体设计选择。
参考资料
- 国内:JavaGuide:MySQL索引详解:依据文中复合索引与排序的说明,设计正文中按会话读取历史、按最近活动列出会话的查询索引。
- 国内:ranxi2001:工具调用与消息语义:借鉴用户、助手调用和工具结果的语义区分,解释消息表为何需要角色字段及工具调用关联。
- 国内:WeThinkIn:Agent平台运行标识:参考会话与单次执行的标识划分,说明正文中 run_id 的用途,以及消息历史不能替代完整执行状态的原因。
- 国外:PostgreSQL:Constraints:依据主键、唯一约束、外键和自引用的定义,说明正文中消息标识、会话内顺序与分支引用的数据库约束。
- 国外:LangChain:Messages:借鉴内容块、工具结果配对和流式片段的消息语义,说明正文中结构化内容、调用关联及同一消息内合并片段的设计。

