Agent 工程体系 · 第 56/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。

多模态 Agent:图像、音频、视频、文档、工具和证据对齐

多模态 Agent 不是“把图片、音频和视频一起塞给大模型”这么简单。它真正要解决的是:不同模态如何进入同一个任务状态,模型如何决定下一步动作,工具如何改变外部世界,以及最终回答中的每个结论如何回指到可验证证据

如果只做模态理解,系统通常是一个多模态问答接口;如果加入规划、工具调用、状态维护和环境反馈,才接近 Agent。OpenAI 将 Agent 描述为能够规划、调用工具、协作并保留足够状态以完成多步骤工作的应用;Anthropic 则区分了固定代码路径编排的 workflow 与由模型动态决定过程和工具使用的 Agent。(developers.openai.com)

本文采用一个统一问题来组织各个部分:

用户上传一段客服通话、一张设备照片、一份维修手册和一段现场视频,要求判断故障原因、给出处理步骤,并说明每个判断依据。

这个问题同时包含图像、音频、视频、文档、工具和证据。如果其中任意一环没有对齐,最终答案就可能“看起来合理”,但无法复核。


一、先建立统一模型:模态不是答案,模态是带来源的观测

设一次 Agent 任务为:

T=(U,O,S,A,E,G)\mathcal{T}=(U, O, S, A, E, G)

其中:

  • UU:用户目标,例如“判断设备是否过热”;
  • OO:观测集合,包括图像、音频、视频帧和文档片段;
  • SS:当前任务状态;
  • AA:可执行动作,例如检索、测温、查询工单或请求人工确认;
  • EE:环境反馈,例如工具返回值、传感器结果或人工决策;
  • GG:完成条件,例如“给出故障判断并列出证据”。

Agent 的一次循环可以写为:

St+1=F(St,Ot,At,Et)S_{t+1}=F(S_t, O_t, A_t, E_t)

模型并不是直接从原始文件生成最终答案,而是在每一步根据当前状态选择动作:

Atπθ(ASt,U)A_t \sim \pi_\theta(A \mid S_t, U)

其中 πθ\pi_\theta 是模型形成的策略。工具执行后得到环境反馈:

Et=exec(At)E_t = \operatorname{exec}(A_t)

这一区分非常重要:

  • 图像中的“红色区域”是观测;
  • “可能过热”是解释;
  • “调用温度查询工具”是动作;
  • 工具返回的 86℃ 是环境反馈;
  • “确认超过安全阈值”才可能是结论。

如果把模型的解释直接当成事实,系统就失去了证据边界。

1. 证据对象的最小结构

工程上不要只把检索结果表示成字符串。至少应保留以下字段:

{
  "evidence_id": "ev-017",
  "source_id": "manual-v3.pdf",
  "source_type": "document",
  "locator": {
    "page": 42,
    "section": "过热保护"
  },
  "content": "当壳体温度高于 80℃ 时,应停止运行并等待冷却。",
  "timestamp": null,
  "region": null,
  "confidence": 0.98,
  "observed_at": "2026-08-31T10:20:00+08:00"
}

这里的 locator 不只是展示信息,它是证据可验证性的核心:

  • 文档使用页码、章节、段落或表格坐标;
  • 音频使用起止时间;
  • 视频使用时间区间和帧号;
  • 图像使用区域坐标;
  • 工具使用请求参数、响应摘要和响应时间;
  • 人工确认使用操作者、时间和审批记录。

一个答案中的主张 cic_i 应映射到证据集合:

M(ci)={ejej 支持 ci}M(c_i)=\{e_j \mid e_j \text{ 支持 } c_i\}

M(ci)=M(c_i)=\varnothing 时,系统不能把该主张写成确定事实;它最多只能写成待验证假设。


二、图像:从视觉内容到可引用的空间证据

1. 图像 Agent 需要回答三个不同问题

图像输入通常被混成一个问题:“图片里有什么?”但生产系统至少要区分:

  1. 检测:是否存在某个对象或状态;
  2. 定位:对象位于图像什么区域;
  3. 解释:该视觉现象对当前任务意味着什么。

例如:

图像右上方出现一块深色烧蚀区域。

这是视觉观察。

该区域可能表示外壳局部过热。

这是诊断假设。

设备已经超过允许温度。

这是需要额外证据支持的结论,不能仅凭颜色直接推出。

