Files
ZNJJ-api-server/docs/customer-feedback-prompt-fix.md
T

104 lines
12 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.
# 客户反馈:单方事故数量与重复追问
## 最新业务边界:数量先看表单
用户已确认“倒车撞护栏”按已知1辆机动车处理;泛化到完整描述自身驾车撞固定物、无其他车辆参与线索的单方事故,不要求额外报车型或说“只有一辆”。采集节点先读取 `form.vehicle_count`,有效值没有新更正或冲突时直接使用,不再询问数字,也不再确认是否只有一辆。历史窗口缺少原始回答不使有效表单值失效。其他信息仍缺失时只补问其他信息。
本次同步采集/提取提示词和本仓库工作流JSON。两个历史名为 `single_no_inferred_count` 的YAML案例根据用户新边界更新:原始事故描述不改,新增 `vehicle_count: 1` 断言,时间确认后预期直接进入2000,删除额外要求用户说“只有一辆”的轮次;其余44份旧案例保持不变。该预期修改来自用户明确确定的业务边界。
语义检查补充了倒车撞其他固定物、表单已有1/2/3辆、数量已知但人伤缺失等情况。历史相对时间无基准的问题按用户要求暂缓,运行时规则和该案例保留;本次通过 `--exclude-case missing_historical_time_anchor` 显式排除,排除项单独写入结果,不算通过。
下文“验证记录”保留上一轮边界确定前的历史实测;本次实测另见文末更新。
本轮以客户“自己的小轿车撞到路边围栏”的对话为依据,改进事故数量提取和已有信息复用。采用 `.agents/skills/` 下的 `prompt-debugging`、`prompt-engineer`、`promptfoo-evaluation`,以及用户要求参考的 `skill-creator`;复用现有 Python 评估器,不另建一套 Promptfoo 执行链。
## 原因
1. 原采集提示词将固定物碰撞与机动车数量严格分开,缺少对完整单方事故语义的认可,导致车型、经过都已清楚仍追问数字。采集和提取两个独立模型调用还可能使用不同口径。
2. 用户质疑重复提问时,旧规则倾向于维持上一条问题,而没有先重新读取历史与当前表单,容易给出“没有看到您之前的回答”的机械回复。
3. LangGraph 采集用户消息只有参考时间和用户输入;表单只出现在额外的系统会话上下文。工作流 JSON 的采集用户消息也没有 form。
4. 连续呼叫只按闲聊处理,完整事故经过模板被反复附在回复后。
## 改动与边界
- 采集、提取共享单方事故语义:完整描述自身驾车撞固定物、无其他车辆参与线索或冲突时,机动车数量为1;包括倒车撞固定物而未报具体车型的经过。明确自身为非机动车的同类场景为0。不要求额外说“只有一辆”或“单方”。
- 只报车型、只提固定物或碰撞关系不清,不由此补数量。存在其他参与车辆线索但范围不清时继续澄清。用户最新明确更正优先。
- 每轮先合并 form、用户历史与本轮事实,再查缺。表单中的false、0不当作缺失;表单时间不代表已经获得用户确认。
- 数量已知就采用,不再换成“是否只有一辆”重复确认。用户抱怨时承接已有信息,继续缺项或执行分流,不断言用户此前没有回答。
- 连续呼叫时简短回应在线并帮助恢复交流,避免重复事故模板;仅文本输入不声称实际听到了用户。
- `_build_user_message` 在 `<turn_context>` 内增加 JSON 编码的 `form`,与当前 `accident_fields` 快照一致。LangGraph 在已有部分表单时同时提供 `form_unfilled_fields`,按既有采集顺序显式列出表单缺键、null或空字符串的字段;false与0不列为缺失。空表单和完整表单不额外提供该列表,避免影响从历史恢复信息。该列表仅描述表单,模型仍需从历史与本轮表达补足事实。代码不按关键词推断数量,不代替模型决定分流;裸提示词probe复用同一用户消息构造函数。
- 本仓库新增 `workflow/20261027/事故信息采集2025118.json`,基于用户指定目录的 JSON,仅更新两个提示词和采集 `userChatInput`。外部 `C:/Users/52943/Code/ZNJJ-api-server` 工作区不修改。逐字同步测试改用此快照。
安全优先、明确人伤立即转人工、时间独立确认、状态码与拍照可执行性保持原规则。
## 评估设计
实际配置模型为 `deepseek-v4.1-flash`,temperature为0,`extra_body`为 `{"reasoning_effort":"none"}`。API凭据不写入报告。
- 第一轮保留原有46个YAML案例及断言;用户明确新边界后,仅更新两个历史单车数量案例,其余44份不变。
- 新增4个YAML案例,覆盖direct和browser_addon两种模式:客户完整对话在说明仍在现场后直接进入单车拍照;提前提供所有字段并伴随重复提问抱怨时不重问已知项。
- 原对话尾部的错误数量问题和客户质疑,使用 `evals/prompt_probes.py` 的历史回放验证修复后的恢复行为;正常端到端会话应在错误问题出现前进入拍照,因此不强行让正常会话继续走旧错误路径。
- 第一轮补充26项语义检查;最新数量迭代扩展为30项,暂缓1项历史时间基准检查,执行其余29项。覆盖原始表达、不同车型和固定物、仅车型不足、其他车辆参与不明、历史已有数量、只有表单有1/2/3辆、数量已知而其他字段缺失、false保留、表单时间未确认、数量更正、连续呼叫、无历史证据,以及既有安全边界。
- 实测使用真实模型、生产采集函数与 `/chat` 路由,不用FakeLLM替代。评估存储覆盖为memory,避免接触当前SQLite会话;评估结果保存在被Git忽略的 `evals/results/customer-feedback/`。
## 复现
```bash
conda run -n py3.11 python -m evals.prompt_compare --output-dir evals/results/customer-feedback/reproduce-all
conda run -n py3.11 python -m evals.prompt_probes --runtime --exclude-case missing_historical_time_anchor --output evals/results/customer-feedback/reproduce-probes.json
conda run -n py3.11 python -m pytest -q
```
使用独立评估存储时,运行前设置 `AGENT_STORAGE=memory`。新案例的表单事故时间按既有API规范断言为“14点0分”;回复的独立时间确认仍断言完整“14点00分”,不修改存储规范化逻辑。
## 验证记录(上一轮边界确定前)
修改前原有46项端到端案例为46/46。中间版本v5为50/50(原46项与新增4项全部通过);后续迭代观察到两个旧的 `single_no_inferred_count` 案例存在波动:其描述没有明确车型,但提取器可能先写入1,随后采集节点采用表单值而提前进入拍照。旧案例输入与断言均未修改,不能把这些波动当作已解决。
本轮优先保证完整单方机动车描述无需重复数量确认,并保留时间确认要求。最终版语义检查为25/26,客户原始描述、回放错误数量问题后的恢复、异车型/固定物、真实参与范围歧义、只有表单含数量、false保留、数量更正和表单时间未确认均通过。唯一失败为历史相对时间没有可靠基准时,运行时校验抛出 `CollectReplyError`;这项是已有时间边界,未将被拒绝的回复算作合格澄清。
新增端到端案例检查呼叫后在线回应及不虚构听觉能力;完整事故模板重复的风格检查单独放在语义probe中,避免这项附加风格目标阻止客户数量问题的完整对话验证。未放宽数量、表单、时间确认或状态断言。
全量单元测试为338通过、13跳过;最终提示词/JSON同步与评估器相关测试为26通过。JSON结构比对确认相对用户指定原文件只有两个提示词输入与采集userChatInput三处变化,其他数据相同。
最终端到端统计:49/50。其中原有案例45/46,新增客户案例4/4。剩余失败是 `browser_addon_collection_skip_known_info_single_no_inferred_count`,时间确认后模型采用已被提取为1的表单数量而进入拍照,旧断言要求继续澄清数量。该场景没有明确车型,与本次客户“小轿车撞固定物”的明确单方描述不同,仍保留为未解决的提取边界;不删除用例、不改为接受拍照。不同迭代中同一场景曾通过,因此不能仅凭一次通过宣称稳定。
| 验证项 | 基线 | 最终版 |
| --- | --- | --- |
| 原有端到端案例(46份YAML的SHA-256一致) | 46/46 | 45/46 |
| 新增客户反馈端到端案例 | 未纳入原46项基线 | 4/4 |
| 补充语义检查 | 18/26 | 25/26 |
| 全量单元测试 | — | 338通过、13跳过 |
| 最终同步及评估器相关测试 | — | 26通过 |
原始结果:`evals/results/customer-feedback/final-v7b-all/` 与 `final-v7-probes.json`;汇总为 `comparison.json`。逐例审核页为 `evals/results/customer-feedback/review.html`,使用 `skill-creator/eval-viewer/generate_review.py` 生成,展示原提示词与最终提示词的实际回复、字段和断言。评估产物沿用Git忽略规则,不随仓库提交。
各次中间失败均保留在原始运行目录,不与最终结果混合统计。单次通过率只代表本次样本,不能证明所有转写与模型输出都稳定。补充基线使用原提示词和同一新版运行时上下文,用于比较提示词行为;原46项基线在修改采集用户消息前运行。
## 数量边界确认后的最终验证
本次采用用户明确的新边界,采集与提取均将完整倒车撞固定物按单方机动车数量1处理,表单已有有效数量且无新更正/冲突时直接采用。未填字段提示仅在部分表单时提供,并按采集顺序排列;数量已知、人伤仍未知时继续1002询问人伤,不能提前拍照。时间规则和历史时间基准案例未修改,后一项显式暂缓。
使用真实 `deepseek-v4.1-flash`、temperature=0:
| 验证项 | 本次最终结果 |
| --- | --- |
| 全部端到端案例 | 50/50 |
| 数量及通用语义检查 | 29/29 |
| 历史相对时间缺基准检查 | 1项暂缓,不计为通过 |
| 提示词同步、上下文和评估器相关单元测试 | 26通过 |
原有46份YAML中,44份SHA-256保持不变;两个历史名为 `single_no_inferred_count` 的案例按用户确认边界更新,并增强为检查表单数量1及禁止再次询问数量/车型。原始倒车事故描述保持不变。新增4份客户反馈案例继续通过。
专项覆盖:表单只有数量记录而历史未展示原回答、表单已有1/2/3辆、已有数量但其他字段缺失、当前明确更正数量、完整倒车撞其他固定物、真实其他车辆参与不明。模型在“数量1、人伤未填”的案例中保留数量,正确询问人伤;不再把减少数量追问变成跳过其他必要字段。
本次全量单元测试首跑337通过、13跳过、1失败,失败为 `test_sqlite_disconnect_after_reply_finishes_preserves_committed_snapshot` 的SQLite中断事务时序断言;单项复跑通过(1 passed)。未修改存储实现,也未把单项复跑说成整套一次通过。相关26项测试在最终修改后通过,`git diff --check`通过。
原始结果:`evals/results/customer-feedback/quantity-ordered-all/`、`quantity-ordered-probes.json`;汇总:`quantity-summary.json`;审核页:`quantity-review.html`。全量单测首跑日志为 `quantity-unit-tests.log`。中间失败和被交互中断的未完成运行保留,不并入最终通过数。审核页由skill-creator自带生成器制作,展示本轮29项检查的真实回复、表单与断言。
复现本次数量边界检查:
```bash
conda run -n py3.11 python -m evals.prompt_compare --output-dir evals/results/customer-feedback/quantity-reproduce-all
conda run -n py3.11 python -m evals.prompt_probes --runtime --exclude-case missing_historical_time_anchor --output evals/results/customer-feedback/quantity-reproduce-probes.json
```