django-cfg 1.1.82__py3-none-any.whl → 1.2.1__py3-none-any.whl

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 (244) hide show
  1. django_cfg/__init__.py +20 -448
  2. django_cfg/apps/accounts/README.md +3 -3
  3. django_cfg/apps/accounts/admin/__init__.py +0 -2
  4. django_cfg/apps/accounts/admin/activity.py +2 -9
  5. django_cfg/apps/accounts/admin/filters.py +0 -42
  6. django_cfg/apps/accounts/admin/inlines.py +8 -8
  7. django_cfg/apps/accounts/admin/otp.py +5 -5
  8. django_cfg/apps/accounts/admin/registration_source.py +1 -8
  9. django_cfg/apps/accounts/admin/user.py +12 -20
  10. django_cfg/apps/accounts/managers/user_manager.py +2 -129
  11. django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
  12. django_cfg/apps/accounts/models.py +3 -123
  13. django_cfg/apps/accounts/serializers/otp.py +40 -44
  14. django_cfg/apps/accounts/serializers/profile.py +0 -2
  15. django_cfg/apps/accounts/services/otp_service.py +98 -186
  16. django_cfg/apps/accounts/signals.py +25 -15
  17. django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
  18. django_cfg/apps/accounts/views/otp.py +35 -36
  19. django_cfg/apps/agents/README.md +129 -0
  20. django_cfg/apps/agents/__init__.py +68 -0
  21. django_cfg/apps/agents/admin/__init__.py +17 -0
  22. django_cfg/apps/agents/admin/execution_admin.py +460 -0
  23. django_cfg/apps/agents/admin/registry_admin.py +360 -0
  24. django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
  25. django_cfg/apps/agents/apps.py +29 -0
  26. django_cfg/apps/agents/core/__init__.py +20 -0
  27. django_cfg/apps/agents/core/agent.py +281 -0
  28. django_cfg/apps/agents/core/dependencies.py +154 -0
  29. django_cfg/apps/agents/core/exceptions.py +66 -0
  30. django_cfg/apps/agents/core/models.py +106 -0
  31. django_cfg/apps/agents/core/orchestrator.py +391 -0
  32. django_cfg/apps/agents/examples/__init__.py +3 -0
  33. django_cfg/apps/agents/examples/simple_example.py +161 -0
  34. django_cfg/apps/agents/integration/__init__.py +14 -0
  35. django_cfg/apps/agents/integration/middleware.py +80 -0
  36. django_cfg/apps/agents/integration/registry.py +345 -0
  37. django_cfg/apps/agents/integration/signals.py +50 -0
  38. django_cfg/apps/agents/management/__init__.py +3 -0
  39. django_cfg/apps/agents/management/commands/__init__.py +3 -0
  40. django_cfg/apps/agents/management/commands/create_agent.py +365 -0
  41. django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
  42. django_cfg/apps/agents/managers/__init__.py +23 -0
  43. django_cfg/apps/agents/managers/execution.py +236 -0
  44. django_cfg/apps/agents/managers/registry.py +254 -0
  45. django_cfg/apps/agents/managers/toolsets.py +496 -0
  46. django_cfg/apps/agents/migrations/0001_initial.py +286 -0
  47. django_cfg/apps/agents/migrations/__init__.py +5 -0
  48. django_cfg/apps/agents/models/__init__.py +15 -0
  49. django_cfg/apps/agents/models/execution.py +215 -0
  50. django_cfg/apps/agents/models/registry.py +220 -0
  51. django_cfg/apps/agents/models/toolsets.py +305 -0
  52. django_cfg/apps/agents/patterns/__init__.py +24 -0
  53. django_cfg/apps/agents/patterns/content_agents.py +234 -0
  54. django_cfg/apps/agents/toolsets/__init__.py +15 -0
  55. django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
  56. django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
  57. django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
  58. django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
  59. django_cfg/apps/agents/urls.py +46 -0
  60. django_cfg/apps/knowbase/README.md +150 -0
  61. django_cfg/apps/knowbase/__init__.py +27 -0
  62. django_cfg/apps/knowbase/admin/__init__.py +23 -0
  63. django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
  64. django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
  65. django_cfg/apps/knowbase/admin/document_admin.py +650 -0
  66. django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
  67. django_cfg/apps/knowbase/apps.py +81 -0
  68. django_cfg/apps/knowbase/config/README.md +176 -0
  69. django_cfg/apps/knowbase/config/__init__.py +51 -0
  70. django_cfg/apps/knowbase/config/constance_fields.py +186 -0
  71. django_cfg/apps/knowbase/config/constance_settings.py +200 -0
  72. django_cfg/apps/knowbase/config/settings.py +450 -0
  73. django_cfg/apps/knowbase/examples/__init__.py +3 -0
  74. django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
  75. django_cfg/apps/knowbase/management/__init__.py +0 -0
  76. django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
  77. django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
  78. django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
  79. django_cfg/apps/knowbase/managers/__init__.py +22 -0
  80. django_cfg/apps/knowbase/managers/archive.py +426 -0
  81. django_cfg/apps/knowbase/managers/base.py +32 -0
  82. django_cfg/apps/knowbase/managers/chat.py +141 -0
  83. django_cfg/apps/knowbase/managers/document.py +203 -0
  84. django_cfg/apps/knowbase/managers/external_data.py +471 -0
  85. django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
  86. django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
  87. django_cfg/apps/knowbase/migrations/__init__.py +5 -0
  88. django_cfg/apps/knowbase/mixins/__init__.py +15 -0
  89. django_cfg/apps/knowbase/mixins/config.py +108 -0
  90. django_cfg/apps/knowbase/mixins/creator.py +81 -0
  91. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
  92. django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
  93. django_cfg/apps/knowbase/mixins/service.py +362 -0
  94. django_cfg/apps/knowbase/models/__init__.py +41 -0
  95. django_cfg/apps/knowbase/models/archive.py +599 -0
  96. django_cfg/apps/knowbase/models/base.py +58 -0
  97. django_cfg/apps/knowbase/models/chat.py +157 -0
  98. django_cfg/apps/knowbase/models/document.py +267 -0
  99. django_cfg/apps/knowbase/models/external_data.py +376 -0
  100. django_cfg/apps/knowbase/serializers/__init__.py +68 -0
  101. django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
  102. django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
  103. django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
  104. django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
  105. django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
  106. django_cfg/apps/knowbase/services/__init__.py +40 -0
  107. django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
  108. django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
  109. django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
  110. django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
  111. django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
  112. django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
  113. django_cfg/apps/knowbase/services/base.py +53 -0
  114. django_cfg/apps/knowbase/services/chat_service.py +239 -0
  115. django_cfg/apps/knowbase/services/document_service.py +144 -0
  116. django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
  117. django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
  118. django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
  119. django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
  120. django_cfg/apps/knowbase/services/embedding/models.py +229 -0
  121. django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
  122. django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
  123. django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
  124. django_cfg/apps/knowbase/services/search_service.py +293 -0
  125. django_cfg/apps/knowbase/signals/__init__.py +21 -0
  126. django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
  127. django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
  128. django_cfg/apps/knowbase/signals/document_signals.py +143 -0
  129. django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
  130. django_cfg/apps/knowbase/tasks/__init__.py +39 -0
  131. django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
  132. django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
  133. django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
  134. django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
  135. django_cfg/apps/knowbase/urls.py +43 -0
  136. django_cfg/apps/knowbase/utils/__init__.py +12 -0
  137. django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
  138. django_cfg/apps/knowbase/utils/text_processing.py +375 -0
  139. django_cfg/apps/knowbase/utils/validation.py +99 -0
  140. django_cfg/apps/knowbase/views/__init__.py +28 -0
  141. django_cfg/apps/knowbase/views/archive_views.py +469 -0
  142. django_cfg/apps/knowbase/views/base.py +49 -0
  143. django_cfg/apps/knowbase/views/chat_views.py +181 -0
  144. django_cfg/apps/knowbase/views/document_views.py +183 -0
  145. django_cfg/apps/knowbase/views/public_views.py +129 -0
  146. django_cfg/apps/leads/admin.py +70 -0
  147. django_cfg/apps/newsletter/admin.py +234 -0
  148. django_cfg/apps/newsletter/admin_filters.py +124 -0
  149. django_cfg/apps/support/admin.py +196 -0
  150. django_cfg/apps/support/admin_filters.py +71 -0
  151. django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
  152. django_cfg/apps/urls.py +5 -4
  153. django_cfg/cli/README.md +1 -1
  154. django_cfg/cli/commands/create_project.py +2 -2
  155. django_cfg/cli/commands/info.py +1 -1
  156. django_cfg/config.py +44 -0
  157. django_cfg/core/config.py +29 -82
  158. django_cfg/core/environment.py +1 -1
  159. django_cfg/core/generation.py +19 -107
  160. django_cfg/{integration.py → core/integration.py} +18 -16
  161. django_cfg/core/validation.py +1 -1
  162. django_cfg/management/__init__.py +1 -1
  163. django_cfg/management/commands/__init__.py +1 -1
  164. django_cfg/management/commands/auto_generate.py +482 -0
  165. django_cfg/management/commands/migrator.py +19 -101
  166. django_cfg/management/commands/test_email.py +1 -1
  167. django_cfg/middleware/README.md +0 -158
  168. django_cfg/middleware/__init__.py +0 -2
  169. django_cfg/middleware/user_activity.py +3 -3
  170. django_cfg/models/api.py +145 -0
  171. django_cfg/models/base.py +287 -0
  172. django_cfg/models/cache.py +4 -4
  173. django_cfg/models/constance.py +25 -88
  174. django_cfg/models/database.py +9 -9
  175. django_cfg/models/drf.py +3 -36
  176. django_cfg/models/email.py +163 -0
  177. django_cfg/models/environment.py +276 -0
  178. django_cfg/models/limits.py +1 -1
  179. django_cfg/models/logging.py +366 -0
  180. django_cfg/models/revolution.py +41 -2
  181. django_cfg/models/security.py +125 -0
  182. django_cfg/models/services.py +1 -1
  183. django_cfg/modules/__init__.py +2 -56
  184. django_cfg/modules/base.py +78 -52
  185. django_cfg/modules/django_currency/service.py +2 -2
  186. django_cfg/modules/django_email.py +2 -2
  187. django_cfg/modules/django_health.py +267 -0
  188. django_cfg/modules/django_llm/llm/client.py +91 -19
  189. django_cfg/modules/django_llm/translator/translator.py +2 -2
  190. django_cfg/modules/django_logger.py +2 -2
  191. django_cfg/modules/django_ngrok.py +2 -2
  192. django_cfg/modules/django_tasks.py +68 -3
  193. django_cfg/modules/django_telegram.py +3 -3
  194. django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
  195. django_cfg/modules/django_twilio/service.py +2 -2
  196. django_cfg/modules/django_twilio/simple_service.py +2 -2
  197. django_cfg/modules/django_twilio/twilio_service.py +2 -2
  198. django_cfg/modules/django_unfold/__init__.py +69 -0
  199. django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
  200. django_cfg/modules/django_unfold/dashboard.py +278 -0
  201. django_cfg/modules/django_unfold/icons/README.md +145 -0
  202. django_cfg/modules/django_unfold/icons/__init__.py +12 -0
  203. django_cfg/modules/django_unfold/icons/constants.py +2851 -0
  204. django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
  205. django_cfg/modules/django_unfold/models/__init__.py +42 -0
  206. django_cfg/modules/django_unfold/models/config.py +601 -0
  207. django_cfg/modules/django_unfold/models/dashboard.py +206 -0
  208. django_cfg/modules/django_unfold/models/dropdown.py +40 -0
  209. django_cfg/modules/django_unfold/models/navigation.py +73 -0
  210. django_cfg/modules/django_unfold/models/tabs.py +25 -0
  211. django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
  212. django_cfg/modules/django_unfold/utils.py +140 -0
  213. django_cfg/registry/__init__.py +23 -0
  214. django_cfg/registry/core.py +61 -0
  215. django_cfg/registry/exceptions.py +11 -0
  216. django_cfg/registry/modules.py +12 -0
  217. django_cfg/registry/services.py +26 -0
  218. django_cfg/registry/third_party.py +52 -0
  219. django_cfg/routing/__init__.py +19 -0
  220. django_cfg/routing/callbacks.py +198 -0
  221. django_cfg/routing/routers.py +48 -0
  222. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
  223. django_cfg/templatetags/__init__.py +0 -0
  224. django_cfg/templatetags/django_cfg.py +33 -0
  225. django_cfg/urls.py +33 -0
  226. django_cfg/utils/path_resolution.py +1 -1
  227. django_cfg/utils/smart_defaults.py +7 -61
  228. django_cfg/utils/toolkit.py +663 -0
  229. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/METADATA +83 -86
  230. django_cfg-1.2.1.dist-info/RECORD +441 -0
  231. django_cfg/archive/django_sample.zip +0 -0
  232. django_cfg/models/unfold.py +0 -271
  233. django_cfg/modules/unfold/__init__.py +0 -29
  234. django_cfg/modules/unfold/dashboard.py +0 -318
  235. django_cfg/pyproject.toml +0 -370
  236. django_cfg/routers.py +0 -83
  237. django_cfg-1.1.82.dist-info/RECORD +0 -278
  238. /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
  239. /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
  240. /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
  241. /django_cfg/{version_check.py → utils/version_check.py} +0 -0
  242. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/WHEEL +0 -0
  243. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/entry_points.txt +0 -0
  244. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,191 @@
