2026-09-17 F059 DMS迁移:API/CLI已停止本地会话JSON保存,前端将逐轮问答保存到DMS草稿;后台只读问答,静默后分析更新「助手会话」。旧SESSION_DIR文件原样保留,不再观察。本页后续保留期限仅指Redis,上下文机制不变。详见 DMS会话监视。
2026-09-17 F057:请求历史列表与模型上下文是不同机制。此前请求虽已持久化,意图模型仍只读取本轮问题,导致“没有上下文”。现在从同会话图检查点恢复历史并供三类回答使用;旧检查点兼容恢复不修改旧回答。最近8轮/6000 UTF-8字节为模型历史窗口,省略会标记;不是将全部请求日志或历史政策答案作为事实依据。详情见step3-agent.md的Redis上下文持久化章节。
POST /api/chat 接纳的请求在Redis中额外保存一份普通JSON记录,供后续后台统计历史问题。每次HTTP请求一条记录,request_id与SSE、响应头X-Request-ID、终端日志一致。同一请求处理期间更新同一条记录,不会因多个LangGraph节点重复计数。原接口请求体和SSE结果结构不变。
日志及新增历史时间统一为上海时间(UTC+08:00),ISO 8601格式,包含毫秒和+08:00,不依赖服务器操作系统时区。已有日志不重写,旧会话检查点不回填无法确定的提问时间;LangGraph自身的检查点时间仍由框架维护。
business-assistant:context:v1:<thread_id的完整SHA-256十六进制摘要>。['request','v1',request_id] 的标准JSON编码,例如 ["request","v1","example-request"]。c、w开头。以下是合成示例,时间仅用于说明:
{
"schema_version": 1,
"request_id": "example-request",
"thread_id": "example-window",
"question": "我想了解创业方面的支持",
"original_question": "我想了解创业方面的支持",
"input_kind": "new_question",
"received_at": "2026-09-14T17:00:00.000+08:00",
"started_at": "2026-09-14T17:00:00.010+08:00",
"finished_at": "2026-09-14T17:00:05.010+08:00",
"duration_ms": 5000,
"status": "completed",
"degraded": false,
"error_code": null
}
| 字段 | 中文含义 |
|---|---|
| schema_version | 历史记录格式版本,目前为1 |
| request_id | 单次请求标识;后台归档可作为唯一键 |
| thread_id | 会话窗口标识,不是用户身份;一个用户可有多个窗口 |
| question | 本次请求的原始输入,保留补问回复和命令 |
| original_question | 本轮最初的问题;补问或故障恢复时仍保留最初问题,会话状态未读取成功时为null |
| input_kind | 输入类别,详见下表 |
| received_at | 请求进入接口处理函数的时间,早于请求体解析与接纳 |
| started_at | 后台工作线程开始执行本次请求的时间 |
| finished_at | 本次图处理结束时间;不等于用户收到全部网络数据,也不代表整段会话关闭 |
| duration_ms | 从工作线程开始到图处理结束的毫秒耗时,使用单调时钟计算;包含等待模型/检索及事件队列,不含最终历史写入耗时 |
| status | 处理状态,详见下表 |
| degraded | 是否出现图错误、候选分析错误、结果不完整或备用综合说明;不代表资格是否符合 |
| error_code | 失败固定码:processing_failed处理失败、unfinished_turn上一轮未完成;正常为null,不保存原始异常 |
| input_kind取值 | 中文含义 |
|---|---|
| new_question | 开始新一轮问题;问题量统计通常筛选此项 |
| clarification_reply | 回复当前公司补问/候选确认,含相关翻页或取消命令 |
| retry | 无待补问但图未完成时,通过/retry恢复 |
| unfinished_turn_input | 上轮故障未完成时输入了其他内容,被流程拒绝 |
| unknown | 初始记录,尚未成功读取会话状态以判断输入类别 |
| status取值 | 中文含义 |
|---|---|
| running | 已开始处理;finished_at/duration_ms为null |
| completed | 本次处理完成;可能degraded=true,不能直接等同于高质量回答 |
| needs_input | 本次处理结束,等待用户下一次补问回复;finished_at有值 |
| failed | 本次处理失败,详见error_code |
| stopped | 观察到断开连接、超时或服务关闭的停止信号,图未完成 |
| incomplete | 图退出但未生成结束事件,且未观察到停止信号 |
请求验证失败、同会话忙409、超限429等未接纳请求不写提问历史,仍有脱敏日志。记录只由第四步API创建,CLI沿用原检查点机制。
先写running记录,再开始图执行;最终历史保存成功后才发送done。若Redis不可用,接口返回通用处理失败;首次写入失败可能没有记录,最终写入失败或进程被强制杀死可能留下running/null,后台不能把此状态自动当作成功。该记录是后台处理结果,具体断线/超时原因另按request_id关联流日志。
同项目Python代码可读取单个会话,按接收时间升序返回(同毫秒以request_id排序,不保证同毫秒先后):
from src.step3_question_answer.context import RedisContextSaver, load_context_config
saver = RedisContextSaver(load_context_config())
try:
records = saver.list_requests("example-window")
new_questions = [r for r in records if r["input_kind"] == "new_question"]
finally:
saver.close()
其他语言后台可直接用Redis客户端读取JSON。全量分析可使用SCAN遍历上述键前缀,再HSCAN遍历各Hash,仅解析字段数组前两项为["request","v1"]的值。使用游标分批读取;SCAN不是一致性快照,可能重复,归档必须按request_id幂等更新,尤其要更新最初读到running的记录。本轮没有新增历史查询HTTP接口、全局时间索引、用户账户字段或分析后台。
历史与检查点共享REDIS_CONTEXT_TTL_SECONDS:默认1296000秒(15天)。成功写入整会话续期,读取不续期;会话过期或调用delete_thread时历史一并删除。活跃会话的旧历史会随新写入继续保留,当前未做会话内历史裁剪。
这是有过期时间的会话存储,不是永久历史仓库。长期统计需要后台在过期前归档到持久数据库,不能依赖15天后仍能查到。问题原文仅写Redis业务历史,不写终端日志;后台访问权限需由后续服务控制,当前thread_id不能用于识别真实用户。