from typing import Optional, List from pydantic import BaseModel, model_validator from fastapi import APIRouter, HTTPException, status from app.api.deps import SessionDep from app.models import EntityPublic, RelationshipPublic, FeedbackCreate from app.knowledge_graph import editor router = APIRouter() @router.get("/graph/entities/search", response_model=List[EntityPublic]) def search_similar_entities(session: SessionDep, query: str, top_k: int = 10): return editor.search_similar_entities(session, query, top_k) class SynopsisEntityCreate(BaseModel): name: str description: str topic: str meta: dict entities: List[int] @model_validator(mode="after") def validate_entities(self): if len(self.entities) == 0: raise ValueError("Entities list should not be empty") return self @router.post("/graph/entities/synopsis", response_model=EntityPublic) def create_synopsis_entity(session: SessionDep, request: SynopsisEntityCreate): return editor.create_synopsis_entity( session, request.name, request.description, request.topic, request.meta, request.entities, ) @router.get("/graph/entities/{entity_id}", response_model=EntityPublic) def get_entity(session: SessionDep, entity_id: int): entity = editor.get_entity(session, entity_id) if entity is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Entity not found", ) return entity @router.get("/graph/entities/{entity_id}/subgraph") def get_entity_subgraph(session: SessionDep, entity_id: int) -> dict: entity = editor.get_entity(session, entity_id) if entity is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Entity not found", ) relationships, entities = editor.get_entity_subgraph(session, entity) return { "relationships": relationships, "entities": entities, } @router.get("/graph/chunks/{chunk_uri:path}/subgraph") def get_chunk_subgraph(session: SessionDep, chunk_uri: str) -> dict: chunk = editor.get_chunk(session, chunk_uri) if chunk is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Chunk not found", ) relationships, entities = editor.get_chunk_subgraph(session, chunk) return { "relationships": relationships, "entities": entities, } class EntityUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None meta: Optional[dict] = None @router.put("/graph/entities/{entity_id}", response_model=EntityPublic) def update_entity(session: SessionDep, entity_id: int, entity_update: EntityUpdate): old_entity = editor.get_entity(session, entity_id) if old_entity is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Entity not found", ) entity = editor.update_entity(session, old_entity, entity_update.model_dump()) return entity @router.get("/graph/relationships/{relationship_id}", response_model=RelationshipPublic) def get_relationship(session: SessionDep, relationship_id: int): relationship = editor.get_relationship(session, relationship_id) if relationship is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Relationship not found", ) return relationship class RelationUpdate(BaseModel): description: Optional[str] = None meta: Optional[dict] = None weight: Optional[int] = None @router.put("/graph/relationships/{relationship_id}", response_model=RelationshipPublic) def update_relationship( session: SessionDep, relationship_id: int, relationship_update: RelationUpdate ): old_relationship = editor.get_relationship(session, relationship_id) if old_relationship is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Relationship not found", ) relationship = editor.update_relationship( session, old_relationship, relationship_update.model_dump() ) return relationship @router.post("/graph/feedback") def feedback(session: SessionDep, feedback: FeedbackCreate): editor.save_feedback(session, feedback) return {"message": "Feedback saved"}