因此,图像分析输出最好分层:

{
  "observations": [
    {
      "text": "连接器右侧存在明显变色区域",
      "region": [612, 188, 804, 376],
      "confidence": 0.91
    }
  ],
  "hypotheses": [
    {
      "text": "可能存在热损伤或材料老化",
      "supported_by": ["img-obs-001"],
      "requires": ["温度测量", "维修手册比对"]
    }
  ]
}

2. 空间定位必须进入证据模型

如果 Agent 只保存“图片中有烧焦痕迹”,后续工程师无法确认模型看的是否是同一位置。应把空间信息当作引用坐标:

r=(x1,y1,x2,y2)r=(x_1,y_1,x_2,y_2)

对于不同分辨率的图片,建议同时保存原图尺寸:

{
  "image_id": "img-001",
  "width": 1920,
  "height": 1080,
  "region": {
    "x1": 612,
    "y1": 188,
    "x2": 804,
    "y2": 376
  }
}

如果后续生成缩略图,坐标可以按比例换算:

x=xWW,y=yHHx' = x \cdot \frac{W'}{W}, \qquad y' = y \cdot \frac{H'}{H}

其中 W,HW,H 是原图尺寸,W,HW',H' 是展示图尺寸。

3. 图像的真实边界

图像适合提供:

  • 外观状态;
  • 位置关系;
  • 标识和读数;
  • 结构损伤线索;
  • 与文档插图的视觉比对。

图像不适合单独证明:

  • 精确温度;
  • 材料内部缺陷;
  • 时间顺序;
  • 因果关系;
  • 某个安全阈值是否已经被突破。

一个典型反例是:照片中有红色指示灯。模型回答“设备已经过热”。但红灯也可能表示待机、联网、告警或电源状态。正确做法是将“红灯亮起”作为图像证据,再查询设备手册或调用设备状态工具。


三、音频:语义、说话人和时间轴必须分离

音频 Agent 不只是 ASR,即自动语音识别。一个可用的语音链路通常包括:

音频流VADASR说话人/时间对齐意图与行动TTS\text{音频流} \rightarrow \text{VAD} \rightarrow \text{ASR} \rightarrow \text{说话人/时间对齐} \rightarrow \text{意图与行动} \rightarrow \text{TTS}

1. VAD:判断“有人说话”而不是识别内容

VAD,即 Voice Activity Detection,负责判断音频中哪些时间区间包含语音:

00:00.000 - 00:01.240  静音
00:01.240 - 00:04.880  语音
00:04.880 - 00:05.310  短暂停顿
00:05.310 - 00:08.920  语音

VAD 的错误会直接影响延迟和语义:

  • 起点过晚:丢掉首字;
  • 终点过早:把一句话切成两段;
  • 噪声误触发:浪费 ASR 和推理资源;
  • 长时间不切分:用户等待时间增加。

所以 VAD 区间不是最终证据,它只是 ASR 的分段边界。

2. ASR:文本必须携带时间戳和置信度

普通转写:

设备现在有点烫,而且风扇声音不对。

可引用转写:

{
  "segment_id": "aud-seg-003",
  "speaker": "user",
  "start_ms": 5310,
  "end_ms": 8920,
  "text": "设备现在有点烫,而且风扇声音不对。",
  "confidence": 0.94,
  "language": "zh"
}

时间戳允许最终回答引用:

用户在 00:05.310–00:08.920 提到设备发热且风扇声音异常。

但这句话仍然只是用户陈述,不等价于测量事实。证据类型必须标为 user_report,而不是 sensor_observation

3. 流式推理与可撤销中间结果

流式 ASR 常会先输出不稳定文本:

partial-1: 设备现在有点热
partial-2: 设备现在有点烫,而且
final:    设备现在有点烫,而且风扇声音不对。

Agent 不应在 partial 阶段执行不可逆工具动作。例如,不能因为临时识别出“关闭设备”,就立即切断电源。安全动作至少要等待:

  1. ASR 标记为 final;
  2. 意图解析完成;
  3. 工具参数校验通过;
  4. 必要时取得用户确认。

4. 语音 Agent 的延迟预算

一次语音交互的端到端延迟可以近似为:

