这篇能做出什么
先看结果。按这篇走完一遍,团队可以拿出三份可以直接给法务、采购、安全和管理层看的东西:
1. 一张模型资产台账:公司里到底有哪些地方在调用外部模型服务、用的是哪个供应商、走的是哪条通道、由谁付费、经手过哪些数据。这张表能覆盖“合同里有的”和“合同里没有的”两类,而后者通常是风险最大的部分。
2. 一个证据包目录:每一条台账记录后面都挂着一组可复核的材料,包括服务条款快照、数据驻留声明、子处理商(subprocessor)名单、配置截图或配置文件、调用日志样本。每份材料带时间戳和哈希,能证明你什么时候、以什么方式取到的。
3. 一份替换演练报告:证明当某个供应商因为政策、合规或商业原因被要求停用时,业务能在多长时间内切到备选模型,切换过程中哪些功能会退化,回滚路径是什么。
触发这件事的场景不止一种。美国若干联邦机构曾以安全审查为由停用某款中国厂商的模型服务,这类事件这几年在不同国家和行业反复出现:某个供应商突然被列入限制名单、某个 API 区域突然停止服务、某份服务条款悄悄改了数据使用条款。这些事件对企业来说,真正要回答的问题始终是同一个:如果明天必须换掉这个模型,你知不知道它被用在了哪里,能不能在两三天内切走。
这篇把这件事拆成六步,全程可以用免费工具和通用配置完成,不需要采购专门平台。
前置条件清单
动手之前,确认下面这些东西能拿到,缺哪项就先补哪项:
- 代码仓库的只读权限(或至少能让平台方导出全部仓库的快照)。要用它做调用痕迹扫描。
- 云账号的账单只读权限。用来发现“用个人信用卡在云市场或模型平台直购”的漏网资产。
- 模型网关 / API 网关的访问日志。至少保留最近 30 天的请求日志,包含时间、来源服务、目标域名、状态码。
- 各业务线的应用清单。哪怕是口头确认,也要有人能回答“你们组有没有调外部大模型”。
- 一份合规口径(可以由法务或安全给一句话):哪些数据等级不允许出境、哪些数据等级不允许离开公司内网、哪些业务必须可审计。没有这个口径,后面所有判断都是拍脑袋。
- 一个备选模型的可测试入口。可以是另一家公有云、可以是自建推理服务,只要能发请求就行。没有备选就没法演练,这一步不能省。
- 一个共享目录(对象存储或 Git 仓库都可以),用于存放证据包。
工具方面,一台能联网的 Linux 或 macOS 机器、Python 3、curl、sha256sum、jq、git 就够了。
步骤一:先定口径,再动手
顺序很重要。先扫资产再定口径,会导致你扫出一堆东西却不知道怎么分类。先花半小时写清楚三件事:
第一,模型资产的判定粒度。 建议按“供应商 + 接口地址 + 模型标识 + 调用方服务”四元组来切分。同一个供应商的两个模型算两条资产,因为它们的可替换性和合规条款可能不同。
第二,证据分级。 用一套简单的 A/B/C 三级,避免每次开会都重新争论“这算不算证据”:
| 等级 | 含义 | 例子 |
|---|---|---|
| A | 来自供应商官方渠道、可取回、可验证 | 官网服务条款页面的快照 + 哈希、官方状态页历史、合同正本扫描件 |
| B | 来自公司内部系统、可复现 | 模型网关配置文件、API 调用日志导出、云账单明细 |
| C | 口头说明、聊天记录、第三方转述 | 销售口头承诺、同事转发的截图 |
台账里不同等级要分开标。审计报告里只把 A 级证据当作“可对外陈述的事实”,B 级用于内部核查,C 级只能当作线索。
第三,风险打分口径。 建议用四个维度各自 1–3 分,加总后排序:
- 数据敏感度:1 = 公开数据,2 = 内部数据,3 = 个人信息或客户数据
- 可替换性:1 = 已有多家备选且验证过,2 = 有备选但没验证,3 = 无备选
- 供应商稳定性:1 = 有正式合同与 SLA,2 = 有账号无合同,3 = 个人账号直购
- 证据完整度:1 = 有 A 级证据,2 = 只有 B 级,3 = 只有 C 级
总分 4–12 分。分数高的排前面处置,这就是你的审计结论落地的抓手。
步骤二:模型资产盘点
不要只查采购合同。合同只能覆盖走完采购流程的部分,而实际情况里,模型调用往往从工程团队的一次试验开始,用个人账号充了值,跑起来之后没人回头补流程。盘点要从五个互相独立的来源交叉验证:
1. 代码仓库扫描——找调用痕迹。
2. 云账单与 SaaS 账单——找付费痕迹。
3. 网关与出口日志——找实际流量痕迹。
4. 域名解析与出口 IP 观察——找“以为走了代理其实没走”的情况。
5. 各业务线自述——补充上面四个都覆盖不到的(比如某个业务在客户端里内嵌了带 key 的 SDK)。
扫描代码仓库
把各仓库拉到本地统一目录后,跑一个扫描脚本。它不追求精准,目标是“宁多勿漏”,后面再人工筛。
```python
scan_model_calls.py
用途:扫描代码库中的模型调用痕迹,输出候选清单供人工确认
用法:python scan_model_calls.py /path/to/repos model_calls.csv
import csv, os, re, sys
PATTERNS = [
("sdk", re.compile(r"\b(openai|anthropic|google\.generativeai|dashscope|zhipuai|volcengine|huggingface_hub|ollama)\b", re.I)),
("base_url", re.compile(r"base_url\s*[=:]\s*\"'", re.I)),
("endpoint", re.compile(r"https?://[a-z0-9.\-]+/(v1|api|chat/completions|embeddings)", re.I)),
("model", re.compile(r"[\"']?model[\"']?\s*[=:]\s*\"'[\"']", re.I)),
("api_key", re.compile(r"[A-Z0-9_]*(API_KEY|APIKEY|TOKEN|BASE_URL|ENDPOINT)[A-Z0-9_]*", re.I)),
]
SKIP_DIRS = {".git", "node_modules", "vendor", "dist", "build", "__pycache__", ".venv", "venv"}
EXTS = {".py", ".js", ".ts", ".tsx", ".go", ".java", ".kt", ".rb", ".php",
".yml", ".yaml", ".json", ".toml", ".ini", ".env", ".conf", ".properties"}
def iter_files(root):
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for fn in filenames:
if os.path.splitext(fn)[1].lower() in EXTS or fn.startswith(".env"):
yield os.path.join(dirpath, fn)
def main(root, out):
rows = []
for path in iter_files(root):
try:
text = open(path, encoding="utf-8", errors="ignore").read()
except OSError:
continue
for lineno, line in enumerate(text.splitlines(), 1):
for kind, pat in PATTERNS:
m = pat.search(line)
if m:
rows.append({
"file": os.path.relpath(path, root),
"line": lineno,
"kind": kind,
"match": (m.group(1) if m.groups() else m.group(0))[:160],
"snippet": line.strip()[:220],
})
break
with open(out, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["file", "line", "kind", "match", "snippet"])
w.writeheader()
w.writerows(rows)
print(f"命中 {len(rows)} 处,已写入 {out}")
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "model_calls.csv")
```
跑完之后按 match 列去重,人工过一遍,把“文档里提到模型名”和“代码里真的在调”区分开。经验上,真正需要进台账的大概是命中量的十分之一。
看账单和日志
账单侧重点看三类关键词:模型 API、GPU 算力、AI 类 SaaS。云厂商的账单导出成 CSV 后用 grep 过滤即可:
```bash
账单 CSV 里筛出与模型/算力相关的条目
grep -Ei 'openai|anthropic|bedrock|vertex|azure.*openai|dashscope|model|llm|gpu|inference' billing_export.csv \
| awk -F',' '{print $0}' | sort -u > billing_ai_items.csv
```
日志侧重点是找出目标域名清单。有了完整的目标域名清单,你就知道流量到底去了哪些外部服务:
```bash
从网关访问日志中提取外部目标域名并统计调用量
假设日志为 JSON Lines,字段包含 ts / src_service / host / status
jq -r 'select(.host != null) | [.src_service, .host] | @tsv' gateway.log \
| sort | uniq -c | sort -rn | head -50 > egress_hosts.tsv
```
这一步经常有惊喜:某个“只用了 A 供应商”的服务,日志里同时出现了三个不同厂商的域名。
步骤三:建立供应商清单与证据链
台账字段设计
用 CSV 起步就够了,字段建议如下(model_id 用你内部编号,不要用供应商的模型名字当主键,因为供应商改名是常事):
```csv
asset_id,upstream_vendor,access_channel,model_identifier,owner_team,env,data_classes,residency_claim,residency_verified,contract_type,evidence_level,evidence_path,review_date,fallback_asset,risk_score,notes
M-001,<供应商A>,公有云 API,<模型标识>,客服技术组,生产,PII-低,声明为境内部署,待核,按量付费,B,evidence/M-001/,2025-01-01,M-002,7,
M-002,<供应商B>,自建推理,<模型标识>,客服技术组,预发,内部数据,完全内网,已核,自持硬件,A,evidence/M-002/,2025-01-01,,4,备选候选
```
两个字段值得单独说:residency_claim 记录供应商声称的数据驻留地,residency_verified 记录你们核实过没有。这两个永远不要合并成一个字段——审计里最有价值的发现,就是“声称”和“核实”之间的差距。
证据包目录规范和取证脚本
每条资产的证据放在一个独立目录,命名固定,内容不超过六类:
```bash
evidence_collect.sh —— 为一条件资产采集一份带时间戳的证据快照
用法:bash evidence_collect.sh evidence/M-001 https://<供应商域名>/legal/terms
set -euo pipefail
EVID="$1"
TERMS_URL="$2"
mkdir -p "$EVID"
1) 抓取条款页面(注意:动态渲染页面可能抓不全,必要时改为浏览器打印 PDF)
curl -sSL --max-time 30 -A "compliance-audit-bot" "$TERMS_URL" -o "$EVID/terms.html" || true
2) 抓取子处理商页面(很多供应商把名单单列一页)
curl -sSL --max-time 30 -A "compliance-audit-bot" "${TERMS_URL%/legal/terms}/legal/subprocessors" \
-o "$EVID/subprocessors.html" || true
3) 记录取证时间、取证人和来源地址
{
echo "collected_at_utc: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "collected_by: $(whoami)"
echo "source_url: $TERMS_URL"
echo "method: curl + browser-pdf"
} > "$EVID/collection_log.txt"
4) 生成哈希清单,证明文件此后未被修改
sha256sum "$EVID"/* > "$EVID/SHA256SUMS"
cat "$EVID/SHA256SUMS"
```
几个实操细节:
- 条款页面多数是动态渲染的,
curl抓下来的 HTML 可能是空壳。这种情况用浏览器“打印为 PDF”,然后照样算哈希,并在collection_log.txt里注明取证方式是人工打印。 - 每季度重跑一次这个脚本,把新快照存成
terms-YYYYQ1.html这样的名字,用diff对比条款变更。供应商改数据使用条款时,你往往是最后知道的。 - 证据目录本身要纳入版本管理或用对象存储的版本控制,别让它躺在某个人电脑里。
台账完整性校验
台账写完先跑一遍校验,把明显的问题拦在外面:
```python
validate_ledger.py
用途:检查台账必填字段、枚举值、以及证据目录是否存在
import csv, os, sys
REQUIRED = ["asset_id", "upstream_vendor", "access_channel", "model_identifier",
"owner_team", "env", "data_classes", "evidence_level", "evidence_path"]
ALLOWED_ENV = {"生产", "预发", "测试", "个人"}
ALLOWED_EVID = {"A", "B", "C"}
def main(path, base):
problems, seen = [], set()
with open(path, encoding="utf-8") as f:
for i, row in enumerate(csv.DictReader(f), start=2):
for k in REQUIRED:
if not (row.get(k) or "").strip():
problems.append(f"第{i}行缺少字段 {k}")
aid = row.get("asset_id", "")
if aid in seen:
problems.append(f"第{i}行 asset_id 重复:{aid}")
seen.add(aid)
if row.get("env") not in ALLOWED_ENV:
problems.append(f"第{i}行 env 取值不在允许范围:{row.get('env')}")
if row.get("evidence_level") not in ALLOWED_EVID:
problems.append(f"第{i}行 evidence_level 取值非法:{row.get('evidence_level')}")
p = row.get("evidence_path", "")
if p and not os.path.isdir(os.path.join(base, p)):
problems.append(f"第{i}行证据目录不存在:{p}")
print("\n".join(problems) if problems else "校验通过")
return 1 if problems else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "."))
```
步骤四:核查数据驻留
数据驻留核查要回答的是:请求发出去之后,数据实际经过了哪些地方、停在了哪里。 这件事不能只看合同,至少要做三层验证。
第一层:配置层
在部署环境里把实际生效的接口地址捞出来。最容易出问题的是这一层——代码里写的是供应商官网地址,但环境变量里被改成了某个第三方代理;或者代码里写的是境内地址,容器里却配了全局代理。
```bash
在部署机或容器内执行,确认实际生效的接口地址与代理设置
env | grep -Ei 'base_url|endpoint|proxy|openai|anthropic|api_host' | sort
如果是容器部署,从容器内部确认
docker exec -it <容器名> env | grep -Ei 'base_url|proxy|api'
确认容器/主机的 DNS 与出站代理
cat /etc/resolv.conf
cat /etc/hosts
```
任何在 base_url 里出现的非供应商官方域名,都要单独立项查清楚是谁在中间转发——转发的这一跳同样构成数据出境或数据交付。
第二层:网络层
看请求最终解析到哪、从哪个出口出去。注意这一层能看到的是入口节点,不一定等于存储位置,所以要结合第三层一起看。
```bash
观察模型接口域名的解析结果(在部署环境内执行)
for host in api.<供应商A域名> api.<供应商B域名>; do
echo "== $host"
getent hosts "$host" || nslookup "$host" 2>/dev/null
done
看出口 IP 的归属组织与国家(仅作线索,不能单独作为驻留结论)
for ip in $(getent hosts api.<供应商A域名> | awk '{print $1}' | head -3); do
echo "== $ip"
whois "$ip" | grep -Ei 'orgname|org-name|country|netname|descr' | head -10
done
```
如果解析结果落在某个 CDN 的任播地址上,whois 显示的国家基本没有参考价值。这种情况要退回第一层和第三层:查供应商文档里写的区域端点(region endpoint),如果有,就切换到明确的区域端点,再验证一次。
第三层:文档与合同层
对照供应商当前公开的文档核对三件事,并都存进证据包:
- 有没有区域端点,以及你的调用有没有真的走区域端点。
- 数据留存策略:请求内容留多久、是否用于训练、如何申请关闭。
- 子处理商名单:模型服务商自己也可能调用第三方做审核、缓存、日志,这些转委托方同样在审计范围内。
这一步的结论写进台账的 residency_verified 字段,可选值就用“已核”“待核”“不符”三个,别搞太细。
步骤五:可替换性演练
这一步是整个审计里唯一能给出可量化答案的环节。目标不是找一个更强的模型,而是回答:“如果 M-001 明天不可用,切到 M-002 需要多久,哪些功能会掉。”
前置改造:把模型名从业务代码里拆出来
很多团队卡在这一步,因为模型标识硬编码在几十个地方。最常见的做法是加一层模型网关,业务代码只认一个内部别名,别名到真实供应商的映射放在网关配置里。
```yaml
gateway.yaml —— 统一模型出口,业务只认内部别名
字段名与能力以所用网关项目的官方文档当前版本为准
model_list:
- model_name: chat-default # 业务代码里只出现这个名字
litellm_params:
model: openai/<供应商A的模型标识>
api_base: https://api.<供应商A域名>/v1
api_key: os.environ/PROVIDER_A_KEY
- model_name: chat-default # 同名候选,用于分流与兜底
litellm_params:
model: openai/<供应商B的模型标识>
api_base: https://api.<供应商B域名>/v1
api_key: os.environ/PROVIDER_B_KEY
router_settings:
num_retries: 1
timeout: 30
allowed_fails: 3
cooldown_time: 60
```
如果没有网关,退一步的做法是统一环境变量名:所有服务只读 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL 三个变量,切换时改配置中心,而不是改代码。这个改造投入小,收益明确。
演练一:影子流量比对
把一批真实请求(脱敏后)同时打到 A、B 两端,记录可比指标。注意这里不追求“谁更好”,而是看可切换性:B 端能不能接住 A 端的输入形态。
```python
shadow_compare.py
用途:同一批请求同时打 A/B 两个端点,记录成功率、延迟、输出长度等可比指标
用法:A_URL=... A_KEY=... A_MODEL=... B_URL=... B_KEY=... B_MODEL=... \
python shadow_compare.py prompts.txt shadow_result.jsonl
import json, os, statistics, time, urllib.request
A = {"name": "A", "url": os.environ["A_URL"], "key": os.environ["A_KEY"], "model": os.environ["A_MODEL"]}
B = {"name": "B", "url": os.environ["B_URL"], "key": os.environ["B_KEY"], "model": os.environ["B_MODEL"]}
def call(ep, prompt, timeout=60):
body = json.dumps({
"model": ep["model"],
"messages": [{"role": "user", "content": prompt}],
"temperature": 0,
}).encode("utf-8")
req = urllib.request.Request(
ep["url"].rstrip("/") + "/chat/completions",
data=body,
headers={"Content-Type": "application/json",
"Authorization": f"Bearer {ep['key']}"},
)
t0 = time.perf_counter()
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
data = json.loads(r.read().decode("utf-8"))
text = data["choices"][0]["message"].get("content") or ""
return {"ok": True, "ms": int((time.perf_counter() - t0) * 1000), "text": text}
except Exception as e: # 超时、限流、鉴权失败都走这里
return {"ok": False, "ms": int((time.perf_counter() - t0) * 1000), "err": repr(e), "text": ""}
def main(prompts_path, out_path):
prompts = [l.strip() for l in open(prompts_path, encoding="utf-8") if l.strip()]
rows = []
for p in prompts:
ra, rb = call(A, p), call(B, p)
rows.append({
"prompt": p,
"a_ok": ra["ok"], "a_ms": ra["ms"], "a_len": len(ra["text"]), "a_text": ra["text"][:2000],
"b_ok": rb["ok"], "b_ms": rb["ms"], "b_len": len(rb["text"]), "b_text": rb["text"][:2000],
})
with open(out_path, "w", encoding="utf-8") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
both = [r for r in rows if r["a_ok"] and r["b_ok"]]
print(f"总样本 {len(rows)},双端成功 {len(both)}")
if both:
print("A 成功率 %.1f%%,中位延迟 %d ms" % (100 * sum(r['a_ok'] for r in rows) / len(rows),
statistics.median(r["a_ms"] for r in both)))
print("B 成功率 %.1f%%,中位延迟 %d ms" % (100 * sum(r['b_ok'] for r in rows) / len(rows),
statistics.median(r["b_ms"] for r in both)))
if __name__ == "__main__":
import sys
main(sys.argv[1], sys.argv[2])
```
prompts.txt 不要只放短问答。至少覆盖五类输入形态,否则演练结论会过于乐观:
1. 短问答(10 字级)
2. 长上下文(接近你线上最长的那一档)
3. 结构化输出(要求返回 JSON 的场景)
4. 工具调用 / 函数调用(如果业务在用)
5. 流式输出(如果业务在用)
演练二:真切换,掐表
影子流量只能证明“B 能返回结果”,不能证明“切过去业务不炸”。所以要安排一次真正的切换,选出一条风险最低的生产链路(比如内部知识库问答,不是对客核心流程),在低峰期执行:
```bash
切换演练的最小操作集(以环境变量注入为例)
1) 记录切换开始时间
date -u +"%Y-%m-%dT%H:%M:%SZ" | tee switch_timeline.log
2) 改配置中心的模型别名指向(或用网关的路由权重)
例如把 chat-default 的权重从 A:100% 改为 B:100%
3) 等待配置生效后,打一笔探针请求
curl -sS -X POST "$LLM_BASE_URL/chat/completions" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"'"$LLM_MODEL"'","messages":[{"role":"user","content":"ping"}],"max_tokens":16}' \
| jq -r '.choices[0].message.content' |
|---|
4) 记录切换完成时间,并观察 30 分钟内错误率与 P95 延迟
date -u +"%Y-%m-%dT%H:%M:%SZ" | tee -a switch_timeline.log
5) 回滚
把权重改回 A:100%,重复探针,确认业务恢复
```
演练报告里必须写清楚这几个数字:
| 指标 | 记录方式 |
|---|---|
| 切换操作耗时 | switch_timeline.log 首尾时间差 |
| 业务中断时长 | 从切走到探针成功的时间 |
| 错误率变化 | 切换前后各 30 分钟网关错误码统计 |
| P95 延迟变化 | 同上,按来源服务分 |
| 功能退化清单 | 哪些能力变差(结构化输出稳定性、长上下文、工具调用成功率) |
| 回滚耗时 | 从决定回滚到探针成功的时间 |
验收标准建议提前定好并写进报告,例如“切换操作 ≤ 30 分钟、业务中断 ≤ 5 分钟、核心链路错误率上升不超过 1 个百分点”。数字由团队自己按业务定,但一定要提前定,不能事后补。
常见坑与排错
| 现象 | 常见原因 | 处理办法 | |
|---|---|---|---|
| 台账条数明显偏少 | 只查了采购合同,漏掉工程团队个人账号直购 | 用云账单 + 网关日志交叉验证;把“个人账号”作为一个独立的 env 取值显式列出 | |
| 台账条数明显偏多、大部分是重复 | 按模型名字而非四元组切分 | 回到步骤一的粒度定义,合并同类项 | |
| 驻留核查结论是“境内”,但心里没底 | 只看了配置,没看实际出口和供应商文档 | 三层都要做;解析落在 CDN 任播时不要下结论,改用区域端点重新验证 | |
| 以为走了内网代理,实际没走 | 容器里有全局代理变量但被 SDK 忽略,或 SDK 读了另一个变量名 | 在容器内 `env | grep -i proxy 交叉确认;用网关日志的 host` 字段作为最终事实 |
| 条款快照打开是空白 | 页面动态渲染,curl 抓的是空壳 | 改用浏览器打印 PDF,并在取证日志里注明方式 | |
| 演练时 B 端大量超时 | B 端限流配额低于 A 端,或长上下文能力不足 | 演练前先确认 B 端的配额与上下文上限;分批压测而不是一次性打满 | |
| 切换后功能“看起来正常”但输出质量下降 | 只比了成功率,没比输出结构 | 把结构化输出解析成功率、工具调用成功率纳入演练指标 | |
| 回滚失败 | 配置只改了一处,其他地方硬编码了模型名 | 演练前先做硬编码清理;回滚脚本和切换脚本一起写、一起测 | |
| 证据包过两个月就打不开 | 只存了截图,没存原文和哈希 | 原文 + 哈希 + 取证日志三件套;重要页面同时存 PDF | |
| 供应商改了子处理商名单没人发现 | 没有定期复采机制 | 每季度重跑取证脚本,用 diff 对比历史快照 | |
| 审计做完就没人管了 | 没有责任人也没有复审日期 | 台账里 review_date 字段必填,到期进日历提醒 |
再补一个容易被忽略的坑:供应商转售。有些“小平台”其实是转售大厂能力,条款里写的数据驻留地是大厂的,但实际多经过了一跳第三方。判断方法是看接口域名和响应头里的服务标识,以及条款里是否出现“our third-party providers”这类表述。凡是出现转售链路的资产,risk_score 至少加 2 分。
下一步建议
审计做完不是终点,把下面几件事变成常态,下一次审计的成本会下降一个量级:
1. 把台账接到 CI 上。 在流水线里加一个检查:新增代码里出现新的模型域名或新的 base_url,就要求提交人在 PR 里补一条台账记录。这比事后盘点省力得多。
2. 把网关当成默认入口。 所有模型调用统一走网关,业务只认内部别名。这一步做完,“可替换”从项目管理问题变成配置问题。
3. 把演练排进日历。 建议每个季度挑一条链路做一次切换演练,一年覆盖完主要链路。演练报告归档,作为下次审计的 A 级证据。
4. 建立条款变更监控。 对清单里所有供应商的条款页和子处理商页做定期抓取加 diff,有变更就发通知。这类变更往往不打招呼。
5. 明确退出条款。 在新签或续签合同里加进去:数据删除的时限与证明方式、过渡期支持时长、模型下线前的提前通知期。这些条款在切换那一刻的价值,远高于签合同时省下的那点折扣。
6. 准备一份对外可用的说明。 一页纸,写清楚公司用了哪些模型、分别处理什么数据、驻留在哪里、备选方案是什么。客户和监管问起来,直接给这一页,比临时攒材料从容得多。
最后提醒一句:所有涉及具体供应商条款、区域端点、配额和能力的判断,都要以官方文档当前版本为准,本教程里的配置字段和命令只作结构参考。审计的价值不在于一次做得多完美,而在于结论可复核、缺口可追踪、切换可执行——这三条做到了,模型来源这件事就从“说不清”变成了“说得清”。
