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,466 @@
1
+ """
2
+ Router Handler for CLI.
3
+
4
+ Provides smart model selection based on task complexity.
5
+ Usage: praisonai "Complex task" --router
6
+
7
+ Supports custom model configuration from agents.yaml/workflow.yaml:
8
+
9
+ models:
10
+ gpt-4o-mini:
11
+ provider: openai
12
+ complexity: [simple, moderate]
13
+ cost_per_1k: 0.00075
14
+ capabilities: [text, function-calling]
15
+ """
16
+
17
+ from typing import Any, Dict, List, Optional, Tuple
18
+ from dataclasses import dataclass
19
+ from enum import IntEnum
20
+ from .base import FlagHandler
21
+
22
+
23
+ class TaskComplexity(IntEnum):
24
+ """Enum for task complexity levels."""
25
+ SIMPLE = 1
26
+ MODERATE = 2
27
+ COMPLEX = 3
28
+ VERY_COMPLEX = 4
29
+
30
+
31
+ @dataclass
32
+ class ModelProfile:
33
+ """Profile for an LLM model with its characteristics."""
34
+ name: str
35
+ provider: str
36
+ complexity_range: Tuple[TaskComplexity, TaskComplexity]
37
+ cost_per_1k_tokens: float
38
+ capabilities: List[str]
39
+ context_window: int = 128000
40
+ supports_tools: bool = True
41
+ supports_streaming: bool = True
42
+ strengths: Optional[List[str]] = None
43
+
44
+
45
+ class RouterHandler(FlagHandler):
46
+ """
47
+ Handler for --router flag.
48
+
49
+ Automatically selects the best model based on task complexity.
50
+
51
+ Example:
52
+ praisonai "Simple question" --router
53
+ praisonai "Complex analysis" --router
54
+ """
55
+
56
+ # Default model tiers for routing (used when no custom models configured)
57
+ # Note: Use full model names for API compatibility
58
+ DEFAULT_MODEL_TIERS = {
59
+ 'simple': ['gpt-4o-mini', 'anthropic/claude-3-haiku-20240307', 'gemini/gemini-1.5-flash'],
60
+ 'medium': ['gpt-4o', 'anthropic/claude-3-5-sonnet-20241022', 'gemini/gemini-1.5-pro'],
61
+ 'complex': ['gpt-4-turbo', 'anthropic/claude-3-opus-20240229', 'o1-preview'],
62
+ }
63
+
64
+ # Default model profiles
65
+ DEFAULT_MODELS: List[ModelProfile] = [
66
+ ModelProfile(
67
+ name="gpt-4o-mini",
68
+ provider="openai",
69
+ complexity_range=(TaskComplexity.SIMPLE, TaskComplexity.MODERATE),
70
+ cost_per_1k_tokens=0.00075,
71
+ capabilities=["text", "function-calling"],
72
+ context_window=128000
73
+ ),
74
+ ModelProfile(
75
+ name="gpt-4o",
76
+ provider="openai",
77
+ complexity_range=(TaskComplexity.MODERATE, TaskComplexity.COMPLEX),
78
+ cost_per_1k_tokens=0.0075,
79
+ capabilities=["text", "vision", "function-calling"],
80
+ context_window=128000
81
+ ),
82
+ ModelProfile(
83
+ name="anthropic/claude-3-haiku-20240307",
84
+ provider="anthropic",
85
+ complexity_range=(TaskComplexity.SIMPLE, TaskComplexity.MODERATE),
86
+ cost_per_1k_tokens=0.0008,
87
+ capabilities=["text", "function-calling"],
88
+ context_window=200000
89
+ ),
90
+ ModelProfile(
91
+ name="anthropic/claude-3-5-sonnet-20241022",
92
+ provider="anthropic",
93
+ complexity_range=(TaskComplexity.MODERATE, TaskComplexity.VERY_COMPLEX),
94
+ cost_per_1k_tokens=0.009,
95
+ capabilities=["text", "vision", "function-calling"],
96
+ context_window=200000,
97
+ strengths=["reasoning", "code-generation", "analysis"]
98
+ ),
99
+ ModelProfile(
100
+ name="gemini/gemini-1.5-flash",
101
+ provider="google",
102
+ complexity_range=(TaskComplexity.SIMPLE, TaskComplexity.MODERATE),
103
+ cost_per_1k_tokens=0.000125,
104
+ capabilities=["text", "vision", "function-calling"],
105
+ context_window=1048576
106
+ ),
107
+ ModelProfile(
108
+ name="gemini/gemini-1.5-pro",
109
+ provider="google",
110
+ complexity_range=(TaskComplexity.COMPLEX, TaskComplexity.VERY_COMPLEX),
111
+ cost_per_1k_tokens=0.00625,
112
+ capabilities=["text", "vision", "function-calling"],
113
+ context_window=2097152
114
+ ),
115
+ ]
116
+
117
+ def __init__(self, verbose: bool = False):
118
+ """Initialize RouterHandler with model routing support."""
119
+ super().__init__(verbose=verbose)
120
+ self._model_router: Optional[List[ModelProfile]] = None
121
+ self._custom_models: Dict[str, ModelProfile] = {}
122
+ self._cost_threshold: Optional[float] = None
123
+ self._model_tiers = self.DEFAULT_MODEL_TIERS.copy()
124
+
125
+ @property
126
+ def model_router(self) -> List[ModelProfile]:
127
+ """Get the list of available model profiles."""
128
+ if self._model_router is None:
129
+ return self.DEFAULT_MODELS.copy()
130
+ return self._model_router
131
+
132
+ @property
133
+ def feature_name(self) -> str:
134
+ return "router"
135
+
136
+ @property
137
+ def flag_name(self) -> str:
138
+ return "router"
139
+
140
+ @property
141
+ def flag_help(self) -> str:
142
+ return "Auto-select best model based on task complexity"
143
+
144
+ def check_dependencies(self) -> Tuple[bool, str]:
145
+ """Router is built-in, always available."""
146
+ return True, ""
147
+
148
+ def analyze_complexity(self, prompt: str) -> str:
149
+ """
150
+ Analyze prompt complexity.
151
+
152
+ Args:
153
+ prompt: The task prompt
154
+
155
+ Returns:
156
+ Complexity level: 'simple', 'medium', or 'complex'
157
+ """
158
+ # Simple heuristics for complexity
159
+ prompt_lower = prompt.lower()
160
+ word_count = len(prompt.split())
161
+
162
+ # Complex indicators
163
+ complex_keywords = [
164
+ 'analyze', 'research', 'comprehensive', 'detailed',
165
+ 'compare', 'evaluate', 'synthesize', 'multi-step',
166
+ 'code review', 'architecture', 'design pattern',
167
+ 'optimize', 'debug', 'refactor'
168
+ ]
169
+
170
+ # Simple indicators
171
+ simple_keywords = [
172
+ 'what is', 'define', 'list', 'name', 'when',
173
+ 'where', 'who', 'simple', 'quick', 'brief'
174
+ ]
175
+
176
+ complex_count = sum(1 for kw in complex_keywords if kw in prompt_lower)
177
+ simple_count = sum(1 for kw in simple_keywords if kw in prompt_lower)
178
+
179
+ # Determine complexity
180
+ if complex_count >= 2 or word_count > 100:
181
+ return 'complex'
182
+ elif simple_count >= 2 or word_count < 20:
183
+ return 'simple'
184
+ else:
185
+ return 'medium'
186
+
187
+ def select_model(self, prompt: str, preferred_provider: str = None) -> str:
188
+ """
189
+ Select the best model for the prompt.
190
+
191
+ Args:
192
+ prompt: The task prompt
193
+ preferred_provider: Optional preferred provider (openai, anthropic, google)
194
+
195
+ Returns:
196
+ Selected model name
197
+ """
198
+ complexity = self.analyze_complexity(prompt)
199
+
200
+ # If custom models are loaded, use ModelProfile-based selection
201
+ if self._custom_models:
202
+ return self._select_from_profiles(prompt, complexity, preferred_provider)
203
+
204
+ # Otherwise use tier-based selection
205
+ models = self._model_tiers.get(complexity, self._model_tiers['medium'])
206
+
207
+ # Filter by provider if specified
208
+ if preferred_provider:
209
+ provider_prefixes = {
210
+ 'openai': ['gpt-', 'o1-'],
211
+ 'anthropic': ['anthropic/', 'claude-'],
212
+ 'google': ['gemini/', 'gemini-']
213
+ }
214
+ prefixes = provider_prefixes.get(preferred_provider.lower(), [])
215
+ filtered = [m for m in models if any(p in m for p in prefixes)]
216
+ if filtered:
217
+ models = filtered
218
+
219
+ # Apply cost threshold if set
220
+ if self._cost_threshold:
221
+ filtered = [m for m in models if self.get_model_cost(m) <= self._cost_threshold]
222
+ if filtered:
223
+ models = filtered
224
+
225
+ selected = models[0] if models else 'gpt-4o-mini'
226
+
227
+ self.print_status(f"🎯 Task complexity: {complexity}", "info")
228
+ self.print_status(f"🤖 Selected model: {selected}", "success")
229
+
230
+ return selected
231
+
232
+ def _select_from_profiles(
233
+ self,
234
+ prompt: str,
235
+ complexity: str,
236
+ preferred_provider: str = None
237
+ ) -> str:
238
+ """Select model from custom ModelProfile objects."""
239
+ # Map string complexity to TaskComplexity enum
240
+ complexity_map = {
241
+ 'simple': TaskComplexity.SIMPLE,
242
+ 'medium': TaskComplexity.MODERATE,
243
+ 'complex': TaskComplexity.COMPLEX,
244
+ }
245
+ task_complexity = complexity_map.get(complexity, TaskComplexity.MODERATE)
246
+
247
+ # Get all models that support this complexity level
248
+ candidates = []
249
+ for name, profile in self._custom_models.items():
250
+ min_c, max_c = profile.complexity_range
251
+ if min_c.value <= task_complexity.value <= max_c.value:
252
+ candidates.append(profile)
253
+
254
+ if not candidates:
255
+ return 'gpt-4o-mini'
256
+
257
+ # Filter by provider if specified
258
+ if preferred_provider:
259
+ filtered = [m for m in candidates if m.provider == preferred_provider]
260
+ if filtered:
261
+ candidates = filtered
262
+
263
+ # Filter by cost threshold if set
264
+ if self._cost_threshold:
265
+ filtered = [m for m in candidates if m.cost_per_1k_tokens <= self._cost_threshold]
266
+ if filtered:
267
+ candidates = filtered
268
+
269
+ # Sort by cost (cheapest first)
270
+ candidates.sort(key=lambda m: m.cost_per_1k_tokens)
271
+
272
+ return candidates[0].name if candidates else 'gpt-4o-mini'
273
+
274
+ def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
275
+ """
276
+ Apply router configuration.
277
+
278
+ Args:
279
+ config: Agent configuration dictionary
280
+ flag_value: Boolean or dict with provider preference
281
+
282
+ Returns:
283
+ Modified configuration with selected model
284
+ """
285
+ if not flag_value:
286
+ return config
287
+
288
+ # Get prompt from config if available
289
+ prompt = config.get('prompt', config.get('goal', ''))
290
+
291
+ preferred_provider = None
292
+ if isinstance(flag_value, dict):
293
+ preferred_provider = flag_value.get('provider')
294
+
295
+ if prompt:
296
+ selected_model = self.select_model(prompt, preferred_provider)
297
+ config['llm'] = selected_model
298
+
299
+ config['use_router'] = True
300
+ return config
301
+
302
+ def execute(self, prompt: str = None, provider: str = None, **kwargs) -> str:
303
+ """
304
+ Execute model selection.
305
+
306
+ Args:
307
+ prompt: Task prompt
308
+ provider: Preferred provider
309
+
310
+ Returns:
311
+ Selected model name
312
+ """
313
+ if not prompt:
314
+ return 'gpt-4o-mini' # Default
315
+
316
+ return self.select_model(prompt, provider)
317
+
318
+ # =========================================================================
319
+ # Custom Model Configuration Methods
320
+ # =========================================================================
321
+
322
+ def yaml_to_model_profile(self, name: str, config: Dict[str, Any]) -> ModelProfile:
323
+ """
324
+ Convert YAML model configuration to ModelProfile object.
325
+
326
+ Args:
327
+ name: Model name
328
+ config: Model configuration from YAML
329
+
330
+ Returns:
331
+ ModelProfile object
332
+ """
333
+ # Parse complexity strings to TaskComplexity enum
334
+ complexity_list = config.get('complexity', ['moderate'])
335
+ complexity_map = {
336
+ 'simple': TaskComplexity.SIMPLE,
337
+ 'moderate': TaskComplexity.MODERATE,
338
+ 'complex': TaskComplexity.COMPLEX,
339
+ 'very_complex': TaskComplexity.VERY_COMPLEX,
340
+ }
341
+
342
+ complexities = [complexity_map.get(c.lower(), TaskComplexity.MODERATE) for c in complexity_list]
343
+ min_complexity = min(complexities, key=lambda x: x.value)
344
+ max_complexity = max(complexities, key=lambda x: x.value)
345
+
346
+ return ModelProfile(
347
+ name=name,
348
+ provider=config.get('provider', 'unknown'),
349
+ complexity_range=(min_complexity, max_complexity),
350
+ cost_per_1k_tokens=config.get('cost_per_1k', 0.001),
351
+ capabilities=config.get('capabilities', ['text']),
352
+ context_window=config.get('context_window', 128000),
353
+ supports_tools=config.get('supports_tools', True),
354
+ supports_streaming=config.get('supports_streaming', True),
355
+ strengths=config.get('strengths', None)
356
+ )
357
+
358
+ def load_models_from_config(
359
+ self,
360
+ models_config: Dict[str, Dict[str, Any]],
361
+ merge_with_defaults: bool = False
362
+ ) -> None:
363
+ """
364
+ Load custom models from YAML configuration.
365
+
366
+ Args:
367
+ models_config: Dictionary of model configurations from YAML
368
+ merge_with_defaults: If True, merge with default models
369
+ """
370
+ # Start with defaults if merging
371
+ if merge_with_defaults:
372
+ for profile in self.DEFAULT_MODELS:
373
+ self._custom_models[profile.name] = profile
374
+
375
+ # Add custom models
376
+ for name, config in models_config.items():
377
+ profile = self.yaml_to_model_profile(name, config)
378
+ self._custom_models[name] = profile
379
+
380
+ # Rebuild model tiers from profiles
381
+ self._rebuild_model_tiers()
382
+
383
+ def _rebuild_model_tiers(self) -> None:
384
+ """Rebuild MODEL_TIERS from custom model profiles."""
385
+ self._model_tiers = {
386
+ 'simple': [],
387
+ 'medium': [],
388
+ 'complex': [],
389
+ }
390
+
391
+ for name, profile in self._custom_models.items():
392
+ min_c, max_c = profile.complexity_range
393
+
394
+ if min_c.value <= TaskComplexity.SIMPLE.value <= max_c.value:
395
+ self._model_tiers['simple'].append(name)
396
+ if min_c.value <= TaskComplexity.MODERATE.value <= max_c.value:
397
+ self._model_tiers['medium'].append(name)
398
+ if min_c.value <= TaskComplexity.COMPLEX.value <= max_c.value:
399
+ self._model_tiers['complex'].append(name)
400
+
401
+ # Sort by cost (cheapest first)
402
+ for tier in self._model_tiers:
403
+ self._model_tiers[tier].sort(
404
+ key=lambda n: self._custom_models[n].cost_per_1k_tokens
405
+ )
406
+
407
+ def get_available_models(self) -> List[str]:
408
+ """
409
+ Get list of available model names.
410
+
411
+ Returns:
412
+ List of model names
413
+ """
414
+ if self._custom_models:
415
+ return list(self._custom_models.keys())
416
+ return [p.name for p in self.DEFAULT_MODELS]
417
+
418
+ def get_model_cost(self, model_name: str) -> float:
419
+ """
420
+ Get cost per 1k tokens for a model.
421
+
422
+ Args:
423
+ model_name: Name of the model
424
+
425
+ Returns:
426
+ Cost per 1k tokens
427
+ """
428
+ # Check custom models first
429
+ if model_name in self._custom_models:
430
+ return self._custom_models[model_name].cost_per_1k_tokens
431
+
432
+ # Check default models
433
+ for profile in self.DEFAULT_MODELS:
434
+ if profile.name == model_name:
435
+ return profile.cost_per_1k_tokens
436
+
437
+ # Default cost for unknown models
438
+ return 0.01
439
+
440
+ def set_cost_threshold(self, threshold: float) -> None:
441
+ """
442
+ Set maximum cost per 1k tokens for model selection.
443
+
444
+ Args:
445
+ threshold: Maximum cost per 1k tokens
446
+ """
447
+ self._cost_threshold = threshold
448
+
449
+ def get_model_profile(self, model_name: str) -> Optional[ModelProfile]:
450
+ """
451
+ Get ModelProfile for a model.
452
+
453
+ Args:
454
+ model_name: Name of the model
455
+
456
+ Returns:
457
+ ModelProfile or None if not found
458
+ """
459
+ if model_name in self._custom_models:
460
+ return self._custom_models[model_name]
461
+
462
+ for profile in self.DEFAULT_MODELS:
463
+ if profile.name == model_name:
464
+ return profile
465
+
466
+ return None