fix(workflow): align non-motor accident extraction and evaluations

This commit is contained in:
Eric Wang
2026-10-21 15:34:11 +08:00
parent 3fe99e71fd
commit 248eb41c78
56 changed files with 7641 additions and 821 deletions
+64 -5
View File
@@ -1,12 +1,38 @@
# Workflow evaluations
The evaluation runner calls a live FastAPI service backed by FastGPT. For now,
each conversation step checks only the SSE `stage_code.nextStageCode` value.
During the run it also prints the live dialogue and saves transcripts.
The evaluation runner calls a live FastAPI service backed by FastGPT. Each
conversation step checks the SSE `stage_code.nextStageCode` value and optional
form / reply expectations. During the run it prints the dialogue and saves
transcripts. Cases target `workflow/20261021`; import that workflow into FastGPT
before running against a service.
## Case format
Add YAML files anywhere under `evals/cases/` (e.g. `direct/`, `browser_addon/`, `no_false/` for false-interrupt / false-transfer checks):
Add YAML files anywhere under `evals/cases/`:
- `collection_skip_known_info/`: skip known fields and clarify missing information.
- `non_motor_vehicle/`: both client modes, with separate flow and extraction cases.
`all_info`, `self_collision`, `no_other_vehicle`, and `explicit_correction`
cover conversation flow and known facts. `electric_bicycle` verifies an
electric bicycle hitting a fixed object and transferring to `0003` without
asking for car counts. These cases accept the described non-motor-vehicle
collision as having `vehicle_count: 0` from the first accident description.
`zero_count_bundle` / `zero_count_history` check zero extraction and retention.
Explicit correction to one car must update the count to `1`.
Both collection and extraction prompts in `workflow/20261021` use this policy
only for an explicitly described non-motor-vehicle single-party collision with
a fixed object; other vehicle involvement or unclear relations do not imply zero.
- `time_confirmation/`: mandatory full-date confirmation, retaining supplemental
information while confirmation is pending, and confirming corrected times.
- `no_false/`: ASR noise must not cause premature transfer.
- `photo_button_guidance/`: button help and failure handling.
- `photo_skip_plate_confirm/`: current photo transitions without plate confirmation.
Obsolete scripts requiring repeated time/count confirmations or the removed
`2004` / `2016` photo states have been removed. Photo transition cases use a
complete accident description, confirm its standardized time, then reach their
target branch. Time confirmation is mandatory even when all other information
is already known; an unchanged time is confirmed only once.
```yaml
name: direct_initial_stage
@@ -26,6 +52,29 @@ steps:
`browser_addon`). Steps in one file share the same generated `sessionId`.
Different cases always use different session IDs.
Optional `expect` fields:
- `form`: partial equality against the cumulative form, including value types.
- `formMatches`: field-to-regex mapping for free-text descriptions.
- `formAbsent`: fields that must not have been extracted yet.
- `replyMatches` / `replyNotMatches`: required / forbidden reply regexes.
If any step has a form expectation, the runner sends `needFormUpdate: true`
throughout that case, merges SSE `formUpdate` deltas across turns, and records
both updates and form snapshots. Thus an omitted unchanged field is accepted,
while a later incorrect overwrite fails. SSE errors also fail the step.
Existing cases without reply expectations still require manual review of their
commented wording requirements.
```yaml
- input: 没有人员伤亡。
expect:
stageCode: "1002"
form: {has_injury: false, has_non_motor_vehicle_hit: true}
replyMatches: "(什么时候|几点|事故.*时间)"
replyNotMatches: "(有没有|是否).*非机动车"
```
## Run
Start the API, then execute:
@@ -45,9 +94,19 @@ Run selected cases:
```bash
python evals/run.py --case direct
python evals/run.py --case browser_addon
python evals/run.py --case no_false
python evals/run.py --case non_motor_vehicle
python evals/run.py --case non_motor_vehicle_electric_bicycle
python evals/run.py --case non_motor_vehicle_zero_count
python evals/run.py --case time_confirmation
python evals/run.py --case photo_skip_plate_confirm
```
There are 14 non-motor-vehicle cases: 10 flow cases and 4 zero-count extraction
cases. Both groups require `vehicle_count: 0` for the described non-motor-vehicle
collisions, while unrelated chatter must leave accident fields absent. Vehicle
type corrections, time confirmation, and transfer checks remain active. The
runner still stops each case at its first failure.
## Dialogue and transcripts
While a case runs, each step prints the user input, bot reply (`text_delta`),
-67
View File
@@ -1,67 +0,0 @@
name: browser_addon_initial_stage
clientMode: browser_addon
steps:
- input: hi
expect:
stageCode: browser_addon.1001
- input: 继续办理
expect:
stageCode: browser_addon.1002
- input: 十分钟之前两车追尾
expect:
stageCode: browser_addon.1002
- input: 没有
expect:
stageCode: browser_addon.1002
- input: 没有
expect:
stageCode: browser_addon.1002
- input: 十分钟之前
expect:
stageCode: browser_addon.1002
- input: 对
expect:
stageCode: browser_addon.1002
- input: 是的
expect:
stageCode: browser_addon.1002
- input: 对
expect:
stageCode: browser_addon.2010
- input: 【拍摄完成】
expect:
stageCode: browser_addon.2011
- input: 【拍摄完成】
expect:
stageCode: browser_addon.2012
- input: 【拍摄完成】
expect:
stageCode: browser_addon.2013
- input: 【拍摄完成】
expect:
stageCode: browser_addon.2014
- input: 【拍摄完成】
expect:
stageCode: browser_addon.2015
- input: 【拍摄完成】
expect:
stageCode: browser_addon.2016
- input: 没错
expect:
stageCode: browser_addon.0000
@@ -1,8 +1,8 @@
name: browser_addon_collection_skip_known_info_dual_all_info
clientMode: browser_addon
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,7 +14,13 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 信息齐全,直接引导双车拍照;不得追加任何已知字段的确认问题。
# 其他信息齐全,先确认完整时间;确认后直接双车拍照。
- input: 十分钟前两辆汽车追尾,没有人员受伤,没有撞到非机动车或行人,我还在事故现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2010"
@@ -1,8 +1,8 @@
name: browser_addon_collection_skip_known_info_dual_bundle_remaining
clientMode: browser_addon
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -19,7 +19,12 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 本轮同时补齐三个字段,直接分流,不拆成多个确认轮次。
# 一次补齐剩余信息,先复述完整时间等待确认。
- input: 没有撞到非机动车或行人,十分钟前发生的,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2010"
@@ -1,8 +1,8 @@
name: browser_addon_collection_skip_known_info_dual_fill_missing
clientMode: browser_addon
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,17 +14,24 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 仅询问是否撞到非机动车,不再核对人伤、时间或两辆车的数量。
# 先询问是否涉及非机动车,不再核对已知人伤或数量。
- input: 十分钟前两辆汽车追尾,没有人受伤。
expect:
stageCode: "browser_addon.1002"
# 仅询问是否还在现场,不要求确认十分钟前的标准时间。
# 复述换算后的完整事故时间,等待用户确认。
- input: 没有撞到非机动车,也没有行人。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.1002"
# 最后补齐在场信息即分流,不再询问或确认车辆数量。
- input: 在,我还在现场。
expect:
stageCode: "browser_addon.2010"
@@ -1,8 +1,8 @@
name: browser_addon_collection_skip_known_info_dual_unclear_injury
clientMode: browser_addon
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -19,7 +19,13 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 人伤澄清后所有字段齐全,直接双车拍照。
# 人伤澄清后确认完整时间,用户确认后直接双车拍照。
- input: 看清楚了,没有人受伤。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2010"
@@ -1,8 +1,8 @@
name: browser_addon_collection_skip_known_info_single_all_info
clientMode: browser_addon
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,7 +14,13 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 信息齐全,直接引导拍摄车辆前方;不得再确认时间、数量、人伤或在场情况。
# 其他信息齐全,先确认完整时间;确认后直接单车拍照。
- input: 十分钟前我倒车撞到护栏,事故只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在事故现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2000"
@@ -1,8 +1,8 @@
name: browser_addon_collection_skip_known_info_single_no_inferred_count
clientMode: browser_addon
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 只询问机动车数量,不能从撞护栏推断只有一辆车;不得重问其他字段。
# 先确认完整时间,再询问缺失的机动车数量;不能从撞护栏推断只有一辆车。
- input: 十分钟前我倒车撞到了护栏,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.1002"
# 明确数量后直接单车拍照,不再要求数量确认。
- input: 只有一辆汽车。
expect:
stageCode: "browser_addon.2000"
@@ -1,8 +1,8 @@
name: direct_collection_skip_known_info_dual_all_info
clientMode: direct
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,7 +14,13 @@ steps:
expect:
stageCode: "1002"
# 信息齐全,直接引导双车拍照;不得追加任何已知字段的确认问题。
# 其他信息齐全,先确认完整时间;确认后直接双车拍照。
- input: 十分钟前两辆汽车追尾,没有人员受伤,没有撞到非机动车或行人,我还在事故现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2010"
@@ -1,8 +1,8 @@
name: direct_collection_skip_known_info_dual_bundle_remaining
clientMode: direct
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -19,7 +19,12 @@ steps:
expect:
stageCode: "1002"
# 本轮同时补齐三个字段,直接分流,不拆成多个确认轮次。
# 一次补齐剩余信息,先复述完整时间等待确认。
- input: 没有撞到非机动车或行人,十分钟前发生的,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2010"
@@ -1,8 +1,8 @@
name: direct_collection_skip_known_info_dual_fill_missing
clientMode: direct
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,17 +14,24 @@ steps:
expect:
stageCode: "1002"
# 仅询问是否撞到非机动车,不再核对人伤、时间或两辆车的数量。
# 先询问是否涉及非机动车,不再核对已知人伤或数量。
- input: 十分钟前两辆汽车追尾,没有人受伤。
expect:
stageCode: "1002"
# 仅询问是否还在现场,不要求确认十分钟前的标准时间。
# 复述换算后的完整事故时间,等待用户确认。
- input: 没有撞到非机动车,也没有行人。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "1002"
# 最后补齐在场信息即分流,不再询问或确认车辆数量。
- input: 在,我还在现场。
expect:
stageCode: "2010"
@@ -1,8 +1,8 @@
name: direct_collection_skip_known_info_dual_unclear_injury
clientMode: direct
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -19,7 +19,13 @@ steps:
expect:
stageCode: "1002"
# 人伤澄清后所有字段齐全,直接双车拍照。
# 人伤澄清后确认完整时间,用户确认后直接双车拍照。
- input: 看清楚了,没有人受伤。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2010"
@@ -1,8 +1,8 @@
name: direct_collection_skip_known_info_single_all_info
clientMode: direct
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,7 +14,13 @@ steps:
expect:
stageCode: "1002"
# 信息齐全,直接引导拍摄车辆前方;不得再确认时间、数量、人伤或在场情况。
# 其他信息齐全,先确认完整时间;确认后直接单车拍照。
- input: 十分钟前我倒车撞到护栏,事故只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在事故现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2000"
@@ -1,8 +1,8 @@
name: direct_collection_skip_known_info_single_no_inferred_count
clientMode: direct
# 对应 workflow/20261016 的事故信息收集提示词。
# runner 仅校验状态码;以下注释中的提问内容需结合输出对话手动核对。
# 对应 workflow/20261021 的事故信息收集提示词。
# 时间确认由回复断言检查;其余注释要求结合对话核对。
steps:
# 新会话返回安全准备提示。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "1002"
# 只询问机动车数量,不能从撞护栏推断只有一辆车;不得重问其他字段。
# 先确认完整时间,再询问缺失的机动车数量;不能从撞护栏推断只有一辆车。
- input: 十分钟前我倒车撞到了护栏,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "1002"
# 明确数量后直接单车拍照,不再要求数量确认。
- input: 只有一辆汽车。
expect:
stageCode: "2000"
-103
View File
@@ -1,103 +0,0 @@
name: direct_initial_stage
clientMode: direct
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟之前两车追尾
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 十分钟之前
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 是的
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "2010"
- input: 【拍摄完成】
expect:
stageCode: "2011"
- input: 【拍摄完成】
expect:
stageCode: "2012"
- input: 【拍摄完成】
expect:
stageCode: "2013"
- input: 【拍摄完成】
expect:
stageCode: "2014"
- input: 【拍摄完成】
expect:
stageCode: "2015"
- input: 【拍摄完成】
expect:
stageCode: "2016"
- input: 没错
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 李四
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 移交完毕
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 刘云
expect:
stageCode: "1002"
- input: 对的
expect:
stageCode: "1002"
- input: 是
expect:
stageCode: "0000"
@@ -1,89 +0,0 @@
name: direct_confirm_dual_no_false_0002
clientMode: direct
# During dual-car info confirm (3002 -> external 1002), short/garbled answers
# must stay on 1002, not jump to 0002 after a few unclear turns.
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟之前两车追尾
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 十分钟之前
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 是的
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "2010"
- input: 【拍摄完成】
expect:
stageCode: "2011"
- input: 【拍摄完成】
expect:
stageCode: "2012"
- input: 【拍摄完成】
expect:
stageCode: "2013"
- input: 【拍摄完成】
expect:
stageCode: "2014"
- input: 【拍摄完成】
expect:
stageCode: "2015"
- input: 【拍摄完成】
expect:
stageCode: "2016"
- input: 没错
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 李四
expect:
stageCode: "1002"
- input: 这个
expect:
stageCode: "1002"
- input: 试试
expect:
stageCode: "1002"
- input: 什么意思
expect:
stageCode: "1002"
@@ -1,81 +0,0 @@
name: direct_confirm_dual_no_false_0003
clientMode: direct
# During dual-car info confirm, ASR damage/garbled injury-like words must not
# jump to 0003; stay on 1002 (closed confirm / continue current question).
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟之前两车追尾
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 十分钟之前
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 是的
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "2010"
- input: 【拍摄完成】
expect:
stageCode: "2011"
- input: 【拍摄完成】
expect:
stageCode: "2012"
- input: 【拍摄完成】
expect:
stageCode: "2013"
- input: 【拍摄完成】
expect:
stageCode: "2014"
- input: 【拍摄完成】
expect:
stageCode: "2015"
- input: 【拍摄完成】
expect:
stageCode: "2016"
- input: 没错
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 不,刚摧毁了
expect:
stageCode: "1002"
- input: 伤……没有吧
expect:
stageCode: "1002"
@@ -1,81 +0,0 @@
name: direct_confirm_single_no_false_0002
clientMode: direct
# During single-car info confirm (3001 -> external 1002), short/garbled answers
# must stay on 1002, not jump to 0002 after a few unclear turns.
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟之前一辆车撞到护栏
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "2000"
- input: 【拍摄完成】
expect:
stageCode: "2001"
- input: 【拍摄完成】
expect:
stageCode: "2002"
- input: 【拍摄完成】
expect:
stageCode: "2003"
- input: 【拍摄完成】
expect:
stageCode: "2004"
- input: 是的
expect:
stageCode: "2005"
- input: 前方
expect:
stageCode: "1002"
- input: 是的
expect:
stageCode: "1002"
- input: 张三
expect:
stageCode: "1002"
- input: 这个
expect:
stageCode: "1002"
- input: 试试
expect:
stageCode: "1002"
- input: 什么意思
expect:
stageCode: "1002"
@@ -1,73 +0,0 @@
name: direct_confirm_single_no_false_0003
clientMode: direct
# During single-car info confirm, ASR damage/garbled injury-like words must not
# jump to 0003; stay on 1002 (closed confirm / continue current question).
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟之前一辆车撞到护栏
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "2000"
- input: 【拍摄完成】
expect:
stageCode: "2001"
- input: 【拍摄完成】
expect:
stageCode: "2002"
- input: 【拍摄完成】
expect:
stageCode: "2003"
- input: 【拍摄完成】
expect:
stageCode: "2004"
- input: 是的
expect:
stageCode: "2005"
- input: 前方
expect:
stageCode: "1002"
- input: 是的
expect:
stageCode: "1002"
- input: 不,刚摧毁了
expect:
stageCode: "1002"
- input: 伤……没有吧
expect:
stageCode: "1002"
@@ -1,77 +0,0 @@
name: direct_photo_dual_no_false_0002
clientMode: direct
# At dual-car plate confirm (2016), short/garbled answers must stay on 2016,
# not jump to 0002 after a few unclear turns.
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟之前两车追尾
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 十分钟之前
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 是的
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "2010"
- input: 【拍摄完成】
expect:
stageCode: "2011"
- input: 【拍摄完成】
expect:
stageCode: "2012"
- input: 【拍摄完成】
expect:
stageCode: "2013"
- input: 【拍摄完成】
expect:
stageCode: "2014"
- input: 【拍摄完成】
expect:
stageCode: "2015"
- input: 【拍摄完成】
expect:
stageCode: "2016"
- input: 这个
expect:
stageCode: "2016"
- input: 试试
expect:
stageCode: "2016"
- input: 什么意思
expect:
stageCode: "2016"
@@ -1,81 +0,0 @@
name: direct_photo_single_no_false_0002
clientMode: direct
# At single-car plate/damage confirm, short/garbled answers must stay on 2004/2005,
# not jump to 0002 after a few unclear turns.
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟之前一辆车撞到护栏
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 十分钟之前
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "1002"
- input: 是的
expect:
stageCode: "1002"
- input: 对
expect:
stageCode: "2000"
- input: 【拍摄完成】
expect:
stageCode: "2001"
- input: 【拍摄完成】
expect:
stageCode: "2002"
- input: 【拍摄完成】
expect:
stageCode: "2003"
- input: 【拍摄完成】
expect:
stageCode: "2004"
- input: 这个
expect:
stageCode: "2004"
- input: 试试
expect:
stageCode: "2004"
- input: 是的
expect:
stageCode: "2005"
- input: 这个
expect:
stageCode: "2005"
- input: 试试
expect:
stageCode: "2005"
- input: 什么意思
expect:
stageCode: "2005"
@@ -0,0 +1,24 @@
# 流程回归:非机动车撞固定物按本组口径机动车数量为0,检查事实保持、时间确认及0003分流。
name: browser_addon_non_motor_vehicle_all_info
clientMode: browser_addon
steps:
- input: "【开始】"
expect:
stageCode: "browser_addon.1001"
- input: "继续办理"
expect:
stageCode: "browser_addon.1002"
- input: "十分钟前我骑非机动车自己撞在路边石墩上,只有我这一辆非机动车,没有其他车辆或行人,没有人员伤亡,我还在现场。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"has_injury":false,"is_on_scene":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.0003"
form: {"vehicle_count":0}
replyNotMatches: "[??]"
@@ -0,0 +1,45 @@
# 电动自行车撞固定物:按本组口径机动车数量为0,完成时间确认及现场采集后进入0003,不追问汽车数量。
name: browser_addon_non_motor_vehicle_electric_bicycle
clientMode: browser_addon
steps:
- input: "【开始】"
expect:
stageCode: "browser_addon.1001"
- input: "继续办理"
expect:
stageCode: "browser_addon.1002"
- input: "我骑的是非机动车,电动自行车,撞到了路边的石墩子上。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员伤亡。"
expect:
stageCode: "browser_addon.1002"
form: {"has_injury":false,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
replyNotMatches: "(几辆|数量|是否.*非机动车|有没有.*非机动车)"
- input: "十分钟前发生的。"
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: "对的。"
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
replyMatches: "现场"
replyNotMatches: "几辆|数量|这个时间对吗"
- input: "我还在事故现场。"
expect:
stageCode: "browser_addon.0003"
form: {"has_non_motor_vehicle_hit":true,"has_injury":false,"is_on_scene":true,"vehicle_count":0}
replyMatches: "转接.*(人工|警员)|转.*人工"
replyNotMatches: "几辆|数量|这个时间对吗"
@@ -0,0 +1,33 @@
# 非机动车撞固定物时机动车数量为0;用户明确更正为一辆汽车后,数量更新为1。
name: browser_addon_non_motor_vehicle_explicit_correction
clientMode: browser_addon
steps:
- input: "【开始】"
expect:
stageCode: "browser_addon.1001"
- input: "继续办理"
expect:
stageCode: "browser_addon.1002"
- input: "我这里是一辆非机动车,撞到了路边的石墩子上。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
formAbsent: ["has_injury","accident_time","is_on_scene"]
replyMatches: "(有没有|是否).*受伤"
- input: "刚才说错了,我的是汽车,不是非机动车,事故只有我这一辆汽车,没有涉及非机动车或行人。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":false,"vehicle_count":1}
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员受伤,十分钟前发生的,我还在事故现场。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":false,"vehicle_count":1,"has_injury":false,"is_on_scene":true}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2000"
@@ -0,0 +1,51 @@
# 流程回归:非机动车撞固定物按本组口径机动车数量为0,检查事实保持、时间确认及0003分流。
name: browser_addon_non_motor_vehicle_no_other_vehicle
clientMode: browser_addon
steps:
- input: "【开始】"
expect:
stageCode: "browser_addon.1001"
- input: "继续办理"
expect:
stageCode: "browser_addon.1002"
- input: "我现在没有按住说话,为什么也会记录进去?是不是有bug?"
expect:
stageCode: "browser_addon.1002"
formAbsent: ["has_injury","has_non_motor_vehicle_hit","vehicle_count","accident_cause","accident_time","is_on_scene"]
- input: "我这里是一辆非机动车,撞到了路边的石墩子上。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
formAbsent: ["has_injury","accident_time","is_on_scene"]
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员伤亡。"
expect:
stageCode: "browser_addon.1002"
form: {"has_injury":false,"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
replyNotMatches: "(有没有|是否).*非机动车"
- input: "我已经说过了,是撞在石墩子上,没有别的车子,就我自己的车子。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
replyMatches: "(什么时候|几点|事故.*时间)"
- input: "十分钟前。"
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
replyMatches: "现场"
- input: "我还在事故现场。"
expect:
stageCode: "browser_addon.0003"
form: {"has_non_motor_vehicle_hit":true,"is_on_scene":true,"vehicle_count":0}
@@ -0,0 +1,46 @@
# 流程回归:非机动车撞固定物按本组口径机动车数量为0,检查事实保持、时间确认及0003分流。
name: browser_addon_non_motor_vehicle_self_collision
clientMode: browser_addon
steps:
- input: "【开始】"
expect:
stageCode: "browser_addon.1001"
- input: "继续办理"
expect:
stageCode: "browser_addon.1002"
- input: "我这个是非机动车,然后自己撞在路边的石墩子上。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
formAbsent: ["has_injury","accident_time","is_on_scene"]
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员伤亡。"
expect:
stageCode: "browser_addon.1002"
form: {"has_injury":false,"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
replyNotMatches: "(有没有|是否).*非机动车"
- input: "没有别的车子,就我自己的车子。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
- input: "十分钟前发生的。"
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
replyMatches: "现场"
- input: "我还在事故现场。"
expect:
stageCode: "browser_addon.0003"
form: {"has_non_motor_vehicle_hit":true,"is_on_scene":true,"vehicle_count":0}
@@ -0,0 +1,17 @@
# 提取专项:明确只有非机动车时必须向前端发送机动车数量0,不能被空值比较吞掉。
name: browser_addon_non_motor_vehicle_zero_count_bundle
clientMode: browser_addon
steps:
- input: "【开始】"
expect:
stageCode: "browser_addon.1001"
- input: "继续办理"
expect:
stageCode: "browser_addon.1002"
- input: "十分钟前我骑非机动车自己撞在路边石墩上,只有我这一辆非机动车,没有其他车辆或行人,没有人员伤亡,我还在现场。"
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
@@ -0,0 +1,27 @@
# 提取专项:非机动车撞固定物按本组口径记录机动车数量0,后续补充“没有别的车”时保留0和非机动车事实。
name: browser_addon_non_motor_vehicle_zero_count_history
clientMode: browser_addon
steps:
- input: "【开始】"
expect:
stageCode: "browser_addon.1001"
- input: "继续办理"
expect:
stageCode: "browser_addon.1002"
- input: "我这里是一辆非机动车,撞到了路边的石墩子上。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
- input: "没有人员伤亡。"
expect:
stageCode: "browser_addon.1002"
form: {"vehicle_count":0}
- input: "我已经说过了,是撞在石墩子上,没有别的车子,就我自己的车子。"
expect:
stageCode: "browser_addon.1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
@@ -0,0 +1,24 @@
# 流程回归:非机动车撞固定物按本组口径机动车数量为0,检查事实保持、时间确认及0003分流。
name: direct_non_motor_vehicle_all_info
clientMode: direct
steps:
- input: "【开始】"
expect:
stageCode: "1001"
- input: "继续办理"
expect:
stageCode: "1002"
- input: "十分钟前我骑非机动车自己撞在路边石墩上,只有我这一辆非机动车,没有其他车辆或行人,没有人员伤亡,我还在现场。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"has_injury":false,"is_on_scene":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "0003"
form: {"vehicle_count":0}
replyNotMatches: "[??]"
@@ -0,0 +1,45 @@
# 电动自行车撞固定物:按本组口径机动车数量为0,完成时间确认及现场采集后进入0003,不追问汽车数量。
name: direct_non_motor_vehicle_electric_bicycle
clientMode: direct
steps:
- input: "【开始】"
expect:
stageCode: "1001"
- input: "继续办理"
expect:
stageCode: "1002"
- input: "我骑的是非机动车,电动自行车,撞到了路边的石墩子上。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员伤亡。"
expect:
stageCode: "1002"
form: {"has_injury":false,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
replyNotMatches: "(几辆|数量|是否.*非机动车|有没有.*非机动车)"
- input: "十分钟前发生的。"
expect:
stageCode: "1002"
form: {"vehicle_count":0}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: "对的。"
expect:
stageCode: "1002"
form: {"vehicle_count":0}
replyMatches: "现场"
replyNotMatches: "几辆|数量|这个时间对吗"
- input: "我还在事故现场。"
expect:
stageCode: "0003"
form: {"has_non_motor_vehicle_hit":true,"has_injury":false,"is_on_scene":true,"vehicle_count":0}
replyMatches: "转接.*(人工|警员)|转.*人工"
replyNotMatches: "几辆|数量|这个时间对吗"
@@ -0,0 +1,33 @@
# 非机动车撞固定物时机动车数量为0;用户明确更正为一辆汽车后,数量更新为1。
name: direct_non_motor_vehicle_explicit_correction
clientMode: direct
steps:
- input: "【开始】"
expect:
stageCode: "1001"
- input: "继续办理"
expect:
stageCode: "1002"
- input: "我这里是一辆非机动车,撞到了路边的石墩子上。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
formAbsent: ["has_injury","accident_time","is_on_scene"]
replyMatches: "(有没有|是否).*受伤"
- input: "刚才说错了,我的是汽车,不是非机动车,事故只有我这一辆汽车,没有涉及非机动车或行人。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":false,"vehicle_count":1}
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员受伤,十分钟前发生的,我还在事故现场。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":false,"vehicle_count":1,"has_injury":false,"is_on_scene":true}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2000"
@@ -0,0 +1,51 @@
# 流程回归:非机动车撞固定物按本组口径机动车数量为0,检查事实保持、时间确认及0003分流。
name: direct_non_motor_vehicle_no_other_vehicle
clientMode: direct
steps:
- input: "【开始】"
expect:
stageCode: "1001"
- input: "继续办理"
expect:
stageCode: "1002"
- input: "我现在没有按住说话,为什么也会记录进去?是不是有bug?"
expect:
stageCode: "1002"
formAbsent: ["has_injury","has_non_motor_vehicle_hit","vehicle_count","accident_cause","accident_time","is_on_scene"]
- input: "我这里是一辆非机动车,撞到了路边的石墩子上。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
formAbsent: ["has_injury","accident_time","is_on_scene"]
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员伤亡。"
expect:
stageCode: "1002"
form: {"has_injury":false,"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
replyNotMatches: "(有没有|是否).*非机动车"
- input: "我已经说过了,是撞在石墩子上,没有别的车子,就我自己的车子。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
replyMatches: "(什么时候|几点|事故.*时间)"
- input: "十分钟前。"
expect:
stageCode: "1002"
form: {"vehicle_count":0}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "1002"
form: {"vehicle_count":0}
replyMatches: "现场"
- input: "我还在事故现场。"
expect:
stageCode: "0003"
form: {"has_non_motor_vehicle_hit":true,"is_on_scene":true,"vehicle_count":0}
@@ -0,0 +1,46 @@
# 流程回归:非机动车撞固定物按本组口径机动车数量为0,检查事实保持、时间确认及0003分流。
name: direct_non_motor_vehicle_self_collision
clientMode: direct
steps:
- input: "【开始】"
expect:
stageCode: "1001"
- input: "继续办理"
expect:
stageCode: "1002"
- input: "我这个是非机动车,然后自己撞在路边的石墩子上。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
formMatches: {"accident_cause":"^其他:(?=.*非机动车)(?=.*石墩).+"}
formAbsent: ["has_injury","accident_time","is_on_scene"]
replyMatches: "(有没有|是否).*受伤"
- input: "没有人员伤亡。"
expect:
stageCode: "1002"
form: {"has_injury":false,"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
replyNotMatches: "(有没有|是否).*非机动车"
- input: "没有别的车子,就我自己的车子。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
replyMatches: "(什么时候|几点|事故.*时间)"
- input: "十分钟前发生的。"
expect:
stageCode: "1002"
form: {"vehicle_count":0}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "1002"
form: {"vehicle_count":0}
replyMatches: "现场"
- input: "我还在事故现场。"
expect:
stageCode: "0003"
form: {"has_non_motor_vehicle_hit":true,"is_on_scene":true,"vehicle_count":0}
@@ -0,0 +1,17 @@
# 提取专项:明确只有非机动车时必须向前端发送机动车数量0,不能被空值比较吞掉。
name: direct_non_motor_vehicle_zero_count_bundle
clientMode: direct
steps:
- input: "【开始】"
expect:
stageCode: "1001"
- input: "继续办理"
expect:
stageCode: "1002"
- input: "十分钟前我骑非机动车自己撞在路边石墩上,只有我这一辆非机动车,没有其他车辆或行人,没有人员伤亡,我还在现场。"
expect:
stageCode: "1002"
form: {"vehicle_count":0}
@@ -0,0 +1,27 @@
# 提取专项:非机动车撞固定物按本组口径记录机动车数量0,后续补充“没有别的车”时保留0和非机动车事实。
name: direct_non_motor_vehicle_zero_count_history
clientMode: direct
steps:
- input: "【开始】"
expect:
stageCode: "1001"
- input: "继续办理"
expect:
stageCode: "1002"
- input: "我这里是一辆非机动车,撞到了路边的石墩子上。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
- input: "没有人员伤亡。"
expect:
stageCode: "1002"
form: {"vehicle_count":0}
- input: "我已经说过了,是撞在石墩子上,没有别的车子,就我自己的车子。"
expect:
stageCode: "1002"
form: {"has_non_motor_vehicle_hit":true,"vehicle_count":0}
@@ -1,8 +1,8 @@
name: browser_addon_photo_button_guidance_dual_failure
clientMode: browser_addon
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前两辆汽车追尾,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2010"
# 说明界面中间的拍照按钮并保留当前固定拍摄指令。
- input: 拍照按钮在哪?
expect:
stageCode: "browser_addon.2010"
@@ -1,8 +1,8 @@
name: browser_addon_photo_button_guidance_dual_guidance
clientMode: browser_addon
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前两辆汽车追尾,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2010"
# 必须说明可点击手机界面中间的拍照按钮,并完整保留当前固定指令(第一辆车侧前方、看清车牌);不得推进。
- input: 拍照按钮在哪?
expect:
stageCode: "browser_addon.2010"
@@ -2,7 +2,7 @@ name: browser_addon_photo_button_guidance_dual_no_repeat
clientMode: browser_addon
# 回归:解释一次按钮后,正常推进不再重复;明确再次求助仍可解释。
# runner 只校验状态码,按钮说明是否重复必须手动检查输出对话。
# runner 校验状态码和时间确认,按钮说明是否重复必须手动检查输出对话。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 只输出原有拍摄指令,不主动附加按钮说明。
# 先确认完整事故时间,确认后只输出原有拍摄指令。
- input: 十分钟前两辆汽车追尾,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2010"
# 首次笼统求助可说明一次界面中间的拍照按钮,保留固定指令。
- input: 这个什么
expect:
stageCode: "browser_addon.2010"
@@ -1,8 +1,8 @@
name: browser_addon_photo_button_guidance_single_failure
clientMode: browser_addon
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前我倒车撞到了护栏,事故只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2000"
# 说明界面中间的拍照按钮并保留当前固定拍摄指令。
- input: 拍照按钮在哪?
expect:
stageCode: "browser_addon.2000"
@@ -1,8 +1,8 @@
name: browser_addon_photo_button_guidance_single_guidance
clientMode: browser_addon
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前我倒车撞到了护栏,事故只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2000"
# 必须说明可点击手机界面中间的拍照按钮,并完整保留当前固定指令(车辆前方);不得推进。
- input: 拍照按钮在哪?
expect:
stageCode: "browser_addon.2000"
@@ -2,7 +2,7 @@ name: browser_addon_photo_button_guidance_single_no_repeat
clientMode: browser_addon
# 回归:解释一次按钮后,正常推进不再重复;明确再次求助仍可解释。
# runner 只校验状态码,按钮说明是否重复必须手动检查输出对话。
# runner 校验状态码和时间确认,按钮说明是否重复必须手动检查输出对话。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "browser_addon.1002"
# 只输出原有拍摄指令,不主动附加按钮说明。
# 先确认完整事故时间,确认后只输出原有拍摄指令。
- input: 十分钟前我倒车撞到护栏,只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.2000"
# 首次笼统求助可说明一次界面中间的拍照按钮,保留固定指令。
- input: 这个什么
expect:
stageCode: "browser_addon.2000"
@@ -1,8 +1,8 @@
name: direct_photo_button_guidance_dual_failure
clientMode: direct
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前两辆汽车追尾,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2010"
# 说明界面中间的拍照按钮并保留当前固定拍摄指令。
- input: 拍照按钮在哪?
expect:
stageCode: "2010"
@@ -1,8 +1,8 @@
name: direct_photo_button_guidance_dual_guidance
clientMode: direct
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前两辆汽车追尾,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2010"
# 必须说明可点击手机界面中间的拍照按钮,并完整保留当前固定指令(第一辆车侧前方、看清车牌);不得推进。
- input: 拍照按钮在哪?
expect:
stageCode: "2010"
@@ -2,7 +2,7 @@ name: direct_photo_button_guidance_dual_no_repeat
clientMode: direct
# 回归:解释一次按钮后,正常推进不再重复;明确再次求助仍可解释。
# runner 只校验状态码,按钮说明是否重复必须手动检查输出对话。
# runner 校验状态码和时间确认,按钮说明是否重复必须手动检查输出对话。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "1002"
# 只输出原有拍摄指令,不主动附加按钮说明。
# 先确认完整事故时间,确认后只输出原有拍摄指令。
- input: 十分钟前两辆汽车追尾,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2010"
# 首次笼统求助可说明一次界面中间的拍照按钮,保留固定指令。
- input: 这个什么
expect:
stageCode: "2010"
@@ -1,8 +1,8 @@
name: direct_photo_button_guidance_single_failure
clientMode: direct
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前我倒车撞到了护栏,事故只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2000"
# 说明界面中间的拍照按钮并保留当前固定拍摄指令。
- input: 拍照按钮在哪?
expect:
stageCode: "2000"
@@ -1,8 +1,8 @@
name: direct_photo_button_guidance_single_guidance
clientMode: direct
# 对应 workflow/20261016 拍照提示词;每个文件使用独立会话。
# runner 只校验状态码,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
# 对应 workflow/20261021 拍照提示词;每个文件使用独立会话。
# runner 校验状态码和时间确认,按钮位置、固定话术及结束后不再提示按钮需结合对话记录手动检查。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "1002"
# 事故信息齐全,直接进入拍照,只输出原有固定指令,不主动追加按钮说明。
# 先确认完整事故时间,确认后进入拍照,不主动追加按钮说明。
- input: 十分钟前我倒车撞到了护栏,事故只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2000"
# 必须说明可点击手机界面中间的拍照按钮,并完整保留当前固定指令(车辆前方);不得推进。
- input: 拍照按钮在哪?
expect:
stageCode: "2000"
@@ -2,7 +2,7 @@ name: direct_photo_button_guidance_single_no_repeat
clientMode: direct
# 回归:解释一次按钮后,正常推进不再重复;明确再次求助仍可解释。
# runner 只校验状态码,按钮说明是否重复必须手动检查输出对话。
# runner 校验状态码和时间确认,按钮说明是否重复必须手动检查输出对话。
steps:
# 新会话。
- input: 新对话
@@ -14,12 +14,19 @@ steps:
expect:
stageCode: "1002"
# 只输出原有拍摄指令,不主动附加按钮说明。
# 先确认完整事故时间,确认后只输出原有拍摄指令。
- input: 十分钟前我倒车撞到护栏,只有一辆汽车,没有人受伤,没有撞到非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "2000"
# 首次笼统求助可说明一次界面中间的拍照按钮,保留固定指令。
- input: 这个什么
expect:
stageCode: "2000"
@@ -1,7 +1,7 @@
name: browser_addon_photo_skip_plate_confirm_dual
clientMode: browser_addon
# 对应 workflow/20261016;每个案例使用独立会话。
# 对应 workflow/20261021;每个案例使用独立会话。
# 本人照片完成后直接结束拍照,不经过 2016。
# direct 的内部 3001/3002 对外映射为 1002。
steps:
@@ -13,34 +13,16 @@ steps:
expect:
stageCode: "browser_addon.1002"
- input: 两辆汽车发生了追尾。
expect:
stageCode: "browser_addon.1002"
- input: 没有
expect:
stageCode: "browser_addon.1002"
- input: 没有
expect:
stageCode: "browser_addon.1002"
- input: 十分钟之前
- input: 十分钟前两辆汽车追尾,没有人受伤,没有涉及非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.1002"
- input: 是的,我还在事故现场
expect:
stageCode: "browser_addon.1002"
- input: 两辆汽车
expect:
stageCode: "browser_addon.2010"
- input: 【拍摄完成】
expect:
stageCode: "browser_addon.2011"
@@ -1,7 +1,7 @@
name: browser_addon_photo_skip_plate_confirm_single
clientMode: browser_addon
# 对应 workflow/20261016;每个案例使用独立会话。
# 对应 workflow/20261021;每个案例使用独立会话。
# 本人照片完成后直接进入车损位置确认 2005,不经过 2004。
# direct 的内部 3001/3002 对外映射为 1002。
steps:
@@ -13,34 +13,16 @@ steps:
expect:
stageCode: "browser_addon.1002"
- input: 我倒车撞到了护栏,涉及一辆汽车。
expect:
stageCode: "browser_addon.1002"
- input: 没有
expect:
stageCode: "browser_addon.1002"
- input: 没有
expect:
stageCode: "browser_addon.1002"
- input: 十分钟之前
- input: 十分钟前我倒车撞到护栏,只有一辆汽车,没有人受伤,没有涉及非机动车或行人,我还在现场。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "browser_addon.1002"
- input: 是的,我还在事故现场
expect:
stageCode: "browser_addon.1002"
- input: 一辆汽车
expect:
stageCode: "browser_addon.2000"
- input: 【拍摄完成】
expect:
stageCode: "browser_addon.2001"
@@ -1,11 +1,11 @@
name: direct_photo_skip_plate_confirm_dual
clientMode: direct
# 对应 workflow/20261016;每个案例使用独立会话。
# 对应 workflow/20261021;每个案例使用独立会话。
# 本人照片完成后直接结束拍照,不经过 2016。
# 手动检查:最后一次拍摄完成的回复同时说明进入信息确认,并询问第一辆车是否由本人驾驶。
# 紧接着回答“是的”后应询问姓名,不应再次询问驾驶员身份。
# 回答“李四”后应核实身份证后四位;runner 仅校验状态码,以上正文需查看对话记录。
# 回答“李四”后应核实身份证后四位;runner 校验状态码和时间确认,身份确认正文需查看对话记录。
# direct 的内部 3001/3002 对外映射为 1002。
steps:
- input: 新对话
@@ -16,34 +16,16 @@ steps:
expect:
stageCode: "1002"
- input: 两辆汽车发生了追尾。
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 十分钟之前
- input: 十分钟前两辆汽车追尾,没有人受伤,没有涉及非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "1002"
- input: 是的,我还在事故现场
expect:
stageCode: "1002"
- input: 两辆汽车
expect:
stageCode: "2010"
- input: 【拍摄完成】
expect:
stageCode: "2011"
@@ -1,7 +1,7 @@
name: direct_photo_skip_plate_confirm_single
clientMode: direct
# 对应 workflow/20261016;每个案例使用独立会话。
# 对应 workflow/20261021;每个案例使用独立会话。
# 本人照片完成后直接进入车损位置确认 2005,不经过 2004。
# direct 的内部 3001/3002 对外映射为 1002。
steps:
@@ -13,34 +13,16 @@ steps:
expect:
stageCode: "1002"
- input: 我倒车撞到了护栏,涉及一辆汽车。
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 没有
expect:
stageCode: "1002"
- input: 十分钟之前
- input: 十分钟前我倒车撞到护栏,只有一辆汽车,没有人受伤,没有涉及非机动车或行人,我还在现场。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 对
expect:
stageCode: "1002"
- input: 是的,我还在事故现场
expect:
stageCode: "1002"
- input: 一辆汽车
expect:
stageCode: "2000"
- input: 【拍摄完成】
expect:
stageCode: "2001"
@@ -0,0 +1,37 @@
# 必须独立确认完整时间;补充在场信息不算确认,更正后重新确认。
name: browser_addon_time_confirmation_correction
clientMode: browser_addon
steps:
- input: 新对话
expect:
stageCode: "browser_addon.1001"
- input: 继续办理
expect:
stageCode: "browser_addon.1002"
- input: 十分钟前两辆汽车追尾,没有人受伤,没有涉及非机动车或行人。
expect:
stageCode: "browser_addon.1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
replyNotMatches: "现场|拍照"
- input: 我还在事故现场。
expect:
stageCode: "browser_addon.1002"
form: {is_on_scene: true}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 不对,事故时间是2025年1月1日8点30分。
expect:
stageCode: "browser_addon.1002"
form: {accident_time: "2025年1月1日8点30分"}
replyMatches: "我记录的时间是2025年0?1月0?1日0?8点30分.*这个时间对吗"
replyNotMatches: "现场|拍照"
- input: 对,就是这个时间。
expect:
stageCode: "browser_addon.2010"
form: {accident_time: "2025年1月1日8点30分", is_on_scene: true}
replyNotMatches: "这个时间对吗|还在.*现场"
@@ -0,0 +1,37 @@
# 必须独立确认完整时间;补充在场信息不算确认,更正后重新确认。
name: direct_time_confirmation_correction
clientMode: direct
steps:
- input: 新对话
expect:
stageCode: "1001"
- input: 继续办理
expect:
stageCode: "1002"
- input: 十分钟前两辆汽车追尾,没有人受伤,没有涉及非机动车或行人。
expect:
stageCode: "1002"
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
replyNotMatches: "现场|拍照"
- input: 我还在事故现场。
expect:
stageCode: "1002"
form: {is_on_scene: true}
replyMatches: "我记录的时间是.*年.*月.*日.*点.*分.*这个时间对吗"
- input: 不对,事故时间是2025年1月1日8点30分。
expect:
stageCode: "1002"
form: {accident_time: "2025年1月1日8点30分"}
replyMatches: "我记录的时间是2025年0?1月0?1日0?8点30分.*这个时间对吗"
replyNotMatches: "现场|拍照"
- input: 对,就是这个时间。
expect:
stageCode: "2010"
form: {accident_time: "2025年1月1日8点30分", is_on_scene: true}
replyNotMatches: "这个时间对吗|还在.*现场"
+78 -5
View File
@@ -25,6 +25,7 @@ class ChatResult:
stage_codes: list[str]
errors: list[dict]
text: str = ""
form_update: dict = field(default_factory=dict)
@dataclass
@@ -37,6 +38,9 @@ class TranscriptTurn:
errors: list[dict]
ok: bool
error: str | None = None
form_update: dict = field(default_factory=dict)
form: dict = field(default_factory=dict)
expectations: dict = field(default_factory=dict)
@dataclass
@@ -88,6 +92,25 @@ def load_cases(cases_dir: Path, name_filters: list[str]) -> list[dict]:
f"{path}: step {index} expect.stageCode is required"
)
expect = step["expect"]
for key in ("form", "formMatches"):
if key in expect and not isinstance(expect[key], dict):
raise ValueError(f"{path}: step {index} expect.{key} must be an object")
for pattern in expect.get("formMatches", {}).values():
if not isinstance(pattern, str):
raise ValueError(f"{path}: step {index} formMatches values must be regex strings")
re.compile(pattern)
if "formAbsent" in expect and (
not isinstance(expect["formAbsent"], list)
or not all(isinstance(key, str) for key in expect["formAbsent"])
):
raise ValueError(f"{path}: step {index} expect.formAbsent must be a list of keys")
for key in ("replyMatches", "replyNotMatches"):
if key in expect:
if not isinstance(expect[key], str):
raise ValueError(f"{path}: step {index} expect.{key} must be a regex string")
re.compile(expect[key])
if name_filters and not any(value in name for value in name_filters):
continue
@@ -103,6 +126,7 @@ def parse_sse(response: httpx.Response) -> ChatResult:
stage_codes: list[str] = []
errors: list[dict] = []
text_parts: list[str] = []
form_update: dict = {}
event_name = ""
data_lines: list[str] = []
@@ -116,6 +140,10 @@ def parse_sse(response: httpx.Response) -> ChatResult:
chunk = payload.get("text")
if isinstance(chunk, str) and chunk:
text_parts.append(chunk)
elif event_name == "formUpdate":
if not isinstance(payload, dict):
raise ValueError("formUpdate SSE payload must be an object")
form_update.update(payload)
elif event_name == "error":
errors.append(payload)
event_name = ""
@@ -134,6 +162,7 @@ def parse_sse(response: httpx.Response) -> ChatResult:
stage_codes=stage_codes,
errors=errors,
text="".join(text_parts),
form_update=form_update,
)
@@ -143,6 +172,7 @@ def run_chat_step(
session_id: str,
client_mode: str,
text: str,
need_form_update: bool = False,
) -> ChatResult:
with client.stream(
"POST",
@@ -153,7 +183,7 @@ def run_chat_step(
"timeStamp": timestamp(),
"text": text,
"clientMode": client_mode,
"needFormUpdate": False,
"needFormUpdate": need_form_update,
"useTextChunk": False,
},
) as response:
@@ -194,6 +224,10 @@ def print_turn(turn: TranscriptTurn) -> None:
f" stage: expected={turn.expected_stage_code} "
f"actual={turn.actual_stage_codes}"
)
if turn.expectations.keys() - {"stageCode"}:
print(f" expect: {turn.expectations}")
if turn.form_update or turn.form:
print(f" formUpdate: {turn.form_update}; form: {turn.form}")
if turn.error:
print(f" error: {turn.error}")
if turn.errors:
@@ -224,6 +258,10 @@ def transcript_to_markdown(transcript: CaseTranscript) -> str:
f"- actual stageCodes: `{turn.actual_stage_codes}`",
]
)
if turn.expectations.keys() - {"stageCode"}:
lines.append(f"- expectations: `{json.dumps(turn.expectations, ensure_ascii=False)}`")
lines.append(f"- formUpdate: `{json.dumps(turn.form_update, ensure_ascii=False)}`")
lines.append(f"- cumulative form: `{json.dumps(turn.form, ensure_ascii=False)}`")
if turn.error:
lines.append(f"- error: `{turn.error}`")
if turn.errors:
@@ -256,6 +294,9 @@ def transcript_to_json(transcript: CaseTranscript) -> dict:
"errors": turn.errors,
"ok": turn.ok,
"error": turn.error,
"expectations": turn.expectations,
"formUpdate": turn.form_update,
"form": turn.form,
}
for turn in transcript.turns
],
@@ -280,6 +321,27 @@ def save_transcript(output_dir: Path, transcript: CaseTranscript) -> Path:
return md_path
def check_expectations(result: ChatResult, expect: dict, form: dict) -> list[str]:
failures = []
if result.stage_codes != [expect["stageCode"]]:
failures.append("stageCode mismatch")
if result.errors:
failures.append("SSE errors returned")
for key, value in expect.get("form", {}).items():
if key not in form or type(form[key]) is not type(value) or form[key] != value:
failures.append(f"form.{key}: expected {value!r}, actual {form.get(key)!r}")
for key, pattern in expect.get("formMatches", {}).items():
if not isinstance(form.get(key), str) or not re.search(pattern, form[key]):
failures.append(f"form.{key}: expected /{pattern}/, actual {form.get(key)!r}")
for key in expect.get("formAbsent", []):
if key in form:
failures.append(f"form.{key}: expected absent, actual {form[key]!r}")
for key, should_match in (("replyMatches", True), ("replyNotMatches", False)):
if key in expect and bool(re.search(expect[key], result.text)) != should_match:
failures.append(f"{key}: {expect[key]!r}")
return failures
def run_case(client: httpx.Client, case: dict) -> CaseTranscript:
session_id = session_id_for(case["name"])
transcript = CaseTranscript(
@@ -295,6 +357,11 @@ def run_case(client: httpx.Client, case: dict) -> CaseTranscript:
f"({case['_path']}) session={session_id} ==="
)
form: dict = {}
need_form_update = any(
any(key in step["expect"] for key in ("form", "formAbsent", "formMatches"))
for step in case["steps"]
)
try:
for step_number, step in enumerate(case["steps"], start=1):
expected = step["expect"]["stageCode"]
@@ -305,8 +372,9 @@ def run_case(client: httpx.Client, case: dict) -> CaseTranscript:
session_id=session_id,
client_mode=case["clientMode"],
text=user_input,
need_form_update=need_form_update,
)
except (httpx.HTTPError, json.JSONDecodeError, KeyError) as exc:
except (httpx.HTTPError, ValueError, KeyError) as exc:
turn = TranscriptTurn(
step=step_number,
input=user_input,
@@ -322,7 +390,9 @@ def run_case(client: httpx.Client, case: dict) -> CaseTranscript:
print_turn(turn)
break
ok = result.stage_codes == [expected]
form.update(result.form_update)
failures = check_expectations(result, step["expect"], form)
ok = not failures
turn = TranscriptTurn(
step=step_number,
input=user_input,
@@ -331,7 +401,10 @@ def run_case(client: httpx.Client, case: dict) -> CaseTranscript:
reply=result.text,
errors=result.errors,
ok=ok,
error=None if ok else "stageCode mismatch",
error="; ".join(failures) if failures else None,
form_update=dict(result.form_update),
form=dict(form),
expectations=step["expect"],
)
transcript.turns.append(turn)
print_turn(turn)
@@ -395,7 +468,7 @@ def main() -> int:
args = parse_args()
try:
cases = load_cases(args.cases_dir, args.case)
except (OSError, ValueError, yaml.YAMLError) as exc:
except (OSError, ValueError, yaml.YAMLError, re.error) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 2
+121
View File
@@ -0,0 +1,121 @@
import json
import httpx
import pytest
from evals.run import (
ChatResult,
DEFAULT_CASES_DIR,
check_expectations,
load_cases,
parse_sse,
run_case,
transcript_to_json,
)
def sse_response(*events):
body = "".join(
f"event: {name}\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n"
for name, payload in events
)
return httpx.Response(200, text=body)
def test_parse_form_deltas_and_text():
result = parse_sse(sse_response(
("stage_code", {"nextStageCode": "1002"}),
("text_delta", {"text": "请问事故"}),
("formUpdate", {"has_non_motor_vehicle_hit": True}),
("text_delta", {"text": "是什么时候发生的?"}),
("formUpdate", {"vehicle_count": 0}),
))
assert result.stage_codes == ["1002"]
assert result.text == "请问事故是什么时候发生的?"
assert result.form_update == {"has_non_motor_vehicle_hit": True, "vehicle_count": 0}
@pytest.mark.parametrize("update,reply,error", [
({"has_non_motor_vehicle_hit": False}, "事故什么时候发生的?", "has_non_motor_vehicle_hit"),
({"vehicle_count": 1}, "事故什么时候发生的?", "vehicle_count"),
({"vehicle_count": False}, "事故什么时候发生的?", "vehicle_count"),
({}, "有没有涉及非机动车?", "replyMatches"),
({"is_on_scene": True}, "事故什么时候发生的?", "is_on_scene"),
])
def test_regression_assertions_reject_bad_extraction_and_repeated_questions(update, reply, error):
form = {"has_non_motor_vehicle_hit": True, "vehicle_count": 0, **update}
failures = check_expectations(ChatResult(["1002"], [], reply), {
"stageCode": "1002",
"form": {"has_non_motor_vehicle_hit": True, "vehicle_count": 0},
"formAbsent": ["is_on_scene"],
"replyMatches": "(什么时候|几点|事故.*时间)",
"replyNotMatches": "(有没有|是否).*非机动车",
}, form)
assert any(error in failure for failure in failures)
def test_description_assertion_and_sse_error():
expect = {"stageCode": "1002", "formMatches": {
"accident_cause": "^其他:(?=.*非机动车)(?=.*石墩).+",
}}
form = {"accident_cause": "其他:非机动车自行撞到路边石墩"}
assert not check_expectations(ChatResult(["1002"], []), expect, form)
assert check_expectations(ChatResult(["1002"], [{"message": "failed"}]), expect, form)
assert check_expectations(ChatResult(["1002"], []), expect, {"accident_cause": "其他"})
@pytest.mark.parametrize("last_update,passed", [
({}, True),
({"has_non_motor_vehicle_hit": False, "vehicle_count": 1}, False),
])
def test_run_case_checks_cumulative_form_and_requests_extraction(last_update, passed):
calls = []
responses = iter([
{"has_non_motor_vehicle_hit": True},
{"vehicle_count": 0},
last_update,
])
def handler(request):
payload = json.loads(request.content)
calls.append((request.method, payload))
if request.method == "DELETE":
return httpx.Response(200, json={"code": "200"})
assert payload["needFormUpdate"] is True
return sse_response(
("stage_code", {"nextStageCode": "1002"}),
("formUpdate", next(responses)),
("text_delta", {"text": "事故什么时候发生的?"}),
)
case = {"name": "cumulative", "_path": "mock.yaml", "clientMode": "direct", "steps": [
{"input": "非机动车撞石墩", "expect": {"stageCode": "1002", "form": {"has_non_motor_vehicle_hit": True}}},
{"input": "没有别的车", "expect": {"stageCode": "1002", "form": {"vehicle_count": 0}}},
{"input": "没有人员伤亡", "expect": {"stageCode": "1002", "form": {"has_non_motor_vehicle_hit": True, "vehicle_count": 0}}},
]}
with httpx.Client(base_url="http://eval.test", transport=httpx.MockTransport(handler)) as client:
transcript = run_case(client, case)
assert transcript.passed is passed
assert calls[-1][0] == "DELETE"
assert transcript.turns[0].form == {"has_non_motor_vehicle_hit": True}
saved = transcript_to_json(transcript)
assert saved["turns"][1]["form"]["vehicle_count"] == 0
assert saved["turns"][2]["formUpdate"] == last_update
def test_explicit_correction_can_replace_prior_fact():
form = {"has_non_motor_vehicle_hit": True}
form.update({"has_non_motor_vehicle_hit": False, "vehicle_count": 1})
assert not check_expectations(ChatResult(["1002"], []), {
"stageCode": "1002", "form": {"has_non_motor_vehicle_hit": False, "vehicle_count": 1},
}, form)
def test_current_cases_load_without_removed_photo_states():
cases = load_cases(DEFAULT_CASES_DIR, [])
assert len({case["name"] for case in cases}) == len(cases)
regressions = [case for case in cases if "non_motor_vehicle" in case["name"]]
assert {case["clientMode"] for case in regressions} == {"direct", "browser_addon"}
for case in cases:
for step in case["steps"]:
assert step["expect"]["stageCode"].split(".")[-1] not in {"2004", "2016"}
File diff suppressed because one or more lines are too long