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,266 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe Data Models
|
|
3
|
+
|
|
4
|
+
Defines the core data structures for recipe execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from enum import IntEnum
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExitCode(IntEnum):
|
|
14
|
+
"""Stable exit codes for CLI operations."""
|
|
15
|
+
SUCCESS = 0
|
|
16
|
+
GENERAL_ERROR = 1
|
|
17
|
+
VALIDATION_ERROR = 2
|
|
18
|
+
RUNTIME_ERROR = 3
|
|
19
|
+
POLICY_DENIED = 4
|
|
20
|
+
TIMEOUT = 5
|
|
21
|
+
MISSING_DEPS = 6
|
|
22
|
+
NOT_FOUND = 7
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RecipeStatus:
|
|
26
|
+
"""Recipe execution status constants."""
|
|
27
|
+
SUCCESS = "success"
|
|
28
|
+
FAILED = "failed"
|
|
29
|
+
DRY_RUN = "dry_run"
|
|
30
|
+
POLICY_DENIED = "policy_denied"
|
|
31
|
+
TIMEOUT = "timeout"
|
|
32
|
+
MISSING_DEPS = "missing_deps"
|
|
33
|
+
VALIDATION_ERROR = "validation_error"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class RecipeResult:
|
|
38
|
+
"""
|
|
39
|
+
Result of a recipe execution.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
run_id: Unique identifier for this execution
|
|
43
|
+
recipe: Recipe name
|
|
44
|
+
version: Recipe version
|
|
45
|
+
status: Execution status (success, failed, dry_run, policy_denied, etc.)
|
|
46
|
+
output: Recipe-specific output data
|
|
47
|
+
metrics: Execution metrics (duration, tokens, etc.)
|
|
48
|
+
error: Error message if failed
|
|
49
|
+
trace: Tracing identifiers (run_id, trace_id, session_id, agent_id)
|
|
50
|
+
"""
|
|
51
|
+
run_id: str
|
|
52
|
+
recipe: str
|
|
53
|
+
version: str
|
|
54
|
+
status: str
|
|
55
|
+
output: Any = None
|
|
56
|
+
metrics: Dict[str, Any] = field(default_factory=dict)
|
|
57
|
+
error: Optional[str] = None
|
|
58
|
+
trace: Dict[str, str] = field(default_factory=dict)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def ok(self) -> bool:
|
|
62
|
+
"""Check if execution was successful."""
|
|
63
|
+
return self.status in (RecipeStatus.SUCCESS, RecipeStatus.DRY_RUN)
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
66
|
+
"""Convert to dictionary for JSON serialization."""
|
|
67
|
+
return {
|
|
68
|
+
"ok": self.ok,
|
|
69
|
+
"run_id": self.run_id,
|
|
70
|
+
"recipe": self.recipe,
|
|
71
|
+
"version": self.version,
|
|
72
|
+
"status": self.status,
|
|
73
|
+
"output": self.output,
|
|
74
|
+
"metrics": self.metrics,
|
|
75
|
+
"error": self.error,
|
|
76
|
+
"trace": self.trace,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def to_exit_code(self) -> int:
|
|
80
|
+
"""Convert status to CLI exit code."""
|
|
81
|
+
status_to_code = {
|
|
82
|
+
RecipeStatus.SUCCESS: ExitCode.SUCCESS,
|
|
83
|
+
RecipeStatus.DRY_RUN: ExitCode.SUCCESS,
|
|
84
|
+
RecipeStatus.FAILED: ExitCode.RUNTIME_ERROR,
|
|
85
|
+
RecipeStatus.POLICY_DENIED: ExitCode.POLICY_DENIED,
|
|
86
|
+
RecipeStatus.TIMEOUT: ExitCode.TIMEOUT,
|
|
87
|
+
RecipeStatus.MISSING_DEPS: ExitCode.MISSING_DEPS,
|
|
88
|
+
RecipeStatus.VALIDATION_ERROR: ExitCode.VALIDATION_ERROR,
|
|
89
|
+
}
|
|
90
|
+
return status_to_code.get(self.status, ExitCode.GENERAL_ERROR)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class RecipeEvent:
|
|
95
|
+
"""
|
|
96
|
+
Streaming event from recipe execution.
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
event_type: Type of event (started, progress, log, output, completed, error)
|
|
100
|
+
data: Event-specific data
|
|
101
|
+
timestamp: ISO format timestamp
|
|
102
|
+
"""
|
|
103
|
+
event_type: str
|
|
104
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
105
|
+
timestamp: str = ""
|
|
106
|
+
|
|
107
|
+
def __post_init__(self):
|
|
108
|
+
if not self.timestamp:
|
|
109
|
+
self.timestamp = datetime.now(timezone.utc).isoformat()
|
|
110
|
+
|
|
111
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
112
|
+
"""Convert to dictionary for JSON/SSE serialization."""
|
|
113
|
+
return {
|
|
114
|
+
"event": self.event_type,
|
|
115
|
+
"data": self.data,
|
|
116
|
+
"timestamp": self.timestamp,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def to_sse(self) -> str:
|
|
120
|
+
"""Convert to Server-Sent Events format."""
|
|
121
|
+
import json
|
|
122
|
+
return f"event: {self.event_type}\ndata: {json.dumps(self.data)}\n\n"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class RecipeConfig:
|
|
127
|
+
"""
|
|
128
|
+
Recipe configuration and metadata.
|
|
129
|
+
|
|
130
|
+
Attributes:
|
|
131
|
+
name: Recipe name (kebab-case)
|
|
132
|
+
version: SemVer version string
|
|
133
|
+
description: Recipe description
|
|
134
|
+
author: Author name or identifier
|
|
135
|
+
license: License identifier (e.g., Apache-2.0)
|
|
136
|
+
tags: Discovery tags
|
|
137
|
+
requires: Dependencies (packages, env, tools, external)
|
|
138
|
+
tools: Tool permissions (allow, deny)
|
|
139
|
+
config_schema: JSON Schema for input configuration
|
|
140
|
+
defaults: Default configuration values
|
|
141
|
+
outputs: Expected output definitions
|
|
142
|
+
governance: Governance settings (approval, cost limits, audit)
|
|
143
|
+
data_policy: Data handling policy (PII, retention)
|
|
144
|
+
path: Source path of the recipe
|
|
145
|
+
"""
|
|
146
|
+
name: str
|
|
147
|
+
version: str = "1.0.0"
|
|
148
|
+
description: str = ""
|
|
149
|
+
author: Optional[str] = None
|
|
150
|
+
license: Optional[str] = None
|
|
151
|
+
tags: List[str] = field(default_factory=list)
|
|
152
|
+
|
|
153
|
+
# Dependencies
|
|
154
|
+
requires: Dict[str, Any] = field(default_factory=dict)
|
|
155
|
+
|
|
156
|
+
# Tool permissions
|
|
157
|
+
tools: Dict[str, List[str]] = field(default_factory=dict)
|
|
158
|
+
|
|
159
|
+
# Configuration
|
|
160
|
+
config_schema: Dict[str, Any] = field(default_factory=dict)
|
|
161
|
+
defaults: Dict[str, Any] = field(default_factory=dict)
|
|
162
|
+
|
|
163
|
+
# Outputs
|
|
164
|
+
outputs: List[Dict[str, Any]] = field(default_factory=list)
|
|
165
|
+
|
|
166
|
+
# Governance
|
|
167
|
+
governance: Dict[str, Any] = field(default_factory=dict)
|
|
168
|
+
|
|
169
|
+
# Data policy
|
|
170
|
+
data_policy: Dict[str, Any] = field(default_factory=dict)
|
|
171
|
+
|
|
172
|
+
# Source path
|
|
173
|
+
path: Optional[str] = None
|
|
174
|
+
|
|
175
|
+
# Raw config dict
|
|
176
|
+
raw: Dict[str, Any] = field(default_factory=dict)
|
|
177
|
+
|
|
178
|
+
def get_allowed_tools(self) -> List[str]:
|
|
179
|
+
"""Get list of explicitly allowed tools."""
|
|
180
|
+
return self.tools.get("allow", [])
|
|
181
|
+
|
|
182
|
+
def get_denied_tools(self) -> List[str]:
|
|
183
|
+
"""Get list of explicitly denied tools."""
|
|
184
|
+
return self.tools.get("deny", [])
|
|
185
|
+
|
|
186
|
+
def get_required_packages(self) -> List[str]:
|
|
187
|
+
"""Get list of required Python packages."""
|
|
188
|
+
packages = self.requires.get("packages", [])
|
|
189
|
+
return [packages] if isinstance(packages, str) else packages
|
|
190
|
+
|
|
191
|
+
def get_required_env(self) -> List[str]:
|
|
192
|
+
"""Get list of required environment variables."""
|
|
193
|
+
env = self.requires.get("env", [])
|
|
194
|
+
return [env] if isinstance(env, str) else env
|
|
195
|
+
|
|
196
|
+
def get_required_tools(self) -> List[str]:
|
|
197
|
+
"""Get list of required tools."""
|
|
198
|
+
tools = self.requires.get("tools", [])
|
|
199
|
+
return [tools] if isinstance(tools, str) else tools
|
|
200
|
+
|
|
201
|
+
def get_external_deps(self) -> List[Dict[str, Any]]:
|
|
202
|
+
"""Get list of external dependencies (ffmpeg, etc.)."""
|
|
203
|
+
return self.requires.get("external", [])
|
|
204
|
+
|
|
205
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
206
|
+
"""Convert to dictionary."""
|
|
207
|
+
return {
|
|
208
|
+
"name": self.name,
|
|
209
|
+
"version": self.version,
|
|
210
|
+
"description": self.description,
|
|
211
|
+
"author": self.author,
|
|
212
|
+
"license": self.license,
|
|
213
|
+
"tags": self.tags,
|
|
214
|
+
"requires": self.requires,
|
|
215
|
+
"tools": self.tools,
|
|
216
|
+
"config_schema": self.config_schema,
|
|
217
|
+
"defaults": self.defaults,
|
|
218
|
+
"outputs": self.outputs,
|
|
219
|
+
"governance": self.governance,
|
|
220
|
+
"data_policy": self.data_policy,
|
|
221
|
+
"path": self.path,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class ValidationResult:
|
|
227
|
+
"""Result of recipe validation."""
|
|
228
|
+
valid: bool
|
|
229
|
+
recipe: str
|
|
230
|
+
version: str
|
|
231
|
+
errors: List[str] = field(default_factory=list)
|
|
232
|
+
warnings: List[str] = field(default_factory=list)
|
|
233
|
+
dependencies: Dict[str, Any] = field(default_factory=dict)
|
|
234
|
+
|
|
235
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
236
|
+
"""Convert to dictionary."""
|
|
237
|
+
return {
|
|
238
|
+
"valid": self.valid,
|
|
239
|
+
"recipe": self.recipe,
|
|
240
|
+
"version": self.version,
|
|
241
|
+
"errors": self.errors,
|
|
242
|
+
"warnings": self.warnings,
|
|
243
|
+
"dependencies": self.dependencies,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass
|
|
248
|
+
class RecipeInfo:
|
|
249
|
+
"""Recipe information for listing/discovery."""
|
|
250
|
+
name: str
|
|
251
|
+
version: str
|
|
252
|
+
description: str
|
|
253
|
+
tags: List[str]
|
|
254
|
+
path: str
|
|
255
|
+
source: str # local, package, github
|
|
256
|
+
|
|
257
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
258
|
+
"""Convert to dictionary."""
|
|
259
|
+
return {
|
|
260
|
+
"name": self.name,
|
|
261
|
+
"version": self.version,
|
|
262
|
+
"description": self.description,
|
|
263
|
+
"tags": self.tags,
|
|
264
|
+
"path": self.path,
|
|
265
|
+
"source": self.source,
|
|
266
|
+
}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe Operations Module.
|
|
3
|
+
|
|
4
|
+
Provides high-level APIs for running recipes in different operational modes:
|
|
5
|
+
- run_background(): Execute recipe as a background task
|
|
6
|
+
- submit_job(): Submit recipe to async jobs server
|
|
7
|
+
- schedule(): Create a scheduled recipe executor
|
|
8
|
+
|
|
9
|
+
All functions are lazy-loaded and have zero performance impact when not used.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Safe defaults
|
|
20
|
+
DEFAULT_TIMEOUT_SEC = 300
|
|
21
|
+
DEFAULT_MAX_COST_USD = 1.00
|
|
22
|
+
DEFAULT_MAX_RETRIES = 3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class BackgroundTaskHandle:
|
|
27
|
+
"""Handle for a background recipe task."""
|
|
28
|
+
task_id: str
|
|
29
|
+
recipe_name: str
|
|
30
|
+
session_id: Optional[str] = None
|
|
31
|
+
_runner: Any = field(default=None, repr=False)
|
|
32
|
+
_task: Any = field(default=None, repr=False)
|
|
33
|
+
|
|
34
|
+
async def status(self) -> str:
|
|
35
|
+
"""Get task status."""
|
|
36
|
+
if self._runner and self._task:
|
|
37
|
+
return self._task.status.value
|
|
38
|
+
return "unknown"
|
|
39
|
+
|
|
40
|
+
async def wait(self, timeout: Optional[float] = None) -> Any:
|
|
41
|
+
"""Wait for task completion and return result."""
|
|
42
|
+
if self._runner and self._task:
|
|
43
|
+
return await self._runner.wait_for_task(self._task.id, timeout=timeout)
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
async def cancel(self) -> bool:
|
|
47
|
+
"""Cancel the task."""
|
|
48
|
+
if self._runner:
|
|
49
|
+
return await self._runner.cancel(self.task_id)
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class JobHandle:
|
|
55
|
+
"""Handle for an async job."""
|
|
56
|
+
job_id: str
|
|
57
|
+
recipe_name: str
|
|
58
|
+
status: str = "queued"
|
|
59
|
+
poll_url: Optional[str] = None
|
|
60
|
+
stream_url: Optional[str] = None
|
|
61
|
+
api_url: str = "http://127.0.0.1:8005"
|
|
62
|
+
|
|
63
|
+
def get_status(self) -> Dict[str, Any]:
|
|
64
|
+
"""Get job status from API."""
|
|
65
|
+
try:
|
|
66
|
+
import httpx
|
|
67
|
+
with httpx.Client(timeout=30.0) as client:
|
|
68
|
+
response = client.get(f"{self.api_url}/api/v1/runs/{self.job_id}")
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return response.json()
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Failed to get job status: {e}")
|
|
73
|
+
return {"job_id": self.job_id, "status": "unknown", "error": str(e)}
|
|
74
|
+
|
|
75
|
+
def get_result(self) -> Any:
|
|
76
|
+
"""Get job result from API."""
|
|
77
|
+
try:
|
|
78
|
+
import httpx
|
|
79
|
+
with httpx.Client(timeout=30.0) as client:
|
|
80
|
+
response = client.get(f"{self.api_url}/api/v1/runs/{self.job_id}/result")
|
|
81
|
+
response.raise_for_status()
|
|
82
|
+
return response.json()
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error(f"Failed to get job result: {e}")
|
|
85
|
+
return {"job_id": self.job_id, "error": str(e)}
|
|
86
|
+
|
|
87
|
+
def cancel(self) -> bool:
|
|
88
|
+
"""Cancel the job."""
|
|
89
|
+
try:
|
|
90
|
+
import httpx
|
|
91
|
+
with httpx.Client(timeout=30.0) as client:
|
|
92
|
+
response = client.post(f"{self.api_url}/api/v1/runs/{self.job_id}/cancel")
|
|
93
|
+
return response.status_code < 400
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Failed to cancel job: {e}")
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def wait(self, poll_interval: int = 5, timeout: Optional[int] = None) -> Dict[str, Any]:
|
|
99
|
+
"""Wait for job completion by polling."""
|
|
100
|
+
import time
|
|
101
|
+
start_time = time.time()
|
|
102
|
+
|
|
103
|
+
while True:
|
|
104
|
+
status = self.get_status()
|
|
105
|
+
if status.get("status") in ("succeeded", "failed", "cancelled"):
|
|
106
|
+
return self.get_result()
|
|
107
|
+
|
|
108
|
+
if timeout and (time.time() - start_time) > timeout:
|
|
109
|
+
return {"job_id": self.job_id, "status": "timeout", "error": "Wait timeout exceeded"}
|
|
110
|
+
|
|
111
|
+
time.sleep(poll_interval)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class RecipeScheduler:
|
|
116
|
+
"""Wrapper around AgentScheduler for recipe-based scheduling."""
|
|
117
|
+
recipe_name: str
|
|
118
|
+
interval: str = "hourly"
|
|
119
|
+
max_retries: int = DEFAULT_MAX_RETRIES
|
|
120
|
+
timeout_sec: int = DEFAULT_TIMEOUT_SEC
|
|
121
|
+
max_cost_usd: float = DEFAULT_MAX_COST_USD
|
|
122
|
+
run_immediately: bool = False
|
|
123
|
+
_scheduler: Any = field(default=None, repr=False)
|
|
124
|
+
_resolved: Any = field(default=None, repr=False)
|
|
125
|
+
|
|
126
|
+
def start(self) -> bool:
|
|
127
|
+
"""Start the scheduler."""
|
|
128
|
+
if self._scheduler:
|
|
129
|
+
return self._scheduler.start(
|
|
130
|
+
schedule_expr=self.interval,
|
|
131
|
+
max_retries=self.max_retries,
|
|
132
|
+
run_immediately=self.run_immediately
|
|
133
|
+
)
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def stop(self) -> bool:
|
|
137
|
+
"""Stop the scheduler."""
|
|
138
|
+
if self._scheduler:
|
|
139
|
+
return self._scheduler.stop()
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
143
|
+
"""Get scheduler statistics."""
|
|
144
|
+
if self._scheduler:
|
|
145
|
+
return self._scheduler.get_stats()
|
|
146
|
+
return {}
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def is_running(self) -> bool:
|
|
150
|
+
"""Check if scheduler is running."""
|
|
151
|
+
if self._scheduler:
|
|
152
|
+
return self._scheduler.is_running
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def run_background(
|
|
157
|
+
name: str,
|
|
158
|
+
*,
|
|
159
|
+
input: Any = None,
|
|
160
|
+
config: Optional[Dict[str, Any]] = None,
|
|
161
|
+
session_id: Optional[str] = None,
|
|
162
|
+
timeout_sec: Optional[int] = None,
|
|
163
|
+
max_concurrent: int = 5,
|
|
164
|
+
on_complete: Optional[callable] = None,
|
|
165
|
+
) -> BackgroundTaskHandle:
|
|
166
|
+
"""
|
|
167
|
+
Run a recipe as a background task.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
name: Recipe name
|
|
171
|
+
input: Input data for the recipe
|
|
172
|
+
config: Configuration overrides
|
|
173
|
+
session_id: Session ID for conversation continuity
|
|
174
|
+
timeout_sec: Timeout in seconds (default: 300)
|
|
175
|
+
max_concurrent: Max concurrent tasks (default: 5)
|
|
176
|
+
on_complete: Callback when task completes
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
BackgroundTaskHandle for tracking the task
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
task = recipe.run_background("my-recipe", input={"query": "test"})
|
|
183
|
+
print(f"Task ID: {task.task_id}")
|
|
184
|
+
result = await task.wait()
|
|
185
|
+
"""
|
|
186
|
+
import asyncio
|
|
187
|
+
from .bridge import resolve, execute_resolved_recipe
|
|
188
|
+
|
|
189
|
+
# Resolve the recipe
|
|
190
|
+
resolved = resolve(
|
|
191
|
+
name,
|
|
192
|
+
input_data=input,
|
|
193
|
+
config=config,
|
|
194
|
+
session_id=session_id,
|
|
195
|
+
options={'timeout_sec': timeout_sec or DEFAULT_TIMEOUT_SEC},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Import BackgroundRunner lazily
|
|
199
|
+
try:
|
|
200
|
+
from praisonaiagents.background import BackgroundRunner
|
|
201
|
+
except ImportError:
|
|
202
|
+
raise RuntimeError(
|
|
203
|
+
"Background tasks require praisonaiagents. "
|
|
204
|
+
"Install with: pip install praisonaiagents"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Create or get runner
|
|
208
|
+
runner = BackgroundRunner(max_concurrent_tasks=max_concurrent)
|
|
209
|
+
|
|
210
|
+
# Define the task function
|
|
211
|
+
def recipe_task():
|
|
212
|
+
return execute_resolved_recipe(resolved)
|
|
213
|
+
|
|
214
|
+
# Submit the task
|
|
215
|
+
loop = asyncio.get_event_loop()
|
|
216
|
+
if loop.is_running():
|
|
217
|
+
# We're in an async context, use create_task
|
|
218
|
+
import concurrent.futures
|
|
219
|
+
future = concurrent.futures.Future()
|
|
220
|
+
|
|
221
|
+
async def submit_and_return():
|
|
222
|
+
task = await runner.submit(
|
|
223
|
+
recipe_task,
|
|
224
|
+
name=f"recipe:{resolved.name}",
|
|
225
|
+
timeout=timeout_sec,
|
|
226
|
+
on_complete=on_complete,
|
|
227
|
+
)
|
|
228
|
+
return task
|
|
229
|
+
|
|
230
|
+
asyncio.ensure_future(submit_and_return()).add_done_callback(
|
|
231
|
+
lambda f: future.set_result(f.result())
|
|
232
|
+
)
|
|
233
|
+
task = future.result(timeout=10)
|
|
234
|
+
else:
|
|
235
|
+
# Sync context
|
|
236
|
+
task = loop.run_until_complete(runner.submit(
|
|
237
|
+
recipe_task,
|
|
238
|
+
name=f"recipe:{resolved.name}",
|
|
239
|
+
timeout=timeout_sec,
|
|
240
|
+
on_complete=on_complete,
|
|
241
|
+
))
|
|
242
|
+
|
|
243
|
+
return BackgroundTaskHandle(
|
|
244
|
+
task_id=task.id,
|
|
245
|
+
recipe_name=resolved.name,
|
|
246
|
+
session_id=resolved.session_id,
|
|
247
|
+
_runner=runner,
|
|
248
|
+
_task=task,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def submit_job(
|
|
253
|
+
name: str,
|
|
254
|
+
*,
|
|
255
|
+
input: Any = None,
|
|
256
|
+
config: Optional[Dict[str, Any]] = None,
|
|
257
|
+
session_id: Optional[str] = None,
|
|
258
|
+
timeout_sec: Optional[int] = None,
|
|
259
|
+
idempotency_key: Optional[str] = None,
|
|
260
|
+
webhook_url: Optional[str] = None,
|
|
261
|
+
api_url: str = "http://127.0.0.1:8005",
|
|
262
|
+
wait: bool = False,
|
|
263
|
+
poll_interval: int = 5,
|
|
264
|
+
) -> JobHandle:
|
|
265
|
+
"""
|
|
266
|
+
Submit a recipe to the async jobs server.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
name: Recipe name
|
|
270
|
+
input: Input data for the recipe
|
|
271
|
+
config: Configuration overrides
|
|
272
|
+
session_id: Session ID for conversation continuity
|
|
273
|
+
timeout_sec: Timeout in seconds (default: 3600)
|
|
274
|
+
idempotency_key: Key for deduplication
|
|
275
|
+
webhook_url: URL for completion webhook
|
|
276
|
+
api_url: Jobs API URL (default: http://127.0.0.1:8005)
|
|
277
|
+
wait: If True, wait for completion before returning
|
|
278
|
+
poll_interval: Polling interval when waiting (seconds)
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
JobHandle for tracking the job
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
job = recipe.submit_job("my-recipe", input={"query": "test"}, wait=True)
|
|
285
|
+
print(f"Result: {job.get_result()}")
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
import httpx
|
|
289
|
+
except ImportError:
|
|
290
|
+
raise RuntimeError(
|
|
291
|
+
"Job submission requires httpx. "
|
|
292
|
+
"Install with: pip install httpx"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Build request payload
|
|
296
|
+
payload = {
|
|
297
|
+
"prompt": str(input) if input else f"Execute recipe: {name}",
|
|
298
|
+
"recipe_name": name,
|
|
299
|
+
"timeout": timeout_sec or 3600,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if config:
|
|
303
|
+
payload["config"] = config
|
|
304
|
+
if session_id:
|
|
305
|
+
payload["session_id"] = session_id
|
|
306
|
+
if idempotency_key:
|
|
307
|
+
payload["idempotency_key"] = idempotency_key
|
|
308
|
+
if webhook_url:
|
|
309
|
+
payload["webhook_url"] = webhook_url
|
|
310
|
+
|
|
311
|
+
# Submit to API
|
|
312
|
+
with httpx.Client(timeout=30.0) as client:
|
|
313
|
+
response = client.post(
|
|
314
|
+
f"{api_url}/api/v1/runs",
|
|
315
|
+
json=payload,
|
|
316
|
+
)
|
|
317
|
+
response.raise_for_status()
|
|
318
|
+
data = response.json()
|
|
319
|
+
|
|
320
|
+
handle = JobHandle(
|
|
321
|
+
job_id=data["job_id"],
|
|
322
|
+
recipe_name=name,
|
|
323
|
+
status=data.get("status", "queued"),
|
|
324
|
+
poll_url=data.get("poll_url"),
|
|
325
|
+
stream_url=data.get("stream_url"),
|
|
326
|
+
api_url=api_url,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if wait:
|
|
330
|
+
handle.wait(poll_interval=poll_interval, timeout=timeout_sec)
|
|
331
|
+
|
|
332
|
+
return handle
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def schedule(
|
|
336
|
+
name: str,
|
|
337
|
+
*,
|
|
338
|
+
input: Any = None,
|
|
339
|
+
config: Optional[Dict[str, Any]] = None,
|
|
340
|
+
interval: Optional[str] = None,
|
|
341
|
+
max_retries: Optional[int] = None,
|
|
342
|
+
run_immediately: Optional[bool] = None,
|
|
343
|
+
timeout_sec: Optional[int] = None,
|
|
344
|
+
max_cost_usd: Optional[float] = None,
|
|
345
|
+
on_success: Optional[callable] = None,
|
|
346
|
+
on_failure: Optional[callable] = None,
|
|
347
|
+
) -> RecipeScheduler:
|
|
348
|
+
"""
|
|
349
|
+
Create a scheduler for periodic recipe execution.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
name: Recipe name
|
|
353
|
+
input: Input data for the recipe
|
|
354
|
+
config: Configuration overrides
|
|
355
|
+
interval: Schedule interval (hourly, daily, */30m, etc.)
|
|
356
|
+
max_retries: Max retry attempts (default: 3)
|
|
357
|
+
run_immediately: Run once immediately (default: False)
|
|
358
|
+
timeout_sec: Timeout per execution (default: 300)
|
|
359
|
+
max_cost_usd: Budget limit (default: $1.00)
|
|
360
|
+
on_success: Callback on successful execution
|
|
361
|
+
on_failure: Callback on failed execution
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
RecipeScheduler instance (call .start() to begin)
|
|
365
|
+
|
|
366
|
+
Example:
|
|
367
|
+
scheduler = recipe.schedule("news-monitor", interval="hourly")
|
|
368
|
+
scheduler.start()
|
|
369
|
+
# ... later ...
|
|
370
|
+
scheduler.stop()
|
|
371
|
+
"""
|
|
372
|
+
from .bridge import resolve, execute_resolved_recipe, get_recipe_task_description
|
|
373
|
+
|
|
374
|
+
# Resolve the recipe
|
|
375
|
+
resolved = resolve(
|
|
376
|
+
name,
|
|
377
|
+
input_data=input,
|
|
378
|
+
config=config,
|
|
379
|
+
options={'timeout_sec': timeout_sec or DEFAULT_TIMEOUT_SEC},
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Get runtime config defaults
|
|
383
|
+
runtime = resolved.runtime_config
|
|
384
|
+
if runtime and hasattr(runtime, 'schedule'):
|
|
385
|
+
sched_config = runtime.schedule
|
|
386
|
+
interval = interval or sched_config.interval
|
|
387
|
+
max_retries = max_retries if max_retries is not None else sched_config.max_retries
|
|
388
|
+
run_immediately = run_immediately if run_immediately is not None else sched_config.run_immediately
|
|
389
|
+
timeout_sec = timeout_sec or sched_config.timeout_sec
|
|
390
|
+
max_cost_usd = max_cost_usd if max_cost_usd is not None else sched_config.max_cost_usd
|
|
391
|
+
|
|
392
|
+
# Apply defaults
|
|
393
|
+
interval = interval or "hourly"
|
|
394
|
+
max_retries = max_retries if max_retries is not None else DEFAULT_MAX_RETRIES
|
|
395
|
+
run_immediately = run_immediately if run_immediately is not None else False
|
|
396
|
+
timeout_sec = timeout_sec or DEFAULT_TIMEOUT_SEC
|
|
397
|
+
max_cost_usd = max_cost_usd if max_cost_usd is not None else DEFAULT_MAX_COST_USD
|
|
398
|
+
|
|
399
|
+
# Import AgentScheduler lazily
|
|
400
|
+
try:
|
|
401
|
+
from praisonai.scheduler import AgentScheduler
|
|
402
|
+
except ImportError:
|
|
403
|
+
raise RuntimeError(
|
|
404
|
+
"Scheduling requires praisonai scheduler module."
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Create a recipe executor agent wrapper
|
|
408
|
+
class RecipeExecutorAgent:
|
|
409
|
+
"""Wrapper that makes a recipe look like an agent for the scheduler."""
|
|
410
|
+
def __init__(self, resolved_recipe):
|
|
411
|
+
self.resolved = resolved_recipe
|
|
412
|
+
self.name = f"RecipeAgent:{resolved_recipe.name}"
|
|
413
|
+
|
|
414
|
+
def start(self, task: str) -> Any:
|
|
415
|
+
return execute_resolved_recipe(self.resolved)
|
|
416
|
+
|
|
417
|
+
# Create the agent wrapper
|
|
418
|
+
agent = RecipeExecutorAgent(resolved)
|
|
419
|
+
task = get_recipe_task_description(resolved)
|
|
420
|
+
|
|
421
|
+
# Create the scheduler
|
|
422
|
+
scheduler = AgentScheduler(
|
|
423
|
+
agent=agent,
|
|
424
|
+
task=task,
|
|
425
|
+
timeout=timeout_sec,
|
|
426
|
+
max_cost=max_cost_usd,
|
|
427
|
+
on_success=on_success,
|
|
428
|
+
on_failure=on_failure,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
return RecipeScheduler(
|
|
432
|
+
recipe_name=resolved.name,
|
|
433
|
+
interval=interval,
|
|
434
|
+
max_retries=max_retries,
|
|
435
|
+
timeout_sec=timeout_sec,
|
|
436
|
+
max_cost_usd=max_cost_usd,
|
|
437
|
+
run_immediately=run_immediately,
|
|
438
|
+
_scheduler=scheduler,
|
|
439
|
+
_resolved=resolved,
|
|
440
|
+
)
|