Feat: settings default (#6328)

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
tofarr
2025-01-17 20:17:18 -07:00
committed by GitHub
parent 532c7cdf02
commit b4d20e3e18
4 changed files with 100 additions and 3 deletions

View File

@@ -1,6 +1,11 @@
from __future__ import annotations
from pydantic import BaseModel, SecretStr, SerializationInfo, field_serializer
from pydantic.json import pydantic_encoder
from openhands.core.config.llm_config import LLMConfig
from openhands.core.config.utils import load_app_config
class Settings(BaseModel):
"""
@@ -28,3 +33,24 @@ class Settings(BaseModel):
return llm_api_key.get_secret_value()
return pydantic_encoder(llm_api_key)
@staticmethod
def from_config() -> Settings | None:
app_config = load_app_config()
llm_config: LLMConfig = app_config.get_llm_config()
if llm_config.api_key is None:
# If no api key has been set, we take this to mean that there is no reasonable default
return None
security = app_config.security
settings = Settings(
language='en',
agent=app_config.default_agent,
max_iterations=app_config.max_iterations,
security_analyzer=security.security_analyzer,
confirmation_mode=security.confirmation_mode,
llm_model=llm_config.model,
llm_api_key=llm_config.api_key,
llm_base_url=llm_config.base_url,
remote_runtime_resource_factor=app_config.sandbox.remote_runtime_resource_factor,
)
return settings

View File

@@ -23,7 +23,7 @@ class FileSettingsStore(SettingsStore):
settings = Settings(**kwargs)
return settings
except FileNotFoundError:
return None
return Settings.from_config()
async def store(self, settings: Settings):
json_str = settings.model_dump_json(context={'expose_secrets': True})

View File

@@ -20,8 +20,12 @@ def file_settings_store(mock_file_store):
@pytest.mark.asyncio
async def test_load_nonexistent_data(file_settings_store):
file_settings_store.file_store.read.side_effect = FileNotFoundError()
assert await file_settings_store.load() is None
with patch(
'openhands.server.settings.load_app_config',
MagicMock(return_value=AppConfig()),
):
file_settings_store.file_store.read.side_effect = FileNotFoundError()
assert await file_settings_store.load() is None
@pytest.mark.asyncio

View File

@@ -0,0 +1,67 @@
from unittest.mock import patch
from pydantic import SecretStr
from openhands.core.config.app_config import AppConfig
from openhands.core.config.llm_config import LLMConfig
from openhands.core.config.sandbox_config import SandboxConfig
from openhands.core.config.security_config import SecurityConfig
from openhands.server.settings import Settings
def test_settings_from_config():
# Mock configuration
mock_app_config = AppConfig(
default_agent='test-agent',
max_iterations=100,
security=SecurityConfig(
security_analyzer='test-analyzer', confirmation_mode=True
),
llms={
'llm': LLMConfig(
model='test-model',
api_key=SecretStr('test-key'),
base_url='https://test.example.com',
)
},
sandbox=SandboxConfig(remote_runtime_resource_factor=2),
)
with patch(
'openhands.server.settings.load_app_config', return_value=mock_app_config
):
settings = Settings.from_config()
assert settings is not None
assert settings.language == 'en'
assert settings.agent == 'test-agent'
assert settings.max_iterations == 100
assert settings.security_analyzer == 'test-analyzer'
assert settings.confirmation_mode is True
assert settings.llm_model == 'test-model'
assert settings.llm_api_key.get_secret_value() == 'test-key'
assert settings.llm_base_url == 'https://test.example.com'
assert settings.remote_runtime_resource_factor == 2
def test_settings_from_config_no_api_key():
# Mock configuration without API key
mock_app_config = AppConfig(
default_agent='test-agent',
max_iterations=100,
security=SecurityConfig(
security_analyzer='test-analyzer', confirmation_mode=True
),
llms={
'llm': LLMConfig(
model='test-model', api_key=None, base_url='https://test.example.com'
)
},
sandbox=SandboxConfig(remote_runtime_resource_factor=2),
)
with patch(
'openhands.server.settings.load_app_config', return_value=mock_app_config
):
settings = Settings.from_config()
assert settings is None