PraisonAI 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides MCP (Model Context Protocol) server management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from ..output.console import get_output_controller
|
|
12
|
+
from ..configuration.loader import get_config_loader
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="MCP server management")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command("list")
|
|
18
|
+
def mcp_list(
|
|
19
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON"),
|
|
20
|
+
):
|
|
21
|
+
"""List configured MCP servers."""
|
|
22
|
+
output = get_output_controller()
|
|
23
|
+
loader = get_config_loader()
|
|
24
|
+
|
|
25
|
+
config = loader.load()
|
|
26
|
+
servers = config.mcp.servers
|
|
27
|
+
|
|
28
|
+
if output.is_json_mode or json_output:
|
|
29
|
+
output.print_json({
|
|
30
|
+
"servers": {
|
|
31
|
+
name: {
|
|
32
|
+
"command": s.command,
|
|
33
|
+
"args": s.args,
|
|
34
|
+
"enabled": s.enabled,
|
|
35
|
+
}
|
|
36
|
+
for name, s in servers.items()
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if not servers:
|
|
42
|
+
output.print_info("No MCP servers configured")
|
|
43
|
+
output.print("\nAdd a server with: praisonai mcp add <name> <command>")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
headers = ["Name", "Command", "Enabled"]
|
|
47
|
+
rows = []
|
|
48
|
+
for name, server in servers.items():
|
|
49
|
+
cmd = f"{server.command} {' '.join(server.args)}"
|
|
50
|
+
if len(cmd) > 50:
|
|
51
|
+
cmd = cmd[:47] + "..."
|
|
52
|
+
rows.append([name, cmd, "ā" if server.enabled else "ā"])
|
|
53
|
+
|
|
54
|
+
output.print_table(headers, rows, title="MCP Servers")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("add")
|
|
58
|
+
def mcp_add(
|
|
59
|
+
name: str = typer.Argument(..., help="Server name"),
|
|
60
|
+
command: str = typer.Argument(..., help="Command to run"),
|
|
61
|
+
args: Optional[str] = typer.Option(None, "--args", "-a", help="Command arguments (space-separated)"),
|
|
62
|
+
env: Optional[str] = typer.Option(None, "--env", "-e", help="Environment variables (KEY=VALUE,...)"),
|
|
63
|
+
):
|
|
64
|
+
"""Add an MCP server."""
|
|
65
|
+
output = get_output_controller()
|
|
66
|
+
loader = get_config_loader()
|
|
67
|
+
|
|
68
|
+
# Parse args
|
|
69
|
+
arg_list = args.split() if args else []
|
|
70
|
+
|
|
71
|
+
# Parse env
|
|
72
|
+
env_dict = {}
|
|
73
|
+
if env:
|
|
74
|
+
for pair in env.split(","):
|
|
75
|
+
if "=" in pair:
|
|
76
|
+
key, value = pair.split("=", 1)
|
|
77
|
+
env_dict[key.strip()] = value.strip()
|
|
78
|
+
|
|
79
|
+
# Build server config
|
|
80
|
+
server_config = {
|
|
81
|
+
"command": command,
|
|
82
|
+
"args": arg_list,
|
|
83
|
+
"env": env_dict,
|
|
84
|
+
"enabled": True,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Save to config
|
|
88
|
+
loader.set(f"mcp.servers.{name}", server_config)
|
|
89
|
+
|
|
90
|
+
if output.is_json_mode:
|
|
91
|
+
output.print_json({"added": name, "config": server_config})
|
|
92
|
+
else:
|
|
93
|
+
output.print_success(f"Added MCP server: {name}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command("remove")
|
|
97
|
+
def mcp_remove(
|
|
98
|
+
name: str = typer.Argument(..., help="Server name to remove"),
|
|
99
|
+
confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
100
|
+
):
|
|
101
|
+
"""Remove an MCP server."""
|
|
102
|
+
output = get_output_controller()
|
|
103
|
+
loader = get_config_loader()
|
|
104
|
+
|
|
105
|
+
config = loader.load()
|
|
106
|
+
if name not in config.mcp.servers:
|
|
107
|
+
output.print_error(f"Server not found: {name}")
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
|
|
110
|
+
if not confirm:
|
|
111
|
+
confirmed = typer.confirm(f"Remove MCP server '{name}'?")
|
|
112
|
+
if not confirmed:
|
|
113
|
+
output.print_info("Cancelled")
|
|
114
|
+
raise typer.Exit(0)
|
|
115
|
+
|
|
116
|
+
# Remove from config by setting to None (will be cleaned up)
|
|
117
|
+
# For now, we need to reload and modify the raw config
|
|
118
|
+
from ..configuration.paths import get_user_config_path
|
|
119
|
+
from ..configuration.loader import _load_toml, _save_toml
|
|
120
|
+
|
|
121
|
+
config_path = get_user_config_path()
|
|
122
|
+
if config_path.exists():
|
|
123
|
+
raw_config = _load_toml(config_path)
|
|
124
|
+
if "mcp" in raw_config and "servers" in raw_config["mcp"]:
|
|
125
|
+
if name in raw_config["mcp"]["servers"]:
|
|
126
|
+
del raw_config["mcp"]["servers"][name]
|
|
127
|
+
_save_toml(config_path, raw_config)
|
|
128
|
+
|
|
129
|
+
if output.is_json_mode:
|
|
130
|
+
output.print_json({"removed": name})
|
|
131
|
+
else:
|
|
132
|
+
output.print_success(f"Removed MCP server: {name}")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.command("test")
|
|
136
|
+
def mcp_test(
|
|
137
|
+
name: str = typer.Argument(..., help="Server name to test"),
|
|
138
|
+
timeout: float = typer.Option(10.0, "--timeout", "-t", help="Timeout in seconds"),
|
|
139
|
+
):
|
|
140
|
+
"""Test an MCP server connection."""
|
|
141
|
+
output = get_output_controller()
|
|
142
|
+
loader = get_config_loader()
|
|
143
|
+
|
|
144
|
+
config = loader.load()
|
|
145
|
+
if name not in config.mcp.servers:
|
|
146
|
+
output.print_error(f"Server not found: {name}")
|
|
147
|
+
raise typer.Exit(1)
|
|
148
|
+
|
|
149
|
+
server = config.mcp.servers[name]
|
|
150
|
+
|
|
151
|
+
output.print_info(f"Testing MCP server: {name}")
|
|
152
|
+
output.print(f" Command: {server.command} {' '.join(server.args)}")
|
|
153
|
+
|
|
154
|
+
# Try to start the server and check if it responds
|
|
155
|
+
import subprocess
|
|
156
|
+
import time
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
cmd = [server.command] + server.args
|
|
160
|
+
env = dict(**server.env) if server.env else None
|
|
161
|
+
|
|
162
|
+
# Start process
|
|
163
|
+
proc = subprocess.Popen(
|
|
164
|
+
cmd,
|
|
165
|
+
stdin=subprocess.PIPE,
|
|
166
|
+
stdout=subprocess.PIPE,
|
|
167
|
+
stderr=subprocess.PIPE,
|
|
168
|
+
env=env,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Wait briefly and check if it's running
|
|
172
|
+
time.sleep(0.5)
|
|
173
|
+
|
|
174
|
+
if proc.poll() is None:
|
|
175
|
+
# Process is running
|
|
176
|
+
proc.terminate()
|
|
177
|
+
proc.wait(timeout=2)
|
|
178
|
+
|
|
179
|
+
if output.is_json_mode:
|
|
180
|
+
output.print_json({"name": name, "status": "ok", "message": "Server started successfully"})
|
|
181
|
+
else:
|
|
182
|
+
output.print_success(f"Server '{name}' started successfully")
|
|
183
|
+
else:
|
|
184
|
+
# Process exited
|
|
185
|
+
stderr = proc.stderr.read().decode() if proc.stderr else ""
|
|
186
|
+
if output.is_json_mode:
|
|
187
|
+
output.print_json({"name": name, "status": "error", "message": stderr or "Server exited immediately"})
|
|
188
|
+
else:
|
|
189
|
+
output.print_error(f"Server exited immediately: {stderr}")
|
|
190
|
+
raise typer.Exit(1)
|
|
191
|
+
|
|
192
|
+
except FileNotFoundError:
|
|
193
|
+
if output.is_json_mode:
|
|
194
|
+
output.print_json({"name": name, "status": "error", "message": f"Command not found: {server.command}"})
|
|
195
|
+
else:
|
|
196
|
+
output.print_error(f"Command not found: {server.command}")
|
|
197
|
+
raise typer.Exit(1)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
if output.is_json_mode:
|
|
200
|
+
output.print_json({"name": name, "status": "error", "message": str(e)})
|
|
201
|
+
else:
|
|
202
|
+
output.print_error(f"Test failed: {e}")
|
|
203
|
+
raise typer.Exit(1)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@app.callback(invoke_without_command=True)
|
|
207
|
+
def mcp_callback(ctx: typer.Context):
|
|
208
|
+
"""Show MCP help or list servers."""
|
|
209
|
+
if ctx.invoked_subcommand is None:
|
|
210
|
+
mcp_list(json_output=False)
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Profile command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides detailed cProfile-based profiling for query execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Performance profiling and diagnostics")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command("query")
|
|
16
|
+
def profile_query(
|
|
17
|
+
prompt: str = typer.Argument(..., help="Prompt to profile"),
|
|
18
|
+
model: Optional[str] = typer.Option(None, "--model", "-m", help="Model to use"),
|
|
19
|
+
stream: bool = typer.Option(False, "--stream/--no-stream", help="Use streaming mode"),
|
|
20
|
+
deep: bool = typer.Option(False, "--deep", help="Enable deep call tracing (higher overhead)"),
|
|
21
|
+
limit: int = typer.Option(30, "--limit", "-n", help="Top N functions to show"),
|
|
22
|
+
sort: str = typer.Option("cumulative", "--sort", "-s", help="Sort by: cumulative or tottime"),
|
|
23
|
+
show_files: bool = typer.Option(False, "--show-files", help="Group timing by file/module"),
|
|
24
|
+
show_callers: bool = typer.Option(False, "--show-callers", help="Show caller functions"),
|
|
25
|
+
show_callees: bool = typer.Option(False, "--show-callees", help="Show callee functions"),
|
|
26
|
+
importtime: bool = typer.Option(False, "--importtime", help="Show module import times"),
|
|
27
|
+
first_token: bool = typer.Option(False, "--first-token", help="Track time to first token (streaming)"),
|
|
28
|
+
save: Optional[str] = typer.Option(None, "--save", help="Save artifacts to path (creates .prof, .txt)"),
|
|
29
|
+
output_format: str = typer.Option("text", "--format", "-f", help="Output format: text or json"),
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Profile a query execution with detailed timing breakdown.
|
|
33
|
+
|
|
34
|
+
Shows per-function and per-file timing, call graphs, and latency metrics.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
praisonai profile query "What is 2+2?"
|
|
38
|
+
praisonai profile query "Hello" --show-files --limit 20
|
|
39
|
+
praisonai profile query "Test" --stream --first-token
|
|
40
|
+
praisonai profile query "Test" --deep --show-callers --show-callees
|
|
41
|
+
praisonai profile query "Test" --save ./profile_results
|
|
42
|
+
"""
|
|
43
|
+
# Lazy import to avoid startup overhead
|
|
44
|
+
try:
|
|
45
|
+
from ..features.profiler import (
|
|
46
|
+
ProfilerConfig,
|
|
47
|
+
QueryProfiler,
|
|
48
|
+
format_profile_report,
|
|
49
|
+
)
|
|
50
|
+
except ImportError as e:
|
|
51
|
+
typer.echo(f"Error: Profiler module not available: {e}", err=True)
|
|
52
|
+
raise typer.Exit(1)
|
|
53
|
+
|
|
54
|
+
# Warn about deep tracing overhead
|
|
55
|
+
if deep:
|
|
56
|
+
typer.echo("ā ļø Deep call tracing enabled - this adds significant overhead", err=True)
|
|
57
|
+
|
|
58
|
+
# Create config
|
|
59
|
+
config = ProfilerConfig(
|
|
60
|
+
deep=deep,
|
|
61
|
+
limit=limit,
|
|
62
|
+
sort_by=sort,
|
|
63
|
+
show_files=show_files,
|
|
64
|
+
show_callers=show_callers,
|
|
65
|
+
show_callees=show_callees,
|
|
66
|
+
importtime=importtime,
|
|
67
|
+
first_token=first_token,
|
|
68
|
+
save_path=save,
|
|
69
|
+
output_format=output_format,
|
|
70
|
+
stream=stream,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Run profiler
|
|
74
|
+
typer.echo("š¬ Starting profiled execution...", err=True)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
profiler = QueryProfiler(config)
|
|
78
|
+
result = profiler.profile_query(prompt, model=model, stream=stream)
|
|
79
|
+
except ImportError as e:
|
|
80
|
+
typer.echo(f"Error: {e}", err=True)
|
|
81
|
+
typer.echo("Install praisonaiagents: pip install praisonaiagents", err=True)
|
|
82
|
+
raise typer.Exit(1)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
typer.echo(f"Error during profiling: {e}", err=True)
|
|
85
|
+
raise typer.Exit(1)
|
|
86
|
+
|
|
87
|
+
# Output results
|
|
88
|
+
if output_format == "json":
|
|
89
|
+
import json
|
|
90
|
+
typer.echo(json.dumps(result.to_dict(), indent=2, default=str))
|
|
91
|
+
else:
|
|
92
|
+
report = format_profile_report(result, config)
|
|
93
|
+
typer.echo(report)
|
|
94
|
+
|
|
95
|
+
# Save artifacts if requested
|
|
96
|
+
if save:
|
|
97
|
+
try:
|
|
98
|
+
prof_path, txt_path = profiler.save_artifacts(result, save)
|
|
99
|
+
typer.echo("\nā
Artifacts saved:", err=True)
|
|
100
|
+
typer.echo(f" {prof_path}", err=True)
|
|
101
|
+
typer.echo(f" {txt_path}", err=True)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
typer.echo(f"Warning: Failed to save artifacts: {e}", err=True)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command("imports")
|
|
107
|
+
def profile_imports():
|
|
108
|
+
"""
|
|
109
|
+
Profile module import times.
|
|
110
|
+
|
|
111
|
+
Shows which modules take longest to import, useful for optimizing startup time.
|
|
112
|
+
"""
|
|
113
|
+
import subprocess
|
|
114
|
+
import re
|
|
115
|
+
|
|
116
|
+
typer.echo("š¬ Profiling import times...", err=True)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
[sys.executable, "-X", "importtime", "-c", "import praisonaiagents"],
|
|
121
|
+
capture_output=True,
|
|
122
|
+
text=True,
|
|
123
|
+
timeout=60,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Parse import times
|
|
127
|
+
import_times = []
|
|
128
|
+
for line in result.stderr.split('\n'):
|
|
129
|
+
if 'import time:' in line:
|
|
130
|
+
match = re.search(r'import time:\s+(\d+)\s+\|\s+(\d+)\s+\|\s+(.+)', line)
|
|
131
|
+
if match:
|
|
132
|
+
self_time = int(match.group(1))
|
|
133
|
+
cumulative = int(match.group(2))
|
|
134
|
+
module = match.group(3).strip()
|
|
135
|
+
import_times.append((module, self_time, cumulative))
|
|
136
|
+
|
|
137
|
+
# Sort by cumulative time
|
|
138
|
+
import_times.sort(key=lambda x: x[2], reverse=True)
|
|
139
|
+
|
|
140
|
+
# Display
|
|
141
|
+
typer.echo("\n" + "=" * 70)
|
|
142
|
+
typer.echo("Import Time Analysis")
|
|
143
|
+
typer.echo("=" * 70)
|
|
144
|
+
typer.echo(f"{'Module':<45} {'Self (μs)':>10} {'Cumul (μs)':>12}")
|
|
145
|
+
typer.echo("-" * 70)
|
|
146
|
+
|
|
147
|
+
for module, self_time, cumulative in import_times[:30]:
|
|
148
|
+
module_name = module
|
|
149
|
+
if len(module_name) > 43:
|
|
150
|
+
module_name = "..." + module_name[-40:]
|
|
151
|
+
typer.echo(f"{module_name:<45} {self_time:>10} {cumulative:>12}")
|
|
152
|
+
|
|
153
|
+
typer.echo("-" * 70)
|
|
154
|
+
|
|
155
|
+
if import_times:
|
|
156
|
+
total_time = max(t[2] for t in import_times)
|
|
157
|
+
typer.echo(f"Total import time: {total_time / 1000:.2f} ms")
|
|
158
|
+
|
|
159
|
+
except subprocess.TimeoutExpired:
|
|
160
|
+
typer.echo("Error: Import profiling timed out", err=True)
|
|
161
|
+
raise typer.Exit(1)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
typer.echo(f"Error: {e}", err=True)
|
|
164
|
+
raise typer.Exit(1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@app.command("startup")
|
|
168
|
+
def profile_startup():
|
|
169
|
+
"""
|
|
170
|
+
Profile CLI startup time.
|
|
171
|
+
|
|
172
|
+
Measures time from CLI invocation to ready state.
|
|
173
|
+
"""
|
|
174
|
+
import time
|
|
175
|
+
import subprocess
|
|
176
|
+
|
|
177
|
+
typer.echo("š¬ Profiling startup time...", err=True)
|
|
178
|
+
|
|
179
|
+
# Measure cold start (new process)
|
|
180
|
+
start = time.perf_counter()
|
|
181
|
+
subprocess.run(
|
|
182
|
+
[sys.executable, "-c", "import praisonai; import praisonai.cli"],
|
|
183
|
+
capture_output=True,
|
|
184
|
+
text=True,
|
|
185
|
+
)
|
|
186
|
+
cold_start = (time.perf_counter() - start) * 1000
|
|
187
|
+
|
|
188
|
+
# Measure warm start (imports cached)
|
|
189
|
+
start = time.perf_counter()
|
|
190
|
+
subprocess.run(
|
|
191
|
+
[sys.executable, "-c", "import praisonai; import praisonai.cli"],
|
|
192
|
+
capture_output=True,
|
|
193
|
+
text=True,
|
|
194
|
+
)
|
|
195
|
+
warm_start = (time.perf_counter() - start) * 1000
|
|
196
|
+
|
|
197
|
+
typer.echo("\n" + "=" * 50)
|
|
198
|
+
typer.echo("Startup Time Analysis")
|
|
199
|
+
typer.echo("=" * 50)
|
|
200
|
+
typer.echo(f"Cold Start: {cold_start:>10.2f} ms")
|
|
201
|
+
typer.echo(f"Warm Start: {warm_start:>10.2f} ms")
|
|
202
|
+
typer.echo("=" * 50)
|
|
203
|
+
|
|
204
|
+
if cold_start > 1000:
|
|
205
|
+
typer.echo("\nā ļø Cold start > 1s - consider lazy imports", err=True)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@app.command("suite")
|
|
209
|
+
def profile_suite(
|
|
210
|
+
output_dir: Optional[str] = typer.Option("/tmp/praisonai_profile_suite", "--output", "-o", help="Output directory for results"),
|
|
211
|
+
iterations: int = typer.Option(3, "--iterations", "-n", help="Iterations per scenario"),
|
|
212
|
+
quick: bool = typer.Option(False, "--quick", help="Quick mode (fewer iterations)"),
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Run a comprehensive profiling suite.
|
|
216
|
+
|
|
217
|
+
Runs multiple scenarios (streaming/non-streaming, simple/complex prompts)
|
|
218
|
+
and produces aggregated statistics and reports.
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
praisonai profile suite
|
|
222
|
+
praisonai profile suite --output ./my_profile_results
|
|
223
|
+
praisonai profile suite --quick
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
from ..features.profiler import run_profile_suite, ScenarioConfig
|
|
227
|
+
except ImportError as e:
|
|
228
|
+
typer.echo(f"Error: Profiler module not available: {e}", err=True)
|
|
229
|
+
raise typer.Exit(1)
|
|
230
|
+
|
|
231
|
+
# Define scenarios
|
|
232
|
+
iters = 1 if quick else iterations
|
|
233
|
+
scenarios = [
|
|
234
|
+
ScenarioConfig(name="simple_non_stream", prompt="hi", stream=False, iterations=iters, warmup=0 if quick else 1),
|
|
235
|
+
ScenarioConfig(name="simple_stream", prompt="hi", stream=True, iterations=iters, warmup=0 if quick else 1),
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
if not quick:
|
|
239
|
+
scenarios.extend([
|
|
240
|
+
ScenarioConfig(name="medium_non_stream", prompt="Explain Python in 2 sentences", stream=False, iterations=max(1, iters-1)),
|
|
241
|
+
ScenarioConfig(name="medium_stream", prompt="Explain Python in 2 sentences", stream=True, iterations=max(1, iters-1)),
|
|
242
|
+
])
|
|
243
|
+
|
|
244
|
+
typer.echo("š¬ Running Profile Suite...", err=True)
|
|
245
|
+
typer.echo(f" Output: {output_dir}", err=True)
|
|
246
|
+
typer.echo(f" Scenarios: {len(scenarios)}", err=True)
|
|
247
|
+
typer.echo(f" Iterations: {iterations}", err=True)
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
result = run_profile_suite(
|
|
251
|
+
output_dir=output_dir,
|
|
252
|
+
scenarios=scenarios,
|
|
253
|
+
verbose=True,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Print summary
|
|
257
|
+
typer.echo("\n" + "=" * 60)
|
|
258
|
+
typer.echo("Profile Suite Summary")
|
|
259
|
+
typer.echo("=" * 60)
|
|
260
|
+
typer.echo(f"Startup Cold: {result.startup_cold_ms:.2f}ms")
|
|
261
|
+
typer.echo(f"Startup Warm: {result.startup_warm_ms:.2f}ms")
|
|
262
|
+
|
|
263
|
+
if result.import_analysis:
|
|
264
|
+
typer.echo(f"\nTop Import: {result.import_analysis[0]['module']}")
|
|
265
|
+
typer.echo(f" Time: {result.import_analysis[0]['cumulative_ms']:.2f}ms")
|
|
266
|
+
|
|
267
|
+
typer.echo("\nScenario Results:")
|
|
268
|
+
for scenario in result.scenarios:
|
|
269
|
+
stats = scenario.get_stats(scenario.total_times)
|
|
270
|
+
typer.echo(f" {scenario.name}: {stats['mean']:.2f}ms (±{stats['stdev']:.2f}ms)")
|
|
271
|
+
|
|
272
|
+
typer.echo(f"\nā
Full results saved to: {output_dir}")
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
typer.echo(f"Error running suite: {e}", err=True)
|
|
276
|
+
raise typer.Exit(1)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@app.command("snapshot")
|
|
280
|
+
def profile_snapshot(
|
|
281
|
+
name: str = typer.Argument("current", help="Name for the snapshot"),
|
|
282
|
+
baseline: bool = typer.Option(False, "--baseline", "-b", help="Save as baseline for comparison"),
|
|
283
|
+
compare: bool = typer.Option(False, "--compare", "-c", help="Compare against baseline"),
|
|
284
|
+
output_format: str = typer.Option("text", "--format", "-f", help="Output format: text or json"),
|
|
285
|
+
):
|
|
286
|
+
"""
|
|
287
|
+
Create or compare performance snapshots.
|
|
288
|
+
|
|
289
|
+
Snapshots record timing data for baseline comparison.
|
|
290
|
+
Use --baseline to save a reference point, then --compare to check for regressions.
|
|
291
|
+
|
|
292
|
+
Examples:
|
|
293
|
+
praisonai profile snapshot --baseline
|
|
294
|
+
praisonai profile snapshot current --compare
|
|
295
|
+
praisonai profile snapshot v2.0 --format json
|
|
296
|
+
"""
|
|
297
|
+
try:
|
|
298
|
+
from ..features.profiler import (
|
|
299
|
+
run_profile_suite,
|
|
300
|
+
ScenarioConfig,
|
|
301
|
+
PerfSnapshotManager,
|
|
302
|
+
create_snapshot_from_suite,
|
|
303
|
+
format_comparison_report,
|
|
304
|
+
)
|
|
305
|
+
except ImportError as e:
|
|
306
|
+
typer.echo(f"Error: Profiler module not available: {e}", err=True)
|
|
307
|
+
raise typer.Exit(1)
|
|
308
|
+
|
|
309
|
+
manager = PerfSnapshotManager()
|
|
310
|
+
|
|
311
|
+
if compare:
|
|
312
|
+
# Load baseline and compare
|
|
313
|
+
baseline_snap = manager.load_baseline()
|
|
314
|
+
if not baseline_snap:
|
|
315
|
+
typer.echo("ā No baseline found. Create one with: praisonai profile snapshot --baseline", err=True)
|
|
316
|
+
raise typer.Exit(1)
|
|
317
|
+
|
|
318
|
+
typer.echo("š¬ Running current profile for comparison...", err=True)
|
|
319
|
+
|
|
320
|
+
# Run quick suite
|
|
321
|
+
scenarios = [
|
|
322
|
+
ScenarioConfig(name="simple", prompt="hi", stream=False, iterations=2, warmup=1),
|
|
323
|
+
]
|
|
324
|
+
result = run_profile_suite(
|
|
325
|
+
output_dir="/tmp/praisonai_snapshot_compare",
|
|
326
|
+
scenarios=scenarios,
|
|
327
|
+
verbose=False,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
current_snap = create_snapshot_from_suite(result, name)
|
|
331
|
+
comparison = manager.compare(current_snap)
|
|
332
|
+
|
|
333
|
+
if output_format == "json":
|
|
334
|
+
import json
|
|
335
|
+
typer.echo(json.dumps(comparison.to_dict(), indent=2))
|
|
336
|
+
else:
|
|
337
|
+
typer.echo(format_comparison_report(comparison))
|
|
338
|
+
|
|
339
|
+
if comparison.is_regression():
|
|
340
|
+
raise typer.Exit(1) # Exit with error on regression
|
|
341
|
+
else:
|
|
342
|
+
# Create new snapshot
|
|
343
|
+
typer.echo(f"š¬ Creating performance snapshot '{name}'...", err=True)
|
|
344
|
+
|
|
345
|
+
scenarios = [
|
|
346
|
+
ScenarioConfig(name="simple", prompt="hi", stream=False, iterations=3, warmup=1),
|
|
347
|
+
ScenarioConfig(name="stream", prompt="hi", stream=True, iterations=2, warmup=1),
|
|
348
|
+
]
|
|
349
|
+
result = run_profile_suite(
|
|
350
|
+
output_dir="/tmp/praisonai_snapshot",
|
|
351
|
+
scenarios=scenarios,
|
|
352
|
+
verbose=True,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
snapshot = create_snapshot_from_suite(result, name)
|
|
356
|
+
|
|
357
|
+
if baseline:
|
|
358
|
+
path = manager.save_baseline(snapshot)
|
|
359
|
+
typer.echo(f"\nā
Baseline saved to: {path}")
|
|
360
|
+
else:
|
|
361
|
+
path = manager.save(snapshot)
|
|
362
|
+
typer.echo(f"\nā
Snapshot saved to: {path}")
|
|
363
|
+
|
|
364
|
+
if output_format == "json":
|
|
365
|
+
import json
|
|
366
|
+
typer.echo(json.dumps(snapshot.to_dict(), indent=2))
|
|
367
|
+
else:
|
|
368
|
+
typer.echo("\nSnapshot Summary:")
|
|
369
|
+
typer.echo(f" Startup Cold: {snapshot.startup_cold_ms:.2f}ms")
|
|
370
|
+
typer.echo(f" Import Time: {snapshot.import_time_ms:.2f}ms")
|
|
371
|
+
typer.echo(f" Query Time: {snapshot.query_time_ms:.2f}ms")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@app.command("optimize")
|
|
375
|
+
def profile_optimize(
|
|
376
|
+
prewarm: bool = typer.Option(False, "--prewarm", help="Enable provider pre-warming"),
|
|
377
|
+
lite: bool = typer.Option(False, "--lite", help="Enable lite mode (skip type validation)"),
|
|
378
|
+
show_config: bool = typer.Option(False, "--show", help="Show current optimization config"),
|
|
379
|
+
):
|
|
380
|
+
"""
|
|
381
|
+
Configure performance optimizations.
|
|
382
|
+
|
|
383
|
+
All optimizations are opt-in and safe by default.
|
|
384
|
+
|
|
385
|
+
Examples:
|
|
386
|
+
praisonai profile optimize --show
|
|
387
|
+
praisonai profile optimize --prewarm
|
|
388
|
+
praisonai profile optimize --lite
|
|
389
|
+
"""
|
|
390
|
+
try:
|
|
391
|
+
from ..features.profiler import (
|
|
392
|
+
PrewarmManager,
|
|
393
|
+
get_lite_mode_config,
|
|
394
|
+
get_provider_cache,
|
|
395
|
+
)
|
|
396
|
+
except ImportError as e:
|
|
397
|
+
typer.echo(f"Error: Profiler module not available: {e}", err=True)
|
|
398
|
+
raise typer.Exit(1)
|
|
399
|
+
|
|
400
|
+
if show_config:
|
|
401
|
+
typer.echo("=" * 50)
|
|
402
|
+
typer.echo("Performance Optimization Status")
|
|
403
|
+
typer.echo("=" * 50)
|
|
404
|
+
|
|
405
|
+
lite_config = get_lite_mode_config()
|
|
406
|
+
typer.echo(f"\nLite Mode: {'Enabled' if lite_config.enabled else 'Disabled'}")
|
|
407
|
+
if lite_config.enabled:
|
|
408
|
+
typer.echo(f" Skip Type Validation: {lite_config.skip_type_validation}")
|
|
409
|
+
typer.echo(f" Skip Model Validation: {lite_config.skip_model_validation}")
|
|
410
|
+
typer.echo(f" Minimal Imports: {lite_config.minimal_imports}")
|
|
411
|
+
|
|
412
|
+
typer.echo(f"\nPre-warming: {'Enabled' if PrewarmManager.is_enabled() else 'Disabled'}")
|
|
413
|
+
|
|
414
|
+
cache = get_provider_cache()
|
|
415
|
+
stats = cache.get_stats()
|
|
416
|
+
typer.echo("\nProvider Cache:")
|
|
417
|
+
typer.echo(f" Hits: {stats['hits']}")
|
|
418
|
+
typer.echo(f" Misses: {stats['misses']}")
|
|
419
|
+
typer.echo(f" Size: {stats['size']}")
|
|
420
|
+
|
|
421
|
+
typer.echo("\n" + "=" * 50)
|
|
422
|
+
typer.echo("\nTo enable optimizations, set environment variables:")
|
|
423
|
+
typer.echo(" PRAISONAI_LITE_MODE=1")
|
|
424
|
+
typer.echo(" PRAISONAI_SKIP_TYPE_VALIDATION=1")
|
|
425
|
+
typer.echo(" PRAISONAI_MINIMAL_IMPORTS=1")
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
if prewarm:
|
|
429
|
+
typer.echo("š„ Enabling provider pre-warming...")
|
|
430
|
+
PrewarmManager.enable()
|
|
431
|
+
|
|
432
|
+
import os
|
|
433
|
+
if os.environ.get('OPENAI_API_KEY'):
|
|
434
|
+
typer.echo(" Pre-warming OpenAI...")
|
|
435
|
+
PrewarmManager.prewarm_provider('openai')
|
|
436
|
+
if os.environ.get('ANTHROPIC_API_KEY'):
|
|
437
|
+
typer.echo(" Pre-warming Anthropic...")
|
|
438
|
+
PrewarmManager.prewarm_provider('anthropic')
|
|
439
|
+
|
|
440
|
+
typer.echo("ā
Pre-warming initiated (runs in background)")
|
|
441
|
+
|
|
442
|
+
if lite:
|
|
443
|
+
typer.echo("ā” Lite mode configuration:")
|
|
444
|
+
typer.echo(" Set these environment variables to enable:")
|
|
445
|
+
typer.echo(" export PRAISONAI_LITE_MODE=1")
|
|
446
|
+
typer.echo(" export PRAISONAI_SKIP_TYPE_VALIDATION=1")
|
|
447
|
+
typer.echo(" export PRAISONAI_MINIMAL_IMPORTS=1")
|
|
448
|
+
|
|
449
|
+
if not prewarm and not lite and not show_config:
|
|
450
|
+
typer.echo("Use --show to see current config, or --prewarm/--lite to enable optimizations")
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
@app.callback(invoke_without_command=True)
|
|
454
|
+
def profile_callback(ctx: typer.Context):
|
|
455
|
+
"""Show profile help if no subcommand."""
|
|
456
|
+
if ctx.invoked_subcommand is None:
|
|
457
|
+
typer.echo(ctx.get_help())
|