Skip to content

Commit 11b6765

Browse files
committed
fix(litellm): map LiteLLM context-window errors to ContextWindowOverflowException
1 parent 776fd93 commit 11b6765

File tree

1 file changed

+40
-8
lines changed

1 file changed

+40
-8
lines changed

src/strands/models/litellm.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing_extensions import Unpack, override
1414

1515
from ..types.content import ContentBlock, Messages
16+
from ..types.exceptions import ContextWindowOverflowException
1617
from ..types.streaming import StreamEvent
1718
from ..types.tools import ToolChoice, ToolSpec
1819
from ._validation import validate_config_keys
@@ -56,6 +57,26 @@ def __init__(self, client_args: Optional[dict[str, Any]] = None, **model_config:
5657

5758
logger.debug("config=<%s> | initializing", self.config)
5859

60+
def _handle_context_window_overflow(self, e: Exception) -> None:
61+
"""Handle context window overflow errors from LiteLLM.
62+
63+
Args:
64+
e: The exception to handle.
65+
66+
Raises:
67+
ContextWindowOverflowException: If the exception is a context window overflow error.
68+
"""
69+
# Prefer litellm-specific typed exception if exposed
70+
litellm_exc_type = getattr(litellm, "ContextWindowExceededError", None) or getattr(
71+
litellm, "ContextWindowExceeded", None
72+
)
73+
if litellm_exc_type and isinstance(e, litellm_exc_type):
74+
logger.warning("litellm client raised context window overflow")
75+
raise ContextWindowOverflowException(e) from e
76+
77+
# Not a context-window error — re-raise original
78+
raise e
79+
5980
@override
6081
def update_config(self, **model_config: Unpack[LiteLLMConfig]) -> None: # type: ignore[override]
6182
"""Update the LiteLLM model configuration with the provided arguments.
@@ -135,7 +156,10 @@ async def stream(
135156
logger.debug("request=<%s>", request)
136157

137158
logger.debug("invoking model")
138-
response = await litellm.acompletion(**self.client_args, **request)
159+
try:
160+
response = await litellm.acompletion(**self.client_args, **request)
161+
except Exception as e:
162+
self._handle_context_window_overflow(e)
139163

140164
logger.debug("got response from model")
141165
yield self.format_chunk({"chunk_type": "message_start"})
@@ -205,15 +229,23 @@ async def structured_output(
205229
Yields:
206230
Model events with the last being the structured output.
207231
"""
208-
if not supports_response_schema(self.get_config()["model_id"]):
232+
supports_schema = supports_response_schema(self.get_config()["model_id"])
233+
234+
# If the provider does not support response schemas, we cannot reliably parse structured output.
235+
# In that case we must not call the provider and must raise the documented ValueError.
236+
if not supports_schema:
209237
raise ValueError("Model does not support response_format")
210238

211-
response = await litellm.acompletion(
212-
**self.client_args,
213-
model=self.get_config()["model_id"],
214-
messages=self.format_request(prompt, system_prompt=system_prompt)["messages"],
215-
response_format=output_model,
216-
)
239+
# For providers that DO support response schemas, call litellm and map context-window errors.
240+
try:
241+
response = await litellm.acompletion(
242+
**self.client_args,
243+
model=self.get_config()["model_id"],
244+
messages=self.format_request(prompt, system_prompt=system_prompt)["messages"],
245+
response_format=output_model,
246+
)
247+
except Exception as e:
248+
self._handle_context_window_overflow(e)
217249

218250
if len(response.choices) > 1:
219251
raise ValueError("Multiple choices found in the response.")

0 commit comments

Comments
 (0)