feat(models): show LangBot Models availability

This commit is contained in:
RockChinQ
2026-09-11 21:17:01 +08:00
parent e631da0073
commit a3509b3626
20 changed files with 375 additions and 6 deletions
@@ -220,6 +220,21 @@ class SystemRouterGroup(group.RouterGroup):
return self.http_status(503, -1, str(exc))
return self.success(data=model)
@self.route(
'/model-availability',
methods=['GET'],
auth_type=group.AuthType.USER_TOKEN,
permission=Permission.RESOURCE_VIEW,
)
async def _(request_context: RequestContext) -> str:
"""Expose Space's latest persisted model probes to the WebUI."""
try:
models = await self.ap.space_service.get_model_selection()
except Exception as exc:
self.ap.logger.warning(f'Failed to load LangBot Models availability: {exc}')
return self.http_status(503, -1, 'Model availability is unavailable')
return self.success(data={'models': [model.model_dump(mode='json') for model in models]})
@self.route(
'/tasks',
methods=['GET'],
+14 -3
View File
@@ -242,13 +242,14 @@ class SpaceService:
models_data = data.get('data', {}).get('models', [])
return [SpaceModel.model_validate(model_dict) for model_dict in models_data]
async def get_model_selection(self, category: str) -> typing.List[SpaceModelSelection]:
async def get_model_selection(self, category: str | None = None) -> typing.List[SpaceModelSelection]:
"""Return Space models in the availability-ranked selection order."""
space_url = self._get_space_config()['url']
session = httpclient.get_session()
params = {'category': category} if category else None
async with session.get(
f'{space_url}/api/v1/models/selection',
params={'category': category},
params=params,
) as response:
if response.status != 200:
error = await httpclient.read_text_limited(response)
@@ -266,7 +267,17 @@ class SpaceService:
models = []
for selection in data:
if isinstance(selection, dict) and isinstance(selection.get('model'), dict):
models.append(selection['model'])
model = dict(selection['model'])
availability = selection.get('availability')
if not isinstance(availability, dict):
# Accept the short-lived pre-release response shape.
availability = {
key: selection[key]
for key in ('up', 'last_probed_at', 'latency_ms', 'http_code')
if key in selection
}
model['availability'] = availability
models.append(model)
else:
models.append(selection)
return [SpaceModelSelection.model_validate(model) for model in models]
+12 -1
View File
@@ -49,8 +49,19 @@ class SpaceModel(pydantic.BaseModel):
updated_at: str | None = None
class SpaceModelAvailability(pydantic.BaseModel):
"""Latest availability probe. ``up`` is None when no probe exists."""
up: bool | None = None
last_probed_at: str | None = None
latency_ms: int = 0
http_code: int = 0
class SpaceModelSelection(pydantic.BaseModel):
"""Minimal model identity returned by the ranked selection endpoint."""
"""Model identity and the latest persisted probe from Space."""
uuid: str
model_id: str
category: str | None = None
availability: SpaceModelAvailability = pydantic.Field(default_factory=SpaceModelAvailability)