Skip to content

Commit 907a362

Browse files
perf: Ai model validate
1 parent f559d2b commit 907a362

File tree

4 files changed

+118
-30
lines changed

4 files changed

+118
-30
lines changed

backend/apps/system/api/aimodel.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import json
22
from typing import List, Union
3+
4+
from fastapi.responses import StreamingResponse
35
from apps.ai_model.model_factory import LLMConfig, LLMFactory
46
from apps.system.schemas.ai_model_schema import AiModelConfigItem, AiModelCreator, AiModelEditor, AiModelGridItem
57
from fastapi import APIRouter, Query
@@ -14,21 +16,31 @@
1416

1517
@router.post("/status")
1618
async def check_llm(info: AiModelCreator, trans: Trans):
17-
try:
18-
additional_params = {item.key: item.val for item in info.config_list}
19-
config = LLMConfig(
20-
model_type="openai",
21-
model_name=info.base_model,
22-
api_key=info.api_key,
23-
api_base_url=info.api_domain,
24-
additional_params=additional_params,
25-
)
26-
llm_instance = LLMFactory.create_llm(config)
27-
result = llm_instance.llm.invoke("who are you?")
28-
SQLBotLogUtil.info(f"check_llm result: {result}")
29-
except Exception as e:
30-
SQLBotLogUtil.error(f"Error checking LLM: {e}")
31-
raise Exception(trans('i18n_llm.validate_error', msg = str(e)))
19+
async def generate():
20+
try:
21+
additional_params = {item.key: item.val for item in info.config_list}
22+
config = LLMConfig(
23+
model_type="openai",
24+
model_name=info.base_model,
25+
api_key=info.api_key,
26+
api_base_url=info.api_domain,
27+
additional_params=additional_params,
28+
)
29+
llm_instance = LLMFactory.create_llm(config)
30+
31+
res = llm_instance.llm.stream("who are you?")
32+
33+
for chunk in res:
34+
if chunk and chunk.content:
35+
SQLBotLogUtil.info(chunk)
36+
yield json.dumps({"content": chunk.content}) + "\n"
37+
38+
except Exception as e:
39+
SQLBotLogUtil.error(f"Error checking LLM: {e}")
40+
error_msg = trans('i18n_llm.validate_error', msg=str(e))
41+
yield json.dumps({"error": error_msg}) + "\n"
42+
43+
return StreamingResponse(generate(), media_type="application/x-ndjson")
3244

3345
@router.get("", response_model=list[AiModelGridItem])
3446
async def query(

frontend/src/api/system.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ export const modelApi = {
88
delete: (id: number) => request.delete(`/system/aimodel/${id}`),
99
query: (id: number) => request.get(`/system/aimodel/${id}`),
1010
setDefault: (id: number) => request.put(`/system/aimodel/default/${id}`),
11-
check: (data: any) =>
12-
request.post('/system/aimodel/status', data, { requestOptions: { silent: true } }),
11+
check: (data: any) => request.fetchStream('/system/aimodel/status', data),
1312
}

frontend/src/views/system/model/Card.vue

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import delIcon from '@/assets/svg/icon_delete.svg'
33
import edit from '@/assets/svg/icon_edit_outlined.svg'
44
import { get_supplier } from '@/entity/supplier'
5-
import { computed } from 'vue'
5+
import { computed, ref } from 'vue'
66
const props = withDefaults(
77
defineProps<{
88
name: string
@@ -21,12 +21,22 @@ const props = withDefaults(
2121
supplier: 0,
2222
}
2323
)
24+
const errorMsg = ref('')
2425
const current_supplier = computed(() => {
2526
if (!props.supplier) {
2627
return null
2728
}
2829
return get_supplier(props.supplier)
2930
})
31+
const showErrorMask = (msg?: string) => {
32+
if (!msg) {
33+
return
34+
}
35+
errorMsg.value = msg
36+
setTimeout(() => {
37+
errorMsg.value = ''
38+
}, 3000)
39+
}
3040
const emits = defineEmits(['edit', 'del'])
3141
3242
const handleEdit = () => {
@@ -36,10 +46,17 @@ const handleEdit = () => {
3646
const handleDel = () => {
3747
emits('del', { id: props.id, name: props.name, default_model: props.isDefault })
3848
}
49+
50+
defineExpose({ showErrorMask })
3951
</script>
4052

4153
<template>
42-
<div class="card">
54+
<div
55+
v-loading="!!errorMsg"
56+
class="card"
57+
:element-loading-text="errorMsg"
58+
element-loading-custom-class="model-card-loading"
59+
>
4360
<div class="name-icon">
4461
<img :src="current_supplier?.icon" width="32px" height="32px" />
4562
<span :title="name" class="name ellipsis">{{ name }}</span>
@@ -163,5 +180,29 @@ const handleDel = () => {
163180
display: flex;
164181
}
165182
}
183+
:deep(.model-card-loading) {
184+
border-radius: 12px;
185+
display: flex;
186+
flex-direction: column;
187+
justify-content: flex-end;
188+
align-items: end;
189+
background-color: rgb(122 122 122 / 87%);
190+
.ed-loading-spinner {
191+
top: auto;
192+
margin: 8px 4px;
193+
display: flex;
194+
position: relative;
195+
justify-content: flex-end;
196+
align-items: center;
197+
width: calc(100% - 8px);
198+
}
199+
svg {
200+
display: none;
201+
}
202+
p {
203+
text-align: left;
204+
color: var(--ed-color-danger);
205+
}
206+
}
166207
}
167208
</style>

frontend/src/views/system/model/Model.vue

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ const activeStep = ref(0)
3434
const activeName = ref('')
3535
const activeType = ref('')
3636
const modelFormRef = ref()
37-
37+
const cardRefs = ref<any[]>([])
38+
const showCardError = ref(true)
3839
reactive({
3940
form: {
4041
id: '',
@@ -69,16 +70,46 @@ const defaultModelListWithSearch = computed(() => {
6970
})
7071
})
7172
72-
const modelCheckHandler = (item: any) => {
73+
const modelCheckHandler = async (item: any) => {
74+
const response = await modelApi.check(item)
75+
const reader = response.body.getReader()
76+
const decoder = new TextDecoder()
77+
let checkTimeout = false
7378
setTimeout(() => {
74-
modelApi.check(item).catch((err: any) => {
75-
if (err.response?.data?.msg) {
76-
ElMessage.error(t('model.check_failed', { msg: err.response.data.msg || '' }))
77-
} else {
78-
ElMessage.error(t('model.check_failed', { msg: err.message || '' }))
79+
checkTimeout = true
80+
}, 9000)
81+
let checkMsg = ''
82+
while (true) {
83+
if (checkTimeout) {
84+
break
85+
}
86+
const { done, value } = await reader.read()
87+
if (done) break
88+
const lines = decoder.decode(value).trim().split('\n')
89+
for (const line of lines) {
90+
const data = JSON.parse(line)
91+
if (data.error) {
92+
checkMsg += data.error
93+
} else if (data.content) {
94+
console.debug(data.content)
7995
}
80-
})
81-
}, 1000)
96+
}
97+
}
98+
if (!checkMsg) {
99+
return
100+
}
101+
console.error(checkMsg)
102+
if (!showCardError.value) {
103+
ElMessage.error(checkMsg)
104+
return
105+
}
106+
nextTick(() => {
107+
const index = modelListWithSearch.value.findIndex((el: any) => el.id === item.id)
108+
if (index > -1) {
109+
const currentRef = cardRefs.value[index]
110+
currentRef?.showErrorMask(checkMsg)
111+
}
112+
})
82113
}
83114
const duplicateName = async (item: any) => {
84115
const res = await modelApi.queryAll()
@@ -218,7 +249,11 @@ const preStep = () => {
218249
const saveModel = () => {
219250
modelFormRef.value.submitModel()
220251
}
221-
252+
const setCardRef = (el: any, index: number) => {
253+
if (el) {
254+
cardRefs.value[index] = el
255+
}
256+
}
222257
const search = () => {
223258
modelApi.queryAll().then((res: any) => {
224259
modelList.value = res
@@ -308,8 +343,9 @@ const submit = (item: any) => {
308343

309344
<div v-else class="card-content">
310345
<card
311-
v-for="ele in modelListWithSearch"
346+
v-for="(ele, index) in modelListWithSearch"
312347
:id="ele.id"
348+
:ref="(el: any) => setCardRef(el, index)"
313349
:key="ele.id"
314350
:name="ele.name"
315351
:supplier="ele.supplier"

0 commit comments

Comments
 (0)