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.
|
Get Wiki.js client singleton.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Initialized Wiki.js GraphQL client
|
Initialized Wiki.js GraphQL client with username/password auth
|
||||||
"""
|
"""
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
client = WikiJSClient(
|
client = WikiJSClient(
|
||||||
base_url=settings.wikijs_url,
|
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")
|
logger.debug("Created Wiki.js client instance")
|
||||||
return client
|
return client
|
||||||
@@ -255,9 +256,8 @@ async def check_service_health() -> dict:
|
|||||||
# SearXNG
|
# SearXNG
|
||||||
try:
|
try:
|
||||||
searxng = get_searxng_client()
|
searxng = get_searxng_client()
|
||||||
# Try a simple search
|
# Just check if service is up (no actual search)
|
||||||
await searxng.search_general("test", limit=1)
|
health["searxng"] = await searxng.health_check()
|
||||||
health["searxng"] = True
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"SearXNG health check failed: {e}")
|
logger.error(f"SearXNG health check failed: {e}")
|
||||||
health["searxng"] = False
|
health["searxng"] = False
|
||||||
@@ -274,6 +274,72 @@ async def check_service_health() -> dict:
|
|||||||
return health
|
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
|
# Utility: Get default user from settings or multi_tenancy
|
||||||
def get_default_user() -> str:
|
def get_default_user() -> str:
|
||||||
"""
|
"""
|
||||||
@@ -284,3 +350,35 @@ def get_default_user() -> str:
|
|||||||
"""
|
"""
|
||||||
from src.core.multi_tenancy import DEFAULT_USER
|
from src.core.multi_tenancy import DEFAULT_USER
|
||||||
return 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
|
- OpenAPI documentation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Depends, Security
|
from fastapi import FastAPI, HTTPException, Depends
|
||||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from src.config import Settings, get_settings
|
from src.config import Settings, get_settings
|
||||||
|
from src.core.dependencies import verify_api_key
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -24,9 +26,6 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Security
|
|
||||||
security = HTTPBearer()
|
|
||||||
|
|
||||||
# Initialize FastAPI app
|
# Initialize FastAPI app
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Library Desk API",
|
title="Library Desk API",
|
||||||
@@ -45,22 +44,23 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Register routers
|
||||||
|
from src.routers import wiki, tools, graph, vector, hybrid_rag, consolidation, ingestion, entity_linking
|
||||||
|
|
||||||
# Dependencies
|
app.include_router(wiki.router)
|
||||||
async def verify_api_key(
|
app.include_router(tools.router)
|
||||||
credentials: HTTPAuthorizationCredentials = Security(security),
|
app.include_router(graph.router)
|
||||||
settings: Settings = Depends(get_settings)
|
app.include_router(vector.router)
|
||||||
) -> str:
|
app.include_router(hybrid_rag.router)
|
||||||
"""
|
app.include_router(consolidation.router)
|
||||||
Verify API key from Bearer token.
|
app.include_router(ingestion.router)
|
||||||
Following best practice: use dependencies for validation.
|
app.include_router(entity_linking.router)
|
||||||
"""
|
|
||||||
if credentials.credentials != settings.library_api_key:
|
# Mount static files directory for Wiki.js integration scripts
|
||||||
raise HTTPException(
|
static_dir = Path(__file__).parent.parent / "static"
|
||||||
status_code=403,
|
if static_dir.exists():
|
||||||
detail="Invalid API key"
|
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
||||||
)
|
logger.info(f"Mounted static files from {static_dir}")
|
||||||
return credentials.credentials
|
|
||||||
|
|
||||||
|
|
||||||
# Response Models
|
# Response Models
|
||||||
@@ -264,22 +264,7 @@ async def get_repo_status(
|
|||||||
|
|
||||||
|
|
||||||
# Query endpoints (stubs for future implementation)
|
# Query endpoints (stubs for future implementation)
|
||||||
@app.post("/query/hybrid", tags=["Query"])
|
# NOTE: /query/hybrid is now implemented in routers/hybrid_rag.py
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/query/semantic", tags=["Query"])
|
@app.post("/query/semantic", tags=["Query"])
|
||||||
async def semantic_query(
|
async def semantic_query(
|
||||||
|
|||||||
Reference in New Issue
Block a user