wbcore 1.46.0__py2.py3-none-any.whl → 1.58.2__py2.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.
- wbcore/cache/decorators.py +5 -3
- wbcore/cache/registry.py +14 -7
- wbcore/configs/__init__.py +1 -0
- wbcore/configs/configs.py +5 -0
- wbcore/configs/decorators.py +1 -1
- wbcore/configurations/configurations/apps.py +3 -2
- wbcore/configurations/configurations/authentication.py +1 -1
- wbcore/configurations/configurations/base.py +1 -1
- wbcore/configurations/configurations/cache.py +1 -1
- wbcore/configurations/configurations/i18nl10n.py +2 -1
- wbcore/configurations/configurations/maintenance.py +1 -1
- wbcore/configurations/configurations/media.py +1 -1
- wbcore/configurations/configurations/middleware.py +1 -1
- wbcore/configurations/configurations/rest_framework.py +1 -1
- wbcore/configurations/configurations/static.py +3 -3
- wbcore/configurations/configurations/wbcore.py +1 -1
- wbcore/content_type/serializers.py +13 -5
- wbcore/content_type/utils.py +3 -3
- wbcore/content_type/viewsets.py +2 -2
- wbcore/contrib/agenda/filters/calendar_item.py +5 -4
- wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po +145 -52
- wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po.translated +236 -0
- wbcore/contrib/agenda/locale/en/LC_MESSAGES/django.po +200 -0
- wbcore/contrib/agenda/locale/fr/LC_MESSAGES/django.po +201 -0
- wbcore/contrib/agenda/viewsets/calendar_items.py +7 -7
- wbcore/contrib/agenda/viewsets/menu/calendar_items.py +0 -6
- wbcore/contrib/ai/exceptions.py +5 -5
- wbcore/contrib/ai/llm/config.py +76 -27
- wbcore/contrib/ai/llm/mixins.py +5 -8
- wbcore/contrib/ai/llm/utils.py +50 -26
- wbcore/contrib/authentication/admin.py +2 -2
- wbcore/contrib/authentication/factories/__init__.py +8 -1
- wbcore/contrib/authentication/factories/users.py +19 -0
- wbcore/contrib/authentication/filters.py +1 -2
- wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po +209 -187
- wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po.translated +634 -0
- wbcore/contrib/authentication/locale/en/LC_MESSAGES/django.po +590 -0
- wbcore/contrib/authentication/locale/fr/LC_MESSAGES/django.po +592 -0
- wbcore/contrib/authentication/models/users.py +3 -3
- wbcore/contrib/authentication/models/users_activities.py +1 -1
- wbcore/contrib/authentication/serializers/users.py +2 -2
- wbcore/contrib/authentication/tests/test_tokens.py +3 -3
- wbcore/contrib/authentication/tests/test_users.py +0 -1
- wbcore/contrib/authentication/urls.py +0 -4
- wbcore/contrib/authentication/viewsets/endpoints/user_activities.py +2 -11
- wbcore/contrib/authentication/viewsets/endpoints/users.py +0 -3
- wbcore/contrib/authentication/viewsets/user_activities.py +2 -1
- wbcore/contrib/authentication/viewsets/users.py +6 -4
- wbcore/contrib/color/models.py +2 -1
- wbcore/contrib/currency/factories.py +1 -1
- wbcore/contrib/currency/import_export/backends/fixerio/currency_fx_rates.py +3 -1
- wbcore/contrib/currency/models.py +30 -8
- wbcore/contrib/currency/serializers.py +5 -1
- wbcore/contrib/currency/tests/test_serializers.py +7 -3
- wbcore/contrib/currency/tests/test_viewsets.py +1 -1
- wbcore/contrib/currency/viewsets/currency.py +2 -2
- wbcore/contrib/currency/viewsets/endpoints/currency_fx_rates.py +0 -9
- wbcore/contrib/dataloader/tests/test/dataloaders/protocols.py +1 -2
- wbcore/contrib/dataloader/utils.py +2 -2
- wbcore/contrib/directory/factories/__init__.py +1 -1
- wbcore/contrib/directory/factories/entries.py +2 -1
- wbcore/contrib/directory/filters/entries.py +9 -0
- wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po +728 -714
- wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po.translated +1779 -0
- wbcore/contrib/directory/locale/en/LC_MESSAGES/django.po +1652 -0
- wbcore/contrib/directory/locale/fr/LC_MESSAGES/django.po +1654 -0
- wbcore/contrib/directory/migrations/0011_person_description_person_i18n.py +24 -0
- wbcore/contrib/directory/migrations/0012_alter_person_managers.py +20 -0
- wbcore/contrib/directory/migrations/0013_alter_clientmanagerrelationship_options.py +17 -0
- wbcore/contrib/directory/models/contacts.py +2 -2
- wbcore/contrib/directory/models/entries.py +31 -5
- wbcore/contrib/directory/models/relationships.py +31 -35
- wbcore/contrib/directory/permissions.py +6 -0
- wbcore/contrib/directory/serializers/companies.py +16 -8
- wbcore/contrib/directory/serializers/contacts.py +8 -8
- wbcore/contrib/directory/serializers/entries.py +26 -15
- wbcore/contrib/directory/serializers/entry_representations.py +4 -2
- wbcore/contrib/directory/serializers/persons.py +12 -10
- wbcore/contrib/directory/serializers/relationships.py +2 -2
- wbcore/contrib/directory/tests/conftest.py +2 -0
- wbcore/contrib/directory/tests/disable_signals.py +11 -1
- wbcore/contrib/directory/tests/signals.py +2 -2
- wbcore/contrib/directory/tests/test_models.py +88 -66
- wbcore/contrib/directory/tests/test_serializers.py +1 -1
- wbcore/contrib/directory/tests/test_viewsets.py +8 -8
- wbcore/contrib/directory/viewsets/buttons/__init__.py +1 -1
- wbcore/contrib/directory/viewsets/buttons/relationships.py +32 -0
- wbcore/contrib/directory/viewsets/contacts.py +6 -6
- wbcore/contrib/directory/viewsets/display/__init__.py +1 -1
- wbcore/contrib/directory/viewsets/display/contacts.py +1 -14
- wbcore/contrib/directory/viewsets/display/entries.py +68 -38
- wbcore/contrib/directory/viewsets/display/relationships.py +26 -50
- wbcore/contrib/directory/viewsets/endpoints/relationships.py +1 -26
- wbcore/contrib/directory/viewsets/entries.py +8 -6
- wbcore/contrib/directory/viewsets/previews/entries.py +3 -3
- wbcore/contrib/directory/viewsets/relationships.py +16 -2
- wbcore/contrib/directory/viewsets/titles/relationships.py +2 -3
- wbcore/contrib/documents/filters.py +0 -2
- wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po +103 -94
- wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po.translated +285 -0
- wbcore/contrib/documents/locale/en/LC_MESSAGES/django.po +271 -0
- wbcore/contrib/documents/locale/fr/LC_MESSAGES/django.po +270 -0
- wbcore/contrib/documents/tests/test_models.py +32 -28
- wbcore/contrib/documents/viewsets/endpoints/shareable_links.py +2 -21
- wbcore/contrib/dynamic_preferences/types.py +108 -0
- wbcore/contrib/dynamic_preferences/viewsets.py +27 -0
- wbcore/contrib/example_app/filters/event.py +3 -1
- wbcore/contrib/example_app/filters/match.py +1 -1
- wbcore/contrib/example_app/models.py +91 -22
- wbcore/contrib/example_app/serializers/person_team.py +4 -4
- wbcore/contrib/example_app/templates/example_app/embedded_view.html +19 -0
- wbcore/contrib/example_app/tests/e2e/test_teams.py +1 -1
- wbcore/contrib/example_app/tests/test_models/test_match.py +17 -7
- wbcore/contrib/example_app/urls.py +2 -0
- wbcore/contrib/example_app/views.py +7 -0
- wbcore/contrib/example_app/viewsets/displays/team.py +23 -4
- wbcore/contrib/example_app/viewsets/menu/menus.py +1 -1
- wbcore/contrib/example_app/viewsets/menus.py +1 -1
- wbcore/contrib/geography/tests/conftest.py +14 -0
- wbcore/contrib/geography/tests/test_models.py +23 -8
- wbcore/contrib/geography/tests/test_viewsets.py +96 -2
- wbcore/contrib/guardian/tests/test_model_mixins.py +3 -4
- wbcore/contrib/guardian/tests/test_tasks.py +9 -9
- wbcore/contrib/guardian/tests/test_viewsets.py +2 -2
- wbcore/contrib/guardian/viewsets/configs/__init__.py +1 -1
- wbcore/contrib/guardian/viewsets/configs/buttons.py +9 -0
- wbcore/contrib/guardian/viewsets/configs/endpoints.py +7 -0
- wbcore/contrib/guardian/viewsets/viewsets.py +2 -0
- wbcore/contrib/i18n/__init__.py +2 -0
- wbcore/contrib/i18n/buttons.py +33 -0
- wbcore/contrib/i18n/serializers/__init__.py +0 -0
- wbcore/contrib/i18n/serializers/fields.py +20 -0
- wbcore/contrib/i18n/serializers/mixins.py +13 -0
- wbcore/contrib/i18n/tests/conftest.py +11 -0
- wbcore/contrib/i18n/tests/test_viewsets.py +67 -0
- wbcore/contrib/i18n/translation.py +140 -0
- wbcore/contrib/i18n/viewsets.py +36 -0
- wbcore/contrib/icons/backends/default.py +1 -0
- wbcore/contrib/icons/backends/material.py +1 -0
- wbcore/contrib/icons/icons.py +5 -8
- wbcore/contrib/io/admin.py +1 -0
- wbcore/contrib/io/backends/mail.py +3 -2
- wbcore/contrib/io/backends/utils.py +14 -17
- wbcore/contrib/io/exceptions.py +8 -0
- wbcore/contrib/io/factories.py +1 -1
- wbcore/contrib/io/import_export/backends/mail.py +1 -0
- wbcore/contrib/io/import_export/backends/sftp.py +29 -20
- wbcore/contrib/io/import_export/backends/stream.py +2 -2
- wbcore/contrib/io/import_export/parsers/__init__.py +0 -0
- wbcore/contrib/io/import_export/parsers/base_csv.py +36 -0
- wbcore/contrib/io/import_export/parsers/resources.py +50 -0
- wbcore/contrib/io/imports.py +33 -25
- wbcore/contrib/io/locale/de/LC_MESSAGES/django.po +114 -22
- wbcore/contrib/io/locale/de/LC_MESSAGES/django.po.translated +103 -0
- wbcore/contrib/io/locale/en/LC_MESSAGES/django.po +138 -0
- wbcore/contrib/io/locale/fr/LC_MESSAGES/django.po +138 -0
- wbcore/contrib/io/migrations/0008_importsource_resource_kwargs.py +18 -0
- wbcore/contrib/io/models.py +65 -45
- wbcore/contrib/io/resources.py +0 -6
- wbcore/contrib/io/serializers.py +2 -2
- wbcore/contrib/io/signals.py +4 -0
- wbcore/contrib/io/tests/test_backends.py +19 -13
- wbcore/contrib/io/tests/test_exports.py +1 -1
- wbcore/contrib/io/tests/test_imports.py +1 -1
- wbcore/contrib/io/tests/test_models.py +47 -14
- wbcore/contrib/io/tests/test_viewsets.py +271 -0
- wbcore/contrib/io/viewset_mixins.py +41 -54
- wbcore/contrib/notifications/admin.py +1 -0
- wbcore/contrib/notifications/apps.py +2 -1
- wbcore/contrib/notifications/backends/abstract_backend.py +2 -4
- wbcore/contrib/notifications/backends/firebase/backends.py +5 -2
- wbcore/contrib/notifications/dispatch.py +18 -7
- wbcore/contrib/notifications/factories/notification_types.py +1 -0
- wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po +25 -19
- wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po.translated +63 -0
- wbcore/contrib/notifications/locale/en/LC_MESSAGES/django.po +61 -0
- wbcore/contrib/notifications/locale/fr/LC_MESSAGES/django.po +62 -0
- wbcore/contrib/notifications/migrations/0008_notificationtype_is_lock.py +18 -0
- wbcore/contrib/notifications/migrations/0009_alter_notificationtypesetting_options_and_more.py +32 -0
- wbcore/contrib/notifications/models/notification_types.py +67 -24
- wbcore/contrib/notifications/serializers/notification_types.py +16 -1
- wbcore/contrib/notifications/tests/test_models/test_tokens.py +8 -0
- wbcore/contrib/notifications/tests/test_serializers/test_notification_types.py +5 -0
- wbcore/contrib/notifications/tests/test_viewsets/test_notification_types.py +3 -5
- wbcore/contrib/notifications/utils.py +3 -2
- wbcore/contrib/notifications/viewsets/configs/notification_types.py +28 -6
- wbcore/contrib/notifications/viewsets/menus.py +1 -1
- wbcore/contrib/notifications/viewsets/notification_types.py +12 -2
- wbcore/contrib/pandas/fields.py +38 -10
- wbcore/contrib/pandas/filters.py +4 -1
- wbcore/contrib/pandas/filterset.py +8 -7
- wbcore/contrib/pandas/tests/test_fields/test_number_fields.py +2 -7
- wbcore/contrib/pandas/utils.py +1 -1
- wbcore/contrib/pandas/views.py +14 -13
- wbcore/contrib/tags/models/tags.py +4 -1
- wbcore/contrib/workflow/factories/display.py +2 -2
- wbcore/contrib/workflow/factories/transition.py +16 -15
- wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po +457 -566
- wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po.translated +1326 -0
- wbcore/contrib/workflow/locale/en/LC_MESSAGES/django.po +1102 -0
- wbcore/contrib/workflow/locale/fr/LC_MESSAGES/django.po +1114 -0
- wbcore/contrib/workflow/models/data.py +7 -4
- wbcore/contrib/workflow/models/process.py +2 -2
- wbcore/contrib/workflow/models/step.py +57 -15
- wbcore/contrib/workflow/serializers/data.py +8 -8
- wbcore/contrib/workflow/serializers/process.py +3 -2
- wbcore/contrib/workflow/tests/conftest.py +224 -0
- wbcore/contrib/workflow/tests/test_dispatch.py +82 -77
- wbcore/contrib/workflow/tests/test_displays.py +10 -88
- wbcore/contrib/workflow/tests/test_filters.py +57 -40
- wbcore/contrib/workflow/tests/test_models/step/test_decision_step.py +71 -68
- wbcore/contrib/workflow/tests/test_models/step/test_email_step.py +78 -38
- wbcore/contrib/workflow/tests/test_models/step/test_finish_step.py +152 -90
- wbcore/contrib/workflow/tests/test_models/step/test_join_step.py +100 -110
- wbcore/contrib/workflow/tests/test_models/step/test_step.py +168 -33
- wbcore/contrib/workflow/tests/test_models/test_condition.py +1 -1
- wbcore/contrib/workflow/tests/test_models/test_workflow.py +3 -3
- wbcore/contrib/workflow/tests/test_serializers.py +172 -150
- wbcore/contrib/workflow/tests/test_viewsets.py +264 -323
- wbcore/contrib/workflow/tests/test_workflow_assignees.py +215 -205
- wbcore/contrib/workflow/viewsets/process.py +4 -1
- wbcore/contrib/workflow/workflows/assignees.py +12 -7
- wbcore/dynamic_preferences_registry.py +102 -0
- wbcore/enums.py +2 -51
- wbcore/filters/fields/choices.py +4 -6
- wbcore/filters/fields/content_type.py +15 -4
- wbcore/filters/fields/datetime.py +50 -25
- wbcore/filters/fields/models.py +18 -9
- wbcore/filters/fields/numbers.py +9 -8
- wbcore/filters/filterset.py +27 -6
- wbcore/filters/mixins.py +41 -42
- wbcore/forms.py +6 -6
- wbcore/fsm/markdown_extensions.py +1 -1
- wbcore/fsm/mixins.py +20 -6
- wbcore/locale/de/LC_MESSAGES/django.po +982 -397
- wbcore/locale/de/LC_MESSAGES/django.po.translated +1580 -0
- wbcore/locale/en/LC_MESSAGES/django.po +1234 -0
- wbcore/locale/fr/LC_MESSAGES/django.po +1235 -0
- wbcore/markdown/models.py +8 -5
- wbcore/markdown/views.py +1 -1
- wbcore/menus/menus.py +2 -2
- wbcore/metadata/configs/buttons/bases.py +10 -7
- wbcore/metadata/configs/buttons/buttons.py +2 -1
- wbcore/metadata/configs/buttons/enums.py +50 -0
- wbcore/metadata/configs/buttons/view_config.py +13 -46
- wbcore/metadata/configs/display/display.py +2 -2
- wbcore/metadata/configs/display/formatting.py +6 -9
- wbcore/metadata/configs/display/instance_display/display.py +5 -2
- wbcore/metadata/configs/display/instance_display/pages.py +1 -1
- wbcore/metadata/configs/display/instance_display/shortcuts.py +1 -1
- wbcore/metadata/configs/display/list_display.py +54 -40
- wbcore/metadata/configs/display/models.py +6 -0
- wbcore/metadata/configs/display/view_config.py +11 -9
- wbcore/metadata/configs/endpoints.py +11 -4
- wbcore/metadata/configs/fields.py +6 -1
- wbcore/metadata/configs/filter_fields.py +12 -13
- wbcore/metadata/configs/identifiers.py +3 -1
- wbcore/metadata/tests/test_buttons.py +13 -16
- wbcore/models/fields.py +2 -2
- wbcore/pagination.py +1 -2
- wbcore/permissions/permissions.py +2 -2
- wbcore/permissions/utils.py +2 -2
- wbcore/release_notes/display.py +2 -8
- wbcore/release_notes/serializers.py +2 -9
- wbcore/release_notes/viewsets.py +8 -2
- wbcore/reversion/viewsets/titles.py +4 -3
- wbcore/serializers/__init__.py +2 -0
- wbcore/serializers/fields/__init__.py +2 -1
- wbcore/serializers/fields/boolean.py +1 -1
- wbcore/serializers/fields/choice.py +28 -4
- wbcore/serializers/fields/datetime.py +45 -36
- wbcore/serializers/fields/fields.py +1 -1
- wbcore/serializers/fields/fsm.py +1 -1
- wbcore/serializers/fields/list.py +2 -5
- wbcore/serializers/fields/mixins.py +24 -11
- wbcore/serializers/fields/number.py +6 -23
- wbcore/serializers/fields/other.py +2 -10
- wbcore/serializers/fields/related.py +4 -6
- wbcore/serializers/fields/text.py +1 -1
- wbcore/serializers/fields/types.py +2 -0
- wbcore/serializers/serializers.py +12 -3
- wbcore/signals/__init__.py +1 -0
- wbcore/signals/clone.py +4 -0
- wbcore/signals/models.py +2 -6
- wbcore/tasks.py +2 -2
- wbcore/templates/wbcore/email_base_template.html +3 -3
- wbcore/test/e2e_helpers_methods/e2e_checks.py +10 -4
- wbcore/test/e2e_helpers_methods/e2e_helper_methods.py +4 -2
- wbcore/test/mixins.py +52 -102
- wbcore/test/tests.py +6 -9
- wbcore/test/utils.py +3 -4
- wbcore/tests/e2e/test_e2e.py +2 -2
- wbcore/tests/test_cache/test_decorators.py +4 -7
- wbcore/tests/test_configs.py +2 -5
- wbcore/tests/test_enums.py +2 -1
- wbcore/tests/test_fields/test_choice_fields.py +9 -1
- wbcore/tests/test_fields/test_number_fields.py +7 -15
- wbcore/tests/test_fields/test_other_fields.py +1 -2
- wbcore/tests/test_filters/test_mixins.py +35 -35
- wbcore/tests/test_list_display.py +0 -2
- wbcore/tests/test_models/test_mixins.py +1 -1
- wbcore/tests/test_utils/test_date.py +1 -1
- wbcore/tests/test_utils/test_date_builder.py +25 -1
- wbcore/tests/test_utils/test_primary.py +1 -1
- wbcore/urls.py +4 -0
- wbcore/utils/date.py +18 -2
- wbcore/utils/figures.py +2 -2
- wbcore/utils/models.py +21 -4
- wbcore/utils/reportlab.py +7 -0
- wbcore/utils/rrules.py +3 -1
- wbcore/utils/string_loader.py +1 -1
- wbcore/utils/strings.py +3 -3
- wbcore/utils/views.py +8 -3
- wbcore/viewsets/mixins.py +9 -4
- {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/METADATA +9 -5
- {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/RECORD +317 -271
- wbcore/contrib/geography/tests/test_serializers.py +0 -7
- wbcore/contrib/geography/tests/tests.py +0 -13
- wbcore/contrib/io/tests/tests.py +0 -19
- wbcore/contrib/workflow/tests/tests.py +0 -25
- {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/WHEEL +0 -0
wbcore/contrib/ai/exceptions.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from langchain_core.exceptions import LangChainException, OutputParserException
|
|
2
2
|
|
|
3
3
|
APIStatusErrors = [LangChainException, OutputParserException]
|
|
4
|
+
BadRequestErrors = []
|
|
4
5
|
|
|
5
6
|
try:
|
|
6
7
|
from openai._exceptions import (
|
|
7
8
|
AuthenticationError,
|
|
9
|
+
BadRequestError,
|
|
8
10
|
ConflictError,
|
|
9
11
|
InternalServerError,
|
|
10
12
|
NotFoundError,
|
|
@@ -24,17 +26,15 @@ try:
|
|
|
24
26
|
InternalServerError,
|
|
25
27
|
]
|
|
26
28
|
)
|
|
29
|
+
BadRequestErrors.append(BadRequestError)
|
|
27
30
|
except ImportError:
|
|
28
31
|
pass
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
try:
|
|
32
|
-
from anthropic._exceptions import
|
|
33
|
-
APIConnectionError,
|
|
34
|
-
APIResponseValidationError,
|
|
35
|
-
APIStatusError,
|
|
36
|
-
)
|
|
35
|
+
from anthropic._exceptions import APIConnectionError, APIResponseValidationError, APIStatusError, BadRequestError
|
|
37
36
|
|
|
38
37
|
APIStatusErrors.extend([APIResponseValidationError, APIStatusError, APIConnectionError])
|
|
38
|
+
BadRequestErrors.append(BadRequestError)
|
|
39
39
|
except ImportError:
|
|
40
40
|
pass
|
wbcore/contrib/ai/llm/config.py
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any, Callable, Generic, TypeVar
|
|
3
3
|
|
|
4
4
|
from celery import shared_task
|
|
5
5
|
from django.db import models
|
|
6
6
|
from django.db.models.signals import ModelSignal
|
|
7
7
|
from langchain_core.language_models import BaseChatModel
|
|
8
|
+
from langchain_core.messages import BaseMessage
|
|
8
9
|
from langchain_openai import ChatOpenAI
|
|
10
|
+
from pydantic import BaseModel
|
|
9
11
|
|
|
10
|
-
from ..exceptions import APIStatusErrors
|
|
11
|
-
from .utils import
|
|
12
|
+
from ..exceptions import APIStatusErrors, BadRequestErrors
|
|
13
|
+
from .utils import run_llm
|
|
12
14
|
|
|
13
15
|
logger = logging.getLogger("llm")
|
|
14
16
|
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
from pydantic import BaseModel
|
|
17
|
-
|
|
18
17
|
|
|
19
18
|
@shared_task(
|
|
19
|
+
queue="llm",
|
|
20
20
|
autoretry_for=tuple(APIStatusErrors),
|
|
21
21
|
retry_backoff=10,
|
|
22
22
|
max_retries=5, # retry 5 times maximum
|
|
@@ -31,16 +31,31 @@ def invoke_as_task(
|
|
|
31
31
|
max_tokens: int,
|
|
32
32
|
model_field: str | None,
|
|
33
33
|
output_model: "type[BaseModel] | None",
|
|
34
|
-
|
|
34
|
+
tools: list[dict[str, Any]],
|
|
35
|
+
query: Callable | dict[str, Any] | None,
|
|
36
|
+
extra_query: dict[str, Any] | None,
|
|
37
|
+
llm_kwargs: dict[str, Any] | None,
|
|
38
|
+
result_parser: Callable | None,
|
|
35
39
|
):
|
|
36
40
|
try:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if
|
|
42
|
-
|
|
41
|
+
if callable(query):
|
|
42
|
+
query = query(instance)
|
|
43
|
+
if not query:
|
|
44
|
+
query = dict()
|
|
45
|
+
if extra_query:
|
|
46
|
+
query.update(extra_query)
|
|
47
|
+
|
|
48
|
+
output_result, ai_msg = run_llm(
|
|
49
|
+
prompt, output_model, chat_model, chat_model_name, max_tokens, query=query, extra_tools=tools, **llm_kwargs
|
|
50
|
+
)
|
|
51
|
+
# if a result parser is provided, we use this to parse the results into the instance
|
|
52
|
+
if result_parser:
|
|
53
|
+
instance = result_parser(instance, output_result, ai_msg)
|
|
54
|
+
elif output_result and isinstance(output_result, BaseModel):
|
|
55
|
+
for field, value in output_result.model_dump().items():
|
|
43
56
|
setattr(instance, field, value)
|
|
57
|
+
except tuple(BadRequestErrors) as e: # we silent bad request error because there is nothing we can do about it
|
|
58
|
+
logger.warning(str(e))
|
|
44
59
|
except tuple(APIStatusErrors) as e: # for APIStatusError, we let celery retry it
|
|
45
60
|
raise e
|
|
46
61
|
except Exception as e: # otherwise we log the error and silently fail
|
|
@@ -58,15 +73,18 @@ class LLMConfig(Generic[T]):
|
|
|
58
73
|
def __init__(
|
|
59
74
|
self,
|
|
60
75
|
key: str,
|
|
61
|
-
prompt: Callable | str,
|
|
76
|
+
prompt: Callable | str | list[BaseMessage],
|
|
62
77
|
field: str | None = None,
|
|
63
78
|
output_model: "type[BaseModel] | None" = None,
|
|
64
79
|
on_save: bool = True,
|
|
65
80
|
on_condition: Callable | bool | None = None,
|
|
66
|
-
chat_model: type[BaseChatModel] = ChatOpenAI,
|
|
67
|
-
chat_model_name: str = "gpt-4o-mini",
|
|
81
|
+
chat_model: Callable | tuple[type[BaseChatModel], str] = (ChatOpenAI, "gpt-4o-mini"),
|
|
68
82
|
max_tokens: int = 16000,
|
|
69
|
-
|
|
83
|
+
tools: Callable | list[dict[str, str]] | None = None,
|
|
84
|
+
query: Callable | dict[str, Any] | None = None,
|
|
85
|
+
llm_kwargs_callback: Callable | None = None,
|
|
86
|
+
result_parser: Callable | None = None,
|
|
87
|
+
**llm_kwargs,
|
|
70
88
|
):
|
|
71
89
|
self.key = key
|
|
72
90
|
self.on_save = on_save
|
|
@@ -75,9 +93,12 @@ class LLMConfig(Generic[T]):
|
|
|
75
93
|
self.prompt = prompt
|
|
76
94
|
self.output_model = output_model
|
|
77
95
|
self.chat_model = chat_model
|
|
78
|
-
self.chat_model_name = chat_model_name
|
|
79
96
|
self.max_tokens = max_tokens
|
|
80
|
-
self.
|
|
97
|
+
self.tools = tools
|
|
98
|
+
self.query = query
|
|
99
|
+
self.llm_kwargs_callback = llm_kwargs_callback
|
|
100
|
+
self.llm_kwargs = llm_kwargs
|
|
101
|
+
self.result_parser = result_parser
|
|
81
102
|
|
|
82
103
|
def check_condition(self, instance: T) -> bool:
|
|
83
104
|
if self.on_condition is None:
|
|
@@ -86,27 +107,55 @@ class LLMConfig(Generic[T]):
|
|
|
86
107
|
return self.on_condition(instance)
|
|
87
108
|
return bool(self.on_condition)
|
|
88
109
|
|
|
89
|
-
def
|
|
90
|
-
prompt =
|
|
110
|
+
def _get_prompt(self, instance: T):
|
|
111
|
+
prompt = self.prompt
|
|
112
|
+
extra_query = dict()
|
|
113
|
+
if callable(prompt):
|
|
114
|
+
prompt = prompt(instance)
|
|
91
115
|
for _, response in add_llm_prompt.send(sender=instance.__class__, instance=instance, key=self.key):
|
|
92
|
-
|
|
116
|
+
remote_prompts, remote_query = response
|
|
117
|
+
prompt.extend(remote_prompts)
|
|
118
|
+
extra_query.update(remote_query)
|
|
119
|
+
|
|
120
|
+
return prompt, extra_query
|
|
121
|
+
|
|
122
|
+
def _get_chat_model(self, instance: T) -> tuple[type[BaseChatModel], str]:
|
|
123
|
+
if callable(self.chat_model):
|
|
124
|
+
return self.chat_model(instance)
|
|
125
|
+
return self.chat_model
|
|
126
|
+
|
|
127
|
+
def _get_tools(self, instance: T) -> list[dict[str, Any]]:
|
|
128
|
+
if callable(self.tools):
|
|
129
|
+
return self.tools(instance)
|
|
130
|
+
return self.tools
|
|
93
131
|
|
|
94
|
-
|
|
132
|
+
def _get_llm_kwargs(self, instance: T) -> dict[str, Any]:
|
|
133
|
+
llm_kwargs = self.llm_kwargs
|
|
134
|
+
if callable(self.llm_kwargs_callback):
|
|
135
|
+
llm_kwargs = self.llm_kwargs_callback(instance)
|
|
136
|
+
return llm_kwargs
|
|
95
137
|
|
|
96
138
|
def schedule(self, instance: T, initial: bool = True):
|
|
97
|
-
prompt = self.
|
|
139
|
+
prompt, extra_query = self._get_prompt(instance)
|
|
98
140
|
args = []
|
|
99
141
|
if initial:
|
|
100
142
|
args.append(instance)
|
|
143
|
+
chat_model, chat_model_name = self._get_chat_model(instance)
|
|
144
|
+
tools = self._get_tools(instance)
|
|
145
|
+
llm_kwargs = self._get_llm_kwargs(instance)
|
|
101
146
|
args.extend(
|
|
102
147
|
[
|
|
103
148
|
prompt,
|
|
104
|
-
|
|
105
|
-
|
|
149
|
+
chat_model,
|
|
150
|
+
chat_model_name,
|
|
106
151
|
self.max_tokens,
|
|
107
152
|
self.field,
|
|
108
153
|
self.output_model,
|
|
109
|
-
|
|
154
|
+
tools,
|
|
155
|
+
self.query,
|
|
156
|
+
extra_query,
|
|
157
|
+
llm_kwargs,
|
|
158
|
+
self.result_parser,
|
|
110
159
|
]
|
|
111
160
|
)
|
|
112
161
|
return invoke_as_task.s(*args)
|
wbcore/contrib/ai/llm/mixins.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
from contextlib import suppress
|
|
2
|
-
|
|
3
1
|
from celery import chain, shared_task
|
|
4
2
|
from django.db.models import Model
|
|
5
3
|
|
|
6
4
|
|
|
7
|
-
@shared_task
|
|
5
|
+
@shared_task(queue="llm")
|
|
8
6
|
def save_instance_as_task(instance):
|
|
9
7
|
instance.save(_with_llm=False)
|
|
10
8
|
|
|
@@ -25,11 +23,10 @@ class LLMMixin(Model):
|
|
|
25
23
|
tasks.append(config.schedule(self, initial=index == 0))
|
|
26
24
|
if tasks:
|
|
27
25
|
res = chain(*tasks, save_instance_as_task.s())
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
res.apply_async()
|
|
26
|
+
if _llm_synchronous:
|
|
27
|
+
res.apply()
|
|
28
|
+
else:
|
|
29
|
+
res.apply_async()
|
|
33
30
|
|
|
34
31
|
class Meta:
|
|
35
32
|
abstract = True
|
wbcore/contrib/ai/llm/utils.py
CHANGED
|
@@ -1,45 +1,69 @@
|
|
|
1
|
-
from
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
from langchain_core.language_models import BaseChatModel
|
|
5
|
+
from langchain_core.messages import SystemMessage
|
|
4
6
|
from langchain_core.messages.base import BaseMessage
|
|
5
|
-
from langchain_core.output_parsers import StrOutputParser
|
|
7
|
+
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
|
|
8
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
6
9
|
from langchain_openai import ChatOpenAI
|
|
7
|
-
from pydantic import BaseModel
|
|
10
|
+
from pydantic import BaseModel, ValidationError
|
|
8
11
|
|
|
9
12
|
type Prompt = str | list[BaseMessage] | BaseMessage
|
|
10
13
|
|
|
11
14
|
|
|
12
|
-
def
|
|
15
|
+
def _convert_prompt_to_chat_prompt_template(prompt: Prompt, format_instructions=None) -> ChatPromptTemplate:
|
|
16
|
+
if isinstance(prompt, BaseMessage):
|
|
17
|
+
prompt = [prompt]
|
|
13
18
|
if isinstance(prompt, str):
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
prompt = SystemMessage(content=prompt)
|
|
20
|
+
messages = []
|
|
21
|
+
for index, msg in enumerate(prompt):
|
|
22
|
+
content = msg.content
|
|
23
|
+
if index == 0 and format_instructions:
|
|
24
|
+
if "{format_instructions}" not in content:
|
|
25
|
+
content += "\n{format_instructions}"
|
|
26
|
+
messages.append((msg.type, content))
|
|
27
|
+
return ChatPromptTemplate.from_messages(messages).partial(format_instructions=format_instructions)
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
def run_llm(
|
|
23
|
-
prompt: Prompt
|
|
24
|
-
instance: Any | None = None,
|
|
31
|
+
prompt: Prompt,
|
|
25
32
|
output_model: "type[BaseModel] | None" = None,
|
|
26
33
|
chat_model: type[BaseChatModel] = ChatOpenAI,
|
|
27
34
|
chat_model_name: str = "gpt-4o-mini",
|
|
28
35
|
max_tokens: int = 16000,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
extra_tools: list[dict[str, str]] | None = None,
|
|
37
|
+
query: dict[str, Any] | None = None,
|
|
38
|
+
**llm_kwargs,
|
|
39
|
+
) -> tuple[BaseModel | None, BaseMessage]:
|
|
40
|
+
llm = chat_model(model=chat_model_name, max_tokens=max_tokens, **llm_kwargs) # type: ignore
|
|
41
|
+
tools = []
|
|
42
|
+
if not query:
|
|
43
|
+
query = {}
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
if extra_tools:
|
|
46
|
+
tools.extend(extra_tools)
|
|
47
|
+
if output_model:
|
|
48
|
+
tools.append(output_model)
|
|
49
|
+
if tools:
|
|
50
|
+
llm = llm.bind_tools(tools)
|
|
51
|
+
|
|
52
|
+
# Set up a parser
|
|
53
|
+
if output_model:
|
|
54
|
+
parser = PydanticOutputParser(pydantic_object=output_model)
|
|
55
|
+
else:
|
|
56
|
+
parser = StrOutputParser()
|
|
57
|
+
try:
|
|
58
|
+
format_instructions = parser.get_format_instructions()
|
|
59
|
+
except NotImplementedError:
|
|
60
|
+
format_instructions = None
|
|
61
|
+
chat_prompt_template = _convert_prompt_to_chat_prompt_template(prompt, format_instructions=format_instructions)
|
|
40
62
|
|
|
41
|
-
|
|
42
|
-
for field, value in result:
|
|
43
|
-
output[field] = value
|
|
63
|
+
chain = chat_prompt_template | llm
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
response = chain.invoke(query) # type: ignore
|
|
66
|
+
if output_model:
|
|
67
|
+
with suppress(ValidationError, IndexError):
|
|
68
|
+
return output_model.model_validate(response.tool_calls[0]["args"]), response
|
|
69
|
+
return None, response
|
|
@@ -202,11 +202,11 @@ class UserAdmin(admin.ModelAdmin):
|
|
|
202
202
|
form = self.change_password_form(user)
|
|
203
203
|
|
|
204
204
|
fieldsets = [(None, {"fields": list(form.base_fields)})]
|
|
205
|
-
|
|
205
|
+
admin_form = admin.helpers.AdminForm(form, fieldsets, {})
|
|
206
206
|
|
|
207
207
|
context = {
|
|
208
208
|
"title": _("Change password: %s") % escape(user.get_username()),
|
|
209
|
-
"adminForm":
|
|
209
|
+
"adminForm": admin_form,
|
|
210
210
|
"form_url": form_url,
|
|
211
211
|
"form": form,
|
|
212
212
|
"is_popup": (IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET),
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
from .tokens import TokenFactory
|
|
2
|
-
from .users import
|
|
2
|
+
from .users import (
|
|
3
|
+
AuthenticatedPersonFactory,
|
|
4
|
+
GroupFactory,
|
|
5
|
+
PermissionFactory,
|
|
6
|
+
SuperUserFactory,
|
|
7
|
+
UserFactory,
|
|
8
|
+
InternalUserFactory,
|
|
9
|
+
)
|
|
3
10
|
from .users_activities import UserActivityFactory
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import factory
|
|
2
2
|
from django.contrib.auth.models import Permission
|
|
3
|
+
from django.contrib.contenttypes.models import ContentType
|
|
3
4
|
from dynamic_preferences.registries import global_preferences_registry
|
|
4
5
|
|
|
5
6
|
from wbcore.contrib.directory.factories import CompanyFactory, PersonFactory
|
|
@@ -90,3 +91,21 @@ class GroupFactory(factory.django.DjangoModelFactory):
|
|
|
90
91
|
|
|
91
92
|
class Meta:
|
|
92
93
|
model = Group
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ContentTypeFactory(factory.django.DjangoModelFactory):
|
|
97
|
+
class Meta:
|
|
98
|
+
model = ContentType
|
|
99
|
+
|
|
100
|
+
app_label = "app"
|
|
101
|
+
model = "model"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class PermissionFactory(factory.django.DjangoModelFactory):
|
|
105
|
+
class Meta:
|
|
106
|
+
model = Permission
|
|
107
|
+
django_get_or_create = ("content_type", "codename")
|
|
108
|
+
|
|
109
|
+
name = factory.Sequence(lambda n: f"Permission {n}")
|
|
110
|
+
codename = factory.Sequence(lambda n: f"codename_{n}")
|
|
111
|
+
content_type = factory.SubFactory(ContentTypeFactory)
|
|
@@ -6,11 +6,10 @@ from .models import UserActivity
|
|
|
6
6
|
|
|
7
7
|
class UserActivityChartFilter(wb_filters.FilterSet):
|
|
8
8
|
date = wb_filters.DateTimeRangeFilter(
|
|
9
|
-
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
10
9
|
label="Date Range",
|
|
11
10
|
required=True,
|
|
12
11
|
clearable=False,
|
|
13
|
-
|
|
12
|
+
initial=current_month_date_range,
|
|
14
13
|
)
|
|
15
14
|
|
|
16
15
|
class Meta:
|