diff --git a/backend/.env.example b/backend/.env.example index 4ee51a7..26920d8 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -19,3 +19,5 @@ KEYCLOAK_REALM=plfai KEYCLOAK_CLIENT_ID=graphrag-backend KEYCLOAK_CLIENT_SECRET=your_keycloak_client_secret_here KEYCLOAK_AUDIENCE=account + +CORS_ORIGINS=https://graphrag-backend.plfai.cn,https://test-graphrag-backend.plfai.cn,https://graphrag.plfai.cn,https://test-graphrag.plfai.cn diff --git a/backend/main.py b/backend/main.py index 96eb6d7..5eab48d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2,6 +2,7 @@ GraphRAG Studio — FastAPI Backend Entry point: uvicorn main:app --host 0.0.0.0 --port 8000 --reload """ +import os import sys from pathlib import Path @@ -25,10 +26,22 @@ app = FastAPI( redoc_url="/redoc", ) +# CORS — explicit origins when set, wildcard otherwise. +# Auth uses Bearer headers (not cookies), so credentials are only needed when +# the browser is also sending cookies; keep credentials=False with wildcard to +# avoid the CORS spec rejecting the combo at runtime. +_cors_origins_raw = os.getenv("CORS_ORIGINS", "").strip() +if _cors_origins_raw: + _cors_origins = [o.strip() for o in _cors_origins_raw.split(",") if o.strip()] + _cors_credentials = True +else: + _cors_origins = ["*"] + _cors_credentials = False + app.add_middleware( CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, + allow_origins=_cors_origins, + allow_credentials=_cors_credentials, allow_methods=["*"], allow_headers=["*"], ) diff --git a/backend/routers/query.py b/backend/routers/query.py index 52bf9a8..20d7e28 100644 --- a/backend/routers/query.py +++ b/backend/routers/query.py @@ -7,6 +7,7 @@ from fastapi.responses import JSONResponse from models.schemas import APIResponse, BatchQueryRequest, QueryRequest from services import qa_service as svc +from services.qa_service import KGEmptyError router = APIRouter(prefix="/query", tags=["QA"]) @@ -20,12 +21,12 @@ async def run_query(body: QueryRequest): partial(svc.run_query, body.question, [m.model_dump() for m in body.history]), ) return APIResponse.ok(result) + except KGEmptyError as e: + return JSONResponse( + status_code=400, + content=APIResponse.err(3002, str(e)).model_dump(), + ) except ValueError as e: - if "KG_EMPTY" in str(e): - return JSONResponse( - status_code=400, - content=APIResponse.err(3002, "Knowledge graph is empty. Index documents first.").model_dump(), - ) return JSONResponse( status_code=500, content=APIResponse.err(4001, str(e)).model_dump(), diff --git a/backend/services/qa_service.py b/backend/services/qa_service.py index 62e4fbb..4afc1ba 100644 --- a/backend/services/qa_service.py +++ b/backend/services/qa_service.py @@ -8,6 +8,13 @@ from datetime import datetime, timezone from storage import file_store as fs +class KGEmptyError(Exception): + """Raised when a QA request is made but the knowledge graph has no nodes.""" + + def __init__(self, msg: str = "Knowledge graph is empty. Index documents first."): + super().__init__(msg) + + def run_query(question: str, history: list[dict]) -> dict: from pipeline.qa_agent import run_qa @@ -15,7 +22,7 @@ def run_query(question: str, history: list[dict]) -> dict: edges = fs.load_kg_edges() if not nodes: - raise ValueError("KG_EMPTY") + raise KGEmptyError() start = time.time() result = run_qa(question, history, nodes, edges)