diff --git a/src/langbot/pkg/api/http/controller/groups/provider/models.py b/src/langbot/pkg/api/http/controller/groups/provider/models.py
index 236000d9f..5d17d7ffc 100644
--- a/src/langbot/pkg/api/http/controller/groups/provider/models.py
+++ b/src/langbot/pkg/api/http/controller/groups/provider/models.py
@@ -1,10 +1,14 @@
import quart
+from langbot.pkg.provider.modelmgr import errors as provider_errors
from ....authz import Permission, has_permission
from ....context import RequestContext
from ... import group
+MODEL_TEST_ERROR_CODE = 'model_test_failed'
+
+
@group.group_class('models/llm', '/api/v1/provider/models/llm')
class LLMModelsRouterGroup(group.RouterGroup):
async def initialize(self) -> None:
@@ -96,7 +100,14 @@ class LLMModelsRouterGroup(group.RouterGroup):
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
- await self.ap.llm_model_service.test_llm_model(request_context, model_uuid, await quart.request.json)
+ try:
+ await self.ap.llm_model_service.test_llm_model(
+ request_context,
+ model_uuid,
+ await quart.request.json,
+ )
+ except (provider_errors.RequesterError, ValueError) as exc:
+ return self.http_status(400, MODEL_TEST_ERROR_CODE, str(exc))
return self.success()
@@ -191,9 +202,14 @@ class EmbeddingModelsRouterGroup(group.RouterGroup):
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
- await self.ap.embedding_models_service.test_embedding_model(
- request_context, model_uuid, await quart.request.json
- )
+ try:
+ await self.ap.embedding_models_service.test_embedding_model(
+ request_context,
+ model_uuid,
+ await quart.request.json,
+ )
+ except (provider_errors.RequesterError, ValueError) as exc:
+ return self.http_status(400, MODEL_TEST_ERROR_CODE, str(exc))
return self.success()
@@ -288,5 +304,12 @@ class RerankModelsRouterGroup(group.RouterGroup):
permission=Permission.PROVIDER_SECRET_MANAGE,
)
async def _(model_uuid: str, request_context: RequestContext) -> str:
- await self.ap.rerank_models_service.test_rerank_model(request_context, model_uuid, await quart.request.json)
+ try:
+ await self.ap.rerank_models_service.test_rerank_model(
+ request_context,
+ model_uuid,
+ await quart.request.json,
+ )
+ except (provider_errors.RequesterError, ValueError) as exc:
+ return self.http_status(400, MODEL_TEST_ERROR_CODE, str(exc))
return self.success()
diff --git a/tests/integration/api/test_providers.py b/tests/integration/api/test_providers.py
index 4aa2e1342..df72d9084 100644
--- a/tests/integration/api/test_providers.py
+++ b/tests/integration/api/test_providers.py
@@ -12,6 +12,7 @@ import pytest
from unittest.mock import MagicMock, AsyncMock, Mock
from types import SimpleNamespace
+from langbot.pkg.provider.modelmgr.errors import RequesterError
from tests.factories import FakeApp
@@ -111,16 +112,19 @@ def fake_provider_app():
app.llm_model_service.create_llm_model = AsyncMock(return_value={'uuid': 'new-model-uuid'})
app.llm_model_service.update_llm_model = AsyncMock(return_value={})
app.llm_model_service.delete_llm_model = AsyncMock()
+ app.llm_model_service.test_llm_model = AsyncMock()
# Embedding model service
app.embedding_models_service = Mock()
app.embedding_models_service.get_embedding_models = AsyncMock(return_value=[])
app.embedding_models_service.create_embedding_model = AsyncMock(return_value={'uuid': 'new-embedding-uuid'})
+ app.embedding_models_service.test_embedding_model = AsyncMock()
# Rerank model service
app.rerank_models_service = Mock()
app.rerank_models_service.get_rerank_models = AsyncMock(return_value=[])
app.rerank_models_service.create_rerank_model = AsyncMock(return_value={'uuid': 'new-rerank-uuid'})
+ app.rerank_models_service.test_rerank_model = AsyncMock()
# Model manager
app.model_mgr = Mock()
@@ -290,6 +294,43 @@ class TestModelEndpoints:
assert response.status_code == 200
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize(
+ ('service_name', 'method_name', 'path'),
+ [
+ ('llm_model_service', 'test_llm_model', '/api/v1/provider/models/llm/_/test'),
+ (
+ 'embedding_models_service',
+ 'test_embedding_model',
+ '/api/v1/provider/models/embedding/_/test',
+ ),
+ ('rerank_models_service', 'test_rerank_model', '/api/v1/provider/models/rerank/_/test'),
+ ],
+ )
+ async def test_model_test_returns_requester_error_details(
+ self,
+ quart_test_client,
+ fake_provider_app,
+ service_name,
+ method_name,
+ path,
+ ):
+ test_model = getattr(getattr(fake_provider_app, service_name), method_name)
+ test_model.side_effect = RequesterError('API key invalid')
+ try:
+ response = await quart_test_client.post(
+ path,
+ headers={'Authorization': 'Bearer test_token'},
+ json={'name': 'broken-model'},
+ )
+ finally:
+ test_model.side_effect = None
+
+ assert response.status_code == 400
+ data = await response.get_json()
+ assert data['code'] == 'model_test_failed'
+ assert data['msg'] == '模型请求失败: API key invalid'
+
@pytest.mark.usefixtures('mock_circular_import_chain')
class TestEmbeddingModelEndpoints:
diff --git a/web/src/app/home/agents/components/AgentDebugPanel.tsx b/web/src/app/home/agents/components/AgentDebugPanel.tsx
index 913d7ecc3..2cdbe06c6 100644
--- a/web/src/app/home/agents/components/AgentDebugPanel.tsx
+++ b/web/src/app/home/agents/components/AgentDebugPanel.tsx
@@ -312,48 +312,55 @@ export default function AgentDebugPanel({
}
>
{entry.direction === 'error' &&
- {entry.text}
-
- {entry.detail && (
-
- {entry.detail}
-
- + {testResult.message} +
+ )} ) : ( diff --git a/web/src/app/home/components/models-dialog/components/ModelItem.tsx b/web/src/app/home/components/models-dialog/components/ModelItem.tsx index 12e61da77..fae9be86f 100644 --- a/web/src/app/home/components/models-dialog/components/ModelItem.tsx +++ b/web/src/app/home/components/models-dialog/components/ModelItem.tsx @@ -412,6 +412,14 @@ export default function ModelItem({ )} + {testResult?.success === false && ( ++ {testResult.message} +
+ )} diff --git a/web/src/app/home/components/models-dialog/types.ts b/web/src/app/home/components/models-dialog/types.ts index 8ed008e46..f8f72e0b1 100644 --- a/web/src/app/home/components/models-dialog/types.ts +++ b/web/src/app/home/components/models-dialog/types.ts @@ -27,10 +27,9 @@ export interface ProviderModels { rerank: RerankModel[]; } -export interface TestResult { - success: boolean; - duration: number; -} +export type TestResult = + | { success: true; duration: number } + | { success: false; message: string }; export type SelectedScannedModel = { model: ScannedProviderModel;