AI Evidence-Grounded Bug 的端到端修复流程
从发现 unsupported AI output 开始,经过冻结事故、写不变量、First Bad Boundary 归因、Evidence Contract 设计、双层 durable gate、migration/replay/eval/E2E,最后走 Issue→PR→CI→Merge 的完整修复闭环。
[!info] related notes
- 前置笔记:
- 相关 MOC:
- 相关资源:
AI Evidence-Grounded Bug 的端到端修复流程
目标
这是一套专门处理下面这类 production Agent Bug 的完整流程:
输入/证据不足或不支持某个 claim
→ AI 却生成了 claim / observation / decision
→ 系统把它继续投影到业务状态
典型例子:
没有体态证据
→ Assessment 生成“高低肩”
→ BodyState 保存
→ UI 展示
这篇是执行顺序页。具体概念不重复展开,分别下钻到原子笔记。
通用的 Issue / Branch / Commit / PR / CI 操作见 Bug 修复完整工作流。
全流程总图
发现异常
↓
冻结事故现场
↓
Actual / Expected
↓
写 Hard Invariant
↓
画 Boundary Chain
↓
收集 Replay / Provenance / Raw Output
↓
列可证伪假设
↓
Find First Bad Boundary
↓
区分 Introduction / Escape / Propagation
↓
写最窄 RED Contract Test
↓
设计 Evidence Contract
↓
缩小 Model Authority
↓
Primary Runtime Gate
↓
Durable Owner Revalidation
↓
No-Evidence Deterministic Path
↓
Schema / Config / DB Versioning
↓
Replay / Rollout / Regression 对齐
↓
Web Backward Compatibility
↓
Deterministic Eval
↓
Real E2E
↓
Full Repo Gates
↓
Diff Review
↓
Commit / PR / Required CI
↓
Merge
↓
Post-Merge Verification
Phase A · 先诊断,不要先修
Step 0 · 冻结事故现场
第一件事不是改 Prompt,而是保存“当时世界”。
尽量获取:
business entity/report ID
frozen input / replay input
configuration ID
prompt/schema/policy revisions
execution provenance
raw model output
validated output
persisted DB state
API response
如果事故现场会继续变化,优先保存可重放 artifact。
为什么:
current state
≠ historical run state
详见 Historical Replay 与 Counterfactual Replay。
Step 1 · 写 Actual / Expected
禁止写成:
AI 结果不太对
应该写:
Actual:
无 posture evidence,却生成 posture_asymmetry
Expected:
无 posture evidence 时,不得生成 posture-derived observation
目标是让错误可以成为 assertion。
Step 2 · 把需求压成 Hard Invariant
例如:
posture observation
⇒ posture evidence exists
或者更成熟:
accepted observation
⇒ exactly one authoritative evidence ref
⇒ ref admissible
⇒ source/kind compatible
这一步把“产品感觉”变成 Behavioral Contract。
Step 3 · 画完整 Boundary Chain
不要只画模型层。
例如:
DB / Upload / BodyState
→ Go input assembly
→ Frozen Replay
→ HTTP request
→ Python dependencies
→ Prompt
→ LLM raw output
→ Pydantic validation
→ Go validation
→ Domain projection
→ DB
→ Read Model / API
→ UI
每条箭头都是候选 fault boundary。
Step 4 · 建 Boundary Table
对每一层记录:
| Boundary | Input | Output | Normal? |
|---|---|---|---|
| source → assembler | ? | ? | ✅/❌ |
| assembler → replay | ? | ? | ✅/❌ |
| replay → model | ? | ? | ✅/❌ |
| model → validator | ? | ? | ✅/❌ |
| validator → durable | ? | ? | ✅/❌ |
最重要的问题始终是:
这一层进去的对吗?
这一层出来的对吗?
Step 5 · 列出可证伪假设
不要只留一句:
H = 模型 hallucination
应同时覆盖其它层:
H1 hidden evidence
H2 input assembler stale/incorrect
H3 historical BodyState contamination
H4 RAG/deps injected context
H5 LLM generated unsupported output
H6 transform/mapper created the bad claim
H7 DB/read model correct but UI wrong
假设的价值在于设计下一次观测,而不是让文档看起来专业。
Step 6 · 用 Frozen Replay 先排除隐藏 Evidence
在说“无证据”之前,逐项检查:
profile
body_state
report_indicators
posture_analysis
images
rag_context
other tool observations
只有当真正的历史输入干净,才能把问题继续归因到 generation。
Step 7 · 找 First Bad Boundary
原则:
last known good
→ first known bad
如果:
model input clean ✅
raw model output already contains bad posture claim ❌
则:
first bad boundary = model generation
通用方法见 Agent Failure Attribution。
Step 8 · 再找 Error Escape Point
找到错误第一次产生还不够。
继续问:
“哪一道 gate 本应该拦住它?”
例如:
LLM hallucination ← introduction
Pydantic accepts ← escape #1
Go accepts ← escape #2
BodyState persists ← propagation / side effect
UI displays ← delivery
一个 production incident 经常同时包含:
stochastic semantic failure
+
missing deterministic guard
Step 9 · 区分 Propagation,不要误修下游
如果 DB 已经保存错误事实,而 UI 只是忠实展示:
UI 不是 root cause
不要优先写:
if no evidence:
hide card
这只遮住症状,错误 durable state 还在。
具体案例见 无体态证据却生成高低肩的排查。
Phase B · 先写会失败的 Contract Test
Step 10 · 在 First Bad Boundary 附近写最小 RED Test
不要先写庞大的 E2E。
最小反例:
Given:
no posture evidence
Model deliberately returns:
posture_asymmetry
Expect:
Evidence Gate rejects
模型最好用 deterministic fake / TestModel,故意输出错误结果。
这样测试验证的是:
runtime guard works even when model misbehaves
而不是:
希望某个真实模型今天比较听话
Step 11 · 同时写 Positive Case
只写拒绝测试容易把 gate 写成“一律拒绝”。
必须补:
valid posture evidence
+ compatible posture selection
→ accept
然后增加边界反例:
nonexistent ref → reject
wrong source/kind → reject
duplicate ref → reject
unverified evidence → unavailable
Phase C · 设计真正的 Trust Boundary
Step 12 · 不要把 Prompt Hardening 当最终修复
Prompt 可以加强,但只能作为 soft guidance:
Prompt
→ encourage correct behavior
真正 durable rule 必须是:
Runtime
→ enforce correct behavior
如果事故的影响是 durable state / side effect,Prompt-only 通常不足。
Step 13 · 定义 Authoritative Evidence Catalog
应用先从 frozen input 构建证据集合,例如:
body_state:fact:<uuid>
body_state:observation:<uuid>
report:upload:<uuid>:indicator:<index>
posture:upload:<uuid>:finding:<index>
模型不能凭空声明 ref。
再过滤:
rejected
unverified
inactive
excluded_from_reasoning
得到本轮真正可用的 catalog。
Step 14 · 实现 Evidence Consistency Contract
至少验证:
ref exists
ref unique
source allowed
kind compatible
evidence lifecycle admissible
完整原则见 Agent Evidence Consistency Contract。
Step 15 · 评估 Model Authority 是否过大
如果模型当前拥有:
kind
label
description
severity
confidence
summary
score
逐个问:
这个字段真的必须由模型拥有吗?
如果任务只是“从已有证据中选择 observation 类型”,就把权限收缩成:
kind + evidence_ref
Step 16 · 对 Durable Prose 使用 Deterministic Rendering
当正确 ref 仍可能被模型扩写时:
correct provenance
≠ claim entailment
考虑:
model = selector
application = renderer
见 Selection-Only Agent Output 与 Deterministic Rendering。
这是从结构上消掉 claim expansion,而不是再叠一个 LLM Judge。
Step 17 · Primary Runtime 先验证一次
通常模型运行服务最了解:
- Pydantic schema;
- exact runtime catalog;
- model output;
- prompt/config。
所以可在 Python/AI runtime 做第一层:
raw selection
→ typed validation
→ evidence validation
→ deterministic rendering
Step 18 · Durable Truth Owner 再独立验证
如果真正保存 Domain State 的是 Go/Java/backend:
不要完全相信 AI Service 已经校验好
在写入前:
rebuild catalog from frozen request
→ revalidate ref/source/kind
→ rerender durable representation
→ persist
这是 defense in depth,也是明确的 trust boundary。
Phase D · 把“不知道”也变成确定性行为
Step 19 · 建 No-Evidence Short-Circuit
如果 catalog 为空:
不要调用模型
直接:
status = insufficient_information
observations = []
model_executed = false
见 No-Evidence No-Model Short-Circuit。
Step 20 · 不要用假默认值表达 Unknown
避免:
unknown → grade D
unknown → score 0
unknown → score 50
这些值看起来都像真实业务判断。
应该显式表达:
not assessed
insufficient information
missing evidence
Phase E · Versioning 与 Persistence Migration
Step 21 · 判断这是不是 Behavior Contract 变更
如果你改变了:
model output authority
prompt contract
schema
Evidence Policy
Decision Policy
这不是“小修一下 validator”,而是行为 identity 改变。
应产生新的:
configuration revision
prompt revision
output schema revision
evidence policy revision
decision policy revision
旧 configuration 保留用于 historical replay,不要原地改写。
Step 22 · 设计 DB Migration
新 contract 可能需要:
contract_revision
evidence_coverage
evidence_gaps
nullable legacy fields
同时明确:
historical rows keep historical semantics
new rows use new semantics
不要把旧数据“升级成好像当时就是新 contract”。
Step 23 · 审查 Down Migration 的业务语义
如果新世界表达:
grade = NULL = not assessed
旧世界要求:
grade NOT NULL
不要为了让 down 成功而写:
NULL → D
应判断是否要 fail closed。
见 语义不可逆 Schema Downgrade 的 Fail-Closed 策略。
Migration 测试至少:
no-new-data: up → down → up = PASS
with-new-semantic-data: down = REFUSED
Phase F · Replay / Rollout / Regression 不能成为旁路
Step 24 · Historical Replay 保留历史语义
旧报告:
old input + old config + old contract
继续可以解释历史。
不要用新 contract 改写历史 artifact。
Step 25 · Counterfactual Replay 走同一个新 Gate
如果新 config 的 replay:
frozen input
→ model
→ directly compare raw output
就绕过了 serving contract。
正确:
frozen input
→ candidate config
→ evidence validation
→ deterministic rendering
→ compare
Step 26 · 把 Production Incident 导出成 Regression Case
事故不要只留在 Issue。
保存:
frozen input fingerprint
contract revision
configuration identity
expected hard invariant
expected model_executed
expected coverage/gaps
forbidden legacy fields/side effects
让同类事故进入未来 qualification。
Phase G · API / Web 向后兼容
Step 27 · 不要让新旧 Contract 假装成同一个类型
如果历史报告有:
health_grade
dimension_scores
新报告有:
contract_revision
evidence_coverage
evidence_gaps
前端应使用 discriminated union 或显式版本分支:
v1 report
vs
v2 report
不要给旧报告补一个假的 {} coverage 然后告诉 TypeScript“它是 v2”。
Step 28 · Error Mapping 不泄漏 Raw Model Output
Evidence rejection 应成为明确业务/上游错误类别,例如:
AI runtime 422
→ backend ErrOutputRejected
→ public API 502 / controlled error
同时不要把完整模型输出、Prompt、内部 evidence 直接泄漏给客户端。
Phase H · 建立 Qualification,而不是只写几个 Unit Tests
Step 29 · 写 Deterministic Policy Eval
Evidence Contract 最适合确定性 dataset。
覆盖:
profile-only is not health evidence
valid source-only rendering
unverified evidence exclusion
wrong source/kind
nonexistent ref
duplicate ref
posture governed finding
report vs injury domain separation
它验证的是 contract behavior,不依赖某个供应商模型质量。
Step 30 · Eval 接入正式 Repo Gate
不要让新 eval 只能手动运行。
加入:
Nx / Make / task target
→ repo verify/release gate
→ CI quality lane
否则它很快会腐化成没人跑的脚本。
Phase I · E2E 必须证明 Durable Invariant
Step 31 · 跑 Model-Executed Grounded Path
真实 E2E 应验证:
confirmed evidence exists
→ Assessment executes model/selector
→ observation exact ref
→ deterministic source restatement
→ BodyState candidate persisted
→ provenance says model_executed=true
Step 32 · 跑 Zero-Evidence Path
另一条 E2E:
no admissible health evidence
→ insufficient_information
→ model requests = 0
→ BodyState revision unchanged
→ provenance model_executed=false
这两个正好覆盖 stochastic path 与 deterministic path。
Phase J · 全量工程验证
Step 33 · 从小到大跑 Gate
顺序建议:
focused unit
→ related service tests
→ language full tests
→ lint/typecheck/vet
→ Web tests
→ migration validation
→ contract eval
→ focused E2E
→ repo-level verify/release
这样失败时容易定位,不会一上来跑 15 分钟全仓库然后不知道哪层红。
Step 34 · 对 Migration 做真实数据库验证
不要只看 SQL 文件。
用 disposable database 验证:
full up
latest down
latest up again
semantic incompatible down refusal
禁止拿真实开发/生产数据随便 reset 来做 migration experiment。
Step 35 · 最终重启运行态再 E2E
特别是使用:
- dev reload;
- Go compiled process;
- Python reload;
- Vite
时,要防止测试其实还跑在旧进程上。
最终:
stop old app processes
→ start exact final source
→ health check
→ rerun focused E2E
Phase K · Diff / Architecture Review
Step 36 · 看 Diff Stat,也看职责边界
不仅问:
测试绿了吗?
还问:
是不是把 600 行 contract 全塞进 Service?
是否出现重复 evidence builder?
是否有旁路仍使用旧 schema?
是否有 test stub 绕过真实 governance?
如果功能正确但结构糟糕,在提交前做纯结构重构,然后重新跑相关 gate。
Step 37 · 审计所有旁路
至少搜索:
E2E stub
deterministic fake
replay
rollout shadow
regression export
old config pointer
legacy grade/score refs
每个旁路都问:
“它能否绕过新 contract?”
Step 38 · 检查 Provenance 自洽
例如 no-evidence 结果如果写:
execution_provenance = skipped_no_evidence
但 generation trace 仍写:
status = generated
model_executed = true
这也是 bug。
Trace 不只是日志,还是未来 replay / eval / incident attribution 的事实。
Phase L · GitHub 修复闭环
Step 39 · Issue 记录 Problem 与 Acceptance Criteria
Issue 不要只写“修 AI 幻觉”。
应写:
Problem:
unsupported observation can enter durable BodyState
Acceptance:
- unsupported source/kind rejected
- no-evidence path does not call model
- replay cannot bypass gate
- historical reports remain readable
- migration semantics safe
- regression tests/eval/E2E exist
Step 40 · 从最新主干创建 Fix Branch
遵循仓库 Git 流程,保持 scope 单一。
Step 41 · Commit 前跑 Staged Diff Review
至少:
git diff --check
git diff --staged --check
git diff --staged --stat
确认:
- 没有 secrets;
- 没有生成物污染;
- config IDs 一致;
- migration checksum 正确;
- 文档描述和最终实现一致。
Step 42 · PR 写清根因,不只列文件
推荐 PR Summary 结构:
Root cause
→ shape validation without evidence consistency
Contract change
→ selection-only + exact evidence refs
Durable safety
→ Python gate + backend revalidation
Compatibility
→ legacy reports preserved
Migration
→ fail-closed downgrade
Verification
→ unit / full / eval / E2E / repo gate
Step 43 · Required CI 全绿后才 Merge
本地 REPO_QUALITY=PASS 只是证据之一。
仍要等待仓库真正 required checks,例如:
commit lint
governance
quality
database
browser E2E
experience
delivery observation
不要把“本地全绿”当成可以绕过 protected main 的理由。
Step 44 · Merge 后同步 Main,再区分 Deploy
Merge:
code entered main
不等于:
staging deployed
production released
最后明确记录:
PR merged?
Issue closed?
main synced?
post-merge CI?
staging?
production?
不要混成一句“已经上线”。
Definition of Done
一次 Evidence-Grounded Agent Bug 真正收口,至少满足:
[ ] historical incident frozen
[ ] Actual / Expected 明确
[ ] hard invariant 写出
[ ] boundary chain 完整
[ ] first bad boundary 已定位
[ ] introduction / escape / propagation 已区分
[ ] first bad boundary 有最窄 regression test
[ ] evidence truth 由应用拥有
[ ] unsupported output deterministic fail closed
[ ] durable owner 独立 revalidation
[ ] model authority 已评估并最小化
[ ] no-evidence path 不制造假事实
[ ] configuration/schema/policy 已正确版本化
[ ] historical replay 语义保留
[ ] counterfactual replay 不绕 gate
[ ] DB migration + downgrade 语义验证
[ ] API/Web 兼容旧 contract
[ ] deterministic policy eval
[ ] real E2E 证明 durable invariant
[ ] full tests/lint/typecheck/build/migration gate
[ ] final runtime restart + E2E
[ ] staged diff review
[ ] PR required checks green
[ ] merge/post-merge state verified
[ ] deployment state 单独确认
最短记忆模型
Freeze
→ Invariant
→ Boundaries
→ First Bad Boundary
→ RED Contract Test
→ Evidence Gate
→ Minimize Model Authority
→ Durable Revalidation
→ Version + Migrate
→ Replay + Eval + E2E
→ Repo Gate
→ PR / CI / Merge
核心不是“把某次 hallucination 修没了”,而是:
把一次事故转化成新的系统不变量、运行时 trust boundary、回归数据和发布门禁,使同一类错误以后结构上更难再次进入 durable truth。