django-spire 0.16.12__py3-none-any.whl → 0.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. django_spire/ai/admin.py +3 -1
  2. django_spire/ai/apps.py +2 -0
  3. django_spire/ai/chat/admin.py +15 -9
  4. django_spire/ai/chat/apps.py +4 -1
  5. django_spire/ai/chat/auth/controller.py +3 -1
  6. django_spire/ai/chat/choices.py +2 -0
  7. django_spire/ai/chat/intelligence/maps/intent_llm_map.py +8 -5
  8. django_spire/ai/chat/intelligence/prompts.py +4 -2
  9. django_spire/ai/chat/intelligence/workflows/chat_workflow.py +27 -28
  10. django_spire/ai/chat/message_intel.py +7 -4
  11. django_spire/ai/chat/models.py +8 -9
  12. django_spire/ai/chat/querysets.py +3 -1
  13. django_spire/ai/chat/responses.py +19 -10
  14. django_spire/ai/chat/tools.py +20 -15
  15. django_spire/ai/chat/urls/message_urls.py +2 -1
  16. django_spire/ai/chat/urls/page_urls.py +1 -0
  17. django_spire/ai/chat/views/message_request_views.py +2 -0
  18. django_spire/ai/chat/views/message_response_views.py +4 -4
  19. django_spire/ai/chat/views/message_views.py +2 -0
  20. django_spire/ai/chat/views/page_views.py +7 -2
  21. django_spire/ai/chat/views/template_views.py +2 -0
  22. django_spire/ai/decorators.py +13 -7
  23. django_spire/ai/mixins.py +4 -2
  24. django_spire/ai/models.py +7 -2
  25. django_spire/ai/prompt/bots.py +14 -32
  26. django_spire/ai/prompt/intel.py +1 -1
  27. django_spire/ai/prompt/prompts.py +7 -1
  28. django_spire/ai/prompt/system/bots.py +42 -75
  29. django_spire/ai/prompt/system/intel.py +5 -4
  30. django_spire/ai/prompt/system/prompts.py +5 -1
  31. django_spire/ai/prompt/system/system_prompt_cli.py +15 -9
  32. django_spire/ai/prompt/tests/test_bots.py +14 -11
  33. django_spire/ai/prompt/text_to_prompt_cli.py +5 -2
  34. django_spire/ai/prompt/tuning/bot_tuning_cli.py +14 -13
  35. django_spire/ai/prompt/tuning/bots.py +68 -116
  36. django_spire/ai/prompt/tuning/intel.py +1 -1
  37. django_spire/ai/prompt/tuning/mixins.py +2 -0
  38. django_spire/ai/prompt/tuning/prompt_tuning_cli.py +8 -8
  39. django_spire/ai/prompt/tuning/prompts.py +4 -2
  40. django_spire/ai/sms/admin.py +3 -1
  41. django_spire/ai/sms/apps.py +2 -0
  42. django_spire/ai/sms/decorators.py +2 -0
  43. django_spire/ai/sms/intel.py +4 -2
  44. django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +8 -8
  45. django_spire/ai/sms/models.py +16 -14
  46. django_spire/ai/sms/querysets.py +4 -1
  47. django_spire/ai/sms/tools.py +18 -16
  48. django_spire/ai/sms/urls.py +1 -1
  49. django_spire/ai/sms/views.py +2 -0
  50. django_spire/ai/tests/test_ai.py +3 -5
  51. django_spire/ai/urls.py +1 -0
  52. django_spire/consts.py +1 -1
  53. django_spire/contrib/seeding/field/django/seeder.py +6 -4
  54. django_spire/contrib/seeding/field/llm.py +1 -2
  55. django_spire/contrib/seeding/intelligence/bots/field_seeding_bots.py +7 -8
  56. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +15 -16
  57. django_spire/contrib/seeding/intelligence/intel.py +1 -1
  58. django_spire/contrib/seeding/intelligence/prompts/factory.py +5 -7
  59. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +3 -5
  60. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +1 -2
  61. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +3 -5
  62. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +1 -1
  63. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +3 -3
  64. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +1 -1
  65. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +2 -4
  66. django_spire/contrib/seeding/management/commands/seeding.py +5 -2
  67. django_spire/contrib/seeding/model/base.py +12 -10
  68. django_spire/contrib/seeding/model/django/seeder.py +13 -10
  69. django_spire/contrib/seeding/tests/test_seeding.py +1 -1
  70. django_spire/core/management/commands/spire_startapp.py +84 -46
  71. django_spire/core/management/commands/spire_startapp_pkg/__init__.py +60 -0
  72. django_spire/core/management/commands/spire_startapp_pkg/builder.py +91 -0
  73. django_spire/core/management/commands/spire_startapp_pkg/config.py +115 -0
  74. django_spire/core/management/commands/spire_startapp_pkg/filesystem.py +125 -0
  75. django_spire/core/management/commands/spire_startapp_pkg/generator.py +167 -0
  76. django_spire/core/management/commands/spire_startapp_pkg/maps.py +783 -25
  77. django_spire/core/management/commands/spire_startapp_pkg/permissions.py +147 -0
  78. django_spire/core/management/commands/spire_startapp_pkg/processor.py +144 -57
  79. django_spire/core/management/commands/spire_startapp_pkg/registry.py +89 -0
  80. django_spire/core/management/commands/spire_startapp_pkg/reporter.py +245 -108
  81. django_spire/core/management/commands/spire_startapp_pkg/resolver.py +86 -0
  82. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +252 -0
  83. django_spire/core/management/commands/spire_startapp_pkg/validator.py +96 -0
  84. django_spire/core/middleware/__init__.py +1 -2
  85. django_spire/knowledge/admin.py +2 -0
  86. django_spire/knowledge/apps.py +2 -0
  87. django_spire/knowledge/auth/controller.py +2 -0
  88. django_spire/knowledge/collection/admin.py +3 -0
  89. django_spire/knowledge/collection/forms.py +2 -0
  90. django_spire/knowledge/collection/models.py +4 -0
  91. django_spire/knowledge/collection/querysets.py +3 -3
  92. django_spire/knowledge/collection/seeding/seed.py +2 -0
  93. django_spire/knowledge/collection/tests/factories.py +2 -0
  94. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +2 -0
  95. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +2 -0
  96. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +2 -0
  97. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +2 -0
  98. django_spire/knowledge/collection/views/json_views.py +2 -0
  99. django_spire/knowledge/collection/views/page_views.py +2 -0
  100. django_spire/knowledge/context_processors.py +2 -0
  101. django_spire/knowledge/entry/services/tool_service.py +1 -0
  102. django_spire/knowledge/entry/services/transformation_services.py +0 -1
  103. django_spire/knowledge/entry/tests/factories.py +3 -0
  104. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +2 -0
  105. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +2 -0
  106. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +2 -0
  107. django_spire/knowledge/entry/urls/form_urls.py +1 -0
  108. django_spire/knowledge/entry/urls/json_urls.py +1 -0
  109. django_spire/knowledge/entry/urls/page_urls.py +1 -0
  110. django_spire/knowledge/entry/urls/template_urls.py +1 -0
  111. django_spire/knowledge/entry/version/admin.py +2 -0
  112. django_spire/knowledge/entry/version/block/admin.py +2 -0
  113. django_spire/knowledge/entry/version/block/blocks/heading_block.py +2 -0
  114. django_spire/knowledge/entry/version/block/blocks/sub_heading_block.py +2 -0
  115. django_spire/knowledge/entry/version/block/blocks/text_block.py +2 -0
  116. django_spire/knowledge/entry/version/block/maps.py +2 -0
  117. django_spire/knowledge/entry/version/block/models.py +2 -0
  118. django_spire/knowledge/entry/version/block/tests/factories.py +2 -0
  119. django_spire/knowledge/entry/version/block/tests/test_urls/test_json_urls.py +2 -0
  120. django_spire/knowledge/entry/version/block/views/json_views.py +2 -0
  121. django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +12 -9
  122. django_spire/knowledge/entry/version/maps.py +2 -0
  123. django_spire/knowledge/entry/version/models.py +2 -0
  124. django_spire/knowledge/entry/version/querysets.py +2 -0
  125. django_spire/knowledge/entry/version/seeding/seeder.py +1 -0
  126. django_spire/knowledge/entry/version/tests/factories.py +2 -0
  127. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +3 -0
  128. django_spire/knowledge/entry/version/tests/test_urls/test_form_urls.py +2 -0
  129. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +2 -0
  130. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +2 -0
  131. django_spire/knowledge/entry/version/urls/form_urls.py +1 -0
  132. django_spire/knowledge/entry/version/urls/json_urls.py +1 -0
  133. django_spire/knowledge/entry/version/urls/page_urls.py +1 -0
  134. django_spire/knowledge/entry/version/urls/redirect_urls.py +1 -0
  135. django_spire/knowledge/entry/version/views/form_views.py +2 -0
  136. django_spire/knowledge/entry/version/views/json_views.py +2 -0
  137. django_spire/knowledge/entry/version/views/page_views.py +2 -0
  138. django_spire/knowledge/entry/version/views/redirect_views.py +2 -0
  139. django_spire/knowledge/exceptions.py +2 -0
  140. django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +5 -16
  141. django_spire/knowledge/intelligence/intel/collection_intel.py +3 -1
  142. django_spire/knowledge/intelligence/intel/entry_intel.py +3 -3
  143. django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
  144. django_spire/knowledge/intelligence/maps/collection_map.py +9 -10
  145. django_spire/knowledge/intelligence/maps/entry_map.py +8 -9
  146. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +8 -6
  147. django_spire/knowledge/models.py +2 -0
  148. django_spire/knowledge/seeding/seed.py +2 -0
  149. django_spire/knowledge/templatetags/spire_knowledge_tags.py +3 -0
  150. django_spire/knowledge/urls/__init__.py +1 -0
  151. django_spire/profiling/__init__.py +13 -0
  152. django_spire/profiling/middleware/__init__.py +6 -0
  153. django_spire/{core → profiling}/middleware/profiling.py +63 -58
  154. django_spire/profiling/panel.py +345 -0
  155. django_spire/profiling/templates/panel.html +166 -0
  156. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/METADATA +5 -4
  157. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/RECORD +160 -157
  158. django_spire/core/management/commands/spire_startapp_pkg/constants.py +0 -4
  159. django_spire/core/management/commands/spire_startapp_pkg/manager.py +0 -176
  160. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_detail_card.html +0 -24
  161. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_form_card.html +0 -9
  162. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html +0 -18
  163. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/spirechildapp_form.html +0 -22
  164. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
  165. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_detail_page.html +0 -13
  166. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_form_page.html +0 -13
  167. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_list_page.html +0 -9
  168. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/WHEEL +0 -0
  169. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/licenses/LICENSE.md +0 -0
  170. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/top_level.txt +0 -0
