"""LLM configuration management API endpoints.""" from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from sqlalchemy import or_ from ...db.database import get_db from ...models.user import User from ...models.llm_config import LLMConfig from ...core.simple_permissions import require_super_admin, require_authenticated_user from ...services.auth import AuthService from ...utils.logger import get_logger from ...schemas.llm_config import ( LLMConfigCreate, LLMConfigUpdate, LLMConfigResponse, LLMConfigTest ) from th_agenter.services.document_processor import get_document_processor logger = get_logger(__name__) router = APIRouter(prefix="/llm-configs", tags=["llm-configs"]) @router.get("/", response_model=List[LLMConfigResponse]) async def get_llm_configs( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), search: Optional[str] = Query(None), provider: Optional[str] = Query(None), is_active: Optional[bool] = Query(None), is_embedding: Optional[bool] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(require_authenticated_user) ): """获取大模型配置列表.""" try: query = db.query(LLMConfig) # 搜索 if search: query = query.filter( or_( LLMConfig.name.ilike(f"%{search}%"), LLMConfig.model_name.ilike(f"%{search}%"), LLMConfig.description.ilike(f"%{search}%") ) ) # 服务商筛选 if provider: query = query.filter(LLMConfig.provider == provider) # 状态筛选 if is_active is not None: query = query.filter(LLMConfig.is_active == is_active) # 模型类型筛选 if is_embedding is not None: query = query.filter(LLMConfig.is_embedding == is_embedding) # 排序 query = query.order_by(LLMConfig.name) # 分页 configs = query.offset(skip).limit(limit).all() return [config.to_dict(include_sensitive=True) for config in configs] except Exception as e: logger.error(f"Error getting LLM configs: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="获取大模型配置列表失败" ) @router.get("/providers") async def get_llm_providers( db: Session = Depends(get_db), current_user: User = Depends(require_authenticated_user) ): """获取支持的大模型服务商列表.""" try: providers = db.query(LLMConfig.provider).distinct().all() return [provider[0] for provider in providers if provider[0]] except Exception as e: logger.error(f"Error getting LLM providers: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="获取服务商列表失败" ) @router.get("/active", response_model=List[LLMConfigResponse]) async def get_active_llm_configs( is_embedding: Optional[bool] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(require_authenticated_user) ): """获取所有激活的大模型配置.""" try: query = db.query(LLMConfig).filter(LLMConfig.is_active == True) if is_embedding is not None: query = query.filter(LLMConfig.is_embedding == is_embedding) configs = query.order_by(LLMConfig.created_at).all() return [config.to_dict(include_sensitive=True) for config in configs] except Exception as e: logger.error(f"Error getting active LLM configs: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="获取激活配置列表失败" ) @router.get("/default", response_model=LLMConfigResponse) async def get_default_llm_config( is_embedding: bool = Query(False, description="是否获取嵌入模型默认配置"), db: Session = Depends(get_db), current_user: User = Depends(require_authenticated_user) ): """获取默认大模型配置.""" try: config = db.query(LLMConfig).filter( LLMConfig.is_default == True, LLMConfig.is_embedding == is_embedding, LLMConfig.is_active == True ).first() if not config: model_type = "嵌入模型" if is_embedding else "对话模型" raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"未找到默认{model_type}配置" ) return config.to_dict(include_sensitive=True) except HTTPException: raise except Exception as e: logger.error(f"Error getting default LLM config: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="获取默认配置失败" ) @router.get("/{config_id}", response_model=LLMConfigResponse) async def get_llm_config( config_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_authenticated_user) ): """获取大模型配置详情.""" try: config = db.query(LLMConfig).filter(LLMConfig.id == config_id).first() if not config: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="大模型配置不存在" ) return config.to_dict(include_sensitive=True) except HTTPException: raise except Exception as e: logger.error(f"Error getting LLM config {config_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="获取大模型配置详情失败" ) @router.post("/", response_model=LLMConfigResponse, status_code=status.HTTP_201_CREATED) async def create_llm_config( config_data: LLMConfigCreate, db: Session = Depends(get_db), current_user: User = Depends(require_super_admin) ): """创建大模型配置.""" try: # 检查配置名称是否已存在 existing_config = db.query(LLMConfig).filter( LLMConfig.name == config_data.name ).first() if existing_config: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="配置名称已存在" ) # 创建临时配置对象进行验证 temp_config = LLMConfig( name=config_data.name, provider=config_data.provider, model_name=config_data.model_name, api_key=config_data.api_key, base_url=config_data.base_url, max_tokens=config_data.max_tokens, temperature=config_data.temperature, top_p=config_data.top_p, frequency_penalty=config_data.frequency_penalty, presence_penalty=config_data.presence_penalty, description=config_data.description, is_active=config_data.is_active, is_default=config_data.is_default, is_embedding=config_data.is_embedding, extra_config=config_data.extra_config or {} ) # 验证配置 validation_result = temp_config.validate_config() if not validation_result['valid']: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=validation_result['error'] ) # 如果设为默认,取消同类型的其他默认配置 if config_data.is_default: db.query(LLMConfig).filter( LLMConfig.is_embedding == config_data.is_embedding ).update({"is_default": False}) # 创建配置 config = LLMConfig( name=config_data.name, provider=config_data.provider, model_name=config_data.model_name, api_key=config_data.api_key, base_url=config_data.base_url, max_tokens=config_data.max_tokens, temperature=config_data.temperature, top_p=config_data.top_p, frequency_penalty=config_data.frequency_penalty, presence_penalty=config_data.presence_penalty, description=config_data.description, is_active=config_data.is_active, is_default=config_data.is_default, is_embedding=config_data.is_embedding, extra_config=config_data.extra_config or {} ) config.set_audit_fields(current_user.id) db.add(config) db.commit() db.refresh(config) logger.info(f"LLM config created: {config.name} by user {current_user.username}") return config.to_dict() except HTTPException: raise except Exception as e: db.rollback() logger.error(f"Error creating LLM config: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="创建大模型配置失败" ) @router.put("/{config_id}", response_model=LLMConfigResponse) async def update_llm_config( config_id: int, config_data: LLMConfigUpdate, db: Session = Depends(get_db), current_user: User = Depends(require_super_admin) ): """更新大模型配置.""" try: config = db.query(LLMConfig).filter(LLMConfig.id == config_id).first() if not config: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="大模型配置不存在" ) # 检查配置名称是否已存在(排除自己) if config_data.name and config_data.name != config.name: existing_config = db.query(LLMConfig).filter( LLMConfig.name == config_data.name, LLMConfig.id != config_id ).first() if existing_config: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="配置名称已存在" ) # 如果设为默认,取消同类型的其他默认配置 if config_data.is_default is True: # 获取当前配置的embedding类型,如果更新中包含is_embedding则使用新值 is_embedding = config_data.is_embedding if config_data.is_embedding is not None else config.is_embedding db.query(LLMConfig).filter( LLMConfig.is_embedding == is_embedding, LLMConfig.id != config_id ).update({"is_default": False}) # 更新字段 update_data = config_data.dict(exclude_unset=True) for field, value in update_data.items(): setattr(config, field, value) config.set_audit_fields(current_user.id, is_update=True) db.commit() db.refresh(config) logger.info(f"LLM config updated: {config.name} by user {current_user.username}") return config.to_dict() except HTTPException: raise except Exception as e: db.rollback() logger.error(f"Error updating LLM config {config_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="更新大模型配置失败" ) @router.delete("/{config_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_llm_config( config_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_super_admin) ): """删除大模型配置.""" try: config = db.query(LLMConfig).filter(LLMConfig.id == config_id).first() if not config: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="大模型配置不存在" ) # TODO: 检查是否有对话或其他功能正在使用该配置 # 这里可以添加相关的检查逻辑 db.delete(config) db.commit() logger.info(f"LLM config deleted: {config.name} by user {current_user.username}") except HTTPException: raise except Exception as e: db.rollback() logger.error(f"Error deleting LLM config {config_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="删除大模型配置失败" ) @router.post("/{config_id}/test") async def test_llm_config( config_id: int, test_data: LLMConfigTest, db: Session = Depends(get_db), current_user: User = Depends(require_super_admin) ): """测试大模型配置.""" try: config = db.query(LLMConfig).filter(LLMConfig.id == config_id).first() if not config: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="大模型配置不存在" ) # 验证配置 validation_result = config.validate_config() if not validation_result["valid"]: return { "success": False, "message": f"配置验证失败: {validation_result['error']}", "details": validation_result } # 尝试创建客户端并发送测试请求 try: # 这里应该根据不同的服务商创建相应的客户端 # 由于具体的客户端实现可能因服务商而异,这里提供一个通用的框架 test_message = test_data.message or "Hello, this is a test message." # TODO: 实现具体的测试逻辑 # 例如: # client = config.get_client() # response = client.chat.completions.create( # model=config.model_name, # messages=[{"role": "user", "content": test_message}], # max_tokens=100 # ) # 模拟测试成功 logger.info(f"LLM config test: {config.name} by user {current_user.username}") return { "success": True, "message": "配置测试成功", "test_message": test_message, "response": "这是一个模拟的测试响应。实际实现中,这里会是大模型的真实响应。", "latency_ms": 150, # 模拟延迟 "config_info": config.get_client_config() } except Exception as test_error: logger.error(f"LLM config test failed: {config.name}, error: {str(test_error)}") return { "success": False, "message": f"配置测试失败: {str(test_error)}", "test_message": test_message, "config_info": config.get_client_config() } except HTTPException: raise except Exception as e: logger.error(f"Error testing LLM config {config_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="测试大模型配置失败" ) @router.post("/{config_id}/toggle-status") async def toggle_llm_config_status( config_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_super_admin) ): """切换大模型配置状态.""" try: config = db.query(LLMConfig).filter(LLMConfig.id == config_id).first() if not config: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="大模型配置不存在" ) # 切换状态 config.is_active = not config.is_active config.set_audit_fields(current_user.id, is_update=True) db.commit() db.refresh(config) status_text = "激活" if config.is_active else "禁用" logger.info(f"LLM config status toggled: {config.name} {status_text} by user {current_user.username}") return { "message": f"配置已{status_text}", "is_active": config.is_active } except HTTPException: raise except Exception as e: db.rollback() logger.error(f"Error toggling LLM config status {config_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="切换配置状态失败" ) @router.post("/{config_id}/set-default") async def set_default_llm_config( config_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_super_admin) ): """设置默认大模型配置.""" try: config = db.query(LLMConfig).filter(LLMConfig.id == config_id).first() if not config: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="大模型配置不存在" ) # 检查配置是否激活 if not config.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="只能将激活的配置设为默认" ) # 取消同类型的其他默认配置 db.query(LLMConfig).filter( LLMConfig.is_embedding == config.is_embedding, LLMConfig.id != config_id ).update({"is_default": False}) # 设置当前配置为默认 config.is_default = True config.set_audit_fields(current_user.id, is_update=True) db.commit() db.refresh(config) model_type = "嵌入模型" if config.is_embedding else "对话模型" logger.info(f"Default LLM config set: {config.name} ({model_type}) by user {current_user.username}") # 更新文档处理器默认embedding get_document_processor()._init_embeddings() return { "message": f"已将 {config.name} 设为默认{model_type}配置", "is_default": config.is_default } except HTTPException: raise except Exception as e: db.rollback() logger.error(f"Error setting default LLM config {config_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="设置默认配置失败" )