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,671 @@
1
+ """
2
+ Base API Provider Interface
3
+
4
+ Abstract base class for all API data source providers in the API Source Tool.
5
+ Provides common functionality for rate limiting, caching, error handling, and metadata.
6
+
7
+ Enhanced with:
8
+ - Detailed metrics and health monitoring
9
+ - Smart error handling with retries
10
+ - Data quality assessment
11
+ - Comprehensive metadata with quality scores
12
+ - Operation exposure for AI agent visibility
13
+ """
14
+
15
+ import logging
16
+ import time
17
+ from abc import ABC, abstractmethod
18
+ from datetime import datetime
19
+ from threading import Lock
20
+ from typing import Any, Dict, List, Optional, Tuple
21
+
22
+ from aiecs.tools.apisource.monitoring.metrics import DetailedMetrics
23
+ from aiecs.tools.apisource.reliability.error_handler import SmartErrorHandler
24
+ from aiecs.tools.apisource.utils.validators import DataValidator
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ def expose_operation(operation_name: str, description: str):
30
+ """
31
+ Decorator: Mark provider operations that should be exposed to AI agents.
32
+
33
+ This decorator allows provider operations to be automatically discovered by the
34
+ LangChain adapter and exposed as individual tools to AI agents, providing
35
+ fine-grained visibility into provider capabilities.
36
+
37
+ Args:
38
+ operation_name: The name of the operation (e.g., 'get_series_observations')
39
+ description: Human-readable description of what the operation does
40
+
41
+ Returns:
42
+ Decorated function with metadata for operation discovery
43
+
44
+ Example:
45
+ @expose_operation(
46
+ operation_name='get_series_observations',
47
+ description='Get FRED economic time series data'
48
+ )
49
+ def get_series_observations(self, series_id: str, ...):
50
+ pass
51
+ """
52
+
53
+ def decorator(func):
54
+ func._exposed_operation = True
55
+ func._operation_name = operation_name
56
+ func._operation_description = description
57
+ return func
58
+
59
+ return decorator
60
+
61
+
62
+ class RateLimiter:
63
+ """Token bucket rate limiter for API requests"""
64
+
65
+ def __init__(self, tokens_per_second: float = 1.0, max_tokens: int = 10):
66
+ """
67
+ Initialize rate limiter with token bucket algorithm.
68
+
69
+ Args:
70
+ tokens_per_second: Rate at which tokens are added to the bucket
71
+ max_tokens: Maximum number of tokens the bucket can hold
72
+ """
73
+ self.tokens_per_second = tokens_per_second
74
+ self.max_tokens = max_tokens
75
+ self.tokens = max_tokens
76
+ self.last_update = time.time()
77
+ self.lock = Lock()
78
+
79
+ def acquire(self, tokens: int = 1) -> bool:
80
+ """
81
+ Acquire tokens from the bucket.
82
+
83
+ Args:
84
+ tokens: Number of tokens to acquire
85
+
86
+ Returns:
87
+ True if tokens were acquired, False otherwise
88
+ """
89
+ with self.lock:
90
+ now = time.time()
91
+ elapsed = now - self.last_update
92
+
93
+ # Add new tokens based on elapsed time
94
+ self.tokens = int(min(self.max_tokens, self.tokens + elapsed * self.tokens_per_second))
95
+ self.last_update = now
96
+
97
+ if self.tokens >= tokens:
98
+ self.tokens -= tokens
99
+ return True
100
+ return False
101
+
102
+ def wait(self, tokens: int = 1, timeout: float = 30.0) -> bool:
103
+ """
104
+ Wait until tokens are available.
105
+
106
+ Args:
107
+ tokens: Number of tokens to acquire
108
+ timeout: Maximum time to wait in seconds
109
+
110
+ Returns:
111
+ True if tokens were acquired, False if timeout
112
+ """
113
+ start_time = time.time()
114
+ while time.time() - start_time < timeout:
115
+ if self.acquire(tokens):
116
+ return True
117
+ time.sleep(0.1)
118
+ return False
119
+
120
+
121
+ class BaseAPIProvider(ABC):
122
+ """
123
+ Abstract base class for all API data source providers.
124
+
125
+ Provides:
126
+ - Rate limiting with token bucket algorithm
127
+ - Standardized error handling
128
+ - Metadata about provider capabilities
129
+ - Parameter validation
130
+ - Response formatting
131
+ """
132
+
133
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
134
+ """
135
+ Initialize the API provider.
136
+
137
+ Args:
138
+ config: Configuration dictionary with API keys, rate limits, etc.
139
+ """
140
+ self.config = config or {}
141
+ self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
142
+
143
+ # Initialize rate limiter
144
+ rate_limit = self.config.get("rate_limit", 10) # requests per second
145
+ max_burst = self.config.get("max_burst", 20)
146
+ self.rate_limiter = RateLimiter(tokens_per_second=rate_limit, max_tokens=max_burst)
147
+
148
+ # Initialize detailed metrics
149
+ self.metrics = DetailedMetrics(max_response_times=100)
150
+
151
+ # Initialize smart error handler
152
+ self.error_handler = SmartErrorHandler(
153
+ max_retries=self.config.get("max_retries", 3),
154
+ backoff_factor=self.config.get("backoff_factor", 2.0),
155
+ initial_delay=self.config.get("initial_delay", 1.0),
156
+ max_delay=self.config.get("max_delay", 30.0),
157
+ )
158
+
159
+ # Initialize data validator
160
+ self.validator = DataValidator()
161
+
162
+ # Legacy stats for backwards compatibility
163
+ self.stats: Dict[str, Any] = {
164
+ "total_requests": 0,
165
+ "successful_requests": 0,
166
+ "failed_requests": 0,
167
+ "last_request_time": None,
168
+ }
169
+ self.stats_lock = Lock()
170
+
171
+ @property
172
+ @abstractmethod
173
+ def name(self) -> str:
174
+ """Provider name (e.g., 'fred', 'worldbank')"""
175
+
176
+ @property
177
+ @abstractmethod
178
+ def description(self) -> str:
179
+ """Human-readable description of the provider"""
180
+
181
+ @property
182
+ @abstractmethod
183
+ def supported_operations(self) -> List[str]:
184
+ """List of supported operation names"""
185
+
186
+ @abstractmethod
187
+ def validate_params(self, operation: str, params: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
188
+ """
189
+ Validate parameters for a specific operation.
190
+
191
+ Args:
192
+ operation: Operation name
193
+ params: Parameters to validate
194
+
195
+ Returns:
196
+ Tuple of (is_valid, error_message)
197
+ """
198
+
199
+ @abstractmethod
200
+ def fetch(self, operation: str, params: Dict[str, Any]) -> Dict[str, Any]:
201
+ """
202
+ Fetch data from the API.
203
+
204
+ Args:
205
+ operation: Operation to perform
206
+ params: Operation parameters
207
+
208
+ Returns:
209
+ Response data in standardized format
210
+
211
+ Raises:
212
+ ValueError: If operation is not supported
213
+ Exception: If API request fails
214
+ """
215
+
216
+ def get_metadata(self) -> Dict[str, Any]:
217
+ """
218
+ Get provider metadata including health status and detailed metrics.
219
+
220
+ Returns:
221
+ Dictionary with comprehensive provider information
222
+ """
223
+ return {
224
+ "name": self.name,
225
+ "description": self.description,
226
+ "operations": self.supported_operations,
227
+ "stats": self.metrics.get_summary(), # Use detailed metrics
228
+ "health": {
229
+ "score": self.metrics.get_health_score(),
230
+ "status": ("healthy" if self.metrics.get_health_score() > 0.7 else "degraded"),
231
+ },
232
+ "config": {
233
+ "rate_limit": self.config.get("rate_limit", 10),
234
+ "timeout": self.config.get("timeout", 30),
235
+ "max_retries": self.config.get("max_retries", 3),
236
+ },
237
+ }
238
+
239
+ def get_operation_schema(self, operation: str) -> Optional[Dict[str, Any]]:
240
+ """
241
+ Get schema for a specific operation.
242
+
243
+ Args:
244
+ operation: Operation name
245
+
246
+ Returns:
247
+ Schema dictionary or None if not available
248
+ """
249
+ # Override in subclass to provide operation-specific schemas
250
+ return None
251
+
252
+ @classmethod
253
+ def get_exposed_operations(cls) -> List[Dict[str, Any]]:
254
+ """
255
+ Get all operations that are exposed to AI agents via the @expose_operation decorator.
256
+
257
+ This method discovers all methods decorated with @expose_operation and returns
258
+ their metadata along with their schemas. This enables the LangChain adapter to
259
+ automatically create individual tools for each provider operation.
260
+
261
+ Returns:
262
+ List of operation dictionaries, each containing:
263
+ - name: Operation name
264
+ - description: Operation description
265
+ - schema: Operation schema (parameters, types, descriptions)
266
+ - method_name: The actual method name on the class
267
+
268
+ Example:
269
+ >>> FREDProvider.get_exposed_operations()
270
+ [
271
+ {
272
+ 'name': 'get_series_observations',
273
+ 'description': 'Get FRED economic time series data',
274
+ 'schema': {...},
275
+ 'method_name': 'get_series_observations'
276
+ },
277
+ ...
278
+ ]
279
+ """
280
+ operations = []
281
+
282
+ # Create a temporary instance to access get_operation_schema
283
+ # We need this because get_operation_schema might be an instance method
284
+ try:
285
+ # Try to get schema without instantiation first
286
+ for attr_name in dir(cls):
287
+ # Skip private and special methods
288
+ if attr_name.startswith("_"):
289
+ continue
290
+
291
+ try:
292
+ attr = getattr(cls, attr_name)
293
+ except AttributeError:
294
+ continue
295
+
296
+ # Check if this is an exposed operation
297
+ if callable(attr) and hasattr(attr, "_exposed_operation"):
298
+ operation_name = attr._operation_name
299
+ operation_description = attr._operation_description
300
+
301
+ # Schema retrieval requires an instance, so skip at class level
302
+ # Schema will be available at runtime when provider instances are created
303
+ schema = None
304
+
305
+ operations.append(
306
+ {
307
+ "name": operation_name,
308
+ "description": operation_description,
309
+ "schema": schema,
310
+ "method_name": attr_name,
311
+ }
312
+ )
313
+
314
+ logger.debug(f"Discovered exposed operation: {operation_name} from {cls.__name__}")
315
+
316
+ except Exception as e:
317
+ logger.warning(f"Error discovering exposed operations for {cls.__name__}: {e}")
318
+
319
+ return operations
320
+
321
+ def validate_and_clean_data(self, operation: str, raw_data: Any) -> Dict[str, Any]:
322
+ """
323
+ Validate and clean data (optional, override in subclass).
324
+
325
+ Providers can implement custom validation logic for their specific data formats.
326
+
327
+ Args:
328
+ operation: Operation that produced the data
329
+ raw_data: Raw data from API
330
+
331
+ Returns:
332
+ Dictionary with:
333
+ - data: Cleaned data
334
+ - validation_warnings: List of warnings
335
+ - statistics: Data quality statistics
336
+ """
337
+ # Default implementation: no validation
338
+ return {"data": raw_data, "validation_warnings": [], "statistics": {}}
339
+
340
+ def calculate_data_quality(self, operation: str, data: Any, response_time_ms: float) -> Dict[str, Any]:
341
+ """
342
+ Calculate quality metadata for the response.
343
+
344
+ Can be overridden by providers for custom quality assessment.
345
+
346
+ Args:
347
+ operation: Operation performed
348
+ data: Response data
349
+ response_time_ms: Response time in milliseconds
350
+
351
+ Returns:
352
+ Quality metadata dictionary
353
+ """
354
+ quality: Dict[str, Any] = {
355
+ "score": 0.7, # Default quality score
356
+ "completeness": 1.0, # Assume complete unless validated otherwise
357
+ "freshness_hours": None, # Unknown freshness
358
+ "confidence": 0.8, # Default confidence
359
+ "authority_level": "verified", # Provider is verified
360
+ }
361
+
362
+ # Adjust score based on response time
363
+ if response_time_ms < 500:
364
+ score = quality.get("score", 0.7)
365
+ if isinstance(score, (int, float)):
366
+ quality["score"] = min(score + 0.1, 1.0)
367
+ elif response_time_ms > 5000:
368
+ score = quality.get("score", 0.7)
369
+ if isinstance(score, (int, float)):
370
+ quality["score"] = max(score - 0.1, 0.0)
371
+
372
+ # Check if data is empty
373
+ if data is None:
374
+ quality["completeness"] = 0.0
375
+ quality["score"] = 0.0
376
+ elif isinstance(data, list) and len(data) == 0:
377
+ quality["completeness"] = 0.0
378
+ score = quality.get("score", 0.7)
379
+ if isinstance(score, (int, float)):
380
+ quality["score"] = max(score - 0.3, 0.0)
381
+
382
+ return quality
383
+
384
+ def _update_stats(self, success: bool):
385
+ """Update request statistics"""
386
+ with self.stats_lock:
387
+ total = self.stats.get("total_requests", 0)
388
+ if isinstance(total, (int, float)):
389
+ self.stats["total_requests"] = total + 1
390
+ if success:
391
+ successful = self.stats.get("successful_requests", 0)
392
+ if isinstance(successful, (int, float)):
393
+ self.stats["successful_requests"] = successful + 1
394
+ else:
395
+ failed = self.stats.get("failed_requests", 0)
396
+ if isinstance(failed, (int, float)):
397
+ self.stats["failed_requests"] = failed + 1
398
+ self.stats["last_request_time"] = datetime.utcnow().isoformat()
399
+
400
+ def _format_response(
401
+ self,
402
+ operation: str,
403
+ data: Any,
404
+ source: Optional[str] = None,
405
+ response_time_ms: Optional[float] = None,
406
+ validation_result: Optional[Dict[str, Any]] = None,
407
+ ) -> Dict[str, Any]:
408
+ """
409
+ Format response in standardized format with enhanced metadata.
410
+
411
+ Args:
412
+ operation: Operation that was performed
413
+ data: Response data
414
+ source: Data source URL or identifier
415
+ response_time_ms: Response time in milliseconds
416
+ validation_result: Optional validation result from validate_and_clean_data
417
+
418
+ Returns:
419
+ Standardized response dictionary with comprehensive metadata
420
+ """
421
+ # Calculate quality metadata
422
+ quality = self.calculate_data_quality(operation, data, response_time_ms or 0)
423
+
424
+ # Calculate coverage information
425
+ coverage = self._calculate_coverage(data)
426
+
427
+ # Build metadata
428
+ metadata = {
429
+ "timestamp": datetime.utcnow().isoformat(),
430
+ "source": source or f"{self.name} API",
431
+ "quality": quality,
432
+ "coverage": coverage,
433
+ }
434
+
435
+ # Add API info if response time provided
436
+ if response_time_ms is not None:
437
+ metadata["api_info"] = {
438
+ "response_time_ms": round(response_time_ms, 2),
439
+ "provider": self.name,
440
+ }
441
+
442
+ # Add validation warnings if present
443
+ if validation_result and validation_result.get("validation_warnings"):
444
+ metadata["validation_warnings"] = validation_result["validation_warnings"]
445
+
446
+ # Add statistics if present
447
+ if validation_result and validation_result.get("statistics"):
448
+ metadata["statistics"] = validation_result["statistics"]
449
+
450
+ return {
451
+ "provider": self.name,
452
+ "operation": operation,
453
+ "data": data,
454
+ "metadata": metadata,
455
+ }
456
+
457
+ def _calculate_coverage(self, data: Any) -> Dict[str, Any]:
458
+ """
459
+ Calculate data coverage information.
460
+
461
+ Args:
462
+ data: Response data
463
+
464
+ Returns:
465
+ Coverage information dictionary
466
+ """
467
+ coverage: Dict[str, Any] = {}
468
+
469
+ # Calculate record count
470
+ if isinstance(data, list):
471
+ coverage["total_records"] = len(data)
472
+
473
+ # Try to extract date range from time series data
474
+ if len(data) > 0 and isinstance(data[0], dict):
475
+ date_fields = ["date", "observation_date", "timestamp"]
476
+ for date_field in date_fields:
477
+ if date_field in data[0]:
478
+ dates = [item.get(date_field) for item in data if date_field in item and item.get(date_field)]
479
+ if dates:
480
+ try:
481
+ # Sort to get earliest and latest
482
+ dates_sorted = sorted(dates)
483
+ coverage["start_date"] = dates_sorted[0]
484
+ coverage["end_date"] = dates_sorted[-1]
485
+
486
+ # Try to infer frequency
487
+ frequency = self.validator.infer_data_frequency(data, date_field)
488
+ if frequency:
489
+ coverage["frequency"] = frequency
490
+ except Exception:
491
+ pass
492
+ break
493
+ elif isinstance(data, dict):
494
+ # For dict responses
495
+ if "articles" in data:
496
+ coverage["total_records"] = len(data["articles"])
497
+ elif "total_results" in data:
498
+ coverage["total_results"] = data["total_results"]
499
+ else:
500
+ coverage["total_records"] = 1
501
+ else:
502
+ coverage["total_records"] = 1 if data is not None else 0
503
+
504
+ return coverage
505
+
506
+ def _get_api_key(self, key_name: Optional[str] = None) -> Optional[str]:
507
+ """
508
+ Get API key from config or environment.
509
+
510
+ Args:
511
+ key_name: Specific key name to retrieve
512
+
513
+ Returns:
514
+ API key or None if not found
515
+
516
+ Note: When used through APISourceTool, API keys are loaded from .env files
517
+ via BaseSettings and passed via config dict. This fallback is for backward
518
+ compatibility and independent provider usage.
519
+ """
520
+ import os
521
+
522
+ # Try config first (primary path - API keys come from APISourceTool's BaseSettings)
523
+ if "api_key" in self.config:
524
+ return self.config["api_key"]
525
+
526
+ # Fallback: Try environment variable (ensures .env files are loaded)
527
+ # Use ToolConfigLoader to ensure .env files are loaded if not already
528
+ try:
529
+ from aiecs.config.tool_config import get_tool_config_loader
530
+
531
+ loader = get_tool_config_loader()
532
+ loader.load_env_config() # Ensures .env files are loaded
533
+ except Exception:
534
+ # If loader is unavailable, try direct dotenv load as fallback
535
+ try:
536
+ from dotenv import load_dotenv
537
+ from pathlib import Path
538
+
539
+ # Try to load .env files from common locations
540
+ for env_file in [".env", ".env.local"]:
541
+ env_path = Path(env_file)
542
+ if env_path.exists():
543
+ load_dotenv(env_path, override=False)
544
+ break
545
+ except Exception:
546
+ pass # If dotenv is unavailable, continue with os.environ
547
+
548
+ # Try environment variable (now includes values from .env files)
549
+ env_var = key_name or f"{self.name.upper()}_API_KEY"
550
+ return os.environ.get(env_var)
551
+
552
+ def execute(self, operation: str, params: Dict[str, Any]) -> Dict[str, Any]:
553
+ """
554
+ Execute an operation with rate limiting, error handling, and metrics tracking.
555
+
556
+ Args:
557
+ operation: Operation to perform
558
+ params: Operation parameters
559
+
560
+ Returns:
561
+ Response data with enhanced metadata
562
+
563
+ Raises:
564
+ ValueError: If operation is invalid or parameters are invalid
565
+ Exception: If API request fails after all retries
566
+ """
567
+ # Validate operation
568
+ if operation not in self.supported_operations:
569
+ available_ops = ", ".join(self.supported_operations)
570
+ schema = self.get_operation_schema(operation)
571
+ operation_error_msg = f"Operation '{operation}' not supported by {self.name}.\n" f"Supported operations: {available_ops}"
572
+ if schema:
573
+ operation_error_msg += f"\nSee get_operation_schema('{operation}') for details"
574
+ raise ValueError(operation_error_msg)
575
+
576
+ # Validate parameters with enhanced error messages
577
+ validation_result = self.validate_params(operation, params)
578
+ is_valid: bool = validation_result[0]
579
+ error_msg: Optional[str] = validation_result[1]
580
+ if not is_valid:
581
+ schema = self.get_operation_schema(operation)
582
+ enhanced_error = f"Invalid parameters for {self.name}.{operation}: {error_msg or 'Unknown error'}"
583
+
584
+ if schema and "parameters" in schema:
585
+ # Add helpful parameter information
586
+ required_params = [name for name, info in schema["parameters"].items() if info.get("required", False)]
587
+ if required_params:
588
+ enhanced_error += f"\nRequired parameters: {', '.join(required_params)}"
589
+
590
+ # Add examples if available
591
+ if "examples" in schema and schema["examples"]:
592
+ example = schema["examples"][0]
593
+ enhanced_error += f"\nExample: {example.get('params', {})}"
594
+
595
+ raise ValueError(enhanced_error)
596
+
597
+ # Apply rate limiting
598
+ wait_start = time.time()
599
+ if not self.rate_limiter.wait(tokens=1, timeout=30):
600
+ self.metrics.record_request(success=False, response_time_ms=0, error_type="rate_limit")
601
+ raise Exception(f"Rate limit exceeded for {self.name}. " "Please try again later or increase rate limits in config.")
602
+
603
+ # Track rate limit wait time
604
+ wait_time_ms = (time.time() - wait_start) * 1000
605
+ if wait_time_ms > 100: # Only record significant waits
606
+ self.metrics.record_rate_limit_wait(wait_time_ms)
607
+
608
+ # Execute with smart retry logic
609
+ def fetch_operation():
610
+ """Wrapper for fetch with timing"""
611
+ start_time = time.time()
612
+ result = self.fetch(operation, params)
613
+ response_time_ms = (time.time() - start_time) * 1000
614
+ return result, response_time_ms
615
+
616
+ # Use error handler for retries
617
+ execution_result = self.error_handler.execute_with_retry(
618
+ operation_func=fetch_operation,
619
+ operation_name=operation,
620
+ provider_name=self.name,
621
+ )
622
+
623
+ if execution_result["success"]:
624
+ result, response_time_ms = execution_result["data"]
625
+
626
+ # Calculate data size for metrics
627
+ data = result.get("data") if isinstance(result, dict) else result
628
+ record_count = len(data) if isinstance(data, list) else (1 if data else 0)
629
+
630
+ # Record success metrics
631
+ self.metrics.record_request(
632
+ success=True,
633
+ response_time_ms=response_time_ms,
634
+ record_count=record_count,
635
+ cached=False,
636
+ )
637
+
638
+ # Update legacy stats
639
+ self._update_stats(success=True)
640
+
641
+ self.logger.info(f"Successfully executed {self.name}.{operation} " f"in {response_time_ms:.0f}ms ({record_count} records)")
642
+
643
+ return result
644
+ else:
645
+ # All retries failed
646
+ error_info = execution_result["error"]
647
+ retry_info = execution_result["retry_info"]
648
+
649
+ # Record failure metrics
650
+ self.metrics.record_request(
651
+ success=False,
652
+ response_time_ms=0,
653
+ error_type=error_info.get("type", "unknown"),
654
+ error_message=error_info.get("message"),
655
+ )
656
+
657
+ # Update legacy stats
658
+ self._update_stats(success=False)
659
+
660
+ # Build comprehensive error message
661
+ error_msg = f"Failed to execute {self.name}.{operation} after " f"{retry_info['attempts']} attempts.\n" f"Error: {error_info['message']}"
662
+
663
+ # Add recovery suggestions
664
+ if retry_info.get("recovery_suggestions"):
665
+ error_msg += "\n\nSuggestions:"
666
+ for suggestion in retry_info["recovery_suggestions"][:3]:
667
+ error_msg += f"\n - {suggestion}"
668
+
669
+ self.logger.error(error_msg)
670
+
671
+ raise Exception(error_msg)