feat: support multi-catalog for LLMs
- Create llm_catalog_rel model for one-to-many relationship - Remove llmcatelogid from llm model - Update SQL queries in utils.py and dspy files to use join - Add maintenance UI (llm_catalog_rel_manage.ui) and API endpoints - Filter options by user's orgid
This commit is contained in:
parent
6a33c5e9aa
commit
6cc3986a2d
@ -141,29 +141,37 @@ async def get_llmcatelogs():
|
||||
|
||||
return []
|
||||
|
||||
async def get_llms_by_catelog():
|
||||
async def get_llms_by_catelog(catelogid=None):
|
||||
env = ServerEnv()
|
||||
async with get_sor_context(env, 'llmage') as sor:
|
||||
today = curDateString()
|
||||
sql = """select a.*, b.name as catelogname from llm a, llmcatelog b
|
||||
where a.llmcatelogid = b.id
|
||||
and enabled_date <= ${today}$
|
||||
and expired_date > ${today}$
|
||||
order by a.llmcatelogid, a.id
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
# Join with llm_catalog_rel to support multiple catalogs per LLM
|
||||
sql = """select a.*, b.name as catelogname, rel.llmcatelogid as catelog_id
|
||||
from llm a
|
||||
join llm_catalog_rel rel on a.id = rel.llmid
|
||||
join llmcatelog b on rel.llmcatelogid = b.id
|
||||
where a.enabled_date <= ${today}$
|
||||
and a.expired_date > ${today}$"""
|
||||
params = {'today': today}
|
||||
if catelogid:
|
||||
sql += " and rel.llmcatelogid = ${catelogid}$"
|
||||
params['catelogid'] = catelogid
|
||||
|
||||
sql += " order by rel.llmcatelogid, a.id"
|
||||
|
||||
recs = await sor.sqlExe(sql, params)
|
||||
d = []
|
||||
cid = ''
|
||||
x = None
|
||||
for r in recs:
|
||||
if cid != r.llmcatelogid:
|
||||
if cid != r.catelog_id:
|
||||
x = {
|
||||
'catelogid': r.llmcatelogid,
|
||||
'catelogid': r.catelog_id,
|
||||
'catelogname': r.catelogname,
|
||||
'llms': [r]
|
||||
}
|
||||
d.append(x)
|
||||
cid = r.llmcatelogid
|
||||
cid = r.catelog_id
|
||||
else:
|
||||
x['llms'].append(r)
|
||||
return d
|
||||
|
||||
BIN
models/llm.xlsx
BIN
models/llm.xlsx
Binary file not shown.
11
models/llm_catalog_rel.xlsx
Normal file
11
models/llm_catalog_rel.xlsx
Normal file
@ -0,0 +1,11 @@
|
||||
fields:
|
||||
name|title|type|length:int|dec:int|nullable|default|comments
|
||||
id|id|str|32|None|None|None|None
|
||||
llmid|模型id|str|32|None|None|None|None
|
||||
llmcatelogid|模型类型id|str|32|None|None|None|None
|
||||
|
||||
indexes:
|
||||
name|fields|unique
|
||||
idx_llm|llmid|None
|
||||
idx_catelog|llmcatelogid|None
|
||||
uq_llm_catelog|llmid,llmcatelogid|1
|
||||
32
wwwroot/api/llm_catalog_rel_create.dspy
Normal file
32
wwwroot/api/llm_catalog_rel_create.dspy
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid', 'type': 'error'}}
|
||||
|
||||
try:
|
||||
dbname = get_module_dbname('llmage')
|
||||
llmid = params_kw.get('llmid', '')
|
||||
catelogid = params_kw.get('llmcatelogid', '')
|
||||
|
||||
if not llmid or not catelogid:
|
||||
result['options'] = {'title': 'Error', 'message': '请选择模型和目录', 'type': 'error'}
|
||||
else:
|
||||
from appPublic.uniqueID import getID
|
||||
new_id = getID()
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
# 检查是否已存在
|
||||
check_sql = "select id from llm_catalog_rel where llmid = ${llmid}$ and llmcatelogid = ${catelogid}$"
|
||||
exists = await sor.sqlExe(check_sql, {'llmid': llmid, 'catelogid': catelogid})
|
||||
|
||||
if exists:
|
||||
result['options'] = {'title': '提示', 'message': '该关联已存在', 'type': 'warning'}
|
||||
else:
|
||||
data = {'id': new_id, 'llmid': llmid, 'llmcatelogid': catelogid}
|
||||
await sor.C('llm_catalog_rel', data)
|
||||
result['options'] = {'title': 'Success', 'message': '添加成功', 'type': 'success'}
|
||||
|
||||
except Exception as e:
|
||||
result['options'] = {'title': 'Error', 'message': f'添加失败: {str(e)}', 'type': 'error'}
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
20
wwwroot/api/llm_catalog_rel_delete.dspy
Normal file
20
wwwroot/api/llm_catalog_rel_delete.dspy
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid', 'type': 'error'}}
|
||||
|
||||
try:
|
||||
dbname = get_module_dbname('llmage')
|
||||
rel_id = params_kw.get('id', '')
|
||||
|
||||
if not rel_id:
|
||||
result['options'] = {'title': 'Error', 'message': '缺少ID参数', 'type': 'error'}
|
||||
else:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
await sor.sqlExe("delete from llm_catalog_rel where id = ${id}$", {'id': rel_id})
|
||||
result['options'] = {'title': 'Success', 'message': '删除成功', 'type': 'success'}
|
||||
|
||||
except Exception as e:
|
||||
result['options'] = {'title': 'Error', 'message': f'删除失败: {str(e)}', 'type': 'error'}
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
25
wwwroot/api/llm_catalog_rel_list.dspy
Normal file
25
wwwroot/api/llm_catalog_rel_list.dspy
Normal file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
result = {'success': False, 'rows': [], 'total': 0}
|
||||
|
||||
try:
|
||||
dbname = get_module_dbname('llmage')
|
||||
sql = """
|
||||
select r.id, r.llmid, l.name as llm_name, r.llmcatelogid, c.name as catelog_name
|
||||
from llm_catalog_rel r
|
||||
join llm l on r.llmid = l.id
|
||||
join llmcatelog c on r.llmcatelogid = c.id
|
||||
order by l.name, c.name
|
||||
"""
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
rows = await sor.sqlExe(sql, {})
|
||||
result['rows'] = [dict(r) for r in (rows or [])]
|
||||
result['total'] = len(result['rows'])
|
||||
result['success'] = True
|
||||
|
||||
except Exception as e:
|
||||
result['error'] = str(e)
|
||||
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
30
wwwroot/api/llm_catelog_options.dspy
Normal file
30
wwwroot/api/llm_catelog_options.dspy
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
result = {'success': False, 'data': {'llms': [], 'catelogs': []}}
|
||||
|
||||
try:
|
||||
dbname = get_module_dbname('llmage')
|
||||
user_orgid = await get_userorgid()
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
# Get all active LLMs belonging to the user's organization
|
||||
today = await get_business_date(sor)
|
||||
llms_sql = """select id, name from llm
|
||||
where enabled_date <= ${today}$
|
||||
and expired_date > ${today}$
|
||||
and ownerid = ${ownerid}$
|
||||
order by name"""
|
||||
llms = await sor.sqlExe(llms_sql, {'today': today, 'ownerid': user_orgid})
|
||||
result['data']['llms'] = [{'id': r['id'], 'text': r['name']} for r in (llms or [])]
|
||||
|
||||
# Get all catalogs (assuming catalogs are global or filtered similarly if needed)
|
||||
catelogs = await sor.sqlExe("select id, name from llmcatelog order by name", {})
|
||||
result['data']['catelogs'] = [{'id': r['id'], 'text': r['name']} for r in (catelogs or [])]
|
||||
|
||||
result['success'] = True
|
||||
|
||||
except Exception as e:
|
||||
result['error'] = str(e)
|
||||
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
@ -3,16 +3,16 @@ lt = '文生视频'
|
||||
if params_kw.type in ['文生视频', '参考生视频', '图生视频']:
|
||||
lt = params_kw.type
|
||||
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||
sql = '''select a.*, e.input_fields from llm a, llmcatelog b, upapp c, uapi d, uapiio e
|
||||
where a.llmcatelogid = b.id
|
||||
and a.upappid = c.id
|
||||
and c.apisetid = d.apisetid
|
||||
and a.apiname = d.name
|
||||
and d.ioid = e.id
|
||||
sql = '''select a.*, e.input_fields from llm a
|
||||
join llm_catalog_rel rel on a.id = rel.llmid
|
||||
join llmcatelog b on rel.llmcatelogid = b.id
|
||||
join upapp c on a.upappid = c.id
|
||||
join uapi d on c.apisetid = d.apisetid and a.apiname = d.name
|
||||
join uapiio e on d.ioid = e.id
|
||||
where b.name=${lt}$
|
||||
and a.enabled_date <= ${biz_date}$
|
||||
and ${biz_date}$ < a.expired_date
|
||||
and ppid is not NULL
|
||||
and b.name=${lt}$'''
|
||||
and ppid is not NULL'''
|
||||
biz_date = await get_business_date(sor)
|
||||
recs = await sor.sqlExe(sql, {
|
||||
'biz_date': biz_date,
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
dbname = get_module_dbname('llmage')
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
sql = """select * from llm where llmcatelogid = ${llmcatelogid}$ and id != ${llmid}$"""
|
||||
sql = """select * from llm a
|
||||
join llm_catalog_rel rel on a.id = rel.llmid
|
||||
where rel.llmcatelogid = ${llmcatelogid}$ and a.id != ${llmid}$"""
|
||||
ns = params_kw.copy()
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
for r in recs.get('rows', []):
|
||||
|
||||
@ -13,15 +13,15 @@ y.user_message,
|
||||
y.assisant_message
|
||||
from (
|
||||
select a.*, b.hfid, e.ioid, e.stream
|
||||
from llm a, llmcatelog b,upapp c, uapiset d, uapi e
|
||||
where a.llmcatelogid = b.id
|
||||
and a.upappid = c.id
|
||||
and c.apisetid = d.id
|
||||
and e.apisetid = d.id
|
||||
and a.apiname = e.name
|
||||
from llm a
|
||||
join llm_catalog_rel rel on a.id = rel.llmid
|
||||
join llmcatelog b on rel.llmcatelogid = b.id
|
||||
join upapp c on a.upappid = c.id
|
||||
join uapiset d on c.apisetid = d.id
|
||||
join uapi e on e.apisetid = d.id and a.apiname = e.name
|
||||
) x left join historyformat y on x.hfid = y.id
|
||||
left join uapiio z on x.ioid = z.id
|
||||
where llmcatelogid = ${llmcatelogid}$
|
||||
where rel.llmcatelogid = ${llmcatelogid}$
|
||||
and x.id != ${llmid}$
|
||||
"""
|
||||
ns = params_kw.copy()
|
||||
|
||||
145
wwwroot/llm_catalog_rel_manage.ui
Normal file
145
wwwroot/llm_catalog_rel_manage.ui
Normal file
@ -0,0 +1,145 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"spacing": 16
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"text": "LLM 目录关联管理",
|
||||
"halign": "left"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "calc(100% - 40px)",
|
||||
"margin": "0 20px",
|
||||
"padding": "16px",
|
||||
"bgcolor": "#f5f5f5",
|
||||
"spacing": 12
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "添加新关联",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Form",
|
||||
"id": "add_form",
|
||||
"options": {
|
||||
"layout": "horizontal",
|
||||
"cols": 3,
|
||||
"fields": [
|
||||
{
|
||||
"name": "llmid",
|
||||
"label": "选择模型",
|
||||
"uitype": "select",
|
||||
"dataurl": "{{entire_url('./api/llm_catelog_options.dspy')}}",
|
||||
"data_field": "llms",
|
||||
"placeholder": "请选择模型"
|
||||
},
|
||||
{
|
||||
"name": "llmcatelogid",
|
||||
"label": "选择目录",
|
||||
"uitype": "select",
|
||||
"dataurl": "{{entire_url('./api/llm_catelog_options.dspy')}}",
|
||||
"data_field": "catelogs",
|
||||
"placeholder": "请选择目录"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"name": "add_btn",
|
||||
"label": "添加关联",
|
||||
"variant": "primary"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "add_btn",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "PopupWindow",
|
||||
"popup_options": {"archor": "cc", "width": "30%", "height": "20%"},
|
||||
"options": {
|
||||
"url": "{{entire_url('./api/llm_catalog_rel_create.dspy')}}",
|
||||
"params": {
|
||||
"llmid": "$[add_form.llmid]$",
|
||||
"llmcatelogid": "$[add_form.llmcatelogid]$"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "calc(100% - 40px)",
|
||||
"margin": "0 20px",
|
||||
"spacing": 12
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "当前关联列表",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Tabular",
|
||||
"id": "rel_table",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "400px",
|
||||
"data_url": "{{entire_url('./api/llm_catalog_rel_list.dspy')}}",
|
||||
"data_method": "GET",
|
||||
"page_rows": 20,
|
||||
"row_options": {
|
||||
"fields": [
|
||||
{"name": "llm_name", "title": "模型名称", "width": 200},
|
||||
{"name": "catelog_name", "title": "目录名称", "width": 150},
|
||||
{
|
||||
"name": "actions",
|
||||
"title": "操作",
|
||||
"width": 100,
|
||||
"uitype": "button",
|
||||
"data": [
|
||||
{"text": "删除", "event": "delete_rel"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "delete_rel",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "PopupWindow",
|
||||
"popup_options": {"archor": "cc", "width": "30%", "height": "20%"},
|
||||
"options": {
|
||||
"url": "{{entire_url('./api/llm_catalog_rel_delete.dspy')}}",
|
||||
"params": {
|
||||
"id": "$[event.params.id]$"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -22,7 +22,6 @@
|
||||
"models":[
|
||||
{
|
||||
"llmid":"{{llm.id}}",
|
||||
"llmcatelogid":"{{llm.llmcatelogid}}",
|
||||
"response_mode": "{{llm.stream}}",
|
||||
"icon":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}",
|
||||
"url":"{{entire_url('/llmage/llminference.dspy')}}",
|
||||
|
||||
@ -17,10 +17,11 @@ if not params_kw.prompt:
|
||||
return json_response(d, status=400)
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, 'llmage') as sor:
|
||||
sql = """select a.* from llm a, llmcatelog b
|
||||
where a.llmcatelogid=b.id
|
||||
and a.model=${model}$
|
||||
and b.name = ${lctype}$"""
|
||||
sql = """select a.* from llm a
|
||||
join llm_catalog_rel rel on a.id = rel.llmid
|
||||
join llmcatelog b on rel.llmcatelogid = b.id
|
||||
where b.name = ${lctype}$
|
||||
and a.model=${model}$"""
|
||||
recs = await sor.sqlExe(sql, {
|
||||
'lctype': lctype,
|
||||
'model': params_kw.model or 'qwen3-max'
|
||||
|
||||
@ -30,10 +30,11 @@ if not params_kw.prompt and not params_kw.messages:
|
||||
return json_response(d, status=400)
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, 'llmage') as sor:
|
||||
sql = """select a.* from llm a, llmcatelog b
|
||||
where a.llmcatelogid=b.id
|
||||
and a.model=${model}$
|
||||
and b.name = ${lctype}$"""
|
||||
sql = """select a.* from llm a
|
||||
join llm_catalog_rel rel on a.id = rel.llmid
|
||||
join llmcatelog b on rel.llmcatelogid = b.id
|
||||
where b.name = ${lctype}$
|
||||
and a.model=${model}$"""
|
||||
recs = await sor.sqlExe(sql, {
|
||||
'lctype': lctype,
|
||||
'model': params_kw.model or 'qwen3-max'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user