Compare commits
1 Commits
fix/sandbo
...
revert-218
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
524aed19d4 |
@@ -872,44 +872,3 @@ async def update_workflow_config(
|
||||
workspace_id = current_user.current_workspace_id
|
||||
cfg = app_service.update_workflow_config(db, app_id=app_id, data=payload, workspace_id=workspace_id)
|
||||
return success(data=WorkflowConfigSchema.model_validate(cfg))
|
||||
|
||||
|
||||
@router.get("/{app_id}/statistics", summary="应用统计数据")
|
||||
@cur_workspace_access_guard()
|
||||
def get_app_statistics(
|
||||
app_id: uuid.UUID,
|
||||
start_date: int,
|
||||
end_date: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
"""获取应用统计数据
|
||||
|
||||
Args:
|
||||
app_id: 应用ID
|
||||
start_date: 开始时间戳(毫秒)
|
||||
end_date: 结束时间戳(毫秒)
|
||||
|
||||
Returns:
|
||||
- daily_conversations: 每日会话数统计
|
||||
- total_conversations: 总会话数
|
||||
- daily_new_users: 每日新增用户数
|
||||
- total_new_users: 总新增用户数
|
||||
- daily_api_calls: 每日API调用次数
|
||||
- total_api_calls: 总API调用次数
|
||||
- daily_tokens: 每日token消耗
|
||||
- total_tokens: 总token消耗
|
||||
"""
|
||||
workspace_id = current_user.current_workspace_id
|
||||
|
||||
from app.services.app_statistics_service import AppStatisticsService
|
||||
stats_service = AppStatisticsService(db)
|
||||
|
||||
result = stats_service.get_app_statistics(
|
||||
app_id=app_id,
|
||||
workspace_id=workspace_id,
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
|
||||
return success(data=result)
|
||||
|
||||
@@ -3,17 +3,15 @@ from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
import uuid
|
||||
|
||||
from app.core.error_codes import BizCode
|
||||
from app.core.exceptions import BusinessException
|
||||
|
||||
from app.db import get_db
|
||||
from app.dependencies import get_current_user
|
||||
from app.models.models_model import ModelProvider, ModelType
|
||||
from app.models.user_model import User
|
||||
from app.repositories.model_repository import ModelConfigRepository
|
||||
from app.schemas import model_schema
|
||||
from app.core.response_utils import success
|
||||
from app.schemas.response_schema import ApiResponse, PageData
|
||||
from app.services.model_service import ModelConfigService, ModelApiKeyService, ModelBaseService
|
||||
from app.services.model_service import ModelConfigService, ModelApiKeyService
|
||||
from app.core.logging_config import get_api_logger
|
||||
|
||||
# 获取API专用日志器
|
||||
@@ -26,6 +24,7 @@ router = APIRouter(
|
||||
|
||||
@router.get("/type", response_model=ApiResponse)
|
||||
def get_model_types():
|
||||
|
||||
return success(msg="获取模型类型成功", data=list(ModelType))
|
||||
|
||||
|
||||
@@ -36,39 +35,33 @@ def get_model_providers():
|
||||
|
||||
@router.get("", response_model=ApiResponse)
|
||||
def get_model_list(
|
||||
type: Optional[list[str]] = Query(None, description="模型类型筛选(支持多个,如 ?type=LLM 或 ?type=LLM,EMBEDDING)"),
|
||||
provider: Optional[model_schema.ModelProvider] = Query(None, description="提供商筛选(基于API Key)"),
|
||||
is_active: Optional[bool] = Query(None, description="激活状态筛选"),
|
||||
is_public: Optional[bool] = Query(None, description="公开状态筛选"),
|
||||
search: Optional[str] = Query(None, description="搜索关键词"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
pagesize: int = Query(10, ge=1, le=100, description="每页数量"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
type: Optional[str] = Query(None, description="模型类型筛选(支持多个,如 ?type=LLM 或 ?type=LLM,EMBEDDING)"),
|
||||
provider: Optional[model_schema.ModelProvider] = Query(None, description="提供商筛选(基于API Key)"),
|
||||
is_active: Optional[bool] = Query(None, description="激活状态筛选"),
|
||||
is_public: Optional[bool] = Query(None, description="公开状态筛选"),
|
||||
search: Optional[str] = Query(None, description="搜索关键词"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
pagesize: int = Query(10, ge=1, le=100, description="每页数量"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
获取模型配置列表
|
||||
|
||||
|
||||
支持多个 type 参数:
|
||||
- 单个:?type=LLM
|
||||
- 多个(逗号分隔):?type=LLM,EMBEDDING
|
||||
- 多个(重复参数):?type=LLM&type=EMBEDDING
|
||||
"""
|
||||
api_logger.info(
|
||||
f"获取模型配置列表请求: type={type}, provider={provider}, page={page}, pagesize={pagesize}, tenant_id={current_user.tenant_id}")
|
||||
|
||||
api_logger.info(f"获取模型配置列表请求: type={type}, provider={provider}, page={page}, pagesize={pagesize}, tenant_id={current_user.tenant_id}")
|
||||
|
||||
try:
|
||||
# 解析 type 参数(支持逗号分隔)
|
||||
type_list = []
|
||||
if type is not None:
|
||||
flat_type = []
|
||||
for item in type:
|
||||
split_items = [t.strip() for t in item.split(',') if t.strip()]
|
||||
flat_type.extend(split_items)
|
||||
|
||||
unique_flat_type = list(dict.fromkeys(flat_type))
|
||||
type_list = [ModelType(t.lower()) for t in unique_flat_type]
|
||||
|
||||
type_list = None
|
||||
if type:
|
||||
type_values = [t.strip() for t in type.split(',')]
|
||||
type_list = [model_schema.ModelType(t.lower()) for t in type_values if t]
|
||||
|
||||
api_logger.error(f"获取模型type_list: {type_list}")
|
||||
query = model_schema.ModelConfigQuery(
|
||||
type=type_list,
|
||||
@@ -79,7 +72,7 @@ def get_model_list(
|
||||
page=page,
|
||||
pagesize=pagesize
|
||||
)
|
||||
|
||||
|
||||
api_logger.debug(f"开始获取模型配置列表: {query.dict()}")
|
||||
result_orm = ModelConfigService.get_model_list(db=db, query=query, tenant_id=current_user.tenant_id)
|
||||
result = PageData.model_validate(result_orm)
|
||||
@@ -90,142 +83,6 @@ def get_model_list(
|
||||
raise
|
||||
|
||||
|
||||
@router.get("/new", response_model=ApiResponse)
|
||||
def get_model_list(
|
||||
type: Optional[list[str]] = Query(None, description="模型类型筛选(支持多个,如 ?type=LLM 或 ?type=LLM,EMBEDDING)"),
|
||||
provider: Optional[model_schema.ModelProvider] = Query(None, description="提供商筛选(基于ModelConfig)"),
|
||||
is_active: Optional[bool] = Query(None, description="激活状态筛选"),
|
||||
is_public: Optional[bool] = Query(None, description="公开状态筛选"),
|
||||
search: Optional[str] = Query(None, description="搜索关键词"),
|
||||
is_composite: Optional[bool] = Query(None, description="组合模型筛选"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
获取模型配置列表
|
||||
|
||||
支持多个 type 参数:
|
||||
- 单个:?type=LLM
|
||||
- 多个(逗号分隔):?type=LLM,EMBEDDING
|
||||
- 多个(重复参数):?type=LLM&type=EMBEDDING
|
||||
"""
|
||||
api_logger.info(f"获取模型配置列表请求: type={type}, provider={provider}, tenant_id={current_user.tenant_id}")
|
||||
|
||||
try:
|
||||
# 解析 type 参数(支持逗号分隔)
|
||||
type_list = []
|
||||
if type is not None:
|
||||
flat_type = []
|
||||
for item in type:
|
||||
split_items = [t.strip() for t in item.split(',') if t.strip()]
|
||||
flat_type.extend(split_items)
|
||||
|
||||
unique_flat_type = list(dict.fromkeys(flat_type))
|
||||
type_list = [ModelType(t.lower()) for t in unique_flat_type]
|
||||
|
||||
api_logger.info(f"获取模型type_list: {type_list}")
|
||||
query = model_schema.ModelConfigQueryNew(
|
||||
type=type_list,
|
||||
provider=provider,
|
||||
is_active=is_active,
|
||||
is_public=is_public,
|
||||
is_composite=is_composite,
|
||||
search=search
|
||||
)
|
||||
|
||||
api_logger.debug(f"开始获取模型配置列表: {query.model_dump()}")
|
||||
result = ModelConfigService.get_model_list_new(db=db, query=query, tenant_id=current_user.tenant_id)
|
||||
api_logger.info(f"模型配置列表获取成功: 分组数={len(result)}, 总模型数={sum(len(item['models']) for item in result)}")
|
||||
return success(data=result, msg="模型配置列表获取成功")
|
||||
except Exception as e:
|
||||
api_logger.error(f"获取模型配置列表失败: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
@router.get("/model_plaza", response_model=ApiResponse)
|
||||
def get_model_plaza_list(
|
||||
type: Optional[ModelType] = Query(None, description="模型类型"),
|
||||
provider: Optional[ModelProvider] = Query(None, description="供应商"),
|
||||
is_official: Optional[bool] = Query(None, description="是否官方模型"),
|
||||
is_deprecated: Optional[bool] = Query(False, description="是否弃用"),
|
||||
search: Optional[str] = Query(None, description="搜索关键词"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""模型广场查询接口(按供应商分组)"""
|
||||
|
||||
query = model_schema.ModelBaseQuery(
|
||||
type=type,
|
||||
provider=provider,
|
||||
is_official=is_official,
|
||||
is_deprecated=is_deprecated,
|
||||
search=search
|
||||
)
|
||||
result = ModelBaseService.get_model_base_list(db=db, query=query, tenant_id=current_user.tenant_id)
|
||||
return success(data=result, msg="模型广场列表获取成功")
|
||||
|
||||
|
||||
@router.get("/model_plaza/{model_base_id}", response_model=ApiResponse)
|
||||
def get_model_base_by_id(
|
||||
model_base_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""获取基础模型详情"""
|
||||
|
||||
result = ModelBaseService.get_model_base_by_id(db=db, model_base_id=model_base_id)
|
||||
return success(data=model_schema.ModelBase.model_validate(result), msg="基础模型获取成功")
|
||||
|
||||
|
||||
@router.post("/model_plaza", response_model=ApiResponse)
|
||||
def create_model_base(
|
||||
data: model_schema.ModelBaseCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""创建基础模型"""
|
||||
|
||||
result = ModelBaseService.create_model_base(db=db, data=data)
|
||||
return success(data=model_schema.ModelBase.model_validate(result), msg="基础模型创建成功")
|
||||
|
||||
|
||||
@router.put("/model_plaza/{model_base_id}", response_model=ApiResponse)
|
||||
def update_model_base(
|
||||
model_base_id: uuid.UUID,
|
||||
data: model_schema.ModelBaseUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""更新基础模型"""
|
||||
|
||||
result = ModelBaseService.update_model_base(db=db, model_base_id=model_base_id, data=data)
|
||||
return success(data=model_schema.ModelBase.model_validate(result), msg="基础模型更新成功")
|
||||
|
||||
|
||||
@router.delete("/model_plaza/{model_base_id}", response_model=ApiResponse)
|
||||
def delete_model_base(
|
||||
model_base_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""删除基础模型"""
|
||||
|
||||
ModelBaseService.delete_model_base(db=db, model_base_id=model_base_id)
|
||||
return success(msg="基础模型删除成功")
|
||||
|
||||
|
||||
@router.post("/model_plaza/{model_base_id}/add", response_model=ApiResponse)
|
||||
def add_model_from_plaza(
|
||||
model_base_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""从模型广场添加模型到模型列表"""
|
||||
|
||||
result = ModelBaseService.add_model_from_plaza(db=db, model_base_id=model_base_id, tenant_id=current_user.tenant_id)
|
||||
return success(data=model_schema.ModelConfig.model_validate(result), msg="模型添加成功")
|
||||
|
||||
|
||||
@router.get("/{model_id}", response_model=ApiResponse)
|
||||
def get_model_by_id(
|
||||
model_id: uuid.UUID,
|
||||
@@ -281,71 +138,6 @@ async def create_model(
|
||||
raise
|
||||
|
||||
|
||||
@router.post("/composite", response_model=ApiResponse)
|
||||
async def create_composite_model(
|
||||
model_data: model_schema.CompositeModelCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
创建组合模型
|
||||
|
||||
- 绑定一个或多个现有的 API Key
|
||||
- 所有 API Key 必须来自非组合模型
|
||||
- 所有 API Key 关联的模型类型必须与组合模型类型一致
|
||||
"""
|
||||
api_logger.info(f"创建组合模型请求: {model_data.name}, 用户: {current_user.username}, tenant_id={current_user.tenant_id}")
|
||||
|
||||
try:
|
||||
result_orm = await ModelConfigService.create_composite_model(db=db, model_data=model_data, tenant_id=current_user.tenant_id)
|
||||
api_logger.info(f"组合模型创建成功: {result_orm.name} (ID: {result_orm.id})")
|
||||
|
||||
result = model_schema.ModelConfig.model_validate(result_orm)
|
||||
return success(data=result, msg="组合模型创建成功")
|
||||
except Exception as e:
|
||||
api_logger.error(f"创建组合模型失败: {model_data.name} - {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
@router.put("/composite/{model_id}", response_model=ApiResponse)
|
||||
async def update_composite_model(
|
||||
model_id: uuid.UUID,
|
||||
model_data: model_schema.CompositeModelCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""更新组合模型"""
|
||||
api_logger.info(f"更新组合模型请求: model_id={model_id}, 用户: {current_user.username}")
|
||||
|
||||
try:
|
||||
result_orm = await ModelConfigService.update_composite_model(db=db, model_id=model_id, model_data=model_data, tenant_id=current_user.tenant_id)
|
||||
api_logger.info(f"组合模型更新成功: {result_orm.name} (ID: {model_id})")
|
||||
|
||||
result = model_schema.ModelConfig.model_validate(result_orm)
|
||||
return success(data=result, msg="组合模型更新成功")
|
||||
except Exception as e:
|
||||
api_logger.error(f"更新组合模型失败: model_id={model_id} - {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
@router.delete("/composite/{model_id}", response_model=ApiResponse)
|
||||
def delete_composite_model(
|
||||
model_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""删除组合模型"""
|
||||
api_logger.info(f"删除组合模型请求: model_id={model_id}, 用户: {current_user.username}")
|
||||
|
||||
try:
|
||||
ModelConfigService.delete_model(db=db, model_id=model_id, tenant_id=current_user.tenant_id)
|
||||
api_logger.info(f"组合模型删除成功: model_id={model_id}")
|
||||
return success(msg="组合模型删除成功")
|
||||
except Exception as e:
|
||||
api_logger.error(f"删除组合模型失败: model_id={model_id} - {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
@router.put("/{model_id}", response_model=ApiResponse)
|
||||
def update_model(
|
||||
model_id: uuid.UUID,
|
||||
@@ -422,51 +214,6 @@ def get_model_api_keys(
|
||||
raise
|
||||
|
||||
|
||||
@router.post("/provider/apikeys", response_model=ApiResponse)
|
||||
async def create_model_api_key_by_provider(
|
||||
api_key_data: model_schema.ModelApiKeyCreateByProvider,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
根据供应商为所有匹配的模型创建API Key
|
||||
"""
|
||||
api_logger.info(f"创建API Key请求: provider={api_key_data.provider}, 用户: {current_user.username}")
|
||||
|
||||
try:
|
||||
# 根据tenant_id和provider筛选model_config_id列表
|
||||
model_config_ids = api_key_data.model_config_ids
|
||||
if not model_config_ids:
|
||||
model_config_ids = ModelConfigRepository.get_model_config_ids_by_provider(
|
||||
db=db,
|
||||
tenant_id=current_user.tenant_id,
|
||||
provider=api_key_data.provider
|
||||
)
|
||||
|
||||
if not model_config_ids:
|
||||
raise BusinessException(f"未找到供应商 {api_key_data.provider} 的模型配置", BizCode.MODEL_NOT_FOUND)
|
||||
|
||||
# 构造schema并调用service
|
||||
create_data = model_schema.ModelApiKeyCreateByProvider(
|
||||
provider=api_key_data.provider,
|
||||
api_key=api_key_data.api_key,
|
||||
api_base=api_key_data.api_base,
|
||||
description=api_key_data.description,
|
||||
config=api_key_data.config,
|
||||
is_active=api_key_data.is_active,
|
||||
priority=api_key_data.priority,
|
||||
model_config_ids=model_config_ids
|
||||
)
|
||||
created_keys = await ModelApiKeyService.create_api_key_by_provider(db=db, data=create_data)
|
||||
|
||||
api_logger.info(f"API Key创建成功: 关联{len(created_keys)}个模型")
|
||||
result_list = [model_schema.ModelApiKey.model_validate(key) for key in created_keys]
|
||||
return success(data=result_list, msg=f"成功为 {len(created_keys)} 个模型创建API Key")
|
||||
except Exception as e:
|
||||
api_logger.error(f"创建API Key失败: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
@router.post("/{model_id}/apikeys", response_model=ApiResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_model_api_key(
|
||||
model_id: uuid.UUID,
|
||||
@@ -481,12 +228,11 @@ async def create_model_api_key(
|
||||
|
||||
try:
|
||||
# 设置模型配置ID
|
||||
api_key_data.model_config_ids = [model_id]
|
||||
api_key_data.model_config_id = model_id
|
||||
|
||||
api_logger.debug(f"开始创建模型API Key: {api_key_data.model_name}")
|
||||
result_orm = await ModelApiKeyService.create_api_key(db=db, api_key_data=api_key_data)
|
||||
api_logger.info(f"模型API Key创建成功: {result_orm.model_name} (ID: {result_orm.id})")
|
||||
result = model_schema.ModelApiKey.model_validate(result_orm)
|
||||
result = await ModelApiKeyService.create_api_key(db=db, api_key_data=api_key_data)
|
||||
api_logger.info(f"模型API Key创建成功: {result.model_name} (ID: {result.id})")
|
||||
return success(data=result, msg="模型API Key创建成功")
|
||||
except Exception as e:
|
||||
api_logger.error(f"创建模型API Key失败: {api_key_data.model_name} - {str(e)}")
|
||||
@@ -588,3 +334,5 @@ async def validate_model_config(
|
||||
return success(data=model_schema.ModelValidateResponse(**result), msg="验证完成")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from .document_model import Document
|
||||
from .file_model import File
|
||||
from .file_metadata_model import FileMetadata
|
||||
from .generic_file_model import GenericFile
|
||||
from .models_model import ModelConfig, ModelProvider, ModelType, ModelApiKey, ModelBase, LoadBalanceStrategy
|
||||
from .models_model import ModelConfig, ModelProvider, ModelType, ModelApiKey
|
||||
from .memory_short_model import ShortTermMemory, LongTermMemory
|
||||
from .knowledgeshare_model import KnowledgeShare
|
||||
from .app_model import App
|
||||
@@ -79,6 +79,4 @@ __all__ = [
|
||||
"AuthType",
|
||||
"ExecutionStatus",
|
||||
"MemoryPerceptualModel",
|
||||
"ModelBase",
|
||||
"LoadBalanceStrategy"
|
||||
]
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
import datetime
|
||||
import uuid
|
||||
from enum import StrEnum
|
||||
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, Text, ForeignKey, Enum as SQLEnum, UniqueConstraint, Integer, ARRAY, Table
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, Text, ForeignKey, Enum as SQLEnum
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.db import Base
|
||||
|
||||
|
||||
class BaseModel(Base):
|
||||
"""基础模型(抽象类,提取公共字段)"""
|
||||
__abstract__ = True # 标记为抽象类,不生成表
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
|
||||
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
|
||||
is_active = Column(Boolean, default=True, nullable=False, comment="是否激活")
|
||||
|
||||
|
||||
class ModelType(StrEnum):
|
||||
"""模型类型枚举"""
|
||||
LLM = "llm"
|
||||
CHAT = "chat"
|
||||
EMBEDDING = "embedding"
|
||||
RERANK = "rerank"
|
||||
# IMAGE = "image"
|
||||
# AUDIO = "audio"
|
||||
# VISION = "vision"
|
||||
|
||||
|
||||
class ModelProvider(StrEnum):
|
||||
@@ -42,37 +30,16 @@ class ModelProvider(StrEnum):
|
||||
XINFERENCE = "xinference"
|
||||
GPUSTACK = "gpustack"
|
||||
BEDROCK = "bedrock"
|
||||
COMPOSITE = "composite"
|
||||
|
||||
|
||||
class LoadBalanceStrategy(StrEnum):
|
||||
"""API Key负载均衡策略枚举"""
|
||||
ROUND_ROBIN = "round_robin" # 轮询
|
||||
WEIGHTED_ROUND_ROBIN = "weighted_round_robin" # 加权轮询
|
||||
RANDOM = "random" # 随机
|
||||
|
||||
|
||||
# 多对多关联表
|
||||
model_config_api_key_association = Table(
|
||||
'model_config_api_key_association',
|
||||
Base.metadata,
|
||||
Column('model_config_id', UUID(as_uuid=True), ForeignKey('model_configs.id'), primary_key=True),
|
||||
Column('api_key_id', UUID(as_uuid=True), ForeignKey('model_api_keys.id'), primary_key=True),
|
||||
Column('created_at', DateTime, default=datetime.datetime.now)
|
||||
)
|
||||
|
||||
|
||||
class ModelConfig(BaseModel):
|
||||
class ModelConfig(Base):
|
||||
"""模型配置表"""
|
||||
__tablename__ = "model_configs"
|
||||
|
||||
model_id = Column(UUID(as_uuid=True), ForeignKey("model_bases.id"), nullable=True, index=True, comment="基础模型ID")
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=False, index=True, comment="租户ID")
|
||||
logo = Column(String(255), nullable=True, comment="模型logo图片URL")
|
||||
name = Column(String, nullable=False, comment="模型显示名称")
|
||||
provider = Column(String, nullable=False, comment="供应商", server_default=ModelProvider.COMPOSITE)
|
||||
type = Column(String, nullable=False, index=True, comment="模型类型")
|
||||
is_composite = Column(Boolean, default=False, server_default="true", nullable=False, comment="是否为组合模型")
|
||||
description = Column(String, comment="模型描述")
|
||||
|
||||
# 模型配置参数
|
||||
@@ -89,28 +56,29 @@ class ModelConfig(BaseModel):
|
||||
# context_length = Column(String, comment="上下文长度")
|
||||
|
||||
# 状态管理
|
||||
is_active = Column(Boolean, default=True, nullable=False, comment="是否激活")
|
||||
is_public = Column(Boolean, default=False, nullable=False, comment="是否公开")
|
||||
load_balance_strategy = Column(String, nullable=True, comment="负载均衡策略")
|
||||
|
||||
# 时间戳
|
||||
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
|
||||
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
|
||||
|
||||
# 关联关系
|
||||
model_base = relationship("ModelBase", back_populates="configs")
|
||||
api_keys = relationship(
|
||||
"ModelApiKey",
|
||||
secondary=model_config_api_key_association,
|
||||
back_populates="model_configs"
|
||||
)
|
||||
api_keys = relationship("ModelApiKey", back_populates="model_config", cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ModelConfig(id={self.id}, name={self.name}, type={self.type})>"
|
||||
|
||||
|
||||
class ModelApiKey(BaseModel):
|
||||
class ModelApiKey(Base):
|
||||
"""模型API密钥表"""
|
||||
__tablename__ = "model_api_keys"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||
model_config_id = Column(UUID(as_uuid=True), ForeignKey("model_configs.id"), nullable=False, comment="模型配置ID")
|
||||
|
||||
# API Key 信息
|
||||
model_name = Column(String, nullable=False, comment="模型实际名称")
|
||||
description = Column(String, comment="备注")
|
||||
provider = Column(String, nullable=False, comment="API Key提供商")
|
||||
api_key = Column(String, nullable=False, comment="API密钥")
|
||||
api_base = Column(String, comment="API基础URL")
|
||||
@@ -123,41 +91,15 @@ class ModelApiKey(BaseModel):
|
||||
last_used_at = Column(DateTime, comment="最后使用时间")
|
||||
|
||||
# 状态管理
|
||||
is_active = Column(Boolean, default=True, nullable=False, comment="是否激活")
|
||||
priority = Column(String, default="1", comment="优先级")
|
||||
|
||||
|
||||
# 时间戳
|
||||
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
|
||||
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
|
||||
|
||||
# 关联关系
|
||||
model_configs = relationship(
|
||||
"ModelConfig",
|
||||
secondary=model_config_api_key_association,
|
||||
back_populates="api_keys"
|
||||
)
|
||||
|
||||
model_config = relationship("ModelConfig", back_populates="api_keys")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ModelApiKey(id={self.id}, model_name={self.model_name}, provider={self.provider})>"
|
||||
|
||||
|
||||
class ModelBase(Base):
|
||||
"""基础模型信息表(模型广场)"""
|
||||
__tablename__ = "model_bases"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
||||
logo = Column(String(255), nullable=True, comment="模型logo图片URL")
|
||||
name = Column(String, nullable=False, comment="模型唯一标识(如gpt-3.5-turbo)")
|
||||
type = Column(String, nullable=False, index=True, comment="模型类型")
|
||||
provider = Column(String, nullable=False, index=True)
|
||||
description = Column(Text, comment="模型描述")
|
||||
is_deprecated = Column(Boolean, default=False, nullable=False, comment="是否弃用")
|
||||
is_official = Column(Boolean, default=True, comment="是否供应商官方模型(区分自定义)")
|
||||
tags = Column(ARRAY(String), default=list, nullable=False, comment="模型标签(如['聊天', '创作'])")
|
||||
add_count = Column(Integer, default=0, nullable=False, comment="模型被用户添加的次数")
|
||||
|
||||
# 关联关系
|
||||
configs = relationship("ModelConfig", back_populates="model_base", cascade="all, delete-orphan")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name", "provider", name="uk_model_name_provider"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ModelBase(name={self.name}, provider={self.provider}, type={self.type})>"
|
||||
return f"<ModelApiKey(id={self.id}, model_name={self.model_name}, provider={self.provider}, model_config_id={self.model_config_id})>"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from sqlalchemy.orm import Session, joinedload, selectinload
|
||||
from sqlalchemy import and_, or_, func, desc, select
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy import and_, or_, func, desc
|
||||
from typing import List, Optional, Dict, Any, Tuple
|
||||
import uuid
|
||||
|
||||
from app.models.models_model import ModelConfig, ModelApiKey, ModelType, ModelBase, model_config_api_key_association
|
||||
from app.models.models_model import ModelConfig, ModelApiKey, ModelType
|
||||
from app.schemas.model_schema import (
|
||||
ModelConfigUpdate, ModelApiKeyCreate, ModelApiKeyUpdate,
|
||||
ModelConfigQuery, ModelConfigQueryNew
|
||||
ModelConfigQuery
|
||||
)
|
||||
from app.core.logging_config import get_db_logger
|
||||
|
||||
@@ -107,80 +107,6 @@ class ModelConfigRepository:
|
||||
def get_list(db: Session, query: ModelConfigQuery, tenant_id: uuid.UUID | None = None) -> Tuple[List[ModelConfig], int]:
|
||||
"""获取模型配置列表"""
|
||||
db_logger.debug(f"查询模型配置列表: {query.dict()}, tenant_id={tenant_id}")
|
||||
|
||||
try:
|
||||
# 构建查询条件
|
||||
filters = []
|
||||
|
||||
# 添加租户过滤(查询本租户的模型或公开模型)
|
||||
if tenant_id:
|
||||
filters.append(
|
||||
or_(
|
||||
ModelConfig.tenant_id == tenant_id,
|
||||
ModelConfig.is_public
|
||||
)
|
||||
)
|
||||
|
||||
# 支持多个 type 值(使用 IN 查询)
|
||||
# 兼容 chat 和 llm 类型:如果查询包含其中一个,则同时匹配两者
|
||||
if query.type:
|
||||
type_values = list(query.type)
|
||||
# 如果包含 chat 或 llm,则同时包含两者
|
||||
if ModelType.CHAT in type_values or ModelType.LLM in type_values:
|
||||
if ModelType.CHAT not in type_values:
|
||||
type_values.append(ModelType.CHAT)
|
||||
if ModelType.LLM not in type_values:
|
||||
type_values.append(ModelType.LLM)
|
||||
filters.append(ModelConfig.type.in_(type_values))
|
||||
|
||||
if query.is_active is not None:
|
||||
filters.append(ModelConfig.is_active == query.is_active)
|
||||
|
||||
if query.is_public is not None:
|
||||
filters.append(ModelConfig.is_public == query.is_public)
|
||||
|
||||
if query.search:
|
||||
# 搜索逻辑需要join ModelApiKey表来搜索model_name
|
||||
search_filter = or_(
|
||||
ModelConfig.name.ilike(f"%{query.search}%"),
|
||||
# ModelConfig.description.ilike(f"%{query.search}%")
|
||||
)
|
||||
filters.append(search_filter)
|
||||
|
||||
# 构建基础查询
|
||||
base_query = db.query(ModelConfig).options(
|
||||
joinedload(ModelConfig.api_keys)
|
||||
)
|
||||
|
||||
# 如果需要按provider筛选,需要join ModelApiKey表
|
||||
if query.provider:
|
||||
base_query = base_query.join(ModelApiKey).filter(
|
||||
ModelApiKey.provider == query.provider
|
||||
).distinct()
|
||||
|
||||
if filters:
|
||||
base_query = base_query.filter(and_(*filters))
|
||||
|
||||
# 获取总数
|
||||
total = base_query.count()
|
||||
|
||||
# 分页查询
|
||||
models = base_query.order_by(desc(ModelConfig.updated_at)).offset(
|
||||
(query.page - 1) * query.pagesize
|
||||
).limit(query.pagesize).all()
|
||||
|
||||
db_logger.debug(f"模型配置列表查询成功: 总数={total}, 当前页={len(models)}, type筛选={query.type}")
|
||||
return models, total
|
||||
|
||||
except Exception as e:
|
||||
db_logger.error(f"查询模型配置列表失败: {str(e)}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def get_list_new(db: Session, query: ModelConfigQueryNew, tenant_id: uuid.UUID | None = None) -> tuple[
|
||||
dict[str, list[ModelConfig]], Any]:
|
||||
"""获取模型配置列表"""
|
||||
db_logger.debug(f"查询模型配置列表: {query.model_dump()}, tenant_id={tenant_id}")
|
||||
|
||||
try:
|
||||
# 构建查询条件
|
||||
@@ -212,15 +138,13 @@ class ModelConfigRepository:
|
||||
|
||||
if query.is_public is not None:
|
||||
filters.append(ModelConfig.is_public == query.is_public)
|
||||
|
||||
if query.is_composite is not None:
|
||||
filters.append(ModelConfig.is_composite == query.is_composite)
|
||||
|
||||
if query.provider:
|
||||
filters.append(ModelConfig.provider == query.provider)
|
||||
|
||||
if query.search:
|
||||
search_filter = ModelConfig.name.ilike(f"%{query.search}%")
|
||||
# 搜索逻辑需要join ModelApiKey表来搜索model_name
|
||||
search_filter = or_(
|
||||
ModelConfig.name.ilike(f"%{query.search}%"),
|
||||
# ModelConfig.description.ilike(f"%{query.search}%")
|
||||
)
|
||||
filters.append(search_filter)
|
||||
|
||||
# 构建基础查询
|
||||
@@ -228,30 +152,28 @@ class ModelConfigRepository:
|
||||
joinedload(ModelConfig.api_keys)
|
||||
)
|
||||
|
||||
# 如果需要按provider筛选,需要join ModelApiKey表
|
||||
if query.provider:
|
||||
base_query = base_query.join(ModelApiKey).filter(
|
||||
ModelApiKey.provider == query.provider
|
||||
).distinct()
|
||||
|
||||
if filters:
|
||||
base_query = base_query.filter(and_(*filters))
|
||||
|
||||
# 获取总数
|
||||
total = base_query.count()
|
||||
|
||||
query_results = base_query.order_by(desc(ModelConfig.updated_at)).all()
|
||||
|
||||
provider_groups: Dict[str, List[ModelConfig]] = {}
|
||||
for model_config in query_results:
|
||||
provider = model_config.provider
|
||||
if provider not in provider_groups:
|
||||
provider_groups[provider] = []
|
||||
provider_groups[provider].append(model_config)
|
||||
|
||||
db_logger.debug(
|
||||
f"模型配置列表查询成功: 总数={total}, "
|
||||
f"分组数={len(provider_groups)}, "
|
||||
f"各分组模型数={[len(v) for v in provider_groups.values()]}, "
|
||||
f"type筛选={query.type}")
|
||||
return provider_groups, total
|
||||
# 分页查询
|
||||
models = base_query.order_by(desc(ModelConfig.updated_at)).offset(
|
||||
(query.page - 1) * query.pagesize
|
||||
).limit(query.pagesize).all()
|
||||
|
||||
db_logger.debug(f"模型配置列表查询成功: 总数={total}, 当前页={len(models)}, type筛选={query.type}")
|
||||
return models, total
|
||||
|
||||
except Exception as e:
|
||||
db_logger.error(f"查询模型配置列表失败(按provider分组/无分页): {str(e)}")
|
||||
db_logger.error(f"查询模型配置列表失败: {str(e)}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
@@ -319,7 +241,7 @@ class ModelConfigRepository:
|
||||
return None
|
||||
|
||||
# 更新字段
|
||||
update_data = model_data.model_dump(exclude_unset=True)
|
||||
update_data = model_data.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_model, field, value)
|
||||
|
||||
@@ -381,18 +303,8 @@ class ModelConfigRepository:
|
||||
# 按提供商统计 - 现在从ModelApiKey表获取
|
||||
provider_stats = {}
|
||||
provider_results = db.query(
|
||||
# 保留 provider 字段
|
||||
ModelApiKey.provider,
|
||||
# 统计中间表中 唯一的 model_config_id 数量(替换原 ModelApiKey.model_config_id)
|
||||
func.count(func.distinct(model_config_api_key_association.c.model_config_id))
|
||||
).join(
|
||||
# 联表:ModelApiKey <-> 中间表(多对多关联)
|
||||
model_config_api_key_association,
|
||||
ModelApiKey.id == model_config_api_key_association.c.api_key_id
|
||||
).group_by(
|
||||
# 按 provider 分组(保留原有逻辑)
|
||||
ModelApiKey.provider
|
||||
).all()
|
||||
ModelApiKey.provider, func.count(func.distinct(ModelApiKey.model_config_id))
|
||||
).group_by(ModelApiKey.provider).all()
|
||||
|
||||
for provider, count in provider_results:
|
||||
provider_stats[provider.value] = count
|
||||
@@ -413,37 +325,6 @@ class ModelConfigRepository:
|
||||
db_logger.error(f"获取模型统计信息失败: {str(e)}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def get_model_config_ids_by_provider(
|
||||
db: Session,
|
||||
tenant_id: uuid.UUID,
|
||||
provider: Any
|
||||
) -> List[uuid.UUID]:
|
||||
"""根据tenant_id和provider获取model_config_id列表"""
|
||||
db_logger.debug(f"查询model_config_id列表: tenant_id={tenant_id}, provider={provider}")
|
||||
|
||||
try:
|
||||
# 查询ModelConfig关联的ModelApiKey,筛选出匹配的model_config_id
|
||||
model_config_ids = db.query(ModelConfig.id).join(
|
||||
ModelBase, ModelConfig.model_id == ModelBase.id
|
||||
).filter(
|
||||
and_(
|
||||
or_(
|
||||
ModelConfig.tenant_id == tenant_id,
|
||||
ModelConfig.is_public
|
||||
),
|
||||
ModelBase.provider == provider,
|
||||
~ModelConfig.is_composite
|
||||
)
|
||||
).distinct().all()
|
||||
|
||||
db_logger.debug(f"查询成功: 数量={len(model_config_ids)}")
|
||||
return [row[0] for row in model_config_ids]
|
||||
|
||||
except Exception as e:
|
||||
db_logger.error(f"查询model_config_id列表失败: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
class ModelApiKeyRepository:
|
||||
"""模型API Key Repository"""
|
||||
@@ -468,14 +349,7 @@ class ModelApiKeyRepository:
|
||||
db_logger.debug(f"根据模型配置ID查询API Key: model_config_id={model_config_id}")
|
||||
|
||||
try:
|
||||
from app.models.models_model import ModelConfig, model_config_api_key_association
|
||||
|
||||
query = db.query(ModelApiKey).join(
|
||||
model_config_api_key_association,
|
||||
ModelApiKey.id == model_config_api_key_association.c.api_key_id
|
||||
).filter(
|
||||
model_config_api_key_association.c.model_config_id == model_config_id
|
||||
)
|
||||
query = db.query(ModelApiKey).filter(ModelApiKey.model_config_id == model_config_id)
|
||||
|
||||
if is_active:
|
||||
query = query.filter(ModelApiKey.is_active)
|
||||
@@ -494,20 +368,8 @@ class ModelApiKeyRepository:
|
||||
db_logger.debug(f"创建API Key: {api_key_data.provider}")
|
||||
|
||||
try:
|
||||
from app.models.models_model import ModelConfig
|
||||
|
||||
# 创建API Key,不包含model_config_ids
|
||||
api_key_dict = api_key_data.model_dump(exclude={"model_config_ids"})
|
||||
db_api_key = ModelApiKey(**api_key_dict)
|
||||
db_api_key = ModelApiKey(**api_key_data.dict())
|
||||
db.add(db_api_key)
|
||||
db.flush() # 获取生成的ID
|
||||
|
||||
# 关联ModelConfig
|
||||
if api_key_data.model_config_ids:
|
||||
for model_config_id in api_key_data.model_config_ids:
|
||||
model_config = db.query(ModelConfig).filter(ModelConfig.id == model_config_id).first()
|
||||
if model_config:
|
||||
db_api_key.model_configs.append(model_config)
|
||||
|
||||
db_logger.info(f"API Key已添加到会话: {db_api_key.provider}")
|
||||
return db_api_key
|
||||
@@ -529,7 +391,7 @@ class ModelApiKeyRepository:
|
||||
return None
|
||||
|
||||
# 更新字段
|
||||
update_data = api_key_data.model_dump(exclude_unset=True)
|
||||
update_data = api_key_data.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_api_key, field, value)
|
||||
|
||||
@@ -589,74 +451,4 @@ class ModelApiKeyRepository:
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
db_logger.error(f"更新API Key使用统计失败: api_key_id={api_key_id} - {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
class ModelBaseRepository:
|
||||
"""基础模型Repository"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, model_base_id: uuid.UUID) -> Optional['ModelBase']:
|
||||
return db.query(ModelBase).filter(ModelBase.id == model_base_id).first()
|
||||
|
||||
@staticmethod
|
||||
def get_list(db: Session, query: 'ModelBaseQuery') -> List['ModelBase']:
|
||||
|
||||
filters = []
|
||||
if query.type:
|
||||
filters.append(ModelBase.type == query.type)
|
||||
if query.provider:
|
||||
filters.append(ModelBase.provider == query.provider)
|
||||
if query.is_official is not None:
|
||||
filters.append(ModelBase.is_official == query.is_official)
|
||||
if query.is_deprecated is not None:
|
||||
filters.append(ModelBase.is_deprecated == query.is_deprecated)
|
||||
if query.search:
|
||||
filters.append(or_(
|
||||
ModelBase.name.ilike(f"%{query.search}%"),
|
||||
# ModelBase.description.ilike(f"%{query.search}%")
|
||||
))
|
||||
|
||||
q = db.query(ModelBase)
|
||||
if filters:
|
||||
q = q.filter(and_(*filters))
|
||||
|
||||
return q.order_by(ModelBase.add_count.desc()).all()
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, data: dict) -> 'ModelBase':
|
||||
model_base = ModelBase(**data)
|
||||
db.add(model_base)
|
||||
return model_base
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session, model_base_id: uuid.UUID, data: dict) -> Optional['ModelBase']:
|
||||
model_base = db.query(ModelBase).filter(ModelBase.id == model_base_id).first()
|
||||
if not model_base:
|
||||
return None
|
||||
for key, value in data.items():
|
||||
setattr(model_base, key, value)
|
||||
return model_base
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, model_base_id: uuid.UUID) -> bool:
|
||||
model_base = db.query(ModelBase).filter(ModelBase.id == model_base_id).first()
|
||||
if not model_base:
|
||||
return False
|
||||
db.delete(model_base)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def increment_add_count(db: Session, model_base_id: uuid.UUID) -> bool:
|
||||
model_base = db.query(ModelBase).filter(ModelBase.id == model_base_id).first()
|
||||
if not model_base:
|
||||
return False
|
||||
model_base.add_count += 1
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def check_added_by_tenant(db: Session, model_base_id: uuid.UUID, tenant_id: uuid.UUID) -> bool:
|
||||
return db.query(ModelConfig).filter(
|
||||
ModelConfig.model_id == model_base_id,
|
||||
ModelConfig.tenant_id == tenant_id
|
||||
).first() is not None
|
||||
raise
|
||||
@@ -4,10 +4,6 @@ import datetime
|
||||
import uuid
|
||||
|
||||
from app.models.models_model import ModelProvider, ModelType
|
||||
from app.core.logging_config import get_business_logger
|
||||
|
||||
schema_logger = get_business_logger()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,9 +12,7 @@ class ModelConfigBase(BaseModel):
|
||||
"""模型配置基础Schema"""
|
||||
name: str = Field(..., description="模型显示名称", max_length=255)
|
||||
type: ModelType = Field(..., description="模型类型")
|
||||
logo: Optional[str] = Field(None, description="模型logo图片URL", max_length=255)
|
||||
description: Optional[str] = Field(None, description="模型描述")
|
||||
provider: str = Field(..., description="供应商")
|
||||
config: Optional[Dict[str, Any]] = Field({}, description="模型配置参数")
|
||||
is_active: bool = Field(True, description="是否激活")
|
||||
is_public: bool = Field(False, description="是否公开")
|
||||
@@ -27,7 +21,6 @@ class ModelConfigBase(BaseModel):
|
||||
class ApiKeyCreateNested(BaseModel):
|
||||
"""用于在创建模型时内嵌创建API Key的Schema"""
|
||||
model_name: str = Field(..., description="模型实际名称", max_length=255)
|
||||
description: Optional[str] = Field(None, description="备注")
|
||||
provider: ModelProvider = Field(..., description="API Key提供商")
|
||||
api_key: str = Field(..., description="API密钥", max_length=500)
|
||||
api_base: Optional[str] = Field(None, description="API基础URL", max_length=500)
|
||||
@@ -37,22 +30,10 @@ class ApiKeyCreateNested(BaseModel):
|
||||
|
||||
class ModelConfigCreate(ModelConfigBase):
|
||||
"""创建模型配置Schema"""
|
||||
api_keys: Optional[List[ApiKeyCreateNested]] = Field(None, description="同时创建的API Key配置")
|
||||
api_keys: Optional[ApiKeyCreateNested] = Field(None, description="同时创建的API Key配置")
|
||||
skip_validation: Optional[bool] = Field(False, description="是否跳过配置验证")
|
||||
|
||||
|
||||
class CompositeModelCreate(BaseModel):
|
||||
"""创建组合模型Schema"""
|
||||
name: str = Field(..., description="组合模型名称", max_length=255)
|
||||
type: ModelType = Field(..., description="模型类型")
|
||||
logo: Optional[str] = Field(None, description="模型logo图片URL", max_length=255)
|
||||
description: Optional[str] = Field(None, description="模型描述")
|
||||
config: Optional[Dict[str, Any]] = Field({}, description="模型配置参数")
|
||||
is_active: bool = Field(True, description="是否激活")
|
||||
is_public: bool = Field(False, description="是否公开")
|
||||
api_key_ids: List[uuid.UUID] = Field(..., description="绑定的API Key ID列表")
|
||||
|
||||
|
||||
class ModelConfigUpdate(BaseModel):
|
||||
"""更新模型配置Schema"""
|
||||
name: Optional[str] = Field(None, description="模型显示名称", max_length=255)
|
||||
@@ -72,48 +53,22 @@ class ModelConfig(ModelConfigBase):
|
||||
updated_at: datetime.datetime
|
||||
api_keys: List["ModelApiKey"] = []
|
||||
|
||||
@field_validator("api_keys", mode="after")
|
||||
@classmethod
|
||||
def filter_active_api_keys(cls, api_keys: List["ModelApiKey"]) -> List["ModelApiKey"]:
|
||||
return [key for key in api_keys if key.is_active]
|
||||
|
||||
@field_serializer("created_at", when_used="json")
|
||||
def _serialize_created_at(self, dt: datetime.datetime | None):
|
||||
return int(dt.timestamp() * 1000) if dt else None
|
||||
|
||||
@field_serializer("updated_at", when_used="json")
|
||||
def _serialize_updated_at(self, dt: datetime.datetime):
|
||||
return int(dt.timestamp() * 1000) if dt else None
|
||||
|
||||
|
||||
# ModelApiKey Schemas
|
||||
class ModelApiKeyCreateByProvider(BaseModel):
|
||||
"""基于供应商创建API Key Schema"""
|
||||
provider: ModelProvider = Field(..., description="API Key提供商")
|
||||
api_key: str = Field(..., description="API密钥", max_length=500)
|
||||
api_base: Optional[str] = Field(None, description="API基础URL", max_length=500)
|
||||
description: Optional[str] = Field(None, description="备注")
|
||||
config: Optional[Dict[str, Any]] = Field({}, description="API Key特定配置")
|
||||
is_active: bool = Field(True, description="是否激活")
|
||||
priority: str = Field("1", description="优先级", max_length=10)
|
||||
model_config_ids: Optional[List[uuid.UUID]] = Field(None, description="关联的模型配置ID列表")
|
||||
|
||||
|
||||
class ModelApiKeyBase(BaseModel):
|
||||
"""API Key基础Schema"""
|
||||
model_name: str = Field(..., description="模型实际名称", max_length=255)
|
||||
description: Optional[str] = Field(None, description="备注")
|
||||
provider: ModelProvider = Field(..., description="API Key提供商")
|
||||
api_key: str = Field(..., description="API密钥", max_length=500)
|
||||
api_base: Optional[str] = Field(None, description="API基础URL", max_length=500)
|
||||
config: Optional[Dict[str, Any]] = Field({}, description="API Key特定配置")
|
||||
config: Optional[Dict[str, Any]] = Field(None, description="API Key特定配置")
|
||||
is_active: bool = Field(True, description="是否激活")
|
||||
priority: str = Field("1", description="优先级", max_length=10)
|
||||
|
||||
|
||||
class ModelApiKeyCreate(ModelApiKeyBase):
|
||||
"""创建API Key Schema"""
|
||||
model_config_ids: Optional[List[uuid.UUID]] = Field(None, description="关联的模型配置ID列表")
|
||||
model_config_id: uuid.UUID = Field(..., description="模型配置ID")
|
||||
|
||||
|
||||
class ModelApiKeyUpdate(BaseModel):
|
||||
@@ -130,54 +85,23 @@ class ModelApiKeyUpdate(BaseModel):
|
||||
class ModelApiKey(ModelApiKeyBase):
|
||||
"""API Key Schema"""
|
||||
id: uuid.UUID
|
||||
model_config_id: uuid.UUID
|
||||
usage_count: str
|
||||
last_used_at: Optional[datetime.datetime]
|
||||
created_at: datetime.datetime
|
||||
updated_at: datetime.datetime
|
||||
model_configs: Any = Field(default=None, exclude=True)
|
||||
model_config_ids: List[uuid.UUID] = Field(default_factory=list, description="关联的模型配置ID列表")
|
||||
|
||||
def model_post_init(self, __context: Any) -> None:
|
||||
"""实例化后强制提取 model_configs 的ID到 model_config_ids"""
|
||||
# 如果手动传入了 model_config_ids,不覆盖
|
||||
if self.model_config_ids and len(self.model_config_ids) > 0:
|
||||
return
|
||||
|
||||
# 从 model_configs 提取ID(只提取与 model_name 相同的非组合模型)
|
||||
if self.model_configs is not None:
|
||||
@field_validator("config", mode="before")
|
||||
@classmethod
|
||||
def parse_config(cls, v):
|
||||
"""处理 config 字段,如果是字符串则解析为字典"""
|
||||
if isinstance(v, str):
|
||||
import json
|
||||
try:
|
||||
# 情况1:ORM 对象列表(SQLAlchemy 关联)
|
||||
if hasattr(self.model_configs, '__iter__') and not isinstance(self.model_configs, dict):
|
||||
self.model_config_ids = [
|
||||
mc.id for mc in self.model_configs
|
||||
if hasattr(mc, 'id')
|
||||
and not getattr(mc, 'is_composite', False)
|
||||
and getattr(mc, 'name', None) == self.model_name
|
||||
]
|
||||
# 情况2:字典列表
|
||||
elif isinstance(self.model_configs, list):
|
||||
self.model_config_ids = [
|
||||
mc['id'] if isinstance(mc, dict) else mc.id
|
||||
for mc in self.model_configs
|
||||
if ((isinstance(mc, dict)
|
||||
and 'id' in mc
|
||||
and not mc.get('is_composite', False)
|
||||
and mc.get('name') == self.model_name) or
|
||||
(hasattr(mc, 'id')
|
||||
and not getattr(mc, 'is_composite', False)
|
||||
and getattr(mc, 'name', None) == self.model_name))
|
||||
]
|
||||
except Exception as e:
|
||||
schema_logger.warning(f"提取 model_config_ids 失败:{e}")
|
||||
self.model_config_ids = []
|
||||
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True, # 支持从 ORM 解析
|
||||
arbitrary_types_allowed=True, # 允许任意类型(ORM 对象)
|
||||
populate_by_name=True, # 按属性名匹配字段
|
||||
validate_assignment=True # 确保赋值触发校验
|
||||
)
|
||||
|
||||
return json.loads(v)
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
return v
|
||||
|
||||
@field_serializer("created_at", when_used="json")
|
||||
def _serialize_created_at(self, dt: datetime.datetime):
|
||||
@@ -186,12 +110,15 @@ class ModelApiKey(ModelApiKeyBase):
|
||||
@field_serializer("updated_at", when_used="json")
|
||||
def _serialize_updated_at(self, dt: datetime.datetime):
|
||||
return int(dt.timestamp() * 1000) if dt else None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@field_serializer("last_used_at", when_used="json")
|
||||
def _serialize_last_used_at(self, dt: datetime.datetime):
|
||||
return int(dt.timestamp() * 1000) if dt else None
|
||||
|
||||
|
||||
# 查询和响应Schemas
|
||||
class ModelConfigQuery(BaseModel):
|
||||
"""模型配置查询Schema"""
|
||||
type: Optional[List[ModelType]] = Field(None, description="模型类型筛选(支持多个)")
|
||||
@@ -202,17 +129,6 @@ class ModelConfigQuery(BaseModel):
|
||||
page: int = Field(1, description="页码", ge=1)
|
||||
pagesize: int = Field(10, description="每页数量", ge=1, le=100)
|
||||
|
||||
|
||||
# 查询和响应Schemas
|
||||
class ModelConfigQueryNew(BaseModel):
|
||||
"""模型配置查询Schema"""
|
||||
type: Optional[List[ModelType]] = Field(None, description="模型类型筛选(支持多个)")
|
||||
provider: Optional[ModelProvider] = Field(None, description="提供商筛选(通过API Key)")
|
||||
is_active: Optional[bool] = Field(None, description="激活状态筛选")
|
||||
is_public: Optional[bool] = Field(None, description="公开状态筛选")
|
||||
is_composite: Optional[bool] = Field(None, description="组合模型筛选")
|
||||
search: Optional[str] = Field(None, description="搜索关键词", max_length=255)
|
||||
|
||||
class ModelMarketplace(BaseModel):
|
||||
"""模型广场响应Schema"""
|
||||
llm_models: List[ModelConfig] = []
|
||||
@@ -255,53 +171,4 @@ class ModelValidateResponse(BaseModel):
|
||||
|
||||
|
||||
# 更新前向引用
|
||||
ModelConfig.model_rebuild()
|
||||
|
||||
|
||||
# ModelBase Schemas
|
||||
class ModelBaseCreate(BaseModel):
|
||||
"""创建基础模型Schema"""
|
||||
name: str = Field(..., description="模型唯一标识", max_length=255)
|
||||
type: ModelType = Field(..., description="模型类型")
|
||||
provider: ModelProvider = Field(..., description="提供商")
|
||||
logo: Optional[str] = Field(None, description="模型logo图片URL", max_length=255)
|
||||
description: Optional[str] = Field(None, description="模型描述")
|
||||
is_official: bool = Field(True, description="是否供应商官方模型")
|
||||
tags: List[str] = Field(default_factory=list, description="模型标签")
|
||||
|
||||
|
||||
class ModelBaseUpdate(BaseModel):
|
||||
"""更新基础模型Schema"""
|
||||
name: Optional[str] = Field(None, description="模型唯一标识", max_length=255)
|
||||
type: Optional[ModelType] = Field(None, description="模型类型")
|
||||
provider: Optional[ModelProvider] = Field(None, description="提供商")
|
||||
logo: Optional[str] = Field(None, description="模型logo图片URL", max_length=255)
|
||||
description: Optional[str] = Field(None, description="模型描述")
|
||||
is_deprecated: Optional[bool] = Field(None, description="是否弃用")
|
||||
is_official: Optional[bool] = Field(None, description="是否供应商官方模型")
|
||||
tags: Optional[List[str]] = Field(None, description="模型标签")
|
||||
|
||||
|
||||
class ModelBase(BaseModel):
|
||||
"""基础模型Schema"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: uuid.UUID
|
||||
name: str
|
||||
type: str
|
||||
provider: str
|
||||
logo: Optional[str]
|
||||
description: Optional[str]
|
||||
is_deprecated: bool
|
||||
is_official: bool
|
||||
tags: List[str]
|
||||
add_count: int
|
||||
|
||||
|
||||
class ModelBaseQuery(BaseModel):
|
||||
"""基础模型查询Schema"""
|
||||
type: Optional[ModelType] = Field(None, description="模型类型")
|
||||
provider: Optional[ModelProvider] = Field(None, description="提供商")
|
||||
is_official: Optional[bool] = Field(None, description="是否官方模型")
|
||||
is_deprecated: Optional[bool] = Field(None, description="是否弃用")
|
||||
search: Optional[str] = Field(None, description="搜索关键词", max_length=255)
|
||||
ModelConfig.model_rebuild()
|
||||
@@ -1,193 +0,0 @@
|
||||
"""应用统计服务"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any, List
|
||||
import uuid
|
||||
from sqlalchemy import func, and_, cast, Date
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.conversation_model import Conversation, Message
|
||||
from app.models.end_user_model import EndUser
|
||||
from app.models.api_key_model import ApiKey, ApiKeyLog
|
||||
from app.core.exceptions import BusinessException
|
||||
from app.core.error_codes import BizCode
|
||||
|
||||
|
||||
class AppStatisticsService:
|
||||
"""应用统计服务"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_app_statistics(
|
||||
self,
|
||||
app_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
start_date: int,
|
||||
end_date: int
|
||||
) -> Dict[str, Any]:
|
||||
"""获取应用统计数据
|
||||
|
||||
Args:
|
||||
app_id: 应用ID
|
||||
workspace_id: 工作空间ID
|
||||
start_date: 开始时间戳(毫秒)
|
||||
end_date: 结束时间戳(毫秒)
|
||||
|
||||
Returns:
|
||||
统计数据字典
|
||||
"""
|
||||
# 将毫秒时间戳转换为 datetime
|
||||
start_dt = datetime.fromtimestamp(start_date / 1000)
|
||||
end_dt = datetime.fromtimestamp(end_date / 1000) + timedelta(days=1)
|
||||
|
||||
# 1. 会话统计
|
||||
conversations_stats = self._get_conversations_statistics(app_id, workspace_id, start_dt, end_dt)
|
||||
|
||||
# 2. 新增用户统计
|
||||
users_stats = self._get_new_users_statistics(app_id, start_dt, end_dt)
|
||||
|
||||
# 3. API调用统计
|
||||
api_stats = self._get_api_calls_statistics(app_id, start_dt, end_dt)
|
||||
|
||||
# 4. Token消耗统计
|
||||
token_stats = self._get_token_statistics(app_id, start_dt, end_dt)
|
||||
|
||||
return {
|
||||
"daily_conversations": conversations_stats["daily"],
|
||||
"total_conversations": conversations_stats["total"],
|
||||
"daily_new_users": users_stats["daily"],
|
||||
"total_new_users": users_stats["total"],
|
||||
"daily_api_calls": api_stats["daily"],
|
||||
"total_api_calls": api_stats["total"],
|
||||
"daily_tokens": token_stats["daily"],
|
||||
"total_tokens": token_stats["total"]
|
||||
}
|
||||
|
||||
def _get_conversations_statistics(
|
||||
self,
|
||||
app_id: uuid.UUID,
|
||||
workspace_id: uuid.UUID,
|
||||
start_dt: datetime,
|
||||
end_dt: datetime
|
||||
) -> Dict[str, Any]:
|
||||
"""获取会话统计"""
|
||||
# 每日会话数
|
||||
daily_query = self.db.query(
|
||||
cast(Conversation.created_at, Date).label('date'),
|
||||
func.count(Conversation.id).label('count')
|
||||
).filter(
|
||||
and_(
|
||||
Conversation.app_id == app_id,
|
||||
Conversation.workspace_id == workspace_id,
|
||||
Conversation.created_at >= start_dt,
|
||||
Conversation.created_at < end_dt
|
||||
)
|
||||
).group_by(cast(Conversation.created_at, Date)).all()
|
||||
|
||||
daily_data = [{"date": str(row.date), "count": row.count} for row in daily_query]
|
||||
total = sum(row["count"] for row in daily_data)
|
||||
|
||||
return {"daily": daily_data, "total": total}
|
||||
|
||||
def _get_new_users_statistics(
|
||||
self,
|
||||
app_id: uuid.UUID,
|
||||
start_dt: datetime,
|
||||
end_dt: datetime
|
||||
) -> Dict[str, Any]:
|
||||
"""获取新增用户统计"""
|
||||
# 每日新增用户数
|
||||
daily_query = self.db.query(
|
||||
cast(EndUser.created_at, Date).label('date'),
|
||||
func.count(EndUser.id).label('count')
|
||||
).filter(
|
||||
and_(
|
||||
EndUser.app_id == app_id,
|
||||
EndUser.created_at >= start_dt,
|
||||
EndUser.created_at < end_dt
|
||||
)
|
||||
).group_by(cast(EndUser.created_at, Date)).all()
|
||||
|
||||
daily_data = [{"date": str(row.date), "count": row.count} for row in daily_query]
|
||||
total = sum(row["count"] for row in daily_data)
|
||||
|
||||
return {"daily": daily_data, "total": total}
|
||||
|
||||
def _get_api_calls_statistics(
|
||||
self,
|
||||
app_id: uuid.UUID,
|
||||
start_dt: datetime,
|
||||
end_dt: datetime
|
||||
) -> Dict[str, Any]:
|
||||
"""获取API调用统计"""
|
||||
# 每日API调用次数
|
||||
daily_query = self.db.query(
|
||||
cast(ApiKeyLog.created_at, Date).label('date'),
|
||||
func.count(ApiKeyLog.id).label('count')
|
||||
).join(
|
||||
ApiKey, ApiKeyLog.api_key_id == ApiKey.id
|
||||
).filter(
|
||||
and_(
|
||||
ApiKey.resource_id == app_id,
|
||||
ApiKeyLog.created_at >= start_dt,
|
||||
ApiKeyLog.created_at < end_dt
|
||||
)
|
||||
).group_by(cast(ApiKeyLog.created_at, Date)).all()
|
||||
|
||||
daily_data = [{"date": str(row.date), "count": row.count} for row in daily_query]
|
||||
total = sum(row["count"] for row in daily_data)
|
||||
|
||||
return {"daily": daily_data, "total": total}
|
||||
|
||||
def _get_token_statistics(
|
||||
self,
|
||||
app_id: uuid.UUID,
|
||||
start_dt: datetime,
|
||||
end_dt: datetime
|
||||
) -> Dict[str, Any]:
|
||||
"""获取Token消耗统计(从Message的meta_data中提取)"""
|
||||
from sqlalchemy import text
|
||||
|
||||
# 查询所有相关消息的token使用情况
|
||||
# meta_data中可能包含: {"usage": {"total_tokens": 100}} 或 {"tokens": 100}
|
||||
daily_query = self.db.query(
|
||||
cast(Message.created_at, Date).label('date'),
|
||||
Message.meta_data
|
||||
).join(
|
||||
Conversation, Message.conversation_id == Conversation.id
|
||||
).filter(
|
||||
and_(
|
||||
Conversation.app_id == app_id,
|
||||
Message.created_at >= start_dt,
|
||||
Message.created_at < end_dt,
|
||||
Message.meta_data.isnot(None)
|
||||
)
|
||||
).all()
|
||||
|
||||
# 按日期聚合token
|
||||
daily_tokens = {}
|
||||
for row in daily_query:
|
||||
date_str = str(row.date)
|
||||
meta = row.meta_data or {}
|
||||
|
||||
# 提取token数量(支持多种格式)
|
||||
tokens = 0
|
||||
if isinstance(meta, dict):
|
||||
# 格式1: {"usage": {"total_tokens": 100}}
|
||||
if "usage" in meta and isinstance(meta["usage"], dict):
|
||||
tokens = meta["usage"].get("total_tokens", 0)
|
||||
# 格式2: {"tokens": 100}
|
||||
elif "tokens" in meta:
|
||||
tokens = meta.get("tokens", 0)
|
||||
# 格式3: {"total_tokens": 100}
|
||||
elif "total_tokens" in meta:
|
||||
tokens = meta.get("total_tokens", 0)
|
||||
|
||||
if date_str not in daily_tokens:
|
||||
daily_tokens[date_str] = 0
|
||||
daily_tokens[date_str] += int(tokens)
|
||||
|
||||
daily_data = [{"date": date, "tokens": tokens} for date, tokens in sorted(daily_tokens.items()) if tokens != 0]
|
||||
total = sum(row["tokens"] for row in daily_data)
|
||||
|
||||
return {"daily": daily_data, "total": total}
|
||||
@@ -16,7 +16,6 @@ from app.core.exceptions import BusinessException
|
||||
from app.core.logging_config import get_business_logger
|
||||
from app.core.rag.nlp.search import knowledge_retrieval
|
||||
from app.models import AgentConfig, ModelApiKey, ModelConfig
|
||||
from app.repositories.model_repository import ModelApiKeyRepository
|
||||
from app.repositories.tool_repository import ToolRepository
|
||||
from app.schemas.prompt_schema import PromptMessageRole, render_prompt_message
|
||||
from app.services import task_service
|
||||
@@ -725,21 +724,17 @@ class DraftRunService:
|
||||
Raises:
|
||||
BusinessException: 当没有可用的 API Key 时
|
||||
"""
|
||||
api_keys = ModelApiKeyRepository.get_by_model_config(self.db, model_config_id)
|
||||
# stmt = (
|
||||
# select(ModelApiKey).join(
|
||||
# ModelConfig, ModelApiKey.model_configs
|
||||
# )
|
||||
# .where(
|
||||
# ModelConfig.id == model_config_id,
|
||||
# ModelApiKey.is_active.is_(True)
|
||||
# )
|
||||
# .order_by(ModelApiKey.priority.desc())
|
||||
# .limit(1)
|
||||
# )
|
||||
#
|
||||
# api_key = self.db.scalars(stmt).first()
|
||||
api_key = api_keys[0] if api_keys else None
|
||||
stmt = (
|
||||
select(ModelApiKey)
|
||||
.where(
|
||||
ModelApiKey.model_config_id == model_config_id,
|
||||
ModelApiKey.is_active.is_(True)
|
||||
)
|
||||
.order_by(ModelApiKey.priority.desc())
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
api_key = self.db.scalars(stmt).first()
|
||||
|
||||
if not api_key:
|
||||
raise BusinessException("没有可用的 API Key", BizCode.AGENT_CONFIG_MISSING)
|
||||
|
||||
@@ -5,7 +5,6 @@ import uuid
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.repositories.model_repository import ModelApiKeyRepository
|
||||
from app.services.conversation_state_manager import ConversationStateManager
|
||||
from app.models import ModelConfig, AgentConfig
|
||||
from app.core.logging_config import get_business_logger
|
||||
@@ -383,14 +382,11 @@ class LLMRouter:
|
||||
from app.core.models.base import RedBearModelConfig
|
||||
from app.models import ModelApiKey, ModelType
|
||||
|
||||
# 获取 API Key 配置(通过关联关系)
|
||||
# api_key_config = self.db.query(ModelApiKey).join(
|
||||
# ModelConfig, ModelApiKey.model_configs
|
||||
# ).filter(ModelConfig.id == self.routing_model_config.id,
|
||||
# ModelApiKey.is_active == True
|
||||
# ).first()
|
||||
api_keys = ModelApiKeyRepository.get_by_model_config(self.db, self.routing_model_config.id)
|
||||
api_key_config = api_keys[0] if api_keys else None
|
||||
# 获取 API Key 配置
|
||||
api_key_config = self.db.query(ModelApiKey).filter(
|
||||
ModelApiKey.model_config_id == self.routing_model_config.id,
|
||||
ModelApiKey.is_active
|
||||
).first()
|
||||
|
||||
if not api_key_config:
|
||||
raise Exception("路由模型没有可用的 API Key")
|
||||
@@ -423,9 +419,6 @@ class LLMRouter:
|
||||
|
||||
# 调用模型
|
||||
response = await llm.ainvoke(prompt)
|
||||
|
||||
from app.services.model_service import ModelApiKeyService
|
||||
ModelApiKeyService.record_api_key_usage(self.db, api_key_config.id)
|
||||
|
||||
# 提取响应内容
|
||||
if hasattr(response, 'content'):
|
||||
|
||||
@@ -338,7 +338,7 @@ class MemoryConfigService:
|
||||
"provider": api_config.provider,
|
||||
"api_key": api_config.api_key,
|
||||
"base_url": api_config.api_base,
|
||||
"model_config_id": str(config.id),
|
||||
"model_config_id": api_config.model_config_id,
|
||||
"type": config.type,
|
||||
"timeout": settings.LLM_TIMEOUT,
|
||||
"max_retries": settings.LLM_MAX_RETRIES,
|
||||
@@ -370,7 +370,7 @@ class MemoryConfigService:
|
||||
"provider": api_config.provider,
|
||||
"api_key": api_config.api_key,
|
||||
"base_url": api_config.api_base,
|
||||
"model_config_id": str(config.id),
|
||||
"model_config_id": api_config.model_config_id,
|
||||
"type": config.type,
|
||||
"timeout": 120.0,
|
||||
"max_retries": 5,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional, Dict, Any
|
||||
import uuid
|
||||
@@ -7,11 +6,11 @@ import time
|
||||
import asyncio
|
||||
|
||||
from app.models.models_model import ModelConfig, ModelApiKey, ModelType
|
||||
from app.repositories.model_repository import ModelConfigRepository, ModelApiKeyRepository, ModelBaseRepository
|
||||
from app.repositories.model_repository import ModelConfigRepository, ModelApiKeyRepository
|
||||
from app.schemas import model_schema
|
||||
from app.schemas.model_schema import (
|
||||
ModelConfigCreate, ModelConfigUpdate, ModelApiKeyCreate, ModelApiKeyUpdate,
|
||||
ModelConfigQuery, ModelStats, ModelConfigQueryNew
|
||||
ModelConfigQuery, ModelStats
|
||||
)
|
||||
from app.core.logging_config import get_business_logger
|
||||
from app.schemas.response_schema import PageData, PageMeta
|
||||
@@ -48,26 +47,6 @@ class ModelConfigService:
|
||||
items=[model_schema.ModelConfig.model_validate(model) for model in models]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_model_list_new(db: Session, query: ModelConfigQueryNew, tenant_id: uuid.UUID | None = None) -> List[dict]:
|
||||
"""获取模型配置列表"""
|
||||
provider_groups, total = ModelConfigRepository.get_list_new(db, query, tenant_id=tenant_id)
|
||||
|
||||
items = []
|
||||
for provider, models in provider_groups.items():
|
||||
# 验证每个模型并封装分组信息
|
||||
validated_models = [model_schema.ModelConfig.model_validate(model) for model in models]
|
||||
tags = list({model.type for model in validated_models})
|
||||
group_item = {
|
||||
"provider": provider, # 服务商名称
|
||||
"logo": validated_models[0].logo,
|
||||
"tags": tags,
|
||||
"models": validated_models # 该服务商下的所有模型
|
||||
}
|
||||
items.append(group_item)
|
||||
|
||||
return items
|
||||
|
||||
@staticmethod
|
||||
def get_model_by_name(db: Session, name: str, tenant_id: uuid.UUID | None = None) -> ModelConfig:
|
||||
"""根据名称获取模型配置"""
|
||||
@@ -249,39 +228,37 @@ class ModelConfigService:
|
||||
|
||||
# 验证配置
|
||||
if not model_data.skip_validation and model_data.api_keys:
|
||||
api_key_data_list = model_data.api_keys
|
||||
for api_key_data in api_key_data_list:
|
||||
validation_result = await ModelConfigService.validate_model_config(
|
||||
db=db,
|
||||
model_name=api_key_data.model_name,
|
||||
provider=api_key_data.provider,
|
||||
api_key=api_key_data.api_key,
|
||||
api_base=api_key_data.api_base,
|
||||
model_type=model_data.type, # 传递模型类型
|
||||
test_message="Hello"
|
||||
api_key_data = model_data.api_keys
|
||||
validation_result = await ModelConfigService.validate_model_config(
|
||||
db=db,
|
||||
model_name=api_key_data.model_name,
|
||||
provider=api_key_data.provider,
|
||||
api_key=api_key_data.api_key,
|
||||
api_base=api_key_data.api_base,
|
||||
model_type=model_data.type, # 传递模型类型
|
||||
test_message="Hello"
|
||||
)
|
||||
if not validation_result["valid"]:
|
||||
raise BusinessException(
|
||||
f"模型配置验证失败: {validation_result['error']}",
|
||||
BizCode.INVALID_PARAMETER
|
||||
)
|
||||
if not validation_result["valid"]:
|
||||
raise BusinessException(
|
||||
f"模型配置验证失败: {validation_result['error']}",
|
||||
BizCode.INVALID_PARAMETER
|
||||
)
|
||||
|
||||
# 事务处理
|
||||
api_key_datas = model_data.api_keys
|
||||
model_config_data = model_data.model_dump(exclude={"api_keys", "skip_validation"})
|
||||
api_key_data = model_data.api_keys
|
||||
model_config_data = model_data.dict(exclude={"api_keys", "skip_validation"})
|
||||
# 添加租户ID
|
||||
model_config_data["tenant_id"] = tenant_id
|
||||
|
||||
model = ModelConfigRepository.create(db, model_config_data)
|
||||
db.flush() # 获取生成的 ID
|
||||
|
||||
if api_key_datas:
|
||||
for api_key_data in api_key_datas:
|
||||
api_key_create_schema = ModelApiKeyCreate(
|
||||
model_config_ids=[model.id],
|
||||
**api_key_data.model_dump()
|
||||
)
|
||||
ModelApiKeyRepository.create(db, api_key_create_schema)
|
||||
if api_key_data:
|
||||
api_key_create_schema = ModelApiKeyCreate(
|
||||
model_config_id=model.id,
|
||||
**api_key_data.dict()
|
||||
)
|
||||
ModelApiKeyRepository.create(db, api_key_create_schema)
|
||||
|
||||
db.commit()
|
||||
db.refresh(model)
|
||||
@@ -303,112 +280,6 @@ class ModelConfigService:
|
||||
db.refresh(model)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
async def create_composite_model(db: Session, model_data: model_schema.CompositeModelCreate, tenant_id: uuid.UUID) -> ModelConfig:
|
||||
"""创建组合模型"""
|
||||
if ModelConfigRepository.get_by_name(db, model_data.name, tenant_id=tenant_id):
|
||||
raise BusinessException("模型名称已存在", BizCode.DUPLICATE_NAME)
|
||||
|
||||
# 验证所有 API Key 存在且类型匹配
|
||||
for api_key_id in model_data.api_key_ids:
|
||||
api_key = ModelApiKeyRepository.get_by_id(db, api_key_id)
|
||||
if not api_key:
|
||||
raise BusinessException(f"API Key {api_key_id} 不存在", BizCode.NOT_FOUND)
|
||||
|
||||
# 检查 API Key 关联的模型配置类型
|
||||
for model_config in api_key.model_configs:
|
||||
# chat 和 llm 类型可以兼容
|
||||
compatible_types = {ModelType.LLM, ModelType.CHAT}
|
||||
config_type = model_config.type
|
||||
request_type = model_data.type
|
||||
|
||||
if not (config_type == request_type or
|
||||
(config_type in compatible_types and request_type in compatible_types)):
|
||||
raise BusinessException(
|
||||
f"API Key {api_key_id} 关联的模型类型 ({model_config.type}) 与组合模型类型 ({model_data.type}) 不匹配",
|
||||
BizCode.INVALID_PARAMETER
|
||||
)
|
||||
# if model_config.is_composite:
|
||||
# raise BusinessException(
|
||||
# f"API Key {api_key_id} 关联的模型是组合模型,不能用于创建新的组合模型",
|
||||
# BizCode.INVALID_PARAMETER
|
||||
# )
|
||||
|
||||
# 创建组合模型
|
||||
model_config_data = {
|
||||
"tenant_id": tenant_id,
|
||||
"name": model_data.name,
|
||||
"type": model_data.type,
|
||||
"logo": model_data.logo,
|
||||
"description": model_data.description,
|
||||
"provider": "composite",
|
||||
"config": model_data.config,
|
||||
"is_active": model_data.is_active,
|
||||
"is_public": model_data.is_public,
|
||||
"is_composite": True
|
||||
}
|
||||
|
||||
model = ModelConfigRepository.create(db, model_config_data)
|
||||
db.flush()
|
||||
|
||||
# 关联 API Keys
|
||||
for api_key_id in model_data.api_key_ids:
|
||||
api_key = ModelApiKeyRepository.get_by_id(db, api_key_id)
|
||||
if api_key:
|
||||
model.api_keys.append(api_key)
|
||||
|
||||
db.commit()
|
||||
db.refresh(model)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
async def update_composite_model(db: Session, model_id: uuid.UUID, model_data: model_schema.CompositeModelCreate, tenant_id: uuid.UUID) -> ModelConfig:
|
||||
"""更新组合模型"""
|
||||
existing_model = ModelConfigRepository.get_by_id(db, model_id, tenant_id=tenant_id)
|
||||
if not existing_model:
|
||||
raise BusinessException("模型配置不存在", BizCode.MODEL_NOT_FOUND)
|
||||
|
||||
if not existing_model.is_composite:
|
||||
raise BusinessException("该模型不是组合模型", BizCode.INVALID_PARAMETER)
|
||||
|
||||
# 验证所有 API Key 存在且类型匹配
|
||||
for api_key_id in model_data.api_key_ids:
|
||||
api_key = ModelApiKeyRepository.get_by_id(db, api_key_id)
|
||||
if not api_key:
|
||||
raise BusinessException(f"API Key {api_key_id} 不存在", BizCode.NOT_FOUND)
|
||||
|
||||
for model_config in api_key.model_configs:
|
||||
compatible_types = {ModelType.LLM, ModelType.CHAT}
|
||||
config_type = model_config.type
|
||||
request_type = model_data.type
|
||||
|
||||
if not (config_type == request_type or
|
||||
(config_type in compatible_types and request_type in compatible_types)):
|
||||
raise BusinessException(
|
||||
f"API Key {api_key_id} 关联的模型类型 ({model_config.type}) 与组合模型类型 ({model_data.type}) 不匹配",
|
||||
BizCode.INVALID_PARAMETER
|
||||
)
|
||||
|
||||
# 更新基本信息
|
||||
existing_model.name = model_data.name
|
||||
existing_model.type = model_data.type
|
||||
existing_model.logo = model_data.logo
|
||||
existing_model.description = model_data.description
|
||||
existing_model.config = model_data.config
|
||||
existing_model.is_active = model_data.is_active
|
||||
existing_model.is_public = model_data.is_public
|
||||
|
||||
# 更新 API Keys 关联
|
||||
existing_model.api_keys.clear()
|
||||
for api_key_id in model_data.api_key_ids:
|
||||
api_key = ModelApiKeyRepository.get_by_id(db, api_key_id)
|
||||
if api_key:
|
||||
existing_model.api_keys.append(api_key)
|
||||
|
||||
db.commit()
|
||||
db.refresh(existing_model)
|
||||
return existing_model
|
||||
|
||||
@staticmethod
|
||||
def delete_model(db: Session, model_id: uuid.UUID, tenant_id: uuid.UUID | None = None) -> bool:
|
||||
"""删除模型配置"""
|
||||
@@ -453,132 +324,27 @@ class ModelApiKeyService:
|
||||
return ModelApiKeyRepository.get_by_model_config(db, model_config_id, is_active)
|
||||
|
||||
@staticmethod
|
||||
async def create_api_key_by_provider(db: Session, data: model_schema.ModelApiKeyCreateByProvider) -> List[ModelApiKey]:
|
||||
"""根据provider为多个ModelConfig创建API Key"""
|
||||
created_keys = []
|
||||
|
||||
for model_config_id in data.model_config_ids:
|
||||
model_config = ModelConfigRepository.get_by_id(db, model_config_id)
|
||||
if not model_config:
|
||||
continue
|
||||
|
||||
# 从ModelBase获取model_name
|
||||
model_name = model_config.model_base.name if model_config.model_base else model_config.name
|
||||
|
||||
# 检查是否存在API Key(包括软删除)
|
||||
existing_key = db.query(ModelApiKey).filter(
|
||||
ModelApiKey.api_key == data.api_key,
|
||||
ModelApiKey.provider == data.provider,
|
||||
ModelApiKey.model_name == model_name
|
||||
).first()
|
||||
|
||||
if existing_key:
|
||||
# 如果已存在,重新激活并更新
|
||||
if existing_key.is_active:
|
||||
continue
|
||||
existing_key.is_active = True
|
||||
existing_key.api_base = data.api_base
|
||||
existing_key.description = data.description
|
||||
existing_key.config = data.config
|
||||
existing_key.priority = data.priority
|
||||
existing_key.model_name = model_name
|
||||
|
||||
# 检查是否已关联该模型配置
|
||||
if model_config not in existing_key.model_configs:
|
||||
existing_key.model_configs.append(model_config)
|
||||
|
||||
created_keys.append(existing_key)
|
||||
continue
|
||||
|
||||
# 验证配置
|
||||
validation_result = await ModelConfigService.validate_model_config(
|
||||
async def create_api_key(db: Session, api_key_data: ModelApiKeyCreate) -> ModelApiKey:
|
||||
"""创建API Key"""
|
||||
model_config = ModelConfigRepository.get_by_id(db, api_key_data.model_config_id)
|
||||
if not model_config:
|
||||
raise BusinessException("模型配置不存在", BizCode.MODEL_NOT_FOUND)
|
||||
|
||||
validation_result = await ModelConfigService.validate_model_config(
|
||||
db=db,
|
||||
model_name=model_name,
|
||||
provider=data.provider,
|
||||
api_key=data.api_key,
|
||||
api_base=data.api_base,
|
||||
model_type=model_config.type,
|
||||
model_name=api_key_data.model_name,
|
||||
provider=api_key_data.provider,
|
||||
api_key=api_key_data.api_key,
|
||||
api_base=api_key_data.api_base,
|
||||
model_type=model_config.type, # 传递模型类型
|
||||
test_message="Hello"
|
||||
)
|
||||
if not validation_result["valid"]:
|
||||
print(validation_result)
|
||||
if not validation_result["valid"]:
|
||||
raise BusinessException(
|
||||
f"模型配置验证失败: {validation_result['error']}",
|
||||
BizCode.INVALID_PARAMETER
|
||||
)
|
||||
|
||||
# 创建API Key
|
||||
api_key_data = ModelApiKeyCreate(
|
||||
model_config_ids=[model_config_id],
|
||||
model_name=model_name,
|
||||
description=data.description,
|
||||
provider=data.provider,
|
||||
api_key=data.api_key,
|
||||
api_base=data.api_base,
|
||||
config=data.config,
|
||||
is_active=data.is_active,
|
||||
priority=data.priority
|
||||
)
|
||||
api_key_obj = ModelApiKeyRepository.create(db, api_key_data)
|
||||
created_keys.append(api_key_obj)
|
||||
|
||||
if created_keys:
|
||||
db.commit()
|
||||
for key in created_keys:
|
||||
db.refresh(key)
|
||||
|
||||
return created_keys
|
||||
|
||||
@staticmethod
|
||||
async def create_api_key(db: Session, api_key_data: ModelApiKeyCreate) -> ModelApiKey:
|
||||
# 验证所有关联的模型配置是否存在
|
||||
if api_key_data.model_config_ids:
|
||||
for model_config_id in api_key_data.model_config_ids:
|
||||
model_config = ModelConfigRepository.get_by_id(db, model_config_id)
|
||||
if not model_config:
|
||||
raise BusinessException("模型配置不存在", BizCode.MODEL_NOT_FOUND)
|
||||
|
||||
# 检查API Key是否已存在(包括软删除)
|
||||
existing_key = db.query(ModelApiKey).filter(
|
||||
ModelApiKey.api_key == api_key_data.api_key,
|
||||
ModelApiKey.provider == api_key_data.provider,
|
||||
ModelApiKey.model_name == api_key_data.model_name
|
||||
).first()
|
||||
|
||||
if existing_key:
|
||||
if existing_key.is_active:
|
||||
# 如果已激活,跳过
|
||||
raise BusinessException("该API Key已存在", BizCode.DUPLICATE_NAME)
|
||||
# 如果已存在,重新激活并更新
|
||||
existing_key.is_active = True
|
||||
existing_key.api_base = api_key_data.api_base
|
||||
existing_key.description = api_key_data.description
|
||||
existing_key.config = api_key_data.config
|
||||
existing_key.priority = api_key_data.priority
|
||||
existing_key.model_name = api_key_data.model_name
|
||||
|
||||
# 检查是否已关联该模型配置
|
||||
if model_config not in existing_key.model_configs:
|
||||
existing_key.model_configs.append(model_config)
|
||||
|
||||
db.commit()
|
||||
db.refresh(existing_key)
|
||||
return existing_key
|
||||
|
||||
# 验证配置
|
||||
validation_result = await ModelConfigService.validate_model_config(
|
||||
db=db,
|
||||
model_name=api_key_data.model_name,
|
||||
provider=api_key_data.provider,
|
||||
api_key=api_key_data.api_key,
|
||||
api_base=api_key_data.api_base,
|
||||
model_type=model_config.type,
|
||||
test_message="Hello"
|
||||
)
|
||||
if not validation_result["valid"]:
|
||||
raise BusinessException(
|
||||
f"模型配置验证失败: {validation_result['error']}",
|
||||
BizCode.INVALID_PARAMETER
|
||||
)
|
||||
|
||||
api_key = ModelApiKeyRepository.create(db, api_key_data)
|
||||
db.commit()
|
||||
@@ -593,19 +359,21 @@ class ModelApiKeyService:
|
||||
raise BusinessException("API Key不存在", BizCode.NOT_FOUND)
|
||||
|
||||
# 获取关联的模型配置以获取模型类型
|
||||
if existing_api_key.model_configs:
|
||||
model_config = existing_api_key.model_configs[0]
|
||||
|
||||
validation_result = await ModelConfigService.validate_model_config(
|
||||
model_config = ModelConfigRepository.get_by_id(db, existing_api_key.model_config_id)
|
||||
if not model_config:
|
||||
raise BusinessException("关联的模型配置不存在", BizCode.MODEL_NOT_FOUND)
|
||||
|
||||
validation_result = await ModelConfigService.validate_model_config(
|
||||
db=db,
|
||||
model_name=api_key_data.model_name or existing_api_key.model_name,
|
||||
provider=api_key_data.provider or existing_api_key.provider,
|
||||
api_key=api_key_data.api_key or existing_api_key.api_key,
|
||||
api_base=api_key_data.api_base or existing_api_key.api_base,
|
||||
model_type=model_config.type,
|
||||
model_name=api_key_data.model_name,
|
||||
provider=api_key_data.provider,
|
||||
api_key=api_key_data.api_key,
|
||||
api_base=api_key_data.api_base,
|
||||
model_type=model_config.type, # 传递模型类型
|
||||
test_message="Hello"
|
||||
)
|
||||
if not validation_result["valid"]:
|
||||
print(validation_result)
|
||||
if not validation_result["valid"]:
|
||||
raise BusinessException(
|
||||
f"模型配置验证失败: {validation_result['error']}",
|
||||
BizCode.INVALID_PARAMETER
|
||||
@@ -649,84 +417,3 @@ class ModelApiKeyService:
|
||||
if api_kes and len(api_kes) > 0:
|
||||
return api_kes[0]
|
||||
raise BusinessException("没有可用的 API Key", BizCode.AGENT_CONFIG_MISSING)
|
||||
|
||||
|
||||
|
||||
class ModelBaseService:
|
||||
"""基础模型服务"""
|
||||
|
||||
@staticmethod
|
||||
def get_model_base_list(db: Session, query: model_schema.ModelBaseQuery, tenant_id: uuid.UUID = None) -> List:
|
||||
models = ModelBaseRepository.get_list(db, query)
|
||||
|
||||
provider_groups = {}
|
||||
for m in models:
|
||||
model_dict = model_schema.ModelBase.model_validate(m).model_dump()
|
||||
if tenant_id:
|
||||
model_dict['is_added'] = ModelBaseRepository.check_added_by_tenant(db, m.id, tenant_id)
|
||||
|
||||
provider = m.provider
|
||||
if provider not in provider_groups:
|
||||
provider_groups[provider] = {
|
||||
"provider": provider,
|
||||
"models": []
|
||||
}
|
||||
provider_groups[provider]["models"].append(model_dict)
|
||||
|
||||
return list(provider_groups.values())
|
||||
|
||||
@staticmethod
|
||||
def get_model_base_by_id(db: Session, model_base_id: uuid.UUID):
|
||||
model = ModelBaseRepository.get_by_id(db, model_base_id)
|
||||
if not model:
|
||||
raise BusinessException("基础模型不存在", BizCode.MODEL_NOT_FOUND)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def create_model_base(db: Session, data: model_schema.ModelBaseCreate):
|
||||
model_base = ModelBaseRepository.create(db, data.model_dump())
|
||||
db.commit()
|
||||
db.refresh(model_base)
|
||||
return model_base
|
||||
|
||||
@staticmethod
|
||||
def update_model_base(db: Session, model_base_id: uuid.UUID, data: model_schema.ModelBaseUpdate):
|
||||
model_base = ModelBaseRepository.update(db, model_base_id, data.model_dump(exclude_unset=True))
|
||||
if not model_base:
|
||||
raise BusinessException("基础模型不存在", BizCode.MODEL_NOT_FOUND)
|
||||
db.commit()
|
||||
db.refresh(model_base)
|
||||
return model_base
|
||||
|
||||
@staticmethod
|
||||
def delete_model_base(db: Session, model_base_id: uuid.UUID) -> bool:
|
||||
success = ModelBaseRepository.delete(db, model_base_id)
|
||||
if not success:
|
||||
raise BusinessException("基础模型不存在", BizCode.MODEL_NOT_FOUND)
|
||||
db.commit()
|
||||
return success
|
||||
|
||||
@staticmethod
|
||||
def add_model_from_plaza(db: Session, model_base_id: uuid.UUID, tenant_id: uuid.UUID) -> ModelConfig:
|
||||
model_base = ModelBaseRepository.get_by_id(db, model_base_id)
|
||||
if not model_base:
|
||||
raise BusinessException("基础模型不存在", BizCode.MODEL_NOT_FOUND)
|
||||
|
||||
if ModelBaseRepository.check_added_by_tenant(db, model_base_id, tenant_id):
|
||||
raise BusinessException("模型已添加", BizCode.DUPLICATE_NAME)
|
||||
|
||||
model_config_data = {
|
||||
"model_id": model_base_id,
|
||||
"tenant_id": tenant_id,
|
||||
"name": model_base.name,
|
||||
"provider": model_base.provider,
|
||||
"type": model_base.type,
|
||||
"logo": model_base.logo,
|
||||
"description": model_base.description,
|
||||
"is_composite": False
|
||||
}
|
||||
model_config = ModelConfigRepository.create(db, model_config_data)
|
||||
ModelBaseRepository.increment_add_count(db, model_base_id)
|
||||
db.commit()
|
||||
db.refresh(model_config)
|
||||
return model_config
|
||||
|
||||
@@ -7,7 +7,6 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import MultiAgentConfig, AgentConfig, ModelConfig
|
||||
from app.models.multi_agent_model import AggregationStrategy, OrchestrationMode
|
||||
from app.repositories.model_repository import ModelApiKeyRepository
|
||||
from app.services.agent_registry import AgentRegistry
|
||||
from app.services.master_agent_router import MasterAgentRouter
|
||||
from app.services.conversation_state_manager import ConversationStateManager
|
||||
@@ -2547,14 +2546,10 @@ class MultiAgentOrchestrator:
|
||||
return self._smart_merge_results(results, strategy)
|
||||
|
||||
# 获取 API Key 配置
|
||||
# api_key_config = self.db.query(ModelApiKey).join(
|
||||
# ModelConfig, ModelApiKey.model_configs
|
||||
# ).filter(
|
||||
# ModelConfig.id == default_model_config_id,
|
||||
# ModelApiKey.is_active.is_(True)
|
||||
# ).first()
|
||||
api_keys = ModelApiKeyRepository.get_by_model_config(self.db, default_model_config_id)
|
||||
api_key_config = api_keys[0] if api_keys else None
|
||||
api_key_config = self.db.query(ModelApiKey).filter(
|
||||
ModelApiKey.model_config_id == default_model_config_id,
|
||||
ModelApiKey.is_active.is_(True)
|
||||
).first()
|
||||
|
||||
if not api_key_config:
|
||||
logger.warning("Master Agent 没有可用的 API Key,使用简单整合")
|
||||
@@ -2708,14 +2703,10 @@ class MultiAgentOrchestrator:
|
||||
return
|
||||
|
||||
# 获取 API Key 配置
|
||||
# api_key_config = self.db.query(ModelApiKey).join(
|
||||
# ModelConfig, ModelApiKey.model_configs
|
||||
# ).filter(
|
||||
# ModelConfig.id == default_model_config_id,
|
||||
# ModelApiKey.is_active.is_(True)
|
||||
# ).first()
|
||||
api_keys = ModelApiKeyRepository.get_by_model_config(self.db, default_model_config_id)
|
||||
api_key_config = api_keys[0] if api_keys else None
|
||||
api_key_config = self.db.query(ModelApiKey).filter(
|
||||
ModelApiKey.model_config_id == default_model_config_id,
|
||||
ModelApiKey.is_active.is_(True)
|
||||
).first()
|
||||
|
||||
if not api_key_config:
|
||||
logger.warning("Master Agent 没有可用的 API Key,使用简单整合")
|
||||
|
||||
@@ -4,8 +4,6 @@ import time
|
||||
import asyncio
|
||||
from typing import Optional, Dict, Any, AsyncGenerator
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.repositories.model_repository import ModelApiKeyRepository
|
||||
from app.services.memory_konwledges_server import write_rag
|
||||
from app.models import ReleaseShare, AppRelease, Conversation
|
||||
from app.services.conversation_service import ConversationService
|
||||
@@ -166,20 +164,16 @@ class SharedChatService:
|
||||
raise ResourceNotFoundException("模型配置", str(model_config_id))
|
||||
|
||||
# 获取 API Key
|
||||
# stmt = (
|
||||
# select(ModelApiKey).join(
|
||||
# ModelConfig, ModelApiKey.model_configs
|
||||
# )
|
||||
# .where(
|
||||
# ModelConfig.id == model_config_id,
|
||||
# ModelApiKey.is_active.is_(True)
|
||||
# )
|
||||
# .order_by(ModelApiKey.priority.desc())
|
||||
# .limit(1)
|
||||
# )
|
||||
# api_key_obj = self.db.scalars(stmt).first()
|
||||
api_keys = ModelApiKeyRepository.get_by_model_config(self.db, model_config_id)
|
||||
api_key_obj = api_keys[0] if api_keys else None
|
||||
stmt = (
|
||||
select(ModelApiKey)
|
||||
.where(
|
||||
ModelApiKey.model_config_id == model_config_id,
|
||||
ModelApiKey.is_active.is_(True)
|
||||
)
|
||||
.order_by(ModelApiKey.priority.desc())
|
||||
.limit(1)
|
||||
)
|
||||
api_key_obj = self.db.scalars(stmt).first()
|
||||
if not api_key_obj:
|
||||
raise BusinessException("没有可用的 API Key", BizCode.AGENT_CONFIG_MISSING)
|
||||
|
||||
@@ -364,20 +358,16 @@ class SharedChatService:
|
||||
raise ResourceNotFoundException("模型配置", str(model_config_id))
|
||||
|
||||
# 获取 API Key
|
||||
# stmt = (
|
||||
# select(ModelApiKey).join(
|
||||
# ModelConfig, ModelApiKey.model_configs
|
||||
# )
|
||||
# .where(
|
||||
# ModelConfig.id == model_config_id,
|
||||
# ModelApiKey.is_active.is_(True)
|
||||
# )
|
||||
# .order_by(ModelApiKey.priority.desc())
|
||||
# .limit(1)
|
||||
# )
|
||||
# api_key_obj = self.db.scalars(stmt).first()
|
||||
api_keys = ModelApiKeyRepository.get_by_model_config(self.db, model_config_id)
|
||||
api_key_obj = api_keys[0] if api_keys else None
|
||||
stmt = (
|
||||
select(ModelApiKey)
|
||||
.where(
|
||||
ModelApiKey.model_config_id == model_config_id,
|
||||
ModelApiKey.is_active.is_(True)
|
||||
)
|
||||
.order_by(ModelApiKey.priority.desc())
|
||||
.limit(1)
|
||||
)
|
||||
api_key_obj = self.db.scalars(stmt).first()
|
||||
if not api_key_obj:
|
||||
raise BusinessException("没有可用的 API Key", BizCode.AGENT_CONFIG_MISSING)
|
||||
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
"releaseDate": "2026-1-23",
|
||||
"upgradePosition": "\uD83D\uDC3B 本次更新主要优化使用体验和修复已知问题,让系统更稳定、更好用。",
|
||||
"coreUpgrades": [
|
||||
"1. 工作流更好用了<br>* 界面更清晰,一眼看懂怎么配置<br>* 新增节点输出变量展示,方便其他节点引用<br>* 修复了几个影响体验的bug",
|
||||
"2. 智能体配置更简单<br>* 提示词和变量联动更顺畅<br>* 配置界面重新整理,找功能更方便",
|
||||
"3. 记忆系统更稳定<br>* 优化了情绪记忆和隐性记忆的缓存更新<br>* 修复了记忆配置页面的报错问题<br>* 现在能自动识别用户和AI的身份了",
|
||||
"4. 知识库体验提升<br>* 修复了文档解析异常的问题<br>* 上传文档时能看到处理进度了<br>* 取消了操作也不会报错了",
|
||||
"5. 系统整体更可靠<br>* 修复了新用户访问跳转问题<br>* 流式接口更稳定,长对话不断线<br>* 调整了菜单顺序,操作更顺手",
|
||||
"<br>",
|
||||
"1. 工作流更好用了\n* 界面更清晰,一眼看懂怎么配置\n* 新增节点输出变量展示,方便其他节点引用\n* 修复了几个影响体验的bug",
|
||||
"2. 智能体配置更简单\n* 提示词和变量联动更顺畅\n* 配置界面重新整理,找功能更方便",
|
||||
"3. 记忆系统更稳定\n* 优化了情绪记忆和隐性记忆的缓存更新\n* 修复了记忆配置页面的报错问题\n* 现在能自动识别用户和AI的身份了",
|
||||
"4. 知识库体验提升\n* 修复了文档解析异常的问题\n* 上传文档时能看到处理进度了\n* 取消了操作也不会报错了",
|
||||
"5. 系统整体更可靠\n* 修复了新用户访问跳转问题\n* 流式接口更稳定,长对话不断线\n* 调整了菜单顺序,操作更顺手\n",
|
||||
"这次更新虽然不大,但让记忆熊的基础更扎实、体验更流畅。我们继续努力,让AI记忆更好用!",
|
||||
"记忆熊,记得更牢,用得更好。\uD83D\uDC3B✨"
|
||||
]
|
||||
@@ -20,13 +19,12 @@
|
||||
"releaseDate": "2026-1-23",
|
||||
"upgradePosition": "\uD83D\uDC3B This update focuses on improving usability and fixing known issues, making the system more stable and easier to use overall.",
|
||||
"coreUpgrades": [
|
||||
"1. Improved Workflow Experience<br>* Cleaner, more intuitive UI for easier configuration at a glance<br>* Added visibility of node output variables, making them easier to reference in downstream nodes<br>* Fixed several usability-related bugs that affected the workflow experience",
|
||||
"2. Simpler Agent Configuration<br>* Smoother linkage between prompts and variables<br>* Reorganized configuration layout for easier navigation and better clarity",
|
||||
"3. More Stable Memory System<br>* Optimized cache refresh for emotional memory and implicit memory<br>* Fixed error issues on the memory configuration page<br>* The system can now automatically distinguish between user and AI roles",
|
||||
"4. Enhanced Knowledge Base Experience<br>* Fixed issues with document parsing failures<br>* Upload progress is now displayed during document processing<br>* Canceling an upload no longer triggers errors",
|
||||
"5. Overall System Reliability Improvements<br>* Fixed redirect issues affecting new users<br>* Improved stability of streaming APIs to prevent interruptions during long conversations<br>* Adjusted menu ordering for a smoother and more intuitive workflow",
|
||||
"<br>",
|
||||
"Although this is a relatively small update, it strengthens MemoryBear’s foundation and delivers a noticeably smoother experience. We’ll keep refining the system to make AI memory more powerful and easier to use.",
|
||||
"1. Improved Workflow Experience\nCleaner, more intuitive UI for easier configuration at a glance\nAdded visibility of node output variables, making them easier to reference in downstream nodes\nFixed several usability-related bugs that affected the workflow experience",
|
||||
"2. Simpler Agent Configuration\nSmoother linkage between prompts and variables\nReorganized configuration layout for easier navigation and better clarity",
|
||||
"3. More Stable Memory System\nOptimized cache refresh for emotional memory and implicit memory\nFixed error issues on the memory configuration page\nThe system can now automatically distinguish between user and AI roles",
|
||||
"4. Enhanced Knowledge Base Experience\nFixed issues with document parsing failures\nUpload progress is now displayed during document processing\nCanceling an upload no longer triggers errors",
|
||||
"5. Overall System Reliability Improvements\nFixed redirect issues affecting new users\nImproved stability of streaming APIs to prevent interruptions during long conversations\nAdjusted menu ordering for a smoother and more intuitive workflow\n",
|
||||
"Although this is a relatively small update, it strengthens MemoryBear’s foundation and delivers a noticeably smoother experience.\nWe’ll keep refining the system to make AI memory more powerful and easier to use.",
|
||||
"MemoryBear — remember better, work smarter. \uD83D\uDC3B✨"
|
||||
]
|
||||
}
|
||||
@@ -37,10 +35,10 @@
|
||||
"releaseDate": "2026-1-16",
|
||||
"upgradePosition": "本次为架构升级,核心目标是把\"被动存储\"升级为\"主动认知\",让系统具备情绪感知、情景理解与类人记忆机制,为后续多智能体协作与专业场景落地奠定底座。",
|
||||
"coreUpgrades": [
|
||||
"1. 记忆详情:拟人记忆——情绪引擎、情景记忆、短期记忆、工作记忆、感知记忆、显性记忆、隐性记忆,并配套类脑遗忘机制,实现从感知→情绪→情景→长期沉淀的完整人类记忆闭环",
|
||||
"2. 可视化工作流:拖拽式节点编排(LLM、知识库、逻辑、工具),业务落地周期由天缩至小时。",
|
||||
"3. 多模态知识处理:PDF、PPT、MP3、MP4 一键解析,时间感知检索准确率 94.3%,问答对数据即插即用。",
|
||||
"4. Agent集群内置\"记忆-知识-工具-审核\"四类角色模板,用户一键生成;主控Agent把复杂任务拆为子任务并行分发,再靠情景记忆统一消解冲突、校验一致性,输出完整报告。"
|
||||
"记忆详情:拟人记忆——情绪引擎、情景记忆、短期记忆、工作记忆、感知记忆、显性记忆、隐性记忆,并配套类脑遗忘机制,实现从感知→情绪→情景→长期沉淀的完整人类记忆闭环",
|
||||
"可视化工作流:拖拽式节点编排(LLM、知识库、逻辑、工具),业务落地周期由天缩至小时。",
|
||||
"多模态知识处理:PDF、PPT、MP3、MP4 一键解析,时间感知检索准确率 94.3%,问答对数据即插即用。",
|
||||
"Agent集群内置\"记忆-知识-工具-审核\"四类角色模板,用户一键生成;主控Agent把复杂任务拆为子任务并行分发,再靠情景记忆统一消解冲突、校验一致性,输出完整报告。"
|
||||
]
|
||||
},
|
||||
"introduction_en": {
|
||||
@@ -48,10 +46,10 @@
|
||||
"releaseDate": "2026-1-16",
|
||||
"upgradePosition": "This release marks a foundational upgrade to the system’s cognitive architecture. The core objective is to evolve the platform from passive information storage into active cognitive intelligence—enabling emotional awareness, situational understanding, and human-like memory mechanisms. This upgrade lays the groundwork for future multi-agent collaboration and domain-specific, production-grade AI applications.",
|
||||
"coreUpgrades": [
|
||||
"1. Human-Like Memory Architecture: A comprehensive, human-inspired memory system is introduced, encompassing emotional processing, situational memory, short-term and working memory, perceptual memory, as well as explicit and implicit memory. Combined with brain-inspired forgetting mechanisms, the system now supports a complete cognitive loop—from perception → emotion → context → long-term consolidation, closely mirroring human memory formation.",
|
||||
"2. Visual Workflow Orchestration: A fully visual, drag-and-drop workflow enables modular composition of LLMs, knowledge bases, logic, and tools. This dramatically reduces the time required to move from experimentation to production—from days to hours.",
|
||||
"3. Multimodal Knowledge Processing: The system now supports one-click parsing and ingestion of PDF, PPT, MP3, and MP4 content. With time-aware retrieval accuracy reaching 94.3%, structured Q&A data becomes instantly usable for downstream reasoning and generation.",
|
||||
"4. Built-in Agent Clusters: Predefined role templates across four categories—Memory, Knowledge, Tools, and Review—can be generated with a single click. A Coordinator Agent decomposes complex tasks into parallel subtasks, while situational memory is used to resolve conflicts, validate consistency, and synthesize outputs into a coherent, end-to-end report."
|
||||
"Human-Like Memory Architecture: A comprehensive, human-inspired memory system is introduced, encompassing emotional processing, situational memory, short-term and working memory, perceptual memory, as well as explicit and implicit memory. Combined with brain-inspired forgetting mechanisms, the system now supports a complete cognitive loop—from perception → emotion → context → long-term consolidation, closely mirroring human memory formation.",
|
||||
"Visual Workflow Orchestration: A fully visual, drag-and-drop workflow enables modular composition of LLMs, knowledge bases, logic, and tools. This dramatically reduces the time required to move from experimentation to production—from days to hours.",
|
||||
"Multimodal Knowledge Processing: The system now supports one-click parsing and ingestion of PDF, PPT, MP3, and MP4 content. With time-aware retrieval accuracy reaching 94.3%, structured Q&A data becomes instantly usable for downstream reasoning and generation.",
|
||||
"Built-in Agent Clusters: Predefined role templates across four categories—Memory, Knowledge, Tools, and Review—can be generated with a single click. A Coordinator Agent decomposes complex tasks into parallel subtasks, while situational memory is used to resolve conflicts, validate consistency, and synthesize outputs into a coherent, end-to-end report."
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -61,17 +59,16 @@
|
||||
"releaseDate": "2025-12-01",
|
||||
"upgradePosition": "这是一款专注于管理和利用AI记忆的工具,支持RAG和知识图谱两种主流存储方式,旨在为AI应用提供持久化、结构化的\"记忆\"能力。",
|
||||
"coreUpgrades": [
|
||||
"1. 记忆空间:用户可以创建独立的空间来隔离不同记忆,并灵活选择存储方式。",
|
||||
"2. 记忆配置:简化了配置流程,内置自动提取关键信息的\"记忆萃取\"和管理生命周期的\"遗忘\"引擎。",
|
||||
"3. 知识检索:提供语义、分词和混合三种检索模式,并支持多种参数微调和结果重排序,以提升召回效果。",
|
||||
"4. 全局管理:支持统一设置默认检索参数,并可一键应用到所有知识库。",
|
||||
"5. 测试与调试:内置\"召回测试\"功能,方便用户实时验证检索效果并调整参数,支持通过分享码与他人协作。",
|
||||
"6. 记忆洞察:可查看详细的对话记录、用户画像和分析报告,帮助理解AI的\"记忆\"内容。",
|
||||
"7. 集成与管理:提供API Key用于系统集成,并包含基本的用户管理功能。",
|
||||
"8. 界面与体验:采用现代化的卡片式布局和渐变色设计,注重交互的流畅性和视觉美感。",
|
||||
"9. 起步与使用:文档中提供了清晰的基础使用流程,引导用户从创建空间、配置记忆到测试检索快速上手。",
|
||||
"10. 版本说明与限制: 记忆熊 v0.1.0 版本\"初心\"囊括智能记忆管理的核心思路和基础能力,为后续开发奠定了基础。",
|
||||
"<br>",
|
||||
"记忆空间:用户可以创建独立的空间来隔离不同记忆,并灵活选择存储方式。",
|
||||
"记忆配置:简化了配置流程,内置自动提取关键信息的\"记忆萃取\"和管理生命周期的\"遗忘\"引擎。",
|
||||
"知识检索:提供语义、分词和混合三种检索模式,并支持多种参数微调和结果重排序,以提升召回效果。",
|
||||
"全局管理:支持统一设置默认检索参数,并可一键应用到所有知识库。",
|
||||
"测试与调试:内置\"召回测试\"功能,方便用户实时验证检索效果并调整参数,支持通过分享码与他人协作。",
|
||||
"记忆洞察:可查看详细的对话记录、用户画像和分析报告,帮助理解AI的\"记忆\"内容。",
|
||||
"集成与管理:提供API Key用于系统集成,并包含基本的用户管理功能。",
|
||||
"界面与体验:采用现代化的卡片式布局和渐变色设计,注重交互的流畅性和视觉美感。",
|
||||
"起步与使用:文档中提供了清晰的基础使用流程,引导用户从创建空间、配置记忆到测试检索快速上手。",
|
||||
"版本说明与限制: 记忆熊 v0.1.0 版本\"初心\"囊括智能记忆管理的核心思路和基础能力,为后续开发奠定了基础。",
|
||||
"文档资源:用户手册、API文档、FAQ",
|
||||
"问题反馈:GitHub Issues、邮件支持",
|
||||
"致谢:感谢所有参与测试和提供反馈的用户!"
|
||||
@@ -82,17 +79,16 @@
|
||||
"releaseDate": "2025-12-01",
|
||||
"upgradePosition": "A tool focused on managing and utilizing AI memory, supporting both RAG and knowledge graph storage methods, aiming to provide persistent and structured 'memory' capabilities for AI applications.",
|
||||
"coreUpgrades": [
|
||||
"1. Memory Space: Users can create independent spaces to isolate different memories and flexibly choose storage methods.",
|
||||
"2. Memory Configuration: Simplified configuration process with built-in 'memory extraction' for automatic key information extraction and 'forgetting' engine for lifecycle management.",
|
||||
"3. Knowledge Retrieval: Provides semantic, tokenization, and hybrid retrieval modes with various parameter tuning and result reranking to improve recall.",
|
||||
"4. Global Management: Supports unified default retrieval parameter settings with one-click application to all knowledge bases.",
|
||||
"5. Testing & Debugging: Built-in 'recall testing' for real-time verification of retrieval effects and parameter adjustment, with sharing code support for collaboration.",
|
||||
"6. Memory Insights: View detailed conversation records, user profiles, and analysis reports to understand AI 'memory' content.",
|
||||
"7. Integration & Management: Provides API Key for system integration with basic user management features.",
|
||||
"8. Interface & Experience: Modern card-based layout with gradient design, focusing on interaction fluidity and visual aesthetics.",
|
||||
"9. Getting Started: Documentation provides clear basic usage flow, guiding users from creating spaces, configuring memory to testing retrieval.",
|
||||
"10. Version Notes: MemoryBear v0.1.0 'Original Intent' encompasses core concepts and basic capabilities of intelligent memory management, laying foundation for future development.",
|
||||
"<br>",
|
||||
"Memory Space: Users can create independent spaces to isolate different memories and flexibly choose storage methods.",
|
||||
"Memory Configuration: Simplified configuration process with built-in 'memory extraction' for automatic key information extraction and 'forgetting' engine for lifecycle management.",
|
||||
"Knowledge Retrieval: Provides semantic, tokenization, and hybrid retrieval modes with various parameter tuning and result reranking to improve recall.",
|
||||
"Global Management: Supports unified default retrieval parameter settings with one-click application to all knowledge bases.",
|
||||
"Testing & Debugging: Built-in 'recall testing' for real-time verification of retrieval effects and parameter adjustment, with sharing code support for collaboration.",
|
||||
"Memory Insights: View detailed conversation records, user profiles, and analysis reports to understand AI 'memory' content.",
|
||||
"Integration & Management: Provides API Key for system integration with basic user management features.",
|
||||
"Interface & Experience: Modern card-based layout with gradient design, focusing on interaction fluidity and visual aesthetics.",
|
||||
"Getting Started: Documentation provides clear basic usage flow, guiding users from creating spaces, configuring memory to testing retrieval.",
|
||||
"Version Notes: MemoryBear v0.1.0 'Original Intent' encompasses core concepts and basic capabilities of intelligent memory management, laying foundation for future development.",
|
||||
"Documentation: User Manual, API Documentation, FAQ",
|
||||
"Feedback: GitHub Issues, Email Support",
|
||||
"Acknowledgments: Thanks to all users who participated in testing and provided feedback!"
|
||||
|
||||
Reference in New Issue
Block a user