openrag 0.4.0.dev6__tar.gz → 0.4.0.dev7__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.
Files changed (165) hide show
  1. {openrag-0.4.0.dev6/src/openrag.egg-info → openrag-0.4.0.dev7}/PKG-INFO +1 -1
  2. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/pyproject.toml +1 -1
  3. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/main.py +17 -0
  4. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7/src/openrag.egg-info}/PKG-INFO +1 -1
  5. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/openrag.egg-info/SOURCES.txt +1 -0
  6. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/flows_service.py +110 -36
  7. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/docker-compose.yml +3 -1
  8. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/ingestion_flow.json +2 -2
  9. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/openrag_agent.json +2 -2
  10. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/openrag_nudges.json +2 -2
  11. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/openrag_url_mcp.json +2 -2
  12. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/config_fields.py +6 -0
  13. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/managers/container_manager.py +1 -1
  14. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/managers/env_manager.py +5 -0
  15. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/screens/config.py +83 -0
  16. openrag-0.4.0.dev7/src/utils/opensearch_filter_merge.py +188 -0
  17. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/LICENSE +0 -0
  18. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/MANIFEST.in +0 -0
  19. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/README.md +0 -0
  20. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/setup.cfg +0 -0
  21. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/agent.py +0 -0
  22. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/__init__.py +0 -0
  23. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/auth.py +0 -0
  24. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/chat.py +0 -0
  25. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/connector_router.py +0 -0
  26. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/connectors.py +0 -0
  27. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/docling.py +0 -0
  28. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/documents.py +0 -0
  29. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/flows.py +0 -0
  30. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/keys.py +0 -0
  31. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/knowledge_filter.py +0 -0
  32. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/langflow_files.py +0 -0
  33. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/models.py +0 -0
  34. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/nudges.py +0 -0
  35. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/oidc.py +0 -0
  36. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/provider_health.py +0 -0
  37. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/provider_validation.py +0 -0
  38. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/router.py +0 -0
  39. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/search.py +0 -0
  40. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/settings.py +0 -0
  41. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/tasks.py +0 -0
  42. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/upload.py +0 -0
  43. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/v1/__init__.py +0 -0
  44. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/v1/chat.py +0 -0
  45. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/v1/documents.py +0 -0
  46. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/v1/knowledge_filters.py +0 -0
  47. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/v1/models.py +0 -0
  48. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/v1/search.py +0 -0
  49. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/api/v1/settings.py +0 -0
  50. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/auth_context.py +0 -0
  51. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/config/__init__.py +0 -0
  52. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/config/config_manager.py +0 -0
  53. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/config/model_constants.py +0 -0
  54. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/config/settings.py +0 -0
  55. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/__init__.py +0 -0
  56. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/aws_s3/__init__.py +0 -0
  57. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/aws_s3/api.py +0 -0
  58. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/aws_s3/auth.py +0 -0
  59. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/aws_s3/connector.py +0 -0
  60. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/aws_s3/models.py +0 -0
  61. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/aws_s3/support.py +0 -0
  62. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/base.py +0 -0
  63. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/connection_manager.py +0 -0
  64. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/google_drive/__init__.py +0 -0
  65. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/google_drive/connector.py +0 -0
  66. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/google_drive/oauth.py +0 -0
  67. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/ibm_cos/__init__.py +0 -0
  68. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/ibm_cos/api.py +0 -0
  69. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/ibm_cos/auth.py +0 -0
  70. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/ibm_cos/connector.py +0 -0
  71. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/ibm_cos/models.py +0 -0
  72. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/ibm_cos/support.py +0 -0
  73. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/langflow_connector_service.py +0 -0
  74. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/onedrive/__init__.py +0 -0
  75. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/onedrive/connector.py +0 -0
  76. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/onedrive/oauth.py +0 -0
  77. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/service.py +0 -0
  78. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/sharepoint/__init__.py +0 -0
  79. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/sharepoint/connector.py +0 -0
  80. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/sharepoint/oauth.py +0 -0
  81. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/connectors/sharepoint/utils.py +0 -0
  82. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/dependencies.py +0 -0
  83. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/models/__init__.py +0 -0
  84. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/models/processors.py +0 -0
  85. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/models/tasks.py +0 -0
  86. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/models/url.py +0 -0
  87. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/openrag.egg-info/dependency_links.txt +0 -0
  88. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/openrag.egg-info/entry_points.txt +0 -0
  89. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/openrag.egg-info/requires.txt +0 -0
  90. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/openrag.egg-info/top_level.txt +0 -0
  91. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/__init__.py +0 -0
  92. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/api_key_service.py +0 -0
  93. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/auth_service.py +0 -0
  94. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/chat_service.py +0 -0
  95. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/conversation_persistence_service.py +0 -0
  96. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/document_service.py +0 -0
  97. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/knowledge_filter_service.py +0 -0
  98. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/langflow_file_service.py +0 -0
  99. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/langflow_history_service.py +0 -0
  100. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/langflow_mcp_service.py +0 -0
  101. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/models_service.py +0 -0
  102. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/monitor_service.py +0 -0
  103. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/search_service.py +0 -0
  104. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/session_ownership_service.py +0 -0
  105. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/services/task_service.py +0 -0
  106. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/session_manager.py +0 -0
  107. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/__init__.py +0 -0
  108. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/docker-compose.gpu.yml +0 -0
  109. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/components/ollama_embedding.json +0 -0
  110. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/components/ollama_llm.json +0 -0
  111. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/components/ollama_llm_text.json +0 -0
  112. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/components/watsonx_embedding.json +0 -0
  113. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/components/watsonx_llm.json +0 -0
  114. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/flows/components/watsonx_llm_text.json +0 -0
  115. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/openrag-documents/docling.pdf +0 -0
  116. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/openrag-documents/ibm_anthropic.pdf +0 -0
  117. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/openrag-documents/openrag-documentation.pdf +0 -0
  118. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/_assets/openrag-documents/warmup_ocr.pdf +0 -0
  119. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/cli.py +0 -0
  120. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/main.py +0 -0
  121. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/managers/__init__.py +0 -0
  122. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/managers/docling_manager.py +0 -0
  123. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/screens/__init__.py +0 -0
  124. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/screens/diagnostics.py +0 -0
  125. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/screens/logs.py +0 -0
  126. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/screens/monitor.py +0 -0
  127. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/screens/welcome.py +0 -0
  128. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/utils/__init__.py +0 -0
  129. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/utils/clipboard.py +0 -0
  130. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/utils/platform.py +0 -0
  131. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/utils/startup_checks.py +0 -0
  132. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/utils/validation.py +0 -0
  133. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/utils/version_check.py +0 -0
  134. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/__init__.py +0 -0
  135. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/command_modal.py +0 -0
  136. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/diagnostics_notification.py +0 -0
  137. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/error_notification.py +0 -0
  138. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/factory_reset_warning_modal.py +0 -0
  139. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/flow_backup_warning_modal.py +0 -0
  140. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/prune_options_modal.py +0 -0
  141. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/upgrade_instructions_modal.py +0 -0
  142. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/version_mismatch_warning_modal.py +0 -0
  143. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/tui/widgets/waves.py +0 -0
  144. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/__init__.py +0 -0
  145. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/acl_utils.py +0 -0
  146. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/container_utils.py +0 -0
  147. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/docling_client.py +0 -0
  148. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/document_processing.py +0 -0
  149. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/embedding_fields.py +0 -0
  150. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/embeddings.py +0 -0
  151. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/env_utils.py +0 -0
  152. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/file_utils.py +0 -0
  153. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/gpu_detection.py +0 -0
  154. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/hash_utils.py +0 -0
  155. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/langflow_headers.py +0 -0
  156. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/langflow_utils.py +0 -0
  157. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/logging_config.py +0 -0
  158. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/opensearch_queries.py +0 -0
  159. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/opensearch_utils.py +0 -0
  160. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/paths.py +0 -0
  161. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/telemetry/__init__.py +0 -0
  162. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/telemetry/category.py +0 -0
  163. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/telemetry/client.py +0 -0
  164. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/telemetry/message_id.py +0 -0
  165. {openrag-0.4.0.dev6 → openrag-0.4.0.dev7}/src/utils/version_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrag
