PraisonAI 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import ssl
|
|
3
|
+
import uuid
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import aiofiles
|
|
10
|
+
import aiohttp
|
|
11
|
+
|
|
12
|
+
from chainlit.data.base import BaseDataLayer
|
|
13
|
+
from chainlit.data.utils import queue_until_user_message
|
|
14
|
+
from praisonai.ui.chainlit_compat import EXPIRY_TIME, BaseStorageClient
|
|
15
|
+
from chainlit.element import ElementDict
|
|
16
|
+
from chainlit.logger import logger
|
|
17
|
+
from chainlit.step import StepDict
|
|
18
|
+
from chainlit.types import (
|
|
19
|
+
Feedback,
|
|
20
|
+
FeedbackDict,
|
|
21
|
+
PageInfo,
|
|
22
|
+
PaginatedResponse,
|
|
23
|
+
Pagination,
|
|
24
|
+
ThreadDict,
|
|
25
|
+
ThreadFilter,
|
|
26
|
+
)
|
|
27
|
+
from chainlit.user import PersistedUser, User
|
|
28
|
+
from sqlalchemy import text
|
|
29
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
30
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
|
|
31
|
+
from sqlalchemy.orm import sessionmaker
|
|
32
|
+
from database_config import get_database_config_for_sqlalchemy
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from chainlit.element import Element
|
|
36
|
+
from chainlit.step import StepDict
|
|
37
|
+
|
|
38
|
+
# Check FORCE_SQLITE flag to bypass external database detection
|
|
39
|
+
DATABASE_URL, SUPABASE_DATABASE_URL = get_database_config_for_sqlalchemy()
|
|
40
|
+
|
|
41
|
+
class SQLAlchemyDataLayer(BaseDataLayer):
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
conninfo: str,
|
|
45
|
+
ssl_require: bool = False,
|
|
46
|
+
storage_provider: Optional[BaseStorageClient] = None,
|
|
47
|
+
user_thread_limit: Optional[int] = 1000,
|
|
48
|
+
show_logger: Optional[bool] = False,
|
|
49
|
+
):
|
|
50
|
+
self._conninfo = conninfo
|
|
51
|
+
self.user_thread_limit = user_thread_limit
|
|
52
|
+
self.show_logger = show_logger
|
|
53
|
+
ssl_args = {}
|
|
54
|
+
if ssl_require:
|
|
55
|
+
ssl_context = ssl.create_default_context()
|
|
56
|
+
ssl_context.check_hostname = False
|
|
57
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
58
|
+
ssl_args["ssl"] = ssl_context
|
|
59
|
+
self.engine: AsyncEngine = create_async_engine(
|
|
60
|
+
self._conninfo, connect_args=ssl_args
|
|
61
|
+
)
|
|
62
|
+
self.async_session = sessionmaker(
|
|
63
|
+
bind=self.engine, expire_on_commit=False, class_=AsyncSession
|
|
64
|
+
)
|
|
65
|
+
if storage_provider:
|
|
66
|
+
self.storage_provider: Optional[BaseStorageClient] = storage_provider
|
|
67
|
+
if self.show_logger:
|
|
68
|
+
logger.info("SQLAlchemyDataLayer storage client initialized")
|
|
69
|
+
else:
|
|
70
|
+
self.storage_provider = None
|
|
71
|
+
logger.warning(
|
|
72
|
+
"SQLAlchemyDataLayer storage client is not initialized and elements will not be persisted!"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def build_debug_url(self) -> str:
|
|
76
|
+
return ""
|
|
77
|
+
|
|
78
|
+
###### SQL Helpers ######
|
|
79
|
+
async def execute_sql(
|
|
80
|
+
self, query: str, parameters: dict
|
|
81
|
+
) -> Union[List[Dict[str, Any]], int, None]:
|
|
82
|
+
parameterized_query = text(query)
|
|
83
|
+
async with self.async_session() as session:
|
|
84
|
+
try:
|
|
85
|
+
await session.begin()
|
|
86
|
+
result = await session.execute(parameterized_query, parameters)
|
|
87
|
+
await session.commit()
|
|
88
|
+
if result.returns_rows:
|
|
89
|
+
json_result = [dict(row._mapping) for row in result.fetchall()]
|
|
90
|
+
clean_json_result = self.clean_result(json_result)
|
|
91
|
+
assert isinstance(clean_json_result, list) or isinstance(
|
|
92
|
+
clean_json_result, int
|
|
93
|
+
)
|
|
94
|
+
return clean_json_result
|
|
95
|
+
else:
|
|
96
|
+
return result.rowcount
|
|
97
|
+
except SQLAlchemyError as e:
|
|
98
|
+
await session.rollback()
|
|
99
|
+
logger.warning(f"An error occurred: {e}")
|
|
100
|
+
return None
|
|
101
|
+
except Exception as e:
|
|
102
|
+
await session.rollback()
|
|
103
|
+
logger.warning(f"An unexpected error occurred: {e}")
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
async def get_current_timestamp(self) -> str:
|
|
107
|
+
return datetime.now().isoformat() + "Z"
|
|
108
|
+
|
|
109
|
+
def clean_result(self, obj):
|
|
110
|
+
if isinstance(obj, dict):
|
|
111
|
+
return {k: self.clean_result(v) for k, v in obj.items()}
|
|
112
|
+
elif isinstance(obj, list):
|
|
113
|
+
return [self.clean_result(item) for item in obj]
|
|
114
|
+
elif isinstance(obj, uuid.UUID):
|
|
115
|
+
return str(obj)
|
|
116
|
+
return obj
|
|
117
|
+
|
|
118
|
+
###### User ######
|
|
119
|
+
async def get_user(self, identifier: str) -> Optional[PersistedUser]:
|
|
120
|
+
if self.show_logger:
|
|
121
|
+
logger.info(f"SQLAlchemy: get_user, identifier={identifier}")
|
|
122
|
+
query = 'SELECT * FROM users WHERE "identifier" = :identifier'
|
|
123
|
+
parameters = {"identifier": identifier}
|
|
124
|
+
result = await self.execute_sql(query=query, parameters=parameters)
|
|
125
|
+
if result and isinstance(result, list):
|
|
126
|
+
user_data = result[0]
|
|
127
|
+
|
|
128
|
+
meta = user_data.get("meta", "{}")
|
|
129
|
+
if isinstance(meta, str):
|
|
130
|
+
meta = json.loads(meta)
|
|
131
|
+
|
|
132
|
+
return PersistedUser(
|
|
133
|
+
id=user_data["id"],
|
|
134
|
+
identifier=user_data["identifier"],
|
|
135
|
+
createdAt=user_data["createdAt"],
|
|
136
|
+
metadata=meta,
|
|
137
|
+
)
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
async def _get_user_identifer_by_id(self, user_id: str) -> str:
|
|
141
|
+
if self.show_logger:
|
|
142
|
+
logger.info(f"SQLAlchemy: _get_user_identifer_by_id, user_id={user_id}")
|
|
143
|
+
query = 'SELECT "identifier" FROM users WHERE "id" = :user_id'
|
|
144
|
+
parameters = {"user_id": user_id}
|
|
145
|
+
result = await self.execute_sql(query=query, parameters=parameters)
|
|
146
|
+
assert result
|
|
147
|
+
assert isinstance(result, list)
|
|
148
|
+
return result[0]["identifier"]
|
|
149
|
+
|
|
150
|
+
async def _get_user_id_by_thread(self, thread_id: str) -> Optional[str]:
|
|
151
|
+
if self.show_logger:
|
|
152
|
+
logger.info(f"SQLAlchemy: _get_user_id_by_thread, thread_id={thread_id}")
|
|
153
|
+
query = 'SELECT "userId" FROM threads WHERE "id" = :thread_id'
|
|
154
|
+
parameters = {"thread_id": thread_id}
|
|
155
|
+
result = await self.execute_sql(query=query, parameters=parameters)
|
|
156
|
+
if result and isinstance(result, list):
|
|
157
|
+
return result[0]["userId"]
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
async def create_user(self, user: User) -> Optional[PersistedUser]:
|
|
161
|
+
if self.show_logger:
|
|
162
|
+
logger.info(f"SQLAlchemy: create_user, user_identifier={user.identifier}")
|
|
163
|
+
existing_user: Optional["PersistedUser"] = await self.get_user(user.identifier)
|
|
164
|
+
user_dict: Dict[str, Any] = {
|
|
165
|
+
"identifier": str(user.identifier),
|
|
166
|
+
"meta": json.dumps(user.metadata) or "{}",
|
|
167
|
+
}
|
|
168
|
+
if not existing_user:
|
|
169
|
+
user_dict["id"] = str(uuid.uuid4())
|
|
170
|
+
user_dict["createdAt"] = await self.get_current_timestamp()
|
|
171
|
+
query = 'INSERT INTO users ("id", "identifier", "createdAt", "meta") VALUES (:id, :identifier, :createdAt, :meta)'
|
|
172
|
+
await self.execute_sql(query=query, parameters=user_dict)
|
|
173
|
+
else:
|
|
174
|
+
query = 'UPDATE users SET "meta" = :meta WHERE "identifier" = :identifier'
|
|
175
|
+
await self.execute_sql(query=query, parameters=user_dict)
|
|
176
|
+
return await self.get_user(user.identifier)
|
|
177
|
+
|
|
178
|
+
###### Threads ######
|
|
179
|
+
async def get_thread_author(self, thread_id: str) -> str:
|
|
180
|
+
if self.show_logger:
|
|
181
|
+
logger.info(f"SQLAlchemy: get_thread_author, thread_id={thread_id}")
|
|
182
|
+
query = 'SELECT "userIdentifier" FROM threads WHERE "id" = :id'
|
|
183
|
+
parameters = {"id": thread_id}
|
|
184
|
+
result = await self.execute_sql(query=query, parameters=parameters)
|
|
185
|
+
if isinstance(result, list) and result:
|
|
186
|
+
author_identifier = result[0].get("userIdentifier")
|
|
187
|
+
if author_identifier is not None:
|
|
188
|
+
return author_identifier
|
|
189
|
+
raise ValueError(f"Author not found for thread_id {thread_id}")
|
|
190
|
+
|
|
191
|
+
async def get_thread(self, thread_id: str) -> Optional[ThreadDict]:
|
|
192
|
+
if self.show_logger:
|
|
193
|
+
logger.info(f"SQLAlchemy: get_thread, thread_id={thread_id}")
|
|
194
|
+
user_threads: Optional[List[ThreadDict]] = await self.get_all_user_threads(
|
|
195
|
+
thread_id=thread_id
|
|
196
|
+
)
|
|
197
|
+
if user_threads:
|
|
198
|
+
return user_threads[0]
|
|
199
|
+
else:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
async def update_thread(
|
|
203
|
+
self,
|
|
204
|
+
thread_id: str,
|
|
205
|
+
name: Optional[str] = None,
|
|
206
|
+
user_id: Optional[str] = None,
|
|
207
|
+
metadata: Optional[Dict] = None,
|
|
208
|
+
tags: Optional[List[str]] = None,
|
|
209
|
+
):
|
|
210
|
+
if self.show_logger:
|
|
211
|
+
logger.info(f"SQLAlchemy: update_thread, thread_id={thread_id}")
|
|
212
|
+
|
|
213
|
+
user_identifier = None
|
|
214
|
+
if user_id:
|
|
215
|
+
user_identifier = await self._get_user_identifer_by_id(user_id)
|
|
216
|
+
|
|
217
|
+
data = {
|
|
218
|
+
"id": thread_id,
|
|
219
|
+
"createdAt": (
|
|
220
|
+
await self.get_current_timestamp() if metadata is None else None
|
|
221
|
+
),
|
|
222
|
+
"name": (
|
|
223
|
+
name
|
|
224
|
+
if name is not None
|
|
225
|
+
else (metadata.get("name") if metadata and "name" in metadata else None)
|
|
226
|
+
),
|
|
227
|
+
"userId": user_id,
|
|
228
|
+
"userIdentifier": user_identifier,
|
|
229
|
+
"tags": json.dumps(tags) if tags else None,
|
|
230
|
+
"meta": json.dumps(metadata) if metadata else None,
|
|
231
|
+
}
|
|
232
|
+
parameters = {key: value for key, value in data.items() if value is not None}
|
|
233
|
+
columns = ", ".join(f'"{key}"' for key in parameters.keys())
|
|
234
|
+
values = ", ".join(f':{key}' for key in parameters.keys())
|
|
235
|
+
updates = ", ".join(
|
|
236
|
+
f'"{key}" = EXCLUDED."{key}"' for key in parameters.keys() if key != "id"
|
|
237
|
+
)
|
|
238
|
+
query = f"""
|
|
239
|
+
INSERT INTO threads ({columns})
|
|
240
|
+
VALUES ({values})
|
|
241
|
+
ON CONFLICT ("id") DO UPDATE
|
|
242
|
+
SET {updates};
|
|
243
|
+
"""
|
|
244
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
245
|
+
|
|
246
|
+
async def delete_thread(self, thread_id: str):
|
|
247
|
+
if self.show_logger:
|
|
248
|
+
logger.info(f"SQLAlchemy: delete_thread, thread_id={thread_id}")
|
|
249
|
+
feedbacks_query = 'DELETE FROM feedbacks WHERE "forId" IN (SELECT "id" FROM steps WHERE "threadId" = :id)'
|
|
250
|
+
elements_query = 'DELETE FROM elements WHERE "threadId" = :id'
|
|
251
|
+
steps_query = 'DELETE FROM steps WHERE "threadId" = :id'
|
|
252
|
+
thread_query = 'DELETE FROM threads WHERE "id" = :id'
|
|
253
|
+
parameters = {"id": thread_id}
|
|
254
|
+
await self.execute_sql(query=feedbacks_query, parameters=parameters)
|
|
255
|
+
await self.execute_sql(query=elements_query, parameters=parameters)
|
|
256
|
+
await self.execute_sql(query=steps_query, parameters=parameters)
|
|
257
|
+
await self.execute_sql(query=thread_query, parameters=parameters)
|
|
258
|
+
|
|
259
|
+
async def list_threads(
|
|
260
|
+
self, pagination: Pagination, filters: ThreadFilter
|
|
261
|
+
) -> PaginatedResponse:
|
|
262
|
+
if self.show_logger:
|
|
263
|
+
logger.info(
|
|
264
|
+
f"SQLAlchemy: list_threads, pagination={pagination}, filters={filters}"
|
|
265
|
+
)
|
|
266
|
+
if not filters.userId:
|
|
267
|
+
raise ValueError("userId is required")
|
|
268
|
+
all_user_threads: Optional[List[ThreadDict]] = (
|
|
269
|
+
await self.get_all_user_threads(user_id=filters.userId) or []
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
search_keyword = filters.search.lower() if filters.search else None
|
|
273
|
+
feedback_value = int(filters.feedback) if filters.feedback else None
|
|
274
|
+
|
|
275
|
+
filtered_threads = []
|
|
276
|
+
for thread in all_user_threads:
|
|
277
|
+
keyword_match = True
|
|
278
|
+
feedback_match = True
|
|
279
|
+
if search_keyword or feedback_value is not None:
|
|
280
|
+
if search_keyword:
|
|
281
|
+
keyword_match = any(
|
|
282
|
+
search_keyword in step["output"].lower()
|
|
283
|
+
for step in thread["steps"]
|
|
284
|
+
if "output" in step and isinstance(step["output"], str)
|
|
285
|
+
)
|
|
286
|
+
if feedback_value is not None:
|
|
287
|
+
feedback_match = False
|
|
288
|
+
for step in thread["steps"]:
|
|
289
|
+
feedback = step.get("feedback")
|
|
290
|
+
if feedback and feedback.get("value") == feedback_value:
|
|
291
|
+
feedback_match = True
|
|
292
|
+
break
|
|
293
|
+
if keyword_match and feedback_match:
|
|
294
|
+
filtered_threads.append(thread)
|
|
295
|
+
|
|
296
|
+
start = 0
|
|
297
|
+
if pagination.cursor:
|
|
298
|
+
for i, thr in enumerate(filtered_threads):
|
|
299
|
+
if thr["id"] == pagination.cursor:
|
|
300
|
+
start = i + 1
|
|
301
|
+
break
|
|
302
|
+
end = start + pagination.first
|
|
303
|
+
paginated_threads = filtered_threads[start:end] or []
|
|
304
|
+
|
|
305
|
+
has_next_page = len(filtered_threads) > end
|
|
306
|
+
start_cursor = paginated_threads[0]["id"] if paginated_threads else None
|
|
307
|
+
end_cursor = paginated_threads[-1]["id"] if paginated_threads else None
|
|
308
|
+
|
|
309
|
+
return PaginatedResponse(
|
|
310
|
+
pageInfo=PageInfo(
|
|
311
|
+
hasNextPage=has_next_page,
|
|
312
|
+
startCursor=start_cursor,
|
|
313
|
+
endCursor=end_cursor,
|
|
314
|
+
),
|
|
315
|
+
data=paginated_threads,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
###### Steps ######
|
|
319
|
+
@queue_until_user_message()
|
|
320
|
+
async def create_step(self, step_dict: "StepDict"):
|
|
321
|
+
if self.show_logger:
|
|
322
|
+
logger.info(f"SQLAlchemy: create_step, step_id={step_dict.get('id')}")
|
|
323
|
+
|
|
324
|
+
step_dict["showInput"] = (
|
|
325
|
+
str(step_dict.get("showInput", "")).lower()
|
|
326
|
+
if "showInput" in step_dict
|
|
327
|
+
else None
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
tags = step_dict.get("tags")
|
|
331
|
+
if not tags:
|
|
332
|
+
tags = []
|
|
333
|
+
meta = json.dumps(step_dict.get("metadata", {}))
|
|
334
|
+
generation = json.dumps(step_dict.get("generation", {}))
|
|
335
|
+
parameters = {
|
|
336
|
+
"id": step_dict.get("id"),
|
|
337
|
+
"name": step_dict.get("name"),
|
|
338
|
+
"type": step_dict.get("type"),
|
|
339
|
+
"threadId": step_dict.get("threadId"),
|
|
340
|
+
"parentId": step_dict.get("parentId"),
|
|
341
|
+
"disableFeedback": step_dict.get("disableFeedback", False),
|
|
342
|
+
"streaming": step_dict.get("streaming", False),
|
|
343
|
+
"waitForAnswer": step_dict.get("waitForAnswer", False),
|
|
344
|
+
"isError": step_dict.get("isError", False),
|
|
345
|
+
"meta": meta,
|
|
346
|
+
"tags": json.dumps(tags),
|
|
347
|
+
"input": step_dict.get("input"),
|
|
348
|
+
"output": step_dict.get("output"),
|
|
349
|
+
"createdAt": step_dict.get("createdAt"),
|
|
350
|
+
"startTime": step_dict.get("start"),
|
|
351
|
+
"endTime": step_dict.get("end"),
|
|
352
|
+
"generation": generation,
|
|
353
|
+
"showInput": step_dict.get("showInput"),
|
|
354
|
+
"language": step_dict.get("language"),
|
|
355
|
+
"indent": step_dict.get("indent"),
|
|
356
|
+
}
|
|
357
|
+
parameters = {k: v for k, v in parameters.items() if v is not None}
|
|
358
|
+
columns = ", ".join(f'"{key}"' for key in parameters.keys())
|
|
359
|
+
values = ", ".join(f':{key}' for key in parameters.keys())
|
|
360
|
+
updates = ", ".join(
|
|
361
|
+
f'"{key}" = :{key}' for key in parameters.keys() if key != "id"
|
|
362
|
+
)
|
|
363
|
+
query = f"""
|
|
364
|
+
INSERT INTO steps ({columns})
|
|
365
|
+
VALUES ({values})
|
|
366
|
+
ON CONFLICT ("id") DO UPDATE
|
|
367
|
+
SET {updates};
|
|
368
|
+
"""
|
|
369
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
370
|
+
|
|
371
|
+
@queue_until_user_message()
|
|
372
|
+
async def update_step(self, step_dict: "StepDict"):
|
|
373
|
+
if self.show_logger:
|
|
374
|
+
logger.info(f"SQLAlchemy: update_step, step_id={step_dict.get('id')}")
|
|
375
|
+
await self.create_step(step_dict)
|
|
376
|
+
|
|
377
|
+
@queue_until_user_message()
|
|
378
|
+
async def delete_step(self, step_id: str):
|
|
379
|
+
if self.show_logger:
|
|
380
|
+
logger.info(f"SQLAlchemy: delete_step, step_id={step_id}")
|
|
381
|
+
feedbacks_query = 'DELETE FROM feedbacks WHERE "forId" = :id'
|
|
382
|
+
elements_query = 'DELETE FROM elements WHERE "forId" = :id'
|
|
383
|
+
steps_query = 'DELETE FROM steps WHERE "id" = :id'
|
|
384
|
+
parameters = {"id": step_id}
|
|
385
|
+
await self.execute_sql(query=feedbacks_query, parameters=parameters)
|
|
386
|
+
await self.execute_sql(query=elements_query, parameters=parameters)
|
|
387
|
+
await self.execute_sql(query=steps_query, parameters=parameters)
|
|
388
|
+
|
|
389
|
+
###### Feedback ######
|
|
390
|
+
async def upsert_feedback(self, feedback: Feedback) -> str:
|
|
391
|
+
if self.show_logger:
|
|
392
|
+
logger.info(f"SQLAlchemy: upsert_feedback, feedback_id={feedback.id}")
|
|
393
|
+
feedback.id = feedback.id or str(uuid.uuid4())
|
|
394
|
+
feedback_dict = asdict(feedback)
|
|
395
|
+
parameters = {k: v for k, v in feedback_dict.items() if v is not None}
|
|
396
|
+
columns = ", ".join(f'"{key}"' for key in parameters.keys())
|
|
397
|
+
values = ", ".join(f':{key}' for key in parameters.keys())
|
|
398
|
+
updates = ", ".join(
|
|
399
|
+
f'"{key}" = :{key}' for key in parameters.keys() if key != "id"
|
|
400
|
+
)
|
|
401
|
+
query = f"""
|
|
402
|
+
INSERT INTO feedbacks ({columns})
|
|
403
|
+
VALUES ({values})
|
|
404
|
+
ON CONFLICT ("id") DO UPDATE
|
|
405
|
+
SET {updates};
|
|
406
|
+
"""
|
|
407
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
408
|
+
return feedback.id
|
|
409
|
+
|
|
410
|
+
async def delete_feedback(self, feedback_id: str) -> bool:
|
|
411
|
+
if self.show_logger:
|
|
412
|
+
logger.info(f"SQLAlchemy: delete_feedback, feedback_id={feedback_id}")
|
|
413
|
+
query = 'DELETE FROM feedbacks WHERE "id" = :feedback_id'
|
|
414
|
+
parameters = {"feedback_id": feedback_id}
|
|
415
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
416
|
+
return True
|
|
417
|
+
|
|
418
|
+
###### Elements ######
|
|
419
|
+
async def get_element(
|
|
420
|
+
self, thread_id: str, element_id: str
|
|
421
|
+
) -> Optional["ElementDict"]:
|
|
422
|
+
if self.show_logger:
|
|
423
|
+
logger.info(
|
|
424
|
+
f"SQLAlchemy: get_element, thread_id={thread_id}, element_id={element_id}"
|
|
425
|
+
)
|
|
426
|
+
query = 'SELECT * FROM elements WHERE "threadId" = :thread_id AND "id" = :element_id'
|
|
427
|
+
parameters = {"thread_id": thread_id, "element_id": element_id}
|
|
428
|
+
element = await self.execute_sql(query=query, parameters=parameters)
|
|
429
|
+
if isinstance(element, list) and element:
|
|
430
|
+
element_dict = element[0]
|
|
431
|
+
return ElementDict(
|
|
432
|
+
id=element_dict["id"],
|
|
433
|
+
threadId=element_dict.get("threadId"),
|
|
434
|
+
type=element_dict.get("type"),
|
|
435
|
+
chainlitKey=element_dict.get("chainlitKey"),
|
|
436
|
+
url=element_dict.get("url"),
|
|
437
|
+
objectKey=element_dict.get("objectKey"),
|
|
438
|
+
name=element_dict["name"],
|
|
439
|
+
display=element_dict["display"],
|
|
440
|
+
size=element_dict.get("size"),
|
|
441
|
+
language=element_dict.get("language"),
|
|
442
|
+
page=element_dict.get("page"),
|
|
443
|
+
autoPlay=element_dict.get("autoPlay"),
|
|
444
|
+
playerConfig=element_dict.get("playerConfig"),
|
|
445
|
+
forId=element_dict.get("forId"),
|
|
446
|
+
mime=element_dict.get("mime"),
|
|
447
|
+
)
|
|
448
|
+
else:
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
@queue_until_user_message()
|
|
452
|
+
async def create_element(self, element: "Element"):
|
|
453
|
+
if self.show_logger:
|
|
454
|
+
logger.info(f"SQLAlchemy: create_element, element_id = {element.id}")
|
|
455
|
+
|
|
456
|
+
if not self.storage_provider:
|
|
457
|
+
logger.warning("SQLAlchemy: create_element error. No storage client!")
|
|
458
|
+
return
|
|
459
|
+
if not element.for_id:
|
|
460
|
+
return
|
|
461
|
+
|
|
462
|
+
content: Optional[Union[bytes, str]] = None
|
|
463
|
+
|
|
464
|
+
if element.path:
|
|
465
|
+
async with aiofiles.open(element.path, "rb") as f:
|
|
466
|
+
content = await f.read()
|
|
467
|
+
elif element.url:
|
|
468
|
+
async with aiohttp.ClientSession() as session:
|
|
469
|
+
async with session.get(element.url) as response:
|
|
470
|
+
if response.status == 200:
|
|
471
|
+
content = await response.read()
|
|
472
|
+
else:
|
|
473
|
+
content = None
|
|
474
|
+
elif element.content:
|
|
475
|
+
content = element.content
|
|
476
|
+
else:
|
|
477
|
+
raise ValueError("Element url, path or content must be provided")
|
|
478
|
+
if content is None:
|
|
479
|
+
raise ValueError("Content is None, cannot upload file")
|
|
480
|
+
|
|
481
|
+
user_id: str = await self._get_user_id_by_thread(element.thread_id) or "unknown"
|
|
482
|
+
file_object_key = f"{user_id}/{element.id}" + (
|
|
483
|
+
f"/{element.name}" if element.name else ""
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
if not element.mime:
|
|
487
|
+
element.mime = "application/octet-stream"
|
|
488
|
+
|
|
489
|
+
uploaded_file = await self.storage_provider.upload_file(
|
|
490
|
+
object_key=file_object_key, data=content, mime=element.mime, overwrite=True
|
|
491
|
+
)
|
|
492
|
+
if not uploaded_file:
|
|
493
|
+
raise ValueError("Failed to persist data in storage_provider")
|
|
494
|
+
|
|
495
|
+
element_dict: ElementDict = element.to_dict()
|
|
496
|
+
element_dict["url"] = uploaded_file.get("url")
|
|
497
|
+
element_dict["objectKey"] = uploaded_file.get("object_key")
|
|
498
|
+
element_dict_cleaned = {k: v for k, v in element_dict.items() if v is not None}
|
|
499
|
+
|
|
500
|
+
columns = ", ".join(f'"{column}"' for column in element_dict_cleaned.keys())
|
|
501
|
+
placeholders = ", ".join(f':{column}' for column in element_dict_cleaned.keys())
|
|
502
|
+
query = f"INSERT INTO elements ({columns}) VALUES ({placeholders})"
|
|
503
|
+
await self.execute_sql(query=query, parameters=element_dict_cleaned)
|
|
504
|
+
|
|
505
|
+
@queue_until_user_message()
|
|
506
|
+
async def delete_element(self, element_id: str, thread_id: Optional[str] = None):
|
|
507
|
+
if self.show_logger:
|
|
508
|
+
logger.info(f"SQLAlchemy: delete_element, element_id={element_id}")
|
|
509
|
+
query = 'DELETE FROM elements WHERE "id" = :id'
|
|
510
|
+
parameters = {"id": element_id}
|
|
511
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
512
|
+
|
|
513
|
+
async def get_all_user_threads(
|
|
514
|
+
self, user_id: Optional[str] = None, thread_id: Optional[str] = None
|
|
515
|
+
) -> Optional[List[ThreadDict]]:
|
|
516
|
+
if self.show_logger:
|
|
517
|
+
logger.info("SQLAlchemy: get_all_user_threads")
|
|
518
|
+
user_threads_query = """
|
|
519
|
+
SELECT
|
|
520
|
+
"id" AS thread_id,
|
|
521
|
+
"createdAt" AS thread_createdat,
|
|
522
|
+
"name" AS thread_name,
|
|
523
|
+
"userId" AS user_id,
|
|
524
|
+
"userIdentifier" AS user_identifier,
|
|
525
|
+
"tags" AS thread_tags,
|
|
526
|
+
"meta" AS thread_meta
|
|
527
|
+
FROM threads
|
|
528
|
+
WHERE ("userId" = :user_id OR :user_id IS NULL)
|
|
529
|
+
AND ("id" = :thread_id OR :thread_id IS NULL)
|
|
530
|
+
ORDER BY "createdAt" DESC
|
|
531
|
+
LIMIT :limit
|
|
532
|
+
"""
|
|
533
|
+
params = {
|
|
534
|
+
"user_id": user_id,
|
|
535
|
+
"thread_id": thread_id,
|
|
536
|
+
"limit": self.user_thread_limit,
|
|
537
|
+
}
|
|
538
|
+
user_threads = await self.execute_sql(
|
|
539
|
+
query=user_threads_query,
|
|
540
|
+
parameters=params,
|
|
541
|
+
)
|
|
542
|
+
if not isinstance(user_threads, list):
|
|
543
|
+
return None
|
|
544
|
+
if not user_threads:
|
|
545
|
+
return []
|
|
546
|
+
else:
|
|
547
|
+
thread_ids = (
|
|
548
|
+
"('" + "','".join([t["thread_id"] for t in user_threads]) + "')"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
steps_feedbacks_query = f"""
|
|
552
|
+
SELECT
|
|
553
|
+
s."id" AS step_id,
|
|
554
|
+
s."name" AS step_name,
|
|
555
|
+
s."type" AS step_type,
|
|
556
|
+
s."threadId" AS step_threadid,
|
|
557
|
+
s."parentId" AS step_parentid,
|
|
558
|
+
s."streaming" AS step_streaming,
|
|
559
|
+
s."waitForAnswer" AS step_waitforanswer,
|
|
560
|
+
s."isError" AS step_iserror,
|
|
561
|
+
s."meta" AS step_meta,
|
|
562
|
+
s."tags" AS step_tags,
|
|
563
|
+
s."input" AS step_input,
|
|
564
|
+
s."output" AS step_output,
|
|
565
|
+
s."createdAt" AS step_createdat,
|
|
566
|
+
s."startTime" AS step_start,
|
|
567
|
+
s."endTime" AS step_end,
|
|
568
|
+
s."generation" AS step_generation,
|
|
569
|
+
s."showInput" AS step_showinput,
|
|
570
|
+
s."language" AS step_language,
|
|
571
|
+
s."indent" AS step_indent,
|
|
572
|
+
f."value" AS feedback_value,
|
|
573
|
+
f."comment" AS feedback_comment,
|
|
574
|
+
f."id" AS feedback_id
|
|
575
|
+
FROM steps s LEFT JOIN feedbacks f ON s."id" = f."forId"
|
|
576
|
+
WHERE s."threadId" IN {thread_ids}
|
|
577
|
+
ORDER BY s."createdAt" ASC
|
|
578
|
+
"""
|
|
579
|
+
steps_feedbacks = await self.execute_sql(
|
|
580
|
+
query=steps_feedbacks_query, parameters={}
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
elements_query = f"""
|
|
584
|
+
SELECT
|
|
585
|
+
e."id" AS element_id,
|
|
586
|
+
e."threadId" as element_threadid,
|
|
587
|
+
e."type" AS element_type,
|
|
588
|
+
e."chainlitKey" AS element_chainlitkey,
|
|
589
|
+
e."url" AS element_url,
|
|
590
|
+
e."objectKey" as element_objectkey,
|
|
591
|
+
e."name" AS element_name,
|
|
592
|
+
e."display" AS element_display,
|
|
593
|
+
e."size" AS element_size,
|
|
594
|
+
e."language" AS element_language,
|
|
595
|
+
e."page" AS element_page,
|
|
596
|
+
e."forId" AS element_forid,
|
|
597
|
+
e."mime" AS element_mime
|
|
598
|
+
FROM elements e
|
|
599
|
+
WHERE e."threadId" IN {thread_ids}
|
|
600
|
+
"""
|
|
601
|
+
elements = await self.execute_sql(query=elements_query, parameters={})
|
|
602
|
+
|
|
603
|
+
thread_dicts = {}
|
|
604
|
+
for thread in user_threads:
|
|
605
|
+
t_id = thread["thread_id"]
|
|
606
|
+
meta = thread["thread_meta"]
|
|
607
|
+
if isinstance(meta, str):
|
|
608
|
+
try:
|
|
609
|
+
meta = json.loads(meta)
|
|
610
|
+
except:
|
|
611
|
+
meta = {}
|
|
612
|
+
tags = thread["thread_tags"]
|
|
613
|
+
if isinstance(tags, str):
|
|
614
|
+
try:
|
|
615
|
+
tags = json.loads(tags)
|
|
616
|
+
except:
|
|
617
|
+
tags = []
|
|
618
|
+
thread_dicts[t_id] = ThreadDict(
|
|
619
|
+
id=t_id,
|
|
620
|
+
createdAt=thread["thread_createdat"],
|
|
621
|
+
name=thread["thread_name"],
|
|
622
|
+
userId=thread["user_id"],
|
|
623
|
+
userIdentifier=thread["user_identifier"],
|
|
624
|
+
tags=tags,
|
|
625
|
+
metadata=meta,
|
|
626
|
+
steps=[],
|
|
627
|
+
elements=[],
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
if isinstance(steps_feedbacks, list):
|
|
631
|
+
for step_feedback in steps_feedbacks:
|
|
632
|
+
t_id = step_feedback["step_threadid"]
|
|
633
|
+
if t_id in thread_dicts:
|
|
634
|
+
meta = step_feedback["step_meta"]
|
|
635
|
+
if isinstance(meta, str):
|
|
636
|
+
try:
|
|
637
|
+
meta = json.loads(meta)
|
|
638
|
+
except:
|
|
639
|
+
meta = {}
|
|
640
|
+
tags = step_feedback["step_tags"]
|
|
641
|
+
if isinstance(tags, str):
|
|
642
|
+
try:
|
|
643
|
+
tags = json.loads(tags)
|
|
644
|
+
except:
|
|
645
|
+
tags = []
|
|
646
|
+
feedback = None
|
|
647
|
+
if step_feedback["feedback_value"] is not None:
|
|
648
|
+
feedback = FeedbackDict(
|
|
649
|
+
forId=step_feedback["step_id"],
|
|
650
|
+
id=step_feedback.get("feedback_id"),
|
|
651
|
+
value=step_feedback["feedback_value"],
|
|
652
|
+
comment=step_feedback.get("feedback_comment"),
|
|
653
|
+
)
|
|
654
|
+
input_val = step_feedback.get("step_input", "")
|
|
655
|
+
show_input = step_feedback.get("step_showinput", "false")
|
|
656
|
+
if show_input == "false":
|
|
657
|
+
input_val = ""
|
|
658
|
+
step_dict = StepDict(
|
|
659
|
+
id=step_feedback["step_id"],
|
|
660
|
+
name=step_feedback["step_name"],
|
|
661
|
+
type=step_feedback["step_type"],
|
|
662
|
+
threadId=t_id,
|
|
663
|
+
parentId=step_feedback.get("step_parentid"),
|
|
664
|
+
streaming=step_feedback.get("step_streaming", False),
|
|
665
|
+
waitForAnswer=step_feedback.get("step_waitforanswer"),
|
|
666
|
+
isError=step_feedback.get("step_iserror"),
|
|
667
|
+
metadata=meta,
|
|
668
|
+
tags=tags,
|
|
669
|
+
input=input_val,
|
|
670
|
+
output=step_feedback.get("step_output", ""),
|
|
671
|
+
createdAt=step_feedback.get("step_createdat"),
|
|
672
|
+
start=step_feedback.get("step_start"),
|
|
673
|
+
end=step_feedback.get("step_end"),
|
|
674
|
+
generation=step_feedback.get("step_generation"),
|
|
675
|
+
showInput=step_feedback.get("step_showinput"),
|
|
676
|
+
language=step_feedback.get("step_language"),
|
|
677
|
+
indent=step_feedback.get("step_indent"),
|
|
678
|
+
feedback=feedback,
|
|
679
|
+
)
|
|
680
|
+
thread_dicts[t_id]["steps"].append(step_dict)
|
|
681
|
+
|
|
682
|
+
if isinstance(elements, list):
|
|
683
|
+
for element in elements:
|
|
684
|
+
t_id = element["element_threadid"]
|
|
685
|
+
if t_id in thread_dicts:
|
|
686
|
+
element_dict = ElementDict(
|
|
687
|
+
id=element["element_id"],
|
|
688
|
+
threadId=t_id,
|
|
689
|
+
type=element["element_type"],
|
|
690
|
+
chainlitKey=element.get("element_chainlitkey"),
|
|
691
|
+
url=element.get("element_url"),
|
|
692
|
+
objectKey=element.get("element_objectkey"),
|
|
693
|
+
name=element["element_name"],
|
|
694
|
+
display=element["element_display"],
|
|
695
|
+
size=element.get("element_size"),
|
|
696
|
+
language=element.get("element_language"),
|
|
697
|
+
autoPlay=element.get("element_autoPlay"),
|
|
698
|
+
playerConfig=element.get("element_playerconfig"),
|
|
699
|
+
page=element.get("element_page"),
|
|
700
|
+
forId=element.get("element_forid"),
|
|
701
|
+
mime=element.get("element_mime"),
|
|
702
|
+
)
|
|
703
|
+
thread_dicts[t_id]["elements"].append(element_dict)
|
|
704
|
+
|
|
705
|
+
return list(thread_dicts.values())
|
|
706
|
+
|
|
707
|
+
async def close(self) -> None:
|
|
708
|
+
"""Close the database connection and cleanup resources."""
|
|
709
|
+
if hasattr(self, 'engine') and self.engine:
|
|
710
|
+
await self.engine.dispose()
|