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.

Files changed (340) hide show
  1. aiecs/__init__.py +13 -16
  2. aiecs/__main__.py +7 -7
  3. aiecs/aiecs_client.py +269 -75
  4. aiecs/application/executors/operation_executor.py +79 -54
  5. aiecs/application/knowledge_graph/__init__.py +7 -0
  6. aiecs/application/knowledge_graph/builder/__init__.py +37 -0
  7. aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
  8. aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
  9. aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
  10. aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
  11. aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
  12. aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
  13. aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
  14. aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
  15. aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
  16. aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
  17. aiecs/application/knowledge_graph/extractors/base.py +98 -0
  18. aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
  19. aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
  20. aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
  21. aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
  22. aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
  23. aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
  24. aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
  25. aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
  26. aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
  27. aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
  28. aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
  29. aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
  30. aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
  31. aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
  32. aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
  33. aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
  34. aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
  35. aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
  36. aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
  37. aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
  38. aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
  39. aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
  40. aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
  41. aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
  42. aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
  43. aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
  44. aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
  45. aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
  46. aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
  47. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
  48. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
  49. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
  50. aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
  51. aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
  52. aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
  53. aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
  54. aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
  55. aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
  56. aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
  57. aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
  58. aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
  59. aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
  60. aiecs/application/knowledge_graph/search/__init__.py +59 -0
  61. aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
  62. aiecs/application/knowledge_graph/search/reranker.py +293 -0
  63. aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
  64. aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
  65. aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
  66. aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
  67. aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
  68. aiecs/application/knowledge_graph/validators/__init__.py +13 -0
  69. aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
  70. aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
  71. aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
  72. aiecs/common/__init__.py +9 -0
  73. aiecs/common/knowledge_graph/__init__.py +17 -0
  74. aiecs/common/knowledge_graph/runnable.py +471 -0
  75. aiecs/config/__init__.py +20 -5
  76. aiecs/config/config.py +762 -31
  77. aiecs/config/graph_config.py +131 -0
  78. aiecs/config/tool_config.py +399 -0
  79. aiecs/core/__init__.py +29 -13
  80. aiecs/core/interface/__init__.py +2 -2
  81. aiecs/core/interface/execution_interface.py +22 -22
  82. aiecs/core/interface/storage_interface.py +37 -88
  83. aiecs/core/registry/__init__.py +31 -0
  84. aiecs/core/registry/service_registry.py +92 -0
  85. aiecs/domain/__init__.py +270 -1
  86. aiecs/domain/agent/__init__.py +191 -0
  87. aiecs/domain/agent/base_agent.py +3870 -0
  88. aiecs/domain/agent/exceptions.py +99 -0
  89. aiecs/domain/agent/graph_aware_mixin.py +569 -0
  90. aiecs/domain/agent/hybrid_agent.py +1435 -0
  91. aiecs/domain/agent/integration/__init__.py +29 -0
  92. aiecs/domain/agent/integration/context_compressor.py +216 -0
  93. aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
  94. aiecs/domain/agent/integration/protocols.py +281 -0
  95. aiecs/domain/agent/integration/retry_policy.py +218 -0
  96. aiecs/domain/agent/integration/role_config.py +213 -0
  97. aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
  98. aiecs/domain/agent/lifecycle.py +291 -0
  99. aiecs/domain/agent/llm_agent.py +692 -0
  100. aiecs/domain/agent/memory/__init__.py +12 -0
  101. aiecs/domain/agent/memory/conversation.py +1124 -0
  102. aiecs/domain/agent/migration/__init__.py +14 -0
  103. aiecs/domain/agent/migration/conversion.py +163 -0
  104. aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
  105. aiecs/domain/agent/models.py +884 -0
  106. aiecs/domain/agent/observability.py +479 -0
  107. aiecs/domain/agent/persistence.py +449 -0
  108. aiecs/domain/agent/prompts/__init__.py +29 -0
  109. aiecs/domain/agent/prompts/builder.py +159 -0
  110. aiecs/domain/agent/prompts/formatters.py +187 -0
  111. aiecs/domain/agent/prompts/template.py +255 -0
  112. aiecs/domain/agent/registry.py +253 -0
  113. aiecs/domain/agent/tool_agent.py +444 -0
  114. aiecs/domain/agent/tools/__init__.py +15 -0
  115. aiecs/domain/agent/tools/schema_generator.py +364 -0
  116. aiecs/domain/community/__init__.py +155 -0
  117. aiecs/domain/community/agent_adapter.py +469 -0
  118. aiecs/domain/community/analytics.py +432 -0
  119. aiecs/domain/community/collaborative_workflow.py +648 -0
  120. aiecs/domain/community/communication_hub.py +634 -0
  121. aiecs/domain/community/community_builder.py +320 -0
  122. aiecs/domain/community/community_integration.py +796 -0
  123. aiecs/domain/community/community_manager.py +803 -0
  124. aiecs/domain/community/decision_engine.py +849 -0
  125. aiecs/domain/community/exceptions.py +231 -0
  126. aiecs/domain/community/models/__init__.py +33 -0
  127. aiecs/domain/community/models/community_models.py +234 -0
  128. aiecs/domain/community/resource_manager.py +461 -0
  129. aiecs/domain/community/shared_context_manager.py +589 -0
  130. aiecs/domain/context/__init__.py +40 -10
  131. aiecs/domain/context/context_engine.py +1910 -0
  132. aiecs/domain/context/conversation_models.py +87 -53
  133. aiecs/domain/context/graph_memory.py +582 -0
  134. aiecs/domain/execution/model.py +12 -4
  135. aiecs/domain/knowledge_graph/__init__.py +19 -0
  136. aiecs/domain/knowledge_graph/models/__init__.py +52 -0
  137. aiecs/domain/knowledge_graph/models/entity.py +148 -0
  138. aiecs/domain/knowledge_graph/models/evidence.py +178 -0
  139. aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
  140. aiecs/domain/knowledge_graph/models/path.py +171 -0
  141. aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
  142. aiecs/domain/knowledge_graph/models/query.py +261 -0
  143. aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
  144. aiecs/domain/knowledge_graph/models/relation.py +202 -0
  145. aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
  146. aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
  147. aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
  148. aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
  149. aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
  150. aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
  151. aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
  152. aiecs/domain/task/dsl_processor.py +172 -56
  153. aiecs/domain/task/model.py +20 -8
  154. aiecs/domain/task/task_context.py +27 -24
  155. aiecs/infrastructure/__init__.py +0 -2
  156. aiecs/infrastructure/graph_storage/__init__.py +11 -0
  157. aiecs/infrastructure/graph_storage/base.py +837 -0
  158. aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
  159. aiecs/infrastructure/graph_storage/cache.py +424 -0
  160. aiecs/infrastructure/graph_storage/distributed.py +223 -0
  161. aiecs/infrastructure/graph_storage/error_handling.py +380 -0
  162. aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
  163. aiecs/infrastructure/graph_storage/health_checks.py +378 -0
  164. aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
  165. aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
  166. aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
  167. aiecs/infrastructure/graph_storage/metrics.py +344 -0
  168. aiecs/infrastructure/graph_storage/migration.py +400 -0
  169. aiecs/infrastructure/graph_storage/pagination.py +483 -0
  170. aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
  171. aiecs/infrastructure/graph_storage/postgres.py +1563 -0
  172. aiecs/infrastructure/graph_storage/property_storage.py +353 -0
  173. aiecs/infrastructure/graph_storage/protocols.py +76 -0
  174. aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
  175. aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
  176. aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
  177. aiecs/infrastructure/graph_storage/streaming.py +487 -0
  178. aiecs/infrastructure/graph_storage/tenant.py +412 -0
  179. aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
  180. aiecs/infrastructure/messaging/websocket_manager.py +51 -35
  181. aiecs/infrastructure/monitoring/__init__.py +22 -0
  182. aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
  183. aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
  184. aiecs/infrastructure/monitoring/structured_logger.py +3 -7
  185. aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
  186. aiecs/infrastructure/persistence/__init__.py +14 -1
  187. aiecs/infrastructure/persistence/context_engine_client.py +184 -0
  188. aiecs/infrastructure/persistence/database_manager.py +67 -43
  189. aiecs/infrastructure/persistence/file_storage.py +180 -103
  190. aiecs/infrastructure/persistence/redis_client.py +74 -21
  191. aiecs/llm/__init__.py +73 -25
  192. aiecs/llm/callbacks/__init__.py +11 -0
  193. aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
  194. aiecs/llm/client_factory.py +224 -36
  195. aiecs/llm/client_resolver.py +155 -0
  196. aiecs/llm/clients/__init__.py +38 -0
  197. aiecs/llm/clients/base_client.py +324 -0
  198. aiecs/llm/clients/google_function_calling_mixin.py +457 -0
  199. aiecs/llm/clients/googleai_client.py +241 -0
  200. aiecs/llm/clients/openai_client.py +158 -0
  201. aiecs/llm/clients/openai_compatible_mixin.py +367 -0
  202. aiecs/llm/clients/vertex_client.py +897 -0
  203. aiecs/llm/clients/xai_client.py +201 -0
  204. aiecs/llm/config/__init__.py +51 -0
  205. aiecs/llm/config/config_loader.py +272 -0
  206. aiecs/llm/config/config_validator.py +206 -0
  207. aiecs/llm/config/model_config.py +143 -0
  208. aiecs/llm/protocols.py +149 -0
  209. aiecs/llm/utils/__init__.py +10 -0
  210. aiecs/llm/utils/validate_config.py +89 -0
  211. aiecs/main.py +140 -121
  212. aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
  213. aiecs/scripts/aid/__init__.py +19 -0
  214. aiecs/scripts/aid/module_checker.py +499 -0
  215. aiecs/scripts/aid/version_manager.py +235 -0
  216. aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
  217. aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
  218. aiecs/scripts/dependance_check/__init__.py +15 -0
  219. aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
  220. aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
  221. aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
  222. aiecs/scripts/dependance_patch/__init__.py +7 -0
  223. aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
  224. aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
  225. aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
  226. aiecs/scripts/knowledge_graph/__init__.py +3 -0
  227. aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
  228. aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
  229. aiecs/scripts/tools_develop/README.md +671 -0
  230. aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
  231. aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
  232. aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
  233. aiecs/scripts/tools_develop/__init__.py +21 -0
  234. aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
  235. aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
  236. aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
  237. aiecs/scripts/tools_develop/schema_coverage.py +511 -0
  238. aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
  239. aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
  240. aiecs/scripts/tools_develop/verify_tools.py +352 -0
  241. aiecs/tasks/__init__.py +0 -1
  242. aiecs/tasks/worker.py +115 -47
  243. aiecs/tools/__init__.py +194 -72
  244. aiecs/tools/apisource/__init__.py +99 -0
  245. aiecs/tools/apisource/intelligence/__init__.py +19 -0
  246. aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
  247. aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
  248. aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
  249. aiecs/tools/apisource/monitoring/__init__.py +9 -0
  250. aiecs/tools/apisource/monitoring/metrics.py +330 -0
  251. aiecs/tools/apisource/providers/__init__.py +112 -0
  252. aiecs/tools/apisource/providers/base.py +671 -0
  253. aiecs/tools/apisource/providers/census.py +397 -0
  254. aiecs/tools/apisource/providers/fred.py +535 -0
  255. aiecs/tools/apisource/providers/newsapi.py +409 -0
  256. aiecs/tools/apisource/providers/worldbank.py +352 -0
  257. aiecs/tools/apisource/reliability/__init__.py +12 -0
  258. aiecs/tools/apisource/reliability/error_handler.py +363 -0
  259. aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
  260. aiecs/tools/apisource/tool.py +832 -0
  261. aiecs/tools/apisource/utils/__init__.py +9 -0
  262. aiecs/tools/apisource/utils/validators.py +334 -0
  263. aiecs/tools/base_tool.py +415 -21
  264. aiecs/tools/docs/__init__.py +121 -0
  265. aiecs/tools/docs/ai_document_orchestrator.py +607 -0
  266. aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
  267. aiecs/tools/docs/content_insertion_tool.py +1320 -0
  268. aiecs/tools/docs/document_creator_tool.py +1323 -0
  269. aiecs/tools/docs/document_layout_tool.py +1160 -0
  270. aiecs/tools/docs/document_parser_tool.py +1011 -0
  271. aiecs/tools/docs/document_writer_tool.py +1829 -0
  272. aiecs/tools/knowledge_graph/__init__.py +17 -0
  273. aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
  274. aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
  275. aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
  276. aiecs/tools/langchain_adapter.py +300 -138
  277. aiecs/tools/schema_generator.py +455 -0
  278. aiecs/tools/search_tool/__init__.py +100 -0
  279. aiecs/tools/search_tool/analyzers.py +581 -0
  280. aiecs/tools/search_tool/cache.py +264 -0
  281. aiecs/tools/search_tool/constants.py +128 -0
  282. aiecs/tools/search_tool/context.py +224 -0
  283. aiecs/tools/search_tool/core.py +778 -0
  284. aiecs/tools/search_tool/deduplicator.py +119 -0
  285. aiecs/tools/search_tool/error_handler.py +242 -0
  286. aiecs/tools/search_tool/metrics.py +343 -0
  287. aiecs/tools/search_tool/rate_limiter.py +172 -0
  288. aiecs/tools/search_tool/schemas.py +275 -0
  289. aiecs/tools/statistics/__init__.py +80 -0
  290. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
  291. aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
  292. aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
  293. aiecs/tools/statistics/data_loader_tool.py +555 -0
  294. aiecs/tools/statistics/data_profiler_tool.py +638 -0
  295. aiecs/tools/statistics/data_transformer_tool.py +580 -0
  296. aiecs/tools/statistics/data_visualizer_tool.py +498 -0
  297. aiecs/tools/statistics/model_trainer_tool.py +507 -0
  298. aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
  299. aiecs/tools/task_tools/__init__.py +49 -36
  300. aiecs/tools/task_tools/chart_tool.py +200 -184
  301. aiecs/tools/task_tools/classfire_tool.py +268 -267
  302. aiecs/tools/task_tools/image_tool.py +175 -131
  303. aiecs/tools/task_tools/office_tool.py +226 -146
  304. aiecs/tools/task_tools/pandas_tool.py +477 -121
  305. aiecs/tools/task_tools/report_tool.py +390 -142
  306. aiecs/tools/task_tools/research_tool.py +149 -79
  307. aiecs/tools/task_tools/scraper_tool.py +339 -145
  308. aiecs/tools/task_tools/stats_tool.py +448 -209
  309. aiecs/tools/temp_file_manager.py +26 -24
  310. aiecs/tools/tool_executor/__init__.py +18 -16
  311. aiecs/tools/tool_executor/tool_executor.py +364 -52
  312. aiecs/utils/LLM_output_structor.py +74 -48
  313. aiecs/utils/__init__.py +14 -3
  314. aiecs/utils/base_callback.py +0 -3
  315. aiecs/utils/cache_provider.py +696 -0
  316. aiecs/utils/execution_utils.py +50 -31
  317. aiecs/utils/prompt_loader.py +1 -0
  318. aiecs/utils/token_usage_repository.py +37 -11
  319. aiecs/ws/socket_server.py +14 -4
  320. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
  321. aiecs-1.7.6.dist-info/RECORD +337 -0
  322. aiecs-1.7.6.dist-info/entry_points.txt +13 -0
  323. aiecs/config/registry.py +0 -19
  324. aiecs/domain/context/content_engine.py +0 -982
  325. aiecs/llm/base_client.py +0 -99
  326. aiecs/llm/openai_client.py +0 -125
  327. aiecs/llm/vertex_client.py +0 -186
  328. aiecs/llm/xai_client.py +0 -184
  329. aiecs/scripts/dependency_checker.py +0 -857
  330. aiecs/scripts/quick_dependency_check.py +0 -269
  331. aiecs/tools/task_tools/search_api.py +0 -7
  332. aiecs-1.0.1.dist-info/RECORD +0 -90
  333. aiecs-1.0.1.dist-info/entry_points.txt +0 -7
  334. /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
  335. /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
  336. /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
  337. /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
  338. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
  339. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
  340. {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)