3
- Version: 0.4.0.dev6
3
+ Version: 0.4.0.dev7
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "openrag"
7
- version = "0.4.0.dev6"
7
+ version = "0.4.0.dev7"
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"
@@ -1198,6 +1198,20 @@ async def startup_tasks(services):
1198
1198
  # Update MCP servers with provider credentials (especially important for no-auth mode)
1199
1199
  await _update_mcp_servers_with_provider_credentials(services)
1200
1200
 
1201
+ # Ensure all configured flows exist in Langflow (create-only, never overwrites).
1202
+ # This replaces LANGFLOW_LOAD_FLOWS_PATH, which performed a blind upsert on
1203
+ # every container start and discarded any user edits made in the Langflow UI.
1204
+ newly_created: set[str] = set()
1205
+ try:
1206
+ flows_service = services["flows_service"]
1207
+ newly_created = await flows_service.ensure_flows_exist()
1208
+ except Exception as e:
1209
+ logger.error(
1210
+ "Failed to ensure Langflow flows exist at startup — "
1211
+ "flows may be missing until the next restart",
1212
+ error=str(e),
1213
+ )
1214
+
1201
1215
  # Check if flows were reset and reapply settings if config is edited
1202
1216
  try:
1203
1217
  config = get_openrag_config()
@@ -1205,6 +1219,9 @@ async def startup_tasks(services):
1205
1219
  logger.info("Checking if Langflow flows were reset")
