新增配置模型测试按钮
This commit is contained in:
@@ -741,6 +741,67 @@ async def model_list(current_user: dict = Depends(get_current_user)) -> dict[str
|
|||||||
return ok(get_platform_store().models())
|
return ok(get_platform_store().models())
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/model-manage/test-online")
|
||||||
|
async def test_online_model(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||||
|
"""测试在线模型 API 是否可用:发送一个简单的 chat/completions 请求验证连通性。"""
|
||||||
|
api_url = (payload.get("api_url") or "").rstrip("/")
|
||||||
|
api_key = payload.get("api_key") or ""
|
||||||
|
model_name = payload.get("online_model_name") or ""
|
||||||
|
if not api_url:
|
||||||
|
raise fail(400, "api_url is required")
|
||||||
|
if not model_name:
|
||||||
|
raise fail(400, "online_model_name is required")
|
||||||
|
import httpx
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=15) as client:
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if api_key:
|
||||||
|
headers["Authorization"] = f"Bearer {api_key}"
|
||||||
|
# 尝试多种 OpenAI 兼容路径
|
||||||
|
chat_paths = [
|
||||||
|
f"{api_url}/chat/completions",
|
||||||
|
f"{api_url}/v1/chat/completions",
|
||||||
|
f"{api_url}/modelTF/v1/chat/completions",
|
||||||
|
]
|
||||||
|
resp = None
|
||||||
|
for path in chat_paths:
|
||||||
|
try:
|
||||||
|
r = await client.post(
|
||||||
|
path,
|
||||||
|
json={
|
||||||
|
"model": model_name,
|
||||||
|
"messages": [{"role": "user", "content": "Hi"}],
|
||||||
|
"max_tokens": 5,
|
||||||
|
"temperature": 0,
|
||||||
|
},
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
if r.status_code in (200, 201):
|
||||||
|
resp = r
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if resp is None:
|
||||||
|
return ok({"success": False, "error": f"无法连接到 {api_url},请检查地址和端口"})
|
||||||
|
body = resp.json()
|
||||||
|
usage = body.get("usage", {})
|
||||||
|
return ok({
|
||||||
|
"success": True,
|
||||||
|
"model": body.get("model", model_name),
|
||||||
|
"provider": body.get("object", ""),
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": usage.get("prompt_tokens", 0),
|
||||||
|
"completion_tokens": usage.get("completion_tokens", 0),
|
||||||
|
"total_tokens": usage.get("total_tokens", 0),
|
||||||
|
},
|
||||||
|
"latency_ms": None, # 由前端计算
|
||||||
|
})
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
return ok({"success": False, "error": "连接超时(15s),请检查网络或 API 地址是否正确"})
|
||||||
|
except Exception as exc:
|
||||||
|
return ok({"success": False, "error": str(exc)})
|
||||||
|
|
||||||
|
|
||||||
@router.post("/model-manage")
|
@router.post("/model-manage")
|
||||||
async def create_model(payload: dict[str, Any] = Body(...), current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
|
async def create_model(payload: dict[str, Any] = Body(...), current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
|
||||||
payload.setdefault("created_by", current_user.get("id"))
|
payload.setdefault("created_by", current_user.get("id"))
|
||||||
|
|||||||
@@ -100,3 +100,14 @@ export const mergeModel = (data: {
|
|||||||
/** 导出已训练模型权重 */
|
/** 导出已训练模型权重 */
|
||||||
export const exportModelUrl = (modelName: string) =>
|
export const exportModelUrl = (modelName: string) =>
|
||||||
`/modelTF/model-manage/trained-models/${encodeURIComponent(modelName)}/export`
|
`/modelTF/model-manage/trained-models/${encodeURIComponent(modelName)}/export`
|
||||||
|
|
||||||
|
/** 测试在线模型连通性 */
|
||||||
|
export const testOnlineModel = (data: {
|
||||||
|
api_url: string
|
||||||
|
api_key: string
|
||||||
|
online_model_name: string
|
||||||
|
}) =>
|
||||||
|
post<{ success: boolean; error?: string; model?: string; usage?: object }>(
|
||||||
|
'/model-manage/test-online',
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
createModel,
|
createModel,
|
||||||
updateModel,
|
updateModel,
|
||||||
getLocalModels,
|
getLocalModels,
|
||||||
|
testOnlineModel,
|
||||||
} from '@/api/modules/model'
|
} from '@/api/modules/model'
|
||||||
import { MODEL_TYPE_MAP } from '@/constants'
|
import { MODEL_TYPE_MAP } from '@/constants'
|
||||||
import type { ModelForm, ModelSource } from '@/types'
|
import type { ModelForm, ModelSource } from '@/types'
|
||||||
@@ -35,6 +36,10 @@ const form = reactive<ModelForm>({
|
|||||||
online_model_name: '',
|
online_model_name: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 在线模型测试
|
||||||
|
const testLoading = ref(false)
|
||||||
|
const testResult = ref<{ success: boolean; error?: string; model?: string; usage?: object } | null>(null)
|
||||||
|
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
name: [
|
name: [
|
||||||
{ required: true, message: '请输入模型名称', trigger: 'blur' },
|
{ required: true, message: '请输入模型名称', trigger: 'blur' },
|
||||||
@@ -74,7 +79,7 @@ async function loadEditData() {
|
|||||||
path: model.path || '',
|
path: model.path || '',
|
||||||
api_url: model.api_url || '',
|
api_url: model.api_url || '',
|
||||||
api_key: model.api_key || '',
|
api_key: model.api_key || '',
|
||||||
online_model_name: model.model_name || '',
|
online_model_name: model.online_model_name || '',
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
@@ -133,6 +138,36 @@ async function handleSubmit() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleTestOnline() {
|
||||||
|
if (!form.api_url || !form.online_model_name) {
|
||||||
|
ElMessage.warning('请先填写 API 地址和模型名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
testLoading.value = true
|
||||||
|
testResult.value = null
|
||||||
|
try {
|
||||||
|
const start = Date.now()
|
||||||
|
const res = await testOnlineModel({
|
||||||
|
api_url: form.api_url,
|
||||||
|
api_key: form.api_key,
|
||||||
|
online_model_name: form.online_model_name,
|
||||||
|
})
|
||||||
|
const data = res as any
|
||||||
|
if (data.success) {
|
||||||
|
data.latency_ms = Date.now() - start
|
||||||
|
ElMessage.success(`模型 ${data.model || form.online_model_name} 连接成功 (${data.latency_ms}ms)`)
|
||||||
|
} else {
|
||||||
|
ElMessage.error(data.error || '连接失败')
|
||||||
|
}
|
||||||
|
testResult.value = data
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message || e?.response?.data?.message || '测试请求失败')
|
||||||
|
testResult.value = { success: false, error: String(e) }
|
||||||
|
} finally {
|
||||||
|
testLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
@@ -217,7 +252,25 @@ onMounted(() => {
|
|||||||
<el-input v-model="form.api_key" type="password" show-password placeholder="请输入 API Key" />
|
<el-input v-model="form.api_key" type="password" show-password placeholder="请输入 API Key" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="模型名称" prop="online_model_name">
|
<el-form-item label="模型名称" prop="online_model_name">
|
||||||
<el-input v-model="form.online_model_name" placeholder="如:gpt-4、qwen-turbo" />
|
<div class="model-test-row">
|
||||||
|
<el-input v-model="form.online_model_name" placeholder="如:gpt-4、qwen-turbo" />
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="testLoading"
|
||||||
|
:disabled="!form.api_url || !form.online_model_name"
|
||||||
|
@click="handleTestOnline"
|
||||||
|
>测试连接</el-button>
|
||||||
|
</div>
|
||||||
|
<!-- 测试结果 -->
|
||||||
|
<div v-if="testResult" class="test-result" :class="{ success: testResult.success, error: !testResult.success }">
|
||||||
|
<template v-if="testResult.success">
|
||||||
|
<i class="fa fa-check-circle" /> 连接成功 · 模型:{{ testResult.model }} · 耗时:{{ testResult.latency_ms }}ms
|
||||||
|
<span v-if="testResult.usage" class="usage-info">tokens: {{ (testResult.usage as any)?.total_tokens }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<i class="fa fa-exclamation-circle" /> {{ testResult.error }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -255,4 +308,41 @@ onMounted(() => {
|
|||||||
color: #64748b;
|
color: #64748b;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.model-test-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-result {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #166534;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #991b1b;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-info {
|
||||||
|
margin-left: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user