gofannon 0.25.18__py3-none-any.whl → 0.25.20__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.
@@ -0,0 +1,167 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any, List
4
+
5
+ from .base import SimplerGrantsGovBase
6
+ from ..config import FunctionRegistry # Will be used by subclasses
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class SearchOpportunitiesBase(SimplerGrantsGovBase):
11
+ """
12
+ Base class for Simpler Grants Gov tools that search for opportunities.
13
+ Handles common pagination and opportunity status filtering logic.
14
+ """
15
+
16
+ # Common descriptions
17
+ _pagination_description = (
18
+ " Control the pagination of results. If not provided, defaults will be used by the API "
19
+ "(typically page 1, a small number of items, and sorted by relevance or ID)."
20
+ )
21
+ _status_filter_description = (
22
+ " Filter results by opportunity status. By default, only 'posted' opportunities are shown. "
23
+ "Set other flags to True to include them."
24
+ )
25
+ _common_search_description_suffix = (
26
+ " Returns a JSON string of matching opportunities."
27
+ )
28
+
29
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "search_opportunities_base"):
30
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
31
+ # 'name' will be overridden by subclasses for their specific tool name
32
+
33
+ def _get_common_parameter_definitions(self) -> Dict[str, Any]:
34
+ """
35
+ Returns a dictionary of common parameter definitions for pagination and status.
36
+ """
37
+ return {
38
+ "items_per_page": {
39
+ "type": "integer",
40
+ "description": "Number of results per page (e.g., 10, 25, 50). Default: 5.",
41
+ "default": 5
42
+ },
43
+ "page_number": {
44
+ "type": "integer",
45
+ "description": "The page number to retrieve (starts at 1). Default: 1.",
46
+ "default": 1
47
+ },
48
+ "order_by": {
49
+ "type": "string",
50
+ "description": "Field to sort results by (e.g., 'relevancy', 'post_date', 'opportunity_id', 'agency_code'). Default: 'relevancy'.",
51
+ "default": "relevancy" # API default might be opportunity_id or post_date
52
+ },
53
+ "sort_direction": {
54
+ "type": "string",
55
+ "enum": ["ascending", "descending"],
56
+ "description": "Direction to sort (ascending or descending). Default: 'descending'.",
57
+ "default": "descending"
58
+ },
59
+ "show_posted": {
60
+ "type": "boolean",
61
+ "description": "Include 'posted' opportunities. Default: True.",
62
+ "default": True
63
+ },
64
+ "show_forecasted": {
65
+ "type": "boolean",
66
+ "description": "Include 'forecasted' opportunities. Default: False.",
67
+ "default": False
68
+ },
69
+ "show_closed": {
70
+ "type": "boolean",
71
+ "description": "Include 'closed' opportunities. Default: False.",
72
+ "default": False
73
+ },
74
+ "show_archived": {
75
+ "type": "boolean",
76
+ "description": "Include 'archived' opportunities. Default: False.",
77
+ "default": False
78
+ }
79
+ }
80
+
81
+ def _build_api_payload(
82
+ self,
83
+ specific_query_params: Optional[Dict[str, Any]] = None,
84
+ specific_filters: Optional[Dict[str, Any]] = None,
85
+ # Pagination and status args from function call
86
+ items_per_page: int = 5,
87
+ page_number: int = 1,
88
+ order_by: str = "relevancy",
89
+ sort_direction: str = "descending",
90
+ show_posted: bool = True,
91
+ show_forecasted: bool = False,
92
+ show_closed: bool = False,
93
+ show_archived: bool = False,
94
+ query_operator: str = "AND"
95
+ ) -> Dict[str, Any]:
96
+ """
97
+ Constructs the full payload for the /v1/opportunities/search API endpoint.
98
+ """
99
+ payload: Dict[str, Any] = {}
100
+
101
+ # 1. Pagination
102
+ payload["pagination"] = {
103
+ "page_offset": page_number,
104
+ "page_size": items_per_page,
105
+ "sort_order": [{"order_by": order_by, "sort_direction": sort_direction}]
106
+ }
107
+
108
+ # 2. Query and Specific Query Params
109
+ if specific_query_params and specific_query_params.get("query"): # Check if 'query' key actually has a value
110
+ payload.update(specific_query_params)
111
+ payload["query_operator"] = query_operator
112
+ elif specific_query_params: # If other specific_query_params exist without 'query'
113
+ payload.update(specific_query_params)
114
+
115
+
116
+ # 3. Filters
117
+ filters_dict: Dict[str, Any] = {}
118
+
119
+ # 3a. Opportunity Status Filter
120
+ active_statuses: List[str] = []
121
+ if show_posted:
122
+ active_statuses.append("posted")
123
+ if show_forecasted:
124
+ active_statuses.append("forecasted")
125
+ if show_closed:
126
+ active_statuses.append("closed")
127
+ if show_archived:
128
+ active_statuses.append("archived")
129
+
130
+ if active_statuses:
131
+ filters_dict["opportunity_status"] = {"one_of": active_statuses}
132
+ else:
133
+ self.logger.warning("No opportunity statuses selected for filtering.")
134
+ # Consider if an empty list should be sent or if the key should be omitted.
135
+ # For now, omitting if empty, as API might require at least one status if key is present.
136
+ # filters_dict["opportunity_status"] = {"one_of": []} # Alternative
137
+
138
+ # 3b. Specific Filters
139
+ if specific_filters:
140
+ filters_dict.update(specific_filters)
141
+
142
+ if filters_dict:
143
+ payload["filters"] = filters_dict
144
+
145
+ self.logger.debug(f"Constructed API payload: {json.dumps(payload, indent=2)}")
146
+ return payload
147
+
148
+ def _execute_search(self, payload: Dict[str, Any]) -> str:
149
+ """
150
+ Shared method to make the API call.
151
+ """
152
+ endpoint = "/v1/opportunities/search"
153
+ try:
154
+ result = self._make_request("POST", endpoint, json_payload=payload)
155
+ self.logger.debug(f"Search successful. Response length: {len(result)}")
156
+ return result
157
+ except Exception as e:
158
+ self.logger.error(f"Opportunity search failed: {e}", exc_info=True)
159
+ return json.dumps({"error": f"Opportunity search API request failed: {str(e)}", "success": False})
160
+
161
+ # Subclasses will implement their specific 'definition' and 'fn'
162
+ @property
163
+ def definition(self):
164
+ raise NotImplementedError("Subclasses must implement the 'definition' property.")
165
+
166
+ def fn(self, *args, **kwargs):
167
+ raise NotImplementedError("Subclasses must implement the 'fn' method.")
@@ -0,0 +1,360 @@
1
+ import logging
2
+ from typing import Optional, Dict, Any, List
3
+ import json
4
+
5
+ from .base import SimplerGrantsGovBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Enum values extracted from API source for definition and validation
11
+ FUNDING_INSTRUMENT_ENUM = ["grant", "cooperative_agreement", "other"]
12
+ FUNDING_CATEGORY_ENUM = [
13
+ "recovery_act", "agriculture", "arts", "business_and_commerce",
14
+ "community_development", "consumer_protection", "disaster_prevention_and_relief",
15
+ "education", "employment_labor_and_training", "energy", "environment",
16
+ "food_and_nutrition", "health", "housing", "humanities",
17
+ "information_and_statistics", "infrastructure", "internal_security",
18
+ "law_justice_and_legal_services", "natural_resources", "opportunity_zone_benefits",
19
+ "regional_development", "science_and_technology", "social_services",
20
+ "transportation", "other"
21
+ ]
22
+ APPLICANT_TYPE_ENUM = [
23
+ "state_governments", "county_governments", "city_or_township_governments",
24
+ "special_district_governments", "independent_school_districts",
25
+ "public_and_state_controlled_institutions_of_higher_education",
26
+ "indian_native_american_tribal_governments_federally_recognized",
27
+ "indian_native_american_tribal_governments_other_than_federally_recognized",
28
+ "indian_native_american_tribal_organizations_other_than_governments",
29
+ "nonprofits_having_a_501c3_status_with_the_irs_other_than_institutions_of_higher_education",
30
+ "nonprofits_that_do_not_have_a_501c3_status_with_the_irs_other_than_institutions_of_higher_education",
31
+ "private_institutions_of_higher_education", "individuals",
32
+ "for_profit_organizations_other_than_small_businesses", "small_businesses",
33
+ "hispanic_serving_institutions", "historically_black_colleges_and_universities",
34
+ "tribally_controlled_colleges_and_universities",
35
+ "alaska_native_and_native_hawaiian_serving_institutions",
36
+ "non_domestic_non_us_entities", "other"
37
+ ]
38
+ OPPORTUNITY_STATUS_ENUM = ["forecasted", "posted", "closed", "archived"]
39
+ ALLOWED_SORT_ORDERS = [
40
+ "relevancy", "opportunity_id", "opportunity_number", "opportunity_title",
41
+ "post_date", "close_date", "agency_code", "agency_name", "top_level_agency_name"
42
+ ]
43
+ # Internal Defaults for Pagination
44
+ DEFAULT_PAGE_OFFSET = 1
45
+ DEFAULT_PAGE_SIZE = 10 # Return fewer results by default for LLM context
46
+ DEFAULT_SORT_ORDER = [{"order_by": "opportunity_id", "sort_direction": "descending"}]
47
+
48
+ @FunctionRegistry.register
49
+ class SearchOpportunities(SimplerGrantsGovBase):
50
+ """
51
+ Tool to search for grant opportunities using the Simpler Grants Gov API.
52
+ Corresponds to the POST /v1/opportunities/search endpoint. Pagination is handled internally.
53
+ Invalid enum values provided in list filters will be omitted with a warning.
54
+ """
55
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "search_opportunities"):
56
+ super().__init__(api_key=api_key, base_url=base_url)
57
+ self.name = name
58
+
59
+ @property
60
+ def definition(self):
61
+ # Definition remains largely the same, but descriptions can emphasize valid values
62
+ return {
63
+ "type": "function",
64
+ "function": {
65
+ "name": self.name,
66
+ "description": "Search for grant opportunities based on optional criteria like query text and various filters. Returns the first page of results. Invalid filter values may be omitted.",
67
+ "parameters": {
68
+ "type": "object",
69
+ "properties": {
70
+ # --- Core Search ---
71
+ "query": {
72
+ "type": "string",
73
+ "description": "Optional. Query string which searches against several text fields (e.g., 'research', 'health')."
74
+ },
75
+ "query_operator": {
76
+ "type": "string",
77
+ "enum": ["AND", "OR"],
78
+ "description": "Optional. Query operator for combining search conditions if 'query' is provided (default: AND).",
79
+ "default": "AND"
80
+ },
81
+ # --- Elevated Filters ---
82
+ "funding_instrument": {
83
+ "type": "array",
84
+ "items": {"type": "string"}, # Don't use enum here, validate in fn
85
+ "description": f"Optional. Filter by a list of funding instrument types. Valid values: {', '.join(FUNDING_INSTRUMENT_ENUM)}"
86
+ },
87
+ "funding_category": {
88
+ "type": "array",
89
+ "items": {"type": "string"},
90
+ "description": f"Optional. Filter by a list of funding categories. Valid values: {', '.join(FUNDING_CATEGORY_ENUM)}"
91
+ },
92
+ "applicant_type": {
93
+ "type": "array",
94
+ "items": {"type": "string"},
95
+ "description": f"Optional. Filter by a list of applicant types. Valid values: {', '.join(APPLICANT_TYPE_ENUM)}"
96
+ },
97
+ "opportunity_status": {
98
+ "type": "array",
99
+ "items": {"type": "string"},
100
+ "description": f"Optional. Filter by a list of opportunity statuses. Valid values: {', '.join(OPPORTUNITY_STATUS_ENUM)}"
101
+ },
102
+ "agency": {
103
+ "type": "array",
104
+ "items": {"type": "string", "description": "Agency code, e.g., HHS, USAID"},
105
+ "description": "Optional. Filter by a list of agency codes. No strict validation applied."
106
+ },
107
+ "assistance_listing_number": {
108
+ "type": "array",
109
+ "items": {"type": "string", "pattern": r"^\d{2}\.\d{2,3}$", "description": "ALN format ##.### e.g. 45.149"},
110
+ "description": "Optional. Filter by a list of Assistance Listing Numbers (ALN / CFDA)."
111
+ },
112
+ "is_cost_sharing": {
113
+ "type": "boolean",
114
+ "description": "Optional. Filter opportunities based on whether cost sharing is required (True) or not required (False)."
115
+ },
116
+ "expected_number_of_awards": {
117
+ "type": "object",
118
+ "properties": {
119
+ "min": {"type": "integer", "minimum": 0},
120
+ "max": {"type": "integer", "minimum": 0}
121
+ },
122
+ "description": "Optional. Filter by expected number of awards range (provide 'min', 'max', or both)."
123
+ },
124
+ "award_floor": {
125
+ "type": "object",
126
+ "properties": {
127
+ "min": {"type": "integer", "minimum": 0},
128
+ "max": {"type": "integer", "minimum": 0}
129
+ },
130
+ "description": "Optional. Filter by award floor range (minimum award amount)."
131
+ },
132
+ "award_ceiling": {
133
+ "type": "object",
134
+ "properties": {
135
+ "min": {"type": "integer", "minimum": 0},
136
+ "max": {"type": "integer", "minimum": 0}
137
+ },
138
+ "description": "Optional. Filter by award ceiling range (maximum award amount)."
139
+ },
140
+ "estimated_total_program_funding": {
141
+ "type": "object",
142
+ "properties": {
143
+ "min": {"type": "integer", "minimum": 0},
144
+ "max": {"type": "integer", "minimum": 0}
145
+ },
146
+ "description": "Optional. Filter by estimated total program funding range."
147
+ },
148
+ "post_date": {
149
+ "type": "object",
150
+ "properties": {
151
+ "start_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"},
152
+ "end_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"}
153
+ },
154
+ "description": "Optional. Filter by post date range (provide 'start_date', 'end_date', or both)."
155
+ },
156
+ "close_date": {
157
+ "type": "object",
158
+ "properties": {
159
+ "start_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"},
160
+ "end_date": {"type": "string", "format": "date", "description": "YYYY-MM-DD"}
161
+ },
162
+ "description": "Optional. Filter by close date range (provide 'start_date', 'end_date', or both)."
163
+ }
164
+ },
165
+ "required": [] # No parameters are strictly required anymore
166
+ }
167
+ }
168
+ }
169
+
170
+ def _validate_and_filter_list(self, input_list: Optional[List[str]], filter_name: str, valid_enums: List[str], warnings_list: List[Dict]) -> List[str]:
171
+ """Helper to validate items in a list against enums and collect warnings."""
172
+ if input_list is None:
173
+ return []
174
+
175
+ if not isinstance(input_list, list):
176
+ # If the input is not a list, treat it as an invalid attempt and warn
177
+ warning_msg = f"Invalid type for filter '{filter_name}': Expected a list, got {type(input_list).__name__}. Filter omitted."
178
+ logger.warning(warning_msg)
179
+ warnings_list.append({"filter": filter_name, "error": warning_msg})
180
+ return []
181
+
182
+ valid_items = []
183
+ for item in input_list:
184
+ if isinstance(item, str) and item in valid_enums:
185
+ valid_items.append(item)
186
+ else:
187
+ # Log and record warning for the invalid item
188
+ warning_msg = f"Invalid value '{item}' provided for filter '{filter_name}'. It was omitted. Valid values are: {', '.join(valid_enums)}."
189
+ logger.warning(warning_msg)
190
+ warnings_list.append({"filter": filter_name, "invalid_value": item, "valid_values": valid_enums, "message": warning_msg})
191
+
192
+ return valid_items
193
+
194
+ def _add_warnings_to_response(self, response_str: str, warnings_list: List[Dict]) -> str:
195
+ """Adds a 'warnings' field to a JSON response string if warnings exist."""
196
+ if not warnings_list:
197
+ return response_str
198
+
199
+ try:
200
+ response_data = json.loads(response_str)
201
+ # Ensure response_data is a dictionary before adding warnings
202
+ if isinstance(response_data, dict):
203
+ response_data['warnings'] = warnings_list
204
+ else:
205
+ # If the response wasn't a dict (e.g., just a string error message), wrap it
206
+ logger.warning(f"Original response was not a dict, wrapping to add warnings. Original: {response_str[:100]}")
207
+ response_data = {
208
+ "original_response": response_data,
209
+ "warnings": warnings_list
210
+ }
211
+ return json.dumps(response_data)
212
+ except json.JSONDecodeError:
213
+ logger.error(f"Failed to parse response JSON to add warnings. Original response: {response_str[:500]}...")
214
+ # Return original response string if parsing fails
215
+ return response_str
216
+ except Exception as e:
217
+ logger.error(f"Unexpected error adding warnings to response: {e}", exc_info=True)
218
+ return response_str # Fallback to original
219
+
220
+ def fn(self,
221
+ query: Optional[str] = None,
222
+ query_operator: str = "AND",
223
+ # --- Elevated Filter Args ---
224
+ funding_instrument: Optional[List[str]] = None,
225
+ funding_category: Optional[List[str]] = None,
226
+ applicant_type: Optional[List[str]] = None,
227
+ opportunity_status: Optional[List[str]] = None,
228
+ agency: Optional[List[str]] = None,
229
+ assistance_listing_number: Optional[List[str]] = None,
230
+ is_cost_sharing: Optional[bool] = None,
231
+ expected_number_of_awards: Optional[Dict[str, int]] = None,
232
+ award_floor: Optional[Dict[str, int]] = None,
233
+ award_ceiling: Optional[Dict[str, int]] = None,
234
+ estimated_total_program_funding: Optional[Dict[str, int]] = None,
235
+ post_date: Optional[Dict[str, str]] = None,
236
+ close_date: Optional[Dict[str, str]] = None
237
+ ) -> str:
238
+ """
239
+ Executes the opportunity search with input validation, internal pagination,
240
+ and reconstructed filters. Adds warnings to the response for omitted invalid filter values.
241
+
242
+ Args:
243
+ (Refer to definition for descriptions)
244
+
245
+ Returns:
246
+ A JSON string representing the search results (potentially with a 'warnings' field) or an error.
247
+ """
248
+ self.logger.info(f"Executing Simpler Grants Gov opportunity search tool with query='{query}'")
249
+ warnings_list: List[Dict] = [] # Store warnings here
250
+
251
+ try:
252
+ # --- Internal Pagination ---
253
+ internal_pagination = {
254
+ "page_offset": DEFAULT_PAGE_OFFSET,
255
+ "page_size": DEFAULT_PAGE_SIZE,
256
+ "sort_order": DEFAULT_SORT_ORDER
257
+ }
258
+ self.logger.debug(f"Using internal pagination: {internal_pagination}")
259
+
260
+ # --- Reconstruct and Validate Filters for API ---
261
+ api_filters: Dict[str, Any] = {}
262
+
263
+ # Validate and filter list-based enums
264
+ valid_fi = self._validate_and_filter_list(funding_instrument, "funding_instrument", FUNDING_INSTRUMENT_ENUM, warnings_list)
265
+ if valid_fi: api_filters["funding_instrument"] = {"one_of": valid_fi}
266
+
267
+ valid_fc = self._validate_and_filter_list(funding_category, "funding_category", FUNDING_CATEGORY_ENUM, warnings_list)
268
+ if valid_fc: api_filters["funding_category"] = {"one_of": valid_fc}
269
+
270
+ valid_at = self._validate_and_filter_list(applicant_type, "applicant_type", APPLICANT_TYPE_ENUM, warnings_list)
271
+ if valid_at: api_filters["applicant_type"] = {"one_of": valid_at}
272
+
273
+ valid_os = self._validate_and_filter_list(opportunity_status, "opportunity_status", OPPORTUNITY_STATUS_ENUM, warnings_list)
274
+ if valid_os: api_filters["opportunity_status"] = {"one_of": valid_os}
275
+
276
+ # Filters without strict enum validation in this example (pass if list)
277
+ if agency is not None:
278
+ if isinstance(agency, list):
279
+ api_filters["agency"] = {"one_of": agency}
280
+ else:
281
+ warnings_list.append({"filter": "agency", "error": f"Expected a list, got {type(agency).__name__}. Filter omitted."})
282
+ logger.warning(f"Invalid type for filter 'agency'. Expected list, got {type(agency).__name__}. Filter omitted.")
283
+
284
+ if assistance_listing_number is not None:
285
+ # Add pattern validation if needed, or rely on API
286
+ if isinstance(assistance_listing_number, list):
287
+ api_filters["assistance_listing_number"] = {"one_of": assistance_listing_number}
288
+ else:
289
+ warnings_list.append({"filter": "assistance_listing_number", "error": f"Expected a list, got {type(assistance_listing_number).__name__}. Filter omitted."})
290
+ logger.warning(f"Invalid type for filter 'assistance_listing_number'. Expected list, got {type(assistance_listing_number).__name__}. Filter omitted.")
291
+
292
+
293
+ # Boolean filter
294
+ if is_cost_sharing is not None:
295
+ if isinstance(is_cost_sharing, bool):
296
+ api_filters["is_cost_sharing"] = {"one_of": [is_cost_sharing]}
297
+ else:
298
+ warnings_list.append({"filter": "is_cost_sharing", "error": f"Expected a boolean, got {type(is_cost_sharing).__name__}. Filter omitted."})
299
+ logger.warning(f"Invalid type for filter 'is_cost_sharing'. Expected boolean, got {type(is_cost_sharing).__name__}. Filter omitted.")
300
+
301
+
302
+ # Range/Date filters (basic type check)
303
+ range_date_filters = {
304
+ "expected_number_of_awards": expected_number_of_awards,
305
+ "award_floor": award_floor,
306
+ "award_ceiling": award_ceiling,
307
+ "estimated_total_program_funding": estimated_total_program_funding,
308
+ "post_date": post_date,
309
+ "close_date": close_date
310
+ }
311
+ for name, value in range_date_filters.items():
312
+ if value is not None:
313
+ if isinstance(value, dict):
314
+ api_filters[name] = value
315
+ else:
316
+ warnings_list.append({"filter": name, "error": f"Expected a dictionary object, got {type(value).__name__}. Filter omitted."})
317
+ logger.warning(f"Invalid type for filter '{name}'. Expected dict, got {type(value).__name__}. Filter omitted.")
318
+
319
+
320
+ # --- Payload Construction ---
321
+ payload: Dict[str, Any] = {
322
+ "pagination": internal_pagination,
323
+ "query_operator": query_operator
324
+ }
325
+ if query:
326
+ payload["query"] = query
327
+ if api_filters:
328
+ payload["filters"] = api_filters
329
+ self.logger.debug(f"Constructed API filters: {api_filters}")
330
+ elif not query:
331
+ # If no query and no filters, API might require something? Or just return all?
332
+ # Assuming returning all is ok. If not, add a check/error here.
333
+ self.logger.info("No query or filters provided for opportunity search.")
334
+
335
+
336
+ # --- API Call ---
337
+ endpoint = "/v1/opportunities/search"
338
+ api_response_str = self._make_request("POST", endpoint, json_payload=payload)
339
+ self.logger.debug(f"Search successful. Response length: {len(api_response_str)}")
340
+
341
+ # Add warnings to the successful response if any occurred during validation
342
+ final_response_str = self._add_warnings_to_response(api_response_str, warnings_list)
343
+ return final_response_str
344
+
345
+ except ValueError as ve: # Catch potential errors during filter reconstruction if needed
346
+ error_msg = f"Input processing failed for SearchOpportunities: {ve}"
347
+ self.logger.error(error_msg)
348
+ error_response = {"error": error_msg, "success": False}
349
+ # Add warnings even to validation error responses
350
+ if warnings_list:
351
+ error_response["warnings"] = warnings_list
352
+ return json.dumps(error_response)
353
+ except Exception as e:
354
+ error_msg = f"Opportunity search failed: {str(e)}"
355
+ self.logger.error(f"Opportunity search failed: {e}", exc_info=True)
356
+ error_response = {"error": error_msg, "success": False}
357
+ # Add warnings even to general error responses
358
+ if warnings_list:
359
+ error_response["warnings"] = warnings_list
360
+ return json.dumps(error_response)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gofannon
3
- Version: 0.25.18
3
+ Version: 0.25.20
4
4
  Summary: A collection of tools for LLMs