Ltotal=Lcapture+LVAD+LASR+Lreason+Ltool+LTTS+LplayL_{\text{total}} = L_{\text{capture}} +L_{\text{VAD}} +L_{\text{ASR}} +L_{\text{reason}} +L_{\text{tool}} +L_{\text{TTS}} +L_{\text{play}}

其中:

  • LcaptureL_{\text{capture}}:采集和缓冲;
  • LVADL_{\text{VAD}}:语音结束判断;
  • LASRL_{\text{ASR}}:识别延迟;
  • LreasonL_{\text{reason}}:模型决策延迟;
  • LtoolL_{\text{tool}}:工具调用延迟;
  • LTTSL_{\text{TTS}}:语音合成首包延迟;
  • LplayL_{\text{play}}:播放缓冲延迟。

优化时不能只看模型推理时间。如果 VAD 为了避免截断而等待很久,或者工具每次都串行查询,用户感知的延迟仍然很高。

5. 打断是状态转换,不是停止播放

语音 Agent 至少需要维护:

IDLE
  -> LISTENING
  -> THINKING
  -> SPEAKING
  -> INTERRUPTED
  -> LISTENING

当用户在 SPEAKING 状态开始说话时,系统应:

  1. VAD 检测到新的语音;
  2. 立即停止或衰减 TTS 播放;
  3. 标记当前输出为 interrupted
  4. 保存已经播放和未播放的文本区间;
  5. 将新语音作为新的输入事件;
  6. 决定是否复用旧推理结果。

如果只调用播放器的 stop(),但不更新 Agent 状态,模型可能继续认为自己已经完成回答,导致下一轮上下文错乱。


四、视频:关键问题是时间采样和事件连续性

视频不是“很多张图片”。它额外包含时间关系:

V={(fi,ti)}i=1nV=\{(f_i,t_i)\}_{i=1}^{n}

其中 fif_i 是第 ii 帧,tit_i 是该帧时间戳。

1. 视频理解的三层输出

视频 Agent 应将结果分成:

  • 帧级观察:某一时刻看到什么;
  • 片段级事件:某一时间区间发生了什么;
  • 任务级判断:该事件是否满足目标条件。

例如:

{
  "frame_observation": {
    "time_ms": 12400,
    "text": "风扇叶片停止转动",
    "frame_id": "frame-372"
  },
  "event": {
    "start_ms": 11800,
    "end_ms": 14600,
    "text": "风扇逐渐减速并停止"
  },
  "task_claim": {
    "text": "设备在运行期间出现风扇停转",
    "supported_by": ["frame-372", "video-event-09"]
  }
}

“某一帧看起来停了”不能直接证明“整个设备运行期间风扇都停了”。后者需要时间范围内的连续观察。

2. 采样策略影响结论

设视频总时长为 TT,采样间隔为 Δt\Delta t。若事件持续时间小于 Δt\Delta t,均匀采样可能完全漏掉事件。

例如:

  • 视频时长:60 秒;
  • 每 5 秒采样一帧;
  • 故障闪烁持续 1 秒;
  • 故障恰好发生在两个采样点之间。

则采样结果可能显示“没有故障”。这不是模型推理错误,而是观测策略造成的信息缺失。

因此,视频 Agent 常采用两阶段策略:

  1. 低成本均匀采样,定位疑似异常区间;
  2. 对异常区间提高采样率或提取连续片段;
  3. 让模型对片段而不是孤立帧进行判断。

3. 视频与音频要共用时间轴

现场视频中的“风扇停转”和音频中的“声音突然消失”只有在时间轴对齐后,才能形成联合证据:

视频:00:11.800 - 00:14.600 风扇减速并停止
音频:00:12.030 - 00:14.510 风扇噪声消失

这两个证据相互支持,但不能重复计数成两个完全独立的事实,因为它们来自同一段视频。证据聚合时应记录 source_id 和派生关系,避免把同一来源的多个观察误当成独立投票。


五、文档:解析、切分和引用位置决定知识质量

文档 Agent 面临的难点不是“把 PDF 转成文本”,而是保留文档结构:

  • 页码;
  • 标题层级;
  • 表格行列;
  • 脚注;
  • 图片与图注;
  • 版本号;
  • 生效日期;
  • 页眉页脚;
  • OCR 不确定区域。

1. 文档证据的结构化表示

