OpenRAG 0.8.0.dev21__tar.gz → 0.8.1.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openrag-0.8.0.dev21/src/openrag.egg-info → openrag-0.8.1.dev1}/PKG-INFO +10 -7
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/pyproject.toml +15 -7
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/agent.py +141 -91
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/connectors.py +74 -2
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/docling.py +26 -3
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/langflow_files.py +17 -4
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/models.py +151 -23
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/provider_health.py +237 -3
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/provider_validation.py +273 -44
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/router.py +97 -6
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/settings/endpoints.py +140 -18
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/settings/langflow_sync.py +5 -1
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/settings/models.py +60 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/upload.py +30 -5
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/documents.py +4 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/llm.py +44 -8
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/search.py +13 -8
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/lifespan.py +7 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/routes/internal.py +6 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/config_manager.py +228 -49
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/model_providers.py +13 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/model_providers.yaml +41 -21
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/settings.py +205 -11
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/service.py +10 -2
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/migrations_runtime.py +1 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/session_ownership.py +2 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/permission_repo.py +3 -6
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/session_ownership_repo.py +32 -12
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/dependencies.py +9 -2
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/models/processors.py +340 -211
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/models/url.py +3 -0
- openrag-0.8.1.dev1/src/observability/litellm_langfuse.py +89 -0
- openrag-0.8.1.dev1/src/observability/spans.py +185 -0
- openrag-0.8.1.dev1/src/observability/tracing.py +221 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1/src/openrag.egg-info}/PKG-INFO +10 -7
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/openrag.egg-info/SOURCES.txt +4 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/openrag.egg-info/requires.txt +10 -6
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/chat_service.py +19 -4
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/conversation_persistence_service.py +24 -33
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/default_docs_service.py +6 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/docling_service.py +328 -23
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/document_service.py +4 -8
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/flows_service.py +68 -23
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/langflow_file_service.py +37 -12
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/langflow_llm_token_service.py +18 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/langflow_mcp_service.py +1 -0
- openrag-0.8.1.dev1/src/services/llm_gateway.py +2131 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/model_catalog.py +118 -20
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/provider_error_log.py +5 -1
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/search_service.py +148 -74
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/session_ownership_service.py +182 -23
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/status_checks.py +44 -17
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/status_service.py +5 -1
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/task_service.py +126 -7
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/_assets/docker-compose.yml +17 -5
- openrag-0.8.1.dev1/src/tui/_assets/flows/ingestion_flow.json +5154 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/_assets/flows/openrag_agent.json +2 -2
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/_assets/flows/openrag_nudges.json +2 -2
- openrag-0.8.1.dev1/src/tui/_assets/flows/openrag_url_mcp.json +6830 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/config_fields.py +18 -3
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/managers/env_manager.py +16 -3
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/document_processing.py +63 -2
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/langflow_headers.py +5 -2
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/langflow_utils.py +1 -0
- openrag-0.8.1.dev1/src/utils/upload_limits.py +167 -0
- openrag-0.8.0.dev21/src/services/llm_gateway.py +0 -1128
- openrag-0.8.0.dev21/src/tui/_assets/flows/ingestion_flow.json +0 -5145
- openrag-0.8.0.dev21/src/tui/_assets/flows/openrag_url_mcp.json +0 -6823
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/LICENSE +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/MANIFEST.in +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/README.md +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/setup.cfg +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/acl.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/auth.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/chat.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/config.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/documents.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/files.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/flows.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/health.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/ingest_preview.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/keys.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/knowledge_filter.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/langflow_ingest.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/nudges.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/oidc.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/schemas/status.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/schemas/tasks.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/search.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/settings/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/settings/helpers.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/status_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/tasks.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/users.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/_filter_resolution.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/chat.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/files.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/knowledge_filters.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/models.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/settings.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v1/status.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v2/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/api/v2/files.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/container.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/factory.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/middleware.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/router_app.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/routes/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/routes/mcp.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/routes/public_v1.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/app/routes/public_v2.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/auth/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/auth/ibm_auth.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/auth/jwt_roles.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/auth/permission_dependencies.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/auth/request_identity.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/auth/user_identity_cache.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/auth_context.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/bootstrap.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/embedding_constants.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/legacy_prompts.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/model_constants.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/paths.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/storage_mode.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/config/utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/aws_s3/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/aws_s3/api.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/aws_s3/auth.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/aws_s3/connector.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/aws_s3/models.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/aws_s3/support.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/base.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/chunk_cleanup.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/connection_manager.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/google_drive/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/google_drive/connector.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/google_drive/oauth.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/google_drive_acl.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/microsoft_graph_acl.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/microsoft_oauth_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/onedrive/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/onedrive/connector.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/onedrive/oauth.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/registry.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/sharepoint/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/sharepoint/connector.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/sharepoint/oauth.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/connectors/sharepoint/utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/base.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/engine.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/api_key.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/audit_log.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/conversation.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/migration_status.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/permission.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/role.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/role_permission.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/user.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/user_preferences.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/user_role.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/models/workspace_config.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/_helpers.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/api_key_repo.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/audit_repo.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/conversation_repo.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/preferences_repo.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/role_repo.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/user_repo.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/repositories/workspace_config_repo.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/seed.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/db/types.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/main.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/mcp_http/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/mcp_http/server.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/models/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/models/tasks.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/observability/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/observability/instana_boot.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/openrag.egg-info/dependency_links.txt +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/openrag.egg-info/entry_points.txt +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/openrag.egg-info/top_level.txt +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/api_key_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/auth_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/component_logs.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/connector_access_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/connector_oauth_config_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/default_role_sync.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/dev_role_toggle.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/dls_principal_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/docling_polling_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/document_index_writer.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/file_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/file_service_v2.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/group_acl_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/ingest_preview_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/knowledge_filter_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/langflow_history_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/langflow_ingest_token_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/models_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/monitor_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/onboarding_reset.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/rbac_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/startup_orchestrator.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/status_diagnostics.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/user_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/services/workspace_config_service.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/session_manager.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/_assets/docker-compose.gpu.yml +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/_assets/openrag-documents/docling.pdf +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/_assets/openrag-documents/ibm_anthropic.pdf +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/_assets/openrag-documents/warmup_ocr.pdf +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/cli.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/main.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/managers/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/managers/container_manager.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/managers/docling_manager.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/screens/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/screens/config.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/screens/diagnostics.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/screens/logs.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/screens/monitor.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/screens/welcome.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/utils/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/utils/clipboard.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/utils/platform.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/utils/startup_checks.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/utils/validation.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/utils/version_check.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/command_modal.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/diagnostics_notification.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/error_notification.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/factory_reset_warning_modal.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/flow_backup_warning_modal.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/prune_options_modal.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/upgrade_instructions_modal.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/version_mismatch_warning_modal.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/tui/widgets/waves.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/acl_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/container_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/embedding_fields.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/embeddings.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/encryption.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/env_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/file_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/filename_claims.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/gpu_detection.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/group_acl.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/hash_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/ingest_preview_flag.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/jwt_keygen.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/jwt_verification.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/logging_config.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/opensearch_delete.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/opensearch_filenames.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/opensearch_init.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/opensearch_queries.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/opensearch_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/paths.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/provider_health_cache.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/run_mode_utils.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/telemetry/__init__.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/telemetry/category.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/telemetry/client.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/telemetry/message_id.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/url_content_fetcher.py +0 -0
- {openrag-0.8.0.dev21 → openrag-0.8.1.dev1}/src/utils/version_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrag
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.1.dev1
|
|
4
4
|
Summary: OpenRAG is a comprehensive Retrieval-Augmented Generation platform that enables intelligent document search and AI-powered conversations.
|
|
5
5
|
Classifier: Development Status :: 4 - Beta
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: agentd>=0.8.7
|
|
19
19
|
Requires-Dist: aiofiles>=24.1.0
|
|
20
|
-
Requires-Dist: authlib<2.0.0,>=1.
|
|
20
|
+
Requires-Dist: authlib<2.0.0,>=1.8.0
|
|
21
21
|
Requires-Dist: aiohttp>=3.14.1
|
|
22
22
|
Requires-Dist: cryptography>=48.0.1
|
|
23
23
|
Requires-Dist: click>=8.3.3
|
|
@@ -29,7 +29,7 @@ Requires-Dist: msal>=1.29.0
|
|
|
29
29
|
Requires-Dist: httpx>=0.27.0
|
|
30
30
|
Requires-Dist: idna>=3.18
|
|
31
31
|
Requires-Dist: opensearch-py[async]>=3.0.0
|
|
32
|
-
Requires-Dist: pyjwt>=2.
|
|
32
|
+
Requires-Dist: pyjwt>=2.15.1
|
|
33
33
|
Requires-Dist: python-multipart>=0.0.31
|
|
34
34
|
Requires-Dist: fastapi>=0.115.0
|
|
35
35
|
Requires-Dist: starlette>=1.6.0
|
|
@@ -45,19 +45,20 @@ Requires-Dist: textual-fspicker>=0.6.0
|
|
|
45
45
|
Requires-Dist: structlog>=25.4.0
|
|
46
46
|
Requires-Dist: zxcvbn>=4.5.0
|
|
47
47
|
Requires-Dist: prometheus-fastapi-instrumentator>=8.0.0
|
|
48
|
-
Requires-Dist: litellm<2.0.0,>=1.
|
|
48
|
+
Requires-Dist: litellm<2.0.0,>=1.105.0
|
|
49
49
|
Requires-Dist: joserfc<2.0.0,>=1.7.3
|
|
50
50
|
Requires-Dist: pyyaml>=6.0
|
|
51
51
|
Requires-Dist: tiktoken>=0.7.0
|
|
52
52
|
Requires-Dist: fastmcp>=3.4.0
|
|
53
|
-
Requires-Dist: mcp>=1.
|
|
54
|
-
Requires-Dist: openai
|
|
53
|
+
Requires-Dist: mcp>=1.30.0
|
|
54
|
+
Requires-Dist: openai<3,>=2.24.0
|
|
55
55
|
Requires-Dist: tenacity>=9.1.4
|
|
56
56
|
Requires-Dist: pygments>=2.20.0
|
|
57
57
|
Requires-Dist: pyasn1>=0.6.4
|
|
58
58
|
Requires-Dist: protobuf<8,>=7.35.0
|
|
59
59
|
Requires-Dist: requests>=2.33.0
|
|
60
|
-
Requires-Dist: urllib3>=2.
|
|
60
|
+
Requires-Dist: urllib3>=2.8.0
|
|
61
|
+
Requires-Dist: fsspec>=2026.6.0
|
|
61
62
|
Requires-Dist: sqlmodel>=0.0.22
|
|
62
63
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.36
|
|
63
64
|
Requires-Dist: aiosqlite>=0.20.0
|
|
@@ -66,6 +67,8 @@ Requires-Dist: asyncpg>=0.30.0
|
|
|
66
67
|
Requires-Dist: cachetools>=5.5.0
|
|
67
68
|
Provides-Extra: apm
|
|
68
69
|
Requires-Dist: instana<4,>=3.17.0; extra == "apm"
|
|
70
|
+
Provides-Extra: tracing
|
|
71
|
+
Requires-Dist: langfuse<5,>=4.16; extra == "tracing"
|
|
69
72
|
Dynamic: license-file
|
|
70
73
|
|
|
71
74
|
<div align="center">
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openrag"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.1.dev1"
|
|
8
8
|
description = "OpenRAG is a comprehensive Retrieval-Augmented Generation platform that enables intelligent document search and AI-powered conversations."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
dependencies = [
|
|
24
24
|
"agentd>=0.8.7",
|
|
25
25
|
"aiofiles>=24.1.0",
|
|
26
|
-
"authlib>=1.
|
|
26
|
+
"authlib>=1.8.0,<2.0.0",
|
|
27
27
|
"aiohttp>=3.14.1",
|
|
28
28
|
"cryptography>=48.0.1",
|
|
29
29
|
"click>=8.3.3",
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
"httpx>=0.27.0",
|
|
36
36
|
"idna>=3.18",
|
|
37
37
|
"opensearch-py[async]>=3.0.0",
|
|
38
|
-
"pyjwt>=2.
|
|
38
|
+
"pyjwt>=2.15.1",
|
|
39
39
|
"python-multipart>=0.0.31",
|
|
40
40
|
"fastapi>=0.115.0",
|
|
41
41
|
"starlette>=1.6.0",
|
|
@@ -51,19 +51,20 @@ dependencies = [
|
|
|
51
51
|
"structlog>=25.4.0",
|
|
52
52
|
"zxcvbn>=4.5.0",
|
|
53
53
|
"prometheus-fastapi-instrumentator>=8.0.0",
|
|
54
|
-
"litellm>=1.
|
|
54
|
+
"litellm>=1.105.0,<2.0.0",
|
|
55
55
|
"joserfc>=1.7.3,<2.0.0",
|
|
56
56
|
"pyyaml>=6.0",
|
|
57
57
|
"tiktoken>=0.7.0",
|
|
58
58
|
"fastmcp>=3.4.0",
|
|
59
|
-
"mcp>=1.
|
|
60
|
-
"openai
|
|
59
|
+
"mcp>=1.30.0",
|
|
60
|
+
"openai>=2.24.0,<3",
|
|
61
61
|
"tenacity>=9.1.4",
|
|
62
62
|
"pygments>=2.20.0",
|
|
63
63
|
"pyasn1>=0.6.4",
|
|
64
64
|
"protobuf>=7.35.0,<8",
|
|
65
65
|
"requests>=2.33.0",
|
|
66
|
-
"urllib3>=2.
|
|
66
|
+
"urllib3>=2.8.0",
|
|
67
|
+
"fsspec>=2026.6.0",
|
|
67
68
|
"sqlmodel>=0.0.22",
|
|
68
69
|
"sqlalchemy[asyncio]>=2.0.36",
|
|
69
70
|
"aiosqlite>=0.20.0",
|
|
@@ -83,6 +84,9 @@ dependencies = [
|
|
|
83
84
|
# be adopted explicitly, not resolved into an install.
|
|
84
85
|
# src/main.py imports it defensively, so its absence downgrades to a warning.
|
|
85
86
|
apm = ["instana>=3.17.0,<4"]
|
|
87
|
+
tracing = [
|
|
88
|
+
"langfuse>=4.16,<5",
|
|
89
|
+
]
|
|
86
90
|
|
|
87
91
|
[dependency-groups]
|
|
88
92
|
dev = [
|
|
@@ -92,6 +96,10 @@ dev = [
|
|
|
92
96
|
"pytest-cov>=4.0.0",
|
|
93
97
|
"ruff>=0.15.12",
|
|
94
98
|
"mypy>=2.0.0",
|
|
99
|
+
# The tracing extra, so its unit tests (tests/unit/observability) run.
|
|
100
|
+
"langfuse>=4.16,<5",
|
|
101
|
+
# Used by the Langflow Split Text component, so its unit tests run the real splitter.
|
|
102
|
+
"langchain-text-splitters>=1.1.2,<2",
|
|
95
103
|
]
|
|
96
104
|
|
|
97
105
|
[project.scripts]
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import uuid
|
|
3
|
+
from contextlib import aclosing
|
|
2
4
|
from typing import Any
|
|
3
5
|
|
|
6
|
+
import anyio
|
|
7
|
+
|
|
4
8
|
from config.config_manager import DEFAULT_SYSTEM_PROMPT
|
|
5
9
|
from services.conversation_persistence_service import conversation_persistence
|
|
6
10
|
from utils.langflow_utils import parse_knowledge_chunks, strip_untrusted_fence_recursive
|
|
@@ -91,13 +95,10 @@ async def _persist_error_conversation(
|
|
|
91
95
|
|
|
92
96
|
from services.session_ownership_service import session_ownership_service
|
|
93
97
|
|
|
98
|
+
if not await session_ownership_service.claim_session(user_id, store_id):
|
|
99
|
+
raise RuntimeError("Unable to establish chat session ownership")
|
|
94
100
|
conversation_state["last_activity"] = datetime.now()
|
|
95
101
|
await store_conversation_thread(user_id, store_id, conversation_state)
|
|
96
|
-
try:
|
|
97
|
-
await session_ownership_service.claim_session(user_id, store_id)
|
|
98
|
-
logger.debug(f"Claimed session {store_id} for user {user_id} after stream error")
|
|
99
|
-
except Exception as e:
|
|
100
|
-
logger.warning(f"Failed to claim session ownership after stream error: {e}")
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
def _stream_error_chunk(message: str, response_id: str | None = None) -> bytes:
|
|
@@ -228,6 +229,7 @@ async def async_response_stream(
|
|
|
228
229
|
):
|
|
229
230
|
logger.info("User prompt received", prompt=prompt)
|
|
230
231
|
|
|
232
|
+
response = None
|
|
231
233
|
try:
|
|
232
234
|
# Build request parameters
|
|
233
235
|
request_params = {
|
|
@@ -422,6 +424,10 @@ async def async_response_stream(
|
|
|
422
424
|
except Exception:
|
|
423
425
|
logger.exception("[AGENT] Streaming failed")
|
|
424
426
|
raise
|
|
427
|
+
finally:
|
|
428
|
+
if response is not None:
|
|
429
|
+
with anyio.CancelScope(shield=True):
|
|
430
|
+
await response.close()
|
|
425
431
|
|
|
426
432
|
|
|
427
433
|
# Generic async response function for non-streaming
|
|
@@ -494,15 +500,18 @@ async def async_stream(
|
|
|
494
500
|
previous_response_id: str = None,
|
|
495
501
|
log_prefix: str = "response",
|
|
496
502
|
):
|
|
497
|
-
async
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
503
|
+
async with aclosing(
|
|
504
|
+
async_response_stream(
|
|
505
|
+
client,
|
|
506
|
+
prompt,
|
|
507
|
+
model,
|
|
508
|
+
extra_headers=extra_headers,
|
|
509
|
+
previous_response_id=previous_response_id,
|
|
510
|
+
log_prefix=log_prefix,
|
|
511
|
+
)
|
|
512
|
+
) as stream:
|
|
513
|
+
async for chunk in stream:
|
|
514
|
+
yield chunk
|
|
506
515
|
|
|
507
516
|
|
|
508
517
|
# Async langflow function (non-streaming only)
|
|
@@ -718,6 +727,7 @@ async def async_chat_stream(
|
|
|
718
727
|
await _persist_error_conversation(user_id, store_id, conversation_state)
|
|
719
728
|
except Exception as store_error:
|
|
720
729
|
logger.error(f"Failed to store error conversation: {store_error}")
|
|
730
|
+
store_id = None
|
|
721
731
|
yield _stream_error_chunk(display_text, store_id)
|
|
722
732
|
|
|
723
733
|
|
|
@@ -850,18 +860,13 @@ async def async_langflow_chat(
|
|
|
850
860
|
|
|
851
861
|
# Store the conversation thread with its response_id
|
|
852
862
|
if response_id:
|
|
863
|
+
from services.session_ownership_service import session_ownership_service
|
|
864
|
+
|
|
865
|
+
if not await session_ownership_service.claim_session(user_id, response_id):
|
|
866
|
+
raise RuntimeError("Unable to establish chat session ownership")
|
|
853
867
|
conversation_state["last_activity"] = datetime.now()
|
|
854
868
|
await store_conversation_thread(user_id, response_id, conversation_state)
|
|
855
869
|
|
|
856
|
-
# Claim session ownership for this user
|
|
857
|
-
try:
|
|
858
|
-
from services.session_ownership_service import session_ownership_service
|
|
859
|
-
|
|
860
|
-
await session_ownership_service.claim_session(user_id, response_id)
|
|
861
|
-
logger.debug(f"Claimed session {response_id} for user {user_id}")
|
|
862
|
-
except Exception as e:
|
|
863
|
-
logger.warning(f"Failed to claim session ownership: {e}")
|
|
864
|
-
|
|
865
870
|
logger.debug(
|
|
866
871
|
"Stored langflow conversation thread",
|
|
867
872
|
user_id=user_id,
|
|
@@ -894,6 +899,16 @@ async def async_langflow_chat_stream(
|
|
|
894
899
|
# conversation_id keeps the OpenRAG sidebar thread; previous_response_id chains
|
|
895
900
|
# the Langflow session. After errors the client omits previous_response_id.
|
|
896
901
|
thread_id = conversation_id or previous_response_id
|
|
902
|
+
if not _conversation_thread_in_memory(user_id, thread_id):
|
|
903
|
+
# Clients may continue using the latest Langflow id without a sidebar id.
|
|
904
|
+
thread_id = next(
|
|
905
|
+
(
|
|
906
|
+
stored_id
|
|
907
|
+
for stored_id, state in active_conversations.get(user_id, {}).items()
|
|
908
|
+
if thread_id and thread_id in state.get("response_ids", [])
|
|
909
|
+
),
|
|
910
|
+
thread_id,
|
|
911
|
+
)
|
|
897
912
|
previous_loaded_from_memory = _conversation_thread_in_memory(user_id, thread_id)
|
|
898
913
|
conversation_state = get_conversation_thread(user_id, thread_id)
|
|
899
914
|
|
|
@@ -915,30 +930,74 @@ async def async_langflow_chat_stream(
|
|
|
915
930
|
usage_data = None
|
|
916
931
|
collected_chunks = [] # Store all chunks for function call data
|
|
917
932
|
error_occurred = False
|
|
933
|
+
store_id = None
|
|
934
|
+
claimed_ids: set[str] = set()
|
|
935
|
+
turn_finished = False
|
|
936
|
+
|
|
937
|
+
async def persist_stream_state(*session_ids, mint_if_missing=False):
|
|
938
|
+
nonlocal store_id
|
|
939
|
+
from services.session_ownership_service import session_ownership_service
|
|
940
|
+
|
|
941
|
+
if store_id is None:
|
|
942
|
+
store_id = _resolve_conversation_store_id(
|
|
943
|
+
response_id,
|
|
944
|
+
thread_id,
|
|
945
|
+
previous_loaded_from_memory=previous_loaded_from_memory,
|
|
946
|
+
mint_if_missing=mint_if_missing,
|
|
947
|
+
)
|
|
948
|
+
if store_id is None:
|
|
949
|
+
return
|
|
950
|
+
for session_id in dict.fromkeys((store_id, *session_ids)):
|
|
951
|
+
if not session_id or session_id in claimed_ids:
|
|
952
|
+
continue
|
|
953
|
+
if not await session_ownership_service.claim_session(
|
|
954
|
+
user_id, session_id, conversation_id=store_id
|
|
955
|
+
):
|
|
956
|
+
raise RuntimeError("Unable to establish chat session ownership")
|
|
957
|
+
claimed_ids.add(session_id)
|
|
958
|
+
response_ids = conversation_state.setdefault("response_ids", [])
|
|
959
|
+
if session_id not in response_ids:
|
|
960
|
+
response_ids.append(session_id)
|
|
961
|
+
conversation_state["last_activity"] = datetime.now()
|
|
962
|
+
await store_conversation_thread(user_id, store_id, conversation_state)
|
|
963
|
+
|
|
964
|
+
upstream = async_stream(
|
|
965
|
+
langflow_client,
|
|
966
|
+
prompt,
|
|
967
|
+
flow_id,
|
|
968
|
+
extra_headers=extra_headers,
|
|
969
|
+
previous_response_id=langflow_previous_response_id,
|
|
970
|
+
log_prefix="langflow",
|
|
971
|
+
)
|
|
918
972
|
|
|
919
973
|
try:
|
|
920
|
-
async for chunk in
|
|
921
|
-
langflow_client,
|
|
922
|
-
prompt,
|
|
923
|
-
flow_id,
|
|
924
|
-
extra_headers=extra_headers,
|
|
925
|
-
previous_response_id=langflow_previous_response_id,
|
|
926
|
-
log_prefix="langflow",
|
|
927
|
-
):
|
|
974
|
+
async for chunk in upstream:
|
|
928
975
|
# Extract text content to build full response for history
|
|
976
|
+
chunk_ids = []
|
|
929
977
|
try:
|
|
930
978
|
import json
|
|
931
979
|
|
|
932
980
|
chunk_data = json.loads(chunk.decode("utf-8"))
|
|
933
981
|
collected_chunks.append(chunk_data) # Collect all chunk data
|
|
934
982
|
|
|
983
|
+
# Own every response/session ID callers can use for continuation,
|
|
984
|
+
# not tool item IDs. OpenAI events may nest the ID in response.
|
|
985
|
+
response_obj = chunk_data.get("response")
|
|
986
|
+
if not isinstance(response_obj, dict):
|
|
987
|
+
response_obj = {}
|
|
988
|
+
chunk_ids = [
|
|
989
|
+
value
|
|
990
|
+
for value in (
|
|
991
|
+
chunk_data.get("id"),
|
|
992
|
+
chunk_data.get("response_id"),
|
|
993
|
+
response_obj.get("id"),
|
|
994
|
+
)
|
|
995
|
+
if isinstance(value, str) and value
|
|
996
|
+
]
|
|
997
|
+
if chunk_ids:
|
|
998
|
+
response_id = chunk_ids[0]
|
|
935
999
|
if "delta" in chunk_data and "content" in chunk_data["delta"]:
|
|
936
1000
|
full_response += chunk_data["delta"]["content"]
|
|
937
|
-
# Extract response_id from chunk
|
|
938
|
-
if "id" in chunk_data:
|
|
939
|
-
response_id = chunk_data["id"]
|
|
940
|
-
elif "response_id" in chunk_data:
|
|
941
|
-
response_id = chunk_data["response_id"]
|
|
942
1001
|
|
|
943
1002
|
# Check for error status. Do not forward bare Langflow error
|
|
944
1003
|
# chunks — they often lack a useful message and the client would
|
|
@@ -972,6 +1031,10 @@ async def async_langflow_chat_stream(
|
|
|
972
1031
|
usage_data = response_obj.get("usage")
|
|
973
1032
|
except Exception:
|
|
974
1033
|
pass
|
|
1034
|
+
# This await must stay outside the permissive JSON parser above.
|
|
1035
|
+
# Persist/claim BEFORE yielding the first occurrence of any ID.
|
|
1036
|
+
if any(session_id not in claimed_ids for session_id in chunk_ids):
|
|
1037
|
+
await persist_stream_state(*chunk_ids)
|
|
975
1038
|
yield chunk
|
|
976
1039
|
|
|
977
1040
|
from api.provider_validation import (
|
|
@@ -994,16 +1057,8 @@ async def async_langflow_chat_stream(
|
|
|
994
1057
|
"error": True,
|
|
995
1058
|
}
|
|
996
1059
|
)
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
thread_id,
|
|
1000
|
-
previous_loaded_from_memory=previous_loaded_from_memory,
|
|
1001
|
-
mint_if_missing=True,
|
|
1002
|
-
)
|
|
1003
|
-
try:
|
|
1004
|
-
await _persist_error_conversation(user_id, store_id, conversation_state)
|
|
1005
|
-
except Exception as store_error:
|
|
1006
|
-
logger.error(f"Failed to store error conversation: {store_error}")
|
|
1060
|
+
turn_finished = True
|
|
1061
|
+
await persist_stream_state(response_id, mint_if_missing=True)
|
|
1007
1062
|
yield _stream_error_chunk(display_text, store_id)
|
|
1008
1063
|
return
|
|
1009
1064
|
|
|
@@ -1020,20 +1075,29 @@ async def async_langflow_chat_stream(
|
|
|
1020
1075
|
assistant_message["response_data"] = {"usage": usage_data}
|
|
1021
1076
|
conversation_state["messages"].append(assistant_message)
|
|
1022
1077
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1078
|
+
turn_finished = True
|
|
1079
|
+
await persist_stream_state(response_id)
|
|
1080
|
+
except (asyncio.CancelledError, GeneratorExit):
|
|
1081
|
+
# Disconnects do not run the normal tail and are not provider failures.
|
|
1082
|
+
# Keep exactly one partial assistant turn and propagate cancellation;
|
|
1083
|
+
# never replay the prompt or synthesize a terminal event to a dead client.
|
|
1084
|
+
with anyio.CancelScope(shield=True):
|
|
1085
|
+
if not turn_finished and full_response:
|
|
1086
|
+
conversation_state["messages"].append(
|
|
1087
|
+
{
|
|
1088
|
+
"role": "assistant",
|
|
1089
|
+
"content": full_response,
|
|
1090
|
+
"response_id": response_id,
|
|
1091
|
+
"timestamp": datetime.now(),
|
|
1092
|
+
"chunks": collected_chunks,
|
|
1093
|
+
"interrupted": True,
|
|
1094
|
+
}
|
|
1095
|
+
)
|
|
1031
1096
|
try:
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
logger.warning(f"Failed to claim session ownership: {e}")
|
|
1097
|
+
await persist_stream_state(response_id)
|
|
1098
|
+
except Exception:
|
|
1099
|
+
logger.exception("Failed to store interrupted Langflow turn")
|
|
1100
|
+
raise
|
|
1037
1101
|
except Exception as e:
|
|
1038
1102
|
logger.error(f"Error in langflow chat stream: {e}", exc_info=True)
|
|
1039
1103
|
from api.provider_validation import resolve_chat_stream_error_message_async
|
|
@@ -1051,17 +1115,16 @@ async def async_langflow_chat_stream(
|
|
|
1051
1115
|
"error": True,
|
|
1052
1116
|
}
|
|
1053
1117
|
)
|
|
1054
|
-
|
|
1055
|
-
response_id,
|
|
1056
|
-
thread_id,
|
|
1057
|
-
previous_loaded_from_memory=previous_loaded_from_memory,
|
|
1058
|
-
mint_if_missing=True,
|
|
1059
|
-
)
|
|
1118
|
+
error_store_id = None
|
|
1060
1119
|
try:
|
|
1061
|
-
await
|
|
1120
|
+
await persist_stream_state(response_id, mint_if_missing=True)
|
|
1121
|
+
error_store_id = store_id
|
|
1062
1122
|
except Exception as store_error:
|
|
1063
1123
|
logger.error(f"Failed to store error conversation: {store_error}")
|
|
1064
|
-
yield _stream_error_chunk(display_text,
|
|
1124
|
+
yield _stream_error_chunk(display_text, error_store_id)
|
|
1125
|
+
finally:
|
|
1126
|
+
with anyio.CancelScope(shield=True):
|
|
1127
|
+
await upstream.aclose()
|
|
1065
1128
|
|
|
1066
1129
|
|
|
1067
1130
|
async def delete_user_conversation(user_id: str, response_id: str) -> bool:
|
|
@@ -1075,31 +1138,18 @@ async def delete_user_conversation(user_id: str, response_id: str) -> bool:
|
|
|
1075
1138
|
Exception — on unexpected storage errors so callers can distinguish
|
|
1076
1139
|
a confirmed "not found" from a backend failure.
|
|
1077
1140
|
"""
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
# Delete from in-memory storage (cannot raise)
|
|
1081
|
-
if user_id in active_conversations and response_id in active_conversations[user_id]:
|
|
1082
|
-
del active_conversations[user_id][response_id]
|
|
1083
|
-
logger.debug(f"Deleted conversation {response_id} from memory for user {user_id}")
|
|
1084
|
-
deleted = True
|
|
1141
|
+
from services.session_ownership_service import session_ownership_service
|
|
1085
1142
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1143
|
+
session_ids = await session_ownership_service.get_conversation_sessions(user_id, response_id)
|
|
1144
|
+
if not session_ids:
|
|
1145
|
+
return False
|
|
1146
|
+
canonical_id = session_ids[0]
|
|
1147
|
+
# Revoke ALL durable aliases before removing the local index. Failures must
|
|
1148
|
+
# reach the caller, not turn into a successful delete with reusable IDs.
|
|
1149
|
+
await session_ownership_service.release_conversation_sessions(user_id, session_ids)
|
|
1150
|
+
deleted = await conversation_persistence.delete_conversation_thread(user_id, canonical_id)
|
|
1151
|
+
if canonical_id in active_conversations.get(user_id, {}):
|
|
1152
|
+
del active_conversations[user_id][canonical_id]
|
|
1094
1153
|
deleted = True
|
|
1095
1154
|
|
|
1096
|
-
# Release session ownership (best-effort; never masks storage errors above)
|
|
1097
|
-
try:
|
|
1098
|
-
from services.session_ownership_service import session_ownership_service
|
|
1099
|
-
|
|
1100
|
-
await session_ownership_service.release_session(user_id, response_id)
|
|
1101
|
-
logger.debug(f"Released session ownership for {response_id} for user {user_id}")
|
|
1102
|
-
except Exception as e:
|
|
1103
|
-
logger.warning(f"Failed to release session ownership: {e}")
|
|
1104
|
-
|
|
1105
1155
|
return deleted
|
|
@@ -1073,6 +1073,7 @@ async def _expand_selected_connector_files(
|
|
|
1073
1073
|
|
|
1074
1074
|
async def _classify_connector_duplicates(
|
|
1075
1075
|
connector,
|
|
1076
|
+
connector_type: str,
|
|
1076
1077
|
selected_files_raw: list[Any],
|
|
1077
1078
|
session_manager,
|
|
1078
1079
|
user_id: str,
|
|
@@ -1081,6 +1082,14 @@ async def _classify_connector_duplicates(
|
|
|
1081
1082
|
"""Expand connector selections and split them into duplicate/non-duplicate files."""
|
|
1082
1083
|
expanded_files_info = await _expand_selected_connector_files(connector, selected_files_raw)
|
|
1083
1084
|
if not expanded_files_info:
|
|
1085
|
+
# The selection named only folders that expanded to nothing, so there is
|
|
1086
|
+
# nothing to compare. Counted against what the caller asked for, which
|
|
1087
|
+
# separates an empty expansion from an empty request.
|
|
1088
|
+
logger.info(
|
|
1089
|
+
"Duplicate check found nothing to check",
|
|
1090
|
+
connector_type=connector_type,
|
|
1091
|
+
selected=len(selected_files_raw),
|
|
1092
|
+
)
|
|
1084
1093
|
return {
|
|
1085
1094
|
"duplicate_names": [],
|
|
1086
1095
|
"duplicate_files": [],
|
|
@@ -1101,6 +1110,13 @@ async def _classify_connector_duplicates(
|
|
|
1101
1110
|
all_candidates.update(aliases)
|
|
1102
1111
|
|
|
1103
1112
|
if not all_candidates:
|
|
1113
|
+
# Files expanded but none yielded a comparable name, so the index was
|
|
1114
|
+
# never queried: every file is reported new without having been checked.
|
|
1115
|
+
logger.info(
|
|
1116
|
+
"Duplicate check skipped: no comparable filenames",
|
|
1117
|
+
connector_type=connector_type,
|
|
1118
|
+
listed=len(cleaned_files),
|
|
1119
|
+
)
|
|
1104
1120
|
return {
|
|
1105
1121
|
"duplicate_names": [],
|
|
1106
1122
|
"duplicate_files": [],
|
|
@@ -1131,6 +1147,16 @@ async def _classify_connector_duplicates(
|
|
|
1131
1147
|
else:
|
|
1132
1148
|
non_duplicate_files.append(file_info)
|
|
1133
1149
|
|
|
1150
|
+
# See the bucket classifier: logged so a "no dialog appeared" report can be
|
|
1151
|
+
# answered from the server side.
|
|
1152
|
+
logger.info(
|
|
1153
|
+
"Duplicate check complete",
|
|
1154
|
+
connector_type=connector_type,
|
|
1155
|
+
selected=len(cleaned_files),
|
|
1156
|
+
duplicates=len(duplicate_files),
|
|
1157
|
+
new_files=len(non_duplicate_files),
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1134
1160
|
return {
|
|
1135
1161
|
"duplicate_names": list(dict.fromkeys(duplicate_names)),
|
|
1136
1162
|
"duplicate_files": duplicate_files,
|
|
@@ -1335,6 +1361,11 @@ async def _classify_bucket_connector_duplicates(
|
|
|
1335
1361
|
break
|
|
1336
1362
|
|
|
1337
1363
|
if not all_files:
|
|
1364
|
+
logger.info(
|
|
1365
|
+
"Duplicate check found nothing to check",
|
|
1366
|
+
connector_type=connector_type,
|
|
1367
|
+
buckets=bucket_filter,
|
|
1368
|
+
)
|
|
1338
1369
|
return {
|
|
1339
1370
|
"duplicate_names": [],
|
|
1340
1371
|
"duplicate_files": [],
|
|
@@ -1366,18 +1397,38 @@ async def _classify_bucket_connector_duplicates(
|
|
|
1366
1397
|
duplicate_names: list[str] = []
|
|
1367
1398
|
non_duplicate_files: list[dict[str, Any]] = []
|
|
1368
1399
|
total = 0
|
|
1400
|
+
already_synced = 0
|
|
1369
1401
|
for f in all_files:
|
|
1370
1402
|
fid = f.get("id")
|
|
1371
1403
|
if not fid:
|
|
1372
1404
|
continue
|
|
1373
1405
|
total += 1
|
|
1374
1406
|
response_file = _connector_file_response(f, cleaned_name=_cleaned_blob_filename(f))
|
|
1375
|
-
|
|
1407
|
+
id_match = fid in existing_set
|
|
1408
|
+
if id_match:
|
|
1409
|
+
already_synced += 1
|
|
1410
|
+
if id_match or fid in name_taken_ids:
|
|
1376
1411
|
duplicate_files.append(response_file)
|
|
1377
1412
|
duplicate_names.append(response_file["name"])
|
|
1378
1413
|
else:
|
|
1379
1414
|
non_duplicate_files.append(response_file)
|
|
1380
1415
|
|
|
1416
|
+
# Both halves of the answer, so a "the dialog never appeared" report can be
|
|
1417
|
+
# settled from the logs: whether the listing came back, whether the names
|
|
1418
|
+
# were found in the index, and what the caller was told. Without it the only
|
|
1419
|
+
# way to tell a check that found nothing from one that never ran is the
|
|
1420
|
+
# browser's network tab.
|
|
1421
|
+
logger.info(
|
|
1422
|
+
"Duplicate check complete",
|
|
1423
|
+
connector_type=connector_type,
|
|
1424
|
+
buckets=bucket_filter,
|
|
1425
|
+
listed=total,
|
|
1426
|
+
already_synced_here=already_synced,
|
|
1427
|
+
name_taken_elsewhere=len(set(name_taken_ids) - existing_set),
|
|
1428
|
+
duplicates=len(duplicate_files),
|
|
1429
|
+
new_files=len(non_duplicate_files),
|
|
1430
|
+
)
|
|
1431
|
+
|
|
1381
1432
|
return {
|
|
1382
1433
|
"duplicate_names": list(dict.fromkeys(duplicate_names)),
|
|
1383
1434
|
"duplicate_files": duplicate_files,
|
|
@@ -1402,7 +1453,19 @@ async def connector_check_duplicates(
|
|
|
1402
1453
|
|
|
1403
1454
|
selected_files_raw = body.selected_files
|
|
1404
1455
|
if not selected_files_raw and not body.bucket_filter:
|
|
1405
|
-
|
|
1456
|
+
# Returning an empty duplicate list here reads to the caller as "checked,
|
|
1457
|
+
# found nothing", and the UI goes on to ingest without ever offering the
|
|
1458
|
+
# overwrite choice — the same thing a broken check looks like. The
|
|
1459
|
+
# request asked about nothing, so say that instead of answering it.
|
|
1460
|
+
logger.warning(
|
|
1461
|
+
"Duplicate check asked about nothing",
|
|
1462
|
+
connector_type=connector_type,
|
|
1463
|
+
connection_id=body.connection_id,
|
|
1464
|
+
)
|
|
1465
|
+
return JSONResponse(
|
|
1466
|
+
{"error": "Specify selected_files or bucket_filter to check for duplicates"},
|
|
1467
|
+
status_code=400,
|
|
1468
|
+
)
|
|
1406
1469
|
|
|
1407
1470
|
try:
|
|
1408
1471
|
jwt_token = user.jwt_token
|
|
@@ -1458,6 +1521,7 @@ async def connector_check_duplicates(
|
|
|
1458
1521
|
return JSONResponse(
|
|
1459
1522
|
await _classify_connector_duplicates(
|
|
1460
1523
|
connector=connector,
|
|
1524
|
+
connector_type=connector_type,
|
|
1461
1525
|
selected_files_raw=selected_files_raw,
|
|
1462
1526
|
session_manager=session_manager,
|
|
1463
1527
|
user_id=user.user_id,
|
|
@@ -1786,6 +1850,7 @@ async def connector_sync(
|
|
|
1786
1850
|
connector=await connector_service.get_connector(
|
|
1787
1851
|
working_connection.connection_id
|
|
1788
1852
|
),
|
|
1853
|
+
connector_type=connector_type,
|
|
1789
1854
|
selected_files_raw=file_infos,
|
|
1790
1855
|
session_manager=session_manager,
|
|
1791
1856
|
user_id=user.user_id,
|
|
@@ -2341,6 +2406,12 @@ async def connector_webhook(
|
|
|
2341
2406
|
# shared=None like the other re-sync paths: no user is in the
|
|
2342
2407
|
# loop to express an intent, so each file keeps the sharing state
|
|
2343
2408
|
# it already has.
|
|
2409
|
+
# allow_anonymous_delete=False for the same reason: nobody is
|
|
2410
|
+
# here to authorize removing a document the whole instance can
|
|
2411
|
+
# see, so a webhook replaces only what the connection's owner
|
|
2412
|
+
# owns. A file indexed as shared is left as it is until someone
|
|
2413
|
+
# holding the permission syncs it; no connector that delivers
|
|
2414
|
+
# webhooks can index one today.
|
|
2344
2415
|
task_id = await connector_service.sync_specific_files(
|
|
2345
2416
|
connection.connection_id,
|
|
2346
2417
|
connection.user_id,
|
|
@@ -2348,6 +2419,7 @@ async def connector_webhook(
|
|
|
2348
2419
|
jwt_token=jwt_token,
|
|
2349
2420
|
replace_duplicates=_connector_sync_should_replace(connector_type),
|
|
2350
2421
|
shared=None,
|
|
2422
|
+
allow_anonymous_delete=False,
|
|
2351
2423
|
)
|
|
2352
2424
|
|
|
2353
2425
|
result = {
|