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,157 @@
|
|
1
|
+
"""
|
2
|
+
Chat models for RAG-powered conversations.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.db import models
|
6
|
+
from .base import UserScopedModel
|
7
|
+
|
8
|
+
|
9
|
+
class ChatSession(UserScopedModel):
|
10
|
+
"""User chat session for conversation tracking."""
|
11
|
+
|
12
|
+
# Custom managers
|
13
|
+
from ..managers.chat import ChatSessionManager
|
14
|
+
objects = ChatSessionManager()
|
15
|
+
|
16
|
+
title = models.CharField(
|
17
|
+
max_length=255,
|
18
|
+
blank=True,
|
19
|
+
help_text="Session title (auto-generated if empty)"
|
20
|
+
)
|
21
|
+
is_active = models.BooleanField(
|
22
|
+
default=True,
|
23
|
+
help_text="Whether session accepts new messages"
|
24
|
+
)
|
25
|
+
|
26
|
+
# Session statistics
|
27
|
+
messages_count = models.PositiveIntegerField(default=0)
|
28
|
+
total_tokens_used = models.PositiveIntegerField(default=0)
|
29
|
+
total_cost_usd = models.FloatField(
|
30
|
+
default=0.0,
|
31
|
+
help_text="Total session cost for monitoring"
|
32
|
+
)
|
33
|
+
|
34
|
+
# Configuration
|
35
|
+
model_name = models.CharField(
|
36
|
+
max_length=100,
|
37
|
+
default="openai/gpt-4o-mini",
|
38
|
+
help_text="LLM model used for this session"
|
39
|
+
)
|
40
|
+
temperature = models.FloatField(
|
41
|
+
default=0.7,
|
42
|
+
help_text="Temperature setting for LLM"
|
43
|
+
)
|
44
|
+
max_context_chunks = models.PositiveIntegerField(
|
45
|
+
default=5,
|
46
|
+
help_text="Maximum chunks to include in context"
|
47
|
+
)
|
48
|
+
|
49
|
+
class Meta:
|
50
|
+
db_table = 'django_cfg_knowbase_chat_sessions'
|
51
|
+
indexes = [
|
52
|
+
models.Index(fields=['user', '-created_at']),
|
53
|
+
models.Index(fields=['is_active']),
|
54
|
+
]
|
55
|
+
|
56
|
+
def __str__(self) -> str:
|
57
|
+
return self.title or f"Session {self.id}"
|
58
|
+
|
59
|
+
def generate_title_if_empty(self) -> None:
|
60
|
+
"""Auto-generate title based on first message."""
|
61
|
+
if not self.title and self.messages.exists():
|
62
|
+
first_message = self.messages.filter(
|
63
|
+
role='user'
|
64
|
+
).first()
|
65
|
+
if first_message:
|
66
|
+
# Take first 50 characters as title
|
67
|
+
self.title = first_message.content[:50] + "..."
|
68
|
+
self.save(update_fields=['title'])
|
69
|
+
|
70
|
+
def archive(self) -> None:
|
71
|
+
"""Archive (deactivate) this session."""
|
72
|
+
self.is_active = False
|
73
|
+
self.save(update_fields=['is_active'])
|
74
|
+
|
75
|
+
def activate(self) -> None:
|
76
|
+
"""Activate this session."""
|
77
|
+
self.is_active = True
|
78
|
+
self.save(update_fields=['is_active'])
|
79
|
+
|
80
|
+
@property
|
81
|
+
def is_archived(self) -> bool:
|
82
|
+
"""Check if session is archived."""
|
83
|
+
return not self.is_active
|
84
|
+
|
85
|
+
|
86
|
+
class ChatMessage(UserScopedModel):
|
87
|
+
"""Individual chat message with context tracking."""
|
88
|
+
|
89
|
+
# Custom managers
|
90
|
+
from ..managers.chat import ChatMessageManager
|
91
|
+
objects = ChatMessageManager()
|
92
|
+
|
93
|
+
class MessageRole(models.TextChoices):
|
94
|
+
USER = 'user', 'User'
|
95
|
+
ASSISTANT = 'assistant', 'Assistant'
|
96
|
+
SYSTEM = 'system', 'System'
|
97
|
+
|
98
|
+
session = models.ForeignKey(
|
99
|
+
ChatSession,
|
100
|
+
on_delete=models.CASCADE,
|
101
|
+
related_name='messages',
|
102
|
+
help_text="Parent chat session"
|
103
|
+
)
|
104
|
+
|
105
|
+
role = models.CharField(
|
106
|
+
max_length=10,
|
107
|
+
choices=MessageRole.choices,
|
108
|
+
help_text="Message sender role"
|
109
|
+
)
|
110
|
+
content = models.TextField(
|
111
|
+
help_text="Message content"
|
112
|
+
)
|
113
|
+
|
114
|
+
# Context tracking
|
115
|
+
context_chunks = models.JSONField(
|
116
|
+
default=list,
|
117
|
+
help_text="IDs of chunks used for context"
|
118
|
+
)
|
119
|
+
|
120
|
+
# Usage tracking (for monitoring, not billing)
|
121
|
+
tokens_used = models.PositiveIntegerField(
|
122
|
+
default=0,
|
123
|
+
help_text="Tokens used for this message"
|
124
|
+
)
|
125
|
+
cost_usd = models.FloatField(
|
126
|
+
default=0.0,
|
127
|
+
help_text="Cost in USD for this message"
|
128
|
+
)
|
129
|
+
processing_time_ms = models.PositiveIntegerField(
|
130
|
+
default=0,
|
131
|
+
help_text="Processing time in milliseconds"
|
132
|
+
)
|
133
|
+
|
134
|
+
# Response metadata
|
135
|
+
model_name = models.CharField(
|
136
|
+
max_length=100,
|
137
|
+
blank=True,
|
138
|
+
help_text="Model used for response generation"
|
139
|
+
)
|
140
|
+
finish_reason = models.CharField(
|
141
|
+
max_length=20,
|
142
|
+
blank=True,
|
143
|
+
help_text="Why the model stopped generating"
|
144
|
+
)
|
145
|
+
|
146
|
+
class Meta:
|
147
|
+
db_table = 'django_cfg_knowbase_chat_messages'
|
148
|
+
indexes = [
|
149
|
+
models.Index(fields=['session', '-created_at']),
|
150
|
+
models.Index(fields=['role']),
|
151
|
+
models.Index(fields=['-created_at']),
|
152
|
+
]
|
153
|
+
ordering = ['created_at']
|
154
|
+
|
155
|
+
def __str__(self) -> str:
|
156
|
+
preview = self.content[:50] + "..." if len(self.content) > 50 else self.content
|
157
|
+
return f"{self.role}: {preview}"
|
@@ -0,0 +1,267 @@
|
|
1
|
+
"""
|
2
|
+
Document models with pgvector support.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.db import models
|
6
|
+
from pgvector.django import VectorField
|
7
|
+
from typing import Optional, List
|
8
|
+
from .base import UserScopedModel, ProcessingStatus, TimestampedModel
|
9
|
+
|
10
|
+
|
11
|
+
class DocumentCategory(TimestampedModel):
|
12
|
+
"""Document category for organization and access control."""
|
13
|
+
|
14
|
+
name = models.CharField(
|
15
|
+
max_length=255,
|
16
|
+
unique=True,
|
17
|
+
help_text="Category name"
|
18
|
+
)
|
19
|
+
description = models.TextField(
|
20
|
+
blank=True,
|
21
|
+
help_text="Category description"
|
22
|
+
)
|
23
|
+
is_public = models.BooleanField(
|
24
|
+
default=True,
|
25
|
+
help_text="Whether documents in this category are publicly accessible"
|
26
|
+
)
|
27
|
+
|
28
|
+
class Meta:
|
29
|
+
db_table = 'django_cfg_knowbase_document_categories'
|
30
|
+
verbose_name = 'Document Category'
|
31
|
+
verbose_name_plural = 'Document Categories'
|
32
|
+
ordering = ['name']
|
33
|
+
|
34
|
+
def __str__(self) -> str:
|
35
|
+
return f"{self.name} ({'Public' if self.is_public else 'Private'})"
|
36
|
+
|
37
|
+
|
38
|
+
class Document(UserScopedModel):
|
39
|
+
"""Knowledge document with processing status tracking."""
|
40
|
+
|
41
|
+
# Custom managers
|
42
|
+
from ..managers.document import DocumentManager
|
43
|
+
objects = DocumentManager()
|
44
|
+
|
45
|
+
title = models.CharField(
|
46
|
+
max_length=512,
|
47
|
+
help_text="Document title"
|
48
|
+
)
|
49
|
+
content = models.TextField(
|
50
|
+
help_text="Full document content"
|
51
|
+
)
|
52
|
+
# Multiple categories field
|
53
|
+
categories = models.ManyToManyField(
|
54
|
+
DocumentCategory,
|
55
|
+
blank=True,
|
56
|
+
related_name='documents',
|
57
|
+
help_text="Document categories (supports multiple)"
|
58
|
+
)
|
59
|
+
is_public = models.BooleanField(
|
60
|
+
default=True,
|
61
|
+
help_text="Whether this document is publicly accessible"
|
62
|
+
)
|
63
|
+
content_hash = models.CharField(
|
64
|
+
max_length=64,
|
65
|
+
db_index=True,
|
66
|
+
help_text="SHA-256 hash for duplicate detection"
|
67
|
+
)
|
68
|
+
file_type = models.CharField(
|
69
|
+
max_length=100,
|
70
|
+
default="text/plain",
|
71
|
+
help_text="MIME type of original file"
|
72
|
+
)
|
73
|
+
file_size = models.PositiveIntegerField(
|
74
|
+
default=0,
|
75
|
+
help_text="Original file size in bytes"
|
76
|
+
)
|
77
|
+
|
78
|
+
# Processing status
|
79
|
+
processing_status = models.CharField(
|
80
|
+
max_length=20,
|
81
|
+
choices=ProcessingStatus.choices,
|
82
|
+
default=ProcessingStatus.PENDING,
|
83
|
+
db_index=True
|
84
|
+
)
|
85
|
+
processing_started_at = models.DateTimeField(null=True, blank=True)
|
86
|
+
processing_completed_at = models.DateTimeField(null=True, blank=True)
|
87
|
+
processing_error = models.TextField(blank=True, default="")
|
88
|
+
|
89
|
+
# Chunk statistics
|
90
|
+
chunks_count = models.PositiveIntegerField(default=0)
|
91
|
+
total_tokens = models.PositiveIntegerField(default=0)
|
92
|
+
|
93
|
+
# Cost tracking for monitoring
|
94
|
+
total_cost_usd = models.FloatField(
|
95
|
+
default=0.0,
|
96
|
+
help_text="Total processing cost in USD"
|
97
|
+
)
|
98
|
+
|
99
|
+
# Metadata
|
100
|
+
metadata = models.JSONField(
|
101
|
+
default=dict,
|
102
|
+
blank=True,
|
103
|
+
null=True,
|
104
|
+
help_text="Additional document metadata"
|
105
|
+
)
|
106
|
+
|
107
|
+
class Meta:
|
108
|
+
db_table = 'django_cfg_knowbase_documents'
|
109
|
+
indexes = [
|
110
|
+
models.Index(fields=['user', 'processing_status']),
|
111
|
+
models.Index(fields=['content_hash']),
|
112
|
+
models.Index(fields=['-processing_completed_at']),
|
113
|
+
models.Index(fields=['is_public', '-created_at']), # For multiple categories queries
|
114
|
+
]
|
115
|
+
constraints = [
|
116
|
+
models.UniqueConstraint(
|
117
|
+
fields=['user', 'content_hash'],
|
118
|
+
name='unique_user_document'
|
119
|
+
)
|
120
|
+
]
|
121
|
+
|
122
|
+
def save(self, *args, **kwargs):
|
123
|
+
"""Override save to generate content_hash if not provided."""
|
124
|
+
if not self.content_hash and self.content:
|
125
|
+
import hashlib
|
126
|
+
self.content_hash = hashlib.sha256(self.content.encode()).hexdigest()
|
127
|
+
|
128
|
+
# Set file_size if not provided
|
129
|
+
if not self.file_size and self.content:
|
130
|
+
self.file_size = len(self.content.encode('utf-8'))
|
131
|
+
|
132
|
+
super().save(*args, **kwargs)
|
133
|
+
|
134
|
+
def __str__(self) -> str:
|
135
|
+
return f"{self.title} ({self.user.username})"
|
136
|
+
|
137
|
+
@property
|
138
|
+
def is_processed(self) -> bool:
|
139
|
+
"""Check if document processing is completed."""
|
140
|
+
return self.processing_status == ProcessingStatus.COMPLETED
|
141
|
+
|
142
|
+
@property
|
143
|
+
def processing_duration(self) -> Optional[float]:
|
144
|
+
"""Calculate processing duration in seconds."""
|
145
|
+
if self.processing_started_at and self.processing_completed_at:
|
146
|
+
delta = self.processing_completed_at - self.processing_started_at
|
147
|
+
return delta.total_seconds()
|
148
|
+
return None
|
149
|
+
|
150
|
+
@property
|
151
|
+
def is_publicly_accessible(self) -> bool:
|
152
|
+
"""Check if document is publicly accessible (document and at least one category must be public)."""
|
153
|
+
if not self.is_public:
|
154
|
+
return False
|
155
|
+
|
156
|
+
# If document has categories, at least one must be public
|
157
|
+
if self.categories.exists():
|
158
|
+
return self.categories.filter(is_public=True).exists()
|
159
|
+
|
160
|
+
# If no categories assigned, document is public by default
|
161
|
+
return True
|
162
|
+
|
163
|
+
def get_all_categories(self):
|
164
|
+
"""Get all categories for this document."""
|
165
|
+
return list(self.categories.all())
|
166
|
+
|
167
|
+
def add_category(self, category):
|
168
|
+
"""Add a category to this document."""
|
169
|
+
self.categories.add(category)
|
170
|
+
|
171
|
+
def remove_category(self, category):
|
172
|
+
"""Remove a category from this document."""
|
173
|
+
self.categories.remove(category)
|
174
|
+
|
175
|
+
def set_categories(self, categories_list):
|
176
|
+
"""Set multiple categories for this document."""
|
177
|
+
self.categories.set(categories_list)
|
178
|
+
|
179
|
+
|
180
|
+
class DocumentChunk(UserScopedModel):
|
181
|
+
"""Text chunk with vector embedding for semantic search."""
|
182
|
+
|
183
|
+
# Custom managers
|
184
|
+
from ..managers.document import DocumentChunkManager
|
185
|
+
objects = DocumentChunkManager()
|
186
|
+
|
187
|
+
document = models.ForeignKey(
|
188
|
+
Document,
|
189
|
+
on_delete=models.CASCADE,
|
190
|
+
related_name='chunks',
|
191
|
+
help_text="Parent document"
|
192
|
+
)
|
193
|
+
content = models.TextField(
|
194
|
+
help_text="Chunk text content"
|
195
|
+
)
|
196
|
+
chunk_index = models.PositiveIntegerField(
|
197
|
+
help_text="Sequential chunk number within document"
|
198
|
+
)
|
199
|
+
|
200
|
+
# Vector embedding (1536 dimensions for OpenAI text-embedding-ada-002)
|
201
|
+
embedding = VectorField(
|
202
|
+
dimensions=1536,
|
203
|
+
help_text="Vector embedding for semantic search"
|
204
|
+
)
|
205
|
+
|
206
|
+
# Chunk statistics
|
207
|
+
token_count = models.PositiveIntegerField(
|
208
|
+
default=0,
|
209
|
+
help_text="Number of tokens in chunk"
|
210
|
+
)
|
211
|
+
character_count = models.PositiveIntegerField(
|
212
|
+
default=0,
|
213
|
+
help_text="Number of characters in chunk"
|
214
|
+
)
|
215
|
+
|
216
|
+
# Processing metadata
|
217
|
+
embedding_model = models.CharField(
|
218
|
+
max_length=100,
|
219
|
+
default="text-embedding-ada-002",
|
220
|
+
help_text="Model used for embedding generation"
|
221
|
+
)
|
222
|
+
embedding_cost = models.FloatField(
|
223
|
+
default=0.0,
|
224
|
+
help_text="Cost in USD for embedding generation"
|
225
|
+
)
|
226
|
+
|
227
|
+
# Additional metadata
|
228
|
+
metadata = models.JSONField(
|
229
|
+
default=dict,
|
230
|
+
blank=True,
|
231
|
+
null=True,
|
232
|
+
help_text="Chunk-specific metadata"
|
233
|
+
)
|
234
|
+
|
235
|
+
class Meta:
|
236
|
+
db_table = 'django_cfg_knowbase_document_chunks'
|
237
|
+
indexes = [
|
238
|
+
models.Index(fields=['user']),
|
239
|
+
models.Index(fields=['document', 'chunk_index']),
|
240
|
+
]
|
241
|
+
constraints = [
|
242
|
+
models.UniqueConstraint(
|
243
|
+
fields=['document', 'chunk_index'],
|
244
|
+
name='unique_document_chunk'
|
245
|
+
)
|
246
|
+
]
|
247
|
+
ordering = ['document', 'chunk_index']
|
248
|
+
|
249
|
+
def __str__(self) -> str:
|
250
|
+
return f"Chunk {self.chunk_index} of {self.document.title}"
|
251
|
+
|
252
|
+
@classmethod
|
253
|
+
def semantic_search(
|
254
|
+
cls,
|
255
|
+
user,
|
256
|
+
query_embedding: List[float],
|
257
|
+
limit: int = 5,
|
258
|
+
similarity_threshold: float = 0.7
|
259
|
+
):
|
260
|
+
"""Perform semantic search using pgvector."""
|
261
|
+
from pgvector.django import CosineDistance
|
262
|
+
|
263
|
+
return cls.objects.filter(user=user).annotate(
|
264
|
+
similarity=1 - CosineDistance('embedding', query_embedding)
|
265
|
+
).filter(
|
266
|
+
similarity__gte=similarity_threshold
|
267
|
+
).order_by('-similarity')[:limit]
|