aiecs 1.0.1__py3-none-any.whl → 1.7.6__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -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 +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -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 +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +399 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +884 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +364 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +175 -131
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Source Tool
|
|
3
|
+
|
|
4
|
+
Unified interface for querying various external real-time API data sources including
|
|
5
|
+
economic indicators, news, public databases, and custom APIs with plugin architecture.
|
|
6
|
+
|
|
7
|
+
Enhanced Features:
|
|
8
|
+
- Auto-discovery of API providers
|
|
9
|
+
- Unified query interface with intelligent parameter enhancement
|
|
10
|
+
- Intelligent caching with TTL strategies
|
|
11
|
+
- Cross-provider data fusion
|
|
12
|
+
- Automatic fallback to alternative providers
|
|
13
|
+
- Advanced search with relevance scoring
|
|
14
|
+
- Comprehensive error handling with recovery suggestions
|
|
15
|
+
- Detailed metrics and health monitoring
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
23
|
+
|
|
24
|
+
from aiecs.tools import register_tool
|
|
25
|
+
from aiecs.tools.base_tool import BaseTool
|
|
26
|
+
from aiecs.tools.tool_executor import (
|
|
27
|
+
cache_result_with_strategy,
|
|
28
|
+
cache_result,
|
|
29
|
+
measure_execution_time,
|
|
30
|
+
)
|
|
31
|
+
from aiecs.tools.apisource.providers import (
|
|
32
|
+
get_provider,
|
|
33
|
+
list_providers,
|
|
34
|
+
PROVIDER_REGISTRY,
|
|
35
|
+
)
|
|
36
|
+
from aiecs.tools.apisource.intelligence import (
|
|
37
|
+
QueryIntentAnalyzer,
|
|
38
|
+
QueryEnhancer,
|
|
39
|
+
DataFusionEngine,
|
|
40
|
+
SearchEnhancer,
|
|
41
|
+
)
|
|
42
|
+
from aiecs.tools.apisource.reliability import FallbackStrategy
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Custom exceptions
|
|
48
|
+
class APISourceError(Exception):
|
|
49
|
+
"""Base exception for API Source Tool errors"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ProviderNotFoundError(APISourceError):
|
|
53
|
+
"""Raised when requested provider is not found"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class APIRateLimitError(APISourceError):
|
|
57
|
+
"""Raised when API rate limit is exceeded"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class APIAuthenticationError(APISourceError):
|
|
61
|
+
"""Raised when API authentication fails"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@register_tool("apisource")
|
|
65
|
+
class APISourceTool(BaseTool):
|
|
66
|
+
"""
|
|
67
|
+
Query external real-time API data sources including economic indicators, news, public databases, and custom APIs.
|
|
68
|
+
|
|
69
|
+
Supports multiple data providers through a plugin architecture:
|
|
70
|
+
- FRED: Federal Reserve Economic Data (US economic indicators)
|
|
71
|
+
- World Bank: Global development indicators
|
|
72
|
+
- News API: News articles and headlines
|
|
73
|
+
- Census: US Census Bureau demographic and economic data
|
|
74
|
+
|
|
75
|
+
Provides unified interface with automatic rate limiting, caching, and error handling.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# Configuration schema
|
|
79
|
+
class Config(BaseSettings):
|
|
80
|
+
"""Configuration for the API Source Tool
|
|
81
|
+
|
|
82
|
+
Automatically reads from environment variables with APISOURCE_TOOL_ prefix.
|
|
83
|
+
Example: APISOURCE_TOOL_FRED_API_KEY -> fred_api_key
|
|
84
|
+
|
|
85
|
+
Sensitive fields (API keys) are loaded from .env files via dotenv.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
model_config = SettingsConfigDict(env_prefix="APISOURCE_TOOL_")
|
|
89
|
+
|
|
90
|
+
cache_ttl: int = Field(
|
|
91
|
+
default=300,
|
|
92
|
+
description="Cache time-to-live in seconds for API responses",
|
|
93
|
+
)
|
|
94
|
+
default_timeout: int = Field(
|
|
95
|
+
default=30,
|
|
96
|
+
description="Default timeout in seconds for API requests",
|
|
97
|
+
)
|
|
98
|
+
max_retries: int = Field(
|
|
99
|
+
default=3,
|
|
100
|
+
description="Maximum number of retries for failed requests",
|
|
101
|
+
)
|
|
102
|
+
enable_rate_limiting: bool = Field(
|
|
103
|
+
default=True,
|
|
104
|
+
description="Whether to enable rate limiting for API requests",
|
|
105
|
+
)
|
|
106
|
+
enable_fallback: bool = Field(
|
|
107
|
+
default=True,
|
|
108
|
+
description="Enable automatic fallback to alternative providers",
|
|
109
|
+
)
|
|
110
|
+
enable_data_fusion: bool = Field(
|
|
111
|
+
default=True,
|
|
112
|
+
description="Enable cross-provider data fusion in search",
|
|
113
|
+
)
|
|
114
|
+
enable_query_enhancement: bool = Field(
|
|
115
|
+
default=True,
|
|
116
|
+
description="Enable intelligent query parameter enhancement",
|
|
117
|
+
)
|
|
118
|
+
fred_api_key: Optional[str] = Field(
|
|
119
|
+
default=None,
|
|
120
|
+
description="API key for Federal Reserve Economic Data (FRED)",
|
|
121
|
+
)
|
|
122
|
+
newsapi_api_key: Optional[str] = Field(default=None, description="API key for News API")
|
|
123
|
+
census_api_key: Optional[str] = Field(default=None, description="API key for US Census Bureau")
|
|
124
|
+
|
|
125
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
126
|
+
"""
|
|
127
|
+
Initialize API Source Tool with enhanced intelligence features.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
config: Configuration dictionary with API keys and settings
|
|
131
|
+
**kwargs: Additional arguments passed to BaseTool (e.g., tool_name)
|
|
132
|
+
|
|
133
|
+
Configuration is automatically loaded by BaseTool from:
|
|
134
|
+
1. Explicit config dict (highest priority)
|
|
135
|
+
2. YAML config files (config/tools/apisource.yaml)
|
|
136
|
+
3. Environment variables (via dotenv from .env files)
|
|
137
|
+
4. Tool defaults (lowest priority)
|
|
138
|
+
|
|
139
|
+
Sensitive fields (API keys) are loaded from .env files.
|
|
140
|
+
"""
|
|
141
|
+
super().__init__(config, **kwargs)
|
|
142
|
+
|
|
143
|
+
# Configuration is automatically loaded by BaseTool into self._config_obj
|
|
144
|
+
# Access config via self._config_obj (BaseSettings instance)
|
|
145
|
+
self.config = self._config_obj if self._config_obj else self.Config()
|
|
146
|
+
|
|
147
|
+
self.logger = logging.getLogger(__name__)
|
|
148
|
+
if not self.logger.handlers:
|
|
149
|
+
handler = logging.StreamHandler()
|
|
150
|
+
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
|
151
|
+
self.logger.addHandler(handler)
|
|
152
|
+
|
|
153
|
+
# Initialize intelligence components
|
|
154
|
+
self.query_analyzer = QueryIntentAnalyzer()
|
|
155
|
+
self.query_enhancer = QueryEnhancer(self.query_analyzer)
|
|
156
|
+
self.data_fusion = DataFusionEngine()
|
|
157
|
+
self.fallback_strategy = FallbackStrategy()
|
|
158
|
+
self.search_enhancer = SearchEnhancer(relevance_weight=0.5, popularity_weight=0.3, recency_weight=0.2)
|
|
159
|
+
|
|
160
|
+
# Load providers (they auto-discover on import)
|
|
161
|
+
self._providers: Dict[str, Any] = {}
|
|
162
|
+
self._load_providers()
|
|
163
|
+
|
|
164
|
+
def _load_providers(self):
|
|
165
|
+
"""Load and cache provider instances"""
|
|
166
|
+
for provider_name in PROVIDER_REGISTRY.keys():
|
|
167
|
+
try:
|
|
168
|
+
# Create provider config from tool config
|
|
169
|
+
provider_config = {
|
|
170
|
+
"timeout": self.config.default_timeout,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# Add provider-specific API key if available
|
|
174
|
+
api_key_attr = f"{provider_name}_api_key"
|
|
175
|
+
if hasattr(self.config, api_key_attr):
|
|
176
|
+
api_key = getattr(self.config, api_key_attr)
|
|
177
|
+
if api_key:
|
|
178
|
+
provider_config["api_key"] = api_key
|
|
179
|
+
|
|
180
|
+
provider = get_provider(provider_name, provider_config)
|
|
181
|
+
self._providers[provider_name] = provider
|
|
182
|
+
self.logger.debug(f"Loaded provider: {provider_name}")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
self.logger.warning(f"Failed to load provider {provider_name}: {e}")
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def _discover_provider_operations(cls) -> List[Dict[str, Any]]:
|
|
188
|
+
"""
|
|
189
|
+
Discover all exposed operations from all registered providers.
|
|
190
|
+
|
|
191
|
+
This method enables the LangChain adapter to automatically create individual
|
|
192
|
+
tools for each provider operation, giving AI agents fine-grained visibility
|
|
193
|
+
into provider capabilities.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of operation dictionaries, each containing:
|
|
197
|
+
- name: Full operation name (e.g., 'fred_get_series_observations')
|
|
198
|
+
- schema: Pydantic schema for the operation
|
|
199
|
+
- description: Operation description
|
|
200
|
+
- method: Callable method to execute the operation
|
|
201
|
+
"""
|
|
202
|
+
operations = []
|
|
203
|
+
|
|
204
|
+
for provider_name, provider_class in PROVIDER_REGISTRY.items():
|
|
205
|
+
try:
|
|
206
|
+
# Get exposed operations from provider
|
|
207
|
+
exposed_ops = provider_class.get_exposed_operations()
|
|
208
|
+
|
|
209
|
+
for op in exposed_ops:
|
|
210
|
+
# Convert Dict-based schema to Pydantic schema
|
|
211
|
+
pydantic_schema = cls._convert_dict_schema_to_pydantic(op["schema"], f"{provider_name}_{op['name']}") if op["schema"] else None
|
|
212
|
+
|
|
213
|
+
# Create operation info
|
|
214
|
+
operation_info = {
|
|
215
|
+
"name": f"{provider_name}_{op['name']}",
|
|
216
|
+
"schema": pydantic_schema,
|
|
217
|
+
"description": op["description"],
|
|
218
|
+
# Store original operation name
|
|
219
|
+
"method_name": op["name"],
|
|
220
|
+
"provider_name": provider_name, # Store provider name
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
operations.append(operation_info)
|
|
224
|
+
logger.debug(f"Discovered provider operation: {operation_info['name']}")
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.warning(f"Error discovering operations for provider {provider_name}: {e}")
|
|
228
|
+
|
|
229
|
+
logger.info(f"Discovered {len(operations)} provider operations across {len(PROVIDER_REGISTRY)} providers")
|
|
230
|
+
return operations
|
|
231
|
+
|
|
232
|
+
@staticmethod
|
|
233
|
+
def _convert_dict_schema_to_pydantic(dict_schema: Optional[Dict[str, Any]], schema_name: str) -> Optional[type[BaseModel]]:
|
|
234
|
+
"""
|
|
235
|
+
Convert Dict-based provider schema to Pydantic BaseModel schema.
|
|
236
|
+
|
|
237
|
+
This enables provider operation schemas to be used by the LangChain adapter
|
|
238
|
+
and exposed to AI agents with full type information.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
dict_schema: Dictionary schema from provider.get_operation_schema()
|
|
242
|
+
schema_name: Name for the generated Pydantic schema class
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Pydantic BaseModel class or None if schema is invalid
|
|
246
|
+
"""
|
|
247
|
+
if not dict_schema or "parameters" not in dict_schema:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
from pydantic import create_model
|
|
252
|
+
|
|
253
|
+
fields = {}
|
|
254
|
+
parameters = dict_schema.get("parameters", {})
|
|
255
|
+
|
|
256
|
+
for param_name, param_info in parameters.items():
|
|
257
|
+
# Determine field type from schema
|
|
258
|
+
param_type_str = param_info.get("type", "string")
|
|
259
|
+
|
|
260
|
+
# Map schema types to Python types
|
|
261
|
+
type_mapping = {
|
|
262
|
+
"string": str,
|
|
263
|
+
"integer": int,
|
|
264
|
+
"number": float,
|
|
265
|
+
"boolean": bool,
|
|
266
|
+
"array": List[Any],
|
|
267
|
+
"object": Dict[str, Any],
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
field_type = type_mapping.get(param_type_str, str)
|
|
271
|
+
|
|
272
|
+
# Make optional if not required
|
|
273
|
+
is_required = param_info.get("required", False)
|
|
274
|
+
if not is_required:
|
|
275
|
+
field_type = Optional[field_type] # type: ignore[assignment]
|
|
276
|
+
|
|
277
|
+
# Build field description
|
|
278
|
+
description_parts = [param_info.get("description", "")]
|
|
279
|
+
|
|
280
|
+
# Add examples if available
|
|
281
|
+
if "examples" in param_info and param_info["examples"]:
|
|
282
|
+
examples_str = ", ".join(str(ex) for ex in param_info["examples"][:3])
|
|
283
|
+
description_parts.append(f"Examples: {examples_str}")
|
|
284
|
+
|
|
285
|
+
# Add validation info if available
|
|
286
|
+
if "validation" in param_info:
|
|
287
|
+
validation = param_info["validation"]
|
|
288
|
+
if "pattern" in validation:
|
|
289
|
+
description_parts.append(f"Pattern: {validation['pattern']}")
|
|
290
|
+
if "min" in validation or "max" in validation:
|
|
291
|
+
range_str = f"Range: {validation.get('min', 'any')}-{validation.get('max', 'any')}"
|
|
292
|
+
description_parts.append(range_str)
|
|
293
|
+
|
|
294
|
+
full_description = ". ".join(filter(None, description_parts))
|
|
295
|
+
|
|
296
|
+
# Create field with default value if not required
|
|
297
|
+
if is_required:
|
|
298
|
+
fields[param_name] = (
|
|
299
|
+
field_type,
|
|
300
|
+
Field(description=full_description),
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
fields[param_name] = (
|
|
304
|
+
field_type,
|
|
305
|
+
Field(default=None, description=full_description),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Create the Pydantic model
|
|
309
|
+
# Dynamic model creation - use type ignore for create_model overload issues
|
|
310
|
+
schema_class = create_model( # type: ignore[call-overload]
|
|
311
|
+
f"{schema_name.replace('_', '').title()}Schema",
|
|
312
|
+
__doc__=dict_schema.get("description", ""),
|
|
313
|
+
**fields,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
logger.debug(f"Created Pydantic schema: {schema_class.__name__} with {len(fields)} fields")
|
|
317
|
+
return schema_class
|
|
318
|
+
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.error(f"Error converting schema {schema_name}: {e}")
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
def _create_query_ttl_strategy(self):
|
|
324
|
+
"""
|
|
325
|
+
Create intelligent TTL strategy for API query results.
|
|
326
|
+
|
|
327
|
+
This strategy calculates TTL based on:
|
|
328
|
+
1. Data type (historical vs real-time)
|
|
329
|
+
2. Provider characteristics
|
|
330
|
+
3. Operation type
|
|
331
|
+
4. Data quality and freshness
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Callable: TTL strategy function compatible with cache_result_with_strategy
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
def calculate_query_ttl(result: Any, args: tuple, kwargs: dict) -> int:
|
|
338
|
+
"""
|
|
339
|
+
Calculate intelligent TTL for API query results.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
result: The query result dictionary
|
|
343
|
+
args: Positional arguments (not used)
|
|
344
|
+
kwargs: Keyword arguments containing provider, operation, params
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
int: TTL in seconds
|
|
348
|
+
"""
|
|
349
|
+
kwargs.get("provider", "")
|
|
350
|
+
operation = kwargs.get("operation", "")
|
|
351
|
+
|
|
352
|
+
# Default TTL
|
|
353
|
+
default_ttl = 600 # 10 minutes
|
|
354
|
+
|
|
355
|
+
# Extract metadata if available
|
|
356
|
+
metadata = result.get("metadata", {}) if isinstance(result, dict) else {}
|
|
357
|
+
quality = metadata.get("quality", {})
|
|
358
|
+
freshness_hours = quality.get("freshness_hours", 24)
|
|
359
|
+
|
|
360
|
+
# Historical time series data - cache longer
|
|
361
|
+
if operation in [
|
|
362
|
+
"get_series_observations",
|
|
363
|
+
"get_indicator",
|
|
364
|
+
"get_series",
|
|
365
|
+
]:
|
|
366
|
+
# Check if data is historical (older than 24 hours)
|
|
367
|
+
if freshness_hours > 24:
|
|
368
|
+
# Historical data: cache for 7 days
|
|
369
|
+
ttl = 86400 * 7
|
|
370
|
+
self.logger.debug(f"Historical data detected, TTL: {ttl}s (7 days)")
|
|
371
|
+
return ttl
|
|
372
|
+
else:
|
|
373
|
+
# Recent data: cache for 1 hour
|
|
374
|
+
ttl = 3600
|
|
375
|
+
self.logger.debug(f"Recent time series data, TTL: {ttl}s (1 hour)")
|
|
376
|
+
return ttl
|
|
377
|
+
|
|
378
|
+
# News data - cache very short time
|
|
379
|
+
elif operation in [
|
|
380
|
+
"get_top_headlines",
|
|
381
|
+
"search_everything",
|
|
382
|
+
"get_everything",
|
|
383
|
+
]:
|
|
384
|
+
ttl = 300 # 5 minutes
|
|
385
|
+
self.logger.debug(f"News data, TTL: {ttl}s (5 minutes)")
|
|
386
|
+
return ttl
|
|
387
|
+
|
|
388
|
+
# Metadata operations - cache longer
|
|
389
|
+
elif operation in [
|
|
390
|
+
"list_countries",
|
|
391
|
+
"list_indicators",
|
|
392
|
+
"get_sources",
|
|
393
|
+
"get_categories",
|
|
394
|
+
"get_releases",
|
|
395
|
+
"list_sources",
|
|
396
|
+
]:
|
|
397
|
+
ttl = 86400 # 1 day
|
|
398
|
+
self.logger.debug(f"Metadata operation, TTL: {ttl}s (1 day)")
|
|
399
|
+
return ttl
|
|
400
|
+
|
|
401
|
+
# Search operations - moderate cache time
|
|
402
|
+
elif operation in ["search_series", "search_indicators", "search"]:
|
|
403
|
+
ttl = 600 # 10 minutes
|
|
404
|
+
self.logger.debug(f"Search operation, TTL: {ttl}s (10 minutes)")
|
|
405
|
+
return ttl
|
|
406
|
+
|
|
407
|
+
# Info operations - cache longer
|
|
408
|
+
elif operation in ["get_series_info", "get_indicator_info"]:
|
|
409
|
+
ttl = 3600 # 1 hour
|
|
410
|
+
self.logger.debug(f"Info operation, TTL: {ttl}s (1 hour)")
|
|
411
|
+
return ttl
|
|
412
|
+
|
|
413
|
+
# Default
|
|
414
|
+
self.logger.debug(f"Default TTL: {default_ttl}s (10 minutes)")
|
|
415
|
+
return default_ttl
|
|
416
|
+
|
|
417
|
+
return calculate_query_ttl
|
|
418
|
+
|
|
419
|
+
def _create_search_ttl_strategy(self):
|
|
420
|
+
"""
|
|
421
|
+
Create intelligent TTL strategy for multi-provider search results.
|
|
422
|
+
|
|
423
|
+
This strategy calculates TTL based on:
|
|
424
|
+
1. Query intent type
|
|
425
|
+
2. Number of providers queried
|
|
426
|
+
3. Whether data fusion was applied
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Callable: TTL strategy function compatible with cache_result_with_strategy
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
def calculate_search_ttl(result: Any, args: tuple, kwargs: dict) -> int:
|
|
433
|
+
"""
|
|
434
|
+
Calculate intelligent TTL for search results.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
result: The search result dictionary
|
|
438
|
+
args: Positional arguments (not used)
|
|
439
|
+
kwargs: Keyword arguments containing query, providers, etc.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
int: TTL in seconds
|
|
443
|
+
"""
|
|
444
|
+
# Default TTL for search results
|
|
445
|
+
default_ttl = 300 # 5 minutes
|
|
446
|
+
|
|
447
|
+
if not isinstance(result, dict):
|
|
448
|
+
return default_ttl
|
|
449
|
+
|
|
450
|
+
# Extract metadata
|
|
451
|
+
metadata = result.get("metadata", {})
|
|
452
|
+
intent_analysis = metadata.get("intent_analysis", {})
|
|
453
|
+
intent_type = intent_analysis.get("intent_type", "general")
|
|
454
|
+
|
|
455
|
+
# Adjust TTL based on intent type
|
|
456
|
+
if intent_type in ["metadata", "definition"]:
|
|
457
|
+
# Metadata and definitions change rarely
|
|
458
|
+
ttl = 3600 # 1 hour
|
|
459
|
+
self.logger.debug(f"Search intent: {intent_type}, TTL: {ttl}s (1 hour)")
|
|
460
|
+
return ttl
|
|
461
|
+
|
|
462
|
+
elif intent_type in ["time_series", "comparison"]:
|
|
463
|
+
# Time series and comparisons - moderate cache
|
|
464
|
+
ttl = 600 # 10 minutes
|
|
465
|
+
self.logger.debug(f"Search intent: {intent_type}, TTL: {ttl}s (10 minutes)")
|
|
466
|
+
return ttl
|
|
467
|
+
|
|
468
|
+
elif intent_type == "search":
|
|
469
|
+
# General search - short cache
|
|
470
|
+
ttl = 300 # 5 minutes
|
|
471
|
+
self.logger.debug(f"Search intent: {intent_type}, TTL: {ttl}s (5 minutes)")
|
|
472
|
+
return ttl
|
|
473
|
+
|
|
474
|
+
# Default
|
|
475
|
+
self.logger.debug(f"Search default TTL: {default_ttl}s (5 minutes)")
|
|
476
|
+
return default_ttl
|
|
477
|
+
|
|
478
|
+
return calculate_search_ttl
|
|
479
|
+
|
|
480
|
+
# Schema definitions
|
|
481
|
+
class QuerySchema(BaseModel):
|
|
482
|
+
"""Schema for query operation"""
|
|
483
|
+
|
|
484
|
+
provider: str = Field(description="API provider name (e.g., 'fred', 'worldbank', 'newsapi', 'census')")
|
|
485
|
+
operation: str = Field(description="Provider-specific operation to perform (e.g., 'get_series', 'search_indicators')")
|
|
486
|
+
params: Dict[str, Any] = Field(description="Operation-specific parameters as key-value pairs")
|
|
487
|
+
|
|
488
|
+
class List_providersSchema(BaseModel):
|
|
489
|
+
"""Schema for list_providers operation (no parameters required)"""
|
|
490
|
+
|
|
491
|
+
pass
|
|
492
|
+
|
|
493
|
+
class Get_provider_infoSchema(BaseModel):
|
|
494
|
+
"""Schema for get_provider_info operation"""
|
|
495
|
+
|
|
496
|
+
provider: str = Field(description="API provider name to get information about")
|
|
497
|
+
|
|
498
|
+
class SearchSchema(BaseModel):
|
|
499
|
+
"""Schema for search operation"""
|
|
500
|
+
|
|
501
|
+
query: str = Field(description="Search query text to find across providers")
|
|
502
|
+
providers: Optional[List[str]] = Field(
|
|
503
|
+
default=None,
|
|
504
|
+
description="List of provider names to search (searches all if not specified)",
|
|
505
|
+
)
|
|
506
|
+
limit: int = Field(
|
|
507
|
+
default=10,
|
|
508
|
+
description="Maximum number of results to return per provider",
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
class Get_metrics_reportSchema(BaseModel):
|
|
512
|
+
"""Schema for get_metrics_report operation (no parameters required)"""
|
|
513
|
+
|
|
514
|
+
pass
|
|
515
|
+
|
|
516
|
+
@cache_result_with_strategy(ttl_strategy=lambda self, result, args, kwargs: self._create_query_ttl_strategy()(result, args, kwargs))
|
|
517
|
+
@measure_execution_time
|
|
518
|
+
def query(
|
|
519
|
+
self,
|
|
520
|
+
provider: str,
|
|
521
|
+
operation: str,
|
|
522
|
+
params: Dict[str, Any],
|
|
523
|
+
query_text: Optional[str] = None,
|
|
524
|
+
enable_fallback: Optional[bool] = None,
|
|
525
|
+
) -> Dict[str, Any]:
|
|
526
|
+
"""
|
|
527
|
+
Query a specific API provider with intelligent parameter enhancement and automatic fallback.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
provider: API provider name (e.g., 'fred', 'worldbank', 'newsapi', 'census')
|
|
531
|
+
operation: Provider-specific operation (e.g., 'get_series', 'search_indicators')
|
|
532
|
+
params: Operation-specific parameters as dictionary
|
|
533
|
+
query_text: Optional natural language query for intelligent parameter enhancement
|
|
534
|
+
enable_fallback: Override config setting for fallback (defaults to config value)
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Dictionary containing response data with enhanced metadata
|
|
538
|
+
|
|
539
|
+
Raises:
|
|
540
|
+
ProviderNotFoundError: If the specified provider is not available
|
|
541
|
+
ValueError: If operation or parameters are invalid
|
|
542
|
+
APISourceError: If the API request fails after all retries and fallbacks
|
|
543
|
+
"""
|
|
544
|
+
if provider not in self._providers:
|
|
545
|
+
available = ", ".join(self._providers.keys())
|
|
546
|
+
raise ProviderNotFoundError(f"Provider '{provider}' not found. Available providers: {available}")
|
|
547
|
+
|
|
548
|
+
# Apply query enhancement if enabled
|
|
549
|
+
enhanced_params = params
|
|
550
|
+
if self.config.enable_query_enhancement and query_text:
|
|
551
|
+
try:
|
|
552
|
+
enhanced_params = self.query_enhancer.auto_complete_params(provider, operation, params, query_text)
|
|
553
|
+
if enhanced_params != params:
|
|
554
|
+
self.logger.debug(f"Enhanced parameters from {params} to {enhanced_params}")
|
|
555
|
+
except Exception as e:
|
|
556
|
+
self.logger.warning(f"Parameter enhancement failed: {e}")
|
|
557
|
+
enhanced_params = params
|
|
558
|
+
|
|
559
|
+
# Determine if fallback should be used
|
|
560
|
+
use_fallback = enable_fallback if enable_fallback is not None else self.config.enable_fallback
|
|
561
|
+
|
|
562
|
+
if use_fallback:
|
|
563
|
+
# Use fallback strategy
|
|
564
|
+
def provider_executor(prov: str, op: str, par: Dict[str, Any]) -> Dict[str, Any]:
|
|
565
|
+
"""Execute provider operation"""
|
|
566
|
+
return self._providers[prov].execute(op, par)
|
|
567
|
+
|
|
568
|
+
result = self.fallback_strategy.execute_with_fallback(
|
|
569
|
+
primary_provider=provider,
|
|
570
|
+
operation=operation,
|
|
571
|
+
params=enhanced_params,
|
|
572
|
+
provider_executor=provider_executor,
|
|
573
|
+
providers_available=list(self._providers.keys()),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
if result["success"]:
|
|
577
|
+
return result["data"]
|
|
578
|
+
else:
|
|
579
|
+
# Build comprehensive error message
|
|
580
|
+
error_msg = f"Failed to execute {provider}.{operation}"
|
|
581
|
+
if result["attempts"]:
|
|
582
|
+
error_msg += f" after {len(result['attempts'])} attempts"
|
|
583
|
+
if result.get("fallback_used"):
|
|
584
|
+
error_msg += " (including fallback providers)"
|
|
585
|
+
|
|
586
|
+
raise APISourceError(error_msg)
|
|
587
|
+
else:
|
|
588
|
+
# Direct execution without fallback
|
|
589
|
+
try:
|
|
590
|
+
provider_instance = self._providers[provider]
|
|
591
|
+
result = provider_instance.execute(operation, enhanced_params)
|
|
592
|
+
return result
|
|
593
|
+
except Exception as e:
|
|
594
|
+
self.logger.error(f"Error querying {provider}.{operation}: {e}")
|
|
595
|
+
raise APISourceError(f"Failed to query {provider}: {str(e)}")
|
|
596
|
+
|
|
597
|
+
@cache_result(ttl=3600) # Cache provider list for 1 hour
|
|
598
|
+
@measure_execution_time
|
|
599
|
+
def list_providers(self) -> List[Dict[str, Any]]:
|
|
600
|
+
"""
|
|
601
|
+
List all available API providers with their metadata.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
List of provider metadata dictionaries containing name, description, supported operations, and statistics
|
|
605
|
+
"""
|
|
606
|
+
return list_providers()
|
|
607
|
+
|
|
608
|
+
@cache_result(ttl=1800) # Cache provider info for 30 minutes
|
|
609
|
+
@measure_execution_time
|
|
610
|
+
def get_provider_info(self, provider: str) -> Dict[str, Any]:
|
|
611
|
+
"""
|
|
612
|
+
Get detailed information about a specific API provider.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
provider: API provider name to get information about
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
Dictionary with provider metadata including name, description, operations, and configuration
|
|
619
|
+
|
|
620
|
+
Raises:
|
|
621
|
+
ProviderNotFoundError: If the specified provider is not found
|
|
622
|
+
"""
|
|
623
|
+
if provider not in self._providers:
|
|
624
|
+
available = ", ".join(self._providers.keys())
|
|
625
|
+
raise ProviderNotFoundError(f"Provider '{provider}' not found. Available providers: {available}")
|
|
626
|
+
|
|
627
|
+
provider_instance = self._providers[provider]
|
|
628
|
+
return provider_instance.get_metadata()
|
|
629
|
+
|
|
630
|
+
@cache_result_with_strategy(ttl_strategy=lambda self, result, args, kwargs: self._create_search_ttl_strategy()(result, args, kwargs))
|
|
631
|
+
@measure_execution_time
|
|
632
|
+
def search(
|
|
633
|
+
self,
|
|
634
|
+
query: str,
|
|
635
|
+
providers: Optional[List[str]] = None,
|
|
636
|
+
limit: int = 10,
|
|
637
|
+
enable_fusion: Optional[bool] = None,
|
|
638
|
+
enable_enhancement: Optional[bool] = None,
|
|
639
|
+
fusion_strategy: str = "best_quality",
|
|
640
|
+
search_options: Optional[Dict[str, Any]] = None,
|
|
641
|
+
) -> Dict[str, Any]:
|
|
642
|
+
"""
|
|
643
|
+
Search across multiple API providers with intelligent fusion and enhancement.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
query: Search query text to find relevant data
|
|
647
|
+
providers: List of provider names to search (searches all if not specified)
|
|
648
|
+
limit: Maximum number of results to return per provider
|
|
649
|
+
enable_fusion: Override config for data fusion (defaults to config value)
|
|
650
|
+
enable_enhancement: Override config for search enhancement (defaults to config value)
|
|
651
|
+
fusion_strategy: Strategy for data fusion ('best_quality', 'merge_all', 'consensus')
|
|
652
|
+
search_options: Options for search enhancement:
|
|
653
|
+
- relevance_threshold: Minimum relevance score (0-1)
|
|
654
|
+
- sort_by: Sort method ('relevance', 'popularity', 'recency', 'composite')
|
|
655
|
+
- max_results: Maximum results after enhancement
|
|
656
|
+
|
|
657
|
+
Returns:
|
|
658
|
+
Dictionary with:
|
|
659
|
+
- results: Enhanced and potentially fused search results
|
|
660
|
+
- metadata: Search metadata including fusion info and query analysis
|
|
661
|
+
- providers_queried: List of providers that were queried
|
|
662
|
+
"""
|
|
663
|
+
if providers is None:
|
|
664
|
+
providers = list(self._providers.keys())
|
|
665
|
+
|
|
666
|
+
# Analyze query intent
|
|
667
|
+
intent_analysis = self.query_analyzer.analyze_intent(query)
|
|
668
|
+
self.logger.info(f"Query intent: {intent_analysis['intent_type']} " f"(confidence: {intent_analysis['confidence']:.2f})")
|
|
669
|
+
|
|
670
|
+
# Get provider suggestions from intent analysis
|
|
671
|
+
if intent_analysis.get("suggested_providers"):
|
|
672
|
+
suggested = [p for p in intent_analysis["suggested_providers"] if p in self._providers]
|
|
673
|
+
if suggested:
|
|
674
|
+
providers = suggested
|
|
675
|
+
self.logger.debug(f"Using suggested providers: {providers}")
|
|
676
|
+
|
|
677
|
+
results = []
|
|
678
|
+
providers_queried = []
|
|
679
|
+
|
|
680
|
+
for provider_name in providers:
|
|
681
|
+
if provider_name not in self._providers:
|
|
682
|
+
self.logger.warning(f"Skipping unknown provider: {provider_name}")
|
|
683
|
+
continue
|
|
684
|
+
|
|
685
|
+
try:
|
|
686
|
+
provider_instance = self._providers[provider_name]
|
|
687
|
+
|
|
688
|
+
# Enhance query for provider if enabled
|
|
689
|
+
enhanced_query = query
|
|
690
|
+
if self.config.enable_query_enhancement:
|
|
691
|
+
enhanced_query = self.query_enhancer.enhance_query_text(query, provider_name)
|
|
692
|
+
|
|
693
|
+
# Try provider-specific search operations
|
|
694
|
+
if provider_name == "fred":
|
|
695
|
+
result = provider_instance.execute(
|
|
696
|
+
"search_series",
|
|
697
|
+
{"search_text": enhanced_query, "limit": limit},
|
|
698
|
+
)
|
|
699
|
+
elif provider_name == "worldbank":
|
|
700
|
+
result = provider_instance.execute(
|
|
701
|
+
"search_indicators",
|
|
702
|
+
{"search_text": enhanced_query, "limit": limit},
|
|
703
|
+
)
|
|
704
|
+
elif provider_name == "newsapi":
|
|
705
|
+
result = provider_instance.execute(
|
|
706
|
+
"search_everything",
|
|
707
|
+
{"q": enhanced_query, "page_size": limit},
|
|
708
|
+
)
|
|
709
|
+
else:
|
|
710
|
+
# Skip providers without search capability
|
|
711
|
+
continue
|
|
712
|
+
|
|
713
|
+
results.append(result)
|
|
714
|
+
providers_queried.append(provider_name)
|
|
715
|
+
|
|
716
|
+
except Exception as e:
|
|
717
|
+
self.logger.warning(f"Search failed for provider {provider_name}: {e}")
|
|
718
|
+
# Continue with other providers
|
|
719
|
+
|
|
720
|
+
if not results:
|
|
721
|
+
return {
|
|
722
|
+
"results": [],
|
|
723
|
+
"metadata": {
|
|
724
|
+
"query": query,
|
|
725
|
+
"intent_analysis": intent_analysis,
|
|
726
|
+
"providers_queried": providers_queried,
|
|
727
|
+
"total_results": 0,
|
|
728
|
+
},
|
|
729
|
+
"providers_queried": providers_queried,
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
# Apply data fusion if enabled
|
|
733
|
+
use_fusion = enable_fusion if enable_fusion is not None else self.config.enable_data_fusion
|
|
734
|
+
|
|
735
|
+
if use_fusion and len(results) > 1:
|
|
736
|
+
fused_result = self.data_fusion.fuse_multi_provider_results(results, fusion_strategy)
|
|
737
|
+
final_data = fused_result.get("data", []) if fused_result else []
|
|
738
|
+
else:
|
|
739
|
+
# Use single result or merge without fusion logic
|
|
740
|
+
if len(results) == 1:
|
|
741
|
+
final_data = results[0].get("data", [])
|
|
742
|
+
else:
|
|
743
|
+
# Simple merge
|
|
744
|
+
final_data = []
|
|
745
|
+
for result in results:
|
|
746
|
+
data = result.get("data", [])
|
|
747
|
+
if isinstance(data, list):
|
|
748
|
+
final_data.extend(data)
|
|
749
|
+
|
|
750
|
+
# Apply search enhancement if enabled
|
|
751
|
+
use_enhancement = enable_enhancement if enable_enhancement is not None else True # Always enhance search results
|
|
752
|
+
|
|
753
|
+
if use_enhancement and isinstance(final_data, list):
|
|
754
|
+
search_opts = search_options or {}
|
|
755
|
+
enhanced_results = self.search_enhancer.enhance_search_results(query, final_data, search_opts)
|
|
756
|
+
final_data = enhanced_results
|
|
757
|
+
|
|
758
|
+
# Build response
|
|
759
|
+
return {
|
|
760
|
+
"results": final_data,
|
|
761
|
+
"metadata": {
|
|
762
|
+
"query": query,
|
|
763
|
+
"intent_analysis": intent_analysis,
|
|
764
|
+
"providers_queried": providers_queried,
|
|
765
|
+
"total_results": (len(final_data) if isinstance(final_data, list) else 1),
|
|
766
|
+
"fusion_applied": use_fusion and len(results) > 1,
|
|
767
|
+
"fusion_strategy": fusion_strategy if use_fusion else None,
|
|
768
|
+
"enhancement_applied": use_enhancement,
|
|
769
|
+
},
|
|
770
|
+
"providers_queried": providers_queried,
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
def get_metrics_report(self) -> Dict[str, Any]:
|
|
774
|
+
"""
|
|
775
|
+
Get comprehensive metrics report from all providers.
|
|
776
|
+
|
|
777
|
+
Returns:
|
|
778
|
+
Dictionary with metrics from all providers and fallback statistics
|
|
779
|
+
"""
|
|
780
|
+
report: Dict[str, Any] = {
|
|
781
|
+
"providers": {},
|
|
782
|
+
"fallback_stats": self.fallback_strategy.get_fallback_stats(),
|
|
783
|
+
"total_providers": len(self._providers),
|
|
784
|
+
"healthy_providers": 0,
|
|
785
|
+
"degraded_providers": 0,
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
for provider_name, provider_instance in self._providers.items():
|
|
789
|
+
try:
|
|
790
|
+
provider_metadata = provider_instance.get_metadata()
|
|
791
|
+
health_score = provider_metadata.get("health", {}).get("score", 0)
|
|
792
|
+
|
|
793
|
+
report["providers"][provider_name] = {
|
|
794
|
+
"health": provider_metadata.get("health", {}),
|
|
795
|
+
"stats": provider_metadata.get("stats", {}),
|
|
796
|
+
"config": provider_metadata.get("config", {}),
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if health_score > 0.7:
|
|
800
|
+
healthy = report.get("healthy_providers", 0)
|
|
801
|
+
if isinstance(healthy, (int, float)):
|
|
802
|
+
report["healthy_providers"] = healthy + 1
|
|
803
|
+
else:
|
|
804
|
+
degraded = report.get("degraded_providers", 0)
|
|
805
|
+
if isinstance(degraded, (int, float)):
|
|
806
|
+
report["degraded_providers"] = degraded + 1
|
|
807
|
+
|
|
808
|
+
except Exception as e:
|
|
809
|
+
self.logger.warning(f"Failed to get metrics for {provider_name}: {e}")
|
|
810
|
+
report["providers"][provider_name] = {
|
|
811
|
+
"error": str(e),
|
|
812
|
+
"status": "unavailable",
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
# Add overall health assessment
|
|
816
|
+
total = report.get("total_providers", 0)
|
|
817
|
+
healthy = report.get("healthy_providers", 0)
|
|
818
|
+
if isinstance(total, (int, float)) and isinstance(healthy, (int, float)) and total > 0:
|
|
819
|
+
health_ratio = healthy / total
|
|
820
|
+
if health_ratio >= 0.8:
|
|
821
|
+
report["overall_status"] = "healthy"
|
|
822
|
+
elif health_ratio >= 0.5:
|
|
823
|
+
report["overall_status"] = "degraded"
|
|
824
|
+
else:
|
|
825
|
+
report["overall_status"] = "unhealthy"
|
|
826
|
+
else:
|
|
827
|
+
report["overall_status"] = "no_providers"
|
|
828
|
+
|
|
829
|
+
return report
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
# Register the tool (done via decorator)
|