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' | |