mirror of
https://github.com/browser-use/browser-use
synced 2026-05-06 17:52:15 +02:00
291 lines
12 KiB
Python
291 lines
12 KiB
Python
import logging
|
|
from typing import cast
|
|
|
|
from browser_use.agent.service import Agent
|
|
from browser_use.llm.anthropic.chat import ChatAnthropic
|
|
from browser_use.llm.anthropic.serializer import AnthropicMessageSerializer, NonSystemMessage
|
|
from browser_use.llm.messages import (
|
|
AssistantMessage,
|
|
BaseMessage,
|
|
ContentPartImageParam,
|
|
ContentPartTextParam,
|
|
Function,
|
|
ImageURL,
|
|
SystemMessage,
|
|
ToolCall,
|
|
UserMessage,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TestAnthropicCache:
|
|
"""Comprehensive test for Anthropic cache serialization."""
|
|
|
|
def test_cache_basic_functionality(self):
|
|
"""Test basic cache functionality for all message types."""
|
|
# Test cache with different message types
|
|
messages: list[BaseMessage] = [
|
|
SystemMessage(content='System message!', cache=True),
|
|
UserMessage(content='User message!', cache=True),
|
|
AssistantMessage(content='Assistant message!', cache=False),
|
|
]
|
|
|
|
anthropic_messages, system_message = AnthropicMessageSerializer.serialize_messages(messages)
|
|
|
|
assert len(anthropic_messages) == 2
|
|
assert isinstance(system_message, list)
|
|
assert isinstance(anthropic_messages[0]['content'], list)
|
|
assert isinstance(anthropic_messages[1]['content'], str)
|
|
|
|
# Test cache with assistant message
|
|
agent_messages: list[BaseMessage] = [
|
|
SystemMessage(content='System message!'),
|
|
UserMessage(content='User message!'),
|
|
AssistantMessage(content='Assistant message!', cache=True),
|
|
]
|
|
|
|
anthropic_messages, system_message = AnthropicMessageSerializer.serialize_messages(agent_messages)
|
|
|
|
assert isinstance(system_message, str)
|
|
assert isinstance(anthropic_messages[0]['content'], str)
|
|
assert isinstance(anthropic_messages[1]['content'], list)
|
|
|
|
def test_cache_with_tool_calls(self):
|
|
"""Test cache functionality with tool calls."""
|
|
tool_call = ToolCall(id='test_id', function=Function(name='test_function', arguments='{"arg": "value"}'))
|
|
|
|
# Assistant with tool calls and cache
|
|
assistant_with_tools = AssistantMessage(content='Assistant with tools', tool_calls=[tool_call], cache=True)
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([assistant_with_tools])
|
|
|
|
assert len(messages) == 1
|
|
assert isinstance(messages[0]['content'], list)
|
|
# Should have both text and tool_use blocks
|
|
assert len(messages[0]['content']) >= 2
|
|
|
|
def test_cache_with_images(self):
|
|
"""Test cache functionality with image content."""
|
|
user_with_image = UserMessage(
|
|
content=[
|
|
ContentPartTextParam(text='Here is an image:', type='text'),
|
|
ContentPartImageParam(image_url=ImageURL(url='https://example.com/image.jpg'), type='image_url'),
|
|
],
|
|
cache=True,
|
|
)
|
|
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([user_with_image])
|
|
|
|
assert len(messages) == 1
|
|
assert isinstance(messages[0]['content'], list)
|
|
assert len(messages[0]['content']) == 2
|
|
|
|
def test_cache_with_base64_images(self):
|
|
"""Test cache functionality with base64 images."""
|
|
base64_url = 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
|
|
|
|
user_with_base64 = UserMessage(
|
|
content=[
|
|
ContentPartTextParam(text='Base64 image:', type='text'),
|
|
ContentPartImageParam(image_url=ImageURL(url=base64_url), type='image_url'),
|
|
],
|
|
cache=True,
|
|
)
|
|
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([user_with_base64])
|
|
|
|
assert len(messages) == 1
|
|
assert isinstance(messages[0]['content'], list)
|
|
|
|
def test_cache_content_types(self):
|
|
"""Test different content types with cache."""
|
|
# String content with cache should become list
|
|
user_string_cached = UserMessage(content='String message', cache=True)
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([user_string_cached])
|
|
assert isinstance(messages[0]['content'], list)
|
|
|
|
# String content without cache should remain string
|
|
user_string_no_cache = UserMessage(content='String message', cache=False)
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([user_string_no_cache])
|
|
assert isinstance(messages[0]['content'], str)
|
|
|
|
# List content maintains list format regardless of cache
|
|
user_list_cached = UserMessage(content=[ContentPartTextParam(text='List message', type='text')], cache=True)
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([user_list_cached])
|
|
assert isinstance(messages[0]['content'], list)
|
|
|
|
user_list_no_cache = UserMessage(content=[ContentPartTextParam(text='List message', type='text')], cache=False)
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([user_list_no_cache])
|
|
assert isinstance(messages[0]['content'], list)
|
|
|
|
def test_assistant_cache_empty_content(self):
|
|
"""Test AssistantMessage with empty content and cache."""
|
|
# With cache
|
|
assistant_empty_cached = AssistantMessage(content=None, cache=True)
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([assistant_empty_cached])
|
|
|
|
assert len(messages) == 1
|
|
assert isinstance(messages[0]['content'], list)
|
|
|
|
# Without cache
|
|
assistant_empty_no_cache = AssistantMessage(content=None, cache=False)
|
|
messages, _ = AnthropicMessageSerializer.serialize_messages([assistant_empty_no_cache])
|
|
|
|
assert len(messages) == 1
|
|
assert isinstance(messages[0]['content'], str)
|
|
|
|
def test_mixed_cache_scenarios(self):
|
|
"""Test various combinations of cached and non-cached messages."""
|
|
messages_list: list[BaseMessage] = [
|
|
SystemMessage(content='System with cache', cache=True),
|
|
UserMessage(content='User with cache', cache=True),
|
|
AssistantMessage(content='Assistant without cache', cache=False),
|
|
UserMessage(content='User without cache', cache=False),
|
|
AssistantMessage(content='Assistant with cache', cache=True),
|
|
]
|
|
|
|
serialized_messages, system_message = AnthropicMessageSerializer.serialize_messages(messages_list)
|
|
|
|
# Check system message is cached (becomes list)
|
|
assert isinstance(system_message, list)
|
|
|
|
# Check serialized messages
|
|
assert len(serialized_messages) == 4
|
|
|
|
# User with cache should be list
|
|
assert isinstance(serialized_messages[0]['content'], list)
|
|
|
|
# Assistant without cache should be string
|
|
assert isinstance(serialized_messages[1]['content'], str)
|
|
|
|
# User without cache should be string
|
|
assert isinstance(serialized_messages[2]['content'], str)
|
|
|
|
# Assistant with cache should be list
|
|
assert isinstance(serialized_messages[3]['content'], list)
|
|
|
|
def test_system_message_cache_behavior(self):
|
|
"""Test SystemMessage specific cache behavior."""
|
|
# With cache
|
|
system_cached = SystemMessage(content='System message with cache', cache=True)
|
|
result = AnthropicMessageSerializer.serialize(system_cached)
|
|
assert isinstance(result, SystemMessage)
|
|
|
|
# Test serialization to string format
|
|
serialized_content = AnthropicMessageSerializer._serialize_content_to_str(result.content, use_cache=True)
|
|
assert isinstance(serialized_content, list)
|
|
|
|
# Without cache
|
|
system_no_cache = SystemMessage(content='System message without cache', cache=False)
|
|
result = AnthropicMessageSerializer.serialize(system_no_cache)
|
|
assert isinstance(result, SystemMessage)
|
|
|
|
serialized_content = AnthropicMessageSerializer._serialize_content_to_str(result.content, use_cache=False)
|
|
assert isinstance(serialized_content, str)
|
|
|
|
def test_agent_messages_integration(self):
|
|
"""Test integration with actual agent messages."""
|
|
agent = Agent(task='Hello, world!', llm=ChatAnthropic(''))
|
|
|
|
messages = agent.message_manager.get_messages()
|
|
anthropic_messages, system_message = AnthropicMessageSerializer.serialize_messages(messages)
|
|
|
|
# System message should be properly handled
|
|
assert system_message is not None
|
|
|
|
def test_cache_cleaning_last_message_only(self):
|
|
"""Test that only the last cache=True message remains cached."""
|
|
# Create multiple messages with cache=True
|
|
messages_list: list[BaseMessage] = [
|
|
UserMessage(content='First user message', cache=True),
|
|
AssistantMessage(content='First assistant message', cache=True),
|
|
UserMessage(content='Second user message', cache=True),
|
|
AssistantMessage(content='Second assistant message', cache=False),
|
|
UserMessage(content='Third user message', cache=True), # This should be the only one cached
|
|
]
|
|
|
|
# Test the cleaning method directly (only accepts non-system messages)
|
|
normal_messages = cast(list[NonSystemMessage], [msg for msg in messages_list if not isinstance(msg, SystemMessage)])
|
|
cleaned_messages = AnthropicMessageSerializer._clean_cache_messages(normal_messages)
|
|
|
|
# Verify only the last cache=True message remains cached
|
|
assert not cleaned_messages[0].cache # First user message should be uncached
|
|
assert not cleaned_messages[1].cache # First assistant message should be uncached
|
|
assert not cleaned_messages[2].cache # Second user message should be uncached
|
|
assert not cleaned_messages[3].cache # Second assistant message was already uncached
|
|
assert cleaned_messages[4].cache # Third user message should remain cached
|
|
|
|
# Test through serialize_messages
|
|
serialized_messages, system_message = AnthropicMessageSerializer.serialize_messages(messages_list)
|
|
|
|
# Count how many messages have list content (indicating caching)
|
|
cached_content_count = sum(1 for msg in serialized_messages if isinstance(msg['content'], list))
|
|
|
|
# Only one message should have cached content
|
|
assert cached_content_count == 1
|
|
|
|
# The last message should be the cached one
|
|
assert isinstance(serialized_messages[-1]['content'], list)
|
|
|
|
def test_cache_cleaning_with_system_message(self):
|
|
"""Test that system messages are not affected by cache cleaning logic."""
|
|
messages_list: list[BaseMessage] = [
|
|
SystemMessage(content='System message', cache=True), # System messages are handled separately
|
|
UserMessage(content='First user message', cache=True),
|
|
AssistantMessage(content='Assistant message', cache=True), # This should be the only normal message cached
|
|
]
|
|
|
|
# Test through serialize_messages to see the full integration
|
|
serialized_messages, system_message = AnthropicMessageSerializer.serialize_messages(messages_list)
|
|
|
|
# System message should be cached
|
|
assert isinstance(system_message, list)
|
|
|
|
# Only one normal message should have cached content (the last one)
|
|
cached_content_count = sum(1 for msg in serialized_messages if isinstance(msg['content'], list))
|
|
assert cached_content_count == 1
|
|
|
|
# The last message should be the cached one
|
|
assert isinstance(serialized_messages[-1]['content'], list)
|
|
|
|
def test_cache_cleaning_no_cached_messages(self):
|
|
"""Test that messages without cache=True are not affected."""
|
|
normal_messages_list = [
|
|
UserMessage(content='User message 1', cache=False),
|
|
AssistantMessage(content='Assistant message 1', cache=False),
|
|
UserMessage(content='User message 2', cache=False),
|
|
]
|
|
|
|
cleaned_messages = AnthropicMessageSerializer._clean_cache_messages(normal_messages_list)
|
|
|
|
# All messages should remain uncached
|
|
for msg in cleaned_messages:
|
|
assert not msg.cache
|
|
|
|
def test_max_4_cache_blocks(self):
|
|
"""Test that the max number of cache blocks is 4."""
|
|
agent = Agent(task='Hello, world!', llm=ChatAnthropic(''))
|
|
messages = agent.message_manager.get_messages()
|
|
anthropic_messages, system_message = AnthropicMessageSerializer.serialize_messages(messages)
|
|
|
|
logger.info(anthropic_messages)
|
|
logger.info(system_message)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_instance = TestAnthropicCache()
|
|
test_instance.test_cache_basic_functionality()
|
|
test_instance.test_cache_with_tool_calls()
|
|
test_instance.test_cache_with_images()
|
|
test_instance.test_cache_with_base64_images()
|
|
test_instance.test_cache_content_types()
|
|
test_instance.test_assistant_cache_empty_content()
|
|
test_instance.test_mixed_cache_scenarios()
|
|
test_instance.test_system_message_cache_behavior()
|
|
test_instance.test_agent_messages_integration()
|
|
test_instance.test_cache_cleaning_last_message_only()
|
|
test_instance.test_cache_cleaning_with_system_message()
|
|
test_instance.test_cache_cleaning_no_cached_messages()
|
|
test_instance.test_max_4_cache_blocks()
|
|
print('All cache tests passed!')
|