mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(mcp): search explicit exception links before __context__ when finding the upstream response
This commit is contained in:
parent
eefd5e31e5
commit
424443c11f
2 changed files with 34 additions and 5 deletions
|
|
@ -63,7 +63,10 @@ class AggregateToolListing(NamedTuple):
|
|||
|
||||
def _find_upstream_response(exc: BaseException) -> httpx.Response | None:
|
||||
"""Walk the exception tree (``__cause__``/``__context__``/ExceptionGroup members) for an
|
||||
``httpx.Response``, mirroring how upstream failures surface through the MCP SDK's task groups."""
|
||||
``httpx.Response``, mirroring how upstream failures surface through the MCP SDK's task groups.
|
||||
Explicit links are searched first: each node's ``raise ... from`` cause, then group members in
|
||||
raise order, then the incidental ``__context__`` chain, so a response raised while handling the
|
||||
real failure can never shadow the response on the explicit causal chain."""
|
||||
seen: set[int] = set()
|
||||
stack = [exc]
|
||||
while stack:
|
||||
|
|
@ -74,12 +77,13 @@ def _find_upstream_response(exc: BaseException) -> httpx.Response | None:
|
|||
response = getattr(current, "response", None)
|
||||
if isinstance(response, httpx.Response):
|
||||
return response
|
||||
if current.__context__ is not None:
|
||||
stack.append(current.__context__)
|
||||
exceptions = getattr(current, "exceptions", None)
|
||||
if isinstance(exceptions, tuple):
|
||||
stack.extend(exceptions)
|
||||
for link in (current.__cause__, current.__context__):
|
||||
if link is not None:
|
||||
stack.append(link)
|
||||
stack.extend(reversed(exceptions))
|
||||
if current.__cause__ is not None:
|
||||
stack.append(current.__cause__)
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,31 @@ def test_embedded_401_classifies_auth_required():
|
|||
assert classify_list_exception(exc).tag == "auth_required"
|
||||
|
||||
|
||||
def test_context_response_does_not_shadow_the_causal_chain_response():
|
||||
real_response = httpx.Response(401, request=httpx.Request("POST", "https://mcp.example.com/mcp"))
|
||||
real = httpx.HTTPStatusError("upstream rejected", request=real_response.request, response=real_response)
|
||||
incidental_response = httpx.Response(500, request=httpx.Request("POST", "https://hooks.example.com/log"))
|
||||
incidental = httpx.HTTPStatusError(
|
||||
"logging hook failed", request=incidental_response.request, response=incidental_response
|
||||
)
|
||||
wrapper = RuntimeError("wrapper")
|
||||
wrapper.__cause__ = real
|
||||
wrapper.__context__ = incidental
|
||||
fault = classify_list_exception(wrapper)
|
||||
assert fault.tag == "auth_required"
|
||||
assert fault.status_code == 401
|
||||
|
||||
|
||||
def test_exception_group_members_are_searched_in_raise_order():
|
||||
first_response = httpx.Response(502, request=httpx.Request("POST", "https://mcp.example.com/mcp"))
|
||||
first = httpx.HTTPStatusError("first", request=first_response.request, response=first_response)
|
||||
second_response = httpx.Response(503, request=httpx.Request("POST", "https://mcp.example.com/mcp"))
|
||||
second = httpx.HTTPStatusError("second", request=second_response.request, response=second_response)
|
||||
fault = classify_list_exception(BaseExceptionGroup("task group", [first, second]))
|
||||
assert fault.tag == "upstream_error"
|
||||
assert fault.status_code == 502
|
||||
|
||||
|
||||
def test_unknown_exception_is_internal():
|
||||
assert classify_list_exception(ValueError("who knows")).tag == "internal"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue