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,87 @@
|
|
|
1
|
+
"""API v1 router aggregation."""
|
|
2
|
+
{%- if cookiecutter.use_jwt or cookiecutter.enable_oauth or cookiecutter.include_example_crud or cookiecutter.enable_conversation_persistence or cookiecutter.enable_webhooks or cookiecutter.enable_websockets or cookiecutter.enable_ai_agent %}
|
|
3
|
+
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
|
|
4
|
+
{%- endif %}
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter
|
|
7
|
+
|
|
8
|
+
from app.api.routes.v1 import health
|
|
9
|
+
{%- if cookiecutter.use_jwt %}
|
|
10
|
+
from app.api.routes.v1 import auth, users
|
|
11
|
+
{%- endif %}
|
|
12
|
+
{%- if cookiecutter.enable_oauth %}
|
|
13
|
+
from app.api.routes.v1 import oauth
|
|
14
|
+
{%- endif %}
|
|
15
|
+
{%- if cookiecutter.enable_session_management and cookiecutter.use_jwt %}
|
|
16
|
+
from app.api.routes.v1 import sessions
|
|
17
|
+
{%- endif %}
|
|
18
|
+
{%- if cookiecutter.include_example_crud and cookiecutter.use_database %}
|
|
19
|
+
from app.api.routes.v1 import items
|
|
20
|
+
{%- endif %}
|
|
21
|
+
{%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
|
|
22
|
+
from app.api.routes.v1 import conversations
|
|
23
|
+
{%- endif %}
|
|
24
|
+
{%- if cookiecutter.enable_webhooks and cookiecutter.use_database %}
|
|
25
|
+
from app.api.routes.v1 import webhooks
|
|
26
|
+
{%- endif %}
|
|
27
|
+
{%- if cookiecutter.enable_websockets %}
|
|
28
|
+
from app.api.routes.v1 import ws
|
|
29
|
+
{%- endif %}
|
|
30
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
31
|
+
from app.api.routes.v1 import agent
|
|
32
|
+
{%- endif %}
|
|
33
|
+
|
|
34
|
+
v1_router = APIRouter()
|
|
35
|
+
|
|
36
|
+
# Health check routes (no auth required)
|
|
37
|
+
v1_router.include_router(health.router, tags=["health"])
|
|
38
|
+
|
|
39
|
+
{%- if cookiecutter.use_jwt %}
|
|
40
|
+
|
|
41
|
+
# Authentication routes
|
|
42
|
+
v1_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
|
43
|
+
|
|
44
|
+
# User routes
|
|
45
|
+
v1_router.include_router(users.router, prefix="/users", tags=["users"])
|
|
46
|
+
{%- endif %}
|
|
47
|
+
|
|
48
|
+
{%- if cookiecutter.enable_oauth %}
|
|
49
|
+
|
|
50
|
+
# OAuth2 routes
|
|
51
|
+
v1_router.include_router(oauth.router, prefix="/oauth", tags=["oauth"])
|
|
52
|
+
{%- endif %}
|
|
53
|
+
|
|
54
|
+
{%- if cookiecutter.enable_session_management and cookiecutter.use_jwt %}
|
|
55
|
+
|
|
56
|
+
# Session management routes
|
|
57
|
+
v1_router.include_router(sessions.router, prefix="/sessions", tags=["sessions"])
|
|
58
|
+
{%- endif %}
|
|
59
|
+
|
|
60
|
+
{%- if cookiecutter.include_example_crud and cookiecutter.use_database %}
|
|
61
|
+
|
|
62
|
+
# Example CRUD routes (items)
|
|
63
|
+
v1_router.include_router(items.router, prefix="/items", tags=["items"])
|
|
64
|
+
{%- endif %}
|
|
65
|
+
|
|
66
|
+
{%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
|
|
67
|
+
|
|
68
|
+
# Conversation routes (AI chat persistence)
|
|
69
|
+
v1_router.include_router(conversations.router, prefix="/conversations", tags=["conversations"])
|
|
70
|
+
{%- endif %}
|
|
71
|
+
|
|
72
|
+
{%- if cookiecutter.enable_webhooks and cookiecutter.use_database %}
|
|
73
|
+
|
|
74
|
+
# Webhook routes
|
|
75
|
+
v1_router.include_router(webhooks.router, prefix="/webhooks", tags=["webhooks"])
|
|
76
|
+
{%- endif %}
|
|
77
|
+
|
|
78
|
+
{%- if cookiecutter.enable_websockets %}
|
|
79
|
+
|
|
80
|
+
# WebSocket routes
|
|
81
|
+
v1_router.include_router(ws.router, tags=["websocket"])
|
|
82
|
+
{%- endif %}
|
|
83
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
84
|
+
|
|
85
|
+
# AI Agent routes
|
|
86
|
+
v1_router.include_router(agent.router, tags=["agent"])
|
|
87
|
+
{%- endif %}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
2
|
+
"""AI Agent WebSocket routes with streaming support."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any
|
|
6
|
+
{%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
|
|
7
|
+
from datetime import datetime, UTC
|
|
8
|
+
{%- if cookiecutter.use_postgresql %}
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
{%- endif %}
|
|
11
|
+
{%- endif %}
|
|
12
|
+
|
|
13
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect{%- if cookiecutter.websocket_auth_jwt %}, Depends{%- endif %}{%- if cookiecutter.websocket_auth_api_key %}, Query{%- endif %}
|
|
14
|
+
|
|
15
|
+
from pydantic_ai import (
|
|
16
|
+
Agent,
|
|
17
|
+
FinalResultEvent,
|
|
18
|
+
FunctionToolCallEvent,
|
|
19
|
+
FunctionToolResultEvent,
|
|
20
|
+
PartDeltaEvent,
|
|
21
|
+
PartStartEvent,
|
|
22
|
+
TextPartDelta,
|
|
23
|
+
ToolCallPartDelta,
|
|
24
|
+
)
|
|
25
|
+
from pydantic_ai.messages import (
|
|
26
|
+
ModelRequest,
|
|
27
|
+
ModelResponse,
|
|
28
|
+
SystemPromptPart,
|
|
29
|
+
TextPart,
|
|
30
|
+
UserPromptPart,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from app.agents.assistant import Deps, get_agent
|
|
34
|
+
{%- if cookiecutter.websocket_auth_jwt %}
|
|
35
|
+
from app.api.deps import get_current_user_ws
|
|
36
|
+
from app.db.models.user import User
|
|
37
|
+
{%- endif %}
|
|
38
|
+
{%- if cookiecutter.websocket_auth_api_key %}
|
|
39
|
+
from app.core.config import settings
|
|
40
|
+
{%- endif %}
|
|
41
|
+
{%- if cookiecutter.enable_conversation_persistence and (cookiecutter.use_postgresql or cookiecutter.use_sqlite) %}
|
|
42
|
+
from app.db.session import get_db_session
|
|
43
|
+
from app.api.deps import ConversationSvc, get_conversation_service
|
|
44
|
+
from app.schemas.conversation import ConversationCreate, MessageCreate, ToolCallCreate, ToolCallComplete
|
|
45
|
+
{%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_mongodb %}
|
|
46
|
+
from app.api.deps import ConversationSvc, get_conversation_service
|
|
47
|
+
from app.schemas.conversation import ConversationCreate, MessageCreate, ToolCallCreate, ToolCallComplete
|
|
48
|
+
{%- endif %}
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
router = APIRouter()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AgentConnectionManager:
|
|
56
|
+
"""WebSocket connection manager for AI agent."""
|
|
57
|
+
|
|
58
|
+
def __init__(self) -> None:
|
|
59
|
+
self.active_connections: list[WebSocket] = []
|
|
60
|
+
|
|
61
|
+
async def connect(self, websocket: WebSocket) -> None:
|
|
62
|
+
"""Accept and store a new WebSocket connection."""
|
|
63
|
+
await websocket.accept()
|
|
64
|
+
self.active_connections.append(websocket)
|
|
65
|
+
logger.info(f"Agent WebSocket connected. Total connections: {len(self.active_connections)}")
|
|
66
|
+
|
|
67
|
+
def disconnect(self, websocket: WebSocket) -> None:
|
|
68
|
+
"""Remove a WebSocket connection."""
|
|
69
|
+
if websocket in self.active_connections:
|
|
70
|
+
self.active_connections.remove(websocket)
|
|
71
|
+
logger.info(f"Agent WebSocket disconnected. Total connections: {len(self.active_connections)}")
|
|
72
|
+
|
|
73
|
+
async def send_event(self, websocket: WebSocket, event_type: str, data: Any) -> bool:
|
|
74
|
+
"""Send a JSON event to a specific WebSocket client.
|
|
75
|
+
|
|
76
|
+
Returns True if sent successfully, False if connection is closed.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
await websocket.send_json({"type": event_type, "data": data})
|
|
80
|
+
return True
|
|
81
|
+
except (WebSocketDisconnect, RuntimeError):
|
|
82
|
+
# Connection already closed
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
manager = AgentConnectionManager()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def build_message_history(history: list[dict[str, str]]) -> list[ModelRequest | ModelResponse]:
|
|
90
|
+
"""Convert conversation history to PydanticAI message format."""
|
|
91
|
+
model_history: list[ModelRequest | ModelResponse] = []
|
|
92
|
+
|
|
93
|
+
for msg in history:
|
|
94
|
+
if msg["role"] == "user":
|
|
95
|
+
model_history.append(ModelRequest(parts=[UserPromptPart(content=msg["content"])]))
|
|
96
|
+
elif msg["role"] == "assistant":
|
|
97
|
+
model_history.append(ModelResponse(parts=[TextPart(content=msg["content"])]))
|
|
98
|
+
elif msg["role"] == "system":
|
|
99
|
+
model_history.append(ModelRequest(parts=[SystemPromptPart(content=msg["content"])]))
|
|
100
|
+
|
|
101
|
+
return model_history
|
|
102
|
+
|
|
103
|
+
{%- if cookiecutter.websocket_auth_api_key %}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def verify_api_key(api_key: str) -> bool:
|
|
107
|
+
"""Verify the API key for WebSocket authentication."""
|
|
108
|
+
return api_key == settings.API_KEY
|
|
109
|
+
{%- endif %}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@router.websocket("/ws/agent")
|
|
113
|
+
async def agent_websocket(
|
|
114
|
+
websocket: WebSocket,
|
|
115
|
+
{%- if cookiecutter.websocket_auth_jwt %}
|
|
116
|
+
user: User = Depends(get_current_user_ws),
|
|
117
|
+
{%- elif cookiecutter.websocket_auth_api_key %}
|
|
118
|
+
api_key: str = Query(..., alias="api_key"),
|
|
119
|
+
{%- endif %}
|
|
120
|
+
) -> None:
|
|
121
|
+
"""WebSocket endpoint for AI agent with full event streaming.
|
|
122
|
+
|
|
123
|
+
Uses PydanticAI iter() to stream all agent events including:
|
|
124
|
+
- user_prompt: When user input is received
|
|
125
|
+
- model_request_start: When model request begins
|
|
126
|
+
- text_delta: Streaming text from the model
|
|
127
|
+
- tool_call_delta: Streaming tool call arguments
|
|
128
|
+
- tool_call: When a tool is called (with full args)
|
|
129
|
+
- tool_result: When a tool returns a result
|
|
130
|
+
- final_result: When the final result is ready
|
|
131
|
+
- complete: When processing is complete
|
|
132
|
+
- error: When an error occurs
|
|
133
|
+
|
|
134
|
+
Expected input message format:
|
|
135
|
+
{
|
|
136
|
+
"message": "user message here",
|
|
137
|
+
"history": [{"role": "user|assistant|system", "content": "..."}]{% if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %},
|
|
138
|
+
"conversation_id": "optional-uuid-to-continue-existing-conversation"{% endif %}
|
|
139
|
+
}
|
|
140
|
+
{%- if cookiecutter.websocket_auth_jwt %}
|
|
141
|
+
|
|
142
|
+
Authentication: Requires a valid JWT token passed as a query parameter or header.
|
|
143
|
+
{%- elif cookiecutter.websocket_auth_api_key %}
|
|
144
|
+
|
|
145
|
+
Authentication: Requires a valid API key passed as 'api_key' query parameter.
|
|
146
|
+
Example: ws://localhost:{{ cookiecutter.backend_port }}/api/v1/ws/agent?api_key=your-api-key
|
|
147
|
+
{%- endif %}
|
|
148
|
+
{%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
|
|
149
|
+
|
|
150
|
+
Persistence: Set 'conversation_id' to continue an existing conversation.
|
|
151
|
+
If not provided, a new conversation is created. The conversation_id is
|
|
152
|
+
returned in the 'conversation_started' event.
|
|
153
|
+
{%- endif %}
|
|
154
|
+
"""
|
|
155
|
+
{%- if cookiecutter.websocket_auth_api_key %}
|
|
156
|
+
# Verify API key before accepting connection
|
|
157
|
+
if not await verify_api_key(api_key):
|
|
158
|
+
await websocket.close(code=4001, reason="Invalid API key")
|
|
159
|
+
return
|
|
160
|
+
{%- endif %}
|
|
161
|
+
|
|
162
|
+
await manager.connect(websocket)
|
|
163
|
+
|
|
164
|
+
# Conversation state per connection
|
|
165
|
+
conversation_history: list[dict[str, str]] = []
|
|
166
|
+
deps = Deps()
|
|
167
|
+
{%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
|
|
168
|
+
current_conversation_id: str | None = None
|
|
169
|
+
{%- endif %}
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
while True:
|
|
173
|
+
# Receive user message
|
|
174
|
+
data = await websocket.receive_json()
|
|
175
|
+
user_message = data.get("message", "")
|
|
176
|
+
# Optionally accept history from client (or use server-side tracking)
|
|
177
|
+
if "history" in data:
|
|
178
|
+
conversation_history = data["history"]
|
|
179
|
+
|
|
180
|
+
if not user_message:
|
|
181
|
+
await manager.send_event(websocket, "error", {"message": "Empty message"})
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
{%- if cookiecutter.enable_conversation_persistence and (cookiecutter.use_postgresql or cookiecutter.use_sqlite) %}
|
|
185
|
+
|
|
186
|
+
# Handle conversation persistence
|
|
187
|
+
db_gen = get_db_session()
|
|
188
|
+
db = await anext(db_gen) if hasattr(db_gen, "__anext__") else next(db_gen)
|
|
189
|
+
try:
|
|
190
|
+
conv_service = get_conversation_service(db)
|
|
191
|
+
|
|
192
|
+
# Get or create conversation
|
|
193
|
+
requested_conv_id = data.get("conversation_id")
|
|
194
|
+
if requested_conv_id:
|
|
195
|
+
{%- if cookiecutter.use_postgresql %}
|
|
196
|
+
current_conversation_id = requested_conv_id
|
|
197
|
+
# Verify conversation exists
|
|
198
|
+
await conv_service.get_conversation(UUID(requested_conv_id))
|
|
199
|
+
{%- else %}
|
|
200
|
+
current_conversation_id = requested_conv_id
|
|
201
|
+
conv_service.get_conversation(requested_conv_id)
|
|
202
|
+
{%- endif %}
|
|
203
|
+
elif not current_conversation_id:
|
|
204
|
+
# Create new conversation
|
|
205
|
+
conv_data = ConversationCreate(
|
|
206
|
+
{%- if cookiecutter.use_jwt %}
|
|
207
|
+
user_id={% if cookiecutter.use_postgresql %}user.id{% else %}str(user.id){% endif %},
|
|
208
|
+
{%- endif %}
|
|
209
|
+
title=user_message[:50] if len(user_message) > 50 else user_message,
|
|
210
|
+
)
|
|
211
|
+
{%- if cookiecutter.use_postgresql %}
|
|
212
|
+
conversation = await conv_service.create_conversation(conv_data)
|
|
213
|
+
{%- else %}
|
|
214
|
+
conversation = conv_service.create_conversation(conv_data)
|
|
215
|
+
{%- endif %}
|
|
216
|
+
current_conversation_id = str(conversation.id)
|
|
217
|
+
await manager.send_event(
|
|
218
|
+
websocket,
|
|
219
|
+
"conversation_started",
|
|
220
|
+
{"conversation_id": current_conversation_id},
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Save user message
|
|
224
|
+
{%- if cookiecutter.use_postgresql %}
|
|
225
|
+
user_msg = await conv_service.add_message(
|
|
226
|
+
UUID(current_conversation_id),
|
|
227
|
+
MessageCreate(role="user", content=user_message),
|
|
228
|
+
)
|
|
229
|
+
{%- else %}
|
|
230
|
+
user_msg = conv_service.add_message(
|
|
231
|
+
current_conversation_id,
|
|
232
|
+
MessageCreate(role="user", content=user_message),
|
|
233
|
+
)
|
|
234
|
+
{%- endif %}
|
|
235
|
+
await db.commit()
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.warning(f"Failed to persist conversation: {e}")
|
|
238
|
+
# Continue without persistence
|
|
239
|
+
{%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_mongodb %}
|
|
240
|
+
|
|
241
|
+
# Handle conversation persistence (MongoDB)
|
|
242
|
+
conv_service = get_conversation_service()
|
|
243
|
+
|
|
244
|
+
requested_conv_id = data.get("conversation_id")
|
|
245
|
+
if requested_conv_id:
|
|
246
|
+
current_conversation_id = requested_conv_id
|
|
247
|
+
await conv_service.get_conversation(requested_conv_id)
|
|
248
|
+
elif not current_conversation_id:
|
|
249
|
+
conv_data = ConversationCreate(
|
|
250
|
+
{%- if cookiecutter.use_jwt %}
|
|
251
|
+
user_id=str(user.id),
|
|
252
|
+
{%- endif %}
|
|
253
|
+
title=user_message[:50] if len(user_message) > 50 else user_message,
|
|
254
|
+
)
|
|
255
|
+
conversation = await conv_service.create_conversation(conv_data)
|
|
256
|
+
current_conversation_id = str(conversation.id)
|
|
257
|
+
await manager.send_event(
|
|
258
|
+
websocket,
|
|
259
|
+
"conversation_started",
|
|
260
|
+
{"conversation_id": current_conversation_id},
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Save user message
|
|
264
|
+
user_msg = await conv_service.add_message(
|
|
265
|
+
current_conversation_id,
|
|
266
|
+
MessageCreate(role="user", content=user_message),
|
|
267
|
+
)
|
|
268
|
+
{%- endif %}
|
|
269
|
+
|
|
270
|
+
await manager.send_event(websocket, "user_prompt", {"content": user_message})
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
assistant = get_agent()
|
|
274
|
+
model_history = build_message_history(conversation_history)
|
|
275
|
+
|
|
276
|
+
# Use iter() on the underlying PydanticAI agent to stream all events
|
|
277
|
+
async with assistant.agent.iter(
|
|
278
|
+
user_message,
|
|
279
|
+
deps=deps,
|
|
280
|
+
message_history=model_history,
|
|
281
|
+
) as agent_run:
|
|
282
|
+
async for node in agent_run:
|
|
283
|
+
if Agent.is_user_prompt_node(node):
|
|
284
|
+
await manager.send_event(
|
|
285
|
+
websocket,
|
|
286
|
+
"user_prompt_processed",
|
|
287
|
+
{"prompt": node.user_prompt},
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
elif Agent.is_model_request_node(node):
|
|
291
|
+
await manager.send_event(websocket, "model_request_start", {})
|
|
292
|
+
|
|
293
|
+
async with node.stream(agent_run.ctx) as request_stream:
|
|
294
|
+
async for event in request_stream:
|
|
295
|
+
if isinstance(event, PartStartEvent):
|
|
296
|
+
await manager.send_event(
|
|
297
|
+
websocket,
|
|
298
|
+
"part_start",
|
|
299
|
+
{
|
|
300
|
+
"index": event.index,
|
|
301
|
+
"part_type": type(event.part).__name__,
|
|
302
|
+
},
|
|
303
|
+
)
|
|
304
|
+
# Send initial content from TextPart if present
|
|
305
|
+
if isinstance(event.part, TextPart) and event.part.content:
|
|
306
|
+
await manager.send_event(
|
|
307
|
+
websocket,
|
|
308
|
+
"text_delta",
|
|
309
|
+
{
|
|
310
|
+
"index": event.index,
|
|
311
|
+
"content": event.part.content,
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
elif isinstance(event, PartDeltaEvent):
|
|
316
|
+
if isinstance(event.delta, TextPartDelta):
|
|
317
|
+
await manager.send_event(
|
|
318
|
+
websocket,
|
|
319
|
+
"text_delta",
|
|
320
|
+
{
|
|
321
|
+
"index": event.index,
|
|
322
|
+
"content": event.delta.content_delta,
|
|
323
|
+
},
|
|
324
|
+
)
|
|
325
|
+
elif isinstance(event.delta, ToolCallPartDelta):
|
|
326
|
+
await manager.send_event(
|
|
327
|
+
websocket,
|
|
328
|
+
"tool_call_delta",
|
|
329
|
+
{
|
|
330
|
+
"index": event.index,
|
|
331
|
+
"args_delta": event.delta.args_delta,
|
|
332
|
+
},
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
elif isinstance(event, FinalResultEvent):
|
|
336
|
+
await manager.send_event(
|
|
337
|
+
websocket,
|
|
338
|
+
"final_result_start",
|
|
339
|
+
{"tool_name": event.tool_name},
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
elif Agent.is_call_tools_node(node):
|
|
343
|
+
await manager.send_event(websocket, "call_tools_start", {})
|
|
344
|
+
|
|
345
|
+
async with node.stream(agent_run.ctx) as handle_stream:
|
|
346
|
+
async for event in handle_stream:
|
|
347
|
+
if isinstance(event, FunctionToolCallEvent):
|
|
348
|
+
await manager.send_event(
|
|
349
|
+
websocket,
|
|
350
|
+
"tool_call",
|
|
351
|
+
{
|
|
352
|
+
"tool_name": event.part.tool_name,
|
|
353
|
+
"args": event.part.args,
|
|
354
|
+
"tool_call_id": event.part.tool_call_id,
|
|
355
|
+
},
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
elif isinstance(event, FunctionToolResultEvent):
|
|
359
|
+
await manager.send_event(
|
|
360
|
+
websocket,
|
|
361
|
+
"tool_result",
|
|
362
|
+
{
|
|
363
|
+
"tool_call_id": event.tool_call_id,
|
|
364
|
+
"content": str(event.result.content),
|
|
365
|
+
},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
elif Agent.is_end_node(node) and agent_run.result is not None:
|
|
369
|
+
await manager.send_event(
|
|
370
|
+
websocket,
|
|
371
|
+
"final_result",
|
|
372
|
+
{"output": agent_run.result.output},
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Update conversation history
|
|
376
|
+
conversation_history.append({"role": "user", "content": user_message})
|
|
377
|
+
if agent_run.result:
|
|
378
|
+
conversation_history.append(
|
|
379
|
+
{"role": "assistant", "content": agent_run.result.output}
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
{%- if cookiecutter.enable_conversation_persistence and (cookiecutter.use_postgresql or cookiecutter.use_sqlite) %}
|
|
383
|
+
|
|
384
|
+
# Save assistant response to database
|
|
385
|
+
if current_conversation_id and agent_run.result:
|
|
386
|
+
try:
|
|
387
|
+
{%- if cookiecutter.use_postgresql %}
|
|
388
|
+
await conv_service.add_message(
|
|
389
|
+
UUID(current_conversation_id),
|
|
390
|
+
MessageCreate(
|
|
391
|
+
role="assistant",
|
|
392
|
+
content=agent_run.result.output,
|
|
393
|
+
model_name=assistant.model_name if hasattr(assistant, "model_name") else None,
|
|
394
|
+
),
|
|
395
|
+
)
|
|
396
|
+
await db.commit()
|
|
397
|
+
{%- else %}
|
|
398
|
+
conv_service.add_message(
|
|
399
|
+
current_conversation_id,
|
|
400
|
+
MessageCreate(
|
|
401
|
+
role="assistant",
|
|
402
|
+
content=agent_run.result.output,
|
|
403
|
+
model_name=assistant.model_name if hasattr(assistant, "model_name") else None,
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
db.commit()
|
|
407
|
+
{%- endif %}
|
|
408
|
+
except Exception as e:
|
|
409
|
+
logger.warning(f"Failed to persist assistant response: {e}")
|
|
410
|
+
{%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_mongodb %}
|
|
411
|
+
|
|
412
|
+
# Save assistant response to database
|
|
413
|
+
if current_conversation_id and agent_run.result:
|
|
414
|
+
try:
|
|
415
|
+
await conv_service.add_message(
|
|
416
|
+
current_conversation_id,
|
|
417
|
+
MessageCreate(
|
|
418
|
+
role="assistant",
|
|
419
|
+
content=agent_run.result.output,
|
|
420
|
+
model_name=assistant.model_name if hasattr(assistant, "model_name") else None,
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
except Exception as e:
|
|
424
|
+
logger.warning(f"Failed to persist assistant response: {e}")
|
|
425
|
+
{%- endif %}
|
|
426
|
+
|
|
427
|
+
await manager.send_event(websocket, "complete", {
|
|
428
|
+
{%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
|
|
429
|
+
"conversation_id": current_conversation_id,
|
|
430
|
+
{%- endif %}
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
except WebSocketDisconnect:
|
|
434
|
+
# Client disconnected during processing - this is normal
|
|
435
|
+
logger.info("Client disconnected during agent processing")
|
|
436
|
+
break
|
|
437
|
+
except Exception as e:
|
|
438
|
+
logger.exception(f"Error processing agent request: {e}")
|
|
439
|
+
# Try to send error, but don't fail if connection is closed
|
|
440
|
+
await manager.send_event(websocket, "error", {"message": str(e)})
|
|
441
|
+
|
|
442
|
+
except WebSocketDisconnect:
|
|
443
|
+
pass # Normal disconnect
|
|
444
|
+
finally:
|
|
445
|
+
manager.disconnect(websocket)
|
|
446
|
+
{%- else %}
|
|
447
|
+
"""AI Agent routes - not configured."""
|
|
448
|
+
{%- endif %}
|