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
praisonai/ui/chat.py ADDED
@@ -0,0 +1,532 @@
1
+ # Standard library imports
2
+ import os
3
+ from datetime import datetime
4
+ from typing import Dict, Optional
5
+ import logging
6
+ import json
7
+ import asyncio
8
+ import io
9
+ import base64
10
+ import importlib.util
11
+ import inspect
12
+
13
+ # Third-party imports
14
+ from dotenv import load_dotenv
15
+ from PIL import Image
16
+ from tavily import TavilyClient
17
+ from crawl4ai import AsyncWebCrawler
18
+
19
+ # Local application/library imports
20
+ import chainlit as cl
21
+ from chainlit.input_widget import TextInput
22
+ from chainlit.types import ThreadDict
23
+ import chainlit.data as cl_data
24
+ from litellm import acompletion
25
+ from literalai.helper import utc_now
26
+ from db import DatabaseManager
27
+
28
+ # Load environment variables
29
+ load_dotenv()
30
+
31
+ # Logging configuration
32
+ logger = logging.getLogger(__name__)
33
+ log_level = os.getenv("LOGLEVEL", "INFO").upper() or "INFO"
34
+ logger.handlers = []
35
+ console_handler = logging.StreamHandler()
36
+ console_handler.setLevel(log_level)
37
+ console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
38
+ console_handler.setFormatter(console_formatter)
39
+ logger.addHandler(console_handler)
40
+ logger.setLevel(log_level)
41
+
42
+ CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
43
+ if not CHAINLIT_AUTH_SECRET:
44
+ os.environ["CHAINLIT_AUTH_SECRET"] = "p8BPhQChpg@J>jBz$wGxqLX2V>yTVgP*7Ky9H$aV:axW~ANNX-7_T:o@lnyCBu^U"
45
+ CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
46
+
47
+ now = utc_now()
48
+ create_step_counter = 0
49
+
50
+ # Initialize database
51
+ db_manager = DatabaseManager()
52
+ db_manager.initialize()
53
+
54
+ def save_setting(key: str, value: str):
55
+ """Save a setting to the database"""
56
+ asyncio.run(db_manager.save_setting(key, value))
57
+
58
+ def load_setting(key: str) -> str:
59
+ """Load a setting from the database"""
60
+ return asyncio.run(db_manager.load_setting(key))
61
+
62
+ cl_data._data_layer = db_manager
63
+
64
+ def load_custom_tools():
65
+ """
66
+ Load custom tools from tools.py if it exists.
67
+
68
+ Resolution order:
69
+ 1. PRAISONAI_TOOLS_PATH environment variable (file or directory)
70
+ 2. ./tools.py in current working directory
71
+ 3. No tools (silent, no warning)
72
+ """
73
+ custom_tools = {}
74
+
75
+ # Determine tools path
76
+ tools_path = os.getenv("PRAISONAI_TOOLS_PATH")
77
+ if tools_path:
78
+ if not os.path.exists(tools_path):
79
+ logger.warning(f"PRAISONAI_TOOLS_PATH set but path does not exist: {tools_path}")
80
+ return custom_tools
81
+ else:
82
+ # Check current working directory for tools.py
83
+ cwd_tools = os.path.join(os.getcwd(), "tools.py")
84
+ if os.path.exists(cwd_tools):
85
+ tools_path = cwd_tools
86
+ else:
87
+ # No tools configured - this is fine, return silently
88
+ logger.debug("No tools.py found in current directory (this is normal)")
89
+ return custom_tools
90
+
91
+ try:
92
+ spec = importlib.util.spec_from_file_location("tools", tools_path)
93
+ if spec is None or spec.loader is None:
94
+ logger.debug(f"Could not load tools from {tools_path}")
95
+ return custom_tools
96
+
97
+ module = importlib.util.module_from_spec(spec)
98
+ spec.loader.exec_module(module)
99
+
100
+ # Load all functions from tools.py
101
+ for name, obj in inspect.getmembers(module):
102
+ if not name.startswith('_') and callable(obj) and not inspect.isclass(obj):
103
+ # Store function in globals for access
104
+ globals()[name] = obj
105
+
106
+ # Get function signature to build parameters
107
+ sig = inspect.signature(obj)
108
+ params_properties = {}
109
+ required_params = []
110
+
111
+ for param_name, param in sig.parameters.items():
112
+ if param_name != 'self': # Skip self parameter
113
+ # Get type annotation if available
114
+ param_type = "string" # Default type
115
+ if param.annotation != inspect.Parameter.empty:
116
+ if param.annotation == int:
117
+ param_type = "integer"
118
+ elif param.annotation == float:
119
+ param_type = "number"
120
+ elif param.annotation == bool:
121
+ param_type = "boolean"
122
+
123
+ params_properties[param_name] = {
124
+ "type": param_type,
125
+ "description": f"Parameter {param_name}"
126
+ }
127
+
128
+ # Add to required if no default value
129
+ if param.default == inspect.Parameter.empty:
130
+ required_params.append(param_name)
131
+
132
+ # Build tool definition
133
+ tool_def = {
134
+ "type": "function",
135
+ "function": {
136
+ "name": name,
137
+ "description": obj.__doc__ or f"Function {name.replace('_', ' ')}",
138
+ "parameters": {
139
+ "type": "object",
140
+ "properties": params_properties,
141
+ "required": required_params
142
+ }
143
+ }
144
+ }
145
+
146
+ custom_tools[name] = tool_def
147
+ logger.info(f"Loaded custom tool: {name}")
148
+
149
+ if custom_tools:
150
+ logger.info(f"Loaded {len(custom_tools)} custom tools from {tools_path}")
151
+ except FileNotFoundError:
152
+ # File doesn't exist - this is fine, no warning needed
153
+ logger.debug(f"Tools file not found: {tools_path}")
154
+ except Exception as e:
155
+ logger.warning(f"Error loading custom tools from {tools_path}: {e}")
156
+
157
+ return custom_tools
158
+
159
+ # Load custom tools
160
+ custom_tools_dict = load_custom_tools()
161
+
162
+ tavily_api_key = os.getenv("TAVILY_API_KEY")
163
+ tavily_client = TavilyClient(api_key=tavily_api_key) if tavily_api_key else None
164
+
165
+ async def tavily_web_search(query):
166
+ if not tavily_client:
167
+ return json.dumps({
168
+ "query": query,
169
+ "error": "Tavily API key is not set. Web search is unavailable."
170
+ })
171
+
172
+ response = tavily_client.search(query)
173
+ logger.debug(f"Tavily search response: {response}")
174
+
175
+ async with AsyncWebCrawler() as crawler:
176
+ results = []
177
+ for result in response.get('results', []):
178
+ url = result.get('url')
179
+ if url:
180
+ try:
181
+ crawl_result = await crawler.arun(url=url)
182
+ results.append({
183
+ "content": result.get('content'),
184
+ "url": url,
185
+ "full_content": crawl_result.markdown
186
+ })
187
+ except Exception as e:
188
+ logger.error(f"Error crawling {url}: {str(e)}")
189
+ results.append({
190
+ "content": result.get('content'),
191
+ "url": url,
192
+ "full_content": "Error: Unable to crawl this URL"
193
+ })
194
+
195
+ return json.dumps({
196
+ "query": query,
197
+ "results": results
198
+ })
199
+
200
+ # Build tools list with Tavily and custom tools
201
+ tools = []
202
+
203
+ # Add Tavily tool if API key is available
204
+ if tavily_api_key:
205
+ tools.append({
206
+ "type": "function",
207
+ "function": {
208
+ "name": "tavily_web_search",
209
+ "description": "Search the web using Tavily API and crawl the resulting URLs",
210
+ "parameters": {
211
+ "type": "object",
212
+ "properties": {
213
+ "query": {"type": "string", "description": "Search query"}
214
+ },
215
+ "required": ["query"]
216
+ }
217
+ }
218
+ })
219
+
220
+ # Add custom tools from tools.py
221
+ tools.extend(list(custom_tools_dict.values()))
222
+
223
+ # Authentication configuration
224
+ AUTH_PASSWORD_ENABLED = os.getenv("AUTH_PASSWORD_ENABLED", "true").lower() == "true" # Password authentication enabled by default
225
+ AUTH_OAUTH_ENABLED = os.getenv("AUTH_OAUTH_ENABLED", "false").lower() == "true" # OAuth authentication disabled by default
226
+
227
+ expected_username = os.getenv("CHAINLIT_USERNAME", "admin")
228
+ expected_password = os.getenv("CHAINLIT_PASSWORD", "admin")
229
+
230
+ # Warn if using default credentials
231
+ if expected_username == "admin" and expected_password == "admin":
232
+ logger.warning("⚠️ Using default admin credentials. Set CHAINLIT_USERNAME and CHAINLIT_PASSWORD environment variables for production.")
233
+
234
+ @cl.password_auth_callback
235
+ def auth_callback(username: str, password: str):
236
+ logger.debug(f"Auth attempt: username='{username}', expected='{expected_username}'")
237
+ if (username, password) == (expected_username, expected_password):
238
+ logger.info(f"Login successful for user: {username}")
239
+ return cl.User(identifier=username, metadata={"role": "admin", "provider": "credentials"})
240
+ else:
241
+ logger.warning(f"Login failed for user: {username}")
242
+ return None
243
+
244
+
245
+ async def send_count():
246
+ await cl.Message(
247
+ f"Create step counter: {create_step_counter}", disable_feedback=True
248
+ ).send()
249
+
250
+ @cl.on_chat_start
251
+ async def start():
252
+ model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-5-nano")
253
+ cl.user_session.set("model_name", model_name)
254
+ logger.debug(f"Model name: {model_name}")
255
+ settings = cl.ChatSettings(
256
+ [
257
+ TextInput(
258
+ id="model_name",
259
+ label="Enter the Model Name",
260
+ placeholder="e.g., gpt-5-nano",
261
+ initial=model_name
262
+ )
263
+ ]
264
+ )
265
+ cl.user_session.set("settings", settings)
266
+ await settings.send()
267
+
268
+ @cl.on_settings_update
269
+ async def setup_agent(settings):
270
+ logger.debug(settings)
271
+ cl.user_session.set("settings", settings)
272
+ model_name = settings["model_name"]
273
+ cl.user_session.set("model_name", model_name)
274
+
275
+ save_setting("model_name", model_name)
276
+
277
+ thread_id = cl.user_session.get("thread_id")
278
+ if thread_id:
279
+ thread = await cl_data.get_thread(thread_id)
280
+ if thread:
281
+ metadata = thread.get("metadata", {})
282
+ if isinstance(metadata, str):
283
+ try:
284
+ metadata = json.loads(metadata)
285
+ except json.JSONDecodeError:
286
+ metadata = {}
287
+ metadata["model_name"] = model_name
288
+ await cl_data.update_thread(thread_id, metadata=metadata)
289
+ cl.user_session.set("metadata", metadata)
290
+
291
+ @cl.on_message
292
+ async def main(message: cl.Message):
293
+ model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-5-nano")
294
+ message_history = cl.user_session.get("message_history", [])
295
+ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
296
+
297
+ image = None
298
+ if message.elements and isinstance(message.elements[0], cl.Image):
299
+ image_element = message.elements[0]
300
+ try:
301
+ image = Image.open(image_element.path)
302
+ image.load()
303
+ cl.user_session.set("image", image)
304
+ except Exception as e:
305
+ logger.error(f"Error processing image: {str(e)}")
306
+ await cl.Message(content="Error processing the image. Please try again.").send()
307
+ return
308
+
309
+ user_message = f"""
310
+ Answer the question and use tools if needed:
311
+
312
+ Current Date and Time: {now}
313
+
314
+ User Question: {message.content}
315
+ """
316
+
317
+ if image:
318
+ user_message = f"Image uploaded. {user_message}"
319
+
320
+ message_history.append({"role": "user", "content": user_message})
321
+ msg = cl.Message(content="")
322
+
323
+ completion_params = {
324
+ "model": model_name,
325
+ "messages": message_history,
326
+ "stream": True,
327
+ }
328
+
329
+ if image:
330
+ buffered = io.BytesIO()
331
+ image.save(buffered, format="PNG")
332
+ img_str = base64.b64encode(buffered.getvalue()).decode()
333
+ completion_params["messages"][-1] = {
334
+ "role": "user",
335
+ "content": [
336
+ {"type": "text", "text": user_message},
337
+ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_str}"}}
338
+ ]
339
+ }
340
+
341
+ # Pass tools if we have any (Tavily or custom)
342
+ if tools:
343
+ completion_params["tools"] = tools
344
+ completion_params["tool_choice"] = "auto"
345
+
346
+ response = await acompletion(**completion_params)
347
+
348
+ full_response = ""
349
+ tool_calls = []
350
+ current_tool_call = None
351
+ msg_sent = False
352
+
353
+ async for part in response:
354
+ if 'choices' in part and len(part['choices']) > 0:
355
+ delta = part['choices'][0].get('delta', {})
356
+
357
+ if 'content' in delta and delta['content'] is not None:
358
+ token = delta['content']
359
+ # Send message on first token to avoid delay
360
+ if not msg_sent:
361
+ await msg.send()
362
+ msg_sent = True
363
+ await msg.stream_token(token)
364
+ full_response += token
365
+
366
+ if tools and 'tool_calls' in delta and delta['tool_calls'] is not None:
367
+ for tool_call in delta['tool_calls']:
368
+ if current_tool_call is None or tool_call.index != current_tool_call['index']:
369
+ if current_tool_call:
370
+ tool_calls.append(current_tool_call)
371
+ current_tool_call = {
372
+ 'id': tool_call.id,
373
+ 'type': tool_call.type,
374
+ 'index': tool_call.index,
375
+ 'function': {
376
+ 'name': tool_call.function.name if tool_call.function else None,
377
+ 'arguments': ''
378
+ }
379
+ }
380
+ if tool_call.function:
381
+ if tool_call.function.name:
382
+ current_tool_call['function']['name'] = tool_call.function.name
383
+ if tool_call.function.arguments:
384
+ current_tool_call['function']['arguments'] += tool_call.function.arguments
385
+
386
+ if current_tool_call:
387
+ tool_calls.append(current_tool_call)
388
+
389
+ # Ensure message is sent even if no content (e.g., tool calls only)
390
+ if not msg_sent:
391
+ await msg.send()
392
+
393
+ logger.debug(f"Full response: {full_response}")
394
+ logger.debug(f"Tool calls: {tool_calls}")
395
+ message_history.append({"role": "assistant", "content": full_response})
396
+ logger.debug(f"Message history: {message_history}")
397
+ cl.user_session.set("message_history", message_history)
398
+ await msg.update()
399
+
400
+ if tool_calls and tools: # Check if we have any tools and tool calls
401
+ available_functions = {}
402
+
403
+ # Add Tavily function if available
404
+ if tavily_api_key:
405
+ available_functions["tavily_web_search"] = tavily_web_search
406
+
407
+ # Add all custom tool functions from globals
408
+ for tool_name in custom_tools_dict:
409
+ if tool_name in globals():
410
+ available_functions[tool_name] = globals()[tool_name]
411
+ messages = message_history + [{"role": "assistant", "content": None, "function_call": {
412
+ "name": tool_calls[0]['function']['name'],
413
+ "arguments": tool_calls[0]['function']['arguments']
414
+ }}]
415
+
416
+ for tool_call in tool_calls:
417
+ function_name = tool_call['function']['name']
418
+ if function_name in available_functions:
419
+ function_to_call = available_functions[function_name]
420
+ function_args = tool_call['function']['arguments']
421
+ if function_args:
422
+ try:
423
+ function_args = json.loads(function_args)
424
+
425
+ # Call function based on whether it's async or sync
426
+ if asyncio.iscoroutinefunction(function_to_call):
427
+ # For async functions like tavily_web_search
428
+ if function_name == "tavily_web_search":
429
+ function_response = await function_to_call(
430
+ query=function_args.get("query"),
431
+ )
432
+ else:
433
+ # For custom async functions, pass all arguments
434
+ function_response = await function_to_call(**function_args)
435
+ else:
436
+ # For sync functions (most custom tools)
437
+ function_response = function_to_call(**function_args)
438
+
439
+ # Convert response to string if needed
440
+ if not isinstance(function_response, str):
441
+ function_response = json.dumps(function_response)
442
+
443
+ messages.append(
444
+ {
445
+ "role": "function",
446
+ "name": function_name,
447
+ "content": function_response,
448
+ }
449
+ )
450
+ except json.JSONDecodeError:
451
+ logger.error(f"Failed to parse function arguments: {function_args}")
452
+ except Exception as e:
453
+ logger.error(f"Error calling function {function_name}: {str(e)}")
454
+ messages.append(
455
+ {
456
+ "role": "function",
457
+ "name": function_name,
458
+ "content": f"Error: {str(e)}",
459
+ }
460
+ )
461
+
462
+ second_response = await acompletion(
463
+ model=model_name,
464
+ stream=True,
465
+ messages=messages,
466
+ )
467
+ logger.debug(f"Second LLM response: {second_response}")
468
+
469
+ full_response = ""
470
+ async for part in second_response:
471
+ if 'choices' in part and len(part['choices']) > 0:
472
+ delta = part['choices'][0].get('delta', {})
473
+ if 'content' in delta and delta['content'] is not None:
474
+ token = delta['content']
475
+ await msg.stream_token(token)
476
+ full_response += token
477
+
478
+ msg.content = full_response
479
+ await msg.update()
480
+ else:
481
+ msg.content = full_response
482
+ await msg.update()
483
+
484
+ @cl.on_chat_resume
485
+ async def on_chat_resume(thread: ThreadDict):
486
+ logger.info(f"Resuming chat: {thread['id']}")
487
+ model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-5-nano")
488
+ logger.debug(f"Model name: {model_name}")
489
+ settings = cl.ChatSettings(
490
+ [
491
+ TextInput(
492
+ id="model_name",
493
+ label="Enter the Model Name",
494
+ placeholder="e.g., gpt-5-nano",
495
+ initial=model_name
496
+ )
497
+ ]
498
+ )
499
+ await settings.send()
500
+ thread_id = thread["id"]
501
+ cl.user_session.set("thread_id", thread_id)
502
+
503
+ metadata = thread.get("metadata", {})
504
+ if isinstance(metadata, str):
505
+ try:
506
+ metadata = json.loads(metadata)
507
+ except json.JSONDecodeError:
508
+ metadata = {}
509
+ cl.user_session.set("metadata", metadata)
510
+
511
+ message_history = cl.user_session.get("message_history", [])
512
+ steps = thread["steps"]
513
+
514
+ for m in steps:
515
+ msg_type = m.get("type")
516
+ if msg_type == "user_message":
517
+ message_history.append({"role": "user", "content": m.get("output", "")})
518
+ elif msg_type == "assistant_message":
519
+ message_history.append({"role": "assistant", "content": m.get("output", "")})
520
+ elif msg_type == "run":
521
+ if m.get("isError"):
522
+ message_history.append({"role": "system", "content": f"Error: {m.get('output', '')}"})
523
+ else:
524
+ logger.warning(f"Message without recognized type: {m}")
525
+
526
+ cl.user_session.set("message_history", message_history)
527
+
528
+ image_data = metadata.get("image")
529
+ if image_data:
530
+ image = Image.open(io.BytesIO(base64.b64decode(image_data)))
531
+ cl.user_session.set("image", image)
532
+ await cl.Message(content="Previous image loaded.").send()