diff --git a/apps/coordinator-api/src/app/contexts/knowledge/routers/__init__.py b/apps/coordinator-api/src/app/contexts/knowledge/routers/__init__.py new file mode 100644 index 00000000..a28ee69a --- /dev/null +++ b/apps/coordinator-api/src/app/contexts/knowledge/routers/__init__.py @@ -0,0 +1,3 @@ +from .knowledge import router + +__all__ = ["router"] diff --git a/apps/coordinator-api/src/app/contexts/knowledge/routers/knowledge.py b/apps/coordinator-api/src/app/contexts/knowledge/routers/knowledge.py new file mode 100644 index 00000000..ac874ccd --- /dev/null +++ b/apps/coordinator-api/src/app/contexts/knowledge/routers/knowledge.py @@ -0,0 +1,177 @@ +from typing import Any, Dict, List, Optional + +from fastapi import APIRouter, Depends, HTTPException, Query, Request +from pydantic import BaseModel, Field +from sqlalchemy.orm import Session + +from aitbc import get_logger +from aitbc.rate_limiting import rate_limit +from ....storage import get_session + +router = APIRouter(prefix="/knowledge", tags=["knowledge"]) + +logger = get_logger(__name__) + + +# Pydantic models for API requests/responses +class KnowledgeGraphCreateRequest(BaseModel): + """Request model for creating a knowledge graph""" + name: str = Field(..., min_length=1, max_length=100) + description: str = Field(..., min_length=1, max_length=500) + schema: str = Field(default=None) + + +class KnowledgeGraphResponse(BaseModel): + """Response model for knowledge graph""" + id: str + name: str + description: str + schema: Optional[str] + owner: str + created_at: str + node_count: int + edge_count: int + + +class KnowledgeNodeRequest(BaseModel): + """Request model for contributing knowledge""" + graph_id: str = Field(..., min_length=1) + node_type: str = Field(..., min_length=1) + data: Dict[str, Any] = Field(default_factory=dict) + relationships: List[Dict[str, Any]] = Field(default_factory=list) + + +class KnowledgeNodeResponse(BaseModel): + """Response model for knowledge node""" + id: str + graph_id: str + node_type: str + data: Dict[str, Any] + relationships: List[Dict[str, Any]] + created_at: str + + +# API endpoints +@router.post("/graphs", response_model=KnowledgeGraphResponse) +@rate_limit(rate=20, per=60) +async def create_knowledge_graph( + request: Request, + graph_request: KnowledgeGraphCreateRequest, + session: Session = Depends(get_session) +) -> KnowledgeGraphResponse: + """Create a new knowledge graph""" + try: + # Simplified implementation - return mock response + graph_id = f"kg-{hash(graph_request.name)}" + return KnowledgeGraphResponse( + id=graph_id, + name=graph_request.name, + description=graph_request.description, + schema=graph_request.schema, + owner="test_user", + created_at="2026-05-19T00:00:00Z", + node_count=0, + edge_count=0 + ) + except Exception as e: + logger.error(f"Failed to create knowledge graph: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/graphs", response_model=List[KnowledgeGraphResponse]) +@rate_limit(rate=200, per=60) +async def list_knowledge_graphs( + request: Request, + session: Session = Depends(get_session) +) -> List[KnowledgeGraphResponse]: + """List all available knowledge graphs""" + try: + # Simplified implementation - return empty list + return [] + except Exception as e: + logger.error(f"Failed to list knowledge graphs: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/graphs/{graph_id}", response_model=KnowledgeGraphResponse) +@rate_limit(rate=200, per=60) +async def get_knowledge_graph( + request: Request, + graph_id: str, + session: Session = Depends(get_session) +) -> KnowledgeGraphResponse: + """Get details of a specific knowledge graph""" + try: + # Simplified implementation - return mock response + return KnowledgeGraphResponse( + id=graph_id, + name="Test Graph", + description="Test knowledge graph", + schema=None, + owner="test_user", + created_at="2026-05-19T00:00:00Z", + node_count=0, + edge_count=0 + ) + except Exception as e: + logger.error(f"Failed to get knowledge graph {graph_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/graphs/{graph_id}/nodes", response_model=KnowledgeNodeResponse) +@rate_limit(rate=20, per=60) +async def contribute_knowledge( + request: Request, + graph_id: str, + node_request: KnowledgeNodeRequest, + session: Session = Depends(get_session) +) -> KnowledgeNodeResponse: + """Contribute knowledge to a graph""" + try: + # Simplified implementation - return mock response + node_id = f"node-{hash(node_request.node_type)}" + return KnowledgeNodeResponse( + id=node_id, + graph_id=graph_id, + node_type=node_request.node_type, + data=node_request.data, + relationships=node_request.relationships, + created_at="2026-05-19T00:00:00Z" + ) + except Exception as e: + logger.error(f"Failed to contribute knowledge to graph {graph_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/graphs/{graph_id}/query", response_model=List[KnowledgeNodeResponse]) +@rate_limit(rate=200, per=60) +async def query_knowledge_graph( + request: Request, + graph_id: str, + node_type: Optional[str] = Query(default=None), + filters: Optional[str] = Query(default=None), + session: Session = Depends(get_session) +) -> List[KnowledgeNodeResponse]: + """Query knowledge from a graph""" + try: + # Simplified implementation - return empty list + return [] + except Exception as e: + logger.error(f"Failed to query knowledge graph {graph_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/graphs/{graph_id}/join") +@rate_limit(rate=20, per=60) +async def join_knowledge_graph( + request: Request, + graph_id: str, + session: Session = Depends(get_session) +) -> Dict[str, str]: + """Join an existing knowledge graph""" + try: + # Simplified implementation - return success + return {"status": "success", "message": f"Joined graph {graph_id}"} + except Exception as e: + logger.error(f"Failed to join knowledge graph {graph_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) diff --git a/apps/coordinator-api/src/app/main.py b/apps/coordinator-api/src/app/main.py index 8fc0e0ae..db03d422 100755 --- a/apps/coordinator-api/src/app/main.py +++ b/apps/coordinator-api/src/app/main.py @@ -540,6 +540,14 @@ def create_app() -> FastAPI: except Exception as e: logger.warning(f"Failed to include rewards router: {e}") + # Include Knowledge Graph router + try: + from .contexts.knowledge.routers.knowledge import router as knowledge_router + app.include_router(knowledge_router, prefix="/v1") + logger.info("Knowledge Graph router included") + except Exception as e: + logger.warning(f"Failed to include Knowledge Graph router: {e}") + # Include marketplace_offers AFTER global_marketplace to override the /offers endpoint app.include_router(marketplace_offers, prefix="/v1")