{
  "chunk_id": "doc-042-sec-3",
  "source_id": "manual-v3.pdf",
  "source_version": "v3",
  "page_start": 42,
  "page_end": 42,
  "section": "3.4 过热保护",
  "text": "当壳体温度高于 80℃ 时,应停止运行并等待冷却。",
  "table_coordinates": null,
  "content_hash": "sha256:..."
}

content_hash 的作用是防止引用漂移。若文档重新上传后内容发生变化,即使文件名相同,也应视为新的来源版本。

2. 切分不能破坏条件和例外

错误切分:

片段 A:当壳体温度高于 80℃ 时,应停止运行。
片段 B:环境温度低于 5℃ 时除外。

如果检索只召回片段 A,模型可能给出过于绝对的结论。更安全的切分应保留条件、例外和适用范围:

当壳体温度高于 80℃ 时,应停止运行并等待冷却;环境温度低于 5℃ 时,适用另一套低温保护流程。

文档检索的目标不是找到“相似句子”,而是找到足以约束结论的完整证据单元。

3. 文档版本冲突

假设:

  • manual-v2.pdf:温度阈值为 80℃;
  • manual-v3.pdf:温度阈值改为 75℃;
  • 工单系统记录设备固件为 v3。

此时 Agent 不应简单地把两个数字平均,也不应选择检索排名更高的片段。它需要执行版本解析:

e=argmaxeEApplicable(e,device_version,effective_date)e^* = \arg\max_{e \in E} \operatorname{Applicable}(e,\text{device\_version},\text{effective\_date})

如果无法判断适用版本,应明确报告冲突:

发现两个手册版本给出不同阈值。当前设备固件对应关系尚未确认,因此不能可靠判断 78℃ 是否触发保护。


六、工具:工具调用是状态变更,不是函数装饰

工具可以分为三类:

  1. 查询工具:读取订单、设备状态、知识库;
  2. 计算工具:执行公式、解析文件、运行代码;
  3. 副作用工具:发送邮件、退款、关机、修改数据库。

工具调用的风险随副作用增加。一个安全的工具描述至少应包含:

{
  "name": "get_device_temperature",
  "description": "查询设备最近一次温度测量值,不执行任何控制动作。",
  "input_schema": {
    "type": "object",
    "properties": {
      "device_id": {
        "type": "string",
        "description": "设备唯一标识"
      }
    },
    "required": ["device_id"],
    "additionalProperties": false
  },
  "side_effect": "read_only",
  "freshness_seconds": 30,
  "permission": "device:read"
}

工具说明应接近人机界面设计,而不是只写函数名。Anthropic 特别强调,工具定义和工具规范需要像提示词一样被认真设计;工具格式、参数约束和错误信息会直接影响 Agent 能否正确行动。(anthropic.com)

1. 工具结果必须进入证据链

错误做法:

工具返回:86
模型回答:温度很高,建议停机。

正确做法:

{
  "tool_call_id": "call-19",
  "tool": "get_device_temperature",
  "arguments": {
    "device_id": "D-1007"
  },
  "result": {
    "temperature_c": 86,
    "measured_at": "2026-09-01T09:24:10+08:00"
  },
  "evidence": {
    "evidence_id": "tool-ev-19",
    "source_type": "device_sensor",
    "freshness_seconds": 30
  }
}

然后由规则或文档证据完成比较:

86>80超过阈值86 > 80 \Rightarrow \text{超过阈值}

这里的关键不是模型“觉得 86 很高”,而是把工具结果与适用版本的阈值证据放在同一推理上下文中。

2. 工具错误也属于状态

工具失败不能只返回字符串 "error"。至少要区分:

{
  "status": "failed",
  "error_code": "DEVICE_TIMEOUT",
  "retryable": true,
  "attempt": 2,
  "safe_to_retry": true,
  "user_visible_message": "设备暂时没有响应"
}

Agent 的处理路径应根据错误类型分支:

超时
  -> 若幂等且未超过次数:重试
  -> 若不可确认是否执行成功:查询状态,不直接重试
  -> 若超过预算:转人工或返回不确定性

参数错误
  -> 不重试
  -> 修正参数或请求用户补充信息

权限错误
  -> 不重试
  -> 请求授权或改变流程

副作用执行未知
  -> 查询操作状态
  -> 禁止盲目重复执行

“发送邮件”或“退款”这类工具尤其需要幂等键:

