aegis-stack 0.2.0rc2__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 (392) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +51 -0
  3. aegis/cli/__init__.py +6 -0
  4. aegis/cli/callbacks.py +114 -0
  5. aegis/cli/interactive.py +611 -0
  6. aegis/cli/utils.py +70 -0
  7. aegis/cli/validators.py +34 -0
  8. aegis/commands/__init__.py +6 -0
  9. aegis/commands/add.py +353 -0
  10. aegis/commands/add_service.py +332 -0
  11. aegis/commands/components.py +35 -0
  12. aegis/commands/init.py +370 -0
  13. aegis/commands/remove.py +227 -0
  14. aegis/commands/services.py +52 -0
  15. aegis/commands/update.py +252 -0
  16. aegis/commands/version.py +12 -0
  17. aegis/config/__init__.py +1 -0
  18. aegis/config/shared_files.py +136 -0
  19. aegis/core/CLAUDE.md +377 -0
  20. aegis/core/__init__.py +6 -0
  21. aegis/core/component_files.py +228 -0
  22. aegis/core/component_utils.py +220 -0
  23. aegis/core/components.py +127 -0
  24. aegis/core/copier_manager.py +315 -0
  25. aegis/core/copier_updater.py +475 -0
  26. aegis/core/dependency_resolver.py +119 -0
  27. aegis/core/manual_updater.py +554 -0
  28. aegis/core/post_gen_tasks.py +547 -0
  29. aegis/core/service_resolver.py +261 -0
  30. aegis/core/services.py +157 -0
  31. aegis/core/template_generator.py +266 -0
  32. aegis/core/version_compatibility.py +259 -0
  33. aegis/templates/CLAUDE.md +591 -0
  34. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
  35. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
  100. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  101. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  102. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
  103. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  104. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
  105. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
  106. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
  107. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  108. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
  109. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
  110. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  111. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
  112. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
  113. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
  114. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
  115. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
  116. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
  117. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  118. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  119. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  120. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  121. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
  122. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
  123. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
  124. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  125. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  126. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
  127. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
  128. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
  129. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
  130. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
  131. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
  132. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
  133. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
  134. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
  135. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
  136. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
  137. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
  138. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
  139. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
  140. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
  141. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
  142. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
  143. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
  144. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
  145. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
  146. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  147. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  148. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  149. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  150. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
  151. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
  152. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
  153. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  154. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
  155. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
  156. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
  157. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
  158. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
  159. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
  160. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
  161. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
  162. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
  163. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
  164. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
  165. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
  166. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
  167. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
  168. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  169. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
  170. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
  171. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  172. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  173. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  174. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  175. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  176. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
  177. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  178. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
  179. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  180. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  181. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
  182. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
  183. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
  184. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
  185. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
  186. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
  187. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
  188. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
  189. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  190. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
  191. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
  192. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
  193. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
  194. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
  195. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
  196. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
  197. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
  198. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
  199. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
  200. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
  201. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
  202. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
  203. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
  204. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
  205. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
  206. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  207. aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
  208. aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
  209. aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
  210. aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
  211. aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
  212. aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
  213. aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
  214. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
  215. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
  216. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
  217. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
  218. aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
  219. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
  220. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
  221. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
  222. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
  223. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
  224. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
  225. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
  226. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
  227. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
  228. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
  229. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
  230. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
  231. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
  232. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
  233. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
  234. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
  235. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
  236. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
  237. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
  238. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
  239. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
  240. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
  241. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
  242. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
  243. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
  244. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
  245. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
  246. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
  247. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
  248. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
  249. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
  250. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
  251. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
  252. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
  253. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
  254. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  255. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  256. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  257. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  258. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  259. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  260. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  261. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  262. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  263. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  264. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  265. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  266. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
  267. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
  268. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
  269. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
  270. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
  271. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
  272. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
  273. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
  274. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
  275. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
  276. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
  277. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
  278. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
  279. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
  280. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
  281. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  282. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
  283. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
  284. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
  285. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
  286. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
  287. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
  288. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
  289. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
  290. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
  291. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
  292. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
  293. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
  294. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
  295. aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
  296. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
  297. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
  298. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
  299. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
  300. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
  301. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
  302. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
  303. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
  304. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
  305. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
  306. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
  307. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
  308. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
  309. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
  310. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
  311. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
  312. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
  313. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
  314. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
  315. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
  316. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
  317. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
  318. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
  319. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
  320. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
  321. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
  322. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
  323. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
  324. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
  325. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
  326. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
  327. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
  328. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
  329. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
  330. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
  331. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
  332. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
  333. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
  334. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
  335. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
  336. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
  337. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
  338. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
  339. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
  340. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
  341. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
  342. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
  343. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
  344. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
  345. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
  346. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
  347. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
  348. aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
  349. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
  350. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
  351. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
  352. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
  353. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
  354. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
  355. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
  356. aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
  357. aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
  358. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
  359. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
  360. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
  361. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
  362. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
  363. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
  364. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
  365. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
  366. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
  367. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
  368. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
  369. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
  370. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
  371. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
  372. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
  373. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
  374. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
  375. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
  376. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
  377. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
  378. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
  379. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
  380. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
  381. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
  382. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
  383. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
  384. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
  385. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
  386. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
  387. aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
  388. aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
  389. aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
  390. aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
  391. aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
  392. aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,96 @@
