mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
fix: switching from own model to breaks functionality (#11916)
This commit is contained in:
parent
72c7d9c497
commit
d7b36c9579
@ -102,6 +102,7 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
sandbox_startup_poll_frequency: int
|
||||
httpx_client: httpx.AsyncClient
|
||||
web_url: str | None
|
||||
openhands_provider_base_url: str | None
|
||||
access_token_hard_timeout: timedelta | None
|
||||
app_mode: str | None = None
|
||||
keycloak_auth_cookie: str | None = None
|
||||
@ -590,9 +591,12 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
"""
|
||||
# Configure LLM
|
||||
model = llm_model or user.llm_model
|
||||
base_url = user.llm_base_url
|
||||
if model and model.startswith('openhands/'):
|
||||
base_url = user.llm_base_url or self.openhands_provider_base_url
|
||||
llm = LLM(
|
||||
model=model,
|
||||
base_url=user.llm_base_url,
|
||||
base_url=base_url,
|
||||
api_key=user.llm_api_key,
|
||||
usage_id='agent',
|
||||
)
|
||||
@ -1082,6 +1086,7 @@ class LiveStatusAppConversationServiceInjector(AppConversationServiceInjector):
|
||||
sandbox_startup_poll_frequency=self.sandbox_startup_poll_frequency,
|
||||
httpx_client=httpx_client,
|
||||
web_url=web_url,
|
||||
openhands_provider_base_url=config.openhands_provider_base_url,
|
||||
access_token_hard_timeout=access_token_hard_timeout,
|
||||
app_mode=app_mode,
|
||||
keycloak_auth_cookie=keycloak_auth_cookie,
|
||||
|
||||
@ -74,6 +74,11 @@ def get_default_web_url() -> str | None:
|
||||
return f'https://{web_host}'
|
||||
|
||||
|
||||
def get_openhands_provider_base_url() -> str | None:
|
||||
"""Return the base URL for the OpenHands provider, if configured."""
|
||||
return os.getenv('OPENHANDS_PROVIDER_BASE_URL') or None
|
||||
|
||||
|
||||
def _get_default_lifespan():
|
||||
# Check legacy parameters for saas mode. If we are in SAAS mode do not apply
|
||||
# OSS alembic migrations
|
||||
@ -88,6 +93,10 @@ class AppServerConfig(OpenHandsModel):
|
||||
default_factory=get_default_web_url,
|
||||
description='The URL where OpenHands is running (e.g., http://localhost:3000)',
|
||||
)
|
||||
openhands_provider_base_url: str | None = Field(
|
||||
default_factory=get_openhands_provider_base_url,
|
||||
description='Base URL for the OpenHands provider',
|
||||
)
|
||||
# Dependency Injection Injectors
|
||||
event: EventServiceInjector | None = None
|
||||
event_callback: EventCallbackServiceInjector | None = None
|
||||
|
||||
@ -52,6 +52,7 @@ class TestLiveStatusAppConversationService:
|
||||
sandbox_startup_poll_frequency=1,
|
||||
httpx_client=self.mock_httpx_client,
|
||||
web_url='https://test.example.com',
|
||||
openhands_provider_base_url='https://provider.example.com',
|
||||
access_token_hard_timeout=None,
|
||||
app_mode='test',
|
||||
keycloak_auth_cookie=None,
|
||||
@ -66,6 +67,7 @@ class TestLiveStatusAppConversationService:
|
||||
self.mock_user.confirmation_mode = False
|
||||
self.mock_user.search_api_key = None # Default to None
|
||||
self.mock_user.condenser_max_size = None # Default to None
|
||||
self.mock_user.llm_base_url = 'https://api.openai.com/v1'
|
||||
|
||||
# Mock sandbox
|
||||
self.mock_sandbox = Mock(spec=SandboxInfo)
|
||||
@ -241,6 +243,70 @@ class TestLiveStatusAppConversationService:
|
||||
assert mcp_config['default']['url'] == 'https://test.example.com/mcp/mcp'
|
||||
assert mcp_config['default']['headers']['X-Session-API-Key'] == 'mcp_api_key'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_llm_and_mcp_openhands_model_prefers_user_base_url(self):
|
||||
"""openhands/* model uses user.llm_base_url when provided."""
|
||||
# Arrange
|
||||
self.mock_user.llm_model = 'openhands/special'
|
||||
self.mock_user.llm_base_url = 'https://user-llm.example.com'
|
||||
self.mock_user_context.get_mcp_api_key.return_value = None
|
||||
|
||||
# Act
|
||||
llm, _ = await self.service._configure_llm_and_mcp(
|
||||
self.mock_user, self.mock_user.llm_model
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert llm.base_url == 'https://user-llm.example.com'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_llm_and_mcp_openhands_model_uses_provider_default(self):
|
||||
"""openhands/* model falls back to configured provider base URL."""
|
||||
# Arrange
|
||||
self.mock_user.llm_model = 'openhands/default'
|
||||
self.mock_user.llm_base_url = None
|
||||
self.mock_user_context.get_mcp_api_key.return_value = None
|
||||
|
||||
# Act
|
||||
llm, _ = await self.service._configure_llm_and_mcp(
|
||||
self.mock_user, self.mock_user.llm_model
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert llm.base_url == 'https://provider.example.com'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_llm_and_mcp_openhands_model_no_base_urls(self):
|
||||
"""openhands/* model sets base_url to None when no sources available."""
|
||||
# Arrange
|
||||
self.mock_user.llm_model = 'openhands/default'
|
||||
self.mock_user.llm_base_url = None
|
||||
self.service.openhands_provider_base_url = None
|
||||
self.mock_user_context.get_mcp_api_key.return_value = None
|
||||
|
||||
# Act
|
||||
llm, _ = await self.service._configure_llm_and_mcp(
|
||||
self.mock_user, self.mock_user.llm_model
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert llm.base_url is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_llm_and_mcp_non_openhands_model_ignores_provider(self):
|
||||
"""Non-openhands model ignores provider base URL and uses user base URL."""
|
||||
# Arrange
|
||||
self.mock_user.llm_model = 'gpt-4'
|
||||
self.mock_user.llm_base_url = 'https://user-llm.example.com'
|
||||
self.service.openhands_provider_base_url = 'https://provider.example.com'
|
||||
self.mock_user_context.get_mcp_api_key.return_value = None
|
||||
|
||||
# Act
|
||||
llm, _ = await self.service._configure_llm_and_mcp(self.mock_user, None)
|
||||
|
||||
# Assert
|
||||
assert llm.base_url == 'https://user-llm.example.com'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_llm_and_mcp_with_user_default_model(self):
|
||||
"""Test _configure_llm_and_mcp using user's default model."""
|
||||
|
||||
@ -188,6 +188,7 @@ class TestExperimentManagerIntegration:
|
||||
sandbox_startup_poll_frequency=1,
|
||||
httpx_client=httpx_client,
|
||||
web_url=None,
|
||||
openhands_provider_base_url=None,
|
||||
access_token_hard_timeout=None,
|
||||
)
|
||||
|
||||
|
||||
@ -2166,6 +2166,7 @@ async def test_delete_v1_conversation_with_sub_conversations():
|
||||
sandbox_startup_poll_frequency=2,
|
||||
httpx_client=mock_httpx_client,
|
||||
web_url=None,
|
||||
openhands_provider_base_url=None,
|
||||
access_token_hard_timeout=None,
|
||||
)
|
||||
|
||||
@ -2287,6 +2288,7 @@ async def test_delete_v1_conversation_with_no_sub_conversations():
|
||||
sandbox_startup_poll_frequency=2,
|
||||
httpx_client=mock_httpx_client,
|
||||
web_url=None,
|
||||
openhands_provider_base_url=None,
|
||||
access_token_hard_timeout=None,
|
||||
)
|
||||
|
||||
@ -2438,6 +2440,7 @@ async def test_delete_v1_conversation_sub_conversation_deletion_error():
|
||||
sandbox_startup_poll_frequency=2,
|
||||
httpx_client=mock_httpx_client,
|
||||
web_url=None,
|
||||
openhands_provider_base_url=None,
|
||||
access_token_hard_timeout=None,
|
||||
)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user