diff --git a/autobot-backend/api/knowledge_audit.py b/autobot-backend/api/knowledge_audit.py index a1e3124db..3991db558 100644 --- a/autobot-backend/api/knowledge_audit.py +++ b/autobot-backend/api/knowledge_audit.py @@ -15,6 +15,11 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel, Field +from api.schemas_common import ( + KnowledgeAuditEventsResponse, + KnowledgeComplianceReportResponse, + KnowledgePermissionChangesResponse, +) from auth_middleware import get_current_user from autobot_shared.models.pagination import PaginationParams from knowledge.audit_log import AuditEventType, KnowledgeAuditLog @@ -70,7 +75,7 @@ async def _get_audit_log(kb) -> KnowledgeAuditLog: # ============================================================================= -@router.get("/user-activity") +@router.get("/user-activity", response_model=KnowledgeAuditEventsResponse) async def get_user_activity_log( request: Request, current_user: Dict = Depends(get_current_user), @@ -112,7 +117,7 @@ async def get_user_activity_log( raise HTTPException(status_code=500, detail="Internal server error") -@router.get("/fact/{fact_id}/access-log") +@router.get("/fact/{fact_id}/access-log", response_model=KnowledgeAuditEventsResponse) async def get_fact_access_log( fact_id: str, request: Request, @@ -175,7 +180,7 @@ async def get_fact_access_log( raise HTTPException(status_code=500, detail="Internal server error") -@router.get("/organization/audit-log") +@router.get("/organization/audit-log", response_model=KnowledgeAuditEventsResponse) async def get_organization_audit_log( request: Request, current_user: Dict = Depends(get_current_user), @@ -241,7 +246,7 @@ async def get_organization_audit_log( # ============================================================================= -@router.get("/permission-changes") +@router.get("/permission-changes", response_model=KnowledgePermissionChangesResponse) async def get_permission_changes( request: Request, current_user: Dict = Depends(get_current_user), @@ -290,7 +295,7 @@ async def get_permission_changes( # ============================================================================= -@router.post("/compliance-report") +@router.post("/compliance-report", response_model=KnowledgeComplianceReportResponse) async def generate_compliance_report( report_request: ComplianceReportRequest, request: Request, @@ -359,7 +364,7 @@ async def generate_compliance_report( raise HTTPException(status_code=500, detail="Internal server error") -@router.get("/compliance-summary") +@router.get("/compliance-summary", response_model=KnowledgeComplianceReportResponse) async def get_compliance_summary( request: Request, current_user: Dict = Depends(get_current_user), diff --git a/autobot-backend/api/knowledge_boards.py b/autobot-backend/api/knowledge_boards.py index 563e32d4a..7e8cbe1d0 100644 --- a/autobot-backend/api/knowledge_boards.py +++ b/autobot-backend/api/knowledge_boards.py @@ -29,6 +29,11 @@ from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel, Field, field_validator +from api.schemas_common import ( + KnowledgeBoardCreateResponse, + KnowledgeBoardDeleteResponse, + KnowledgeBoardsListResponse, +) from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from knowledge_factory import get_or_create_knowledge_base @@ -103,7 +108,7 @@ async def _get_redis(req: Request): operation="list_boards", error_code_prefix="KB_BOARDS", ) -@router.get("/boards") +@router.get("/boards", response_model=KnowledgeBoardsListResponse) async def list_boards( admin_check: bool = Depends(check_admin_permission), req: Request = None, @@ -139,7 +144,7 @@ async def list_boards( operation="create_board", error_code_prefix="KB_BOARDS", ) -@router.post("/boards", status_code=201) +@router.post("/boards", status_code=201, response_model=KnowledgeBoardCreateResponse) async def create_board( request: CreateBoardRequest = None, admin_check: bool = Depends(check_admin_permission), @@ -170,7 +175,7 @@ async def create_board( operation="delete_board", error_code_prefix="KB_BOARDS", ) -@router.delete("/boards/{board_id}") +@router.delete("/boards/{board_id}", response_model=KnowledgeBoardDeleteResponse) async def delete_board( board_id: str, admin_check: bool = Depends(check_admin_permission), diff --git a/autobot-backend/api/knowledge_cognition.py b/autobot-backend/api/knowledge_cognition.py index 5c23d278a..d16fcdb1e 100644 --- a/autobot-backend/api/knowledge_cognition.py +++ b/autobot-backend/api/knowledge_cognition.py @@ -16,6 +16,10 @@ from fastapi import APIRouter, BackgroundTasks, HTTPException from pydantic import BaseModel +from api.schemas_common import ( + KnowledgeCognitionSeedResponse, + KnowledgeCognitionStatusResponse, +) from auth_middleware import check_admin_permission from constants.path_constants import PATH from services.knowledge.cognition_seeder import get_cognition_seeder @@ -34,7 +38,7 @@ class SeedRequest(BaseModel): manifest_path: str = _DEFAULT_MANIFEST -@router.get("/cognition-store/status") +@router.get("/cognition-store/status", response_model=KnowledgeCognitionStatusResponse) async def get_cognition_store_status(): """Return seed status for all ChromaDB collections that contain seeded docs. @@ -67,7 +71,7 @@ async def _run_seed(manifest_path: str) -> None: logger.error("Background seed failed: manifest=%s error=%s", manifest_path, exc) -@router.post("/cognition-store/seed") +@router.post("/cognition-store/seed", response_model=KnowledgeCognitionSeedResponse) async def trigger_cognition_seed( request: SeedRequest, background_tasks: BackgroundTasks, diff --git a/autobot-backend/api/knowledge_collaboration.py b/autobot-backend/api/knowledge_collaboration.py index 6e35b6b81..672ad1ad0 100644 --- a/autobot-backend/api/knowledge_collaboration.py +++ b/autobot-backend/api/knowledge_collaboration.py @@ -19,6 +19,13 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel, Field +from api.schemas_common import ( + KnowledgeAccessInfoResponse, + KnowledgePermissionsUpdateResponse, + KnowledgeScopedFactsResponse, + KnowledgeShareResponse, + KnowledgeUnshareResponse, +) from auth_middleware import get_current_user from autobot_shared.models.pagination import PaginationParams from knowledge.ownership import VisibilityLevel @@ -275,7 +282,7 @@ async def _unshare_fact_by_entity( # ============================================================================= -@router.get("/facts") +@router.get("/facts", response_model=KnowledgeScopedFactsResponse) async def get_knowledge_by_scope( request: Request, current_user: Dict = Depends(get_current_user), @@ -332,7 +339,7 @@ async def get_knowledge_by_scope( raise HTTPException(status_code=500, detail="Internal server error") -@router.get("/facts/organization/{organization_id}") +@router.get("/facts/organization/{organization_id}", response_model=KnowledgeScopedFactsResponse) async def get_organization_knowledge( organization_id: str, request: Request, @@ -381,7 +388,7 @@ async def get_organization_knowledge( raise HTTPException(status_code=500, detail="Internal server error") -@router.get("/facts/group/{group_id}") +@router.get("/facts/group/{group_id}", response_model=KnowledgeScopedFactsResponse) async def get_group_knowledge( group_id: str, request: Request, @@ -432,7 +439,7 @@ async def get_group_knowledge( # ============================================================================= -@router.post("/facts/{fact_id}/share") +@router.post("/facts/{fact_id}/share", response_model=KnowledgeShareResponse) async def share_knowledge( fact_id: str, share_request: ShareKnowledgeRequest, @@ -497,7 +504,7 @@ async def share_knowledge( raise HTTPException(status_code=500, detail="Internal server error") -@router.delete("/facts/{fact_id}/share/{entity_id}") +@router.delete("/facts/{fact_id}/share/{entity_id}", response_model=KnowledgeUnshareResponse) async def unshare_knowledge( fact_id: str, entity_id: str, @@ -556,7 +563,7 @@ async def unshare_knowledge( raise HTTPException(status_code=500, detail="Internal server error") -@router.put("/facts/{fact_id}/permissions") +@router.put("/facts/{fact_id}/permissions", response_model=KnowledgePermissionsUpdateResponse) async def update_knowledge_permissions( fact_id: str, permissions_request: UpdatePermissionsRequest, @@ -619,7 +626,7 @@ async def update_knowledge_permissions( raise HTTPException(status_code=500, detail="Internal server error") -@router.get("/facts/{fact_id}/access") +@router.get("/facts/{fact_id}/access", response_model=KnowledgeAccessInfoResponse) async def get_knowledge_access_info( fact_id: str, request: Request, current_user: Dict = Depends(get_current_user) ): diff --git a/autobot-backend/api/knowledge_debug.py b/autobot-backend/api/knowledge_debug.py index 7fd312191..c817774ad 100644 --- a/autobot-backend/api/knowledge_debug.py +++ b/autobot-backend/api/knowledge_debug.py @@ -11,6 +11,11 @@ from fastapi import APIRouter, Depends, Request +from api.schemas_common import ( + KnowledgeDebugRedisResponse, + KnowledgeFreshStatsResponse, + KnowledgeRebuildIndexResponse, +) from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from autobot_shared.redis_management.types import DATABASE_MAPPING @@ -27,7 +32,7 @@ operation="get_fresh_knowledge_stats", error_code_prefix="KNOWLEDGE_FRESH", ) -@router.get("/fresh_stats") +@router.get("/fresh_stats", response_model=KnowledgeFreshStatsResponse) async def get_fresh_knowledge_stats(request: Request = None): """Get knowledge base stats using a completely fresh instance (bypasses all cache)""" try: @@ -98,7 +103,7 @@ async def get_fresh_knowledge_stats(request: Request = None): operation="debug_redis_connection", error_code_prefix="KNOWLEDGE_FRESH", ) -@router.get("/debug_redis") +@router.get("/debug_redis", response_model=KnowledgeDebugRedisResponse) async def debug_redis_connection(): """Debug Redis connection and vector counts using canonical utility. @@ -169,7 +174,7 @@ def _debug_redis_connection(): operation="rebuild_search_index", error_code_prefix="KNOWLEDGE_FRESH", ) -@router.post("/rebuild_index") +@router.post("/rebuild_index", response_model=KnowledgeRebuildIndexResponse) async def rebuild_search_index(): """Rebuild the search index to sync vectors with search index""" try: diff --git a/autobot-backend/api/knowledge_grounding.py b/autobot-backend/api/knowledge_grounding.py index 8348d4428..31c99792e 100644 --- a/autobot-backend/api/knowledge_grounding.py +++ b/autobot-backend/api/knowledge_grounding.py @@ -29,6 +29,13 @@ from pydantic import BaseModel, Field from api.knowledge_models import SearchRequest +from api.schemas_common import ( + KnowledgeConflictsListResponse, + KnowledgeGroundingStatsResponse, + KnowledgeGroundResponseResponse, + KnowledgeResolveConflictResponse, + KnowledgeVerifyClaimResponse, +) from auth_middleware import check_admin_permission, get_current_user from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from constants.threshold_constants import QueryDefaults @@ -108,7 +115,7 @@ class ConflictSchema(BaseModel): # ===== ENDPOINTS ===== -@router.post("/ground-response") +@router.post("/ground-response", response_model=KnowledgeGroundResponseResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="ground_response", @@ -187,7 +194,7 @@ async def ground_response( } -@router.post("/verify-claim") +@router.post("/verify-claim", response_model=KnowledgeVerifyClaimResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="verify_claim", @@ -260,7 +267,7 @@ async def verify_claim( } -@router.get("/kb-conflicts") +@router.get("/kb-conflicts", response_model=KnowledgeConflictsListResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="list_conflicts", @@ -396,7 +403,7 @@ async def list_conflicts( } -@router.post("/kb-conflicts/{conflict_id}/resolve") +@router.post("/kb-conflicts/{conflict_id}/resolve", response_model=KnowledgeResolveConflictResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="resolve_conflict", @@ -461,7 +468,7 @@ async def resolve_conflict( } -@router.get("/kb-stats") +@router.get("/kb-stats", response_model=KnowledgeGroundingStatsResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="get_stats", diff --git a/autobot-backend/api/knowledge_organization.py b/autobot-backend/api/knowledge_organization.py index 34b8d9e0d..d32509c92 100644 --- a/autobot-backend/api/knowledge_organization.py +++ b/autobot-backend/api/knowledge_organization.py @@ -13,6 +13,11 @@ from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel, Field +from api.schemas_common import ( + KnowledgeOrganizationCleanupResponse, + KnowledgeOrganizationPolicyResponse, + KnowledgeOrganizationStatsResponse, +) from auth_middleware import get_current_user from knowledge.ownership import VisibilityLevel from knowledge_factory import get_or_create_knowledge_base @@ -75,7 +80,7 @@ class UpdateOrganizationPolicyRequest(BaseModel): # ============================================================================= -@router.get("/policy") +@router.get("/policy", response_model=KnowledgeOrganizationPolicyResponse) async def get_organization_policy( request: Request, current_user: Dict = Depends(get_current_user) ): @@ -119,7 +124,7 @@ async def get_organization_policy( raise HTTPException(status_code=500, detail="Internal server error") -@router.put("/policy") +@router.put("/policy", response_model=KnowledgeOrganizationPolicyResponse) async def update_organization_policy( policy_request: UpdateOrganizationPolicyRequest, request: Request, @@ -244,7 +249,7 @@ def _get_organization_team_count(current_user: Dict) -> int: ) -@router.get("/stats") +@router.get("/stats", response_model=KnowledgeOrganizationStatsResponse) async def get_organization_knowledge_stats( request: Request, current_user: Dict = Depends(get_current_user) ): @@ -343,7 +348,7 @@ async def _delete_expired_facts(kb, fact_ids: list, cutoff_date) -> int: return deleted_count -@router.delete("/cleanup") +@router.delete("/cleanup", response_model=KnowledgeOrganizationCleanupResponse) async def cleanup_organization_knowledge( request: Request, current_user: Dict = Depends(get_current_user), diff --git a/autobot-backend/api/knowledge_ownership.py b/autobot-backend/api/knowledge_ownership.py index c5b52b1c4..56e1426ac 100644 --- a/autobot-backend/api/knowledge_ownership.py +++ b/autobot-backend/api/knowledge_ownership.py @@ -15,6 +15,13 @@ from fastapi import APIRouter, Depends, HTTPException, Request from api.knowledge_models import ShareFactRequest, UpdateVisibilityRequest +from api.schemas_common import ( + KnowledgeMyFactsResponse, + KnowledgeShareFactResponse, + KnowledgeSharedWithMeResponse, + KnowledgeUnshareFactResponse, + KnowledgeUpdateVisibilityResponse, +) from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from knowledge_factory import get_or_create_knowledge_base @@ -75,7 +82,7 @@ async def _get_fact_with_ownership(kb, fact_id: str, user_id: str): return fact -@router.post("/api/knowledge/facts/{fact_id}/share") +@router.post("/api/knowledge/facts/{fact_id}/share", response_model=KnowledgeShareFactResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="share_fact", @@ -138,7 +145,7 @@ async def share_fact( } -@router.delete("/api/knowledge/facts/{fact_id}/share/{user_id_to_remove}") +@router.delete("/api/knowledge/facts/{fact_id}/share/{user_id_to_remove}", response_model=KnowledgeUnshareFactResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="unshare_fact", @@ -201,7 +208,7 @@ async def unshare_fact( } -@router.put("/api/knowledge/facts/{fact_id}/visibility") +@router.put("/api/knowledge/facts/{fact_id}/visibility", response_model=KnowledgeUpdateVisibilityResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="update_fact_visibility", @@ -293,7 +300,7 @@ async def _fetch_fact_details( return facts -@router.get("/api/knowledge/facts/mine") +@router.get("/api/knowledge/facts/mine", response_model=KnowledgeMyFactsResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="get_my_facts", @@ -355,7 +362,7 @@ async def get_my_facts( } -@router.get("/api/knowledge/facts/shared-with-me") +@router.get("/api/knowledge/facts/shared-with-me", response_model=KnowledgeSharedWithMeResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="get_shared_facts", diff --git a/autobot-backend/api/knowledge_relations.py b/autobot-backend/api/knowledge_relations.py index 51533c444..cfddbcc18 100644 --- a/autobot-backend/api/knowledge_relations.py +++ b/autobot-backend/api/knowledge_relations.py @@ -19,6 +19,10 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel, Field +from api.schemas_common import ( + KnowledgeRelationResultResponse, + KnowledgeRelationTypesResponse, +) from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from knowledge_factory import get_or_create_knowledge_base @@ -95,7 +99,7 @@ class HybridSearchRequest(BaseModel): # ============================================================================ -@router.post("/create") +@router.post("/create", response_model=KnowledgeRelationResultResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="create_fact_relation", @@ -142,7 +146,7 @@ async def create_fact_relation(req: Request, body: CreateRelationRequest): return result -@router.delete("/delete") +@router.delete("/delete", response_model=KnowledgeRelationResultResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="delete_fact_relation", @@ -174,7 +178,7 @@ async def delete_fact_relation(req: Request, body: DeleteRelationRequest): return result -@router.get("/fact/{fact_id}") +@router.get("/fact/{fact_id}", response_model=KnowledgeRelationResultResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="get_fact_relations", @@ -223,7 +227,7 @@ async def get_fact_relations( return result -@router.post("/traverse") +@router.post("/traverse", response_model=KnowledgeRelationResultResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="traverse_relations", @@ -257,7 +261,7 @@ async def traverse_relations(req: Request, body: TraverseRequest): return result -@router.post("/hybrid-search") +@router.post("/hybrid-search", response_model=KnowledgeRelationResultResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="hybrid_search", @@ -295,7 +299,7 @@ async def hybrid_search(req: Request, body: HybridSearchRequest): return result -@router.get("/stats") +@router.get("/stats", response_model=KnowledgeRelationResultResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="get_relation_stats", @@ -323,7 +327,7 @@ async def get_relation_stats(req: Request): return result -@router.get("/types") +@router.get("/types", response_model=KnowledgeRelationTypesResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="get_available_relation_types", diff --git a/autobot-backend/api/knowledge_search_aggregator.py b/autobot-backend/api/knowledge_search_aggregator.py index 9fba37062..1b0039815 100644 --- a/autobot-backend/api/knowledge_search_aggregator.py +++ b/autobot-backend/api/knowledge_search_aggregator.py @@ -24,6 +24,14 @@ from fastapi import APIRouter, HTTPException, Query, Request from pydantic import BaseModel, Field +from api.schemas_common import ( + KnowledgeDocumentationSearchResponse, + KnowledgeDocumentationStatsResponse, + KnowledgeUnifiedContextResponse, + KnowledgeUnifiedGraphResponse, + KnowledgeUnifiedSearchResponse, + KnowledgeUnifiedStatsResponse, +) from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from knowledge_factory import get_or_create_knowledge_base @@ -362,7 +370,7 @@ def _search_documentation( operation="unified_search", error_code_prefix="KNOWLEDGE_UNIFIED", ) -@router.post("/search") +@router.post("/search", response_model=KnowledgeUnifiedSearchResponse) async def unified_search(req: Request, body: UnifiedSearchRequest): """ Search across all knowledge sources in a unified query. @@ -411,7 +419,7 @@ async def unified_search(req: Request, body: UnifiedSearchRequest): operation="unified_stats", error_code_prefix="KNOWLEDGE_UNIFIED", ) -@router.get("/stats") +@router.get("/stats", response_model=KnowledgeUnifiedStatsResponse) async def unified_stats(req: Request): """Get statistics from all unified knowledge sources (KB facts, relations, docs). Ref: #1088.""" kb = await get_or_create_knowledge_base(req.app, force_refresh=False) @@ -479,7 +487,7 @@ async def unified_stats(req: Request): operation="get_llm_context", error_code_prefix="KNOWLEDGE_UNIFIED", ) -@router.post("/context") +@router.post("/context", response_model=KnowledgeUnifiedContextResponse) async def get_llm_context(req: Request, body: ContextRequest): """ Get formatted context for LLM prompts from unified knowledge sources. @@ -550,7 +558,7 @@ async def get_llm_context(req: Request, body: ContextRequest): operation="search_documentation", error_code_prefix="KNOWLEDGE_UNIFIED", ) -@router.get("/documentation/search") +@router.get("/documentation/search", response_model=KnowledgeDocumentationSearchResponse) async def search_documentation( query: str, n_results: int = 5, @@ -603,7 +611,7 @@ async def search_documentation( operation="documentation_stats", error_code_prefix="KNOWLEDGE_UNIFIED", ) -@router.get("/documentation/stats") +@router.get("/documentation/stats", response_model=KnowledgeDocumentationStatsResponse) async def documentation_stats(): """ Get statistics about indexed documentation. @@ -943,7 +951,7 @@ def _update_category_fact_counts( operation="get_unified_graph", error_code_prefix="KNOWLEDGE_UNIFIED", ) -@router.post("/graph") +@router.post("/graph", response_model=KnowledgeUnifiedGraphResponse) async def get_unified_graph(req: Request, body: GraphRequest): """ Get unified knowledge graph combining categories, facts, and relations. @@ -1013,7 +1021,7 @@ async def get_unified_graph(req: Request, body: GraphRequest): operation="get_unified_graph_simple", error_code_prefix="KNOWLEDGE_UNIFIED", ) -@router.get("/graph") +@router.get("/graph", response_model=KnowledgeUnifiedGraphResponse) async def get_unified_graph_simple( req: Request, max_facts: int = Query(50, ge=1, le=200, description="Maximum facts to include"), diff --git a/autobot-backend/api/knowledge_search_scoped.py b/autobot-backend/api/knowledge_search_scoped.py index 202a0a433..bc4bcfa4c 100644 --- a/autobot-backend/api/knowledge_search_scoped.py +++ b/autobot-backend/api/knowledge_search_scoped.py @@ -13,6 +13,10 @@ from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel, Field +from api.schemas_common import ( + KnowledgeAccessibleScopesResponse, + KnowledgeScopedSearchResponse, +) from auth_middleware import get_current_user from knowledge.search_filters import ( augment_search_request_with_permissions, @@ -144,7 +148,7 @@ async def _build_scoped_search_response( } -@router.post("/scoped") +@router.post("/scoped", response_model=KnowledgeScopedSearchResponse) async def scoped_search( search_request: ScopedSearchRequest, request: Request, @@ -234,7 +238,7 @@ async def _synthesize_rag_response( } -@router.post("/rag/scoped") +@router.post("/rag/scoped", response_model=KnowledgeScopedSearchResponse) async def scoped_rag_search( search_request: ScopedSearchRequest, request: Request, @@ -286,7 +290,7 @@ async def scoped_rag_search( raise HTTPException(status_code=500, detail="Internal server error") -@router.get("/accessible-scopes") +@router.get("/accessible-scopes", response_model=KnowledgeAccessibleScopesResponse) async def get_accessible_scopes( request: Request, current_user: User = Depends(get_current_user) ): diff --git a/autobot-backend/api/knowledge_suggestions.py b/autobot-backend/api/knowledge_suggestions.py index bc8094a95..b84ec390a 100644 --- a/autobot-backend/api/knowledge_suggestions.py +++ b/autobot-backend/api/knowledge_suggestions.py @@ -29,6 +29,13 @@ SuggestCategoriesRequest, SuggestTagsRequest, ) +from api.schemas_common import ( + KnowledgeAutoApplySuggestionsResponse, + KnowledgeSuggestionsAllResponse, + KnowledgeSuggestionsCategoriesResponse, + KnowledgeSuggestionsContextResponse, + KnowledgeSuggestionsTagsResponse, +) from knowledge import get_knowledge_base logger = logging.getLogger(__name__) @@ -36,7 +43,7 @@ router = APIRouter(tags=["knowledge-suggestions"]) -@router.post("/suggestions/tags") +@router.post("/suggestions/tags", response_model=KnowledgeSuggestionsTagsResponse) async def suggest_tags(request: SuggestTagsRequest): """ Suggest tags for content based on similar documents. @@ -101,7 +108,7 @@ async def suggest_tags(request: SuggestTagsRequest): raise HTTPException(status_code=500, detail="Internal server error") -@router.post("/suggestions/categories") +@router.post("/suggestions/categories", response_model=KnowledgeSuggestionsCategoriesResponse) async def suggest_categories(request: SuggestCategoriesRequest): """ Suggest categories for content based on similar documents. @@ -188,7 +195,7 @@ async def _call_kb_suggest_all(request: "SuggestAllRequest") -> dict: return result -@router.post("/suggestions/all") +@router.post("/suggestions/all", response_model=KnowledgeSuggestionsAllResponse) async def suggest_all(request: SuggestAllRequest): """Suggest both tags and categories in a single call. @@ -206,7 +213,7 @@ async def suggest_all(request: SuggestAllRequest): raise HTTPException(status_code=500, detail="Internal server error") -@router.post("/suggestions/context") +@router.post("/suggestions/context", response_model=KnowledgeSuggestionsContextResponse) async def suggest_by_context(request: ContextSuggestionsRequest): """ Suggest KB documents relevant to the current conversation context (Issue #3284). @@ -324,7 +331,7 @@ def _log_auto_apply_result(fact_id: str, result: dict) -> None: ) -@router.post("/facts/{fact_id}/auto-apply") +@router.post("/facts/{fact_id}/auto-apply", response_model=KnowledgeAutoApplySuggestionsResponse) async def auto_apply_suggestions(fact_id: str, request: AutoApplySuggestionsRequest): """ Automatically apply high-confidence suggestions to a fact. diff --git a/autobot-backend/api/knowledge_sync_queue.py b/autobot-backend/api/knowledge_sync_queue.py index ce8d740ba..2f774ce5e 100644 --- a/autobot-backend/api/knowledge_sync_queue.py +++ b/autobot-backend/api/knowledge_sync_queue.py @@ -14,6 +14,10 @@ from fastapi import APIRouter, Depends, Query +from api.schemas_common import ( + KnowledgeSyncQueuePruneResponse, + KnowledgeSyncQueueResponse, +) from auth_middleware import check_admin_permission from services.knowledge.sync_queue import ( SyncStatus, @@ -26,7 +30,7 @@ router = APIRouter(prefix="/knowledge", tags=["knowledge-sync-queue"]) -@router.get("/sync-queue") +@router.get("/sync-queue", response_model=KnowledgeSyncQueueResponse) async def get_sync_queue( limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0), @@ -51,7 +55,7 @@ async def get_sync_queue( } -@router.post("/sync-queue/prune") +@router.post("/sync-queue/prune", response_model=KnowledgeSyncQueuePruneResponse) async def prune_done_entries( older_than_seconds: int = Query(7 * 24 * 3600, ge=60), admin_check: bool = Depends(check_admin_permission), diff --git a/autobot-backend/api/knowledge_verification.py b/autobot-backend/api/knowledge_verification.py index 540104273..5aab5cf25 100644 --- a/autobot-backend/api/knowledge_verification.py +++ b/autobot-backend/api/knowledge_verification.py @@ -28,6 +28,12 @@ VerificationConfig, VerificationRequest, ) +from api.schemas_common import ( + KnowledgeVerificationApproveResponse, + KnowledgeVerificationConfigResponse, + KnowledgeVerificationPendingResponse, + KnowledgeVerificationRejectResponse, +) from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from constants.threshold_constants import QueryDefaults @@ -73,7 +79,7 @@ def _build_pending_response(fact: dict) -> PendingSourceResponse: ) -@router.get("/verification/pending") +@router.get("/verification/pending", response_model=KnowledgeVerificationPendingResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="list_pending_verification", @@ -132,7 +138,7 @@ async def list_pending_verification( } -@router.post("/verification/{fact_id}/approve") +@router.post("/verification/{fact_id}/approve", response_model=KnowledgeVerificationApproveResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="approve_fact", @@ -185,7 +191,7 @@ async def approve_fact( } -@router.post("/verification/{fact_id}/reject") +@router.post("/verification/{fact_id}/reject", response_model=KnowledgeVerificationRejectResponse) @with_error_handling( category=ErrorCategory.SERVER_ERROR, operation="reject_fact", @@ -250,7 +256,7 @@ async def reject_fact( } -@router.get("/verification/config") +@router.get("/verification/config", response_model=KnowledgeVerificationConfigResponse) async def get_verification_config(req: Request = None): """Return current verification mode configuration. @@ -266,7 +272,7 @@ async def get_verification_config(req: Request = None): } -@router.put("/verification/config") +@router.put("/verification/config", response_model=KnowledgeVerificationConfigResponse) async def update_verification_config( body: VerificationConfig, req: Request = None, diff --git a/autobot-backend/api/schemas_common.py b/autobot-backend/api/schemas_common.py index c50a06682..acd7f177f 100644 --- a/autobot-backend/api/schemas_common.py +++ b/autobot-backend/api/schemas_common.py @@ -2445,3 +2445,568 @@ class DatabaseMCPStatusResponse(BaseModel): timestamp: str +# --------------------------------------------------------------------------- +# knowledge_search_aggregator.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeUnifiedSearchResponse(BaseModel): + """Response for POST /unified/search.""" + + success: bool + query: str + facts: List[Any] + related_facts: List[Any] + documentation: List[Any] + sources_searched: List[str] + total_results: int + + +class KnowledgeUnifiedStatsResponse(BaseModel): + """Response for GET /unified/stats. + + Sections are populated dynamically — extra fields allowed. + """ + + model_config = {"extra": "allow"} + + success: bool + knowledge_base: Dict[str, Any] + relations: Dict[str, Any] + documentation: Dict[str, Any] + + +class KnowledgeUnifiedContextResponse(BaseModel): + """Response for POST /unified/context.""" + + success: bool + context: str + context_length: int + citations: List[Any] + sources_used: List[Any] + + +class KnowledgeDocumentationSearchResponse(BaseModel): + """Response for GET /unified/documentation/search.""" + + success: bool + query: Optional[str] = None + results: List[Any] + total_results: Optional[int] = None + message: Optional[str] = None + + +class KnowledgeDocumentationStatsResponse(BaseModel): + """Response for GET /unified/documentation/stats.""" + + success: bool + indexed: Optional[bool] = None + message: Optional[str] = None + how_to_index: Optional[str] = None + collection_name: Optional[str] = None + document_count: Optional[int] = None + + +class KnowledgeUnifiedGraphResponse(BaseModel): + """Response for POST /unified/graph and GET /unified/graph.""" + + success: bool + data: Dict[str, Any] + stats: Dict[str, Any] + + +# --------------------------------------------------------------------------- +# knowledge_relations.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeRelationResultResponse(BaseModel): + """Generic response for relation create/delete/traverse/hybrid-search/stats endpoints. + + KB methods return opaque success dicts — extra fields allowed. + """ + + model_config = {"extra": "allow"} + + success: bool + + +class KnowledgeRelationTypesResponse(BaseModel): + """Response for GET /types.""" + + success: bool + relation_types: List[Dict[str, Any]] + + +# --------------------------------------------------------------------------- +# knowledge_collaboration.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeScopedFactsResponse(BaseModel): + """Response for GET /knowledge/collaboration/facts and /facts/organization/{id} and /facts/group/{id}.""" + + facts: List[Any] + count: int + total: Optional[int] = None + + +class KnowledgeShareResponse(BaseModel): + """Response for POST /knowledge/collaboration/facts/{id}/share.""" + + success: bool + fact_id: str + visibility: Optional[str] = None + shared_with: List[Any] + group_ids: List[Any] + + +class KnowledgeUnshareResponse(BaseModel): + """Response for DELETE /knowledge/collaboration/facts/{id}/share/{entity_id}.""" + + success: bool + fact_id: str + visibility: Optional[str] = None + shared_with: List[Any] + group_ids: List[Any] + + +class KnowledgePermissionsUpdateResponse(BaseModel): + """Response for PUT /knowledge/collaboration/facts/{id}/permissions.""" + + success: bool + fact_id: str + visibility: Optional[str] = None + organization_id: Optional[str] = None + group_ids: List[Any] + + +class KnowledgeAccessInfoResponse(BaseModel): + """Response for GET /knowledge/collaboration/facts/{id}/access.""" + + fact_id: str + owner_id: Optional[str] = None + visibility: Optional[str] = None + organization_id: Optional[str] = None + group_ids: List[Any] + shared_with: List[Any] + can_edit: bool + can_share: bool + can_delete: bool + has_access: bool + + +# --------------------------------------------------------------------------- +# knowledge_audit.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeAuditEventsResponse(BaseModel): + """Response for GET /knowledge/audit/user-activity, /fact/{id}/access-log, /organization/audit-log.""" + + events: List[Any] + count: int + user_id: Optional[str] = None + fact_id: Optional[str] = None + organization_id: Optional[str] = None + + +class KnowledgePermissionChangesResponse(BaseModel): + """Response for GET /knowledge/audit/permission-changes.""" + + events: List[Any] + count: int + + +class KnowledgeComplianceReportResponse(BaseModel): + """Response for POST /knowledge/audit/compliance-report and GET /compliance-summary. + + Shape is defined by AuditLog.generate_compliance_report() — allow extra fields. + """ + + model_config = {"extra": "allow"} + + total_events: int + + +# --------------------------------------------------------------------------- +# knowledge_verification.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeVerificationPendingResponse(BaseModel): + """Response for GET /verification/pending.""" + + status: str + pending: List[Any] + total: int + limit: int + offset: int + has_more: bool + + +class KnowledgeVerificationApproveResponse(BaseModel): + """Response for POST /verification/{fact_id}/approve.""" + + status: str + fact_id: str + verified_by: Optional[str] = None + verified_at: Optional[str] = None + message: Optional[str] = None + + +class KnowledgeVerificationRejectResponse(BaseModel): + """Response for POST /verification/{fact_id}/reject.""" + + status: str + fact_id: str + deleted: bool + message: Optional[str] = None + + +class KnowledgeVerificationConfigResponse(BaseModel): + """Response for GET/PUT /verification/config.""" + + status: str + config: Dict[str, Any] + message: Optional[str] = None + + +# --------------------------------------------------------------------------- +# knowledge_suggestions.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeSuggestionsTagsResponse(BaseModel): + """Response for POST /suggestions/tags. + + KB method returns opaque success dict — allow extra fields. + """ + + model_config = {"extra": "allow"} + + success: bool + suggestions: Optional[List[Any]] = None + similar_docs_analyzed: Optional[int] = None + + +class KnowledgeSuggestionsCategoriesResponse(BaseModel): + """Response for POST /suggestions/categories.""" + + model_config = {"extra": "allow"} + + success: bool + suggestions: Optional[List[Any]] = None + similar_docs_analyzed: Optional[int] = None + + +class KnowledgeSuggestionsAllResponse(BaseModel): + """Response for POST /suggestions/all.""" + + model_config = {"extra": "allow"} + + success: bool + + +class KnowledgeSuggestionsContextResponse(BaseModel): + """Response for POST /suggestions/context.""" + + model_config = {"extra": "allow"} + + success: bool + suggestions: Optional[List[Any]] = None + total_candidates: Optional[int] = None + + +class KnowledgeAutoApplySuggestionsResponse(BaseModel): + """Response for POST /facts/{fact_id}/auto-apply.""" + + model_config = {"extra": "allow"} + + success: bool + + +# --------------------------------------------------------------------------- +# knowledge_ownership.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeShareFactResponse(BaseModel): + """Response for POST /api/knowledge/facts/{fact_id}/share.""" + + success: bool + fact_id: str + shared_with: List[Any] + visibility: Optional[str] = None + + +class KnowledgeUnshareFactResponse(BaseModel): + """Response for DELETE /api/knowledge/facts/{fact_id}/share/{user_id_to_remove}.""" + + success: bool + fact_id: str + shared_with: List[Any] + visibility: Optional[str] = None + + +class KnowledgeUpdateVisibilityResponse(BaseModel): + """Response for PUT /api/knowledge/facts/{fact_id}/visibility.""" + + success: bool + fact_id: str + visibility: Optional[str] = None + + +class KnowledgeMyFactsResponse(BaseModel): + """Response for GET /api/knowledge/facts/mine.""" + + success: bool + user_id: str + owned_count: int + shared_count: int + facts: List[Any] + total_returned: int + + +class KnowledgeSharedWithMeResponse(BaseModel): + """Response for GET /api/knowledge/facts/shared-with-me.""" + + success: bool + user_id: str + facts: List[Any] + total_returned: int + + +# --------------------------------------------------------------------------- +# knowledge_grounding.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeGroundResponseResponse(BaseModel): + """Response for POST /api/ground-response.""" + + status: str + data: Dict[str, Any] + + +class KnowledgeVerifyClaimResponse(BaseModel): + """Response for POST /api/verify-claim.""" + + status: str + claim_text: str + kb_status: str + confidence: float + evidence: Optional[List[Any]] = None + verification_method: Optional[str] = None + kb_source: Optional[str] = None + + +class KnowledgeConflictsListResponse(BaseModel): + """Response for GET /api/kb-conflicts.""" + + status: str + conflicts: List[Any] + total: int + limit: int + offset: int + has_more: bool + + +class KnowledgeResolveConflictResponse(BaseModel): + """Response for POST /api/kb-conflicts/{conflict_id}/resolve.""" + + status: str + data: Dict[str, Any] + + +class KnowledgeGroundingStatsResponse(BaseModel): + """Response for GET /api/kb-stats. + + Stats shape is dynamic — extra fields allowed. + """ + + model_config = {"extra": "allow"} + + status: str + period: str + + +# --------------------------------------------------------------------------- +# knowledge_organization.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeOrganizationPolicyResponse(BaseModel): + """Response for GET/PUT /knowledge/organization/policy. + + Returns OrganizationKnowledgePolicy fields — allow extra fields. + """ + + model_config = {"extra": "allow"} + + default_visibility: Any + allow_user_private: bool + allow_user_shared: bool + allow_user_organization: bool + require_approval_for_system: bool + retention_days: Optional[int] = None + + +class KnowledgeOrganizationStatsResponse(BaseModel): + """Response for GET /knowledge/organization/stats.""" + + organization_id: str + total_facts: int + by_visibility: Dict[str, Any] + by_source: Dict[str, Any] + total_size_bytes: int + user_count: int + team_count: int + top_contributors: List[Any] + + +class KnowledgeOrganizationCleanupResponse(BaseModel): + """Response for DELETE /knowledge/organization/cleanup.""" + + success: bool + organization_id: str + retention_days: int + deleted_count: int + cutoff_date: str + + +# --------------------------------------------------------------------------- +# knowledge_search_scoped.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeScopedSearchResponse(BaseModel): + """Response for POST /knowledge/search/scoped and POST /knowledge/search/rag/scoped.""" + + model_config = {"extra": "allow"} + + results: Optional[List[Any]] = None + total_results: Optional[int] = None + query: Optional[str] = None + mode: Optional[str] = None + user_id: Optional[str] = None + filtered_by_permissions: Optional[bool] = None + + +class KnowledgeAccessibleScopesResponse(BaseModel): + """Response for GET /knowledge/search/accessible-scopes.""" + + user_id: Optional[str] = None + organization_id: Optional[str] = None + group_count: int + accessible_scopes: List[Any] + + +# --------------------------------------------------------------------------- +# knowledge_debug.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeFreshStatsResponse(BaseModel): + """Response for GET /fresh_stats. + + Shape varies between success and error paths — extra fields allowed. + """ + + model_config = {"extra": "allow"} + + source: str + total_facts: int + status: str + + +class KnowledgeDebugRedisResponse(BaseModel): + """Response for GET /debug_redis. + + Shape varies between success and failure — extra fields allowed. + """ + + model_config = {"extra": "allow"} + + redis_connection: str + + +class KnowledgeRebuildIndexResponse(BaseModel): + """Response for POST /rebuild_index. + + Shape varies depending on result.get('status') — extra fields allowed. + """ + + model_config = {"extra": "allow"} + + operation: str + success: bool + + +# --------------------------------------------------------------------------- +# knowledge_boards.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeBoardsListResponse(BaseModel): + """Response for GET /boards.""" + + boards: List[Any] + total: int + + +class KnowledgeBoardCreateResponse(BaseModel): + """Response for POST /boards.""" + + board_id: str + name: str + created: bool + + +class KnowledgeBoardDeleteResponse(BaseModel): + """Response for DELETE /boards/{board_id}.""" + + board_id: str + deleted: bool + + +# --------------------------------------------------------------------------- +# knowledge_cognition.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeCognitionStatusResponse(BaseModel): + """Response for GET /cognition-store/status.""" + + collections: List[Any] + total_seeded_collections: int + + +class KnowledgeCognitionSeedResponse(BaseModel): + """Response for POST /cognition-store/seed.""" + + status: str + manifest: str + + +# --------------------------------------------------------------------------- +# knowledge_sync_queue.py schemas (Issue #5317) +# --------------------------------------------------------------------------- + + +class KnowledgeSyncQueueResponse(BaseModel): + """Response for GET /knowledge/sync-queue.""" + + pending: List[Any] + failed: List[Any] + counts: Dict[str, Any] + limit: int + offset: int + + +class KnowledgeSyncQueuePruneResponse(BaseModel): + """Response for POST /knowledge/sync-queue/prune.""" + + pruned: int + +