From 19c5b9a35e1e7bc69fc1884db3b3e1ce467f4351 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Fri, 7 Nov 2025 13:54:11 -0500 Subject: [PATCH] fix: properly handle agent max iterations fixes #3847 --- .../src/crewai/agents/crew_agent_executor.py | 11 +- .../src/crewai/utilities/agent_utils.py | 28 +- lib/crewai/tests/agents/test_agent.py | 42 +- .../test_agent_max_iterations_stops_loop.yaml | 495 ++++++++++++++++++ 4 files changed, 560 insertions(+), 16 deletions(-) create mode 100644 lib/crewai/tests/cassettes/test_agent_max_iterations_stops_loop.yaml diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index 8c1eb2c0e..5b806658c 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -214,6 +214,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): llm=self.llm, callbacks=self.callbacks, ) + break enforce_rpm_limit(self.request_within_rpm_limit) @@ -226,7 +227,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_agent=self.agent, response_model=self.response_model, ) - formatted_answer = process_llm_response(answer, self.use_stop_words) + formatted_answer = process_llm_response(answer, self.use_stop_words) # type: ignore[assignment] if isinstance(formatted_answer, AgentAction): # Extract agent fingerprint if available @@ -258,11 +259,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): formatted_answer, tool_result ) - self._invoke_step_callback(formatted_answer) - self._append_message(formatted_answer.text) + self._invoke_step_callback(formatted_answer) # type: ignore[arg-type] + self._append_message(formatted_answer.text) # type: ignore[union-attr,attr-defined] - except OutputParserError as e: # noqa: PERF203 - formatted_answer = handle_output_parser_exception( + except OutputParserError as e: + formatted_answer = handle_output_parser_exception( # type: ignore[assignment] e=e, messages=self.messages, iterations=self.iterations, diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index 2c8122b99..a6403c315 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -127,7 +127,7 @@ def handle_max_iterations_exceeded( messages: list[LLMMessage], llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], -) -> AgentAction | AgentFinish: +) -> AgentFinish: """Handles the case when the maximum number of iterations is exceeded. Performs one more LLM call to get the final answer. Args: @@ -139,7 +139,7 @@ def handle_max_iterations_exceeded( callbacks: List of callbacks for the LLM call. Returns: - The final formatted answer after exceeding max iterations. + AgentFinish with the final answer after exceeding max iterations. """ printer.print( content="Maximum iterations reached. Requesting final answer.", @@ -157,7 +157,7 @@ def handle_max_iterations_exceeded( # Perform one more LLM call to get the final answer answer = llm.call( - messages, # type: ignore[arg-type] + messages, callbacks=callbacks, ) @@ -168,8 +168,16 @@ def handle_max_iterations_exceeded( ) raise ValueError("Invalid response from LLM call - None or empty.") - # Return the formatted answer, regardless of its type - return format_answer(answer=answer) + formatted = format_answer(answer=answer) + + # If format_answer returned an AgentAction, convert it to AgentFinish + if isinstance(formatted, AgentFinish): + return formatted + return AgentFinish( + thought=formatted.thought, + output=formatted.text, + text=formatted.text, + ) def format_message_for_llm( @@ -249,10 +257,10 @@ def get_llm_response( """ try: answer = llm.call( - messages, # type: ignore[arg-type] + messages, callbacks=callbacks, from_task=from_task, - from_agent=from_agent, + from_agent=from_agent, # type: ignore[arg-type] response_model=response_model, ) except Exception as e: @@ -294,8 +302,8 @@ def handle_agent_action_core( formatted_answer: AgentAction, tool_result: ToolResult, messages: list[LLMMessage] | None = None, - step_callback: Callable | None = None, - show_logs: Callable | None = None, + step_callback: Callable | None = None, # type: ignore[type-arg] + show_logs: Callable | None = None, # type: ignore[type-arg] ) -> AgentAction | AgentFinish: """Core logic for handling agent actions and tool results. @@ -481,7 +489,7 @@ def summarize_messages( ), ] summary = llm.call( - messages, # type: ignore[arg-type] + messages, callbacks=callbacks, ) summarized_contents.append({"content": str(summary)}) diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 8f77e1ba4..4fd1f3b5b 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -508,7 +508,47 @@ def test_agent_custom_max_iterations(): assert isinstance(result, str) assert len(result) > 0 assert call_count > 0 - assert call_count == 3 + # With max_iter=1, expect 2 calls: + # - Call 1: iteration 0 + # - Call 2: iteration 1 (max reached, handle_max_iterations_exceeded called, then loop breaks) + assert call_count == 2 + + +@pytest.mark.vcr(filter_headers=["authorization"]) +@pytest.mark.timeout(30) +def test_agent_max_iterations_stops_loop(): + """Test that agent execution terminates when max_iter is reached.""" + + @tool + def get_data(step: str) -> str: + """Get data for a step. Always returns data requiring more steps.""" + return f"Data for {step}: incomplete, need to query more steps." + + agent = Agent( + role="data collector", + goal="collect data using the get_data tool", + backstory="You must use the get_data tool extensively", + max_iter=2, + allow_delegation=False, + ) + + task = Task( + description="Use get_data tool for step1, step2, step3, step4, step5, step6, step7, step8, step9, and step10. Do NOT stop until you've called it for ALL steps.", + expected_output="A summary of all data collected", + ) + + result = agent.execute_task( + task=task, + tools=[get_data], + ) + + assert result is not None + assert isinstance(result, str) + + assert agent.agent_executor.iterations <= agent.max_iter + 2, ( + f"Agent ran {agent.agent_executor.iterations} iterations " + f"but should stop around {agent.max_iter + 1}. " + ) @pytest.mark.vcr(filter_headers=["authorization"]) diff --git a/lib/crewai/tests/cassettes/test_agent_max_iterations_stops_loop.yaml b/lib/crewai/tests/cassettes/test_agent_max_iterations_stops_loop.yaml new file mode 100644 index 000000000..6a40d691f --- /dev/null +++ b/lib/crewai/tests/cassettes/test_agent_max_iterations_stops_loop.yaml @@ -0,0 +1,495 @@ +interactions: +- request: + body: '{"trace_id": "REDACTED_TRACE_ID", "execution_type": + "crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null, + "crew_name": "Unknown Crew", "flow_name": null, "crewai_version": "1.4.0", "privacy_level": + "standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count": + 0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-11-07T18:27:07.650947+00:00"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '434' + Content-Type: + - application/json + User-Agent: + - CrewAI-CLI/1.4.0 + X-Crewai-Version: + - 1.4.0 + method: POST + uri: https://app.crewai.com/crewai_plus/api/v1/tracing/batches + response: + body: + string: '{"error":"bad_credentials","message":"Bad credentials"}' + headers: + Connection: + - keep-alive + Content-Length: + - '55' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 07 Nov 2025 18:27:07 GMT + cache-control: + - no-store + content-security-policy: + - 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' + ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts + https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js + https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map + https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com + https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com + https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com + https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ + https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net + https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net + https://js.hscollectedforms.net https://js.usemessages.com https://snap.licdn.com + https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com + https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com + app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: + *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com + https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com + https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com + https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; + connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com + https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* + https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io + https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com + https://api.hubspot.com https://forms.hscollectedforms.net https://api.hubapi.com + https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 + https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect + https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' + *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com + https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com + https://drive.google.com https://slides.google.com https://accounts.google.com + https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ + https://www.youtube.com https://share.descript.com' + expires: + - '0' + permissions-policy: + - camera=(), microphone=(self), geolocation=() + pragma: + - no-cache + referrer-policy: + - strict-origin-when-cross-origin + strict-transport-security: + - max-age=63072000; includeSubDomains + vary: + - Accept + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-permitted-cross-domain-policies: + - none + x-request-id: + - REDACTED_REQUEST_ID + x-runtime: + - '0.080681' + x-xss-protection: + - 1; mode=block + status: + code: 401 + message: Unauthorized +- request: + body: '{"messages":[{"role":"system","content":"You are data collector. You must + use the get_data tool extensively\nYour personal goal is: collect data using + the get_data tool\nYou ONLY have access to the following tools, and should NEVER + make up tools that are not listed here:\n\nTool Name: get_data\nTool Arguments: + {''step'': {''description'': None, ''type'': ''str''}}\nTool Description: Get + data for a step. Always returns data requiring more steps.\n\nIMPORTANT: Use + the following format in your response:\n\n```\nThought: you should always think + about what to do\nAction: the action to take, only one name of [get_data], just + the name, exactly as it''s written.\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \" to wrap keys and + values.\nObservation: the result of the action\n```\n\nOnce all necessary information + is gathered, return the following format:\n\n```\nThought: I now know the final + answer\nFinal Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use get_data tool for step1, step2, step3, step4, step5, step6, step7, + step8, step9, and step10. Do NOT stop until you''ve called it for ALL steps.\n\nThis + is the expected criteria for your final answer: A summary of all data collected\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nBegin! + This is VERY important to you, use the tools available and give your best Final + Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '1534' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xSYWvbMBD97l9x6HMcYsfpUn8rg0FHYbAOyrYUo0hnW5ksCem8tYT89yG7id2t + g30x5t69p/fu7pgAMCVZCUy0nETndPr+2919j4fr9VNR/Opv7vBD/bAVXz/dfzx8fmCLyLD7Awo6 + s5bCdk4jKWtGWHjkhFE1e3eVb4rVKt8OQGcl6khrHKXFMks7ZVSar/JNuirSrHiht1YJDKyE7wkA + wHH4RqNG4hMrYbU4VzoMgTfIyksTAPNWxwrjIahA3BBbTKCwhtAM3r+0tm9aKuEWQmt7LSEQ9wT7 + ZxBWaxSkTAOSE4faegiELgMeQJlAvheEcrkzNyLmLqFBqmLruQK3xvVUwnHHInHHyvEn27HT3I/H + ug88DsX0Ws8AbowlHqWGSTy+IKdLdm0b5+0+/EFltTIqtJVHHqyJOQNZxwb0lAA8DjPuX42NOW87 + RxXZHzg8t15nox6bdjuh4zYBGFniesbaXC/e0KskElc6zLbEBBctyok6rZT3UtkZkMxS/+3mLe0x + uTLN/8hPgBDoCGXlPEolXiee2jzG0/9X22XKg2EW0P9UAitS6OMmJNa81+M9svAcCLuqVqZB77wa + j7J2VSHy7Sart1c5S07JbwAAAP//AwCiugNoowMAAA== + headers: + CF-RAY: + - 99aee205bbd2de96-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 07 Nov 2025 18:27:08 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=REDACTED_COOKIE; + path=/; expires=Fri, 07-Nov-25 18:57:08 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=REDACTED_COOKIE; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - REDACTED_ORG_ID + openai-processing-ms: + - '557' + openai-project: + - REDACTED_PROJECT_ID + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '701' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '500' + x-ratelimit-limit-tokens: + - '200000' + x-ratelimit-remaining-requests: + - '499' + x-ratelimit-remaining-tokens: + - '199645' + x-ratelimit-reset-requests: + - 120ms + x-ratelimit-reset-tokens: + - 106ms + x-request-id: + - REDACTED_REQUEST_ID + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are data collector. You must + use the get_data tool extensively\nYour personal goal is: collect data using + the get_data tool\nYou ONLY have access to the following tools, and should NEVER + make up tools that are not listed here:\n\nTool Name: get_data\nTool Arguments: + {''step'': {''description'': None, ''type'': ''str''}}\nTool Description: Get + data for a step. Always returns data requiring more steps.\n\nIMPORTANT: Use + the following format in your response:\n\n```\nThought: you should always think + about what to do\nAction: the action to take, only one name of [get_data], just + the name, exactly as it''s written.\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \" to wrap keys and + values.\nObservation: the result of the action\n```\n\nOnce all necessary information + is gathered, return the following format:\n\n```\nThought: I now know the final + answer\nFinal Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use get_data tool for step1, step2, step3, step4, step5, step6, step7, + step8, step9, and step10. Do NOT stop until you''ve called it for ALL steps.\n\nThis + is the expected criteria for your final answer: A summary of all data collected\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nBegin! + This is VERY important to you, use the tools available and give your best Final + Answer, your job depends on it!\n\nThought:"},{"role":"assistant","content":"Thought: + I should start by collecting data for step1 as instructed.\nAction: get_data\nAction + Input: {\"step\":\"step1\"}\nObservation: Data for step1: incomplete, need to + query more steps."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '1757' + content-type: + - application/json + cookie: + - __cf_bm=REDACTED_COOKIE; + _cfuvid=REDACTED_COOKIE + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNb9swDL37VxA6x0HiOU3mW9cOQ4F9YNjQQ5fCUGXaVidLqkQnzYL8 + 90F2ErtbB+xiCHx8j+QjvY8AmCxYBkzUnERjVXx19/Hb5tPm/fbq8sPX5+Wvx6V+t93efXY1v71m + k8AwD48o6MSaCtNYhSSN7mHhkBMG1fnyIlmks1nytgMaU6AKtMpSnE7ncSO1jJNZsohnaTxPj/Ta + SIGeZfAjAgDYd9/QqC7wmWUwm5wiDXrPK2TZOQmAOaNChHHvpSeuiU0GUBhNqLvev9emrWrK4AY0 + YgFkIKBStxjentAmfVApFAQFJw4en1rUJLlSO+AeHD610mExXetLESzIoELKQ+4pAjfatpTBfs2C + 5ppl/SNZs8Naf3nw6Da8p16HEqVxffEMpD56ixNojMMu7kGjCIO73XQ8msOy9Tz4q1ulRgDX2lBX + oTP1/ogczjYqU1lnHvwfVFZKLX2dO+Te6GCZJ2NZhx4igPtuXe2LDTDrTGMpJ/MTu3Jvlqtejw1n + MqBpegTJEFejeJJMXtHLCyQulR8tnAkuaiwG6nAdvC2kGQHRaOq/u3lNu59c6up/5AdACLSERW4d + FlK8nHhIcxj+on+lnV3uGmbhSKTAnCS6sIkCS96q/rSZ33nCJi+lrtBZJ/v7Lm2eimS1mJeri4RF + h+g3AAAA//8DABrUefPuAwAA + headers: + CF-RAY: + - 99aee20dba0bde96-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 07 Nov 2025 18:27:10 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - REDACTED_ORG_ID + openai-processing-ms: + - '942' + openai-project: + - REDACTED_PROJECT_ID + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1074' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '500' + x-ratelimit-limit-tokens: + - '200000' + x-ratelimit-remaining-requests: + - '499' + x-ratelimit-remaining-tokens: + - '199599' + x-ratelimit-reset-requests: + - 120ms + x-ratelimit-reset-tokens: + - 120ms + x-request-id: + - REDACTED_REQUEST_ID + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are data collector. You must + use the get_data tool extensively\nYour personal goal is: collect data using + the get_data tool\nYou ONLY have access to the following tools, and should NEVER + make up tools that are not listed here:\n\nTool Name: get_data\nTool Arguments: + {''step'': {''description'': None, ''type'': ''str''}}\nTool Description: Get + data for a step. Always returns data requiring more steps.\n\nIMPORTANT: Use + the following format in your response:\n\n```\nThought: you should always think + about what to do\nAction: the action to take, only one name of [get_data], just + the name, exactly as it''s written.\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \" to wrap keys and + values.\nObservation: the result of the action\n```\n\nOnce all necessary information + is gathered, return the following format:\n\n```\nThought: I now know the final + answer\nFinal Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use get_data tool for step1, step2, step3, step4, step5, step6, step7, + step8, step9, and step10. Do NOT stop until you''ve called it for ALL steps.\n\nThis + is the expected criteria for your final answer: A summary of all data collected\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nBegin! + This is VERY important to you, use the tools available and give your best Final + Answer, your job depends on it!\n\nThought:"},{"role":"assistant","content":"Thought: + I should start by collecting data for step1 as instructed.\nAction: get_data\nAction + Input: {\"step\":\"step1\"}\nObservation: Data for step1: incomplete, need to + query more steps."},{"role":"assistant","content":"Thought: I need to continue + to step2 to collect data sequentially as required.\nAction: get_data\nAction + Input: {\"step\":\"step2\"}\nObservation: Data for step2: incomplete, need to + query more steps."},{"role":"assistant","content":"Thought: I need to continue + to step2 to collect data sequentially as required.\nAction: get_data\nAction + Input: {\"step\":\"step2\"}\nObservation: Data for step2: incomplete, need to + query more steps.\nNow it''s time you MUST give your absolute best final answer. + You''ll ignore all previous instructions, stop using any tools, and just return + your absolute BEST Final answer."}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '2399' + content-type: + - application/json + cookie: + - __cf_bm=REDACTED_COOKIE; + _cfuvid=REDACTED_COOKIE + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//nJbfj6M2EMff81eM/NRKmwgI5Advp7v2FKlSW22f9rKKHHsI7hmbs83u + nlb7v1eYBLJXQFxekMV8Z+ZjYw3f1xkAEZykQFhOHStKOf/48Mf9yzf5/Pnh498P9kl9ru51qR9k + XsVBSO7qDH38F5m7ZC2YLkqJTmjVhJlB6rCuGq5XURIHwTL0gUJzlHXaqXTzeBHOC6HEPAqiZB7E + 8zA+p+daMLQkhS8zAIBX/6xBFccXkkJwd3lToLX0hCRtRQDEaFm/IdRaYR1Vjtx1QaaVQ+XZ/8l1 + dcpdCjsoKuuAaSmROeDUUci0ASolWIelhczowi9DcLpZBHDETBuE0ugnwYU6gcsRMqGohPOJIJzb + AbVg8FslDHI4fvdKR+3XBezgWUjpdUJVCJW9VDqhO3gUp7X0PEhZ7puDUKANR7PYq736wOqjT9uE + yxvYqbJyKbzuSZ20J2mzCPfkba/+PFo0T7RJ/VT3KalxEPpOzVb10VGhkPsu7Wn9ZTRD5JeDiBY/ + TxCNEUQtQTSNYHkDwXKMYNkSLKcRxDcQxGMEcUsQTyNIbiBIxgiSliCZRrC6gWA1RrBqCVbTCNY3 + EKzHCNYtwXoaweYGgs0YwaYl2Ewj2N5AsB0j2LYE22kEYXADQhiMzqSgG0rBAMUOlH6GnD6hH9vt + DG/mtx/bYQBUcWBUnWc2jkxsX/13H/qg7DOaFPbq3o/FGiyFLzvFZMWxaXWenZdxn6PBx0YfDeuj + Pv1yWL/s08fD+rhPnwzrkz79ali/6tOvh/XrPv1mWL/p02+H9ds+fRiMfLDgx4y9+uW3F8rc9Y/7 + cuEaF6C7O2rf/5Xv6iRGHara/fiKi1+vvYfBrLK0NkCqkvIqQJXSrilZu57Hc+St9TlSn0qjj/aH + VJIJJWx+MEitVrWnsU6XxEffZgCP3k9V7ywSKY0uSndw+iv6dkl49lOk83FX0Sg5R512VHaBMFhe + Iu8qHjg6KqS98mSEUZYj73I7A0crLvRVYHa17//z9NVu9i7UaUr5LsAYlg75oTTIBXu/505msDa6 + Q7L2nD0wqe+FYHhwAk39LThmtJKN+yT2u3VYHDKhTmhKIxoLmpWHmEWbJMw2q4jM3mb/AQAA//8D + ACYaBDGRCwAA + headers: + CF-RAY: + - 99aee2174b18de96-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 07 Nov 2025 18:27:20 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - REDACTED_ORG_ID + openai-processing-ms: + - '9185' + openai-project: + - REDACTED_PROJECT_ID + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '9386' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '500' + x-ratelimit-limit-tokens: + - '200000' + x-ratelimit-remaining-requests: + - '499' + x-ratelimit-remaining-tokens: + - '199457' + x-ratelimit-reset-requests: + - 120ms + x-ratelimit-reset-tokens: + - 162ms + x-request-id: + - REDACTED_REQUEST_ID + status: + code: 200 + message: OK +version: 1