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.
- django_cfg/__init__.py +20 -448
- django_cfg/apps/accounts/README.md +3 -3
- django_cfg/apps/accounts/admin/__init__.py +0 -2
- django_cfg/apps/accounts/admin/activity.py +2 -9
- django_cfg/apps/accounts/admin/filters.py +0 -42
- django_cfg/apps/accounts/admin/inlines.py +8 -8
- django_cfg/apps/accounts/admin/otp.py +5 -5
- django_cfg/apps/accounts/admin/registration_source.py +1 -8
- django_cfg/apps/accounts/admin/user.py +12 -20
- django_cfg/apps/accounts/managers/user_manager.py +2 -129
- django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
- django_cfg/apps/accounts/models.py +3 -123
- django_cfg/apps/accounts/serializers/otp.py +40 -44
- django_cfg/apps/accounts/serializers/profile.py +0 -2
- django_cfg/apps/accounts/services/otp_service.py +98 -186
- django_cfg/apps/accounts/signals.py +25 -15
- django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
- django_cfg/apps/accounts/views/otp.py +35 -36
- django_cfg/apps/agents/README.md +129 -0
- django_cfg/apps/agents/__init__.py +68 -0
- django_cfg/apps/agents/admin/__init__.py +17 -0
- django_cfg/apps/agents/admin/execution_admin.py +460 -0
- django_cfg/apps/agents/admin/registry_admin.py +360 -0
- django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
- django_cfg/apps/agents/apps.py +29 -0
- django_cfg/apps/agents/core/__init__.py +20 -0
- django_cfg/apps/agents/core/agent.py +281 -0
- django_cfg/apps/agents/core/dependencies.py +154 -0
- django_cfg/apps/agents/core/exceptions.py +66 -0
- django_cfg/apps/agents/core/models.py +106 -0
- django_cfg/apps/agents/core/orchestrator.py +391 -0
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/agents/integration/__init__.py +14 -0
- django_cfg/apps/agents/integration/middleware.py +80 -0
- django_cfg/apps/agents/integration/registry.py +345 -0
- django_cfg/apps/agents/integration/signals.py +50 -0
- django_cfg/apps/agents/management/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/create_agent.py +365 -0
- django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
- django_cfg/apps/agents/managers/__init__.py +23 -0
- django_cfg/apps/agents/managers/execution.py +236 -0
- django_cfg/apps/agents/managers/registry.py +254 -0
- django_cfg/apps/agents/managers/toolsets.py +496 -0
- django_cfg/apps/agents/migrations/0001_initial.py +286 -0
- django_cfg/apps/agents/migrations/__init__.py +5 -0
- django_cfg/apps/agents/models/__init__.py +15 -0
- django_cfg/apps/agents/models/execution.py +215 -0
- django_cfg/apps/agents/models/registry.py +220 -0
- django_cfg/apps/agents/models/toolsets.py +305 -0
- django_cfg/apps/agents/patterns/__init__.py +24 -0
- django_cfg/apps/agents/patterns/content_agents.py +234 -0
- django_cfg/apps/agents/toolsets/__init__.py +15 -0
- django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
- django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
- django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
- django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
- django_cfg/apps/agents/urls.py +46 -0
- django_cfg/apps/knowbase/README.md +150 -0
- django_cfg/apps/knowbase/__init__.py +27 -0
- django_cfg/apps/knowbase/admin/__init__.py +23 -0
- django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
- django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
- django_cfg/apps/knowbase/admin/document_admin.py +650 -0
- django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
- django_cfg/apps/knowbase/apps.py +81 -0
- django_cfg/apps/knowbase/config/README.md +176 -0
- django_cfg/apps/knowbase/config/__init__.py +51 -0
- django_cfg/apps/knowbase/config/constance_fields.py +186 -0
- django_cfg/apps/knowbase/config/constance_settings.py +200 -0
- django_cfg/apps/knowbase/config/settings.py +450 -0
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/management/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
- django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
- django_cfg/apps/knowbase/managers/__init__.py +22 -0
- django_cfg/apps/knowbase/managers/archive.py +426 -0
- django_cfg/apps/knowbase/managers/base.py +32 -0
- django_cfg/apps/knowbase/managers/chat.py +141 -0
- django_cfg/apps/knowbase/managers/document.py +203 -0
- django_cfg/apps/knowbase/managers/external_data.py +471 -0
- django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
- django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
- django_cfg/apps/knowbase/migrations/__init__.py +5 -0
- django_cfg/apps/knowbase/mixins/__init__.py +15 -0
- django_cfg/apps/knowbase/mixins/config.py +108 -0
- django_cfg/apps/knowbase/mixins/creator.py +81 -0
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
- django_cfg/apps/knowbase/mixins/service.py +362 -0
- django_cfg/apps/knowbase/models/__init__.py +41 -0
- django_cfg/apps/knowbase/models/archive.py +599 -0
- django_cfg/apps/knowbase/models/base.py +58 -0
- django_cfg/apps/knowbase/models/chat.py +157 -0
- django_cfg/apps/knowbase/models/document.py +267 -0
- django_cfg/apps/knowbase/models/external_data.py +376 -0
- django_cfg/apps/knowbase/serializers/__init__.py +68 -0
- django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
- django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
- django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
- django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
- django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
- django_cfg/apps/knowbase/services/__init__.py +40 -0
- django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
- django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
- django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
- django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
- django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
- django_cfg/apps/knowbase/services/base.py +53 -0
- django_cfg/apps/knowbase/services/chat_service.py +239 -0
- django_cfg/apps/knowbase/services/document_service.py +144 -0
- django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
- django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
- django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
- django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
- django_cfg/apps/knowbase/services/embedding/models.py +229 -0
- django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
- django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
- django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
- django_cfg/apps/knowbase/services/search_service.py +293 -0
- django_cfg/apps/knowbase/signals/__init__.py +21 -0
- django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
- django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
- django_cfg/apps/knowbase/signals/document_signals.py +143 -0
- django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
- django_cfg/apps/knowbase/tasks/__init__.py +39 -0
- django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
- django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
- django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
- django_cfg/apps/knowbase/urls.py +43 -0
- django_cfg/apps/knowbase/utils/__init__.py +12 -0
- django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
- django_cfg/apps/knowbase/utils/text_processing.py +375 -0
- django_cfg/apps/knowbase/utils/validation.py +99 -0
- django_cfg/apps/knowbase/views/__init__.py +28 -0
- django_cfg/apps/knowbase/views/archive_views.py +469 -0
- django_cfg/apps/knowbase/views/base.py +49 -0
- django_cfg/apps/knowbase/views/chat_views.py +181 -0
- django_cfg/apps/knowbase/views/document_views.py +183 -0
- django_cfg/apps/knowbase/views/public_views.py +129 -0
- django_cfg/apps/leads/admin.py +70 -0
- django_cfg/apps/newsletter/admin.py +234 -0
- django_cfg/apps/newsletter/admin_filters.py +124 -0
- django_cfg/apps/support/admin.py +196 -0
- django_cfg/apps/support/admin_filters.py +71 -0
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/apps/urls.py +5 -4
- django_cfg/cli/README.md +1 -1
- django_cfg/cli/commands/create_project.py +2 -2
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/config.py +44 -0
- django_cfg/core/config.py +29 -82
- django_cfg/core/environment.py +1 -1
- django_cfg/core/generation.py +19 -107
- django_cfg/{integration.py → core/integration.py} +18 -16
- django_cfg/core/validation.py +1 -1
- django_cfg/management/__init__.py +1 -1
- django_cfg/management/commands/__init__.py +1 -1
- django_cfg/management/commands/auto_generate.py +482 -0
- django_cfg/management/commands/migrator.py +19 -101
- django_cfg/management/commands/test_email.py +1 -1
- django_cfg/middleware/README.md +0 -158
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/api.py +145 -0
- django_cfg/models/base.py +287 -0
- django_cfg/models/cache.py +4 -4
- django_cfg/models/constance.py +25 -88
- django_cfg/models/database.py +9 -9
- django_cfg/models/drf.py +3 -36
- django_cfg/models/email.py +163 -0
- django_cfg/models/environment.py +276 -0
- django_cfg/models/limits.py +1 -1
- django_cfg/models/logging.py +366 -0
- django_cfg/models/revolution.py +41 -2
- django_cfg/models/security.py +125 -0
- django_cfg/models/services.py +1 -1
- django_cfg/modules/__init__.py +2 -56
- django_cfg/modules/base.py +78 -52
- django_cfg/modules/django_currency/service.py +2 -2
- django_cfg/modules/django_email.py +2 -2
- django_cfg/modules/django_health.py +267 -0
- django_cfg/modules/django_llm/llm/client.py +91 -19
- django_cfg/modules/django_llm/translator/translator.py +2 -2
- django_cfg/modules/django_logger.py +2 -2
- django_cfg/modules/django_ngrok.py +2 -2
- django_cfg/modules/django_tasks.py +68 -3
- django_cfg/modules/django_telegram.py +3 -3
- django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
- django_cfg/modules/django_twilio/service.py +2 -2
- django_cfg/modules/django_twilio/simple_service.py +2 -2
- django_cfg/modules/django_twilio/twilio_service.py +2 -2
- django_cfg/modules/django_unfold/__init__.py +69 -0
- django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
- django_cfg/modules/django_unfold/dashboard.py +278 -0
- django_cfg/modules/django_unfold/icons/README.md +145 -0
- django_cfg/modules/django_unfold/icons/__init__.py +12 -0
- django_cfg/modules/django_unfold/icons/constants.py +2851 -0
- django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
- django_cfg/modules/django_unfold/models/__init__.py +42 -0
- django_cfg/modules/django_unfold/models/config.py +601 -0
- django_cfg/modules/django_unfold/models/dashboard.py +206 -0
- django_cfg/modules/django_unfold/models/dropdown.py +40 -0
- django_cfg/modules/django_unfold/models/navigation.py +73 -0
- django_cfg/modules/django_unfold/models/tabs.py +25 -0
- django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
- django_cfg/modules/django_unfold/utils.py +140 -0
- django_cfg/registry/__init__.py +23 -0
- django_cfg/registry/core.py +61 -0
- django_cfg/registry/exceptions.py +11 -0
- django_cfg/registry/modules.py +12 -0
- django_cfg/registry/services.py +26 -0
- django_cfg/registry/third_party.py +52 -0
- django_cfg/routing/__init__.py +19 -0
- django_cfg/routing/callbacks.py +198 -0
- django_cfg/routing/routers.py +48 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
- django_cfg/templatetags/__init__.py +0 -0
- django_cfg/templatetags/django_cfg.py +33 -0
- django_cfg/urls.py +33 -0
- django_cfg/utils/path_resolution.py +1 -1
- django_cfg/utils/smart_defaults.py +7 -61
- django_cfg/utils/toolkit.py +663 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/METADATA +83 -86
- django_cfg-1.2.1.dist-info/RECORD +441 -0
- django_cfg/archive/django_sample.zip +0 -0
- django_cfg/models/unfold.py +0 -271
- django_cfg/modules/unfold/__init__.py +0 -29
- django_cfg/modules/unfold/dashboard.py +0 -318
- django_cfg/pyproject.toml +0 -370
- django_cfg/routers.py +0 -83
- django_cfg-1.1.82.dist-info/RECORD +0 -278
- /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
- /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
- /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
- /django_cfg/{version_check.py → utils/version_check.py} +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/entry_points.txt +0 -0
- {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
|
+
]
|