django-cfg 1.3.13__py3-none-any.whl → 1.4.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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/user_admin.py +39 -16
- django_cfg/apps/accounts/serializers/profile.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +18 -11
- django_cfg/apps/accounts/signals.py +15 -24
- django_cfg/apps/accounts/utils/notifications.py +217 -358
- django_cfg/apps/accounts/views/otp.py +2 -2
- django_cfg/apps/accounts/views/webhook.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +1 -1
- django_cfg/apps/api/commands/views.py +66 -83
- django_cfg/apps/api/health/drf_views.py +269 -0
- django_cfg/apps/api/health/serializers.py +45 -0
- django_cfg/apps/api/health/urls.py +6 -1
- django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
- django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
- django_cfg/apps/knowbase/admin/document_admin.py +136 -270
- django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
- django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
- django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
- django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
- django_cfg/apps/knowbase/config/constance_fields.py +1 -1
- django_cfg/apps/knowbase/config/settings.py +2 -2
- django_cfg/apps/knowbase/mixins/__init__.py +19 -2
- django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
- django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
- django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
- django_cfg/apps/knowbase/mixins/creator.py +10 -10
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
- django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
- django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
- django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
- django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
- django_cfg/apps/knowbase/mixins/service.py +2 -2
- django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
- django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
- django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
- django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
- django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
- django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
- django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
- django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
- django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
- django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
- django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
- django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
- django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
- django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
- django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
- django_cfg/apps/knowbase/services/embedding/models.py +18 -14
- django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
- django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
- django_cfg/apps/leads/tests.py +1 -1
- django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
- django_cfg/apps/payments/admin/balance_admin.py +1 -1
- django_cfg/apps/payments/admin/currencies_admin.py +1 -1
- django_cfg/apps/payments/admin/payments_admin.py +1 -1
- django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
- django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
- django_cfg/apps/payments/admin_interface/views/base.py +29 -2
- django_cfg/apps/payments/apps.py +1 -1
- django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
- django_cfg/apps/payments/config/helpers.py +3 -2
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
- django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
- django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
- django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
- django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
- django_cfg/apps/payments/management/commands/test_providers.py +1 -1
- django_cfg/apps/payments/middleware/api_access.py +1 -1
- django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
- django_cfg/apps/payments/models/balance.py +2 -2
- django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
- django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
- django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
- django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
- django_cfg/apps/payments/models/payments.py +2 -2
- django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
- django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
- django_cfg/apps/payments/services/core/base.py +1 -1
- django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
- django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
- django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
- django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
- django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
- django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
- django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
- django_cfg/apps/payments/services/core/payment_service.py +124 -612
- django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
- django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
- django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
- django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
- django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
- django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
- django_cfg/apps/payments/services/providers/base.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
- django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
- django_cfg/apps/payments/services/providers/registry.py +1 -1
- django_cfg/apps/payments/services/providers/sync_service.py +1 -1
- django_cfg/apps/payments/signals/__init__.py +1 -1
- django_cfg/apps/payments/signals/api_key_signals.py +1 -1
- django_cfg/apps/payments/signals/balance_signals.py +1 -1
- django_cfg/apps/payments/signals/payment_signals.py +1 -1
- django_cfg/apps/payments/signals/subscription_signals.py +1 -1
- django_cfg/apps/payments/views/api/api_keys.py +1 -1
- django_cfg/apps/payments/views/api/balances.py +1 -1
- django_cfg/apps/payments/views/api/base.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +1 -1
- django_cfg/apps/payments/views/api/payments.py +1 -1
- django_cfg/apps/payments/views/api/subscriptions.py +1 -1
- django_cfg/apps/payments/views/api/webhooks.py +1 -1
- django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
- django_cfg/apps/payments/views/serializers/balances.py +1 -1
- django_cfg/apps/payments/views/serializers/currencies.py +1 -1
- django_cfg/apps/payments/views/serializers/payments.py +1 -1
- django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
- django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
- django_cfg/apps/support/admin/support_admin.py +21 -13
- django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
- django_cfg/apps/support/utils/support_email_service.py +1 -1
- django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
- django_cfg/apps/tasks/utils/simulator.py +1 -1
- django_cfg/apps/tasks/views/dashboard.py +33 -3
- django_cfg/apps/urls.py +5 -1
- django_cfg/cli/README.md +57 -471
- django_cfg/cli/commands/create_project.py +140 -529
- django_cfg/cli/main.py +13 -10
- django_cfg/core/__init__.py +63 -6
- django_cfg/core/base/__init__.py +5 -0
- django_cfg/core/base/config_model.py +652 -0
- django_cfg/core/builders/__init__.py +11 -0
- django_cfg/core/builders/apps_builder.py +258 -0
- django_cfg/core/builders/middleware_builder.py +115 -0
- django_cfg/core/builders/security_builder.py +96 -0
- django_cfg/core/config.py +20 -892
- django_cfg/core/constants.py +69 -0
- django_cfg/core/environment/__init__.py +9 -0
- django_cfg/core/exceptions.py +45 -298
- django_cfg/core/generation/__init__.py +51 -0
- django_cfg/core/generation/core_generators/__init__.py +0 -0
- django_cfg/core/generation/core_generators/settings.py +90 -0
- django_cfg/core/generation/core_generators/static.py +82 -0
- django_cfg/core/generation/core_generators/templates.py +141 -0
- django_cfg/core/generation/data_generators/__init__.py +15 -0
- django_cfg/core/generation/data_generators/cache.py +132 -0
- django_cfg/core/generation/data_generators/database.py +117 -0
- django_cfg/core/generation/generation.py +92 -0
- django_cfg/core/generation/integration_generators/__init__.py +21 -0
- django_cfg/core/generation/integration_generators/api.py +237 -0
- django_cfg/core/generation/integration_generators/sessions.py +65 -0
- django_cfg/core/generation/integration_generators/tailwind.py +54 -0
- django_cfg/core/generation/integration_generators/tasks.py +92 -0
- django_cfg/core/generation/integration_generators/third_party.py +144 -0
- django_cfg/core/generation/orchestrator.py +285 -0
- django_cfg/core/generation/protocols.py +30 -0
- django_cfg/core/generation/security_generators/__init__.py +0 -0
- django_cfg/core/generation/utility_generators/__init__.py +24 -0
- django_cfg/core/generation/utility_generators/email.py +58 -0
- django_cfg/core/generation/utility_generators/i18n.py +66 -0
- django_cfg/core/generation/utility_generators/limits.py +58 -0
- django_cfg/core/generation/utility_generators/logging.py +66 -0
- django_cfg/core/generation/utility_generators/security.py +101 -0
- django_cfg/core/generation/utils/__init__.py +0 -0
- django_cfg/core/generation/utils/helpers.py +32 -0
- django_cfg/core/integration/__init__.py +18 -25
- django_cfg/core/integration/display/startup.py +146 -133
- django_cfg/core/integration/url_integration.py +13 -2
- django_cfg/core/services/__init__.py +5 -0
- django_cfg/core/services/config_service.py +121 -0
- django_cfg/core/state/__init__.py +9 -0
- django_cfg/core/state/registry.py +84 -0
- django_cfg/core/types/__init__.py +15 -0
- django_cfg/core/types/aliases.py +15 -0
- django_cfg/core/types/enums.py +49 -0
- django_cfg/dashboard/DEBUG_README.md +105 -0
- django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
- django_cfg/dashboard/__init__.py +24 -0
- django_cfg/dashboard/components.py +308 -0
- django_cfg/dashboard/debug.py +176 -0
- django_cfg/dashboard/management/__init__.py +0 -0
- django_cfg/dashboard/management/commands/__init__.py +0 -0
- django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
- django_cfg/dashboard/sections/__init__.py +1 -0
- django_cfg/dashboard/sections/base.py +128 -0
- django_cfg/dashboard/sections/commands.py +32 -0
- django_cfg/dashboard/sections/overview.py +394 -0
- django_cfg/dashboard/sections/stats.py +48 -0
- django_cfg/dashboard/sections/system.py +73 -0
- django_cfg/management/commands/check_settings.py +6 -2
- django_cfg/management/commands/clear_constance.py +6 -1
- django_cfg/management/commands/create_token.py +5 -4
- django_cfg/management/commands/generate.py +5 -0
- django_cfg/management/commands/list_urls.py +7 -2
- django_cfg/management/commands/migrate_all.py +6 -2
- django_cfg/management/commands/migrator.py +6 -1
- django_cfg/management/commands/rundramatiq.py +6 -1
- django_cfg/management/commands/rundramatiq_simulator.py +11 -4
- django_cfg/management/commands/runserver_ngrok.py +9 -7
- django_cfg/management/commands/script.py +25 -21
- django_cfg/management/commands/show_config.py +6 -1
- django_cfg/management/commands/show_urls.py +8 -3
- django_cfg/management/commands/superuser.py +5 -4
- django_cfg/management/commands/task_clear.py +8 -3
- django_cfg/management/commands/task_status.py +8 -3
- django_cfg/management/commands/test_email.py +6 -1
- django_cfg/management/commands/test_telegram.py +6 -1
- django_cfg/management/commands/test_twilio.py +6 -1
- django_cfg/management/commands/tree.py +7 -4
- django_cfg/models/__init__.py +88 -3
- django_cfg/models/api/__init__.py +27 -0
- django_cfg/models/{api.py → api/config.py} +1 -1
- django_cfg/models/api/drf/__init__.py +21 -0
- django_cfg/models/api/drf/config.py +101 -0
- django_cfg/models/api/drf/redoc.py +31 -0
- django_cfg/models/api/drf/spectacular.py +129 -0
- django_cfg/models/api/drf/swagger.py +59 -0
- django_cfg/models/{api_keys.py → api/keys.py} +16 -6
- django_cfg/models/{limits.py → api/limits.py} +0 -1
- django_cfg/models/base/__init__.py +14 -0
- django_cfg/models/django/__init__.py +16 -0
- django_cfg/models/{constance.py → django/constance.py} +1 -1
- django_cfg/models/{environment.py → django/environment.py} +1 -1
- django_cfg/models/infrastructure/__init__.py +17 -0
- django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
- django_cfg/models/infrastructure/database/__init__.py +22 -0
- django_cfg/models/infrastructure/database/config.py +265 -0
- django_cfg/models/infrastructure/database/converters.py +91 -0
- django_cfg/models/infrastructure/database/parsers.py +96 -0
- django_cfg/models/infrastructure/database/routing.py +85 -0
- django_cfg/models/infrastructure/database/validators.py +170 -0
- django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
- django_cfg/models/{security.py → infrastructure/security.py} +2 -2
- django_cfg/models/ngrok/__init__.py +11 -0
- django_cfg/models/ngrok/auth.py +37 -0
- django_cfg/models/ngrok/config.py +77 -0
- django_cfg/models/ngrok/tunnel.py +35 -0
- django_cfg/models/payments/__init__.py +20 -0
- django_cfg/models/payments/api_keys.py +57 -0
- django_cfg/models/{payments.py → payments/config.py} +56 -154
- django_cfg/models/payments/providers/__init__.py +15 -0
- django_cfg/models/payments/providers/base.py +25 -0
- django_cfg/models/payments/providers/nowpayments.py +48 -0
- django_cfg/models/services/__init__.py +18 -0
- django_cfg/models/services/base.py +65 -0
- django_cfg/models/{email.py → services/email.py} +1 -1
- django_cfg/models/services/telegram.py +172 -0
- django_cfg/models/tasks/__init__.py +51 -0
- django_cfg/models/tasks/backends.py +250 -0
- django_cfg/models/tasks/config.py +314 -0
- django_cfg/models/tasks/utils.py +174 -0
- django_cfg/modules/base.py +18 -3
- django_cfg/modules/django_admin/decorators/actions.py +1 -1
- django_cfg/modules/django_admin/decorators/display.py +1 -1
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
- django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
- django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
- django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
- django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
- django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
- django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
- django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
- django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
- django_cfg/modules/django_drf_theme/README.md +207 -0
- django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
- django_cfg/modules/django_drf_theme/__init__.py +23 -0
- django_cfg/modules/django_drf_theme/apps.py +15 -0
- django_cfg/modules/django_drf_theme/renderers.py +58 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
- django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
- django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
- django_cfg/modules/django_email/__init__.py +14 -0
- django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
- django_cfg/modules/django_email/utils.py +40 -0
- django_cfg/modules/django_health/__init__.py +9 -0
- django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
- django_cfg/modules/django_llm/llm/client.py +155 -550
- django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
- django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
- django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
- django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
- django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
- django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
- django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
- django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
- django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
- django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
- django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
- django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
- django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
- django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
- django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
- django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
- django_cfg/modules/django_llm/translator/translator.py +150 -603
- django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
- django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
- django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
- django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
- django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
- django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
- django_cfg/modules/django_logging/__init__.py +14 -0
- django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
- django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
- django_cfg/modules/django_ngrok/__init__.py +39 -0
- django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
- django_cfg/modules/django_rpc_old/POETRY.md +344 -0
- django_cfg/modules/django_rpc_old/README.md +397 -0
- django_cfg/modules/django_rpc_old/TESTING.md +358 -0
- django_cfg/modules/django_rpc_old/__init__.py +39 -0
- django_cfg/modules/django_rpc_old/client.py +531 -0
- django_cfg/modules/django_rpc_old/config.py +279 -0
- django_cfg/modules/django_rpc_old/exceptions.py +172 -0
- django_cfg/modules/django_tailwind/README.md +478 -0
- django_cfg/modules/django_tailwind/__init__.py +7 -0
- django_cfg/modules/django_tailwind/apps.py +10 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
- django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
- django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
- django_cfg/modules/django_tasks/__init__.py +29 -0
- django_cfg/modules/django_tasks/factory.py +127 -0
- django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
- django_cfg/modules/django_tasks/settings.py +107 -0
- django_cfg/modules/django_telegram/__init__.py +29 -0
- django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
- django_cfg/modules/django_telegram/utils.py +62 -0
- django_cfg/modules/django_twilio/__init__.py +54 -107
- django_cfg/modules/django_twilio/_imports.py +30 -0
- django_cfg/modules/django_twilio/base.py +192 -0
- django_cfg/modules/django_twilio/email_otp.py +227 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
- django_cfg/modules/django_twilio/simple_service.py +1 -2
- django_cfg/modules/django_twilio/sms.py +94 -0
- django_cfg/modules/django_twilio/twilio_service.py +2 -3
- django_cfg/modules/django_twilio/unified.py +310 -0
- django_cfg/modules/django_twilio/utils.py +190 -0
- django_cfg/modules/django_twilio/whatsapp.py +137 -0
- django_cfg/modules/django_unfold/callbacks/base.py +198 -7
- django_cfg/modules/django_unfold/callbacks/main.py +102 -10
- django_cfg/modules/django_unfold/dashboard.py +65 -43
- django_cfg/modules/django_unfold/models/config.py +13 -12
- django_cfg/modules/django_unfold/models/navigation.py +8 -3
- django_cfg/modules/django_unfold/models/tabs.py +2 -2
- django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
- django_cfg/registry/core.py +24 -26
- django_cfg/registry/modules.py +5 -2
- django_cfg/registry/services.py +20 -3
- django_cfg/registry/third_party.py +8 -8
- django_cfg/static/admin/css/dashboard.css +260 -0
- django_cfg/static/admin/js/commands.js +171 -0
- django_cfg/static/admin/js/dashboard.js +126 -0
- django_cfg/templates/admin/components/management_commands.js +375 -0
- django_cfg/templates/admin/components/progress_bar.html +18 -23
- django_cfg/templates/admin/index.html +48 -20
- django_cfg/templates/admin/index_new.html +106 -0
- django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
- django_cfg/templates/admin/sections/commands_section.html +626 -0
- django_cfg/templates/admin/sections/overview_section.html +112 -0
- django_cfg/templates/admin/sections/stats_section.html +35 -0
- django_cfg/templates/admin/sections/system_section.html +99 -0
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
- django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
- django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
- django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
- django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
- django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
- django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
- django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
- django_cfg/templatetags/django_cfg.py +7 -1
- django_cfg/utils/smart_defaults.py +4 -4
- django_cfg-1.4.0.dist-info/METADATA +920 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/RECORD +424 -195
- django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
- django_cfg/core/generation.py +0 -621
- django_cfg/management/commands/validate_config.py +0 -189
- django_cfg/models/database.py +0 -480
- django_cfg/models/drf.py +0 -272
- django_cfg/models/ngrok.py +0 -122
- django_cfg/models/services.py +0 -440
- django_cfg/models/tasks.py +0 -550
- django_cfg/modules/django_twilio/service.py +0 -942
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/rest_framework/api.html +0 -12
- django_cfg/utils/toolkit.py +0 -703
- django_cfg-1.3.13.dist-info/METADATA +0 -1029
- /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
- /django_cfg/core/{environment.py → environment/detector.py} +0 -0
- /django_cfg/models/{cors.py → api/cors.py} +0 -0
- /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
- /django_cfg/models/{base.py → base/config.py} +0 -0
- /django_cfg/models/{cfg.py → base/module.py} +0 -0
- /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
- /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,46 +5,32 @@ Universal LLM client supporting multiple providers with caching and token optimi
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import logging
|
8
|
-
import
|
9
|
-
import json
|
10
|
-
from datetime import datetime
|
11
|
-
from typing import Dict, List, Optional, Any, Union
|
8
|
+
from typing import Dict, List, Optional, Any
|
12
9
|
from pathlib import Path
|
13
10
|
|
14
|
-
from openai import OpenAI
|
15
|
-
from openai.types.chat import ChatCompletion
|
16
|
-
from openai import (
|
17
|
-
OpenAIError,
|
18
|
-
RateLimitError,
|
19
|
-
BadRequestError,
|
20
|
-
APIConnectionError,
|
21
|
-
AuthenticationError,
|
22
|
-
)
|
23
|
-
|
24
|
-
from .cache import LLMCache
|
25
11
|
from .models_cache import ModelsCache, ModelInfo
|
26
|
-
from .costs import calculate_chat_cost, calculate_embedding_cost, estimate_cost
|
27
12
|
from .tokenizer import Tokenizer
|
28
13
|
from .extractor import JSONExtractor
|
29
14
|
from .models import (
|
30
|
-
EmbeddingResponse,
|
31
|
-
ChatCompletionResponse,
|
32
|
-
TokenUsage,
|
33
|
-
ChatChoice,
|
34
|
-
LLMStats,
|
35
|
-
CostEstimate,
|
36
|
-
ValidationResult,
|
37
|
-
CacheInfo,
|
38
|
-
LLMError
|
15
|
+
EmbeddingResponse,
|
16
|
+
ChatCompletionResponse,
|
39
17
|
)
|
40
18
|
from ...base import BaseCfgModule
|
41
19
|
|
20
|
+
# Import new components
|
21
|
+
from .providers import ProviderManager, ProviderSelector
|
22
|
+
from .stats import StatsManager
|
23
|
+
from .embeddings import OpenAIEmbedder, MockEmbedder
|
24
|
+
from .responses import ResponseBuilder
|
25
|
+
from .requests import RequestCacheManager, ChatRequestHandler, EmbeddingRequestHandler
|
26
|
+
from .models_api import ModelsQueryAPI
|
27
|
+
|
42
28
|
logger = logging.getLogger(__name__)
|
43
29
|
|
44
30
|
|
45
31
|
class LLMClient(BaseCfgModule):
|
46
32
|
"""Universal LLM client with caching and token optimization."""
|
47
|
-
|
33
|
+
|
48
34
|
def __init__(
|
49
35
|
self,
|
50
36
|
apikey_openrouter: Optional[str] = None,
|
@@ -58,7 +44,7 @@ class LLMClient(BaseCfgModule):
|
|
58
44
|
):
|
59
45
|
"""
|
60
46
|
Initialize LLM client.
|
61
|
-
|
47
|
+
|
62
48
|
Args:
|
63
49
|
apikey_openrouter: API key for OpenRouter (auto-detected if not provided)
|
64
50
|
apikey_openai: API key for OpenAI (auto-detected if not provided)
|
@@ -67,11 +53,11 @@ class LLMClient(BaseCfgModule):
|
|
67
53
|
max_cache_size: Maximum cache size
|
68
54
|
models_cache_ttl: Models cache TTL in seconds (default: 24 hours)
|
69
55
|
config: DjangoConfig instance for getting headers and settings
|
70
|
-
preferred_provider: Preferred provider ("openai" or "openrouter").
|
56
|
+
preferred_provider: Preferred provider ("openai" or "openrouter").
|
71
57
|
If None, defaults to "openai" for embeddings, "openrouter" for chat
|
72
58
|
"""
|
73
59
|
super().__init__()
|
74
|
-
|
60
|
+
|
75
61
|
# Auto-detect API keys from config if not provided
|
76
62
|
django_config = self.get_config()
|
77
63
|
if django_config:
|
@@ -81,156 +67,88 @@ class LLMClient(BaseCfgModule):
|
|
81
67
|
apikey_openai = django_config.api_keys.get_openai_key()
|
82
68
|
else:
|
83
69
|
apikey_openai = getattr(django_config, 'openai_api_key', None)
|
84
|
-
|
70
|
+
|
85
71
|
if apikey_openrouter is None:
|
86
72
|
# Try new api_keys system first
|
87
73
|
if hasattr(django_config, 'api_keys') and django_config.api_keys:
|
88
74
|
apikey_openrouter = django_config.api_keys.get_openrouter_key()
|
89
|
-
|
90
|
-
#
|
91
|
-
self.
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
self.
|
98
|
-
|
99
|
-
|
100
|
-
self.
|
101
|
-
|
102
|
-
|
103
|
-
|
75
|
+
|
76
|
+
# Initialize provider management
|
77
|
+
self.provider_manager = ProviderManager(
|
78
|
+
apikey_openrouter=apikey_openrouter,
|
79
|
+
apikey_openai=apikey_openai,
|
80
|
+
preferred_provider=preferred_provider,
|
81
|
+
django_config=config
|
82
|
+
)
|
83
|
+
self.provider_selector = ProviderSelector(self.provider_manager)
|
84
|
+
|
85
|
+
# Initialize cache and stats
|
86
|
+
self.cache_manager = RequestCacheManager(
|
87
|
+
cache_dir=cache_dir,
|
88
|
+
cache_ttl=cache_ttl,
|
89
|
+
max_cache_size=max_cache_size
|
90
|
+
)
|
91
|
+
self.stats_manager = StatsManager()
|
92
|
+
|
93
|
+
# Initialize models cache if OpenRouter available
|
94
|
+
self.models_cache = None
|
95
|
+
if apikey_openrouter:
|
104
96
|
self.models_cache = ModelsCache(
|
105
|
-
api_key=
|
97
|
+
api_key=apikey_openrouter,
|
106
98
|
cache_dir=cache_dir,
|
107
99
|
cache_ttl=models_cache_ttl
|
108
100
|
)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# Initialize tokenizer and extractor
|
101
|
+
|
102
|
+
# Initialize tokenizer and utilities
|
113
103
|
self.tokenizer = Tokenizer()
|
114
|
-
self.
|
115
|
-
|
116
|
-
# Initialize
|
117
|
-
self.
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
if self.apikey_openai:
|
127
|
-
self.clients["openai"] = OpenAI(
|
128
|
-
api_key=self.apikey_openai
|
129
|
-
)
|
130
|
-
|
131
|
-
# Set primary client
|
132
|
-
self.client = self.clients[self.primary_provider]
|
133
|
-
|
134
|
-
# Default models for each provider
|
135
|
-
self.default_models = {
|
136
|
-
"openrouter": "openai/gpt-4o-mini",
|
137
|
-
"openai": "gpt-4o-mini"
|
138
|
-
}
|
139
|
-
|
140
|
-
# Statistics
|
141
|
-
self.stats = {
|
142
|
-
'total_requests': 0,
|
143
|
-
'successful_requests': 0,
|
144
|
-
'failed_requests': 0,
|
145
|
-
'cache_hits': 0,
|
146
|
-
'cache_misses': 0,
|
147
|
-
'total_tokens_used': 0,
|
148
|
-
'total_cost_usd': 0.0,
|
149
|
-
'model_usage': {},
|
150
|
-
'provider_usage': {}
|
151
|
-
}
|
152
|
-
|
153
|
-
def _get_api_key(self) -> str:
|
154
|
-
"""Get API key from environment."""
|
155
|
-
import os
|
156
|
-
env_var = f"{self.provider.upper()}_API_KEY"
|
157
|
-
api_key = os.getenv(env_var)
|
158
|
-
if not api_key:
|
159
|
-
raise ValueError(f"API key not found. Set {env_var} environment variable.")
|
160
|
-
return api_key
|
161
|
-
|
162
|
-
def _get_provider_config(self) -> Dict[str, Any]:
|
163
|
-
"""Get provider configuration with config-based headers."""
|
164
|
-
base_configs = {
|
165
|
-
"openrouter": {
|
166
|
-
"base_url": "https://openrouter.ai/api/v1",
|
167
|
-
"headers": {}
|
168
|
-
},
|
169
|
-
"openai": {
|
170
|
-
"base_url": "https://api.openai.com/v1",
|
171
|
-
"headers": {}
|
172
|
-
}
|
173
|
-
}
|
174
|
-
|
175
|
-
if self.provider not in base_configs:
|
176
|
-
raise ValueError(f"Unsupported provider: {self.provider}")
|
177
|
-
|
178
|
-
config = base_configs[self.provider].copy()
|
179
|
-
|
180
|
-
site_url = getattr(self.django_config, 'site_url', 'https://djangocfg.com')
|
181
|
-
project_name = getattr(self.django_config, 'project_name', 'Django CFG LLM Client')
|
182
|
-
|
183
|
-
# Get headers from django config if available
|
184
|
-
if self.django_config:
|
185
|
-
if self.provider == "openrouter":
|
186
|
-
# Get site URL and project name from config like in django_email
|
187
|
-
|
188
|
-
config["headers"].update({
|
189
|
-
"HTTP-Referer": site_url,
|
190
|
-
"X-Title": project_name
|
191
|
-
})
|
192
|
-
|
193
|
-
# Add any custom headers from LLM config
|
194
|
-
if hasattr(self.django_config, 'llm') and self.django_config.llm:
|
195
|
-
llm_config = self.django_config.llm
|
196
|
-
if hasattr(llm_config, 'custom_headers'):
|
197
|
-
config["headers"].update(llm_config.custom_headers)
|
198
|
-
else:
|
199
|
-
# Fallback headers if no config
|
200
|
-
if self.provider == "openrouter":
|
201
|
-
config["headers"].update({
|
202
|
-
"HTTP-Referer": site_url,
|
203
|
-
"X-Title": project_name
|
204
|
-
})
|
205
|
-
|
206
|
-
return config
|
207
|
-
|
208
|
-
def _get_openrouter_headers(self) -> Dict[str, str]:
|
209
|
-
"""Get headers for OpenRouter API."""
|
210
|
-
headers = {}
|
211
|
-
|
212
|
-
# Add site info from Django config if available
|
213
|
-
if self.django_config:
|
214
|
-
try:
|
215
|
-
site_url = getattr(self.django_config, 'site_url', 'http://localhost:8000')
|
216
|
-
project_name = getattr(self.django_config, 'project_name', 'Django CFG')
|
217
|
-
headers.update({
|
218
|
-
"HTTP-Referer": site_url,
|
219
|
-
"X-Title": project_name
|
220
|
-
})
|
221
|
-
except Exception:
|
222
|
-
pass
|
223
|
-
|
224
|
-
return headers
|
104
|
+
self.json_extractor = JSONExtractor()
|
105
|
+
|
106
|
+
# Initialize response builder
|
107
|
+
self.response_builder = ResponseBuilder(
|
108
|
+
models_cache=self.models_cache,
|
109
|
+
json_extractor=self.json_extractor
|
110
|
+
)
|
111
|
+
|
112
|
+
# Initialize embedding strategies
|
113
|
+
self.openai_embedder = OpenAIEmbedder(models_cache=self.models_cache)
|
114
|
+
self.mock_embedder = MockEmbedder(models_cache=self.models_cache)
|
225
115
|
|
116
|
+
# Initialize request handlers
|
117
|
+
self.chat_handler = ChatRequestHandler(
|
118
|
+
provider_manager=self.provider_manager,
|
119
|
+
cache_manager=self.cache_manager,
|
120
|
+
stats_manager=self.stats_manager,
|
121
|
+
response_builder=self.response_builder,
|
122
|
+
tokenizer=self.tokenizer
|
123
|
+
)
|
124
|
+
|
125
|
+
self.embedding_handler = EmbeddingRequestHandler(
|
126
|
+
provider_manager=self.provider_manager,
|
127
|
+
provider_selector=self.provider_selector,
|
128
|
+
cache_manager=self.cache_manager,
|
129
|
+
stats_manager=self.stats_manager,
|
130
|
+
openai_embedder=self.openai_embedder,
|
131
|
+
mock_embedder=self.mock_embedder
|
132
|
+
)
|
133
|
+
|
134
|
+
# Initialize models query API
|
135
|
+
self.models_api = ModelsQueryAPI(models_cache=self.models_cache)
|
136
|
+
|
137
|
+
logger.info(
|
138
|
+
f"LLMClient initialized with primary provider: "
|
139
|
+
f"{self.provider_manager.primary_provider}"
|
140
|
+
)
|
141
|
+
|
142
|
+
# Token counting (delegate to tokenizer)
|
226
143
|
def count_tokens(self, text: str, model: str) -> int:
|
227
144
|
"""Count tokens in text using tokenizer."""
|
228
145
|
return self.tokenizer.count_tokens(text, model)
|
229
|
-
|
146
|
+
|
230
147
|
def count_messages_tokens(self, messages: List[Dict[str, str]], model: str) -> int:
|
231
148
|
"""Count total tokens in messages using tokenizer."""
|
232
149
|
return self.tokenizer.count_messages_tokens(messages, model)
|
233
|
-
|
150
|
+
|
151
|
+
# Chat completion (delegate to chat handler)
|
234
152
|
def chat_completion(
|
235
153
|
self,
|
236
154
|
messages: List[Dict[str, str]],
|
@@ -242,7 +160,7 @@ class LLMClient(BaseCfgModule):
|
|
242
160
|
) -> ChatCompletionResponse:
|
243
161
|
"""
|
244
162
|
Send chat completion request.
|
245
|
-
|
163
|
+
|
246
164
|
Args:
|
247
165
|
messages: List of chat messages
|
248
166
|
model: Model to use
|
@@ -250,24 +168,11 @@ class LLMClient(BaseCfgModule):
|
|
250
168
|
temperature: Temperature for generation
|
251
169
|
response_format: Response format (e.g., "json")
|
252
170
|
**kwargs: Additional parameters
|
253
|
-
|
171
|
+
|
254
172
|
Returns:
|
255
173
|
Chat completion response
|
256
174
|
"""
|
257
|
-
|
258
|
-
raise RuntimeError("OpenAI client not initialized")
|
259
|
-
|
260
|
-
# Use default model if not specified
|
261
|
-
if model is None:
|
262
|
-
model = self.default_models[self.primary_provider]
|
263
|
-
|
264
|
-
# For OpenAI, remove provider prefix if present
|
265
|
-
api_model = model
|
266
|
-
if self.primary_provider == "openai" and model.startswith("openai/"):
|
267
|
-
api_model = model.replace("openai/", "")
|
268
|
-
|
269
|
-
# Generate cache key
|
270
|
-
request_hash = self.cache.generate_request_hash(
|
175
|
+
return self.chat_handler.chat_completion(
|
271
176
|
messages=messages,
|
272
177
|
model=model,
|
273
178
|
max_tokens=max_tokens,
|
@@ -275,409 +180,109 @@ class LLMClient(BaseCfgModule):
|
|
275
180
|
response_format=response_format,
|
276
181
|
**kwargs
|
277
182
|
)
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
params = {
|
299
|
-
"model": api_model,
|
300
|
-
"messages": messages,
|
301
|
-
"stream": False
|
302
|
-
}
|
303
|
-
|
304
|
-
# Add optional parameters
|
305
|
-
if max_tokens is not None:
|
306
|
-
params["max_tokens"] = max_tokens
|
307
|
-
if temperature is not None:
|
308
|
-
params["temperature"] = temperature
|
309
|
-
if response_format:
|
310
|
-
params["response_format"] = {"type": response_format}
|
311
|
-
|
312
|
-
# Add any additional kwargs
|
313
|
-
params.update(kwargs)
|
314
|
-
|
315
|
-
# Make request
|
316
|
-
response: ChatCompletion = self.client.chat.completions.create(**params)
|
317
|
-
|
318
|
-
# Calculate processing time and cost
|
319
|
-
processing_time = time.time() - start_time
|
320
|
-
tokens_used = response.usage.total_tokens if response.usage else 0
|
321
|
-
usage_dict = response.usage.model_dump() if response.usage else {'total_tokens': 0, 'prompt_tokens': 0, 'completion_tokens': 0}
|
322
|
-
cost_usd = calculate_chat_cost(usage_dict, model, self.models_cache)
|
323
|
-
|
324
|
-
# Extract content
|
325
|
-
content = response.choices[0].message.content if response.choices else ""
|
326
|
-
|
327
|
-
# Try to extract JSON if response_format was "json"
|
328
|
-
extracted_json = None
|
329
|
-
if response_format == "json" and content:
|
330
|
-
extracted_json = self.extractor.extract_json_from_response(content)
|
331
|
-
|
332
|
-
# Create Pydantic response object
|
333
|
-
completion_response = ChatCompletionResponse(
|
334
|
-
id=response.id,
|
335
|
-
model=response.model,
|
336
|
-
created=datetime.fromtimestamp(response.created).isoformat(),
|
337
|
-
choices=[
|
338
|
-
ChatChoice(
|
339
|
-
index=choice.index,
|
340
|
-
message=choice.message.model_dump() if hasattr(choice.message, 'model_dump') else choice.message,
|
341
|
-
finish_reason=choice.finish_reason
|
342
|
-
) for choice in response.choices
|
343
|
-
] if response.choices else [],
|
344
|
-
usage=TokenUsage(
|
345
|
-
prompt_tokens=response.usage.prompt_tokens if response.usage else 0,
|
346
|
-
completion_tokens=response.usage.completion_tokens if response.usage else 0,
|
347
|
-
total_tokens=response.usage.total_tokens if response.usage else tokens_used
|
348
|
-
) if response.usage else TokenUsage(total_tokens=tokens_used),
|
349
|
-
finish_reason=response.choices[0].finish_reason if response.choices else None,
|
350
|
-
content=content,
|
351
|
-
tokens_used=tokens_used,
|
352
|
-
cost_usd=cost_usd,
|
353
|
-
processing_time=processing_time,
|
354
|
-
extracted_json=extracted_json
|
355
|
-
)
|
356
|
-
|
357
|
-
# Cache the response (serialize to dict for caching)
|
358
|
-
self.cache.set_response(request_hash, completion_response.model_dump(), model)
|
359
|
-
|
360
|
-
# Update stats
|
361
|
-
self.stats['successful_requests'] += 1
|
362
|
-
self.stats['total_tokens_used'] += tokens_used
|
363
|
-
self.stats['total_cost_usd'] += cost_usd
|
364
|
-
self.stats['model_usage'][model] = self.stats['model_usage'].get(model, 0) + 1
|
365
|
-
self.stats['provider_usage'][self.primary_provider] = self.stats['provider_usage'].get(self.primary_provider, 0) + 1
|
366
|
-
|
367
|
-
return completion_response
|
368
|
-
|
369
|
-
except Exception as e:
|
370
|
-
self.stats['failed_requests'] += 1
|
371
|
-
logger.error(f"Chat completion failed: {e}")
|
372
|
-
raise
|
373
|
-
|
374
|
-
|
183
|
+
|
184
|
+
# Embedding generation (delegate to embedding handler)
|
185
|
+
def generate_embedding(
|
186
|
+
self,
|
187
|
+
text: str,
|
188
|
+
model: str = "text-embedding-ada-002"
|
189
|
+
) -> EmbeddingResponse:
|
190
|
+
"""
|
191
|
+
Generate embedding for text.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
text: Text to generate embedding for
|
195
|
+
model: Embedding model to use
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
Dictionary with embedding data and metadata
|
199
|
+
"""
|
200
|
+
return self.embedding_handler.generate_embedding(text=text, model=model)
|
201
|
+
|
202
|
+
# Cost estimation
|
375
203
|
def estimate_cost(self, model: str, input_tokens: int, output_tokens: int) -> float:
|
376
204
|
"""
|
377
205
|
Estimate cost for a model.
|
378
|
-
|
206
|
+
|
379
207
|
Args:
|
380
208
|
model: Model ID
|
381
209
|
input_tokens: Number of input tokens
|
382
210
|
output_tokens: Number of output tokens
|
383
|
-
|
211
|
+
|
384
212
|
Returns:
|
385
213
|
Estimated cost in USD
|
386
214
|
"""
|
387
|
-
|
388
|
-
if self.models_cache:
|
389
|
-
try:
|
390
|
-
cost = self.models_cache.get_model_cost_estimate(model, input_tokens, output_tokens)
|
391
|
-
if cost is not None:
|
392
|
-
return cost
|
393
|
-
except Exception as e:
|
394
|
-
logger.warning(f"Failed to estimate cost from models cache: {e}")
|
395
|
-
|
396
|
-
# Fallback to internal calculation
|
397
|
-
usage_dict = {
|
398
|
-
'total_tokens': input_tokens + output_tokens,
|
399
|
-
'prompt_tokens': input_tokens,
|
400
|
-
'completion_tokens': output_tokens
|
401
|
-
}
|
215
|
+
from .costs import estimate_cost
|
402
216
|
return estimate_cost(model, input_tokens, output_tokens, self.models_cache)
|
403
|
-
|
217
|
+
|
218
|
+
# Statistics and info (delegate to stats manager)
|
404
219
|
def get_stats(self) -> Dict[str, Any]:
|
405
220
|
"""Get usage statistics."""
|
406
|
-
return self.
|
407
|
-
|
221
|
+
return self.stats_manager.get_stats()
|
222
|
+
|
408
223
|
def get_client_info(self) -> Dict[str, Any]:
|
409
224
|
"""Get client information."""
|
410
225
|
return {
|
411
|
-
"primary_provider": self.primary_provider,
|
412
|
-
"available_providers":
|
413
|
-
"
|
414
|
-
"
|
415
|
-
"
|
416
|
-
"has_openai": self.apikey_openai is not None
|
226
|
+
"primary_provider": self.provider_manager.primary_provider,
|
227
|
+
"available_providers": self.provider_manager.get_available_providers(),
|
228
|
+
"cache_info": self.cache_manager.get_cache_info(),
|
229
|
+
"has_openrouter": self.provider_manager.has_provider("openrouter"),
|
230
|
+
"has_openai": self.provider_manager.has_provider("openai")
|
417
231
|
}
|
418
|
-
|
232
|
+
|
419
233
|
def clear_cache(self):
|
420
234
|
"""Clear the cache."""
|
421
|
-
self.
|
422
|
-
|
423
|
-
# Models
|
235
|
+
self.cache_manager.clear_cache()
|
236
|
+
|
237
|
+
# Models API delegation
|
424
238
|
async def fetch_models(self, force_refresh: bool = False) -> Dict[str, ModelInfo]:
|
425
239
|
"""
|
426
240
|
Fetch available models with pricing information.
|
427
|
-
|
241
|
+
|
428
242
|
Args:
|
429
243
|
force_refresh: Force refresh even if cache is valid
|
430
|
-
|
244
|
+
|
431
245
|
Returns:
|
432
246
|
Dictionary of model_id -> ModelInfo
|
433
247
|
"""
|
434
|
-
|
435
|
-
|
436
|
-
return {}
|
437
|
-
|
438
|
-
return await self.models_cache.fetch_models(force_refresh=force_refresh)
|
439
|
-
|
248
|
+
return await self.models_api.fetch_models(force_refresh=force_refresh)
|
249
|
+
|
440
250
|
def get_model_info(self, model_id: str) -> Optional[ModelInfo]:
|
441
|
-
"""Get information about a specific model"""
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
return []
|
453
|
-
|
454
|
-
return self.models_cache.get_models_by_price_range(min_price, max_price)
|
455
|
-
|
251
|
+
"""Get information about a specific model."""
|
252
|
+
return self.models_api.get_model_info(model_id)
|
253
|
+
|
254
|
+
def get_models_by_price(
|
255
|
+
self,
|
256
|
+
min_price: float = 0.0,
|
257
|
+
max_price: float = float('inf')
|
258
|
+
) -> List[ModelInfo]:
|
259
|
+
"""Get models within a price range."""
|
260
|
+
return self.models_api.get_models_by_price(min_price, max_price)
|
261
|
+
|
456
262
|
def get_free_models(self) -> List[ModelInfo]:
|
457
|
-
"""Get all free models"""
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
return self.models_cache.get_free_models()
|
462
|
-
|
263
|
+
"""Get all free models."""
|
264
|
+
return self.models_api.get_free_models()
|
265
|
+
|
463
266
|
def get_budget_models(self, max_price: float = 1.0) -> List[ModelInfo]:
|
464
|
-
"""Get budget models"""
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
return self.models_cache.get_budget_models(max_price)
|
469
|
-
|
267
|
+
"""Get budget models."""
|
268
|
+
return self.models_api.get_budget_models(max_price)
|
269
|
+
|
470
270
|
def get_premium_models(self, min_price: float = 10.0) -> List[ModelInfo]:
|
471
|
-
"""Get premium models"""
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
return self.models_cache.get_premium_models(min_price)
|
476
|
-
|
271
|
+
"""Get premium models."""
|
272
|
+
return self.models_api.get_premium_models(min_price)
|
273
|
+
|
477
274
|
def search_models(self, query: str) -> List[ModelInfo]:
|
478
|
-
"""Search models by name, description, or tags"""
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
return self.models_cache.search_models(query)
|
483
|
-
|
275
|
+
"""Search models by name, description, or tags."""
|
276
|
+
return self.models_api.search_models(query)
|
277
|
+
|
484
278
|
def get_models_summary(self) -> Dict[str, Any]:
|
485
|
-
"""Get summary of available models"""
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
return self.models_cache.get_models_summary()
|
490
|
-
|
279
|
+
"""Get summary of available models."""
|
280
|
+
return self.models_api.get_models_summary()
|
281
|
+
|
491
282
|
def get_models_cache_info(self) -> Dict[str, Any]:
|
492
|
-
"""Get models cache information"""
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
return self.models_cache.get_cache_info()
|
497
|
-
|
283
|
+
"""Get models cache information."""
|
284
|
+
return self.models_api.get_models_cache_info()
|
285
|
+
|
498
286
|
def clear_models_cache(self):
|
499
|
-
"""Clear the models cache"""
|
500
|
-
|
501
|
-
self.models_cache.clear_cache()
|
502
|
-
logger.info("LLM client cache cleared")
|
503
|
-
|
504
|
-
def generate_embedding(self, text: str, model: str = "text-embedding-ada-002") -> EmbeddingResponse:
|
505
|
-
"""
|
506
|
-
Generate embedding for text.
|
507
|
-
|
508
|
-
Args:
|
509
|
-
text: Text to generate embedding for
|
510
|
-
model: Embedding model to use
|
511
|
-
|
512
|
-
Returns:
|
513
|
-
Dictionary with embedding data and metadata
|
514
|
-
"""
|
515
|
-
if not self.client:
|
516
|
-
raise RuntimeError("OpenAI client not initialized")
|
517
|
-
|
518
|
-
# Generate cache key for embedding
|
519
|
-
request_hash = self.cache.generate_request_hash(
|
520
|
-
messages=[{"role": "user", "content": text}],
|
521
|
-
model=model,
|
522
|
-
task="embedding"
|
523
|
-
)
|
524
|
-
|
525
|
-
# Check cache
|
526
|
-
cached_response = self.cache.get_response(request_hash)
|
527
|
-
if cached_response:
|
528
|
-
logger.debug("Cache hit for embedding generation")
|
529
|
-
self.stats['cache_hits'] += 1
|
530
|
-
# Convert cached dict back to Pydantic model
|
531
|
-
return EmbeddingResponse(**cached_response)
|
532
|
-
|
533
|
-
self.stats['cache_misses'] += 1
|
534
|
-
self.stats['total_requests'] += 1
|
535
|
-
|
536
|
-
start_time = time.time()
|
537
|
-
try:
|
538
|
-
# Get the best provider for embedding task
|
539
|
-
embedding_provider = self.get_provider_for_task("embedding")
|
540
|
-
|
541
|
-
# For OpenRouter, we need to use a different model for embeddings
|
542
|
-
# OpenRouter doesn't support OpenAI embedding models directly
|
543
|
-
if embedding_provider == "openrouter":
|
544
|
-
# Use a text generation model to simulate embeddings
|
545
|
-
# This is a workaround since OpenRouter doesn't have embedding endpoints
|
546
|
-
logger.warning("OpenRouter doesn't support embedding models, using text generation as fallback")
|
547
|
-
|
548
|
-
# Create a simple embedding simulation using text generation
|
549
|
-
messages = [
|
550
|
-
{"role": "system", "content": "Generate a numerical representation (embedding-like) for the following text. Return only numbers separated by commas."},
|
551
|
-
{"role": "user", "content": f"Text: {text[:500]}"} # Limit text length
|
552
|
-
]
|
553
|
-
|
554
|
-
# Use a cheap model for this
|
555
|
-
chat_model = "openai/gpt-4o-mini"
|
556
|
-
response = self.client.chat.completions.create(
|
557
|
-
model=chat_model,
|
558
|
-
messages=messages,
|
559
|
-
max_tokens=100,
|
560
|
-
temperature=0.0
|
561
|
-
)
|
562
|
-
|
563
|
-
# Create a mock embedding (this is not a real embedding!)
|
564
|
-
import hashlib
|
565
|
-
text_hash = hashlib.md5(text.encode()).hexdigest()
|
566
|
-
mock_embedding = [float(int(text_hash[i:i+2], 16)) / 255.0 for i in range(0, min(32, len(text_hash)), 2)]
|
567
|
-
|
568
|
-
# Pad to standard embedding size
|
569
|
-
while len(mock_embedding) < 1536:
|
570
|
-
mock_embedding.append(0.0)
|
571
|
-
mock_embedding = mock_embedding[:1536]
|
572
|
-
|
573
|
-
tokens_used = len(text.split()) # Rough estimate
|
574
|
-
cost = calculate_embedding_cost(tokens_used, model, self.models_cache)
|
575
|
-
|
576
|
-
result = EmbeddingResponse(
|
577
|
-
embedding=mock_embedding,
|
578
|
-
tokens=tokens_used,
|
579
|
-
cost=cost,
|
580
|
-
model=model,
|
581
|
-
text_length=len(text),
|
582
|
-
dimension=len(mock_embedding),
|
583
|
-
response_time=time.time() - start_time,
|
584
|
-
warning="This is a mock embedding, not a real one. OpenRouter doesn't support embedding models."
|
585
|
-
)
|
586
|
-
else:
|
587
|
-
# Use real OpenAI embedding API
|
588
|
-
embedding_client = self.clients[embedding_provider]
|
589
|
-
# For OpenAI, remove provider prefix if present
|
590
|
-
api_model = model
|
591
|
-
if embedding_provider == "openai" and model.startswith("openai/"):
|
592
|
-
api_model = model.replace("openai/", "")
|
593
|
-
|
594
|
-
response = embedding_client.embeddings.create(
|
595
|
-
input=text,
|
596
|
-
model=api_model
|
597
|
-
)
|
598
|
-
|
599
|
-
# Extract embedding data
|
600
|
-
embedding_data = response.data[0]
|
601
|
-
embedding_vector = embedding_data.embedding
|
602
|
-
|
603
|
-
# Calculate tokens and cost
|
604
|
-
tokens_used = response.usage.total_tokens
|
605
|
-
cost = calculate_embedding_cost(tokens_used, model, self.models_cache)
|
606
|
-
|
607
|
-
result = EmbeddingResponse(
|
608
|
-
embedding=embedding_vector,
|
609
|
-
tokens=tokens_used,
|
610
|
-
cost=cost,
|
611
|
-
model=model,
|
612
|
-
text_length=len(text),
|
613
|
-
dimension=len(embedding_vector),
|
614
|
-
response_time=time.time() - start_time
|
615
|
-
)
|
616
|
-
|
617
|
-
# Update statistics
|
618
|
-
self.stats['successful_requests'] += 1
|
619
|
-
self.stats['total_tokens_used'] += result.tokens
|
620
|
-
self.stats['total_cost_usd'] += result.cost
|
621
|
-
|
622
|
-
# Cache the result (convert to dict for caching)
|
623
|
-
self.cache.set_response(request_hash, result.model_dump(), model)
|
624
|
-
|
625
|
-
logger.debug(f"Generated embedding: {result.tokens} tokens, ${result.cost:.6f}")
|
626
|
-
return result
|
627
|
-
|
628
|
-
except Exception as e:
|
629
|
-
self.stats['failed_requests'] += 1
|
630
|
-
error_msg = f"Embedding generation failed: {e}"
|
631
|
-
logger.error(error_msg)
|
632
|
-
raise RuntimeError(error_msg) from e
|
633
|
-
|
634
|
-
def _determine_primary_provider(self) -> str:
|
635
|
-
"""
|
636
|
-
Determine primary provider based on preference and available keys.
|
637
|
-
|
638
|
-
Returns:
|
639
|
-
Primary provider name
|
640
|
-
"""
|
641
|
-
# If preferred provider is explicitly set and available, use it
|
642
|
-
if self.preferred_provider:
|
643
|
-
if self.preferred_provider == "openai" and self.apikey_openai:
|
644
|
-
return "openai"
|
645
|
-
elif self.preferred_provider == "openrouter" and self.apikey_openrouter:
|
646
|
-
return "openrouter"
|
647
|
-
else:
|
648
|
-
logger.warning(f"Preferred provider '{self.preferred_provider}' not available, falling back to auto-detection")
|
649
|
-
|
650
|
-
# Auto-detection: prefer OpenAI for embeddings, OpenRouter for chat
|
651
|
-
if self.apikey_openai:
|
652
|
-
return "openai"
|
653
|
-
elif self.apikey_openrouter:
|
654
|
-
return "openrouter"
|
655
|
-
else:
|
656
|
-
raise ValueError("At least one API key (openrouter or openai) must be provided")
|
657
|
-
|
658
|
-
def _get_primary_api_key(self) -> str:
|
659
|
-
"""Get API key for the primary provider."""
|
660
|
-
if self.primary_provider == "openai":
|
661
|
-
return self.apikey_openai
|
662
|
-
elif self.primary_provider == "openrouter":
|
663
|
-
return self.apikey_openrouter
|
664
|
-
else:
|
665
|
-
raise ValueError(f"Unknown primary provider: {self.primary_provider}")
|
666
|
-
|
667
|
-
def get_provider_for_task(self, task: str = "chat") -> str:
|
668
|
-
"""
|
669
|
-
Get the best provider for a specific task.
|
670
|
-
|
671
|
-
Args:
|
672
|
-
task: Task type ("chat", "embedding", "completion")
|
673
|
-
|
674
|
-
Returns:
|
675
|
-
Provider name for the task
|
676
|
-
"""
|
677
|
-
# For embeddings, always prefer OpenAI if available
|
678
|
-
if task == "embedding" and "openai" in self.clients:
|
679
|
-
return "openai"
|
680
|
-
|
681
|
-
# For other tasks, use primary provider or preferred
|
682
|
-
return self.primary_provider
|
683
|
-
|
287
|
+
"""Clear the models cache."""
|
288
|
+
self.models_api.clear_models_cache()
|