1206
1220
  flows_service = services["flows_service"]
1207
1221
  reset_flows = await flows_service.check_flows_reset()
1222
+ # Exclude flows that were just seeded — they match the JSON by design,
1223
+ # not because they were externally reset.
1224
+ reset_flows = [f for f in reset_flows if f not in newly_created]
1208
1225
 
1209
1226
  if reset_flows:
1210
1227
  logger.info(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrag
3
- Version: 0.4.0.dev6
3
+ Version: 0.4.0.dev7
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
@@ -152,6 +152,7 @@ src/utils/hash_utils.py
152
152
  src/utils/langflow_headers.py
153
153
  src/utils/langflow_utils.py
154
154
  src/utils/logging_config.py
155
+ src/utils/opensearch_filter_merge.py
155
156
  src/utils/opensearch_queries.py
156
157
  src/utils/opensearch_utils.py
157
158
  src/utils/paths.py
@@ -47,7 +47,7 @@ class FlowsService:
47
47
  resolved_url = None
48
48
  for cand in candidates:
49
49
  test_url = replace_localhost_patterns(endpoint, cand)
50
-
50
+
51
51
  logger.debug(f"Probing Ollama candidate via Langflow: {test_url}")
52
52
  try:
53
53
  response = await clients.langflow_request(
@@ -61,7 +61,7 @@ class FlowsService:
61
61
  except Exception as e:
62
62
  logger.debug(f"Probe failed for {test_url}: {e}")
63
63
  continue
64
-
64
+
65
65
  if not resolved_url:
66
66
  # Fallback to simple transformation if probing fails
67
67
  resolved_url = transform_localhost_url(endpoint)
@@ -95,23 +95,23 @@ class FlowsService:
95
95
  def _get_latest_backup_path(self, flow_id: str, flow_type: str):
96
96
  """
97
97
  Get the path to the latest backup file for a flow.
98
-
98
+
99
99
  Args:
100
100
  flow_id: The flow ID
101
101
  flow_type: The flow type name
102
-
102
+
103
103
  Returns:
104
104
  str: Path to latest backup file, or None if no backup exists
105
105
  """
106
106
  backup_dir = self._get_backup_directory()
107
-
107
+
108
108
  if not os.path.exists(backup_dir):
109
109
  return None
110
-
110
+
111
111
  # Find all backup files for this flow
112
112
  backup_files = []
113
113
  prefix = f"{flow_type}_"
114
-
114
+
115
115
  try:
116
116
  for filename in os.listdir(backup_dir):
117
117
  if filename.startswith(prefix) and filename.endswith(".json"):
@@ -122,10 +122,10 @@ class FlowsService:
122
122
  except Exception as e:
123
123
  logger.warning(f"Error reading backup directory: {str(e)}")
124
124
  return None
125
-
125
+
126
126
  if not backup_files:
127
127
  return None
128
-
128
+
129
129
  # Return the most recent backup (highest mtime)
130
130
  backup_files.sort(key=lambda x: x[0], reverse=True)
131
131
  return backup_files[0][1]
@@ -134,17 +134,17 @@ class FlowsService:
134
134
  """
135
135
  Compare two flow structures to see if they're different.
136
136
  Normalizes both flows before comparison.
137
-
137
+
138
138
  Args:
139
139
  flow1: First flow data
140
140
  flow2: Second flow data
141
-
141
+
142
142
  Returns:
143
143
  bool: True if flows are different, False if they're the same
144
144
  """
145
145
  normalized1 = self._normalize_flow_structure(flow1)
146
146
  normalized2 = self._normalize_flow_structure(flow2)
147
-
147
+
148
148
  # Compare normalized structures
149
149
  return normalized1 != normalized2
150
150
 
@@ -152,10 +152,10 @@ class FlowsService:
152
152
  """
153
153
  Backup all flows from Langflow to the backup folder.
154
154
  Only backs up flows that have changed since the last backup.
155
-
155
+
156
156
  Args:
157
157
  only_if_changed: If True, only backup flows that differ from latest backup
158
-
158
+
159
159
  Returns:
160
160
  dict: Summary of backup operations with success/failure status
161
161
  """
@@ -200,7 +200,7 @@ class FlowsService:
200
200
  flow_locked = current_flow.get("locked", False)
201
201
  latest_backup_path = self._get_latest_backup_path(flow_id, flow_type)
202
202
  has_backups = latest_backup_path is not None
203
-
203
+
204
204
  # If flow is locked and no backups exist, skip backup
205
205
  if flow_locked and not has_backups:
206
206
  logger.debug(
@@ -212,13 +212,13 @@ class FlowsService:
212
212
  "reason": "locked_without_backups",
213
213
  })
214
214
  continue
215
-
215
+
216
216
  # Check if we need to backup (only if changed)
217
217
  if only_if_changed and has_backups:
218
218
  try:
219
219
  with open(latest_backup_path, "r") as f:
220
220
  latest_backup = json.load(f)
221
-
221
+
222
222
  # Compare flows
223
223
  if not self._compare_flows(current_flow, latest_backup):
224
224
  logger.debug(
@@ -280,12 +280,12 @@ class FlowsService:
280
280
  async def _backup_flow(self, flow_id: str, flow_type: str, flow_data: dict = None):
281
281
  """
282
282
  Backup a single flow to the backup folder.
283
-
283
+
284
284
  Args:
285
285
  flow_id: The flow ID to backup
286
286
  flow_type: The flow type name (nudges, retrieval, ingest, url_ingest)
287
287
  flow_data: The flow data to backup (if None, fetches from API)
288
-
288
+
289
289
  Returns:
290
290
  str: Path to the backup file, or None if backup failed
291
291
  """
@@ -717,7 +717,7 @@ class FlowsService:
717
717
  for node in nodes:
718
718
  node_data = node.get("data", {})
719
719
  node_template = node_data.get("node", {})
720
-
720
+
721
721
  normalized_node = {
722
722
  "id": node.get("id"), # Keep ID for edge matching
723
723
  "type": node.get("type"),
@@ -775,20 +775,20 @@ class FlowsService:
775
775
  # Compare entire normalized structures exactly
776
776
  # Sort nodes and edges for consistent comparison
777
777
  normalized_langflow["data"]["nodes"] = sorted(
778
- normalized_langflow["data"]["nodes"],
778
+ normalized_langflow["data"]["nodes"],
779
779
  key=lambda x: (x.get("id", ""), x.get("type", ""))
780
780
  )
781
781
  normalized_file["data"]["nodes"] = sorted(
782
- normalized_file["data"]["nodes"],
782
+ normalized_file["data"]["nodes"],
783
783
  key=lambda x: (x.get("id", ""), x.get("type", ""))
784
784
  )
785
785
 
786
786
  normalized_langflow["data"]["edges"] = sorted(
787
- normalized_langflow["data"]["edges"],
787
+ normalized_langflow["data"]["edges"],
788
788
  key=lambda x: (x.get("source", ""), x.get("target", ""), x.get("sourceHandle", ""), x.get("targetHandle", ""))
789
789
  )
790
790
  normalized_file["data"]["edges"] = sorted(
791
- normalized_file["data"]["edges"],
791
+ normalized_file["data"]["edges"],
792
792
  key=lambda x: (x.get("source", ""), x.get("target", ""), x.get("sourceHandle", ""), x.get("targetHandle", ""))
793
793
  )
794
794
 
@@ -799,6 +799,80 @@ class FlowsService:
799
799
  logger.error(f"Error comparing flow {flow_id} with file: {str(e)}")
800
800
  return False
801
801
 
802
+ async def ensure_flows_exist(self) -> set[str]:
803
+ """
804
+ Ensure all configured flows exist in Langflow.
805
+
806
+ Creates flows from their JSON files if they are not already present in
807
+ the Langflow database. This is intentionally create-only: it never
808
+ patches or overwrites an existing flow, preserving any edits the user
809
+ has made in the Langflow UI.
810
+
811
+ This replaces the LANGFLOW_LOAD_FLOWS_PATH mechanism, which performed a
812
+ blind upsert on every container start and discarded user edits.
813
+
814
+ Returns the set of flow type names that were actually created.
815
+ """
816
+ flow_configs = [
817
+ ("nudges", NUDGES_FLOW_ID),
818
+ ("retrieval", LANGFLOW_CHAT_FLOW_ID),
819
+ ("ingest", LANGFLOW_INGEST_FLOW_ID),
820
+ ("url_ingest", LANGFLOW_URL_INGEST_FLOW_ID),
821
+ ]
822
+ created_flow_types: set[str] = set()
823
+
824
+ for flow_type, flow_id in flow_configs:
825
+ if not flow_id:
826
+ continue
827
+
828
+ try:
829
+ response = await clients.langflow_request(
830
+ "GET", f"/api/v1/flows/{flow_id}"
831
+ )
832
+ if response.status_code == 200:
833
+ logger.info(
834
+ f"Flow {flow_type} (ID: {flow_id}) already exists, skipping creation"
835
+ )
836
+ continue
837
+
838
+ if response.status_code != 404:
839
+ logger.warning(
840
+ f"Unexpected status checking {flow_type} flow (ID: {flow_id}): "
841
+ f"HTTP {response.status_code} — skipping creation to avoid overwriting existing data"
842
+ )
843
+ continue
844
+
845
+ flow_path = self._find_flow_file_by_id(flow_id)
846
+ if not flow_path:
847
+ logger.warning(
848
+ f"No flow file found for {flow_type} (ID: {flow_id}), cannot create"
849
+ )
850
+ continue
851
+
852
+ with open(flow_path, "r") as f:
853
+ flow_data = json.load(f)
854
+
855
+ response = await clients.langflow_request(
856
+ "PUT", f"/api/v1/flows/{flow_id}", json=flow_data
857
+ )
858
+ if response.status_code in (200, 201):
859
+ logger.info(
860
+ f"Created {flow_type} flow (ID: {flow_id}) from {os.path.basename(flow_path)}"
861
+ )
862
+ created_flow_types.add(flow_type)
863
+ else:
864
+ logger.warning(
865
+ f"Failed to create {flow_type} flow (ID: {flow_id}): "
866
+ f"HTTP {response.status_code} — {response.text}"
867
+ )
868
+
869
+ except Exception as e:
870
+ logger.error(
871
+ f"Error ensuring {flow_type} flow (ID: {flow_id}) exists: {e}"
872
+ )
873
+
874
+ return created_flow_types
875
+
802
876
  async def check_flows_reset(self):
803
877
  """
804
878
  Check if any flows have been reset by comparing with JSON files.
@@ -819,7 +893,7 @@ class FlowsService:
819
893
 
820
894
  logger.info(f"Checking if {flow_type} flow (ID: {flow_id}) was reset")
821
895
  is_reset = await self._compare_flow_with_file(flow_id)
822
-
896
+
823
897
  if is_reset:
824
898
  logger.info(f"Flow {flow_type} (ID: {flow_id}) appears to have been reset")
825
899
  reset_flows.append(flow_type)
@@ -827,7 +901,7 @@ class FlowsService:
827
901
  logger.info(f"Flow {flow_type} (ID: {flow_id}) does not match reset state")
828
902
 
829
903
  return reset_flows
830
-
904
+
831
905
  async def change_langflow_model_value(
832
906
  self,
833
907
  provider: str,
@@ -917,23 +991,23 @@ class FlowsService:
917
991
  # Get all embedding nodes in the flow
918
992
  embedding_nodes = self._find_nodes_in_flow(flow_data, display_name=OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME)
919
993
  logger.info(f"Found {len(embedding_nodes)} embedding nodes in flow {flow_name} with display name '{OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME}'")
920
-
994
+
921
995
  # Count configured embedding-enabled providers
922
996
  config_obj = get_openrag_config()
923
997
  configured_providers = []
924
998
  if config_obj.providers.openai.configured: configured_providers.append("openai")
925
999
  if config_obj.providers.watsonx.configured: configured_providers.append("watsonx")
926
1000
  if config_obj.providers.ollama.configured: configured_providers.append("ollama")
927
-
1001
+
928
1002
  # Ensure current provider is in the list for counting purposes if it's being configured
929
1003
  if provider in ["openai", "watsonx", "ollama"] and provider not in configured_providers:
930
1004
  configured_providers.append(provider)
931
-
1005
+
932
1006
  all_possible = ["openai", "watsonx", "ollama"]
933
1007
  configured_providers = [p for p in all_possible if p in configured_providers]
934
1008
  provider_count = len(configured_providers)
935
1009
  logger.info(f"Configured embedding providers: {configured_providers} (count: {provider_count})")
936
-
1010
+
937
1011
  # Determine slot mapping context
938
1012
  if provider_count == 1:
939
1013
  logger.info("Configuration mode: all 3 slots belong to the single active provider")
@@ -948,7 +1022,7 @@ class FlowsService:
948
1022
  for node, idx in embedding_nodes:
949
1023
  if self._get_node_provider(node) == provider_display:
950
1024
  matched_nodes.append((node, idx))
951
-
1025
+
952
1026
  if matched_nodes:
953
1027
  logger.info(f"Found {len(matched_nodes)} nodes already configured for provider '{provider}'")
954
1028
  for node, idx in matched_nodes:
@@ -1035,7 +1109,7 @@ class FlowsService:
1035
1109
  # Only call if code field exists (custom components should have code)
1036
1110
  if "code" in template and "value" in template["code"]:
1037
1111
  code_value = template["code"]["value"]
1038
-
1112
+
1039
1113
  try:
1040
1114
  update_payload = {
1041
1115
  "code": code_value,
@@ -1044,11 +1118,11 @@ class FlowsService:
1044
1118
  "field_value": model,
1045
1119
  "tool_mode": False,
1046
1120
  }
1047
-
1121
+
1048
1122
  response = await clients.langflow_request(
1049
1123
  "POST", "/api/v1/custom_component/update", json=update_payload
1050
1124
  )
1051
-
1125
+
1052
1126
  if response.status_code == 200:
1053
1127
  response_data = response.json()
1054
1128
  # Update template with the new template from response.data
@@ -1161,11 +1235,11 @@ class FlowsService:
1161
1235
  "model_id": model_value,
1162
1236
  "enabled": True
1163
1237
  }]
1164
-
1238
+
1165
1239
  response = await clients.langflow_request(
1166
1240
  "POST", "/api/v1/models/enabled_models", json=enable_payload
1167
1241
  )
1168
-
1242
+
1169
1243
  if response.status_code == 200:
1170
1244
  logger.info(f"Successfully enabled model {model_value} for provider {provider_name}")
1171
1245
  else:
@@ -127,6 +127,7 @@ services:
127
127
  langflow:
128
128
  volumes:
129
129
  - ${OPENRAG_FLOWS_PATH:-./flows}:/app/flows:U,z
130
+ - ${LANGFLOW_DATA_PATH:-./langflow-data}:/app/langflow-data:U,z
130
131
  image: langflowai/openrag-langflow:${OPENRAG_VERSION:-latest}
131
132
  build:
132
133
  context: .
@@ -145,7 +146,8 @@ services:
145
146
  - WATSONX_URL=${WATSONX_URL:-${WATSONX_ENDPOINT}}
146
147
  - WATSONX_PROJECT_ID=${WATSONX_PROJECT_ID}
147
148
  - OLLAMA_BASE_URL=${OLLAMA_ENDPOINT}
148
- - LANGFLOW_LOAD_FLOWS_PATH=/app/flows
149
+ - LANGFLOW_CONFIG_DIR=/app/langflow-data
150
+ - LANGFLOW_DATABASE_URL=${LANGFLOW_DATABASE_URL:-sqlite:////app/langflow-data/langflow.db}
149
151
  - LANGFLOW_SECRET_KEY=${LANGFLOW_SECRET_KEY}
150
152
  - JWT=None
151
153
  - OWNER=None