empathy-framework 3.2.3__py3-none-any.whl → 3.8.2__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.
- coach_wizards/__init__.py +11 -12
- coach_wizards/accessibility_wizard.py +12 -12
- coach_wizards/api_wizard.py +12 -12
- coach_wizards/base_wizard.py +26 -20
- coach_wizards/cicd_wizard.py +15 -13
- coach_wizards/code_reviewer_README.md +60 -0
- coach_wizards/code_reviewer_wizard.py +180 -0
- coach_wizards/compliance_wizard.py +12 -12
- coach_wizards/database_wizard.py +12 -12
- coach_wizards/debugging_wizard.py +12 -12
- coach_wizards/documentation_wizard.py +12 -12
- coach_wizards/generate_wizards.py +1 -2
- coach_wizards/localization_wizard.py +101 -19
- coach_wizards/migration_wizard.py +12 -12
- coach_wizards/monitoring_wizard.py +12 -12
- coach_wizards/observability_wizard.py +12 -12
- coach_wizards/performance_wizard.py +12 -12
- coach_wizards/prompt_engineering_wizard.py +22 -25
- coach_wizards/refactoring_wizard.py +12 -12
- coach_wizards/scaling_wizard.py +12 -12
- coach_wizards/security_wizard.py +12 -12
- coach_wizards/testing_wizard.py +12 -12
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/METADATA +513 -58
- empathy_framework-3.8.2.dist-info/RECORD +333 -0
- empathy_framework-3.8.2.dist-info/entry_points.txt +22 -0
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/top_level.txt +5 -1
- empathy_healthcare_plugin/__init__.py +1 -2
- empathy_healthcare_plugin/monitors/__init__.py +9 -0
- empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
- empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
- empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
- empathy_llm_toolkit/__init__.py +7 -7
- empathy_llm_toolkit/agent_factory/__init__.py +53 -0
- empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
- empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
- empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
- empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
- empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
- empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
- empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
- empathy_llm_toolkit/agent_factory/base.py +305 -0
- empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
- empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
- empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
- empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
- empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
- empathy_llm_toolkit/agent_factory/decorators.py +286 -0
- empathy_llm_toolkit/agent_factory/factory.py +558 -0
- empathy_llm_toolkit/agent_factory/framework.py +192 -0
- empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
- empathy_llm_toolkit/agent_factory/resilient.py +320 -0
- empathy_llm_toolkit/claude_memory.py +14 -15
- empathy_llm_toolkit/cli/__init__.py +8 -0
- empathy_llm_toolkit/cli/sync_claude.py +487 -0
- empathy_llm_toolkit/code_health.py +177 -22
- empathy_llm_toolkit/config/__init__.py +29 -0
- empathy_llm_toolkit/config/unified.py +295 -0
- empathy_llm_toolkit/contextual_patterns.py +11 -12
- empathy_llm_toolkit/core.py +51 -49
- empathy_llm_toolkit/git_pattern_extractor.py +16 -12
- empathy_llm_toolkit/levels.py +6 -13
- empathy_llm_toolkit/pattern_confidence.py +14 -18
- empathy_llm_toolkit/pattern_resolver.py +10 -12
- empathy_llm_toolkit/pattern_summary.py +13 -11
- empathy_llm_toolkit/providers.py +194 -28
- empathy_llm_toolkit/routing/__init__.py +32 -0
- empathy_llm_toolkit/routing/model_router.py +362 -0
- empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
- empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
- empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
- empathy_llm_toolkit/security/README.md +262 -0
- empathy_llm_toolkit/security/__init__.py +62 -0
- empathy_llm_toolkit/security/audit_logger.py +929 -0
- empathy_llm_toolkit/security/audit_logger_example.py +152 -0
- empathy_llm_toolkit/security/pii_scrubber.py +640 -0
- empathy_llm_toolkit/security/secrets_detector.py +678 -0
- empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
- empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
- empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
- empathy_llm_toolkit/session_status.py +18 -20
- empathy_llm_toolkit/state.py +20 -21
- empathy_llm_toolkit/wizards/__init__.py +38 -0
- empathy_llm_toolkit/wizards/base_wizard.py +364 -0
- empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
- empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
- empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
- empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
- empathy_os/__init__.py +76 -77
- empathy_os/adaptive/__init__.py +13 -0
- empathy_os/adaptive/task_complexity.py +127 -0
- empathy_os/{monitoring.py → agent_monitoring.py} +27 -27
- empathy_os/cache/__init__.py +117 -0
- empathy_os/cache/base.py +166 -0
- empathy_os/cache/dependency_manager.py +253 -0
- empathy_os/cache/hash_only.py +248 -0
- empathy_os/cache/hybrid.py +390 -0
- empathy_os/cache/storage.py +282 -0
- empathy_os/cli.py +515 -109
- empathy_os/cli_unified.py +189 -42
- empathy_os/config/__init__.py +63 -0
- empathy_os/config/xml_config.py +239 -0
- empathy_os/config.py +87 -36
- empathy_os/coordination.py +48 -54
- empathy_os/core.py +90 -99
- empathy_os/cost_tracker.py +20 -23
- empathy_os/dashboard/__init__.py +15 -0
- empathy_os/dashboard/server.py +743 -0
- empathy_os/discovery.py +9 -11
- empathy_os/emergence.py +20 -21
- empathy_os/exceptions.py +18 -30
- empathy_os/feedback_loops.py +27 -30
- empathy_os/levels.py +31 -34
- empathy_os/leverage_points.py +27 -28
- empathy_os/logging_config.py +11 -12
- empathy_os/memory/__init__.py +195 -0
- empathy_os/memory/claude_memory.py +466 -0
- empathy_os/memory/config.py +224 -0
- empathy_os/memory/control_panel.py +1298 -0
- empathy_os/memory/edges.py +179 -0
- empathy_os/memory/graph.py +567 -0
- empathy_os/memory/long_term.py +1194 -0
- empathy_os/memory/nodes.py +179 -0
- empathy_os/memory/redis_bootstrap.py +540 -0
- empathy_os/memory/security/__init__.py +31 -0
- empathy_os/memory/security/audit_logger.py +930 -0
- empathy_os/memory/security/pii_scrubber.py +640 -0
- empathy_os/memory/security/secrets_detector.py +678 -0
- empathy_os/memory/short_term.py +2119 -0
- empathy_os/memory/storage/__init__.py +15 -0
- empathy_os/memory/summary_index.py +583 -0
- empathy_os/memory/unified.py +619 -0
- empathy_os/metrics/__init__.py +12 -0
- empathy_os/metrics/prompt_metrics.py +190 -0
- empathy_os/models/__init__.py +136 -0
- empathy_os/models/__main__.py +13 -0
- empathy_os/models/cli.py +655 -0
- empathy_os/models/empathy_executor.py +354 -0
- empathy_os/models/executor.py +252 -0
- empathy_os/models/fallback.py +671 -0
- empathy_os/models/provider_config.py +563 -0
- empathy_os/models/registry.py +382 -0
- empathy_os/models/tasks.py +302 -0
- empathy_os/models/telemetry.py +548 -0
- empathy_os/models/token_estimator.py +378 -0
- empathy_os/models/validation.py +274 -0
- empathy_os/monitoring/__init__.py +52 -0
- empathy_os/monitoring/alerts.py +23 -0
- empathy_os/monitoring/alerts_cli.py +268 -0
- empathy_os/monitoring/multi_backend.py +271 -0
- empathy_os/monitoring/otel_backend.py +363 -0
- empathy_os/optimization/__init__.py +19 -0
- empathy_os/optimization/context_optimizer.py +272 -0
- empathy_os/pattern_library.py +29 -28
- empathy_os/persistence.py +30 -34
- empathy_os/platform_utils.py +261 -0
- empathy_os/plugins/__init__.py +28 -0
- empathy_os/plugins/base.py +361 -0
- empathy_os/plugins/registry.py +268 -0
- empathy_os/project_index/__init__.py +30 -0
- empathy_os/project_index/cli.py +335 -0
- empathy_os/project_index/crew_integration.py +430 -0
- empathy_os/project_index/index.py +425 -0
- empathy_os/project_index/models.py +501 -0
- empathy_os/project_index/reports.py +473 -0
- empathy_os/project_index/scanner.py +538 -0
- empathy_os/prompts/__init__.py +61 -0
- empathy_os/prompts/config.py +77 -0
- empathy_os/prompts/context.py +177 -0
- empathy_os/prompts/parser.py +285 -0
- empathy_os/prompts/registry.py +313 -0
- empathy_os/prompts/templates.py +208 -0
- empathy_os/redis_config.py +144 -58
- empathy_os/redis_memory.py +53 -56
- empathy_os/resilience/__init__.py +56 -0
- empathy_os/resilience/circuit_breaker.py +256 -0
- empathy_os/resilience/fallback.py +179 -0
- empathy_os/resilience/health.py +300 -0
- empathy_os/resilience/retry.py +209 -0
- empathy_os/resilience/timeout.py +135 -0
- empathy_os/routing/__init__.py +43 -0
- empathy_os/routing/chain_executor.py +433 -0
- empathy_os/routing/classifier.py +217 -0
- empathy_os/routing/smart_router.py +234 -0
- empathy_os/routing/wizard_registry.py +307 -0
- empathy_os/templates.py +12 -11
- empathy_os/trust/__init__.py +28 -0
- empathy_os/trust/circuit_breaker.py +579 -0
- empathy_os/trust_building.py +44 -36
- empathy_os/validation/__init__.py +19 -0
- empathy_os/validation/xml_validator.py +281 -0
- empathy_os/wizard_factory_cli.py +170 -0
- empathy_os/{workflows.py → workflow_commands.py} +123 -31
- empathy_os/workflows/__init__.py +360 -0
- empathy_os/workflows/base.py +1660 -0
- empathy_os/workflows/bug_predict.py +962 -0
- empathy_os/workflows/code_review.py +960 -0
- empathy_os/workflows/code_review_adapters.py +310 -0
- empathy_os/workflows/code_review_pipeline.py +720 -0
- empathy_os/workflows/config.py +600 -0
- empathy_os/workflows/dependency_check.py +648 -0
- empathy_os/workflows/document_gen.py +1069 -0
- empathy_os/workflows/documentation_orchestrator.py +1205 -0
- empathy_os/workflows/health_check.py +679 -0
- empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
- empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
- empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
- empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
- empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
- empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
- empathy_os/workflows/manage_documentation.py +804 -0
- empathy_os/workflows/new_sample_workflow1.py +146 -0
- empathy_os/workflows/new_sample_workflow1_README.md +150 -0
- empathy_os/workflows/perf_audit.py +687 -0
- empathy_os/workflows/pr_review.py +748 -0
- empathy_os/workflows/progress.py +445 -0
- empathy_os/workflows/progress_server.py +322 -0
- empathy_os/workflows/refactor_plan.py +693 -0
- empathy_os/workflows/release_prep.py +808 -0
- empathy_os/workflows/research_synthesis.py +404 -0
- empathy_os/workflows/secure_release.py +585 -0
- empathy_os/workflows/security_adapters.py +297 -0
- empathy_os/workflows/security_audit.py +1046 -0
- empathy_os/workflows/step_config.py +234 -0
- empathy_os/workflows/test5.py +125 -0
- empathy_os/workflows/test5_README.md +158 -0
- empathy_os/workflows/test_gen.py +1855 -0
- empathy_os/workflows/test_lifecycle.py +526 -0
- empathy_os/workflows/test_maintenance.py +626 -0
- empathy_os/workflows/test_maintenance_cli.py +590 -0
- empathy_os/workflows/test_maintenance_crew.py +821 -0
- empathy_os/workflows/xml_enhanced_crew.py +285 -0
- empathy_software_plugin/__init__.py +1 -2
- empathy_software_plugin/cli/__init__.py +120 -0
- empathy_software_plugin/cli/inspect.py +362 -0
- empathy_software_plugin/cli.py +35 -26
- empathy_software_plugin/plugin.py +4 -8
- empathy_software_plugin/wizards/__init__.py +42 -0
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
- empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
- empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
- empathy_software_plugin/wizards/base_wizard.py +288 -0
- empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
- empathy_software_plugin/wizards/code_review_wizard.py +606 -0
- empathy_software_plugin/wizards/debugging/__init__.py +50 -0
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
- empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
- empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
- empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
- empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
- empathy_software_plugin/wizards/debugging/verification.py +369 -0
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
- empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
- empathy_software_plugin/wizards/performance/__init__.py +9 -0
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
- empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
- empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
- empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
- empathy_software_plugin/wizards/security/__init__.py +32 -0
- empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
- empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
- empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
- empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
- empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
- empathy_software_plugin/wizards/testing/__init__.py +27 -0
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
- empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
- empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
- empathy_software_plugin/wizards/testing_wizard.py +274 -0
- hot_reload/README.md +473 -0
- hot_reload/__init__.py +62 -0
- hot_reload/config.py +84 -0
- hot_reload/integration.py +228 -0
- hot_reload/reloader.py +298 -0
- hot_reload/watcher.py +179 -0
- hot_reload/websocket.py +176 -0
- scaffolding/README.md +589 -0
- scaffolding/__init__.py +35 -0
- scaffolding/__main__.py +14 -0
- scaffolding/cli.py +240 -0
- test_generator/__init__.py +38 -0
- test_generator/__main__.py +14 -0
- test_generator/cli.py +226 -0
- test_generator/generator.py +325 -0
- test_generator/risk_analyzer.py +216 -0
- workflow_patterns/__init__.py +33 -0
- workflow_patterns/behavior.py +249 -0
- workflow_patterns/core.py +76 -0
- workflow_patterns/output.py +99 -0
- workflow_patterns/registry.py +255 -0
- workflow_patterns/structural.py +288 -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
- agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
- agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
- agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
- agents/compliance_anticipation_agent.py +0 -1427
- agents/epic_integration_wizard.py +0 -541
- agents/trust_building_behaviors.py +0 -891
- empathy_framework-3.2.3.dist-info/RECORD +0 -104
- empathy_framework-3.2.3.dist-info/entry_points.txt +0 -7
- empathy_llm_toolkit/htmlcov/status.json +0 -1
- empathy_llm_toolkit/security/htmlcov/status.json +0 -1
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
"""Provider Configuration System
|
|
2
|
+
|
|
3
|
+
Handles user provider selection during install/update and runtime configuration.
|
|
4
|
+
Supports single-provider mode (default) and hybrid mode (multi-provider).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from .registry import MODEL_REGISTRY, ModelInfo, ModelTier
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProviderMode(str, Enum):
|
|
20
|
+
"""How the system selects models across providers."""
|
|
21
|
+
|
|
22
|
+
SINGLE = "single" # Use one provider for all tiers
|
|
23
|
+
HYBRID = "hybrid" # Best-of across providers (requires multiple API keys)
|
|
24
|
+
CUSTOM = "custom" # User-defined per-tier mapping
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ProviderConfig:
|
|
29
|
+
"""User's provider configuration."""
|
|
30
|
+
|
|
31
|
+
# Primary mode
|
|
32
|
+
mode: ProviderMode = ProviderMode.SINGLE
|
|
33
|
+
|
|
34
|
+
# For SINGLE mode: which provider to use
|
|
35
|
+
primary_provider: str = "anthropic"
|
|
36
|
+
|
|
37
|
+
# For CUSTOM mode: per-tier provider overrides
|
|
38
|
+
tier_providers: dict[str, str] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
# API key availability (detected at runtime)
|
|
41
|
+
available_providers: list[str] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
# User preferences
|
|
44
|
+
prefer_local: bool = False # Prefer Ollama when available
|
|
45
|
+
cost_optimization: bool = True # Use cheaper tiers when appropriate
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def detect_available_providers(cls) -> list[str]:
|
|
49
|
+
"""Detect which providers have API keys configured."""
|
|
50
|
+
available = []
|
|
51
|
+
|
|
52
|
+
# Load .env files if they exist (project root and home)
|
|
53
|
+
env_keys = cls._load_env_files()
|
|
54
|
+
|
|
55
|
+
# Check environment variables for API keys
|
|
56
|
+
provider_env_vars = {
|
|
57
|
+
"anthropic": ["ANTHROPIC_API_KEY"],
|
|
58
|
+
"openai": ["OPENAI_API_KEY"],
|
|
59
|
+
"google": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
60
|
+
"ollama": [], # Ollama is local, check if running
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for provider, env_vars in provider_env_vars.items():
|
|
64
|
+
if provider == "ollama":
|
|
65
|
+
# Check if Ollama is available (local)
|
|
66
|
+
if cls._check_ollama_available():
|
|
67
|
+
available.append(provider)
|
|
68
|
+
elif any(os.getenv(var) or env_keys.get(var) for var in env_vars):
|
|
69
|
+
available.append(provider)
|
|
70
|
+
|
|
71
|
+
return available
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _load_env_files() -> dict[str, str]:
|
|
75
|
+
"""Load API keys from .env files without modifying os.environ."""
|
|
76
|
+
env_keys: dict[str, str] = {}
|
|
77
|
+
|
|
78
|
+
# Possible .env file locations
|
|
79
|
+
env_paths = [
|
|
80
|
+
Path.cwd() / ".env",
|
|
81
|
+
Path.home() / ".env",
|
|
82
|
+
Path.home() / ".empathy" / ".env",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
for env_path in env_paths:
|
|
86
|
+
if env_path.exists():
|
|
87
|
+
try:
|
|
88
|
+
with open(env_path) as f:
|
|
89
|
+
for line in f:
|
|
90
|
+
line = line.strip()
|
|
91
|
+
if line and not line.startswith("#") and "=" in line:
|
|
92
|
+
key, _, value = line.partition("=")
|
|
93
|
+
key = key.strip()
|
|
94
|
+
value = value.strip().strip("'\"")
|
|
95
|
+
if key and value:
|
|
96
|
+
env_keys[key] = value
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
return env_keys
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _check_ollama_available() -> bool:
|
|
104
|
+
"""Check if Ollama is running locally."""
|
|
105
|
+
try:
|
|
106
|
+
import socket
|
|
107
|
+
|
|
108
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
109
|
+
sock.settimeout(1)
|
|
110
|
+
result = sock.connect_ex(("localhost", 11434))
|
|
111
|
+
sock.close()
|
|
112
|
+
return result == 0
|
|
113
|
+
except Exception:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def auto_detect(cls) -> ProviderConfig:
|
|
118
|
+
"""Auto-detect the best configuration based on available API keys.
|
|
119
|
+
|
|
120
|
+
Logic:
|
|
121
|
+
- If only one provider available → SINGLE mode with that provider
|
|
122
|
+
- If multiple providers available → SINGLE mode with first cloud provider
|
|
123
|
+
- If no providers available → SINGLE mode with anthropic (will prompt for key)
|
|
124
|
+
"""
|
|
125
|
+
available = cls.detect_available_providers()
|
|
126
|
+
|
|
127
|
+
if len(available) == 0:
|
|
128
|
+
# No providers detected, default to anthropic
|
|
129
|
+
return cls(
|
|
130
|
+
mode=ProviderMode.SINGLE,
|
|
131
|
+
primary_provider="anthropic",
|
|
132
|
+
available_providers=[],
|
|
133
|
+
)
|
|
134
|
+
if len(available) == 1:
|
|
135
|
+
# Single provider available, use it
|
|
136
|
+
return cls(
|
|
137
|
+
mode=ProviderMode.SINGLE,
|
|
138
|
+
primary_provider=available[0],
|
|
139
|
+
available_providers=available,
|
|
140
|
+
)
|
|
141
|
+
# Multiple providers available
|
|
142
|
+
# Default to first cloud provider (prefer anthropic > openai > google > ollama)
|
|
143
|
+
priority = ["anthropic", "openai", "google", "ollama"]
|
|
144
|
+
primary = next((p for p in priority if p in available), available[0])
|
|
145
|
+
return cls(
|
|
146
|
+
mode=ProviderMode.SINGLE,
|
|
147
|
+
primary_provider=primary,
|
|
148
|
+
available_providers=available,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def get_model_for_tier(self, tier: str | ModelTier) -> ModelInfo | None:
|
|
152
|
+
"""Get the model to use for a given tier based on current config."""
|
|
153
|
+
tier_str = tier.value if isinstance(tier, ModelTier) else tier
|
|
154
|
+
|
|
155
|
+
if self.mode == ProviderMode.HYBRID:
|
|
156
|
+
# Use hybrid provider from registry
|
|
157
|
+
return MODEL_REGISTRY.get("hybrid", {}).get(tier_str)
|
|
158
|
+
if self.mode == ProviderMode.CUSTOM:
|
|
159
|
+
# Use per-tier provider mapping
|
|
160
|
+
provider = self.tier_providers.get(tier_str, self.primary_provider)
|
|
161
|
+
return MODEL_REGISTRY.get(provider, {}).get(tier_str)
|
|
162
|
+
# SINGLE mode: use primary provider for all tiers
|
|
163
|
+
return MODEL_REGISTRY.get(self.primary_provider, {}).get(tier_str)
|
|
164
|
+
|
|
165
|
+
def get_effective_registry(self) -> dict[str, ModelInfo]:
|
|
166
|
+
"""Get the effective model registry based on current config."""
|
|
167
|
+
result = {}
|
|
168
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
169
|
+
model = self.get_model_for_tier(tier)
|
|
170
|
+
if model:
|
|
171
|
+
result[tier] = model
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
def to_dict(self) -> dict[str, Any]:
|
|
175
|
+
"""Serialize to dictionary."""
|
|
176
|
+
return {
|
|
177
|
+
"mode": self.mode.value,
|
|
178
|
+
"primary_provider": self.primary_provider,
|
|
179
|
+
"tier_providers": self.tier_providers,
|
|
180
|
+
"prefer_local": self.prefer_local,
|
|
181
|
+
"cost_optimization": self.cost_optimization,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def from_dict(cls, data: dict[str, Any]) -> ProviderConfig:
|
|
186
|
+
"""Deserialize from dictionary."""
|
|
187
|
+
return cls(
|
|
188
|
+
mode=ProviderMode(data.get("mode", "single")),
|
|
189
|
+
primary_provider=data.get("primary_provider", "anthropic"),
|
|
190
|
+
tier_providers=data.get("tier_providers", {}),
|
|
191
|
+
prefer_local=data.get("prefer_local", False),
|
|
192
|
+
cost_optimization=data.get("cost_optimization", True),
|
|
193
|
+
available_providers=cls.detect_available_providers(),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def save(self, path: Path | None = None) -> None:
|
|
197
|
+
"""Save configuration to file."""
|
|
198
|
+
if path is None:
|
|
199
|
+
path = Path.home() / ".empathy" / "provider_config.json"
|
|
200
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
with open(path, "w") as f:
|
|
202
|
+
json.dump(self.to_dict(), f, indent=2)
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def load(cls, path: Path | None = None) -> ProviderConfig:
|
|
206
|
+
"""Load configuration from file, or auto-detect if not found."""
|
|
207
|
+
if path is None:
|
|
208
|
+
path = Path.home() / ".empathy" / "provider_config.json"
|
|
209
|
+
|
|
210
|
+
if path.exists():
|
|
211
|
+
try:
|
|
212
|
+
with open(path) as f:
|
|
213
|
+
data = json.load(f)
|
|
214
|
+
return cls.from_dict(data)
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
# Auto-detect if no config exists
|
|
219
|
+
return cls.auto_detect()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# Interactive configuration for install/update
|
|
223
|
+
def configure_provider_interactive() -> ProviderConfig:
|
|
224
|
+
"""Interactive provider configuration for install/update.
|
|
225
|
+
|
|
226
|
+
Returns configured ProviderConfig after user selection.
|
|
227
|
+
"""
|
|
228
|
+
print("\n" + "=" * 60)
|
|
229
|
+
print("Empathy Framework - Provider Configuration")
|
|
230
|
+
print("=" * 60)
|
|
231
|
+
|
|
232
|
+
# Detect available providers
|
|
233
|
+
config = ProviderConfig.auto_detect()
|
|
234
|
+
available = config.available_providers
|
|
235
|
+
|
|
236
|
+
print(f"\nDetected API keys for: {', '.join(available) if available else 'None'}")
|
|
237
|
+
|
|
238
|
+
if not available:
|
|
239
|
+
print("\n⚠️ No API keys detected. Please set one of:")
|
|
240
|
+
print(" - ANTHROPIC_API_KEY (recommended)")
|
|
241
|
+
print(" - OPENAI_API_KEY")
|
|
242
|
+
print(" - GOOGLE_API_KEY or GEMINI_API_KEY (2M context window)")
|
|
243
|
+
print(" - Or run Ollama locally")
|
|
244
|
+
print("\nDefaulting to Anthropic. You'll need to set ANTHROPIC_API_KEY.")
|
|
245
|
+
return ProviderConfig(
|
|
246
|
+
mode=ProviderMode.SINGLE,
|
|
247
|
+
primary_provider="anthropic",
|
|
248
|
+
available_providers=[],
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Show options
|
|
252
|
+
print("\nSelect your provider configuration:")
|
|
253
|
+
print("-" * 40)
|
|
254
|
+
|
|
255
|
+
options = []
|
|
256
|
+
|
|
257
|
+
# Option 1: Single provider (for each available)
|
|
258
|
+
for i, provider in enumerate(available, 1):
|
|
259
|
+
provider_name = provider.capitalize()
|
|
260
|
+
if provider == "anthropic":
|
|
261
|
+
desc = "Claude models (Haiku/Sonnet/Opus)"
|
|
262
|
+
elif provider == "openai":
|
|
263
|
+
desc = "GPT models (GPT-4o-mini/GPT-4o/o1)"
|
|
264
|
+
elif provider == "google":
|
|
265
|
+
desc = "Gemini models (Flash/Pro - 2M context window)"
|
|
266
|
+
elif provider == "ollama":
|
|
267
|
+
desc = "Local models (Llama 3.2)"
|
|
268
|
+
else:
|
|
269
|
+
desc = "Unknown provider"
|
|
270
|
+
options.append((provider, ProviderMode.SINGLE))
|
|
271
|
+
print(f" [{i}] {provider_name} only - {desc}")
|
|
272
|
+
|
|
273
|
+
# Option: Hybrid (if multiple providers available)
|
|
274
|
+
if len(available) > 1:
|
|
275
|
+
options.append(("hybrid", ProviderMode.HYBRID))
|
|
276
|
+
print(f" [{len(options)}] Hybrid - Best model from each provider per tier")
|
|
277
|
+
print(" (Recommended if you have multiple API keys)")
|
|
278
|
+
|
|
279
|
+
# Default selection
|
|
280
|
+
default_idx = 0
|
|
281
|
+
if len(available) == 1:
|
|
282
|
+
default_idx = 0
|
|
283
|
+
elif "anthropic" in available:
|
|
284
|
+
default_idx = available.index("anthropic")
|
|
285
|
+
|
|
286
|
+
print(f"\nDefault: [{default_idx + 1}]")
|
|
287
|
+
|
|
288
|
+
# Get user input
|
|
289
|
+
try:
|
|
290
|
+
choice = input(f"\nYour choice [1-{len(options)}]: ").strip()
|
|
291
|
+
if not choice:
|
|
292
|
+
choice = str(default_idx + 1)
|
|
293
|
+
idx = int(choice) - 1
|
|
294
|
+
if idx < 0 or idx >= len(options):
|
|
295
|
+
idx = default_idx
|
|
296
|
+
except (ValueError, EOFError):
|
|
297
|
+
idx = default_idx
|
|
298
|
+
|
|
299
|
+
selected_provider, selected_mode = options[idx]
|
|
300
|
+
|
|
301
|
+
if selected_mode == ProviderMode.HYBRID:
|
|
302
|
+
config = ProviderConfig(
|
|
303
|
+
mode=ProviderMode.HYBRID,
|
|
304
|
+
primary_provider="hybrid",
|
|
305
|
+
available_providers=available,
|
|
306
|
+
)
|
|
307
|
+
print("\n✓ Configured: Hybrid mode (best-of across providers)")
|
|
308
|
+
else:
|
|
309
|
+
config = ProviderConfig(
|
|
310
|
+
mode=ProviderMode.SINGLE,
|
|
311
|
+
primary_provider=selected_provider,
|
|
312
|
+
available_providers=available,
|
|
313
|
+
)
|
|
314
|
+
print(f"\n✓ Configured: {selected_provider.capitalize()} as primary provider")
|
|
315
|
+
|
|
316
|
+
# Show effective models
|
|
317
|
+
print("\nEffective model mapping:")
|
|
318
|
+
effective = config.get_effective_registry()
|
|
319
|
+
for tier, model in effective.items():
|
|
320
|
+
if model:
|
|
321
|
+
print(f" {tier:8} → {model.id} ({model.provider})")
|
|
322
|
+
|
|
323
|
+
# Save configuration
|
|
324
|
+
config.save()
|
|
325
|
+
print("\nConfiguration saved to ~/.empathy/provider_config.json")
|
|
326
|
+
|
|
327
|
+
return config
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def configure_provider_cli(
|
|
331
|
+
provider: str | None = None,
|
|
332
|
+
mode: str | None = None,
|
|
333
|
+
) -> ProviderConfig:
|
|
334
|
+
"""CLI-based provider configuration (non-interactive).
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
provider: Provider name (anthropic, openai, google, ollama, hybrid)
|
|
338
|
+
mode: Mode (single, hybrid, custom)
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Configured ProviderConfig
|
|
342
|
+
|
|
343
|
+
"""
|
|
344
|
+
available = ProviderConfig.detect_available_providers()
|
|
345
|
+
|
|
346
|
+
if provider == "hybrid" or mode == "hybrid":
|
|
347
|
+
return ProviderConfig(
|
|
348
|
+
mode=ProviderMode.HYBRID,
|
|
349
|
+
primary_provider="hybrid",
|
|
350
|
+
available_providers=available,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if provider:
|
|
354
|
+
return ProviderConfig(
|
|
355
|
+
mode=ProviderMode.SINGLE,
|
|
356
|
+
primary_provider=provider,
|
|
357
|
+
available_providers=available,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Auto-detect
|
|
361
|
+
return ProviderConfig.auto_detect()
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# Global config instance (lazy-loaded)
|
|
365
|
+
_global_config: ProviderConfig | None = None
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_provider_config() -> ProviderConfig:
|
|
369
|
+
"""Get the global provider configuration."""
|
|
370
|
+
global _global_config
|
|
371
|
+
if _global_config is None:
|
|
372
|
+
_global_config = ProviderConfig.load()
|
|
373
|
+
return _global_config
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def set_provider_config(config: ProviderConfig) -> None:
|
|
377
|
+
"""Set the global provider configuration."""
|
|
378
|
+
global _global_config
|
|
379
|
+
_global_config = config
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def reset_provider_config() -> None:
|
|
383
|
+
"""Reset the global provider configuration (forces reload)."""
|
|
384
|
+
global _global_config
|
|
385
|
+
_global_config = None
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def configure_hybrid_interactive() -> ProviderConfig:
|
|
389
|
+
"""Interactive hybrid configuration - let users pick models for each tier.
|
|
390
|
+
|
|
391
|
+
Shows available models from all providers with detected API keys,
|
|
392
|
+
allowing users to mix and match the best models for their workflow.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
ProviderConfig with custom tier mappings
|
|
396
|
+
|
|
397
|
+
"""
|
|
398
|
+
print("\n" + "=" * 60)
|
|
399
|
+
print("🔀 Hybrid Model Configuration")
|
|
400
|
+
print("=" * 60)
|
|
401
|
+
print("\nSelect the best model for each tier from available providers.")
|
|
402
|
+
print("This creates a custom mix optimized for your workflow.\n")
|
|
403
|
+
|
|
404
|
+
# Detect available providers
|
|
405
|
+
available = ProviderConfig.detect_available_providers()
|
|
406
|
+
|
|
407
|
+
if not available:
|
|
408
|
+
print("⚠️ No API keys detected. Please set at least one of:")
|
|
409
|
+
print(" - ANTHROPIC_API_KEY")
|
|
410
|
+
print(" - OPENAI_API_KEY")
|
|
411
|
+
print(" - GOOGLE_API_KEY")
|
|
412
|
+
print(" - Or run Ollama locally")
|
|
413
|
+
return ProviderConfig.auto_detect()
|
|
414
|
+
|
|
415
|
+
print(f"✓ Available providers: {', '.join(available)}\n")
|
|
416
|
+
|
|
417
|
+
# Collect models for each tier from available providers
|
|
418
|
+
tier_selections: dict[str, str] = {}
|
|
419
|
+
|
|
420
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
421
|
+
tier_upper = tier.upper()
|
|
422
|
+
print("-" * 60)
|
|
423
|
+
print(f" {tier_upper} TIER - Select a model:")
|
|
424
|
+
print("-" * 60)
|
|
425
|
+
|
|
426
|
+
# Build options from available providers
|
|
427
|
+
options: list[tuple[str, ModelInfo]] = []
|
|
428
|
+
for provider in available:
|
|
429
|
+
model_info = MODEL_REGISTRY.get(provider, {}).get(tier)
|
|
430
|
+
if model_info:
|
|
431
|
+
options.append((provider, model_info))
|
|
432
|
+
|
|
433
|
+
if not options:
|
|
434
|
+
print(f" No models available for {tier} tier")
|
|
435
|
+
continue
|
|
436
|
+
|
|
437
|
+
# Display options with pricing info
|
|
438
|
+
for i, (provider, info) in enumerate(options, 1):
|
|
439
|
+
provider_label = provider.capitalize()
|
|
440
|
+
cost_info = f"${info.input_cost_per_million:.2f}/${info.output_cost_per_million:.2f} per M tokens"
|
|
441
|
+
if provider == "ollama":
|
|
442
|
+
cost_info = "FREE (local)"
|
|
443
|
+
|
|
444
|
+
# Add feature badges
|
|
445
|
+
features = []
|
|
446
|
+
if info.supports_vision:
|
|
447
|
+
features.append("👁 vision")
|
|
448
|
+
if info.supports_tools:
|
|
449
|
+
features.append("🔧 tools")
|
|
450
|
+
if provider == "google":
|
|
451
|
+
features.append("📚 2M context")
|
|
452
|
+
|
|
453
|
+
features_str = f" [{', '.join(features)}]" if features else ""
|
|
454
|
+
|
|
455
|
+
print(f" [{i}] {info.id}")
|
|
456
|
+
print(f" Provider: {provider_label} | {cost_info}{features_str}")
|
|
457
|
+
|
|
458
|
+
# Get user choice
|
|
459
|
+
default_idx = 0
|
|
460
|
+
# Set smart defaults based on tier
|
|
461
|
+
if tier == "cheap":
|
|
462
|
+
# Prefer cheapest: ollama > google > openai > anthropic
|
|
463
|
+
for pref in ["ollama", "google", "openai", "anthropic"]:
|
|
464
|
+
for i, (p, _) in enumerate(options):
|
|
465
|
+
if p == pref:
|
|
466
|
+
default_idx = i
|
|
467
|
+
break
|
|
468
|
+
else:
|
|
469
|
+
continue
|
|
470
|
+
break
|
|
471
|
+
elif tier == "capable":
|
|
472
|
+
# Prefer best reasoning: anthropic > openai > google > ollama
|
|
473
|
+
for pref in ["anthropic", "openai", "google", "ollama"]:
|
|
474
|
+
for i, (p, _) in enumerate(options):
|
|
475
|
+
if p == pref:
|
|
476
|
+
default_idx = i
|
|
477
|
+
break
|
|
478
|
+
else:
|
|
479
|
+
continue
|
|
480
|
+
break
|
|
481
|
+
elif tier == "premium":
|
|
482
|
+
# Prefer most capable: anthropic > openai > google > ollama
|
|
483
|
+
for pref in ["anthropic", "openai", "google", "ollama"]:
|
|
484
|
+
for i, (p, _) in enumerate(options):
|
|
485
|
+
if p == pref:
|
|
486
|
+
default_idx = i
|
|
487
|
+
break
|
|
488
|
+
else:
|
|
489
|
+
continue
|
|
490
|
+
break
|
|
491
|
+
|
|
492
|
+
print(f"\n Recommended: [{default_idx + 1}] {options[default_idx][1].id}")
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
choice = input(f" Your choice [1-{len(options)}]: ").strip()
|
|
496
|
+
if not choice:
|
|
497
|
+
idx = default_idx
|
|
498
|
+
else:
|
|
499
|
+
idx = int(choice) - 1
|
|
500
|
+
if idx < 0 or idx >= len(options):
|
|
501
|
+
idx = default_idx
|
|
502
|
+
except (ValueError, EOFError):
|
|
503
|
+
idx = default_idx
|
|
504
|
+
|
|
505
|
+
selected_provider, selected_model = options[idx]
|
|
506
|
+
tier_selections[tier] = selected_model.id
|
|
507
|
+
print(f" ✓ Selected: {selected_model.id} ({selected_provider})\n")
|
|
508
|
+
|
|
509
|
+
# Create custom config
|
|
510
|
+
config = ProviderConfig(
|
|
511
|
+
mode=ProviderMode.CUSTOM,
|
|
512
|
+
primary_provider="custom",
|
|
513
|
+
tier_providers={}, # Not used in CUSTOM mode
|
|
514
|
+
available_providers=available,
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
# Store the custom tier->model mapping
|
|
518
|
+
# We'll save this to workflows.yaml custom_models section
|
|
519
|
+
print("\n" + "=" * 60)
|
|
520
|
+
print("✅ Hybrid Configuration Complete!")
|
|
521
|
+
print("=" * 60)
|
|
522
|
+
print("\nYour custom model mapping:")
|
|
523
|
+
for tier, model_id in tier_selections.items():
|
|
524
|
+
print(f" {tier:8} → {model_id}")
|
|
525
|
+
|
|
526
|
+
# Save to workflows.yaml
|
|
527
|
+
_save_hybrid_to_workflows_yaml(tier_selections)
|
|
528
|
+
|
|
529
|
+
print("\n✓ Configuration saved to .empathy/workflows.yaml")
|
|
530
|
+
print(" Run workflows with: python -m empathy_os.cli workflow run <name>")
|
|
531
|
+
|
|
532
|
+
return config
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _save_hybrid_to_workflows_yaml(tier_selections: dict[str, str]) -> None:
|
|
536
|
+
"""Save hybrid tier selections to workflows.yaml."""
|
|
537
|
+
from pathlib import Path
|
|
538
|
+
|
|
539
|
+
import yaml
|
|
540
|
+
|
|
541
|
+
workflows_path = Path(".empathy/workflows.yaml")
|
|
542
|
+
|
|
543
|
+
# Load existing config or create new
|
|
544
|
+
if workflows_path.exists():
|
|
545
|
+
with open(workflows_path) as f:
|
|
546
|
+
config = yaml.safe_load(f) or {}
|
|
547
|
+
else:
|
|
548
|
+
config = {}
|
|
549
|
+
workflows_path.parent.mkdir(parents=True, exist_ok=True)
|
|
550
|
+
|
|
551
|
+
# Update config
|
|
552
|
+
config["default_provider"] = "hybrid"
|
|
553
|
+
|
|
554
|
+
# Ensure custom_models exists
|
|
555
|
+
if "custom_models" not in config or config["custom_models"] is None:
|
|
556
|
+
config["custom_models"] = {}
|
|
557
|
+
|
|
558
|
+
# Set hybrid model mapping
|
|
559
|
+
config["custom_models"]["hybrid"] = tier_selections
|
|
560
|
+
|
|
561
|
+
# Write back
|
|
562
|
+
with open(workflows_path, "w") as f:
|
|
563
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|