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.
Files changed (438) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/user_admin.py +39 -16
  3. django_cfg/apps/accounts/serializers/profile.py +1 -1
  4. django_cfg/apps/accounts/services/otp_service.py +18 -11
  5. django_cfg/apps/accounts/signals.py +15 -24
  6. django_cfg/apps/accounts/utils/notifications.py +217 -358
  7. django_cfg/apps/accounts/views/otp.py +2 -2
  8. django_cfg/apps/accounts/views/webhook.py +1 -1
  9. django_cfg/apps/agents/core/django_agent.py +1 -1
  10. django_cfg/apps/api/commands/views.py +66 -83
  11. django_cfg/apps/api/health/drf_views.py +269 -0
  12. django_cfg/apps/api/health/serializers.py +45 -0
  13. django_cfg/apps/api/health/urls.py +6 -1
  14. django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
  15. django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
  16. django_cfg/apps/knowbase/admin/document_admin.py +136 -270
  17. django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
  18. django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
  19. django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
  20. django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
  21. django_cfg/apps/knowbase/config/constance_fields.py +1 -1
  22. django_cfg/apps/knowbase/config/settings.py +2 -2
  23. django_cfg/apps/knowbase/mixins/__init__.py +19 -2
  24. django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
  25. django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
  26. django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
  27. django_cfg/apps/knowbase/mixins/creator.py +10 -10
  28. django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
  29. django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
  30. django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
  31. django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
  32. django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
  33. django_cfg/apps/knowbase/mixins/service.py +2 -2
  34. django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
  35. django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
  36. django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
  37. django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
  38. django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
  39. django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
  40. django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
  41. django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
  42. django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
  43. django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
  44. django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
  45. django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
  46. django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
  47. django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
  48. django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
  49. django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
  50. django_cfg/apps/knowbase/services/embedding/models.py +18 -14
  51. django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
  52. django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
  53. django_cfg/apps/leads/tests.py +1 -1
  54. django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
  55. django_cfg/apps/payments/admin/balance_admin.py +1 -1
  56. django_cfg/apps/payments/admin/currencies_admin.py +1 -1
  57. django_cfg/apps/payments/admin/payments_admin.py +1 -1
  58. django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
  59. django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
  60. django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
  61. django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
  62. django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
  63. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
  64. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
  65. django_cfg/apps/payments/admin_interface/views/base.py +29 -2
  66. django_cfg/apps/payments/apps.py +1 -1
  67. django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
  68. django_cfg/apps/payments/config/helpers.py +3 -2
  69. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
  70. django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
  71. django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
  72. django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
  73. django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
  74. django_cfg/apps/payments/management/commands/test_providers.py +1 -1
  75. django_cfg/apps/payments/middleware/api_access.py +1 -1
  76. django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
  77. django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
  78. django_cfg/apps/payments/models/balance.py +2 -2
  79. django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
  80. django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
  81. django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
  82. django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
  83. django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
  84. django_cfg/apps/payments/models/payments.py +2 -2
  85. django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
  86. django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
  87. django_cfg/apps/payments/services/core/base.py +1 -1
  88. django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
  89. django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
  90. django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
  91. django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
  92. django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
  93. django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
  94. django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
  95. django_cfg/apps/payments/services/core/payment_service.py +124 -612
  96. django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
  97. django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
  98. django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
  99. django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
  100. django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
  101. django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
  102. django_cfg/apps/payments/services/providers/base.py +1 -1
  103. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
  104. django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
  105. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
  106. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
  107. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
  108. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
  109. django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
  110. django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
  111. django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
  112. django_cfg/apps/payments/services/providers/registry.py +1 -1
  113. django_cfg/apps/payments/services/providers/sync_service.py +1 -1
  114. django_cfg/apps/payments/signals/__init__.py +1 -1
  115. django_cfg/apps/payments/signals/api_key_signals.py +1 -1
  116. django_cfg/apps/payments/signals/balance_signals.py +1 -1
  117. django_cfg/apps/payments/signals/payment_signals.py +1 -1
  118. django_cfg/apps/payments/signals/subscription_signals.py +1 -1
  119. django_cfg/apps/payments/views/api/api_keys.py +1 -1
  120. django_cfg/apps/payments/views/api/balances.py +1 -1
  121. django_cfg/apps/payments/views/api/base.py +1 -1
  122. django_cfg/apps/payments/views/api/currencies.py +1 -1
  123. django_cfg/apps/payments/views/api/payments.py +1 -1
  124. django_cfg/apps/payments/views/api/subscriptions.py +1 -1
  125. django_cfg/apps/payments/views/api/webhooks.py +1 -1
  126. django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
  127. django_cfg/apps/payments/views/serializers/balances.py +1 -1
  128. django_cfg/apps/payments/views/serializers/currencies.py +1 -1
  129. django_cfg/apps/payments/views/serializers/payments.py +1 -1
  130. django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
  131. django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
  132. django_cfg/apps/support/admin/support_admin.py +21 -13
  133. django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
  134. django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
  135. django_cfg/apps/support/utils/support_email_service.py +1 -1
  136. django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
  137. django_cfg/apps/tasks/utils/simulator.py +1 -1
  138. django_cfg/apps/tasks/views/dashboard.py +33 -3
  139. django_cfg/apps/urls.py +5 -1
  140. django_cfg/cli/README.md +57 -471
  141. django_cfg/cli/commands/create_project.py +140 -529
  142. django_cfg/cli/main.py +13 -10
  143. django_cfg/core/__init__.py +63 -6
  144. django_cfg/core/base/__init__.py +5 -0
  145. django_cfg/core/base/config_model.py +652 -0
  146. django_cfg/core/builders/__init__.py +11 -0
  147. django_cfg/core/builders/apps_builder.py +258 -0
  148. django_cfg/core/builders/middleware_builder.py +115 -0
  149. django_cfg/core/builders/security_builder.py +96 -0
  150. django_cfg/core/config.py +20 -892
  151. django_cfg/core/constants.py +69 -0
  152. django_cfg/core/environment/__init__.py +9 -0
  153. django_cfg/core/exceptions.py +45 -298
  154. django_cfg/core/generation/__init__.py +51 -0
  155. django_cfg/core/generation/core_generators/__init__.py +0 -0
  156. django_cfg/core/generation/core_generators/settings.py +90 -0
  157. django_cfg/core/generation/core_generators/static.py +82 -0
  158. django_cfg/core/generation/core_generators/templates.py +141 -0
  159. django_cfg/core/generation/data_generators/__init__.py +15 -0
  160. django_cfg/core/generation/data_generators/cache.py +132 -0
  161. django_cfg/core/generation/data_generators/database.py +117 -0
  162. django_cfg/core/generation/generation.py +92 -0
  163. django_cfg/core/generation/integration_generators/__init__.py +21 -0
  164. django_cfg/core/generation/integration_generators/api.py +237 -0
  165. django_cfg/core/generation/integration_generators/sessions.py +65 -0
  166. django_cfg/core/generation/integration_generators/tailwind.py +54 -0
  167. django_cfg/core/generation/integration_generators/tasks.py +92 -0
  168. django_cfg/core/generation/integration_generators/third_party.py +144 -0
  169. django_cfg/core/generation/orchestrator.py +285 -0
  170. django_cfg/core/generation/protocols.py +30 -0
  171. django_cfg/core/generation/security_generators/__init__.py +0 -0
  172. django_cfg/core/generation/utility_generators/__init__.py +24 -0
  173. django_cfg/core/generation/utility_generators/email.py +58 -0
  174. django_cfg/core/generation/utility_generators/i18n.py +66 -0
  175. django_cfg/core/generation/utility_generators/limits.py +58 -0
  176. django_cfg/core/generation/utility_generators/logging.py +66 -0
  177. django_cfg/core/generation/utility_generators/security.py +101 -0
  178. django_cfg/core/generation/utils/__init__.py +0 -0
  179. django_cfg/core/generation/utils/helpers.py +32 -0
  180. django_cfg/core/integration/__init__.py +18 -25
  181. django_cfg/core/integration/display/startup.py +146 -133
  182. django_cfg/core/integration/url_integration.py +13 -2
  183. django_cfg/core/services/__init__.py +5 -0
  184. django_cfg/core/services/config_service.py +121 -0
  185. django_cfg/core/state/__init__.py +9 -0
  186. django_cfg/core/state/registry.py +84 -0
  187. django_cfg/core/types/__init__.py +15 -0
  188. django_cfg/core/types/aliases.py +15 -0
  189. django_cfg/core/types/enums.py +49 -0
  190. django_cfg/dashboard/DEBUG_README.md +105 -0
  191. django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
  192. django_cfg/dashboard/__init__.py +24 -0
  193. django_cfg/dashboard/components.py +308 -0
  194. django_cfg/dashboard/debug.py +176 -0
  195. django_cfg/dashboard/management/__init__.py +0 -0
  196. django_cfg/dashboard/management/commands/__init__.py +0 -0
  197. django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
  198. django_cfg/dashboard/sections/__init__.py +1 -0
  199. django_cfg/dashboard/sections/base.py +128 -0
  200. django_cfg/dashboard/sections/commands.py +32 -0
  201. django_cfg/dashboard/sections/overview.py +394 -0
  202. django_cfg/dashboard/sections/stats.py +48 -0
  203. django_cfg/dashboard/sections/system.py +73 -0
  204. django_cfg/management/commands/check_settings.py +6 -2
  205. django_cfg/management/commands/clear_constance.py +6 -1
  206. django_cfg/management/commands/create_token.py +5 -4
  207. django_cfg/management/commands/generate.py +5 -0
  208. django_cfg/management/commands/list_urls.py +7 -2
  209. django_cfg/management/commands/migrate_all.py +6 -2
  210. django_cfg/management/commands/migrator.py +6 -1
  211. django_cfg/management/commands/rundramatiq.py +6 -1
  212. django_cfg/management/commands/rundramatiq_simulator.py +11 -4
  213. django_cfg/management/commands/runserver_ngrok.py +9 -7
  214. django_cfg/management/commands/script.py +25 -21
  215. django_cfg/management/commands/show_config.py +6 -1
  216. django_cfg/management/commands/show_urls.py +8 -3
  217. django_cfg/management/commands/superuser.py +5 -4
  218. django_cfg/management/commands/task_clear.py +8 -3
  219. django_cfg/management/commands/task_status.py +8 -3
  220. django_cfg/management/commands/test_email.py +6 -1
  221. django_cfg/management/commands/test_telegram.py +6 -1
  222. django_cfg/management/commands/test_twilio.py +6 -1
  223. django_cfg/management/commands/tree.py +7 -4
  224. django_cfg/models/__init__.py +88 -3
  225. django_cfg/models/api/__init__.py +27 -0
  226. django_cfg/models/{api.py → api/config.py} +1 -1
  227. django_cfg/models/api/drf/__init__.py +21 -0
  228. django_cfg/models/api/drf/config.py +101 -0
  229. django_cfg/models/api/drf/redoc.py +31 -0
  230. django_cfg/models/api/drf/spectacular.py +129 -0
  231. django_cfg/models/api/drf/swagger.py +59 -0
  232. django_cfg/models/{api_keys.py → api/keys.py} +16 -6
  233. django_cfg/models/{limits.py → api/limits.py} +0 -1
  234. django_cfg/models/base/__init__.py +14 -0
  235. django_cfg/models/django/__init__.py +16 -0
  236. django_cfg/models/{constance.py → django/constance.py} +1 -1
  237. django_cfg/models/{environment.py → django/environment.py} +1 -1
  238. django_cfg/models/infrastructure/__init__.py +17 -0
  239. django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
  240. django_cfg/models/infrastructure/database/__init__.py +22 -0
  241. django_cfg/models/infrastructure/database/config.py +265 -0
  242. django_cfg/models/infrastructure/database/converters.py +91 -0
  243. django_cfg/models/infrastructure/database/parsers.py +96 -0
  244. django_cfg/models/infrastructure/database/routing.py +85 -0
  245. django_cfg/models/infrastructure/database/validators.py +170 -0
  246. django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
  247. django_cfg/models/{security.py → infrastructure/security.py} +2 -2
  248. django_cfg/models/ngrok/__init__.py +11 -0
  249. django_cfg/models/ngrok/auth.py +37 -0
  250. django_cfg/models/ngrok/config.py +77 -0
  251. django_cfg/models/ngrok/tunnel.py +35 -0
  252. django_cfg/models/payments/__init__.py +20 -0
  253. django_cfg/models/payments/api_keys.py +57 -0
  254. django_cfg/models/{payments.py → payments/config.py} +56 -154
  255. django_cfg/models/payments/providers/__init__.py +15 -0
  256. django_cfg/models/payments/providers/base.py +25 -0
  257. django_cfg/models/payments/providers/nowpayments.py +48 -0
  258. django_cfg/models/services/__init__.py +18 -0
  259. django_cfg/models/services/base.py +65 -0
  260. django_cfg/models/{email.py → services/email.py} +1 -1
  261. django_cfg/models/services/telegram.py +172 -0
  262. django_cfg/models/tasks/__init__.py +51 -0
  263. django_cfg/models/tasks/backends.py +250 -0
  264. django_cfg/models/tasks/config.py +314 -0
  265. django_cfg/models/tasks/utils.py +174 -0
  266. django_cfg/modules/base.py +18 -3
  267. django_cfg/modules/django_admin/decorators/actions.py +1 -1
  268. django_cfg/modules/django_admin/decorators/display.py +1 -1
  269. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
  270. django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
  271. django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
  272. django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
  273. django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
  274. django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
  275. django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
  276. django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
  277. django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
  278. django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
  279. django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
  280. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
  281. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
  282. django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
  283. django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
  284. django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
  285. django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
  286. django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
  287. django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
  288. django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
  289. django_cfg/modules/django_drf_theme/README.md +207 -0
  290. django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
  291. django_cfg/modules/django_drf_theme/__init__.py +23 -0
  292. django_cfg/modules/django_drf_theme/apps.py +15 -0
  293. django_cfg/modules/django_drf_theme/renderers.py +58 -0
  294. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
  295. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
  296. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
  297. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
  298. django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
  299. django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
  300. django_cfg/modules/django_email/__init__.py +14 -0
  301. django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
  302. django_cfg/modules/django_email/utils.py +40 -0
  303. django_cfg/modules/django_health/__init__.py +9 -0
  304. django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
  305. django_cfg/modules/django_llm/llm/client.py +155 -550
  306. django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
  307. django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
  308. django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
  309. django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
  310. django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
  311. django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
  312. django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
  313. django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
  314. django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
  315. django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
  316. django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
  317. django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
  318. django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
  319. django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
  320. django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
  321. django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
  322. django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
  323. django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
  324. django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
  325. django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
  326. django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
  327. django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
  328. django_cfg/modules/django_llm/translator/translator.py +150 -603
  329. django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
  330. django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
  331. django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
  332. django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
  333. django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
  334. django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
  335. django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
  336. django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
  337. django_cfg/modules/django_logging/__init__.py +14 -0
  338. django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
  339. django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
  340. django_cfg/modules/django_ngrok/__init__.py +39 -0
  341. django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
  342. django_cfg/modules/django_rpc_old/POETRY.md +344 -0
  343. django_cfg/modules/django_rpc_old/README.md +397 -0
  344. django_cfg/modules/django_rpc_old/TESTING.md +358 -0
  345. django_cfg/modules/django_rpc_old/__init__.py +39 -0
  346. django_cfg/modules/django_rpc_old/client.py +531 -0
  347. django_cfg/modules/django_rpc_old/config.py +279 -0
  348. django_cfg/modules/django_rpc_old/exceptions.py +172 -0
  349. django_cfg/modules/django_tailwind/README.md +478 -0
  350. django_cfg/modules/django_tailwind/__init__.py +7 -0
  351. django_cfg/modules/django_tailwind/apps.py +10 -0
  352. django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
  353. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
  354. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
  355. django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
  356. django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
  357. django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
  358. django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
  359. django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
  360. django_cfg/modules/django_tasks/__init__.py +29 -0
  361. django_cfg/modules/django_tasks/factory.py +127 -0
  362. django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
  363. django_cfg/modules/django_tasks/settings.py +107 -0
  364. django_cfg/modules/django_telegram/__init__.py +29 -0
  365. django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
  366. django_cfg/modules/django_telegram/utils.py +62 -0
  367. django_cfg/modules/django_twilio/__init__.py +54 -107
  368. django_cfg/modules/django_twilio/_imports.py +30 -0
  369. django_cfg/modules/django_twilio/base.py +192 -0
  370. django_cfg/modules/django_twilio/email_otp.py +227 -0
  371. django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
  372. django_cfg/modules/django_twilio/simple_service.py +1 -2
  373. django_cfg/modules/django_twilio/sms.py +94 -0
  374. django_cfg/modules/django_twilio/twilio_service.py +2 -3
  375. django_cfg/modules/django_twilio/unified.py +310 -0
  376. django_cfg/modules/django_twilio/utils.py +190 -0
  377. django_cfg/modules/django_twilio/whatsapp.py +137 -0
  378. django_cfg/modules/django_unfold/callbacks/base.py +198 -7
  379. django_cfg/modules/django_unfold/callbacks/main.py +102 -10
  380. django_cfg/modules/django_unfold/dashboard.py +65 -43
  381. django_cfg/modules/django_unfold/models/config.py +13 -12
  382. django_cfg/modules/django_unfold/models/navigation.py +8 -3
  383. django_cfg/modules/django_unfold/models/tabs.py +2 -2
  384. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
  385. django_cfg/registry/core.py +24 -26
  386. django_cfg/registry/modules.py +5 -2
  387. django_cfg/registry/services.py +20 -3
  388. django_cfg/registry/third_party.py +8 -8
  389. django_cfg/static/admin/css/dashboard.css +260 -0
  390. django_cfg/static/admin/js/commands.js +171 -0
  391. django_cfg/static/admin/js/dashboard.js +126 -0
  392. django_cfg/templates/admin/components/management_commands.js +375 -0
  393. django_cfg/templates/admin/components/progress_bar.html +18 -23
  394. django_cfg/templates/admin/index.html +48 -20
  395. django_cfg/templates/admin/index_new.html +106 -0
  396. django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
  397. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
  398. django_cfg/templates/admin/sections/commands_section.html +626 -0
  399. django_cfg/templates/admin/sections/overview_section.html +112 -0
  400. django_cfg/templates/admin/sections/stats_section.html +35 -0
  401. django_cfg/templates/admin/sections/system_section.html +99 -0
  402. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
  403. django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
  404. django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
  405. django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
  406. django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
  407. django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
  408. django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
  409. django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
  410. django_cfg/templatetags/django_cfg.py +7 -1
  411. django_cfg/utils/smart_defaults.py +4 -4
  412. django_cfg-1.4.0.dist-info/METADATA +920 -0
  413. {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/RECORD +424 -195
  414. django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
  415. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
  416. django_cfg/core/generation.py +0 -621
  417. django_cfg/management/commands/validate_config.py +0 -189
  418. django_cfg/models/database.py +0 -480
  419. django_cfg/models/drf.py +0 -272
  420. django_cfg/models/ngrok.py +0 -122
  421. django_cfg/models/services.py +0 -440
  422. django_cfg/models/tasks.py +0 -550
  423. django_cfg/modules/django_twilio/service.py +0 -942
  424. django_cfg/template_archive/django_sample.zip +0 -0
  425. django_cfg/templates/rest_framework/api.html +0 -12
  426. django_cfg/utils/toolkit.py +0 -703
  427. django_cfg-1.3.13.dist-info/METADATA +0 -1029
  428. /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
  429. /django_cfg/core/{environment.py → environment/detector.py} +0 -0
  430. /django_cfg/models/{cors.py → api/cors.py} +0 -0
  431. /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
  432. /django_cfg/models/{base.py → base/config.py} +0 -0
  433. /django_cfg/models/{cfg.py → base/module.py} +0 -0
  434. /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
  435. /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
  436. {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
  437. {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
  438. {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 time
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
- # Store API keys and preferred provider
91
- self.apikey_openrouter = apikey_openrouter
92
- self.apikey_openai = apikey_openai
93
- self.preferred_provider = preferred_provider
94
-
95
- # Determine primary provider based on preference and available keys
96
- self.primary_provider = self._determine_primary_provider()
97
- self.primary_api_key = self._get_primary_api_key()
98
-
99
- self.cache = LLMCache(cache_dir=cache_dir, ttl=cache_ttl, max_size=max_cache_size)
100
- self.django_config = config
101
-
102
- # Initialize models cache for OpenRouter if available
103
- if self.apikey_openrouter:
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=self.apikey_openrouter,
97
+ api_key=apikey_openrouter,
106
98
  cache_dir=cache_dir,
107
99
  cache_ttl=models_cache_ttl
108
100
  )
109
- else:
110
- self.models_cache = None
111
-
112
- # Initialize tokenizer and extractor
101
+
102
+ # Initialize tokenizer and utilities
113
103
  self.tokenizer = Tokenizer()
114
- self.extractor = JSONExtractor()
115
-
116
- # Initialize clients for available providers
117
- self.clients = {}
118
-
119
- if self.apikey_openrouter:
120
- self.clients["openrouter"] = OpenAI(
121
- base_url="https://openrouter.ai/api/v1",
122
- api_key=self.apikey_openrouter,
123
- default_headers=self._get_openrouter_headers()
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
- if not self.client:
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
- # Check cache
280
- cached_response = self.cache.get_response(request_hash)
281
- if cached_response:
282
- logger.debug("Cache hit for chat completion")
283
- self.stats['cache_hits'] += 1
284
- # Convert cached dict back to Pydantic model
285
- return ChatCompletionResponse(**cached_response)
286
-
287
- self.stats['cache_misses'] += 1
288
- self.stats['total_requests'] += 1
289
-
290
- # Estimate tokens before API call
291
- estimated_input_tokens = self.count_messages_tokens(messages, model)
292
- logger.debug(f"Estimated input tokens: {estimated_input_tokens}")
293
-
294
- # Make API call
295
- start_time = time.time()
296
- try:
297
- # Prepare parameters
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
- # Try to use models cache first
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.stats.copy()
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": list(self.clients.keys()),
413
- "default_models": self.default_models,
414
- "cache_info": self.cache.get_cache_info(),
415
- "has_openrouter": self.apikey_openrouter is not None,
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.cache.clear_cache()
422
-
423
- # Models cache methods
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
- if not self.models_cache:
435
- logger.warning("Models cache not available for this provider")
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
- if not self.models_cache:
443
- return None
444
-
445
- return self.models_cache.get_model(model_id)
446
-
447
- def get_models_by_price(self,
448
- min_price: float = 0.0,
449
- max_price: float = float('inf')) -> List[ModelInfo]:
450
- """Get models within a price range"""
451
- if not self.models_cache:
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
- if not self.models_cache:
459
- return []
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
- if not self.models_cache:
466
- return []
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
- if not self.models_cache:
473
- return []
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
- if not self.models_cache:
480
- return []
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
- if not self.models_cache:
487
- return {"error": "Models cache not available for this provider"}
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
- if not self.models_cache:
494
- return {"error": "Models cache not available for this provider"}
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
- if self.models_cache:
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()