{
  "operation_id": "refund-order-1007-v1",
  "order_id": "1007",
  "amount": 19900,
  "currency": "CNY"
}

服务端应保证同一个 operation_id 不会产生两次副作用。


七、证据对齐:从“有引用”到“引用真的支持结论”

1. 主张和证据不是一对一关系

一个结论可能需要多个证据:

设备需要停机。

可能需要:

  • 图像:看到烧蚀区域;
  • 传感器:当前温度 86℃;
  • 文档:当前版本规定 80℃以上停机;
  • 工具:设备仍处于运行状态。

可以定义一个主张的支持条件:

Support(c)=Visual(c)Sensor(c)Policy(c)\operatorname{Support}(c)= \operatorname{Visual}(c) \land \operatorname{Sensor}(c) \land \operatorname{Policy}(c)

缺少任意一项时,结论强度应下降。

2. 支持、反驳和无关证据

证据关系至少分为三类:

{
  "claim_id": "claim-07",
  "text": "设备应立即停止运行",
  "relations": [
    {
      "evidence_id": "tool-ev-19",
      "relation": "supports",
      "reason": "温度测量为 86℃"
    },
    {
      "evidence_id": "doc-ev-42",
      "relation": "supports",
      "reason": "适用手册规定超过 80℃ 应停止运行"
    },
    {
      "evidence_id": "user-ev-03",
      "relation": "context",
      "reason": "用户报告设备外壳发烫"
    }
  ]
}

用户报告可以作为上下文,但不能与传感器读数拥有相同的事实等级。证据类型、来源可信度、时间新鲜度和版本适用性都应参与判断。

3. 冲突处理不能用平均值解决

假设:

  • 音频:用户说“风扇已经停了”;
  • 视频:00:12–00:14 风扇停转;
  • 实时设备状态:风扇当前转速为 1200 RPM。

这些结果不一定矛盾,因为它们对应不同时间。应先按时间切片:

Et={et(e)t}E_{t}=\{e \mid t(e)\cap t \neq \varnothing\}

如果用户问的是“刚才是否停过”,视频证据有效;如果问的是“现在是否停转”,实时工具更相关。

真正的冲突是:

  • 同一设备;
  • 同一时间范围;
  • 同一指标;
  • 不同来源给出不一致结果。

此时应输出冲突本身,并说明下一步验证动作,而不是隐藏冲突后给出单一答案。


八、一个完整的多模态 Agent 数据流

flowchart TD
    U[用户目标] --> I[输入接入层]
    I --> N[模态归一化]
    N --> O[观测对象]
    O --> P[任务状态与证据图]
    P --> D[Agent 决策循环]
    D -->|检索| R[文档/知识库]
    D -->|查询| T[只读工具]
    D -->|控制| A[副作用工具]
    D -->|补充观察| V[图像/音频/视频分析]
    R --> P
    T --> P
    V --> P
    A --> F[工具执行反馈]
    F --> P
    P --> C[主张-证据校验]
    C -->|证据不足| Q[追问/人工确认]
    C -->|证据充分| O1[结构化回答]
    O1 --> X[文本或TTS输出]

关键路径不是“模型调用一次”,而是:

  1. 输入被转换成带时间、空间和来源的观测;
  2. 观测写入任务状态;
  3. Agent 选择检索、分析或工具动作;
  4. 环境反馈再次写入状态;
  5. 输出主张必须经过证据校验;
  6. 证据不足时转为追问或人工确认。

OpenAI Agents SDK 当前文档也将运行循环、工具调用、交接、审批、状态和可观测性作为独立能力;如果应用需要完全自定义循环,则可以自行管理模型交互、输出项、工具和分支。(developers.openai.com)


九、可执行的最小证据对齐实现

下面的代码不依赖具体模型供应商,演示“主张必须绑定证据”的最小机制。它可以直接用 Python 运行。

from dataclasses import dataclass, field
from typing import List, Literal


Relation = Literal["supports", "contradicts", "context"]


@dataclass
class Evidence:
    evidence_id: str
    source_type: str
    content: str
    confidence: float
    freshness_seconds: int | None = None


@dataclass
class Claim:
    claim_id: str
    text: str
    evidence_ids: List[str] = field(default_factory=list)


