Files
ZNJJ-api-server/docs/time-boundary-fix.md
T
Eric Wang 9e28f69b3c feat(agent): trusted time evidence and collection prompt optimization
- time_context.py: server-side observation clocks for relative-time
  evidence (anti client-forgery); resolves X分钟前/小时前 against the
  observation timestamp of the turn that produced them, never re-basing
  history on the current clock; flags historical references without a
  reliable baseline
- state: dialogue_timestamps keyed by dialogue message index, persisted
  with checkpoint; welcome seeds index 0
- collect: single clock per turn shared by reply/extraction/repair;
  time_evidence injected into runtime context; reply_validator hook
  rejects time claims lacking evidence (one shared repair budget);
  model still interprets facts and chooses actions
- prompts: collection prompt restructured (shorter, state-bound-to-action
  table, gatekeeper principle, time source rules); extraction prompt
  aligned; workflow@20261021 json updated to match
- evals: prompt_probes / prompt_compare / time_boundary probe scripts
  (supplementary checks, original cases untouched)
- docs: collection-prompt-optimization.md (method + baselines),
  time-boundary-fix.md (problem, design, verification)
- gitignore: editor artifact dirs (.agents/.claude/.vscode, skills-lock)

Verified: 171 unit tests pass (16 new time-context tests)
2026-10-22 09:41:32 +08:00

47 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 信息采集的历史相对时间边界修复
## 问题与结果
纯提示词优化阶段发现:历史用户说过“十分钟前”,但系统没有记录该消息当轮时钟,模型可能使用最新时钟换算并向用户复述错误日期。提示词的禁止规则无法稳定避免这一行为。
本次在当前 LangGraph 文本采集链路加入服务器时间依据及输出校验。采集与字段提取仍由模型完成,代码不按人伤关键词替模型选择阶段;代码记录时钟、换算有明确基准的相对表达,并拒绝缺乏时间依据的候选结果。
## 实现
- `AgentState.dialogue_timestamps` 使用完整对话的用户消息索引作为key,保存服务器观察该消息时的带时区ISO时间。它独立于消息正文,随现有checkpoint保存。旧会话缺失的历史时钟不回填为当前时间。
- `run_collect` 进入本轮时只取一次时钟;对话、提取和一次受控修复使用同一个基准。服务器时钟不会因模型调用耗时漂移。
- `time_context.time_evidence` 将用户相对表达、消息索引、原始时钟和换算值一并提供给两个模型调用。历史只截取最近50条发送给模型时,完整checkpoint中保留的时间依据仍可提供。明确的“数值/中文数字/半 + 分钟/小时(之)前”按其原始时钟换算,支持跨日。
- 旧会话历史只有相对表达,且没有原始时钟、已有具体候选值或本轮新时间时,标记 `historical_reference_missing`。提示词要求继续询问大概事故时间。
- 在该缺失状态下,采集回复中的具体年月日/时分及拍照出口会被校验拒绝,与原有格式/阶段校验共用一次修复预算;修复仍失败抛出已有的可识别错误,不提交该轮业务状态,不向客户端发布错误时间。
- 同一状态下,提取结果中的 `accident_time` 不写入表单,但其余有独立证据的字段照常保留。明确人伤及其他终止/转人工出口不被缺失时钟阻塞。
- 已存在的具体时间候选仍可让用户确认;用户本轮新给出的时间或明确更正不受旧历史缺失基准阻塞。服务器提供的时间依据不等于用户已经确认时间。
两份提示词及工作流中的对应输入已同步。`evals/cases/` 输入与断言保持不变。
## 验证与复现
使用 Conda `py3.11`、真实 `deepseek-v4.1-flash`、温度0:
```bash
conda run -n py3.11 python -m pytest -q
conda run -n py3.11 python -m evals.time_boundary --output evals/results/time-boundary/boundary-live.json
conda run -n py3.11 python -m evals.prompt_probes --runtime --output evals/results/time-boundary/runtime-probes.json
conda run -n py3.11 python -m evals.prompt_compare --output-dir evals/results/time-boundary/cases
```
`--runtime` 复用原14项补充检查的输入和断言,但通过实际 `run_collect` 执行时间依据与校验。默认不带该选项时仍可单独测试裸提示词;裸提示词自身不具备代码校验,不能拿裸提示词结果宣称运行时验证覆盖。
专项真实模型8/8通过:两种不同延迟下的跨日换算、5次缺少历史基准的澄清,以及明确绝对时间更正。输出同时检查回复时间和表单时间,记录原始调用回复,不只检查状态码。补充14项首次13/14(明确无人伤未提取出字段),复跑14/14;原缺失历史时钟场景两轮都通过。没有删除或放宽原有断言。
离线测试覆盖首次提取失败、跨日、同轮双调用同一时钟、JSON序列化、LangGraph checkpoint、历史窗口截断、缺失/无效/无时区历史时钟、拒绝无法支持的日期或拍照、一次修复后仍失败不提交状态、安全中断优先、时间更正和旧会话具体候选。
最终全量单元测试171/171通过,退出码0(日志:`evals/results/time-boundary/unit-tests.log`;退出状态:`unit-result.json`)。原有46个cases首次45/46,唯一失败为 `browser_addon_non_motor_vehicle_zero_count_history` 的模型空响应;完整复跑46/46通过。首轮失败保留,没有通过修改case或断言掩盖。
46份原有YAML的SHA-256与上一轮优化前基线完全一致。工作流除采集与提取提示词外,其他节点配置与Git基线相同。最终机器可读核对结果:`evals/results/time-boundary/verification.json`;`git diff --check`通过。
## 范围
该修复作用于当前 LangGraph 文本采集节点。仅向外部FastGPT导入同步提示词,并不会获得本项目新增的服务器时钟记录与校验,不能据此宣称外部工作流同样完成修复。
持久性取决于项目使用的checkpointer。当前默认是内存checkpointer;本次保证其跨轮状态携带时间记录,没有新增进程重启后的数据库持久化能力。无法精确到分的“刚刚”等近似表达仍按提示词询问大概时间,不任意指定分钟。