为什么把这篇单独成篇:它把「原生视觉能力 → 结构化产出 → 质量度量 → 失败编码」压进一条能被测试方法纳入的链路——不训模型、不改架构,仅用工程接口就能让人亲手做一次"把多模态当成可测组件"的 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 的嵌入拼进同一段上下文,一起送进解码器。这就是"模型即组件·多模态"里那个"能看图的语言模型"的接口真相——它并没有一个单独的"视觉子系统"在等你调用,视觉信息被摊平成了序列的一部分。

$$\mathbf{Z}=\big[\underbrace{\mathbf{v}_1,\cdots,\mathbf{v}_N}_{\text{图像 patch 嵌入}},\ \underbrace{\mathbf{t}_1,\cdots,\mathbf{t}_M}_{\text{文本 token}}\big],\quad \mathbf{v}_i=E_{\text{img}}(\text{patch}_i),\ \ P=\text{dec}(\mathbf{Z})$$

其中 N 由输入分辨率与 patch 大小决定:分辨率越高、patch 越小,N 越大、占用的图像 token 越多、显存与延迟也越高(这条对 §5 评测和 §7 成本都有直接工程含义)。至于 E_img 内部是 ViT 还是原生视觉编码器、跨模态对齐用何种投影层,数学细节见 pr02 §2,本篇不复述——我们只关心它作为"可替换组件"的输入契约(一张图 + 一段带 schema 的提示)与输出契约(一段 JSON)。

输入截图UI / 报错图 切成 N 个 patch E_img 编码patch → v₁…v_N + 文本 token t₁…t_M 提示 / JSON schema 解码器 dec自回归输出 缺陷报告 JSON 工程真相:视觉信息被摊平成序列的一部分。换模型只换 E_img 与 dec 的权重,输入/输出契约不变(组件可替换)。
图 2-1 · VLM 的接口视角:截图切 patch → 编码成嵌入 → 与文本 token 拼进同一上下文 → 解码器产出结构化 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:

JSON · defect_report.schema.json
{
  "$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 的失败编码风格,让"翻车"从轶事变成可统计的工程指标。

编码失败模式触发层护栏
F10OCR 误读致错误归因视觉编码 / 分辨率不足要求逐字引用 + 低分辨率先拒判
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 兼容)

Bash
# 依赖: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

Python
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 校验输出结构

Python
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。

Python
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)与责任边界,噪声污染缺陷库
截图/报错图+ 脱敏(F13) VLM + schema本地部署优先 jsonschema契约校验 traces.csv+人工标注 评测:完整率/Kappa 失败编码 F10–F13 落点:结构化产出接入 pr05 测试资产;失败编码接入 pr09;脱敏/本地部署接入 pr07 —— 一条正规 QA 链路,而非聊天窗口。
图 9-1 · 从"一张截图"到"进缺陷库"的可度量 QA 链路,及其与 pr05 / pr07 / pr09 的接口

与三条主线的接口: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 传凭据别用。