attune-ai 2.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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Batch Processing Workflow using Anthropic Batch API.
|
|
2
|
+
|
|
3
|
+
Enables 50% cost reduction by processing non-urgent tasks asynchronously.
|
|
4
|
+
Batch API processes requests within 24 hours - not suitable for interactive workflows.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from attune_llm.providers import AnthropicBatchProvider
|
|
17
|
+
from attune.config import _validate_file_path
|
|
18
|
+
from attune.models import get_model
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class BatchRequest:
|
|
25
|
+
"""Single request in a batch."""
|
|
26
|
+
|
|
27
|
+
task_id: str
|
|
28
|
+
task_type: str
|
|
29
|
+
input_data: dict[str, Any]
|
|
30
|
+
model_tier: str = "capable" # cheap, capable, premium
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class BatchResult:
|
|
35
|
+
"""Result from batch processing."""
|
|
36
|
+
|
|
37
|
+
task_id: str
|
|
38
|
+
success: bool
|
|
39
|
+
output: dict[str, Any] | None = None
|
|
40
|
+
error: str | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BatchProcessingWorkflow:
|
|
44
|
+
"""Process multiple tasks via Anthropic Batch API (50% cost savings).
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> workflow = BatchProcessingWorkflow()
|
|
48
|
+
>>> requests = [
|
|
49
|
+
... BatchRequest(
|
|
50
|
+
... task_id="task_1",
|
|
51
|
+
... task_type="analyze_logs",
|
|
52
|
+
... input_data={"logs": "ERROR: Connection failed..."}
|
|
53
|
+
... ),
|
|
54
|
+
... BatchRequest(
|
|
55
|
+
... task_id="task_2",
|
|
56
|
+
... task_type="generate_report",
|
|
57
|
+
... input_data={"data": {...}}
|
|
58
|
+
... )
|
|
59
|
+
... ]
|
|
60
|
+
>>> results = await workflow.execute_batch(requests)
|
|
61
|
+
>>> print(f"{sum(r.success for r in results)}/{len(results)} successful")
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, api_key: str | None = None):
|
|
65
|
+
"""Initialize batch workflow.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
api_key: Anthropic API key (optional, uses ANTHROPIC_API_KEY env var)
|
|
69
|
+
"""
|
|
70
|
+
self.batch_provider = AnthropicBatchProvider(api_key=api_key)
|
|
71
|
+
|
|
72
|
+
async def execute_batch(
|
|
73
|
+
self,
|
|
74
|
+
requests: list[BatchRequest],
|
|
75
|
+
poll_interval: int = 300, # 5 minutes
|
|
76
|
+
timeout: int = 86400, # 24 hours
|
|
77
|
+
) -> list[BatchResult]:
|
|
78
|
+
"""Execute batch of requests.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
requests: List of batch requests
|
|
82
|
+
poll_interval: Seconds between status checks (default: 300)
|
|
83
|
+
timeout: Maximum wait time in seconds (default: 86400)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of results matching input order
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If requests is empty
|
|
90
|
+
TimeoutError: If batch doesn't complete within timeout
|
|
91
|
+
RuntimeError: If batch processing fails
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> workflow = BatchProcessingWorkflow()
|
|
95
|
+
>>> requests = [
|
|
96
|
+
... BatchRequest(
|
|
97
|
+
... task_id="task_1",
|
|
98
|
+
... task_type="analyze_logs",
|
|
99
|
+
... input_data={"logs": "..."}
|
|
100
|
+
... )
|
|
101
|
+
... ]
|
|
102
|
+
>>> results = await workflow.execute_batch(requests)
|
|
103
|
+
>>> for result in results:
|
|
104
|
+
... if result.success:
|
|
105
|
+
... print(f"Task {result.task_id}: Success")
|
|
106
|
+
... else:
|
|
107
|
+
... print(f"Task {result.task_id}: {result.error}")
|
|
108
|
+
"""
|
|
109
|
+
if not requests:
|
|
110
|
+
raise ValueError("requests cannot be empty")
|
|
111
|
+
|
|
112
|
+
# Convert to Anthropic Message Batches format
|
|
113
|
+
api_requests = []
|
|
114
|
+
for req in requests:
|
|
115
|
+
model = get_model("anthropic", req.model_tier)
|
|
116
|
+
if model is None:
|
|
117
|
+
raise ValueError(f"Unknown model tier: {req.model_tier}")
|
|
118
|
+
|
|
119
|
+
# Use correct format with params wrapper
|
|
120
|
+
api_requests.append(
|
|
121
|
+
{
|
|
122
|
+
"custom_id": req.task_id,
|
|
123
|
+
"params": {
|
|
124
|
+
"model": model.id,
|
|
125
|
+
"messages": self._format_messages(req),
|
|
126
|
+
"max_tokens": 4096,
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Submit batch
|
|
132
|
+
logger.info(f"Submitting batch of {len(requests)} requests")
|
|
133
|
+
batch_id = self.batch_provider.create_batch(api_requests)
|
|
134
|
+
|
|
135
|
+
logger.info(f"Batch {batch_id} created, polling every {poll_interval}s (max {timeout}s)")
|
|
136
|
+
|
|
137
|
+
# Wait for completion
|
|
138
|
+
try:
|
|
139
|
+
raw_results = await self.batch_provider.wait_for_batch(
|
|
140
|
+
batch_id, poll_interval=poll_interval, timeout=timeout
|
|
141
|
+
)
|
|
142
|
+
except TimeoutError:
|
|
143
|
+
logger.error(f"Batch {batch_id} timed out after {timeout}s")
|
|
144
|
+
return [
|
|
145
|
+
BatchResult(
|
|
146
|
+
task_id=req.task_id,
|
|
147
|
+
success=False,
|
|
148
|
+
error="Batch processing timed out",
|
|
149
|
+
)
|
|
150
|
+
for req in requests
|
|
151
|
+
]
|
|
152
|
+
except RuntimeError as e:
|
|
153
|
+
logger.error(f"Batch {batch_id} failed: {e}")
|
|
154
|
+
return [
|
|
155
|
+
BatchResult(task_id=req.task_id, success=False, error=f"Batch failed: {e}")
|
|
156
|
+
for req in requests
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
# Parse results - new Message Batches API format
|
|
160
|
+
results = []
|
|
161
|
+
for raw in raw_results:
|
|
162
|
+
task_id = raw.get("custom_id", "unknown")
|
|
163
|
+
result = raw.get("result", {})
|
|
164
|
+
result_type = result.get("type", "unknown")
|
|
165
|
+
|
|
166
|
+
if result_type == "succeeded":
|
|
167
|
+
# Extract message content from succeeded result
|
|
168
|
+
message = result.get("message", {})
|
|
169
|
+
content_blocks = message.get("content", [])
|
|
170
|
+
|
|
171
|
+
# Convert content blocks to simple output format
|
|
172
|
+
output_text = ""
|
|
173
|
+
for block in content_blocks:
|
|
174
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
175
|
+
output_text += block.get("text", "")
|
|
176
|
+
|
|
177
|
+
output = {
|
|
178
|
+
"content": output_text,
|
|
179
|
+
"usage": message.get("usage", {}),
|
|
180
|
+
"model": message.get("model"),
|
|
181
|
+
"stop_reason": message.get("stop_reason"),
|
|
182
|
+
}
|
|
183
|
+
results.append(BatchResult(task_id=task_id, success=True, output=output))
|
|
184
|
+
|
|
185
|
+
elif result_type == "errored":
|
|
186
|
+
# Extract error from errored result
|
|
187
|
+
error = result.get("error", {})
|
|
188
|
+
error_msg = error.get("message", "Unknown error")
|
|
189
|
+
error_type = error.get("type", "unknown_error")
|
|
190
|
+
results.append(
|
|
191
|
+
BatchResult(task_id=task_id, success=False, error=f"{error_type}: {error_msg}")
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
elif result_type == "expired":
|
|
195
|
+
results.append(
|
|
196
|
+
BatchResult(task_id=task_id, success=False, error="Request expired")
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
elif result_type == "canceled":
|
|
200
|
+
results.append(
|
|
201
|
+
BatchResult(task_id=task_id, success=False, error="Request canceled")
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
else:
|
|
205
|
+
results.append(
|
|
206
|
+
BatchResult(
|
|
207
|
+
task_id=task_id,
|
|
208
|
+
success=False,
|
|
209
|
+
error=f"Unknown result type: {result_type}",
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Log summary
|
|
214
|
+
success_count = sum(r.success for r in results)
|
|
215
|
+
logger.info(f"Batch {batch_id} completed: {success_count}/{len(results)} successful")
|
|
216
|
+
|
|
217
|
+
return results
|
|
218
|
+
|
|
219
|
+
def _format_messages(self, request: BatchRequest) -> list[dict[str, str]]:
|
|
220
|
+
"""Format request into Anthropic messages format.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
request: BatchRequest with task_type and input_data
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List of message dicts for Anthropic API
|
|
227
|
+
"""
|
|
228
|
+
# Task-specific prompts
|
|
229
|
+
task_prompts = {
|
|
230
|
+
"analyze_logs": "Analyze the following logs and identify issues:\n\n{logs}",
|
|
231
|
+
"generate_report": "Generate a report based on:\n\n{data}",
|
|
232
|
+
"classify_bulk": "Classify the following items:\n\n{items}",
|
|
233
|
+
"generate_docs": "Generate documentation for:\n\n{code}",
|
|
234
|
+
"generate_tests": "Generate unit tests for:\n\n{code}",
|
|
235
|
+
# Add more task types as needed
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# Get prompt template or use default
|
|
239
|
+
prompt_template = task_prompts.get(request.task_type, "Process the following:\n\n{input}")
|
|
240
|
+
|
|
241
|
+
# Format with input data
|
|
242
|
+
try:
|
|
243
|
+
content = prompt_template.format(**request.input_data)
|
|
244
|
+
except KeyError as e:
|
|
245
|
+
logger.warning(
|
|
246
|
+
f"Missing required field {e} for task {request.task_type}, using raw input"
|
|
247
|
+
)
|
|
248
|
+
# Use default template instead of the specific one
|
|
249
|
+
default_template = "Process the following:\n\n{input}"
|
|
250
|
+
content = default_template.format(input=json.dumps(request.input_data))
|
|
251
|
+
|
|
252
|
+
return [{"role": "user", "content": content}]
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def from_json_file(cls, file_path: str) -> "BatchProcessingWorkflow":
|
|
256
|
+
"""Create workflow from JSON input file.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
file_path: Path to JSON file with batch requests
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
BatchProcessingWorkflow instance
|
|
263
|
+
|
|
264
|
+
Input file format:
|
|
265
|
+
[
|
|
266
|
+
{
|
|
267
|
+
"task_id": "1",
|
|
268
|
+
"task_type": "analyze_logs",
|
|
269
|
+
"input_data": {"logs": "ERROR: ..."},
|
|
270
|
+
"model_tier": "capable"
|
|
271
|
+
},
|
|
272
|
+
...
|
|
273
|
+
]
|
|
274
|
+
"""
|
|
275
|
+
return cls()
|
|
276
|
+
|
|
277
|
+
def load_requests_from_file(self, file_path: str) -> list[BatchRequest]:
|
|
278
|
+
"""Load batch requests from JSON file.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
file_path: Path to JSON file
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
List of BatchRequest objects
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
FileNotFoundError: If file doesn't exist
|
|
288
|
+
json.JSONDecodeError: If file is not valid JSON
|
|
289
|
+
ValueError: If file format is invalid
|
|
290
|
+
"""
|
|
291
|
+
path = Path(file_path)
|
|
292
|
+
if not path.exists():
|
|
293
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
294
|
+
|
|
295
|
+
with open(path) as f:
|
|
296
|
+
data = json.load(f)
|
|
297
|
+
|
|
298
|
+
if not isinstance(data, list):
|
|
299
|
+
raise ValueError("Input file must contain a JSON array")
|
|
300
|
+
|
|
301
|
+
requests = []
|
|
302
|
+
for item in data:
|
|
303
|
+
if not isinstance(item, dict):
|
|
304
|
+
raise ValueError("Each item must be a JSON object")
|
|
305
|
+
|
|
306
|
+
requests.append(
|
|
307
|
+
BatchRequest(
|
|
308
|
+
task_id=item["task_id"],
|
|
309
|
+
task_type=item["task_type"],
|
|
310
|
+
input_data=item["input_data"],
|
|
311
|
+
model_tier=item.get("model_tier", "capable"),
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return requests
|
|
316
|
+
|
|
317
|
+
def save_results_to_file(self, results: list[BatchResult], output_path: str) -> None:
|
|
318
|
+
"""Save batch results to JSON file.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
results: List of BatchResult objects
|
|
322
|
+
output_path: Path to output file
|
|
323
|
+
|
|
324
|
+
Raises:
|
|
325
|
+
OSError: If file cannot be written
|
|
326
|
+
"""
|
|
327
|
+
output_data = [
|
|
328
|
+
{
|
|
329
|
+
"task_id": r.task_id,
|
|
330
|
+
"success": r.success,
|
|
331
|
+
"output": r.output,
|
|
332
|
+
"error": r.error,
|
|
333
|
+
}
|
|
334
|
+
for r in results
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
path = Path(output_path)
|
|
338
|
+
validated_path = _validate_file_path(str(path))
|
|
339
|
+
with open(validated_path, "w") as f:
|
|
340
|
+
json.dump(output_data, f, indent=2)
|
|
341
|
+
|
|
342
|
+
logger.info(f"Results saved to {validated_path}")
|