fastapi-fullstack 0.1.2__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.
- fastapi_fullstack-0.1.2.dist-info/METADATA +545 -0
- fastapi_fullstack-0.1.2.dist-info/RECORD +221 -0
- fastapi_fullstack-0.1.2.dist-info/WHEEL +4 -0
- fastapi_fullstack-0.1.2.dist-info/entry_points.txt +2 -0
- fastapi_fullstack-0.1.2.dist-info/licenses/LICENSE +21 -0
- fastapi_gen/__init__.py +3 -0
- fastapi_gen/cli.py +256 -0
- fastapi_gen/config.py +255 -0
- fastapi_gen/generator.py +181 -0
- fastapi_gen/prompts.py +648 -0
- fastapi_gen/template/cookiecutter.json +76 -0
- fastapi_gen/template/hooks/post_gen_project.py +111 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.example +136 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +357 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +298 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +723 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +115 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +202 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +528 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +448 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +490 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +247 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +326 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +760 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +195 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +797 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +158 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +121 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +382 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +693 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +66 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/layout.tsx +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/login/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/register/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/chat/page.tsx +20 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/dashboard/page.tsx +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/layout.tsx +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/profile/page.tsx +156 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/auth/callback/page.tsx +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/page.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +135 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +261 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +8 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +83 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +6 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +175 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +33 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +6 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +81 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_docker %}
|
|
2
|
+
# Git
|
|
3
|
+
.git
|
|
4
|
+
.gitignore
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.so
|
|
11
|
+
.Python
|
|
12
|
+
.venv
|
|
13
|
+
venv/
|
|
14
|
+
ENV/
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.idea
|
|
18
|
+
.vscode
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
|
|
22
|
+
# Testing
|
|
23
|
+
.pytest_cache
|
|
24
|
+
.coverage
|
|
25
|
+
htmlcov/
|
|
26
|
+
.tox
|
|
27
|
+
.nox
|
|
28
|
+
|
|
29
|
+
# Documentation
|
|
30
|
+
docs/_build/
|
|
31
|
+
*.md
|
|
32
|
+
!README.md
|
|
33
|
+
|
|
34
|
+
# Build artifacts
|
|
35
|
+
dist/
|
|
36
|
+
build/
|
|
37
|
+
*.egg-info/
|
|
38
|
+
|
|
39
|
+
# Development files
|
|
40
|
+
.env
|
|
41
|
+
.env.local
|
|
42
|
+
*.db
|
|
43
|
+
*.sqlite
|
|
44
|
+
|
|
45
|
+
# Docker
|
|
46
|
+
Dockerfile*
|
|
47
|
+
docker-compose*.yml
|
|
48
|
+
.docker
|
|
49
|
+
|
|
50
|
+
# CI/CD
|
|
51
|
+
.github/
|
|
52
|
+
.gitlab-ci.yml
|
|
53
|
+
|
|
54
|
+
# Misc
|
|
55
|
+
.DS_Store
|
|
56
|
+
Thumbs.db
|
|
57
|
+
*.log
|
|
58
|
+
{%- else %}
|
|
59
|
+
# Docker is disabled
|
|
60
|
+
{%- endif %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_precommit %}
|
|
2
|
+
repos:
|
|
3
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
4
|
+
rev: v5.0.0
|
|
5
|
+
hooks:
|
|
6
|
+
- id: trailing-whitespace
|
|
7
|
+
- id: end-of-file-fixer
|
|
8
|
+
- id: check-yaml
|
|
9
|
+
- id: check-toml
|
|
10
|
+
- id: check-added-large-files
|
|
11
|
+
args: ['--maxkb=1000']
|
|
12
|
+
- id: check-merge-conflict
|
|
13
|
+
- id: detect-private-key
|
|
14
|
+
|
|
15
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
16
|
+
rev: v0.8.0
|
|
17
|
+
hooks:
|
|
18
|
+
- id: ruff
|
|
19
|
+
args: [--fix]
|
|
20
|
+
- id: ruff-format
|
|
21
|
+
|
|
22
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
23
|
+
rev: v1.13.0
|
|
24
|
+
hooks:
|
|
25
|
+
- id: mypy
|
|
26
|
+
additional_dependencies:
|
|
27
|
+
- pydantic>=2.0.0
|
|
28
|
+
- pydantic-settings>=2.0.0
|
|
29
|
+
args: [--ignore-missing-imports]
|
|
30
|
+
{%- else %}
|
|
31
|
+
# Pre-commit is disabled for this project
|
|
32
|
+
{%- endif %}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_docker %}
|
|
2
|
+
# Build stage
|
|
3
|
+
FROM python:{{ cookiecutter.python_version }}-slim AS builder
|
|
4
|
+
|
|
5
|
+
ENV PYTHONUNBUFFERED=1
|
|
6
|
+
ENV PYTHONDONTWRITEBYTECODE=1
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
# Install uv
|
|
10
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
|
11
|
+
ENV UV_COMPILE_BYTECODE=1
|
|
12
|
+
ENV UV_LINK_MODE=copy
|
|
13
|
+
|
|
14
|
+
# Install dependencies
|
|
15
|
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
16
|
+
--mount=type=bind,source=uv.lock,target=uv.lock \
|
|
17
|
+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
|
18
|
+
uv sync --frozen --no-install-project --no-dev
|
|
19
|
+
|
|
20
|
+
# Copy application
|
|
21
|
+
COPY . /app
|
|
22
|
+
|
|
23
|
+
# Install project
|
|
24
|
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
25
|
+
uv sync --frozen --no-dev
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Runtime stage
|
|
29
|
+
FROM python:{{ cookiecutter.python_version }}-slim
|
|
30
|
+
|
|
31
|
+
ENV PYTHONUNBUFFERED=1
|
|
32
|
+
ENV PYTHONDONTWRITEBYTECODE=1
|
|
33
|
+
WORKDIR /app
|
|
34
|
+
|
|
35
|
+
# Copy virtual environment from builder
|
|
36
|
+
COPY --from=builder /app/.venv /app/.venv
|
|
37
|
+
COPY --from=builder /app /app
|
|
38
|
+
|
|
39
|
+
# Add venv to path
|
|
40
|
+
ENV PATH="/app/.venv/bin:$PATH"
|
|
41
|
+
|
|
42
|
+
# Create non-root user
|
|
43
|
+
RUN adduser --disabled-password --gecos "" appuser && \
|
|
44
|
+
chown -R appuser:appuser /app
|
|
45
|
+
USER appuser
|
|
46
|
+
|
|
47
|
+
EXPOSE {{ cookiecutter.backend_port }}
|
|
48
|
+
|
|
49
|
+
# Health check
|
|
50
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
51
|
+
CMD python -c "import httpx; httpx.get('http://localhost:{{ cookiecutter.backend_port }}/api/v1/health')" || exit 1
|
|
52
|
+
|
|
53
|
+
CMD ["python", "-m", "cli.commands", "server", "run", "--host", "0.0.0.0", "--port", "{{ cookiecutter.backend_port }}"]
|
|
54
|
+
{%- else %}
|
|
55
|
+
# Docker is disabled for this project
|
|
56
|
+
{%- endif %}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
|
|
2
|
+
"""Alembic migration environment."""
|
|
3
|
+
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
|
|
4
|
+
|
|
5
|
+
from logging.config import fileConfig
|
|
6
|
+
|
|
7
|
+
from alembic import context
|
|
8
|
+
from sqlalchemy import engine_from_config, pool
|
|
9
|
+
|
|
10
|
+
from app.core.config import settings
|
|
11
|
+
from app.db.base import Base
|
|
12
|
+
|
|
13
|
+
# Import all models here to ensure they are registered with Base.metadata
|
|
14
|
+
{%- if cookiecutter.use_jwt %}
|
|
15
|
+
from app.db.models.user import User # noqa: F401
|
|
16
|
+
{%- endif %}
|
|
17
|
+
|
|
18
|
+
config = context.config
|
|
19
|
+
|
|
20
|
+
if config.config_file_name is not None:
|
|
21
|
+
fileConfig(config.config_file_name)
|
|
22
|
+
|
|
23
|
+
target_metadata = Base.metadata
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_url() -> str:
|
|
27
|
+
"""Get database URL from settings."""
|
|
28
|
+
{%- if cookiecutter.use_postgresql %}
|
|
29
|
+
return settings.DATABASE_URL_SYNC
|
|
30
|
+
{%- else %}
|
|
31
|
+
return settings.DATABASE_URL
|
|
32
|
+
{%- endif %}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_migrations_offline() -> None:
|
|
36
|
+
"""Run migrations in 'offline' mode."""
|
|
37
|
+
url = get_url()
|
|
38
|
+
context.configure(
|
|
39
|
+
url=url,
|
|
40
|
+
target_metadata=target_metadata,
|
|
41
|
+
literal_binds=True,
|
|
42
|
+
dialect_opts={"paramstyle": "named"},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
with context.begin_transaction():
|
|
46
|
+
context.run_migrations()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_migrations_online() -> None:
|
|
50
|
+
"""Run migrations in 'online' mode."""
|
|
51
|
+
configuration = config.get_section(config.config_ini_section)
|
|
52
|
+
configuration["sqlalchemy.url"] = get_url()
|
|
53
|
+
|
|
54
|
+
connectable = engine_from_config(
|
|
55
|
+
configuration,
|
|
56
|
+
prefix="sqlalchemy.",
|
|
57
|
+
poolclass=pool.NullPool,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
with connectable.connect() as connection:
|
|
61
|
+
context.configure(
|
|
62
|
+
connection=connection,
|
|
63
|
+
target_metadata=target_metadata,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with context.begin_transaction():
|
|
67
|
+
context.run_migrations()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if context.is_offline_mode():
|
|
71
|
+
run_migrations_offline()
|
|
72
|
+
else:
|
|
73
|
+
run_migrations_online()
|
|
74
|
+
{%- else %}
|
|
75
|
+
# Alembic - not configured (no SQL database)
|
|
76
|
+
{%- endif %}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
|
|
2
|
+
"""${message}
|
|
3
|
+
|
|
4
|
+
Revision ID: ${up_revision}
|
|
5
|
+
Revises: ${down_revision | comma,n}
|
|
6
|
+
Create Date: ${create_date}
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
from typing import Sequence, Union
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
import sqlalchemy as sa
|
|
13
|
+
${imports if imports else ""}
|
|
14
|
+
|
|
15
|
+
# revision identifiers, used by Alembic.
|
|
16
|
+
revision: str = ${repr(up_revision)}
|
|
17
|
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
|
18
|
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
19
|
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def upgrade() -> None:
|
|
23
|
+
${upgrades if upgrades else "pass"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def downgrade() -> None:
|
|
27
|
+
${downgrades if downgrades else "pass"}
|
|
28
|
+
{%- else %}
|
|
29
|
+
# Alembic - not configured
|
|
30
|
+
{%- endif %}
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
|
|
2
|
+
# Alembic Configuration
|
|
3
|
+
|
|
4
|
+
[alembic]
|
|
5
|
+
script_location = alembic
|
|
6
|
+
prepend_sys_path = .
|
|
7
|
+
version_path_separator = os
|
|
8
|
+
# Human-readable migration file names: 2024-01-15_add_users_table.py
|
|
9
|
+
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(slug)s
|
|
10
|
+
|
|
11
|
+
[post_write_hooks]
|
|
12
|
+
|
|
13
|
+
[loggers]
|
|
14
|
+
keys = root,sqlalchemy,alembic
|
|
15
|
+
|
|
16
|
+
[handlers]
|
|
17
|
+
keys = console
|
|
18
|
+
|
|
19
|
+
[formatters]
|
|
20
|
+
keys = generic
|
|
21
|
+
|
|
22
|
+
[logger_root]
|
|
23
|
+
level = WARN
|
|
24
|
+
handlers = console
|
|
25
|
+
qualname =
|
|
26
|
+
|
|
27
|
+
[logger_sqlalchemy]
|
|
28
|
+
level = WARN
|
|
29
|
+
handlers =
|
|
30
|
+
qualname = sqlalchemy.engine
|
|
31
|
+
|
|
32
|
+
[logger_alembic]
|
|
33
|
+
level = INFO
|
|
34
|
+
handlers =
|
|
35
|
+
qualname = alembic
|
|
36
|
+
|
|
37
|
+
[handler_console]
|
|
38
|
+
class = StreamHandler
|
|
39
|
+
args = (sys.stderr,)
|
|
40
|
+
level = NOTSET
|
|
41
|
+
formatter = generic
|
|
42
|
+
|
|
43
|
+
[formatter_generic]
|
|
44
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
45
|
+
datefmt = %H:%M:%S
|
|
46
|
+
{%- else %}
|
|
47
|
+
# Alembic - not configured (no SQL database)
|
|
48
|
+
{%- endif %}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_admin_panel and cookiecutter.use_postgresql %}
|
|
2
|
+
"""SQLAdmin configuration."""
|
|
3
|
+
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import create_engine
|
|
7
|
+
from sqladmin import Admin, ModelView
|
|
8
|
+
{%- if cookiecutter.admin_require_auth %}
|
|
9
|
+
from sqladmin.authentication import AuthenticationBackend
|
|
10
|
+
from starlette.requests import Request
|
|
11
|
+
{%- endif %}
|
|
12
|
+
|
|
13
|
+
from app.core.config import settings
|
|
14
|
+
from app.db.models.user import User
|
|
15
|
+
{%- if cookiecutter.admin_require_auth %}
|
|
16
|
+
from app.core.security import verify_password
|
|
17
|
+
{%- endif %}
|
|
18
|
+
|
|
19
|
+
# SQLAdmin requires a synchronous engine
|
|
20
|
+
sync_engine = create_engine(settings.DATABASE_URL_SYNC, echo=settings.DEBUG)
|
|
21
|
+
|
|
22
|
+
{%- if cookiecutter.admin_require_auth %}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AdminAuth(AuthenticationBackend):
|
|
26
|
+
"""Admin panel authentication backend.
|
|
27
|
+
|
|
28
|
+
Requires superuser credentials to access the admin panel.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
async def login(self, request: Request) -> bool:
|
|
32
|
+
"""Validate admin login credentials."""
|
|
33
|
+
form = await request.form()
|
|
34
|
+
email = form.get("username")
|
|
35
|
+
password = form.get("password")
|
|
36
|
+
|
|
37
|
+
if not email or not password:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
# Get user from database
|
|
41
|
+
from sqlalchemy.orm import Session
|
|
42
|
+
with Session(sync_engine) as session:
|
|
43
|
+
user = session.query(User).filter(User.email == email).first()
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
user
|
|
47
|
+
and verify_password(str(password), user.hashed_password)
|
|
48
|
+
and user.is_superuser
|
|
49
|
+
):
|
|
50
|
+
# Store user info in session
|
|
51
|
+
request.session["admin_user_id"] = str(user.id)
|
|
52
|
+
request.session["admin_email"] = user.email
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
async def logout(self, request: Request) -> bool:
|
|
58
|
+
"""Clear admin session."""
|
|
59
|
+
request.session.clear()
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
async def authenticate(self, request: Request) -> bool:
|
|
63
|
+
"""Check if user is authenticated."""
|
|
64
|
+
admin_user_id = request.session.get("admin_user_id")
|
|
65
|
+
if not admin_user_id:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
# Verify user still exists and is superuser
|
|
69
|
+
from sqlalchemy.orm import Session
|
|
70
|
+
with Session(sync_engine) as session:
|
|
71
|
+
user = session.query(User).filter(User.id == admin_user_id).first()
|
|
72
|
+
if user and user.is_superuser and user.is_active:
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
# User no longer valid, clear session
|
|
76
|
+
request.session.clear()
|
|
77
|
+
return False
|
|
78
|
+
{%- endif %}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class UserAdmin(ModelView, model=User): # type: ignore[call-arg]
|
|
82
|
+
"""User admin view."""
|
|
83
|
+
|
|
84
|
+
column_list: ClassVar = [User.id, User.email, User.is_active, User.is_superuser, User.created_at]
|
|
85
|
+
column_searchable_list: ClassVar = [User.email, User.full_name]
|
|
86
|
+
column_sortable_list: ClassVar = [User.id, User.email, User.is_active, User.created_at]
|
|
87
|
+
form_excluded_columns: ClassVar = [User.hashed_password, User.created_at, User.updated_at]
|
|
88
|
+
can_create: ClassVar = True
|
|
89
|
+
can_edit: ClassVar = True
|
|
90
|
+
can_delete: ClassVar = True
|
|
91
|
+
can_view_details: ClassVar = True
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def setup_admin(app):
|
|
95
|
+
"""Setup SQLAdmin for the FastAPI app."""
|
|
96
|
+
{%- if cookiecutter.admin_require_auth %}
|
|
97
|
+
authentication_backend = AdminAuth(secret_key=settings.SECRET_KEY)
|
|
98
|
+
admin = Admin(
|
|
99
|
+
app,
|
|
100
|
+
sync_engine,
|
|
101
|
+
title="{{ cookiecutter.project_name }} Admin",
|
|
102
|
+
authentication_backend=authentication_backend,
|
|
103
|
+
)
|
|
104
|
+
{%- else %}
|
|
105
|
+
admin = Admin(
|
|
106
|
+
app,
|
|
107
|
+
sync_engine,
|
|
108
|
+
title="{{ cookiecutter.project_name }} Admin",
|
|
109
|
+
)
|
|
110
|
+
{%- endif %}
|
|
111
|
+
admin.add_view(UserAdmin)
|
|
112
|
+
return admin
|
|
113
|
+
{%- else %}
|
|
114
|
+
"""Admin panel - not configured."""
|
|
115
|
+
{%- endif %}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
2
|
+
"""AI Agents module using PydanticAI.
|
|
3
|
+
|
|
4
|
+
This module contains agents that handle AI-powered interactions.
|
|
5
|
+
Tools are defined in the tools/ subdirectory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from app.agents.assistant import AssistantAgent, Deps
|
|
9
|
+
|
|
10
|
+
__all__ = ["AssistantAgent", "Deps"]
|
|
11
|
+
{%- else %}
|
|
12
|
+
"""AI Agents - not configured."""
|
|
13
|
+
{%- endif %}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
2
|
+
"""Assistant agent with PydanticAI.
|
|
3
|
+
|
|
4
|
+
The main conversational agent that can be extended with custom tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic_ai import Agent, RunContext
|
|
12
|
+
from pydantic_ai.messages import (
|
|
13
|
+
ModelRequest,
|
|
14
|
+
ModelResponse,
|
|
15
|
+
SystemPromptPart,
|
|
16
|
+
TextPart,
|
|
17
|
+
UserPromptPart,
|
|
18
|
+
)
|
|
19
|
+
from pydantic_ai.models.openai import OpenAIChatModel
|
|
20
|
+
from pydantic_ai.providers.openai import OpenAIProvider
|
|
21
|
+
from pydantic_ai.settings import ModelSettings
|
|
22
|
+
|
|
23
|
+
from app.agents.tools import get_current_datetime
|
|
24
|
+
from app.core.config import settings
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Deps:
|
|
31
|
+
"""Dependencies for the assistant agent.
|
|
32
|
+
|
|
33
|
+
These are passed to tools via RunContext.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
user_id: str | None = None
|
|
37
|
+
user_name: str | None = None
|
|
38
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AssistantAgent:
|
|
42
|
+
"""Assistant agent wrapper for conversational AI.
|
|
43
|
+
|
|
44
|
+
Encapsulates agent creation and execution with tool support.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
model_name: str | None = None,
|
|
50
|
+
temperature: float | None = None,
|
|
51
|
+
system_prompt: str = "You are a helpful assistant.",
|
|
52
|
+
):
|
|
53
|
+
self.model_name = model_name or settings.AI_MODEL
|
|
54
|
+
self.temperature = temperature or settings.AI_TEMPERATURE
|
|
55
|
+
self.system_prompt = system_prompt
|
|
56
|
+
self._agent: Agent[Deps, str] | None = None
|
|
57
|
+
|
|
58
|
+
def _create_agent(self) -> Agent[Deps, str]:
|
|
59
|
+
"""Create and configure the PydanticAI agent."""
|
|
60
|
+
model = OpenAIChatModel(
|
|
61
|
+
self.model_name,
|
|
62
|
+
provider=OpenAIProvider(api_key=settings.OPENAI_API_KEY),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
agent = Agent[Deps, str](
|
|
66
|
+
model=model,
|
|
67
|
+
model_settings=ModelSettings(temperature=self.temperature),
|
|
68
|
+
system_prompt=self.system_prompt,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self._register_tools(agent)
|
|
72
|
+
|
|
73
|
+
return agent
|
|
74
|
+
|
|
75
|
+
def _register_tools(self, agent: Agent[Deps, str]) -> None:
|
|
76
|
+
"""Register all tools on the agent."""
|
|
77
|
+
|
|
78
|
+
@agent.tool
|
|
79
|
+
async def current_datetime(ctx: RunContext[Deps]) -> str:
|
|
80
|
+
"""Get the current date and time.
|
|
81
|
+
|
|
82
|
+
Use this tool when you need to know the current date or time.
|
|
83
|
+
"""
|
|
84
|
+
return get_current_datetime()
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def agent(self) -> Agent[Deps, str]:
|
|
88
|
+
"""Get or create the agent instance."""
|
|
89
|
+
if self._agent is None:
|
|
90
|
+
self._agent = self._create_agent()
|
|
91
|
+
return self._agent
|
|
92
|
+
|
|
93
|
+
async def run(
|
|
94
|
+
self,
|
|
95
|
+
user_input: str,
|
|
96
|
+
history: list[dict[str, str]] | None = None,
|
|
97
|
+
deps: Deps | None = None,
|
|
98
|
+
) -> tuple[str, list[Any], Deps]:
|
|
99
|
+
"""Run agent and return the output along with tool call events.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
user_input: User's message.
|
|
103
|
+
history: Conversation history as list of {"role": "...", "content": "..."}.
|
|
104
|
+
deps: Optional dependencies. If not provided, a new Deps will be created.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Tuple of (output_text, tool_events, deps).
|
|
108
|
+
"""
|
|
109
|
+
model_history: list[ModelRequest | ModelResponse] = []
|
|
110
|
+
|
|
111
|
+
for msg in history or []:
|
|
112
|
+
if msg["role"] == "user":
|
|
113
|
+
model_history.append(ModelRequest(parts=[UserPromptPart(content=msg["content"])]))
|
|
114
|
+
elif msg["role"] == "assistant":
|
|
115
|
+
model_history.append(ModelResponse(parts=[TextPart(content=msg["content"])]))
|
|
116
|
+
elif msg["role"] == "system":
|
|
117
|
+
model_history.append(ModelRequest(parts=[SystemPromptPart(content=msg["content"])]))
|
|
118
|
+
|
|
119
|
+
agent_deps = deps if deps is not None else Deps()
|
|
120
|
+
|
|
121
|
+
logger.info(f"Running agent with user input: {user_input[:100]}...")
|
|
122
|
+
result = await self.agent.run(user_input, deps=agent_deps, message_history=model_history)
|
|
123
|
+
|
|
124
|
+
tool_events: list[Any] = []
|
|
125
|
+
for message in result.all_messages():
|
|
126
|
+
if hasattr(message, "parts"):
|
|
127
|
+
for part in message.parts:
|
|
128
|
+
if hasattr(part, "tool_name"):
|
|
129
|
+
tool_events.append(part)
|
|
130
|
+
|
|
131
|
+
logger.info(f"Agent run complete. Output length: {len(result.output)} chars")
|
|
132
|
+
|
|
133
|
+
return result.output, tool_events, agent_deps
|
|
134
|
+
|
|
135
|
+
async def iter(
|
|
136
|
+
self,
|
|
137
|
+
user_input: str,
|
|
138
|
+
history: list[dict[str, str]] | None = None,
|
|
139
|
+
deps: Deps | None = None,
|
|
140
|
+
):
|
|
141
|
+
"""Stream agent execution with full event access.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
user_input: User's message.
|
|
145
|
+
history: Conversation history.
|
|
146
|
+
deps: Optional dependencies.
|
|
147
|
+
|
|
148
|
+
Yields:
|
|
149
|
+
Agent events for streaming responses.
|
|
150
|
+
"""
|
|
151
|
+
model_history: list[ModelRequest | ModelResponse] = []
|
|
152
|
+
|
|
153
|
+
for msg in history or []:
|
|
154
|
+
if msg["role"] == "user":
|
|
155
|
+
model_history.append(ModelRequest(parts=[UserPromptPart(content=msg["content"])]))
|
|
156
|
+
elif msg["role"] == "assistant":
|
|
157
|
+
model_history.append(ModelResponse(parts=[TextPart(content=msg["content"])]))
|
|
158
|
+
elif msg["role"] == "system":
|
|
159
|
+
model_history.append(ModelRequest(parts=[SystemPromptPart(content=msg["content"])]))
|
|
160
|
+
|
|
161
|
+
agent_deps = deps if deps is not None else Deps()
|
|
162
|
+
|
|
163
|
+
async with self.agent.iter(
|
|
164
|
+
user_input,
|
|
165
|
+
deps=agent_deps,
|
|
166
|
+
message_history=model_history,
|
|
167
|
+
) as run:
|
|
168
|
+
async for event in run:
|
|
169
|
+
yield event
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_agent() -> AssistantAgent:
|
|
173
|
+
"""Factory function to create an AssistantAgent.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Configured AssistantAgent instance.
|
|
177
|
+
"""
|
|
178
|
+
return AssistantAgent()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def run_agent(
|
|
182
|
+
user_input: str,
|
|
183
|
+
history: list[dict[str, str]],
|
|
184
|
+
deps: Deps | None = None,
|
|
185
|
+
) -> tuple[str, list[Any], Deps]:
|
|
186
|
+
"""Run agent and return the output along with tool call events.
|
|
187
|
+
|
|
188
|
+
This is a convenience function for backwards compatibility.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
user_input: User's message.
|
|
192
|
+
history: Conversation history.
|
|
193
|
+
deps: Optional dependencies.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Tuple of (output_text, tool_events, deps).
|
|
197
|
+
"""
|
|
198
|
+
agent = get_agent()
|
|
199
|
+
return await agent.run(user_input, history, deps)
|
|
200
|
+
{%- else %}
|
|
201
|
+
"""Assistant agent - not configured."""
|
|
202
|
+
{%- endif %}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
2
|
+
"""Agent tools module.
|
|
3
|
+
|
|
4
|
+
This module contains utility functions that can be used as agent tools.
|
|
5
|
+
Tools are registered in the agent definition using @agent.tool decorator.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from app.agents.tools.datetime_tool import get_current_datetime
|
|
9
|
+
|
|
10
|
+
__all__ = ["get_current_datetime"]
|
|
11
|
+
{%- else %}
|
|
12
|
+
"""Agent tools - not configured."""
|
|
13
|
+
{%- endif %}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
2
|
+
"""Date and time utilities for agents."""
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_current_datetime() -> str:
|
|
8
|
+
"""Get the current date and time.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
A string with the current date and time.
|
|
12
|
+
"""
|
|
13
|
+
now = datetime.now()
|
|
14
|
+
return f"Current date: {now.strftime('%Y-%m-%d')}, Current time: {now.strftime('%H:%M:%S')}"
|
|
15
|
+
{%- else %}
|
|
16
|
+
"""Datetime tools - not configured."""
|
|
17
|
+
{%- endif %}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API module."""
|