PMFlow/backend/app/api/llm_engines.py

213 lines
6.8 KiB
Python

"""LLM 引擎配置 API。"""
from __future__ import annotations
from typing import Literal, Optional
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from ..core.response import success_response
from ..core.database import get_db
from ..dependencies.auth import get_current_user
from ..dependencies.tenant import get_tenant_id
from ..models import User
from ..services.auth import has_any_role
from ..services.llm_service import llm_service
router = APIRouter(prefix="/ai-steward/llm-engines", tags=["AI Steward"])
class LLMEngineCreateRequest(BaseModel):
name: str = Field(..., min_length=1, max_length=80)
provider: Literal["deepseek", "glm", "openai", "ollama"]
api_key: Optional[str] = Field(default="", max_length=500)
model: Optional[str] = Field(default=None, max_length=120)
base_url: Optional[str] = Field(default=None, max_length=255)
set_active: bool = True
class LLMEngineUpdateRequest(BaseModel):
name: Optional[str] = Field(default=None, min_length=1, max_length=80)
provider: Optional[Literal["deepseek", "glm", "openai", "ollama"]] = None
api_key: Optional[str] = Field(default=None, max_length=500)
model: Optional[str] = Field(default=None, max_length=120)
base_url: Optional[str] = Field(default=None, max_length=255)
class LLMEngineTestRequest(BaseModel):
prompt: Optional[str] = Field(default=None, max_length=1000)
class LLMEngineSettingsUpdateRequest(BaseModel):
enable_local_kb_qa: bool = False
def _require_manager(user: User) -> None:
if not has_any_role(str(user.role or ""), ["BOSS", "ADMIN"]):
raise HTTPException(status_code=403, detail="仅管理员可操作 LLM 引擎配置")
@router.get("")
async def list_llm_engines(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_ = current_user
data = await llm_service.list_engines(db, tenant_id=tenant_id)
return success_response(data)
@router.post("")
async def create_llm_engine(
body: LLMEngineCreateRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_require_manager(current_user)
try:
engine = await llm_service.create_engine(
db,
tenant_id=tenant_id,
name=body.name,
provider=body.provider,
api_key=body.api_key,
model=body.model,
base_url=body.base_url,
set_active=body.set_active,
)
all_items = await llm_service.list_engines(db, tenant_id=tenant_id)
return success_response({"engine": engine, **all_items})
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.put("/settings")
async def update_llm_engine_settings(
body: LLMEngineSettingsUpdateRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_require_manager(current_user)
try:
settings = await llm_service.update_engine_settings(
db,
tenant_id=tenant_id,
enable_local_kb_qa=body.enable_local_kb_qa,
)
all_items = await llm_service.list_engines(db, tenant_id=tenant_id)
return success_response({"settings": settings, **all_items})
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.put("/{engine_id}")
async def update_llm_engine(
engine_id: str,
body: LLMEngineUpdateRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_require_manager(current_user)
try:
engine = await llm_service.update_engine(
db,
tenant_id=tenant_id,
engine_id=engine_id,
name=body.name,
provider=body.provider,
api_key=body.api_key,
model=body.model,
base_url=body.base_url,
)
all_items = await llm_service.list_engines(db, tenant_id=tenant_id)
return success_response({"engine": engine, **all_items})
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.post("/{engine_id}/activate")
async def activate_llm_engine(
engine_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_require_manager(current_user)
try:
result = await llm_service.activate_engine(
db,
tenant_id=tenant_id,
engine_id=engine_id,
)
all_items = await llm_service.list_engines(db, tenant_id=tenant_id)
return success_response({**result, **all_items})
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.post("/test-active")
async def test_active_llm_engine(
body: LLMEngineTestRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_require_manager(current_user)
try:
data = await llm_service.test_engine_connection(
db,
tenant_id=tenant_id,
engine_id=None,
prompt=body.prompt,
)
return success_response(data)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.post("/{engine_id}/test")
async def test_llm_engine(
engine_id: str,
body: LLMEngineTestRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_require_manager(current_user)
try:
data = await llm_service.test_engine_connection(
db,
tenant_id=tenant_id,
engine_id=engine_id,
prompt=body.prompt,
)
return success_response(data)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@router.delete("/{engine_id}")
async def delete_llm_engine(
engine_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_require_manager(current_user)
try:
result = await llm_service.delete_engine(
db,
tenant_id=tenant_id,
engine_id=engine_id,
)
all_items = await llm_service.list_engines(db, tenant_id=tenant_id)
return success_response({**result, **all_items})
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc