Skip to content

Commit ee7ba28

Browse files
committed
Allow LLM test to use request payload
1 parent 409abb6 commit ee7ba28

2 files changed

Lines changed: 97 additions & 10 deletions

File tree

app/api/endpoints/system.py

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,22 @@
4545
from app.utils.http import RequestUtils, AsyncRequestUtils
4646
from app.utils.security import SecurityUtils
4747
from app.utils.url import UrlUtils
48+
from pydantic import BaseModel
4849
from version import APP_VERSION
4950

5051
router = APIRouter()
5152

5253
_NETTEST_REDIRECT_STATUS_CODES = {301, 302, 303, 307, 308}
5354

5455

56+
class LlmTestRequest(BaseModel):
57+
enabled: Optional[bool] = None
58+
provider: Optional[str] = None
59+
model: Optional[str] = None
60+
api_key: Optional[str] = None
61+
base_url: Optional[str] = None
62+
63+
5564
def _match_nettest_prefix(url: str, prefix: str) -> bool:
5665
"""
5766
判断目标URL是否仍然落在允许的协议、主机、端口和路径前缀内。
@@ -276,16 +285,50 @@ def _build_llm_test_data(
276285
return data
277286

278287

279-
def _build_llm_test_snapshot() -> dict[str, Any]:
288+
def _normalize_llm_test_value(
289+
value: Optional[str], *, empty_as_none: bool = False
290+
) -> Optional[str]:
280291
"""
281-
冻结当前 LLM 测试所需配置,避免请求执行过程中被新的保存动作改写。
292+
清理来自前端的 LLM 测试字段。
293+
"""
294+
if value is None:
295+
return None
296+
stripped = value.strip()
297+
if empty_as_none and not stripped:
298+
return None
299+
return stripped
300+
301+
302+
def _build_llm_test_snapshot(payload: Optional[LlmTestRequest] = None) -> dict[str, Any]:
282303
"""
304+
冻结当前 LLM 测试所需配置。
305+
306+
优先使用前端传入的临时参数;未传入时回退到已保存配置,兼容旧调用。
307+
"""
308+
provider = settings.LLM_PROVIDER
309+
model = settings.LLM_MODEL
310+
api_key = settings.LLM_API_KEY
311+
base_url = settings.LLM_BASE_URL
312+
enabled = bool(settings.AI_AGENT_ENABLE)
313+
314+
if payload:
315+
if payload.enabled is not None:
316+
enabled = bool(payload.enabled)
317+
if payload.provider is not None:
318+
provider = _normalize_llm_test_value(payload.provider) or ""
319+
if payload.model is not None:
320+
model = _normalize_llm_test_value(payload.model) or ""
321+
if payload.api_key is not None:
322+
api_key = _normalize_llm_test_value(payload.api_key, empty_as_none=True)
323+
if payload.base_url is not None:
324+
base_url = _normalize_llm_test_value(payload.base_url, empty_as_none=True)
325+
283326
return {
284-
"enabled": bool(settings.AI_AGENT_ENABLE),
285-
"provider": settings.LLM_PROVIDER,
286-
"model": settings.LLM_MODEL,
287-
"api_key": settings.LLM_API_KEY,
288-
"base_url": settings.LLM_BASE_URL,
327+
"enabled": enabled,
328+
"provider": provider,
329+
"model": model,
330+
"api_key": api_key,
331+
"base_url": base_url,
289332
}
290333

291334

@@ -679,11 +722,14 @@ async def get_llm_models(
679722

680723

681724
@router.post("/llm-test", summary="测试LLM调用", response_model=schemas.Response)
682-
async def llm_test(_: User = Depends(get_current_active_superuser_async)):
725+
async def llm_test(
726+
payload: Annotated[Optional[LlmTestRequest], Body()] = None,
727+
_: User = Depends(get_current_active_superuser_async),
728+
):
683729
"""
684-
使用当前已保存配置执行一次最小 LLM 调用。
730+
使用传入配置或当前已保存配置执行一次最小 LLM 调用。
685731
"""
686-
snapshot = _build_llm_test_snapshot()
732+
snapshot = _build_llm_test_snapshot(payload)
687733
data = _build_llm_test_data(
688734
provider=snapshot["provider"],
689735
model=snapshot["model"],

tests/test_system_llm_test.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,47 @@ def test_llm_test_returns_successful_reply_preview(self):
142142
self.assertEqual(resp.data["duration_ms"], 321)
143143
self.assertEqual(resp.data["reply_preview"], "OK")
144144

145+
def test_llm_test_prefers_request_payload_over_saved_settings(self):
146+
llm_test_mock = AsyncMock(
147+
return_value={
148+
"provider": "openai",
149+
"model": "gpt-4.1-mini",
150+
"duration_ms": 123,
151+
"reply_preview": "OK",
152+
}
153+
)
154+
payload = system_endpoint.LlmTestRequest(
155+
enabled=True,
156+
provider="openai",
157+
model="gpt-4.1-mini",
158+
api_key="sk-live",
159+
base_url="https://example.com/v1",
160+
)
161+
162+
with patch.object(system_endpoint.settings, "AI_AGENT_ENABLE", False), patch.object(
163+
system_endpoint.settings, "LLM_PROVIDER", "deepseek"
164+
), patch.object(system_endpoint.settings, "LLM_MODEL", "deepseek-chat"), patch.object(
165+
system_endpoint.settings, "LLM_API_KEY", "sk-saved"
166+
), patch.object(
167+
system_endpoint.settings, "LLM_BASE_URL", "https://api.deepseek.com"
168+
), patch.object(
169+
system_endpoint.LLMHelper,
170+
"test_current_settings",
171+
llm_test_mock,
172+
create=True,
173+
):
174+
resp = asyncio.run(system_endpoint.llm_test(payload=payload, _="token"))
175+
176+
llm_test_mock.assert_awaited_once_with(
177+
provider="openai",
178+
model="gpt-4.1-mini",
179+
api_key="sk-live",
180+
base_url="https://example.com/v1",
181+
)
182+
self.assertTrue(resp.success)
183+
self.assertEqual(resp.data["provider"], "openai")
184+
self.assertEqual(resp.data["model"], "gpt-4.1-mini")
185+
145186
def test_llm_test_rejects_empty_reply(self):
146187
with patch.object(system_endpoint.settings, "AI_AGENT_ENABLE", True), patch.object(
147188
system_endpoint.settings, "LLM_PROVIDER", "deepseek"

0 commit comments

Comments
 (0)