def validate_claim(
    claim: Claim,
    evidence_map: dict[str, Evidence],
    min_confidence: float = 0.8,
) -> tuple[bool, str]:
    if not claim.evidence_ids:
        return False, "没有绑定任何证据"

    missing = [
        evidence_id
        for evidence_id in claim.evidence_ids
        if evidence_id not in evidence_map
    ]
    if missing:
        return False, f"证据不存在: {missing}"

    evidences = [evidence_map[eid] for eid in claim.evidence_ids]

    weak = [
        e.evidence_id
        for e in evidences
        if e.confidence < min_confidence
    ]
    if weak:
        return False, f"证据置信度不足: {weak}"

    return True, "证据绑定通过"


evidence = {
    "sensor-001": Evidence(
        evidence_id="sensor-001",
        source_type="device_sensor",
        content="设备 D-1007 温度为 86℃",
        confidence=0.99,
        freshness_seconds=10,
    ),
    "manual-042": Evidence(
        evidence_id="manual-042",
        source_type="document",
        content="当前手册规定壳体温度高于 80℃ 时应停止运行",
        confidence=0.98,
    ),
}

claim = Claim(
    claim_id="claim-001",
    text="设备 D-1007 应停止运行",
    evidence_ids=["sensor-001", "manual-042"],
)

ok, message = validate_claim(claim, evidence)
print(ok, message)

预期输出:

True 证据绑定通过

这段代码只检查“是否存在足够可信的绑定证据”,并没有证明语义蕴含关系。例如,它不能自动判断 86℃ 是否真的高于 80℃,也不能确认手册是否适用于当前设备。因此生产实现还需要:

  • 结构化数值比较;
  • 文档版本匹配;
  • 时间新鲜度校验;
  • 冲突检测;
  • 主张类型约束;
  • 人工审核策略。

可以把主张限制为结构化形式:

{
  "claim_type": "threshold_violation",
  "metric": "temperature",
  "observed_value": 86,
  "threshold": 80,
  "operator": ">",
  "unit": "C",
  "evidence_ids": ["sensor-001", "manual-042"]
}

这样,核心判断可以由确定性代码执行,而不是完全交给自然语言模型。


十、从观测到回答的完整算例

输入:

  • 图片:连接器右侧有变色区域;
  • 视频:00:11.800–00:14.600 风扇逐渐停止;
  • 音频:00:05.310–00:08.920 用户说“设备有点烫,风扇声音不对”;
  • 文档:当前版本手册规定温度超过 80℃ 应停机;
  • 工具:设备 D-1007 当前温度为 86℃,运行状态为 running

第一步:形成观测

O1 = 图像观察:连接器右侧变色
O2 = 视频观察:风扇在 00:11.800–00:14.600 停止
O3 = 用户陈述:设备发烫、风扇声音异常
O4 = 传感器事实:当前温度 86℃
O5 = 规范事实:超过 80℃ 应停机
O6 = 当前状态:设备仍在运行

第二步:提出假设

H1:设备可能存在散热或风扇故障
H2:设备当前温度超过安全阈值
H3:设备应立即停止运行

其中:

  • H1 由 O1、O2、O3 支持,但仍是诊断假设;
  • H2 由 O4、O5 经过确定性比较得到;
  • H3 还需要 O6,因为“应停止”意味着当前仍处于运行状态。

第三步:选择动作

若系统具备关机工具,不能直接执行。因为这是副作用动作,应先确认:

当前设备运行中,温度 86℃,适用阈值为 80℃。
是否立即停止设备?

如果业务政策规定该条件下自动停机,则可以执行,但必须记录:

{
  "action": "stop_device",
  "device_id": "D-1007",
  "policy_basis": ["manual-042", "safety-policy-07"],
  "approval": "automatic_policy",
  "idempotency_key": "stop-D-1007-20260901-092410"
}

第四步:生成回答

较好的回答是:

设备存在需要立即处理的过热风险。当前温度为 86℃,而适用手册规定壳体温度高于 80℃ 时应停止运行,因此已满足停机条件。现场视频显示风扇在 00:11.800–00:14.600 期间减速并停止,图片还显示连接器右侧有变色区域,这些现象支持散热或连接器热损伤的诊断假设。用户在 00:05.310–00:08.920 也报告了设备发烫和风扇声音异常,但该部分属于用户陈述,不能替代传感器测量。建议先停止设备,待冷却后检查风扇供电、叶片阻塞和连接器烧蚀情况。

