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,509 @@
|
|
|
1
|
+
"""Keyboard Shortcuts Workflow
|
|
2
|
+
|
|
3
|
+
Generates optimized keyboard shortcuts for any project following
|
|
4
|
+
the "Keyboard Conductor" musical scale pattern.
|
|
5
|
+
|
|
6
|
+
Stages:
|
|
7
|
+
1. DISCOVER - Parse features from project sources
|
|
8
|
+
2. ANALYZE - Categorize features and suggest mnemonics (LLM)
|
|
9
|
+
3. GENERATE - Create shortcuts for all layouts (LLM)
|
|
10
|
+
4. VALIDATE - Check for conflicts and ergonomic issues (LLM)
|
|
11
|
+
5. EXPORT - Generate output files (local)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import yaml
|
|
19
|
+
|
|
20
|
+
from attune.workflows.base import BaseWorkflow, ModelTier
|
|
21
|
+
|
|
22
|
+
from .generators import ComprehensiveGenerator
|
|
23
|
+
from .parsers import CompositeParser
|
|
24
|
+
from .prompts import format_analyze_prompt, format_generate_prompt, format_validate_prompt
|
|
25
|
+
from .schema import (
|
|
26
|
+
FeatureManifest,
|
|
27
|
+
FrequencyTier,
|
|
28
|
+
GeneratedShortcuts,
|
|
29
|
+
KeyboardLayout,
|
|
30
|
+
LayoutShortcuts,
|
|
31
|
+
ScaleAssignments,
|
|
32
|
+
ShortcutAssignment,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class KeyboardShortcutWorkflow(BaseWorkflow):
|
|
37
|
+
"""Generate optimized keyboard shortcuts for any project.
|
|
38
|
+
|
|
39
|
+
Uses the "Keyboard Conductor" pattern:
|
|
40
|
+
- Scale 1 (Daily): 4 most-used features on home row
|
|
41
|
+
- Scale 2 (Frequent): Next 4 features on adjacent keys
|
|
42
|
+
- Scale 3 (Advanced): Remaining features logically placed
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
name = "keyboard-shortcuts"
|
|
46
|
+
description = "Generate ergonomic keyboard shortcuts with multi-layout support"
|
|
47
|
+
stages = ["discover", "analyze", "generate", "validate", "export"]
|
|
48
|
+
tier_map = {
|
|
49
|
+
"discover": ModelTier.CHEAP, # Parse files, no LLM
|
|
50
|
+
"analyze": ModelTier.CAPABLE, # Categorize features
|
|
51
|
+
"generate": ModelTier.CAPABLE, # Generate shortcuts
|
|
52
|
+
"validate": ModelTier.CHEAP, # Check conflicts
|
|
53
|
+
"export": ModelTier.CHEAP, # Write files, no LLM
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def __init__(self, **kwargs):
|
|
57
|
+
super().__init__(**kwargs)
|
|
58
|
+
self.parser = CompositeParser()
|
|
59
|
+
self.generator = ComprehensiveGenerator()
|
|
60
|
+
|
|
61
|
+
async def run_stage(
|
|
62
|
+
self,
|
|
63
|
+
stage_name: str,
|
|
64
|
+
tier: ModelTier,
|
|
65
|
+
input_data: dict[str, Any],
|
|
66
|
+
) -> tuple[Any, int, int]:
|
|
67
|
+
"""Execute a single workflow stage."""
|
|
68
|
+
if stage_name == "discover":
|
|
69
|
+
return await self._discover_features(input_data)
|
|
70
|
+
if stage_name == "analyze":
|
|
71
|
+
return await self._analyze_features(input_data, tier)
|
|
72
|
+
if stage_name == "generate":
|
|
73
|
+
return await self._generate_shortcuts(input_data, tier)
|
|
74
|
+
if stage_name == "validate":
|
|
75
|
+
return await self._validate_shortcuts(input_data, tier)
|
|
76
|
+
if stage_name == "export":
|
|
77
|
+
return await self._export_outputs(input_data)
|
|
78
|
+
raise ValueError(f"Unknown stage: {stage_name}")
|
|
79
|
+
|
|
80
|
+
def should_skip_stage(
|
|
81
|
+
self,
|
|
82
|
+
stage_name: str,
|
|
83
|
+
input_data: dict[str, Any],
|
|
84
|
+
) -> tuple[bool, str | None]:
|
|
85
|
+
"""Check if a stage should be skipped."""
|
|
86
|
+
if stage_name == "analyze":
|
|
87
|
+
# Skip LLM analysis if features already have frequencies set
|
|
88
|
+
manifest = input_data.get("manifest")
|
|
89
|
+
if manifest and all(f.frequency for f in manifest.all_features()):
|
|
90
|
+
return True, "Features already categorized"
|
|
91
|
+
|
|
92
|
+
if stage_name == "validate":
|
|
93
|
+
# Skip validation if user explicitly requests it
|
|
94
|
+
if input_data.get("skip_validation", False):
|
|
95
|
+
return True, "Validation skipped by user request"
|
|
96
|
+
|
|
97
|
+
return False, None
|
|
98
|
+
|
|
99
|
+
# ========================================================================
|
|
100
|
+
# Stage 1: Discover Features
|
|
101
|
+
# ========================================================================
|
|
102
|
+
|
|
103
|
+
async def _discover_features(
|
|
104
|
+
self,
|
|
105
|
+
input_data: dict[str, Any],
|
|
106
|
+
) -> tuple[dict[str, Any], int, int]:
|
|
107
|
+
"""Parse features from project sources.
|
|
108
|
+
|
|
109
|
+
Supports:
|
|
110
|
+
- VSCode package.json commands
|
|
111
|
+
- Python pyproject.toml entry points
|
|
112
|
+
- Custom features.yaml manifest
|
|
113
|
+
"""
|
|
114
|
+
project_path = Path(input_data.get("path", "."))
|
|
115
|
+
|
|
116
|
+
# Use the composite parser to discover features
|
|
117
|
+
manifest = self.parser.discover_features(project_path)
|
|
118
|
+
|
|
119
|
+
# If no features found, return early
|
|
120
|
+
if not manifest.all_features():
|
|
121
|
+
return (
|
|
122
|
+
{
|
|
123
|
+
"manifest": manifest,
|
|
124
|
+
"feature_count": 0,
|
|
125
|
+
"error": "No features found in project",
|
|
126
|
+
},
|
|
127
|
+
0,
|
|
128
|
+
0,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
{
|
|
133
|
+
"manifest": manifest,
|
|
134
|
+
"feature_count": len(manifest.all_features()),
|
|
135
|
+
"categories": len(manifest.categories),
|
|
136
|
+
},
|
|
137
|
+
0,
|
|
138
|
+
0,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# ========================================================================
|
|
142
|
+
# Stage 2: Analyze Features (LLM)
|
|
143
|
+
# ========================================================================
|
|
144
|
+
|
|
145
|
+
async def _analyze_features(
|
|
146
|
+
self,
|
|
147
|
+
input_data: dict[str, Any],
|
|
148
|
+
tier: ModelTier,
|
|
149
|
+
) -> tuple[dict[str, Any], int, int]:
|
|
150
|
+
"""Use LLM to categorize features and suggest mnemonics.
|
|
151
|
+
|
|
152
|
+
Input: FeatureManifest with discovered features
|
|
153
|
+
Output: Enhanced manifest with frequency tiers and mnemonic suggestions
|
|
154
|
+
"""
|
|
155
|
+
manifest: FeatureManifest = input_data["manifest"]
|
|
156
|
+
|
|
157
|
+
# Convert features to YAML for the prompt
|
|
158
|
+
features_yaml = self._features_to_yaml(manifest)
|
|
159
|
+
|
|
160
|
+
# Format the analysis prompt
|
|
161
|
+
prompt = format_analyze_prompt(
|
|
162
|
+
project_name=manifest.project_name,
|
|
163
|
+
project_type=manifest.project_type,
|
|
164
|
+
feature_count=len(manifest.all_features()),
|
|
165
|
+
features_yaml=features_yaml,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Call LLM (using inherited method from BaseWorkflow)
|
|
169
|
+
response, in_tokens, out_tokens = await self._invoke_llm(
|
|
170
|
+
prompt=prompt,
|
|
171
|
+
tier=tier,
|
|
172
|
+
system="You are a UX expert specializing in keyboard ergonomics.",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Parse the response
|
|
176
|
+
analysis = self._parse_yaml_response(response)
|
|
177
|
+
|
|
178
|
+
# Update manifest with analysis results
|
|
179
|
+
if analysis and "analyzed_features" in analysis:
|
|
180
|
+
self._update_manifest_from_analysis(manifest, analysis)
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
{
|
|
184
|
+
"manifest": manifest,
|
|
185
|
+
"analysis": analysis,
|
|
186
|
+
"phrase_mnemonic": analysis.get("phrase_mnemonic", "") if analysis else "",
|
|
187
|
+
},
|
|
188
|
+
in_tokens,
|
|
189
|
+
out_tokens,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# ========================================================================
|
|
193
|
+
# Stage 3: Generate Shortcuts (LLM)
|
|
194
|
+
# ========================================================================
|
|
195
|
+
|
|
196
|
+
async def _generate_shortcuts(
|
|
197
|
+
self,
|
|
198
|
+
input_data: dict[str, Any],
|
|
199
|
+
tier: ModelTier,
|
|
200
|
+
) -> tuple[dict[str, Any], int, int]:
|
|
201
|
+
"""Use LLM to generate optimal shortcuts for each layout.
|
|
202
|
+
|
|
203
|
+
Input: Analyzed manifest
|
|
204
|
+
Output: GeneratedShortcuts with shortcuts for all layouts
|
|
205
|
+
"""
|
|
206
|
+
manifest: FeatureManifest = input_data["manifest"]
|
|
207
|
+
analysis = input_data.get("analysis", {})
|
|
208
|
+
|
|
209
|
+
# Format the generation prompt
|
|
210
|
+
prompt = format_generate_prompt(
|
|
211
|
+
analyzed_yaml=yaml.dump(analysis) if analysis else self._features_to_yaml(manifest),
|
|
212
|
+
existing_shortcuts="[]",
|
|
213
|
+
reserved_keys='["q", "w", "s", "z", "x"]',
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Call LLM
|
|
217
|
+
response, in_tokens, out_tokens = await self._invoke_llm(
|
|
218
|
+
prompt=prompt,
|
|
219
|
+
tier=tier,
|
|
220
|
+
system="You are a keyboard layout specialist.",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Parse the response
|
|
224
|
+
shortcuts_data = self._parse_json_response(response)
|
|
225
|
+
|
|
226
|
+
# Build GeneratedShortcuts from response
|
|
227
|
+
generated = self._build_generated_shortcuts(manifest, shortcuts_data)
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
{
|
|
231
|
+
"manifest": manifest,
|
|
232
|
+
"generated": generated,
|
|
233
|
+
},
|
|
234
|
+
in_tokens,
|
|
235
|
+
out_tokens,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# ========================================================================
|
|
239
|
+
# Stage 4: Validate Shortcuts (LLM)
|
|
240
|
+
# ========================================================================
|
|
241
|
+
|
|
242
|
+
async def _validate_shortcuts(
|
|
243
|
+
self,
|
|
244
|
+
input_data: dict[str, Any],
|
|
245
|
+
tier: ModelTier,
|
|
246
|
+
) -> tuple[dict[str, Any], int, int]:
|
|
247
|
+
"""Validate generated shortcuts for conflicts and issues.
|
|
248
|
+
|
|
249
|
+
Input: GeneratedShortcuts
|
|
250
|
+
Output: Validated shortcuts with any warnings/conflicts
|
|
251
|
+
"""
|
|
252
|
+
generated: GeneratedShortcuts = input_data["generated"]
|
|
253
|
+
|
|
254
|
+
# Format the validation prompt
|
|
255
|
+
shortcuts_json = json.dumps(
|
|
256
|
+
{
|
|
257
|
+
layout.value: {
|
|
258
|
+
"shortcuts": [
|
|
259
|
+
{"key": s.key, "feature_id": s.feature_id, "mnemonic": s.mnemonic}
|
|
260
|
+
for s in layout_shortcuts.shortcuts
|
|
261
|
+
],
|
|
262
|
+
}
|
|
263
|
+
for layout, layout_shortcuts in generated.layouts.items()
|
|
264
|
+
},
|
|
265
|
+
indent=2,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
prompt = format_validate_prompt(shortcuts_json)
|
|
269
|
+
|
|
270
|
+
# Call LLM
|
|
271
|
+
response, in_tokens, out_tokens = await self._invoke_llm(
|
|
272
|
+
prompt=prompt,
|
|
273
|
+
tier=tier,
|
|
274
|
+
system="You are a keyboard shortcut validator.",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Parse the response
|
|
278
|
+
validation = self._parse_json_response(response)
|
|
279
|
+
|
|
280
|
+
# Update generated shortcuts with validation results
|
|
281
|
+
if validation:
|
|
282
|
+
generated.validation_passed = validation.get("valid", True)
|
|
283
|
+
generated.conflicts = validation.get("conflicts", [])
|
|
284
|
+
generated.warnings = validation.get("warnings", [])
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
{
|
|
288
|
+
"manifest": input_data["manifest"],
|
|
289
|
+
"generated": generated,
|
|
290
|
+
"validation": validation,
|
|
291
|
+
},
|
|
292
|
+
in_tokens,
|
|
293
|
+
out_tokens,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# ========================================================================
|
|
297
|
+
# Stage 5: Export Outputs
|
|
298
|
+
# ========================================================================
|
|
299
|
+
|
|
300
|
+
async def _export_outputs(
|
|
301
|
+
self,
|
|
302
|
+
input_data: dict[str, Any],
|
|
303
|
+
) -> tuple[dict[str, Any], int, int]:
|
|
304
|
+
"""Generate output files in all formats.
|
|
305
|
+
|
|
306
|
+
Output:
|
|
307
|
+
- VSCode keybindings (per layout)
|
|
308
|
+
- CLI aliases script
|
|
309
|
+
- Markdown documentation
|
|
310
|
+
"""
|
|
311
|
+
generated: GeneratedShortcuts = input_data["generated"]
|
|
312
|
+
output_dir = Path(input_data.get("output_dir", "."))
|
|
313
|
+
|
|
314
|
+
# Generate all outputs
|
|
315
|
+
generated_files = self.generator.generate_all(generated, output_dir)
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
{
|
|
319
|
+
"generated": generated,
|
|
320
|
+
"output_files": generated_files,
|
|
321
|
+
"output_dir": str(output_dir),
|
|
322
|
+
},
|
|
323
|
+
0,
|
|
324
|
+
0,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# ========================================================================
|
|
328
|
+
# Helper Methods
|
|
329
|
+
# ========================================================================
|
|
330
|
+
|
|
331
|
+
def _features_to_yaml(self, manifest: FeatureManifest) -> str:
|
|
332
|
+
"""Convert features to YAML for LLM prompts."""
|
|
333
|
+
features_list = []
|
|
334
|
+
for feature in manifest.all_features():
|
|
335
|
+
features_list.append(
|
|
336
|
+
{
|
|
337
|
+
"id": feature.id,
|
|
338
|
+
"name": feature.name,
|
|
339
|
+
"description": feature.description,
|
|
340
|
+
"frequency": feature.frequency.value,
|
|
341
|
+
"context": feature.context,
|
|
342
|
+
},
|
|
343
|
+
)
|
|
344
|
+
return yaml.dump({"features": features_list}, default_flow_style=False)
|
|
345
|
+
|
|
346
|
+
def _parse_yaml_response(self, response: str) -> dict[str, Any] | None:
|
|
347
|
+
"""Parse YAML from LLM response."""
|
|
348
|
+
try:
|
|
349
|
+
# Try to extract YAML from code blocks
|
|
350
|
+
if "```yaml" in response:
|
|
351
|
+
yaml_content = response.split("```yaml")[1].split("```")[0]
|
|
352
|
+
elif "```" in response:
|
|
353
|
+
yaml_content = response.split("```")[1].split("```")[0]
|
|
354
|
+
else:
|
|
355
|
+
yaml_content = response
|
|
356
|
+
|
|
357
|
+
result = yaml.safe_load(yaml_content.strip())
|
|
358
|
+
if isinstance(result, dict):
|
|
359
|
+
return result
|
|
360
|
+
return None
|
|
361
|
+
except Exception: # noqa: BLE001
|
|
362
|
+
# INTENTIONAL: LLM responses may have unparseable YAML.
|
|
363
|
+
# Return None and let caller handle fallback gracefully.
|
|
364
|
+
return None
|
|
365
|
+
|
|
366
|
+
def _parse_json_response(self, response: str) -> dict[str, Any] | None:
|
|
367
|
+
"""Parse JSON from LLM response."""
|
|
368
|
+
try:
|
|
369
|
+
# Try to extract JSON from code blocks
|
|
370
|
+
if "```json" in response:
|
|
371
|
+
json_content = response.split("```json")[1].split("```")[0]
|
|
372
|
+
elif "```" in response:
|
|
373
|
+
json_content = response.split("```")[1].split("```")[0]
|
|
374
|
+
else:
|
|
375
|
+
json_content = response
|
|
376
|
+
|
|
377
|
+
result = json.loads(json_content.strip())
|
|
378
|
+
if isinstance(result, dict):
|
|
379
|
+
return result
|
|
380
|
+
return None
|
|
381
|
+
except Exception: # noqa: BLE001
|
|
382
|
+
# INTENTIONAL: LLM responses may have unparseable JSON.
|
|
383
|
+
# Return None and let caller handle fallback gracefully.
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
def _update_manifest_from_analysis(
|
|
387
|
+
self,
|
|
388
|
+
manifest: FeatureManifest,
|
|
389
|
+
analysis: dict,
|
|
390
|
+
) -> None:
|
|
391
|
+
"""Update manifest features with analysis results."""
|
|
392
|
+
analyzed = {f["id"]: f for f in analysis.get("analyzed_features", [])}
|
|
393
|
+
|
|
394
|
+
for feature in manifest.all_features():
|
|
395
|
+
if feature.id in analyzed:
|
|
396
|
+
af = analyzed[feature.id]
|
|
397
|
+
freq_str = af.get("frequency", "frequent")
|
|
398
|
+
if freq_str in FrequencyTier._value2member_map_:
|
|
399
|
+
feature.frequency = FrequencyTier(freq_str)
|
|
400
|
+
|
|
401
|
+
def _build_generated_shortcuts(
|
|
402
|
+
self,
|
|
403
|
+
manifest: FeatureManifest,
|
|
404
|
+
shortcuts_data: dict | None,
|
|
405
|
+
) -> GeneratedShortcuts:
|
|
406
|
+
"""Build GeneratedShortcuts from LLM response."""
|
|
407
|
+
generated = GeneratedShortcuts(manifest=manifest)
|
|
408
|
+
|
|
409
|
+
if not shortcuts_data:
|
|
410
|
+
# Fallback: generate basic shortcuts
|
|
411
|
+
generated = self._generate_fallback_shortcuts(manifest)
|
|
412
|
+
return generated
|
|
413
|
+
|
|
414
|
+
# Process each layout
|
|
415
|
+
for layout_str in ["qwerty", "dvorak", "colemak"]:
|
|
416
|
+
if layout_str not in shortcuts_data:
|
|
417
|
+
continue
|
|
418
|
+
|
|
419
|
+
layout = KeyboardLayout(layout_str)
|
|
420
|
+
layout_data = shortcuts_data[layout_str]
|
|
421
|
+
|
|
422
|
+
shortcuts = []
|
|
423
|
+
for s in layout_data.get("shortcuts", []):
|
|
424
|
+
shortcuts.append(
|
|
425
|
+
ShortcutAssignment(
|
|
426
|
+
feature_id=s["feature_id"],
|
|
427
|
+
key=s["key"],
|
|
428
|
+
mnemonic=s.get("mnemonic", f"{s['key'].upper()} = {s['feature_id']}"),
|
|
429
|
+
layout=layout,
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
scales = layout_data.get("scale_assignments", {})
|
|
434
|
+
scale_assignments = ScaleAssignments(
|
|
435
|
+
daily=scales.get("daily", []),
|
|
436
|
+
frequent=scales.get("frequent", []),
|
|
437
|
+
advanced=scales.get("advanced", []),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
generated.layouts[layout] = LayoutShortcuts(
|
|
441
|
+
layout=layout,
|
|
442
|
+
shortcuts=shortcuts,
|
|
443
|
+
scale_assignments=scale_assignments,
|
|
444
|
+
phrase_mnemonic=layout_data.get("phrase_mnemonic", ""),
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
return generated
|
|
448
|
+
|
|
449
|
+
def _generate_fallback_shortcuts(self, manifest: FeatureManifest) -> GeneratedShortcuts:
|
|
450
|
+
"""Generate basic shortcuts without LLM."""
|
|
451
|
+
generated = GeneratedShortcuts(manifest=manifest)
|
|
452
|
+
features = manifest.all_features()
|
|
453
|
+
|
|
454
|
+
# Simple assignment: use first letter of feature ID
|
|
455
|
+
used_keys: set[str] = set()
|
|
456
|
+
shortcuts = []
|
|
457
|
+
|
|
458
|
+
for feature in features:
|
|
459
|
+
# Try first letter
|
|
460
|
+
key = feature.id[0].lower()
|
|
461
|
+
if key in used_keys:
|
|
462
|
+
# Find next available letter
|
|
463
|
+
for c in feature.id.lower():
|
|
464
|
+
if c.isalpha() and c not in used_keys:
|
|
465
|
+
key = c
|
|
466
|
+
break
|
|
467
|
+
else:
|
|
468
|
+
# Use any available letter
|
|
469
|
+
for c in "abcdefghijklmnopqrstuvwxyz":
|
|
470
|
+
if c not in used_keys:
|
|
471
|
+
key = c
|
|
472
|
+
break
|
|
473
|
+
|
|
474
|
+
used_keys.add(key)
|
|
475
|
+
shortcuts.append(
|
|
476
|
+
ShortcutAssignment(
|
|
477
|
+
feature_id=feature.id,
|
|
478
|
+
key=key,
|
|
479
|
+
mnemonic=f"{key.upper()} = {feature.name}",
|
|
480
|
+
),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# Assign to QWERTY layout
|
|
484
|
+
generated.layouts[KeyboardLayout.QWERTY] = LayoutShortcuts(
|
|
485
|
+
layout=KeyboardLayout.QWERTY,
|
|
486
|
+
shortcuts=shortcuts,
|
|
487
|
+
phrase_mnemonic="First letter of each command",
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
return generated
|
|
491
|
+
|
|
492
|
+
async def _invoke_llm(
|
|
493
|
+
self,
|
|
494
|
+
prompt: str,
|
|
495
|
+
tier: ModelTier,
|
|
496
|
+
system: str = "",
|
|
497
|
+
) -> tuple[str, int, int]:
|
|
498
|
+
"""Call LLM with the given prompt.
|
|
499
|
+
|
|
500
|
+
Uses the inherited _call_llm method from BaseWorkflow
|
|
501
|
+
which handles provider selection and telemetry.
|
|
502
|
+
"""
|
|
503
|
+
# Use inherited _call_llm from BaseWorkflow
|
|
504
|
+
return await self._call_llm(
|
|
505
|
+
tier=tier,
|
|
506
|
+
system=system,
|
|
507
|
+
user_message=prompt,
|
|
508
|
+
max_tokens=4096,
|
|
509
|
+
)
|