fix(backend): initial titles show full uuids instead of shortened uuids (v1 conversations) (#12020)

This commit is contained in:
Hiep Le 2025-12-15 00:39:32 +07:00 committed by GitHub
parent f2def8fd7f
commit b937d344db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 115 additions and 3 deletions

View File

@ -270,7 +270,7 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
user_id = await self.user_context.get_user_id()
app_conversation_info = AppConversationInfo(
id=info.id,
title=f'Conversation {info.id.hex}',
title=f'Conversation {info.id.hex[:5]}',
sandbox_id=sandbox.id,
created_by_user_id=user_id,
llm_model=start_conversation_request.agent.llm.model,

View File

@ -6,11 +6,20 @@ from uuid import UUID, uuid4
import pytest
from openhands.agent_server.models import SendMessageRequest, StartConversationRequest
from openhands.app_server.app_conversation.app_conversation_models import AgentType
from openhands.app_server.app_conversation.app_conversation_models import (
AgentType,
AppConversationStartRequest,
)
from openhands.app_server.app_conversation.live_status_app_conversation_service import (
LiveStatusAppConversationService,
)
from openhands.app_server.sandbox.sandbox_models import SandboxInfo, SandboxStatus
from openhands.app_server.sandbox.sandbox_models import (
AGENT_SERVER,
ExposedUrl,
SandboxInfo,
SandboxStatus,
)
from openhands.app_server.sandbox.sandbox_spec_models import SandboxSpecInfo
from openhands.app_server.user.user_context import UserContext
from openhands.integrations.provider import ProviderType
from openhands.sdk import Agent
@ -842,6 +851,109 @@ class TestLiveStatusAppConversationService:
)
self.service._finalize_conversation_request.assert_called_once()
@pytest.mark.asyncio
@patch(
'openhands.app_server.app_conversation.live_status_app_conversation_service.AsyncRemoteWorkspace'
)
@patch(
'openhands.app_server.app_conversation.live_status_app_conversation_service.ConversationInfo'
)
async def test_start_app_conversation_default_title_uses_first_five_characters(
self, mock_conversation_info_class, mock_remote_workspace_class
):
"""Test that v1 conversations use first 5 characters of conversation ID for default title."""
# Arrange
conversation_id = uuid4()
conversation_id_hex = conversation_id.hex
expected_title = f'Conversation {conversation_id_hex[:5]}'
# Mock user context
self.mock_user_context.get_user_id = AsyncMock(return_value='test_user_123')
self.mock_user_context.get_user_info = AsyncMock(return_value=self.mock_user)
# Mock sandbox and sandbox spec
mock_sandbox_spec = Mock(spec=SandboxSpecInfo)
mock_sandbox_spec.working_dir = '/test/workspace'
self.mock_sandbox.sandbox_spec_id = str(uuid4())
self.mock_sandbox.id = str(uuid4()) # Ensure sandbox.id is a string
self.mock_sandbox.session_api_key = 'test_session_key'
exposed_url = ExposedUrl(
name=AGENT_SERVER, url='http://agent-server:8000', port=60000
)
self.mock_sandbox.exposed_urls = [exposed_url]
self.mock_sandbox_service.get_sandbox = AsyncMock(
return_value=self.mock_sandbox
)
self.mock_sandbox_spec_service.get_sandbox_spec = AsyncMock(
return_value=mock_sandbox_spec
)
# Mock remote workspace
mock_remote_workspace = Mock()
mock_remote_workspace_class.return_value = mock_remote_workspace
# Mock the wait for sandbox and setup scripts
async def mock_wait_for_sandbox(task):
task.sandbox_id = self.mock_sandbox.id
yield task
async def mock_run_setup_scripts(task, sandbox, workspace):
yield task
self.service._wait_for_sandbox_start = mock_wait_for_sandbox
self.service.run_setup_scripts = mock_run_setup_scripts
# Mock build start conversation request
mock_agent = Mock(spec=Agent)
mock_agent.llm = Mock(spec=LLM)
mock_agent.llm.model = 'gpt-4'
mock_start_request = Mock(spec=StartConversationRequest)
mock_start_request.agent = mock_agent
mock_start_request.model_dump.return_value = {'test': 'data'}
self.service._build_start_conversation_request_for_user = AsyncMock(
return_value=mock_start_request
)
# Mock ConversationInfo returned from agent server
mock_conversation_info = Mock()
mock_conversation_info.id = conversation_id
mock_conversation_info_class.model_validate.return_value = (
mock_conversation_info
)
# Mock HTTP response from agent server
mock_response = Mock()
mock_response.json.return_value = {'id': str(conversation_id)}
mock_response.raise_for_status = Mock()
self.mock_httpx_client.post = AsyncMock(return_value=mock_response)
# Mock event callback service
self.mock_event_callback_service.save_event_callback = AsyncMock()
# Create request
request = AppConversationStartRequest()
# Act
async for task in self.service._start_app_conversation(request):
# Consume all tasks to reach the point where title is set
pass
# Assert
# Verify that save_app_conversation_info was called with the correct title format
self.mock_app_conversation_info_service.save_app_conversation_info.assert_called_once()
call_args = (
self.mock_app_conversation_info_service.save_app_conversation_info.call_args
)
saved_info = call_args[0][0] # First positional argument
assert saved_info.title == expected_title, (
f'Expected title to be "{expected_title}" (first 5 chars), '
f'but got "{saved_info.title}"'
)
assert saved_info.id == conversation_id
@pytest.mark.asyncio
async def test_configure_llm_and_mcp_with_custom_sse_servers(self):
"""Test _configure_llm_and_mcp merges custom SSE servers with UUID-based names."""