django_spire/ai/admin.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django import forms
2
4
  from django.contrib import admin
3
5
  from django.urls import reverse
@@ -24,7 +26,7 @@ class AiUsageAdmin(AiUsageAdminMixin):
24
26
  search_fields = ('recorded_date',)
25
27
  ordering = ('-recorded_date',)
26
28
 
27
- def get_readonly_fields(self, request, obj=None):
29
+ def get_readonly_fields(self, request, obj=None) -> list[str]:
28
30
  return [field.name for field in self.model._meta.fields]
29
31
 
30
32
  def view_interactions_link(
django_spire/ai/apps.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import AppConfig
2
4
 
3
5
  from django_spire.utils import check_required_apps
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import ClassVar
4
+
1
5
  from django.contrib import admin
2
6
  from django.shortcuts import reverse
3
7
  from django.utils.html import format_html
@@ -5,23 +9,25 @@ from django.utils.http import urlencode
5
9
 
6
10
  from django_spire.ai.chat import models
7
11
 
12
+
8
13
  @admin.register(models.Chat)
9
14
  class ChatAdmin(admin.ModelAdmin):
10
15
  list_display = ('name', 'user', 'view_chat_messages_link', 'created_datetime')
11
16
  search_fields = ('id', 'name')
12
- ordering = ['-id']
17
+ ordering: ClassVar = ['-id']
13
18
 
14
- def get_readonly_fields(self, request, obj=None):
19
+ def get_readonly_fields(self, request, obj=None) -> list[str]:
15
20
  return [field.name for field in self.model._meta.fields]
16
21
 
17
22
  def view_chat_messages_link(self, obj):
18
23
  count = obj.messages.count()
19
24
  url = (
20
- reverse("admin:django_spire_ai_chat_chatmessage_changelist")
21
- + "?"
22
- + urlencode({"chat__id": f"{obj.id}"})
25
+ reverse("admin:django_spire_ai_chat_chatmessage_changelist")
26
+ + "?"
27
+ + urlencode({"chat__id": f"{obj.id}"})
23
28
  )
24
- return format_html('<a href="%s">%s Messages</a>' % (url, count))
29
+
30
+ return format_html(f'<a href="{url}">{count} Messages</a>')
25
31
 
26
32
  view_chat_messages_link.short_description = "Messages"
27
33
 
@@ -33,13 +39,13 @@ class ChatAdmin(admin.ModelAdmin):
33
39
  class ChatMessageAdmin(admin.ModelAdmin):
34
40
  list_display = ('content_body', 'chat', 'chat__user','is_processed', 'is_viewed', 'created_datetime')
35
41
  search_fields = ('id', 'content')
36
- ordering = ['-id']
42
+ ordering: ClassVar = ['-id']
37
43
 
38
- def content_body(self, obj):
44
+ def content_body(self, obj) -> str:
39
45
  return str(obj)
40
46
 
41
47
  content_body.short_description = 'Body'
42
48
 
43
- def get_readonly_fields(self, request, obj=None):
49
+ def get_readonly_fields(self, request, obj=None) -> list[str]:
44
50
  return [field.name for field in self.model._meta.fields]
45
51
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import AppConfig
2
4
  from django.conf import settings
3
5
 
@@ -22,6 +24,7 @@ class AiChatConfig(AppConfig):
22
24
 
23
25
  def ready(self) -> None:
24
26
  if not isinstance(getattr(settings, AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME), str):
25
- raise ValueError(f'"{AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME}" must be set in the django settings when using "{self.label}".')
27
+ message = f'"{AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME}" must be set in the django settings when using "{self.label}".'
28
+ raise TypeError(message)
26
29
 
27
30
  check_required_apps(self.label)
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.auth.controller.controller import BaseAuthController
2
4
 
3
5
 
4
6
  class BaseAiChatAuthController(BaseAuthController):
5
- def can_delete(self):
7
+ def can_delete(self) -> bool:
6
8
  return self.request.user.has_perm('django_spire_ai_chat.delete_chat')
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.db.models import Choices
2
4
 
3
5
 
@@ -1,7 +1,10 @@
1
- from dandy.llm import BaseLlmMap
2
- from dandy.map import Map
1
+ from __future__ import annotations
3
2
 
3
+ from typing import ClassVar
4
4
 
5
- class IntentLlmMap(BaseLlmMap):
6
- map_keys_description = 'The user\'s chat intent'
7
- map = Map({})
5
+ from dandy import Decoder
6
+
7
+
8
+ class IntentDecoder(Decoder):
9
+ mapping_keys_description = 'The user\'s chat intent'
10
+ mapping: ClassVar = {}
@@ -1,9 +1,11 @@
1
- from dandy.llm import Prompt
1
+ from __future__ import annotations
2
+
3
+ from dandy import Prompt
2
4
 
3
5
  from django_spire.conf import settings
4
6
 
5
7
 
6
- def organization_prompt():
8
+ def organization_prompt() -> Prompt:
7
9
  return (
8
10
  Prompt()
9
11
  .text(f'You are a chat assistant for a company called "{settings.ORGANIZATION_NAME}".')
@@ -1,63 +1,63 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Type
3
+ from typing import TYPE_CHECKING
4
4
 
5
- from dandy.map import Map
5
+ from dandy import Prompt, Bot
6
6
  from dandy.recorder import recorder_to_html_file
7
- from dandy.workflow import BaseWorkflow
8
- from dandy.llm import LlmBot, MessageHistory, Prompt
9
- from django.core.handlers.wsgi import WSGIRequest
10
7
 
11
- from django_spire.ai.chat.intelligence.maps.intent_llm_map import IntentLlmMap
8
+ from django_spire.ai.chat.intelligence.maps.intent_llm_map import IntentDecoder
12
9
  from django_spire.ai.chat.intelligence.prompts import organization_prompt
13
10
  from django_spire.ai.chat.message_intel import DefaultMessageIntel
14
11
  from django_spire.auth.controller.controller import AppAuthController
15
- from django_spire.knowledge.intelligence.workflows.knowledge_workflow import \
16
- KnowledgeWorkflow
17
-
18
12
 
19
13
  if TYPE_CHECKING:
14
+ from dandy.llm.request.message import MessageHistory
15
+ from django.core.handlers.wsgi import WSGIRequest
16
+
20
17
  from django_spire.ai.chat.message_intel import BaseMessageIntel
21
18
 
22
19
 
23
- class SpireChatWorkflow(BaseWorkflow):
24
- @classmethod
25
- def _generate_intent_map(cls, request: WSGIRequest) -> Type[IntentLlmMap]:
20
+ class SpireChatWorkflow:
21
+ @staticmethod
22
+ def _generate_intent_decoder(request: WSGIRequest) -> IntentDecoder:
23
+ from django_spire.knowledge.intelligence.workflows.knowledge_workflow import KnowledgeWorkflow
24
+
26
25
  intent_dict = {}
27
26
 
28
27
  if AppAuthController(app_name='knowledge', request=request).can_view():
29
28
  intent_message = (
30
29
  'The user is looking for information or knowledge on something.'
31
30
  )
31
+
32
32
  intent_dict[intent_message] = KnowledgeWorkflow
33
33
 
34
34
  intent_dict['None of the above choices match the user\'s intent'] = None
35
35
 
36
- IntentLlmMap.map = Map(intent_dict)
37
- IntentLlmMap._map_enum = IntentLlmMap.map.as_enum()
38
- return IntentLlmMap
36
+ decoder = IntentDecoder()
37
+ decoder.mapping = intent_dict
38
+ return decoder
39
39
 
40
- @classmethod
40
+ @staticmethod
41
41
  @recorder_to_html_file('spire_chat_workflow')
42
42
  def process(
43
- cls,
44
- request: WSGIRequest,
45
- user_input: str,
46
- message_history: MessageHistory | None = None
43
+ request: WSGIRequest,
44
+ user_input: str,
45
+ message_history: MessageHistory | None = None
47
46
  ) -> BaseMessageIntel:
48
- intent_map = cls._generate_intent_map(request)
49
- intents = intent_map.process(user_input, max_return_values=1)
47
+ intent_decoder = SpireChatWorkflow._generate_intent_decoder(request)
48
+ intents = intent_decoder.process(user_input, max_return_values=1)
49
+
50
+ bot = Bot()
50
51
 
51
52
  if intents[0] is None:
52
- response = LlmBot.process(
53
+ return bot.llm.prompt_to_intel(
53
54
  prompt=user_input,
55
+ intel_class=DefaultMessageIntel,
54
56
  message_history=message_history,
55
57
  postfix_system_prompt=organization_prompt()
56
58
  )
57
59
 
58
- return DefaultMessageIntel(text=response.text)
59
-
60
- response = LlmBot.process(
60
+ return bot.llm.prompt_to_intel(
61
61
  prompt=(
62
62
  Prompt()
63
63
  .text(f'User Input: {user_input}')
@@ -69,8 +69,7 @@ class SpireChatWorkflow(BaseWorkflow):
69
69
  .line_break()
70
70
  .text(intents[0].process(user_input))
71
71
  ),
72
+ intel_class=DefaultMessageIntel,
72
73
  message_history=message_history,
73
74
  postfix_system_prompt=organization_prompt()
74
75
  )
75
-
76
- return DefaultMessageIntel(text=response.text)
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import abstractmethod, ABC
2
4
 
3
- from dandy.intel import BaseIntel
5
+ from dandy import BaseIntel
4
6
  from django.template.loader import render_to_string
5
7
 
6
8
 
@@ -11,7 +13,8 @@ class BaseMessageIntel(BaseIntel, ABC):
11
13
  super().__init_subclass__()
12
14
 
13
15
  if cls._template is None or cls._template == '':
14
- raise ValueError(f'{cls.__module__}.{cls.__qualname____}._template must be set')
16
+ message = f'{cls.__module__}.{cls.__qualname__}._template must be set'
17
+ raise ValueError(message)
15
18
 
16
19
  @abstractmethod
17
20
  def content_to_str(self) -> str:
@@ -32,5 +35,5 @@ class DefaultMessageIntel(BaseMessageIntel):
32
35
  _template: str = 'django_spire/ai/chat/message/default_message.html'
33
36
  text: str
34
37
 
35
- def content_to_str(self):
36
- return self.text
38
+ def content_to_str(self) -> str:
39
+ return self.text
@@ -1,8 +1,6 @@
1
- import json
2
- from typing import Type
1
+ from __future__ import annotations
3
2
 
4
- from dandy.llm import MessageHistory
5
- from dandy.llm.service.request.message import RoleLiteralStr
3
+ from dandy.llm.request.message import MessageHistory, RoleLiteralStr
6
4
  from django.contrib.auth.models import User
7
5
  from django.db import models
8
6
  from django.utils.timezone import now
@@ -117,11 +115,11 @@ class ChatMessage(HistoryModelMixin):
117
115
  @property
118
116
  def intel(self):
119
117
  try:
120
- intel_class: Type[BaseMessageIntel] = get_class_from_string(self._intel_class_name)
118
+ intel_class: type[BaseMessageIntel] = get_class_from_string(self._intel_class_name)
121
119
  return intel_class.model_validate(self._intel_data)
122
120
 
123
121
  except ImportError:
124
- intel_class: Type[BaseMessageIntel] = DefaultMessageIntel
122
+ intel_class: type[BaseMessageIntel] = DefaultMessageIntel
125
123
  return intel_class.model_validate(
126
124
  {'text': str(self._intel_data)}
127
125
  )
@@ -135,10 +133,11 @@ class ChatMessage(HistoryModelMixin):
135
133
  def role(self) -> RoleLiteralStr:
136
134
  if self.response_type == 'request':
137
135
  return 'user'
138
- elif self.response_type == 'response':
136
+
137
+ if self.response_type == 'response':
139
138
  return 'assistant'
140
- else:
141
- return 'system'
139
+
140
+ return 'system'
142
141
 
143
142
  def to_message_response(self) -> MessageResponse:
144
143
  return MessageResponse(
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.db.models import Q, Count
2
4
 
3
5
  from django_spire.history.querysets import HistoryQuerySet
@@ -31,4 +33,4 @@ class ChatMessageQuerySet(HistoryQuerySet):
31
33
  return self.order_by('-created_datetime')[:count]
32
34
 
33
35
  def newest_by_count_reversed(self, count: int = 20):
34
- return self.order_by('-created_datetime')[:count][::-1]
36
+ return self.order_by('-created_datetime')[:count][::-1]
@@ -1,10 +1,16 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass, field
2
- from typing import List
4
+ from typing import TYPE_CHECKING
3
5
 
4
6
  from django.template.loader import render_to_string
5
7
 
6
8
  from django_spire.ai.chat.choices import MessageResponseType
7
- from django_spire.ai.chat.message_intel import BaseMessageIntel
9
+
10
+ if TYPE_CHECKING:
11
+ from typing import Any
12
+
13
+ from django_spire.ai.chat.message_intel import BaseMessageIntel
8
14
 
9
15
 
10
16
  @dataclass
@@ -14,7 +20,7 @@ class MessageResponse:
14
20
  message_intel: BaseMessageIntel
15
21
  synthesis_speech: bool = False
16
22
 
17
- def _render_template_to_html_string(self, template: str, context_data: dict = None) -> str:
23
+ def _render_template_to_html_string(self, template: str, context_data: dict[str, Any] | None = None) -> str:
18
24
  return render_to_string(
19
25
  template_name=template,
20
26
  context={
@@ -25,35 +31,38 @@ class MessageResponse:
25
31
  },
26
32
  )
27
33
 
28
- def render_to_html_string(self, context_data: dict = None) -> str:
34
+ def render_to_html_string(self, context_data: dict[str, Any] | None = None) -> str:
29
35
  if self.type == MessageResponseType.REQUEST:
30
36
  return self._render_template_to_html_string(
31
37
  'django_spire/ai/chat/message/request_message.html',
32
38
  context_data
33
39
  )
34
- elif self.type == MessageResponseType.RESPONSE:
40
+
41
+ if self.type == MessageResponseType.RESPONSE:
35
42
  return self._render_template_to_html_string(
36
43
  'django_spire/ai/chat/message/response_message.html',
37
44
  context_data
38
45
  )
39
- elif self.type == MessageResponseType.LOADING_RESPONSE:
46
+
47
+ if self.type == MessageResponseType.LOADING_RESPONSE:
40
48
  return self._render_template_to_html_string(
41
49
  'django_spire/ai/chat/message/loading_response_message.html',
42
50
  context_data
43
51
  )
44
- else:
45
- return ''
52
+
53
+ return ''
46
54
 
47
55
 
48
56
  @dataclass
49
57
  class MessageResponseGroup:
50
- message_responses: List[MessageResponse] = field(default_factory=list)
58
+ message_responses: list[MessageResponse] = field(default_factory=list)
51
59
 
52
60
  def add_message_response(self, message_response: MessageResponse) -> None:
53
61
  self.message_responses.append(message_response)
54
62
 
55
- def render_to_html_string(self, context_data: dict = None) -> str:
63
+ def render_to_html_string(self, context_data: dict[str, Any] | None = None) -> str:
56
64
  html_string = ''
65
+
57
66
  for message_response in self.message_responses:
58
67
  html_string += message_response.render_to_html_string(context_data)
59
68
 
@@ -1,28 +1,33 @@
1
+ from __future__ import annotations
2
+
1
3
  from importlib import import_module
4
+ from typing import TYPE_CHECKING
2
5
 
3
- from dandy.llm import MessageHistory
4
- from dandy.workflow import BaseWorkflow
5
6
  from django.conf import settings
6
- from django.core.handlers.wsgi import WSGIRequest
7
7
 
8
8
  from django_spire.ai.chat.message_intel import BaseMessageIntel
9
9
  from django_spire.ai.decorators import log_ai_interaction_from_recorder
10
10
  from django_spire.consts import AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME
11
11
 
12
+ if TYPE_CHECKING:
13
+ from dandy.llm.request.message import MessageHistory
14
+ from django.core.handlers.wsgi import WSGIRequest
15
+
12
16
 
13
17
  def chat_workflow_process(
14
- request: WSGIRequest,
15
- user_input: str | None = None,
16
- message_history: MessageHistory | None = None,
18
+ request: WSGIRequest,
19
+ user_input: str | None = None,
20
+ message_history: MessageHistory | None = None
17
21
  ) -> BaseMessageIntel:
18
-
19
22
  if user_input is None:
20
- raise ValueError('user_input is required')
23
+ message = 'user_input is required'
24
+ raise ValueError(message)
21
25
 
22
26
  chat_workflow_class = getattr(settings, AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME)
23
-
27
+
24
28
  if chat_workflow_class is None:
25
- raise ValueError(f'"{AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME}" must be set in the django settings.')
29
+ message = f'"{AI_CHAT_WORKFLOW_CLASS_SETTINGS_NAME}" must be set in the django settings.'
30
+ raise ValueError(message)
26
31
 
27
32
  module_name = '.'.join(chat_workflow_class.split('.')[:-1])
28
33
  object_name = chat_workflow_class.split('.')[-1]
@@ -30,9 +35,10 @@ def chat_workflow_process(
30
35
  try:
31
36
  workflow_module = import_module(module_name)
32
37
  except ImportError:
33
- raise ImportError(f'Could not import workflow module: {module_name}')
38
+ message = f'Could not import workflow module: {module_name}'
39
+ raise ImportError(message) from None
34
40
 
35
- ChatWorkFlow: BaseWorkflow = getattr(workflow_module, object_name)
41
+ ChatWorkFlow = getattr(workflow_module, object_name)
36
42
 
37
43
  @log_ai_interaction_from_recorder(request.user)
38
44
  def run_workflow_process() -> BaseMessageIntel:
@@ -45,8 +51,7 @@ def chat_workflow_process(
45
51
  output_intel = run_workflow_process()
46
52
 
47
53
  if not issubclass(output_intel.__class__, BaseMessageIntel):
48
- raise ValueError(
49
- f'{ChatWorkFlow.__class__.__module__}.{ChatWorkFlow.__class__.__qualname__}.process must return an instance of a {BaseMessageIntel.__name__} sub class.'
50
- )
54
+ message = f'{ChatWorkFlow.__class__.__module__}.{ChatWorkFlow.__class__.__qualname__}.process must return an instance of a {BaseMessageIntel.__name__} sub class.'
55
+ raise TypeError(message)
51
56
 
52
57
  return output_intel
@@ -1,7 +1,8 @@
1
- from django.urls import path, include
1
+ from django.urls import include, path
2
2
 
3
3
  from django_spire.ai.chat.views import message_views
4
4
 
5
+
5
6
  app_name = 'message'
6
7
 
7
8
  urlpatterns = [
@@ -2,6 +2,7 @@ from django.urls import path
2
2
 
3
3
  from django_spire.ai.chat.views import page_views
4
4
 
5
+
5
6
  app_name = 'page'
6
7
 
7
8
  urlpatterns = [
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
3
5
  from django.conf import settings
@@ -1,9 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
3
5
  from django.conf import settings
4
6
  from django.http import HttpResponse
5
7
 
6
- from django_spire.ai.chat.message_intel import DefaultMessageIntel
7
8
  from django_spire.ai.chat.models import Chat
8
9
  from django_spire.ai.chat.responses import MessageResponse
9
10
  from django_spire.ai.chat.choices import MessageResponseType
@@ -17,9 +18,8 @@ def response_message_render_view(request):
17
18
  chat_workflow_name = getattr(settings, AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME)
18
19
 
19
20
  if chat_workflow_name is None:
20
- raise ValueError(
21
- f'"{AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME}" must be set in the django settings.'
22
- )
21
+ message = f'"{AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME}" must be set in the django settings.'
22
+ raise ValueError(message)
23
23
 
24
24
  chat = Chat.objects.by_user(request.user).get(id=body_data['chat_id'])
25
25
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.http import HttpResponse
2
4
 
3
5
  from django_spire.ai.chat.models import Chat
@@ -1,10 +1,15 @@
1
- from django.core.handlers.wsgi import WSGIRequest
2
- from django.template.response import TemplateResponse
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
3
4
 
4
5
  from django_spire.auth.controller.controller import AppAuthController
5
6
  from django_spire.contrib import Breadcrumbs
6
7
  from django_spire.contrib.generic_views import portal_views
7
8
 
9
+ if TYPE_CHECKING:
10
+ from django.core.handlers.wsgi import WSGIRequest
11
+ from django.template.response import TemplateResponse
12
+
8
13
 
9
14
  @AppAuthController('ai_chat').permission_required('can_delete')
10
15
  def home_view(request: WSGIRequest) -> TemplateResponse:
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
3
5
  from django.template.response import TemplateResponse
@@ -1,21 +1,27 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  import traceback
3
5
  import uuid
4
6
 
5
- from dandy.recorder import Recorder
6
- from django.contrib.auth.models import AbstractBaseUser
7
+ from typing import TYPE_CHECKING
8
+
9
+ from dandy import Recorder
7
10
  from django.utils.timezone import now
8
- from typing_extensions import Any
9
11
 
10
12
  from django_spire.ai.models import AiInteraction, AiUsage
11
13
 
14
+ if TYPE_CHECKING:
15
+ from django.contrib.auth.models import AbstractBaseUser
16
+
12
17
 
13
18
  def log_ai_interaction_from_recorder(
14
- user: AbstractBaseUser | None = None,
15
- actor: str | None = None,
19
+ user: AbstractBaseUser | None = None,
20
+ actor: str | None = None
16
21
  ):
17
22
  if user is None and actor is None:
18
- raise ValueError('user or actor must be provided')
23
+ message = 'user or actor must be provided'
24
+ raise ValueError(message)
19
25
 
20
26
  def decorator(func):
21
27
  def wrapper(*args, **kwargs):
@@ -48,7 +54,7 @@ def log_ai_interaction_from_recorder(
48
54
 
49
55
  ai_interaction.stack_trace = stack_trace
50
56
 
51
- raise e
57
+ raise
52
58
 
53
59
  finally:
54
60
  Recorder.stop_recording(recording_uuid)
django_spire/ai/mixins.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.db import models
2
4
 
3
5
  from django.contrib import admin
@@ -14,7 +16,7 @@ class AiUsageMixin(models.Model):
14
16
 
15
17
 
16
18
  class AiUsageAdminMixin(admin.ModelAdmin):
17
- def run_time_seconds_formatted(self, obj):
19
+ def run_time_seconds_formatted(self, obj) -> str:
18
20
  return f"{obj.run_time_seconds:.3f}s"
19
21
 
20
- run_time_seconds_formatted.short_description = 'Run Time'
22
+ run_time_seconds_formatted.short_description = 'Run Time'
django_spire/ai/models.py CHANGED
@@ -1,4 +1,6 @@
1
- from django.contrib.auth.models import Group, User
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import User
2
4
  from django.db import models
3
5
  from django.utils.formats import localize
4
6
  from django.utils.timezone import now
@@ -53,14 +55,17 @@ class AiInteraction(AiUsageMixin):
53
55
  def __str__(self):
54
56
  return f'"{self.actor}" interaction on "{localize(self.created_datetime)}"'
55
57
 
56
- def save(self, *args, **kwargs):
58
+ def save(self, *args, **kwargs) -> None:
57
59
  if self.user:
58
60
  if self.actor is None:
59
61
  self.actor = self.user.get_full_name()
62
+
60
63
  if self.user_email is None:
61
64
  self.user_email = self.user.email
65
+
62
66
  if self.user_first_name is None:
63
67
  self.user_first_name = self.user.first_name
68
+
64
69
  if self.user_last_name is None:
65
70
  self.user_last_name = self.user.last_name
66
71