Merge branch 'main' of git.opencomputing.cn:yumoqing/kboss
This commit is contained in:
commit
251b71f7f6
@ -1,12 +1,15 @@
|
|||||||
# 可写入/更新的字段(不含 id、created_at、updated_at)
|
# model_management 可写入字段(不含 id、created_at、updated_at)
|
||||||
_MODEL_FIELDS = (
|
_MODEL_FIELDS = (
|
||||||
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
|
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
|
||||||
'context_length', 'input_token_price', 'output_token_price',
|
'context_length', 'input_token_price', 'output_token_price',
|
||||||
'cache_hit_input_price', 'billing_method', 'billing_unit',
|
'cache_hit_input_price', 'billing_method', 'billing_unit',
|
||||||
'capabilities', 'limitations', 'highlights', 'is_active',
|
'capabilities', 'limitations', 'highlights', 'is_active',
|
||||||
'description', 'listing_status',
|
'description', 'listing_status', 'sort_order', 'experience',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# model_api_doc 可写入字段(不含 id、model_id、created_at、updated_at)
|
||||||
|
_API_DOC_FIELDS = ('api_url', 'curl_code', 'python_code')
|
||||||
|
|
||||||
|
|
||||||
def _escape(value):
|
def _escape(value):
|
||||||
if value is None:
|
if value is None:
|
||||||
@ -24,26 +27,63 @@ def _build_model_dict(ns, include_listing_status=False):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _build_api_doc_dict(ns):
|
||||||
|
data = {}
|
||||||
|
for field in _API_DOC_FIELDS:
|
||||||
|
if field in ns and ns.get(field) is not None:
|
||||||
|
data[field] = ns.get(field)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def model_management_add(ns={}):
|
async def model_management_add(ns={}):
|
||||||
"""新增模型,默认待上架 listing_status=0"""
|
"""新增模型及 API 文档,provider、model_name 必传"""
|
||||||
if not ns.get('provider') or not ns.get('model_name'):
|
if not ns.get('provider') or not ns.get('model_name'):
|
||||||
return {'status': False, 'msg': 'provider and model_name are required'}
|
return {'status': False, 'msg': 'provider and model_name are required'}
|
||||||
|
|
||||||
ns_dic = _build_model_dict(ns, include_listing_status=True)
|
model_dic = _build_model_dict(ns, include_listing_status=True)
|
||||||
if 'listing_status' not in ns_dic:
|
if 'listing_status' not in model_dic:
|
||||||
ns_dic['listing_status'] = 0
|
model_dic['listing_status'] = 0
|
||||||
if 'is_active' not in ns_dic:
|
if 'is_active' not in model_dic:
|
||||||
ns_dic['is_active'] = 1
|
model_dic['is_active'] = 1
|
||||||
|
|
||||||
|
api_doc_dic = _build_api_doc_dict(ns)
|
||||||
|
if api_doc_dic and not api_doc_dic.get('api_url'):
|
||||||
|
return {'status': False, 'msg': 'api_url is required when creating api doc'}
|
||||||
|
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
try:
|
try:
|
||||||
await sor.C('model_management', ns_dic)
|
await sor.C('model_management', model_dic)
|
||||||
return {'status': True, 'msg': 'create model success', 'data': ns_dic}
|
|
||||||
|
id_rows = await sor.sqlExe('SELECT LAST_INSERT_ID() AS id;', {})
|
||||||
|
model_id = id_rows[0]['id'] if id_rows else None
|
||||||
|
if not model_id:
|
||||||
|
await sor.rollback()
|
||||||
|
return {'status': False, 'msg': 'create model failed, missing model id'}
|
||||||
|
|
||||||
|
# 未传 sort_order 时,默认与新建主键 id 相同
|
||||||
|
if not (
|
||||||
|
'sort_order' in ns
|
||||||
|
and ns.get('sort_order') is not None
|
||||||
|
and ns.get('sort_order') != ''
|
||||||
|
):
|
||||||
|
await sor.U('model_management', {'id': model_id, 'sort_order': model_id})
|
||||||
|
model_dic['sort_order'] = model_id
|
||||||
|
|
||||||
|
result_data = dict(model_dic)
|
||||||
|
result_data['id'] = model_id
|
||||||
|
|
||||||
|
if api_doc_dic:
|
||||||
|
create_dic = dict(api_doc_dic)
|
||||||
|
create_dic['model_id'] = str(model_id)
|
||||||
|
await sor.C('model_api_doc', create_dic)
|
||||||
|
result_data['api_doc'] = create_dic
|
||||||
|
|
||||||
|
return {'status': True, 'msg': 'create model success', 'data': result_data}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await sor.rollback()
|
await sor.rollback()
|
||||||
return {'status': False, 'msg': 'create model failed, %s' % str(e)}
|
return {'status': False, 'msg': 'create model failed, %s' % str(e)}
|
||||||
|
|
||||||
|
|
||||||
ret = await model_management_add(params_kw)
|
ret = await model_management_add(params_kw)
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
153
b/cntoai/model_management_search_doc.dspy
Normal file
153
b/cntoai/model_management_search_doc.dspy
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# 可写入/更新的字段(不含 id、created_at、updated_at)
|
||||||
|
_MODEL_FIELDS = (
|
||||||
|
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
|
||||||
|
'context_length', 'input_token_price', 'output_token_price',
|
||||||
|
'cache_hit_input_price', 'billing_method', 'billing_unit',
|
||||||
|
'capabilities', 'limitations', 'highlights', 'is_active',
|
||||||
|
'description', 'listing_status', 'sort_order',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _escape(value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return str(value).replace("'", "''")
|
||||||
|
|
||||||
|
|
||||||
|
def _build_model_dict(ns, include_listing_status=False):
|
||||||
|
data = {}
|
||||||
|
for field in _MODEL_FIELDS:
|
||||||
|
if field in ns and ns.get(field) is not None and ns.get(field) != '':
|
||||||
|
data[field] = ns.get(field)
|
||||||
|
if include_listing_status and 'listing_status' not in data:
|
||||||
|
data['listing_status'] = ns.get('listing_status', 0)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _build_where_conditions(ns, table_alias='m'):
|
||||||
|
"""构建 model_management 筛选条件(带表别名,用于 JOIN 查询)"""
|
||||||
|
prefix = '%s.' % table_alias
|
||||||
|
conditions = ['1=1']
|
||||||
|
if ns.get('display_name'):
|
||||||
|
display_name = _escape(ns.get('display_name'))
|
||||||
|
conditions.append("%sdisplay_name LIKE '%%%%%s%%%%'" % (prefix, display_name))
|
||||||
|
if ns.get('model_type'):
|
||||||
|
conditions.append("%smodel_type = '%s'" % (prefix, _escape(ns.get('model_type'))))
|
||||||
|
if ns.get('provider'):
|
||||||
|
conditions.append("%sprovider = '%s'" % (prefix, _escape(ns.get('provider'))))
|
||||||
|
if ns.get('listing_status') is not None and ns.get('listing_status') != '':
|
||||||
|
conditions.append("%slisting_status = '%s'" % (prefix, _escape(ns.get('listing_status'))))
|
||||||
|
return ' AND '.join(conditions)
|
||||||
|
|
||||||
|
|
||||||
|
def _attach_api_doc(row):
|
||||||
|
"""将 JOIN 出的 API 文档字段整理为 api_doc 子对象"""
|
||||||
|
api_doc_id = row.pop('api_doc_id', None)
|
||||||
|
api_url = row.pop('api_url', None)
|
||||||
|
curl_code = row.pop('curl_code', None)
|
||||||
|
python_code = row.pop('python_code', None)
|
||||||
|
api_doc_created_at = row.pop('api_doc_created_at', None)
|
||||||
|
api_doc_updated_at = row.pop('api_doc_updated_at', None)
|
||||||
|
|
||||||
|
if api_doc_id:
|
||||||
|
row['api_doc'] = {
|
||||||
|
'id': api_doc_id,
|
||||||
|
'model_id': str(row.get('id', '')),
|
||||||
|
'api_url': api_url,
|
||||||
|
'curl_code': curl_code,
|
||||||
|
'python_code': python_code,
|
||||||
|
'created_at': api_doc_created_at,
|
||||||
|
'updated_at': api_doc_updated_at,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
row['api_doc'] = None
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
async def model_management_search_doc(ns={}):
|
||||||
|
"""
|
||||||
|
分页查询模型列表(含 API 文档),支持按 display_name / model_type / provider / listing_status 筛选。
|
||||||
|
model_management LEFT JOIN model_api_doc(model_id = model_management.id)。
|
||||||
|
返回模型总数、待上架总数、已上架总数,以及厂商列表、模型类型列表;每条模型含 api_doc。
|
||||||
|
"""
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
page_size = int(ns.get('page_size', 1000))
|
||||||
|
current_page = int(ns.get('current_page', 1))
|
||||||
|
offset = (current_page - 1) * page_size
|
||||||
|
where_clause = _build_where_conditions(ns)
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext('kboss') as sor:
|
||||||
|
try:
|
||||||
|
stats_sql = """
|
||||||
|
SELECT COUNT(*) AS total_count,
|
||||||
|
SUM(CASE WHEN listing_status = 0 THEN 1 ELSE 0 END) AS pending_count,
|
||||||
|
SUM(CASE WHEN listing_status = 1 THEN 1 ELSE 0 END) AS listed_count
|
||||||
|
FROM model_management;
|
||||||
|
"""
|
||||||
|
stats_li = await sor.sqlExe(stats_sql, {})
|
||||||
|
stats = stats_li[0] if stats_li else {}
|
||||||
|
|
||||||
|
provider_sql = """
|
||||||
|
SELECT DISTINCT provider FROM model_management
|
||||||
|
WHERE provider IS NOT NULL AND provider != ''
|
||||||
|
ORDER BY provider;
|
||||||
|
"""
|
||||||
|
model_type_sql = """
|
||||||
|
SELECT DISTINCT model_type FROM model_management
|
||||||
|
WHERE model_type IS NOT NULL AND model_type != ''
|
||||||
|
ORDER BY model_type;
|
||||||
|
"""
|
||||||
|
|
||||||
|
count_sql = """
|
||||||
|
SELECT COUNT(*) AS total_count
|
||||||
|
FROM model_management m
|
||||||
|
WHERE %s;
|
||||||
|
""" % where_clause
|
||||||
|
|
||||||
|
find_sql = """
|
||||||
|
SELECT m.*,
|
||||||
|
d.id AS api_doc_id,
|
||||||
|
d.api_url,
|
||||||
|
d.curl_code,
|
||||||
|
d.python_code,
|
||||||
|
d.created_at AS api_doc_created_at,
|
||||||
|
d.updated_at AS api_doc_updated_at
|
||||||
|
FROM model_management m
|
||||||
|
LEFT JOIN model_api_doc d ON d.model_id = CAST(m.id AS CHAR)
|
||||||
|
WHERE %s
|
||||||
|
ORDER BY m.sort_order ASC
|
||||||
|
LIMIT %s OFFSET %s;
|
||||||
|
""" % (where_clause, page_size, offset)
|
||||||
|
|
||||||
|
provider_rows = await sor.sqlExe(provider_sql, {})
|
||||||
|
model_type_rows = await sor.sqlExe(model_type_sql, {})
|
||||||
|
filter_total = (await sor.sqlExe(count_sql, {}))[0]['total_count']
|
||||||
|
model_rows = await sor.sqlExe(find_sql, {})
|
||||||
|
model_list = [_attach_api_doc(row) for row in model_rows]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'status': True,
|
||||||
|
'msg': 'search model with api doc success',
|
||||||
|
'data': {
|
||||||
|
'total_count': stats.get('total_count', 0),
|
||||||
|
'pending_count': int(stats.get('pending_count') or 0),
|
||||||
|
'listed_count': int(stats.get('listed_count') or 0),
|
||||||
|
'provider_list': [r['provider'] for r in provider_rows],
|
||||||
|
'model_type_list': [r['model_type'] for r in model_type_rows],
|
||||||
|
'filter_total': filter_total,
|
||||||
|
'page_size': page_size,
|
||||||
|
'current_page': current_page,
|
||||||
|
'model_list': model_list,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
'status': False,
|
||||||
|
'msg': 'search model with api doc failed, %s' % traceback.format_exc(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ret = await model_management_search_doc(params_kw)
|
||||||
|
return ret
|
||||||
@ -1,12 +1,15 @@
|
|||||||
# 可写入/更新的字段(不含 id、created_at、updated_at)
|
# model_management 可写入字段(不含 id、created_at、updated_at)
|
||||||
_MODEL_FIELDS = (
|
_MODEL_FIELDS = (
|
||||||
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
|
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
|
||||||
'context_length', 'input_token_price', 'output_token_price',
|
'context_length', 'input_token_price', 'output_token_price',
|
||||||
'cache_hit_input_price', 'billing_method', 'billing_unit',
|
'cache_hit_input_price', 'billing_method', 'billing_unit',
|
||||||
'capabilities', 'limitations', 'highlights', 'is_active',
|
'capabilities', 'limitations', 'highlights', 'is_active',
|
||||||
'description', 'listing_status',
|
'description', 'listing_status', 'sort_order', 'experience',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# model_api_doc 可写入字段(不含 id、model_id、created_at、updated_at)
|
||||||
|
_API_DOC_FIELDS = ('api_url', 'curl_code', 'python_code')
|
||||||
|
|
||||||
|
|
||||||
def _escape(value):
|
def _escape(value):
|
||||||
if value is None:
|
if value is None:
|
||||||
@ -23,23 +26,73 @@ def _build_model_dict(ns, include_listing_status=False):
|
|||||||
data['listing_status'] = ns.get('listing_status', 0)
|
data['listing_status'] = ns.get('listing_status', 0)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _build_api_doc_dict(ns):
|
||||||
|
"""构建 API 文档更新字典;字段在 ns 中即参与更新(含空字符串)"""
|
||||||
|
data = {}
|
||||||
|
for field in _API_DOC_FIELDS:
|
||||||
|
if field in ns and ns.get(field) is not None:
|
||||||
|
data[field] = ns.get(field)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def model_management_update(ns={}):
|
async def model_management_update(ns={}):
|
||||||
"""编辑模型,id 必传"""
|
"""编辑模型及 API 文档,id(model_management 主键)必传"""
|
||||||
model_id = ns.get('id')
|
model_id = ns.get('id')
|
||||||
if not model_id:
|
if not model_id:
|
||||||
return {'status': False, 'msg': 'id is required'}
|
return {'status': False, 'msg': 'id is required'}
|
||||||
|
|
||||||
ns_dic = _build_model_dict(ns)
|
model_dic = _build_model_dict(ns)
|
||||||
ns_dic['id'] = model_id
|
api_doc_dic = _build_api_doc_dict(ns)
|
||||||
|
has_model_update = bool(model_dic)
|
||||||
|
has_api_doc_update = bool(api_doc_dic)
|
||||||
|
|
||||||
|
if not has_model_update and not has_api_doc_update:
|
||||||
|
return {'status': False, 'msg': 'no fields to update'}
|
||||||
|
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
try:
|
try:
|
||||||
await sor.U('model_management', ns_dic)
|
if has_model_update:
|
||||||
|
model_dic['id'] = model_id
|
||||||
|
await sor.U('model_management', model_dic)
|
||||||
|
|
||||||
|
if has_api_doc_update:
|
||||||
|
api_doc_id = ns.get('api_doc_id')
|
||||||
|
existing = None
|
||||||
|
if api_doc_id:
|
||||||
|
find_sql = (
|
||||||
|
"SELECT id FROM model_api_doc WHERE id = '%s' AND model_id = '%s' LIMIT 1;"
|
||||||
|
% (_escape(api_doc_id), _escape(str(model_id)))
|
||||||
|
)
|
||||||
|
existing = await sor.sqlExe(find_sql, {})
|
||||||
|
if not existing:
|
||||||
|
find_sql = (
|
||||||
|
"SELECT id FROM model_api_doc WHERE model_id = '%s' LIMIT 1;"
|
||||||
|
% _escape(str(model_id))
|
||||||
|
)
|
||||||
|
existing = await sor.sqlExe(find_sql, {})
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
doc_update = dict(api_doc_dic)
|
||||||
|
doc_update['id'] = existing[0]['id']
|
||||||
|
await sor.U('model_api_doc', doc_update)
|
||||||
|
else:
|
||||||
|
if not api_doc_dic.get('api_url'):
|
||||||
|
await sor.rollback()
|
||||||
|
return {
|
||||||
|
'status': False,
|
||||||
|
'msg': 'api_url is required when creating api doc',
|
||||||
|
}
|
||||||
|
create_dic = dict(api_doc_dic)
|
||||||
|
create_dic['model_id'] = str(model_id)
|
||||||
|
await sor.C('model_api_doc', create_dic)
|
||||||
|
|
||||||
return {'status': True, 'msg': 'update model success'}
|
return {'status': True, 'msg': 'update model success'}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await sor.rollback()
|
await sor.rollback()
|
||||||
return {'status': False, 'msg': 'update model failed, %s' % str(e)}
|
return {'status': False, 'msg': 'update model failed, %s' % str(e)}
|
||||||
|
|
||||||
|
|
||||||
ret = await model_management_update(params_kw)
|
ret = await model_management_update(params_kw)
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user