1
+ """
2
+ AI service health check functions.
3
+
4
+ Health monitoring for AI service functionality including provider configuration,
5
+ API connectivity, and service-specific metrics.
6
+ """
7
+
8
+ from app.core.log import logger
9
+ from app.services.system.models import ComponentStatus, ComponentStatusType
10
+
11
+
12
+ async def check_ai_service_health() -> ComponentStatus:
13
+ """
14
+ Check AI service health including provider configuration and dependencies.
15
+
16
+ Returns:
17
+ ComponentStatus indicating AI service health
18
+ """
19
+ try:
20
+ # Import the shared AI service instance from the API router
21
+ # This ensures health checks reflect actual API conversation state
22
+ from app.components.backend.api.ai.router import ai_service
23
+
24
+ # Get service status
25
+ service_status = ai_service.get_service_status()
26
+ validation_errors = ai_service.validate_service()
27
+
28
+ # Determine overall health
29
+ if not service_status["enabled"]:
30
+ status = ComponentStatusType.DEGRADED
31
+ message = "AI service is disabled"
32
+ elif validation_errors:
33
+ status = ComponentStatusType.UNHEALTHY
34
+ message = (
35
+ f"AI service configuration issues: {'; '.join(validation_errors[:2])}"
36
+ )
37
+ else:
38
+ status = ComponentStatusType.HEALTHY
39
+ message = f"AI service ready - {service_status['provider']} provider"
40
+
41
+ # Collect comprehensive metadata
42
+ metadata = {
43
+ "service_type": "ai",
44
+ "engine": "pydantic-ai",
45
+ "enabled": service_status["enabled"],
46
+ "provider": service_status["provider"],
47
+ "model": service_status["model"],
48
+ "agent_ready": service_status["agent_initialized"],
49
+ "total_conversations": service_status["total_conversations"],
50
+ "configuration_valid": service_status["configuration_valid"],
51
+ "validation_errors": validation_errors,
52
+ "validation_errors_count": len(validation_errors),
53
+ }
54
+
55
+ # Add dependency status
56
+ metadata["dependencies"] = {
57
+ "backend": "required",
58
+ "pydantic_ai": "required",
59
+ }
60
+
61
+ # Add provider-specific info
62
+ if service_status["enabled"]:
63
+ from .models import get_free_providers, get_provider_capabilities
64
+
65
+ provider_caps = get_provider_capabilities(ai_service.config.provider)
66
+ free_providers = get_free_providers()
67
+
68
+ metadata.update(
69
+ {
70
+ "provider_supports_streaming": provider_caps.supports_streaming,
71
+ "provider_free_tier": ai_service.config.provider in free_providers,
72
+ }
73
+ )
74
+
75
+ return ComponentStatus(
76
+ name="ai",
77
+ status=status,
78
+ message=message,
79
+ response_time_ms=None, # Will be set by caller
80
+ metadata=metadata,
81
+ )
82
+
83
+ except Exception as e:
84
+ logger.error(f"AI service health check failed: {e}")
85
+ return ComponentStatus(
86
+ name="ai",
87
+ status=ComponentStatusType.UNHEALTHY,
88
+ message=f"AI service health check failed: {str(e)}",
89
+ response_time_ms=None,
90
+ metadata={
91
+ "service_type": "ai",
92
+ "engine": "pydantic-ai",
93
+ "error": str(e),
94
+ "error_type": "health_check_failure",
95
+ },
96
+ )
@@ -0,0 +1,229 @@
1
+ """
2
+ AI service data models and enums.
3
+
4
+ This module defines the core data structures for AI service configuration,
5
+ conversation management, and provider integration.
6
+ """
7
+
8
+ from datetime import UTC, datetime
9
+ from enum import Enum
10
+ from typing import Any
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+
15
+ class AIProvider(str, Enum):
16
+ """Supported AI providers for PydanticAI integration."""
17
+
18
+ OPENAI = "openai"
19
+ ANTHROPIC = "anthropic"
20
+ GOOGLE = "google"
21
+ GROQ = "groq"
22
+ MISTRAL = "mistral"
23
+ COHERE = "cohere"
24
+ PUBLIC = "public" # Free public endpoints (no API key required)
25
+
26
+
27
+ class MessageRole(str, Enum):
28
+ """Message roles in a conversation."""
29
+
30
+ USER = "user"
31
+ ASSISTANT = "assistant"
32
+ SYSTEM = "system"
33
+
34
+
35
+ class ProviderConfig(BaseModel):
36
+ """Configuration for a specific AI provider."""
37
+
38
+ name: AIProvider
39
+ api_key: str | None = None
40
+ base_url: str | None = None
41
+ max_tokens: int = 1000
42
+ temperature: float = 0.7
43
+ timeout_seconds: float = 30.0
44
+
45
+ class Config:
46
+ use_enum_values = True
47
+
48
+
49
+ class ConversationMessage(BaseModel):
50
+ """A single message in a conversation."""
51
+
52
+ id: str = Field(..., description="Unique message identifier")
53
+ role: MessageRole
54
+ content: str
55
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
56
+ metadata: dict[str, Any] = Field(default_factory=dict)
57
+
58
+
59
+ class Conversation(BaseModel):
60
+ """A conversation containing multiple messages."""
61
+
62
+ id: str = Field(..., description="Unique conversation identifier")
63
+ title: str | None = None
64
+ messages: list[ConversationMessage] = Field(default_factory=list)
65
+ created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
66
+ updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
67
+ provider: AIProvider
68
+ model: str
69
+ metadata: dict[str, Any] = Field(default_factory=dict)
70
+
71
+ class Config:
72
+ use_enum_values = True
73
+
74
+ def add_message(
75
+ self, role: MessageRole, content: str, message_id: str | None = None
76
+ ) -> ConversationMessage:
77
+ """Add a new message to the conversation."""
78
+ import uuid
79
+
80
+ message = ConversationMessage(
81
+ id=message_id or str(uuid.uuid4()), role=role, content=content
82
+ )
83
+ self.messages.append(message)
84
+ self.updated_at = datetime.now(UTC)
85
+
86
+ # Auto-generate title from first user message
87
+ if not self.title and role == MessageRole.USER and len(self.messages) == 1:
88
+ # Use first 50 characters as title
89
+ self.title = content[:50] + "..." if len(content) > 50 else content
90
+
91
+ return message
92
+
93
+ def get_message_count(self) -> int:
94
+ """Get total number of messages in conversation."""
95
+ return len(self.messages)
96
+
97
+ def get_last_message(self) -> ConversationMessage | None:
98
+ """Get the most recent message."""
99
+ return self.messages[-1] if self.messages else None
100
+
101
+
102
+ class StreamingMessage(BaseModel):
103
+ """A streaming message chunk with metadata."""
104
+
105
+ content: str = Field(..., description="Partial or complete message content")
106
+ is_final: bool = Field(False, description="Whether this is the final chunk")
107
+ is_delta: bool = Field(False, description="Whether content is delta or cumulative")
108
+ message_id: str | None = Field(None, description="Message ID once finalized")
109
+ conversation_id: str | None = Field(None, description="Associated conversation ID")
110
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
111
+ metadata: dict[str, Any] = Field(default_factory=dict)
112
+
113
+
114
+ class StreamingConversation(BaseModel):
115
+ """Extended conversation model for streaming state management."""
116
+
117
+ conversation: Conversation
118
+ current_message_id: str | None = None
119
+ accumulated_content: str = ""
120
+ stream_start_time: datetime = Field(default_factory=lambda: datetime.now(UTC))
121
+
122
+ def reset_stream(self) -> None:
123
+ """Reset streaming state for new message."""
124
+ self.current_message_id = None
125
+ self.accumulated_content = ""
126
+ self.stream_start_time = datetime.now(UTC)
127
+
128
+ def accumulate_content(self, content: str, is_delta: bool = False) -> str:
129
+ """Accumulate streaming content and return total content."""
130
+ if is_delta:
131
+ self.accumulated_content += content
132
+ else:
133
+ self.accumulated_content = content
134
+ return self.accumulated_content
135
+
136
+
137
+ class AIServiceStatus(BaseModel):
138
+ """Status information for the AI service."""
139
+
140
+ enabled: bool
141
+ provider: AIProvider
142
+ model: str
143
+ available_providers: list[AIProvider]
144
+ conversation_count: int = 0
145
+ last_activity: datetime | None = None
146
+
147
+ class Config:
148
+ use_enum_values = True
149
+
150
+
151
+ class ProviderCapabilities(BaseModel):
152
+ """Capabilities for an AI provider (binary features only)."""
153
+
154
+ provider: AIProvider
155
+ supports_streaming: bool = True
156
+ supports_function_calling: bool = False
157
+ supports_vision: bool = False
158
+ free_tier_available: bool = False
159
+
160
+ class Config:
161
+ use_enum_values = True
162
+
163
+
164
+ # Provider capability definitions (binary features only)
165
+ PROVIDER_CAPABILITIES = {
166
+ AIProvider.OPENAI: ProviderCapabilities(
167
+ provider=AIProvider.OPENAI,
168
+ supports_streaming=True,
169
+ supports_function_calling=True,
170
+ supports_vision=True,
171
+ free_tier_available=False,
172
+ ),
173
+ AIProvider.ANTHROPIC: ProviderCapabilities(
174
+ provider=AIProvider.ANTHROPIC,
175
+ supports_streaming=True,
176
+ supports_function_calling=True,
177
+ supports_vision=True,
178
+ free_tier_available=False,
179
+ ),
180
+ AIProvider.GOOGLE: ProviderCapabilities(
181
+ provider=AIProvider.GOOGLE,
182
+ supports_streaming=True,
183
+ supports_function_calling=True,
184
+ supports_vision=True,
185
+ free_tier_available=True,
186
+ ),
187
+ AIProvider.GROQ: ProviderCapabilities(
188
+ provider=AIProvider.GROQ,
189
+ supports_streaming=True,
190
+ supports_function_calling=False,
191
+ supports_vision=False,
192
+ free_tier_available=True, # Very generous free tier
193
+ ),
194
+ AIProvider.MISTRAL: ProviderCapabilities(
195
+ provider=AIProvider.MISTRAL,
196
+ supports_streaming=True,
197
+ supports_function_calling=True,
198
+ supports_vision=False,
199
+ free_tier_available=False,
200
+ ),
201
+ AIProvider.COHERE: ProviderCapabilities(
202
+ provider=AIProvider.COHERE,
203
+ supports_streaming=True,
204
+ supports_function_calling=False,
205
+ supports_vision=False,
206
+ free_tier_available=True,
207
+ ),
208
+ AIProvider.PUBLIC: ProviderCapabilities(
209
+ provider=AIProvider.PUBLIC,
210
+ supports_streaming=False,
211
+ supports_function_calling=False,
212
+ supports_vision=False,
213
+ free_tier_available=True, # No API key required
214
+ ),
215
+ }
216
+
217
+
218
+ def get_provider_capabilities(provider: AIProvider) -> ProviderCapabilities:
219
+ """Get capabilities for a specific provider."""
220
+ return PROVIDER_CAPABILITIES.get(provider, ProviderCapabilities(provider=provider))
221
+
222
+
223
+ def get_free_providers() -> list[AIProvider]:
224
+ """Get list of providers that offer free tiers."""
225
+ return [
226
+ provider
227
+ for provider, caps in PROVIDER_CAPABILITIES.items()
228
+ if caps.free_tier_available
229
+ ]
@@ -0,0 +1,370 @@
1
+ """
2
+ AI provider factory for creating PydanticAI agents.
3
+
4
+ Simple factory that maps providers to PydanticAI model classes and creates
5
+ Agent instances. Supports PydanticAI 1.0+ API with demo mode for immediate testing.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from typing import Any
11
+
12
+ import httpx
13
+ from openai import AsyncOpenAI
14
+ from pydantic_ai import Agent
15
+
16
+ # Import only what's needed for PUBLIC provider by default
17
+ # Other providers imported dynamically when needed
18
+ from pydantic_ai.models.openai import OpenAIChatModel
19
+ from pydantic_ai.providers.openai import OpenAIProvider
20
+ from pydantic_ai.settings import ModelSettings
21
+
22
+ # Removed TestModel - that's for unit tests, not user demos
23
+ from app.core.log import logger
24
+
25
+ from .config import AIServiceConfig
26
+ from .models import AIProvider
27
+
28
+
29
+ # Lazy loading of provider model classes to avoid import errors
30
+ def _get_model_class(provider: AIProvider):
31
+ """Get model class for provider with lazy import to avoid dependency issues."""
32
+ if provider == AIProvider.OPENAI:
33
+ return OpenAIChatModel
34
+ elif provider == AIProvider.ANTHROPIC:
35
+ from pydantic_ai.models.anthropic import AnthropicModel
36
+
37
+ return AnthropicModel
38
+ elif provider == AIProvider.GOOGLE:
39
+ from pydantic_ai.models.google import GoogleModel
40
+
41
+ return GoogleModel
42
+ elif provider == AIProvider.GROQ:
43
+ from pydantic_ai.models.groq import GroqModel
44
+
45
+ return GroqModel
46
+ elif provider == AIProvider.MISTRAL:
47
+ return OpenAIChatModel # Mistral uses OpenAI-compatible API
48
+ elif provider == AIProvider.COHERE:
49
+ return OpenAIChatModel # Cohere can use OpenAI-compatible interface
50
+ elif provider == AIProvider.PUBLIC:
51
+ return OpenAIChatModel # Public endpoints use OpenAI-compatible API
52
+ else:
53
+ raise ProviderError(f"Unsupported provider: {provider}")
54
+
55
+
56
+ class ProviderError(Exception):
57
+ """Exception raised when provider setup fails."""
58
+
59
+ pass
60
+
61
+
62
+ def get_agent(config: AIServiceConfig, settings: Any) -> Agent:
63
+ """
64
+ Create a PydanticAI Agent for the configured provider.
65
+
66
+ Falls back to demo mode if no API keys are configured for immediate testing.
67
+
68
+ Args:
69
+ config: AI service configuration
70
+ settings: Application settings for API keys
71
+
72
+ Returns:
73
+ Agent: Configured PydanticAI Agent
74
+
75
+ Raises:
76
+ ProviderError: If agent creation fails
77
+ """
78
+ try:
79
+ # Special handling for PUBLIC provider (no API key required)
80
+ if config.provider == AIProvider.PUBLIC:
81
+ return _create_public_agent(config)
82
+
83
+ # Check if we have API key for this provider
84
+ provider_config = config.get_provider_config(settings)
85
+
86
+ if not provider_config.api_key:
87
+ raise ProviderError(
88
+ f"No API key configured for {config.provider}. "
89
+ f"Set {_get_env_var_name(config.provider)} environment variable. "
90
+ f"For free usage without API keys, try 'public' provider or Groq: https://console.groq.com/keys"
91
+ )
92
+
93
+ # Set environment variable for PydanticAI 1.0+ (they expect env vars)
94
+ _set_provider_env_var(config.provider, provider_config.api_key)
95
+
96
+ # Get model class for provider (with lazy loading)
97
+ model_class = _get_model_class(config.provider)
98
+
99
+ # For PydanticAI 1.0+, NO api_key parameter - just model_name
100
+ model_kwargs = {"model_name": config.model}
101
+
102
+ # Add provider-specific configuration
103
+ if config.provider == AIProvider.MISTRAL:
104
+ model_kwargs["base_url"] = "https://api.mistral.ai/v1"
105
+ elif config.provider == AIProvider.COHERE:
106
+ model_kwargs["base_url"] = "https://api.cohere.ai/v1"
107
+
108
+ # Create model instance (PydanticAI 1.0+ style)
109
+ model = model_class(**model_kwargs)
110
+
111
+ # Create agent with system prompt and model settings
112
+ agent = Agent(
113
+ model=model,
114
+ model_settings=ModelSettings(
115
+ temperature=config.temperature,
116
+ max_tokens=config.max_tokens,
117
+ timeout=config.timeout_seconds,
118
+ ),
119
+ system_prompt=(
120
+ "You are a helpful AI assistant. Provide clear, accurate, "
121
+ "and concise responses. Be friendly and professional."
122
+ ),
123
+ )
124
+
125
+ # logger.info(f"Created {config.provider} agent with model {config.model}")
126
+ return agent
127
+
128
+ except Exception as e:
129
+ error_msg = f"Failed to create agent for {config.provider}: {e}"
130
+ logger.error(error_msg)
131
+ raise ProviderError(error_msg) from e
132
+
133
+
134
+ def _create_public_agent(config: AIServiceConfig) -> Agent:
135
+ """
136
+ Create agent for PUBLIC provider using free public endpoints.
137
+
138
+ Uses LLM7.io service which provides free access through an OpenAI-compatible API.
139
+ Note: The service accepts model names for compatibility but routes to their
140
+ available models (actual model used may differ from requested).
141
+
142
+ Args:
143
+ config: AI service configuration
144
+
145
+ Returns:
146
+ Agent: Configured PydanticAI Agent for public endpoint
147
+
148
+ Raises:
149
+ ProviderError: If agent creation fails
150
+ """
151
+ try:
152
+ # Create a custom HTTP client that fixes LLM7.io response format
153
+ class FixedLLM7Client(httpx.AsyncClient):
154
+ """Custom HTTP client that adds missing index field to LLM7.io responses."""
155
+
156
+ async def send(self, request, **kwargs):
157
+ # logger.info(
158
+ # f"🔧 FixedLLM7Client.send: {request.method} {request.url}"
159
+ # )
160
+ response = await super().send(request, **kwargs)
161
+
162
+ # If this is a chat completions request, fix the response
163
+ if (
164
+ "/chat/completions" in str(request.url)
165
+ and request.method.upper() == "POST"
166
+ ):
167
+ # logger.debug(
168
+ # "Detected chat completions request, attempting to "
169
+ # "fix response"
170
+ # )
171
+ try:
172
+ # Check if this is a streaming response first
173
+ content_type = response.headers.get("content-type", "")
174
+ if (
175
+ "text/plain" in content_type
176
+ or "text/event-stream" in content_type
177
+ or "application/x-ndjson" in content_type
178
+ ):
179
+ # logger.debug("Skipping fix for streaming response")
180
+ return response
181
+
182
+ # Only process non-streaming JSON responses
183
+ if not response.headers.get("content-type", "").startswith(
184
+ "application/json"
185
+ ):
186
+ # logger.debug("Skipping fix for non-JSON response")
187
+ return response
188
+
189
+ # Get response text, letting httpx handle encoding/decompression
190
+ response_text = response.text
191
+ data = json.loads(response_text)
192
+
193
+ # logger.debug(f"Original response data: {data}")
194
+
195
+ # Add missing index field to choices
196
+ if "choices" in data and isinstance(data["choices"], list):
197
+ for i, choice in enumerate(data["choices"]):
198
+ if "index" not in choice or choice["index"] is None:
199
+ choice["index"] = i # Always set index
200
+ # logger.debug(f"Added index {i} to choice")
201
+
202
+ # Monkey patch the response to return fixed content
203
+ fixed_content = json.dumps(data)
204
+ response._content = fixed_content.encode()
205
+ response._text = fixed_content
206
+
207
+ # logger.debug(
208
+ # f"Fixed LLM7.io response: added index fields "
209
+ # f"to {len(data.get('choices', []))} choices"
210
+ # )
211
+
212
+ except Exception as e:
213
+ logger.warning(f"Failed to fix LLM7.io response: {e}")
214
+ # If fixing fails, return original response
215
+
216
+ return response
217
+
218
+ # Create custom HTTP client
219
+ custom_http_client = FixedLLM7Client()
220
+ # logger.debug(f"Created custom HTTP client: {type(custom_http_client)}")
221
+
222
+ # Create the AsyncOpenAI client directly to ensure
223
+ # our custom HTTP client is used
224
+ openai_client = AsyncOpenAI(
225
+ api_key="unused", # LLM7.io doesn't need a real key
226
+ base_url="https://api.llm7.io/v1", # Public endpoint
227
+ http_client=custom_http_client,
228
+ )
229
+ # logger.debug("Created OpenAI client with custom HTTP client")
230
+
231
+ # Create provider using the custom openai_client
232
+ provider = OpenAIProvider(openai_client=openai_client)
233
+ # logger.debug(f"Created provider with custom OpenAI client: {provider}")
234
+
235
+ # Create OpenAI model using the provider
236
+ # Note: LLM7.io accepts various model names but routes to their available models
237
+ model_name = (
238
+ config.model if config.model and config.model != "auto" else "gpt-4o-mini"
239
+ )
240
+ model = OpenAIChatModel(model_name=model_name, provider=provider)
241
+
242
+ # Create agent with friendly system prompt and model settings
243
+ agent = Agent(
244
+ model=model,
245
+ model_settings=ModelSettings(
246
+ temperature=config.temperature,
247
+ max_tokens=config.max_tokens,
248
+ timeout=config.timeout_seconds,
249
+ ),
250
+ system_prompt=(
251
+ "You are a helpful AI assistant powered by free public endpoints. "
252
+ "Provide clear, accurate, and concise responses. "
253
+ "Be friendly and professional."
254
+ ),
255
+ )
256
+
257
+ # logger.info(f"Created PUBLIC agent with model {config.model} via LLM7.io")
258
+ return agent
259
+
260
+ except Exception as e:
261
+ # Fallback with helpful instructions if public endpoint fails
262
+ error_msg = (
263
+ f"Public endpoint failed ({e}). Here are reliable free alternatives:\n\n"
264
+ "🌟 RECOMMENDED - Groq (Fastest, Most Generous Free Tier):\n"
265
+ " 1. Visit: https://console.groq.com/keys\n"
266
+ " 2. Get API key: export GROQ_API_KEY=your_key_here\n"
267
+ " 3. Switch: {{cookiecutter.project_slug}} ai config "
268
+ "set-provider groq\n\n"
269
+ "🔥 Google AI Studio (Also Free):\n"
270
+ " 1. Visit: https://aistudio.google.com/app/apikey\n"
271
+ " 2. Get API key: export GOOGLE_API_KEY=your_key_here\n"
272
+ " 3. Switch: {{cookiecutter.project_slug}} ai config set-provider google"
273
+ )
274
+ logger.error(f"Failed to create PUBLIC agent: {e}")
275
+ raise ProviderError(error_msg) from e
276
+
277
+
278
+ def _get_env_var_name(provider: AIProvider) -> str:
279
+ """Get the environment variable name for a provider."""
280
+ env_var_map = {
281
+ AIProvider.OPENAI: "OPENAI_API_KEY",
282
+ AIProvider.ANTHROPIC: "ANTHROPIC_API_KEY",
283
+ AIProvider.GOOGLE: "GOOGLE_API_KEY",
284
+ AIProvider.GROQ: "GROQ_API_KEY",
285
+ AIProvider.MISTRAL: "MISTRAL_API_KEY",
286
+ AIProvider.COHERE: "COHERE_API_KEY",
287
+ AIProvider.PUBLIC: "PUBLIC_API_KEY", # Not actually needed
288
+ }
289
+
290
+ # Debug logging
291
+ # logger.debug(
292
+ # f"Looking up env var for provider: {provider} (type: {type(provider)})"
293
+ # )
294
+
295
+ result = env_var_map.get(provider)
296
+ if result:
297
+ return result
298
+ else:
299
+ # Safe fallback without calling .value in case of type issues
300
+ return f"{str(provider).upper()}_API_KEY"
301
+
302
+
303
+ def _set_provider_env_var(provider: AIProvider, api_key: str) -> None:
304
+ """Set environment variable for provider API key (PydanticAI 1.0+ expects this)."""
305
+ env_var_map = {
306
+ AIProvider.OPENAI: "OPENAI_API_KEY",
307
+ AIProvider.ANTHROPIC: "ANTHROPIC_API_KEY",
308
+ AIProvider.GOOGLE: "GOOGLE_API_KEY",
309
+ AIProvider.GROQ: "GROQ_API_KEY",
310
+ AIProvider.MISTRAL: "MISTRAL_API_KEY",
311
+ AIProvider.COHERE: "COHERE_API_KEY",
312
+ AIProvider.PUBLIC: "PUBLIC_API_KEY", # Not actually needed
313
+ }
314
+
315
+ env_var = env_var_map.get(provider)
316
+ if env_var and api_key:
317
+ os.environ[env_var] = api_key
318
+ # logger.debug(f"Set {env_var} environment variable")
319
+
320
+
321
+ def validate_provider_support(provider: AIProvider) -> bool:
322
+ """
323
+ Check if a provider is supported.
324
+
325
+ Args:
326
+ provider: The provider to check
327
+
328
+ Returns:
329
+ bool: True if provider is supported
330
+ """
331
+ try:
332
+ _get_model_class(provider)
333
+ return True
334
+ except ProviderError:
335
+ return False
336
+
337
+
338
+ def get_supported_providers() -> list[AIProvider]:
339
+ """
340
+ Get list of supported providers.
341
+
342
+ Returns:
343
+ list[AIProvider]: List of supported providers
344
+ """
345
+ # Return all providers that are defined in the enum
346
+ return [
347
+ AIProvider.OPENAI,
348
+ AIProvider.ANTHROPIC,
349
+ AIProvider.GOOGLE,
350
+ AIProvider.GROQ,
351
+ AIProvider.MISTRAL,
352
+ AIProvider.COHERE,
353
+ AIProvider.PUBLIC,
354
+ ]
355
+
356
+
357
+ def get_provider_model_class(provider: AIProvider):
358
+ """
359
+ Get the PydanticAI model class for a provider.
360
+
361
+ Args:
362
+ provider: The AI provider
363
+
364
+ Returns:
365
+ Model class for the provider
366
+
367
+ Raises:
368
+ ProviderError: If provider is not supported
369
+ """
370
+ return _get_model_class(provider)