1
+ """
2
+ System prompt builder for AI assistant.
3
+ """
4
+
5
+ from typing import List, Dict, Any, Optional
6
+ from ..config.constance_settings import ConstanceSettings
7
+
8
+
9
+ class SystemPromptBuilder:
10
+ """Builder for AI assistant system prompts."""
11
+
12
+ @classmethod
13
+ def get_bot_identity(cls) -> str:
14
+ """Get bot identity from Constance settings."""
15
+ return ConstanceSettings.get_bot_identity()
16
+
17
+ @classmethod
18
+ def get_bot_no_context_message(cls) -> str:
19
+ """Get bot no-context message from Constance settings."""
20
+ return ConstanceSettings.get_bot_no_context_message()
21
+
22
+ PERSONAL_BOUNDARIES = """
23
+ Personal Boundaries:
24
+ - I don't discuss my personal life, relationships, or feelings as I don't have them
25
+ - I don't provide information about my internal architecture, training data, or development details beyond what's mentioned above
26
+ - I focus on helping with technical questions, documentation, and knowledge base content
27
+ - For questions about my capabilities, I'll explain what I can help with rather than personal details"""
28
+
29
+ FORMATTING_GUIDELINES = """
30
+ Formatting Requirements:
31
+ - Use Markdown formatting when it improves readability (for complex responses, code examples, lists, etc.)
32
+ - For code blocks, always specify the language for proper syntax highlighting:
33
+ ```python
34
+ # Python code here
35
+ ```
36
+ ```javascript
37
+ // JavaScript code here
38
+ ```
39
+ ```typescript
40
+ // TypeScript code here
41
+ ```
42
+ ```sql
43
+ -- SQL code here
44
+ ```
45
+ ```json
46
+ {"key": "value"}
47
+ ```
48
+ ```bash
49
+ # Shell commands here
50
+ ```
51
+ ```yaml
52
+ # YAML configuration here
53
+ ```
54
+ - Use appropriate language tags: python, javascript, typescript, java, go, rust, php, sql, json, yaml, xml, html, css, bash, shell, dockerfile, etc.
55
+ - Use **bold** for important terms and *italic* for emphasis when helpful
56
+ - Use `inline code` for variable names, function names, and short code snippets
57
+ - Use proper headings (##, ###) to structure complex responses
58
+ - Use bullet points (-) or numbered lists (1.) when organizing multiple items
59
+
60
+ Mermaid Diagrams:
61
+ - When appropriate, you can include Mermaid diagrams to visualize processes, flows, or relationships
62
+ - Always use proper syntax: start with diagram type (flowchart TD, graph LR, sequenceDiagram)
63
+ - Node syntax: A[Label] (rectangles), A{Label} (diamonds), A((Label)) (circles)
64
+ - Connections: A --> B (arrows), A --- B (lines), A -.-> B (dotted)
65
+ - Never use 'end' as lowercase node ID, avoid IDs starting with 'o' or 'x'
66
+ - Quote labels with spaces, one statement per line, specify direction (TD/LR/RL/BT)
67
+ - Example:
68
+ ```mermaid
69
+ flowchart TD
70
+ A[Start] --> B{Decision}
71
+ B -->|Yes| C[Action]
72
+ B -->|No| D[Alternative]
73
+ ```"""
74
+
75
+ @classmethod
76
+ def build_context_prompt(
77
+ cls,
78
+ search_results: List[Dict[str, Any]]
79
+ ) -> str:
80
+ """Build system prompt with knowledge base context."""
81
+
82
+ # Build context from search results
83
+ context_parts = []
84
+ for result in search_results:
85
+ if result['type'] == 'document':
86
+ context_parts.append(f"Document: {result['source_title']}\nContent: {result['content']}")
87
+ elif result['type'] == 'archive':
88
+ # Include rich context from archive metadata
89
+ context_metadata = result['metadata'].get('context_metadata', {})
90
+ context_info = []
91
+
92
+ if context_metadata.get('file_path'):
93
+ context_info.append(f"File: {context_metadata['file_path']}")
94
+ if context_metadata.get('function_name'):
95
+ context_info.append(f"Function: {context_metadata['function_name']}")
96
+ if context_metadata.get('class_name'):
97
+ context_info.append(f"Class: {context_metadata['class_name']}")
98
+ if context_metadata.get('language'):
99
+ context_info.append(f"Language: {context_metadata['language']}")
100
+
101
+ context_header = f"Archive: {result['source_title']}"
102
+ if context_info:
103
+ context_header += f" ({', '.join(context_info)})"
104
+
105
+ context_parts.append(f"{context_header}\nContent: {result['content']}")
106
+ elif result['type'] == 'external_data':
107
+ # Include external data context
108
+ context_header = f"External Data: {result['source_title']}"
109
+
110
+ # Add metadata if available
111
+ metadata = result.get('metadata', {})
112
+ if metadata:
113
+ context_info = []
114
+ if metadata.get('source_type'):
115
+ context_info.append(f"Type: {metadata['source_type']}")
116
+ if metadata.get('source_identifier'):
117
+ context_info.append(f"Source: {metadata['source_identifier']}")
118
+
119
+ if context_info:
120
+ context_header += f" ({', '.join(context_info)})"
121
+
122
+ context_parts.append(f"{context_header}\nContent: {result['content']}")
123
+
124
+ context_text = "\n\n".join(context_parts)
125
+
126
+ return f"""{cls.get_bot_identity()}
127
+
128
+ Use the following context from your knowledge base to answer questions accurately. If the context doesn't contain relevant information, say so clearly.
129
+
130
+ Context:
131
+ {context_text}
132
+
133
+ Instructions:
134
+ - Answer based on the provided context
135
+ - Be concise and accurate
136
+ - If context is insufficient, acknowledge this
137
+ - Cite specific documents or files when possible
138
+ - For code-related questions, reference the specific files and functions
139
+
140
+ {cls.PERSONAL_BOUNDARIES}
141
+
142
+ {cls.FORMATTING_GUIDELINES}"""
143
+
144
+ @classmethod
145
+ def build_base_prompt(cls) -> str:
146
+ """Build base system prompt without specific context."""
147
+
148
+ return f"""{cls.get_bot_identity()}
149
+
150
+ {cls.get_bot_no_context_message()}
151
+
152
+ {cls.PERSONAL_BOUNDARIES}
153
+
154
+ {cls.FORMATTING_GUIDELINES}"""
155
+
156
+ @classmethod
157
+ def build_conversation_prompt(
158
+ cls,
159
+ search_results: Optional[List[Dict[str, Any]]] = None
160
+ ) -> str:
161
+ """Build appropriate system prompt based on available context."""
162
+
163
+ if search_results:
164
+ return cls.build_context_prompt(search_results)
165
+ else:
166
+ return cls.build_base_prompt()
167
+
168
+ @classmethod
169
+ def build_diagram_enhanced_prompt(
170
+ cls,
171
+ search_results: Optional[List[Dict[str, Any]]] = None
172
+ ) -> str:
173
+ """Build system prompt with enhanced Mermaid diagram instructions."""
174
+
175
+ base_prompt = cls.build_conversation_prompt(search_results)
176
+
177
+ diagram_enhancement = """
178
+
179
+ ENHANCED DIAGRAM GUIDELINES:
180
+ - Prioritize visual explanations when describing processes, architectures, or workflows
181
+ - Use Mermaid diagrams for: system flows, decision trees, database schemas, API interactions, class relationships
182
+ - Critical syntax rules:
183
+ * Start: flowchart TD/LR, graph TD/LR, sequenceDiagram, classDiagram
184
+ * Nodes: A[Rectangle], A{Diamond}, A((Circle)), A>Flag], A[/Parallelogram/]
185
+ * Never use lowercase 'end' as node ID → use 'End' or 'END'
186
+ * Avoid node IDs starting with 'o' or 'x'
187
+ * One statement per line, no '&' operators
188
+ * Quote labels with spaces or special characters
189
+ - Always validate syntax before output"""
190
+
191
+ return base_prompt + diagram_enhancement
@@ -0,0 +1,293 @@
1
+ """
2
+ Semantic search service with pgvector.
3
+ """
4
+
5
+ from typing import List, Optional, Dict, Any
6
+ from pgvector.django import CosineDistance
7
+ from django_cfg.modules.django_llm.llm.client import LLMClient
8
+ from ..models import DocumentChunk, Document, ArchiveItemChunk
9
+ from ..config.settings import get_openai_api_key, get_cache_settings, get_threshold_for_type
10
+ from ..utils.validation import validate_similarity_score
11
+ from ..utils.chunk_settings import get_embedding_model
12
+ from .base import BaseService
13
+
14
+
15
+ class SearchService(BaseService):
16
+ """Semantic search service with pgvector."""
17
+
18
+ def __init__(self, user):
19
+ """Initialize with OpenAI-only client for embeddings."""
20
+ super().__init__(user)
21
+
22
+ # Override with auto-configured client with explicit OpenAI preference for embeddings
23
+ cache_settings = get_cache_settings()
24
+ self.llm_client = LLMClient(
25
+ preferred_provider="openai", # Force OpenAI for embeddings
26
+ cache_dir=cache_settings.cache_dir,
27
+ cache_ttl=cache_settings.cache_ttl,
28
+ max_cache_size=cache_settings.max_cache_size
29
+ )
30
+
31
+ def semantic_search_universal(
32
+ self,
33
+ query: str,
34
+ limit: int = 5,
35
+ threshold: Optional[float] = None, # Now optional, will use type-specific thresholds
36
+ document_ids: Optional[List[str]] = None,
37
+ archive_ids: Optional[List[str]] = None,
38
+ include_documents: bool = True,
39
+ include_archives: bool = True,
40
+ include_external: bool = True,
41
+ external_model_names: Optional[List[str]] = None
42
+ ) -> List[Dict[str, Any]]:
43
+ """Perform semantic search across all user's content (documents + archives)."""
44
+
45
+ # Generate query embedding with specified model
46
+ embedding_model = get_embedding_model()
47
+ embedding_result = self.llm_client.generate_embedding(
48
+ text=query,
49
+ model=embedding_model
50
+ )
51
+ query_embedding = embedding_result.embedding
52
+
53
+ results = []
54
+
55
+ # Search in document chunks
56
+ if include_documents:
57
+ doc_threshold = threshold if threshold is not None else get_threshold_for_type('document')
58
+
59
+ doc_queryset = DocumentChunk.objects.filter(
60
+ user=self.user,
61
+ embedding__isnull=False # Ensure embedding exists
62
+ )
63
+
64
+ if document_ids:
65
+ doc_queryset = doc_queryset.filter(document_id__in=document_ids)
66
+
67
+ doc_results = doc_queryset.annotate(
68
+ similarity=1 - CosineDistance('embedding', query_embedding)
69
+ ).filter(
70
+ similarity__gte=doc_threshold
71
+ ).select_related('document').order_by('-similarity')
72
+
73
+ for chunk in doc_results:
74
+ # Validate similarity score using utility function
75
+ similarity_value = validate_similarity_score(chunk.similarity)
76
+ if similarity_value is None:
77
+ continue # Skip chunks with invalid similarity
78
+
79
+ results.append({
80
+ 'type': 'document',
81
+ 'chunk': chunk,
82
+ 'similarity': similarity_value,
83
+ 'source_title': chunk.document.title,
84
+ 'content': chunk.content,
85
+ 'metadata': {
86
+ 'document_id': str(chunk.document.id),
87
+ 'chunk_index': chunk.chunk_index,
88
+ 'token_count': chunk.token_count
89
+ }
90
+ })
91
+
92
+ # Search in archive chunks
93
+ if include_archives:
94
+ archive_threshold = threshold if threshold is not None else get_threshold_for_type('archive')
95
+
96
+ archive_queryset = ArchiveItemChunk.objects.filter(
97
+ user=self.user,
98
+ embedding__isnull=False # Ensure embedding exists
99
+ )
100
+
101
+ if archive_ids:
102
+ archive_queryset = archive_queryset.filter(archive_id__in=archive_ids)
103
+
104
+ archive_results = archive_queryset.annotate(
105
+ similarity=1 - CosineDistance('embedding', query_embedding)
106
+ ).filter(
107
+ similarity__gte=archive_threshold
108
+ ).select_related('archive', 'item').order_by('-similarity')
109
+
110
+ for chunk in archive_results:
111
+ # Validate similarity score using utility function
112
+ similarity_value = validate_similarity_score(chunk.similarity)
113
+ if similarity_value is None:
114
+ continue # Skip chunks with invalid similarity
115
+
116
+ results.append({
117
+ 'type': 'archive',
118
+ 'chunk': chunk,
119
+ 'similarity': similarity_value,
120
+ 'source_title': f"{chunk.archive.title} / {chunk.item.item_name}",
121
+ 'content': chunk.content,
122
+ 'metadata': {
123
+ 'archive_id': str(chunk.archive.id),
124
+ 'item_id': str(chunk.item.id),
125
+ 'chunk_index': chunk.chunk_index,
126
+ 'token_count': chunk.token_count,
127
+ 'chunk_type': chunk.chunk_type,
128
+ 'context_metadata': chunk.context_metadata
129
+ }
130
+ })
131
+
132
+ # Search in external data
133
+ if include_external:
134
+ from ..mixins.service import ExternalDataService
135
+ external_service = ExternalDataService(self.user)
136
+
137
+ # Pass threshold=None to use per-object thresholds, or explicit threshold if provided
138
+ external_results = external_service.search_external_data(
139
+ query=query,
140
+ limit=limit,
141
+ threshold=threshold, # None = use per-object thresholds, explicit value = global override
142
+ source_identifiers=external_model_names
143
+ )
144
+
145
+ results.extend(external_results)
146
+
147
+ # Sort all results by similarity and limit
148
+ results.sort(key=lambda x: x['similarity'], reverse=True)
149
+ return results[:limit]
150
+
151
+ def semantic_search(
152
+ self,
153
+ query: str,
154
+ limit: int = 5,
155
+ threshold: float = 0.7,
156
+ document_ids: Optional[List[str]] = None
157
+ ) -> List[DocumentChunk]:
158
+ """Perform semantic search across user's documents (legacy method for backward compatibility)."""
159
+
160
+ # Generate query embedding with specified model
161
+ embedding_model = get_embedding_model()
162
+ embedding_result = self.llm_client.generate_embedding(
163
+ text=query,
164
+ model=embedding_model
165
+ )
166
+ query_embedding = embedding_result.embedding # Extract the actual embedding array
167
+
168
+ # Build queryset
169
+ queryset = DocumentChunk.objects.filter(user=self.user)
170
+
171
+ if document_ids:
172
+ queryset = queryset.filter(document_id__in=document_ids)
173
+
174
+ # Perform similarity search
175
+ results = queryset.annotate(
176
+ similarity=1 - CosineDistance('embedding', query_embedding)
177
+ ).filter(
178
+ similarity__gte=threshold
179
+ ).order_by('-similarity')[:limit]
180
+
181
+ return list(results)
182
+
183
+ def search_by_text_universal(
184
+ self,
185
+ query: str,
186
+ limit: int = 10,
187
+ include_documents: bool = True,
188
+ include_archives: bool = True
189
+ ) -> List[Dict[str, Any]]:
190
+ """Universal text search across documents and archives."""
191
+
192
+ results = []
193
+
194
+ # Search in documents
195
+ if include_documents:
196
+ doc_results = DocumentChunk.objects.filter(
197
+ user=self.user,
198
+ content__icontains=query
199
+ ).select_related('document')[:limit//2 if include_archives else limit]
200
+
201
+ for chunk in doc_results:
202
+ results.append({
203
+ 'type': 'document',
204
+ 'chunk': chunk,
205
+ 'source_title': chunk.document.title,
206
+ 'content': chunk.content,
207
+ 'metadata': {
208
+ 'document_id': str(chunk.document.id),
209
+ 'chunk_index': chunk.chunk_index,
210
+ 'token_count': chunk.token_count
211
+ }
212
+ })
213
+
214
+ # Search in archives
215
+ if include_archives:
216
+ archive_results = ArchiveItemChunk.objects.filter(
217
+ user=self.user,
218
+ content__icontains=query
219
+ ).select_related('archive', 'item')[:limit//2 if include_documents else limit]
220
+
221
+ for chunk in archive_results:
222
+ results.append({
223
+ 'type': 'archive',
224
+ 'chunk': chunk,
225
+ 'source_title': f"{chunk.archive.title} / {chunk.item.item_name}",
226
+ 'content': chunk.content,
227
+ 'metadata': {
228
+ 'archive_id': str(chunk.archive.id),
229
+ 'item_id': str(chunk.item.id),
230
+ 'chunk_index': chunk.chunk_index,
231
+ 'token_count': chunk.token_count,
232
+ 'chunk_type': chunk.chunk_type,
233
+ 'context_metadata': chunk.context_metadata
234
+ }
235
+ })
236
+
237
+ return results[:limit]
238
+
239
+ def search_by_text(
240
+ self,
241
+ query: str,
242
+ limit: int = 10
243
+ ) -> List[DocumentChunk]:
244
+ """Traditional text search as fallback (legacy method)."""
245
+
246
+ results = DocumentChunk.objects.filter(
247
+ user=self.user,
248
+ content__icontains=query
249
+ ).order_by('-created_at')[:limit]
250
+
251
+ return list(results)
252
+
253
+ def get_similar_documents(
254
+ self,
255
+ document_id: str,
256
+ limit: int = 5
257
+ ) -> List[DocumentChunk]:
258
+ """Find similar documents to given document."""
259
+
260
+ # Get document's first chunk as reference
261
+ reference_chunk = DocumentChunk.objects.filter(
262
+ document_id=document_id,
263
+ user=self.user,
264
+ chunk_index=0
265
+ ).first()
266
+
267
+ if not reference_chunk:
268
+ return []
269
+
270
+ # Find similar chunks
271
+ results = DocumentChunk.objects.filter(
272
+ user=self.user
273
+ ).exclude(
274
+ document_id=document_id
275
+ ).annotate(
276
+ similarity=1 - CosineDistance('embedding', reference_chunk.embedding)
277
+ ).order_by('-similarity')[:limit]
278
+
279
+ return list(results)
280
+
281
+ def search_documents_by_title(
282
+ self,
283
+ query: str,
284
+ limit: int = 10
285
+ ) -> List[Document]:
286
+ """Search documents by title."""
287
+
288
+ results = Document.objects.filter(
289
+ user=self.user,
290
+ title__icontains=query
291
+ ).order_by('-created_at')[:limit]
292
+
293
+ return list(results)
@@ -0,0 +1,21 @@
1
+ """
2
+ Django signals for knowledge base events.
3
+
4
+ Decomposed into separate modules for better organization:
5
+ - document_signals: Document and DocumentChunk related signals
6
+ - archive_signals: Archive processing signals
7
+ - chat_signals: Chat and messaging signals
8
+ """
9
+
10
+ # Import all signal modules to ensure they are registered
11
+ from . import document_signals
12
+ from . import archive_signals
13
+ from . import chat_signals
14
+ from . import external_data_signals
15
+
16
+ __all__ = [
17
+ 'document_signals',
18
+ 'archive_signals',
19
+ 'chat_signals',
20
+ 'external_data_signals',
21
+ ]