mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(anthropic): preserve server_tool_use and web_search_tool_result in multi-turn conversations (#17746)
- Extract web_search_tool_result blocks in extract_response_content() - Store web_search_results in provider_specific_fields for round-trip - Detect srvtoolu_ prefix to reconstruct as server_tool_use (not tool_use) - Add corresponding web_search_tool_result after server_tool_use blocks This ensures multi-turn conversations with Anthropic web search + custom tools work correctly without Anthropic expecting tool_result for server- side tool executions.
This commit is contained in:
parent
9fa6c51678
commit
01dec55c2f
4 changed files with 499 additions and 33 deletions
|
|
@ -1623,7 +1623,8 @@ def convert_function_to_anthropic_tool_invoke(
|
|||
|
||||
def convert_to_anthropic_tool_invoke(
|
||||
tool_calls: List[ChatCompletionAssistantToolCall],
|
||||
) -> List[AnthropicMessagesToolUseParam]:
|
||||
web_search_results: Optional[List[Any]] = None,
|
||||
) -> List[Union[AnthropicMessagesToolUseParam, Dict[str, Any]]]:
|
||||
"""
|
||||
OpenAI tool invokes:
|
||||
{
|
||||
|
|
@ -1659,38 +1660,68 @@ def convert_to_anthropic_tool_invoke(
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
For server-side tools (web_search), we need to reconstruct:
|
||||
- server_tool_use blocks (id starts with "srvtoolu_")
|
||||
- web_search_tool_result blocks (from provider_specific_fields)
|
||||
|
||||
Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
"""
|
||||
anthropic_tool_invoke = []
|
||||
anthropic_tool_invoke: List[Union[AnthropicMessagesToolUseParam, Dict[str, Any]]] = []
|
||||
|
||||
for tool in tool_calls:
|
||||
if not get_attribute_or_key(tool, "type") == "function":
|
||||
continue
|
||||
|
||||
_anthropic_tool_use_param = AnthropicMessagesToolUseParam(
|
||||
type="tool_use",
|
||||
id=cast(str, get_attribute_or_key(tool, "id")),
|
||||
name=cast(
|
||||
str,
|
||||
get_attribute_or_key(get_attribute_or_key(tool, "function"), "name"),
|
||||
),
|
||||
input=json.loads(
|
||||
get_attribute_or_key(
|
||||
get_attribute_or_key(tool, "function"), "arguments"
|
||||
)
|
||||
),
|
||||
tool_id = cast(str, get_attribute_or_key(tool, "id"))
|
||||
tool_name = cast(
|
||||
str,
|
||||
get_attribute_or_key(get_attribute_or_key(tool, "function"), "name"),
|
||||
)
|
||||
tool_input = json.loads(
|
||||
get_attribute_or_key(
|
||||
get_attribute_or_key(tool, "function"), "arguments"
|
||||
)
|
||||
)
|
||||
|
||||
_content_element = add_cache_control_to_content(
|
||||
anthropic_content_element=_anthropic_tool_use_param,
|
||||
original_content_element=dict(tool),
|
||||
)
|
||||
# Check if this is a server-side tool (web_search, tool_search, etc.)
|
||||
# Server tool IDs start with "srvtoolu_"
|
||||
if tool_id.startswith("srvtoolu_"):
|
||||
# Create server_tool_use block instead of tool_use
|
||||
_anthropic_server_tool_use: Dict[str, Any] = {
|
||||
"type": "server_tool_use",
|
||||
"id": tool_id,
|
||||
"name": tool_name,
|
||||
"input": tool_input,
|
||||
}
|
||||
anthropic_tool_invoke.append(_anthropic_server_tool_use)
|
||||
|
||||
if "cache_control" in _content_element:
|
||||
_anthropic_tool_use_param["cache_control"] = _content_element[
|
||||
"cache_control"
|
||||
]
|
||||
# Add corresponding web_search_tool_result if available
|
||||
if web_search_results:
|
||||
for result in web_search_results:
|
||||
if result.get("tool_use_id") == tool_id:
|
||||
anthropic_tool_invoke.append(result)
|
||||
break
|
||||
else:
|
||||
# Regular tool_use
|
||||
_anthropic_tool_use_param = AnthropicMessagesToolUseParam(
|
||||
type="tool_use",
|
||||
id=tool_id,
|
||||
name=tool_name,
|
||||
input=tool_input,
|
||||
)
|
||||
|
||||
anthropic_tool_invoke.append(_anthropic_tool_use_param)
|
||||
_content_element = add_cache_control_to_content(
|
||||
anthropic_content_element=_anthropic_tool_use_param,
|
||||
original_content_element=dict(tool),
|
||||
)
|
||||
|
||||
if "cache_control" in _content_element:
|
||||
_anthropic_tool_use_param["cache_control"] = _content_element[
|
||||
"cache_control"
|
||||
]
|
||||
|
||||
anthropic_tool_invoke.append(_anthropic_tool_use_param)
|
||||
|
||||
return anthropic_tool_invoke
|
||||
|
||||
|
|
@ -2052,8 +2083,15 @@ def anthropic_messages_pt( # noqa: PLR0915
|
|||
if (
|
||||
assistant_tool_calls is not None
|
||||
): # support assistant tool invoke conversion
|
||||
# Get web_search_results from provider_specific_fields for server_tool_use reconstruction
|
||||
# Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
_provider_specific_fields = assistant_content_block.get("provider_specific_fields") or {}
|
||||
_web_search_results = _provider_specific_fields.get("web_search_results")
|
||||
assistant_content.extend(
|
||||
convert_to_anthropic_tool_invoke(assistant_tool_calls)
|
||||
convert_to_anthropic_tool_invoke(
|
||||
assistant_tool_calls,
|
||||
web_search_results=_web_search_results,
|
||||
)
|
||||
)
|
||||
|
||||
assistant_function_call = assistant_content_block.get("function_call")
|
||||
|
|
|
|||
|
|
@ -1082,6 +1082,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
],
|
||||
Optional[str],
|
||||
List[ChatCompletionToolCallChunk],
|
||||
Optional[List[Any]],
|
||||
]:
|
||||
text_content = ""
|
||||
citations: Optional[List[Any]] = None
|
||||
|
|
@ -1092,6 +1093,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
] = None
|
||||
reasoning_content: Optional[str] = None
|
||||
tool_calls: List[ChatCompletionToolCallChunk] = []
|
||||
web_search_results: Optional[List[Any]] = None
|
||||
for idx, content in enumerate(completion_response["content"]):
|
||||
if content["type"] == "text":
|
||||
text_content += content["text"]
|
||||
|
|
@ -1117,6 +1119,11 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
# This block contains tool_references that were discovered
|
||||
# We don't need to include this in the response as it's internal metadata
|
||||
pass
|
||||
## WEB SEARCH TOOL RESULT - preserve web search results for multi-turn conversations
|
||||
elif content["type"] == "web_search_tool_result":
|
||||
if web_search_results is None:
|
||||
web_search_results = []
|
||||
web_search_results.append(content)
|
||||
elif content.get("thinking", None) is not None:
|
||||
if thinking_blocks is None:
|
||||
thinking_blocks = []
|
||||
|
|
@ -1148,7 +1155,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
if thinking_content is not None:
|
||||
reasoning_content += thinking_content
|
||||
|
||||
return text_content, citations, thinking_blocks, reasoning_content, tool_calls
|
||||
return text_content, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results
|
||||
|
||||
def calculate_usage(
|
||||
self,
|
||||
|
|
@ -1288,6 +1295,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
thinking_blocks,
|
||||
reasoning_content,
|
||||
tool_calls,
|
||||
web_search_results,
|
||||
) = self.extract_response_content(completion_response=completion_response)
|
||||
|
||||
if (
|
||||
|
|
@ -1307,6 +1315,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
}
|
||||
if context_management is not None:
|
||||
provider_specific_fields["context_management"] = context_management
|
||||
if web_search_results is not None:
|
||||
provider_specific_fields["web_search_results"] = web_search_results
|
||||
|
||||
_message = litellm.Message(
|
||||
tool_calls=tool_calls,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import (
|
|||
anthropic_pt,
|
||||
claude_2_1_pt,
|
||||
convert_to_anthropic_image_obj,
|
||||
convert_to_anthropic_tool_invoke,
|
||||
convert_url_to_base64,
|
||||
create_anthropic_image_param,
|
||||
llama_2_chat_pt,
|
||||
|
|
@ -947,3 +948,217 @@ def test_ollama_pt():
|
|||
]
|
||||
prompt = ollama_pt(model="ollama/llama3.1", messages=messages)
|
||||
print(prompt)
|
||||
|
||||
|
||||
# ============ Server Tool Use Reconstruction Tests ============
|
||||
# Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
|
||||
|
||||
def test_convert_to_anthropic_tool_invoke_regular_tool():
|
||||
"""Test that regular tool_use is converted correctly."""
|
||||
tool_calls = [
|
||||
{
|
||||
"id": "toolu_01ABC123",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": '{"location": "San Francisco"}'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
result = convert_to_anthropic_tool_invoke(tool_calls)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0]["type"] == "tool_use"
|
||||
assert result[0]["id"] == "toolu_01ABC123"
|
||||
assert result[0]["name"] == "get_weather"
|
||||
assert result[0]["input"] == {"location": "San Francisco"}
|
||||
|
||||
|
||||
def test_convert_to_anthropic_tool_invoke_server_tool():
|
||||
"""
|
||||
Test that server_tool_use (srvtoolu_) is reconstructed as server_tool_use.
|
||||
|
||||
Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
"""
|
||||
tool_calls = [
|
||||
{
|
||||
"id": "srvtoolu_01ABC123",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "web_search",
|
||||
"arguments": '{"query": "elephant weight"}'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
result = convert_to_anthropic_tool_invoke(tool_calls)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0]["type"] == "server_tool_use" # NOT tool_use
|
||||
assert result[0]["id"] == "srvtoolu_01ABC123"
|
||||
assert result[0]["name"] == "web_search"
|
||||
assert result[0]["input"] == {"query": "elephant weight"}
|
||||
|
||||
|
||||
def test_convert_to_anthropic_tool_invoke_with_web_search_results():
|
||||
"""
|
||||
Test that web_search_tool_result is included after server_tool_use.
|
||||
|
||||
Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
"""
|
||||
tool_calls = [
|
||||
{
|
||||
"id": "srvtoolu_01ABC123",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "web_search",
|
||||
"arguments": '{"query": "elephant weight"}'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
web_search_results = [
|
||||
{
|
||||
"type": "web_search_tool_result",
|
||||
"tool_use_id": "srvtoolu_01ABC123",
|
||||
"content": [
|
||||
{
|
||||
"type": "web_search_result",
|
||||
"url": "https://example.com",
|
||||
"title": "Elephant Facts",
|
||||
"snippet": "Elephants weigh 5000 kg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
result = convert_to_anthropic_tool_invoke(tool_calls, web_search_results=web_search_results)
|
||||
|
||||
assert len(result) == 2
|
||||
# First: server_tool_use
|
||||
assert result[0]["type"] == "server_tool_use"
|
||||
assert result[0]["id"] == "srvtoolu_01ABC123"
|
||||
# Second: web_search_tool_result
|
||||
assert result[1]["type"] == "web_search_tool_result"
|
||||
assert result[1]["tool_use_id"] == "srvtoolu_01ABC123"
|
||||
|
||||
|
||||
def test_convert_to_anthropic_tool_invoke_mixed_tools():
|
||||
"""
|
||||
Test that mixed server and regular tools are reconstructed correctly.
|
||||
|
||||
Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
"""
|
||||
tool_calls = [
|
||||
{
|
||||
"id": "srvtoolu_01ABC123",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "web_search",
|
||||
"arguments": '{"query": "elephant weight"}'
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "toolu_01XYZ789",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "add_numbers",
|
||||
"arguments": '{"a": 5000, "b": 100}'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
web_search_results = [
|
||||
{
|
||||
"type": "web_search_tool_result",
|
||||
"tool_use_id": "srvtoolu_01ABC123",
|
||||
"content": [{"url": "https://example.com", "title": "Test"}]
|
||||
}
|
||||
]
|
||||
|
||||
result = convert_to_anthropic_tool_invoke(tool_calls, web_search_results=web_search_results)
|
||||
|
||||
assert len(result) == 3
|
||||
# First: server_tool_use
|
||||
assert result[0]["type"] == "server_tool_use"
|
||||
assert result[0]["id"] == "srvtoolu_01ABC123"
|
||||
# Second: web_search_tool_result
|
||||
assert result[1]["type"] == "web_search_tool_result"
|
||||
# Third: regular tool_use
|
||||
assert result[2]["type"] == "tool_use"
|
||||
assert result[2]["id"] == "toolu_01XYZ789"
|
||||
|
||||
|
||||
def test_anthropic_messages_pt_with_server_tool_use():
|
||||
"""
|
||||
Test that anthropic_messages_pt correctly reconstructs server_tool_use from provider_specific_fields.
|
||||
|
||||
Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
"""
|
||||
messages = [
|
||||
{"role": "user", "content": "Search for elephant weight and add 100"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Let me search for that.",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "srvtoolu_01ABC123",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "web_search",
|
||||
"arguments": '{"query": "elephant weight"}'
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "toolu_01XYZ789",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "add_numbers",
|
||||
"arguments": '{"a": 5000, "b": 100}'
|
||||
}
|
||||
}
|
||||
],
|
||||
"provider_specific_fields": {
|
||||
"web_search_results": [
|
||||
{
|
||||
"type": "web_search_tool_result",
|
||||
"tool_use_id": "srvtoolu_01ABC123",
|
||||
"content": [{"url": "https://example.com", "title": "Test", "snippet": "5000 kg"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "toolu_01XYZ789",
|
||||
"content": "5100"
|
||||
}
|
||||
]
|
||||
|
||||
result = anthropic_messages_pt(messages, model="claude-sonnet-4-5", llm_provider="anthropic")
|
||||
|
||||
# Find the assistant message
|
||||
assistant_msg = next(m for m in result if m["role"] == "assistant")
|
||||
content = assistant_msg["content"]
|
||||
|
||||
# Should have: text, server_tool_use, web_search_tool_result, tool_use
|
||||
types = [c.get("type") for c in content]
|
||||
assert "text" in types
|
||||
assert "server_tool_use" in types
|
||||
assert "web_search_tool_result" in types
|
||||
assert "tool_use" in types
|
||||
|
||||
# Verify server_tool_use
|
||||
server_tool = next(c for c in content if c.get("type") == "server_tool_use")
|
||||
assert server_tool["id"] == "srvtoolu_01ABC123"
|
||||
|
||||
# Verify web_search_tool_result comes after server_tool_use
|
||||
server_idx = types.index("server_tool_use")
|
||||
web_result_idx = types.index("web_search_tool_result")
|
||||
assert web_result_idx == server_idx + 1
|
||||
|
||||
# Verify regular tool_use
|
||||
tool_use = next(c for c in content if c.get("type") == "tool_use")
|
||||
assert tool_use["id"] == "toolu_01XYZ789"
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ def test_extract_response_content_with_citations():
|
|||
},
|
||||
}
|
||||
|
||||
_, citations, _, _, _ = config.extract_response_content(completion_response)
|
||||
_, citations, _, _, _, _ = config.extract_response_content(completion_response)
|
||||
assert citations == [
|
||||
[
|
||||
{
|
||||
|
|
@ -286,6 +286,204 @@ def test_web_search_tool_transformation_with_search_context_size(
|
|||
assert anthropic_web_search_tool["max_uses"] == expected_max_uses
|
||||
|
||||
|
||||
def test_web_search_tool_result_extraction():
|
||||
"""
|
||||
Test that web_search_tool_result blocks are correctly extracted and preserved.
|
||||
|
||||
Fixes: https://github.com/BerriAI/litellm/issues/17737
|
||||
- web_search_tool_result was being dropped entirely from the response
|
||||
- This caused multi-turn conversations to fail because the web search results
|
||||
were not available for reconstruction
|
||||
"""
|
||||
config = AnthropicConfig()
|
||||
|
||||
# Simulating actual Anthropic API response with web search
|
||||
completion_response = {
|
||||
"id": "msg_web_search_test",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "server_tool_use",
|
||||
"id": "srvtoolu_01ABC123",
|
||||
"name": "web_search",
|
||||
"input": {"query": "average weight african elephant kg"}
|
||||
},
|
||||
{
|
||||
"type": "web_search_tool_result",
|
||||
"tool_use_id": "srvtoolu_01ABC123",
|
||||
"content": [
|
||||
{
|
||||
"type": "web_search_result",
|
||||
"url": "https://example.com/elephants",
|
||||
"title": "African Elephant Facts",
|
||||
"encrypted_content": "encrypted_data_here",
|
||||
"page_age": "2024-01-15",
|
||||
"snippet": "Adult African elephants weigh between 4,000-6,000 kg..."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Based on my search, African elephants weigh around 5,000 kg."
|
||||
},
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01XYZ789",
|
||||
"name": "add_numbers",
|
||||
"input": {"a": 5000, "b": 100}
|
||||
}
|
||||
],
|
||||
"stop_reason": "tool_use",
|
||||
"usage": {
|
||||
"input_tokens": 100,
|
||||
"output_tokens": 50,
|
||||
"server_tool_use": {"web_search_requests": 1}
|
||||
}
|
||||
}
|
||||
|
||||
text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results = config.extract_response_content(
|
||||
completion_response
|
||||
)
|
||||
|
||||
# Verify text extraction
|
||||
assert "Based on my search" in text
|
||||
assert "5,000 kg" in text
|
||||
|
||||
# Verify tool calls (should have both server_tool_use and tool_use)
|
||||
assert len(tool_calls) == 2
|
||||
assert tool_calls[0]["id"] == "srvtoolu_01ABC123"
|
||||
assert tool_calls[0]["function"]["name"] == "web_search"
|
||||
assert tool_calls[1]["id"] == "toolu_01XYZ789"
|
||||
assert tool_calls[1]["function"]["name"] == "add_numbers"
|
||||
|
||||
# Verify web_search_results is extracted (THIS WAS THE BUG - it was None before the fix)
|
||||
assert web_search_results is not None
|
||||
assert len(web_search_results) == 1
|
||||
assert web_search_results[0]["type"] == "web_search_tool_result"
|
||||
assert web_search_results[0]["tool_use_id"] == "srvtoolu_01ABC123"
|
||||
assert len(web_search_results[0]["content"]) == 1
|
||||
assert web_search_results[0]["content"][0]["url"] == "https://example.com/elephants"
|
||||
assert web_search_results[0]["content"][0]["title"] == "African Elephant Facts"
|
||||
|
||||
|
||||
def test_web_search_tool_result_in_provider_specific_fields():
|
||||
"""
|
||||
Test that web_search_results is included in provider_specific_fields.
|
||||
|
||||
This ensures users can access the web search results via:
|
||||
response.choices[0].message.provider_specific_fields["web_search_results"]
|
||||
"""
|
||||
import httpx
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
config = AnthropicConfig()
|
||||
|
||||
completion_response = {
|
||||
"id": "msg_web_search_provider_fields",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"content": [
|
||||
{
|
||||
"type": "server_tool_use",
|
||||
"id": "srvtoolu_provider_test",
|
||||
"name": "web_search",
|
||||
"input": {"query": "test query"}
|
||||
},
|
||||
{
|
||||
"type": "web_search_tool_result",
|
||||
"tool_use_id": "srvtoolu_provider_test",
|
||||
"content": [
|
||||
{
|
||||
"type": "web_search_result",
|
||||
"url": "https://example.com/test",
|
||||
"title": "Test Result",
|
||||
"snippet": "Test snippet content"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Here is the result."
|
||||
}
|
||||
],
|
||||
"stop_reason": "end_turn",
|
||||
"usage": {
|
||||
"input_tokens": 50,
|
||||
"output_tokens": 25,
|
||||
"server_tool_use": {"web_search_requests": 1}
|
||||
}
|
||||
}
|
||||
|
||||
raw_response = httpx.Response(status_code=200, headers={})
|
||||
model_response = ModelResponse()
|
||||
|
||||
result = config.transform_parsed_response(
|
||||
completion_response=completion_response,
|
||||
raw_response=raw_response,
|
||||
model_response=model_response,
|
||||
json_mode=False,
|
||||
prefix_prompt=None,
|
||||
)
|
||||
|
||||
# Verify web_search_results is in provider_specific_fields
|
||||
provider_fields = result.choices[0].message.provider_specific_fields
|
||||
assert provider_fields is not None
|
||||
assert "web_search_results" in provider_fields
|
||||
assert len(provider_fields["web_search_results"]) == 1
|
||||
assert provider_fields["web_search_results"][0]["type"] == "web_search_tool_result"
|
||||
assert provider_fields["web_search_results"][0]["tool_use_id"] == "srvtoolu_provider_test"
|
||||
|
||||
|
||||
def test_multiple_web_search_tool_results():
|
||||
"""
|
||||
Test that multiple web_search_tool_result blocks are all extracted.
|
||||
"""
|
||||
config = AnthropicConfig()
|
||||
|
||||
completion_response = {
|
||||
"content": [
|
||||
{
|
||||
"type": "server_tool_use",
|
||||
"id": "srvtoolu_search1",
|
||||
"name": "web_search",
|
||||
"input": {"query": "african elephant weight"}
|
||||
},
|
||||
{
|
||||
"type": "web_search_tool_result",
|
||||
"tool_use_id": "srvtoolu_search1",
|
||||
"content": [{"type": "web_search_result", "url": "https://example1.com", "title": "Result 1", "snippet": "First result"}]
|
||||
},
|
||||
{
|
||||
"type": "server_tool_use",
|
||||
"id": "srvtoolu_search2",
|
||||
"name": "web_search",
|
||||
"input": {"query": "asian elephant weight"}
|
||||
},
|
||||
{
|
||||
"type": "web_search_tool_result",
|
||||
"tool_use_id": "srvtoolu_search2",
|
||||
"content": [{"type": "web_search_result", "url": "https://example2.com", "title": "Result 2", "snippet": "Second result"}]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Found information about both elephants."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results = config.extract_response_content(
|
||||
completion_response
|
||||
)
|
||||
|
||||
# Verify both web_search_tool_results are extracted
|
||||
assert web_search_results is not None
|
||||
assert len(web_search_results) == 2
|
||||
assert web_search_results[0]["tool_use_id"] == "srvtoolu_search1"
|
||||
assert web_search_results[1]["tool_use_id"] == "srvtoolu_search2"
|
||||
|
||||
|
||||
def test_add_code_execution_tool():
|
||||
config = AnthropicConfig()
|
||||
|
||||
|
|
@ -693,13 +891,14 @@ def test_server_tool_use_in_response():
|
|||
]
|
||||
}
|
||||
|
||||
text, citations, thinking_blocks, reasoning_content, tool_calls = config.extract_response_content(
|
||||
text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results = config.extract_response_content(
|
||||
completion_response
|
||||
)
|
||||
|
||||
|
||||
assert len(tool_calls) == 1
|
||||
assert tool_calls[0]["id"] == "srvtoolu_01ABC123"
|
||||
assert tool_calls[0]["function"]["name"] == "tool_search_tool_regex"
|
||||
assert web_search_results is None
|
||||
|
||||
|
||||
def test_tool_search_usage_tracking():
|
||||
|
|
@ -820,18 +1019,21 @@ def test_tool_search_complete_response_parsing():
|
|||
}
|
||||
|
||||
# Extract content
|
||||
text, citations, thinking_blocks, reasoning_content, tool_calls = config.extract_response_content(
|
||||
text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results = config.extract_response_content(
|
||||
completion_response
|
||||
)
|
||||
|
||||
|
||||
# Verify text extraction (should concatenate both text blocks)
|
||||
assert "I'll search for weather-related tools" in text
|
||||
assert "Great! I found a weather tool" in text
|
||||
|
||||
|
||||
# Verify tool calls (should have both server_tool_use and tool_use)
|
||||
assert len(tool_calls) == 2
|
||||
assert tool_calls[0]["function"]["name"] == "tool_search_tool_regex"
|
||||
assert tool_calls[1]["function"]["name"] == "get_weather"
|
||||
|
||||
# Verify web_search_results is None (this response has tool_search, not web_search)
|
||||
assert web_search_results is None
|
||||
|
||||
# Verify usage calculation counts tool_search_requests from content
|
||||
usage = config.calculate_usage(
|
||||
|
|
@ -937,14 +1139,15 @@ def test_caller_field_in_response():
|
|||
"usage": {"input_tokens": 100, "output_tokens": 50}
|
||||
}
|
||||
|
||||
text, citations, thinking, reasoning, tool_calls = config.extract_response_content(completion_response)
|
||||
|
||||
text, citations, thinking, reasoning, tool_calls, web_search_results = config.extract_response_content(completion_response)
|
||||
|
||||
assert len(tool_calls) == 1
|
||||
assert tool_calls[0]["id"] == "toolu_123"
|
||||
assert tool_calls[0]["function"]["name"] == "query_database"
|
||||
assert "caller" in tool_calls[0]
|
||||
assert tool_calls[0]["caller"]["type"] == "code_execution_20250825"
|
||||
assert tool_calls[0]["caller"]["tool_id"] == "srvtoolu_abc"
|
||||
assert web_search_results is None
|
||||
|
||||
|
||||
def test_code_execution_20250825_tool_type():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue