feat: 添加模型上线检查功能
- 新增 llm_launch_check_api.dspy:执行完整的上线前检查 * 检查模型记录、日期、状态 * 检查上位系统(upapp)关联 * 检查API映射(uapi) * 检查IO定义(uapiio) * 检查能力映射(llm_api_map) * 检查定价项目(pricing_program) * 检查定价数据(pricingdata) * 支持体验测试(action=inference) - 新增 llm_launch_check.ui:检查结果展示界面 - 修改 llm.json:将'体验'按钮改为'上线检查' - 更新 load_path.py:注册新路径
This commit is contained in:
parent
308e91c61c
commit
3a0a8d4c86
@ -49,8 +49,8 @@
|
||||
"toolbar": {
|
||||
"tools":[
|
||||
{
|
||||
"name":"test",
|
||||
"label":"体验",
|
||||
"name":"launch_check",
|
||||
"label":"上线检查",
|
||||
"selected_row":true
|
||||
},
|
||||
{
|
||||
@ -68,16 +68,16 @@
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"test",
|
||||
"event":"launch_check",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"PopupWindow",
|
||||
"popup_options":{
|
||||
"title":"model Test",
|
||||
"cwidth":22,
|
||||
"height":"75%"
|
||||
"title":"上线检查",
|
||||
"cwidth":25,
|
||||
"cheight":20
|
||||
},
|
||||
"options":{
|
||||
"url":"{{entire_url('./llm_dialog.ui')}}",
|
||||
"url":"{{entire_url('./llm_launch_check.ui')}}",
|
||||
"params":{
|
||||
"id":"${id}"
|
||||
}
|
||||
|
||||
@ -56,6 +56,7 @@ PATHS_LOGINED = [
|
||||
f"/{MOD}/api_doc.ui",
|
||||
f"/{MOD}/api_doc.md",
|
||||
f"/{MOD}/llm_dialog.ui",
|
||||
f"/{MOD}/llm_launch_check.ui",
|
||||
f"/{MOD}/show_same_catelog_llm.ui",
|
||||
f"/{MOD}/show_llms.ui",
|
||||
f"/{MOD}/show_llms_by_providers.ui",
|
||||
@ -91,6 +92,7 @@ PATHS_LOGINED = [
|
||||
f"/{MOD}/api/get_search_providerid.dspy",
|
||||
f"/{MOD}/api/get_search_upappid.dspy",
|
||||
f"/{MOD}/api/get_upapps.dspy",
|
||||
f"/{MOD}/api/llm_launch_check_api.dspy",
|
||||
f"/{MOD}/api/llm_api_map_create.dspy",
|
||||
f"/{MOD}/api/llm_api_map_delete.dspy",
|
||||
f"/{MOD}/api/llm_api_map_list.dspy",
|
||||
|
||||
158
wwwroot/api/llm_launch_check_api.dspy
Normal file
158
wwwroot/api/llm_launch_check_api.dspy
Normal file
@ -0,0 +1,158 @@
|
||||
import json
|
||||
from sqlor.dbpools import get_sor_context
|
||||
from ahserver.serverenv import ServerEnv
|
||||
|
||||
llmid = params_kw.get('llmid', '')
|
||||
action = params_kw.get('action', 'check')
|
||||
|
||||
if not llmid:
|
||||
return json.dumps({'error': 'missing llmid'}, ensure_ascii=False)
|
||||
|
||||
if action == 'check':
|
||||
# Return check results
|
||||
from datetime import date
|
||||
today = date.today().isoformat()
|
||||
env = request._run_ns
|
||||
result = {'llmid': llmid, 'checks': [], 'all_passed': True}
|
||||
|
||||
async def add_check(name, passed, detail=''):
|
||||
result['checks'].append({'name': name, 'passed': passed, 'detail': detail})
|
||||
if not passed:
|
||||
result['all_passed'] = False
|
||||
|
||||
async with get_sor_context(env, 'llmage') as sor:
|
||||
# 1. llm record
|
||||
recs = await sor.sqlExe(
|
||||
"select * from llm where id=${llmid}$", {'llmid': llmid})
|
||||
if not recs:
|
||||
await add_check('模型记录', False, f'llm id={llmid} 不存在')
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
llm = recs[0]
|
||||
await add_check('模型记录', True, f'{llm.name} ({llm.model})')
|
||||
|
||||
# 2. dates & status
|
||||
date_ok = llm.enabled_date and llm.expired_date
|
||||
status_ok = llm.status == 'published'
|
||||
await add_check('日期与状态',
|
||||
date_ok and status_ok,
|
||||
f"启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}")
|
||||
|
||||
# 3. upapp
|
||||
recs = await sor.sqlExe(
|
||||
"select a.* from llm a, upapp b where a.id=${llmid}$ and a.upappid=b.id",
|
||||
{'llmid': llmid})
|
||||
if recs:
|
||||
await add_check('上位系统(upapp)', True, f'upappid={llm.upappid}')
|
||||
else:
|
||||
await add_check('上位系统(upapp)', False, f'upappid={llm.upappid} 未找到关联')
|
||||
|
||||
# 4. uapi
|
||||
recs = await sor.sqlExe("""
|
||||
select a.*, e.ioid, e.stream
|
||||
from llm a
|
||||
join upapp c on a.upappid = c.id
|
||||
join uapi e on c.apisetid = e.apisetid and a.apiname = e.name
|
||||
where a.id=${llmid}$""", {'llmid': llmid})
|
||||
if recs:
|
||||
await add_check('API映射(uapi)', True, f'ioid={recs[0].ioid}, stream={recs[0].stream}')
|
||||
else:
|
||||
await add_check('API映射(uapi)', False, f'apiname={getattr(llm, "apiname", "N/A")} 在upapp中未找到')
|
||||
|
||||
# 5. uapiio
|
||||
if recs:
|
||||
ioid = recs[0].ioid
|
||||
recs2 = await sor.sqlExe(
|
||||
"select * from uapiio where id=${ioid}$", {'ioid': ioid})
|
||||
if recs2:
|
||||
await add_check('IO定义(uapiio)', True, f'uapiio id={ioid}')
|
||||
else:
|
||||
await add_check('IO定义(uapiio)', False, f'ioid={ioid} 未找到')
|
||||
else:
|
||||
await add_check('IO定义(uapiio)', False, '依赖 uapi 未通过')
|
||||
|
||||
# 6. llm_api_map
|
||||
maps = await sor.sqlExe(
|
||||
"select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
|
||||
if maps:
|
||||
ppids = [m.ppid for m in maps if m.ppid]
|
||||
await add_check('能力映射(llm_api_map)', True,
|
||||
f'{len(maps)}条记录, {len(ppids)}个有定价')
|
||||
else:
|
||||
await add_check('能力映射(llm_api_map)', False, '无映射记录')
|
||||
ppids = []
|
||||
|
||||
# 7. pricing_program
|
||||
if ppids:
|
||||
ppid = ppids[0]
|
||||
async with get_sor_context(env, 'pricing') as psor:
|
||||
pregs = await psor.sqlExe(
|
||||
"select * from pricing_program where id=${ppid}$", {'ppid': ppid})
|
||||
if pregs:
|
||||
await add_check('定价项目(pricing_program)', True,
|
||||
f'{pregs[0].name} (id={ppid})')
|
||||
# 8. pricingdata
|
||||
datas = await psor.sqlExe(
|
||||
"select count(*) as cnt from pricingdata where ppid=${ppid}$",
|
||||
{'ppid': ppid})
|
||||
cnt = datas[0].cnt if datas else 0
|
||||
await add_check('定价数据(pricingdata)', cnt > 0,
|
||||
f'{cnt}条记录' if cnt > 0 else '无定价数据')
|
||||
else:
|
||||
await add_check('定价项目(pricing_program)', False,
|
||||
f'ppid={ppid} 未找到')
|
||||
await add_check('定价数据(pricingdata)', False, '依赖定价项目未通过')
|
||||
else:
|
||||
await add_check('定价项目(pricing_program)', False, 'llm_api_map中无ppid')
|
||||
await add_check('定价数据(pricingdata)', False, '无定价项目')
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
|
||||
elif action == 'inference':
|
||||
# Perform test inference
|
||||
# Get llm info
|
||||
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||
recs = await sor.sqlExe(
|
||||
"select * from llm where id=${llmid}$", {'llmid': llmid})
|
||||
if not recs:
|
||||
return json.dumps({'error': 'llm not found'}, ensure_ascii=False)
|
||||
llm = recs[0]
|
||||
|
||||
# Get API mapping
|
||||
maps = await sor.sqlExe(
|
||||
"select * from llm_api_map where llmid=${llmid}$ and isdefaultcatelog='1'",
|
||||
{'llmid': llmid})
|
||||
if not maps:
|
||||
return json.dumps({'error': 'no default api map'}, ensure_ascii=False)
|
||||
|
||||
api_map = maps[0]
|
||||
|
||||
# Call llminference logic
|
||||
try:
|
||||
from llmage.llmclient import llm_inference
|
||||
userid = await get_user()
|
||||
userorgid = await get_userorgid()
|
||||
|
||||
# Simple test message
|
||||
test_input = {'messages': [{'role': 'user', 'content': '你好,这是一条测试消息'}]}
|
||||
|
||||
result = await llm_inference(
|
||||
llmid=llmid,
|
||||
apiname=api_map.apiname,
|
||||
user_input=test_input,
|
||||
userid=userid,
|
||||
userorgid=userorgid,
|
||||
stream=False
|
||||
)
|
||||
|
||||
return json.dumps({
|
||||
'success': True,
|
||||
'response': result.get('response', ''),
|
||||
'usage': result.get('usage', {})
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return json.dumps({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
return json.dumps({'error': 'invalid action'}, ensure_ascii=False)
|
||||
61
wwwroot/llm_launch_check.ui
Normal file
61
wwwroot/llm_launch_check.ui
Normal file
@ -0,0 +1,61 @@
|
||||
{% if params_kw.id %}
|
||||
{% set llmid = params_kw.id %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"spacing": 10,
|
||||
"children": [
|
||||
{
|
||||
"widgettype": "Title",
|
||||
"options": {
|
||||
"text": "模型上线检查",
|
||||
"level": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"name": "check_status",
|
||||
"text": "检查中...",
|
||||
"i18n": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"name": "checks_list",
|
||||
"spacing": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"name": "test_btn",
|
||||
"text": "体验一次",
|
||||
"i18n": false,
|
||||
"disabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"name": "test_result",
|
||||
"text": "",
|
||||
"i18n": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"init": "async function(self) {\n const llmid = '{{llmid}}';\n const statusEl = self.children.check_status;\n const listEl = self.children.checks_list;\n const testBtn = self.children.test_btn;\n const testResult = self.children.test_result;\n \n async function runCheck() {\n statusEl.setText('检查中...');\n listEl.clear();\n testBtn.setDisabled(true);\n \n try {\n const url = bricks.absurl('../api/llm_launch_check_api.dspy?llmid=' + llmid, self);\n const resp = await fetch(url);\n const data = await resp.json();\n \n if (data.error) {\n statusEl.setText('错误: ' + data.error);\n return;\n }\n \n statusEl.setText(data.all_passed ? '✓ 所有检查通过' : '✗ 存在未通过项');\n statusEl.setStyle({color: data.all_passed ? '#4CAF50' : '#F44336', fontSize: '16px', fontWeight: 'bold'});\n \n data.checks.forEach(check => {\n const row = bricks.createWidget('HBox', {\n spacing: 10,\n children: [\n {widgettype: 'Text', options: {text: check.passed ? '✓' : '✗', style: {color: check.passed ? '#4CAF50' : '#F44336', fontSize: '18px', width: '30px'}}},\n {widgettype: 'Text', options: {text: check.name, style: {fontWeight: 'bold', width: '200px'}}},\n {widgettype: 'Text', options: {text: check.detail, style: {color: '#666'}}}\n ]\n });\n listEl.addChild(row);\n });\n \n if (data.all_passed) {\n testBtn.setDisabled(false);\n }\n } catch (e) {\n statusEl.setText('请求失败: ' + e.message);\n }\n }\n \n testBtn.on('click', async () => {\n testResult.setText('体验中...');\n try {\n const url = bricks.absurl('../api/llm_launch_check_api.dspy?llmid=' + llmid + '&action=inference', self);\n const resp = await fetch(url);\n const data = await resp.json();\n \n if (data.success) {\n testResult.setText('响应: ' + (data.response || JSON.stringify(data.usage)));\n testResult.setStyle({color: '#4CAF50'});\n } else {\n testResult.setText('失败: ' + data.error);\n testResult.setStyle({color: '#F44336'});\n }\n } catch (e) {\n testResult.setText('请求失败: ' + e.message);\n testResult.setStyle({color: '#F44336'});\n }\n });\n \n await runCheck();\n}"
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "缺少模型ID参数",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
Loading…
x
Reference in New Issue
Block a user