aiecs 1.5.1__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.
- aiecs/__init__.py +72 -0
- aiecs/__main__.py +41 -0
- aiecs/aiecs_client.py +469 -0
- aiecs/application/__init__.py +10 -0
- aiecs/application/executors/__init__.py +10 -0
- aiecs/application/executors/operation_executor.py +363 -0
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +100 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
- aiecs/application/knowledge_graph/search/reranker.py +295 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +484 -0
- aiecs/config/__init__.py +16 -0
- aiecs/config/config.py +498 -0
- aiecs/config/graph_config.py +137 -0
- aiecs/config/registry.py +23 -0
- aiecs/core/__init__.py +46 -0
- aiecs/core/interface/__init__.py +34 -0
- aiecs/core/interface/execution_interface.py +152 -0
- aiecs/core/interface/storage_interface.py +171 -0
- aiecs/domain/__init__.py +289 -0
- aiecs/domain/agent/__init__.py +189 -0
- aiecs/domain/agent/base_agent.py +697 -0
- aiecs/domain/agent/exceptions.py +103 -0
- aiecs/domain/agent/graph_aware_mixin.py +559 -0
- aiecs/domain/agent/hybrid_agent.py +490 -0
- aiecs/domain/agent/integration/__init__.py +26 -0
- aiecs/domain/agent/integration/context_compressor.py +222 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
- aiecs/domain/agent/integration/retry_policy.py +219 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +646 -0
- aiecs/domain/agent/lifecycle.py +296 -0
- aiecs/domain/agent/llm_agent.py +300 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +197 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +160 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
- aiecs/domain/agent/models.py +317 -0
- aiecs/domain/agent/observability.py +407 -0
- aiecs/domain/agent/persistence.py +289 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +161 -0
- aiecs/domain/agent/prompts/formatters.py +189 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +260 -0
- aiecs/domain/agent/tool_agent.py +257 -0
- aiecs/domain/agent/tools/__init__.py +12 -0
- aiecs/domain/agent/tools/schema_generator.py +221 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +477 -0
- aiecs/domain/community/analytics.py +481 -0
- aiecs/domain/community/collaborative_workflow.py +642 -0
- aiecs/domain/community/communication_hub.py +645 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +800 -0
- aiecs/domain/community/community_manager.py +813 -0
- aiecs/domain/community/decision_engine.py +879 -0
- aiecs/domain/community/exceptions.py +225 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +268 -0
- aiecs/domain/community/resource_manager.py +457 -0
- aiecs/domain/community/shared_context_manager.py +603 -0
- aiecs/domain/context/__init__.py +58 -0
- aiecs/domain/context/context_engine.py +989 -0
- aiecs/domain/context/conversation_models.py +354 -0
- aiecs/domain/context/graph_memory.py +467 -0
- aiecs/domain/execution/__init__.py +12 -0
- aiecs/domain/execution/model.py +57 -0
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +130 -0
- aiecs/domain/knowledge_graph/models/evidence.py +194 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
- aiecs/domain/knowledge_graph/models/path.py +179 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
- aiecs/domain/knowledge_graph/models/query.py +272 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
- aiecs/domain/knowledge_graph/models/relation.py +136 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
- aiecs/domain/task/__init__.py +13 -0
- aiecs/domain/task/dsl_processor.py +613 -0
- aiecs/domain/task/model.py +62 -0
- aiecs/domain/task/task_context.py +268 -0
- aiecs/infrastructure/__init__.py +24 -0
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +601 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
- aiecs/infrastructure/graph_storage/cache.py +429 -0
- aiecs/infrastructure/graph_storage/distributed.py +226 -0
- aiecs/infrastructure/graph_storage/error_handling.py +390 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +514 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
- aiecs/infrastructure/graph_storage/metrics.py +357 -0
- aiecs/infrastructure/graph_storage/migration.py +413 -0
- aiecs/infrastructure/graph_storage/pagination.py +471 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
- aiecs/infrastructure/graph_storage/postgres.py +871 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +623 -0
- aiecs/infrastructure/graph_storage/streaming.py +495 -0
- aiecs/infrastructure/messaging/__init__.py +13 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
- aiecs/infrastructure/messaging/websocket_manager.py +298 -0
- aiecs/infrastructure/monitoring/__init__.py +34 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
- aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
- aiecs/infrastructure/monitoring/structured_logger.py +48 -0
- aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
- aiecs/infrastructure/persistence/__init__.py +24 -0
- aiecs/infrastructure/persistence/context_engine_client.py +187 -0
- aiecs/infrastructure/persistence/database_manager.py +333 -0
- aiecs/infrastructure/persistence/file_storage.py +754 -0
- aiecs/infrastructure/persistence/redis_client.py +220 -0
- aiecs/llm/__init__.py +86 -0
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/callbacks/custom_callbacks.py +264 -0
- aiecs/llm/client_factory.py +420 -0
- aiecs/llm/clients/__init__.py +33 -0
- aiecs/llm/clients/base_client.py +193 -0
- aiecs/llm/clients/googleai_client.py +181 -0
- aiecs/llm/clients/openai_client.py +131 -0
- aiecs/llm/clients/vertex_client.py +437 -0
- aiecs/llm/clients/xai_client.py +184 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +275 -0
- aiecs/llm/config/config_validator.py +236 -0
- aiecs/llm/config/model_config.py +151 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +91 -0
- aiecs/main.py +363 -0
- aiecs/scripts/__init__.py +3 -0
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/version_manager.py +215 -0
- aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
- aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
- aiecs/scripts/dependance_check/__init__.py +17 -0
- aiecs/scripts/dependance_check/dependency_checker.py +938 -0
- aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
- aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
- aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
- aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
- aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
- aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
- aiecs/scripts/tools_develop/README.md +449 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
- aiecs/scripts/tools_develop/verify_tools.py +356 -0
- aiecs/tasks/__init__.py +1 -0
- aiecs/tasks/worker.py +172 -0
- aiecs/tools/__init__.py +299 -0
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +303 -0
- aiecs/tools/apisource/providers/__init__.py +115 -0
- aiecs/tools/apisource/providers/base.py +664 -0
- aiecs/tools/apisource/providers/census.py +401 -0
- aiecs/tools/apisource/providers/fred.py +564 -0
- aiecs/tools/apisource/providers/newsapi.py +412 -0
- aiecs/tools/apisource/providers/worldbank.py +357 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +375 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
- aiecs/tools/apisource/tool.py +850 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +338 -0
- aiecs/tools/base_tool.py +201 -0
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +599 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
- aiecs/tools/docs/content_insertion_tool.py +1333 -0
- aiecs/tools/docs/document_creator_tool.py +1317 -0
- aiecs/tools/docs/document_layout_tool.py +1166 -0
- aiecs/tools/docs/document_parser_tool.py +994 -0
- aiecs/tools/docs/document_writer_tool.py +1818 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
- aiecs/tools/langchain_adapter.py +542 -0
- aiecs/tools/schema_generator.py +275 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +589 -0
- aiecs/tools/search_tool/cache.py +260 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +216 -0
- aiecs/tools/search_tool/core.py +749 -0
- aiecs/tools/search_tool/deduplicator.py +123 -0
- aiecs/tools/search_tool/error_handler.py +271 -0
- aiecs/tools/search_tool/metrics.py +371 -0
- aiecs/tools/search_tool/rate_limiter.py +178 -0
- aiecs/tools/search_tool/schemas.py +277 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
- aiecs/tools/statistics/data_loader_tool.py +564 -0
- aiecs/tools/statistics/data_profiler_tool.py +658 -0
- aiecs/tools/statistics/data_transformer_tool.py +573 -0
- aiecs/tools/statistics/data_visualizer_tool.py +495 -0
- aiecs/tools/statistics/model_trainer_tool.py +487 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
- aiecs/tools/task_tools/__init__.py +86 -0
- aiecs/tools/task_tools/chart_tool.py +732 -0
- aiecs/tools/task_tools/classfire_tool.py +922 -0
- aiecs/tools/task_tools/image_tool.py +447 -0
- aiecs/tools/task_tools/office_tool.py +684 -0
- aiecs/tools/task_tools/pandas_tool.py +635 -0
- aiecs/tools/task_tools/report_tool.py +635 -0
- aiecs/tools/task_tools/research_tool.py +392 -0
- aiecs/tools/task_tools/scraper_tool.py +715 -0
- aiecs/tools/task_tools/stats_tool.py +688 -0
- aiecs/tools/temp_file_manager.py +130 -0
- aiecs/tools/tool_executor/__init__.py +37 -0
- aiecs/tools/tool_executor/tool_executor.py +881 -0
- aiecs/utils/LLM_output_structor.py +445 -0
- aiecs/utils/__init__.py +34 -0
- aiecs/utils/base_callback.py +47 -0
- aiecs/utils/cache_provider.py +695 -0
- aiecs/utils/execution_utils.py +184 -0
- aiecs/utils/logging.py +1 -0
- aiecs/utils/prompt_loader.py +14 -0
- aiecs/utils/token_usage_repository.py +323 -0
- aiecs/ws/__init__.py +0 -0
- aiecs/ws/socket_server.py +52 -0
- aiecs-1.5.1.dist-info/METADATA +608 -0
- aiecs-1.5.1.dist-info/RECORD +302 -0
- aiecs-1.5.1.dist-info/WHEEL +5 -0
- aiecs-1.5.1.dist-info/entry_points.txt +10 -0
- aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
- aiecs-1.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration loader for LLM models.
|
|
3
|
+
|
|
4
|
+
This module provides a singleton configuration loader that loads and manages
|
|
5
|
+
LLM model configurations from YAML files with support for hot-reloading.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
import yaml
|
|
13
|
+
from threading import Lock
|
|
14
|
+
|
|
15
|
+
from aiecs.llm.config.model_config import (
|
|
16
|
+
LLMModelsConfig,
|
|
17
|
+
ProviderConfig,
|
|
18
|
+
ModelConfig,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LLMConfigLoader:
|
|
25
|
+
"""
|
|
26
|
+
Singleton configuration loader for LLM models.
|
|
27
|
+
|
|
28
|
+
Supports:
|
|
29
|
+
- Loading configuration from YAML files
|
|
30
|
+
- Hot-reloading (manual refresh)
|
|
31
|
+
- Thread-safe access
|
|
32
|
+
- Caching for performance
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_instance: Optional["LLMConfigLoader"] = None
|
|
36
|
+
_lock = Lock()
|
|
37
|
+
_config_lock = Lock()
|
|
38
|
+
|
|
39
|
+
def __new__(cls):
|
|
40
|
+
"""Ensure singleton instance"""
|
|
41
|
+
if cls._instance is None:
|
|
42
|
+
with cls._lock:
|
|
43
|
+
if cls._instance is None:
|
|
44
|
+
cls._instance = super().__new__(cls)
|
|
45
|
+
cls._instance._initialized = False
|
|
46
|
+
return cls._instance
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
"""Initialize the configuration loader"""
|
|
50
|
+
if self._initialized:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
self._config: Optional[LLMModelsConfig] = None
|
|
54
|
+
self._config_path: Optional[Path] = None
|
|
55
|
+
self._initialized = True
|
|
56
|
+
logger.info("LLMConfigLoader initialized")
|
|
57
|
+
|
|
58
|
+
def _find_config_file(self) -> Path:
|
|
59
|
+
"""
|
|
60
|
+
Find the configuration file.
|
|
61
|
+
|
|
62
|
+
Search order:
|
|
63
|
+
1. Settings llm_models_config_path
|
|
64
|
+
2. Environment variable LLM_MODELS_CONFIG
|
|
65
|
+
3. aiecs/config/llm_models.yaml
|
|
66
|
+
4. config/llm_models.yaml
|
|
67
|
+
"""
|
|
68
|
+
# Check settings first
|
|
69
|
+
try:
|
|
70
|
+
from aiecs.config.config import get_settings
|
|
71
|
+
|
|
72
|
+
settings = get_settings()
|
|
73
|
+
if settings.llm_models_config_path:
|
|
74
|
+
path = Path(settings.llm_models_config_path)
|
|
75
|
+
if path.exists():
|
|
76
|
+
logger.info(f"Using LLM config from settings: {path}")
|
|
77
|
+
return path
|
|
78
|
+
else:
|
|
79
|
+
logger.warning(f"Settings llm_models_config_path does not exist: {path}")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.debug(f"Could not load settings: {e}")
|
|
82
|
+
|
|
83
|
+
# Check environment variable
|
|
84
|
+
env_path = os.environ.get("LLM_MODELS_CONFIG")
|
|
85
|
+
if env_path:
|
|
86
|
+
path = Path(env_path)
|
|
87
|
+
if path.exists():
|
|
88
|
+
logger.info(f"Using LLM config from environment: {path}")
|
|
89
|
+
return path
|
|
90
|
+
else:
|
|
91
|
+
logger.warning(f"LLM_MODELS_CONFIG path does not exist: {path}")
|
|
92
|
+
|
|
93
|
+
# Check standard locations
|
|
94
|
+
current_dir = Path(__file__).parent.parent # aiecs/
|
|
95
|
+
|
|
96
|
+
# Try aiecs/config/llm_models.yaml
|
|
97
|
+
config_path1 = current_dir / "config" / "llm_models.yaml"
|
|
98
|
+
if config_path1.exists():
|
|
99
|
+
logger.info(f"Using LLM config from: {config_path1}")
|
|
100
|
+
return config_path1
|
|
101
|
+
|
|
102
|
+
# Try config/llm_models.yaml (relative to project root)
|
|
103
|
+
config_path2 = current_dir.parent / "config" / "llm_models.yaml"
|
|
104
|
+
if config_path2.exists():
|
|
105
|
+
logger.info(f"Using LLM config from: {config_path2}")
|
|
106
|
+
return config_path2
|
|
107
|
+
|
|
108
|
+
# Default to the first path even if it doesn't exist
|
|
109
|
+
logger.warning(f"LLM config file not found, using default path: {config_path1}")
|
|
110
|
+
return config_path1
|
|
111
|
+
|
|
112
|
+
def load_config(self, config_path: Optional[Path] = None) -> LLMModelsConfig:
|
|
113
|
+
"""
|
|
114
|
+
Load configuration from YAML file.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
config_path: Optional path to configuration file. If not provided,
|
|
118
|
+
will search in standard locations.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
LLMModelsConfig: Loaded and validated configuration
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
FileNotFoundError: If config file doesn't exist
|
|
125
|
+
ValueError: If config file is invalid
|
|
126
|
+
"""
|
|
127
|
+
with self._config_lock:
|
|
128
|
+
if config_path is None:
|
|
129
|
+
config_path = self._find_config_file()
|
|
130
|
+
else:
|
|
131
|
+
config_path = Path(config_path)
|
|
132
|
+
|
|
133
|
+
if not config_path.exists():
|
|
134
|
+
raise FileNotFoundError(
|
|
135
|
+
f"LLM models configuration file not found: {config_path}\n"
|
|
136
|
+
f"Please create the configuration file or set LLM_MODELS_CONFIG environment variable."
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
141
|
+
config_data = yaml.safe_load(f)
|
|
142
|
+
|
|
143
|
+
if not config_data:
|
|
144
|
+
raise ValueError("Configuration file is empty")
|
|
145
|
+
|
|
146
|
+
# Validate and parse using Pydantic
|
|
147
|
+
self._config = LLMModelsConfig(**config_data)
|
|
148
|
+
self._config_path = config_path
|
|
149
|
+
|
|
150
|
+
logger.info(
|
|
151
|
+
f"Loaded LLM configuration from {config_path}: "
|
|
152
|
+
f"{len(self._config.providers)} providers, "
|
|
153
|
+
f"{sum(len(p.models) for p in self._config.providers.values())} models"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return self._config
|
|
157
|
+
|
|
158
|
+
except yaml.YAMLError as e:
|
|
159
|
+
raise ValueError(f"Invalid YAML in configuration file: {e}")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
raise ValueError(f"Failed to load configuration: {e}")
|
|
162
|
+
|
|
163
|
+
def reload_config(self) -> LLMModelsConfig:
|
|
164
|
+
"""
|
|
165
|
+
Reload configuration from the current config file.
|
|
166
|
+
|
|
167
|
+
This supports the hybrid loading mode - configuration is loaded at startup
|
|
168
|
+
but can be manually refreshed without restarting the application.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
LLMModelsConfig: Reloaded configuration
|
|
172
|
+
"""
|
|
173
|
+
logger.info("Reloading LLM configuration...")
|
|
174
|
+
return self.load_config(self._config_path)
|
|
175
|
+
|
|
176
|
+
def get_config(self) -> LLMModelsConfig:
|
|
177
|
+
"""
|
|
178
|
+
Get the current configuration.
|
|
179
|
+
|
|
180
|
+
Loads configuration on first access if not already loaded.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
LLMModelsConfig: Current configuration
|
|
184
|
+
"""
|
|
185
|
+
if self._config is None:
|
|
186
|
+
self.load_config()
|
|
187
|
+
return self._config
|
|
188
|
+
|
|
189
|
+
def get_provider_config(self, provider_name: str) -> Optional[ProviderConfig]:
|
|
190
|
+
"""
|
|
191
|
+
Get configuration for a specific provider.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
provider_name: Name of the provider (e.g., "Vertex", "OpenAI")
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
ProviderConfig if found, None otherwise
|
|
198
|
+
"""
|
|
199
|
+
config = self.get_config()
|
|
200
|
+
return config.get_provider_config(provider_name)
|
|
201
|
+
|
|
202
|
+
def get_model_config(self, provider_name: str, model_name: str) -> Optional[ModelConfig]:
|
|
203
|
+
"""
|
|
204
|
+
Get configuration for a specific model.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
provider_name: Name of the provider
|
|
208
|
+
model_name: Name of the model (or alias)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
ModelConfig if found, None otherwise
|
|
212
|
+
"""
|
|
213
|
+
config = self.get_config()
|
|
214
|
+
return config.get_model_config(provider_name, model_name)
|
|
215
|
+
|
|
216
|
+
def get_default_model(self, provider_name: str) -> Optional[str]:
|
|
217
|
+
"""
|
|
218
|
+
Get the default model name for a provider.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
provider_name: Name of the provider
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Default model name if found, None otherwise
|
|
225
|
+
"""
|
|
226
|
+
provider_config = self.get_provider_config(provider_name)
|
|
227
|
+
if provider_config:
|
|
228
|
+
return provider_config.default_model
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
def is_loaded(self) -> bool:
|
|
232
|
+
"""Check if configuration has been loaded"""
|
|
233
|
+
return self._config is not None
|
|
234
|
+
|
|
235
|
+
def get_config_path(self) -> Optional[Path]:
|
|
236
|
+
"""Get the path to the current configuration file"""
|
|
237
|
+
return self._config_path
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# Global singleton instance
|
|
241
|
+
_loader = LLMConfigLoader()
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def get_llm_config_loader() -> LLMConfigLoader:
|
|
245
|
+
"""
|
|
246
|
+
Get the global LLM configuration loader instance.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
LLMConfigLoader: Global singleton instance
|
|
250
|
+
"""
|
|
251
|
+
return _loader
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def get_llm_config() -> LLMModelsConfig:
|
|
255
|
+
"""
|
|
256
|
+
Get the current LLM configuration.
|
|
257
|
+
|
|
258
|
+
Convenience function that returns the configuration from the global loader.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
LLMModelsConfig: Current configuration
|
|
262
|
+
"""
|
|
263
|
+
return _loader.get_config()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def reload_llm_config() -> LLMModelsConfig:
|
|
267
|
+
"""
|
|
268
|
+
Reload the LLM configuration.
|
|
269
|
+
|
|
270
|
+
Convenience function that reloads the configuration in the global loader.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
LLMModelsConfig: Reloaded configuration
|
|
274
|
+
"""
|
|
275
|
+
return _loader.reload_config()
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration validation utilities for LLM models.
|
|
3
|
+
|
|
4
|
+
This module provides validation functions to ensure configuration integrity
|
|
5
|
+
and provide helpful error messages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import List, Tuple
|
|
10
|
+
|
|
11
|
+
from aiecs.llm.config.model_config import (
|
|
12
|
+
LLMModelsConfig,
|
|
13
|
+
ProviderConfig,
|
|
14
|
+
ModelConfig,
|
|
15
|
+
ModelCostConfig,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConfigValidationError(Exception):
|
|
22
|
+
"""Raised when configuration validation fails"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_cost_config(cost_config: ModelCostConfig, model_name: str) -> List[str]:
|
|
26
|
+
"""
|
|
27
|
+
Validate cost configuration.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
cost_config: Cost configuration to validate
|
|
31
|
+
model_name: Name of the model (for error messages)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of warning messages (empty if valid)
|
|
35
|
+
"""
|
|
36
|
+
warnings = []
|
|
37
|
+
|
|
38
|
+
if cost_config.input < 0:
|
|
39
|
+
raise ConfigValidationError(
|
|
40
|
+
f"Model '{model_name}': Input cost must be non-negative, got {cost_config.input}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if cost_config.output < 0:
|
|
44
|
+
raise ConfigValidationError(
|
|
45
|
+
f"Model '{model_name}': Output cost must be non-negative, got {cost_config.output}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Warn if costs are zero (might be intentional for free tiers or unknown
|
|
49
|
+
# pricing)
|
|
50
|
+
if cost_config.input == 0 and cost_config.output == 0:
|
|
51
|
+
warnings.append(f"Model '{model_name}': Both input and output costs are 0")
|
|
52
|
+
|
|
53
|
+
return warnings
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def validate_model_config(model_config: ModelConfig) -> List[str]:
|
|
57
|
+
"""
|
|
58
|
+
Validate a single model configuration.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
model_config: Model configuration to validate
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
List of warning messages (empty if valid)
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
ConfigValidationError: If validation fails
|
|
68
|
+
"""
|
|
69
|
+
warnings = []
|
|
70
|
+
|
|
71
|
+
# Validate name
|
|
72
|
+
if not model_config.name or not model_config.name.strip():
|
|
73
|
+
raise ConfigValidationError("Model name cannot be empty")
|
|
74
|
+
|
|
75
|
+
# Validate costs
|
|
76
|
+
cost_warnings = validate_cost_config(model_config.costs, model_config.name)
|
|
77
|
+
warnings.extend(cost_warnings)
|
|
78
|
+
|
|
79
|
+
# Validate capabilities
|
|
80
|
+
if model_config.capabilities.max_tokens <= 0:
|
|
81
|
+
raise ConfigValidationError(
|
|
82
|
+
f"Model '{model_config.name}': max_tokens must be positive, "
|
|
83
|
+
f"got {model_config.capabilities.max_tokens}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if model_config.capabilities.context_window <= 0:
|
|
87
|
+
raise ConfigValidationError(
|
|
88
|
+
f"Model '{model_config.name}': context_window must be positive, "
|
|
89
|
+
f"got {model_config.capabilities.context_window}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Validate default params
|
|
93
|
+
if not (0.0 <= model_config.default_params.temperature <= 2.0):
|
|
94
|
+
raise ConfigValidationError(
|
|
95
|
+
f"Model '{model_config.name}': temperature must be between 0.0 and 2.0, "
|
|
96
|
+
f"got {model_config.default_params.temperature}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if model_config.default_params.max_tokens <= 0:
|
|
100
|
+
raise ConfigValidationError(
|
|
101
|
+
f"Model '{model_config.name}': default max_tokens must be positive, "
|
|
102
|
+
f"got {model_config.default_params.max_tokens}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Warn if default max_tokens exceeds capability max_tokens
|
|
106
|
+
if model_config.default_params.max_tokens > model_config.capabilities.max_tokens:
|
|
107
|
+
warnings.append(
|
|
108
|
+
f"Model '{model_config.name}': default max_tokens ({model_config.default_params.max_tokens}) "
|
|
109
|
+
f"exceeds capability max_tokens ({model_config.capabilities.max_tokens})"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return warnings
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def validate_provider_config(provider_config: ProviderConfig) -> List[str]:
|
|
116
|
+
"""
|
|
117
|
+
Validate a provider configuration.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
provider_config: Provider configuration to validate
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of warning messages (empty if valid)
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ConfigValidationError: If validation fails
|
|
127
|
+
"""
|
|
128
|
+
warnings = []
|
|
129
|
+
|
|
130
|
+
# Validate provider name
|
|
131
|
+
if not provider_config.provider_name or not provider_config.provider_name.strip():
|
|
132
|
+
raise ConfigValidationError("Provider name cannot be empty")
|
|
133
|
+
|
|
134
|
+
# Validate models list
|
|
135
|
+
if not provider_config.models:
|
|
136
|
+
raise ConfigValidationError(
|
|
137
|
+
f"Provider '{provider_config.provider_name}': Must have at least one model"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Validate default model exists
|
|
141
|
+
model_names = provider_config.get_model_names()
|
|
142
|
+
if provider_config.default_model not in model_names:
|
|
143
|
+
raise ConfigValidationError(
|
|
144
|
+
f"Provider '{provider_config.provider_name}': Default model '{provider_config.default_model}' "
|
|
145
|
+
f"not found in models list: {model_names}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Validate each model
|
|
149
|
+
for model in provider_config.models:
|
|
150
|
+
model_warnings = validate_model_config(model)
|
|
151
|
+
warnings.extend(model_warnings)
|
|
152
|
+
|
|
153
|
+
# Check for duplicate model names
|
|
154
|
+
if len(model_names) != len(set(model_names)):
|
|
155
|
+
duplicates = [name for name in model_names if model_names.count(name) > 1]
|
|
156
|
+
raise ConfigValidationError(
|
|
157
|
+
f"Provider '{provider_config.provider_name}': Duplicate model names found: {set(duplicates)}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Validate model mappings if present
|
|
161
|
+
if provider_config.model_mappings:
|
|
162
|
+
for alias, target in provider_config.model_mappings.items():
|
|
163
|
+
if target not in model_names:
|
|
164
|
+
raise ConfigValidationError(
|
|
165
|
+
f"Provider '{provider_config.provider_name}': Model mapping alias '{alias}' "
|
|
166
|
+
f"points to non-existent model '{target}'. Available models: {model_names}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Warn if alias is the same as target
|
|
170
|
+
if alias == target:
|
|
171
|
+
warnings.append(
|
|
172
|
+
f"Provider '{provider_config.provider_name}': Model mapping has redundant entry "
|
|
173
|
+
f"'{alias}' -> '{target}'"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return warnings
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def validate_llm_config(config: LLMModelsConfig) -> Tuple[bool, List[str]]:
|
|
180
|
+
"""
|
|
181
|
+
Validate the entire LLM configuration.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
config: Complete LLM configuration to validate
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Tuple of (is_valid, warnings_list)
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ConfigValidationError: If validation fails critically
|
|
191
|
+
"""
|
|
192
|
+
warnings = []
|
|
193
|
+
|
|
194
|
+
# Validate providers exist
|
|
195
|
+
if not config.providers:
|
|
196
|
+
raise ConfigValidationError("Configuration must have at least one provider")
|
|
197
|
+
|
|
198
|
+
# Validate each provider
|
|
199
|
+
for provider_name, provider_config in config.providers.items():
|
|
200
|
+
try:
|
|
201
|
+
provider_warnings = validate_provider_config(provider_config)
|
|
202
|
+
warnings.extend(provider_warnings)
|
|
203
|
+
except ConfigValidationError as e:
|
|
204
|
+
raise ConfigValidationError(f"Provider '{provider_name}': {e}")
|
|
205
|
+
|
|
206
|
+
# Log warnings
|
|
207
|
+
if warnings:
|
|
208
|
+
logger.warning(f"Configuration validation completed with {len(warnings)} warnings:")
|
|
209
|
+
for warning in warnings:
|
|
210
|
+
logger.warning(f" - {warning}")
|
|
211
|
+
else:
|
|
212
|
+
logger.info("Configuration validation completed successfully with no warnings")
|
|
213
|
+
|
|
214
|
+
return True, warnings
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def validate_config_file(config_path: str) -> Tuple[bool, List[str]]:
|
|
218
|
+
"""
|
|
219
|
+
Validate a configuration file.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
config_path: Path to the configuration file
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Tuple of (is_valid, warnings_list)
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
ConfigValidationError: If validation fails
|
|
229
|
+
FileNotFoundError: If file doesn't exist
|
|
230
|
+
"""
|
|
231
|
+
from aiecs.llm.config.config_loader import LLMConfigLoader
|
|
232
|
+
|
|
233
|
+
loader = LLMConfigLoader()
|
|
234
|
+
config = loader.load_config(config_path)
|
|
235
|
+
|
|
236
|
+
return validate_llm_config(config)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for LLM configuration management.
|
|
3
|
+
|
|
4
|
+
This module defines the configuration schema for all LLM providers and models,
|
|
5
|
+
enabling centralized, type-safe configuration management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Optional, List
|
|
9
|
+
from pydantic import BaseModel, Field, field_validator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ModelCostConfig(BaseModel):
|
|
13
|
+
"""Token cost configuration for a model"""
|
|
14
|
+
|
|
15
|
+
input: float = Field(ge=0, description="Cost per 1K input tokens in USD")
|
|
16
|
+
output: float = Field(ge=0, description="Cost per 1K output tokens in USD")
|
|
17
|
+
|
|
18
|
+
@field_validator("input", "output")
|
|
19
|
+
@classmethod
|
|
20
|
+
def validate_positive(cls, v: float) -> float:
|
|
21
|
+
"""Ensure costs are non-negative"""
|
|
22
|
+
if v < 0:
|
|
23
|
+
raise ValueError("Cost must be non-negative")
|
|
24
|
+
return v
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ModelCapabilities(BaseModel):
|
|
28
|
+
"""Capabilities and limits for a model"""
|
|
29
|
+
|
|
30
|
+
streaming: bool = Field(default=True, description="Whether the model supports streaming")
|
|
31
|
+
vision: bool = Field(
|
|
32
|
+
default=False,
|
|
33
|
+
description="Whether the model supports vision/image input",
|
|
34
|
+
)
|
|
35
|
+
function_calling: bool = Field(
|
|
36
|
+
default=False,
|
|
37
|
+
description="Whether the model supports function calling",
|
|
38
|
+
)
|
|
39
|
+
max_tokens: int = Field(default=8192, ge=1, description="Maximum output tokens")
|
|
40
|
+
context_window: int = Field(default=128000, ge=1, description="Maximum context window size")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ModelDefaultParams(BaseModel):
|
|
44
|
+
"""Default parameters for model inference"""
|
|
45
|
+
|
|
46
|
+
temperature: float = Field(default=0.7, ge=0.0, le=2.0, description="Default temperature")
|
|
47
|
+
max_tokens: int = Field(default=8192, ge=1, description="Default max output tokens")
|
|
48
|
+
top_p: float = Field(default=0.95, ge=0.0, le=1.0, description="Default top_p")
|
|
49
|
+
top_k: int = Field(default=40, ge=0, description="Default top_k")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ModelConfig(BaseModel):
|
|
53
|
+
"""Complete configuration for a single model"""
|
|
54
|
+
|
|
55
|
+
name: str = Field(description="Model identifier")
|
|
56
|
+
display_name: Optional[str] = Field(default=None, description="Human-readable model name")
|
|
57
|
+
costs: ModelCostConfig = Field(description="Token cost configuration")
|
|
58
|
+
capabilities: ModelCapabilities = Field(
|
|
59
|
+
default_factory=ModelCapabilities, description="Model capabilities"
|
|
60
|
+
)
|
|
61
|
+
default_params: ModelDefaultParams = Field(
|
|
62
|
+
default_factory=ModelDefaultParams, description="Default parameters"
|
|
63
|
+
)
|
|
64
|
+
description: Optional[str] = Field(default=None, description="Model description")
|
|
65
|
+
|
|
66
|
+
def __init__(self, **data):
|
|
67
|
+
super().__init__(**data)
|
|
68
|
+
# Set display_name to name if not provided
|
|
69
|
+
if self.display_name is None:
|
|
70
|
+
self.display_name = self.name
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ProviderConfig(BaseModel):
|
|
74
|
+
"""Configuration for a single LLM provider"""
|
|
75
|
+
|
|
76
|
+
provider_name: str = Field(description="Provider identifier")
|
|
77
|
+
default_model: str = Field(description="Default model for this provider")
|
|
78
|
+
models: List[ModelConfig] = Field(description="List of available models")
|
|
79
|
+
model_mappings: Optional[Dict[str, str]] = Field(
|
|
80
|
+
default=None,
|
|
81
|
+
description="Model name aliases (e.g., 'Grok 4' -> 'grok-4')",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@field_validator("models")
|
|
85
|
+
@classmethod
|
|
86
|
+
def validate_models_not_empty(cls, v: List[ModelConfig]) -> List[ModelConfig]:
|
|
87
|
+
"""Ensure at least one model is configured"""
|
|
88
|
+
if not v:
|
|
89
|
+
raise ValueError("Provider must have at least one model configured")
|
|
90
|
+
return v
|
|
91
|
+
|
|
92
|
+
def get_model_config(self, model_name: str) -> Optional[ModelConfig]:
|
|
93
|
+
"""Get configuration for a specific model"""
|
|
94
|
+
# First, check if this is an alias
|
|
95
|
+
if self.model_mappings and model_name in self.model_mappings:
|
|
96
|
+
model_name = self.model_mappings[model_name]
|
|
97
|
+
|
|
98
|
+
# Find the model configuration
|
|
99
|
+
for model in self.models:
|
|
100
|
+
if model.name == model_name:
|
|
101
|
+
return model
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def get_model_names(self) -> List[str]:
|
|
105
|
+
"""Get list of all model names"""
|
|
106
|
+
return [model.name for model in self.models]
|
|
107
|
+
|
|
108
|
+
def get_all_model_names_with_aliases(self) -> List[str]:
|
|
109
|
+
"""Get list of all model names including aliases"""
|
|
110
|
+
names = self.get_model_names()
|
|
111
|
+
if self.model_mappings:
|
|
112
|
+
names.extend(list(self.model_mappings.keys()))
|
|
113
|
+
return names
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class LLMModelsConfig(BaseModel):
|
|
117
|
+
"""Root configuration containing all providers"""
|
|
118
|
+
|
|
119
|
+
providers: Dict[str, ProviderConfig] = Field(
|
|
120
|
+
description="Provider configurations keyed by provider name"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@field_validator("providers")
|
|
124
|
+
@classmethod
|
|
125
|
+
def validate_providers_not_empty(
|
|
126
|
+
cls, v: Dict[str, ProviderConfig]
|
|
127
|
+
) -> Dict[str, ProviderConfig]:
|
|
128
|
+
"""Ensure at least one provider is configured"""
|
|
129
|
+
if not v:
|
|
130
|
+
raise ValueError("At least one provider must be configured")
|
|
131
|
+
return v
|
|
132
|
+
|
|
133
|
+
def get_provider_config(self, provider_name: str) -> Optional[ProviderConfig]:
|
|
134
|
+
"""Get configuration for a specific provider"""
|
|
135
|
+
# Normalize provider name (case-insensitive lookup)
|
|
136
|
+
provider_name_lower = provider_name.lower()
|
|
137
|
+
for key, config in self.providers.items():
|
|
138
|
+
if key.lower() == provider_name_lower:
|
|
139
|
+
return config
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def get_model_config(self, provider_name: str, model_name: str) -> Optional[ModelConfig]:
|
|
143
|
+
"""Get configuration for a specific model from a provider"""
|
|
144
|
+
provider_config = self.get_provider_config(provider_name)
|
|
145
|
+
if provider_config:
|
|
146
|
+
return provider_config.get_model_config(model_name)
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
def get_provider_names(self) -> List[str]:
|
|
150
|
+
"""Get list of all provider names"""
|
|
151
|
+
return list(self.providers.keys())
|