fix: 上线检查改为urlwidget子控件模式,每个检查项独立dspy(bug3)

This commit is contained in:
Hermes Agent 2026-06-16 16:25:26 +08:00
parent 75fe89ac2e
commit ea2f08e443
11 changed files with 311 additions and 185 deletions

View File

@ -57,7 +57,14 @@ PATHS_LOGINED = [
f"/{MOD}/api_doc.md",
f"/{MOD}/llm_dialog.ui",
f"/{MOD}/llm_launch_check.ui",
f"/{MOD}/llm_launch_check_page.dspy",
f"/{MOD}/check_model_record.dspy",
f"/{MOD}/check_date_status.dspy",
f"/{MOD}/check_upapp.dspy",
f"/{MOD}/check_uapi.dspy",
f"/{MOD}/check_uapiio.dspy",
f"/{MOD}/check_llm_api_map.dspy",
f"/{MOD}/check_pricing_program.dspy",
f"/{MOD}/check_pricing_data.dspy",
f"/{MOD}/show_same_catelog_llm.ui",
f"/{MOD}/show_llms.ui",
f"/{MOD}/show_llms_by_providers.ui",

View File

@ -0,0 +1,31 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ 日期与状态: 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
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({
"widgettype": "Text",
"options": {"text": "❌ 日期与状态: 模型不存在", "i18n": false}
}, ensure_ascii=False)
llm = recs[0]
date_ok = bool(llm.enabled_date and llm.expired_date)
status_ok = llm.status == 'published'
if date_ok and status_ok:
text = f"✅ 日期与状态: 启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}"
else:
text = f"❌ 日期与状态: 启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

View File

@ -0,0 +1,22 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ 能力映射(llm_api_map): 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
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]
text = f"✅ 能力映射(llm_api_map): {len(maps)}条记录, {len(ppids)}个有定价"
else:
text = "❌ 能力映射(llm_api_map): 无映射记录"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

View File

@ -0,0 +1,22 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ 模型记录: 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
recs = await sor.sqlExe(
"select * from llm where id=${llmid}$", {'llmid': llmid})
if recs:
llm = recs[0]
text = f"✅ 模型记录: {llm.name} ({llm.model})"
else:
text = f"❌ 模型记录: llm id={llmid} 不存在"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

View File

@ -0,0 +1,36 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ 定价数据(pricingdata): 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
maps = await sor.sqlExe(
"select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
ppids = [m.ppid for m in maps if m.ppid] if maps else []
if not ppids:
text = "❌ 定价数据(pricingdata): 无定价项目"
else:
ppid = ppids[0]
async with get_sor_context(request._run_ns, 'pricing') as psor:
# First check if pricing_program exists
pregs = await psor.sqlExe(
"select * from pricing_program where id=${ppid}$", {'ppid': ppid})
if not pregs:
text = "❌ 定价数据(pricingdata): 依赖定价项目未通过"
else:
datas = await psor.sqlExe(
"select count(*) as cnt from pricingdata where ppid=${ppid}$", {'ppid': ppid})
cnt = datas[0].cnt if datas else 0
if cnt > 0:
text = f"✅ 定价数据(pricingdata): {cnt}条记录"
else:
text = "❌ 定价数据(pricingdata): 无定价数据"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

View File

@ -0,0 +1,29 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ 定价项目(pricing_program): 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
maps = await sor.sqlExe(
"select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
ppids = [m.ppid for m in maps if m.ppid] if maps else []
if not ppids:
text = "❌ 定价项目(pricing_program): llm_api_map中无ppid"
else:
ppid = ppids[0]
async with get_sor_context(request._run_ns, 'pricing') as psor:
pregs = await psor.sqlExe(
"select * from pricing_program where id=${ppid}$", {'ppid': ppid})
if pregs:
text = f"✅ 定价项目(pricing_program): {pregs[0].name} (id={ppid})"
else:
text = f"❌ 定价项目(pricing_program): ppid={ppid} 未找到"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

30
wwwroot/check_uapi.dspy Normal file
View File

@ -0,0 +1,30 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ API映射(uapi): 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
recs = await sor.sqlExe("""
select a.*, e.ioid, e.stream
from llm a
join llm_api_map m on a.id = m.llmid
join upapp c on a.upappid = c.id
join uapi e on c.id = e.upappid and m.apiname = e.name
where a.id=${llmid}$""", {'llmid': llmid})
if recs:
text = f"✅ API映射(uapi): ioid={recs[0].ioid}, stream={recs[0].stream}"
else:
# Get apiname from llm
async with get_sor_context(request._run_ns, 'llmage') as sor:
llm_recs = await sor.sqlExe("select apiname from llm where id=${llmid}$", {'llmid': llmid})
apiname = llm_recs[0].apiname if llm_recs else 'N/A'
text = f"❌ API映射(uapi): apiname={apiname} 在upapp中未找到"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

33
wwwroot/check_uapiio.dspy Normal file
View File

@ -0,0 +1,33 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ IO定义(uapiio): 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
# First get ioid from uapi
recs = await sor.sqlExe("""
select e.ioid
from llm a
join llm_api_map m on a.id = m.llmid
join upapp c on a.upappid = c.id
join uapi e on c.id = e.upappid and m.apiname = e.name
where a.id=${llmid}$""", {'llmid': llmid})
if not recs:
text = "❌ IO定义(uapiio): 依赖 uapi 未通过"
else:
ioid = recs[0].ioid
recs2 = await sor.sqlExe(
"select * from uapiio where id=${ioid}$", {'ioid': ioid})
if recs2:
text = f"✅ IO定义(uapiio): uapiio id={ioid}"
else:
text = f"❌ IO定义(uapiio): ioid={ioid} 未找到"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

27
wwwroot/check_upapp.dspy Normal file
View File

@ -0,0 +1,27 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({
"widgettype": "Text",
"options": {"text": "❌ 上位系统(upapp): 缺少llmid参数", "i18n": false}
}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
recs = await sor.sqlExe(
"select a.* from llm a, upapp b where a.id=${llmid}$ and a.upappid=b.id",
{'llmid': llmid})
if recs:
llm = recs[0]
text = f"✅ 上位系统(upapp): upappid={llm.upappid}"
else:
# Get llm info to show upappid
llm_recs = await sor.sqlExe(
"select upappid from llm where id=${llmid}$", {'llmid': llmid})
upappid = llm_recs[0].upappid if llm_recs else '未知'
text = f"❌ 上位系统(upapp): upappid={upappid} 未找到关联"
return json.dumps({
"widgettype": "Text",
"options": {"text": text, "i18n": false}
}, ensure_ascii=False)

View File

@ -28,15 +28,64 @@
"options": {
"name": "checks_list",
"spacing": 5
}
},
"subwidgets": [
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_model_record.dspy')}}?llmid={{llmid}}"
}
},
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_date_status.dspy')}}?llmid={{llmid}}"
}
},
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_upapp.dspy')}}?llmid={{llmid}}"
}
},
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_uapi.dspy')}}?llmid={{llmid}}"
}
},
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_uapiio.dspy')}}?llmid={{llmid}}"
}
},
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_llm_api_map.dspy')}}?llmid={{llmid}}"
}
},
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_pricing_program.dspy')}}?llmid={{llmid}}"
}
},
{
"widgettype": "urlwidget",
"options": {
"url": "{{entire_url('./check_pricing_data.dspy')}}?llmid={{llmid}}"
}
}
]
},
{
"widgettype": "Button",
"options": {
"name": "test_btn",
"text": "体验一次",
"i18n": false,
"disabled": true
"label": "体验一次",
"i18n": false
}
},
{
@ -50,16 +99,15 @@
{
"widgettype": "Button",
"options": {
"name": "check_charging_btn",
"text": "检查计费",
"i18n": false,
"disabled": true
"name": "charge_btn",
"label": "检查计费",
"i18n": false
}
},
{
"widgettype": "Text",
"options": {
"name": "charging_result",
"name": "charge_result",
"text": "",
"i18n": false
}
@ -67,11 +115,22 @@
],
"binds": [
{
"wid": "self",
"event": "load",
"actiontype": "script",
"target": "self",
"script": "var resp = await fetch('{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=check'); var data = await resp.json(); var statusEl = this.getWidgetByName('check_status'); var listEl = this.getWidgetByName('checks_list'); if(data.error) { statusEl.set_text('错误: ' + data.error); return; } var html = ''; var allOk = true; (data.checks||[]).forEach(function(c){ var icon = c.passed ? '✅' : '❌'; if(!c.passed) allOk = false; html += '<div style=\"padding:4px 0\">' + icon + ' <b>' + c.name + '</b>: ' + (c.detail||'') + '</div>'; }); if(listEl && listEl.dom_element) listEl.dom_element.innerHTML = html; if(allOk) { statusEl.set_text('全部通过 ✅'); var testBtn = this.getWidgetByName('test_btn'); if(testBtn) { testBtn.dom_element.disabled = false; testBtn.dom_element.onclick = async function(){ var r2 = await fetch('{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=inference'); var d2 = await r2.json(); var tr = this.getWidgetByName('test_result'); if(d2.success) tr.set_text('回复: ' + d2.response); else tr.set_text('失败: ' + d2.error); }.bind(this); } } else { statusEl.set_text('存在问题 ❌ 请检查下方详情'); }"
"wid": "test_btn",
"event": "click",
"actiontype": "urldata",
"target": "test_result",
"options": {
"url": "{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=inference"
}
},
{
"wid": "charge_btn",
"event": "click",
"actiontype": "urldata",
"target": "charge_result",
"options": {
"url": "{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=check_charging&usages={% raw %}{{"prompt_tokens":1000,"completion_tokens":500}}{% endraw %}"
}
}
]
}

