标题由服务端生成
新建会话时,标题写入占位串「新的提问」。首轮提问到达后,服务端取该提问的前 24 字覆盖它;若截取结果为空,保留占位串。此后每轮提问只刷新 updated_at——会话列表按它倒序,最近问过的排在最前。
多轮问答需要两样东西:一个容器,一串文本。conversations 记这次连续追问属于谁、属于哪个班、由哪个智能体作答;messages 按写入顺序记下说过的每一句,以及助手这侧的出处。两者以会话编号关联。
会话是容器,消息是内容。会话行只记归属、角色与标题;对话正文与出处全部落在消息行。
| 表 | 字段 | 含义与约束 |
|---|---|---|
conversations | id | 主键,自增 |
class_id | 所属班级,取自登录用户,请求无法指定 | |
user_id | 归属用户;会话列表与单条读取都按它过滤 | |
agent_id | 会话选定的智能体;新建时写入,此后不再变更 | |
title | 新建时为占位串「新的提问」,首轮提问后改为其前 24 字 | |
created_at · updated_at | updated_at 随每次提问刷新,会话列表按它倒序 | |
messages | id | 主键,自增;同一会话内按它排序 |
conversation_id | 所属会话;会话行删除时一并清掉 | |
role | 只有 user 与 assistant,由数据库约束 | |
content | 消息正文 | |
citations_json · skill_name | 出处信息,仅助手消息有:引用列表与本次命中的技能名 | |
created_at | 写入时间 |
agent_id 让每轮回答都能回到当时那套角色设定与绑定。助手消息上的技能名与引用列表,则记下这轮用了什么、引了哪些材料。新建会话时,标题写入占位串「新的提问」。首轮提问到达后,服务端取该提问的前 24 字覆盖它;若截取结果为空,保留占位串。此后每轮提问只刷新 updated_at——会话列表按它倒序,最近问过的排在最前。
新建会话时必须指定一个智能体:服务端校验它属于本班且处于启用状态,通过后把编号写进会话行。此后这个会话一直用它,请求里再带别的编号也不会更换;要换只能新开会话。
该智能体后来被停用,已有会话仍可继续提问——停用只拦住新建时的选用。若本班当时一个已启用的智能体都没有,新建会话会返回 400。
消息全部留在库里,但送进上下文的只有最靠后的一小段。窗口固定,不随会话变长。
全量历史会让上下文无界增长(unbounded context growth),每轮的开销也随会话长度上涨。本课取一个固定窗口:每次回答都重新查询消息表,按写入顺序取末尾 6 条(一问一答算一轮,6 条约合 3 轮),每条正文再截断到 500 字。角色不是 user 或 assistant 的记录、以及正文为空的记录,一律跳过。本轮提问在生成之前已写入库,因此必定落在窗口内。
会话是私有资源。读取与新建都要过两道判定:先看是不是本人,再看是不是本班;任一不符,与「不存在」返回完全一致的响应。
| 情形 | 结果 |
|---|---|
| 读取自己的会话 | 返回会话与消息列表 |
| 读取他人会话 | 查询带上用户编号,查不到这条会话,返回 404 |
| 读取他班会话 | 先按用户取出该行,再比对班级;不一致时同样返回 404 |
| 新建会话时选定智能体 | 只接受本班已启用的;不存在、他班或未启用一律按「不存在」返回 404 |
| 新建会话但本班没有已启用的智能体 | 返回 400,提示本班暂无可用的智能体 |
| 会话所属智能体后来被停用 | 已有会话照常提问;停用只挡住新会话的选用 |
| 在未登录时访问 | 返回 401,不清空任何数据 |