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,341 @@
|
|
1
|
+
"""
|
2
|
+
External Data processing tasks with Dramatiq.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import dramatiq
|
6
|
+
import logging
|
7
|
+
import time
|
8
|
+
from typing import Dict, List, Any, Tuple, Optional
|
9
|
+
from django.db import transaction
|
10
|
+
from django.utils import timezone
|
11
|
+
|
12
|
+
from ..models.external_data import ExternalData, ExternalDataChunk, ExternalDataStatus
|
13
|
+
from ..mixins.service import ExternalDataService
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
@dramatiq.actor(
|
19
|
+
queue_name="knowledge",
|
20
|
+
max_retries=3,
|
21
|
+
min_backoff=1000, # 1 second
|
22
|
+
max_backoff=30000, # 30 seconds
|
23
|
+
priority=5
|
24
|
+
)
|
25
|
+
def process_external_data_async(
|
26
|
+
external_data_id: str,
|
27
|
+
force_reprocess: bool = False
|
28
|
+
) -> Dict[str, Any]:
|
29
|
+
"""
|
30
|
+
Process external data asynchronously with full pipeline.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
external_data_id: ExternalData UUID to process
|
34
|
+
force_reprocess: Force reprocessing even if already completed
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
Processing results with statistics
|
38
|
+
"""
|
39
|
+
start_time = time.time()
|
40
|
+
|
41
|
+
try:
|
42
|
+
with transaction.atomic():
|
43
|
+
# Get external data instance
|
44
|
+
external_data = ExternalData.objects.select_for_update().get(id=external_data_id)
|
45
|
+
|
46
|
+
# Check if already processing
|
47
|
+
if external_data.status == ExternalDataStatus.PROCESSING:
|
48
|
+
logger.warning(f"External data {external_data_id} is already being processed")
|
49
|
+
return {
|
50
|
+
'success': False,
|
51
|
+
'error': 'Already processing',
|
52
|
+
'external_data_id': external_data_id
|
53
|
+
}
|
54
|
+
|
55
|
+
# Check if already completed and not forcing reprocess
|
56
|
+
if external_data.status == ExternalDataStatus.COMPLETED and not force_reprocess:
|
57
|
+
logger.info(f"External data {external_data_id} already completed, skipping")
|
58
|
+
return {
|
59
|
+
'success': True,
|
60
|
+
'skipped': True,
|
61
|
+
'external_data_id': external_data_id,
|
62
|
+
'total_chunks': external_data.total_chunks,
|
63
|
+
'total_tokens': external_data.total_tokens,
|
64
|
+
'processing_cost': external_data.processing_cost
|
65
|
+
}
|
66
|
+
|
67
|
+
# Check if has content
|
68
|
+
if not external_data.content or not external_data.content.strip():
|
69
|
+
logger.warning(f"External data {external_data_id} has no content to process")
|
70
|
+
external_data.status = ExternalDataStatus.COMPLETED
|
71
|
+
external_data.processed_at = timezone.now()
|
72
|
+
external_data.total_chunks = 0
|
73
|
+
external_data.total_tokens = 0
|
74
|
+
external_data.processing_cost = 0.0
|
75
|
+
external_data.save(update_fields=[
|
76
|
+
'status', 'processed_at', 'total_chunks',
|
77
|
+
'total_tokens', 'processing_cost'
|
78
|
+
])
|
79
|
+
return {
|
80
|
+
'success': True,
|
81
|
+
'external_data_id': external_data_id,
|
82
|
+
'total_chunks': 0,
|
83
|
+
'total_tokens': 0,
|
84
|
+
'processing_cost': 0.0,
|
85
|
+
'processing_time': time.time() - start_time
|
86
|
+
}
|
87
|
+
|
88
|
+
logger.info(f"🚀 Starting external data processing: {external_data.title} (ID: {external_data_id})")
|
89
|
+
|
90
|
+
# Update status to processing
|
91
|
+
external_data.status = ExternalDataStatus.PROCESSING
|
92
|
+
external_data.processing_error = ""
|
93
|
+
external_data.save(update_fields=['status', 'processing_error'])
|
94
|
+
|
95
|
+
# Process external data using service (outside transaction for long-running operations)
|
96
|
+
service = ExternalDataService(external_data.user)
|
97
|
+
success = service.vectorize_external_data(external_data)
|
98
|
+
|
99
|
+
# Refresh from database to get updated statistics
|
100
|
+
external_data.refresh_from_db()
|
101
|
+
|
102
|
+
processing_time = time.time() - start_time
|
103
|
+
|
104
|
+
if success:
|
105
|
+
logger.info(
|
106
|
+
f"✅ External data processing completed: {external_data.title} "
|
107
|
+
f"({external_data.total_chunks} chunks, {external_data.total_tokens} tokens, "
|
108
|
+
f"${external_data.processing_cost:.6f}, {processing_time:.2f}s)"
|
109
|
+
)
|
110
|
+
|
111
|
+
return {
|
112
|
+
'success': True,
|
113
|
+
'external_data_id': external_data_id,
|
114
|
+
'title': external_data.title,
|
115
|
+
'total_chunks': external_data.total_chunks,
|
116
|
+
'total_tokens': external_data.total_tokens,
|
117
|
+
'processing_cost': external_data.processing_cost,
|
118
|
+
'processing_time': processing_time
|
119
|
+
}
|
120
|
+
else:
|
121
|
+
logger.error(f"❌ External data processing failed: {external_data.title}")
|
122
|
+
return {
|
123
|
+
'success': False,
|
124
|
+
'external_data_id': external_data_id,
|
125
|
+
'error': external_data.processing_error or 'Unknown processing error',
|
126
|
+
'processing_time': processing_time
|
127
|
+
}
|
128
|
+
|
129
|
+
except ExternalData.DoesNotExist:
|
130
|
+
error_msg = f"External data {external_data_id} not found"
|
131
|
+
logger.error(f"❌ {error_msg}")
|
132
|
+
return {
|
133
|
+
'success': False,
|
134
|
+
'error': error_msg,
|
135
|
+
'external_data_id': external_data_id
|
136
|
+
}
|
137
|
+
|
138
|
+
except Exception as e:
|
139
|
+
processing_time = time.time() - start_time
|
140
|
+
error_msg = f"Unexpected error processing external data {external_data_id}: {e}"
|
141
|
+
logger.error(f"❌ {error_msg}", exc_info=True)
|
142
|
+
|
143
|
+
# Try to update status to failed
|
144
|
+
try:
|
145
|
+
external_data = ExternalData.objects.get(id=external_data_id)
|
146
|
+
external_data.status = ExternalDataStatus.FAILED
|
147
|
+
external_data.processing_error = str(e)
|
148
|
+
external_data.save(update_fields=['status', 'processing_error'])
|
149
|
+
except Exception as save_error:
|
150
|
+
logger.error(f"❌ Failed to update external data status: {save_error}")
|
151
|
+
|
152
|
+
return {
|
153
|
+
'success': False,
|
154
|
+
'error': error_msg,
|
155
|
+
'external_data_id': external_data_id,
|
156
|
+
'processing_time': processing_time
|
157
|
+
}
|
158
|
+
|
159
|
+
|
160
|
+
@dramatiq.actor(
|
161
|
+
queue_name="knowledge",
|
162
|
+
max_retries=2,
|
163
|
+
min_backoff=5000, # 5 seconds
|
164
|
+
max_backoff=60000, # 60 seconds
|
165
|
+
priority=3
|
166
|
+
)
|
167
|
+
def bulk_process_external_data_async(
|
168
|
+
user_id: int,
|
169
|
+
external_data_ids: Optional[List[str]] = None,
|
170
|
+
force_reprocess: bool = False
|
171
|
+
) -> Dict[str, Any]:
|
172
|
+
"""
|
173
|
+
Process multiple external data sources asynchronously.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
user_id: User ID to process external data for
|
177
|
+
external_data_ids: Specific external data IDs to process (None for all pending)
|
178
|
+
force_reprocess: Force reprocessing even if already completed
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
Bulk processing results with statistics
|
182
|
+
"""
|
183
|
+
start_time = time.time()
|
184
|
+
|
185
|
+
try:
|
186
|
+
from django.contrib.auth import get_user_model
|
187
|
+
User = get_user_model()
|
188
|
+
user = User.objects.get(id=user_id)
|
189
|
+
|
190
|
+
# Get external data to process
|
191
|
+
if external_data_ids:
|
192
|
+
external_data_queryset = ExternalData.objects.filter(
|
193
|
+
id__in=external_data_ids,
|
194
|
+
user=user
|
195
|
+
)
|
196
|
+
else:
|
197
|
+
# Process all pending external data for user
|
198
|
+
external_data_queryset = ExternalData.objects.filter(
|
199
|
+
user=user,
|
200
|
+
status=ExternalDataStatus.PENDING
|
201
|
+
)
|
202
|
+
|
203
|
+
external_data_list = list(external_data_queryset)
|
204
|
+
total_count = len(external_data_list)
|
205
|
+
|
206
|
+
if total_count == 0:
|
207
|
+
logger.info(f"No external data to process for user {user_id}")
|
208
|
+
return {
|
209
|
+
'success': True,
|
210
|
+
'user_id': user_id,
|
211
|
+
'total_count': 0,
|
212
|
+
'processed_count': 0,
|
213
|
+
'failed_count': 0,
|
214
|
+
'skipped_count': 0,
|
215
|
+
'processing_time': time.time() - start_time
|
216
|
+
}
|
217
|
+
|
218
|
+
logger.info(f"🚀 Starting bulk external data processing for user {user_id}: {total_count} items")
|
219
|
+
|
220
|
+
# Process each external data
|
221
|
+
processed_count = 0
|
222
|
+
failed_count = 0
|
223
|
+
skipped_count = 0
|
224
|
+
results = []
|
225
|
+
|
226
|
+
for external_data in external_data_list:
|
227
|
+
try:
|
228
|
+
result = process_external_data_async.send(
|
229
|
+
str(external_data.id),
|
230
|
+
force_reprocess=force_reprocess
|
231
|
+
)
|
232
|
+
|
233
|
+
if result.get('success'):
|
234
|
+
if result.get('skipped'):
|
235
|
+
skipped_count += 1
|
236
|
+
else:
|
237
|
+
processed_count += 1
|
238
|
+
else:
|
239
|
+
failed_count += 1
|
240
|
+
|
241
|
+
results.append(result)
|
242
|
+
|
243
|
+
except Exception as e:
|
244
|
+
logger.error(f"❌ Failed to queue external data {external_data.id}: {e}")
|
245
|
+
failed_count += 1
|
246
|
+
results.append({
|
247
|
+
'success': False,
|
248
|
+
'external_data_id': str(external_data.id),
|
249
|
+
'error': f'Failed to queue: {e}'
|
250
|
+
})
|
251
|
+
|
252
|
+
processing_time = time.time() - start_time
|
253
|
+
|
254
|
+
logger.info(
|
255
|
+
f"✅ Bulk external data processing queued for user {user_id}: "
|
256
|
+
f"{processed_count} processed, {failed_count} failed, "
|
257
|
+
f"{skipped_count} skipped out of {total_count} total ({processing_time:.2f}s)"
|
258
|
+
)
|
259
|
+
|
260
|
+
return {
|
261
|
+
'success': True,
|
262
|
+
'user_id': user_id,
|
263
|
+
'total_count': total_count,
|
264
|
+
'processed_count': processed_count,
|
265
|
+
'failed_count': failed_count,
|
266
|
+
'skipped_count': skipped_count,
|
267
|
+
'processing_time': processing_time,
|
268
|
+
'results': results
|
269
|
+
}
|
270
|
+
|
271
|
+
except Exception as e:
|
272
|
+
processing_time = time.time() - start_time
|
273
|
+
error_msg = f"Unexpected error in bulk external data processing for user {user_id}: {e}"
|
274
|
+
logger.error(f"❌ {error_msg}", exc_info=True)
|
275
|
+
|
276
|
+
return {
|
277
|
+
'success': False,
|
278
|
+
'error': error_msg,
|
279
|
+
'user_id': user_id,
|
280
|
+
'processing_time': processing_time
|
281
|
+
}
|
282
|
+
|
283
|
+
|
284
|
+
@dramatiq.actor(
|
285
|
+
queue_name="maintenance",
|
286
|
+
max_retries=1,
|
287
|
+
priority=1
|
288
|
+
)
|
289
|
+
def cleanup_failed_external_data_async(older_than_days: int = 7) -> Dict[str, Any]:
|
290
|
+
"""
|
291
|
+
Clean up old failed external data processing attempts.
|
292
|
+
|
293
|
+
Args:
|
294
|
+
older_than_days: Remove failed external data older than this many days
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
Cleanup results
|
298
|
+
"""
|
299
|
+
start_time = time.time()
|
300
|
+
|
301
|
+
try:
|
302
|
+
from django.utils import timezone
|
303
|
+
from datetime import timedelta
|
304
|
+
|
305
|
+
cutoff_date = timezone.now() - timedelta(days=older_than_days)
|
306
|
+
|
307
|
+
# Find failed external data older than cutoff
|
308
|
+
failed_external_data = ExternalData.objects.filter(
|
309
|
+
status=ExternalDataStatus.FAILED,
|
310
|
+
updated_at__lt=cutoff_date
|
311
|
+
)
|
312
|
+
|
313
|
+
count = failed_external_data.count()
|
314
|
+
|
315
|
+
if count > 0:
|
316
|
+
logger.info(f"🧹 Cleaning up {count} failed external data older than {older_than_days} days")
|
317
|
+
deleted_count, _ = failed_external_data.delete()
|
318
|
+
logger.info(f"✅ Cleaned up {deleted_count} failed external data records")
|
319
|
+
else:
|
320
|
+
logger.info(f"No failed external data older than {older_than_days} days found")
|
321
|
+
deleted_count = 0
|
322
|
+
|
323
|
+
processing_time = time.time() - start_time
|
324
|
+
|
325
|
+
return {
|
326
|
+
'success': True,
|
327
|
+
'deleted_count': deleted_count,
|
328
|
+
'older_than_days': older_than_days,
|
329
|
+
'processing_time': processing_time
|
330
|
+
}
|
331
|
+
|
332
|
+
except Exception as e:
|
333
|
+
processing_time = time.time() - start_time
|
334
|
+
error_msg = f"Error cleaning up failed external data: {e}"
|
335
|
+
logger.error(f"❌ {error_msg}", exc_info=True)
|
336
|
+
|
337
|
+
return {
|
338
|
+
'success': False,
|
339
|
+
'error': error_msg,
|
340
|
+
'processing_time': processing_time
|
341
|
+
}
|
@@ -0,0 +1,195 @@
|
|
1
|
+
"""
|
2
|
+
Maintenance and cleanup tasks.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import dramatiq
|
6
|
+
import logging
|
7
|
+
from typing import Dict, Any
|
8
|
+
from django.db import connection
|
9
|
+
from django.utils import timezone
|
10
|
+
from datetime import timedelta
|
11
|
+
from django_cfg.modules.django_llm.llm.client import LLMClient
|
12
|
+
from django.core.cache import cache
|
13
|
+
from django.conf import settings
|
14
|
+
|
15
|
+
from ..models import Document, DocumentChunk, ProcessingStatus
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
@dramatiq.actor(
|
21
|
+
queue_name="knowledge",
|
22
|
+
max_retries=1,
|
23
|
+
priority=2
|
24
|
+
)
|
25
|
+
def cleanup_old_embeddings(days_old: int = 90) -> Dict[str, Any]:
|
26
|
+
"""
|
27
|
+
Clean up old, unused embeddings and optimize storage.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
days_old: Age threshold for cleanup
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
Cleanup statistics
|
34
|
+
"""
|
35
|
+
try:
|
36
|
+
cutoff_date = timezone.now() - timedelta(days=days_old)
|
37
|
+
|
38
|
+
# Find orphaned chunks (documents deleted but chunks remain)
|
39
|
+
orphaned_chunks = DocumentChunk.objects.filter(
|
40
|
+
document__isnull=True
|
41
|
+
)
|
42
|
+
orphaned_count = orphaned_chunks.count()
|
43
|
+
orphaned_chunks.delete()
|
44
|
+
|
45
|
+
# Find very old, unused chunks
|
46
|
+
old_chunks = DocumentChunk.objects.filter(
|
47
|
+
created_at__lt=cutoff_date,
|
48
|
+
document__processing_status=ProcessingStatus.FAILED
|
49
|
+
)
|
50
|
+
old_count = old_chunks.count()
|
51
|
+
old_chunks.delete()
|
52
|
+
|
53
|
+
# Vacuum and analyze tables
|
54
|
+
with connection.cursor() as cursor:
|
55
|
+
cursor.execute("VACUUM ANALYZE django_cfg_knowbase_document_chunks;")
|
56
|
+
cursor.execute("VACUUM ANALYZE django_cfg_knowbase_documents;")
|
57
|
+
|
58
|
+
result = {
|
59
|
+
"orphaned_chunks_deleted": orphaned_count,
|
60
|
+
"old_chunks_deleted": old_count,
|
61
|
+
"total_deleted": orphaned_count + old_count,
|
62
|
+
"cutoff_date": cutoff_date.isoformat(),
|
63
|
+
"timestamp": timezone.now().isoformat()
|
64
|
+
}
|
65
|
+
|
66
|
+
logger.info(f"Cleanup completed: {result}")
|
67
|
+
return result
|
68
|
+
|
69
|
+
except Exception as exc:
|
70
|
+
logger.error(f"Cleanup failed: {exc}")
|
71
|
+
raise
|
72
|
+
|
73
|
+
|
74
|
+
@dramatiq.actor(
|
75
|
+
queue_name="knowledge",
|
76
|
+
max_retries=1,
|
77
|
+
priority=1
|
78
|
+
)
|
79
|
+
def optimize_vector_indexes() -> Dict[str, Any]:
|
80
|
+
"""
|
81
|
+
Optimize pgvector indexes for better performance.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
Optimization results
|
85
|
+
"""
|
86
|
+
try:
|
87
|
+
with connection.cursor() as cursor:
|
88
|
+
# Reindex vector indexes (match model definition)
|
89
|
+
cursor.execute("REINDEX INDEX CONCURRENTLY embedding_cosine_idx;")
|
90
|
+
|
91
|
+
# Update table statistics
|
92
|
+
cursor.execute("ANALYZE django_cfg_knowbase_document_chunks;")
|
93
|
+
|
94
|
+
# Get index usage statistics
|
95
|
+
cursor.execute("""
|
96
|
+
SELECT
|
97
|
+
schemaname,
|
98
|
+
tablename,
|
99
|
+
indexname,
|
100
|
+
idx_tup_read,
|
101
|
+
idx_tup_fetch
|
102
|
+
FROM pg_stat_user_indexes
|
103
|
+
WHERE tablename = 'django_cfg_knowbase_document_chunks';
|
104
|
+
""")
|
105
|
+
|
106
|
+
index_stats = cursor.fetchall()
|
107
|
+
|
108
|
+
logger.info("Vector indexes optimized successfully")
|
109
|
+
|
110
|
+
return {
|
111
|
+
"status": "optimized",
|
112
|
+
"index_stats": index_stats,
|
113
|
+
"timestamp": timezone.now().isoformat()
|
114
|
+
}
|
115
|
+
|
116
|
+
except Exception as exc:
|
117
|
+
logger.error(f"Index optimization failed: {exc}")
|
118
|
+
raise
|
119
|
+
|
120
|
+
|
121
|
+
@dramatiq.actor(
|
122
|
+
queue_name="knowledge",
|
123
|
+
max_retries=1,
|
124
|
+
priority=1
|
125
|
+
)
|
126
|
+
def health_check_knowledge_base() -> Dict[str, Any]:
|
127
|
+
"""
|
128
|
+
Perform health check on knowledge base components.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
Health check results
|
132
|
+
"""
|
133
|
+
try:
|
134
|
+
health_status = {
|
135
|
+
"timestamp": timezone.now().isoformat(),
|
136
|
+
"database": "unknown",
|
137
|
+
"embeddings": "unknown",
|
138
|
+
"llm_service": "unknown",
|
139
|
+
"redis_cache": "unknown",
|
140
|
+
"statistics": {}
|
141
|
+
}
|
142
|
+
|
143
|
+
# Check database connectivity
|
144
|
+
try:
|
145
|
+
total_docs = Document.objects.count()
|
146
|
+
total_chunks = DocumentChunk.objects.count()
|
147
|
+
|
148
|
+
health_status["database"] = "healthy"
|
149
|
+
health_status["statistics"]["total_documents"] = total_docs
|
150
|
+
health_status["statistics"]["total_chunks"] = total_chunks
|
151
|
+
except Exception:
|
152
|
+
health_status["database"] = "unhealthy"
|
153
|
+
|
154
|
+
# Check LLM service
|
155
|
+
try:
|
156
|
+
llm_service = LLMClient(
|
157
|
+
apikey_openrouter=settings.api_keys.openrouter,
|
158
|
+
apikey_openai=settings.api_keys.openai
|
159
|
+
)
|
160
|
+
if hasattr(llm_service, 'is_configured') and llm_service.is_configured:
|
161
|
+
health_status["llm_service"] = "healthy"
|
162
|
+
elif settings.api_keys.openrouter:
|
163
|
+
health_status["llm_service"] = "healthy"
|
164
|
+
else:
|
165
|
+
health_status["llm_service"] = "not_configured"
|
166
|
+
except Exception:
|
167
|
+
health_status["llm_service"] = "unhealthy"
|
168
|
+
|
169
|
+
# Check Redis cache
|
170
|
+
try:
|
171
|
+
cache.set("health_check", "ok", 10)
|
172
|
+
if cache.get("health_check") == "ok":
|
173
|
+
health_status["redis_cache"] = "healthy"
|
174
|
+
else:
|
175
|
+
health_status["redis_cache"] = "unhealthy"
|
176
|
+
except Exception:
|
177
|
+
health_status["redis_cache"] = "unhealthy"
|
178
|
+
|
179
|
+
# Overall health
|
180
|
+
health_status["overall"] = (
|
181
|
+
"healthy" if all(
|
182
|
+
status == "healthy" for status in [
|
183
|
+
health_status["database"],
|
184
|
+
health_status["llm_service"],
|
185
|
+
health_status["redis_cache"]
|
186
|
+
]
|
187
|
+
) else "degraded"
|
188
|
+
)
|
189
|
+
|
190
|
+
logger.info(f"Health check completed: {health_status['overall']}")
|
191
|
+
return health_status
|
192
|
+
|
193
|
+
except Exception as exc:
|
194
|
+
logger.error(f"Health check failed: {exc}")
|
195
|
+
raise
|
@@ -0,0 +1,43 @@
|
|
1
|
+
"""
|
2
|
+
Knowledge Base URL Configuration
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.urls import path, include
|
6
|
+
from rest_framework.routers import DefaultRouter
|
7
|
+
from .views import (
|
8
|
+
DocumentViewSet, ChatViewSet, ChatSessionViewSet,
|
9
|
+
DocumentArchiveViewSet, ArchiveItemViewSet, ArchiveItemChunkViewSet
|
10
|
+
)
|
11
|
+
from .views.public_views import PublicDocumentViewSet, PublicCategoryViewSet
|
12
|
+
|
13
|
+
# Create router and register viewsets
|
14
|
+
router = DefaultRouter()
|
15
|
+
router.register(r'documents', DocumentViewSet, basename='document')
|
16
|
+
router.register(r'chat', ChatViewSet, basename='chat')
|
17
|
+
router.register(r'sessions', ChatSessionViewSet, basename='session')
|
18
|
+
|
19
|
+
# Archive router for authenticated users
|
20
|
+
archive_router = DefaultRouter()
|
21
|
+
archive_router.register(r'archives', DocumentArchiveViewSet, basename='archive')
|
22
|
+
archive_router.register(r'items', ArchiveItemViewSet, basename='archive-item')
|
23
|
+
archive_router.register(r'chunks', ArchiveItemChunkViewSet, basename='archive-chunk')
|
24
|
+
|
25
|
+
# Public router for client access
|
26
|
+
public_router = DefaultRouter()
|
27
|
+
public_router.register(r'documents', PublicDocumentViewSet, basename='public-document')
|
28
|
+
public_router.register(r'categories', PublicCategoryViewSet, basename='public-category')
|
29
|
+
|
30
|
+
# URL patterns
|
31
|
+
urlpatterns = [
|
32
|
+
# Admin API endpoints (require authentication + admin rights)
|
33
|
+
path('admin/', include(router.urls)),
|
34
|
+
|
35
|
+
# Archive API endpoints (require authentication)
|
36
|
+
path('', include(archive_router.urls)),
|
37
|
+
|
38
|
+
# Public API endpoints (no authentication required)
|
39
|
+
path('public/', include(public_router.urls)),
|
40
|
+
]
|
41
|
+
|
42
|
+
# Add app name for namespacing
|
43
|
+
app_name = 'knowbase'
|