openrag 0.4.0.dev9__tar.gz → 0.4.1__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 (166) hide show
  1. {openrag-0.4.0.dev9/src/openrag.egg-info → openrag-0.4.1}/PKG-INFO +6 -2
  2. {openrag-0.4.0.dev9 → openrag-0.4.1}/pyproject.toml +7 -3
  3. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/agent.py +69 -21
  4. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/settings.py +10 -5
  5. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/upload.py +17 -29
  6. openrag-0.4.1/src/config/embedding_constants.py +4 -0
  7. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/config/model_constants.py +0 -2
  8. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/config/settings.py +48 -44
  9. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/service.py +7 -7
  10. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/main.py +99 -44
  11. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/models/processors.py +37 -20
  12. {openrag-0.4.0.dev9 → openrag-0.4.1/src/openrag.egg-info}/PKG-INFO +6 -2
  13. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/openrag.egg-info/SOURCES.txt +1 -0
  14. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/openrag.egg-info/requires.txt +5 -1
  15. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/document_service.py +15 -9
  16. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/models_service.py +150 -13
  17. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/search_service.py +61 -26
  18. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/task_service.py +15 -1
  19. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/session_manager.py +2 -0
  20. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/docker-compose.yml +22 -19
  21. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/config_fields.py +0 -6
  22. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/main.py +3 -6
  23. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/managers/container_manager.py +27 -13
  24. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/managers/env_manager.py +2 -7
  25. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/screens/config.py +0 -78
  26. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/screens/monitor.py +0 -17
  27. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/utils/startup_checks.py +86 -19
  28. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/embedding_fields.py +4 -2
  29. openrag-0.4.1/src/utils/embeddings.py +39 -0
  30. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/opensearch_utils.py +37 -0
  31. openrag-0.4.0.dev9/src/utils/embeddings.py +0 -196
  32. {openrag-0.4.0.dev9 → openrag-0.4.1}/LICENSE +0 -0
  33. {openrag-0.4.0.dev9 → openrag-0.4.1}/MANIFEST.in +0 -0
  34. {openrag-0.4.0.dev9 → openrag-0.4.1}/README.md +0 -0
  35. {openrag-0.4.0.dev9 → openrag-0.4.1}/setup.cfg +0 -0
  36. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/__init__.py +0 -0
  37. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/auth.py +0 -0
  38. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/chat.py +0 -0
  39. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/connector_router.py +0 -0
  40. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/connectors.py +0 -0
  41. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/docling.py +0 -0
  42. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/documents.py +0 -0
  43. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/flows.py +0 -0
  44. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/keys.py +0 -0
  45. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/knowledge_filter.py +0 -0
  46. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/langflow_files.py +0 -0
  47. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/models.py +0 -0
  48. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/nudges.py +0 -0
  49. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/oidc.py +0 -0
  50. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/provider_health.py +0 -0
  51. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/provider_validation.py +0 -0
  52. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/router.py +0 -0
  53. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/search.py +0 -0
  54. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/tasks.py +0 -0
  55. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/v1/__init__.py +0 -0
  56. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/v1/chat.py +0 -0
  57. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/v1/documents.py +0 -0
  58. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/v1/knowledge_filters.py +0 -0
  59. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/v1/models.py +0 -0
  60. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/v1/search.py +0 -0
  61. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/api/v1/settings.py +0 -0
  62. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/auth_context.py +0 -0
  63. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/config/__init__.py +0 -0
  64. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/config/config_manager.py +0 -0
  65. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/__init__.py +0 -0
  66. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/aws_s3/__init__.py +0 -0
  67. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/aws_s3/api.py +0 -0
  68. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/aws_s3/auth.py +0 -0
  69. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/aws_s3/connector.py +0 -0
  70. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/aws_s3/models.py +0 -0
  71. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/aws_s3/support.py +0 -0
  72. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/base.py +0 -0
  73. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/connection_manager.py +0 -0
  74. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/google_drive/__init__.py +0 -0
  75. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/google_drive/connector.py +0 -0
  76. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/google_drive/oauth.py +0 -0
  77. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/ibm_cos/__init__.py +0 -0
  78. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/ibm_cos/api.py +0 -0
  79. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/ibm_cos/auth.py +0 -0
  80. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/ibm_cos/connector.py +0 -0
  81. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/ibm_cos/models.py +0 -0
  82. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/ibm_cos/support.py +0 -0
  83. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/langflow_connector_service.py +0 -0
  84. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/onedrive/__init__.py +0 -0
  85. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/onedrive/connector.py +0 -0
  86. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/onedrive/oauth.py +0 -0
  87. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/sharepoint/__init__.py +0 -0
  88. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/sharepoint/connector.py +0 -0
  89. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/sharepoint/oauth.py +0 -0
  90. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/connectors/sharepoint/utils.py +0 -0
  91. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/dependencies.py +0 -0
  92. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/models/__init__.py +0 -0
  93. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/models/tasks.py +0 -0
  94. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/models/url.py +0 -0
  95. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/openrag.egg-info/dependency_links.txt +0 -0
  96. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/openrag.egg-info/entry_points.txt +0 -0
  97. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/openrag.egg-info/top_level.txt +0 -0
  98. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/__init__.py +0 -0
  99. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/api_key_service.py +0 -0
  100. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/auth_service.py +0 -0
  101. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/chat_service.py +0 -0
  102. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/conversation_persistence_service.py +0 -0
  103. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/flows_service.py +0 -0
  104. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/knowledge_filter_service.py +0 -0
  105. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/langflow_file_service.py +0 -0
  106. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/langflow_history_service.py +0 -0
  107. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/langflow_mcp_service.py +0 -0
  108. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/monitor_service.py +0 -0
  109. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/services/session_ownership_service.py +0 -0
  110. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/__init__.py +0 -0
  111. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/docker-compose.gpu.yml +0 -0
  112. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/components/ollama_embedding.json +0 -0
  113. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/components/ollama_llm.json +0 -0
  114. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/components/ollama_llm_text.json +0 -0
  115. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/components/watsonx_embedding.json +0 -0
  116. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/components/watsonx_llm.json +0 -0
  117. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/components/watsonx_llm_text.json +0 -0
  118. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/ingestion_flow.json +0 -0
  119. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/openrag_agent.json +0 -0
  120. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/openrag_nudges.json +0 -0
  121. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/flows/openrag_url_mcp.json +0 -0
  122. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/openrag-documents/docling.pdf +0 -0
  123. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/openrag-documents/ibm_anthropic.pdf +0 -0
  124. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/openrag-documents/openrag-documentation.pdf +0 -0
  125. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/_assets/openrag-documents/warmup_ocr.pdf +0 -0
  126. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/cli.py +0 -0
  127. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/managers/__init__.py +0 -0
  128. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/managers/docling_manager.py +0 -0
  129. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/screens/__init__.py +0 -0
  130. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/screens/diagnostics.py +0 -0
  131. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/screens/logs.py +0 -0
  132. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/screens/welcome.py +0 -0
  133. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/utils/__init__.py +0 -0
  134. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/utils/clipboard.py +0 -0
  135. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/utils/platform.py +0 -0
  136. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/utils/validation.py +0 -0
  137. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/utils/version_check.py +0 -0
  138. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/__init__.py +0 -0
  139. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/command_modal.py +0 -0
  140. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/diagnostics_notification.py +0 -0
  141. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/error_notification.py +0 -0
  142. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/factory_reset_warning_modal.py +0 -0
  143. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/flow_backup_warning_modal.py +0 -0
  144. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/prune_options_modal.py +0 -0
  145. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/upgrade_instructions_modal.py +0 -0
  146. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/version_mismatch_warning_modal.py +0 -0
  147. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/tui/widgets/waves.py +0 -0
  148. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/__init__.py +0 -0
  149. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/acl_utils.py +0 -0
  150. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/container_utils.py +0 -0
  151. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/docling_client.py +0 -0
  152. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/document_processing.py +0 -0
  153. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/env_utils.py +0 -0
  154. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/file_utils.py +0 -0
  155. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/gpu_detection.py +0 -0
  156. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/hash_utils.py +0 -0
  157. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/langflow_headers.py +0 -0
  158. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/langflow_utils.py +0 -0
  159. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/logging_config.py +0 -0
  160. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/opensearch_queries.py +0 -0
  161. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/paths.py +0 -0
  162. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/telemetry/__init__.py +0 -0
  163. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/telemetry/category.py +0 -0
  164. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/telemetry/client.py +0 -0
  165. {openrag-0.4.0.dev9 → openrag-0.4.1}/src/utils/telemetry/message_id.py +0 -0
  166. {openrag-0.4.0.dev9 → openrag-0.4.1}/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.dev9