View File

@ -1,170 +0,0 @@
# llm_launch_check_page.dspy
# 服务端渲染模型上线检查页面输出bricks widget JSON
# 所有检查在服务端async执行结果直接嵌入widget
try:
llmid = params_kw.get('id', '')
if not llmid:
return json.dumps({
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "spacing": 10},
"subwidgets": [
{"widgettype": "Title", "options": {"text": "模型上线检查", "level": 2}},
{"widgettype": "Text", "options": {"text": "缺少模型ID参数", "i18n": False}}
]
}, ensure_ascii=False)
# --- 服务端执行所有检查 ---
checks = []
all_passed = True
llm_name = ''
llm_model = ''
async def add_check(name, passed, detail=''):
global all_passed
checks.append({'name': name, 'passed': passed, 'detail': detail})
if not passed:
all_passed = False
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:
await add_check('模型记录', False, f'llm id={llmid} 不存在')
else:
llm = recs[0]
llm_name = llm.name or ''
llm_model = llm.model or ''
await add_check('模型记录', True, f'{llm_name} ({llm_model})')
date_ok = bool(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}")
recs2 = await sor.sqlExe(
"select a.* from llm a, upapp b where a.id=${llmid}$ and a.upappid=b.id",
{'llmid': llmid})
await add_check('上位系统(upapp)', bool(recs2),
f'upappid={llm.upappid}' + ('' if recs2 else ' 未找到关联'))
recs3 = await sor.sqlExe("""
select a.*, e.ioid, e.stream
from llm a
join llm_api_map m on a.id = m.llmid
join upapp c on a.upappid = c.id
join uapi e on c.id = e.upappid and m.apiname = e.name
where a.id=${llmid}$""", {'llmid': llmid})
if recs3:
await add_check('API映射(uapi)', True, f'ioid={recs3[0].ioid}, stream={recs3[0].stream}')
ioid = recs3[0].ioid
recs4 = await sor.sqlExe(
"select * from uapiio where id=${ioid}$", {'ioid': ioid})
await add_check('IO定义(uapiio)', bool(recs4),
f'uapiio id={ioid}' if recs4 else f'ioid={ioid} 未找到')
else:
await add_check('API映射(uapi)', False, f'apiname={getattr(llm, "apiname", "N/A")} 在upapp中未找到')
await add_check('IO定义(uapiio)', False, '依赖 uapi 未通过')
maps = await sor.sqlExe(
"select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
ppids = []
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, '无映射记录')
if ppids:
ppid = ppids[0]
async with get_sor_context(request._run_ns, '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})')
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, '无定价项目')
# --- 构建widget JSON ---
api_url = entire_url('./api/llm_launch_check_api.dspy')
status_text = '全部通过 ✅' if all_passed else '存在问题 ❌ 请检查下方详情'
# 检查项列表每项一行Text widget
check_widgets = []
for c in checks:
icon = '✅' if c['passed'] else '❌'
text = f"{icon} {c['name']}: {c['detail']}"
check_widgets.append({
"widgettype": "Text",
"options": {"text": text, "i18n": False}
})
# 按钮和结果显示区
subwidgets = [
{"widgettype": "Title", "options": {"text": f"模型上线检查 — {llm_name}", "level": 2}},
{"widgettype": "Text", "options": {"text": status_text, "i18n": False}},
] + check_widgets
if all_passed:
subwidgets.append({
"widgettype": "Button",
"options": {"name": "test_btn", "label": "体验一次", "i18n": False},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urldata",
"target": "test_result",
"options": {
"url": f"{api_url}?llmid={llmid}&action=inference"
}
}]
})
subwidgets.append({
"widgettype": "Text",
"options": {"name": "test_result", "text": "", "i18n": False}
})
subwidgets.append({
"widgettype": "Button",
"options": {"name": "charge_btn", "label": "检查计费", "i18n": False},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urldata",
"target": "charge_result",
"options": {
"url": f"{api_url}?llmid={llmid}&action=check_charging&usages=" + json.dumps({"prompt_tokens": 1000, "completion_tokens": 500})
}
}]
})
subwidgets.append({
"widgettype": "Text",
"options": {"name": "charge_result", "text": "", "i18n": False}
})
widget = {
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "spacing": 10},
"subwidgets": subwidgets
}
return json.dumps(widget, ensure_ascii=False)
except Exception as e:
return json.dumps({
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "spacing": 10},
"subwidgets": [
{"widgettype": "Title", "options": {"text": "模型上线检查", "level": 2}},
{"widgettype": "Text", "options": {"text": f"执行错误: {format_exc()}", "i18n": False}}
]
}, ensure_ascii=False)