这段回答把三种内容分开:

  • 事实:86℃、手册阈值、视频时间段;
  • 假设:可能存在散热或连接器热损伤;
  • 行动建议:停止设备并检查。

十一、常见错误及其诊断方式

错误一:把转写文本当成事实

失败表现:

用户说设备温度已经超过 80℃,因此设备过热。

诊断方法:

  • 查看证据类型是否为 user_report
  • 检查是否有传感器或仪表读数;
  • 检查转写是否存在数值识别错误;
  • 检查该语句是否为引用、猜测或反问。

修复方式是把答案改为:

用户报告设备温度可能超过 80℃,但当前没有独立测量结果确认。

错误二:视频只看关键帧,不看事件区间

失败表现:

视频中风扇停止。

但没有说明何时停止、是否恢复,也没有说明是否存在遮挡。

诊断时应查看:

  • 采样间隔;
  • 关键帧前后各若干秒;
  • 是否发生镜头切换;
  • 视频时间戳是否从零开始;
  • 音视频是否同步。

错误三:文档引用没有版本

失败表现:

根据手册,阈值是 80℃。

但系统中同时存在多个版本。

诊断时查看:

  • source_version
  • 生效日期;
  • 设备型号和固件;
  • 检索结果是否跨版本混合;
  • 引用是否能定位到页码和章节。

错误四:工具成功不等于业务成功

例如工具返回 HTTP 200,但响应内容是:

{
  "accepted": true,
  "status": "pending"
}

这只能说明请求已接收,不代表设备已经停机。Agent 应继续查询最终状态,或者向用户明确说明:

停机指令已提交,但设备是否已经停止尚未确认。

错误五:把多个派生证据当成独立投票

图像分析模型从同一张图生成三个相似描述,不代表有三个独立证据。证据聚合应按来源去重:

