为什么把这篇单独成篇:它把「原生视觉能力 → 结构化产出 → 质量度量 → 失败编码」压进一条能被测试方法纳入的链路——不训模型、不改架构,仅用工程接口就能让人亲手做一次"把多模态当成可测组件"的 QA 实验,同时回收 pr02 的兴趣入口。
阅读路径:§1 讲与 pr02 的接力关系;§2–§4 是接口原理与任务/规格设计;§5–§6 是评测方法与失败护栏(本篇的"软件工程"内核);§7 是可运行工程链路;§8 是可复现与诚实标注;§9–§10 给边界与系列收尾。
1. 从"好玩"到"有用":与 pr02 的接力
pr02 用一个"桌面 AI 女友"的本地部署把人骗进来:扩散模型、VAE、LoRA、图生视频——先觉得好玩,再顺手学会"模型=可版本化组件"。那篇里模型的原生视觉能力全花在生成像素上。本篇不重复任何扩散/VAE/LoRA 的数学(详见 pr02 §2),只做一次能力迁移:同一类能"看懂"图像的语言模型,反过来用——把图读进来,输出结构化的判别结果。
| 维度 | pr02(生成侧) | pr18(本篇 · 判别/结构化侧) |
|---|---|---|
| 视觉能力方向 | 文本/参考图 → 像素(文/图生视频) | 像素(截图/报错图)→ 文本 + JSON |
| 任务性质 | 开放生成,无唯一正确答案 | 有规格约束的生成,可逐字段验收 |
| 质量评价 | 帕累托前沿 + LLM-as-a-Judge 打分 | 字段完整率 / 可操作性 / 与人工一致性(Kappa) |
| 失败编码 | F1 身份漂移…F5 字幕乱码(美学层) | F10 OCR 误读…F13 隐私泄露(QA 层,见 §6) |
| 知识锚点 | 构造 · 架构 · 度量 | 多模态 · 测试 · 安全 |
一句话定位:pr02 负责"吸引兴趣 + 讲清原理",pr18 负责"把同一能力焊到软件工程正经场景"。二者构成"好玩 → 有用"的接力,本篇是这条能力线的落点。
2. VLM 工程接口原理(点到为止)
本篇只需要一条工程直觉:图像被切成若干 patch,每个 patch 编码成一个向量嵌入,和文本 token 的嵌入拼进同一段上下文,一起送进解码器。这就是"模型即组件·多模态"里那个"能看图的语言模型"的接口真相——它并没有一个单独的"视觉子系统"在等你调用,视觉信息被摊平成了序列的一部分。
其中 N 由输入分辨率与 patch 大小决定:分辨率越高、patch 越小,N 越大、占用的图像 token 越多、显存与延迟也越高(这条对 §5 评测和 §7 成本都有直接工程含义)。至于 E_img 内部是 ViT 还是原生视觉编码器、跨模态对齐用何种投影层,数学细节见 pr02 §2,本篇不复述——我们只关心它作为"可替换组件"的输入契约(一张图 + 一段带 schema 的提示)与输出契约(一段 JSON)。
为什么这对"软件工程"重要:既然输入是一张图、输出是一段可校验的 JSON,VLM 就和一个函数/组件一样有明确的接口契约——能被 mock、能被测试、能被度量、能被替换。这是本篇一切评测与护栏成立的前提。
3. 任务定义:把 UI 画面当"可测试输入"
把问题收缩成一个有界任务:输入是一张界面截图或一段控制台报错图,输出是一份结构化缺陷报告。它不是"描述这张图",而是"给出足以进入缺陷跟踪系统的字段"。关键论点:这是一次"有规格约束的生成",输出结构可以用 JSON schema 硬约束。
| 字段 | 含义 | 验收方式 |
|---|---|---|
steps | 复现步骤(有序、可操作) | 人工照做能否重现 |
expected | 期望行为 | 是否与规格/文案一致 |
actual | 实际观察到的现象(须引用图中可见文字) | 证据可回溯 |
severity | 严重级别(blocker/critical/major/minor/trivial) | 枚举合法 + 与人工分级相关 |
location_hint | 定位线索(页面/组件/报错栈中的类名等) | 是否指向真实模块 |
evidence / confidence | 图中可见文字引用 + 置信度 | 置信度低时应标"证据不足" |
反例("能看图聊天"式输出):"这张图看起来有个按钮点不动,可能是个 bug。"——没有可回溯证据、没有可操作复现步骤、没有严重级别,无法进入 pr05 的测试用例库,也无法被 pr09 编码。本篇要的就是把它逼成上表那样可度量的结构。
4. 提示与规格设计:schema 约束 + 降幻觉
提示工程在本任务里的目标是规格化而非"更聪明的措辞"。三件套:few-shot 示例 + 输出 JSON schema + 显式的降幻觉指令。
4.1 few-shot + 输出 schema
给 1–2 个"截图 → 合格报告"的示例锚定风格,再用 schema 锁死字段名/类型/枚举,让模型只能往格子里填。示意性的 schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["steps", "expected", "actual", "severity", "confidence"],
"additionalProperties": false,
"properties": {
"steps": {"type": "array", "minItems": 1, "items": {"type": "string"}},
"expected": {"type": "string", "minLength": 1},
"actual": {"type": "string", "minLength": 1},
"severity": {"enum": ["blocker", "critical", "major", "minor", "trivial"]},
"location_hint": {"type": "string"},
"evidence": {"type": "array", "items": {"type": "string"},
"description": "图中逐字可见的文字,用于回溯"},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"insufficient_evidence": {"type": "boolean"}
}
}
4.2 如何降低幻觉
- 强制引用可见文字:要求
actual/evidence只能填图中逐字可见的内容(报错标题、按钮文案、状态码)。编不出引用 → 触发insufficient_evidence=true。 - 要求置信度并留"弃权"出口:明说"看不清就标证据不足,不要猜"。宁可空,不要假——这是把 LLM 的"讨好式补全"关掉。
- 把猜测降级为线索:模型对"可能原因"的推断只能进
location_hint(提示线索),不得写成结论性actual。 - 结构化后校验:schema 校验不过(枚举非法、缺字段、类型错)直接判为一次失败样例,不入库(见 §7)。
与的呼应:这段提示本身又是一份"自然语言需求规格 + 验收标准 + 负面清单"(见 pr02 §4)。区别是这次规格约束的是输出结构,可被 jsonschema 机器校验——比 pr02 那种"只能靠人眼核对"更进一步。
5. 评测方法:缺陷报告质量怎么量化(对接 pr05)
这是本篇的软件工程内核:不可度量的东西不算测试。我们把"报告写得好不好"拆成三类可复算的指标,并用 LLM-as-a-Judge + 人工抽检算一致性来交叉验证(呼应 pr09 的一致性分析)。
5.1 三类指标
- 结构合格率 / 字段完整率:输出通过 jsonschema 的比例;关键字段(steps/expected/actual/severity/confidence)非空的比例。机器可判,零人工。
- 复现步骤可操作性评分:steps 能否让不知情的测试同学照着重现?0–5 分。人工为主,LLM-as-a-Judge 预打分。
- 与人工判定的相关性:模型给出的 severity 与资深 QA 的分级做 Spearman 相关;"这是不是真缺陷"的判定与人工做一致性(Cohen's Kappa)。
5.2 LLM-as-a-Judge + 人工抽检算一致性
让一个(宜与产出模型不同源的)评审模型按 rubric 打"可操作性 / 证据充分性"分,再抽取 10–20% 由人工盲评,计算 Judge 与人工的 Cohen's Kappa / Spearman ρ。未先验证 Judge 可信度就全量用 Judge 打分 = 用一把没校准的尺子量所有东西——这是 pr02 延伸里"未校准概率当概率用"的同类教训。Kappa 太低(如 < 0.4)时,Judge 分数只能当草稿,以人工为准。
5.3 准确率 / 失败样例分类表(结构模板)
给一张可直接落库、可交叉统计的表结构(数值为占位示例,实际以你跑的模型与数据为准,不作承诺):
| 指标 / 失败类别 | 定义 | 口径 | 示例值 |
|---|---|---|---|
| schema 合格率 | 通过 jsonschema 校验 | 机器 | 92% |
| 字段完整率 | 关键字段全部非空 | 机器 | 85% |
| 可操作性均分 | steps 0–5 打分均值 | Judge+人工 | 3.6 |
| severity 相关性 | 与人工 Spearman ρ | 人工对照 | 0.61 |
| 判定一致性 Kappa | 真/误报,vs 人工 | 人工抽检 | 0.55 |
| F10 OCR 误读 | 把图中文字读错致错误归因 | 失败标注 | 见 §6 |
| F11 无中生有 | 描述图中不存在的元素/报错 | 失败标注 | 见 §6 |
| F12 复现不可操作 | steps 缺关键前提,无法重现 | 失败标注 | 见 §6 |
与 pr05 的接口:上表的每一列都能挂到 pr05 的测试方法里——schema 合格率是"契约测试",可操作性/Kappa 是"验收测试"。这样一份 AI 生成的报告就顺着进了正规测试资产库,而不是飘在聊天窗口里。
6. 失败模式与护栏(对接 pr07 安全 / pr09 失败编码)
把典型失败编成 F 码,沿用 pr02 与 pr09 的失败编码风格,让"翻车"从轶事变成可统计的工程指标。
| 编码 | 失败模式 | 触发层 | 护栏 |
|---|---|---|---|
| F10 | OCR 误读致错误归因 | 视觉编码 / 分辨率不足 | 要求逐字引用 + 低分辨率先拒判 |
| F11 | 无中生有(图里没有的报错) | 生成层幻觉 | 证据可回溯校验,缺证据→insufficient |
| F12 | 复现步骤不可操作 | 规格约束不足 | steps 非空 + Judge 可操作性阈值 |
| F13 | 隐私 / 凭据泄露 | 数据层(截图含 token/身份证/密码) | 入库前脱敏 + 本地部署不外传(见下) |
6.1 隐私脱敏(对接 pr07 安全)
截图常含手机号、邮箱、Token、密钥、内网 IP、真实姓名。护栏顺序:先本地脱敏,再决定是否送入模型。对含凭据/PII 的图,首选本地部署的 VLM(模型在你自己机器/内网,见 §7 vLLM),杜绝把敏感画面发给外部 API;上传/落库前对 OCR 命中的 PII 做掩码(sk-****、138****0000)。这既是 pr07 的数据安全,也影响 pr05 的测试数据合规。
错误归因的代价:一个被 OCR 读错的报错码(F10)会把开发引去查错误的模块;一条编造的堆栈(F11)比没有报告更浪费工时。护栏的价值不在"少几个差评",而在"别让噪声进缺陷库污染真实工单"。
7. 工程链路(可运行示例)
思路:本地用 vLLM 部署一个 VL 模型 → 用 OpenAI 兼容的 /v1/chat/completions 接口,图像走 base64 image_url,问"截图 → 缺陷报告 JSON" → 用 jsonschema 校验结构 → 结果与人工标注写入统一的 traces.csv(沿用自 pr03 起的采集 schema)。模型名 / 端口 / 图像 token 数以你所用 VL 模型官方仓库为准;注释只写功能与参数,不写具体机器。
7.1 本地部署多模态服务(vLLM,OpenAI 兼容)
# 依赖:vllm(含 VL 支持) + openai/python 客户端 + jsonschema
pip install vllm openai jsonschema pillow
# 以官方仓库给出的 VL 模型 id 为准,这里用占位
# --limit-mm-per-prompt 控制每请求图像数;max-model-len 决定可容纳的图像 token
python -m vllm.entrypoints.openai.api_server \
--model <VL_MODEL_ID> \
--served-model-name vl-local \
--host 127.0.0.1 --port 8000 \
--limit-mm-per-prompt image=1 \
--max-model-len 8192
7.2 客户端:截图(base64) → 缺陷报告 JSON
import base64, json
from openai import OpenAI
# OpenAI 兼容接口;base_url 指向本地 vLLM 部署
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="EMPTY")
def b64_image(path):
with open(path, "rb") as f:
return base64.b64encode(f.read).decode("utf-8")
SYSTEM = (
"你是软件测试工程师。仅依据截图可见内容填写 JSON。"
"actual/evidence 必须逐字引用图中文字;"
"看不清或缺证据时设 insufficient_evidence=true 并给出低 confidence,不要猜测。"
)
FEW_SHOT_USER = "示例见 schema,输出严格的 JSON 对象,不要多余文本。"
def report_from_screenshot(img_path):
resp = client.chat.completions.create(
model="vl-local",
temperature=0.2, # QA 场景压低随机性,利于可复现
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": FEW_SHOT_USER},
{"role": "user", "content": [
{"type": "text", "text": "为这张界面/报错截图生成缺陷报告 JSON。"},
{"type": "image_url", "image_url": {
"url": f"data:image/png;base64,{b64_image(img_path)}"}},
]},
],
response_format={"type": "json_object"}, # 让解码器只吐 JSON
)
return json.loads(resp.choices[0].message.content)
7.3 用 jsonschema 校验输出结构
import json
from jsonschema import Draft202012Validator
SCHEMA = json.load(open("defect_report.schema.json", encoding="utf-8"))
_validator = Draft202012Validator(SCHEMA)
def validate_report(obj):
"""返回 (是否合格, 错误列表)。不合格即计为一次失败样例,不入库。"""
errs = [e.message for e in _validator.iter_errors(obj)]
if not errs and obj.get("confidence", 0) < 0.3:
errs.append("low_confidence: 应标 insufficient_evidence") # 语义护栏
return (len(errs) == 0, errs)
report = report_from_screenshot("shots/login_error.png")
ok, errs = validate_report(report)
print("schema 合格" if ok else f"拒绝入库: {errs}")
7.4 结果 + 人工标注写入统一 traces.csv
沿用自 pr03 起的 trace schema:一行一次运行,模型产出与人工判定并排存,供 §5 复算完整率 / 可操作性 / Kappa。
import csv, json, datetime, os
FIELDS = ["ts", "image", "model", "pred_json", "schema_ok",
"human_severity", "human_actionable", "is_real_defect",
"fail_code"]
def append_trace(img, report, schema_ok, human):
"""human: dict(human_severity, human_actionable, is_real_defect, fail_code)"""
row = {
"ts": datetime.datetime.now.isoformat(timespec="seconds"),
"image": img, "model": "vl-local",
"pred_json": json.dumps(report, ensure_ascii=False),
"schema_ok": int(schema_ok),
"human_severity": human["human_severity"],
"human_actionable": human["human_actionable"], # 0-5
"is_real_defect": int(human["is_real_defect"]),
"fail_code": human["fail_code"], # F10/F11/... 或空
}
new = not os.path.exists("traces.csv")
with open("traces.csv", "a", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=FIELDS)
if new:
w.writeheader
w.writerow(row)
# 统计口径:完整率 / 与人工一致性(示例,不含绝对承诺)
import pandas as pd, numpy as np
def summarize(path="traces.csv"):
df = pd.read_csv(path)
comp = df[["schema_ok"]].mean.iloc[0]
act = df["human_actionable"].mean
rho = df["confidence"].corr(df["human_actionable"], method="spearman") \
if "confidence" in df else float("nan")
print(f"schema合格率={comp:.0%} 可操作性均分={act:.2f} Spearman(rho)={rho:.2f}")
诚实提示:上面所有模型名 / 端口 / 图像走
data:image/png;base64的细节都以你所用 VL 模型的官方仓库与 vLLM 文档为准;把模型细节隔离在<VL_MODEL_ID>与 schema 文件里,换模型只改配置不改这套 QA 逻辑——正是 §2 "组件可替换"的兑现。
8. 可复现与诚实标注
视觉模型对分辨率 / 图像格式 / 预处理极敏感——同一张图缩到 448px 可能把报错码读糊(触发 F10),token 预算不同还会改变可见 patch 数 N。因此预处理必须写进 trace,否则 §5 的准确率无从解释。
- ☑ 记录图像预处理:目标分辨率、长宽策略(resize / 智能缩放 / 分块)、格式(PNG 无损优先)、EXIF 方向。
- ☑ 记录模型 id、
max-model-len、每请求图像数、温度、response_format——逐条写入traces.csv。 - ☑ Judge 与产出模型不同源;人工抽检比例与 Kappa 留档,保证 §5 一致性可复核。
- ☑ 截图素材合规:先脱敏(F13 护栏),测试图不含真实凭据 / PII(对接 pr07)。
- ☑ 报告中的"准确率 / Kappa"明确标注为方法演示值、非性能承诺,换模型/换数据会变。
9. 结论:VLM 做 QA 的"能用 / 慎用 / 别用"
给一条边界判断,避免把它当"自动测试神器",也避免因噎废食。
| 档位 | 适用场景 | 理由 |
|---|---|---|
| 能用 | UI 视觉缺陷初筛、报错截图分诊、缺陷报告草稿 / 字段规整 | 结构化 + schema + Judge 可度量,人只做终审 |
| 慎用 | 高 DPI 细节、密集文字表格截图、跨页复杂交互归因 | 分辨率/OCR 敏感,F10 误读风险高,需人工复核 |
| 别用 | 含凭据/PII 且只能走外部 API;把模型结论当最终工单直接关闭 | 安全(pr07)与责任边界,噪声污染缺陷库 |
与三条主线的接口:pr05(测试方法)——schema 合格率是契约测试、可操作性/Kappa 是验收测试,AI 报告并入测试资产;pr07(安全)——F13 脱敏、敏感画面本地部署不外传;pr09(失败分析)——F10–F13 进失败编码表、人工/Judge 一致性分析同一套口径。
10. 与前序接口与系列收尾
本篇是"多模态软件工程入口"这条线的收口,也是 pr02 兴趣入口的回收:pr02 让读者觉得"AI 能生成视频好好玩"并讲清扩散/VAE/LoRA 原理;pr18 把同一类原生视觉语言能力调转方向,用到正经的软件工程 QA——让一次截图变成一个可测试、可度量、可编失败码的结构化工单。
兴趣入口(pr02 生成) → 判别与结构化(pr18) → 融入 pr05 测试 / pr07 安全 / pr09 失败分析主线。多模态不再是"锦上添花的聊天功能",而是模型即组件在测试里一次可复现、可度量的落地。
一页纸总结:接口 = 图像切 N 个 patch 嵌入 + 文本 token 拼进同一上下文(原理见 pr02 §2);任务 = 截图/报错图 → 有 JSON schema 约束的结构化缺陷报告;评测 = 字段完整率 / 可操作性 / 与人工 Kappa(LLM-as-a-Judge 需先验自身可信度);护栏 = F10 OCR 误读 / F11 无中生有 / F12 不可操作 / F13 隐私泄露;链路 = vLLM 部署 → OpenAI 兼容接口(base64 image_url) → jsonschema 校验 → traces.csv;边界 = 初筛能用、密集文字慎用、外部 API 传凭据别用。