From f3efd2946a04db0bc9632d2761f839b580ee82c9 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Thu, 22 Jan 2026 23:10:15 -0500 Subject: [PATCH] fix: filter empty VCR responses from stainless client --- conftest.py | 23 +++- ...t_usage_info_non_streaming_with_acall.yaml | 122 ++---------------- ...nfo_non_streaming_with_acall_and_stop.yaml | 112 +--------------- 3 files changed, 34 insertions(+), 223 deletions(-) diff --git a/conftest.py b/conftest.py index 6c6513f70..e5e49a2ed 100644 --- a/conftest.py +++ b/conftest.py @@ -159,12 +159,23 @@ def _filter_request_headers(request: Request) -> Request: # type: ignore[no-any return request -def _filter_response_headers(response: dict[str, Any]) -> dict[str, Any]: - """Filter sensitive headers from response before recording.""" +def _filter_response_headers(response: dict[str, Any]) -> dict[str, Any] | None: + """Filter sensitive headers from response before recording. + + Returns None to skip recording responses with empty bodies. This handles + duplicate recordings caused by OpenAI's stainless client using + with_raw_response which triggers httpx to re-read the consumed stream. + """ + body = response.get("body", {}).get("string", "") + headers = response.get("headers", {}) + content_length = headers.get("content-length", headers.get("Content-Length", [])) + + if body == "" or body == b"" or content_length == ["0"]: + return None for encoding_header in ["Content-Encoding", "content-encoding"]: - if encoding_header in response["headers"]: - encoding = response["headers"].pop(encoding_header) + if encoding_header in headers: + encoding = headers.pop(encoding_header) if encoding and encoding[0] == "gzip": body = response.get("body", {}).get("string", b"") if isinstance(body, bytes) and body.startswith(b"\x1f\x8b"): @@ -172,8 +183,8 @@ def _filter_response_headers(response: dict[str, Any]) -> dict[str, Any]: for header_name, replacement in HEADERS_TO_FILTER.items(): for variant in [header_name, header_name.upper(), header_name.title()]: - if variant in response["headers"]: - response["headers"][variant] = [replacement] + if variant in headers: + headers[variant] = [replacement] return response diff --git a/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall.yaml b/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall.yaml index 5ad8e8f51..7a0c51292 100644 --- a/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall.yaml +++ b/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall.yaml @@ -42,14 +42,14 @@ interactions: uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-D12YSutScUV5GxG9VVhRpEpgeMqBc\",\n \"object\": - \"chat.completion\",\n \"created\": 1769140520,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D12kRWBYMQqvQO0np2uAxgXMuDytW\",\n \"object\": + \"chat.completion\",\n \"created\": 1769141263,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Why don't skeletons fight each other? - \\n\\nThey don't have the guts!\",\n \"refusal\": null,\n \"annotations\": - []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 12,\n \"completion_tokens\": - 15,\n \"total_tokens\": 27,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + \"assistant\",\n \"content\": \"Why did the scarecrow win an award? + \\n\\nBecause he was outstanding in his field!\",\n \"refusal\": null,\n + \ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 12,\n \"completion_tokens\": + 18,\n \"total_tokens\": 30,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": @@ -64,7 +64,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 23 Jan 2026 03:55:20 GMT + - Fri, 23 Jan 2026 04:07:43 GMT Server: - cloudflare Set-Cookie: @@ -80,117 +80,17 @@ interactions: cf-cache-status: - DYNAMIC content-length: - - '874' + - '887' openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '457' + - '497' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' x-envoy-upstream-service-time: - - '472' - x-openai-proxy-wasm: - - v0.1 - x-ratelimit-limit-requests: - - X-RATELIMIT-LIMIT-REQUESTS-XXX - x-ratelimit-limit-tokens: - - X-RATELIMIT-LIMIT-TOKENS-XXX - x-ratelimit-remaining-requests: - - X-RATELIMIT-REMAINING-REQUESTS-XXX - x-ratelimit-remaining-tokens: - - X-RATELIMIT-REMAINING-TOKENS-XXX - x-ratelimit-reset-requests: - - X-RATELIMIT-RESET-REQUESTS-XXX - x-ratelimit-reset-tokens: - - X-RATELIMIT-RESET-TOKENS-XXX - x-request-id: - - X-REQUEST-ID-XXX - status: - code: 200 - message: OK -- request: - body: '{"messages":[{"role":"user","content":"Tell me a joke."}],"model":"gpt-4o-mini"}' - headers: - User-Agent: - - X-USER-AGENT-XXX - accept: - - application/json - accept-encoding: - - ACCEPT-ENCODING-XXX - authorization: - - AUTHORIZATION-XXX - connection: - - keep-alive - content-length: - - '80' - content-type: - - application/json - host: - - api.openai.com - x-stainless-arch: - - X-STAINLESS-ARCH-XXX - x-stainless-async: - - async:asyncio - x-stainless-lang: - - python - x-stainless-os: - - X-STAINLESS-OS-XXX - x-stainless-package-version: - - 1.83.0 - x-stainless-raw-response: - - 'true' - x-stainless-read-timeout: - - X-STAINLESS-READ-TIMEOUT-XXX - x-stainless-retry-count: - - '0' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.12.10 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - body: - string: '' - headers: - Access-Control-Expose-Headers: - - ACCESS-CONTROL-XXX - CF-RAY: - - CF-RAY-XXX - Connection: - - keep-alive - Content-Type: - - application/json - Date: - - Fri, 23 Jan 2026 03:55:20 GMT - Server: - - cloudflare - Set-Cookie: - - SET-COOKIE-XXX - Strict-Transport-Security: - - STS-XXX - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - X-CONTENT-TYPE-XXX - alt-svc: - - h3=":443"; ma=86400 - cf-cache-status: - - DYNAMIC - content-length: - - '0' - openai-organization: - - OPENAI-ORG-XXX - openai-processing-ms: - - '457' - openai-project: - - OPENAI-PROJECT-XXX - openai-version: - - '2020-10-01' - x-envoy-upstream-service-time: - - '472' + - '517' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall_and_stop.yaml b/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall_and_stop.yaml index 32ee39bbd..e65d8c8b1 100644 --- a/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall_and_stop.yaml +++ b/lib/crewai/tests/cassettes/test_usage_info_non_streaming_with_acall_and_stop.yaml @@ -42,8 +42,8 @@ interactions: uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-D12bQ6vMcLYBu6WGhMwQKenus7ejH\",\n \"object\": - \"chat.completion\",\n \"created\": 1769140704,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D12jx3VKcKxsLBNtLVF4QhLbN7Ke4\",\n \"object\": + \"chat.completion\",\n \"created\": 1769141233,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Why did the scarecrow win an award?\\n\\nBecause he was outstanding in his field!\",\n \"refusal\": null,\n \"annotations\": @@ -53,7 +53,7 @@ interactions: 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": - \"default\",\n \"system_fingerprint\": \"fp_29330a9688\"\n}\n" + \"default\",\n \"system_fingerprint\": \"fp_8bbc38b4db\"\n}\n" headers: Access-Control-Expose-Headers: - ACCESS-CONTROL-XXX @@ -64,7 +64,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 23 Jan 2026 03:58:25 GMT + - Fri, 23 Jan 2026 04:07:14 GMT Server: - cloudflare Set-Cookie: @@ -84,113 +84,13 @@ interactions: openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '519' + - '618' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' x-envoy-upstream-service-time: - - '652' - x-openai-proxy-wasm: - - v0.1 - x-ratelimit-limit-requests: - - X-RATELIMIT-LIMIT-REQUESTS-XXX - x-ratelimit-limit-tokens: - - X-RATELIMIT-LIMIT-TOKENS-XXX - x-ratelimit-remaining-requests: - - X-RATELIMIT-REMAINING-REQUESTS-XXX - x-ratelimit-remaining-tokens: - - X-RATELIMIT-REMAINING-TOKENS-XXX - x-ratelimit-reset-requests: - - X-RATELIMIT-RESET-REQUESTS-XXX - x-ratelimit-reset-tokens: - - X-RATELIMIT-RESET-TOKENS-XXX - x-request-id: - - X-REQUEST-ID-XXX - status: - code: 200 - message: OK -- request: - body: '{"messages":[{"role":"user","content":"Tell me a joke."}],"model":"gpt-4o-mini","stop":["END"]}' - headers: - User-Agent: - - X-USER-AGENT-XXX - accept: - - application/json - accept-encoding: - - ACCEPT-ENCODING-XXX - authorization: - - AUTHORIZATION-XXX - connection: - - keep-alive - content-length: - - '95' - content-type: - - application/json - host: - - api.openai.com - x-stainless-arch: - - X-STAINLESS-ARCH-XXX - x-stainless-async: - - async:asyncio - x-stainless-lang: - - python - x-stainless-os: - - X-STAINLESS-OS-XXX - x-stainless-package-version: - - 1.83.0 - x-stainless-raw-response: - - 'true' - x-stainless-read-timeout: - - X-STAINLESS-READ-TIMEOUT-XXX - x-stainless-retry-count: - - '0' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.12.10 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - body: - string: '' - headers: - Access-Control-Expose-Headers: - - ACCESS-CONTROL-XXX - CF-RAY: - - CF-RAY-XXX - Connection: - - keep-alive - Content-Type: - - application/json - Date: - - Fri, 23 Jan 2026 03:58:25 GMT - Server: - - cloudflare - Set-Cookie: - - SET-COOKIE-XXX - Strict-Transport-Security: - - STS-XXX - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - X-CONTENT-TYPE-XXX - alt-svc: - - h3=":443"; ma=86400 - cf-cache-status: - - DYNAMIC - content-length: - - '0' - openai-organization: - - OPENAI-ORG-XXX - openai-processing-ms: - - '519' - openai-project: - - OPENAI-PROJECT-XXX - openai-version: - - '2020-10-01' - x-envoy-upstream-service-time: - - '652' + - '639' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: