aiecs 1.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. aiecs/__init__.py +72 -0
  2. aiecs/__main__.py +41 -0
  3. aiecs/aiecs_client.py +469 -0
  4. aiecs/application/__init__.py +10 -0
  5. aiecs/application/executors/__init__.py +10 -0
  6. aiecs/application/executors/operation_executor.py +363 -0
  7. aiecs/application/knowledge_graph/__init__.py +7 -0
  8. aiecs/application/knowledge_graph/builder/__init__.py +37 -0
  9. aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
  10. aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
  11. aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
  12. aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
  13. aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
  14. aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
  15. aiecs/application/knowledge_graph/extractors/base.py +100 -0
  16. aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
  17. aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
  18. aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
  19. aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
  20. aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
  21. aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
  22. aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
  23. aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
  24. aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
  25. aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
  26. aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
  27. aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
  28. aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
  29. aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
  30. aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
  31. aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
  32. aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
  33. aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
  34. aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
  35. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
  36. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
  37. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
  38. aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
  39. aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
  40. aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
  41. aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
  42. aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
  43. aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
  44. aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
  45. aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
  46. aiecs/application/knowledge_graph/search/__init__.py +59 -0
  47. aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
  48. aiecs/application/knowledge_graph/search/reranker.py +295 -0
  49. aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
  50. aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
  51. aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
  52. aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
  53. aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
  54. aiecs/application/knowledge_graph/validators/__init__.py +13 -0
  55. aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
  56. aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
  57. aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
  58. aiecs/common/__init__.py +9 -0
  59. aiecs/common/knowledge_graph/__init__.py +17 -0
  60. aiecs/common/knowledge_graph/runnable.py +484 -0
  61. aiecs/config/__init__.py +16 -0
  62. aiecs/config/config.py +498 -0
  63. aiecs/config/graph_config.py +137 -0
  64. aiecs/config/registry.py +23 -0
  65. aiecs/core/__init__.py +46 -0
  66. aiecs/core/interface/__init__.py +34 -0
  67. aiecs/core/interface/execution_interface.py +152 -0
  68. aiecs/core/interface/storage_interface.py +171 -0
  69. aiecs/domain/__init__.py +289 -0
  70. aiecs/domain/agent/__init__.py +189 -0
  71. aiecs/domain/agent/base_agent.py +697 -0
  72. aiecs/domain/agent/exceptions.py +103 -0
  73. aiecs/domain/agent/graph_aware_mixin.py +559 -0
  74. aiecs/domain/agent/hybrid_agent.py +490 -0
  75. aiecs/domain/agent/integration/__init__.py +26 -0
  76. aiecs/domain/agent/integration/context_compressor.py +222 -0
  77. aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
  78. aiecs/domain/agent/integration/retry_policy.py +219 -0
  79. aiecs/domain/agent/integration/role_config.py +213 -0
  80. aiecs/domain/agent/knowledge_aware_agent.py +646 -0
  81. aiecs/domain/agent/lifecycle.py +296 -0
  82. aiecs/domain/agent/llm_agent.py +300 -0
  83. aiecs/domain/agent/memory/__init__.py +12 -0
  84. aiecs/domain/agent/memory/conversation.py +197 -0
  85. aiecs/domain/agent/migration/__init__.py +14 -0
  86. aiecs/domain/agent/migration/conversion.py +160 -0
  87. aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
  88. aiecs/domain/agent/models.py +317 -0
  89. aiecs/domain/agent/observability.py +407 -0
  90. aiecs/domain/agent/persistence.py +289 -0
  91. aiecs/domain/agent/prompts/__init__.py +29 -0
  92. aiecs/domain/agent/prompts/builder.py +161 -0
  93. aiecs/domain/agent/prompts/formatters.py +189 -0
  94. aiecs/domain/agent/prompts/template.py +255 -0
  95. aiecs/domain/agent/registry.py +260 -0
  96. aiecs/domain/agent/tool_agent.py +257 -0
  97. aiecs/domain/agent/tools/__init__.py +12 -0
  98. aiecs/domain/agent/tools/schema_generator.py +221 -0
  99. aiecs/domain/community/__init__.py +155 -0
  100. aiecs/domain/community/agent_adapter.py +477 -0
  101. aiecs/domain/community/analytics.py +481 -0
  102. aiecs/domain/community/collaborative_workflow.py +642 -0
  103. aiecs/domain/community/communication_hub.py +645 -0
  104. aiecs/domain/community/community_builder.py +320 -0
  105. aiecs/domain/community/community_integration.py +800 -0
  106. aiecs/domain/community/community_manager.py +813 -0
  107. aiecs/domain/community/decision_engine.py +879 -0
  108. aiecs/domain/community/exceptions.py +225 -0
  109. aiecs/domain/community/models/__init__.py +33 -0
  110. aiecs/domain/community/models/community_models.py +268 -0
  111. aiecs/domain/community/resource_manager.py +457 -0
  112. aiecs/domain/community/shared_context_manager.py +603 -0
  113. aiecs/domain/context/__init__.py +58 -0
  114. aiecs/domain/context/context_engine.py +989 -0
  115. aiecs/domain/context/conversation_models.py +354 -0
  116. aiecs/domain/context/graph_memory.py +467 -0
  117. aiecs/domain/execution/__init__.py +12 -0
  118. aiecs/domain/execution/model.py +57 -0
  119. aiecs/domain/knowledge_graph/__init__.py +19 -0
  120. aiecs/domain/knowledge_graph/models/__init__.py +52 -0
  121. aiecs/domain/knowledge_graph/models/entity.py +130 -0
  122. aiecs/domain/knowledge_graph/models/evidence.py +194 -0
  123. aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
  124. aiecs/domain/knowledge_graph/models/path.py +179 -0
  125. aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
  126. aiecs/domain/knowledge_graph/models/query.py +272 -0
  127. aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
  128. aiecs/domain/knowledge_graph/models/relation.py +136 -0
  129. aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
  130. aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
  131. aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
  132. aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
  133. aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
  134. aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
  135. aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
  136. aiecs/domain/task/__init__.py +13 -0
  137. aiecs/domain/task/dsl_processor.py +613 -0
  138. aiecs/domain/task/model.py +62 -0
  139. aiecs/domain/task/task_context.py +268 -0
  140. aiecs/infrastructure/__init__.py +24 -0
  141. aiecs/infrastructure/graph_storage/__init__.py +11 -0
  142. aiecs/infrastructure/graph_storage/base.py +601 -0
  143. aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
  144. aiecs/infrastructure/graph_storage/cache.py +429 -0
  145. aiecs/infrastructure/graph_storage/distributed.py +226 -0
  146. aiecs/infrastructure/graph_storage/error_handling.py +390 -0
  147. aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
  148. aiecs/infrastructure/graph_storage/health_checks.py +378 -0
  149. aiecs/infrastructure/graph_storage/in_memory.py +514 -0
  150. aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
  151. aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
  152. aiecs/infrastructure/graph_storage/metrics.py +357 -0
  153. aiecs/infrastructure/graph_storage/migration.py +413 -0
  154. aiecs/infrastructure/graph_storage/pagination.py +471 -0
  155. aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
  156. aiecs/infrastructure/graph_storage/postgres.py +871 -0
  157. aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
  158. aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
  159. aiecs/infrastructure/graph_storage/sqlite.py +623 -0
  160. aiecs/infrastructure/graph_storage/streaming.py +495 -0
  161. aiecs/infrastructure/messaging/__init__.py +13 -0
  162. aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
  163. aiecs/infrastructure/messaging/websocket_manager.py +298 -0
  164. aiecs/infrastructure/monitoring/__init__.py +34 -0
  165. aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
  166. aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
  167. aiecs/infrastructure/monitoring/structured_logger.py +48 -0
  168. aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
  169. aiecs/infrastructure/persistence/__init__.py +24 -0
  170. aiecs/infrastructure/persistence/context_engine_client.py +187 -0
  171. aiecs/infrastructure/persistence/database_manager.py +333 -0
  172. aiecs/infrastructure/persistence/file_storage.py +754 -0
  173. aiecs/infrastructure/persistence/redis_client.py +220 -0
  174. aiecs/llm/__init__.py +86 -0
  175. aiecs/llm/callbacks/__init__.py +11 -0
  176. aiecs/llm/callbacks/custom_callbacks.py +264 -0
  177. aiecs/llm/client_factory.py +420 -0
  178. aiecs/llm/clients/__init__.py +33 -0
  179. aiecs/llm/clients/base_client.py +193 -0
  180. aiecs/llm/clients/googleai_client.py +181 -0
  181. aiecs/llm/clients/openai_client.py +131 -0
  182. aiecs/llm/clients/vertex_client.py +437 -0
  183. aiecs/llm/clients/xai_client.py +184 -0
  184. aiecs/llm/config/__init__.py +51 -0
  185. aiecs/llm/config/config_loader.py +275 -0
  186. aiecs/llm/config/config_validator.py +236 -0
  187. aiecs/llm/config/model_config.py +151 -0
  188. aiecs/llm/utils/__init__.py +10 -0
  189. aiecs/llm/utils/validate_config.py +91 -0
  190. aiecs/main.py +363 -0
  191. aiecs/scripts/__init__.py +3 -0
  192. aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
  193. aiecs/scripts/aid/__init__.py +19 -0
  194. aiecs/scripts/aid/version_manager.py +215 -0
  195. aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
  196. aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
  197. aiecs/scripts/dependance_check/__init__.py +17 -0
  198. aiecs/scripts/dependance_check/dependency_checker.py +938 -0
  199. aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
  200. aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
  201. aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
  202. aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
  203. aiecs/scripts/dependance_patch/__init__.py +7 -0
  204. aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
  205. aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
  206. aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
  207. aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
  208. aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
  209. aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
  210. aiecs/scripts/tools_develop/README.md +449 -0
  211. aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
  212. aiecs/scripts/tools_develop/__init__.py +21 -0
  213. aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
  214. aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
  215. aiecs/scripts/tools_develop/verify_tools.py +356 -0
  216. aiecs/tasks/__init__.py +1 -0
  217. aiecs/tasks/worker.py +172 -0
  218. aiecs/tools/__init__.py +299 -0
  219. aiecs/tools/apisource/__init__.py +99 -0
  220. aiecs/tools/apisource/intelligence/__init__.py +19 -0
  221. aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
  222. aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
  223. aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
  224. aiecs/tools/apisource/monitoring/__init__.py +9 -0
  225. aiecs/tools/apisource/monitoring/metrics.py +303 -0
  226. aiecs/tools/apisource/providers/__init__.py +115 -0
  227. aiecs/tools/apisource/providers/base.py +664 -0
  228. aiecs/tools/apisource/providers/census.py +401 -0
  229. aiecs/tools/apisource/providers/fred.py +564 -0
  230. aiecs/tools/apisource/providers/newsapi.py +412 -0
  231. aiecs/tools/apisource/providers/worldbank.py +357 -0
  232. aiecs/tools/apisource/reliability/__init__.py +12 -0
  233. aiecs/tools/apisource/reliability/error_handler.py +375 -0
  234. aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
  235. aiecs/tools/apisource/tool.py +850 -0
  236. aiecs/tools/apisource/utils/__init__.py +9 -0
  237. aiecs/tools/apisource/utils/validators.py +338 -0
  238. aiecs/tools/base_tool.py +201 -0
  239. aiecs/tools/docs/__init__.py +121 -0
  240. aiecs/tools/docs/ai_document_orchestrator.py +599 -0
  241. aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
  242. aiecs/tools/docs/content_insertion_tool.py +1333 -0
  243. aiecs/tools/docs/document_creator_tool.py +1317 -0
  244. aiecs/tools/docs/document_layout_tool.py +1166 -0
  245. aiecs/tools/docs/document_parser_tool.py +994 -0
  246. aiecs/tools/docs/document_writer_tool.py +1818 -0
  247. aiecs/tools/knowledge_graph/__init__.py +17 -0
  248. aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
  249. aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
  250. aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
  251. aiecs/tools/langchain_adapter.py +542 -0
  252. aiecs/tools/schema_generator.py +275 -0
  253. aiecs/tools/search_tool/__init__.py +100 -0
  254. aiecs/tools/search_tool/analyzers.py +589 -0
  255. aiecs/tools/search_tool/cache.py +260 -0
  256. aiecs/tools/search_tool/constants.py +128 -0
  257. aiecs/tools/search_tool/context.py +216 -0
  258. aiecs/tools/search_tool/core.py +749 -0
  259. aiecs/tools/search_tool/deduplicator.py +123 -0
  260. aiecs/tools/search_tool/error_handler.py +271 -0
  261. aiecs/tools/search_tool/metrics.py +371 -0
  262. aiecs/tools/search_tool/rate_limiter.py +178 -0
  263. aiecs/tools/search_tool/schemas.py +277 -0
  264. aiecs/tools/statistics/__init__.py +80 -0
  265. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
  266. aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
  267. aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
  268. aiecs/tools/statistics/data_loader_tool.py +564 -0
  269. aiecs/tools/statistics/data_profiler_tool.py +658 -0
  270. aiecs/tools/statistics/data_transformer_tool.py +573 -0
  271. aiecs/tools/statistics/data_visualizer_tool.py +495 -0
  272. aiecs/tools/statistics/model_trainer_tool.py +487 -0
  273. aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
  274. aiecs/tools/task_tools/__init__.py +86 -0
  275. aiecs/tools/task_tools/chart_tool.py +732 -0
  276. aiecs/tools/task_tools/classfire_tool.py +922 -0
  277. aiecs/tools/task_tools/image_tool.py +447 -0
  278. aiecs/tools/task_tools/office_tool.py +684 -0
  279. aiecs/tools/task_tools/pandas_tool.py +635 -0
  280. aiecs/tools/task_tools/report_tool.py +635 -0
  281. aiecs/tools/task_tools/research_tool.py +392 -0
  282. aiecs/tools/task_tools/scraper_tool.py +715 -0
  283. aiecs/tools/task_tools/stats_tool.py +688 -0
  284. aiecs/tools/temp_file_manager.py +130 -0
  285. aiecs/tools/tool_executor/__init__.py +37 -0
  286. aiecs/tools/tool_executor/tool_executor.py +881 -0
  287. aiecs/utils/LLM_output_structor.py +445 -0
  288. aiecs/utils/__init__.py +34 -0
  289. aiecs/utils/base_callback.py +47 -0
  290. aiecs/utils/cache_provider.py +695 -0
  291. aiecs/utils/execution_utils.py +184 -0
  292. aiecs/utils/logging.py +1 -0
  293. aiecs/utils/prompt_loader.py +14 -0
  294. aiecs/utils/token_usage_repository.py +323 -0
  295. aiecs/ws/__init__.py +0 -0
  296. aiecs/ws/socket_server.py +52 -0
  297. aiecs-1.5.1.dist-info/METADATA +608 -0
  298. aiecs-1.5.1.dist-info/RECORD +302 -0
  299. aiecs-1.5.1.dist-info/WHEEL +5 -0
  300. aiecs-1.5.1.dist-info/entry_points.txt +10 -0
  301. aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
  302. aiecs-1.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,664 @@
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 = 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 = {
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
+ # Try to get schema - this might require instantiation
302
+ schema = None
303
+ if hasattr(cls, "get_operation_schema"):
304
+ try:
305
+ # Try calling as class method first
306
+ schema = cls.get_operation_schema(cls, operation_name)
307
+ except (TypeError, AttributeError):
308
+ # If that fails, we'll need to handle it at runtime
309
+ logger.debug(
310
+ f"Could not get schema for {operation_name} at class level"
311
+ )
312
+
313
+ operations.append(
314
+ {
315
+ "name": operation_name,
316
+ "description": operation_description,
317
+ "schema": schema,
318
+ "method_name": attr_name,
319
+ }
320
+ )
321
+
322
+ logger.debug(
323
+ f"Discovered exposed operation: {operation_name} from {cls.__name__}"
324
+ )
325
+
326
+ except Exception as e:
327
+ logger.warning(f"Error discovering exposed operations for {cls.__name__}: {e}")
328
+
329
+ return operations
330
+
331
+ def validate_and_clean_data(self, operation: str, raw_data: Any) -> Dict[str, Any]:
332
+ """
333
+ Validate and clean data (optional, override in subclass).
334
+
335
+ Providers can implement custom validation logic for their specific data formats.
336
+
337
+ Args:
338
+ operation: Operation that produced the data
339
+ raw_data: Raw data from API
340
+
341
+ Returns:
342
+ Dictionary with:
343
+ - data: Cleaned data
344
+ - validation_warnings: List of warnings
345
+ - statistics: Data quality statistics
346
+ """
347
+ # Default implementation: no validation
348
+ return {"data": raw_data, "validation_warnings": [], "statistics": {}}
349
+
350
+ def calculate_data_quality(
351
+ self, operation: str, data: Any, response_time_ms: float
352
+ ) -> Dict[str, Any]:
353
+ """
354
+ Calculate quality metadata for the response.
355
+
356
+ Can be overridden by providers for custom quality assessment.
357
+
358
+ Args:
359
+ operation: Operation performed
360
+ data: Response data
361
+ response_time_ms: Response time in milliseconds
362
+
363
+ Returns:
364
+ Quality metadata dictionary
365
+ """
366
+ quality = {
367
+ "score": 0.7, # Default quality score
368
+ "completeness": 1.0, # Assume complete unless validated otherwise
369
+ "freshness_hours": None, # Unknown freshness
370
+ "confidence": 0.8, # Default confidence
371
+ "authority_level": "verified", # Provider is verified
372
+ }
373
+
374
+ # Adjust score based on response time
375
+ if response_time_ms < 500:
376
+ quality["score"] = min(quality["score"] + 0.1, 1.0)
377
+ elif response_time_ms > 5000:
378
+ quality["score"] = max(quality["score"] - 0.1, 0.0)
379
+
380
+ # Check if data is empty
381
+ if data is None:
382
+ quality["completeness"] = 0.0
383
+ quality["score"] = 0.0
384
+ elif isinstance(data, list) and len(data) == 0:
385
+ quality["completeness"] = 0.0
386
+ quality["score"] = max(quality["score"] - 0.3, 0.0)
387
+
388
+ return quality
389
+
390
+ def _update_stats(self, success: bool):
391
+ """Update request statistics"""
392
+ with self.stats_lock:
393
+ self.stats["total_requests"] += 1
394
+ if success:
395
+ self.stats["successful_requests"] += 1
396
+ else:
397
+ self.stats["failed_requests"] += 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 = {}
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 = [
479
+ item.get(date_field)
480
+ for item in data
481
+ if date_field in item and item.get(date_field)
482
+ ]
483
+ if dates:
484
+ try:
485
+ # Sort to get earliest and latest
486
+ dates_sorted = sorted(dates)
487
+ coverage["start_date"] = dates_sorted[0]
488
+ coverage["end_date"] = dates_sorted[-1]
489
+
490
+ # Try to infer frequency
491
+ frequency = self.validator.infer_data_frequency(data, date_field)
492
+ if frequency:
493
+ coverage["frequency"] = frequency
494
+ except Exception:
495
+ pass
496
+ break
497
+ elif isinstance(data, dict):
498
+ # For dict responses
499
+ if "articles" in data:
500
+ coverage["total_records"] = len(data["articles"])
501
+ elif "total_results" in data:
502
+ coverage["total_results"] = data["total_results"]
503
+ else:
504
+ coverage["total_records"] = 1
505
+ else:
506
+ coverage["total_records"] = 1 if data is not None else 0
507
+
508
+ return coverage
509
+
510
+ def _get_api_key(self, key_name: Optional[str] = None) -> Optional[str]:
511
+ """
512
+ Get API key from config or environment.
513
+
514
+ Args:
515
+ key_name: Specific key name to retrieve
516
+
517
+ Returns:
518
+ API key or None if not found
519
+ """
520
+ import os
521
+
522
+ # Try config first
523
+ if "api_key" in self.config:
524
+ return self.config["api_key"]
525
+
526
+ # Try environment variable
527
+ env_var = key_name or f"{self.name.upper()}_API_KEY"
528
+ return os.getenv(env_var)
529
+
530
+ def execute(self, operation: str, params: Dict[str, Any]) -> Dict[str, Any]:
531
+ """
532
+ Execute an operation with rate limiting, error handling, and metrics tracking.
533
+
534
+ Args:
535
+ operation: Operation to perform
536
+ params: Operation parameters
537
+
538
+ Returns:
539
+ Response data with enhanced metadata
540
+
541
+ Raises:
542
+ ValueError: If operation is invalid or parameters are invalid
543
+ Exception: If API request fails after all retries
544
+ """
545
+ # Validate operation
546
+ if operation not in self.supported_operations:
547
+ available_ops = ", ".join(self.supported_operations)
548
+ schema = self.get_operation_schema(operation)
549
+ error_msg = (
550
+ f"Operation '{operation}' not supported by {self.name}.\n"
551
+ f"Supported operations: {available_ops}"
552
+ )
553
+ if schema:
554
+ error_msg += f"\nSee get_operation_schema('{operation}') for details"
555
+ raise ValueError(error_msg)
556
+
557
+ # Validate parameters with enhanced error messages
558
+ is_valid, error_msg = self.validate_params(operation, params)
559
+ if not is_valid:
560
+ schema = self.get_operation_schema(operation)
561
+ enhanced_error = f"Invalid parameters for {self.name}.{operation}: {error_msg}"
562
+
563
+ if schema and "parameters" in schema:
564
+ # Add helpful parameter information
565
+ required_params = [
566
+ name
567
+ for name, info in schema["parameters"].items()
568
+ if info.get("required", False)
569
+ ]
570
+ if required_params:
571
+ enhanced_error += f"\nRequired parameters: {', '.join(required_params)}"
572
+
573
+ # Add examples if available
574
+ if "examples" in schema and schema["examples"]:
575
+ example = schema["examples"][0]
576
+ enhanced_error += f"\nExample: {example.get('params', {})}"
577
+
578
+ raise ValueError(enhanced_error)
579
+
580
+ # Apply rate limiting
581
+ wait_start = time.time()
582
+ if not self.rate_limiter.wait(tokens=1, timeout=30):
583
+ self.metrics.record_request(success=False, response_time_ms=0, error_type="rate_limit")
584
+ raise Exception(
585
+ f"Rate limit exceeded for {self.name}. "
586
+ "Please try again later or increase rate limits in config."
587
+ )
588
+
589
+ # Track rate limit wait time
590
+ wait_time_ms = (time.time() - wait_start) * 1000
591
+ if wait_time_ms > 100: # Only record significant waits
592
+ self.metrics.record_rate_limit_wait(wait_time_ms)
593
+
594
+ # Execute with smart retry logic
595
+ def fetch_operation():
596
+ """Wrapper for fetch with timing"""
597
+ start_time = time.time()
598
+ result = self.fetch(operation, params)
599
+ response_time_ms = (time.time() - start_time) * 1000
600
+ return result, response_time_ms
601
+
602
+ # Use error handler for retries
603
+ execution_result = self.error_handler.execute_with_retry(
604
+ operation_func=fetch_operation,
605
+ operation_name=operation,
606
+ provider_name=self.name,
607
+ )
608
+
609
+ if execution_result["success"]:
610
+ result, response_time_ms = execution_result["data"]
611
+
612
+ # Calculate data size for metrics
613
+ data = result.get("data") if isinstance(result, dict) else result
614
+ record_count = len(data) if isinstance(data, list) else (1 if data else 0)
615
+
616
+ # Record success metrics
617
+ self.metrics.record_request(
618
+ success=True,
619
+ response_time_ms=response_time_ms,
620
+ record_count=record_count,
621
+ cached=False,
622
+ )
623
+
624
+ # Update legacy stats
625
+ self._update_stats(success=True)
626
+
627
+ self.logger.info(
628
+ f"Successfully executed {self.name}.{operation} "
629
+ f"in {response_time_ms:.0f}ms ({record_count} records)"
630
+ )
631
+
632
+ return result
633
+ else:
634
+ # All retries failed
635
+ error_info = execution_result["error"]
636
+ retry_info = execution_result["retry_info"]
637
+
638
+ # Record failure metrics
639
+ self.metrics.record_request(
640
+ success=False,
641
+ response_time_ms=0,
642
+ error_type=error_info.get("type", "unknown"),
643
+ error_message=error_info.get("message"),
644
+ )
645
+
646
+ # Update legacy stats
647
+ self._update_stats(success=False)
648
+
649
+ # Build comprehensive error message
650
+ error_msg = (
651
+ f"Failed to execute {self.name}.{operation} after "
652
+ f"{retry_info['attempts']} attempts.\n"
653
+ f"Error: {error_info['message']}"
654
+ )
655
+
656
+ # Add recovery suggestions
657
+ if retry_info.get("recovery_suggestions"):
658
+ error_msg += "\n\nSuggestions:"
659
+ for suggestion in retry_info["recovery_suggestions"][:3]:
660
+ error_msg += f"\n - {suggestion}"
661
+
662
+ self.logger.error(error_msg)
663
+
664
+ raise Exception(error_msg)