mirror of
https://github.com/langgenius/dify.git
synced 2026-09-07 01:43:41 +08:00
The legacy dataset console rate limits every mutating, download, and retrieval route and the dataset service API limits every request, while KnowledgeFS only covered uploads and query admission. Apply the knowledge rate limit to all space-scoped POST/PUT/PATCH/DELETE console routes, the document downloads, and research/query entrypoints (workspace-level source previews, staged-upload discards, the deprecated buffered query route, and the internal stream transport stay exempt). Extract the service API check into `check_knowledge_rate_limit` and call it from the KnowledgeFS profile helper so all service routes share the legacy per-workspace limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zw5G5SX3HmVfnZof6YWAc
3988 lines
158 KiB
Python
3988 lines
158 KiB
Python
"""Typed Console resources for the independent KnowledgeFS product."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import Counter
|
|
from collections.abc import Callable
|
|
from contextlib import ExitStack
|
|
from datetime import UTC
|
|
from functools import wraps
|
|
from hashlib import sha256
|
|
from http import HTTPStatus
|
|
from typing import Literal
|
|
from urllib.parse import quote, urlencode
|
|
|
|
from flask import Response, jsonify, request, send_file
|
|
from flask_restx import Resource
|
|
from pydantic import BaseModel, TypeAdapter, ValidationError
|
|
from werkzeug.exceptions import Conflict, NotFound, RequestEntityTooLarge, ServiceUnavailable, UnprocessableEntity
|
|
|
|
from configs import dify_config
|
|
from controllers.common.fields import BinaryFileResponse
|
|
from controllers.common.schema import (
|
|
query_params_from_model,
|
|
query_params_from_request,
|
|
register_response_schema_models,
|
|
register_schema_models,
|
|
)
|
|
from controllers.console import console_ns
|
|
from controllers.console.knowledge_fs.error import (
|
|
KnowledgeFSAccessDeniedHTTPError,
|
|
KnowledgeFSConflictHTTPError,
|
|
KnowledgeFSInvalidRequestHTTPError,
|
|
KnowledgeFSOperationUnavailableHTTPError,
|
|
KnowledgeFSRateLimitHTTPError,
|
|
KnowledgeFSRequestRejectedHTTPError,
|
|
KnowledgeFSRequestTooLargeHTTPError,
|
|
KnowledgeFSResourceNotFoundHTTPError,
|
|
KnowledgeFSSpaceNotFoundHTTPError,
|
|
KnowledgeFSUpstreamUnavailableHTTPError,
|
|
)
|
|
from controllers.console.wraps import (
|
|
RBACPermission,
|
|
RBACResourceScope,
|
|
account_initialization_required,
|
|
cloud_edition_billing_rate_limit_check,
|
|
rbac_permission_required,
|
|
setup_required,
|
|
)
|
|
from core.db.session_factory import session_factory
|
|
from libs.helper import dump_response
|
|
from libs.login import current_account_with_tenant, login_required
|
|
from models.knowledge_fs import KnowledgeFSAppSpaceJoinType
|
|
from repositories.sqlalchemy_knowledge_fs_capability_issuance_auditor import (
|
|
SQLAlchemyKnowledgeFSCapabilityIssuanceAuditor,
|
|
)
|
|
from services.feature_service import FeatureService
|
|
from services.knowledge_fs.app_binding_management import KnowledgeFSAppBindingManagementError
|
|
from services.knowledge_fs.buffered_upload_admission import DEFAULT_KNOWLEDGE_FS_BUFFERED_UPLOAD_ADMISSION
|
|
from services.knowledge_fs.control_plane_service import (
|
|
KnowledgeFSControlPlaneInvariantError,
|
|
)
|
|
from services.knowledge_fs.download_service import (
|
|
KnowledgeFSDownloadObjectNotFoundError,
|
|
KnowledgeFSDownloadService,
|
|
KnowledgeFSDownloadTooLargeError,
|
|
KnowledgeFSDownloadUnavailableError,
|
|
)
|
|
from services.knowledge_fs.initial_source_preview import KnowledgeFSInitialSourcePreviewService
|
|
from services.knowledge_fs.initial_source_preview_job import (
|
|
KnowledgeFSInitialSourcePreviewJobAlreadyRunningError,
|
|
KnowledgeFSInitialSourcePreviewJobNotFoundError,
|
|
KnowledgeFSInitialSourcePreviewJobService,
|
|
)
|
|
from services.knowledge_fs.object_storage import (
|
|
KnowledgeFSObjectStorageError,
|
|
KnowledgeFSObjectStorageService,
|
|
)
|
|
from services.knowledge_fs.product_authorization import (
|
|
KnowledgeFSProductNotFoundError,
|
|
)
|
|
from services.knowledge_fs.product_dto import (
|
|
KnowledgeFSAdmittedQueryRequest,
|
|
KnowledgeFSAnswerTraceResponse,
|
|
KnowledgeFSAppBindingListResponse,
|
|
KnowledgeFSAppBindingPayload,
|
|
KnowledgeFSAppBindingResponse,
|
|
KnowledgeFSAsyncSourceImportPayload,
|
|
KnowledgeFSBackgroundTaskListQuery,
|
|
KnowledgeFSBackgroundTaskListResponse,
|
|
KnowledgeFSBackgroundTaskResponse,
|
|
KnowledgeFSBadCaseCreatePayload,
|
|
KnowledgeFSBadCaseListResponse,
|
|
KnowledgeFSBadCaseResponse,
|
|
KnowledgeFSBadCaseTraceReferenceResponse,
|
|
KnowledgeFSBadCaseUpdatePayload,
|
|
KnowledgeFSBulkDeletionAcceptedResponse,
|
|
KnowledgeFSBulkDocumentAvailabilityPayload,
|
|
KnowledgeFSBulkDocumentAvailabilityResponse,
|
|
KnowledgeFSBulkDocumentDeletePayload,
|
|
KnowledgeFSBulkJobResponse,
|
|
KnowledgeFSBulkLogicalDocumentDeletePayload,
|
|
KnowledgeFSCrawlImportPayload,
|
|
KnowledgeFSCrawlPreviewPageListQuery,
|
|
KnowledgeFSCrawlPreviewPageListResponse,
|
|
KnowledgeFSCrawlPreviewSelectionPayload,
|
|
KnowledgeFSCursorQuery,
|
|
KnowledgeFSDocumentAvailabilityPayload,
|
|
KnowledgeFSDocumentBatchDownloadPayload,
|
|
KnowledgeFSDocumentChunkListQuery,
|
|
KnowledgeFSDocumentChunkListResponse,
|
|
KnowledgeFSDocumentChunkResponse,
|
|
KnowledgeFSDocumentCompilationJobResponse,
|
|
KnowledgeFSDocumentDeletePayload,
|
|
KnowledgeFSDocumentDownloadDescriptor,
|
|
KnowledgeFSDocumentListResponse,
|
|
KnowledgeFSDocumentMetadataPayload,
|
|
KnowledgeFSDocumentMultimodalAssetQuery,
|
|
KnowledgeFSDocumentMultimodalAssetRef,
|
|
KnowledgeFSDocumentMultimodalItem,
|
|
KnowledgeFSDocumentMultimodalItemResponse,
|
|
KnowledgeFSDocumentMultimodalManifest,
|
|
KnowledgeFSDocumentMultimodalManifestResponse,
|
|
KnowledgeFSDocumentOutlineResponse,
|
|
KnowledgeFSDocumentReindexPayload,
|
|
KnowledgeFSDocumentReindexResponse,
|
|
KnowledgeFSDocumentResponse,
|
|
KnowledgeFSDocumentRevisionListResponse,
|
|
KnowledgeFSDocumentStagedUploadAcceptedResponse,
|
|
KnowledgeFSDocumentStagedUploadPayload,
|
|
KnowledgeFSDocumentUploadAcceptedResponse,
|
|
KnowledgeFSDurableDeletionAcceptedResponse,
|
|
KnowledgeFSExternalAccessPayload,
|
|
KnowledgeFSExternalAccessResponse,
|
|
KnowledgeFSGoldenQuestionBulkImportPayload,
|
|
KnowledgeFSGoldenQuestionBulkImportResponse,
|
|
KnowledgeFSGoldenQuestionEvidenceMatchPayload,
|
|
KnowledgeFSGoldenQuestionEvidenceMatchResponse,
|
|
KnowledgeFSGoldenQuestionListResponse,
|
|
KnowledgeFSGoldenQuestionPayload,
|
|
KnowledgeFSGoldenQuestionResponse,
|
|
KnowledgeFSIdempotencyHeader,
|
|
KnowledgeFSInitialSourcePreviewJobCreateResponse,
|
|
KnowledgeFSInitialSourcePreviewJobResponse,
|
|
KnowledgeFSInitialSourcePreviewPayload,
|
|
KnowledgeFSInitialSourcePreviewResponse,
|
|
KnowledgeFSInitialWebsiteSourcePreviewPayload,
|
|
KnowledgeFSJWKSResponse,
|
|
KnowledgeFSLogicalDocumentDeletePayload,
|
|
KnowledgeFSLogicalDocumentListResponse,
|
|
KnowledgeFSLogicalDocumentResponse,
|
|
KnowledgeFSMembersReplacePayload,
|
|
KnowledgeFSMetadataFieldCreatePayload,
|
|
KnowledgeFSMetadataFieldDeleteQuery,
|
|
KnowledgeFSMetadataFieldDeleteResponse,
|
|
KnowledgeFSMetadataFieldListQuery,
|
|
KnowledgeFSMetadataFieldListResponse,
|
|
KnowledgeFSMetadataFieldResponse,
|
|
KnowledgeFSMetadataFieldUpdatePayload,
|
|
KnowledgeFSOverviewActivityListQuery,
|
|
KnowledgeFSOverviewActivityListResponse,
|
|
KnowledgeFSOverviewAttentionListQuery,
|
|
KnowledgeFSOverviewAttentionListResponse,
|
|
KnowledgeFSOverviewBaseStatsResponse,
|
|
KnowledgeFSOverviewCountComparisonResponse,
|
|
KnowledgeFSOverviewHealthResponse,
|
|
KnowledgeFSOverviewInventoryResponse,
|
|
KnowledgeFSOverviewQueryOutcomesResponse,
|
|
KnowledgeFSOverviewRateComparisonResponse,
|
|
KnowledgeFSOverviewStatsResponse,
|
|
KnowledgeFSOverviewWindowQuery,
|
|
KnowledgeFSPermissionListResponse,
|
|
KnowledgeFSPresignedUploadResponse,
|
|
KnowledgeFSProfileMigrationResponse,
|
|
KnowledgeFSQualityListQuery,
|
|
KnowledgeFSQualityReplayDetailQuery,
|
|
KnowledgeFSQualityReplayListQuery,
|
|
KnowledgeFSQualityReplayListResponse,
|
|
KnowledgeFSQualityReplayPayload,
|
|
KnowledgeFSQualityReplayResponse,
|
|
KnowledgeFSQueryAdmissionResponse,
|
|
KnowledgeFSQueryCreatePayload,
|
|
KnowledgeFSQueryResponse,
|
|
KnowledgeFSQueryStreamCapabilityResponse,
|
|
KnowledgeFSResearchTaskCreatePayload,
|
|
KnowledgeFSResearchTaskListResponse,
|
|
KnowledgeFSResearchTaskPartialListResponse,
|
|
KnowledgeFSResearchTaskPartialsQuery,
|
|
KnowledgeFSResearchTaskPlanPayload,
|
|
KnowledgeFSResearchTaskPlanResponse,
|
|
KnowledgeFSResearchTaskResponse,
|
|
KnowledgeFSResearchTaskStreamQuery,
|
|
KnowledgeFSSettingsPayload,
|
|
KnowledgeFSSettingsResponse,
|
|
KnowledgeFSSettingsUpdateResponse,
|
|
KnowledgeFSSmallFileUploadResponse,
|
|
KnowledgeFSSourceConnectionCreatePayload,
|
|
KnowledgeFSSourceConnectionListQuery,
|
|
KnowledgeFSSourceConnectionListResponse,
|
|
KnowledgeFSSourceConnectionRefreshPayload,
|
|
KnowledgeFSSourceConnectionResponse,
|
|
KnowledgeFSSourceCrawlResponse,
|
|
KnowledgeFSSourceCreatePayload,
|
|
KnowledgeFSSourceCredentialTestResponse,
|
|
KnowledgeFSSourceDeletePayload,
|
|
KnowledgeFSSourceDeleteQuery,
|
|
KnowledgeFSSourceFilesQuery,
|
|
KnowledgeFSSourceFilesResponse,
|
|
KnowledgeFSSourceImportFilesPayload,
|
|
KnowledgeFSSourceImportPagesPayload,
|
|
KnowledgeFSSourceImportResponse,
|
|
KnowledgeFSSourceListQuery,
|
|
KnowledgeFSSourceListResponse,
|
|
KnowledgeFSSourcePagesQuery,
|
|
KnowledgeFSSourcePagesResponse,
|
|
KnowledgeFSSourceProviderListResponse,
|
|
KnowledgeFSSourceResponse,
|
|
KnowledgeFSSourceSyncPolicyPayload,
|
|
KnowledgeFSSourceSyncPolicyResponse,
|
|
KnowledgeFSSourceUpdatePayload,
|
|
KnowledgeFSSourceWorkflowCancelPayload,
|
|
KnowledgeFSSourceWorkflowImportPayload,
|
|
KnowledgeFSSourceWorkflowResponse,
|
|
KnowledgeFSSpaceCreatePayload,
|
|
KnowledgeFSSpaceCreateResponse,
|
|
KnowledgeFSSpaceDetailResponse,
|
|
KnowledgeFSSpaceListQuery,
|
|
KnowledgeFSSpaceListResponse,
|
|
KnowledgeFSSpaceTagListResponse,
|
|
KnowledgeFSSpaceTagsReplacePayload,
|
|
KnowledgeFSSpaceUpdatePayload,
|
|
KnowledgeFSStagedUploadResponse,
|
|
KnowledgeFSStreamCapabilityPayload,
|
|
KnowledgeFSStreamCapabilityResponse,
|
|
KnowledgeFSTraceEntriesQuery,
|
|
KnowledgeFSTraceEntryListResponse,
|
|
KnowledgeFSTraceListResponse,
|
|
KnowledgeFSUploadPartPresignPayload,
|
|
KnowledgeFSUploadSessionAbortPayload,
|
|
KnowledgeFSUploadSessionCompletePayload,
|
|
KnowledgeFSUploadSessionCreatePayload,
|
|
KnowledgeFSUploadSessionCreateResponse,
|
|
KnowledgeFSUploadSessionMutationResponse,
|
|
normalize_knowledge_fs_source_url,
|
|
)
|
|
from services.knowledge_fs.product_remote import (
|
|
KnowledgeFSOperationUnavailableError,
|
|
KnowledgeFSProductRemoteError,
|
|
KnowledgeFSProductRequestRejectedError,
|
|
KnowledgeFSProductResourceNotFoundError,
|
|
KnowledgeFSRemoteMultipartFile,
|
|
KnowledgeFSRemoteSSEResponse,
|
|
)
|
|
from services.knowledge_fs.query_images import KnowledgeFSQueryImageError, validate_query_image_references
|
|
from services.knowledge_fs.runtime import KnowledgeFSRuntime, get_knowledge_fs_runtime
|
|
from services.knowledge_fs.source_import_commit_service import (
|
|
commit_source_import,
|
|
retry_or_resume_source_workflow,
|
|
)
|
|
from services.knowledge_fs.space_tag_service import KnowledgeFSSpaceTagValidationError
|
|
from services.knowledge_fs.staged_upload_service import (
|
|
KnowledgeFSStagedUploadConflictError,
|
|
KnowledgeFSStagedUploadInvalidError,
|
|
KnowledgeFSStagedUploadNotFoundError,
|
|
KnowledgeFSStagedUploadService,
|
|
KnowledgeFSStagedUploadTooLargeError,
|
|
)
|
|
from services.knowledge_fs_capability import (
|
|
KnowledgeFSCapabilityConfigurationError,
|
|
create_configured_knowledge_fs_capability_issuer,
|
|
)
|
|
|
|
register_schema_models(
|
|
console_ns,
|
|
KnowledgeFSAppBindingPayload,
|
|
KnowledgeFSAsyncSourceImportPayload,
|
|
KnowledgeFSBackgroundTaskListQuery,
|
|
KnowledgeFSBadCaseCreatePayload,
|
|
KnowledgeFSBadCaseUpdatePayload,
|
|
KnowledgeFSOverviewWindowQuery,
|
|
KnowledgeFSCursorQuery,
|
|
KnowledgeFSBulkDocumentAvailabilityPayload,
|
|
KnowledgeFSBulkDocumentDeletePayload,
|
|
KnowledgeFSBulkLogicalDocumentDeletePayload,
|
|
KnowledgeFSDocumentBatchDownloadPayload,
|
|
KnowledgeFSDocumentChunkListQuery,
|
|
KnowledgeFSDocumentDeletePayload,
|
|
KnowledgeFSDocumentAvailabilityPayload,
|
|
KnowledgeFSDocumentStagedUploadPayload,
|
|
KnowledgeFSLogicalDocumentDeletePayload,
|
|
KnowledgeFSDocumentMetadataPayload,
|
|
KnowledgeFSDocumentMultimodalAssetQuery,
|
|
KnowledgeFSMetadataFieldCreatePayload,
|
|
KnowledgeFSMetadataFieldDeleteQuery,
|
|
KnowledgeFSMetadataFieldListQuery,
|
|
KnowledgeFSMetadataFieldUpdatePayload,
|
|
KnowledgeFSDocumentReindexPayload,
|
|
KnowledgeFSGoldenQuestionBulkImportPayload,
|
|
KnowledgeFSGoldenQuestionEvidenceMatchPayload,
|
|
KnowledgeFSGoldenQuestionPayload,
|
|
KnowledgeFSExternalAccessPayload,
|
|
KnowledgeFSCrawlPreviewPageListQuery,
|
|
KnowledgeFSMembersReplacePayload,
|
|
KnowledgeFSAdmittedQueryRequest,
|
|
KnowledgeFSQueryCreatePayload,
|
|
KnowledgeFSQualityListQuery,
|
|
KnowledgeFSQualityReplayDetailQuery,
|
|
KnowledgeFSQualityReplayListQuery,
|
|
KnowledgeFSQualityReplayPayload,
|
|
KnowledgeFSResearchTaskPartialsQuery,
|
|
KnowledgeFSResearchTaskStreamQuery,
|
|
KnowledgeFSResearchTaskPlanPayload,
|
|
KnowledgeFSResearchTaskCreatePayload,
|
|
KnowledgeFSSettingsPayload,
|
|
KnowledgeFSSourceCreatePayload,
|
|
KnowledgeFSSourceConnectionCreatePayload,
|
|
KnowledgeFSSourceConnectionListQuery,
|
|
KnowledgeFSSourceConnectionRefreshPayload,
|
|
KnowledgeFSCrawlImportPayload,
|
|
KnowledgeFSCrawlPreviewSelectionPayload,
|
|
KnowledgeFSInitialSourcePreviewPayload,
|
|
KnowledgeFSInitialWebsiteSourcePreviewPayload,
|
|
KnowledgeFSSourceDeletePayload,
|
|
KnowledgeFSSourceDeleteQuery,
|
|
KnowledgeFSSourceFilesQuery,
|
|
KnowledgeFSSourceImportFilesPayload,
|
|
KnowledgeFSSourceImportPagesPayload,
|
|
KnowledgeFSSourcePagesQuery,
|
|
KnowledgeFSSourceUpdatePayload,
|
|
KnowledgeFSSourceSyncPolicyPayload,
|
|
KnowledgeFSSourceWorkflowCancelPayload,
|
|
KnowledgeFSSourceWorkflowImportPayload,
|
|
KnowledgeFSSpaceCreatePayload,
|
|
KnowledgeFSSpaceListQuery,
|
|
KnowledgeFSSpaceTagsReplacePayload,
|
|
KnowledgeFSSpaceUpdatePayload,
|
|
KnowledgeFSStreamCapabilityPayload,
|
|
KnowledgeFSTraceEntriesQuery,
|
|
KnowledgeFSUploadPartPresignPayload,
|
|
KnowledgeFSUploadSessionAbortPayload,
|
|
KnowledgeFSUploadSessionCompletePayload,
|
|
KnowledgeFSUploadSessionCreatePayload,
|
|
)
|
|
register_response_schema_models(
|
|
console_ns,
|
|
BinaryFileResponse,
|
|
KnowledgeFSAnswerTraceResponse,
|
|
KnowledgeFSAppBindingListResponse,
|
|
KnowledgeFSAppBindingResponse,
|
|
KnowledgeFSBackgroundTaskListResponse,
|
|
KnowledgeFSBackgroundTaskResponse,
|
|
KnowledgeFSBadCaseListResponse,
|
|
KnowledgeFSBadCaseResponse,
|
|
KnowledgeFSBadCaseTraceReferenceResponse,
|
|
KnowledgeFSBulkDeletionAcceptedResponse,
|
|
KnowledgeFSBulkDocumentAvailabilityResponse,
|
|
KnowledgeFSBulkJobResponse,
|
|
KnowledgeFSDocumentListResponse,
|
|
KnowledgeFSDocumentChunkListResponse,
|
|
KnowledgeFSDocumentChunkResponse,
|
|
KnowledgeFSDocumentCompilationJobResponse,
|
|
KnowledgeFSDocumentOutlineResponse,
|
|
KnowledgeFSDocumentMultimodalManifestResponse,
|
|
KnowledgeFSDocumentReindexResponse,
|
|
KnowledgeFSDocumentRevisionListResponse,
|
|
KnowledgeFSDocumentResponse,
|
|
KnowledgeFSDocumentUploadAcceptedResponse,
|
|
KnowledgeFSDocumentStagedUploadAcceptedResponse,
|
|
KnowledgeFSDurableDeletionAcceptedResponse,
|
|
KnowledgeFSGoldenQuestionBulkImportResponse,
|
|
KnowledgeFSGoldenQuestionEvidenceMatchResponse,
|
|
KnowledgeFSGoldenQuestionListResponse,
|
|
KnowledgeFSGoldenQuestionResponse,
|
|
KnowledgeFSExternalAccessResponse,
|
|
KnowledgeFSJWKSResponse,
|
|
KnowledgeFSLogicalDocumentListResponse,
|
|
KnowledgeFSMetadataFieldDeleteResponse,
|
|
KnowledgeFSMetadataFieldListResponse,
|
|
KnowledgeFSMetadataFieldResponse,
|
|
KnowledgeFSPermissionListResponse,
|
|
KnowledgeFSQueryResponse,
|
|
KnowledgeFSQualityReplayListResponse,
|
|
KnowledgeFSQualityReplayResponse,
|
|
KnowledgeFSQueryAdmissionResponse,
|
|
KnowledgeFSQueryStreamCapabilityResponse,
|
|
KnowledgeFSResearchTaskResponse,
|
|
KnowledgeFSResearchTaskPartialListResponse,
|
|
KnowledgeFSResearchTaskPlanResponse,
|
|
KnowledgeFSResearchTaskListResponse,
|
|
KnowledgeFSSettingsResponse,
|
|
KnowledgeFSSettingsUpdateResponse,
|
|
KnowledgeFSProfileMigrationResponse,
|
|
KnowledgeFSSmallFileUploadResponse,
|
|
KnowledgeFSCrawlPreviewPageListResponse,
|
|
KnowledgeFSSourceConnectionListResponse,
|
|
KnowledgeFSSourceConnectionResponse,
|
|
KnowledgeFSSourceListResponse,
|
|
KnowledgeFSSourceCrawlResponse,
|
|
KnowledgeFSSourceCredentialTestResponse,
|
|
KnowledgeFSSourceFilesResponse,
|
|
KnowledgeFSSourceImportResponse,
|
|
KnowledgeFSSourcePagesResponse,
|
|
KnowledgeFSSourceResponse,
|
|
KnowledgeFSSourceProviderListResponse,
|
|
KnowledgeFSSourceSyncPolicyResponse,
|
|
KnowledgeFSSourceWorkflowResponse,
|
|
KnowledgeFSSpaceCreateResponse,
|
|
KnowledgeFSSpaceDetailResponse,
|
|
KnowledgeFSSpaceListResponse,
|
|
KnowledgeFSSpaceTagListResponse,
|
|
KnowledgeFSStreamCapabilityResponse,
|
|
KnowledgeFSStagedUploadResponse,
|
|
KnowledgeFSTraceListResponse,
|
|
KnowledgeFSTraceEntryListResponse,
|
|
KnowledgeFSLogicalDocumentResponse,
|
|
KnowledgeFSOverviewActivityListResponse,
|
|
KnowledgeFSOverviewAttentionListResponse,
|
|
KnowledgeFSOverviewHealthResponse,
|
|
KnowledgeFSOverviewInventoryResponse,
|
|
KnowledgeFSOverviewQueryOutcomesResponse,
|
|
KnowledgeFSOverviewStatsResponse,
|
|
KnowledgeFSPresignedUploadResponse,
|
|
KnowledgeFSInitialSourcePreviewResponse,
|
|
KnowledgeFSInitialSourcePreviewJobCreateResponse,
|
|
KnowledgeFSInitialSourcePreviewJobResponse,
|
|
KnowledgeFSUploadSessionCreateResponse,
|
|
KnowledgeFSUploadSessionMutationResponse,
|
|
)
|
|
|
|
|
|
def _console_services() -> KnowledgeFSRuntime:
|
|
if not dify_config.KNOWLEDGE_FS_ENABLED:
|
|
raise NotFound()
|
|
session_maker = session_factory.get_session_maker()
|
|
return get_knowledge_fs_runtime(session_maker)
|
|
|
|
|
|
def _knowledge_fs_errors[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
|
@wraps(view)
|
|
def decorated(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
try:
|
|
return view(*args, **kwargs)
|
|
except KnowledgeFSProductNotFoundError as exc:
|
|
raise KnowledgeFSSpaceNotFoundHTTPError() from exc
|
|
except KnowledgeFSOperationUnavailableError as exc:
|
|
raise KnowledgeFSOperationUnavailableHTTPError() from exc
|
|
except KnowledgeFSInitialSourcePreviewJobAlreadyRunningError as exc:
|
|
raise Conflict() from exc
|
|
except KnowledgeFSProductResourceNotFoundError as exc:
|
|
raise KnowledgeFSResourceNotFoundHTTPError(exc.failure) from exc
|
|
except KnowledgeFSStagedUploadNotFoundError as exc:
|
|
raise NotFound() from exc
|
|
except KnowledgeFSStagedUploadConflictError as exc:
|
|
raise Conflict() from exc
|
|
except KnowledgeFSStagedUploadTooLargeError as exc:
|
|
raise RequestEntityTooLarge() from exc
|
|
except KnowledgeFSStagedUploadInvalidError as exc:
|
|
raise UnprocessableEntity() from exc
|
|
except KnowledgeFSProductRemoteError as exc:
|
|
raise KnowledgeFSUpstreamUnavailableHTTPError(exc.failure) from exc
|
|
except KnowledgeFSProductRequestRejectedError as exc:
|
|
if exc.status_code == HTTPStatus.BAD_REQUEST:
|
|
raise KnowledgeFSInvalidRequestHTTPError(exc.failure) from exc
|
|
if exc.status_code == HTTPStatus.CONFLICT:
|
|
raise KnowledgeFSConflictHTTPError(exc.failure) from exc
|
|
if exc.status_code == HTTPStatus.FORBIDDEN:
|
|
raise KnowledgeFSAccessDeniedHTTPError(exc.failure) from exc
|
|
if exc.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
|
raise KnowledgeFSRequestTooLargeHTTPError(exc.failure) from exc
|
|
if exc.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
|
raise KnowledgeFSRateLimitHTTPError(exc.failure) from exc
|
|
raise KnowledgeFSRequestRejectedHTTPError(exc.failure) from exc
|
|
except KnowledgeFSQueryImageError as exc:
|
|
if exc.code == "QUERY_IMAGE_NOT_FOUND":
|
|
raise NotFound() from exc
|
|
if exc.code in {"QUERY_IMAGE_COUNT_EXCEEDED", "QUERY_IMAGE_TOO_LARGE", "QUERY_IMAGE_TOTAL_TOO_LARGE"}:
|
|
raise RequestEntityTooLarge() from exc
|
|
raise KnowledgeFSInvalidRequestHTTPError() from exc
|
|
except (
|
|
KnowledgeFSAppBindingManagementError,
|
|
KnowledgeFSControlPlaneInvariantError,
|
|
ValidationError,
|
|
) as exc:
|
|
raise KnowledgeFSInvalidRequestHTTPError() from exc
|
|
except PermissionError as exc:
|
|
raise KnowledgeFSAccessDeniedHTTPError() from exc
|
|
except KnowledgeFSCapabilityConfigurationError as exc:
|
|
raise KnowledgeFSOperationUnavailableHTTPError() from exc
|
|
|
|
return decorated
|
|
|
|
|
|
_SMALL_FILE_UPLOAD_PARAMS = {
|
|
"file": {
|
|
"description": "Strictly bounded small-file fallback payload",
|
|
"in": "formData",
|
|
"type": "file",
|
|
"required": True,
|
|
}
|
|
}
|
|
_DOCUMENT_UPLOAD_PARAMS = {
|
|
"file": {
|
|
"description": "Document file to parse, chunk, and index (maximum 15 MB)",
|
|
"in": "formData",
|
|
"type": "file",
|
|
"required": True,
|
|
}
|
|
}
|
|
_STAGED_UPLOAD_PARAMS = {
|
|
"file": {
|
|
"description": "Workspace-scoped document bytes staged before KnowledgeFS admission",
|
|
"in": "formData",
|
|
"type": "file",
|
|
"required": True,
|
|
}
|
|
}
|
|
_IDEMPOTENCY_HEADER_PARAMS = {
|
|
"Idempotency-Key": {
|
|
"description": "Stable key used to make the mutation safe to retry",
|
|
"in": "header",
|
|
"maxLength": 255,
|
|
"minLength": 8,
|
|
"required": True,
|
|
"type": "string",
|
|
}
|
|
}
|
|
_SMALL_FILE_MULTIPART_OVERHEAD_MAX_BYTES = 64 * 1024
|
|
_MAX_STREAM_CAPABILITY_BYTES = 16 * 1024
|
|
_MAX_STREAM_TRACE_ID_BYTES = 255
|
|
_QUERY_STREAM_PROXY_PATH = "/knowledge-fs/query-stream"
|
|
_DOCUMENT_MULTIMODAL_ASSET_MAX_BYTES = 25 * 1024 * 1024
|
|
_INLINE_MULTIMODAL_CONTENT_TYPES = frozenset({"image/gif", "image/jpeg", "image/png", "image/webp"})
|
|
_BACKGROUND_TASK_KIND_ADAPTER: TypeAdapter[Literal["document", "document_bulk", "source"]] = TypeAdapter(
|
|
Literal["document", "document_bulk", "source"]
|
|
)
|
|
|
|
|
|
def _multimodal_asset_ref(
|
|
item: KnowledgeFSDocumentMultimodalItem, variant: str | None = None
|
|
) -> KnowledgeFSDocumentMultimodalAssetRef | None:
|
|
asset_ref = item.asset_ref
|
|
if asset_ref is None or variant is None:
|
|
return asset_ref
|
|
selected = asset_ref.variants.get(variant)
|
|
if selected is None:
|
|
return None
|
|
return KnowledgeFSDocumentMultimodalAssetRef(
|
|
content_type=selected.content_type,
|
|
object_key=selected.object_key,
|
|
sha256=selected.sha256,
|
|
)
|
|
|
|
|
|
def _multimodal_asset_url(*, control_space_id: str, document_id: str, item_id: str, variant: str | None = None) -> str:
|
|
path = (
|
|
f"/knowledge-fs/spaces/{quote(control_space_id, safe='')}/documents/"
|
|
f"{quote(document_id, safe='')}/multimodal/{quote(item_id, safe='')}/asset"
|
|
)
|
|
if variant is not None:
|
|
path = f"{path}?{urlencode({'variant': variant})}"
|
|
return _console_api_url(path)
|
|
|
|
|
|
def _public_multimodal_manifest(
|
|
*,
|
|
control_space_id: str,
|
|
document_id: str,
|
|
manifest: KnowledgeFSDocumentMultimodalManifest,
|
|
) -> KnowledgeFSDocumentMultimodalManifestResponse:
|
|
if manifest.document_asset_id != document_id:
|
|
raise NotFound("KnowledgeFS document multimodal manifest not found")
|
|
|
|
items: list[KnowledgeFSDocumentMultimodalItemResponse] = []
|
|
for item in manifest.items:
|
|
asset_ref = _multimodal_asset_ref(item)
|
|
thumbnail_ref = _multimodal_asset_ref(item, "thumbnail")
|
|
items.append(
|
|
KnowledgeFSDocumentMultimodalItemResponse(
|
|
asset_url=(
|
|
_multimodal_asset_url(
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
item_id=item.id,
|
|
)
|
|
if asset_ref and asset_ref.object_key
|
|
else None
|
|
),
|
|
caption=item.caption,
|
|
end_offset=item.end_offset,
|
|
id=item.id,
|
|
modality=item.modality,
|
|
ocr_text=item.ocr_text,
|
|
page_number=item.page_number,
|
|
parse_element_id=item.parse_element_id,
|
|
section_path=item.section_path,
|
|
start_offset=item.start_offset,
|
|
text_preview=item.text_preview,
|
|
thumbnail_url=(
|
|
_multimodal_asset_url(
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
item_id=item.id,
|
|
variant="thumbnail",
|
|
)
|
|
if thumbnail_ref and thumbnail_ref.object_key
|
|
else None
|
|
),
|
|
title=item.title,
|
|
)
|
|
)
|
|
|
|
return KnowledgeFSDocumentMultimodalManifestResponse(
|
|
artifact_hash=manifest.artifact_hash,
|
|
created_at=manifest.created_at,
|
|
document_asset_id=manifest.document_asset_id,
|
|
id=manifest.id,
|
|
items=items,
|
|
manifest_version=manifest.manifest_version,
|
|
updated_at=manifest.updated_at,
|
|
version=manifest.version,
|
|
)
|
|
|
|
|
|
def _multimodal_object_key_is_scoped(
|
|
*, document_id: str, knowledge_space_id: str, object_key: str, tenant_id: str
|
|
) -> bool:
|
|
return object_key.startswith(f"{tenant_id}/spaces/{knowledge_space_id}/documents/{document_id}/assets/")
|
|
|
|
|
|
def _read_small_file_body(max_bytes: int) -> bytes:
|
|
content_length = request.content_length
|
|
if content_length is not None and content_length > max_bytes + _SMALL_FILE_MULTIPART_OVERHEAD_MAX_BYTES:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=413)
|
|
uploads = request.files.getlist("file")
|
|
if set(request.files) != {"file"} or len(uploads) != 1 or request.form:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=422)
|
|
upload = uploads[0]
|
|
if not upload.filename:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=422)
|
|
body = upload.stream.read(max_bytes + 1)
|
|
if len(body) > max_bytes:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=413)
|
|
if not body:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=422)
|
|
return body
|
|
|
|
|
|
def _stream_capability() -> tuple[str, str]:
|
|
scheme, separator, credential = request.headers.get("Authorization", "").partition(" ")
|
|
token = credential.strip()
|
|
trace_id = request.headers.get("X-Trace-ID", "").strip()
|
|
if (
|
|
separator != " "
|
|
or scheme.lower() != "bearer"
|
|
or not token
|
|
or len(token.encode("utf-8")) > _MAX_STREAM_CAPABILITY_BYTES
|
|
or any(character.isspace() for character in token)
|
|
):
|
|
raise PermissionError("KnowledgeFS stream capability is invalid")
|
|
if (
|
|
not trace_id
|
|
or len(trace_id.encode("utf-8")) > _MAX_STREAM_TRACE_ID_BYTES
|
|
or any(character in trace_id for character in ("\0", "\r", "\n"))
|
|
):
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=422)
|
|
return token, trace_id
|
|
|
|
|
|
def _stream_response(upstream: KnowledgeFSRemoteSSEResponse) -> Response:
|
|
headers = dict(upstream.headers)
|
|
if HTTPStatus.OK <= upstream.status_code < HTTPStatus.MULTIPLE_CHOICES:
|
|
headers.setdefault("cache-control", "no-cache")
|
|
headers.setdefault("content-type", "text/event-stream")
|
|
headers.setdefault("x-accel-buffering", "no")
|
|
|
|
def generate():
|
|
try:
|
|
yield from upstream.chunks
|
|
finally:
|
|
upstream.close()
|
|
|
|
return Response(
|
|
generate(),
|
|
status=upstream.status_code,
|
|
headers=headers,
|
|
direct_passthrough=True,
|
|
)
|
|
|
|
|
|
def _console_api_url(path: str) -> str:
|
|
base_url = str(dify_config.CONSOLE_API_URL or "").strip().rstrip("/")
|
|
if not base_url:
|
|
return f"/console/api{path}"
|
|
if base_url.endswith("/console/api"):
|
|
return f"{base_url}{path}"
|
|
return f"{base_url}/console/api{path}"
|
|
|
|
|
|
def _read_document_upload(max_bytes: int) -> KnowledgeFSRemoteMultipartFile:
|
|
content_length = request.content_length
|
|
if content_length is not None and content_length > max_bytes + _SMALL_FILE_MULTIPART_OVERHEAD_MAX_BYTES:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=413)
|
|
uploads = request.files.getlist("file")
|
|
if set(request.files) != {"file"} or len(uploads) != 1 or request.form:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=422)
|
|
upload = uploads[0]
|
|
if not upload.filename:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=422)
|
|
body = upload.stream.read(max_bytes + 1)
|
|
if len(body) > max_bytes:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=413)
|
|
if not body:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=422)
|
|
return KnowledgeFSRemoteMultipartFile(
|
|
filename=upload.filename,
|
|
content_type=upload.mimetype or "application/octet-stream",
|
|
body=body,
|
|
)
|
|
|
|
|
|
def _read_staged_upload(max_bytes: int) -> KnowledgeFSRemoteMultipartFile:
|
|
return _read_document_upload(max_bytes)
|
|
|
|
|
|
def _actor() -> tuple[str, str]:
|
|
account, tenant_id = current_account_with_tenant()
|
|
return account.id, tenant_id
|
|
|
|
|
|
def _staged_uploads() -> KnowledgeFSStagedUploadService:
|
|
session_maker = session_factory.get_session_maker()
|
|
return KnowledgeFSStagedUploadService(session_maker, facade=_console_services().facade)
|
|
|
|
|
|
def _payload[PayloadT: BaseModel](model: type[PayloadT]) -> PayloadT:
|
|
return model.model_validate(console_ns.payload or {})
|
|
|
|
|
|
def _idempotency_key() -> str:
|
|
return KnowledgeFSIdempotencyHeader.model_validate(
|
|
{"idempotency-key": request.headers.get("Idempotency-Key")}
|
|
).idempotency_key
|
|
|
|
|
|
def _query_pairs(model: BaseModel) -> tuple[tuple[str, str], ...]:
|
|
values = model.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
return tuple(
|
|
(name, str(value).lower() if isinstance(value, bool) else str(value)) for name, value in values.items()
|
|
)
|
|
|
|
|
|
def _source_edit_requires_import(source: KnowledgeFSSourceResponse, payload: KnowledgeFSSourceUpdatePayload) -> bool:
|
|
selection = payload.selection
|
|
if selection is None:
|
|
return False
|
|
provider_kind = source.metadata.get("providerKind")
|
|
configured_kind = "website_crawl" if source.type == "web" else None
|
|
if isinstance(provider_kind, str):
|
|
configured_kind = {"online-document": "online_document", "online-drive": "online_drive"}.get(provider_kind)
|
|
if configured_kind is not None and selection.kind != configured_kind:
|
|
raise ValueError("Source selection kind cannot be changed")
|
|
if getattr(source, "status", None) == "error":
|
|
return True
|
|
if payload.uri is not None and payload.uri != source.uri:
|
|
return True
|
|
if payload.provider_parameters is not None and payload.provider_parameters != source.metadata.get("parameters"):
|
|
return True
|
|
if payload.metadata is not None and any(
|
|
key != "syncPolicy" and value != source.metadata.get(key) for key, value in payload.metadata.items()
|
|
):
|
|
return True
|
|
if selection.kind == "website_crawl":
|
|
crawled = source.metadata.get("crawled")
|
|
current_urls = (
|
|
Counter(normalize_knowledge_fs_source_url(url) for url in crawled)
|
|
if isinstance(crawled, dict)
|
|
else Counter[str]()
|
|
)
|
|
selected_urls = Counter(normalize_knowledge_fs_source_url(url) for url in selection.source_urls)
|
|
return selected_urls != current_urls
|
|
|
|
marker = source.metadata.get("__knowledgeFsProviderSelection")
|
|
current_hashes = (
|
|
set(marker.get("identityHashes", ()))
|
|
if isinstance(marker, dict) and isinstance(marker.get("identityHashes"), list)
|
|
else set()
|
|
)
|
|
provider_kind = "online-document" if selection.kind == "online_document" else "online-drive"
|
|
selected_hashes = {
|
|
sha256(f"{provider_kind}\0{item.provider_item_id}".encode()).hexdigest() for item in selection.items
|
|
}
|
|
return selected_hashes != current_hashes
|
|
|
|
|
|
def _overview_stats_response(
|
|
*,
|
|
stats: KnowledgeFSOverviewBaseStatsResponse,
|
|
outcomes: KnowledgeFSOverviewQueryOutcomesResponse,
|
|
linked_apps: int,
|
|
) -> KnowledgeFSOverviewStatsResponse:
|
|
current_queries = outcomes.current.query_count
|
|
previous_queries = outcomes.previous.query_count
|
|
query_change = None if previous_queries == 0 else (current_queries - previous_queries) / previous_queries
|
|
latest_sync = stats.current.latest_source_sync_at
|
|
freshness_seconds = (
|
|
None if latest_sync is None else max(0, int((outcomes.generated_at - latest_sync).total_seconds()))
|
|
)
|
|
return KnowledgeFSOverviewStatsResponse(
|
|
answer_rate=KnowledgeFSOverviewRateComparisonResponse(
|
|
change_percentage_points=(outcomes.current.answer_rate - outcomes.previous.answer_rate) * 100,
|
|
previous_value=outcomes.previous.answer_rate,
|
|
value=outcomes.current.answer_rate,
|
|
),
|
|
documents=stats.current.knowledge_count,
|
|
fresh_source_count=stats.current.fresh_source_count,
|
|
freshness_seconds=freshness_seconds,
|
|
generated_at=outcomes.generated_at,
|
|
knowledge_space_id=stats.knowledge_space_id,
|
|
latest_source_sync_at=latest_sync,
|
|
linked_apps=linked_apps,
|
|
queries=KnowledgeFSOverviewCountComparisonResponse(
|
|
change_rate=query_change,
|
|
previous_value=previous_queries,
|
|
value=current_queries,
|
|
),
|
|
source_count=stats.current.source_count,
|
|
stale_source_count=stats.current.stale_source_count,
|
|
window=outcomes.window,
|
|
)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/source-provider-preview")
|
|
class KnowledgeFSInitialSourcePreviewApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSInitialSourcePreviewPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"Datasource resources available for an initial Source",
|
|
console_ns.models[KnowledgeFSInitialSourcePreviewResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def post(self):
|
|
account, tenant_id = current_account_with_tenant()
|
|
result = KnowledgeFSInitialSourcePreviewService(session_factory.get_session_maker()).preview(
|
|
tenant_id=tenant_id,
|
|
account=account,
|
|
payload=_payload(KnowledgeFSInitialSourcePreviewPayload),
|
|
)
|
|
return dump_response(KnowledgeFSInitialSourcePreviewResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/source-provider-preview/jobs")
|
|
class KnowledgeFSInitialSourcePreviewJobsApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSInitialWebsiteSourcePreviewPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"Website datasource preview queued",
|
|
console_ns.models[KnowledgeFSInitialSourcePreviewJobCreateResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def post(self):
|
|
account, tenant_id = current_account_with_tenant()
|
|
result = KnowledgeFSInitialSourcePreviewJobService(session_factory.get_session_maker()).start(
|
|
tenant_id=tenant_id,
|
|
account=account,
|
|
payload=_payload(KnowledgeFSInitialWebsiteSourcePreviewPayload),
|
|
)
|
|
return dump_response(KnowledgeFSInitialSourcePreviewJobCreateResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/source-provider-preview/jobs/<string:job_id>")
|
|
class KnowledgeFSInitialSourcePreviewJobApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"Website datasource preview status",
|
|
console_ns.models[KnowledgeFSInitialSourcePreviewJobResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, job_id: str):
|
|
account, tenant_id = current_account_with_tenant()
|
|
try:
|
|
result = KnowledgeFSInitialSourcePreviewJobService.get(
|
|
tenant_id=tenant_id,
|
|
account_id=account.id,
|
|
job_id=job_id,
|
|
)
|
|
except KnowledgeFSInitialSourcePreviewJobNotFoundError as exc:
|
|
raise NotFound() from exc
|
|
return dump_response(KnowledgeFSInitialSourcePreviewJobResponse, result)
|
|
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"Website datasource preview canceled",
|
|
console_ns.models[KnowledgeFSInitialSourcePreviewJobResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def delete(self, job_id: str):
|
|
account, tenant_id = current_account_with_tenant()
|
|
try:
|
|
result = KnowledgeFSInitialSourcePreviewJobService.cancel(
|
|
tenant_id=tenant_id,
|
|
account_id=account.id,
|
|
job_id=job_id,
|
|
)
|
|
except KnowledgeFSInitialSourcePreviewJobNotFoundError as exc:
|
|
raise NotFound() from exc
|
|
return dump_response(KnowledgeFSInitialSourcePreviewJobResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/uploads")
|
|
class KnowledgeFSStagedUploadsApi(Resource):
|
|
@console_ns.doc(consumes=["multipart/form-data"], params=_STAGED_UPLOAD_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS document bytes staged in the current workspace",
|
|
console_ns.models[KnowledgeFSStagedUploadResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self):
|
|
account, tenant_id = current_account_with_tenant()
|
|
file_size_limit_mb = FeatureService.get_knowledge_file_size_limit(tenant_id)
|
|
max_bytes = file_size_limit_mb * 1024 * 1024
|
|
if max_bytes < 1:
|
|
raise KnowledgeFSProductRequestRejectedError(status_code=413)
|
|
with DEFAULT_KNOWLEDGE_FS_BUFFERED_UPLOAD_ADMISSION.admit(reserved_bytes=max_bytes):
|
|
upload = _read_staged_upload(max_bytes)
|
|
result = _staged_uploads().stage(
|
|
tenant_id=tenant_id,
|
|
account=account,
|
|
file_name=upload.filename,
|
|
content_type=upload.content_type,
|
|
body=upload.body,
|
|
file_size_limit_mb=file_size_limit_mb,
|
|
)
|
|
return dump_response(KnowledgeFSStagedUploadResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/uploads/<string:upload_id>")
|
|
class KnowledgeFSStagedUploadApi(Resource):
|
|
@console_ns.response(HTTPStatus.NO_CONTENT, "KnowledgeFS staged upload discarded")
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def delete(self, upload_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
_staged_uploads().abort(tenant_id=tenant_id, account_id=actor_id, upload_id=upload_id)
|
|
return "", HTTPStatus.NO_CONTENT
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces")
|
|
class KnowledgeFSSpacesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSSpaceListQuery))
|
|
@console_ns.response(HTTPStatus.OK, "KnowledgeFS spaces", console_ns.models[KnowledgeFSSpaceListResponse.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self):
|
|
actor_id, tenant_id = _actor()
|
|
query = query_params_from_request(KnowledgeFSSpaceListQuery, list_fields=("creator_ids", "tag_ids"))
|
|
result = _console_services().application.list_spaces(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
page=query.page,
|
|
limit=query.limit,
|
|
creator_ids=query.creator_ids,
|
|
tag_ids=query.tag_ids,
|
|
query=query.query,
|
|
)
|
|
return dump_response(KnowledgeFSSpaceListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSpaceCreatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS provisioning accepted",
|
|
console_ns.models[KnowledgeFSSpaceCreateResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().application.create_space(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
payload=_payload(KnowledgeFSSpaceCreatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSSpaceCreateResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/tags")
|
|
class KnowledgeFSSpaceTagsApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS space tags",
|
|
console_ns.models[KnowledgeFSSpaceTagListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
tags = _console_services().space_tags.list_tags(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSSpaceTagListResponse, {"data": tags})
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSpaceTagsReplacePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS space tags replaced",
|
|
console_ns.models[KnowledgeFSSpaceTagListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def put(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSSpaceTagsReplacePayload)
|
|
try:
|
|
tags = _console_services().space_tags.replace_tags(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
tag_ids=payload.tag_ids,
|
|
)
|
|
except KnowledgeFSSpaceTagValidationError as exc:
|
|
raise UnprocessableEntity(str(exc)) from exc
|
|
return dump_response(KnowledgeFSSpaceTagListResponse, {"data": tags})
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>")
|
|
class KnowledgeFSSpaceApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS space",
|
|
console_ns.models[KnowledgeFSSpaceDetailResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().application.get_space(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSSpaceDetailResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSpaceUpdatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS space updated",
|
|
console_ns.models[KnowledgeFSSpaceDetailResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().application.update_space(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSSpaceUpdatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSSpaceDetailResponse, result)
|
|
|
|
@console_ns.response(HTTPStatus.NO_CONTENT, "KnowledgeFS deletion accepted")
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
_console_services().application.delete_space(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return "", HTTPStatus.NO_CONTENT
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/permissions")
|
|
class KnowledgeFSSpacePermissionsApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS space permissions",
|
|
console_ns.models[KnowledgeFSPermissionListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().control_plane.list_permissions(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSPermissionListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/members")
|
|
class KnowledgeFSSpaceMembersApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSMembersReplacePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS space members replaced",
|
|
console_ns.models[KnowledgeFSPermissionListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def put(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSMembersReplacePayload)
|
|
result = _console_services().control_plane.replace_members(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
members=payload.members,
|
|
)
|
|
return dump_response(KnowledgeFSPermissionListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/external-access")
|
|
class KnowledgeFSSpaceExternalAccessApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS external access",
|
|
console_ns.models[KnowledgeFSExternalAccessResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().control_plane.get_external_access(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSExternalAccessResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSExternalAccessPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS external access updated",
|
|
console_ns.models[KnowledgeFSExternalAccessResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def put(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().control_plane.update_external_access(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSExternalAccessPayload),
|
|
)
|
|
return dump_response(KnowledgeFSExternalAccessResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/app-bindings")
|
|
class KnowledgeFSSpaceAppBindingsApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS app bindings",
|
|
console_ns.models[KnowledgeFSAppBindingListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().app_bindings.list(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSAppBindingListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSAppBindingPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS app binding enabled",
|
|
console_ns.models[KnowledgeFSAppBindingResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def put(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().app_bindings.upsert(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSAppBindingPayload),
|
|
)
|
|
return dump_response(KnowledgeFSAppBindingResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/app-bindings/<string:caller_kind>/<string:app_id>")
|
|
class KnowledgeFSSpaceAppBindingApi(Resource):
|
|
@console_ns.response(HTTPStatus.NO_CONTENT, "KnowledgeFS app binding revoked")
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, caller_kind: str, app_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
try:
|
|
parsed_caller_kind = KnowledgeFSAppSpaceJoinType(caller_kind)
|
|
except ValueError as exc:
|
|
raise KnowledgeFSAppBindingManagementError("KnowledgeFS app caller kind is invalid") from exc
|
|
_console_services().app_bindings.revoke(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
app_id=app_id,
|
|
caller_kind=parsed_caller_kind,
|
|
)
|
|
return "", HTTPStatus.NO_CONTENT
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/settings")
|
|
class KnowledgeFSSpaceSettingsApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS settings",
|
|
console_ns.models[KnowledgeFSSettingsResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_settings(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSSettingsResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSettingsPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS settings updated",
|
|
console_ns.models[KnowledgeFSSettingsUpdateResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.update_settings(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSSettingsPayload),
|
|
)
|
|
return dump_response(KnowledgeFSSettingsUpdateResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/settings/migrations/<string:migration_id>")
|
|
class KnowledgeFSSpaceSettingsMigrationApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS settings migration",
|
|
console_ns.models[KnowledgeFSProfileMigrationResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, migration_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_profile_migration(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
migration_id=migration_id,
|
|
)
|
|
return dump_response(KnowledgeFSProfileMigrationResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/overview/stats")
|
|
class KnowledgeFSSpaceOverviewStatsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewWindowQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS Overview statistics",
|
|
console_ns.models[KnowledgeFSOverviewStatsResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSOverviewWindowQuery.model_validate(request.args.to_dict())
|
|
services = _console_services()
|
|
stats = services.facade.get_overview_stats(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
outcomes = services.facade.get_overview_query_outcomes(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
window=query.window,
|
|
)
|
|
result = _overview_stats_response(
|
|
stats=stats,
|
|
outcomes=outcomes,
|
|
linked_apps=services.app_bindings.count_active(
|
|
tenant_id=tenant_id,
|
|
actor_account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
),
|
|
)
|
|
return dump_response(KnowledgeFSOverviewStatsResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/overview/query-outcomes")
|
|
class KnowledgeFSSpaceOverviewQueryOutcomesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewWindowQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS query outcomes",
|
|
console_ns.models[KnowledgeFSOverviewQueryOutcomesResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSOverviewWindowQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.get_overview_query_outcomes(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
window=query.window,
|
|
)
|
|
return dump_response(KnowledgeFSOverviewQueryOutcomesResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/overview/inventory")
|
|
class KnowledgeFSSpaceOverviewInventoryApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS inventory",
|
|
console_ns.models[KnowledgeFSOverviewInventoryResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_overview_inventory(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSOverviewInventoryResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/overview/attention")
|
|
class KnowledgeFSSpaceOverviewAttentionApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewAttentionListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS attention findings",
|
|
console_ns.models[KnowledgeFSOverviewAttentionListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSOverviewAttentionListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_overview_attention(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
include_dismissed=query.include_dismissed,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSOverviewAttentionListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/overview/activity")
|
|
class KnowledgeFSSpaceOverviewActivityApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewActivityListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS activity",
|
|
console_ns.models[KnowledgeFSOverviewActivityListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSOverviewActivityListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_overview_activity(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
action=query.action,
|
|
actor_id=query.actor_id,
|
|
actor_type=query.actor_type,
|
|
cursor=query.cursor,
|
|
from_at=query.from_at.astimezone(UTC).isoformat().replace("+00:00", "Z") if query.from_at else None,
|
|
limit=query.limit,
|
|
resource_type=query.resource_type,
|
|
result=query.result,
|
|
to_at=query.to_at.astimezone(UTC).isoformat().replace("+00:00", "Z") if query.to_at else None,
|
|
)
|
|
return dump_response(KnowledgeFSOverviewActivityListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/overview/health")
|
|
class KnowledgeFSSpaceOverviewHealthApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS health",
|
|
console_ns.models[KnowledgeFSOverviewHealthResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_overview_health(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSOverviewHealthResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/logical-documents")
|
|
class KnowledgeFSSpaceLogicalDocumentsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS logical documents",
|
|
console_ns.models[KnowledgeFSLogicalDocumentListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_logical_documents(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
)
|
|
return dump_response(KnowledgeFSLogicalDocumentListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSBulkDocumentAvailabilityPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS logical document availability updated",
|
|
console_ns.models[KnowledgeFSBulkDocumentAvailabilityResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.bulk_update_logical_document_availability(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSBulkDocumentAvailabilityPayload),
|
|
)
|
|
return dump_response(KnowledgeFSBulkDocumentAvailabilityResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/logical-documents/bulk")
|
|
class KnowledgeFSSpaceBulkLogicalDocumentsApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSBulkLogicalDocumentDeletePayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS logical document deletions accepted",
|
|
console_ns.models[KnowledgeFSBulkDeletionAcceptedResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.bulk_delete_logical_documents(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSBulkLogicalDocumentDeletePayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSBulkDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/logical-documents/download-zip")
|
|
class KnowledgeFSSpaceLogicalDocumentsDownloadApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSDocumentBatchDownloadPayload.__name__])
|
|
@console_ns.produces(["application/zip"])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS logical documents ZIP",
|
|
console_ns.models[BinaryFileResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@rbac_permission_required(
|
|
RBACResourceScope.DATASET,
|
|
RBACPermission.DATASET_DOCUMENT_DOWNLOAD,
|
|
resource_required=False,
|
|
)
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSDocumentBatchDownloadPayload)
|
|
descriptors = [
|
|
_console_services().facade.prepare_logical_document_download(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
)
|
|
for document_id in payload.document_ids
|
|
]
|
|
service = KnowledgeFSDownloadService()
|
|
try:
|
|
with ExitStack() as stack:
|
|
zip_path = stack.enter_context(service.build_zip_tempfile(descriptors))
|
|
response = send_file(
|
|
zip_path,
|
|
mimetype="application/zip",
|
|
as_attachment=True,
|
|
download_name="knowledge-documents.zip",
|
|
)
|
|
cleanup = stack.pop_all()
|
|
response.call_on_close(cleanup.close)
|
|
return response
|
|
except KnowledgeFSDownloadTooLargeError as exc:
|
|
raise RequestEntityTooLarge(str(exc)) from exc
|
|
except KnowledgeFSDownloadObjectNotFoundError as exc:
|
|
raise NotFound("KnowledgeFS document object not found") from exc
|
|
except KnowledgeFSDownloadUnavailableError as exc:
|
|
raise ServiceUnavailable("KnowledgeFS object storage is unavailable") from exc
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/logical-documents/<string:document_id>/download")
|
|
class KnowledgeFSSpaceLogicalDocumentDownloadApi(Resource):
|
|
@console_ns.produces(["application/octet-stream"])
|
|
@console_ns.response(HTTPStatus.OK, "KnowledgeFS logical document", console_ns.models[BinaryFileResponse.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@rbac_permission_required(
|
|
RBACResourceScope.DATASET,
|
|
RBACPermission.DATASET_DOCUMENT_DOWNLOAD,
|
|
resource_required=False,
|
|
)
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
descriptor = _console_services().facade.prepare_logical_document_download(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
)
|
|
try:
|
|
body = KnowledgeFSDownloadService().load_stream(descriptor)
|
|
except KnowledgeFSDownloadObjectNotFoundError as exc:
|
|
raise NotFound("KnowledgeFS document object not found") from exc
|
|
except KnowledgeFSDownloadUnavailableError as exc:
|
|
raise ServiceUnavailable("KnowledgeFS object storage is unavailable") from exc
|
|
response = Response(body, content_type=descriptor.mime_type or "application/octet-stream")
|
|
response.content_length = descriptor.size_bytes
|
|
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{quote(descriptor.filename, safe='')}"
|
|
response.headers["ETag"] = f'"{descriptor.sha256}"'
|
|
return response
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/logical-documents/<string:document_id>")
|
|
class KnowledgeFSSpaceLogicalDocumentApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS logical document",
|
|
console_ns.models[KnowledgeFSLogicalDocumentResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_logical_document(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
)
|
|
return dump_response(KnowledgeFSLogicalDocumentResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSDocumentAvailabilityPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS logical document availability updated",
|
|
console_ns.models[KnowledgeFSLogicalDocumentResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.update_logical_document_availability(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
payload=_payload(KnowledgeFSDocumentAvailabilityPayload),
|
|
)
|
|
return dump_response(KnowledgeFSLogicalDocumentResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSLogicalDocumentDeletePayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS logical document deletion accepted",
|
|
console_ns.models[KnowledgeFSDurableDeletionAcceptedResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.delete_logical_document(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
payload=_payload(KnowledgeFSLogicalDocumentDeletePayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSDurableDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/documents")
|
|
class KnowledgeFSSpaceDocumentsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS documents",
|
|
console_ns.models[KnowledgeFSDocumentListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_documents(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
)
|
|
return dump_response(KnowledgeFSDocumentListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSDocumentStagedUploadPayload.__name__])
|
|
@console_ns.doc(
|
|
description=(
|
|
"Claim a workspace-staged upload. Multipart file bodies remain accepted as a legacy compatibility path."
|
|
)
|
|
)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS document accepted for processing",
|
|
console_ns.models[KnowledgeFSDocumentStagedUploadAcceptedResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
if request.is_json:
|
|
result = _staged_uploads().claim(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSDocumentStagedUploadPayload),
|
|
)
|
|
return (
|
|
dump_response(KnowledgeFSDocumentStagedUploadAcceptedResponse, result),
|
|
HTTPStatus.ACCEPTED,
|
|
)
|
|
legacy_result = _console_services().facade.create_document(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
body_reader=_read_document_upload,
|
|
)
|
|
return dump_response(KnowledgeFSDocumentUploadAcceptedResponse, legacy_result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/metadata")
|
|
class KnowledgeFSSpaceMetadataApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSMetadataFieldListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS metadata fields",
|
|
console_ns.models[KnowledgeFSMetadataFieldListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = query_params_from_request(KnowledgeFSMetadataFieldListQuery)
|
|
result = _console_services().facade.list_metadata_fields(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSMetadataFieldListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSMetadataFieldCreatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS metadata field created",
|
|
console_ns.models[KnowledgeFSMetadataFieldResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.create_metadata_field(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSMetadataFieldCreatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSMetadataFieldResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/metadata/<string:field_id>")
|
|
class KnowledgeFSSpaceMetadataFieldApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSMetadataFieldUpdatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS metadata field updated",
|
|
console_ns.models[KnowledgeFSMetadataFieldResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str, field_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.update_metadata_field(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
field_id=field_id,
|
|
payload=_payload(KnowledgeFSMetadataFieldUpdatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSMetadataFieldResponse, result)
|
|
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSMetadataFieldDeleteQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS metadata field deleted",
|
|
console_ns.models[KnowledgeFSMetadataFieldDeleteResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, field_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = query_params_from_request(KnowledgeFSMetadataFieldDeleteQuery)
|
|
result = _console_services().facade.delete_metadata_field(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
field_id=field_id,
|
|
expected_row_version=query.expected_row_version,
|
|
)
|
|
return dump_response(KnowledgeFSMetadataFieldDeleteResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/documents/bulk")
|
|
class KnowledgeFSSpaceBulkDocumentsApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSBulkDocumentDeletePayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS document deletions accepted",
|
|
console_ns.models[KnowledgeFSBulkDeletionAcceptedResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.bulk_delete_documents(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSBulkDocumentDeletePayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSBulkDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/documents/reindex")
|
|
class KnowledgeFSSpaceDocumentReindexApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSDocumentReindexPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document reindex queued",
|
|
console_ns.models[KnowledgeFSDocumentReindexResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.reindex_documents(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSDocumentReindexPayload),
|
|
)
|
|
return dump_response(KnowledgeFSDocumentReindexResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/documents/<string:document_id>")
|
|
class KnowledgeFSSpaceDocumentApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document",
|
|
console_ns.models[KnowledgeFSDocumentResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_document(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
)
|
|
return dump_response(KnowledgeFSDocumentResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSDocumentMetadataPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document metadata updated",
|
|
console_ns.models[KnowledgeFSLogicalDocumentResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.update_document_metadata(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
payload=_payload(KnowledgeFSDocumentMetadataPayload),
|
|
)
|
|
return dump_response(KnowledgeFSLogicalDocumentResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSDocumentDeletePayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS document deletion accepted",
|
|
console_ns.models[KnowledgeFSDurableDeletionAcceptedResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.delete_document(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
payload=_payload(KnowledgeFSDocumentDeletePayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSDurableDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/documents/<string:document_id>/outline")
|
|
class KnowledgeFSSpaceDocumentOutlineApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document outline",
|
|
console_ns.models[KnowledgeFSDocumentOutlineResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_document_outline(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
)
|
|
return dump_response(KnowledgeFSDocumentOutlineResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/documents/<string:document_id>/multimodal")
|
|
class KnowledgeFSSpaceDocumentMultimodalManifestApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document multimodal manifest",
|
|
console_ns.models[KnowledgeFSDocumentMultimodalManifestResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
manifest = _console_services().facade.get_document_multimodal_manifest(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
)
|
|
return dump_response(
|
|
KnowledgeFSDocumentMultimodalManifestResponse,
|
|
_public_multimodal_manifest(
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
manifest=manifest,
|
|
),
|
|
)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/documents/<string:document_id>/multimodal/<path:item_id>/asset"
|
|
)
|
|
class KnowledgeFSSpaceDocumentMultimodalAssetApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSDocumentMultimodalAssetQuery))
|
|
@console_ns.produces(["application/octet-stream", "image/gif", "image/jpeg", "image/png", "image/webp"])
|
|
@console_ns.response(
|
|
HTTPStatus.OK, "KnowledgeFS document multimodal asset", console_ns.models[BinaryFileResponse.__name__]
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str, item_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSDocumentMultimodalAssetQuery.model_validate(request.args.to_dict(flat=True))
|
|
manifest = _console_services().facade.get_document_multimodal_manifest(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
)
|
|
if manifest.document_asset_id != document_id:
|
|
raise NotFound("KnowledgeFS document multimodal asset not found")
|
|
item = next((candidate for candidate in manifest.items if candidate.id == item_id), None)
|
|
asset_ref = _multimodal_asset_ref(item, query.variant) if item else None
|
|
if asset_ref is None or not asset_ref.object_key:
|
|
raise NotFound("KnowledgeFS document multimodal asset not found")
|
|
object_key = asset_ref.object_key
|
|
if not _multimodal_object_key_is_scoped(
|
|
document_id=document_id,
|
|
knowledge_space_id=manifest.knowledge_space_id,
|
|
object_key=object_key,
|
|
tenant_id=tenant_id,
|
|
):
|
|
raise NotFound("KnowledgeFS document multimodal asset not found")
|
|
|
|
object_storage = KnowledgeFSObjectStorageService()
|
|
try:
|
|
metadata = object_storage.head_object(key=object_key)
|
|
except KnowledgeFSObjectStorageError as exc:
|
|
raise ServiceUnavailable("KnowledgeFS object storage is unavailable") from exc
|
|
if metadata is None:
|
|
raise NotFound("KnowledgeFS document multimodal asset not found")
|
|
if metadata.size_bytes > _DOCUMENT_MULTIMODAL_ASSET_MAX_BYTES:
|
|
raise RequestEntityTooLarge("KnowledgeFS document multimodal asset is too large")
|
|
|
|
content_type = (asset_ref.content_type or metadata.content_type or "").strip().lower()
|
|
inline = content_type in _INLINE_MULTIMODAL_CONTENT_TYPES
|
|
descriptor = KnowledgeFSDocumentDownloadDescriptor(
|
|
document_id=item_id,
|
|
filename=f"multimodal-{item_id}",
|
|
mime_type=content_type or "application/octet-stream",
|
|
object_key=object_key,
|
|
sha256=asset_ref.sha256 or metadata.checksum_sha256_base64,
|
|
size_bytes=metadata.size_bytes,
|
|
)
|
|
try:
|
|
body = KnowledgeFSDownloadService(object_storage=object_storage).load_stream(descriptor)
|
|
except KnowledgeFSDownloadObjectNotFoundError as exc:
|
|
raise NotFound("KnowledgeFS document multimodal asset not found") from exc
|
|
except KnowledgeFSDownloadUnavailableError as exc:
|
|
raise ServiceUnavailable("KnowledgeFS object storage is unavailable") from exc
|
|
|
|
response = Response(
|
|
body,
|
|
content_type=content_type if inline else "application/octet-stream",
|
|
direct_passthrough=True,
|
|
)
|
|
response.content_length = metadata.size_bytes
|
|
response.headers["Cache-Control"] = "private, max-age=300"
|
|
response.headers["Content-Disposition"] = "inline" if inline else "attachment"
|
|
response.headers["Content-Security-Policy"] = "default-src 'none'; sandbox"
|
|
response.headers["ETag"] = f'"{descriptor.sha256}"'
|
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
return response
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/documents/<string:document_id>/revisions")
|
|
class KnowledgeFSSpaceDocumentRevisionsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document revisions",
|
|
console_ns.models[KnowledgeFSDocumentRevisionListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_document_revisions(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
cursor=query.cursor,
|
|
)
|
|
return dump_response(KnowledgeFSDocumentRevisionListResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/documents/<string:document_id>/revisions/<int:revision>/chunks"
|
|
)
|
|
class KnowledgeFSSpaceDocumentChunksApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSDocumentChunkListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document chunks",
|
|
console_ns.models[KnowledgeFSDocumentChunkListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str, revision: int):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSDocumentChunkListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_document_chunks(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
revision=revision,
|
|
cursor=query.cursor,
|
|
query_text=query.query,
|
|
)
|
|
return dump_response(KnowledgeFSDocumentChunkListResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/documents/<string:document_id>/revisions/<int:revision>/chunks/<string:chunk_id>"
|
|
)
|
|
class KnowledgeFSSpaceDocumentChunkApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS document chunk",
|
|
console_ns.models[KnowledgeFSDocumentChunkResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, document_id: str, revision: int, chunk_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_document_chunk(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
document_id=document_id,
|
|
revision=revision,
|
|
chunk_id=chunk_id,
|
|
)
|
|
return dump_response(KnowledgeFSDocumentChunkResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/jobs/<string:job_id>")
|
|
class KnowledgeFSSpaceCompilationJobApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS compilation job",
|
|
console_ns.models[KnowledgeFSDocumentCompilationJobResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, job_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_compilation_job(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id
|
|
)
|
|
return dump_response(KnowledgeFSDocumentCompilationJobResponse, result)
|
|
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS compilation job canceled",
|
|
console_ns.models[KnowledgeFSDocumentCompilationJobResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, job_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.cancel_compilation_job(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id
|
|
)
|
|
return dump_response(KnowledgeFSDocumentCompilationJobResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/jobs/<string:job_id>/retry")
|
|
class KnowledgeFSSpaceCompilationJobRetryApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS compilation job retried",
|
|
console_ns.models[KnowledgeFSDocumentCompilationJobResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, job_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.retry_compilation_job(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id
|
|
)
|
|
return dump_response(KnowledgeFSDocumentCompilationJobResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/bulk-jobs/<string:job_id>")
|
|
class KnowledgeFSSpaceBulkJobApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS bulk job",
|
|
console_ns.models[KnowledgeFSBulkJobResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, job_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_bulk_job(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id
|
|
)
|
|
return dump_response(KnowledgeFSBulkJobResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/background-tasks")
|
|
class KnowledgeFSSpaceBackgroundTasksApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSBackgroundTaskListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS background tasks",
|
|
console_ns.models[KnowledgeFSBackgroundTaskListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSBackgroundTaskListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_background_tasks(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSBackgroundTaskListResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/background-tasks/<string:task_kind>/<string:task_id>/cancel"
|
|
)
|
|
class KnowledgeFSSpaceBackgroundTaskCancelApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS background task canceled",
|
|
console_ns.models[KnowledgeFSBackgroundTaskResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, task_kind: str, task_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.cancel_background_task(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
task_kind=_BACKGROUND_TASK_KIND_ADAPTER.validate_python(task_kind),
|
|
task_id=task_id,
|
|
)
|
|
return dump_response(KnowledgeFSBackgroundTaskResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/background-tasks/<string:task_kind>/<string:task_id>/retry"
|
|
)
|
|
class KnowledgeFSSpaceBackgroundTaskRetryApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS background task retried",
|
|
console_ns.models[KnowledgeFSBackgroundTaskResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, task_kind: str, task_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.retry_background_task(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
task_kind=_BACKGROUND_TASK_KIND_ADAPTER.validate_python(task_kind),
|
|
task_id=task_id,
|
|
)
|
|
return dump_response(KnowledgeFSBackgroundTaskResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-providers")
|
|
class KnowledgeFSSourceProvidersApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source providers",
|
|
console_ns.models[KnowledgeFSSourceProviderListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.list_source_providers(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
)
|
|
return dump_response(KnowledgeFSSourceProviderListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-connections")
|
|
class KnowledgeFSSourceConnectionsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSSourceConnectionListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source connections",
|
|
console_ns.models[KnowledgeFSSourceConnectionListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSSourceConnectionListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_source_connections(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSSourceConnectionListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceConnectionCreatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS source connection created",
|
|
console_ns.models[KnowledgeFSSourceConnectionResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.create_source_connection(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSSourceConnectionCreatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSSourceConnectionResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-connections/<string:connection_id>/refresh")
|
|
class KnowledgeFSSourceConnectionRefreshApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceConnectionRefreshPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source connection refreshed",
|
|
console_ns.models[KnowledgeFSSourceConnectionResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, connection_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.refresh_source_connection(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
connection_id=connection_id,
|
|
payload=_payload(KnowledgeFSSourceConnectionRefreshPayload),
|
|
)
|
|
return dump_response(KnowledgeFSSourceConnectionResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources")
|
|
class KnowledgeFSSpaceSourcesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSSourceListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS sources",
|
|
console_ns.models[KnowledgeFSSourceListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSSourceListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_sources(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSSourceListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceCreatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS source created",
|
|
console_ns.models[KnowledgeFSSourceResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.create_source(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSSourceCreatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSSourceResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>")
|
|
class KnowledgeFSSpaceSourceApi(Resource):
|
|
@console_ns.response(HTTPStatus.OK, "KnowledgeFS source", console_ns.models[KnowledgeFSSourceResponse.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_source(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id
|
|
)
|
|
return dump_response(KnowledgeFSSourceResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceUpdatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK, "KnowledgeFS source updated", console_ns.models[KnowledgeFSSourceResponse.__name__]
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSSourceUpdatePayload)
|
|
facade = _console_services().facade
|
|
selection = payload.selection
|
|
desired_sync_policy = payload.sync_policy
|
|
current_source = (
|
|
facade.get_source(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
)
|
|
if selection is not None
|
|
else None
|
|
)
|
|
requires_import = current_source is not None and _source_edit_requires_import(current_source, payload)
|
|
source_metadata = payload.metadata
|
|
if requires_import and selection is not None and selection.kind != "website_crawl":
|
|
source_metadata = {**(source_metadata or {}), "__knowledgeFsProviderSelection": None}
|
|
source_payload = payload.model_copy(
|
|
update={
|
|
"metadata": source_metadata,
|
|
"selection": None,
|
|
"sync_policy": None,
|
|
**({"status": "disabled", "sync_after_update": False} if requires_import else {}),
|
|
}
|
|
)
|
|
has_source_update = any(
|
|
value is not None
|
|
for value in (
|
|
source_payload.metadata,
|
|
source_payload.name,
|
|
source_payload.provider_parameters,
|
|
source_payload.status,
|
|
source_payload.uri,
|
|
)
|
|
)
|
|
if has_source_update:
|
|
result = facade.update_source(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=source_payload,
|
|
)
|
|
else:
|
|
result = facade.get_source(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
)
|
|
|
|
if requires_import and selection is not None:
|
|
if desired_sync_policy is None:
|
|
raise ValueError("syncPolicy is required when selection is provided")
|
|
import_payload = KnowledgeFSAsyncSourceImportPayload.model_validate(
|
|
{
|
|
**selection.model_dump(mode="json", by_alias=True),
|
|
"kind": {
|
|
"website_crawl": "website-crawl-import",
|
|
"online_document": "online-document-import",
|
|
"online_drive": "online-drive-import",
|
|
}[selection.kind],
|
|
"syncPolicy": desired_sync_policy.model_dump(mode="json", by_alias=True),
|
|
}
|
|
).root
|
|
workflow = commit_source_import(
|
|
facade=facade,
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=import_payload,
|
|
idempotency_key=f"source-edit:{source_id}:{result.version}",
|
|
)
|
|
result = facade.get_source(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
)
|
|
result.sync_workflow = workflow
|
|
elif desired_sync_policy is not None:
|
|
try:
|
|
current_policy = facade.get_source_sync_policy(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
)
|
|
expected_revision = current_policy.revision
|
|
except KnowledgeFSProductResourceNotFoundError:
|
|
expected_revision = 0
|
|
result.sync_policy = facade.update_source_sync_policy(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=KnowledgeFSSourceSyncPolicyPayload(
|
|
enabled=desired_sync_policy.enabled,
|
|
mode=desired_sync_policy.mode,
|
|
customIntervalSeconds=desired_sync_policy.custom_interval_seconds,
|
|
expectedRevision=expected_revision,
|
|
expectedSourceVersion=result.version,
|
|
),
|
|
)
|
|
return dump_response(KnowledgeFSSourceResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceDeletePayload.__name__])
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSSourceDeleteQuery) | _IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS source deletion accepted",
|
|
console_ns.models[KnowledgeFSDurableDeletionAcceptedResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSSourceDeleteQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.delete_source(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=_payload(KnowledgeFSSourceDeletePayload),
|
|
documents=query.documents,
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSDurableDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/test")
|
|
class KnowledgeFSSpaceSourceTestApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source credential test",
|
|
console_ns.models[KnowledgeFSSourceCredentialTestResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.test_source(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id
|
|
)
|
|
return dump_response(KnowledgeFSSourceCredentialTestResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/sync")
|
|
class KnowledgeFSSpaceSourceSyncApi(Resource):
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS source sync accepted",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.sync_source(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/crawl-preview")
|
|
class KnowledgeFSSpaceSourceCrawlPreviewApi(Resource):
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS source crawl preview accepted",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.preview_source_crawl(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/crawl-import")
|
|
class KnowledgeFSSpaceSourceCrawlImportApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSCrawlImportPayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS selected website crawl import accepted",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.import_selected_source_crawl(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=_payload(KnowledgeFSCrawlImportPayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/workflow-imports")
|
|
class KnowledgeFSSpaceSourceWorkflowImportApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceWorkflowImportPayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS durable provider import accepted",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.import_source_workflow(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=_payload(KnowledgeFSSourceWorkflowImportPayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/sync-policy")
|
|
class KnowledgeFSSpaceSourceSyncPolicyApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source sync policy",
|
|
console_ns.models[KnowledgeFSSourceSyncPolicyResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_source_sync_policy(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
)
|
|
return dump_response(KnowledgeFSSourceSyncPolicyResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceSyncPolicyPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source sync policy updated",
|
|
console_ns.models[KnowledgeFSSourceSyncPolicyResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def put(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.update_source_sync_policy(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=_payload(KnowledgeFSSourceSyncPolicyPayload),
|
|
)
|
|
return dump_response(KnowledgeFSSourceSyncPolicyResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-workflows/<string:run_id>")
|
|
class KnowledgeFSSourceWorkflowApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source workflow",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, run_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_source_workflow(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
run_id=run_id,
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-workflows/<string:run_id>/cancel")
|
|
class KnowledgeFSSourceWorkflowCancelApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceWorkflowCancelPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source workflow canceled",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, run_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.cancel_source_workflow(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
run_id=run_id,
|
|
payload=_payload(KnowledgeFSSourceWorkflowCancelPayload),
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-workflows/<string:run_id>/retry")
|
|
class KnowledgeFSSourceWorkflowRetryApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS source workflow retried",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, run_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = retry_or_resume_source_workflow(
|
|
facade=_console_services().facade,
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
run_id=run_id,
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-workflows/<string:run_id>/pages")
|
|
class KnowledgeFSSourceWorkflowPagesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSCrawlPreviewPageListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS crawl preview pages",
|
|
console_ns.models[KnowledgeFSCrawlPreviewPageListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, run_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSCrawlPreviewPageListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_crawl_preview_pages(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
run_id=run_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSCrawlPreviewPageListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/source-workflows/<string:run_id>/selection")
|
|
class KnowledgeFSSourceWorkflowSelectionApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSCrawlPreviewSelectionPayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS crawl preview selection accepted",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, run_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.select_crawl_preview_pages(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
run_id=run_id,
|
|
payload=_payload(KnowledgeFSCrawlPreviewSelectionPayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/async-import")
|
|
class KnowledgeFSSourceAsyncImportApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSAsyncSourceImportPayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS Source import accepted for asynchronous reconciliation",
|
|
console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSAsyncSourceImportPayload)
|
|
result = commit_source_import(
|
|
facade=_console_services().facade,
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=payload.root,
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/pages")
|
|
class KnowledgeFSSpaceSourcePagesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSSourcePagesQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK, "KnowledgeFS source pages", console_ns.models[KnowledgeFSSourcePagesResponse.__name__]
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSSourcePagesQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_source_pages(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSSourcePagesResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/import")
|
|
class KnowledgeFSSpaceSourcePageImportApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceImportPagesPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK, "KnowledgeFS source pages imported", console_ns.models[KnowledgeFSSourceImportResponse.__name__]
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.import_source_pages(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=_payload(KnowledgeFSSourceImportPagesPayload),
|
|
)
|
|
return dump_response(KnowledgeFSSourceImportResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/files")
|
|
class KnowledgeFSSpaceSourceFilesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSSourceFilesQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK, "KnowledgeFS source files", console_ns.models[KnowledgeFSSourceFilesResponse.__name__]
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSSourceFilesQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_source_files(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
query=_query_pairs(query),
|
|
)
|
|
return dump_response(KnowledgeFSSourceFilesResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/sources/<string:source_id>/import-files")
|
|
class KnowledgeFSSpaceSourceFileImportApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSSourceImportFilesPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK, "KnowledgeFS source files imported", console_ns.models[KnowledgeFSSourceImportResponse.__name__]
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, source_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.import_source_files(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
source_id=source_id,
|
|
payload=_payload(KnowledgeFSSourceImportFilesPayload),
|
|
)
|
|
return dump_response(KnowledgeFSSourceImportResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/queries")
|
|
class KnowledgeFSSpaceQueriesApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSQueryCreatePayload.__name__])
|
|
@console_ns.doc(deprecated=True)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS query accepted",
|
|
console_ns.models[KnowledgeFSQueryResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
_ = control_space_id
|
|
raise KnowledgeFSOperationUnavailableError(
|
|
"Buffered KnowledgeFS query creation is deprecated; use the queries/admission streaming BFF flow"
|
|
)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/queries/admission")
|
|
class KnowledgeFSSpaceQueryAdmissionApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSQueryCreatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS streaming query admitted through Dify API",
|
|
console_ns.models[KnowledgeFSQueryAdmissionResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSQueryCreatePayload)
|
|
validate_query_image_references(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
upload_file_ids=[image.upload_file_id for image in getattr(payload, "query_images", ())],
|
|
mark_used=True,
|
|
)
|
|
issued = _console_services().broker.issue_interactive(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
operation_id="createQuery",
|
|
)
|
|
admitted_request = KnowledgeFSAdmittedQueryRequest.model_validate(
|
|
{
|
|
**payload.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
"knowledgeSpaceId": issued.knowledge_space_id,
|
|
}
|
|
)
|
|
return dump_response(
|
|
KnowledgeFSQueryAdmissionResponse,
|
|
KnowledgeFSQueryAdmissionResponse(
|
|
token=issued.token,
|
|
expires_at=issued.expires_at,
|
|
operation_id="createQuery",
|
|
request=admitted_request,
|
|
url=_console_api_url(_QUERY_STREAM_PROXY_PATH),
|
|
),
|
|
)
|
|
|
|
|
|
@console_ns.route(_QUERY_STREAM_PROXY_PATH)
|
|
class KnowledgeFSQueryStreamProxyApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSAdmittedQueryRequest.__name__])
|
|
@console_ns.doc(produces=["text/event-stream"])
|
|
@console_ns.response(HTTPStatus.OK, "KnowledgeFS query event stream")
|
|
@_knowledge_fs_errors
|
|
def post(self):
|
|
# This endpoint intentionally authenticates with the short-lived, resource-scoped
|
|
# Capability v2 token issued by /queries/admission. Browser session cookies are
|
|
# neither required nor forwarded to KnowledgeFS.
|
|
capability_token, trace_id = _stream_capability()
|
|
payload = _payload(KnowledgeFSAdmittedQueryRequest)
|
|
upstream = _console_services().facade.stream_query(
|
|
capability_token=capability_token,
|
|
trace_id=trace_id,
|
|
payload=payload,
|
|
)
|
|
return _stream_response(upstream)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/research-tasks")
|
|
class KnowledgeFSSpaceResearchTasksApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS research tasks",
|
|
console_ns.models[KnowledgeFSResearchTaskListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_research_tasks(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
)
|
|
return dump_response(KnowledgeFSResearchTaskListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSResearchTaskCreatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS research task accepted",
|
|
console_ns.models[KnowledgeFSResearchTaskResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSResearchTaskCreatePayload)
|
|
validate_query_image_references(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
upload_file_ids=[image.upload_file_id for image in getattr(payload, "query_images", ())],
|
|
mark_used=True,
|
|
)
|
|
result = _console_services().facade.create_research_task(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=payload,
|
|
)
|
|
return dump_response(KnowledgeFSResearchTaskResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/research-tasks/plan")
|
|
class KnowledgeFSSpaceResearchTaskPlanApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSResearchTaskPlanPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS research task plan",
|
|
console_ns.models[KnowledgeFSResearchTaskPlanResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSResearchTaskPlanPayload)
|
|
validate_query_image_references(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
upload_file_ids=[image.upload_file_id for image in getattr(payload, "query_images", ())],
|
|
mark_used=False,
|
|
)
|
|
result = _console_services().facade.plan_research_task(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=payload,
|
|
)
|
|
return dump_response(KnowledgeFSResearchTaskPlanResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/research-tasks/<string:task_id>")
|
|
class KnowledgeFSSpaceResearchTaskApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS research task",
|
|
console_ns.models[KnowledgeFSResearchTaskResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, task_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_research_task(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, task_id=task_id
|
|
)
|
|
return dump_response(KnowledgeFSResearchTaskResponse, result)
|
|
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS research task canceled",
|
|
console_ns.models[KnowledgeFSResearchTaskResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, task_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.cancel_research_task(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, task_id=task_id
|
|
)
|
|
return dump_response(KnowledgeFSResearchTaskResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/research-tasks/<string:task_id>/partials")
|
|
class KnowledgeFSSpaceResearchTaskPartialsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSResearchTaskPartialsQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS research task partial evidence",
|
|
console_ns.models[KnowledgeFSResearchTaskPartialListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, task_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSResearchTaskPartialsQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_research_task_partials(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
task_id=task_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSResearchTaskPartialListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/traces")
|
|
class KnowledgeFSSpaceTracesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS traces",
|
|
console_ns.models[KnowledgeFSTraceListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_traces(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
)
|
|
return dump_response(KnowledgeFSTraceListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/golden-questions")
|
|
class KnowledgeFSSpaceGoldenQuestionsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSQualityListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS golden questions",
|
|
console_ns.models[KnowledgeFSGoldenQuestionListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSQualityListQuery.model_validate(request.args.to_dict(flat=True))
|
|
result = _console_services().facade.list_golden_questions(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSGoldenQuestionListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS golden question created",
|
|
console_ns.models[KnowledgeFSGoldenQuestionResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.create_golden_question(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSGoldenQuestionPayload),
|
|
)
|
|
return dump_response(KnowledgeFSGoldenQuestionResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/golden-questions/evidence-matches")
|
|
class KnowledgeFSSpaceGoldenQuestionEvidenceMatchesApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionEvidenceMatchPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS golden question evidence matches",
|
|
console_ns.models[KnowledgeFSGoldenQuestionEvidenceMatchResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.match_golden_question_evidence(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSGoldenQuestionEvidenceMatchPayload),
|
|
)
|
|
return dump_response(KnowledgeFSGoldenQuestionEvidenceMatchResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/golden-questions/bulk-import")
|
|
class KnowledgeFSSpaceGoldenQuestionBulkImportApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionBulkImportPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS golden questions imported",
|
|
console_ns.models[KnowledgeFSGoldenQuestionBulkImportResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.bulk_import_golden_questions(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSGoldenQuestionBulkImportPayload),
|
|
)
|
|
return dump_response(KnowledgeFSGoldenQuestionBulkImportResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/golden-questions/<string:question_id>")
|
|
class KnowledgeFSSpaceGoldenQuestionApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS golden question updated",
|
|
console_ns.models[KnowledgeFSGoldenQuestionResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str, question_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.update_golden_question(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
question_id=question_id,
|
|
payload=_payload(KnowledgeFSGoldenQuestionPayload),
|
|
)
|
|
return dump_response(KnowledgeFSGoldenQuestionResponse, result)
|
|
|
|
@console_ns.response(HTTPStatus.NO_CONTENT, "KnowledgeFS golden question deleted")
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def delete(self, control_space_id: str, question_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
_console_services().facade.delete_golden_question(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
question_id=question_id,
|
|
)
|
|
return Response(status=HTTPStatus.NO_CONTENT)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/quality/bad-cases")
|
|
class KnowledgeFSSpaceBadCasesApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSQualityListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS bad cases",
|
|
console_ns.models[KnowledgeFSBadCaseListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSQualityListQuery.model_validate(request.args.to_dict(flat=True))
|
|
result = _console_services().facade.list_bad_cases(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSBadCaseListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSBadCaseCreatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS bad case created",
|
|
console_ns.models[KnowledgeFSBadCaseResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.create_bad_case(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSBadCaseCreatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSBadCaseResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/quality/replay-runs")
|
|
class KnowledgeFSSpaceQualityReplayApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSQualityReplayListQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS quality replay history",
|
|
console_ns.models[KnowledgeFSQualityReplayListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSQualityReplayListQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_quality_replays(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
mode=query.mode,
|
|
state=query.state,
|
|
)
|
|
return dump_response(KnowledgeFSQualityReplayListResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSQualityReplayPayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.ACCEPTED,
|
|
"KnowledgeFS quality replay queued",
|
|
console_ns.models[KnowledgeFSQualityReplayResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.create_quality_replay(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSQualityReplayPayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSQualityReplayResponse, result), HTTPStatus.ACCEPTED
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/quality/replay-runs/<string:run_id>")
|
|
class KnowledgeFSSpaceQualityReplayDetailApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSQualityReplayDetailQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS quality replay run",
|
|
console_ns.models[KnowledgeFSQualityReplayResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, run_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSQualityReplayDetailQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.get_quality_replay(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
run_id=run_id,
|
|
evidence_item_id=str(query.evidence_item_id) if query.evidence_item_id else None,
|
|
)
|
|
return dump_response(KnowledgeFSQualityReplayResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/quality/bad-cases/<string:bad_case_id>/trace-reference"
|
|
)
|
|
class KnowledgeFSSpaceBadCaseTraceReferenceApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS bad case trace reference",
|
|
console_ns.models[KnowledgeFSBadCaseTraceReferenceResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, bad_case_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_bad_case_trace_reference(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
bad_case_id=bad_case_id,
|
|
)
|
|
return dump_response(KnowledgeFSBadCaseTraceReferenceResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/quality/bad-cases/<string:bad_case_id>")
|
|
class KnowledgeFSSpaceBadCaseApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS bad case",
|
|
console_ns.models[KnowledgeFSBadCaseResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, bad_case_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_bad_case(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
bad_case_id=bad_case_id,
|
|
)
|
|
return dump_response(KnowledgeFSBadCaseResponse, result)
|
|
|
|
@console_ns.expect(console_ns.models[KnowledgeFSBadCaseUpdatePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS bad case updated",
|
|
console_ns.models[KnowledgeFSBadCaseResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def patch(self, control_space_id: str, bad_case_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.update_bad_case(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
bad_case_id=bad_case_id,
|
|
payload=_payload(KnowledgeFSBadCaseUpdatePayload),
|
|
)
|
|
return dump_response(KnowledgeFSBadCaseResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/traces/<string:trace_id>")
|
|
class KnowledgeFSSpaceTraceApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK, "KnowledgeFS answer trace", console_ns.models[KnowledgeFSAnswerTraceResponse.__name__]
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, trace_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.get_trace(
|
|
tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, trace_id=trace_id
|
|
)
|
|
return dump_response(KnowledgeFSAnswerTraceResponse, result)
|
|
|
|
|
|
def _trace_entries(*, control_space_id: str, trace_id: str, kind: Literal["conflicts", "evidence", "missing"]):
|
|
actor_id, tenant_id = _actor()
|
|
query = KnowledgeFSTraceEntriesQuery.model_validate(request.args.to_dict())
|
|
result = _console_services().facade.list_trace_entries(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
trace_id=trace_id,
|
|
kind=kind,
|
|
cursor=query.cursor,
|
|
limit=query.limit,
|
|
)
|
|
return dump_response(KnowledgeFSTraceEntryListResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/traces/<string:trace_id>/evidence")
|
|
class KnowledgeFSSpaceTraceEvidenceApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSTraceEntriesQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS trace evidence view",
|
|
console_ns.models[KnowledgeFSTraceEntryListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, trace_id: str):
|
|
return _trace_entries(control_space_id=control_space_id, trace_id=trace_id, kind="evidence")
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/traces/<string:trace_id>/conflicts")
|
|
class KnowledgeFSSpaceTraceConflictsApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSTraceEntriesQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS trace conflicts",
|
|
console_ns.models[KnowledgeFSTraceEntryListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, trace_id: str):
|
|
return _trace_entries(control_space_id=control_space_id, trace_id=trace_id, kind="conflicts")
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/traces/<string:trace_id>/missing")
|
|
class KnowledgeFSSpaceTraceMissingApi(Resource):
|
|
@console_ns.doc(params=query_params_from_model(KnowledgeFSTraceEntriesQuery))
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS trace missing evidence",
|
|
console_ns.models[KnowledgeFSTraceEntryListResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@_knowledge_fs_errors
|
|
def get(self, control_space_id: str, trace_id: str):
|
|
return _trace_entries(control_space_id=control_space_id, trace_id=trace_id, kind="missing")
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/upload-sessions",
|
|
)
|
|
class KnowledgeFSSpaceUploadSessionsApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSUploadSessionCreatePayload.__name__])
|
|
@console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.CREATED,
|
|
"KnowledgeFS upload session created",
|
|
console_ns.models[KnowledgeFSUploadSessionCreateResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.create_upload_session(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
payload=_payload(KnowledgeFSUploadSessionCreatePayload),
|
|
idempotency_key=_idempotency_key(),
|
|
)
|
|
return dump_response(KnowledgeFSUploadSessionCreateResponse, result), HTTPStatus.CREATED
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/upload-sessions/"
|
|
"<string:upload_session_id>/parts/<int:part_number>/presign",
|
|
)
|
|
class KnowledgeFSSpaceUploadSessionPartPresignApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSUploadPartPresignPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS upload part URL created",
|
|
console_ns.models[KnowledgeFSPresignedUploadResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, upload_session_id: str, part_number: int):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.presign_upload_session_part(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
upload_session_id=upload_session_id,
|
|
part_number=part_number,
|
|
payload=_payload(KnowledgeFSUploadPartPresignPayload),
|
|
)
|
|
return dump_response(KnowledgeFSPresignedUploadResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/upload-sessions/<string:upload_session_id>/complete",
|
|
)
|
|
class KnowledgeFSSpaceUploadSessionCompleteApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSUploadSessionCompletePayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS upload session completed",
|
|
console_ns.models[KnowledgeFSUploadSessionMutationResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, upload_session_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.complete_upload_session(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
upload_session_id=upload_session_id,
|
|
payload=_payload(KnowledgeFSUploadSessionCompletePayload),
|
|
)
|
|
return dump_response(KnowledgeFSUploadSessionMutationResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/upload-sessions/<string:upload_session_id>/abort",
|
|
)
|
|
class KnowledgeFSSpaceUploadSessionAbortApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSUploadSessionAbortPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS upload session aborted",
|
|
console_ns.models[KnowledgeFSUploadSessionMutationResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, upload_session_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.abort_upload_session(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
upload_session_id=upload_session_id,
|
|
payload=_payload(KnowledgeFSUploadSessionAbortPayload),
|
|
)
|
|
return dump_response(KnowledgeFSUploadSessionMutationResponse, result)
|
|
|
|
|
|
@console_ns.route(
|
|
"/knowledge-fs/spaces/<string:control_space_id>/upload-sessions/<string:upload_session_id>/small-file"
|
|
)
|
|
class KnowledgeFSSpaceSmallFileUploadApi(Resource):
|
|
@console_ns.doc(consumes=["multipart/form-data"], params=_SMALL_FILE_UPLOAD_PARAMS)
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS small-file fallback completed",
|
|
console_ns.models[KnowledgeFSSmallFileUploadResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str, upload_session_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
result = _console_services().facade.upload_small_file(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
upload_session_id=upload_session_id,
|
|
body_reader=_read_small_file_body,
|
|
)
|
|
return dump_response(KnowledgeFSSmallFileUploadResponse, result)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/spaces/<string:control_space_id>/query-stream-capability")
|
|
class KnowledgeFSSpaceQueryStreamCapabilityApi(Resource):
|
|
# Legacy alternative to /queries/admission. Both represent one independently admitted
|
|
# createQuery operation; clients must use one endpoint and never chain them.
|
|
@console_ns.doc(deprecated=True)
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS Dify API query stream capability",
|
|
console_ns.models[KnowledgeFSQueryStreamCapabilityResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, control_space_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
issued = _console_services().broker.issue_interactive(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=control_space_id,
|
|
operation_id="createQuery",
|
|
)
|
|
return dump_response(
|
|
KnowledgeFSQueryStreamCapabilityResponse,
|
|
KnowledgeFSQueryStreamCapabilityResponse(
|
|
token=issued.token,
|
|
expires_at=issued.expires_at,
|
|
operation_id="createQuery",
|
|
url=_console_api_url(_QUERY_STREAM_PROXY_PATH),
|
|
),
|
|
)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/tasks/<string:task_id>/stream-capability")
|
|
class KnowledgeFSTaskStreamCapabilityApi(Resource):
|
|
@console_ns.expect(console_ns.models[KnowledgeFSStreamCapabilityPayload.__name__])
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS task stream capability",
|
|
console_ns.models[KnowledgeFSStreamCapabilityResponse.__name__],
|
|
)
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@cloud_edition_billing_rate_limit_check("knowledge")
|
|
@_knowledge_fs_errors
|
|
def post(self, task_id: str):
|
|
actor_id, tenant_id = _actor()
|
|
payload = _payload(KnowledgeFSStreamCapabilityPayload)
|
|
issued = _console_services().broker.issue_interactive(
|
|
tenant_id=tenant_id,
|
|
account_id=actor_id,
|
|
control_space_id=payload.control_space_id,
|
|
operation_id="streamResearchTask",
|
|
resource_id=task_id,
|
|
)
|
|
stream_url = _research_task_events_url(
|
|
task_id=task_id,
|
|
knowledge_space_id=issued.knowledge_space_id,
|
|
)
|
|
return dump_response(
|
|
KnowledgeFSStreamCapabilityResponse,
|
|
KnowledgeFSStreamCapabilityResponse(
|
|
token=issued.token,
|
|
expires_at=issued.expires_at,
|
|
operation_id="streamResearchTask",
|
|
url=stream_url,
|
|
),
|
|
)
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/research-tasks/<string:task_id>/events")
|
|
class KnowledgeFSResearchTaskStreamProxyApi(Resource):
|
|
@console_ns.doc(
|
|
params=query_params_from_model(KnowledgeFSResearchTaskStreamQuery),
|
|
produces=["text/event-stream"],
|
|
)
|
|
@console_ns.response(HTTPStatus.OK, "KnowledgeFS research task event stream")
|
|
@_knowledge_fs_errors
|
|
def get(self, task_id: str):
|
|
capability_token, trace_id = _stream_capability()
|
|
stream_query = KnowledgeFSResearchTaskStreamQuery.model_validate(request.args.to_dict(flat=True))
|
|
upstream = _console_services().facade.stream_research_task(
|
|
capability_token=capability_token,
|
|
trace_id=trace_id,
|
|
task_id=task_id,
|
|
knowledge_space_id=stream_query.knowledge_space_id,
|
|
cursor=stream_query.cursor,
|
|
limit=stream_query.limit,
|
|
)
|
|
return _stream_response(upstream)
|
|
|
|
|
|
def _research_task_events_url(*, task_id: str, knowledge_space_id: str) -> str:
|
|
path = f"/knowledge-fs/research-tasks/{quote(task_id, safe='')}/events"
|
|
query = urlencode({"knowledgeSpaceId": knowledge_space_id})
|
|
return f"{_console_api_url(path)}?{query}"
|
|
|
|
|
|
@console_ns.route("/knowledge-fs/.well-known/jwks.json")
|
|
class KnowledgeFSJWKSApi(Resource):
|
|
@console_ns.response(
|
|
HTTPStatus.OK,
|
|
"KnowledgeFS Capability v2 public keys",
|
|
console_ns.models[KnowledgeFSJWKSResponse.__name__],
|
|
)
|
|
def get(self) -> Response:
|
|
if not dify_config.KNOWLEDGE_FS_CAPABILITY_V2_ENABLED:
|
|
raise NotFound()
|
|
session_maker = session_factory.get_session_maker()
|
|
try:
|
|
issuer = create_configured_knowledge_fs_capability_issuer(
|
|
audit=SQLAlchemyKnowledgeFSCapabilityIssuanceAuditor(session_maker)
|
|
)
|
|
except KnowledgeFSCapabilityConfigurationError as exc:
|
|
raise ServiceUnavailable("KnowledgeFS capability issuance is not configured") from exc
|
|
if issuer is None:
|
|
raise NotFound()
|
|
payload = KnowledgeFSJWKSResponse.model_validate(issuer.public_jwks()).model_dump(mode="json")
|
|
response = jsonify(payload)
|
|
response.headers["Cache-Control"] = (
|
|
f"public, max-age={dify_config.KNOWLEDGE_FS_JWKS_CACHE_MAX_AGE_SECONDS}, must-revalidate"
|
|
)
|
|
return response
|
|
|
|
|
|
__all__ = ["KnowledgeFSRuntime"]
|