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.
Files changed (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,406 @@
1
+ """
2
+ PersistenceOrchestrator - Central coordinator for all persistence operations.
3
+
4
+ Hooks into agent lifecycle to provide automatic conversation persistence,
5
+ knowledge retrieval, and state management.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ import uuid
11
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
12
+
13
+ from .conversation.base import ConversationStore, ConversationSession, ConversationMessage
14
+ from .knowledge.base import KnowledgeStore, KnowledgeDocument
15
+ from .state.base import StateStore
16
+ from .config import PersistenceConfig
17
+ from .factory import create_stores_from_config
18
+
19
+ if TYPE_CHECKING:
20
+ pass
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class PersistenceOrchestrator:
26
+ """
27
+ Central coordinator for all persistence operations.
28
+
29
+ Lives in wrapper layer, hooks into agent lifecycle to provide:
30
+ - Automatic session loading/creation at agent start
31
+ - Message persistence during conversation
32
+ - Session metadata updates at agent end
33
+ - Knowledge retrieval for RAG
34
+ - State caching
35
+
36
+ Example:
37
+ orchestrator = PersistenceOrchestrator(
38
+ conversation_store=PostgresConversationStore(...),
39
+ knowledge_store=QdrantKnowledgeStore(...),
40
+ state_store=RedisStateStore(...),
41
+ )
42
+
43
+ # Hook into agent lifecycle
44
+ history = orchestrator.on_agent_start(agent, session_id="sess-123")
45
+ # ... agent runs ...
46
+ orchestrator.on_message(session_id, user_message)
47
+ orchestrator.on_message(session_id, assistant_message)
48
+ orchestrator.on_agent_end(agent, session_id)
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ conversation_store: Optional[ConversationStore] = None,
54
+ knowledge_store: Optional[KnowledgeStore] = None,
55
+ state_store: Optional[StateStore] = None,
56
+ config: Optional[PersistenceConfig] = None,
57
+ ):
58
+ """
59
+ Initialize the orchestrator.
60
+
61
+ Args:
62
+ conversation_store: Store for session/message persistence
63
+ knowledge_store: Store for vector embeddings
64
+ state_store: Store for key-value state
65
+ config: Configuration (alternative to passing stores directly)
66
+ """
67
+ if config:
68
+ stores = create_stores_from_config(config)
69
+ self.conversation = stores["conversation"] or conversation_store
70
+ self.knowledge = stores["knowledge"] or knowledge_store
71
+ self.state = stores["state"] or state_store
72
+ self._config = config
73
+ else:
74
+ self.conversation = conversation_store
75
+ self.knowledge = knowledge_store
76
+ self.state = state_store
77
+ self._config = None
78
+
79
+ self._current_session: Optional[ConversationSession] = None
80
+ self._session_cache: Dict[str, ConversationSession] = {}
81
+
82
+ @classmethod
83
+ def from_config(cls, config: PersistenceConfig) -> "PersistenceOrchestrator":
84
+ """Create orchestrator from configuration."""
85
+ return cls(config=config)
86
+
87
+ @classmethod
88
+ def from_env(cls) -> "PersistenceOrchestrator":
89
+ """Create orchestrator from environment variables."""
90
+ config = PersistenceConfig.from_env()
91
+ return cls(config=config)
92
+
93
+ # =========================================================================
94
+ # Agent Lifecycle Hooks
95
+ # =========================================================================
96
+
97
+ def on_agent_start(
98
+ self,
99
+ agent: Any,
100
+ session_id: Optional[str] = None,
101
+ user_id: Optional[str] = None,
102
+ resume: bool = True,
103
+ ) -> List[ConversationMessage]:
104
+ """
105
+ Called before agent run. Loads or creates session.
106
+
107
+ Args:
108
+ agent: The agent instance
109
+ session_id: Session ID (generated if not provided)
110
+ user_id: User ID for session
111
+ resume: Whether to load existing session history
112
+
113
+ Returns:
114
+ List of previous messages if resuming, empty list otherwise
115
+ """
116
+ if not session_id:
117
+ session_id = str(uuid.uuid4())
118
+
119
+ if not self.conversation:
120
+ logger.debug("No conversation store configured, skipping session load")
121
+ return []
122
+
123
+ # Try to load existing session
124
+ session = None
125
+ if resume:
126
+ session = self.conversation.get_session(session_id)
127
+
128
+ if session:
129
+ logger.info(f"Resuming session: {session_id}")
130
+ self._current_session = session
131
+ self._session_cache[session_id] = session
132
+
133
+ # Load previous messages
134
+ messages = self.conversation.get_messages(session_id)
135
+ return messages
136
+ else:
137
+ # Create new session
138
+ agent_id = getattr(agent, "name", None) or getattr(agent, "agent_id", None)
139
+ session = ConversationSession(
140
+ session_id=session_id,
141
+ user_id=user_id,
142
+ agent_id=agent_id,
143
+ name=f"Session {session_id[:8]}",
144
+ metadata={"agent_type": type(agent).__name__},
145
+ )
146
+ self.conversation.create_session(session)
147
+ logger.info(f"Created new session: {session_id}")
148
+ self._current_session = session
149
+ self._session_cache[session_id] = session
150
+ return []
151
+
152
+ def on_message(
153
+ self,
154
+ session_id: str,
155
+ role: str,
156
+ content: str,
157
+ tool_calls: Optional[List[Dict]] = None,
158
+ tool_call_id: Optional[str] = None,
159
+ metadata: Optional[Dict[str, Any]] = None,
160
+ ) -> Optional[ConversationMessage]:
161
+ """
162
+ Called after each message (user or assistant).
163
+
164
+ Args:
165
+ session_id: Session ID
166
+ role: Message role (user, assistant, system, tool)
167
+ content: Message content
168
+ tool_calls: Tool calls (for assistant messages)
169
+ tool_call_id: Tool call ID (for tool response messages)
170
+ metadata: Additional metadata
171
+
172
+ Returns:
173
+ The persisted message, or None if no store configured
174
+ """
175
+ if not self.conversation:
176
+ return None
177
+
178
+ message = ConversationMessage(
179
+ id=str(uuid.uuid4()),
180
+ session_id=session_id,
181
+ role=role,
182
+ content=content,
183
+ tool_calls=tool_calls,
184
+ tool_call_id=tool_call_id,
185
+ metadata=metadata,
186
+ )
187
+
188
+ self.conversation.add_message(session_id, message)
189
+ logger.debug(f"Persisted {role} message to session {session_id}")
190
+ return message
191
+
192
+ def on_agent_end(
193
+ self,
194
+ agent: Any,
195
+ session_id: str,
196
+ metadata: Optional[Dict[str, Any]] = None,
197
+ ) -> None:
198
+ """
199
+ Called after agent run. Updates session metadata.
200
+
201
+ Args:
202
+ agent: The agent instance
203
+ session_id: Session ID
204
+ metadata: Additional metadata to store
205
+ """
206
+ if not self.conversation:
207
+ return
208
+
209
+ session = self._session_cache.get(session_id) or self.conversation.get_session(session_id)
210
+ if session:
211
+ session.updated_at = time.time()
212
+ if metadata:
213
+ session.metadata = {**(session.metadata or {}), **metadata}
214
+ self.conversation.update_session(session)
215
+ logger.debug(f"Updated session metadata: {session_id}")
216
+
217
+ # =========================================================================
218
+ # Knowledge Retrieval
219
+ # =========================================================================
220
+
221
+ def retrieve_knowledge(
222
+ self,
223
+ query_embedding: List[float],
224
+ collection: str = "default",
225
+ limit: int = 5,
226
+ filters: Optional[Dict[str, Any]] = None,
227
+ ) -> List[KnowledgeDocument]:
228
+ """
229
+ Retrieve relevant documents for RAG.
230
+
231
+ Args:
232
+ query_embedding: Query vector embedding
233
+ collection: Collection name
234
+ limit: Max documents to return
235
+ filters: Metadata filters
236
+
237
+ Returns:
238
+ List of relevant documents
239
+ """
240
+ if not self.knowledge:
241
+ logger.debug("No knowledge store configured")
242
+ return []
243
+
244
+ return self.knowledge.search(
245
+ collection=collection,
246
+ query_embedding=query_embedding,
247
+ limit=limit,
248
+ filters=filters,
249
+ )
250
+
251
+ def add_knowledge(
252
+ self,
253
+ documents: List[KnowledgeDocument],
254
+ collection: str = "default",
255
+ ) -> List[str]:
256
+ """
257
+ Add documents to knowledge store.
258
+
259
+ Args:
260
+ documents: Documents with embeddings
261
+ collection: Collection name
262
+
263
+ Returns:
264
+ List of document IDs
265
+ """
266
+ if not self.knowledge:
267
+ raise ValueError("No knowledge store configured")
268
+
269
+ return self.knowledge.upsert(collection, documents)
270
+
271
+ # =========================================================================
272
+ # State Management
273
+ # =========================================================================
274
+
275
+ def get_state(self, key: str) -> Optional[Any]:
276
+ """Get state value."""
277
+ if not self.state:
278
+ return None
279
+ return self.state.get(key)
280
+
281
+ def set_state(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
282
+ """Set state value with optional TTL."""
283
+ if not self.state:
284
+ return
285
+ self.state.set(key, value, ttl)
286
+
287
+ def delete_state(self, key: str) -> bool:
288
+ """Delete state value."""
289
+ if not self.state:
290
+ return False
291
+ return self.state.delete(key)
292
+
293
+ # =========================================================================
294
+ # Session Management
295
+ # =========================================================================
296
+
297
+ def get_session(self, session_id: str) -> Optional[ConversationSession]:
298
+ """Get a session by ID."""
299
+ if not self.conversation:
300
+ return None
301
+ return self.conversation.get_session(session_id)
302
+
303
+ def list_sessions(
304
+ self,
305
+ user_id: Optional[str] = None,
306
+ limit: int = 100,
307
+ ) -> List[ConversationSession]:
308
+ """List sessions for a user."""
309
+ if not self.conversation:
310
+ return []
311
+ return self.conversation.list_sessions(user_id=user_id, limit=limit)
312
+
313
+ def delete_session(self, session_id: str) -> bool:
314
+ """Delete a session and all its messages."""
315
+ if not self.conversation:
316
+ return False
317
+
318
+ if session_id in self._session_cache:
319
+ del self._session_cache[session_id]
320
+
321
+ return self.conversation.delete_session(session_id)
322
+
323
+ def get_messages(
324
+ self,
325
+ session_id: str,
326
+ limit: Optional[int] = None,
327
+ ) -> List[ConversationMessage]:
328
+ """Get messages from a session."""
329
+ if not self.conversation:
330
+ return []
331
+ return self.conversation.get_messages(session_id, limit=limit)
332
+
333
+ # =========================================================================
334
+ # Context Building
335
+ # =========================================================================
336
+
337
+ def build_context(
338
+ self,
339
+ session_id: str,
340
+ query_embedding: Optional[List[float]] = None,
341
+ history_limit: int = 20,
342
+ knowledge_limit: int = 5,
343
+ knowledge_collection: str = "default",
344
+ ) -> Dict[str, Any]:
345
+ """
346
+ Build context for agent including history and knowledge.
347
+
348
+ Args:
349
+ session_id: Session ID
350
+ query_embedding: Query embedding for knowledge retrieval
351
+ history_limit: Max history messages
352
+ knowledge_limit: Max knowledge documents
353
+ knowledge_collection: Knowledge collection name
354
+
355
+ Returns:
356
+ Dict with 'history' and 'knowledge' keys
357
+ """
358
+ context = {
359
+ "history": [],
360
+ "knowledge": [],
361
+ }
362
+
363
+ # Get conversation history
364
+ if self.conversation:
365
+ messages = self.conversation.get_messages(session_id, limit=history_limit)
366
+ context["history"] = [
367
+ {"role": m.role, "content": m.content}
368
+ for m in messages
369
+ ]
370
+
371
+ # Get relevant knowledge
372
+ if self.knowledge and query_embedding:
373
+ docs = self.knowledge.search(
374
+ collection=knowledge_collection,
375
+ query_embedding=query_embedding,
376
+ limit=knowledge_limit,
377
+ )
378
+ context["knowledge"] = [
379
+ {"content": d.content, "metadata": d.metadata}
380
+ for d in docs
381
+ ]
382
+
383
+ return context
384
+
385
+ # =========================================================================
386
+ # Cleanup
387
+ # =========================================================================
388
+
389
+ def close(self) -> None:
390
+ """Close all stores and release resources."""
391
+ if self.conversation:
392
+ self.conversation.close()
393
+ if self.knowledge:
394
+ self.knowledge.close()
395
+ if self.state:
396
+ self.state.close()
397
+
398
+ self._session_cache.clear()
399
+ logger.info("Persistence orchestrator closed")
400
+
401
+ def __enter__(self):
402
+ return self
403
+
404
+ def __exit__(self, exc_type, exc_val, exc_tb):
405
+ self.close()
406
+ return False
@@ -0,0 +1,21 @@
1
+ """
2
+ StateStore implementations for fast key-value state and caching.
3
+
4
+ Supported backends:
5
+ - Redis (redis)
6
+ - DynamoDB (dynamodb)
7
+ - Firestore (firestore)
8
+ - MongoDB (mongodb)
9
+ - Upstash (upstash)
10
+ - In-memory / JSON file (memory)
11
+ """
12
+
13
+ __all__ = [
14
+ "StateStore",
15
+ ]
16
+
17
+ def __getattr__(name: str):
18
+ if name == "StateStore":
19
+ from .base import StateStore
20
+ return StateStore
21
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,200 @@
1
+ """
2
+ Async MongoDB implementation of StateStore.
3
+
4
+ Requires: motor
5
+ Install: pip install motor
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import time
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from .base import StateStore
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class AsyncMongoDBStateStore(StateStore):
19
+ """
20
+ Async MongoDB state store using motor.
21
+
22
+ Provides high-performance async database operations.
23
+
24
+ Example:
25
+ store = AsyncMongoDBStateStore(
26
+ url="mongodb://localhost:27017",
27
+ database="praisonai"
28
+ )
29
+ await store.init()
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ url: str = "mongodb://localhost:27017",
35
+ database: str = "praisonai",
36
+ collection: str = "state",
37
+ ):
38
+ """
39
+ Initialize async MongoDB store.
40
+
41
+ Args:
42
+ url: MongoDB connection URL
43
+ database: Database name
44
+ collection: Collection name
45
+ """
46
+ self.url = url
47
+ self.database_name = database
48
+ self.collection_name = collection
49
+ self._client = None
50
+ self._db = None
51
+ self._collection = None
52
+ self._initialized = False
53
+
54
+ async def init(self):
55
+ """Initialize connection."""
56
+ if self._initialized:
57
+ return
58
+
59
+ try:
60
+ from motor.motor_asyncio import AsyncIOMotorClient
61
+ except ImportError:
62
+ raise ImportError(
63
+ "motor is required for async MongoDB support. "
64
+ "Install with: pip install motor"
65
+ )
66
+
67
+ self._client = AsyncIOMotorClient(self.url)
68
+ self._db = self._client[self.database_name]
69
+ self._collection = self._db[self.collection_name]
70
+
71
+ # Create TTL index for expiration
72
+ await self._collection.create_index("expires_at", expireAfterSeconds=0)
73
+
74
+ self._initialized = True
75
+
76
+ async def async_get(self, key: str) -> Optional[Dict[str, Any]]:
77
+ """Get state by key asynchronously."""
78
+ if not self._initialized:
79
+ await self.init()
80
+
81
+ doc = await self._collection.find_one({"_id": key})
82
+ if doc:
83
+ # Check TTL manually (in case index hasn't cleaned up yet)
84
+ if "expires_at" in doc and doc["expires_at"]:
85
+ if time.time() > doc["expires_at"]:
86
+ await self.async_delete(key)
87
+ return None
88
+
89
+ # Remove internal fields
90
+ result = dict(doc)
91
+ result.pop("_id", None)
92
+ result.pop("expires_at", None)
93
+ return result.get("value", result)
94
+ return None
95
+
96
+ def get(self, key: str) -> Optional[Dict[str, Any]]:
97
+ """Sync wrapper for get."""
98
+ return asyncio.get_event_loop().run_until_complete(self.async_get(key))
99
+
100
+ async def async_set(self, key: str, value: Dict[str, Any], ttl: Optional[int] = None) -> bool:
101
+ """Set state by key with optional TTL asynchronously."""
102
+ if not self._initialized:
103
+ await self.init()
104
+
105
+ doc = {
106
+ "_id": key,
107
+ "value": value,
108
+ "updated_at": time.time(),
109
+ }
110
+
111
+ if ttl:
112
+ doc["expires_at"] = time.time() + ttl
113
+
114
+ try:
115
+ await self._collection.replace_one(
116
+ {"_id": key},
117
+ doc,
118
+ upsert=True
119
+ )
120
+ return True
121
+ except Exception as e:
122
+ logger.error(f"Error setting state {key}: {e}")
123
+ return False
124
+
125
+ def set(self, key: str, value: Dict[str, Any], ttl: Optional[int] = None) -> bool:
126
+ """Sync wrapper for set."""
127
+ return asyncio.get_event_loop().run_until_complete(self.async_set(key, value, ttl))
128
+
129
+ async def async_delete(self, key: str) -> bool:
130
+ """Delete state by key asynchronously."""
131
+ if not self._initialized:
132
+ await self.init()
133
+
134
+ result = await self._collection.delete_one({"_id": key})
135
+ return result.deleted_count > 0
136
+
137
+ def delete(self, key: str) -> bool:
138
+ """Sync wrapper for delete."""
139
+ return asyncio.get_event_loop().run_until_complete(self.async_delete(key))
140
+
141
+ async def async_exists(self, key: str) -> bool:
142
+ """Check if key exists asynchronously."""
143
+ if not self._initialized:
144
+ await self.init()
145
+
146
+ count = await self._collection.count_documents({"_id": key}, limit=1)
147
+ return count > 0
148
+
149
+ def exists(self, key: str) -> bool:
150
+ """Sync wrapper for exists."""
151
+ return asyncio.get_event_loop().run_until_complete(self.async_exists(key))
152
+
153
+ async def async_list_keys(self, prefix: Optional[str] = None) -> List[str]:
154
+ """List all keys with optional prefix filter asynchronously."""
155
+ if not self._initialized:
156
+ await self.init()
157
+
158
+ query = {}
159
+ if prefix:
160
+ query["_id"] = {"$regex": f"^{prefix}"}
161
+
162
+ cursor = self._collection.find(query, {"_id": 1})
163
+ keys = []
164
+ async for doc in cursor:
165
+ keys.append(doc["_id"])
166
+ return keys
167
+
168
+ def list_keys(self, prefix: Optional[str] = None) -> List[str]:
169
+ """Sync wrapper for list_keys."""
170
+ return asyncio.get_event_loop().run_until_complete(self.async_list_keys(prefix))
171
+
172
+ async def async_clear(self, prefix: Optional[str] = None) -> int:
173
+ """Clear all keys with optional prefix filter asynchronously."""
174
+ if not self._initialized:
175
+ await self.init()
176
+
177
+ query = {}
178
+ if prefix:
179
+ query["_id"] = {"$regex": f"^{prefix}"}
180
+
181
+ result = await self._collection.delete_many(query)
182
+ return result.deleted_count
183
+
184
+ def clear(self, prefix: Optional[str] = None) -> int:
185
+ """Sync wrapper for clear."""
186
+ return asyncio.get_event_loop().run_until_complete(self.async_clear(prefix))
187
+
188
+ async def async_close(self) -> None:
189
+ """Close the connection."""
190
+ if self._client:
191
+ self._client.close()
192
+ self._client = None
193
+ self._db = None
194
+ self._collection = None
195
+ self._initialized = False
196
+
197
+ def close(self) -> None:
198
+ """Sync wrapper for close."""
199
+ if self._client:
200
+ asyncio.get_event_loop().run_until_complete(self.async_close())