
匿名 AI 模型审计中最难的不是评估输出质量而是在不接触权重与内部结构的前提下通过黑盒接口确认模型身份。现实中的模型接口常常只给一个公网地址和鉴权信息响应里可能没有model字段模型卡、权重、推理代码都不会开放。此时审计方要面对一个看似矛盾的问题供应商声称接口背后运行的是某个模型但审计方只能通过输入文本、观察输出文本来验证这句声明。这类任务通常叫匿名 AI 模型的黑盒身份验证英文可以称为 Black-Box Identity Verification。它和模型能力评测不同核心目标不是“模型强不强”而是“接口背后的行为和参照模型是否一致、差异是否超出采样误差”。如果只是做一次 benchmark高能力模型可能得到相近分数低能力模型也可能因为训练数据重叠而表现接近能力评测并不能充当身份证据。身份验证需要有稳定的探针集、可重复的采样策略和明确的统计判定规则。下面这套方法把整个审计过程拆成四个阶段固定声明与参照基线、采集行为指纹、做统计身份匹配、输出审计结论并进入持续复核。四阶段协议在授权审计、内部模型治理、供应商准入和模型版本回归场景中可以直接作为执行框架使用。所有探测查询都应在数据使用授权范围内进行不能使用未脱敏个人信息或受版权限制的私密数据作探针否则审计本身会引入合规风险。1. 匿名 AI 模型为什么需要黑盒身份验证1.1 匿名模型接口的常见审计场景先解释一下“匿名 AI 模型”在这里指什么。它不是说模型作者匿名而是指模型以 API 或网关形式暴露给调用方时没有暴露模型卡、权重、训练数据或内部版本信息。调用方看到的只是一个可对话或可补全的接口模型身份处在黑盒状态。实际工作中至少有三类场景要求做身份核验供应商准入。合同约定使用某个版本的模型但实际接口由供应商自研网关转发调用方无法直接确认后端是否真的切换到了约定版本。内部多个模型的统一接入。平台方把多个开源模型做成统一 API路由策略可能按租户、按请求量、按时间窗口切换审计时要确认租户请求实际命中了哪个模型。模型迭代回归。上游模型从 7B 升级到 13B从 FP16 量化为 INT8或者从精调版本切换为基座版本行为变化是否超出允许范围。这些场景有一个共同点审计方拿到的是模型的黑盒观测面而不是模型内部文件。只看网络请求并不能证明“某个模型在运行”因为网关层可以随意修改返回字段。哪怕响应里写了model: candidate-v1这个字段也只代表路由标记不构成身份证据。1.2 为什么不能只信任响应中的模型名字段很多模型服务在返回结果时会附带模型名称。正常情况下它有助于定位问题但不能把它当作审计依据。原因有两层。第一层是字段可能被网关覆盖。企业采购的模型服务通常不是直连官方模型而是经过 API 网关、路由代理和流量治理层。网关在转发时可能重新写入或清洗model字段也可能为了兼容旧版本统一返回一个默认名称。第二层是字段与真实计算单元可能不同步。运营人员切流时可能只改了路由权重没有同步更新响应字段也可能同一个模型部署了多个推理引擎量化位宽、batch 策略、缓存逻辑不同导致同样的文本输入产生不同输出。此时model字段虽然是真实的却不能说明部署形态一致。所以黑盒身份验证必须观察模型本身的输出行为。审计协议设计的出发点就是假定元数据不可信用输入输出证据替代自报家门。1.3 身份验证要回答的不是分数问题黑盒身份验证要回答的问题是给定黑盒目标模型 T以及一个已声明身份 CT 的行为是否与 C 的行为统计上一致。这不同于“谁的能力更高”。两个模型可能都是优秀模型但在同样前缀下的下一个 token 分布差异很大反过来同一个模型在不同采样参数下也可能给出不同答案。因此验证协议需要同时控制两个变量模型本身的差异。采样和部署带来的噪声。四阶段协议的后两个阶段主要处理这一个问题。前面先做阶段一和阶段二是为了确认比对对象、缓解实验噪声。2. 四阶段协议的整体结构与每段交付物2.1 为什么用协议而不用单一工具模型身份验证很难靠一个脚本解决问题。原因是接口格式、概率输出能力、采样参数暴露程度、候选模型版本都会影响结果。如果只做一次“答案是否一致”的比较很容易因为提示词太简单或参差不齐而被误导。用协议来组织审计过程是为了让人在任何一步停下来都能判断结果是否可信。每个阶段都有明确输入、明确操作和明确交付物。四阶段分别如下阶段核心任务典型交付物阶段一固定审计声明、授权范围和参照模型集审计配置、候选模型基线说明阶段二构造探针集并采集黑盒行为指纹探针集、采样配置、指纹 JSON 证据阶段三对行为指纹做统计比对输出匹配结论距离分数、置信区间、结论标签阶段四编写审计报告并设计持续复核机制审计报告、复测计划、变更触发器这四阶段不是一次性顺序执行完就结束。生产环境里模型供应商可能悄悄升级版本路由策略也可能变化。所以阶段四通常会回到阶段一形成循环审计。2.2 每一阶段最该守住的原则阶段一最该守住的原则是“声明必须大于推断”。只有当审计方知道候选模型是谁才能做参照比对。如果完全没有候选身份黑盒探针只能形成行为画像不能可靠地下结论说“这个接口就是某个具体模型”。阶段二最该守住的准则是“控制采样噪声”。请求参数不一致会直接污染指纹比如候选 A 使用temperature0候选 B 使用temperature0.8两者输出差异会被误判成模型身份差异。阶段三最该守住的准则是“阈值需要校准”。任何距离阈值都必须结合已知同源模型和已知异源模型做对照不能拿一个固定数字套所有任务。阶段四最需要守住的准则是“保留证据”。所有请求原文、响应原文、采样参数、时间戳、哈希值都要归档否则审计结论无法复核出了争议也难以定位。3. 阶段一落地声明、授权与参照基线3.1 先固定目标接口的已声明身份审计启动时第一步不是写代码调用接口而是把“这家供应商声称接口后面是什么模型”记录下来。记录内容包括审计目标端点地址。供应商声明的模型名称和版本例如internal-llm-v2.3。发起审计的原因例如“新合同验收”“上线前评估”“随机抽检”。可使用的请求预算例如“每日最多 3000 次调用”。数据使用范围例如“只能使用公司自备合成数据”。这些信息会进入审计配置。在真实项目里审计配置最好用 YAML 管理避免在脚本中散落多个硬编码 URL。audit: name: anonymous-model-v2.3-verification mode: authorized start_time: 2025-01-01T00:00:0008:00 target: name: anonymous-target base_url: ${TARGET_BASE_URL} claimed_identity: internal-llm-v2.3 candidates: - name: internal-llm-v2.3-ref base_url: ${REF_V23_BASE_URL} - name: internal-llm-v2.1-ref base_url: ${REF_V21_BASE_URL} probe: max_queries: 1200 max_tokens: 32 temperature: 0.0 top_p: 1.0 repeat_times: 5 timeout_seconds: 60这段配置说明了一个关键做法审计目标只有一个但参照候选可以放多个。如果只放一个候选模型所有距离分数都只针对“是不是某个模型”这一个结论缺少区分度。放两个以上候选可以观察目标与不同候选之间的相对距离结论会稳健很多。3.2 准备参照模型接口身份验证必须建立在参照模型之上。参照模型应该满足三个条件与声明身份同源优先使用同一供应商在稳定的部署环境中提供的接口。版本信息明确不能是某个未知派生版本或早期灰度版本。有能力限制采样参数至少能让审计方获得稳定的输出观测。如果供应商只承诺模型名不提供独立参照端点可以让对方提供一小段可复核日志但日志不能替代主动采样。最好的参照介质是能够接收相同提示词的 Oracle 接口由审计方自己发请求。没有参照模型时四阶段协议不应该进入结论输出阶段。此时只能输出“能力画像”不能输出“身份验证结论”。这一点在审计报告中要写得非常明确。3.3 阶段一的检查点阶段一做完整后应确认以下问题都有明确答案目标端点和候选参照端点是否在同一个网络可达条件下探针数据是否已经完成脱敏和授权审批是否准备了至少两个候选模型用于校准阈值采样参数是否能在所有候选上保持一致如果这些答案存在“不清楚”不要急着进入阶段二。否则后面采集上来的指纹就算看起来漂亮审计结论也没有可信基础。4. 阶段二落地设计探针集并采集行为指纹4.1 探针集不是普通测试集身份验证用到的探针集不需要追求“高难度”它追求的是“区分度”。一段提示词如果任何模型都能给出同样答案它只能证明系统在工作不能用于判断身份。一段提示词如果同源模型之间稳定一致而异源模型之间差异明显它就是理想探针。探针集应包含三部分低自由度确定性题。例如“只输出 A、B、C、D 中的一个选项”模型在temperature0时输出应该稳定。需要延续前缀的开放题。例如给定一段代码片段或一个故事开头让模型续写有限长度用于观察风格、词汇和格式特点。边界与格式题。例如要求 JSON 输出、要求多行代码、要求给出空行、要求重复特殊符号用来观察模型对格式指令的执行差异。probe_items [ { id: choice-001, type: choice, prompt: 请只输出选项字母A. 北京 B. 上海 C. 广州 D. 深圳。\n答案 }, { id: continuation-002, type: continuation, prompt: 请续写下面的函数只写 Python 代码\ndef deduplicate(items):\n }, { id: format-003, type: json_format, prompt: 请输出 JSON字段包含 name 和 count不要输出其他内容。 } ]这些只是示例实际项目要根据候选模型类型调整。代码模型应增加代码探针公文模型应增加公文格式探针多模态模型如果允许文本输入则只能验证文本链路不能验证视觉链路。4.2 一个最小黑盒探针客户端阶段二需要写一段可重复调用的客户端代码。下面的示例不绑定特定供应商而是假设接口兼容常见的POST /completions协议。不同网关可能改路径也可能要求把请求体包在固定结构中使用时必须按真实协议调整。import os import time import json import requests class BlackBoxModelClient: def __init__(self, name: str, base_url: str): self.name name self.base_url base_url.rstrip(/) self.headers { Authorization: fBearer {os.environ[AUDIT_API_TOKEN]}, Content-Type: application/json, } def complete( self, prompt: str, max_tokens: int 16, temperature: float 0.0, top_p: float 1.0, ): payload { prompt: prompt, max_tokens: max_tokens, temperature: temperature, top_p: top_p, } response requests.post( self.base_url /completions, headersself.headers, jsonpayload, timeout(5, 60), ) response.raise_for_status() return response.json()这里要注意Authorization头来自环境变量不是把密钥写在源码里。如果黑盒接口不支持temperature、top_p参数客户端里可以不传这些字段但阶段三比对的原始记录里必须注明“目标与候选接口采样参数不统一”。4.3 采集行为指纹并保存证据探针执行时不要只保存最终文本最好保存原始 JSON。原始 JSON 里可能包含 finish reason、token 数、概率字段、缓存命中信息这些都能帮助排查异常。def collect_fingerprints( client, probe_items, repeat_times: int 3, save_dir: str evidence, ): os.makedirs(save_dir, exist_okTrue) records [] for item in probe_items: for round_no in range(repeat_times): raw client.complete(item[prompt]) record { client: client.name, probe_id: item[id], round: round_no, prompt: item[prompt], raw_response: raw, timestamp: time.time(), } with open( f{save_dir}/{client.name}-{item[id]}-{round_no}.json, w, encodingutf-8, ) as fp: json.dump(record, fp, ensure_asciiFalse, indent2) records.append(record) time.sleep(0.2) return records保存证据时还可以额外计算每个原始响应的 SHA-256 哈希。这样后续一旦发生“证据被编辑过”的争议可以通过哈希快速校验。import hashlib def evidence_hash(raw_obj) - str: blob json.dumps(raw_obj, sort_keysTrue, ensure_asciiFalse) return hashlib.sha256(blob.encode(utf-8)).hexdigest()4.4 从原始响应中抽取出可用于比对的指纹原始响应不适合直接做统计。阶段二最后要做一步归一化把输出转换成稳定结构。常见指纹结构如下{ probe_id: choice-001, client: target, sample_count: 5, sampling: { temperature: 0.0, max_tokens: 16 }, choice_frequency: { A: 0.8, B: 0.0, C: 0.2, D: 0.0 }, summary: target tends to output A on this probe }如果接口返回 logprobs可以在一轮请求里直接拿到 top-k token 概率。如果接口不返回概率就通过多次重复采样统计频率。两种方式都需要把输出做规范化例如去掉两端空白、统一大小写、把数字1和中文一映射到同一类别。规范化逻辑应该是全程唯一确定的不能在不同候选上分别实现。5. 阶段三落地统计比对与身份判断5.1 用离散选择题的概率分布做基础比对最简单的黑盒身份比对是直接看多次重复采样的答案频率。设目标模型在某个探针上的频次分布为 P候选模型分布为 Q。如果两个模型同源且采样条件相同P 和 Q 不应相差太大如果两个模型异源差异通常会在多个探针上稳定出现。下面是一段计算 Jensen-Shannon 散度的参考实现。它适合处理归一化的概率字典。import math import statistics def _kl_divergence(p, q): keys set(p) | set(q) divergence 0.0 for key in keys: pv max(p.get(key, 0.0), 1e-12) qv max(q.get(key, 0.0), 1e-12) divergence pv * math.log(pv / qv) return divergence def js_divergence(p, q): keys set(p) | set(q) m {} for key in keys: pv max(p.get(key, 0.0), 1e-12) qv max(q.get(key, 0.0), 1e-12) m[key] 0.5 * (pv qv) return 0.5 * _kl_divergence(p, m) 0.5 * _kl_divergence(q, m)两个完全相同分布的距离为 0。分布差异越大距离越大。代码里的平滑处理是为了防止“某项概率为 0”导致对数计算直接崩溃。5.2 对多个探针取平均距离并加入对照候选单个探针的距离没有意义某道题可能因为训练数据重叠而让多数模型表现一致。审计时应该对全部探针计算距离然后取平均并与多个候选模型做对照。def compare_clients( target_fingerprints, candidate_fingerprints, probe_ids, ): distances [] for probe_id in probe_ids: target_dist target_fingerprints[probe_id] candidate_dist candidate_fingerprints[probe_id] if choice_frequency in target_dist: p target_dist[choice_frequency] q candidate_dist[choice_frequency] else: p target_dist[token_probability] q candidate_dist[token_probability] distances.append(js_divergence(p, q)) return statistics.mean(distances)运行后可以得到结果表比较对象平均 JSD判定倾向目标 vs 声明版本 v2.30.028很接近目标 vs 旧版本 v2.10.214距离明显目标 vs 完全不同的模型 C30.386距离很大这样的相对比较比单纯看一个绝对阈值更可信。如果目标与声明版本的分数显著低于与其他候选的分数结论会偏向“支持声明身份”。5.3 判定标签匹配、不匹配、不确认黑盒身份判断最好不要用“是”或“不是”这种二值语言更稳妥的是三标签体系。标签含义支持一致目标与声明版本在可控探针集上差异很小未发现超出采样噪声的证据不支持一致目标与声明版本存在系统性差异且差异在多个探针上重复出现无法确认请求量不足、候选缺失、探针区分度低或参照版本不稳定三标签体系可以预防一个重要问题把“没有发现差异”错误理解为“完全证明同一”。黑盒观测不可能覆盖所有输入只能覆盖采样空间的一小部分。某个结果支持一致不等于模型权重完全相同。5.4 阈值如何才能可靠阈值不能随便拍。推荐用“负对照”校准找两个已知不同的模型采集同样探针集得到一组异源距离。再找同一个模型的两次稳定部署采集同样探针集得到一组同源距离。观察两组距离分布是否有清晰分界。如果异源距离和同源距离分布大量重叠说明探针集区分度不够应该回到阶段二增加探针或调整任务类型。如果两组距离分得开再根据分界点设置阈值。可以把阈值直接写到审计配置里并在报告里注明校准来源。6. 阶段四落地报告管理、误判控制与持续审计6.1 审计报告应包含的事项阶段四写报告不是简单输出一个概率分数。报告要能回答三个问题审计依据是什么。为什么得出这个判断。如果判断错误风险有多大。因此报告至少包含以下部分章节示例内容审计对象匿名目标接口 URL、发起时间、请求量声明身份供应商声称的模型名称及版本参照模型v2.3 参照、v2.1 参照探针配置探针数量、类别分布、重复次数采样参数temperature、max_tokens、top_p比对结果距离分数表、置信区间信息结论支持一致 / 不支持一致 / 无法确认证据归档证据目录、哈希值、复现方法报告里还要写清楚限制条件。比如“本次审计只覆盖文本接口不覆盖视觉链路”“采样预算限制在 1200 次以内”“目标与参照接口的采样温度字段存在差异距离可能被放大”。6.2 审计算法也会产生误判黑盒身份验证存在两类误判。第一类是假阳性把不同模型误判为同一个模型。出现概率高的场景是探针任务过于简单大家都输出“是”或“正确”。第二类是假阴性把同一个模型的两次部署误判为不同模型。出现概率高的场景是模型量化、温度参数差异、路由模型版本有灰度。控制误判要组合使用手段提高探针集区分度。增加负对照候选。多次重复采样观察距离的稳定性。对“不支持一致”的结论先复查阶段二原始响应。如果报告显示目标与声明模型不一致不要急着给供应商定结论。应该先看是否有版本灰度、路由比例、采样参数差异和部署批次变化。必要时请供应商提供变更窗口在变更前后重新采集一轮。6.3 进入持续审计循环轮询式审计适合在以下时间点触发供应商服务上线或升级时。供应商通知模型版本变更时。目标接口长期低负载后切换到新节点时。每隔固定周期例如每月抽取 200 个探针做回归。持续审计可以复用同一套探针集。每次结果与基线距离对比一旦超过告警阈值就自动创建审计任务。这个机制的作用不是定位故障而是在模型身份发生变化时提前暴露风险避免劣化版本或错误路由在无人察觉的情况下长期运行。7. 黑盒身份审计常见坑与排查路径7.1 常见坑一只用简单常识题做探针很多第一批探针集都喜欢加入“11 等于几”“中国的首都是哪里”这类问题。这些问题正确率很高但区分度极低。几乎所有模型都能答对目标与参照模型的距离会普遍接近 0导致审计方误以为模型身份一致。解决办法是增加高熵探针。高熵指的是在模型候选答案空间中多个候选答案都有一部分概率但不同模型对答案的偏爱明显不同。开放续写和格式化输出通常比常识问答更有区分度。7.2 常见坑二忽略输出长度对相似度的影响假设目标模型最大输出 128 token候选模型默认最大输出 32 token。两个相同模型相同参数但故事续写长度不同会导致平均距离显著增大。这不是身份不一致而是参数配置不一致。在阶段二和阶段三所有候选接口的max_tokens、stop、temperature都应保持一致。如果接口不开放这些参数必须在证据文件里标明。7.3 常见坑三直接比较不同词表下的 top logprobslogprobs 是很有价值的信息但它依赖模型词表。同一个 token 在不同词表里可能是不同 ID比较 ID 没有意义。即使都返回中文文本tokenizer 的变化也会让 top-k 序列不同。建议先做两步归一化把 logprobs 输出映射成真实文本片段或字符。或者只比较归一化后的选项概率不直接比较 token ID。如果无法把 logprob 映射到可比较的文本层就不要把它当作主要证据。7.4 排查链路从异常结论倒推原因当审计结论出现“不支持一致”但业务方认为不可能时按下面顺序排查检查请求是否真的到达目标端点有没有被本地缓存或代理命中。检查目标与参照模型的响应是否在同一个时间段内采集。检查两种接口的采样参数是否一致。检查输出清理逻辑是否一致例如是否保留空格、换行和标点。检查探针集是否偏向特定主题是否只在特定领域表现差异。检查参照模型是否已经升级而不只是检查目标模型。检查输出长度字段确认没有把被截断的文本误当成完整答案。任何一步异常都应先修正后再重新计算距离。不要把带噪声的结果直接写入审计报告。