aiecs 1.2.1__py3-none-any.whl → 1.3.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.

Potentially problematic release.


This version of aiecs might be problematic. Click here for more details.

Files changed (56) hide show
  1. aiecs/__init__.py +1 -1
  2. aiecs/config/config.py +2 -1
  3. aiecs/llm/clients/vertex_client.py +5 -0
  4. aiecs/main.py +2 -2
  5. aiecs/scripts/tools_develop/README.md +111 -2
  6. aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
  7. aiecs/scripts/tools_develop/validate_tool_schemas.py +80 -21
  8. aiecs/scripts/tools_develop/verify_tools.py +347 -0
  9. aiecs/tools/__init__.py +94 -30
  10. aiecs/tools/apisource/__init__.py +106 -0
  11. aiecs/tools/apisource/intelligence/__init__.py +20 -0
  12. aiecs/tools/apisource/intelligence/data_fusion.py +378 -0
  13. aiecs/tools/apisource/intelligence/query_analyzer.py +387 -0
  14. aiecs/tools/apisource/intelligence/search_enhancer.py +384 -0
  15. aiecs/tools/apisource/monitoring/__init__.py +12 -0
  16. aiecs/tools/apisource/monitoring/metrics.py +308 -0
  17. aiecs/tools/apisource/providers/__init__.py +114 -0
  18. aiecs/tools/apisource/providers/base.py +684 -0
  19. aiecs/tools/apisource/providers/census.py +412 -0
  20. aiecs/tools/apisource/providers/fred.py +575 -0
  21. aiecs/tools/apisource/providers/newsapi.py +402 -0
  22. aiecs/tools/apisource/providers/worldbank.py +346 -0
  23. aiecs/tools/apisource/reliability/__init__.py +14 -0
  24. aiecs/tools/apisource/reliability/error_handler.py +362 -0
  25. aiecs/tools/apisource/reliability/fallback_strategy.py +420 -0
  26. aiecs/tools/apisource/tool.py +814 -0
  27. aiecs/tools/apisource/utils/__init__.py +12 -0
  28. aiecs/tools/apisource/utils/validators.py +343 -0
  29. aiecs/tools/langchain_adapter.py +95 -17
  30. aiecs/tools/search_tool/__init__.py +102 -0
  31. aiecs/tools/search_tool/analyzers.py +583 -0
  32. aiecs/tools/search_tool/cache.py +280 -0
  33. aiecs/tools/search_tool/constants.py +127 -0
  34. aiecs/tools/search_tool/context.py +219 -0
  35. aiecs/tools/search_tool/core.py +773 -0
  36. aiecs/tools/search_tool/deduplicator.py +123 -0
  37. aiecs/tools/search_tool/error_handler.py +257 -0
  38. aiecs/tools/search_tool/metrics.py +375 -0
  39. aiecs/tools/search_tool/rate_limiter.py +177 -0
  40. aiecs/tools/search_tool/schemas.py +297 -0
  41. aiecs/tools/statistics/data_loader_tool.py +2 -2
  42. aiecs/tools/statistics/data_transformer_tool.py +1 -1
  43. aiecs/tools/task_tools/__init__.py +8 -8
  44. aiecs/tools/task_tools/report_tool.py +1 -1
  45. aiecs/tools/tool_executor/__init__.py +2 -0
  46. aiecs/tools/tool_executor/tool_executor.py +284 -14
  47. aiecs/utils/__init__.py +11 -0
  48. aiecs/utils/cache_provider.py +698 -0
  49. aiecs/utils/execution_utils.py +5 -5
  50. {aiecs-1.2.1.dist-info → aiecs-1.3.1.dist-info}/METADATA +1 -1
  51. {aiecs-1.2.1.dist-info → aiecs-1.3.1.dist-info}/RECORD +55 -23
  52. aiecs/tools/task_tools/search_tool.py +0 -1123
  53. {aiecs-1.2.1.dist-info → aiecs-1.3.1.dist-info}/WHEEL +0 -0
  54. {aiecs-1.2.1.dist-info → aiecs-1.3.1.dist-info}/entry_points.txt +0 -0
  55. {aiecs-1.2.1.dist-info → aiecs-1.3.1.dist-info}/licenses/LICENSE +0 -0
  56. {aiecs-1.2.1.dist-info → aiecs-1.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,575 @@
1
+ """
2
+ Federal Reserve Economic Data (FRED) API Provider
3
+
4
+ Provides access to Federal Reserve Economic Data through the FRED API.
5
+ Supports time series data retrieval, search, and metadata operations.
6
+
7
+ API Documentation: https://fred.stlouisfed.org/docs/api/fred/
8
+ """
9
+
10
+ import logging
11
+ from datetime import datetime
12
+ from typing import Any, Dict, List, Optional, Tuple
13
+ from urllib.parse import urlencode
14
+
15
+ from aiecs.tools.apisource.providers.base import BaseAPIProvider, expose_operation
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Optional HTTP client - graceful degradation
20
+ try:
21
+ import requests
22
+ REQUESTS_AVAILABLE = True
23
+ except ImportError:
24
+ REQUESTS_AVAILABLE = False
25
+
26
+
27
+ class FREDProvider(BaseAPIProvider):
28
+ """
29
+ Federal Reserve Economic Data (FRED) API provider.
30
+
31
+ Provides access to economic indicators including:
32
+ - GDP, unemployment, inflation data
33
+ - Interest rates and monetary indicators
34
+ - Regional economic data
35
+ - International statistics
36
+ """
37
+
38
+ BASE_URL = "https://api.stlouisfed.org/fred"
39
+
40
+ @property
41
+ def name(self) -> str:
42
+ return "fred"
43
+
44
+ @property
45
+ def description(self) -> str:
46
+ return "Federal Reserve Economic Data API for US economic indicators and time series"
47
+
48
+ @property
49
+ def supported_operations(self) -> List[str]:
50
+ return [
51
+ 'get_series',
52
+ 'search_series',
53
+ 'get_series_observations',
54
+ 'get_series_info',
55
+ 'get_categories',
56
+ 'get_releases'
57
+ ]
58
+
59
+ def validate_params(self, operation: str, params: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
60
+ """Validate parameters for FRED operations with detailed guidance"""
61
+
62
+ if operation == 'get_series' or operation == 'get_series_info':
63
+ if 'series_id' not in params:
64
+ return False, (
65
+ "Missing required parameter: series_id\n"
66
+ "Example: {'series_id': 'GDP'}\n"
67
+ "Use search_series operation to find valid series IDs"
68
+ )
69
+
70
+ elif operation == 'get_series_observations':
71
+ if 'series_id' not in params:
72
+ return False, (
73
+ "Missing required parameter: series_id\n"
74
+ "Example: {'series_id': 'GDP', 'observation_start': '2020-01-01'}\n"
75
+ "Use search_series to find valid series IDs"
76
+ )
77
+
78
+ elif operation == 'search_series':
79
+ if 'search_text' not in params:
80
+ return False, (
81
+ "Missing required parameter: search_text\n"
82
+ "Example: {'search_text': 'gdp', 'limit': 10}"
83
+ )
84
+
85
+ return True, None
86
+
87
+ # Exposed operations for AI agent visibility
88
+
89
+ @expose_operation(
90
+ operation_name='get_series_observations',
91
+ description='Get FRED economic time series observation data with optional date range filtering'
92
+ )
93
+ def get_series_observations(
94
+ self,
95
+ series_id: str,
96
+ observation_start: Optional[str] = None,
97
+ observation_end: Optional[str] = None,
98
+ limit: Optional[int] = None,
99
+ offset: Optional[int] = None,
100
+ sort_order: Optional[str] = None
101
+ ) -> Dict[str, Any]:
102
+ """
103
+ Get time series observation data from FRED.
104
+
105
+ Args:
106
+ series_id: FRED series ID (e.g., 'GDP', 'UNRATE', 'CPIAUCSL')
107
+ observation_start: Start date in YYYY-MM-DD format
108
+ observation_end: End date in YYYY-MM-DD format
109
+ limit: Maximum number of observations to return
110
+ offset: Offset for pagination
111
+ sort_order: Sort order ('asc' or 'desc')
112
+
113
+ Returns:
114
+ Dictionary containing observations and metadata
115
+ """
116
+ params = {'series_id': series_id}
117
+ if observation_start:
118
+ params['observation_start'] = observation_start
119
+ if observation_end:
120
+ params['observation_end'] = observation_end
121
+ if limit:
122
+ params['limit'] = limit
123
+ if offset:
124
+ params['offset'] = offset
125
+ if sort_order:
126
+ params['sort_order'] = sort_order
127
+
128
+ return self.execute('get_series_observations', params)
129
+
130
+ @expose_operation(
131
+ operation_name='search_series',
132
+ description='Search for FRED economic data series by keywords'
133
+ )
134
+ def search_series(
135
+ self,
136
+ search_text: str,
137
+ limit: Optional[int] = None,
138
+ offset: Optional[int] = None
139
+ ) -> Dict[str, Any]:
140
+ """
141
+ Search for FRED series by keywords.
142
+
143
+ Args:
144
+ search_text: Search keywords (e.g., 'unemployment', 'GDP growth')
145
+ limit: Maximum number of results to return
146
+ offset: Offset for pagination
147
+
148
+ Returns:
149
+ Dictionary containing search results and metadata
150
+ """
151
+ params = {'search_text': search_text}
152
+ if limit:
153
+ params['limit'] = limit
154
+ if offset:
155
+ params['offset'] = offset
156
+
157
+ return self.execute('search_series', params)
158
+
159
+ @expose_operation(
160
+ operation_name='get_series_info',
161
+ description='Get detailed metadata and information about a specific FRED series'
162
+ )
163
+ def get_series_info(self, series_id: str) -> Dict[str, Any]:
164
+ """
165
+ Get metadata about a FRED series.
166
+
167
+ Args:
168
+ series_id: FRED series ID (e.g., 'GDP', 'UNRATE')
169
+
170
+ Returns:
171
+ Dictionary containing series metadata
172
+ """
173
+ return self.execute('get_series_info', {'series_id': series_id})
174
+
175
+ @expose_operation(
176
+ operation_name='get_categories',
177
+ description='Get FRED data categories for browsing available datasets'
178
+ )
179
+ def get_categories(self, category_id: Optional[int] = None) -> Dict[str, Any]:
180
+ """
181
+ Get FRED categories.
182
+
183
+ Args:
184
+ category_id: Optional category ID to get subcategories
185
+
186
+ Returns:
187
+ Dictionary containing category information
188
+ """
189
+ params = {}
190
+ if category_id:
191
+ params['category_id'] = category_id
192
+
193
+ return self.execute('get_categories', params)
194
+
195
+ @expose_operation(
196
+ operation_name='get_releases',
197
+ description='Get FRED data release information and schedules'
198
+ )
199
+ def get_releases(self, limit: Optional[int] = None) -> Dict[str, Any]:
200
+ """
201
+ Get FRED releases.
202
+
203
+ Args:
204
+ limit: Maximum number of releases to return
205
+
206
+ Returns:
207
+ Dictionary containing release information
208
+ """
209
+ params = {}
210
+ if limit:
211
+ params['limit'] = limit
212
+
213
+ return self.execute('get_releases', params)
214
+
215
+ def fetch(self, operation: str, params: Dict[str, Any]) -> Dict[str, Any]:
216
+ """Fetch data from FRED API"""
217
+
218
+ if not REQUESTS_AVAILABLE:
219
+ raise ImportError("requests library is required for FRED provider. Install with: pip install requests")
220
+
221
+ # Get API key
222
+ api_key = self._get_api_key('FRED_API_KEY')
223
+ if not api_key:
224
+ raise ValueError(
225
+ "FRED API key not found. Set FRED_API_KEY environment variable or "
226
+ "provide 'api_key' in config"
227
+ )
228
+
229
+ # Build endpoint based on operation
230
+ if operation == 'get_series' or operation == 'get_series_observations':
231
+ endpoint = f"{self.BASE_URL}/series/observations"
232
+ query_params = {
233
+ 'series_id': params['series_id'],
234
+ 'api_key': api_key,
235
+ 'file_type': 'json'
236
+ }
237
+
238
+ # Optional parameters
239
+ if 'limit' in params:
240
+ query_params['limit'] = params['limit']
241
+ if 'offset' in params:
242
+ query_params['offset'] = params['offset']
243
+ if 'sort_order' in params:
244
+ query_params['sort_order'] = params['sort_order']
245
+ if 'observation_start' in params:
246
+ query_params['observation_start'] = params['observation_start']
247
+ if 'observation_end' in params:
248
+ query_params['observation_end'] = params['observation_end']
249
+
250
+ elif operation == 'get_series_info':
251
+ endpoint = f"{self.BASE_URL}/series"
252
+ query_params = {
253
+ 'series_id': params['series_id'],
254
+ 'api_key': api_key,
255
+ 'file_type': 'json'
256
+ }
257
+
258
+ elif operation == 'search_series':
259
+ endpoint = f"{self.BASE_URL}/series/search"
260
+ query_params = {
261
+ 'search_text': params['search_text'],
262
+ 'api_key': api_key,
263
+ 'file_type': 'json'
264
+ }
265
+
266
+ if 'limit' in params:
267
+ query_params['limit'] = params['limit']
268
+ if 'offset' in params:
269
+ query_params['offset'] = params['offset']
270
+
271
+ elif operation == 'get_categories':
272
+ endpoint = f"{self.BASE_URL}/category"
273
+ query_params = {
274
+ 'api_key': api_key,
275
+ 'file_type': 'json'
276
+ }
277
+
278
+ if 'category_id' in params:
279
+ query_params['category_id'] = params['category_id']
280
+
281
+ elif operation == 'get_releases':
282
+ endpoint = f"{self.BASE_URL}/releases"
283
+ query_params = {
284
+ 'api_key': api_key,
285
+ 'file_type': 'json'
286
+ }
287
+
288
+ if 'limit' in params:
289
+ query_params['limit'] = params['limit']
290
+
291
+ else:
292
+ raise ValueError(f"Unknown operation: {operation}")
293
+
294
+ # Make API request
295
+ timeout = self.config.get('timeout', 30)
296
+ try:
297
+ response = requests.get(
298
+ endpoint,
299
+ params=query_params,
300
+ timeout=timeout
301
+ )
302
+ response.raise_for_status()
303
+
304
+ data = response.json()
305
+
306
+ # Extract relevant data based on operation
307
+ if operation in ['get_series', 'get_series_observations']:
308
+ result_data = data.get('observations', [])
309
+ elif operation == 'search_series':
310
+ result_data = data.get('seriess', [])
311
+ elif operation == 'get_series_info':
312
+ result_data = data.get('seriess', [])
313
+ elif operation == 'get_categories':
314
+ result_data = data.get('categories', [])
315
+ elif operation == 'get_releases':
316
+ result_data = data.get('releases', [])
317
+ else:
318
+ result_data = data
319
+
320
+ return self._format_response(
321
+ operation=operation,
322
+ data=result_data,
323
+ source=f"FRED API - {endpoint}"
324
+ )
325
+
326
+ except requests.exceptions.RequestException as e:
327
+ self.logger.error(f"FRED API request failed: {e}")
328
+ raise Exception(f"FRED API request failed: {str(e)}")
329
+
330
+ def get_operation_schema(self, operation: str) -> Optional[Dict[str, Any]]:
331
+ """Get detailed schema for FRED operations"""
332
+
333
+ schemas = {
334
+ 'get_series_observations': {
335
+ 'description': 'Get time series observation data from FRED',
336
+ 'parameters': {
337
+ 'series_id': {
338
+ 'type': 'string',
339
+ 'required': True,
340
+ 'description': 'FRED series ID (e.g., GDP, UNRATE, CPIAUCSL)',
341
+ 'examples': ['GDP', 'UNRATE', 'CPIAUCSL', 'DGS10'],
342
+ 'validation': {'pattern': r'^[A-Z0-9]+$', 'max_length': 50}
343
+ },
344
+ 'observation_start': {
345
+ 'type': 'string',
346
+ 'required': False,
347
+ 'description': 'Start date for observations (YYYY-MM-DD)',
348
+ 'examples': ['2020-01-01', '2015-06-15'],
349
+ 'default': 'earliest available'
350
+ },
351
+ 'observation_end': {
352
+ 'type': 'string',
353
+ 'required': False,
354
+ 'description': 'End date for observations (YYYY-MM-DD)',
355
+ 'examples': ['2025-10-15', '2023-12-31'],
356
+ 'default': 'latest available'
357
+ },
358
+ 'limit': {
359
+ 'type': 'integer',
360
+ 'required': False,
361
+ 'description': 'Maximum number of observations',
362
+ 'examples': [100, 1000],
363
+ 'default': 100000
364
+ },
365
+ 'sort_order': {
366
+ 'type': 'string',
367
+ 'required': False,
368
+ 'description': 'Sort order (asc/desc)',
369
+ 'examples': ['desc', 'asc'],
370
+ 'default': 'asc'
371
+ }
372
+ },
373
+ 'examples': [
374
+ {
375
+ 'description': 'Get GDP data for last 5 years',
376
+ 'params': {
377
+ 'series_id': 'GDP',
378
+ 'observation_start': '2020-01-01',
379
+ 'limit': 100
380
+ }
381
+ }
382
+ ]
383
+ },
384
+ 'search_series': {
385
+ 'description': 'Search for FRED series by text query',
386
+ 'parameters': {
387
+ 'search_text': {
388
+ 'type': 'string',
389
+ 'required': True,
390
+ 'description': 'Text to search for in series',
391
+ 'examples': ['gdp', 'unemployment', 'inflation']
392
+ },
393
+ 'limit': {
394
+ 'type': 'integer',
395
+ 'required': False,
396
+ 'description': 'Maximum results to return',
397
+ 'examples': [10, 50],
398
+ 'default': 1000
399
+ }
400
+ },
401
+ 'examples': [
402
+ {
403
+ 'description': 'Search for GDP series',
404
+ 'params': {'search_text': 'gdp', 'limit': 10}
405
+ }
406
+ ]
407
+ },
408
+ 'get_series_info': {
409
+ 'description': 'Get metadata about a FRED series',
410
+ 'parameters': {
411
+ 'series_id': {
412
+ 'type': 'string',
413
+ 'required': True,
414
+ 'description': 'FRED series ID to get information about',
415
+ 'examples': ['GDP', 'UNRATE', 'CPIAUCSL']
416
+ }
417
+ },
418
+ 'examples': [
419
+ {
420
+ 'description': 'Get info about GDP series',
421
+ 'params': {'series_id': 'GDP'}
422
+ }
423
+ ]
424
+ },
425
+ 'get_categories': {
426
+ 'description': 'Get FRED data categories',
427
+ 'parameters': {
428
+ 'category_id': {
429
+ 'type': 'integer',
430
+ 'required': False,
431
+ 'description': 'Category ID to get subcategories (omit for root categories)',
432
+ 'examples': [125, 32991]
433
+ }
434
+ },
435
+ 'examples': [
436
+ {
437
+ 'description': 'Get root categories',
438
+ 'params': {}
439
+ },
440
+ {
441
+ 'description': 'Get subcategories of category 125',
442
+ 'params': {'category_id': 125}
443
+ }
444
+ ]
445
+ },
446
+ 'get_releases': {
447
+ 'description': 'Get FRED data releases',
448
+ 'parameters': {
449
+ 'limit': {
450
+ 'type': 'integer',
451
+ 'required': False,
452
+ 'description': 'Maximum number of releases to return',
453
+ 'examples': [10, 50],
454
+ 'default': 1000
455
+ }
456
+ },
457
+ 'examples': [
458
+ {
459
+ 'description': 'Get recent releases',
460
+ 'params': {'limit': 20}
461
+ }
462
+ ]
463
+ }
464
+ }
465
+
466
+ return schemas.get(operation)
467
+
468
+ def validate_and_clean_data(
469
+ self,
470
+ operation: str,
471
+ raw_data: Any
472
+ ) -> Dict[str, Any]:
473
+ """Validate and clean FRED data"""
474
+
475
+ result = {
476
+ 'data': raw_data,
477
+ 'validation_warnings': [],
478
+ 'statistics': {}
479
+ }
480
+
481
+ if operation in ['get_series', 'get_series_observations']:
482
+ if isinstance(raw_data, list) and len(raw_data) > 0:
483
+ # Check for missing values (FRED uses '.')
484
+ completeness_info = self.validator.check_data_completeness(
485
+ raw_data, 'value', ['.', 'NA']
486
+ )
487
+
488
+ result['statistics']['completeness'] = completeness_info
489
+
490
+ if completeness_info['missing_count'] > 0:
491
+ result['validation_warnings'].append(
492
+ f"{completeness_info['missing_count']} missing values detected "
493
+ f"({completeness_info['completeness']:.1%} complete)"
494
+ )
495
+
496
+ # Extract numeric values for outlier detection
497
+ numeric_values = []
498
+ for item in raw_data:
499
+ value = item.get('value')
500
+ if value not in ['.', 'NA', None]:
501
+ try:
502
+ numeric_values.append(float(value))
503
+ except (ValueError, TypeError):
504
+ pass
505
+
506
+ if len(numeric_values) >= 4:
507
+ # Detect outliers
508
+ outlier_indices = self.validator.detect_outliers(
509
+ numeric_values, method='iqr', threshold=3.0
510
+ )
511
+
512
+ if outlier_indices:
513
+ result['validation_warnings'].append(
514
+ f"{len(outlier_indices)} potential outliers detected"
515
+ )
516
+ result['statistics']['outliers_count'] = len(outlier_indices)
517
+
518
+ # Calculate value range
519
+ value_range = self.validator.calculate_value_range(raw_data, 'value')
520
+ if value_range:
521
+ result['statistics']['value_range'] = value_range
522
+
523
+ # Detect time gaps
524
+ time_gaps = self.validator.detect_time_gaps(raw_data, 'date')
525
+ if time_gaps:
526
+ result['validation_warnings'].append(
527
+ f"{len(time_gaps)} time gaps detected in series"
528
+ )
529
+ result['statistics']['time_gaps'] = len(time_gaps)
530
+
531
+ return result
532
+
533
+ def calculate_data_quality(
534
+ self,
535
+ operation: str,
536
+ data: Any,
537
+ response_time_ms: float
538
+ ) -> Dict[str, Any]:
539
+ """Calculate quality metadata specific to FRED data"""
540
+
541
+ # Get base quality from parent
542
+ quality = super().calculate_data_quality(operation, data, response_time_ms)
543
+
544
+ # FRED-specific quality enhancements
545
+ quality['authority_level'] = 'official' # FRED is official government data
546
+ quality['confidence'] = 0.95 # High confidence in FRED data
547
+
548
+ # For time series, assess freshness
549
+ if operation in ['get_series', 'get_series_observations']:
550
+ if isinstance(data, list) and len(data) > 0:
551
+ # Check the date of most recent observation
552
+ latest_date = None
553
+ for item in data:
554
+ if 'date' in item:
555
+ try:
556
+ from datetime import datetime
557
+ date_obj = datetime.strptime(item['date'], '%Y-%m-%d')
558
+ if latest_date is None or date_obj > latest_date:
559
+ latest_date = date_obj
560
+ except:
561
+ pass
562
+
563
+ if latest_date:
564
+ from datetime import datetime
565
+ age_days = (datetime.now() - latest_date).days
566
+ quality['freshness_hours'] = age_days * 24
567
+
568
+ # Adjust quality score based on data freshness
569
+ if age_days < 30:
570
+ quality['score'] = min(quality['score'] + 0.1, 1.0)
571
+ elif age_days > 365:
572
+ quality['score'] = max(quality['score'] - 0.1, 0.0)
573
+
574
+ return quality
575
+