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,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent hooks for automatic persistence integration.
|
|
3
|
+
|
|
4
|
+
Provides wrapper functions to add persistence capabilities to PraisonAI agents
|
|
5
|
+
without modifying the core SDK.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional, Callable
|
|
12
|
+
from functools import wraps
|
|
13
|
+
|
|
14
|
+
from ..orchestrator import PersistenceOrchestrator
|
|
15
|
+
from ..conversation.base import ConversationMessage, ConversationSession
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def wrap_agent_with_persistence(
|
|
21
|
+
agent,
|
|
22
|
+
orchestrator: PersistenceOrchestrator,
|
|
23
|
+
session_id: Optional[str] = None,
|
|
24
|
+
user_id: Optional[str] = None,
|
|
25
|
+
auto_resume: bool = True,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Wrap an agent to add automatic persistence hooks.
|
|
29
|
+
|
|
30
|
+
This function wraps the agent's chat method to automatically:
|
|
31
|
+
1. Load/create session on first message
|
|
32
|
+
2. Persist each user message
|
|
33
|
+
3. Persist each assistant response
|
|
34
|
+
4. Update session metadata after each interaction
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
agent: PraisonAI Agent instance
|
|
38
|
+
orchestrator: PersistenceOrchestrator instance
|
|
39
|
+
session_id: Session ID (auto-generated if None)
|
|
40
|
+
user_id: User ID for the session
|
|
41
|
+
auto_resume: Whether to auto-resume existing session
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The wrapped agent with persistence capabilities
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
from praisonai.persistence import PersistenceOrchestrator
|
|
48
|
+
from praisonai.persistence.hooks import wrap_agent_with_persistence
|
|
49
|
+
from praisonaiagents import Agent
|
|
50
|
+
|
|
51
|
+
orchestrator = PersistenceOrchestrator(...)
|
|
52
|
+
agent = Agent(name="Assistant", role="Helper")
|
|
53
|
+
|
|
54
|
+
# Wrap agent with persistence
|
|
55
|
+
agent = wrap_agent_with_persistence(agent, orchestrator, session_id="my-session")
|
|
56
|
+
|
|
57
|
+
# Now all chats are automatically persisted
|
|
58
|
+
response = agent.chat("Hello!")
|
|
59
|
+
"""
|
|
60
|
+
session_id = session_id or f"session-{uuid.uuid4().hex[:8]}"
|
|
61
|
+
user_id = user_id or getattr(agent, 'user_id', 'default')
|
|
62
|
+
|
|
63
|
+
# Store original chat method
|
|
64
|
+
original_chat = agent.chat
|
|
65
|
+
_session_initialized = [False] # Use list to allow mutation in closure
|
|
66
|
+
|
|
67
|
+
@wraps(original_chat)
|
|
68
|
+
def chat_with_persistence(prompt, *args, **kwargs):
|
|
69
|
+
nonlocal _session_initialized
|
|
70
|
+
|
|
71
|
+
# Initialize session on first call
|
|
72
|
+
if not _session_initialized[0]:
|
|
73
|
+
history = orchestrator.on_agent_start(
|
|
74
|
+
agent,
|
|
75
|
+
session_id=session_id,
|
|
76
|
+
user_id=user_id,
|
|
77
|
+
resume=auto_resume
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Inject history into agent's chat_history if resuming
|
|
81
|
+
if auto_resume and history:
|
|
82
|
+
agent.chat_history = [
|
|
83
|
+
{"role": msg.role, "content": msg.content}
|
|
84
|
+
for msg in history
|
|
85
|
+
]
|
|
86
|
+
logger.info(f"Resumed session {session_id} with {len(history)} messages")
|
|
87
|
+
|
|
88
|
+
_session_initialized[0] = True
|
|
89
|
+
|
|
90
|
+
# Persist user message
|
|
91
|
+
orchestrator.on_message(session_id, "user", prompt if isinstance(prompt, str) else str(prompt))
|
|
92
|
+
|
|
93
|
+
# Call original chat
|
|
94
|
+
response = original_chat(prompt, *args, **kwargs)
|
|
95
|
+
|
|
96
|
+
# Persist assistant response
|
|
97
|
+
if response:
|
|
98
|
+
orchestrator.on_message(session_id, "assistant", response)
|
|
99
|
+
|
|
100
|
+
return response
|
|
101
|
+
|
|
102
|
+
# Replace chat method
|
|
103
|
+
agent.chat = chat_with_persistence
|
|
104
|
+
|
|
105
|
+
# Add persistence-related methods to agent
|
|
106
|
+
agent._persistence_orchestrator = orchestrator
|
|
107
|
+
agent._persistence_session_id = session_id
|
|
108
|
+
|
|
109
|
+
def get_session():
|
|
110
|
+
"""Get the current session."""
|
|
111
|
+
return orchestrator.get_session(session_id)
|
|
112
|
+
|
|
113
|
+
def get_messages(limit: Optional[int] = None):
|
|
114
|
+
"""Get messages from the current session."""
|
|
115
|
+
return orchestrator.get_messages(session_id, limit=limit)
|
|
116
|
+
|
|
117
|
+
def end_session():
|
|
118
|
+
"""End the current session."""
|
|
119
|
+
orchestrator.on_agent_end(agent, session_id)
|
|
120
|
+
|
|
121
|
+
agent.get_session = get_session
|
|
122
|
+
agent.get_messages = get_messages
|
|
123
|
+
agent.end_session = end_session
|
|
124
|
+
|
|
125
|
+
return agent
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class PersistentAgent:
|
|
129
|
+
"""
|
|
130
|
+
A wrapper class that provides persistence capabilities for any agent.
|
|
131
|
+
|
|
132
|
+
This is an alternative to wrap_agent_with_persistence that uses composition
|
|
133
|
+
instead of monkey-patching.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
from praisonai.persistence import PersistenceOrchestrator
|
|
137
|
+
from praisonai.persistence.hooks import PersistentAgent
|
|
138
|
+
from praisonaiagents import Agent
|
|
139
|
+
|
|
140
|
+
orchestrator = PersistenceOrchestrator(...)
|
|
141
|
+
base_agent = Agent(name="Assistant", role="Helper")
|
|
142
|
+
|
|
143
|
+
agent = PersistentAgent(base_agent, orchestrator, session_id="my-session")
|
|
144
|
+
response = agent.chat("Hello!")
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
agent,
|
|
150
|
+
orchestrator: PersistenceOrchestrator,
|
|
151
|
+
session_id: Optional[str] = None,
|
|
152
|
+
user_id: Optional[str] = None,
|
|
153
|
+
auto_resume: bool = True,
|
|
154
|
+
):
|
|
155
|
+
self._agent = agent
|
|
156
|
+
self._orchestrator = orchestrator
|
|
157
|
+
self._session_id = session_id or f"session-{uuid.uuid4().hex[:8]}"
|
|
158
|
+
self._user_id = user_id or getattr(agent, 'user_id', 'default')
|
|
159
|
+
self._auto_resume = auto_resume
|
|
160
|
+
self._session_initialized = False
|
|
161
|
+
|
|
162
|
+
def __getattr__(self, name):
|
|
163
|
+
"""Delegate attribute access to wrapped agent."""
|
|
164
|
+
return getattr(self._agent, name)
|
|
165
|
+
|
|
166
|
+
def _ensure_session(self):
|
|
167
|
+
"""Ensure session is initialized."""
|
|
168
|
+
if not self._session_initialized:
|
|
169
|
+
history = self._orchestrator.on_agent_start(
|
|
170
|
+
self._agent,
|
|
171
|
+
session_id=self._session_id,
|
|
172
|
+
user_id=self._user_id,
|
|
173
|
+
resume=self._auto_resume
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if self._auto_resume and history:
|
|
177
|
+
self._agent.chat_history = [
|
|
178
|
+
{"role": msg.role, "content": msg.content}
|
|
179
|
+
for msg in history
|
|
180
|
+
]
|
|
181
|
+
logger.info(f"Resumed session {self._session_id} with {len(history)} messages")
|
|
182
|
+
|
|
183
|
+
self._session_initialized = True
|
|
184
|
+
|
|
185
|
+
def chat(self, prompt, *args, **kwargs):
|
|
186
|
+
"""Chat with automatic persistence."""
|
|
187
|
+
self._ensure_session()
|
|
188
|
+
|
|
189
|
+
# Persist user message
|
|
190
|
+
self._orchestrator.on_message(
|
|
191
|
+
self._session_id, "user",
|
|
192
|
+
prompt if isinstance(prompt, str) else str(prompt)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Call agent chat
|
|
196
|
+
response = self._agent.chat(prompt, *args, **kwargs)
|
|
197
|
+
|
|
198
|
+
# Persist assistant response
|
|
199
|
+
if response:
|
|
200
|
+
self._orchestrator.on_message(self._session_id, "assistant", response)
|
|
201
|
+
|
|
202
|
+
return response
|
|
203
|
+
|
|
204
|
+
def get_session(self) -> Optional[ConversationSession]:
|
|
205
|
+
"""Get the current session."""
|
|
206
|
+
return self._orchestrator.get_session(self._session_id)
|
|
207
|
+
|
|
208
|
+
def get_messages(self, limit: Optional[int] = None) -> List[ConversationMessage]:
|
|
209
|
+
"""Get messages from the current session."""
|
|
210
|
+
return self._orchestrator.get_messages(self._session_id, limit=limit)
|
|
211
|
+
|
|
212
|
+
def end_session(self):
|
|
213
|
+
"""End the current session."""
|
|
214
|
+
self._orchestrator.on_agent_end(self._agent, self._session_id)
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def session_id(self) -> str:
|
|
218
|
+
"""Get the session ID."""
|
|
219
|
+
return self._session_id
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def create_persistent_session(
|
|
223
|
+
orchestrator: PersistenceOrchestrator,
|
|
224
|
+
session_id: Optional[str] = None,
|
|
225
|
+
user_id: Optional[str] = None,
|
|
226
|
+
):
|
|
227
|
+
"""
|
|
228
|
+
Create a context manager for persistent sessions.
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
from praisonai.persistence import PersistenceOrchestrator
|
|
232
|
+
from praisonai.persistence.hooks import create_persistent_session
|
|
233
|
+
from praisonaiagents import Agent
|
|
234
|
+
|
|
235
|
+
orchestrator = PersistenceOrchestrator(...)
|
|
236
|
+
|
|
237
|
+
with create_persistent_session(orchestrator, session_id="my-session") as session:
|
|
238
|
+
agent = Agent(name="Assistant", role="Helper")
|
|
239
|
+
|
|
240
|
+
# Messages are persisted within the session context
|
|
241
|
+
session.persist_message("user", "Hello!")
|
|
242
|
+
response = agent.chat("Hello!")
|
|
243
|
+
session.persist_message("assistant", response)
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
class PersistentSession:
|
|
247
|
+
def __init__(self, orchestrator, session_id, user_id):
|
|
248
|
+
self.orchestrator = orchestrator
|
|
249
|
+
self.session_id = session_id or f"session-{uuid.uuid4().hex[:8]}"
|
|
250
|
+
self.user_id = user_id or "default"
|
|
251
|
+
self._started = False
|
|
252
|
+
|
|
253
|
+
def __enter__(self):
|
|
254
|
+
self.start()
|
|
255
|
+
return self
|
|
256
|
+
|
|
257
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
258
|
+
self.end()
|
|
259
|
+
return False
|
|
260
|
+
|
|
261
|
+
def start(self, agent=None):
|
|
262
|
+
"""Start the session."""
|
|
263
|
+
if not self._started:
|
|
264
|
+
class MockAgent:
|
|
265
|
+
name = "session-agent"
|
|
266
|
+
|
|
267
|
+
self.orchestrator.on_agent_start(
|
|
268
|
+
agent or MockAgent(),
|
|
269
|
+
session_id=self.session_id,
|
|
270
|
+
user_id=self.user_id
|
|
271
|
+
)
|
|
272
|
+
self._started = True
|
|
273
|
+
|
|
274
|
+
def end(self, agent=None):
|
|
275
|
+
"""End the session."""
|
|
276
|
+
if self._started:
|
|
277
|
+
class MockAgent:
|
|
278
|
+
name = "session-agent"
|
|
279
|
+
|
|
280
|
+
self.orchestrator.on_agent_end(
|
|
281
|
+
agent or MockAgent(),
|
|
282
|
+
self.session_id
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def persist_message(self, role: str, content: str):
|
|
286
|
+
"""Persist a message to the session."""
|
|
287
|
+
self.orchestrator.on_message(self.session_id, role, content)
|
|
288
|
+
|
|
289
|
+
def get_messages(self, limit: Optional[int] = None):
|
|
290
|
+
"""Get messages from the session."""
|
|
291
|
+
return self.orchestrator.get_messages(self.session_id, limit=limit)
|
|
292
|
+
|
|
293
|
+
def get_session(self):
|
|
294
|
+
"""Get the session object."""
|
|
295
|
+
return self.orchestrator.get_session(self.session_id)
|
|
296
|
+
|
|
297
|
+
return PersistentSession(orchestrator, session_id, user_id)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
KnowledgeStore implementations for vector embeddings and semantic search.
|
|
3
|
+
|
|
4
|
+
Supported backends:
|
|
5
|
+
- Qdrant (qdrant)
|
|
6
|
+
- Pinecone (pinecone)
|
|
7
|
+
- ChromaDB (chroma)
|
|
8
|
+
- Weaviate (weaviate)
|
|
9
|
+
- LanceDB (lancedb)
|
|
10
|
+
- Milvus (milvus)
|
|
11
|
+
- PGVector (pgvector)
|
|
12
|
+
- Redis Vector (redis)
|
|
13
|
+
- Cassandra Vector (cassandra)
|
|
14
|
+
- ClickHouse Vector (clickhouse)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"KnowledgeStore",
|
|
19
|
+
"KnowledgeDocument",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
def __getattr__(name: str):
|
|
23
|
+
if name in ("KnowledgeStore", "KnowledgeDocument"):
|
|
24
|
+
from .base import KnowledgeStore, KnowledgeDocument
|
|
25
|
+
return locals()[name]
|
|
26
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base interfaces for KnowledgeStore.
|
|
3
|
+
|
|
4
|
+
KnowledgeStore handles vector embeddings and semantic search for RAG.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
import hashlib
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class KnowledgeDocument:
|
|
17
|
+
"""A document with vector embedding for semantic search."""
|
|
18
|
+
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
19
|
+
content: str = ""
|
|
20
|
+
embedding: Optional[List[float]] = None
|
|
21
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
22
|
+
content_hash: Optional[str] = None
|
|
23
|
+
created_at: float = field(default_factory=time.time)
|
|
24
|
+
|
|
25
|
+
def __post_init__(self):
|
|
26
|
+
if self.content and not self.content_hash:
|
|
27
|
+
self.content_hash = hashlib.sha256(self.content.encode()).hexdigest()[:16]
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
30
|
+
return {
|
|
31
|
+
"id": self.id,
|
|
32
|
+
"content": self.content,
|
|
33
|
+
"embedding": self.embedding,
|
|
34
|
+
"metadata": self.metadata,
|
|
35
|
+
"content_hash": self.content_hash,
|
|
36
|
+
"created_at": self.created_at,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_dict(cls, data: Dict[str, Any]) -> "KnowledgeDocument":
|
|
41
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class KnowledgeStore(ABC):
|
|
45
|
+
"""
|
|
46
|
+
Abstract base class for vector/knowledge persistence.
|
|
47
|
+
|
|
48
|
+
Implementations handle document storage and semantic search for different backends:
|
|
49
|
+
- Qdrant, Pinecone, ChromaDB, Weaviate (dedicated vector DBs)
|
|
50
|
+
- LanceDB, Milvus (embedded/distributed vector DBs)
|
|
51
|
+
- PGVector, Redis, Cassandra, ClickHouse (vector extensions)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def create_collection(
|
|
56
|
+
self,
|
|
57
|
+
name: str,
|
|
58
|
+
dimension: int,
|
|
59
|
+
distance: str = "cosine",
|
|
60
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Create a new collection/index for vectors."""
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def delete_collection(self, name: str) -> bool:
|
|
67
|
+
"""Delete a collection and all its documents."""
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def collection_exists(self, name: str) -> bool:
|
|
72
|
+
"""Check if a collection exists."""
|
|
73
|
+
raise NotImplementedError
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def list_collections(self) -> List[str]:
|
|
77
|
+
"""List all collections."""
|
|
78
|
+
raise NotImplementedError
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def insert(
|
|
82
|
+
self,
|
|
83
|
+
collection: str,
|
|
84
|
+
documents: List[KnowledgeDocument]
|
|
85
|
+
) -> List[str]:
|
|
86
|
+
"""Insert documents into a collection. Returns list of IDs."""
|
|
87
|
+
raise NotImplementedError
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def upsert(
|
|
91
|
+
self,
|
|
92
|
+
collection: str,
|
|
93
|
+
documents: List[KnowledgeDocument]
|
|
94
|
+
) -> List[str]:
|
|
95
|
+
"""Insert or update documents. Returns list of IDs."""
|
|
96
|
+
raise NotImplementedError
|
|
97
|
+
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def search(
|
|
100
|
+
self,
|
|
101
|
+
collection: str,
|
|
102
|
+
query_embedding: List[float],
|
|
103
|
+
limit: int = 5,
|
|
104
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
105
|
+
score_threshold: Optional[float] = None
|
|
106
|
+
) -> List[KnowledgeDocument]:
|
|
107
|
+
"""Search for similar documents by embedding."""
|
|
108
|
+
raise NotImplementedError
|
|
109
|
+
|
|
110
|
+
@abstractmethod
|
|
111
|
+
def get(
|
|
112
|
+
self,
|
|
113
|
+
collection: str,
|
|
114
|
+
ids: List[str]
|
|
115
|
+
) -> List[KnowledgeDocument]:
|
|
116
|
+
"""Get documents by IDs."""
|
|
117
|
+
raise NotImplementedError
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def delete(
|
|
121
|
+
self,
|
|
122
|
+
collection: str,
|
|
123
|
+
ids: Optional[List[str]] = None,
|
|
124
|
+
filters: Optional[Dict[str, Any]] = None
|
|
125
|
+
) -> int:
|
|
126
|
+
"""Delete documents by IDs or filters. Returns count deleted."""
|
|
127
|
+
raise NotImplementedError
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def count(self, collection: str) -> int:
|
|
131
|
+
"""Count documents in a collection."""
|
|
132
|
+
raise NotImplementedError
|
|
133
|
+
|
|
134
|
+
@abstractmethod
|
|
135
|
+
def close(self) -> None:
|
|
136
|
+
"""Close the store and release resources."""
|
|
137
|
+
raise NotImplementedError
|
|
138
|
+
|
|
139
|
+
def __enter__(self):
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
143
|
+
self.close()
|
|
144
|
+
return False
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cassandra Vector implementation of KnowledgeStore.
|
|
3
|
+
|
|
4
|
+
Requires: cassandra-driver
|
|
5
|
+
Install: pip install cassandra-driver
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from .base import KnowledgeStore, KnowledgeDocument
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CassandraKnowledgeStore(KnowledgeStore):
|
|
17
|
+
"""
|
|
18
|
+
Cassandra-based knowledge store using vector search (SAI).
|
|
19
|
+
|
|
20
|
+
Requires Cassandra 5.0+ or DataStax Astra with vector search.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
store = CassandraKnowledgeStore(
|
|
24
|
+
hosts=["localhost"],
|
|
25
|
+
keyspace="praisonai"
|
|
26
|
+
)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
hosts: Optional[List[str]] = None,
|
|
32
|
+
keyspace: str = "praisonai",
|
|
33
|
+
username: Optional[str] = None,
|
|
34
|
+
password: Optional[str] = None,
|
|
35
|
+
port: int = 9042,
|
|
36
|
+
secure_connect_bundle: Optional[str] = None,
|
|
37
|
+
):
|
|
38
|
+
try:
|
|
39
|
+
from cassandra.cluster import Cluster
|
|
40
|
+
from cassandra.auth import PlainTextAuthProvider
|
|
41
|
+
except ImportError:
|
|
42
|
+
raise ImportError(
|
|
43
|
+
"cassandra-driver is required for Cassandra support. "
|
|
44
|
+
"Install with: pip install cassandra-driver"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self._Cluster = Cluster
|
|
48
|
+
self.keyspace = keyspace
|
|
49
|
+
|
|
50
|
+
auth = None
|
|
51
|
+
if username and password:
|
|
52
|
+
auth = PlainTextAuthProvider(username=username, password=password)
|
|
53
|
+
|
|
54
|
+
if secure_connect_bundle:
|
|
55
|
+
# Astra DB connection
|
|
56
|
+
from cassandra.cluster import Cluster
|
|
57
|
+
cloud_config = {"secure_connect_bundle": secure_connect_bundle}
|
|
58
|
+
self._cluster = Cluster(cloud=cloud_config, auth_provider=auth)
|
|
59
|
+
else:
|
|
60
|
+
hosts = hosts or ["localhost"]
|
|
61
|
+
self._cluster = Cluster(hosts, port=port, auth_provider=auth)
|
|
62
|
+
|
|
63
|
+
self._session = self._cluster.connect()
|
|
64
|
+
|
|
65
|
+
# Create keyspace if not exists
|
|
66
|
+
self._session.execute(f"""
|
|
67
|
+
CREATE KEYSPACE IF NOT EXISTS {keyspace}
|
|
68
|
+
WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 1}}
|
|
69
|
+
""")
|
|
70
|
+
self._session.set_keyspace(keyspace)
|
|
71
|
+
logger.info(f"Connected to Cassandra keyspace: {keyspace}")
|
|
72
|
+
|
|
73
|
+
def create_collection(
|
|
74
|
+
self,
|
|
75
|
+
name: str,
|
|
76
|
+
dimension: int,
|
|
77
|
+
distance: str = "cosine",
|
|
78
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Create a new table with vector column."""
|
|
81
|
+
similarity_map = {"cosine": "COSINE", "euclidean": "EUCLIDEAN", "dot": "DOT_PRODUCT"}
|
|
82
|
+
|
|
83
|
+
self._session.execute(f"""
|
|
84
|
+
CREATE TABLE IF NOT EXISTS {name} (
|
|
85
|
+
id text PRIMARY KEY,
|
|
86
|
+
content text,
|
|
87
|
+
content_hash text,
|
|
88
|
+
created_at double,
|
|
89
|
+
embedding vector<float, {dimension}>
|
|
90
|
+
)
|
|
91
|
+
""")
|
|
92
|
+
|
|
93
|
+
# Create SAI index for vector search
|
|
94
|
+
self._session.execute(f"""
|
|
95
|
+
CREATE CUSTOM INDEX IF NOT EXISTS {name}_embedding_idx ON {name} (embedding)
|
|
96
|
+
USING 'StorageAttachedIndex'
|
|
97
|
+
WITH OPTIONS = {{'similarity_function': '{similarity_map.get(distance, "COSINE")}'}}
|
|
98
|
+
""")
|
|
99
|
+
logger.info(f"Created Cassandra table: {name}")
|
|
100
|
+
|
|
101
|
+
def delete_collection(self, name: str) -> bool:
|
|
102
|
+
"""Delete a table."""
|
|
103
|
+
try:
|
|
104
|
+
self._session.execute(f"DROP TABLE IF EXISTS {name}")
|
|
105
|
+
return True
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.warning(f"Failed to delete table {name}: {e}")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
def collection_exists(self, name: str) -> bool:
|
|
111
|
+
"""Check if a table exists."""
|
|
112
|
+
result = self._session.execute("""
|
|
113
|
+
SELECT table_name FROM system_schema.tables
|
|
114
|
+
WHERE keyspace_name = %s AND table_name = %s
|
|
115
|
+
""", (self.keyspace, name))
|
|
116
|
+
return len(list(result)) > 0
|
|
117
|
+
|
|
118
|
+
def list_collections(self) -> List[str]:
|
|
119
|
+
"""List all tables."""
|
|
120
|
+
result = self._session.execute("""
|
|
121
|
+
SELECT table_name FROM system_schema.tables WHERE keyspace_name = %s
|
|
122
|
+
""", (self.keyspace,))
|
|
123
|
+
return [row.table_name for row in result]
|
|
124
|
+
|
|
125
|
+
def insert(
|
|
126
|
+
self,
|
|
127
|
+
collection: str,
|
|
128
|
+
documents: List[KnowledgeDocument]
|
|
129
|
+
) -> List[str]:
|
|
130
|
+
"""Insert documents."""
|
|
131
|
+
ids = []
|
|
132
|
+
for doc in documents:
|
|
133
|
+
if doc.embedding is None:
|
|
134
|
+
raise ValueError(f"Document {doc.id} has no embedding")
|
|
135
|
+
|
|
136
|
+
self._session.execute(f"""
|
|
137
|
+
INSERT INTO {collection} (id, content, content_hash, created_at, embedding)
|
|
138
|
+
VALUES (%s, %s, %s, %s, %s)
|
|
139
|
+
""", (doc.id, doc.content, doc.content_hash or "", doc.created_at, doc.embedding))
|
|
140
|
+
ids.append(doc.id)
|
|
141
|
+
|
|
142
|
+
return ids
|
|
143
|
+
|
|
144
|
+
def upsert(
|
|
145
|
+
self,
|
|
146
|
+
collection: str,
|
|
147
|
+
documents: List[KnowledgeDocument]
|
|
148
|
+
) -> List[str]:
|
|
149
|
+
"""Insert or update documents."""
|
|
150
|
+
return self.insert(collection, documents) # Cassandra INSERT is upsert
|
|
151
|
+
|
|
152
|
+
def search(
|
|
153
|
+
self,
|
|
154
|
+
collection: str,
|
|
155
|
+
query_embedding: List[float],
|
|
156
|
+
limit: int = 5,
|
|
157
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
158
|
+
score_threshold: Optional[float] = None
|
|
159
|
+
) -> List[KnowledgeDocument]:
|
|
160
|
+
"""Search for similar documents using ANN."""
|
|
161
|
+
result = self._session.execute(f"""
|
|
162
|
+
SELECT id, content, content_hash, created_at, similarity_cosine(embedding, %s) as score
|
|
163
|
+
FROM {collection}
|
|
164
|
+
ORDER BY embedding ANN OF %s
|
|
165
|
+
LIMIT %s
|
|
166
|
+
""", (query_embedding, query_embedding, limit))
|
|
167
|
+
|
|
168
|
+
documents = []
|
|
169
|
+
for row in result:
|
|
170
|
+
if score_threshold and row.score < score_threshold:
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
doc = KnowledgeDocument(
|
|
174
|
+
id=row.id,
|
|
175
|
+
content=row.content,
|
|
176
|
+
embedding=None,
|
|
177
|
+
metadata={},
|
|
178
|
+
content_hash=row.content_hash,
|
|
179
|
+
created_at=row.created_at,
|
|
180
|
+
)
|
|
181
|
+
documents.append(doc)
|
|
182
|
+
|
|
183
|
+
return documents
|
|
184
|
+
|
|
185
|
+
def get(
|
|
186
|
+
self,
|
|
187
|
+
collection: str,
|
|
188
|
+
ids: List[str]
|
|
189
|
+
) -> List[KnowledgeDocument]:
|
|
190
|
+
"""Get documents by IDs."""
|
|
191
|
+
documents = []
|
|
192
|
+
for doc_id in ids:
|
|
193
|
+
result = self._session.execute(f"""
|
|
194
|
+
SELECT id, content, content_hash, created_at, embedding
|
|
195
|
+
FROM {collection} WHERE id = %s
|
|
196
|
+
""", (doc_id,))
|
|
197
|
+
|
|
198
|
+
for row in result:
|
|
199
|
+
doc = KnowledgeDocument(
|
|
200
|
+
id=row.id,
|
|
201
|
+
content=row.content,
|
|
202
|
+
embedding=list(row.embedding) if row.embedding else None,
|
|
203
|
+
metadata={},
|
|
204
|
+
content_hash=row.content_hash,
|
|
205
|
+
created_at=row.created_at,
|
|
206
|
+
)
|
|
207
|
+
documents.append(doc)
|
|
208
|
+
|
|
209
|
+
return documents
|
|
210
|
+
|
|
211
|
+
def delete(
|
|
212
|
+
self,
|
|
213
|
+
collection: str,
|
|
214
|
+
ids: Optional[List[str]] = None,
|
|
215
|
+
filters: Optional[Dict[str, Any]] = None
|
|
216
|
+
) -> int:
|
|
217
|
+
"""Delete documents."""
|
|
218
|
+
if ids:
|
|
219
|
+
for doc_id in ids:
|
|
220
|
+
self._session.execute(f"DELETE FROM {collection} WHERE id = %s", (doc_id,))
|
|
221
|
+
return len(ids)
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
def count(self, collection: str) -> int:
|
|
225
|
+
"""Count documents."""
|
|
226
|
+
result = self._session.execute(f"SELECT COUNT(*) FROM {collection}")
|
|
227
|
+
return result.one()[0]
|
|
228
|
+
|
|
229
|
+
def close(self) -> None:
|
|
230
|
+
"""Close the store."""
|
|
231
|
+
if self._cluster:
|
|
232
|
+
self._cluster.shutdown()
|