3
+ Version: 0.4.1
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
@@ -15,7 +15,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
15
  Requires-Python: >=3.13
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: agentd>=0.2.2
18
+ Requires-Dist: agentd>=0.8.1
19
19
  Requires-Dist: aiofiles>=24.1.0
20
20
  Requires-Dist: cryptography>=45.0.6
21
21
  Requires-Dist: google-api-python-client>=2.143.0
@@ -37,6 +37,10 @@ Requires-Dist: python-dotenv>=1.0.0
37
37
  Requires-Dist: textual-fspicker>=0.6.0
38
38
  Requires-Dist: structlog>=25.4.0
39
39
  Requires-Dist: zxcvbn>=4.5.0
40
+ Requires-Dist: litellm==1.83.3
41
+ Requires-Dist: pyyaml>=6.0
42
+ Requires-Dist: tiktoken>=0.7.0
43
+ Requires-Dist: openai>=2.30.0
40
44
  Dynamic: license-file
41
45
 
42
46
  <div align="center">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "openrag"
7
- version = "0.4.0.dev9"
7
+ version = "0.4.1"
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"
@@ -21,7 +21,7 @@ classifiers = [
21
21
  "Topic :: Software Development :: Libraries :: Python Modules",
22
22
  ]
23
23
  dependencies = [
24
- "agentd>=0.2.2",
24
+ "agentd>=0.8.1",
25
25
  "aiofiles>=24.1.0",
26
26
  "cryptography>=45.0.6",
27
27
  "google-api-python-client>=2.143.0",
@@ -42,7 +42,11 @@ dependencies = [
42
42
  "python-dotenv>=1.0.0",
43
43
  "textual-fspicker>=0.6.0",
44
44
  "structlog>=25.4.0",
45
- "zxcvbn>=4.5.0"
45
+ "zxcvbn>=4.5.0",
46
+ "litellm==1.83.3",
47
+ "pyyaml>=6.0",
48
+ "tiktoken>=0.7.0",
49
+ "openai>=2.30.0",
46
50
  ]
47
51
 
48
52
  [dependency-groups]
@@ -158,7 +158,7 @@ async def async_response_stream(
158
158
  else:
159
159
  delta_text = str(chunk.delta)
160
160
  full_response += delta_text
161
-
161
+
162
162
  # Enhanced logging for tool call detection (Granite 3.3 8b investigation)
163
163
  chunk_attrs = dir(chunk) if hasattr(chunk, '__dict__') else []
164
164
  tool_related_attrs = [attr for attr in chunk_attrs if 'tool' in attr.lower() or 'call' in attr.lower() or 'retrieval' in attr.lower()]
@@ -180,7 +180,7 @@ async def async_response_stream(
180
180
  chunk_data = chunk.__dict__
181
181
  else:
182
182
  chunk_data = str(chunk)
183
-
183
+
184
184
  # Log detailed chunk structure for investigation (especially for Granite 3.3 8b)
185
185
  if isinstance(chunk_data, dict):
186
186
  # Check for any fields that might indicate tool usage
@@ -218,7 +218,7 @@ async def async_response_stream(
218
218
  'retrieved_documents' in chunk_data,
219
219
  'retrieval_results' in chunk_data,
220
220
  ])
221
-
221
+
222
222
  if has_results:
223
223
  logger.info(
224
224
  "Detected implicit tool call in backend, injecting synthetic event",
@@ -242,7 +242,7 @@ async def async_response_stream(
242
242
  # Send the synthetic event first
243
243
  yield (json.dumps(synthetic_event, default=str) + "\n").encode("utf-8")
244
244
  detected_tool_call = True # Mark that we've injected a tool call
245
-
245
+
246
246
  yield (json.dumps(chunk_data, default=str) + "\n").encode("utf-8")
247
247
  except Exception as e:
248
248
  # Fallback to string representation
@@ -626,20 +626,68 @@ async def async_langflow_chat(
626
626
 
627
627
  # Extract sources from retrieval tool calls in the response
628
628
  sources = []
629
+
630
+ # Layer 1: Structured output items (OpenAI Responses API format).
631
+ # Relaxed: check for any output item with a non-empty `results` field,
632
+ # regardless of `type` string (Langflow may use different type names).
629
633
  if hasattr(response_obj, "output") and response_obj.output:
630
634
  for output_item in response_obj.output:
631
- item_type = getattr(output_item, "type", None)
632
- if item_type in ("tool_call", "retrieval_call"):
633
- for result in getattr(output_item, "results", None) or []:
634
- rd = result.model_dump() if hasattr(result, "model_dump") else (result if isinstance(result, dict) else {})
635
- if "text" in rd:
636
- sources.append({
637
- "filename": rd.get("filename", ""),
638
- "text": rd.get("text", ""),
639
- "score": rd.get("score", 0),
640
- "page": rd.get("page"),
641
- "mimetype": rd.get("mimetype"),
642
- })
635
+ for result in getattr(output_item, "results", None) or []:
636
+ rd = (
637
+ result.model_dump()
638
+ if hasattr(result, "model_dump")
639
+ else (result if isinstance(result, dict) else {})
640
+ )
641
+ if "text" in rd:
642
+ sources.append({
643
+ "filename": rd.get("filename", ""),
644
+ "text": rd.get("text", ""),
645
+ "score": rd.get("score", 0),
646
+ "page": rd.get("page"),
647
+ "mimetype": rd.get("mimetype"),
648
+ })
649
+
650
+ # Layer 2: Top-level dict inspection (mirrors streaming middleware in async_response_stream).
651
+ # Langflow may embed retrieval results directly in the response dict rather than
652
+ # inside typed output items.
653
+ if not sources:
654
+ resp_dict = (
655
+ response_obj.model_dump()
656
+ if hasattr(response_obj, "model_dump")
657
+ else getattr(response_obj, "__dict__", {})
658
+ )
659
+ implicit_results = (
660
+ resp_dict.get("results")
661
+ or resp_dict.get("outputs")
662
+ or resp_dict.get("retrieved_documents")
663
+ or resp_dict.get("retrieval_results")
664
+ or []
665
+ )
666
+ if isinstance(implicit_results, list):
667
+ for result in implicit_results:
668
+ if isinstance(result, dict) and "text" in result:
669
+ sources.append({
670
+ "filename": result.get("filename", ""),
671
+ "text": result.get("text", ""),
672
+ "score": result.get("score", 0),
673
+ "page": result.get("page"),
674
+ "mimetype": result.get("mimetype"),
675
+ })
676
+
677
+ # Layer 3: Citation-text fallback.
678
+ # Parse "(Source: filename)" patterns emitted by the LLM when it cites documents.
679
+ # This is the last-resort fallback when Langflow's response object carries no
680
+ # structured retrieval data.
681
+ if not sources:
682
+ import re
683
+ for match in re.finditer(r"\(Source:\s*([^\)]+)\)", response_text):
684
+ sources.append({
685
+ "filename": match.group(1).strip(),
686
+ "text": "",
687
+ "score": 0,
688
+ "page": None,
689
+ "mimetype": None,
690
+ })
643
691
 
644
692
  if not store_conversation:
645
693
  return response_text, response_id, sources
@@ -739,7 +787,7 @@ async def async_langflow_chat_stream(
739
787
  response_id = chunk_data["id"]
740
788
  elif "response_id" in chunk_data:
741
789
  response_id = chunk_data["response_id"]
742
-
790
+
743
791
  # Check for error status
744
792
  if chunk_data.get("finish_reason") == "error" or chunk_data.get("status") == "failed":
745
793
  error_occurred = True
@@ -788,7 +836,7 @@ async def async_langflow_chat_stream(
788
836
  # Log the error
789
837
  logger.error(f"Error in langflow chat stream: {e}", exc_info=True)
790
838
  error_occurred = True
791
-
839
+
792
840
  # Store error message in conversation history so it persists
793
841
  error_message = {
794
842
  "role": "assistant",
@@ -797,19 +845,19 @@ async def async_langflow_chat_stream(
797
845
  "error": True,
798
846
  }
799
847
  conversation_state["messages"].append(error_message)
800
-
848
+
801
849
  # Try to store the conversation with error message
802
850
  # Use a temporary response_id if we don't have one
803
851
  if not response_id:
804
852
  response_id = f"error_{user_id}_{int(datetime.now().timestamp())}"
805
-
853
+
806
854
  try:
807
855
  conversation_state["last_activity"] = datetime.now()
808
856
  await store_conversation_thread(user_id, response_id, conversation_state)
809
857
  logger.debug(f"Stored conversation with error for user {user_id}")
810
858
  except Exception as store_error:
811
859
  logger.error(f"Failed to store error conversation: {store_error}")
812
-
860
+
813
861
  # Re-raise the exception so it propagates to the API layer
814
862
  raise
815
863
 
@@ -1,3 +1,4 @@
1
+ from dependencies import get_models_service
1
2
  import asyncio
2
3
  import json
3
4
  import platform
@@ -9,7 +10,6 @@ from utils.telemetry import TelemetryClient, Category, MessageId
9
10
  from utils.version_utils import OPENRAG_VERSION
10
11
  from config.settings import (
11
12
  DEFAULT_DOCS_URL,
12
- DISABLE_INGEST_WITH_LANGFLOW,
13
13
  INGEST_SAMPLE_DATA,
14
14
  LANGFLOW_URL,
15
15
  LANGFLOW_CHAT_FLOW_ID,
@@ -885,6 +885,7 @@ async def onboarding(
885
885
  flows_service=Depends(get_flows_service),
886
886
  session_manager=Depends(get_session_manager),
887
887
  document_service=Depends(get_document_service),
888
+ models_service=Depends(get_models_service),
888
889
  task_service=Depends(get_task_service),
889
890
  langflow_file_service=Depends(get_langflow_file_service),
890
891
  knowledge_filter_service=Depends(get_knowledge_filter_service),
@@ -936,7 +937,7 @@ async def onboarding(
936
937
  embedding_model_selected = None
937
938
  embedding_provider_selected = None
938
939
 
939
- if body.embedding_model and not DISABLE_INGEST_WITH_LANGFLOW:
940
+ if body.embedding_model:
940
941
  embedding_model_selected = body.embedding_model.strip()
941
942
  current_config.knowledge.embedding_model = embedding_model_selected
942
943
  config_updated = True
@@ -1151,8 +1152,12 @@ async def onboarding(
1151
1152
  # Import the function here to avoid circular imports
1152
1153
  from main import ingest_default_documents_when_ready
1153
1154
 
1155
+ if not config_manager.save_config_file(current_config):
1156
+ logger.error("Failed to save embedding model to config")
1157
+
1154
1158
  task_id = await ingest_default_documents_when_ready(
1155
1159
  document_service,
1160
+ models_service,
1156
1161
  task_service,
1157
1162
  langflow_file_service,
1158
1163
  session_manager,
@@ -1664,8 +1669,6 @@ async def rollback_onboarding(
1664
1669
  {"error": "No onboarding configuration to rollback"}, status_code=400
1665
1670
  )
1666
1671
 
1667
- jwt_token = user.jwt_token
1668
-
1669
1672
  logger.info("Rolling back onboarding configuration due to file failures")
1670
1673
 
1671
1674
  # Get all tasks for the user
@@ -1728,7 +1731,7 @@ async def rollback_onboarding(
1728
1731
  if filename:
1729
1732
  try:
1730
1733
  opensearch_client = session_manager.get_user_opensearch_client(
1731
- user.user_id, jwt_token
1734
+ user.user_id, user.jwt_token
1732
1735
  )
1733
1736
  from utils.opensearch_queries import build_filename_delete_body
1734
1737
  from config.settings import get_index_name
@@ -1890,6 +1893,7 @@ async def update_docling_preset(
1890
1893
  async def refresh_openrag_docs(
1891
1894
  document_service=Depends(get_document_service),
1892
1895
  task_service=Depends(get_task_service),
1896
+ models_service=Depends(get_models_service),
1893
1897
  langflow_file_service=Depends(get_langflow_file_service),
1894
1898
  session_manager=Depends(get_session_manager),
1895
1899
  user: User = Depends(get_current_user),
@@ -1900,6 +1904,7 @@ async def refresh_openrag_docs(
1900
1904
 
1901
1905
  refreshed = await refresh_default_openrag_docs(
1902
1906
  document_service=document_service,
1907
+ models_service=models_service,
1903
1908
  task_service=task_service,
1904
1909
  langflow_file_service=langflow_file_service,
1905
1910
  session_manager=session_manager,
@@ -1,3 +1,4 @@
1
+ from dependencies import get_models_service
1
2
  import os
2
3
  from typing import Optional
3
4
  from urllib.parse import urlparse
@@ -35,15 +36,10 @@ async def upload(
35
36
  try:
36
37
 
37
38
  from config.settings import is_no_auth_mode
38
-
39
- if is_no_auth_mode():
40
- owner_user_id = None
41
- owner_name = None
42
- owner_email = None
43
- else:
44
- owner_user_id = user.user_id
45
- owner_name = user.name
46
- owner_email = user.email
39
+ is_no_auth = is_no_auth_mode()
40
+ owner_user_id = user.user_id if (user and not is_no_auth) else None
41
+ owner_name = user.name if user else None
42
+ owner_email = user.email if user else None
47
43
 
48
44
  result = await document_service.process_upload_file(
49
45
  file,
@@ -84,15 +80,10 @@ async def upload_path(
84
80
  jwt_token = user.jwt_token
85
81
 
86
82
  from config.settings import is_no_auth_mode
87
-
88
- if is_no_auth_mode():
89
- owner_user_id = None
90
- owner_name = None
91
- owner_email = None
92
- else:
93
- owner_user_id = user.user_id
94
- owner_name = user.name
95
- owner_email = user.email
83
+ is_no_auth = is_no_auth_mode()
84
+ owner_user_id = user.user_id if (user and not is_no_auth) else None
85
+ owner_name = user.name if user else None
86
+ owner_email = user.email if user else None
96
87
 
97
88
  from api.documents import _ensure_index_exists
98
89
  await _ensure_index_exists()
@@ -163,6 +154,7 @@ async def upload_options(
163
154
  async def upload_bucket(
164
155
  body: UploadBucketBody,
165
156
  task_service=Depends(get_task_service),
157
+ models_service=Depends(get_models_service),
166
158
  session_manager=Depends(get_session_manager),
167
159
  user: User = Depends(get_current_user),
168
160
  ):
@@ -194,18 +186,13 @@ async def upload_bucket(
194
186
  jwt_token = user.jwt_token
195
187
 
196
188
  from models.processors import S3FileProcessor
197
- from config.settings import is_no_auth_mode
198
189
 
199
- if is_no_auth_mode():
200
- owner_user_id = None
201
- owner_name = None
202
- owner_email = None
203
- task_user_id = None
204
- else:
205
- owner_user_id = user.user_id
206
- owner_name = user.name
207
- owner_email = user.email
208
- task_user_id = user.user_id
190
+ from config.settings import is_no_auth_mode
191
+ is_no_auth = is_no_auth_mode()
192
+ owner_user_id = user.user_id if (user and not is_no_auth) else None
193
+ owner_name = user.name if user else None
194
+ owner_email = user.email if user else None
195
+ task_user_id = user.user_id if (user and not is_no_auth) else None
209
196
 
210
197
  from api.documents import _ensure_index_exists
211
198
  await _ensure_index_exists()
@@ -213,6 +200,7 @@ async def upload_bucket(
213
200
  processor = S3FileProcessor(
214
201
  task_service.document_service,
215
202
  bucket,
203
+ models_service=models_service,
216
204
  s3_client=s3_client,
217
205
  owner_user_id=owner_user_id,
218
206
  jwt_token=jwt_token,
@@ -0,0 +1,4 @@
1
+ """Embedding model constants."""
2
+
3
+ OPENAI_DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
4
+ OPENAI_EMBEDDING_MODEL_PREFIX = "text-embedding"
@@ -31,8 +31,6 @@ OPENAI_VALIDATION_MODELS = [
31
31
  ]
32
32
 
33
33
  OPENAI_DEFAULT_LANGUAGE_MODEL = "gpt-4o"
34
- OPENAI_DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
35
- OPENAI_EMBEDDING_MODEL_PREFIX = "text-embedding"
36
34
 
37
35
  ANTHROPIC_DEFAULT_LANGUAGE_MODEL = "claude-sonnet-4-5-20250929"
38
36
 
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
2
  import os
3
+ import threading
4
+ import concurrent.futures
3
5
  from utils.env_utils import get_env_int, get_env_float
4
6
 
5
7
  import httpx
@@ -8,6 +10,7 @@ from dotenv import load_dotenv
8
10
  from openai import AsyncOpenAI
9
11
  from opensearchpy import AsyncOpenSearch
10
12
  from opensearchpy._async.http_aiohttp import AIOHttpConnection
13
+ from config.embedding_constants import OPENAI_DEFAULT_EMBEDDING_MODEL
11
14
 
12
15
  from utils.container_utils import get_container_host
13
16
  from utils.logging_config import get_logger
@@ -107,26 +110,6 @@ WEBHOOK_BASE_URL = os.getenv(
107
110
  VECTOR_DIM = 1536
108
111
  KNN_EF_CONSTRUCTION = 100
109
112
  KNN_M = 16
110
- EMBED_MODEL = "text-embedding-3-small"
111
-
112
- OPENAI_EMBEDDING_DIMENSIONS = {
113
- "text-embedding-3-small": 1536,
114
- "text-embedding-3-large": 3072,
115
- "text-embedding-ada-002": 1536,
116
- }
117
-
118
- WATSONX_EMBEDDING_DIMENSIONS = {
119
- # IBM Models
120
- "ibm/granite-embedding-107m-multilingual": 384,
121
- "ibm/granite-embedding-278m-multilingual": 1024,
122
- "ibm/slate-125m-english-rtrvr": 768,
123
- "ibm/slate-125m-english-rtrvr-v2": 768,
124
- "ibm/slate-30m-english-rtrvr": 384,
125
- "ibm/slate-30m-english-rtrvr-v2": 384,
126
- # Third Party Models
127
- "intfloat/multilingual-e5-large": 1024,
128
- "sentence-transformers/all-minilm-l6-v2": 384,
129
- }
130
113
 
131
114
  INDEX_BODY = {
132
115
  "settings": {
@@ -320,7 +303,7 @@ class AppClients:
320
303
  self.langflow_client = None
321
304
  self.langflow_http_client = None
322
305
  self._patched_async_client = None # Private attribute - single client for all providers
323
- self._client_init_lock = __import__('threading').Lock() # Lock for thread-safe initialization
306
+ self._client_init_lock = threading.Lock() # Lock for thread-safe initialization
324
307
  self.docling_http_client = None
325
308
 
326
309
  async def initialize(self):
@@ -455,6 +438,11 @@ class AppClients:
455
438
  if config.providers.openai.api_key:
456
439
  os.environ["OPENAI_API_KEY"] = config.providers.openai.api_key
457
440
  logger.debug("Loaded OpenAI API key from config")
441
+ elif not os.environ.get("OPENAI_API_KEY"):
442
+ # Provide dummy key to satisfy AsyncOpenAI constructor;
443
+ # LiteLLM/MCP will handle routing to other providers if needed.
444
+ os.environ["OPENAI_API_KEY"] = "no-key-required"
445
+ logger.debug("Using dummy OpenAI API key to satisfy client constructor")
458
446
 
459
447
  # Set Anthropic credentials
460
448
  if config.providers.anthropic.api_key:
@@ -478,14 +466,23 @@ class AppClients:
478
466
  os.environ["OLLAMA_ENDPOINT"] = config.providers.ollama.endpoint
479
467
  logger.debug("Loaded Ollama endpoint from config")
480
468
 
469
+ # Determine model and provider for both probe and production client
470
+ model_name = config.knowledge.embedding_model or OPENAI_DEFAULT_EMBEDDING_MODEL
471
+ provider = config.knowledge.embedding_provider or "openai"
481
472
  except Exception as e:
482
473
  logger.debug("Could not load provider credentials from config", error=str(e))
474
+ # Provide fallbacks if config loading failed
475
+ model_name = OPENAI_DEFAULT_EMBEDDING_MODEL
476
+ provider = "openai"
477
+ # Ensure a dummy key is available to satisfy the AsyncOpenAI constructor
478
+ # and avoid AuthenticationError if config loading failed.
479
+ if not os.environ.get("OPENAI_API_KEY"):
480
+ os.environ["OPENAI_API_KEY"] = "no-key-required"
481
+ logger.debug("Using dummy OpenAI API key fallback (config load failed)")
482
+
483
483
 
484
- # Try to initialize the client - AsyncOpenAI() will read from environment
485
- # We'll try HTTP/2 first with a probe, then fall back to HTTP/1.1 if it times out
486
- import asyncio
487
- import concurrent.futures
488
- import threading
484
+ # API key for AsyncOpenAI constructor
485
+ api_key = os.environ.get("OPENAI_API_KEY")
489
486
 
490
487
  async def probe_http2():
491
488
  """Returns True if HTTP/2 works, False to fall back to HTTP/1.1.
@@ -495,20 +492,21 @@ class AppClients:
495
492
  production client is created after this thread exits, in the
496
493
  caller's event loop, avoiding cross-loop SSL transport errors.
497
494
  """
498
- client = AsyncOpenAI()
499
- logger.info("Probing OpenAI client with HTTP/2...")
495
+ # Use a standard OpenAI client for the probe (only runs for OpenAI provider)
496
+ client = AsyncOpenAI(api_key=api_key)
497
+ logger.info(f"Probing client with HTTP/2 using model {model_name}...")
500
498
  try:
501
499
  await asyncio.wait_for(
502
500
  client.embeddings.create(
503
- model='text-embedding-3-small',
501
+ model=model_name,
504
502
  input=['test']
505
503
  ),
506
504
  timeout=5.0
507
505
  )
508
- logger.info("HTTP/2 probe successful")
506
+ logger.info(f"HTTP/2 probe successful with {model_name}")
509
507
  return True
510
508
  except (asyncio.TimeoutError, Exception) as probe_error:
511
- logger.warning("HTTP/2 probe failed, falling back to HTTP/1.1", error=str(probe_error))
509
+ logger.warning(f"HTTP/2 probe failed with {model_name}, falling back to HTTP/1.1", error=str(probe_error))
512
510
  return False
513
511
  finally:
514
512
  # Always close the probe client so its connections are fully
@@ -528,26 +526,32 @@ class AppClients:
528
526
  loop.close()
529
527
 
530
528
  try:
531
- # Run the probe in a separate thread with its own event loop.
532
- # Only the probe result (bool) crosses the thread boundary;
533
- # the production client is created here so its connections are
534
- # bound to the caller's event loop, not the (now closed) probe loop.
535
- with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
536
- future = executor.submit(run_probe_in_thread)
537
- use_http2 = future.result(timeout=15)
529
+ # Run the probe only for OpenAI provider; local and other providers
530
+ # (Ollama, WatsonX) typically use HTTP/1.1 for reliability.
531
+ if provider.lower() == "openai":
532
+ # Run the probe in a separate thread with its own event loop.
533
+ # Only the probe result (bool) crosses the thread boundary;
534
+ # the production client is created here so its connections are
535
+ # bound to the caller's event loop, not the (now closed) probe loop.
536
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
537
+ future = executor.submit(run_probe_in_thread)
538
+ use_http2 = future.result(timeout=15)
539
+ else:
540
+ use_http2 = False
541
+ logger.debug(f"Skipping HTTP/2 probe for provider: {provider}")
538
542
 
539
543
  if use_http2:
540
- self._patched_async_client = patch_openai_with_mcp(AsyncOpenAI())
541
- logger.info("OpenAI client initialized with HTTP/2")
544
+ self._patched_async_client = patch_openai_with_mcp(AsyncOpenAI(api_key=api_key))
545
+ logger.info(f"OpenAI-compatible client initialized with HTTP/2 (model: {model_name})")
542
546
  else:
543
547
  http_client = httpx.AsyncClient(
544
548
  http2=False,
545
549
  timeout=httpx.Timeout(60.0, connect=10.0)
546
550
  )
547
551
  self._patched_async_client = patch_openai_with_mcp(
548
- AsyncOpenAI(http_client=http_client)
552
+ AsyncOpenAI(api_key=api_key, http_client=http_client)
549
553
  )
550
- logger.info("OpenAI client initialized with HTTP/1.1 (fallback)")
554
+ logger.info(f"OpenAI-compatible client initialized with HTTP/1.1 fallback (model: {model_name})")
551
555
  logger.info("Successfully initialized OpenAI client")
552
556
  except Exception as e:
553
557
  logger.error(f"Failed to initialize OpenAI client: {e.__class__.__name__}: {str(e)}")
@@ -864,7 +868,7 @@ def get_openrag_config():
864
868
  # Expose configuration settings for backward compatibility and easy access
865
869
  def get_provider_config():
866
870
  """Get provider configuration."""
867
- return get_openrag_config().provider
871
+ return get_openrag_config().providers
868
872
 
869
873
 
870
874
  def get_knowledge_config():
@@ -879,7 +883,7 @@ def get_agent_config():
879
883
 
880
884
  def get_embedding_model() -> str:
881
885
  """Return the currently configured embedding model."""
882
- return get_openrag_config().knowledge.embedding_model or EMBED_MODEL if DISABLE_INGEST_WITH_LANGFLOW else ""
886
+ return get_openrag_config().knowledge.embedding_model or (OPENAI_DEFAULT_EMBEDDING_MODEL if DISABLE_INGEST_WITH_LANGFLOW else "")
883
887
 
884
888
 
885
889
  def get_index_name() -> str:
@@ -20,6 +20,8 @@ class ConnectorService:
20
20
  index_name: str,
21
21
  task_service=None,
22
22
  session_manager=None,
23
+ models_service=None,
24
+ document_service=None,
23
25
  ):
24
26
  self.clients = patched_async_client
25
27
  self.embed_model = embed_model
@@ -27,6 +29,8 @@ class ConnectorService:
27
29
  self.task_service = task_service
28
30
  self.session_manager = session_manager
29
31
  self.connection_manager = ConnectionManager()
32
+ self.models_service = models_service
33
+ self.document_service = document_service
30
34
 
31
35
  async def initialize(self):
32
36
  """Initialize the service by loading existing connections"""
@@ -57,18 +61,12 @@ class ConnectorService:
57
61
  with open(tmp_path, "wb") as f:
58
62
  f.write(document.content)
59
63
 
60
- # Use existing process_file_common function with connector document metadata
61
- # We'll use the document service's process_file_common method
62
- from services.document_service import DocumentService
63
-
64
- doc_service = DocumentService(session_manager=self.session_manager)
65
-
66
64
  logger.debug("Processing connector document", document_id=document.id)
67
65
 
68
66
  # Process using consolidated processing pipeline
69
67
  from models.processors import TaskProcessor
70
68
 
71
- processor = TaskProcessor(document_service=doc_service)
69
+ processor = TaskProcessor(document_service=self.document_service, models_service=self.models_service)
72
70
  result = await processor.process_document_standard(
73
71
  file_path=tmp_path,
74
72
  file_hash=document.id, # Use connector document ID as hash
@@ -284,6 +282,7 @@ class ConnectorService:
284
282
  if self.task_service and self.task_service.document_service
285
283
  else DocumentService(session_manager=self.session_manager)
286
284
  ),
285
+ models_service=self.models_service,
287
286
  )
288
287
 
289
288
  # Use file IDs as items (no more fake file paths!)
@@ -415,6 +414,7 @@ class ConnectorService:
415
414
  if self.task_service and self.task_service.document_service
416
415
  else DocumentService(session_manager=self.session_manager)
417
416
  ),
417
+ models_service=self.models_service,
418
418
  )
419
419
 
420
420
  # Create custom task using TaskService