Skip to content

Commit 7993f18

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

File tree

1 file changed

+38
-8
lines changed

1 file changed

+38
-8
lines changed

src/strands/models/litellm.py

Lines changed: 38 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,24 @@ 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)
71+
if litellm_exc_type and isinstance(e, litellm_exc_type):
72+
logger.warning("litellm client raised context window overflow")
73+
raise ContextWindowOverflowException(e) from e
74+
75+
# Not a context-window error — re-raise original
76+
raise e
77+
5978
@override
6079
def update_config(self, **model_config: Unpack[LiteLLMConfig]) -> None: # type: ignore[override]
6180
"""Update the LiteLLM model configuration with the provided arguments.
@@ -135,7 +154,10 @@ async def stream(
135154
logger.debug("request=<%s>", request)
136155

137156
logger.debug("invoking model")
138-
response = await litellm.acompletion(**self.client_args, **request)
157+
try:
158+
response = await litellm.acompletion(**self.client_args, **request)
159+
except Exception as e:
160+
self._handle_context_window_overflow(e)
139161

140162
logger.debug("got response from model")
141163
yield self.format_chunk({"chunk_type": "message_start"})
@@ -205,15 +227,23 @@ async def structured_output(
205227
Yields:
206228
Model events with the last being the structured output.
207229
"""
208-
if not supports_response_schema(self.get_config()["model_id"]):
230+
supports_schema = supports_response_schema(self.get_config()["model_id"])
231+
232+
# If the provider does not support response schemas, we cannot reliably parse structured output.
233+
# In that case we must not call the provider and must raise the documented ValueError.
234+
if not supports_schema:
209235
raise ValueError("Model does not support response_format")
210236

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-
)
237+
# For providers that DO support response schemas, call litellm and map context-window errors.
238+
try:
239+
response = await litellm.acompletion(
240+
**self.client_args,
241+
model=self.get_config()["model_id"],
242+
messages=self.format_request(prompt, system_prompt=system_prompt)["messages"],
243+
response_format=output_model,
244+
)
245+
except Exception as e:
246+
self._handle_context_window_overflow(e)
217247

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

0 commit comments

Comments
 (0)