praisonai-code 0.0.1__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_code/__init__.py +17 -0
- praisonai_code/cli/__init__.py +12 -0
- praisonai_code/cli/_forward_shim.py +10 -0
- praisonai_code/cli/_paths.py +88 -0
- praisonai_code/cli/_warnings.py +58 -0
- praisonai_code/cli/app.py +757 -0
- praisonai_code/cli/approval_backend.py +272 -0
- praisonai_code/cli/branding.py +94 -0
- praisonai_code/cli/commands/__init__.py +114 -0
- praisonai_code/cli/commands/acp.py +80 -0
- praisonai_code/cli/commands/agent.py +116 -0
- praisonai_code/cli/commands/agents.py +80 -0
- praisonai_code/cli/commands/app.py +139 -0
- praisonai_code/cli/commands/attach.py +95 -0
- praisonai_code/cli/commands/audit.py +102 -0
- praisonai_code/cli/commands/auth.py +508 -0
- praisonai_code/cli/commands/batch.py +848 -0
- praisonai_code/cli/commands/benchmark.py +286 -0
- praisonai_code/cli/commands/browser.py +299 -0
- praisonai_code/cli/commands/call.py +45 -0
- praisonai_code/cli/commands/chat.py +332 -0
- praisonai_code/cli/commands/checkpoint.py +170 -0
- praisonai_code/cli/commands/code.py +276 -0
- praisonai_code/cli/commands/command.py +114 -0
- praisonai_code/cli/commands/commit.py +47 -0
- praisonai_code/cli/commands/completion.py +333 -0
- praisonai_code/cli/commands/config.py +681 -0
- praisonai_code/cli/commands/context.py +414 -0
- praisonai_code/cli/commands/daemon.py +203 -0
- praisonai_code/cli/commands/debug.py +142 -0
- praisonai_code/cli/commands/deploy.py +71 -0
- praisonai_code/cli/commands/diag.py +55 -0
- praisonai_code/cli/commands/docs.py +1575 -0
- praisonai_code/cli/commands/doctor.py +332 -0
- praisonai_code/cli/commands/endpoints.py +51 -0
- praisonai_code/cli/commands/environment.py +179 -0
- praisonai_code/cli/commands/eval.py +131 -0
- praisonai_code/cli/commands/examples.py +953 -0
- praisonai_code/cli/commands/flow.py +436 -0
- praisonai_code/cli/commands/github.py +752 -0
- praisonai_code/cli/commands/hooks.py +74 -0
- praisonai_code/cli/commands/init.py +174 -0
- praisonai_code/cli/commands/knowledge.py +440 -0
- praisonai_code/cli/commands/langextract.py +120 -0
- praisonai_code/cli/commands/langfuse.py +984 -0
- praisonai_code/cli/commands/loop.py +211 -0
- praisonai_code/cli/commands/lsp.py +112 -0
- praisonai_code/cli/commands/managed.py +659 -0
- praisonai_code/cli/commands/mcp.py +763 -0
- praisonai_code/cli/commands/memory.py +298 -0
- praisonai_code/cli/commands/models.py +264 -0
- praisonai_code/cli/commands/n8n.py +326 -0
- praisonai_code/cli/commands/obs.py +19 -0
- praisonai_code/cli/commands/package.py +76 -0
- praisonai_code/cli/commands/paths.py +106 -0
- praisonai_code/cli/commands/permissions.py +272 -0
- praisonai_code/cli/commands/plugins.py +609 -0
- praisonai_code/cli/commands/port.py +530 -0
- praisonai_code/cli/commands/profile.py +466 -0
- praisonai_code/cli/commands/publish.py +193 -0
- praisonai_code/cli/commands/rag.py +913 -0
- praisonai_code/cli/commands/realtime.py +52 -0
- praisonai_code/cli/commands/recipe.py +684 -0
- praisonai_code/cli/commands/registry.py +59 -0
- praisonai_code/cli/commands/replay.py +830 -0
- praisonai_code/cli/commands/research.py +49 -0
- praisonai_code/cli/commands/retrieval.py +377 -0
- praisonai_code/cli/commands/rules.py +71 -0
- praisonai_code/cli/commands/run.py +1573 -0
- praisonai_code/cli/commands/sandbox.py +371 -0
- praisonai_code/cli/commands/schedule.py +529 -0
- praisonai_code/cli/commands/serve.py +690 -0
- praisonai_code/cli/commands/session.py +450 -0
- praisonai_code/cli/commands/setup.py +174 -0
- praisonai_code/cli/commands/skills.py +545 -0
- praisonai_code/cli/commands/standardise.py +711 -0
- praisonai_code/cli/commands/templates.py +54 -0
- praisonai_code/cli/commands/test.py +558 -0
- praisonai_code/cli/commands/todo.py +74 -0
- praisonai_code/cli/commands/tools.py +205 -0
- praisonai_code/cli/commands/traces.py +145 -0
- praisonai_code/cli/commands/tracker.py +852 -0
- praisonai_code/cli/commands/train.py +613 -0
- praisonai_code/cli/commands/ui.py +172 -0
- praisonai_code/cli/commands/up.py +354 -0
- praisonai_code/cli/commands/validate.py +291 -0
- praisonai_code/cli/commands/version.py +101 -0
- praisonai_code/cli/commands/workflow.py +97 -0
- praisonai_code/cli/config_loader.py +437 -0
- praisonai_code/cli/configuration/__init__.py +27 -0
- praisonai_code/cli/configuration/config.schema.json +57 -0
- praisonai_code/cli/configuration/credentials.py +446 -0
- praisonai_code/cli/configuration/loader.py +364 -0
- praisonai_code/cli/configuration/model_resolver.py +161 -0
- praisonai_code/cli/configuration/oauth.py +389 -0
- praisonai_code/cli/configuration/paths.py +224 -0
- praisonai_code/cli/configuration/resolver.py +687 -0
- praisonai_code/cli/configuration/schema.py +317 -0
- praisonai_code/cli/execution/__init__.py +99 -0
- praisonai_code/cli/execution/core.py +208 -0
- praisonai_code/cli/execution/profiler.py +898 -0
- praisonai_code/cli/execution/request.py +85 -0
- praisonai_code/cli/execution/result.py +74 -0
- praisonai_code/cli/fallback_schema.py +416 -0
- praisonai_code/cli/features/__init__.py +278 -0
- praisonai_code/cli/features/_endpoint_registry.py +64 -0
- praisonai_code/cli/features/_search_registry.py +43 -0
- praisonai_code/cli/features/acp.py +236 -0
- praisonai_code/cli/features/action_orchestrator.py +576 -0
- praisonai_code/cli/features/agent_scheduler.py +773 -0
- praisonai_code/cli/features/agent_tools.py +603 -0
- praisonai_code/cli/features/agents.py +397 -0
- praisonai_code/cli/features/at_mentions.py +471 -0
- praisonai_code/cli/features/audit_cli.py +270 -0
- praisonai_code/cli/features/auto_memory.py +182 -0
- praisonai_code/cli/features/auto_mode.py +552 -0
- praisonai_code/cli/features/autonomy_mode.py +546 -0
- praisonai_code/cli/features/background.py +356 -0
- praisonai_code/cli/features/base.py +168 -0
- praisonai_code/cli/features/benchmark.py +1462 -0
- praisonai_code/cli/features/capabilities.py +1326 -0
- praisonai_code/cli/features/checkpoints.py +345 -0
- praisonai_code/cli/features/cli_profiler.py +335 -0
- praisonai_code/cli/features/code_intelligence.py +666 -0
- praisonai_code/cli/features/compaction.py +294 -0
- praisonai_code/cli/features/compare.py +534 -0
- praisonai_code/cli/features/config_hierarchy.py +366 -0
- praisonai_code/cli/features/context_manager.py +597 -0
- praisonai_code/cli/features/cost_tracker.py +514 -0
- praisonai_code/cli/features/csv_test_runner.py +736 -0
- praisonai_code/cli/features/custom_definitions.py +790 -0
- praisonai_code/cli/features/debug.py +810 -0
- praisonai_code/cli/features/deploy.py +605 -0
- praisonai_code/cli/features/diag.py +289 -0
- praisonai_code/cli/features/display_jsonl.py +173 -0
- praisonai_code/cli/features/doctor/__init__.py +63 -0
- praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
- praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
- praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
- praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
- praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
- praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
- praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
- praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
- praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
- praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
- praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai_code/cli/features/doctor/engine.py +266 -0
- praisonai_code/cli/features/doctor/formatters.py +377 -0
- praisonai_code/cli/features/doctor/handler.py +564 -0
- praisonai_code/cli/features/doctor/models.py +276 -0
- praisonai_code/cli/features/doctor/registry.py +239 -0
- praisonai_code/cli/features/endpoints.py +1016 -0
- praisonai_code/cli/features/eval.py +559 -0
- praisonai_code/cli/features/examples.py +707 -0
- praisonai_code/cli/features/external_agents.py +231 -0
- praisonai_code/cli/features/fast_context.py +410 -0
- praisonai_code/cli/features/file_history.py +320 -0
- praisonai_code/cli/features/flow_display.py +566 -0
- praisonai_code/cli/features/git_attribution.py +159 -0
- praisonai_code/cli/features/git_integration.py +651 -0
- praisonai_code/cli/features/guardrail.py +171 -0
- praisonai_code/cli/features/handoff.py +252 -0
- praisonai_code/cli/features/hooks.py +583 -0
- praisonai_code/cli/features/hybrid_workflow.py +391 -0
- praisonai_code/cli/features/image.py +384 -0
- praisonai_code/cli/features/interactive_core_headless.py +450 -0
- praisonai_code/cli/features/interactive_runtime.py +600 -0
- praisonai_code/cli/features/interactive_test_harness.py +537 -0
- praisonai_code/cli/features/interactive_tools.py +428 -0
- praisonai_code/cli/features/interactive_tui.py +603 -0
- praisonai_code/cli/features/job_workflow.py +906 -0
- praisonai_code/cli/features/jobs.py +632 -0
- praisonai_code/cli/features/knowledge.py +531 -0
- praisonai_code/cli/features/knowledge_cli.py +438 -0
- praisonai_code/cli/features/lite.py +244 -0
- praisonai_code/cli/features/logs.py +200 -0
- praisonai_code/cli/features/lsp_cli.py +225 -0
- praisonai_code/cli/features/lsp_diagnostics.py +185 -0
- praisonai_code/cli/features/mcp.py +344 -0
- praisonai_code/cli/features/message_queue.py +587 -0
- praisonai_code/cli/features/metrics.py +210 -0
- praisonai_code/cli/features/migrate.py +1329 -0
- praisonai_code/cli/features/migration_flow.py +463 -0
- praisonai_code/cli/features/migration_spec.py +276 -0
- praisonai_code/cli/features/n8n.py +703 -0
- praisonai_code/cli/features/observability.py +293 -0
- praisonai_code/cli/features/ollama.py +361 -0
- praisonai_code/cli/features/output_modes.py +155 -0
- praisonai_code/cli/features/output_style.py +273 -0
- praisonai_code/cli/features/package.py +631 -0
- praisonai_code/cli/features/performance.py +308 -0
- praisonai_code/cli/features/persistence.py +636 -0
- praisonai_code/cli/features/profiler/__init__.py +81 -0
- praisonai_code/cli/features/profiler/core.py +558 -0
- praisonai_code/cli/features/profiler/optimizations.py +652 -0
- praisonai_code/cli/features/profiler/suite.py +386 -0
- praisonai_code/cli/features/queue/__init__.py +73 -0
- praisonai_code/cli/features/queue/manager.py +435 -0
- praisonai_code/cli/features/queue/models.py +289 -0
- praisonai_code/cli/features/queue/persistence.py +564 -0
- praisonai_code/cli/features/queue/scheduler.py +529 -0
- praisonai_code/cli/features/queue/worker.py +400 -0
- praisonai_code/cli/features/recipe.py +2187 -0
- praisonai_code/cli/features/recipe_creator.py +996 -0
- praisonai_code/cli/features/recipe_optimizer.py +1364 -0
- praisonai_code/cli/features/recipe_prompts.py +226 -0
- praisonai_code/cli/features/registry.py +229 -0
- praisonai_code/cli/features/repo_map.py +860 -0
- praisonai_code/cli/features/router.py +466 -0
- praisonai_code/cli/features/safe_shell.py +427 -0
- praisonai_code/cli/features/sandbox_cli.py +283 -0
- praisonai_code/cli/features/sandbox_executor.py +536 -0
- praisonai_code/cli/features/sdk_knowledge.py +500 -0
- praisonai_code/cli/features/session.py +222 -0
- praisonai_code/cli/features/session_checkpoints.py +208 -0
- praisonai_code/cli/features/setup/__init__.py +9 -0
- praisonai_code/cli/features/setup/handler.py +355 -0
- praisonai_code/cli/features/setup/templates.py +62 -0
- praisonai_code/cli/features/skills.py +940 -0
- praisonai_code/cli/features/slash_commands.py +692 -0
- praisonai_code/cli/features/telemetry.py +179 -0
- praisonai_code/cli/features/templates.py +1390 -0
- praisonai_code/cli/features/thinking.py +343 -0
- praisonai_code/cli/features/todo.py +334 -0
- praisonai_code/cli/features/tools.py +680 -0
- praisonai_code/cli/features/tui/__init__.py +83 -0
- praisonai_code/cli/features/tui/app.py +871 -0
- praisonai_code/cli/features/tui/cli.py +580 -0
- praisonai_code/cli/features/tui/config.py +150 -0
- praisonai_code/cli/features/tui/debug.py +526 -0
- praisonai_code/cli/features/tui/events.py +99 -0
- praisonai_code/cli/features/tui/mock_provider.py +328 -0
- praisonai_code/cli/features/tui/orchestrator.py +652 -0
- praisonai_code/cli/features/tui/screens/__init__.py +50 -0
- praisonai_code/cli/features/tui/screens/help.py +157 -0
- praisonai_code/cli/features/tui/screens/main.py +568 -0
- praisonai_code/cli/features/tui/screens/queue.py +174 -0
- praisonai_code/cli/features/tui/screens/session.py +124 -0
- praisonai_code/cli/features/tui/screens/settings.py +148 -0
- praisonai_code/cli/features/tui/session_store.py +198 -0
- praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
- praisonai_code/cli/features/tui/widgets/chat.py +263 -0
- praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
- praisonai_code/cli/features/tui/widgets/composer.py +292 -0
- praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
- praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
- praisonai_code/cli/features/tui/widgets/status.py +181 -0
- praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
- praisonai_code/cli/features/wizard.py +289 -0
- praisonai_code/cli/features/workflow.py +802 -0
- praisonai_code/cli/features/yaml_utils.py +321 -0
- praisonai_code/cli/interactive/__init__.py +48 -0
- praisonai_code/cli/interactive/async_tui.py +1218 -0
- praisonai_code/cli/interactive/config.py +139 -0
- praisonai_code/cli/interactive/core.py +618 -0
- praisonai_code/cli/interactive/events.py +131 -0
- praisonai_code/cli/interactive/frontends/__init__.py +31 -0
- praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
- praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
- praisonai_code/cli/interactive/praison_io.py +502 -0
- praisonai_code/cli/interactive/repl.py +297 -0
- praisonai_code/cli/interactive/split_tui.py +456 -0
- praisonai_code/cli/interactive/tui_app.py +457 -0
- praisonai_code/cli/langfuse_client.py +360 -0
- praisonai_code/cli/main.py +7421 -0
- praisonai_code/cli/output/__init__.py +25 -0
- praisonai_code/cli/output/console.py +456 -0
- praisonai_code/cli/output/event_bridge.py +191 -0
- praisonai_code/cli/schedule_cli.py +54 -0
- praisonai_code/cli/schema_provider.py +23 -0
- praisonai_code/cli/session/__init__.py +16 -0
- praisonai_code/cli/session/resume.py +148 -0
- praisonai_code/cli/session/unified.py +548 -0
- praisonai_code/cli/state/__init__.py +31 -0
- praisonai_code/cli/state/identifiers.py +161 -0
- praisonai_code/cli/state/project_sessions.py +383 -0
- praisonai_code/cli/state/sessions.py +390 -0
- praisonai_code/cli/ui/__init__.py +160 -0
- praisonai_code/cli/ui/config.py +46 -0
- praisonai_code/cli/ui/events.py +61 -0
- praisonai_code/cli/ui/mg_backend.py +342 -0
- praisonai_code/cli/ui/plain.py +133 -0
- praisonai_code/cli/ui/rich_backend.py +162 -0
- praisonai_code/cli/unified_schema.py +655 -0
- praisonai_code/cli/utils/env_utils.py +126 -0
- praisonai_code/cli/utils/project.py +131 -0
- praisonai_code/cli_backends/__init__.py +73 -0
- praisonai_code/cli_backends/claude.py +373 -0
- praisonai_code/cli_backends/registry.py +113 -0
- praisonai_code/runtime/__init__.py +36 -0
- praisonai_code/runtime/__main__.py +81 -0
- praisonai_code/runtime/client.py +131 -0
- praisonai_code/runtime/descriptor.py +209 -0
- praisonai_code/runtime/server.py +356 -0
- praisonai_code-0.0.1.dist-info/METADATA +80 -0
- praisonai_code-0.0.1.dist-info/RECORD +309 -0
- praisonai_code-0.0.1.dist-info/WHEEL +5 -0
- praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Train command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides two types of training:
|
|
5
|
+
1. LLM Fine-tuning: praisonai train dataset.json
|
|
6
|
+
2. Agent Training: praisonai train agents [OPTIONS]
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(help="Training commands for models and agents")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.callback()
|
|
19
|
+
def train_callback():
|
|
20
|
+
"""
|
|
21
|
+
Training commands for models and agents.
|
|
22
|
+
|
|
23
|
+
For LLM fine-tuning:
|
|
24
|
+
praisonai train llm dataset.json
|
|
25
|
+
|
|
26
|
+
For agent training (iterative improvement):
|
|
27
|
+
praisonai train agents --input "What is Python?"
|
|
28
|
+
praisonai train agents --input "Explain AI" --human
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command("llm")
|
|
34
|
+
def train_llm(
|
|
35
|
+
dataset: str = typer.Argument(..., help="Training dataset path"),
|
|
36
|
+
model: Optional[str] = typer.Option(None, "--model", "-m", help="Base model to fine-tune"),
|
|
37
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Fine-tune LLM models using Unsloth.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
praisonai train llm dataset.json
|
|
44
|
+
praisonai train llm --model llama-3.1 dataset.json
|
|
45
|
+
"""
|
|
46
|
+
from praisonai.cli.main import PraisonAI
|
|
47
|
+
import sys
|
|
48
|
+
|
|
49
|
+
argv = ['train', dataset]
|
|
50
|
+
if model:
|
|
51
|
+
argv.extend(['--model', model])
|
|
52
|
+
if verbose:
|
|
53
|
+
argv.append('--verbose')
|
|
54
|
+
|
|
55
|
+
original_argv = sys.argv
|
|
56
|
+
sys.argv = ['praisonai'] + argv
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
praison = PraisonAI()
|
|
60
|
+
praison.main()
|
|
61
|
+
except SystemExit:
|
|
62
|
+
pass
|
|
63
|
+
finally:
|
|
64
|
+
sys.argv = original_argv
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command("agents")
|
|
68
|
+
def train_agents(
|
|
69
|
+
agent_file: Optional[str] = typer.Argument(
|
|
70
|
+
None,
|
|
71
|
+
help="Path to agent YAML file or Python file"
|
|
72
|
+
),
|
|
73
|
+
iterations: int = typer.Option(
|
|
74
|
+
3,
|
|
75
|
+
"--iterations", "-n",
|
|
76
|
+
help="Number of training iterations"
|
|
77
|
+
),
|
|
78
|
+
human: bool = typer.Option(
|
|
79
|
+
False,
|
|
80
|
+
"--human", "-h",
|
|
81
|
+
help="Use human feedback instead of LLM grading"
|
|
82
|
+
),
|
|
83
|
+
scenarios: Optional[str] = typer.Option(
|
|
84
|
+
None,
|
|
85
|
+
"--scenarios", "-s",
|
|
86
|
+
help="Path to scenarios JSON file"
|
|
87
|
+
),
|
|
88
|
+
input_text: Optional[str] = typer.Option(
|
|
89
|
+
None,
|
|
90
|
+
"--input", "-i",
|
|
91
|
+
help="Single input text for training (alternative to scenarios file)"
|
|
92
|
+
),
|
|
93
|
+
expected: Optional[str] = typer.Option(
|
|
94
|
+
None,
|
|
95
|
+
"--expected", "-e",
|
|
96
|
+
help="Expected output for the input (optional)"
|
|
97
|
+
),
|
|
98
|
+
output_dir: Optional[str] = typer.Option(
|
|
99
|
+
None,
|
|
100
|
+
"--output", "-o",
|
|
101
|
+
help="Output directory for training data"
|
|
102
|
+
),
|
|
103
|
+
model: Optional[str] = typer.Option(
|
|
104
|
+
None,
|
|
105
|
+
"--model", "-m",
|
|
106
|
+
help="LLM model for grading (default: gpt-4o-mini)"
|
|
107
|
+
),
|
|
108
|
+
verbose: bool = typer.Option(
|
|
109
|
+
True,
|
|
110
|
+
"--verbose/--quiet", "-v/-q",
|
|
111
|
+
help="Show detailed progress"
|
|
112
|
+
),
|
|
113
|
+
dry_run: bool = typer.Option(
|
|
114
|
+
False,
|
|
115
|
+
"--dry-run",
|
|
116
|
+
help="Show what would happen without running"
|
|
117
|
+
),
|
|
118
|
+
storage_backend: Optional[str] = typer.Option(
|
|
119
|
+
None,
|
|
120
|
+
"--storage-backend",
|
|
121
|
+
help="Storage backend: 'file', 'sqlite', or 'redis://url'. Default: file"
|
|
122
|
+
),
|
|
123
|
+
storage_path: Optional[str] = typer.Option(
|
|
124
|
+
None,
|
|
125
|
+
"--storage-path",
|
|
126
|
+
help="Path for storage backend (file dir or sqlite db path)"
|
|
127
|
+
),
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
Train agents through iterative feedback loops.
|
|
131
|
+
|
|
132
|
+
Two modes available:
|
|
133
|
+
|
|
134
|
+
1. LLM-as-Judge (default): Agent runs, LLM grades output,
|
|
135
|
+
provides suggestions, agent re-runs with feedback.
|
|
136
|
+
Fully automated, no human intervention needed.
|
|
137
|
+
|
|
138
|
+
2. Human-in-the-Loop (--human): Agent runs, you review
|
|
139
|
+
and provide feedback, agent re-runs with your feedback.
|
|
140
|
+
|
|
141
|
+
Examples:
|
|
142
|
+
# Simple: Train with a single input
|
|
143
|
+
praisonai train agents --input "What is Python?"
|
|
144
|
+
|
|
145
|
+
# With expected output
|
|
146
|
+
praisonai train agents --input "What is 2+2?" --expected "4"
|
|
147
|
+
|
|
148
|
+
# With scenarios file
|
|
149
|
+
praisonai train agents --scenarios scenarios.json
|
|
150
|
+
|
|
151
|
+
# Human feedback mode
|
|
152
|
+
praisonai train agents --input "Explain AI" --human
|
|
153
|
+
|
|
154
|
+
# More iterations
|
|
155
|
+
praisonai train agents --input "Hello" --iterations 5
|
|
156
|
+
|
|
157
|
+
# With agent file
|
|
158
|
+
praisonai train agents my_agent.yaml --scenarios scenarios.json
|
|
159
|
+
"""
|
|
160
|
+
from ..output.console import get_output_controller
|
|
161
|
+
|
|
162
|
+
output = get_output_controller()
|
|
163
|
+
|
|
164
|
+
# Validate inputs
|
|
165
|
+
if not input_text and not scenarios:
|
|
166
|
+
output.print_error(
|
|
167
|
+
"No training input provided",
|
|
168
|
+
remediation="Use --input 'text' or --scenarios file.json"
|
|
169
|
+
)
|
|
170
|
+
raise typer.Exit(1)
|
|
171
|
+
|
|
172
|
+
if dry_run:
|
|
173
|
+
output.print_panel(
|
|
174
|
+
f"Mode: {'Human-in-the-Loop' if human else 'LLM-as-Judge'}\n"
|
|
175
|
+
f"Iterations: {iterations}\n"
|
|
176
|
+
f"Input: {input_text or 'from scenarios file'}\n"
|
|
177
|
+
f"Scenarios file: {scenarios or 'N/A'}\n"
|
|
178
|
+
f"Agent file: {agent_file or 'default agent'}\n"
|
|
179
|
+
f"Output dir: {output_dir or '~/.praison/train/'}",
|
|
180
|
+
title="Dry Run - Would execute:"
|
|
181
|
+
)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Lazy import to avoid overhead
|
|
185
|
+
try:
|
|
186
|
+
from praisonai.train.agents import AgentTrainer, TrainingScenario
|
|
187
|
+
except ImportError as e:
|
|
188
|
+
output.print_error(f"Failed to import training module: {e}")
|
|
189
|
+
raise typer.Exit(1)
|
|
190
|
+
|
|
191
|
+
# Create or load agent
|
|
192
|
+
agent = None
|
|
193
|
+
if agent_file:
|
|
194
|
+
agent = _load_agent_from_file(agent_file, output)
|
|
195
|
+
if agent is None:
|
|
196
|
+
raise typer.Exit(1)
|
|
197
|
+
else:
|
|
198
|
+
# Create a simple default agent
|
|
199
|
+
try:
|
|
200
|
+
from praisonaiagents import Agent
|
|
201
|
+
agent = Agent(
|
|
202
|
+
instructions="You are a helpful assistant. Provide clear, accurate, and helpful responses."
|
|
203
|
+
)
|
|
204
|
+
except ImportError:
|
|
205
|
+
output.print_error(
|
|
206
|
+
"praisonaiagents not installed",
|
|
207
|
+
remediation="pip install praisonaiagents"
|
|
208
|
+
)
|
|
209
|
+
raise typer.Exit(1)
|
|
210
|
+
|
|
211
|
+
# Create trainer
|
|
212
|
+
storage_dir = Path(output_dir) if output_dir else None
|
|
213
|
+
|
|
214
|
+
from praisonai.train.agents.grader import TrainingGrader
|
|
215
|
+
grader = TrainingGrader(model=model) if model else None
|
|
216
|
+
|
|
217
|
+
# Create storage backend if specified
|
|
218
|
+
backend = None
|
|
219
|
+
if storage_backend:
|
|
220
|
+
backend = _create_storage_backend(storage_backend, storage_path, output)
|
|
221
|
+
if backend is None and storage_backend not in ("file", None):
|
|
222
|
+
raise typer.Exit(1)
|
|
223
|
+
|
|
224
|
+
trainer = AgentTrainer(
|
|
225
|
+
agent=agent,
|
|
226
|
+
iterations=iterations,
|
|
227
|
+
human_mode=human,
|
|
228
|
+
grader=grader,
|
|
229
|
+
storage_dir=storage_dir,
|
|
230
|
+
storage_backend=backend,
|
|
231
|
+
verbose=verbose,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Add scenarios
|
|
235
|
+
if scenarios:
|
|
236
|
+
scenarios_list = _load_scenarios_from_file(scenarios, output)
|
|
237
|
+
if scenarios_list is None:
|
|
238
|
+
raise typer.Exit(1)
|
|
239
|
+
trainer.add_scenarios(scenarios_list)
|
|
240
|
+
elif input_text:
|
|
241
|
+
trainer.add_scenario(TrainingScenario(
|
|
242
|
+
id="cli-input",
|
|
243
|
+
input_text=input_text,
|
|
244
|
+
expected_output=expected,
|
|
245
|
+
))
|
|
246
|
+
|
|
247
|
+
# Run training
|
|
248
|
+
try:
|
|
249
|
+
report = trainer.run()
|
|
250
|
+
|
|
251
|
+
if not verbose:
|
|
252
|
+
report.print_summary()
|
|
253
|
+
|
|
254
|
+
output.print_success(
|
|
255
|
+
f"Training complete! Session: {report.session_id}\n"
|
|
256
|
+
f"Average score: {report.avg_score:.1f}/10\n"
|
|
257
|
+
f"Improvement: {report.improvement:+.1f}"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
except KeyboardInterrupt:
|
|
261
|
+
output.print_warning("Training interrupted by user")
|
|
262
|
+
raise typer.Exit(130)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
output.print_error(f"Training failed: {e}")
|
|
265
|
+
raise typer.Exit(1)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@app.command("list")
|
|
269
|
+
def train_list(
|
|
270
|
+
limit: int = typer.Option(20, "--limit", "-n", help="Max sessions to show"),
|
|
271
|
+
json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
272
|
+
):
|
|
273
|
+
"""List all training sessions."""
|
|
274
|
+
from ..output.console import get_output_controller
|
|
275
|
+
|
|
276
|
+
output = get_output_controller()
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
from praisonai.train.agents.storage import list_training_sessions
|
|
280
|
+
except ImportError:
|
|
281
|
+
output.print_error("Training module not available")
|
|
282
|
+
raise typer.Exit(1)
|
|
283
|
+
|
|
284
|
+
sessions = list_training_sessions(limit=limit)
|
|
285
|
+
|
|
286
|
+
if json_output or output.is_json_mode:
|
|
287
|
+
output.print_json({
|
|
288
|
+
"sessions": [s.to_dict() for s in sessions]
|
|
289
|
+
})
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
if not sessions:
|
|
293
|
+
output.print_info("No training sessions found")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
headers = ["Session ID", "Iterations", "Size", "Modified"]
|
|
297
|
+
rows = []
|
|
298
|
+
|
|
299
|
+
for s in sessions:
|
|
300
|
+
modified = s.modified_at.strftime("%Y-%m-%d %H:%M")
|
|
301
|
+
size_kb = s.size_bytes / 1024
|
|
302
|
+
size_str = f"{size_kb:.1f} KB" if size_kb >= 1 else f"{s.size_bytes} B"
|
|
303
|
+
|
|
304
|
+
rows.append([
|
|
305
|
+
s.session_id[:30] + "..." if len(s.session_id) > 30 else s.session_id,
|
|
306
|
+
str(s.iteration_count),
|
|
307
|
+
size_str,
|
|
308
|
+
modified,
|
|
309
|
+
])
|
|
310
|
+
|
|
311
|
+
output.print_table(headers, rows, title="Training Sessions")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@app.command("show")
|
|
315
|
+
def train_show(
|
|
316
|
+
session_id: str = typer.Argument(..., help="Session ID to show"),
|
|
317
|
+
iterations_flag: bool = typer.Option(False, "--iterations", "-i", help="Show detailed iteration info"),
|
|
318
|
+
json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
319
|
+
):
|
|
320
|
+
"""
|
|
321
|
+
Show details of a training session.
|
|
322
|
+
|
|
323
|
+
Examples:
|
|
324
|
+
# Show session summary
|
|
325
|
+
praisonai train show train-abc123
|
|
326
|
+
|
|
327
|
+
# Show with detailed iterations
|
|
328
|
+
praisonai train show train-abc123 --iterations
|
|
329
|
+
|
|
330
|
+
# Output as JSON
|
|
331
|
+
praisonai train show train-abc123 --json
|
|
332
|
+
"""
|
|
333
|
+
from ..output.console import get_output_controller
|
|
334
|
+
|
|
335
|
+
output = get_output_controller()
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
from praisonai.train.agents.storage import TrainingStorage
|
|
339
|
+
except ImportError:
|
|
340
|
+
output.print_error("Training module not available")
|
|
341
|
+
raise typer.Exit(1)
|
|
342
|
+
|
|
343
|
+
storage = TrainingStorage(session_id=session_id)
|
|
344
|
+
|
|
345
|
+
if not storage.storage_path.exists():
|
|
346
|
+
output.print_error(f"Session not found: {session_id}")
|
|
347
|
+
raise typer.Exit(1)
|
|
348
|
+
|
|
349
|
+
report = storage.load_report()
|
|
350
|
+
iterations = storage.load_iterations()
|
|
351
|
+
|
|
352
|
+
if json_output or output.is_json_mode:
|
|
353
|
+
output.print_json({
|
|
354
|
+
"session_id": session_id,
|
|
355
|
+
"report": report.to_dict() if report else None,
|
|
356
|
+
"iterations": [it.to_dict() for it in iterations],
|
|
357
|
+
})
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
if report:
|
|
361
|
+
report.print_summary()
|
|
362
|
+
|
|
363
|
+
# Show best iteration
|
|
364
|
+
best = report.get_best_iteration()
|
|
365
|
+
if best:
|
|
366
|
+
output.print(f"\n✨ Best Iteration: #{best.iteration_num} (Score: {best.score}/10)")
|
|
367
|
+
|
|
368
|
+
if iterations:
|
|
369
|
+
output.print("\nIterations:")
|
|
370
|
+
|
|
371
|
+
# Find best score for highlighting
|
|
372
|
+
best_score = max(it.score for it in iterations) if iterations else 0
|
|
373
|
+
|
|
374
|
+
for it in iterations:
|
|
375
|
+
# Highlight best iteration
|
|
376
|
+
marker = "★" if it.score == best_score else " "
|
|
377
|
+
feedback_preview = it.feedback[:50] + "..." if len(it.feedback) > 50 else it.feedback
|
|
378
|
+
output.print(f" {marker} [{it.iteration_num}] Score: {it.score}/10 - {feedback_preview}")
|
|
379
|
+
|
|
380
|
+
# Show suggestions if --iterations flag
|
|
381
|
+
if iterations_flag and it.suggestions:
|
|
382
|
+
for suggestion in it.suggestions[:3]: # Show top 3 suggestions
|
|
383
|
+
output.print(f" → {suggestion}")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@app.command("apply")
|
|
387
|
+
def train_apply(
|
|
388
|
+
session_id: str = typer.Argument(..., help="Training session ID to apply"),
|
|
389
|
+
agent_file: Optional[str] = typer.Option(
|
|
390
|
+
None,
|
|
391
|
+
"--agent", "-a",
|
|
392
|
+
help="Path to agent YAML file"
|
|
393
|
+
),
|
|
394
|
+
iteration: Optional[int] = typer.Option(
|
|
395
|
+
None,
|
|
396
|
+
"--iteration", "-n",
|
|
397
|
+
help="Specific iteration number (default: best score)"
|
|
398
|
+
),
|
|
399
|
+
run_prompt: Optional[str] = typer.Option(
|
|
400
|
+
None,
|
|
401
|
+
"--run", "-r",
|
|
402
|
+
help="Run agent with this prompt after applying training"
|
|
403
|
+
),
|
|
404
|
+
json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
405
|
+
):
|
|
406
|
+
"""
|
|
407
|
+
Apply training to an agent.
|
|
408
|
+
|
|
409
|
+
Loads training suggestions from a session and applies them to an agent
|
|
410
|
+
via hooks. By default, uses the iteration with the best score.
|
|
411
|
+
|
|
412
|
+
Examples:
|
|
413
|
+
# Apply best iteration to default agent
|
|
414
|
+
praisonai train apply train-abc123
|
|
415
|
+
|
|
416
|
+
# Apply specific iteration
|
|
417
|
+
praisonai train apply train-abc123 --iteration 2
|
|
418
|
+
|
|
419
|
+
# Apply to agent from YAML file
|
|
420
|
+
praisonai train apply train-abc123 --agent my_agent.yaml
|
|
421
|
+
|
|
422
|
+
# Apply and run immediately
|
|
423
|
+
praisonai train apply train-abc123 --run "Hello, how are you?"
|
|
424
|
+
"""
|
|
425
|
+
from ..output.console import get_output_controller
|
|
426
|
+
|
|
427
|
+
output = get_output_controller()
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
from praisonai.train.agents import apply_training, get_training_profile
|
|
431
|
+
from praisonai.train.agents.storage import TrainingStorage
|
|
432
|
+
except ImportError as e:
|
|
433
|
+
output.print_error(f"Training module not available: {e}")
|
|
434
|
+
raise typer.Exit(1)
|
|
435
|
+
|
|
436
|
+
# Verify session exists
|
|
437
|
+
storage = TrainingStorage(session_id=session_id)
|
|
438
|
+
if not storage.storage_path.exists():
|
|
439
|
+
output.print_error(f"Session not found: {session_id}")
|
|
440
|
+
raise typer.Exit(1)
|
|
441
|
+
|
|
442
|
+
# Get the training profile
|
|
443
|
+
profile = get_training_profile(
|
|
444
|
+
session_id=session_id,
|
|
445
|
+
iteration=iteration,
|
|
446
|
+
agent_name="agent",
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if profile is None:
|
|
450
|
+
output.print_error(f"Could not load training profile from session {session_id}")
|
|
451
|
+
raise typer.Exit(1)
|
|
452
|
+
|
|
453
|
+
if json_output or output.is_json_mode:
|
|
454
|
+
output.print_json({
|
|
455
|
+
"session_id": session_id,
|
|
456
|
+
"iteration": profile.iteration_num,
|
|
457
|
+
"score": profile.quality_score,
|
|
458
|
+
"suggestions": profile.suggestions,
|
|
459
|
+
"summary": profile.summary,
|
|
460
|
+
})
|
|
461
|
+
if not run_prompt:
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
# Show profile info
|
|
465
|
+
output.print_panel(
|
|
466
|
+
f"Session: {profile.session_id}\n"
|
|
467
|
+
f"Iteration: {profile.iteration_num}\n"
|
|
468
|
+
f"Score: {profile.quality_score}/10\n"
|
|
469
|
+
f"Suggestions: {len(profile.suggestions)}",
|
|
470
|
+
title="Training Profile"
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
if profile.suggestions:
|
|
474
|
+
output.print("\nSuggestions:")
|
|
475
|
+
for i, suggestion in enumerate(profile.suggestions, 1):
|
|
476
|
+
output.print(f" {i}. {suggestion}")
|
|
477
|
+
|
|
478
|
+
# If run_prompt provided, create agent and run
|
|
479
|
+
if run_prompt:
|
|
480
|
+
# Create or load agent
|
|
481
|
+
agent = None
|
|
482
|
+
if agent_file:
|
|
483
|
+
agent = _load_agent_from_file(agent_file, output)
|
|
484
|
+
if agent is None:
|
|
485
|
+
raise typer.Exit(1)
|
|
486
|
+
else:
|
|
487
|
+
try:
|
|
488
|
+
from praisonaiagents import Agent
|
|
489
|
+
agent = Agent(
|
|
490
|
+
instructions="You are a helpful assistant."
|
|
491
|
+
)
|
|
492
|
+
except ImportError:
|
|
493
|
+
output.print_error(
|
|
494
|
+
"praisonaiagents not installed",
|
|
495
|
+
remediation="pip install praisonaiagents"
|
|
496
|
+
)
|
|
497
|
+
raise typer.Exit(1)
|
|
498
|
+
|
|
499
|
+
# Apply training
|
|
500
|
+
success = apply_training(agent, profile=profile)
|
|
501
|
+
if not success:
|
|
502
|
+
output.print_error("Failed to apply training to agent")
|
|
503
|
+
raise typer.Exit(1)
|
|
504
|
+
|
|
505
|
+
output.print_success("Training applied successfully!")
|
|
506
|
+
output.print(f"\nRunning agent with prompt: {run_prompt}\n")
|
|
507
|
+
|
|
508
|
+
# Run the agent
|
|
509
|
+
try:
|
|
510
|
+
if hasattr(agent, 'chat'):
|
|
511
|
+
response = agent.chat(run_prompt)
|
|
512
|
+
elif hasattr(agent, 'start'):
|
|
513
|
+
response = agent.start(run_prompt)
|
|
514
|
+
else:
|
|
515
|
+
output.print_error("Agent has no chat or start method")
|
|
516
|
+
raise typer.Exit(1)
|
|
517
|
+
|
|
518
|
+
output.print_panel(str(response), title="Agent Response")
|
|
519
|
+
except Exception as e:
|
|
520
|
+
output.print_error(f"Agent execution failed: {e}")
|
|
521
|
+
raise typer.Exit(1)
|
|
522
|
+
else:
|
|
523
|
+
output.print_info(
|
|
524
|
+
"\nTo apply this training to an agent, use:\n"
|
|
525
|
+
f" praisonai train apply {session_id} --run 'Your prompt here'\n"
|
|
526
|
+
"\nOr in Python:\n"
|
|
527
|
+
f" from praisonai.train.agents import apply_training\n"
|
|
528
|
+
f" apply_training(agent, session_id='{session_id}')"
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _load_agent_from_file(file_path: str, output) -> Optional[object]:
|
|
533
|
+
"""Load agent from YAML or Python file."""
|
|
534
|
+
path = Path(file_path)
|
|
535
|
+
|
|
536
|
+
if not path.exists():
|
|
537
|
+
output.print_error(f"File not found: {file_path}")
|
|
538
|
+
return None
|
|
539
|
+
|
|
540
|
+
if path.suffix in ('.yaml', '.yml'):
|
|
541
|
+
try:
|
|
542
|
+
from praisonai.cli.main import PraisonAI
|
|
543
|
+
praison = PraisonAI(agent_file=str(path))
|
|
544
|
+
# Return the agents object
|
|
545
|
+
if hasattr(praison, 'agents') and praison.agents:
|
|
546
|
+
return praison.agents
|
|
547
|
+
output.print_error("Could not load agents from YAML")
|
|
548
|
+
return None
|
|
549
|
+
except Exception as e:
|
|
550
|
+
output.print_error(f"Failed to load YAML: {e}")
|
|
551
|
+
return None
|
|
552
|
+
|
|
553
|
+
elif path.suffix == '.py':
|
|
554
|
+
output.print_warning(
|
|
555
|
+
"Python file support coming soon. "
|
|
556
|
+
"For now, use YAML files or --input flag."
|
|
557
|
+
)
|
|
558
|
+
return None
|
|
559
|
+
|
|
560
|
+
else:
|
|
561
|
+
output.print_error(f"Unsupported file type: {path.suffix}")
|
|
562
|
+
return None
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _create_storage_backend(backend_type: str, storage_path: Optional[str], output):
|
|
566
|
+
"""Create storage backend from CLI options."""
|
|
567
|
+
try:
|
|
568
|
+
if backend_type == "file":
|
|
569
|
+
from praisonaiagents.storage import FileBackend
|
|
570
|
+
return FileBackend(storage_dir=storage_path or "~/.praison/train")
|
|
571
|
+
elif backend_type == "sqlite":
|
|
572
|
+
from praisonaiagents.storage import SQLiteBackend
|
|
573
|
+
db_path = storage_path or "~/.praison/train.db"
|
|
574
|
+
return SQLiteBackend(db_path=db_path)
|
|
575
|
+
elif backend_type.startswith("redis://"):
|
|
576
|
+
from praisonaiagents.storage import RedisBackend
|
|
577
|
+
return RedisBackend(url=backend_type, prefix="train:")
|
|
578
|
+
else:
|
|
579
|
+
output.print_error(
|
|
580
|
+
f"Unknown storage backend: {backend_type}",
|
|
581
|
+
remediation="Use 'file', 'sqlite', or 'redis://url'"
|
|
582
|
+
)
|
|
583
|
+
return None
|
|
584
|
+
except ImportError as e:
|
|
585
|
+
output.print_error(f"Failed to import storage backend: {e}")
|
|
586
|
+
return None
|
|
587
|
+
except Exception as e:
|
|
588
|
+
output.print_error(f"Failed to create storage backend: {e}")
|
|
589
|
+
return None
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _load_scenarios_from_file(file_path: str, output) -> Optional[list]:
|
|
593
|
+
"""Load scenarios from JSON file."""
|
|
594
|
+
path = Path(file_path)
|
|
595
|
+
|
|
596
|
+
if not path.exists():
|
|
597
|
+
output.print_error(f"Scenarios file not found: {file_path}")
|
|
598
|
+
return None
|
|
599
|
+
|
|
600
|
+
try:
|
|
601
|
+
with open(path) as f:
|
|
602
|
+
data = json.load(f)
|
|
603
|
+
|
|
604
|
+
if isinstance(data, list):
|
|
605
|
+
return data
|
|
606
|
+
elif isinstance(data, dict) and "scenarios" in data:
|
|
607
|
+
return data["scenarios"]
|
|
608
|
+
else:
|
|
609
|
+
output.print_error("Invalid scenarios format. Expected list or {scenarios: [...]}")
|
|
610
|
+
return None
|
|
611
|
+
except json.JSONDecodeError as e:
|
|
612
|
+
output.print_error(f"Invalid JSON: {e}")
|
|
613
|
+
return None
|