request-history.md 6.7 KB

用户提问历史与时间(F032)

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自身的检查点时间仍由框架维护。

存储结构

  • Redis类型:Hash,与原会话检查点使用同一键。
  • 键:business-assistant:context:v1:<thread_id的完整SHA-256十六进制摘要>。
  • 历史字段名:JSON数组字符串 ['request','v1',request_id] 的标准JSON编码,例如 ["request","v1","example-request"]。
  • 字段值:UTF-8 JSON;无需解析LangGraph的msgpack或Base64。检查点字段仍以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不能用于识别真实用户。