From 98276a68a803fb0e5e88f0d8143b7569fb67c007 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 16 Aug 2026 08:27:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8DLLM=20manage?= =?UTF-8?q?=E7=AB=AF=E7=82=B9=E7=9B=AE=E5=BD=95=E6=9F=A5=E8=AF=A2=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=88=97=E8=A1=A8=E6=97=A0=E6=B3=95=E9=80=8F=E8=BF=87?= =?UTF-8?q?Dict=E5=93=8D=E5=BA=94=E6=A8=A1=E5=9E=8B=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E8=AE=BE=E7=BD=AE=E9=A1=B5500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/llm.py | 6 ++++-- tests/test_api_response.py | 2 ++ tests/test_llm_provider_manage.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/api/endpoints/llm.py b/app/api/endpoints/llm.py index d9ad4ac5a..33eeb0057 100644 --- a/app/api/endpoints/llm.py +++ b/app/api/endpoints/llm.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Union from fastapi import Depends, Request, Response from fastapi.responses import HTMLResponse @@ -15,7 +15,9 @@ router = ResponseAPIRouter() @router.post( "/manage", summary="LLM提供商统一管理", - response_model=schemas.Response[Dict[str, Any]], + # 各动作 data 形态不一:目录查询返回列表,其余动作返回映射, + # 须用具体联合类型声明,而非单一开放映射 + response_model=schemas.Response[Union[List[Dict[str, Any]], Dict[str, Any]]], ) async def manage_provider( request: Request, diff --git a/tests/test_api_response.py b/tests/test_api_response.py index b88002782..b12f39313 100644 --- a/tests/test_api_response.py +++ b/tests/test_api_response.py @@ -637,6 +637,8 @@ def test_openapi_success_models_have_no_implicit_empty_nested_schemas(): "ManageRequest", # 通用管理响应的 data 为模块自定义结构,按设计不固定字段。 "Response_Dict_str__Any__", + # LLM 提供商管理响应的 data 目录查询为列表、其余动作为映射。 + "Response_Union_List_Dict_str__Any____Dict_str__Any___", } allowed_empty_components = {"McpJsonRpcEmptyResult"} violations = [] diff --git a/tests/test_llm_provider_manage.py b/tests/test_llm_provider_manage.py index 54f24d911..8689a1e6a 100644 --- a/tests/test_llm_provider_manage.py +++ b/tests/test_llm_provider_manage.py @@ -279,3 +279,32 @@ def test_llm_manage_endpoint_accepts_empty_target(monkeypatch): assert resp.success is True assert captured["provider"] == "" assert "callback_url" not in captured["params"] + + +def test_llm_manage_endpoint_response_model_accepts_list_data(): + """目录查询动作 data 为列表,响应模型须同时覆盖列表与映射形态。 + + 回归守护:list_providers 返回 list[dict], + 若响应模型声明为 Dict[str, Any] 会在序列化校验时直接 500; + 同时受响应模型守护测试约束,不得使用 Any/JsonData 弱类型。 + """ + from typing import Any, Dict, List, Union + + from app.api.endpoints import llm as llm_endpoint + + route = next( + r for r in llm_endpoint.router.routes if getattr(r, "path", "").endswith("/manage") + ) + assert route.response_model is schemas.Response[ + Union[List[Dict[str, Any]], Dict[str, Any]] + ] + + list_resp = schemas.Response[Union[List[Dict[str, Any]], Dict[str, Any]]]( + success=True, message="", data=[{"id": "openai", "name": "OpenAI 兼容"}] + ) + assert list_resp.data == [{"id": "openai", "name": "OpenAI 兼容"}] + + dict_resp = schemas.Response[Union[List[Dict[str, Any]], Dict[str, Any]]]( + success=True, message="", data={"models": ["gpt-4o"]} + ) + assert dict_resp.data == {"models": ["gpt-4o"]}