This commit is contained in:
Mrunmayee Daware 2026-10-17 22:38:52 +05:30 • committed by GitHub
commit 090e90b184
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 121 additions and 1 deletions

View file

@ -529,10 +529,16 @@ class LiteLLMAnthropicMessagesAdapter:
self._add_cache_control_if_applicable(content, doc_obj, model)
new_user_content_list.append(doc_obj)
elif content.get("type") == "tool_result":
raw_tool_result_content: Final = self._tool_result_content(content.get("content"))
tool_result_content: Final = (
self._mark_tool_result_as_error(raw_tool_result_content)
if content.get("is_error")
else raw_tool_result_content
)
tool_result = ChatCompletionToolMessage(
role="tool",
tool_call_id=content.get("tool_use_id", ""),
content=self._tool_result_content(content.get("content")),
content=tool_result_content,
)
self._add_cache_control_if_applicable(content, tool_result, model)
tool_message_list.append(tool_result)
@ -1321,6 +1327,20 @@ class LiteLLMAnthropicMessagesAdapter:
case _:
return list(parts)
@staticmethod
def _mark_tool_result_as_error(content: ToolResultContent) -> ToolResultContent:
"""Keep Anthropic's `is_error` signal visible.
Chat Completions tool messages have no error field, so encode the
flag into the content itself; models and downstream provider
mappings can then still tell the tool call failed.
"""
if isinstance(content, str):
return "[tool error] " + content if content else "[tool error]"
if isinstance(content, list):
return [ChatCompletionTextObject(type="text", text="[tool error] "), *content]
return content
def _tool_result_part(self, item: object) -> ToolMessageContentPart | None:
if isinstance(item, str):
return ChatCompletionTextObject(type="text", text=item)

View file

@ -4712,6 +4712,106 @@ def test_tool_result_without_translatable_content_still_answers_its_tool_use(too
assert result[0]["role"] == "assistant"
def test_tool_result_is_error_is_encoded_in_string_content():
adapter = LiteLLMAnthropicMessagesAdapter()
result = adapter.translate_anthropic_messages_to_openai(
messages=[
_anthropic_tool_use_turn("toolu_01"),
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "deploy failed",
"is_error": True,
}
],
},
]
)
assert result[1] == {
"role": "tool",
"tool_call_id": "toolu_01",
"content": "[tool error] deploy failed",
}
def test_tool_result_is_error_puts_the_marker_first_in_part_lists():
adapter = LiteLLMAnthropicMessagesAdapter()
result = adapter.translate_anthropic_messages_to_openai(
messages=[
_anthropic_tool_use_turn("toolu_01"),
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": [{"type": "text", "text": "boom"}, _tool_reference_block()],
"is_error": True,
}
],
},
]
)
assert result[1]["content"] == [
{"type": "text", "text": "[tool error] "},
{"type": "text", "text": "boom"},
{"type": "tool_reference", "tool_name": "WebFetch"},
]
def test_tool_result_is_error_with_empty_content_keeps_the_marker():
adapter = LiteLLMAnthropicMessagesAdapter()
result = adapter.translate_anthropic_messages_to_openai(
messages=[
_anthropic_tool_use_turn("toolu_01"),
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "",
"is_error": True,
}
],
},
]
)
assert result[1]["content"] == "[tool error]"
def test_tool_result_without_is_error_keeps_content_unchanged():
adapter = LiteLLMAnthropicMessagesAdapter()
result = adapter.translate_anthropic_messages_to_openai(
messages=[
_anthropic_tool_use_turn("toolu_01"),
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "deploy succeeded",
"is_error": False,
}
],
},
]
)
assert result[1]["content"] == "deploy succeeded"
def _openai_response_with_usage(usage: Usage) -> ModelResponse:
return ModelResponse(
id="resp_web_search",