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,401 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PostgreSQL implementation of ConversationStore.
|
|
3
|
+
|
|
4
|
+
Requires: psycopg2-binary or psycopg2
|
|
5
|
+
Install: pip install psycopg2-binary
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from .base import ConversationStore, ConversationSession, ConversationMessage
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PostgresConversationStore(ConversationStore):
|
|
18
|
+
"""
|
|
19
|
+
PostgreSQL-based conversation store.
|
|
20
|
+
|
|
21
|
+
Connection URL format: postgresql://user:password@host:port/database
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
store = PostgresConversationStore(
|
|
25
|
+
url="postgresql://postgres:password@localhost:5432/praisonai"
|
|
26
|
+
)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
SCHEMA_VERSION = "1.0.0"
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
url: Optional[str] = None,
|
|
34
|
+
host: str = "localhost",
|
|
35
|
+
port: int = 5432,
|
|
36
|
+
database: str = "praisonai",
|
|
37
|
+
user: str = "postgres",
|
|
38
|
+
password: str = "",
|
|
39
|
+
schema: str = "public",
|
|
40
|
+
table_prefix: str = "praison_",
|
|
41
|
+
auto_create_tables: bool = True,
|
|
42
|
+
pool_size: int = 5,
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
Initialize PostgreSQL conversation store.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
url: Full connection URL (overrides individual params)
|
|
49
|
+
host: Database host
|
|
50
|
+
port: Database port
|
|
51
|
+
database: Database name
|
|
52
|
+
user: Database user
|
|
53
|
+
password: Database password
|
|
54
|
+
schema: PostgreSQL schema
|
|
55
|
+
table_prefix: Prefix for table names
|
|
56
|
+
auto_create_tables: Create tables if they don't exist
|
|
57
|
+
pool_size: Connection pool size
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
import psycopg2
|
|
61
|
+
from psycopg2 import pool as pg_pool
|
|
62
|
+
from psycopg2.extras import RealDictCursor
|
|
63
|
+
except ImportError:
|
|
64
|
+
raise ImportError(
|
|
65
|
+
"psycopg2 is required for PostgreSQL support. "
|
|
66
|
+
"Install with: pip install psycopg2-binary"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self._psycopg2 = psycopg2
|
|
70
|
+
self._RealDictCursor = RealDictCursor
|
|
71
|
+
|
|
72
|
+
self.schema = schema
|
|
73
|
+
self.table_prefix = table_prefix
|
|
74
|
+
self.sessions_table = f"{schema}.{table_prefix}sessions"
|
|
75
|
+
self.messages_table = f"{schema}.{table_prefix}messages"
|
|
76
|
+
|
|
77
|
+
# Build connection params
|
|
78
|
+
if url:
|
|
79
|
+
self._pool = pg_pool.ThreadedConnectionPool(1, pool_size, url)
|
|
80
|
+
else:
|
|
81
|
+
self._pool = pg_pool.ThreadedConnectionPool(
|
|
82
|
+
1, pool_size,
|
|
83
|
+
host=host,
|
|
84
|
+
port=port,
|
|
85
|
+
database=database,
|
|
86
|
+
user=user,
|
|
87
|
+
password=password,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if auto_create_tables:
|
|
91
|
+
self._create_tables()
|
|
92
|
+
|
|
93
|
+
def _get_conn(self):
|
|
94
|
+
"""Get a connection from the pool."""
|
|
95
|
+
return self._pool.getconn()
|
|
96
|
+
|
|
97
|
+
def _put_conn(self, conn):
|
|
98
|
+
"""Return a connection to the pool."""
|
|
99
|
+
self._pool.putconn(conn)
|
|
100
|
+
|
|
101
|
+
def _create_tables(self) -> None:
|
|
102
|
+
"""Create tables if they don't exist."""
|
|
103
|
+
conn = self._get_conn()
|
|
104
|
+
try:
|
|
105
|
+
with conn.cursor() as cur:
|
|
106
|
+
# Create schema if not exists
|
|
107
|
+
cur.execute(f"CREATE SCHEMA IF NOT EXISTS {self.schema}")
|
|
108
|
+
|
|
109
|
+
# Sessions table
|
|
110
|
+
cur.execute(f"""
|
|
111
|
+
CREATE TABLE IF NOT EXISTS {self.sessions_table} (
|
|
112
|
+
session_id VARCHAR(255) PRIMARY KEY,
|
|
113
|
+
user_id VARCHAR(255),
|
|
114
|
+
agent_id VARCHAR(255),
|
|
115
|
+
name VARCHAR(255),
|
|
116
|
+
state JSONB,
|
|
117
|
+
metadata JSONB,
|
|
118
|
+
created_at DOUBLE PRECISION,
|
|
119
|
+
updated_at DOUBLE PRECISION
|
|
120
|
+
)
|
|
121
|
+
""")
|
|
122
|
+
|
|
123
|
+
# Sessions indexes
|
|
124
|
+
cur.execute(f"""
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}sessions_user
|
|
126
|
+
ON {self.sessions_table}(user_id)
|
|
127
|
+
""")
|
|
128
|
+
cur.execute(f"""
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}sessions_agent
|
|
130
|
+
ON {self.sessions_table}(agent_id)
|
|
131
|
+
""")
|
|
132
|
+
cur.execute(f"""
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}sessions_updated
|
|
134
|
+
ON {self.sessions_table}(updated_at DESC)
|
|
135
|
+
""")
|
|
136
|
+
|
|
137
|
+
# Messages table
|
|
138
|
+
cur.execute(f"""
|
|
139
|
+
CREATE TABLE IF NOT EXISTS {self.messages_table} (
|
|
140
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
141
|
+
session_id VARCHAR(255) NOT NULL
|
|
142
|
+
REFERENCES {self.sessions_table}(session_id) ON DELETE CASCADE,
|
|
143
|
+
role VARCHAR(50) NOT NULL,
|
|
144
|
+
content TEXT,
|
|
145
|
+
tool_calls JSONB,
|
|
146
|
+
tool_call_id VARCHAR(255),
|
|
147
|
+
metadata JSONB,
|
|
148
|
+
created_at DOUBLE PRECISION DEFAULT EXTRACT(EPOCH FROM NOW())
|
|
149
|
+
)
|
|
150
|
+
""")
|
|
151
|
+
|
|
152
|
+
# Messages indexes
|
|
153
|
+
cur.execute(f"""
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}messages_session
|
|
155
|
+
ON {self.messages_table}(session_id)
|
|
156
|
+
""")
|
|
157
|
+
cur.execute(f"""
|
|
158
|
+
CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}messages_created
|
|
159
|
+
ON {self.messages_table}(session_id, created_at)
|
|
160
|
+
""")
|
|
161
|
+
|
|
162
|
+
conn.commit()
|
|
163
|
+
logger.info(f"PostgreSQL tables created: {self.sessions_table}, {self.messages_table}")
|
|
164
|
+
finally:
|
|
165
|
+
self._put_conn(conn)
|
|
166
|
+
|
|
167
|
+
def create_session(self, session: ConversationSession) -> ConversationSession:
|
|
168
|
+
"""Create a new session."""
|
|
169
|
+
conn = self._get_conn()
|
|
170
|
+
try:
|
|
171
|
+
with conn.cursor() as cur:
|
|
172
|
+
cur.execute(f"""
|
|
173
|
+
INSERT INTO {self.sessions_table}
|
|
174
|
+
(session_id, user_id, agent_id, name, state, metadata, created_at, updated_at)
|
|
175
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
176
|
+
RETURNING session_id
|
|
177
|
+
""", (
|
|
178
|
+
session.session_id,
|
|
179
|
+
session.user_id,
|
|
180
|
+
session.agent_id,
|
|
181
|
+
session.name,
|
|
182
|
+
json.dumps(session.state) if session.state else None,
|
|
183
|
+
json.dumps(session.metadata) if session.metadata else None,
|
|
184
|
+
session.created_at,
|
|
185
|
+
session.updated_at,
|
|
186
|
+
))
|
|
187
|
+
conn.commit()
|
|
188
|
+
return session
|
|
189
|
+
finally:
|
|
190
|
+
self._put_conn(conn)
|
|
191
|
+
|
|
192
|
+
def get_session(self, session_id: str) -> Optional[ConversationSession]:
|
|
193
|
+
"""Get a session by ID."""
|
|
194
|
+
conn = self._get_conn()
|
|
195
|
+
try:
|
|
196
|
+
with conn.cursor(cursor_factory=self._RealDictCursor) as cur:
|
|
197
|
+
cur.execute(f"""
|
|
198
|
+
SELECT * FROM {self.sessions_table} WHERE session_id = %s
|
|
199
|
+
""", (session_id,))
|
|
200
|
+
row = cur.fetchone()
|
|
201
|
+
if not row:
|
|
202
|
+
return None
|
|
203
|
+
return ConversationSession(
|
|
204
|
+
session_id=row["session_id"],
|
|
205
|
+
user_id=row["user_id"],
|
|
206
|
+
agent_id=row["agent_id"],
|
|
207
|
+
name=row["name"],
|
|
208
|
+
state=row["state"],
|
|
209
|
+
metadata=row["metadata"],
|
|
210
|
+
created_at=row["created_at"],
|
|
211
|
+
updated_at=row["updated_at"],
|
|
212
|
+
)
|
|
213
|
+
finally:
|
|
214
|
+
self._put_conn(conn)
|
|
215
|
+
|
|
216
|
+
def update_session(self, session: ConversationSession) -> ConversationSession:
|
|
217
|
+
"""Update an existing session."""
|
|
218
|
+
conn = self._get_conn()
|
|
219
|
+
try:
|
|
220
|
+
with conn.cursor() as cur:
|
|
221
|
+
cur.execute(f"""
|
|
222
|
+
UPDATE {self.sessions_table}
|
|
223
|
+
SET user_id = %s, agent_id = %s, name = %s,
|
|
224
|
+
state = %s, metadata = %s, updated_at = %s
|
|
225
|
+
WHERE session_id = %s
|
|
226
|
+
""", (
|
|
227
|
+
session.user_id,
|
|
228
|
+
session.agent_id,
|
|
229
|
+
session.name,
|
|
230
|
+
json.dumps(session.state) if session.state else None,
|
|
231
|
+
json.dumps(session.metadata) if session.metadata else None,
|
|
232
|
+
session.updated_at,
|
|
233
|
+
session.session_id,
|
|
234
|
+
))
|
|
235
|
+
conn.commit()
|
|
236
|
+
return session
|
|
237
|
+
finally:
|
|
238
|
+
self._put_conn(conn)
|
|
239
|
+
|
|
240
|
+
def delete_session(self, session_id: str) -> bool:
|
|
241
|
+
"""Delete a session and all its messages."""
|
|
242
|
+
conn = self._get_conn()
|
|
243
|
+
try:
|
|
244
|
+
with conn.cursor() as cur:
|
|
245
|
+
cur.execute(f"""
|
|
246
|
+
DELETE FROM {self.sessions_table} WHERE session_id = %s
|
|
247
|
+
""", (session_id,))
|
|
248
|
+
deleted = cur.rowcount > 0
|
|
249
|
+
conn.commit()
|
|
250
|
+
return deleted
|
|
251
|
+
finally:
|
|
252
|
+
self._put_conn(conn)
|
|
253
|
+
|
|
254
|
+
def list_sessions(
|
|
255
|
+
self,
|
|
256
|
+
user_id: Optional[str] = None,
|
|
257
|
+
agent_id: Optional[str] = None,
|
|
258
|
+
limit: int = 100,
|
|
259
|
+
offset: int = 0
|
|
260
|
+
) -> List[ConversationSession]:
|
|
261
|
+
"""List sessions, optionally filtered by user or agent."""
|
|
262
|
+
conn = self._get_conn()
|
|
263
|
+
try:
|
|
264
|
+
with conn.cursor(cursor_factory=self._RealDictCursor) as cur:
|
|
265
|
+
conditions = []
|
|
266
|
+
params = []
|
|
267
|
+
|
|
268
|
+
if user_id:
|
|
269
|
+
conditions.append("user_id = %s")
|
|
270
|
+
params.append(user_id)
|
|
271
|
+
if agent_id:
|
|
272
|
+
conditions.append("agent_id = %s")
|
|
273
|
+
params.append(agent_id)
|
|
274
|
+
|
|
275
|
+
where_clause = ""
|
|
276
|
+
if conditions:
|
|
277
|
+
where_clause = "WHERE " + " AND ".join(conditions)
|
|
278
|
+
|
|
279
|
+
params.extend([limit, offset])
|
|
280
|
+
|
|
281
|
+
cur.execute(f"""
|
|
282
|
+
SELECT * FROM {self.sessions_table}
|
|
283
|
+
{where_clause}
|
|
284
|
+
ORDER BY updated_at DESC
|
|
285
|
+
LIMIT %s OFFSET %s
|
|
286
|
+
""", params)
|
|
287
|
+
|
|
288
|
+
return [
|
|
289
|
+
ConversationSession(
|
|
290
|
+
session_id=row["session_id"],
|
|
291
|
+
user_id=row["user_id"],
|
|
292
|
+
agent_id=row["agent_id"],
|
|
293
|
+
name=row["name"],
|
|
294
|
+
state=row["state"],
|
|
295
|
+
metadata=row["metadata"],
|
|
296
|
+
created_at=row["created_at"],
|
|
297
|
+
updated_at=row["updated_at"],
|
|
298
|
+
)
|
|
299
|
+
for row in cur.fetchall()
|
|
300
|
+
]
|
|
301
|
+
finally:
|
|
302
|
+
self._put_conn(conn)
|
|
303
|
+
|
|
304
|
+
def add_message(self, session_id: str, message: ConversationMessage) -> ConversationMessage:
|
|
305
|
+
"""Add a message to a session."""
|
|
306
|
+
message.session_id = session_id
|
|
307
|
+
conn = self._get_conn()
|
|
308
|
+
try:
|
|
309
|
+
with conn.cursor() as cur:
|
|
310
|
+
cur.execute(f"""
|
|
311
|
+
INSERT INTO {self.messages_table}
|
|
312
|
+
(id, session_id, role, content, tool_calls, tool_call_id, metadata, created_at)
|
|
313
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
314
|
+
""", (
|
|
315
|
+
message.id,
|
|
316
|
+
session_id,
|
|
317
|
+
message.role,
|
|
318
|
+
message.content,
|
|
319
|
+
json.dumps(message.tool_calls) if message.tool_calls else None,
|
|
320
|
+
message.tool_call_id,
|
|
321
|
+
json.dumps(message.metadata) if message.metadata else None,
|
|
322
|
+
message.created_at,
|
|
323
|
+
))
|
|
324
|
+
conn.commit()
|
|
325
|
+
return message
|
|
326
|
+
finally:
|
|
327
|
+
self._put_conn(conn)
|
|
328
|
+
|
|
329
|
+
def get_messages(
|
|
330
|
+
self,
|
|
331
|
+
session_id: str,
|
|
332
|
+
limit: Optional[int] = None,
|
|
333
|
+
before: Optional[float] = None,
|
|
334
|
+
after: Optional[float] = None
|
|
335
|
+
) -> List[ConversationMessage]:
|
|
336
|
+
"""Get messages from a session."""
|
|
337
|
+
conn = self._get_conn()
|
|
338
|
+
try:
|
|
339
|
+
with conn.cursor(cursor_factory=self._RealDictCursor) as cur:
|
|
340
|
+
conditions = ["session_id = %s"]
|
|
341
|
+
params: List[Any] = [session_id]
|
|
342
|
+
|
|
343
|
+
if before:
|
|
344
|
+
conditions.append("created_at < %s")
|
|
345
|
+
params.append(before)
|
|
346
|
+
if after:
|
|
347
|
+
conditions.append("created_at > %s")
|
|
348
|
+
params.append(after)
|
|
349
|
+
|
|
350
|
+
where_clause = "WHERE " + " AND ".join(conditions)
|
|
351
|
+
limit_clause = f"LIMIT {limit}" if limit else ""
|
|
352
|
+
|
|
353
|
+
cur.execute(f"""
|
|
354
|
+
SELECT * FROM {self.messages_table}
|
|
355
|
+
{where_clause}
|
|
356
|
+
ORDER BY created_at ASC
|
|
357
|
+
{limit_clause}
|
|
358
|
+
""", params)
|
|
359
|
+
|
|
360
|
+
return [
|
|
361
|
+
ConversationMessage(
|
|
362
|
+
id=row["id"],
|
|
363
|
+
session_id=row["session_id"],
|
|
364
|
+
role=row["role"],
|
|
365
|
+
content=row["content"],
|
|
366
|
+
tool_calls=row["tool_calls"],
|
|
367
|
+
tool_call_id=row["tool_call_id"],
|
|
368
|
+
metadata=row["metadata"],
|
|
369
|
+
created_at=row["created_at"],
|
|
370
|
+
)
|
|
371
|
+
for row in cur.fetchall()
|
|
372
|
+
]
|
|
373
|
+
finally:
|
|
374
|
+
self._put_conn(conn)
|
|
375
|
+
|
|
376
|
+
def delete_messages(self, session_id: str, message_ids: Optional[List[str]] = None) -> int:
|
|
377
|
+
"""Delete messages. If message_ids is None, delete all messages in session."""
|
|
378
|
+
conn = self._get_conn()
|
|
379
|
+
try:
|
|
380
|
+
with conn.cursor() as cur:
|
|
381
|
+
if message_ids:
|
|
382
|
+
placeholders = ",".join(["%s"] * len(message_ids))
|
|
383
|
+
cur.execute(f"""
|
|
384
|
+
DELETE FROM {self.messages_table}
|
|
385
|
+
WHERE session_id = %s AND id IN ({placeholders})
|
|
386
|
+
""", [session_id] + message_ids)
|
|
387
|
+
else:
|
|
388
|
+
cur.execute(f"""
|
|
389
|
+
DELETE FROM {self.messages_table} WHERE session_id = %s
|
|
390
|
+
""", (session_id,))
|
|
391
|
+
deleted = cur.rowcount
|
|
392
|
+
conn.commit()
|
|
393
|
+
return deleted
|
|
394
|
+
finally:
|
|
395
|
+
self._put_conn(conn)
|
|
396
|
+
|
|
397
|
+
def close(self) -> None:
|
|
398
|
+
"""Close the store and release resources."""
|
|
399
|
+
if self._pool:
|
|
400
|
+
self._pool.closeall()
|
|
401
|
+
self._pool = None
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SingleStore implementation of ConversationStore.
|
|
3
|
+
|
|
4
|
+
Requires: singlestoredb
|
|
5
|
+
Install: pip install singlestoredb
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, List, Optional
|
|
11
|
+
|
|
12
|
+
from .base import ConversationStore, ConversationSession, ConversationMessage
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SingleStoreConversationStore(ConversationStore):
|
|
18
|
+
"""
|
|
19
|
+
SingleStore-based conversation store.
|
|
20
|
+
|
|
21
|
+
SingleStore is MySQL-compatible with additional vector capabilities.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
store = SingleStoreConversationStore(
|
|
25
|
+
url="singlestoredb://user:password@host:3306/database"
|
|
26
|
+
)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
SCHEMA_VERSION = "1.0.0"
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
url: Optional[str] = None,
|
|
34
|
+
host: str = "localhost",
|
|
35
|
+
port: int = 3306,
|
|
36
|
+
database: str = "praisonai",
|
|
37
|
+
user: str = "root",
|
|
38
|
+
password: str = "",
|
|
39
|
+
table_prefix: str = "praison_",
|
|
40
|
+
auto_create_tables: bool = True,
|
|
41
|
+
):
|
|
42
|
+
try:
|
|
43
|
+
import singlestoredb as s2
|
|
44
|
+
except ImportError:
|
|
45
|
+
raise ImportError(
|
|
46
|
+
"singlestoredb is required for SingleStore support. "
|
|
47
|
+
"Install with: pip install singlestoredb"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self._s2 = s2
|
|
51
|
+
self.table_prefix = table_prefix
|
|
52
|
+
self.sessions_table = f"{table_prefix}sessions"
|
|
53
|
+
self.messages_table = f"{table_prefix}messages"
|
|
54
|
+
|
|
55
|
+
if url:
|
|
56
|
+
self._conn = s2.connect(url)
|
|
57
|
+
else:
|
|
58
|
+
self._conn = s2.connect(
|
|
59
|
+
host=host,
|
|
60
|
+
port=port,
|
|
61
|
+
database=database,
|
|
62
|
+
user=user,
|
|
63
|
+
password=password,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if auto_create_tables:
|
|
67
|
+
self._create_tables()
|
|
68
|
+
|
|
69
|
+
def _create_tables(self) -> None:
|
|
70
|
+
"""Create tables if they don't exist."""
|
|
71
|
+
cur = self._conn.cursor()
|
|
72
|
+
|
|
73
|
+
cur.execute(f"""
|
|
74
|
+
CREATE TABLE IF NOT EXISTS {self.sessions_table} (
|
|
75
|
+
session_id VARCHAR(255) PRIMARY KEY,
|
|
76
|
+
user_id VARCHAR(255),
|
|
77
|
+
agent_id VARCHAR(255),
|
|
78
|
+
name VARCHAR(255),
|
|
79
|
+
state JSON,
|
|
80
|
+
metadata JSON,
|
|
81
|
+
created_at DOUBLE,
|
|
82
|
+
updated_at DOUBLE,
|
|
83
|
+
KEY idx_user (user_id),
|
|
84
|
+
KEY idx_agent (agent_id)
|
|
85
|
+
)
|
|
86
|
+
""")
|
|
87
|
+
|
|
88
|
+
cur.execute(f"""
|
|
89
|
+
CREATE TABLE IF NOT EXISTS {self.messages_table} (
|
|
90
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
91
|
+
session_id VARCHAR(255) NOT NULL,
|
|
92
|
+
role VARCHAR(50) NOT NULL,
|
|
93
|
+
content TEXT,
|
|
94
|
+
tool_calls JSON,
|
|
95
|
+
tool_call_id VARCHAR(255),
|
|
96
|
+
metadata JSON,
|
|
97
|
+
created_at DOUBLE,
|
|
98
|
+
KEY idx_session (session_id),
|
|
99
|
+
KEY idx_created (session_id, created_at)
|
|
100
|
+
)
|
|
101
|
+
""")
|
|
102
|
+
|
|
103
|
+
self._conn.commit()
|
|
104
|
+
logger.info(f"SingleStore tables created")
|
|
105
|
+
|
|
106
|
+
def create_session(self, session: ConversationSession) -> ConversationSession:
|
|
107
|
+
cur = self._conn.cursor()
|
|
108
|
+
cur.execute(f"""
|
|
109
|
+
INSERT INTO {self.sessions_table}
|
|
110
|
+
(session_id, user_id, agent_id, name, state, metadata, created_at, updated_at)
|
|
111
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
112
|
+
""", (
|
|
113
|
+
session.session_id, session.user_id, session.agent_id, session.name,
|
|
114
|
+
json.dumps(session.state) if session.state else None,
|
|
115
|
+
json.dumps(session.metadata) if session.metadata else None,
|
|
116
|
+
session.created_at, session.updated_at,
|
|
117
|
+
))
|
|
118
|
+
self._conn.commit()
|
|
119
|
+
return session
|
|
120
|
+
|
|
121
|
+
def get_session(self, session_id: str) -> Optional[ConversationSession]:
|
|
122
|
+
cur = self._conn.cursor()
|
|
123
|
+
cur.execute(f"SELECT * FROM {self.sessions_table} WHERE session_id = %s", (session_id,))
|
|
124
|
+
row = cur.fetchone()
|
|
125
|
+
if not row:
|
|
126
|
+
return None
|
|
127
|
+
cols = [d[0] for d in cur.description]
|
|
128
|
+
data = dict(zip(cols, row))
|
|
129
|
+
return ConversationSession(
|
|
130
|
+
session_id=data["session_id"], user_id=data["user_id"],
|
|
131
|
+
agent_id=data["agent_id"], name=data["name"],
|
|
132
|
+
state=json.loads(data["state"]) if data["state"] else None,
|
|
133
|
+
metadata=json.loads(data["metadata"]) if data["metadata"] else None,
|
|
134
|
+
created_at=data["created_at"], updated_at=data["updated_at"],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def update_session(self, session: ConversationSession) -> ConversationSession:
|
|
138
|
+
cur = self._conn.cursor()
|
|
139
|
+
cur.execute(f"""
|
|
140
|
+
UPDATE {self.sessions_table}
|
|
141
|
+
SET user_id=%s, agent_id=%s, name=%s, state=%s, metadata=%s, updated_at=%s
|
|
142
|
+
WHERE session_id=%s
|
|
143
|
+
""", (
|
|
144
|
+
session.user_id, session.agent_id, session.name,
|
|
145
|
+
json.dumps(session.state) if session.state else None,
|
|
146
|
+
json.dumps(session.metadata) if session.metadata else None,
|
|
147
|
+
session.updated_at, session.session_id,
|
|
148
|
+
))
|
|
149
|
+
self._conn.commit()
|
|
150
|
+
return session
|
|
151
|
+
|
|
152
|
+
def delete_session(self, session_id: str) -> bool:
|
|
153
|
+
cur = self._conn.cursor()
|
|
154
|
+
cur.execute(f"DELETE FROM {self.messages_table} WHERE session_id = %s", (session_id,))
|
|
155
|
+
cur.execute(f"DELETE FROM {self.sessions_table} WHERE session_id = %s", (session_id,))
|
|
156
|
+
self._conn.commit()
|
|
157
|
+
return cur.rowcount > 0
|
|
158
|
+
|
|
159
|
+
def list_sessions(self, user_id: Optional[str] = None, agent_id: Optional[str] = None,
|
|
160
|
+
limit: int = 100, offset: int = 0) -> List[ConversationSession]:
|
|
161
|
+
cur = self._conn.cursor()
|
|
162
|
+
conditions, params = [], []
|
|
163
|
+
if user_id:
|
|
164
|
+
conditions.append("user_id = %s")
|
|
165
|
+
params.append(user_id)
|
|
166
|
+
if agent_id:
|
|
167
|
+
conditions.append("agent_id = %s")
|
|
168
|
+
params.append(agent_id)
|
|
169
|
+
where = "WHERE " + " AND ".join(conditions) if conditions else ""
|
|
170
|
+
params.extend([limit, offset])
|
|
171
|
+
cur.execute(f"SELECT * FROM {self.sessions_table} {where} ORDER BY updated_at DESC LIMIT %s OFFSET %s", params)
|
|
172
|
+
cols = [d[0] for d in cur.description]
|
|
173
|
+
return [ConversationSession(
|
|
174
|
+
session_id=dict(zip(cols, r))["session_id"],
|
|
175
|
+
user_id=dict(zip(cols, r))["user_id"],
|
|
176
|
+
agent_id=dict(zip(cols, r))["agent_id"],
|
|
177
|
+
name=dict(zip(cols, r))["name"],
|
|
178
|
+
state=json.loads(dict(zip(cols, r))["state"]) if dict(zip(cols, r))["state"] else None,
|
|
179
|
+
metadata=json.loads(dict(zip(cols, r))["metadata"]) if dict(zip(cols, r))["metadata"] else None,
|
|
180
|
+
created_at=dict(zip(cols, r))["created_at"],
|
|
181
|
+
updated_at=dict(zip(cols, r))["updated_at"],
|
|
182
|
+
) for r in cur.fetchall()]
|
|
183
|
+
|
|
184
|
+
def add_message(self, session_id: str, message: ConversationMessage) -> ConversationMessage:
|
|
185
|
+
message.session_id = session_id
|
|
186
|
+
cur = self._conn.cursor()
|
|
187
|
+
cur.execute(f"""
|
|
188
|
+
INSERT INTO {self.messages_table}
|
|
189
|
+
(id, session_id, role, content, tool_calls, tool_call_id, metadata, created_at)
|
|
190
|
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
191
|
+
""", (
|
|
192
|
+
message.id, session_id, message.role, message.content,
|
|
193
|
+
json.dumps(message.tool_calls) if message.tool_calls else None,
|
|
194
|
+
message.tool_call_id,
|
|
195
|
+
json.dumps(message.metadata) if message.metadata else None,
|
|
196
|
+
message.created_at,
|
|
197
|
+
))
|
|
198
|
+
self._conn.commit()
|
|
199
|
+
return message
|
|
200
|
+
|
|
201
|
+
def get_messages(self, session_id: str, limit: Optional[int] = None,
|
|
202
|
+
before: Optional[float] = None, after: Optional[float] = None) -> List[ConversationMessage]:
|
|
203
|
+
cur = self._conn.cursor()
|
|
204
|
+
conditions, params = ["session_id = %s"], [session_id]
|
|
205
|
+
if before:
|
|
206
|
+
conditions.append("created_at < %s")
|
|
207
|
+
params.append(before)
|
|
208
|
+
if after:
|
|
209
|
+
conditions.append("created_at > %s")
|
|
210
|
+
params.append(after)
|
|
211
|
+
where = "WHERE " + " AND ".join(conditions)
|
|
212
|
+
limit_clause = f"LIMIT {limit}" if limit else ""
|
|
213
|
+
cur.execute(f"SELECT * FROM {self.messages_table} {where} ORDER BY created_at ASC {limit_clause}", params)
|
|
214
|
+
cols = [d[0] for d in cur.description]
|
|
215
|
+
return [ConversationMessage(
|
|
216
|
+
id=dict(zip(cols, r))["id"],
|
|
217
|
+
session_id=dict(zip(cols, r))["session_id"],
|
|
218
|
+
role=dict(zip(cols, r))["role"],
|
|
219
|
+
content=dict(zip(cols, r))["content"],
|
|
220
|
+
tool_calls=json.loads(dict(zip(cols, r))["tool_calls"]) if dict(zip(cols, r))["tool_calls"] else None,
|
|
221
|
+
tool_call_id=dict(zip(cols, r))["tool_call_id"],
|
|
222
|
+
metadata=json.loads(dict(zip(cols, r))["metadata"]) if dict(zip(cols, r))["metadata"] else None,
|
|
223
|
+
created_at=dict(zip(cols, r))["created_at"],
|
|
224
|
+
) for r in cur.fetchall()]
|
|
225
|
+
|
|
226
|
+
def delete_messages(self, session_id: str, message_ids: Optional[List[str]] = None) -> int:
|
|
227
|
+
cur = self._conn.cursor()
|
|
228
|
+
if message_ids:
|
|
229
|
+
placeholders = ",".join(["%s"] * len(message_ids))
|
|
230
|
+
cur.execute(f"DELETE FROM {self.messages_table} WHERE session_id = %s AND id IN ({placeholders})",
|
|
231
|
+
[session_id] + message_ids)
|
|
232
|
+
else:
|
|
233
|
+
cur.execute(f"DELETE FROM {self.messages_table} WHERE session_id = %s", (session_id,))
|
|
234
|
+
self._conn.commit()
|
|
235
|
+
return cur.rowcount
|
|
236
|
+
|
|
237
|
+
def close(self) -> None:
|
|
238
|
+
if self._conn:
|
|
239
|
+
self._conn.close()
|
|
240
|
+
self._conn = None
|