资讯动态

大模型 API 单元测试怎么 mock 不误伤

发布时间:2026/10/15 19:20:02 来源:尧图企业网站定制
跑大模型 API 的单元测试时测试还没发出请求就在读取密钥环境变量的那一行抛出了KeyError如果把密钥临时写进代码用例又会真的访问 HTTPS慢而且不稳定。根因通常不是测试框架而是被测代码把客户端创建与网络调用绑得太紧。读完这篇你能拿到一套可复现的 mock 方案使用真实接入参数创建客户端同时把网络调用锁在测试桩里不让用例产生真实请求。1 场景与前置条件这篇教程解决的不是“怎么调用大模型接口”而是“调用大模型 API 的代码怎么做单元测试”。不少项目已经把 SDK 封装成complete(prompt)一类函数测试时却绕不开真实网络。你手上应该已经有 Python 3.10、OpenAI SDK 1.x、pytest、一个sk_live_开头的密钥以及从模型广场复制来的调用名例如glm-4.6。环境限制要说清。本文示例只覆盖 OpenAI 兼容的/chat/completionsBase URL 使用该平台提供的固定地址。如果你走 Anthropic 兼容端点/messages请求体必须传max_tokens测试桩目标也要从chat.completions.create改成messages.create。思路一致但字段不同别直接套用。2 环境准备先装依赖pipinstallopenai pytest python-dotenvpython-dotenv可选不是运行硬条件。密钥不要写进代码放到.env并让.gitignore包含这一项。测试前导出环境变量exportSILVAMUX_API_KEYsk_live_xxxx为什么不用硬编码密钥是创建时只显示一次的凭证提交进仓库等于把凭证交给所有看得到代码的人。CI 环境可以注入同名变量测试代码不需要变化。Base URL 也不要从配置文件里随手编OpenAI SDK 会在这个地址后面自动拼接/chat/completions所以必须写完整的/api/v1这段。3 最小可跑示例先创建llm_client.pyfromopenaiimportOpenAIimportos clientOpenAI(api_keyos.environ[SILVAMUX_API_KEY],base_urlhttps://www.silvamux.com/api/v1,)defcomplete(prompt:str,model:strglm-4.6)-str:respclient.chat.completions.create(modelmodel,messages[{role:user,content:prompt}],)returnresp.choices[0].message.content这段代码的关键是api_key和base_url。它们决定客户端能不能走到兼容端点。model必须填模型广场详情页里的稳定调用名不要根据展示名自行拼成glm-4.6-chat那样会返回gateway_error。messages是列表哪怕只传一句 prompt也要写成[{role: user, content: prompt}]直接传字符串会触发参数校验错误。接着写测试test_llm_client.pyfromunittest.mockimportpatch,MagicMockimportllm_clientdeftest_complete_mocks_http():fake_respMagicMock()fake_resp.choices[0].message.contentmock replywithpatch.object(llm_client.client.chat.completions,create,return_valuefake_resp):resultllm_client.complete(hello)assertresultmock reply这个测试不会有任何网络请求。patch.object替换的是已经创建好的client.chat.completions.create不是openai.OpenAI类。如果你 patch 类方法模块导入时已经存在的实例不会被影响这是单测 mock 常见的失败点。要盯住 patch 目标。4 逐步扩展第一处扩展把client作为参数注入别再让函数依赖模块级全局对象。defcomplete(client,prompt:str,model:strglm-4.6)-str:respclient.chat.completions.create(modelmodel,messages[{role:user,content:prompt}],)returnresp.choices[0].message.content改完后测试直接传一个 stub不需要关心 client 在哪创建。修改前测试必须知道llm_client.client这个内部属性修改后函数签名更清楚也更容易替换成异步客户端或假对象。这个改动没有改变调用协议。第二处扩展处理流式返回里的空choiceschunk。很多大模型 API 的流式接口在结束前会返回一个只有usage字段、没有choices的 chunk代码如果直接访问chunk.choices[0]最后会抛IndexError。写一个能跳过该 chunk 的版本defstream_complete(client,prompt:str,model:strglm-4.6):withclient.chat.completions.create(modelmodel,messages[{role:user,content:prompt}],streamTrue,)asstream:forchunkinstream:ifchunk.choicesandchunk.choices[0].delta.content:yieldchunk.choices[0].delta.content测试时用两个假 chunk 模拟第一个正常带delta.content第二个choices[]表示用量通知。断言结果只包含第一段内容。if chunk.choices这一行不能省省略后错误只在长响应里出现平时短响应可能测不出来。5 排错KeyError: ‘SILVAMUX_API_KEY’直接原因导入模块时os.environ[...]没读到环境变量。修法先按环境准备里的方式导出或在测试启动前用 monkeypatch 给os.environ注入同名键。不要临时把密钥写进源码那样会让 mock 失去意义。openai.APIConnectionError: Connection error.直接原因Base URL 填得不完整SDK 连接不到目标路径。修法回到llm_client.py检查 Base URL补齐www和/api/v1两段。不要只查 API key这个报错先于认证发生。{“error”:{“message”:“model not found”,“type”:“gateway_error”,“code”:“MODEL_NOT_FOUND”}}直接原因model参数不在模型调用名列表里常见于把展示名自动拼成带版本后缀的字符串。修法打开模型广场详情页复制稳定 alias不要自己造。mock 后仍发出真实请求直接原因patch 目标写成了openai.OpenAI但模块里已经创建好客户端实例。修法patchllm_client.client.chat.completions.create或者采用上面扩展一里的依赖注入让测试不依赖全局对象。常见问题Q: 能不能完全不设置那个密钥环境变量跑单元测试A: 可以。把读取环境变量的代码改成os.getenv并提供默认值测试前把create方法 mock 掉即可。客户端初始化阶段通常不会校验密钥只有真正发出请求时才可能返回 401所以纯本地单测不依赖真实凭证。Q: 为什么流式测试里会多一个空的 choices chunkA: 这是 OpenAI 兼容端点在流末尾返回 usage 的行为choices可能是空列表。代码必须用if chunk.choices跳过不能只依赖data: [DONE]结束符判断因为 SDK 已经把协议解析成 chunk 对象循环里会更早遇到这个空块。Q: 想 mock Anthropic 兼容端点的请求应该改哪里A: 把调用从client.chat.completions.create改成client.messages.create请求体里必须传max_tokens字段形状也不同。测试桩的目标也跟着换断言参数时注意content是列表而不是字符串不能照搬/chat/completions的断言。以上示例在千木SilvaMux的 OpenAI 兼容端点上验证模型调用名为 glm-4.6。流式判断参考该端点的 usage chunk 行为。具体模型调用名和错误处理参数以模型广场与错误页为准。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