Compare commits

..

1 Commits

Author SHA1 Message Date
huanghuoguoguo
9a04bfc364 fix(modelmgr): bound Space model sync startup wait 2026-06-14 21:16:40 +08:00
3 changed files with 38 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import asyncio
import sqlalchemy
import traceback
@@ -84,8 +85,19 @@ class ModelManager:
self.ap.logger.info('LangBot Space Models service is disabled, skipping sync.')
return
sync_timeout = space_config.get('models_sync_timeout')
try:
await self.sync_new_models_from_space()
if sync_timeout:
await asyncio.wait_for(
self.sync_new_models_from_space(),
timeout=float(sync_timeout),
)
else:
await self.sync_new_models_from_space()
except asyncio.TimeoutError:
self.ap.logger.warning(
f'LangBot Space model sync timed out after {sync_timeout}s, skipping startup sync.'
)
except Exception as e:
self.ap.logger.warning('Failed to sync new models from LangBot Space, model list may not be updated.')
self.ap.logger.warning(f' - Error: {e}')

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock
@@ -88,6 +89,28 @@ def test_token_manager_next_token_ignores_empty_token_list():
assert token_mgr.using_token_index == 0
@pytest.mark.asyncio
async def test_model_manager_initialize_skips_space_sync_after_timeout():
ap = SimpleNamespace()
ap.discover = SimpleNamespace(get_components_by_kind=Mock(return_value=[]))
ap.instance_config = SimpleNamespace(data={'space': {'models_sync_timeout': 0.01}})
ap.logger = Mock()
mgr = ModelManager(ap)
mgr.load_models_from_db = AsyncMock()
async def slow_sync():
await asyncio.sleep(1)
mgr.sync_new_models_from_space = AsyncMock(side_effect=slow_sync)
await mgr.initialize()
mgr.load_models_from_db.assert_awaited_once()
mgr.sync_new_models_from_space.assert_awaited_once()
ap.logger.warning.assert_any_call('LangBot Space model sync timed out after 0.01s, skipping startup sync.')
@pytest.mark.asyncio
async def test_updated_llm_model_is_immediately_usable_by_local_agent_pipeline():
from langbot.pkg.api.http.service.model import LLMModelsService

View File

@@ -310,7 +310,6 @@ function SingleSelectField({
{options.map((opt) => (
<div key={opt.id}>
<button
type="button"
onClick={() => onChange(opt.id)}
className={`w-full text-left text-sm px-3 py-2 rounded-lg border transition-colors ${
value === opt.id
@@ -362,16 +361,8 @@ function MultiSelectField({
const selected = value.includes(opt.id);
return (
<div key={opt.id}>
<div
role="button"
tabIndex={0}
<button
onClick={() => toggle(opt.id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggle(opt.id);
}
}}
className={`w-full text-left text-sm px-3 py-2 rounded-lg border transition-colors flex items-center gap-2 ${
selected
? 'border-primary bg-primary/5 text-primary'
@@ -380,7 +371,7 @@ function MultiSelectField({
>
<Checkbox checked={selected} className="pointer-events-none" />
{getI18nText(opt.label)}
</div>
</button>
{opt.has_input && selected && (
<input
type="text"