"""知识库 CRUD。前端助手编辑页的"知识库"下拉对接这里。 KB 自身引用一个 Embedding 模型资源。被助手引用时禁止删除 (DB 层 ON DELETE RESTRICT),这里把外键冲突翻译成 409。 """ import uuid from db.models import KnowledgeBase, ModelResource from db.session import get_session from fastapi import APIRouter, Depends, HTTPException from schemas import KnowledgeBaseOut, KnowledgeBaseUpsert from sqlalchemy import select from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession router = APIRouter(prefix="/api/knowledge-bases", tags=["knowledge-bases"]) async def _validate_embedding_resource( session: AsyncSession, resource_id: str | None ) -> None: if not resource_id: return resource = await session.get(ModelResource, resource_id) if not resource or resource.capability != "Embedding": raise HTTPException(400, "知识库必须引用 Embedding 模型资源") def _to_out(kb: KnowledgeBase) -> KnowledgeBaseOut: return KnowledgeBaseOut( id=kb.id, name=kb.name, description=kb.description, embedding_model_resource_id=kb.embedding_model_resource_id, status=kb.status, updated_at=kb.updated_at.isoformat() if kb.updated_at else None, ) @router.get("", response_model=list[KnowledgeBaseOut]) async def list_knowledge_bases(session: AsyncSession = Depends(get_session)): rows = ( await session.execute(select(KnowledgeBase).order_by(KnowledgeBase.name)) ).scalars().all() return [_to_out(kb) for kb in rows] @router.post("", response_model=KnowledgeBaseOut) async def create_knowledge_base( body: KnowledgeBaseUpsert, session: AsyncSession = Depends(get_session) ): await _validate_embedding_resource(session, body.embedding_model_resource_id) kb = KnowledgeBase(id=f"kb_{uuid.uuid4().hex[:12]}", **body.model_dump()) session.add(kb) await session.commit() await session.refresh(kb) return _to_out(kb) @router.get("/{kb_id}", response_model=KnowledgeBaseOut) async def get_knowledge_base( kb_id: str, session: AsyncSession = Depends(get_session) ): kb = await session.get(KnowledgeBase, kb_id) if not kb: raise HTTPException(404, "知识库不存在") return _to_out(kb) @router.put("/{kb_id}", response_model=KnowledgeBaseOut) async def update_knowledge_base( kb_id: str, body: KnowledgeBaseUpsert, session: AsyncSession = Depends(get_session), ): kb = await session.get(KnowledgeBase, kb_id) if not kb: raise HTTPException(404, "知识库不存在") await _validate_embedding_resource(session, body.embedding_model_resource_id) for k, v in body.model_dump().items(): setattr(kb, k, v) await session.commit() await session.refresh(kb) return _to_out(kb) @router.delete("/{kb_id}") async def delete_knowledge_base( kb_id: str, session: AsyncSession = Depends(get_session) ): kb = await session.get(KnowledgeBase, kb_id) if not kb: raise HTTPException(404, "知识库不存在") try: await session.delete(kb) await session.commit() except IntegrityError: # 被助手引用(ON DELETE RESTRICT):先解绑再删 await session.rollback() raise HTTPException(409, "知识库正被助手引用,无法删除") return {"ok": True}