本文从零建立本地响应合同检查,完整代码可复制,运行环境为 Python 3,无需安装 SDK。
在 AI 应用中,供应商返回结构变化、字段缺失或者空内容,都可能让下游代码出错。保留一组回归样本,可以把已知边界变成每次都能重复运行的检查。本文只检查一个人为定义的本地响应合同,不代表任何厂商的真实接口,也不检查答案是否真实、准确或安全。所有数据人工构造,不连接网络、不调用模型、不代表生产日志。
先定合同,再收样本
假设我们的应用接收一个对象,其中 text 必须是非空字符串,最多 100 个 Python 字符;其他字段不检查。检测函数返回 ok、not_object、invalid_type、empty 或 too_long。这是一条演示规则:实际系统应根据协议和业务明确字符、字节、长度单位和未知字段策略。它不能挡住所有不安全内容。
样本分组至少包括正常成功、长度边界、字段缺失、空白、错误类型和非对象;每项记录 ID、输入和预期结果。期望必须来自人工确认的合同,不能把当前程序输出直接批量抄成“正确答案”,否则错误会被固定下来。增加线上问题样本前要走已有隐私和审查流程;即使删除字段也不能保证匿名,优先编写不包含真实用户信息的合成数据。
完整可运行示例
下面只依赖 Python 3 标准库,保存为 fixture_checks.py,执行 python fixture_checks.py;测试结果应该出现 Ran 2 tests 和 OK,其中第一个测试使用 subTest 覆盖六个不同样本。
"""本地合成响应的最小合同检查;不连接网络,不验证回答事实。"""
import json
import unittest
def check_response(payload):
"""应用自定义合同:对象中 text 为非空字符串且最多 100 字符。"""
if not isinstance(payload, dict):
return 'not_object'
text = payload.get('text')
if not isinstance(text, str):
return 'invalid_type'
if not text.strip():
return 'empty'
if len(text) > 100:
return 'too_long'
return 'ok'
# 全为人工合成数据;没有用户输入、密钥、请求头或真实模型输出。
FIXTURES = json.loads('''[
{"id":"success", "payload":{"text":"你好"}, "expected":"ok"},
{"id":"boundary", "payload":{"text":"界"}, "expected":"ok"},
{"id":"missing", "payload":{}, "expected":"invalid_type"},
{"id":"empty", "payload":{"text":" "}, "expected":"empty"},
{"id":"wrong_type", "payload":{"text":3}, "expected":"invalid_type"},
{"id":"not_object", "payload":[], "expected":"not_object"}
]''')
class FixtureRegression(unittest.TestCase):
def test_samples(self):
for fixture in FIXTURES:
with self.subTest(case=fixture['id']):
self.assertEqual(check_response(fixture['payload']), fixture['expected'])
def test_length_boundary(self):
self.assertEqual(check_response({'text': '界' * 100}), 'ok')
self.assertEqual(check_response({'text': '界' * 101}), 'too_long')
if __name__ == '__main__':
unittest.main(verbosity=2)
这两条测试方法不是两次模型请求,也不表示覆盖了所有输入:一个方法逐个检查六个样本,另一个检查 100 和 101 字符的上下界。本次只在本地运行,测试命令和结果见回执,不宣称对网络或生产系统做了验证。
怎么把它变成团队流程
- 为合同编写一个版本说明:字段、类型、边界和错误码分别是什么意思。合同改变时明确记录是否兼容,而不是静默更改所有预期。
- 每个样本维护稳定 ID、构造原因、预期以及人工审核人;不要放原始凭据、请求头、用户文本、日志或可追溯身份信息。
- 在本地或持续集成运行
python fixture_checks.py。失败时先看子测试 ID 和预期/实际结果,再判断是产品合同变更还是解析器回归;不要为变绿直接修改预期。 - 需要模拟厂商响应时,另写适配层,并使用官方文档确定格式;这份示例不含厂商适配。需要检查事实、偏见或工具副作用时,应补充人工评估、受控测试与独立安全机制。
常见问题
JSON 解析失败? 检查引号和逗号是否为合法 JSON;不要在不可信环境无界读取大文件,Python 文档也提醒应限制不可信 JSON 的大小。
为什么有的失败码相同? 此示例将字段缺失与非字符串合为 invalid_type。业务若需要区分,可先修改合同,增加样本,再实现对应分支。
中文长度为何是 1? len 对此处普通汉字返回一个 Python 字符计数,但视觉字符、UTF-8 字节和模型 token 并不等价;本示例只按 Python 字符计数。
测试全部通过能否发布? 不能单凭它判断:这只是本地合同检查,还需真实集成、权限、数据保护等适用于系统的验证。
练习与下一篇
把 text 的上限改为 50 前,先补充 50 和 51 两个边界的人工预期;运行测试、观察原合同失败,再明确记录合同变更。下一篇讨论并发请求中谁负责关闭上下文和客户端资源。
参考:Python unittest 官方文档;Python json 官方文档;NIST AI 风险管理框架。
本文由 AI 辅助整理,代码与示例经本地运行核对。
442



被折叠的 条评论
为什么被折叠?