5
5
  License: ASFv2
6
6
  Author: Trevor Grant
@@ -15,7 +15,7 @@ gofannon/basic_math/division.py,sha256=ZO8ZzWNL9zeFdXTEdPWDpnbDrWMXVeucSu105RbQm
15
15
  gofannon/basic_math/exponents.py,sha256=w4qDlFZ9M1lf6X-tjG-ndpECfCOS7Qtc_VLICw0oh2w,1205
16
16
  gofannon/basic_math/multiplication.py,sha256=PJ5sKWMCVlBaTeZ_j3gwYOEQXAhN-qIXhnrNcyhWGKM,1168
17
17
  gofannon/basic_math/subtraction.py,sha256=gM1_N1mZ3qAXC6qnkzfijKXiOx27Gg93-CaB_ifMbOQ,1164
18
- gofannon/config.py,sha256=KPVtjBnpwfM39EQg-_xiqL1UFxcuQehrQIUS6HHBP6k,1784
18
+ gofannon/config.py,sha256=f9MybgkXSM2MDDumtxRdKPcZAdfaQ5DHGNiDJRFf_Ow,2028
19
19
  gofannon/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  gofannon/file/list_directory.py,sha256=Jw0F50_hdtyDSFBmcy9e8F4KajtsPyT8Gsm_eyjMUuQ,2679
