deepdata: core product code (backend, frontend, runtime, deepflow module)
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
import math
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import Depends, HTTPException
|
||||
from sqlalchemy import select, func, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.module.shared.llm import LLMFactory
|
||||
from app.core.logging import get_logger
|
||||
from app.db.datascope import DataScopeHandle, SYSTEM_USER
|
||||
from app.db.models.models import Models
|
||||
from app.db.session import get_db
|
||||
from app.module.shared.schema import PaginatedData
|
||||
from app.module.system.schema.models import (
|
||||
CreateModelRequest,
|
||||
QueryModelRequest,
|
||||
ModelsResponse,
|
||||
ProviderItem,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# 固定厂商列表
|
||||
PROVIDERS = [
|
||||
ProviderItem(provider="ModelEngine", baseUrl="http://nginxservice/open/router/v1"),
|
||||
ProviderItem(provider="Ollama", baseUrl="http://localhost:11434"),
|
||||
ProviderItem(provider="OpenAI", baseUrl="https://api.openai.com/v1"),
|
||||
ProviderItem(provider="DeepSeek", baseUrl="https://api.deepseek.com/v1"),
|
||||
ProviderItem(provider="火山方舟", baseUrl="https://ark.cn-beijing.volces.com/api/v3"),
|
||||
ProviderItem(provider="阿里云百炼", baseUrl="https://dashscope.aliyuncs.com/compatible-mode/v1"),
|
||||
ProviderItem(provider="硅基流动", baseUrl="https://api.siliconflow.cn/v1"),
|
||||
ProviderItem(provider="智谱AI", baseUrl="https://open.bigmodel.cn/api/paas/v4"),
|
||||
ProviderItem(provider="自定义模型", baseUrl=""),
|
||||
]
|
||||
|
||||
|
||||
def _orm_to_response(row: Models) -> ModelsResponse:
|
||||
return ModelsResponse(
|
||||
id=row.id,
|
||||
modelName=row.model_name,
|
||||
provider=row.provider,
|
||||
baseUrl=row.base_url,
|
||||
apiKey=row.api_key or "",
|
||||
type=row.type or "CHAT",
|
||||
isEnabled=bool(row.is_enabled) if row.is_enabled is not None else True,
|
||||
isDefault=bool(row.is_default) if row.is_default is not None else False,
|
||||
createdAt=row.created_at,
|
||||
updatedAt=row.updated_at,
|
||||
createdBy=row.created_by,
|
||||
updatedBy=row.updated_by,
|
||||
)
|
||||
|
||||
|
||||
class ModelsService:
|
||||
"""模型配置服务:db 通过 FastAPI Depends(get_db) 注入,不在路由中传递。"""
|
||||
|
||||
def __init__(self, db: AsyncSession = Depends(get_db)):
|
||||
self.db = db
|
||||
|
||||
async def get_providers(self) -> list[ProviderItem]:
|
||||
"""返回固定厂商列表,与 Java getProviders() 一致。"""
|
||||
return list(PROVIDERS)
|
||||
|
||||
async def get_models(self, q: QueryModelRequest) -> PaginatedData[ModelsResponse]:
|
||||
"""分页查询,支持 provider/type/isEnabled/isDefault;排除已删除;page 从 0 开始。"""
|
||||
query = select(Models).where(
|
||||
(Models.is_deleted == False) | (Models.is_deleted.is_(None))
|
||||
)
|
||||
if q.provider is not None and q.provider != "":
|
||||
query = query.where(Models.provider == q.provider)
|
||||
if q.type is not None:
|
||||
query = query.where(Models.type == q.type.value)
|
||||
if q.isEnabled is not None:
|
||||
query = query.where(Models.is_enabled == q.isEnabled)
|
||||
if q.isDefault is not None:
|
||||
query = query.where(Models.is_default == q.isDefault)
|
||||
|
||||
total = (await self.db.execute(select(func.count()).select_from(query.subquery()))).scalar_one()
|
||||
size = max(1, min(500, q.size))
|
||||
offset = max(0, q.page) * size
|
||||
rows = (
|
||||
await self.db.execute(
|
||||
query.order_by(Models.created_at.desc()).offset(offset).limit(size)
|
||||
)
|
||||
).scalars().all()
|
||||
total_pages = math.ceil(total / size) if total else 0
|
||||
current = max(0, q.page) + 1
|
||||
return PaginatedData(
|
||||
page=current,
|
||||
size=size,
|
||||
total_elements=total,
|
||||
total_pages=total_pages,
|
||||
content=[_orm_to_response(r) for r in rows],
|
||||
)
|
||||
|
||||
async def get_model_detail(self, model_id: str) -> ModelsResponse:
|
||||
"""获取模型详情,已删除或不存在则 404。"""
|
||||
query = select(Models).where(Models.id == model_id).where(
|
||||
(Models.is_deleted == False) | (Models.is_deleted.is_(None))
|
||||
)
|
||||
r = (await self.db.execute(query)).scalar_one_or_none()
|
||||
if not r:
|
||||
raise HTTPException(status_code=404, detail="模型配置不存在")
|
||||
return _orm_to_response(r)
|
||||
|
||||
async def create_model(self, req: CreateModelRequest) -> ModelsResponse:
|
||||
"""创建模型:健康检查后 saveAndSetDefault;isEnabled 恒为 True。
|
||||
若 uk_model_provider 已存在且 is_deleted=True,则恢复并更新相关字段。"""
|
||||
try:
|
||||
LLMFactory.check_health(req.modelName, req.baseUrl, req.apiKey, req.type.value)
|
||||
except Exception as e:
|
||||
logger.error("Model health check failed: model=%s type=%s err=%s", req.modelName, req.type, e)
|
||||
raise HTTPException(status_code=400, detail="模型健康检查失败") from e
|
||||
|
||||
# 检查 uk_model_provider (model_name, provider, created_by) 是否存在且已删除
|
||||
effective_user = (DataScopeHandle.get_user_info() or "").strip() or SYSTEM_USER
|
||||
created_by_match = (
|
||||
(Models.created_by == effective_user)
|
||||
| ((Models.created_by.is_(None)) & (effective_user == SYSTEM_USER))
|
||||
)
|
||||
deleted_row = (
|
||||
await self.db.execute(
|
||||
select(Models).where(
|
||||
Models.model_name == req.modelName,
|
||||
Models.provider == req.provider,
|
||||
created_by_match,
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if deleted_row and deleted_row.is_deleted:
|
||||
# 恢复并更新
|
||||
entity = deleted_row
|
||||
entity.base_url = req.baseUrl
|
||||
entity.api_key = req.apiKey or ""
|
||||
entity.type = req.type.value
|
||||
entity.is_enabled = True
|
||||
entity.is_deleted = False
|
||||
entity.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
want_default = req.isDefault if req.isDefault is not None else False
|
||||
if want_default:
|
||||
await self.db.execute(
|
||||
update(Models)
|
||||
.where(Models.type == req.type.value, Models.is_default == True)
|
||||
.values(is_default=False)
|
||||
)
|
||||
entity.is_default = want_default
|
||||
|
||||
self.db.add(entity)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(entity)
|
||||
return _orm_to_response(entity)
|
||||
|
||||
# 正常创建
|
||||
existing = (
|
||||
await self.db.execute(
|
||||
select(Models).where(
|
||||
(Models.is_deleted == False) | (Models.is_deleted.is_(None)),
|
||||
Models.type == req.type.value,
|
||||
Models.is_default == True,
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
is_default: bool
|
||||
if existing is None:
|
||||
is_default = True
|
||||
else:
|
||||
await self.db.execute(
|
||||
update(Models)
|
||||
.where(Models.type == req.type.value, Models.is_default == True)
|
||||
.values(is_default=False)
|
||||
)
|
||||
is_default = req.isDefault if req.isDefault is not None else False
|
||||
|
||||
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
entity = Models(
|
||||
id=str(uuid.uuid4()),
|
||||
model_name=req.modelName,
|
||||
provider=req.provider,
|
||||
base_url=req.baseUrl,
|
||||
api_key=req.apiKey or "",
|
||||
type=req.type.value,
|
||||
is_enabled=True,
|
||||
is_default=is_default,
|
||||
is_deleted=False,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
self.db.add(entity)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(entity)
|
||||
return _orm_to_response(entity)
|
||||
|
||||
async def update_model(self, model_id: str, req: CreateModelRequest) -> ModelsResponse:
|
||||
"""更新模型:存在性校验、健康检查后 updateAndSetDefault;isEnabled 恒为 True。"""
|
||||
res = await self.db.execute(
|
||||
select(Models).where(Models.id == model_id).where(
|
||||
(Models.is_deleted == False) | (Models.is_deleted.is_(None))
|
||||
)
|
||||
)
|
||||
entity = res.scalar_one_or_none()
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail="模型配置不存在")
|
||||
|
||||
try:
|
||||
LLMFactory.check_health(req.modelName, req.baseUrl, req.apiKey, req.type.value)
|
||||
except Exception as e:
|
||||
logger.error("Model health check failed: model=%s type=%s err=%s", req.modelName, req.type, e)
|
||||
raise HTTPException(status_code=400, detail="模型健康检查失败") from e
|
||||
|
||||
entity.model_name = req.modelName
|
||||
entity.provider = req.provider
|
||||
entity.base_url = req.baseUrl
|
||||
entity.api_key = req.apiKey or ""
|
||||
entity.type = req.type.value
|
||||
entity.is_enabled = True
|
||||
|
||||
want_default = req.isDefault if req.isDefault is not None else False
|
||||
if (entity.is_default is not True) and want_default:
|
||||
await self.db.execute(
|
||||
update(Models)
|
||||
.where(Models.type == req.type.value, Models.is_default == True)
|
||||
.values(is_default=False)
|
||||
)
|
||||
entity.is_default = want_default
|
||||
entity.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
await self.db.commit()
|
||||
await self.db.refresh(entity)
|
||||
return _orm_to_response(entity)
|
||||
|
||||
async def delete_model(self, model_id: str) -> None:
|
||||
"""软删除模型配置。"""
|
||||
entity = (
|
||||
await self.db.execute(
|
||||
select(Models).where(Models.id == model_id).where(
|
||||
(Models.is_deleted == False) | (Models.is_deleted.is_(None))
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail="模型配置不存在")
|
||||
entity.is_deleted = True
|
||||
entity.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(entity)
|
||||
Reference in New Issue
Block a user