拓冰建站拓冰建站
首页 / 资讯中心 / 正文

LiteLLM llm_translation 测试体系:逐提供商单测目录与 Redis 支撑的 VCR 磁带缓存机制

LiteLLM llm_translation 测试体系逐提供商单测目录与 Redis 支撑的 VCR 磁带缓存机制【免费下载链接】litellmThe fastest, litest AI Gateway. Rust core with Python SDK. Call 100 LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logging [Bedrock, Azure, OpenAI, Anthropic, OpenAI, VertexAI, vLLM, Nvidia NIM]项目地址: https://gitcode.com/GitHub_Trending/li/litellmLiteLLM 的 tests/llm_translation 目录是覆盖各 LLM 提供商翻译层的单元测试主战场本文以该目录下的 Readme.md 为主体结合配套源码讲清两件事测试文件的组织约定以及让付费的实时 LLM 调用在 CI 中几乎零成本重复运行的 Redis-backed VCR 磁带缓存机制。读完后你能理解该目录的测试命名规范、磁带的记录/回放/TTL 策略、请求指纹与归一化匹配的实现细节以及如何用仓库自带的 Makefile target 与开关环境变量控制缓存行为。测试目录定位与文件命名约定tests/llm_translation的定位是针对单个 LLM 提供商的单元测试。目录的 Readme 给出了唯一一条命名规则测试文件名即提供商名称——例如test_openai.py对应 OpenAI。从目录实际内容看这一约定被严格遵循test_openai.py、test_anthropic_completion.py、test_bedrock_completion.py、test_gemini.py、test_cohere.py、test_xai.py等约百个测试文件与提供商一一对应。目录内还放着一组可复用的基类用于消除各提供商测试间的重复断言逻辑base_llm_unit_tests.pybase_embedding_unit_tests.pybase_audio_transcription_unit_tests.pybase_rerank_unit_tests.py批量运行由 Makefile 中的test-llm-translationtarget 驱动内部调用.github/scripts/run_llm_translation_tests.py调试单个文件则使用make test-llm-translation-single FILEtest_openai.py该 target 会以--maxfail100 --timeout300运行并输出test-results/junit.xml。Redis-backed VCR 缓存整体工作原理Readme 的核心主题是该目录下每个测试都会被自动打上pytest.mark.vcr标记通过 conftest.py 完成从而接入 vcrpy 的 HTTP 录制/回放。整个缓存的生命周期如下首次运行cache-miss请求真正打到提供商的线上 API完整 HTTP 交互请求 响应被录制进 Rediskey 形如litellm:vcr:cassette:test_id24 小时内的后续运行直接从 Redis 回放不产生任何网络调用也不消耗 API 额度24 小时 TTL 到期每天的第一次运行会重新录制——这一设计是刻意的目的是让上游 API 的请求/响应契约漂移drift在一天之内就能暴露出来而不是永远回放一份冻结的旧响应。源码中可以逐一验证这些常量。tests/_vcr_redis_persister.py 定义CASSETTE_TTL_SECONDS 24 * 60 * 60 # 24h TTL REDIS_KEY_PREFIX litellm:vcr:cassette: # Redis key 前缀 CASSETTE_REDIS_URL_ENV CASSETTE_REDIS_URL # 独立 Redis 实例地址 MAX_EPISODES_PER_CASSETTE 50 # 单条磁带最多 50 个交互几个值得注意的实现细节TTL 只在写入时设置读取不刷新。load_cassette中的注释明确说明如果读操作滑动续期一个频繁被用的磁带将永生不死每日重录以捕获 API 漂移这条检查就永远不会执行只有通过的测试才落盘。save_cassette会检查该测试的结果由mark_test_outcome_for_cassette在测试 teardown 时写入失败的测试不保存磁带保留上一条已验证的磁带不动持久化是严格 best-effort。Redis 连接抖动、超时、OOM 或只读副本都只会降级为测试通过但磁带未缓存并通过VCRCassetteCacheWarning警告在 pytest 会话结束时的警告汇总里显形而不是直接让测试失败非 2xx 响应不录制。filter_non_2xx_response在录制管道中把状态码过滤到200 code 300错误响应不进磁带——这也解释了为什么重录时仍需真实凭证错误路径天然要走上真实链路磁带过大拒绝保存。当一条磁带的交互数episodes超过MAX_EPISODES_PER_CASSETTE50时persister 直接拒绝写入并打警告提示该测试很可能产生了非确定性的请求体如 UUID在追加而不是回放。这类测试会被会话末的汇总报告单独列出见下文 OVERFLOW 分类因为拒绝保存意味着它在每次 CI 运行中都会打真实 API。请求/响应归一化与匹配策略回放命中是整个机制的价值所在而 LLM 调用的请求体天然充满每次运行都变化的内容。tests/_vcr_conftest_common.py 中的vcr_config_dict()定义了 vcrpy 的匹配维度match_on: ( method, scheme, host, port, TOLERANT_PATH_MATCHER_NAME, # tolerant_path TOLERANT_QUERY_MATCHER_NAME, # tolerant_query KEY_FINGERPRINT_MATCHER_NAME, # key_fingerprint SAFE_BODY_MATCHER_NAME, # safe_body ), record_mode: new_episodes, allow_playback_repeats: True,在这套匹配之上源码实现了多层归一化按问题类型逐一拆解1. API Key 指纹隔离key_fingerprint。不同租户可能共享同一套测试代码但凭证不同若只按 URL 匹配A 租户的磁带会被 B 租户命中。为此_before_record_request钩子在录制前计算所有 API key 类请求头authorization、x-api-key、anthropic-api-key、openai-api-key、azure-api-key、api-key、x-goog-api-key的 SHA-256 前 16 位写入内部头x-litellm-key-fp然后把明文凭证头从磁带中剥掉——回放时按指纹比对磁带上不留秘密。对两种每次请求都会旋转的凭证形态做了特殊稳定化处理AWS SigV4 的Authorization只取CredentialAKIA...中的 access key 部分参与哈希日期、区域、签名每次都变Google OAuth2 的ya29.*访问令牌整体折叠为固定标记google-oauth2项目名已在 URL path 中参与匹配隔离粒度不受影响。2. 易变令牌归一化safe_body。很多测试会给请求体追加 cache-bustertime.time()、uuid.uuid4()LiteLLM 自身的可观测性负载也带每次调用都新的 UUID 与 ISO-8601 时间戳。_normalize_volatile_tokens在比对时而非落盘时把这些子串替换为固定占位符UUID、ISO-8601 时间戳、13 位毫秒 epoch、10 位浮点/整数 epoch 各有专门正则且锚定到 2001–2033 年 epoch 窗口以避免误伤普通标识符。归一化对称地应用于两侧请求因此只影响选中哪条已录制交互永远不会掩盖响应层面的差异磁带本体仍保留真实字节以便调试。safe_body匹配器本身也刻意不用 vcrpy 默认 body 匹配器——后者会对application/json无条件json.loads而 JSON Lines 请求体如 Bedrock batch 的 S3 PUT会让它在返回不匹配之前直接抛异常。3. multipart 边界钉死。httpx 每次用os.urandom生成新的 multipart boundary会导致音频转写等测试的磁带永久 miss。_pin_multipart_boundary会话级 fixture 把MultipartStream.__init__的缺省 boundary 替换为固定字符串vcr-static-boundary录制侧与回放侧看到的字节从此一致。4. 凭据交换与遥测的旁路处理。Google OAuth2/STS 令牌端点oauth2.googleapis.com、sts.googleapis.com等返回的短命 access token 绝不能被录制——过期令牌回放会触发ACCESS_TOKEN_EXPIRED这些请求被强制走实时链路before_record_request返回None即既不录也不放。另一方面其他测试模块在 import 时全局开启litellm.success_callback [langfuse]之类的全局可观测性回调时其后台线程的异步 flush 可能落入别的测试的 VCR 窗口被存成幽灵交互。_should_drop_telemetry_record按当前测试 nodeid 是否为遥测测试判断非遥测测试的遥测 POST 一律放行到实时fire-and-forget而不入磁带遥测域名langfuse.com、arize.com、traceloop.com、braintrust.dev等的请求还跳过 query 与 body 比对因为回读查询天然携带每次新生成的trace_id。5. 响应头与图片负载清洗。FILTERED_RESPONSE_HEADERS剥掉set-cookie、request-id、cf-ray、anthropic-organization-id等易变响应头_strip_image_b64_payloads把图像生成响应里 1–10 MB 的b64_json换成 4 字节占位符dGVzdA解码为btest保持合法 base64使图像测试的磁带体积缩小约 99%且所有形状校验、字段可解码的断言依然成立。自动打标记与 respx 冲突排除Readme 提到已经使用respx的文件会从自动标记中排除因为 respx 与 vcrpy 修补的是同一个 httpx transport两者同时生效会让其中一方静默失效。自动标记的入口是 conftest 中的pytest_collection_modifyitems钩子调用共享模块的apply_vcr_auto_marker_to_items其跳过逻辑按优先级排列为VCR 全局关闭LITELLM_VCR_DISABLE1或未设置CASSETTE_REDIS_URL测试已显式携带pytest.mark.vcr——保持原样该测试项自身触发 respxrespx标记或respx_mockfixture所在模块任意位置接线了 respx——源码用 AST 遍历_RespxUsageVisitor而非子串扫描来判定注释里提到这专门用于识别dead respx import模块导入了 respx 但从未使用conftest 提供的skip_files/skip_nodeid_suffixes白名单——用于观察跨调用提供商状态如 prompt-cache 预热等确定性回放无法建模的测试。tests/llm_translation/conftest.py 中当前的排除项包括test_vcr_redis_persister.py、test_ws_vcr.py它们本身在测 persister不能跑在磁带上下文里以及两个 embedding 用例。每个被跳过的测试项都会被打上vcr_skip_reason属性respx_conflict/respx_conflict_module/incompatible/disabled等供会话末汇总按原因分桶——这样respx 冲突与设计上不兼容在报告里是两类可区分的信号裸file_opt_out且模块内零 respx 用量的条目就是可以剪掉的失效白名单行。此外conftest 在 import 期就os.environ.setdefault(LITELLM_LOCAL_MODEL_COST_MAP, True)强制 litellm 使用本地内置的模型成本表而不是在import litellm时从raw.githubusercontent.com拉取——否则该拉取会在磁带激活期间被录成一条多余的交互源码注释说明约 710/1900 条历史磁带曾被此污染。缓存的覆盖范围与边界同一套 VCR 缓存管道被复用于所有对线上提供商 API 发起调用的测试目录。可复用胶水集中在 tests/_vcr_conftest_common.py被接入的目录包括tests/llm_translation/tests/llm_responses_api_testing/tests/audio_tests/tests/batches_tests/tests/guardrails_tests/tests/image_gen_tests/tests/litellm_utils_tests/tests/local_testing/覆盖local_testing_part1、local_testing_part2、litellm_router_testing、litellm_assistants_api_testing、langfuse_logging_unit_teststests/logging_callback_tests/tests/pass_through_unit_tests/tests/router_unit_tests/tests/unified_google_tests/Readme 特别解释了哪些目录被有意排除在 Docker 中运行 LiteLLM proxy 的测试如build_and_test、proxy_logging_guardrails_model_info_tests、proxy_store_model_in_db_tests不在缓存范围内。原因是 VCR.py 修补的是进程内的 httpx transport而容器内部发起的 LLM 调用发生在另一个进程/网络命名空间里根本无法被拦截。运行所需环境与常用操作命令必需环境变量CASSETTE_REDIS_URL。Readme 强调它必须是独立于应用 RedisREDIS_URL/REDIS_HOST的实例否则 proxy 测试会顺带把测试磁带冲掉——这一点在 persister 的连接构造函数里也有对应的错误提示。提供商凭证ANTHROPIC_API_KEY、OPENAI_API_KEY、AWS_*等只在 cache-miss每日首次重录时需要回放运行不消耗凭证。立即强制重录不想等 24h TTL 到期make test-llm-translation-flush-vcr-cache该 Makefile target 实际执行uv run python tests/_flush_vcr_cache.py。脚本本身tests/_flush_vcr_cache.py用SCAN而非阻塞的KEYS分批遍历litellm:vcr:cassette:*前缀下的所有 key 并管道化删除每批 500 个适合在生产级 key 数量下安全使用。完全关闭 VCR所有调用走实时、且不做录制LITELLM_VCR_DISABLE1 uv run pytest tests/llm_translation/test_openai.py从源码看vcr_disabled()的判断是LITELLM_VCR_DISABLE1或未设置CASSETTE_REDIS_URL——即本地裸跑未配置 Redis 时 VCR 自动旁路但此时未标记测试的真实 API 调用仍会被 socket 探针捕获并计入成本泄漏报告见下节。会话末报告判定分类与成本泄漏检测这套机制不只是省调用费还内置了成本可观测性。每个测试结束后autouse 钩子_vcr_outcome_gate会依据磁带状态打出一个判定verdictxdist 并行模式下通过 pytest 的user_properties通道回传到 controller 进程做聚合。判定体系包括判定含义VCR HIT有回放、无新录制——磁带命中零真实成本VCR MISS:RECORDED全部实时录制首次运行或磁带过期VCR MISS:OVERFLOW磁带交互数超过 50 被拒绝保存每次 CI 都会打真实 APIVCR MISS:NOT_PERSISTED录制了但测试失败磁带未落盘VCR PARTIAL部分回放 部分新录制VCR NOOP无任何 HTTP 流量VCR UNMARKED:LIVE_CALL未打 VCR 标记的测试却连接了真实 LLM 主机——确凿的漏费VCR UNMARKED:NO_TRAFFIC未标记但也没有真实流量其中UNMARKED:LIVE_CALL的实现相当底层对未打标记的测试_LiveCallProbe会临时 monkeypatchsocket.create_connection与socket.socket.connect记录任何指向已知 LLM 主机.openai.com、.anthropic.com、.vertexai.googleapis.com、Bedrock/S3 的amazonaws.com等且显式排除 localhost 与内网前缀的 TCP 连接。探针选择在 socket 层而非 HTTP 层是为了不与 vcrpy/respx 的 transport 补丁打架它只记录不阻断目标是可观测而非硬门禁。会话结束时emit_vcr_classification_summary在终端汇总中输出三部分各判定计数、VCR COST LEAK CHECKPARTIAL/MISS:OVERFLOW/NOT_PERSISTED/UNMARKED:LIVE_CALL任一非零即 FAIL并列出具体 nodeid 与建议——稳定请求体或移出跳过名单、SKIP-REASON 分桶。另有容量哨兵当 cassette Redis 的used_memory达到maxmemory的 85%CASSETTE_CACHE_HIGH_WATER_FRACTION时打印 NEAR CAPACITY 横幅建议运行_flush_vcr_cache.py或等待更多 key 自然过期若出现 load/save 失败计数则打印 DEGRADED 横幅并附带最后一条错误。小结tests/llm_translation的 Readme 篇幅不长但它描述的是一套完整的工程决策以提供商命名的测试文件保证了哪个上游坏了一目了然Redis 磁带 24h 固定 TTL 在零成本回放与契约漂移当天暴露之间取得平衡键指纹、SigV4/OAuth2 稳定化、multipart 边界钉死、易变令牌归一化、2xx 过滤、遥测泄漏抑制共同解决了 LLM 请求天然不确定带来的回放失配问题而判定分类与 socket 级 live-call 探针则把这次 CI 花了多少钱变成了可审计的会话报告。对于维护多提供商集成测试的项目这套模式独立 Redis 实例 自定义 persister 匹配器扩展 成本泄漏汇总本身就是一个可直接参考的实现范本全部源码集中在 tests/_vcr_conftest_common.py、tests/_vcr_redis_persister.py 与 tests/llm_translation/conftest.py 三个文件中。【免费下载链接】litellmThe fastest, litest AI Gateway. Rust core with Python SDK. Call 100 LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logging [Bedrock, Azure, OpenAI, Anthropic, OpenAI, VertexAI, vLLM, Nvidia NIM]项目地址: https://gitcode.com/GitHub_Trending/li/litellm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门