From 2ab79a7dd5623fe3adde03469afb61caefed528b Mon Sep 17 00:00:00 2001 From: Lucas Gomide Date: Fri, 18 Jul 2025 14:54:28 -0300 Subject: [PATCH] feat: drop unsupported stop parameter for LLM models automatically (#3184) --- src/crewai/llm.py | 21 ++ ...est_llm_call_when_stop_is_unsupported.yaml | 209 ++++++++++++++++++ ...en_additional_drop_params_is_provided.yaml | 206 +++++++++++++++++ tests/llm_test.py | 20 ++ 4 files changed, 456 insertions(+) create mode 100644 tests/cassettes/test_llm_call_when_stop_is_unsupported.yaml create mode 100644 tests/cassettes/test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provided.yaml diff --git a/src/crewai/llm.py b/src/crewai/llm.py index d6f40a09a..3b4f0b9f4 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -984,6 +984,27 @@ class LLM(BaseLLM): # whether to summarize the content or abort based on the respect_context_window flag raise except Exception as e: + unsupported_stop = "Unsupported parameter" in str(e) and "'stop'" in str(e) + + if unsupported_stop: + if "additional_drop_params" in self.additional_params and isinstance(self.additional_params["additional_drop_params"], list): + self.additional_params["additional_drop_params"].append("stop") + else: + self.additional_params = {"additional_drop_params": ["stop"]} + + logging.info( + "Retrying LLM call without the unsupported 'stop'" + ) + + return self.call( + messages, + tools=tools, + callbacks=callbacks, + available_functions=available_functions, + from_task=from_task, + from_agent=from_agent, + ) + assert hasattr(crewai_event_bus, "emit") crewai_event_bus.emit( self, diff --git a/tests/cassettes/test_llm_call_when_stop_is_unsupported.yaml b/tests/cassettes/test_llm_call_when_stop_is_unsupported.yaml new file mode 100644 index 000000000..3e9e891ef --- /dev/null +++ b/tests/cassettes/test_llm_call_when_stop_is_unsupported.yaml @@ -0,0 +1,209 @@ +interactions: +- request: + body: '{"messages": [{"role": "user", "content": "What is the capital of France?"}], + "model": "o1-mini", "stop": ["stop"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '115' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.75.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.75.0 + x-stainless-raw-response: + - 'true' + x-stainless-read-timeout: + - '600.0' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"error\": {\n \"message\": \"Unsupported parameter: 'stop' + is not supported with this model.\",\n \"type\": \"invalid_request_error\",\n + \ \"param\": \"stop\",\n \"code\": \"unsupported_parameter\"\n }\n}" + headers: + CF-RAY: + - 961215744c94cb45-GIG + Connection: + - keep-alive + Content-Length: + - '196' + Content-Type: + - application/json + Date: + - Fri, 18 Jul 2025 12:46:46 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=KwJ1K47OHX4n2TZN8bMW37yKzKyK__S4HbTiCfyWjXM-1752842806-1.0.1.1-lweHFR7Kv2v7hT5I6xxYVz_7Ruu6aBdEgpJrSWrMxi_ficAeWC0oDeQ.0w2Lr1WRejIjqqcwSgdl6RixF2qEkjJZfS0pz_Vjjqexe44ayp4; + path=/; expires=Fri, 18-Jul-25 13:16:46 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=zv09c6bwcgNsYU80ah3wXzqeaIKyt_h61EAh_XRA87I-1752842806652-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '20' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '32' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999990' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_7be4715c3ee32aa406eacb68c7cc966e + status: + code: 400 + message: Bad Request +- request: + body: '{"messages": [{"role": "user", "content": "What is the capital of France?"}], + "model": "o1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '97' + content-type: + - application/json + cookie: + - __cf_bm=KwJ1K47OHX4n2TZN8bMW37yKzKyK__S4HbTiCfyWjXM-1752842806-1.0.1.1-lweHFR7Kv2v7hT5I6xxYVz_7Ruu6aBdEgpJrSWrMxi_ficAeWC0oDeQ.0w2Lr1WRejIjqqcwSgdl6RixF2qEkjJZfS0pz_Vjjqexe44ayp4; + _cfuvid=zv09c6bwcgNsYU80ah3wXzqeaIKyt_h61EAh_XRA87I-1752842806652-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.75.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.75.0 + x-stainless-raw-response: + - 'true' + x-stainless-read-timeout: + - '600.0' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RSwU7jMBC95ytGPlYNakJhQ2/sgSsg7QUhFA32pJni2JHtwFao/76yC3XQwsWH + efOe35uZ9wJAsBIbELLHIIdRl78nGvaqOt/dPDxf71/fdg/9bXO3e5ETXt+LZWTY5x3J8Mk6k3YY + NQW25ghLRxgoqla/LupmXTeXqwQMVpGONFuVAxsu61W9LldXZVV/MHvLkrzYwGMBAPCe3ujRKPor + NpB0UmUg73FLYnNqAhDO6lgR6D37gCaIZQalNYFMsv2nJ5A4ckANtoMbh0YSsIfF4g4d+8XibM50 + 1E0eo3MzaT0D0BgbMCZPnp8+kMPJZceGfd86Qm9N/NkHO4qEHgqAp5R6+hJEjM4OY2iDfaEkW62P + ciLPOYPNJxhsQJ3rV83yG7VWUUDWfjY1IVH2pDIzjxgnxXYGFLNs/5v5TvuYm802q1yuf9TPgJQ0 + BlLt6Eix/Jo4tzmKZ/hT22nIybHw5F5ZUhuYXFyEog4nfTwQ4fc+0NB2bLbkRsfpSuKui0PxDwAA + //8DAN7IUy8kAwAA + headers: + CF-RAY: + - 961216c3f9837e07-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 18 Jul 2025 12:47:41 GMT + Server: + - cloudflare + 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: + - crewai-iuxna1 + openai-processing-ms: + - '1027' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '1029' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999990' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_19a0763b09f0410b9d09598078a04cd6 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provided.yaml b/tests/cassettes/test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provided.yaml new file mode 100644 index 000000000..d983d0542 --- /dev/null +++ b/tests/cassettes/test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provided.yaml @@ -0,0 +1,206 @@ +interactions: +- request: + body: '{"messages": [{"role": "user", "content": "What is the capital of France?"}], + "model": "o1-mini", "stop": ["stop"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '115' + content-type: + - application/json + cookie: + - __cf_bm=KwJ1K47OHX4n2TZN8bMW37yKzKyK__S4HbTiCfyWjXM-1752842806-1.0.1.1-lweHFR7Kv2v7hT5I6xxYVz_7Ruu6aBdEgpJrSWrMxi_ficAeWC0oDeQ.0w2Lr1WRejIjqqcwSgdl6RixF2qEkjJZfS0pz_Vjjqexe44ayp4; + _cfuvid=zv09c6bwcgNsYU80ah3wXzqeaIKyt_h61EAh_XRA87I-1752842806652-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.75.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.75.0 + x-stainless-raw-response: + - 'true' + x-stainless-read-timeout: + - '600.0' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"error\": {\n \"message\": \"Unsupported parameter: 'stop' + is not supported with this model.\",\n \"type\": \"invalid_request_error\",\n + \ \"param\": \"stop\",\n \"code\": \"unsupported_parameter\"\n }\n}" + headers: + CF-RAY: + - 961220323a627e05-GRU + Connection: + - keep-alive + Content-Length: + - '196' + Content-Type: + - application/json + Date: + - Fri, 18 Jul 2025 12:54:06 GMT + Server: + - cloudflare + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '9' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '11' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999990' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_e8d7880c5977029062d8487d215e5282 + status: + code: 400 + message: Bad Request +- request: + body: '{"messages": [{"role": "user", "content": "What is the capital of France?"}], + "model": "o1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '97' + content-type: + - application/json + cookie: + - __cf_bm=KwJ1K47OHX4n2TZN8bMW37yKzKyK__S4HbTiCfyWjXM-1752842806-1.0.1.1-lweHFR7Kv2v7hT5I6xxYVz_7Ruu6aBdEgpJrSWrMxi_ficAeWC0oDeQ.0w2Lr1WRejIjqqcwSgdl6RixF2qEkjJZfS0pz_Vjjqexe44ayp4; + _cfuvid=zv09c6bwcgNsYU80ah3wXzqeaIKyt_h61EAh_XRA87I-1752842806652-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.75.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.75.0 + x-stainless-raw-response: + - 'true' + x-stainless-read-timeout: + - '600.0' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA3SSQW/bMAyF7/4Vgo5BXCSeV6c5bkAPPTVbMaAYCoOT6JitLAkSPbQo8t8HKWns + Yu1FB3181HsUXwshJGm5FVL1wGrwpvw2In/fXY3Pcd/sftzf9ENvnurm569dc9/IZVK4P4+o+E11 + odzgDTI5e8QqIDCmruvma7Wpv1T1ZQaD02iSzK3LgSyV1aqqy9VVua5Oyt6Rwii34nchhBCv+Uwe + rcZnuRWr5dvNgDHCHuX2XCSEDM6kGwkxUmSwLJcTVM4y2mz7rkehwBODEa4T1wGsQkFRLBa3ECgu + FhdzZcBujJCc29GYGQBrHUNKnj0/nMjh7LIjS7FvA0J0Nr0c2XmZ6aEQ4iGnHt8FkT64wXPL7glz + 23V9bCenOc/h5kTZMZgZuKyWH/RrNTKQibO5SQWqRz1JpyHDqMnNQDFL97+dj3ofk5Pdz5xVm08f + mIBS6Bl16wNqUu9DT2UB0yZ+Vnaec7YsI4a/pLBlwpD+QmMHoznuiIwvkXFoO7J7DD5QXpT03cWh + +AcAAP//AwAo/zsSJwMAAA== + headers: + CF-RAY: + - 961220338bd47e05-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 18 Jul 2025 12:54:08 GMT + Server: + - cloudflare + 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: + - crewai-iuxna1 + openai-processing-ms: + - '1280' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '1286' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999990' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_b7390d46fa4e14380d42162cb22045df + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llm_test.py b/tests/llm_test.py index 1065876af..07ab2df1e 100644 --- a/tests/llm_test.py +++ b/tests/llm_test.py @@ -1,3 +1,4 @@ +import logging import os from time import sleep from unittest.mock import MagicMock, patch @@ -664,3 +665,22 @@ def test_handle_streaming_tool_calls_no_tools(mock_emit): expected_completed_llm_call=1, expected_final_chunk_result=response, ) + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_llm_call_when_stop_is_unsupported(caplog): + llm = LLM(model="o1-mini", stop=["stop"]) + with caplog.at_level(logging.INFO): + result = llm.call("What is the capital of France?") + assert "Retrying LLM call without the unsupported 'stop'" in caplog.text + assert isinstance(result, str) + assert "Paris" in result + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_llm_call_when_stop_is_unsupported_when_additional_drop_params_is_provided(caplog): + llm = LLM(model="o1-mini", stop=["stop"], additional_drop_params=["another_param"]) + with caplog.at_level(logging.INFO): + result = llm.call("What is the capital of France?") + assert "Retrying LLM call without the unsupported 'stop'" in caplog.text + assert isinstance(result, str) + assert "Paris" in result