aiecs 1.2.2__py3-none-any.whl → 1.3.3__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.
- aiecs/__init__.py +1 -1
- aiecs/llm/clients/vertex_client.py +22 -2
- aiecs/main.py +2 -2
- aiecs/scripts/tools_develop/README.md +111 -2
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +80 -21
- aiecs/scripts/tools_develop/verify_tools.py +347 -0
- aiecs/tools/__init__.py +94 -30
- aiecs/tools/apisource/__init__.py +106 -0
- aiecs/tools/apisource/intelligence/__init__.py +20 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +378 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +387 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +384 -0
- aiecs/tools/apisource/monitoring/__init__.py +12 -0
- aiecs/tools/apisource/monitoring/metrics.py +308 -0
- aiecs/tools/apisource/providers/__init__.py +114 -0
- aiecs/tools/apisource/providers/base.py +684 -0
- aiecs/tools/apisource/providers/census.py +412 -0
- aiecs/tools/apisource/providers/fred.py +575 -0
- aiecs/tools/apisource/providers/newsapi.py +402 -0
- aiecs/tools/apisource/providers/worldbank.py +346 -0
- aiecs/tools/apisource/reliability/__init__.py +14 -0
- aiecs/tools/apisource/reliability/error_handler.py +362 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +420 -0
- aiecs/tools/apisource/tool.py +814 -0
- aiecs/tools/apisource/utils/__init__.py +12 -0
- aiecs/tools/apisource/utils/validators.py +343 -0
- aiecs/tools/langchain_adapter.py +95 -17
- aiecs/tools/search_tool/__init__.py +102 -0
- aiecs/tools/search_tool/analyzers.py +583 -0
- aiecs/tools/search_tool/cache.py +280 -0
- aiecs/tools/search_tool/constants.py +127 -0
- aiecs/tools/search_tool/context.py +219 -0
- aiecs/tools/search_tool/core.py +773 -0
- aiecs/tools/search_tool/deduplicator.py +123 -0
- aiecs/tools/search_tool/error_handler.py +257 -0
- aiecs/tools/search_tool/metrics.py +375 -0
- aiecs/tools/search_tool/rate_limiter.py +177 -0
- aiecs/tools/search_tool/schemas.py +297 -0
- aiecs/tools/statistics/data_loader_tool.py +2 -2
- aiecs/tools/statistics/data_transformer_tool.py +1 -1
- aiecs/tools/task_tools/__init__.py +8 -8
- aiecs/tools/task_tools/report_tool.py +1 -1
- aiecs/tools/tool_executor/__init__.py +2 -0
- aiecs/tools/tool_executor/tool_executor.py +284 -14
- aiecs/utils/__init__.py +11 -0
- aiecs/utils/cache_provider.py +698 -0
- aiecs/utils/execution_utils.py +5 -5
- {aiecs-1.2.2.dist-info → aiecs-1.3.3.dist-info}/METADATA +1 -1
- {aiecs-1.2.2.dist-info → aiecs-1.3.3.dist-info}/RECORD +54 -22
- aiecs/tools/task_tools/search_tool.py +0 -1123
- {aiecs-1.2.2.dist-info → aiecs-1.3.3.dist-info}/WHEEL +0 -0
- {aiecs-1.2.2.dist-info → aiecs-1.3.3.dist-info}/entry_points.txt +0 -0
- {aiecs-1.2.2.dist-info → aiecs-1.3.3.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.2.2.dist-info → aiecs-1.3.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""
|
|
2
|
+
News API Provider
|
|
3
|
+
|
|
4
|
+
Provides access to news articles from various sources worldwide.
|
|
5
|
+
Supports headline retrieval, article search, and source listing.
|
|
6
|
+
|
|
7
|
+
API Documentation: https://newsapi.org/docs
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
from aiecs.tools.apisource.providers.base import BaseAPIProvider, expose_operation
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Optional HTTP client
|
|
19
|
+
try:
|
|
20
|
+
import requests
|
|
21
|
+
REQUESTS_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
REQUESTS_AVAILABLE = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NewsAPIProvider(BaseAPIProvider):
|
|
27
|
+
"""
|
|
28
|
+
News API provider for accessing news articles and headlines.
|
|
29
|
+
|
|
30
|
+
Provides access to:
|
|
31
|
+
- Top headlines from various sources
|
|
32
|
+
- Article search by keywords
|
|
33
|
+
- News sources listing
|
|
34
|
+
- Filtering by country, language, category
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
BASE_URL = "https://newsapi.org/v2"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def name(self) -> str:
|
|
41
|
+
return "newsapi"
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def description(self) -> str:
|
|
45
|
+
return "News API for accessing news articles, headlines, and sources worldwide"
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def supported_operations(self) -> List[str]:
|
|
49
|
+
return [
|
|
50
|
+
'get_top_headlines',
|
|
51
|
+
'search_everything',
|
|
52
|
+
'get_sources'
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
def validate_params(self, operation: str, params: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
|
|
56
|
+
"""Validate parameters for News API operations"""
|
|
57
|
+
|
|
58
|
+
if operation == 'get_top_headlines':
|
|
59
|
+
# At least one of these is required
|
|
60
|
+
if not any(k in params for k in ['q', 'country', 'category', 'sources']):
|
|
61
|
+
return False, "At least one of q, country, category, or sources is required"
|
|
62
|
+
|
|
63
|
+
elif operation == 'search_everything':
|
|
64
|
+
if 'q' not in params:
|
|
65
|
+
return False, "Missing required parameter: q (search query)"
|
|
66
|
+
|
|
67
|
+
return True, None
|
|
68
|
+
|
|
69
|
+
# Exposed operations for AI agent visibility
|
|
70
|
+
|
|
71
|
+
@expose_operation(
|
|
72
|
+
operation_name='get_top_headlines',
|
|
73
|
+
description='Get top news headlines from various sources with optional filtering'
|
|
74
|
+
)
|
|
75
|
+
def get_top_headlines(
|
|
76
|
+
self,
|
|
77
|
+
q: Optional[str] = None,
|
|
78
|
+
country: Optional[str] = None,
|
|
79
|
+
category: Optional[str] = None,
|
|
80
|
+
sources: Optional[str] = None,
|
|
81
|
+
page_size: Optional[int] = None
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
Get top headlines.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
q: Keywords or phrases to search for in article title and body
|
|
88
|
+
country: 2-letter ISO country code (e.g., 'us', 'gb', 'cn')
|
|
89
|
+
category: Category (business, entertainment, general, health, science, sports, technology)
|
|
90
|
+
sources: Comma-separated news source IDs
|
|
91
|
+
page_size: Number of results to return (max 100)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Dictionary containing news articles and metadata
|
|
95
|
+
"""
|
|
96
|
+
params = {}
|
|
97
|
+
if q:
|
|
98
|
+
params['q'] = q
|
|
99
|
+
if country:
|
|
100
|
+
params['country'] = country
|
|
101
|
+
if category:
|
|
102
|
+
params['category'] = category
|
|
103
|
+
if sources:
|
|
104
|
+
params['sources'] = sources
|
|
105
|
+
if page_size:
|
|
106
|
+
params['page_size'] = page_size
|
|
107
|
+
|
|
108
|
+
return self.execute('get_top_headlines', params)
|
|
109
|
+
|
|
110
|
+
@expose_operation(
|
|
111
|
+
operation_name='search_everything',
|
|
112
|
+
description='Search through millions of articles from news sources and blogs'
|
|
113
|
+
)
|
|
114
|
+
def search_everything(
|
|
115
|
+
self,
|
|
116
|
+
q: str,
|
|
117
|
+
from_date: Optional[str] = None,
|
|
118
|
+
to_date: Optional[str] = None,
|
|
119
|
+
language: Optional[str] = None,
|
|
120
|
+
sort_by: Optional[str] = None,
|
|
121
|
+
page_size: Optional[int] = None
|
|
122
|
+
) -> Dict[str, Any]:
|
|
123
|
+
"""
|
|
124
|
+
Search all articles.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
q: Keywords or phrases to search for
|
|
128
|
+
from_date: Start date (YYYY-MM-DD or ISO 8601)
|
|
129
|
+
to_date: End date (YYYY-MM-DD or ISO 8601)
|
|
130
|
+
language: 2-letter ISO language code (e.g., 'en', 'es', 'fr')
|
|
131
|
+
sort_by: Sort order (relevancy, popularity, publishedAt)
|
|
132
|
+
page_size: Number of results to return (max 100)
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dictionary containing search results and metadata
|
|
136
|
+
"""
|
|
137
|
+
params = {'q': q}
|
|
138
|
+
if from_date:
|
|
139
|
+
params['from'] = from_date
|
|
140
|
+
if to_date:
|
|
141
|
+
params['to'] = to_date
|
|
142
|
+
if language:
|
|
143
|
+
params['language'] = language
|
|
144
|
+
if sort_by:
|
|
145
|
+
params['sortBy'] = sort_by
|
|
146
|
+
if page_size:
|
|
147
|
+
params['pageSize'] = page_size
|
|
148
|
+
|
|
149
|
+
return self.execute('search_everything', params)
|
|
150
|
+
|
|
151
|
+
@expose_operation(
|
|
152
|
+
operation_name='get_sources',
|
|
153
|
+
description='Get the list of available news sources'
|
|
154
|
+
)
|
|
155
|
+
def get_sources(
|
|
156
|
+
self,
|
|
157
|
+
category: Optional[str] = None,
|
|
158
|
+
language: Optional[str] = None,
|
|
159
|
+
country: Optional[str] = None
|
|
160
|
+
) -> Dict[str, Any]:
|
|
161
|
+
"""
|
|
162
|
+
Get available news sources.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
category: Filter by category
|
|
166
|
+
language: Filter by language (2-letter ISO code)
|
|
167
|
+
country: Filter by country (2-letter ISO code)
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Dictionary containing list of news sources
|
|
171
|
+
"""
|
|
172
|
+
params = {}
|
|
173
|
+
if category:
|
|
174
|
+
params['category'] = category
|
|
175
|
+
if language:
|
|
176
|
+
params['language'] = language
|
|
177
|
+
if country:
|
|
178
|
+
params['country'] = country
|
|
179
|
+
|
|
180
|
+
return self.execute('get_sources', params)
|
|
181
|
+
|
|
182
|
+
def fetch(self, operation: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
183
|
+
"""Fetch data from News API"""
|
|
184
|
+
|
|
185
|
+
if not REQUESTS_AVAILABLE:
|
|
186
|
+
raise ImportError("requests library is required for News API provider")
|
|
187
|
+
|
|
188
|
+
# Get API key
|
|
189
|
+
api_key = self._get_api_key('NEWSAPI_API_KEY')
|
|
190
|
+
if not api_key:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
"News API key not found. Set NEWSAPI_API_KEY environment variable or "
|
|
193
|
+
"provide 'api_key' in config. Get your key at https://newsapi.org"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
headers = {'X-Api-Key': api_key}
|
|
197
|
+
timeout = self.config.get('timeout', 30)
|
|
198
|
+
|
|
199
|
+
# Build endpoint based on operation
|
|
200
|
+
if operation == 'get_top_headlines':
|
|
201
|
+
endpoint = f"{self.BASE_URL}/top-headlines"
|
|
202
|
+
query_params = {}
|
|
203
|
+
|
|
204
|
+
# Optional parameters
|
|
205
|
+
if 'q' in params:
|
|
206
|
+
query_params['q'] = params['q']
|
|
207
|
+
if 'country' in params:
|
|
208
|
+
query_params['country'] = params['country']
|
|
209
|
+
if 'category' in params:
|
|
210
|
+
query_params['category'] = params['category']
|
|
211
|
+
if 'sources' in params:
|
|
212
|
+
query_params['sources'] = params['sources']
|
|
213
|
+
if 'page_size' in params:
|
|
214
|
+
query_params['pageSize'] = params['page_size']
|
|
215
|
+
if 'page' in params:
|
|
216
|
+
query_params['page'] = params['page']
|
|
217
|
+
|
|
218
|
+
elif operation == 'search_everything':
|
|
219
|
+
endpoint = f"{self.BASE_URL}/everything"
|
|
220
|
+
query_params = {
|
|
221
|
+
'q': params['q']
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Optional parameters
|
|
225
|
+
if 'from_date' in params:
|
|
226
|
+
query_params['from'] = params['from_date']
|
|
227
|
+
elif 'days_back' in params:
|
|
228
|
+
# Convenience parameter: go back N days
|
|
229
|
+
from_date = datetime.now() - timedelta(days=params['days_back'])
|
|
230
|
+
query_params['from'] = from_date.strftime('%Y-%m-%d')
|
|
231
|
+
|
|
232
|
+
if 'to_date' in params:
|
|
233
|
+
query_params['to'] = params['to_date']
|
|
234
|
+
if 'language' in params:
|
|
235
|
+
query_params['language'] = params['language']
|
|
236
|
+
if 'sort_by' in params:
|
|
237
|
+
query_params['sortBy'] = params['sort_by']
|
|
238
|
+
if 'page_size' in params:
|
|
239
|
+
query_params['pageSize'] = params['page_size']
|
|
240
|
+
if 'page' in params:
|
|
241
|
+
query_params['page'] = params['page']
|
|
242
|
+
|
|
243
|
+
elif operation == 'get_sources':
|
|
244
|
+
endpoint = f"{self.BASE_URL}/top-headlines/sources"
|
|
245
|
+
query_params = {}
|
|
246
|
+
|
|
247
|
+
# Optional parameters
|
|
248
|
+
if 'country' in params:
|
|
249
|
+
query_params['country'] = params['country']
|
|
250
|
+
if 'language' in params:
|
|
251
|
+
query_params['language'] = params['language']
|
|
252
|
+
if 'category' in params:
|
|
253
|
+
query_params['category'] = params['category']
|
|
254
|
+
|
|
255
|
+
else:
|
|
256
|
+
raise ValueError(f"Unknown operation: {operation}")
|
|
257
|
+
|
|
258
|
+
# Make API request
|
|
259
|
+
try:
|
|
260
|
+
response = requests.get(
|
|
261
|
+
endpoint,
|
|
262
|
+
params=query_params,
|
|
263
|
+
headers=headers,
|
|
264
|
+
timeout=timeout
|
|
265
|
+
)
|
|
266
|
+
response.raise_for_status()
|
|
267
|
+
|
|
268
|
+
data = response.json()
|
|
269
|
+
|
|
270
|
+
# Check API response status
|
|
271
|
+
if data.get('status') != 'ok':
|
|
272
|
+
raise Exception(f"News API error: {data.get('message', 'Unknown error')}")
|
|
273
|
+
|
|
274
|
+
# Extract relevant data
|
|
275
|
+
if operation == 'get_sources':
|
|
276
|
+
result_data = data.get('sources', [])
|
|
277
|
+
else:
|
|
278
|
+
result_data = {
|
|
279
|
+
'articles': data.get('articles', []),
|
|
280
|
+
'total_results': data.get('totalResults', 0)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return self._format_response(
|
|
284
|
+
operation=operation,
|
|
285
|
+
data=result_data,
|
|
286
|
+
source=f"News API - {endpoint}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
except requests.exceptions.RequestException as e:
|
|
290
|
+
self.logger.error(f"News API request failed: {e}")
|
|
291
|
+
raise Exception(f"News API request failed: {str(e)}")
|
|
292
|
+
|
|
293
|
+
def get_operation_schema(self, operation: str) -> Optional[Dict[str, Any]]:
|
|
294
|
+
"""Get detailed schema for News API operations"""
|
|
295
|
+
|
|
296
|
+
schemas = {
|
|
297
|
+
'get_top_headlines': {
|
|
298
|
+
'description': 'Get top news headlines',
|
|
299
|
+
'parameters': {
|
|
300
|
+
'q': {
|
|
301
|
+
'type': 'string',
|
|
302
|
+
'required': False,
|
|
303
|
+
'description': 'Keywords or phrases to search for',
|
|
304
|
+
'examples': ['bitcoin', 'climate change', 'technology']
|
|
305
|
+
},
|
|
306
|
+
'country': {
|
|
307
|
+
'type': 'string',
|
|
308
|
+
'required': False,
|
|
309
|
+
'description': '2-letter ISO country code',
|
|
310
|
+
'examples': ['us', 'gb', 'cn', 'jp']
|
|
311
|
+
},
|
|
312
|
+
'category': {
|
|
313
|
+
'type': 'string',
|
|
314
|
+
'required': False,
|
|
315
|
+
'description': 'News category',
|
|
316
|
+
'examples': ['business', 'entertainment', 'health', 'science', 'sports', 'technology']
|
|
317
|
+
},
|
|
318
|
+
'sources': {
|
|
319
|
+
'type': 'string',
|
|
320
|
+
'required': False,
|
|
321
|
+
'description': 'Comma-separated news source IDs',
|
|
322
|
+
'examples': ['bbc-news', 'cnn', 'the-verge']
|
|
323
|
+
},
|
|
324
|
+
'page_size': {
|
|
325
|
+
'type': 'integer',
|
|
326
|
+
'required': False,
|
|
327
|
+
'description': 'Number of results (max 100)',
|
|
328
|
+
'examples': [10, 20, 50],
|
|
329
|
+
'default': 20
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
'search_everything': {
|
|
334
|
+
'description': 'Search all news articles',
|
|
335
|
+
'parameters': {
|
|
336
|
+
'q': {
|
|
337
|
+
'type': 'string',
|
|
338
|
+
'required': True,
|
|
339
|
+
'description': 'Keywords or phrases to search for',
|
|
340
|
+
'examples': ['artificial intelligence', 'climate summit', 'stock market']
|
|
341
|
+
},
|
|
342
|
+
'from_date': {
|
|
343
|
+
'type': 'string',
|
|
344
|
+
'required': False,
|
|
345
|
+
'description': 'Start date (YYYY-MM-DD)',
|
|
346
|
+
'examples': ['2024-01-01', '2024-10-01']
|
|
347
|
+
},
|
|
348
|
+
'to_date': {
|
|
349
|
+
'type': 'string',
|
|
350
|
+
'required': False,
|
|
351
|
+
'description': 'End date (YYYY-MM-DD)',
|
|
352
|
+
'examples': ['2024-12-31', '2024-10-17']
|
|
353
|
+
},
|
|
354
|
+
'language': {
|
|
355
|
+
'type': 'string',
|
|
356
|
+
'required': False,
|
|
357
|
+
'description': '2-letter ISO language code',
|
|
358
|
+
'examples': ['en', 'es', 'fr', 'de']
|
|
359
|
+
},
|
|
360
|
+
'sort_by': {
|
|
361
|
+
'type': 'string',
|
|
362
|
+
'required': False,
|
|
363
|
+
'description': 'Sort order',
|
|
364
|
+
'examples': ['relevancy', 'popularity', 'publishedAt'],
|
|
365
|
+
'default': 'publishedAt'
|
|
366
|
+
},
|
|
367
|
+
'page_size': {
|
|
368
|
+
'type': 'integer',
|
|
369
|
+
'required': False,
|
|
370
|
+
'description': 'Number of results (max 100)',
|
|
371
|
+
'examples': [10, 20, 50],
|
|
372
|
+
'default': 20
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
'get_sources': {
|
|
377
|
+
'description': 'Get available news sources',
|
|
378
|
+
'parameters': {
|
|
379
|
+
'category': {
|
|
380
|
+
'type': 'string',
|
|
381
|
+
'required': False,
|
|
382
|
+
'description': 'Filter by category',
|
|
383
|
+
'examples': ['business', 'technology', 'sports']
|
|
384
|
+
},
|
|
385
|
+
'language': {
|
|
386
|
+
'type': 'string',
|
|
387
|
+
'required': False,
|
|
388
|
+
'description': 'Filter by language (2-letter ISO code)',
|
|
389
|
+
'examples': ['en', 'es', 'fr']
|
|
390
|
+
},
|
|
391
|
+
'country': {
|
|
392
|
+
'type': 'string',
|
|
393
|
+
'required': False,
|
|
394
|
+
'description': 'Filter by country (2-letter ISO code)',
|
|
395
|
+
'examples': ['us', 'gb', 'cn']
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return schemas.get(operation)
|
|
402
|
+
|