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,552 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Scheduler for PraisonAI - Run agents periodically 24/7.
|
|
3
|
+
|
|
4
|
+
This module provides scheduling capabilities for running PraisonAI agents
|
|
5
|
+
at regular intervals, enabling 24/7 autonomous agent operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Optional, Dict, Any, Callable
|
|
13
|
+
|
|
14
|
+
from .base import ScheduleParser, PraisonAgentExecutor
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentScheduler:
|
|
20
|
+
"""
|
|
21
|
+
Scheduler for running PraisonAI agents periodically.
|
|
22
|
+
|
|
23
|
+
Features:
|
|
24
|
+
- Interval-based scheduling (hourly, daily, custom)
|
|
25
|
+
- Thread-safe operation
|
|
26
|
+
- Automatic retry on failure
|
|
27
|
+
- Execution logging and monitoring
|
|
28
|
+
- Graceful shutdown
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
scheduler = AgentScheduler(agent, task="Check news")
|
|
32
|
+
scheduler.start(schedule_expr="hourly", max_retries=3)
|
|
33
|
+
# Agent runs every hour automatically
|
|
34
|
+
scheduler.stop() # Stop when needed
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
agent,
|
|
40
|
+
task: str,
|
|
41
|
+
config: Optional[Dict[str, Any]] = None,
|
|
42
|
+
on_success: Optional[Callable] = None,
|
|
43
|
+
on_failure: Optional[Callable] = None,
|
|
44
|
+
timeout: Optional[int] = None,
|
|
45
|
+
max_cost: Optional[float] = 1.00
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Initialize agent scheduler.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
agent: PraisonAI Agent instance
|
|
52
|
+
task: Task description to execute
|
|
53
|
+
config: Optional configuration dict
|
|
54
|
+
on_success: Callback function on successful execution
|
|
55
|
+
on_failure: Callback function on failed execution
|
|
56
|
+
timeout: Maximum execution time per run in seconds (None = no limit)
|
|
57
|
+
max_cost: Maximum total cost in USD (default: $1.00 for safety)
|
|
58
|
+
"""
|
|
59
|
+
self.agent = agent
|
|
60
|
+
self.task = task
|
|
61
|
+
self.config = config or {}
|
|
62
|
+
self.on_success = on_success
|
|
63
|
+
self.on_failure = on_failure
|
|
64
|
+
self.timeout = timeout
|
|
65
|
+
self.max_cost = max_cost
|
|
66
|
+
|
|
67
|
+
self.is_running = False
|
|
68
|
+
self._stop_event = threading.Event()
|
|
69
|
+
self._thread = None
|
|
70
|
+
self._executor = PraisonAgentExecutor(agent)
|
|
71
|
+
self._execution_count = 0
|
|
72
|
+
self._success_count = 0
|
|
73
|
+
self._failure_count = 0
|
|
74
|
+
self._total_cost = 0.0
|
|
75
|
+
self._start_time = None
|
|
76
|
+
|
|
77
|
+
def start(
|
|
78
|
+
self,
|
|
79
|
+
schedule_expr: str,
|
|
80
|
+
max_retries: int = 3,
|
|
81
|
+
run_immediately: bool = False
|
|
82
|
+
) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Start scheduled agent execution.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
schedule_expr: Schedule expression (e.g., "hourly", "*/6h", "3600")
|
|
88
|
+
max_retries: Maximum retry attempts on failure
|
|
89
|
+
run_immediately: If True, run agent immediately before starting schedule
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if scheduler started successfully
|
|
93
|
+
"""
|
|
94
|
+
if self.is_running:
|
|
95
|
+
logger.warning("Scheduler is already running")
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
interval = ScheduleParser.parse(schedule_expr)
|
|
100
|
+
self.is_running = True
|
|
101
|
+
self._stop_event.clear()
|
|
102
|
+
|
|
103
|
+
logger.debug(f"Starting agent scheduler: {getattr(self.agent, 'name', 'Agent')}")
|
|
104
|
+
logger.debug(f"Task: {self.task}")
|
|
105
|
+
logger.debug(f"Schedule: {schedule_expr} ({interval}s interval)")
|
|
106
|
+
self.is_running = True
|
|
107
|
+
self._stop_event.clear()
|
|
108
|
+
self._start_time = datetime.now()
|
|
109
|
+
|
|
110
|
+
# Run immediately if requested
|
|
111
|
+
if run_immediately:
|
|
112
|
+
logger.debug("Running agent immediately before starting schedule...")
|
|
113
|
+
self._execute_with_retry(max_retries)
|
|
114
|
+
|
|
115
|
+
self._thread = threading.Thread(
|
|
116
|
+
target=self._run_schedule,
|
|
117
|
+
args=(interval, max_retries),
|
|
118
|
+
daemon=True
|
|
119
|
+
)
|
|
120
|
+
self._thread.start()
|
|
121
|
+
|
|
122
|
+
logger.debug("Agent scheduler started successfully")
|
|
123
|
+
if self.timeout:
|
|
124
|
+
logger.info(f"Timeout per execution: {self.timeout}s")
|
|
125
|
+
if self.max_cost:
|
|
126
|
+
logger.info(f"Budget limit: ${self.max_cost}")
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Failed to start scheduler: {e}")
|
|
131
|
+
self.is_running = False
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
def stop(self) -> bool:
|
|
135
|
+
"""
|
|
136
|
+
Stop the scheduler gracefully.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if stopped successfully
|
|
140
|
+
"""
|
|
141
|
+
if not self.is_running:
|
|
142
|
+
logger.debug("Scheduler is not running")
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
logger.debug("Stopping agent scheduler...")
|
|
146
|
+
self._stop_event.set()
|
|
147
|
+
|
|
148
|
+
if self._thread and self._thread.is_alive():
|
|
149
|
+
self._thread.join(timeout=10)
|
|
150
|
+
|
|
151
|
+
self.is_running = False
|
|
152
|
+
logger.debug("Agent scheduler stopped")
|
|
153
|
+
logger.debug(f"Execution stats - Total: {self._execution_count}, Success: {self._success_count}, Failed: {self._failure_count}")
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
157
|
+
"""
|
|
158
|
+
Get execution statistics.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dictionary with execution stats including cost
|
|
162
|
+
"""
|
|
163
|
+
runtime = (datetime.now() - self._start_time).total_seconds() if self._start_time else 0
|
|
164
|
+
return {
|
|
165
|
+
"is_running": self.is_running,
|
|
166
|
+
"total_executions": self._execution_count,
|
|
167
|
+
"successful_executions": self._success_count,
|
|
168
|
+
"failed_executions": self._failure_count,
|
|
169
|
+
"success_rate": (self._success_count / self._execution_count * 100) if self._execution_count > 0 else 0,
|
|
170
|
+
"total_cost_usd": round(self._total_cost, 4),
|
|
171
|
+
"runtime_seconds": round(runtime, 1),
|
|
172
|
+
"cost_per_execution": round(self._total_cost / self._execution_count, 4) if self._execution_count > 0 else 0
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
def _update_state_if_daemon(self):
|
|
176
|
+
"""Update state file with execution stats if running as daemon."""
|
|
177
|
+
try:
|
|
178
|
+
import os
|
|
179
|
+
# Check if we're running as a daemon by looking for state file
|
|
180
|
+
state_dir = os.path.expanduser("~/.praisonai/schedulers")
|
|
181
|
+
if not os.path.exists(state_dir):
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Try to find our state file by checking all state files for matching PID
|
|
185
|
+
current_pid = os.getpid()
|
|
186
|
+
for state_file in os.listdir(state_dir):
|
|
187
|
+
if not state_file.endswith('.json'):
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
state_path = os.path.join(state_dir, state_file)
|
|
191
|
+
try:
|
|
192
|
+
import json
|
|
193
|
+
with open(state_path, 'r') as f:
|
|
194
|
+
state = json.load(f)
|
|
195
|
+
|
|
196
|
+
# Check if this is our state file
|
|
197
|
+
if state.get('pid') == current_pid:
|
|
198
|
+
# Update execution stats
|
|
199
|
+
state['executions'] = self._execution_count
|
|
200
|
+
state['cost'] = round(self._total_cost, 4)
|
|
201
|
+
|
|
202
|
+
# Write back
|
|
203
|
+
with open(state_path, 'w') as f:
|
|
204
|
+
json.dump(state, f, indent=2)
|
|
205
|
+
break
|
|
206
|
+
except Exception:
|
|
207
|
+
continue
|
|
208
|
+
except Exception as e:
|
|
209
|
+
# Silently fail - don't break scheduler if state update fails
|
|
210
|
+
logger.debug(f"Failed to update state: {e}")
|
|
211
|
+
|
|
212
|
+
def _run_schedule(self, interval: int, max_retries: int):
|
|
213
|
+
"""Internal method to run scheduled agent executions."""
|
|
214
|
+
while not self._stop_event.is_set():
|
|
215
|
+
# Check budget limit
|
|
216
|
+
if self.max_cost and self._total_cost >= self.max_cost:
|
|
217
|
+
logger.warning(f"Budget limit reached: ${self._total_cost:.4f} >= ${self.max_cost}")
|
|
218
|
+
logger.warning("Stopping scheduler to prevent additional costs")
|
|
219
|
+
self.stop()
|
|
220
|
+
break
|
|
221
|
+
|
|
222
|
+
logger.debug(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Starting scheduled agent execution")
|
|
223
|
+
|
|
224
|
+
self._execute_with_retry(max_retries)
|
|
225
|
+
|
|
226
|
+
# Wait for next scheduled time
|
|
227
|
+
logger.debug(f"Next execution in {interval} seconds ({interval/3600:.1f} hours)")
|
|
228
|
+
if self.max_cost:
|
|
229
|
+
remaining = self.max_cost - self._total_cost
|
|
230
|
+
logger.debug(f"Budget remaining: ${remaining:.4f}")
|
|
231
|
+
self._stop_event.wait(interval)
|
|
232
|
+
|
|
233
|
+
def _execute_with_retry(self, max_retries: int):
|
|
234
|
+
"""Execute agent with retry logic and timeout."""
|
|
235
|
+
self._execution_count += 1
|
|
236
|
+
success = False
|
|
237
|
+
result = None
|
|
238
|
+
|
|
239
|
+
for attempt in range(max_retries):
|
|
240
|
+
try:
|
|
241
|
+
logger.debug(f"Attempt {attempt + 1}/{max_retries}")
|
|
242
|
+
|
|
243
|
+
# Execute with timeout if specified
|
|
244
|
+
if self.timeout:
|
|
245
|
+
import signal
|
|
246
|
+
|
|
247
|
+
def timeout_handler(signum, frame):
|
|
248
|
+
raise TimeoutError(f"Execution exceeded {self.timeout}s timeout")
|
|
249
|
+
|
|
250
|
+
# Set timeout alarm (Unix only)
|
|
251
|
+
try:
|
|
252
|
+
signal.signal(signal.SIGALRM, timeout_handler)
|
|
253
|
+
signal.alarm(self.timeout)
|
|
254
|
+
result = self._executor.execute(self.task)
|
|
255
|
+
signal.alarm(0) # Cancel alarm
|
|
256
|
+
except AttributeError:
|
|
257
|
+
# Windows doesn't support SIGALRM, use threading.Timer fallback
|
|
258
|
+
logger.warning("Timeout not supported on this platform, executing without timeout")
|
|
259
|
+
result = self._executor.execute(self.task)
|
|
260
|
+
else:
|
|
261
|
+
result = self._executor.execute(self.task)
|
|
262
|
+
|
|
263
|
+
logger.debug(f"Agent execution successful on attempt {attempt + 1}")
|
|
264
|
+
logger.debug(f"Result: {result}")
|
|
265
|
+
|
|
266
|
+
# Always print result to stdout (even in non-verbose mode)
|
|
267
|
+
print(f"\n✅ Agent Response:\n{result}\n")
|
|
268
|
+
|
|
269
|
+
# Estimate cost (rough: ~$0.0001 per execution for gpt-4o-mini)
|
|
270
|
+
estimated_cost = 0.0001 # Base cost estimate
|
|
271
|
+
self._total_cost += estimated_cost
|
|
272
|
+
logger.debug(f"Estimated cost this run: ${estimated_cost:.4f}, Total: ${self._total_cost:.4f}")
|
|
273
|
+
|
|
274
|
+
self._success_count += 1
|
|
275
|
+
success = True
|
|
276
|
+
|
|
277
|
+
if self.on_success:
|
|
278
|
+
try:
|
|
279
|
+
self.on_success(result)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(f"Callback error in on_success: {e}")
|
|
282
|
+
|
|
283
|
+
# Update state file after successful execution
|
|
284
|
+
self._update_state_if_daemon()
|
|
285
|
+
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
except TimeoutError as e:
|
|
289
|
+
logger.error(f"Execution timeout on attempt {attempt + 1}: {e}")
|
|
290
|
+
|
|
291
|
+
except Exception as e:
|
|
292
|
+
logger.error(f"Agent execution failed on attempt {attempt + 1}: {e}")
|
|
293
|
+
|
|
294
|
+
if attempt < max_retries - 1:
|
|
295
|
+
wait_time = 30 * (attempt + 1) # Exponential backoff
|
|
296
|
+
logger.debug(f"Waiting {wait_time}s before retry...")
|
|
297
|
+
time.sleep(wait_time)
|
|
298
|
+
|
|
299
|
+
if not success:
|
|
300
|
+
self._failure_count += 1
|
|
301
|
+
logger.error(f"Agent execution failed after {max_retries} attempts")
|
|
302
|
+
|
|
303
|
+
if self.on_failure:
|
|
304
|
+
try:
|
|
305
|
+
self.on_failure(f"Failed after {max_retries} attempts")
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"Callback error in on_failure: {e}")
|
|
308
|
+
|
|
309
|
+
# Update state file even after failure
|
|
310
|
+
self._update_state_if_daemon()
|
|
311
|
+
|
|
312
|
+
def execute_once(self) -> Any:
|
|
313
|
+
"""
|
|
314
|
+
Execute agent immediately (one-time execution).
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Agent execution result
|
|
318
|
+
"""
|
|
319
|
+
logger.debug("Executing agent once")
|
|
320
|
+
try:
|
|
321
|
+
result = self._executor.execute(self.task)
|
|
322
|
+
logger.debug(f"One-time execution successful: {result}")
|
|
323
|
+
|
|
324
|
+
if self.on_success:
|
|
325
|
+
try:
|
|
326
|
+
self.on_success(result)
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.error(f"Callback error in on_success: {e}")
|
|
329
|
+
|
|
330
|
+
return result
|
|
331
|
+
except Exception as e:
|
|
332
|
+
logger.error(f"One-time execution failed: {e}")
|
|
333
|
+
raise
|
|
334
|
+
|
|
335
|
+
@classmethod
|
|
336
|
+
def from_yaml(
|
|
337
|
+
cls,
|
|
338
|
+
yaml_path: str = "agents.yaml",
|
|
339
|
+
interval_override: Optional[str] = None,
|
|
340
|
+
max_retries_override: Optional[int] = None,
|
|
341
|
+
timeout_override: Optional[int] = None,
|
|
342
|
+
max_cost_override: Optional[float] = None,
|
|
343
|
+
on_success: Optional[Callable] = None,
|
|
344
|
+
on_failure: Optional[Callable] = None
|
|
345
|
+
) -> 'AgentScheduler':
|
|
346
|
+
"""
|
|
347
|
+
Create AgentScheduler from agents.yaml file.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
yaml_path: Path to agents.yaml file
|
|
351
|
+
interval_override: Override schedule interval from YAML
|
|
352
|
+
max_retries_override: Override max_retries from YAML
|
|
353
|
+
on_success: Callback function on successful execution
|
|
354
|
+
on_failure: Callback function on failed execution
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Configured AgentScheduler instance
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
scheduler = AgentScheduler.from_yaml("agents.yaml")
|
|
361
|
+
scheduler.start()
|
|
362
|
+
|
|
363
|
+
Example agents.yaml:
|
|
364
|
+
framework: praisonai
|
|
365
|
+
|
|
366
|
+
agents:
|
|
367
|
+
- name: "AI News Monitor"
|
|
368
|
+
role: "News Analyst"
|
|
369
|
+
instructions: "Search and summarize AI news"
|
|
370
|
+
tools:
|
|
371
|
+
- search_tool
|
|
372
|
+
|
|
373
|
+
task: "Search for latest AI news"
|
|
374
|
+
|
|
375
|
+
schedule:
|
|
376
|
+
interval: "hourly"
|
|
377
|
+
max_retries: 3
|
|
378
|
+
run_immediately: true
|
|
379
|
+
"""
|
|
380
|
+
from .yaml_loader import load_agent_yaml_with_schedule, create_agent_from_config
|
|
381
|
+
|
|
382
|
+
# Load configuration from YAML
|
|
383
|
+
agent_config, schedule_config = load_agent_yaml_with_schedule(yaml_path)
|
|
384
|
+
|
|
385
|
+
# Create agent from config
|
|
386
|
+
agent = create_agent_from_config(agent_config)
|
|
387
|
+
|
|
388
|
+
# Get task
|
|
389
|
+
task = agent_config.get('task', '')
|
|
390
|
+
if not task:
|
|
391
|
+
raise ValueError("No task specified in YAML file")
|
|
392
|
+
|
|
393
|
+
# Apply overrides to schedule config
|
|
394
|
+
if interval_override:
|
|
395
|
+
schedule_config['interval'] = interval_override
|
|
396
|
+
if max_retries_override is not None:
|
|
397
|
+
schedule_config['max_retries'] = max_retries_override
|
|
398
|
+
if timeout_override is not None:
|
|
399
|
+
schedule_config['timeout'] = timeout_override
|
|
400
|
+
if max_cost_override is not None:
|
|
401
|
+
schedule_config['max_cost'] = max_cost_override
|
|
402
|
+
|
|
403
|
+
# Create scheduler instance with timeout and cost limits
|
|
404
|
+
scheduler = cls(
|
|
405
|
+
agent=agent,
|
|
406
|
+
task=task,
|
|
407
|
+
config=agent_config,
|
|
408
|
+
timeout=schedule_config.get('timeout'),
|
|
409
|
+
max_cost=schedule_config.get('max_cost'),
|
|
410
|
+
on_success=on_success,
|
|
411
|
+
on_failure=on_failure
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Store schedule config for later use
|
|
415
|
+
scheduler._yaml_schedule_config = schedule_config
|
|
416
|
+
|
|
417
|
+
return scheduler
|
|
418
|
+
|
|
419
|
+
def start_from_yaml_config(self) -> bool:
|
|
420
|
+
"""
|
|
421
|
+
Start scheduler using configuration from YAML file.
|
|
422
|
+
|
|
423
|
+
Must be called after from_yaml() class method.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
True if started successfully
|
|
427
|
+
"""
|
|
428
|
+
if not hasattr(self, '_yaml_schedule_config'):
|
|
429
|
+
raise ValueError("No YAML configuration found. Use from_yaml() first.")
|
|
430
|
+
|
|
431
|
+
schedule_config = self._yaml_schedule_config
|
|
432
|
+
interval = schedule_config.get('interval', 'hourly')
|
|
433
|
+
max_retries = schedule_config.get('max_retries', 3)
|
|
434
|
+
run_immediately = schedule_config.get('run_immediately', False)
|
|
435
|
+
|
|
436
|
+
return self.start(interval, max_retries, run_immediately)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@classmethod
|
|
440
|
+
def from_recipe(
|
|
441
|
+
cls,
|
|
442
|
+
recipe_name: str,
|
|
443
|
+
*,
|
|
444
|
+
input_data: Any = None,
|
|
445
|
+
config: Optional[Dict[str, Any]] = None,
|
|
446
|
+
interval_override: Optional[str] = None,
|
|
447
|
+
max_retries_override: Optional[int] = None,
|
|
448
|
+
timeout_override: Optional[int] = None,
|
|
449
|
+
max_cost_override: Optional[float] = None,
|
|
450
|
+
on_success: Optional[Callable] = None,
|
|
451
|
+
on_failure: Optional[Callable] = None
|
|
452
|
+
) -> 'AgentScheduler':
|
|
453
|
+
"""
|
|
454
|
+
Create AgentScheduler from a recipe name.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
recipe_name: Name of the recipe to schedule
|
|
458
|
+
input_data: Input data for the recipe
|
|
459
|
+
config: Configuration overrides for the recipe
|
|
460
|
+
interval_override: Override schedule interval from recipe runtime config
|
|
461
|
+
max_retries_override: Override max_retries from recipe runtime config
|
|
462
|
+
timeout_override: Override timeout from recipe runtime config
|
|
463
|
+
max_cost_override: Override max_cost from recipe runtime config
|
|
464
|
+
on_success: Callback function on successful execution
|
|
465
|
+
on_failure: Callback function on failed execution
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
Configured AgentScheduler instance
|
|
469
|
+
|
|
470
|
+
Example:
|
|
471
|
+
scheduler = AgentScheduler.from_recipe("news-monitor")
|
|
472
|
+
scheduler.start(schedule_expr="hourly")
|
|
473
|
+
"""
|
|
474
|
+
from praisonai.recipe.bridge import resolve, execute_resolved_recipe, get_recipe_task_description
|
|
475
|
+
|
|
476
|
+
# Resolve the recipe
|
|
477
|
+
resolved = resolve(
|
|
478
|
+
recipe_name,
|
|
479
|
+
input_data=input_data,
|
|
480
|
+
config=config or {},
|
|
481
|
+
options={'timeout_sec': timeout_override or 300},
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# Get runtime config defaults from recipe
|
|
485
|
+
interval = interval_override or "hourly"
|
|
486
|
+
max_retries = max_retries_override if max_retries_override is not None else 3
|
|
487
|
+
timeout = timeout_override or 300
|
|
488
|
+
max_cost = max_cost_override if max_cost_override is not None else 1.00
|
|
489
|
+
|
|
490
|
+
runtime = resolved.runtime_config
|
|
491
|
+
if runtime and hasattr(runtime, 'schedule'):
|
|
492
|
+
sched_config = runtime.schedule
|
|
493
|
+
interval = interval_override or sched_config.interval
|
|
494
|
+
max_retries = max_retries_override if max_retries_override is not None else sched_config.max_retries
|
|
495
|
+
timeout = timeout_override or sched_config.timeout_sec
|
|
496
|
+
max_cost = max_cost_override if max_cost_override is not None else sched_config.max_cost_usd
|
|
497
|
+
|
|
498
|
+
# Create a recipe executor agent wrapper
|
|
499
|
+
class RecipeExecutorAgent:
|
|
500
|
+
"""Wrapper that makes a recipe look like an agent for the scheduler."""
|
|
501
|
+
def __init__(self, resolved_recipe):
|
|
502
|
+
self.resolved = resolved_recipe
|
|
503
|
+
self.name = f"RecipeAgent:{resolved_recipe.name}"
|
|
504
|
+
|
|
505
|
+
def start(self, task: str) -> Any:
|
|
506
|
+
return execute_resolved_recipe(self.resolved)
|
|
507
|
+
|
|
508
|
+
# Create the agent wrapper
|
|
509
|
+
agent = RecipeExecutorAgent(resolved)
|
|
510
|
+
task = get_recipe_task_description(resolved)
|
|
511
|
+
|
|
512
|
+
# Create scheduler instance
|
|
513
|
+
scheduler = cls(
|
|
514
|
+
agent=agent,
|
|
515
|
+
task=task,
|
|
516
|
+
timeout=timeout,
|
|
517
|
+
max_cost=max_cost,
|
|
518
|
+
on_success=on_success,
|
|
519
|
+
on_failure=on_failure,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# Store recipe metadata and schedule config
|
|
523
|
+
scheduler._recipe_name = recipe_name
|
|
524
|
+
scheduler._recipe_resolved = resolved
|
|
525
|
+
scheduler._yaml_schedule_config = {
|
|
526
|
+
'interval': interval,
|
|
527
|
+
'max_retries': max_retries,
|
|
528
|
+
'run_immediately': False,
|
|
529
|
+
'timeout': timeout,
|
|
530
|
+
'max_cost': max_cost,
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return scheduler
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def create_agent_scheduler(
|
|
537
|
+
agent,
|
|
538
|
+
task: str,
|
|
539
|
+
config: Optional[Dict[str, Any]] = None
|
|
540
|
+
) -> AgentScheduler:
|
|
541
|
+
"""
|
|
542
|
+
Factory function to create agent scheduler.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
agent: PraisonAI Agent instance
|
|
546
|
+
task: Task description
|
|
547
|
+
config: Optional configuration
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
Configured AgentScheduler instance
|
|
551
|
+
"""
|
|
552
|
+
return AgentScheduler(agent, task, config)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base components for PraisonAI Scheduler.
|
|
3
|
+
|
|
4
|
+
This module provides shared functionality for all schedulers:
|
|
5
|
+
- ScheduleParser: Parse schedule expressions into intervals
|
|
6
|
+
- ExecutorInterface: Abstract interface for executors
|
|
7
|
+
- PraisonAgentExecutor: Executor for PraisonAI agents
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ScheduleParser:
|
|
18
|
+
"""Parse schedule expressions into intervals in seconds."""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def parse(schedule_expr: str) -> int:
|
|
22
|
+
"""
|
|
23
|
+
Parse schedule expression and return interval in seconds.
|
|
24
|
+
|
|
25
|
+
Supported formats:
|
|
26
|
+
- "daily" -> 86400 seconds
|
|
27
|
+
- "hourly" -> 3600 seconds
|
|
28
|
+
- "*/30m" -> 1800 seconds (every 30 minutes)
|
|
29
|
+
- "*/6h" -> 21600 seconds (every 6 hours)
|
|
30
|
+
- "*/30s" -> 30 seconds (every 30 seconds)
|
|
31
|
+
- "3600" -> 3600 seconds (plain number)
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
schedule_expr: Schedule expression string
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Interval in seconds
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If schedule format is not supported
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
>>> ScheduleParser.parse("hourly")
|
|
44
|
+
3600
|
|
45
|
+
>>> ScheduleParser.parse("*/30m")
|
|
46
|
+
1800
|
|
47
|
+
>>> ScheduleParser.parse("daily")
|
|
48
|
+
86400
|
|
49
|
+
"""
|
|
50
|
+
schedule_expr = schedule_expr.strip().lower()
|
|
51
|
+
|
|
52
|
+
if schedule_expr == "daily":
|
|
53
|
+
return 86400
|
|
54
|
+
elif schedule_expr == "hourly":
|
|
55
|
+
return 3600
|
|
56
|
+
elif schedule_expr.isdigit():
|
|
57
|
+
return int(schedule_expr)
|
|
58
|
+
elif schedule_expr.startswith("*/"):
|
|
59
|
+
interval_part = schedule_expr[2:]
|
|
60
|
+
if interval_part.endswith("m"):
|
|
61
|
+
minutes = int(interval_part[:-1])
|
|
62
|
+
return minutes * 60
|
|
63
|
+
elif interval_part.endswith("h"):
|
|
64
|
+
hours = int(interval_part[:-1])
|
|
65
|
+
return hours * 3600
|
|
66
|
+
elif interval_part.endswith("s"):
|
|
67
|
+
return int(interval_part[:-1])
|
|
68
|
+
else:
|
|
69
|
+
return int(interval_part)
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"Unsupported schedule format: {schedule_expr}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ExecutorInterface(ABC):
|
|
75
|
+
"""Abstract interface for executors."""
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def execute(self, task: str) -> Any:
|
|
79
|
+
"""
|
|
80
|
+
Execute a task.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
task: Task description or instruction
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Execution result
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
Exception: If execution fails
|
|
90
|
+
"""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class PraisonAgentExecutor(ExecutorInterface):
|
|
95
|
+
"""Executor for PraisonAI agents."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, agent):
|
|
98
|
+
"""
|
|
99
|
+
Initialize executor with a PraisonAI agent.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
agent: PraisonAI Agent instance (must have start() method)
|
|
103
|
+
"""
|
|
104
|
+
self.agent = agent
|
|
105
|
+
|
|
106
|
+
def execute(self, task: str) -> Any:
|
|
107
|
+
"""
|
|
108
|
+
Execute the agent with given task.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
task: Task description for the agent
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Agent execution result
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
Exception: If agent execution fails
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
result = self.agent.start(task)
|
|
121
|
+
return result
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"Agent execution failed: {e}")
|
|
124
|
+
raise
|