mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix: detect Anthropic-format tool_use content blocks in orphan detection
- Update _is_orphaned_tool_result to check for tool_use content blocks - Add tool_result to ValidUserMessageContentTypes - Add tests for Anthropic-format tool content handling Fixes #25669 Made-with: Cursor
This commit is contained in:
parent
72a461ba4a
commit
a30596c3dd
3 changed files with 126 additions and 2 deletions
|
|
@ -2218,6 +2218,7 @@ def _is_orphaned_tool_result(
|
|||
for j in range(len(sanitized_messages) - 1, -1, -1):
|
||||
prev_msg = sanitized_messages[j]
|
||||
if prev_msg.get("role") == "assistant":
|
||||
# Check OpenAI format: tool_calls array
|
||||
tool_calls = prev_msg.get("tool_calls")
|
||||
if tool_calls:
|
||||
for tool_call in cast(list, tool_calls):
|
||||
|
|
@ -2231,6 +2232,16 @@ def _is_orphaned_tool_result(
|
|||
found_matching_tool_call = True
|
||||
break
|
||||
|
||||
# Check Anthropic format: content blocks with type=tool_use
|
||||
if not found_matching_tool_call:
|
||||
content = prev_msg.get("content")
|
||||
if isinstance(content, list):
|
||||
for block in content:
|
||||
if isinstance(block, dict) and block.get("type") == "tool_use":
|
||||
if block.get("id") == tool_call_id:
|
||||
found_matching_tool_call = True
|
||||
break
|
||||
|
||||
break
|
||||
|
||||
if not found_matching_tool_call:
|
||||
|
|
|
|||
|
|
@ -797,7 +797,8 @@ ValidUserMessageContentTypes = [
|
|||
"guarded_text",
|
||||
"video_url",
|
||||
"file",
|
||||
] # used for validating user messages. Prevent users from accidentally sending anthropic messages.
|
||||
"tool_result",
|
||||
] # used for validating user messages. Includes tool_result for Anthropic-format content blocks.
|
||||
|
||||
ValidUserMessageContentTypesLiteral = Literal[
|
||||
"text",
|
||||
|
|
@ -808,6 +809,7 @@ ValidUserMessageContentTypesLiteral = Literal[
|
|||
"guarded_text",
|
||||
"video_url",
|
||||
"file",
|
||||
"tool_result",
|
||||
]
|
||||
|
||||
ValidUserMessageContentTypes = [
|
||||
|
|
@ -819,7 +821,8 @@ ValidUserMessageContentTypes = [
|
|||
"guarded_text",
|
||||
"video_url",
|
||||
"file",
|
||||
] # used for validating user messages. Prevent users from accidentally sending anthropic messages.
|
||||
"tool_result",
|
||||
] # used for validating user messages. Includes tool_result for Anthropic-format content blocks.
|
||||
|
||||
# Assistant message content types (text, thinking, redacted_thinking, image_url)
|
||||
ValidAssistantMessageContentTypesLiteral = Literal[
|
||||
|
|
|
|||
110
tests/test_litellm/llms/anthropic/test_anthropic_tool_content.py
Normal file
110
tests/test_litellm/llms/anthropic/test_anthropic_tool_content.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
"""
|
||||
Tests for Anthropic-format tool_use handling in sanitization and user-message validation.
|
||||
|
||||
Covers:
|
||||
- _is_orphaned_tool_result matching prior assistant tool_use content blocks
|
||||
- validate_chat_completion_user_messages accepting tool_result content blocks in user messages
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../.."))
|
||||
)
|
||||
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import _is_orphaned_tool_result
|
||||
from litellm.types.llms.openai import ValidUserMessageContentTypes
|
||||
from litellm.utils import validate_chat_completion_user_messages
|
||||
|
||||
|
||||
class TestIsOrphanedToolResultAnthropicFormat:
|
||||
"""_is_orphaned_tool_result with Anthropic-format tool_use content blocks."""
|
||||
|
||||
def test_not_orphaned_when_anthropic_tool_use_present(self):
|
||||
"""Tool message is not orphaned when the latest assistant has a matching tool_use block."""
|
||||
sanitized_messages = [
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{"type": "text", "text": "Let me check that."},
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "call_123",
|
||||
"name": "get_weather",
|
||||
"input": {"city": "SF"},
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
current_message = {
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_123",
|
||||
"content": "72F sunny",
|
||||
}
|
||||
assert _is_orphaned_tool_result(current_message, sanitized_messages) is False
|
||||
|
||||
def test_orphaned_when_no_tool_use_in_assistant(self):
|
||||
"""Tool message is orphaned when the latest assistant has no tool_calls or tool_use."""
|
||||
sanitized_messages = [
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Just a plain text response",
|
||||
}
|
||||
]
|
||||
current_message = {
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_456",
|
||||
"content": "result data",
|
||||
}
|
||||
assert _is_orphaned_tool_result(current_message, sanitized_messages) is True
|
||||
|
||||
def test_not_orphaned_with_openai_format_tool_calls(self):
|
||||
"""Tool message is not orphaned when the assistant uses OpenAI-format tool_calls."""
|
||||
sanitized_messages = [
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_789",
|
||||
"type": "function",
|
||||
"function": {"name": "search", "arguments": "{}"},
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
current_message = {
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_789",
|
||||
"content": "search results",
|
||||
}
|
||||
assert _is_orphaned_tool_result(current_message, sanitized_messages) is False
|
||||
|
||||
|
||||
class TestToolResultUserMessageValidation:
|
||||
"""validate_chat_completion_user_messages with Anthropic-style tool_result blocks."""
|
||||
|
||||
def test_tool_result_in_valid_types(self):
|
||||
"""tool_result is an allowed user content type for mixed Anthropic-style payloads."""
|
||||
assert "tool_result" in ValidUserMessageContentTypes
|
||||
|
||||
def test_validate_accepts_tool_result_in_user_message(self):
|
||||
"""User messages may include type=tool_result content blocks (e.g. Cursor / Anthropic)."""
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Here is the tool output."},
|
||||
{
|
||||
"type": "tool_result",
|
||||
"tool_use_id": "call_abc",
|
||||
"content": "ok",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
out = validate_chat_completion_user_messages(messages)
|
||||
assert out == messages
|
||||
Loading…
Add table
Reference in a new issue