Files

80 lines
2.5 KiB
Python
Raw Permalink Normal View History

2026-05-19 20:31:52 +09:00
"""Phase 5 projection and GraphRAG search routes."""
from __future__ import annotations
from typing import Annotated, Any
from fastapi import APIRouter, Body, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from ont_platform.api.db_deps import get_db
from ont_platform.config import load_settings
from ont_platform.core.graph.cypher_guard import ReadOnlyCypherGuard, UnsafeCypherError
from ont_platform.core.graph.search import CandidateGraphSearchService
from ont_platform.core.projection.rdf_to_neo4j import RDFToNeo4jProjector
router = APIRouter(prefix="/api/v1/graph", tags=["graph"])
class ProjectionPreviewRequest(BaseModel):
project_id: str = "default"
triples: list[tuple[str, str, str]]
provenance: dict[str, Any] = Field(default_factory=dict)
class ReadOnlyCypherRequest(BaseModel):
query: str
limit: int | None = None
@router.post("/projection/preview")
async def preview_projection(request: Annotated[ProjectionPreviewRequest, Body()]) -> dict:
projector = RDFToNeo4jProjector(project_id=request.project_id)
result = await projector.preview_projection(
request.triples,
provenance=request.provenance,
)
return {"status": "success", "projection": result.to_dict()}
@router.post("/cypher/read")
def sanitize_read_only_cypher(request: Annotated[ReadOnlyCypherRequest, Body()]) -> dict:
settings = load_settings()
guard = ReadOnlyCypherGuard(max_limit=settings.text2cypher_result_limit)
try:
sanitized = guard.sanitize(request.query, limit=request.limit)
except UnsafeCypherError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {
"status": "success",
"read_only": True,
"query": sanitized.query,
"limit": sanitized.limit,
}
@router.get("/search")
def search_graph(
db: Annotated[Session, Depends(get_db)],
q: Annotated[str, Query(min_length=1)],
project_id: Annotated[str, Query()] = "default",
limit: Annotated[int, Query(ge=1, le=100)] = 20,
) -> dict:
settings = load_settings()
effective_limit = min(limit, settings.graph_search_result_limit)
results = CandidateGraphSearchService(db).search(
project_id=project_id,
query=q,
limit=effective_limit,
)
return {
"status": "success",
"query": q,
"result_count": len(results),
"results": [result.to_dict() for result in results],
}
__all__ = ["router"]