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