feat: 模型测试功能 - 测试模型配置是否可用

- API: POST /api/v1/models/{id}/test 发送简单提示词验证模型连通性
- 前端: 模型列表新增测试按钮(实验图标),弹窗显示结果或错误详情
- 帮助用户快速定位配置问题(如 endpoint、api_key、model 参数错误)
This commit is contained in:
zwt13703 2026-07-06 20:13:44 +08:00
parent ba2171e13e
commit a8604ebc34
6 changed files with 61 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
# Storage # Storage
storage/ storage/
backend/.vite/
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View File

@ -0,0 +1,8 @@
{
"hash": "9f45c68f",
"configHash": "0e3c7f00",
"lockfileHash": "e3b0c442",
"browserHash": "3bfaa9a9",
"optimized": {},
"chunks": {}
}

3
.vite/deps/package.json Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@ -5,6 +5,7 @@ from app.core.database import get_db
from app.core.security import encrypt_api_key from app.core.security import encrypt_api_key
from app.models.ai_model import AIModel from app.models.ai_model import AIModel
from app.schemas.ai_model import AIModelCreate, AIModelUpdate, AIModelToggle, AIModelResponse from app.schemas.ai_model import AIModelCreate, AIModelUpdate, AIModelToggle, AIModelResponse
from app.services.ai_adapter import call_ai_model
router = APIRouter(prefix="/models", tags=["AI模型管理"]) router = APIRouter(prefix="/models", tags=["AI模型管理"])
@ -91,3 +92,24 @@ async def toggle_model(model_id: str, data: AIModelToggle, db: AsyncSession = De
await db.flush() await db.flush()
await db.refresh(model) await db.refresh(model)
return model return model
@router.post("/{model_id}/test")
async def test_model(model_id: str, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(AIModel).where(AIModel.id == model_id))
model = result.scalar_one_or_none()
if not model:
raise HTTPException(status_code=404, detail="模型不存在")
model_config = {
"provider": model.provider,
"endpoint": model.endpoint,
"api_key": model.api_key,
"extra_params": model.extra_params,
}
try:
response = await call_ai_model(model_config, "请用一句话介绍你自己。")
return {"success": True, "result": response}
except Exception as e:
return {"success": False, "error": str(e)}

View File

@ -29,3 +29,8 @@ export async function toggleModel(id: string, is_enabled: boolean) {
const { data } = await request.patch<AIModel>(`/models/${id}/toggle`, { is_enabled }); const { data } = await request.patch<AIModel>(`/models/${id}/toggle`, { is_enabled });
return data; return data;
} }
export async function testModel(id: string) {
const { data } = await request.post<{ success: boolean; result?: string; error?: string }>(`/models/${id}/test`);
return data;
}

View File

@ -3,7 +3,7 @@ import {
Table, Button, Modal, Form, Input, Select, Switch, Table, Button, Modal, Form, Input, Select, Switch,
Space, message, Popconfirm, Tag, Space, message, Popconfirm, Tag,
} from "antd"; } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons"; import { PlusOutlined, EditOutlined, DeleteOutlined, ExperimentOutlined } from "@ant-design/icons";
import type { AIModel } from "../types"; import type { AIModel } from "../types";
import * as modelApi from "../api/models"; import * as modelApi from "../api/models";
@ -110,6 +110,21 @@ export default function ModelList() {
fetchModels(); fetchModels();
}; };
const handleTest = async (id: string, name: string) => {
message.loading({ content: `正在测试 ${name}...`, key: "test" });
try {
const res = await modelApi.testModel(id);
if (res.success) {
message.success({ content: `${name} 测试通过`, key: "test" });
Modal.info({ title: `测试结果 - ${name}`, content: res.result, width: 600 });
} else {
message.error({ content: `测试失败: ${res.error}`, key: "test", duration: 5 });
}
} catch (err: unknown) {
message.error({ content: err instanceof Error ? err.message : "测试失败", key: "test", duration: 5 });
}
};
const columns = [ const columns = [
{ title: "名称", dataIndex: "name", key: "name", width: 150 }, { title: "名称", dataIndex: "name", key: "name", width: 150 },
{ {
@ -150,6 +165,12 @@ export default function ModelList() {
width: 160, width: 160,
render: (_: unknown, record: AIModel) => ( render: (_: unknown, record: AIModel) => (
<Space> <Space>
<Button
type="link"
size="small"
icon={<ExperimentOutlined />}
onClick={() => handleTest(record.id, record.name)}
/>
<Button <Button
type="link" type="link"
size="small" size="small"