Spaces:
Build error
Build error
"""Tests for the auto-generate title functionality.""" | |
from datetime import datetime, timezone | |
from unittest.mock import AsyncMock, MagicMock, patch | |
import pytest | |
from openhands.core.config.llm_config import LLMConfig | |
from openhands.core.config.openhands_config import OpenHandsConfig | |
from openhands.events.action import MessageAction | |
from openhands.events.event import EventSource | |
from openhands.events.stream import EventStream | |
from openhands.server.conversation_manager.standalone_conversation_manager import ( | |
StandaloneConversationManager, | |
) | |
from openhands.server.monitoring import MonitoringListener | |
from openhands.storage.data_models.settings import Settings | |
from openhands.storage.memory import InMemoryFileStore | |
from openhands.utils.conversation_summary import auto_generate_title | |
async def test_auto_generate_title_with_llm(): | |
"""Test auto-generating a title using LLM.""" | |
# Mock dependencies | |
file_store = InMemoryFileStore() | |
# Create test conversation with a user message | |
conversation_id = 'test-conversation' | |
user_id = 'test-user' | |
# Create a mock event | |
user_message = MessageAction( | |
content='Help me write a Python script to analyze data' | |
) | |
user_message._source = EventSource.USER | |
user_message._id = 1 | |
user_message._timestamp = datetime.now(timezone.utc).isoformat() | |
# Mock the EventStream class | |
with patch( | |
'openhands.utils.conversation_summary.EventStream' | |
) as mock_event_stream_cls: | |
# Configure the mock event stream to return our test message | |
mock_event_stream = MagicMock(spec=EventStream) | |
mock_event_stream.get_events.return_value = [user_message] | |
mock_event_stream_cls.return_value = mock_event_stream | |
# Mock the LLM response | |
with patch('openhands.utils.conversation_summary.LLM') as mock_llm_cls: | |
mock_llm = mock_llm_cls.return_value | |
mock_response = MagicMock() | |
mock_response.choices = [MagicMock()] | |
mock_response.choices[0].message.content = 'Python Data Analysis Script' | |
mock_llm.completion.return_value = mock_response | |
# Create test settings with LLM config | |
settings = Settings( | |
llm_model='test-model', | |
llm_api_key='test-key', | |
llm_base_url='test-url', | |
) | |
# Call the auto_generate_title function directly | |
title = await auto_generate_title( | |
conversation_id, user_id, file_store, settings | |
) | |
# Verify the result | |
assert title == 'Python Data Analysis Script' | |
# Verify EventStream was created with the correct parameters | |
mock_event_stream_cls.assert_called_once_with( | |
conversation_id, file_store, user_id | |
) | |
# Verify LLM was called with appropriate parameters | |
mock_llm_cls.assert_called_once_with( | |
LLMConfig( | |
model='test-model', | |
api_key='test-key', | |
base_url='test-url', | |
) | |
) | |
mock_llm.completion.assert_called_once() | |
async def test_auto_generate_title_fallback(): | |
"""Test auto-generating a title with fallback to truncation when LLM fails.""" | |
# Mock dependencies | |
file_store = InMemoryFileStore() | |
# Create test conversation with a user message | |
conversation_id = 'test-conversation' | |
user_id = 'test-user' | |
# Create a mock event with a long message | |
long_message = 'This is a very long message that should be truncated when used as a title because it exceeds the maximum length allowed for titles' | |
user_message = MessageAction(content=long_message) | |
user_message._source = EventSource.USER | |
user_message._id = 1 | |
user_message._timestamp = datetime.now(timezone.utc).isoformat() | |
# Mock the EventStream class | |
with patch( | |
'openhands.utils.conversation_summary.EventStream' | |
) as mock_event_stream_cls: | |
# Configure the mock event stream to return our test message | |
mock_event_stream = MagicMock(spec=EventStream) | |
mock_event_stream.get_events.return_value = [user_message] | |
mock_event_stream_cls.return_value = mock_event_stream | |
# Mock the LLM to raise an exception | |
with patch('openhands.utils.conversation_summary.LLM') as mock_llm_cls: | |
mock_llm = mock_llm_cls.return_value | |
mock_llm.completion.side_effect = Exception('Test error') | |
# Create test settings with LLM config | |
settings = Settings( | |
llm_model='test-model', | |
llm_api_key='test-key', | |
llm_base_url='test-url', | |
) | |
# Call the auto_generate_title function directly | |
title = await auto_generate_title( | |
conversation_id, user_id, file_store, settings | |
) | |
# Verify the result is a truncated version of the message | |
assert title == 'This is a very long message th...' | |
assert len(title) <= 35 | |
# Verify EventStream was created with the correct parameters | |
mock_event_stream_cls.assert_called_once_with( | |
conversation_id, file_store, user_id | |
) | |
async def test_auto_generate_title_no_messages(): | |
"""Test auto-generating a title when there are no user messages.""" | |
# Mock dependencies | |
file_store = InMemoryFileStore() | |
# Create test conversation with no messages | |
conversation_id = 'test-conversation' | |
user_id = 'test-user' | |
# Mock the EventStream class | |
with patch( | |
'openhands.utils.conversation_summary.EventStream' | |
) as mock_event_stream_cls: | |
# Configure the mock event stream to return no events | |
mock_event_stream = MagicMock(spec=EventStream) | |
mock_event_stream.get_events.return_value = [] | |
mock_event_stream_cls.return_value = mock_event_stream | |
# Create test settings | |
settings = Settings( | |
llm_model='test-model', | |
llm_api_key='test-key', | |
llm_base_url='test-url', | |
) | |
# Call the auto_generate_title function directly | |
title = await auto_generate_title( | |
conversation_id, user_id, file_store, settings | |
) | |
# Verify the result is empty | |
assert title == '' | |
# Verify EventStream was created with the correct parameters | |
mock_event_stream_cls.assert_called_once_with( | |
conversation_id, file_store, user_id | |
) | |
async def test_update_conversation_with_title(): | |
"""Test that _update_conversation_for_event updates the title when needed.""" | |
# Mock dependencies | |
sio = MagicMock() | |
sio.emit = AsyncMock() | |
file_store = InMemoryFileStore() | |
server_config = MagicMock() | |
# Create test conversation | |
conversation_id = 'test-conversation' | |
user_id = 'test-user' | |
# Create test settings | |
settings = Settings( | |
llm_model='test-model', | |
llm_api_key='test-key', | |
llm_base_url='test-url', | |
) | |
# Mock the conversation store and metadata | |
mock_conversation_store = AsyncMock() | |
mock_metadata = MagicMock() | |
mock_metadata.title = f'Conversation {conversation_id[:5]}' # Default title | |
mock_conversation_store.get_metadata.return_value = mock_metadata | |
# Create the conversation manager | |
manager = StandaloneConversationManager( | |
sio=sio, | |
config=OpenHandsConfig(), | |
file_store=file_store, | |
server_config=server_config, | |
monitoring_listener=MonitoringListener(), | |
) | |
# Mock the _get_conversation_store method | |
manager._get_conversation_store = AsyncMock(return_value=mock_conversation_store) | |
# Mock the auto_generate_title function | |
with patch( | |
'openhands.server.conversation_manager.standalone_conversation_manager.auto_generate_title', | |
AsyncMock(return_value='Generated Title'), | |
): | |
# Call the method | |
await manager._update_conversation_for_event(user_id, conversation_id, settings) | |
# Verify the title was updated | |
assert mock_metadata.title == 'Generated Title' | |
# Verify the socket.io emit was called with the correct parameters | |
sio.emit.assert_called_once() | |
call_args = sio.emit.call_args[0] | |
assert call_args[0] == 'oh_event' | |
assert call_args[1]['status_update'] is True | |
assert call_args[1]['type'] == 'info' | |
assert call_args[1]['message'] == conversation_id | |
assert call_args[1]['conversation_title'] == 'Generated Title' | |