Independent(ei,ej)={1,source(ei)source(ej)0,otherwise\operatorname{Independent}(e_i,e_j) = \begin{cases} 1,& source(e_i)\neq source(e_j)\\ 0,& otherwise \end{cases}

这不是说同一来源的信息没有价值,而是不能错误地放大置信度。


十二、并发、取消和故障路径

多模态 Agent 通常同时处理多个输入:

  • 音频转写持续进行;
  • 视频抽帧异步执行;
  • 文档 OCR 和检索并行;
  • 工具查询需要等待网络;
  • 主模型可能在中间结果到达后重新规划。

并发安全的关键是为每个事件分配单调递增的序号:

{
  "session_id": "s-1007",
  "event_seq": 42,
  "event_type": "sensor_result",
  "created_at": "2026-09-01T09:24:10+08:00",
  "payload": {}
}

状态更新必须检查版本:

St+1={apply(St,e),seq(e)>seq(St)St,otherwiseS_{t+1} = \begin{cases} \operatorname{apply}(S_t,e), & seq(e)>seq(S_t)\\ S_t, & otherwise \end{cases}

否则会出现旧的 ASR partial 覆盖新的 final,或旧工具响应覆盖最新设备状态。

取消传播

当用户打断语音回答时,取消信号应传播到:

TTS 播放器
  -> 音频生成任务
  -> 主模型流
  -> 尚未开始的只读工具
  -> 可取消的视频分析任务

但已经提交的副作用操作不能简单取消,必须查询其最终状态。取消是一种控制消息,不是对外部世界的回滚保证。


十三、Workflow 还是 Agent

不是所有多模态流程都应该做成自主 Agent。

如果流程固定为:

上传视频
-> 抽帧
-> ASR
-> 查询手册
-> 生成报告

那么它更适合 workflow。固定路径更容易测试、计费和审计。

如果任务需要根据内容动态决定:

  • 是否继续分析视频;
  • 是否查询设备状态;
  • 是否搜索多个版本的手册;
  • 是否请求用户补充照片;
  • 是否转人工;
  • 是否执行控制动作;

那么才需要 Agent 的动态决策。

Anthropic 建议从最简单的方案开始,因为 Agent 的灵活性通常伴随更高延迟、更高成本和错误累积风险;固定任务优先使用可预测的 workflow,只有当任务步骤难以预先确定时才增加 Agent 自主性。(anthropic.com)

一个实用的划分方式是:

场景 推荐结构
固定格式的发票识别 Workflow
多份文档中的字段核对 Workflow + 并行处理
根据证据缺口决定是否继续查证 Agent
语音客服中的查询、追问和转人工 Agent
设备控制和安全审批 Agent + 确定性策略门
高风险副作用操作 Agent 规划 + 程序化审批

十四、生产系统必须保留什么

一个可审计的多模态 Agent 运行记录至少应包含:

{
  "run_id": "run-20260901-001",
  "user_goal": "判断设备是否需要停机",
  "inputs": [
    {"type": "image", "id": "img-001", "sha256": "..."},
    {"type": "audio", "id": "aud-001", "sha256": "..."},
    {"type": "video", "id": "vid-001", "sha256": "..."},
    {"type": "document", "id": "manual-v3.pdf", "version": "v3"}
  ],
  "observations": [],
  "claims": [],
  "tool_calls": [],
  "approvals": [],
  "final_answer": {},
  "status": "completed"
}

还应能回答以下问题:

  1. 模型看到了哪一个文件版本;
  2. 视频结论来自哪些时间区间;
  3. 音频转写是否为 final;
  4. 工具调用使用了哪些参数;
  5. 工具结果产生于什么时候;
  6. 哪些结论是事实,哪些是推断;
  7. 哪些证据互相冲突;
  8. 哪个动作造成了外部副作用;
  9. 用户是否打断过输出;
  10. 最终答案是否经过人工确认。

OpenAI 文档将 tracing、状态、审批、运行结果和评估作为 Agent 系统的重要组成部分;这说明可观测性不是上线后的附加功能,而是 Agent 运行时的一部分。(developers.openai.com)


十五、评估不能只评估最终文本

多模态 Agent 的评估至少分成五层:

1. 模态解析正确性

  • 图像区域是否正确;
  • ASR 是否漏词;
  • 时间戳是否偏移;
  • 视频事件起止是否合理;
  • 文档页码和表格结构是否保留。

2. 状态更新正确性

  • 新事件是否覆盖旧事件;
  • partial 是否被 final 替换;
  • 工具失败是否进入状态;
  • 用户打断是否取消输出;
  • 过期传感器结果是否被拒绝。

3. 工具选择和参数正确性

  • 是否选对工具;
  • 是否使用最新设备标识;
  • 是否正确处理单位;
  • 是否在副作用动作前取得授权;
  • 是否避免重复执行。

4. 证据对齐正确性

  • 每个重要主张是否有证据;
  • 证据是否真的支持主张;
  • 引用位置是否可复核;
  • 冲突是否被显式报告;
  • 不确定性是否与证据质量一致。

5. 任务完成质量

  • 是否给出用户真正需要的结论;
  • 是否完成必要动作;
  • 是否在不能确定时正确追问;
  • 是否在风险条件下停止自动化。

一个只检查最终答案“像不像正确答案”的评估,会掩盖中间错误。例如,答案恰好正确,但引用了错误版本的手册;下一次版本更新后系统就会失效。


结语:多模态 Agent 的核心是对齐,而不是模态数量

图像提供空间观察,音频提供语义和声学时间线,视频提供连续事件,文档提供规范和背景,工具提供环境事实与动作能力,证据系统则把这些信息绑定到可复核的来源。

真正可靠的多模态 Agent 需要同时满足:

可靠回答=正确观测正确状态正确行动证据可验证\text{可靠回答} = \text{正确观测} \land \text{正确状态} \land \text{正确行动} \land \text{证据可验证}

任何一个条件缺失,系统都可能产生表面流畅、实际不可审计的回答。

因此,设计重点不应是“支持多少种模态”,而应是:

  • 每种模态是否保留时间、空间和版本信息;
  • 观测、推断、工具结果和用户陈述是否被区分;
  • Agent 是否根据环境反馈而不是自身猜测继续行动;
  • 工具副作用是否受权限、幂等和审批控制;
  • 最终每个关键主张是否能够回指到证据;
  • 证据不足或发生冲突时,系统是否愿意明确说“不确定”。

当这些关系被建模为状态、事件、主张和证据,而不是散落在提示词和日志中的字符串时,多模态 Agent 才从“会看、会听、会调用工具”进化为可以被验证、调试和负责的工程系统。


系列导航与关联阅读

官方资料

本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。