21
21
  gofannon/file/read_file.py,sha256=kYf-4aGCHLj-D-XQs0YBEnbB1MbIccgcWbiLnVXidp4,1669
@@ -47,9 +47,24 @@ gofannon/reasoning/base.py,sha256=D-4JHJqUlqgwMNOkKU0BHYA4GEWwNgPlLxKYHX0FVyg,14
47
47
  gofannon/reasoning/hierarchical_cot.py,sha256=e8ZgMbyJQ0wCBEmv7QJqFv7l3XxTMwDYupGxJ7EF6t8,11516
48
48
  gofannon/reasoning/sequential_cot.py,sha256=m9c8GnyTtmI-JntCuhkoFfULAabVOxsYgTRUd3MjzfY,3166
49
49
  gofannon/reasoning/tree_of_thought.py,sha256=TRhRJQNsFVauCLw4TOvQCDcX1nGmp_wSg9H67GJn1hs,10574
50
+ gofannon/simpler_grants_gov/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
+ gofannon/simpler_grants_gov/base.py,sha256=nOM6R8d02jzIh1K4FumXr2AY0dOtAMHiOnZN0WXX_oY,5639
52
+ gofannon/simpler_grants_gov/get_opportunity.py,sha256=o3dj1TtdMlI9v0no2licc3-ZL-9RnHYaVo6JLa-5Nag,2717
53
+ gofannon/simpler_grants_gov/list_agencies.py,sha256=gKQxVha72ayrsy2cOSOOmywsSjD9sSkAogJPDJgb4n8,3917
54
+ gofannon/simpler_grants_gov/query_by_applicant_eligibility.py,sha256=ixljpwWHuwOO3XNSSuGFhCdAu46eBxlKjnDTEIIuJVk,5658
55
+ gofannon/simpler_grants_gov/query_by_assistance_listing.py,sha256=NjQsNEuv4v80Z0P1cyuEWnzpfwrcit2MD8lW_tqWTjE,4011
56
+ gofannon/simpler_grants_gov/query_by_award_criteria.py,sha256=gpl60LsgXwmrBiqwk1oGHG_gY1HISrPisktVjHyedT8,5684
57
+ gofannon/simpler_grants_gov/query_by_dates.py,sha256=Eif_dJZtLDZgoDJ6cmqxl9J6UV2bA90hw-rDiAIqRD0,4881
58
+ gofannon/simpler_grants_gov/query_by_funding_details.py,sha256=AyX6aTG_mzKJIRFhbEopOrGSofWJzAhpThmCi_z5rTs,5399
59
+ gofannon/simpler_grants_gov/query_by_multiple_criteria.py,sha256=7ADabpejYx_2uNWVM9WQt3ueCwKDhIzI0zqxY61YmM8,6212
60
+ gofannon/simpler_grants_gov/query_opportunities.py,sha256=ZDB0_BQTDKLO14zNCfyfV_c8ODAMSzE6oRei0OxNfso,3342
61
+ gofannon/simpler_grants_gov/query_opportunities_by_agency.py,sha256=TsaE77ez_1hIKNR6Z26a9U7nyQspto3y5TElUWHkq_s,3906
62
+ gofannon/simpler_grants_gov/search_agencies.py,sha256=xNGqAO_xRnDHz37rUuLPEFkUhbm7XiDLN66-X_2xqt8,5062
63
+ gofannon/simpler_grants_gov/search_base.py,sha256=ask8ecAOQ9jZulr8iDxdfQZgSC_EyxDTuSVuB_FLqzc,6786
64
+ gofannon/simpler_grants_gov/search_opportunities.py,sha256=jZD64VqFIW36dBdAshOOQmywzLqBZyB3wLtA_C95sa8,19931
50
65
  gofannon/wikipedia/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
66
  gofannon/wikipedia/wikipedia_lookup.py,sha256=J6wKPbSivCF7cccaiRaJW1o0VqNhQAGfrh5U1ULLesg,2869
52
- gofannon-0.25.18.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
53
- gofannon-0.25.18.dist-info/METADATA,sha256=fEPx-nCBs3eCopXGFC-fo7UODM7aHKBSt5KE2X2i5Qk,5415
54
- gofannon-0.25.18.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
55
- gofannon-0.25.18.dist-info/RECORD,,
67
+ gofannon-0.25.20.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
68
+ gofannon-0.25.20.dist-info/METADATA,sha256=n5vCujMhTpr9mRxBrV-VakPK3zAxcyvWIBr7bWn6g8o,5415
69
+ gofannon-0.25.20.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
70
+ gofannon-0.25.20.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.2
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any