feat(library-desk): integrate entity linking into main app
Main App: - Mount /static directory for serving Wiki.js integration scripts - Register entity_linking router - Refactor API key verification to dependencies module Dependencies: - Add service factory functions for all services - Add get_wiki_service() for wiki operations - Add get_graph_service() for entity operations - Add get_ingestion_service() for auto entity linking - Improve health check for SearXNG
This commit is contained in:
@@ -73,12 +73,13 @@ def get_wikijs_client() -> WikiJSClient:
|
||||
Get Wiki.js client singleton.
|
||||
|
||||
Returns:
|
||||
Initialized Wiki.js GraphQL client
|
||||
Initialized Wiki.js GraphQL client with username/password auth
|
||||
"""
|
||||
settings = get_settings()
|
||||
client = WikiJSClient(
|
||||
base_url=settings.wikijs_url,
|
||||
api_key=settings.wikijs_api_key
|
||||
username=settings.wikijs_username,
|
||||
password=settings.wikijs_password
|
||||
)
|
||||
logger.debug("Created Wiki.js client instance")
|
||||
return client
|
||||
@@ -255,9 +256,8 @@ async def check_service_health() -> dict:
|
||||
# SearXNG
|
||||
try:
|
||||
searxng = get_searxng_client()
|
||||
# Try a simple search
|
||||
await searxng.search_general("test", limit=1)
|
||||
health["searxng"] = True
|
||||
# Just check if service is up (no actual search)
|
||||
health["searxng"] = await searxng.health_check()
|
||||
except Exception as e:
|
||||
logger.error(f"SearXNG health check failed: {e}")
|
||||
health["searxng"] = False
|
||||
@@ -274,6 +274,72 @@ async def check_service_health() -> dict:
|
||||
return health
|
||||
|
||||
|
||||
# Service factory functions
|
||||
@lru_cache
|
||||
def get_vector_service() -> "VectorService":
|
||||
"""Get VectorService singleton."""
|
||||
from src.services.vector_service import VectorService
|
||||
return VectorService(
|
||||
qdrant_client=get_qdrant_client(),
|
||||
wikijs_client=get_wikijs_client(),
|
||||
ollama_client=get_ollama_client()
|
||||
)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_graph_service() -> "GraphService":
|
||||
"""Get GraphService singleton."""
|
||||
from src.services.graph_service import GraphService
|
||||
return GraphService(
|
||||
neo4j_client=get_neo4j_client(),
|
||||
wikijs_client=get_wikijs_client()
|
||||
)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_wiki_service() -> "WikiService":
|
||||
"""Get WikiService singleton."""
|
||||
from src.services.wiki_service import WikiService
|
||||
return WikiService(wiki_client=get_wikijs_client())
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_consolidation_service() -> "ConsolidationService":
|
||||
"""Get ConsolidationService singleton."""
|
||||
from src.services.consolidation_service import ConsolidationService
|
||||
return ConsolidationService(
|
||||
neo4j=get_neo4j_client(),
|
||||
ollama=get_ollama_client(),
|
||||
wiki=get_wikijs_client(),
|
||||
settings=get_settings(),
|
||||
ingestion_service=get_ingestion_service()
|
||||
)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_ingestion_service() -> "IngestionService":
|
||||
"""Get IngestionService singleton."""
|
||||
from src.services.ingestion_service import IngestionService
|
||||
return IngestionService(
|
||||
vector_service=get_vector_service(),
|
||||
graph_service=get_graph_service(),
|
||||
wiki_client=get_wikijs_client()
|
||||
)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_hybrid_rag_service() -> "HybridRAGService":
|
||||
"""Get HybridRAGService singleton."""
|
||||
from src.services.hybrid_rag_service import HybridRAGService
|
||||
return HybridRAGService(
|
||||
vector_service=get_vector_service(),
|
||||
graph_service=get_graph_service(),
|
||||
searxng_client=get_searxng_client(),
|
||||
ollama_client=get_ollama_client(),
|
||||
settings=get_settings()
|
||||
)
|
||||
|
||||
|
||||
# Utility: Get default user from settings or multi_tenancy
|
||||
def get_default_user() -> str:
|
||||
"""
|
||||
@@ -284,3 +350,35 @@ def get_default_user() -> str:
|
||||
"""
|
||||
from src.core.multi_tenancy import DEFAULT_USER
|
||||
return DEFAULT_USER
|
||||
|
||||
|
||||
# Authentication
|
||||
from fastapi import Security, HTTPException
|
||||
from fastapi.security import HTTPBearer
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
async def verify_api_key(
|
||||
credentials: Annotated[HTTPBearer, Security(security)],
|
||||
settings: SettingsDep
|
||||
) -> str:
|
||||
"""
|
||||
Verify API key from Bearer token.
|
||||
|
||||
Args:
|
||||
credentials: HTTP Bearer credentials
|
||||
settings: Application settings
|
||||
|
||||
Returns:
|
||||
API key if valid
|
||||
|
||||
Raises:
|
||||
HTTPException: If API key is invalid
|
||||
"""
|
||||
if credentials.credentials != settings.library_api_key:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Invalid API key"
|
||||
)
|
||||
return credentials.credentials
|
||||
|
||||
@@ -8,14 +8,16 @@ Following best practices:
|
||||
- OpenAPI documentation
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Depends, Security
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from fastapi import FastAPI, HTTPException, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, Any
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from src.config import Settings, get_settings
|
||||
from src.core.dependencies import verify_api_key
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
@@ -24,9 +26,6 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Security
|
||||
security = HTTPBearer()
|
||||
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI(
|
||||
title="Library Desk API",
|
||||
@@ -45,22 +44,23 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Register routers
|
||||
from src.routers import wiki, tools, graph, vector, hybrid_rag, consolidation, ingestion, entity_linking
|
||||
|
||||
# Dependencies
|
||||
async def verify_api_key(
|
||||
credentials: HTTPAuthorizationCredentials = Security(security),
|
||||
settings: Settings = Depends(get_settings)
|
||||
) -> str:
|
||||
"""
|
||||
Verify API key from Bearer token.
|
||||
Following best practice: use dependencies for validation.
|
||||
"""
|
||||
if credentials.credentials != settings.library_api_key:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Invalid API key"
|
||||
)
|
||||
return credentials.credentials
|
||||
app.include_router(wiki.router)
|
||||
app.include_router(tools.router)
|
||||
app.include_router(graph.router)
|
||||
app.include_router(vector.router)
|
||||
app.include_router(hybrid_rag.router)
|
||||
app.include_router(consolidation.router)
|
||||
app.include_router(ingestion.router)
|
||||
app.include_router(entity_linking.router)
|
||||
|
||||
# Mount static files directory for Wiki.js integration scripts
|
||||
static_dir = Path(__file__).parent.parent / "static"
|
||||
if static_dir.exists():
|
||||
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
||||
logger.info(f"Mounted static files from {static_dir}")
|
||||
|
||||
|
||||
# Response Models
|
||||
@@ -264,22 +264,7 @@ async def get_repo_status(
|
||||
|
||||
|
||||
# Query endpoints (stubs for future implementation)
|
||||
@app.post("/query/hybrid", tags=["Query"])
|
||||
async def hybrid_query(
|
||||
query: Dict[str, Any],
|
||||
api_key: str = Depends(verify_api_key)
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
HybridRAG query endpoint.
|
||||
Combines Neo4j (structure) + Qdrant (semantics) + SearXNG (web).
|
||||
|
||||
TODO: Implement HybridRAG logic
|
||||
"""
|
||||
return {
|
||||
"message": "HybridRAG not yet implemented",
|
||||
"query": query
|
||||
}
|
||||
|
||||
# NOTE: /query/hybrid is now implemented in routers/hybrid_rag.py
|
||||
|
||||
@app.post("/query/semantic", tags=["Query"])
|
||||
async def semantic_query(
|
||||
|
||||
Reference in New Issue
Block a user