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,108 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByDates(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities based on post dates and/or close dates.
14
+ Dates should be in YYYY-MM-DD format.
15
+ """
16
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_dates"):
17
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
18
+ self.name = name
19
+
20
+ @property
21
+ def definition(self):
22
+ base_params = self._get_common_parameter_definitions()
23
+ specific_params = {
24
+ "post_start_date": {"type": "string", "description": "Optional. Start of post date range (YYYY-MM-DD)."},
25
+ "post_end_date": {"type": "string", "description": "Optional. End of post date range (YYYY-MM-DD)."},
26
+ "close_start_date": {"type": "string", "description": "Optional. Start of close date range (YYYY-MM-DD)."},
27
+ "close_end_date": {"type": "string", "description": "Optional. End of close date range (YYYY-MM-DD)."},
28
+ "query_text": {
29
+ "type": "string",
30
+ "description": "Optional. Text to search for within the results filtered by dates."
31
+ },
32
+ "query_operator": {
33
+ "type": "string",
34
+ "enum": ["AND", "OR"],
35
+ "description": "Operator for 'query_text' if provided (default: AND).",
36
+ "default": "AND"
37
+ }
38
+ }
39
+ all_properties = {**specific_params, **base_params}
40
+
41
+ return {
42
+ "type": "function",
43
+ "function": {
44
+ "name": self.name,
45
+ "description": (
46
+ "Search for grant opportunities by post and/or close date ranges. "
47
+ "At least one date parameter must be specified. Dates must be YYYY-MM-DD. "
48
+ f"{self._pagination_description}"
49
+ f"{self._status_filter_description}"
50
+ f"{self._common_search_description_suffix}"
51
+ ),
52
+ "parameters": {
53
+ "type": "object",
54
+ "properties": all_properties,
55
+ "required": [] # Logic in fn enforces one date
56
+ }
57
+ }
58
+ }
59
+
60
+ def fn(self,
61
+ post_start_date: Optional[str] = None, post_end_date: Optional[str] = None,
62
+ close_start_date: Optional[str] = None, close_end_date: Optional[str] = None,
63
+ query_text: Optional[str] = None, query_operator: str = "AND",
64
+ # Common params
65
+ items_per_page: int = 5, page_number: int = 1, order_by: str = "relevancy",
66
+ sort_direction: str = "descending", show_posted: bool = True, show_forecasted: bool = False,
67
+ show_closed: bool = False, show_archived: bool = False) -> str:
68
+
69
+ self.logger.info(f"Querying by dates: post={post_start_date}-{post_end_date}, close={close_start_date}-{close_end_date}, query='{query_text}'")
70
+
71
+ specific_filters: Dict[str, Any] = {}
72
+ date_criteria_provided = False
73
+
74
+ post_date_filter = {}
75
+ if post_start_date:
76
+ post_date_filter["start_date"] = post_start_date
77
+ date_criteria_provided = True
78
+ if post_end_date:
79
+ post_date_filter["end_date"] = post_end_date
80
+ date_criteria_provided = True
81
+ if post_date_filter:
82
+ specific_filters["post_date"] = post_date_filter
83
+
84
+ close_date_filter = {}
85
+ if close_start_date:
86
+ close_date_filter["start_date"] = close_start_date
87
+ date_criteria_provided = True
88
+ if close_end_date:
89
+ close_date_filter["end_date"] = close_end_date
90
+ date_criteria_provided = True
91
+ if close_date_filter:
92
+ specific_filters["close_date"] = close_date_filter
93
+
94
+ if not date_criteria_provided and not query_text:
95
+ return json.dumps({"error": "At least one date parameter or query_text must be specified.", "success": False})
96
+
97
+ specific_query_params: Optional[Dict[str, Any]] = None
98
+ if query_text:
99
+ specific_query_params = {"query": query_text}
100
+
101
+ payload = self._build_api_payload(
102
+ specific_query_params=specific_query_params,
103
+ specific_filters=specific_filters if specific_filters else None,
104
+ items_per_page=items_per_page, page_number=page_number, order_by=order_by,
105
+ sort_direction=sort_direction, show_posted=show_posted, show_forecasted=show_forecasted,
106
+ show_closed=show_closed, show_archived=show_archived, query_operator=query_operator
107
+ )
108
+ return self._execute_search(payload)
@@ -0,0 +1,115 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any, List
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByFundingDetails(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities based on funding instruments and/or categories.
14
+ """
15
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_funding_details"):
16
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
17
+ self.name = name
18
+
19
+ @property
20
+ def definition(self):
21
+ base_params = self._get_common_parameter_definitions()
22
+ specific_params = {
23
+ "funding_instruments": {
24
+ "type": "array",
25
+ "items": {"type": "string"},
26
+ "description": "Optional. List of funding instruments (e.g., ['grant', 'cooperative_agreement']). Valid values: "
27
+ "grant, cooperative_agreement, direct_payment_for_specified_use, direct_payment_with_unrestricted_use, "
28
+ "direct_loan, guaranteed_or_insured_loan, insurance, other, procurement_contract."
29
+ },
30
+ "funding_categories": {
31
+ "type": "array",
32
+ "items": {"type": "string"},
33
+ "description": "Optional. List of funding categories (e.g., ['health', 'education']). Valid values: "
34
+ "agriculture, arts, business_and_commerce, community_development, consumer_protection, disaster_prevention_and_relief, "
35
+ "education, employment_labor_and_training, energy, environment, food_and_nutrition, health, housing, "
36
+ "humanities, information_and_statistics, infrastructure, income_security_and_social_services, "
37
+ "international_affairs, law_justice_and_legal_services, natural_resources, opportunity_zone_benefits, "
38
+ "recovery_act, regional_development, science_and_technology, transportation, other."
39
+ },
40
+ "query_text": {
41
+ "type": "string",
42
+ "description": "Optional. Text to search for within the results filtered by funding details."
43
+ },
44
+ "query_operator": {
45
+ "type": "string",
46
+ "enum": ["AND", "OR"],
47
+ "description": "Operator for 'query_text' if provided (default: AND).",
48
+ "default": "AND"
49
+ }
50
+ }
51
+ all_properties = {**specific_params, **base_params}
52
+
53
+ return {
54
+ "type": "function",
55
+ "function": {
56
+ "name": self.name,
57
+ "description": (
58
+ "Search for grant opportunities by funding instruments and/or categories. "
59
+ "At least one of 'funding_instruments' or 'funding_categories' must be provided. "
60
+ f"{self._pagination_description}"
61
+ f"{self._status_filter_description}"
62
+ f"{self._common_search_description_suffix}"
63
+ ),
64
+ "parameters": {
65
+ "type": "object",
66
+ "properties": all_properties,
67
+ "required": [] # No single field is required, but logic in fn enforces at least one funding filter
68
+ }
69
+ }
70
+ }
71
+
72
+ def fn(self,
73
+ funding_instruments: Optional[List[str]] = None,
74
+ funding_categories: Optional[List[str]] = None,
75
+ query_text: Optional[str] = None,
76
+ query_operator: str = "AND",
77
+ # Common params
78
+ items_per_page: int = 5,
79
+ page_number: int = 1,
80
+ order_by: str = "relevancy",
81
+ sort_direction: str = "descending",
82
+ show_posted: bool = True,
83
+ show_forecasted: bool = False,
84
+ show_closed: bool = False,
85
+ show_archived: bool = False) -> str:
86
+
87
+ self.logger.info(f"Querying by funding details: instruments={funding_instruments}, categories={funding_categories}, query='{query_text}'")
88
+
89
+ if not funding_instruments and not funding_categories:
90
+ return json.dumps({"error": "At least one of 'funding_instruments' or 'funding_categories' must be provided.", "success": False})
91
+
92
+ specific_filters: Dict[str, Any] = {}
93
+ if funding_instruments:
94
+ specific_filters["funding_instrument"] = {"one_of": funding_instruments}
95
+ if funding_categories:
96
+ specific_filters["funding_category"] = {"one_of": funding_categories}
97
+
98
+ specific_query_params: Optional[Dict[str, Any]] = None
99
+ if query_text:
100
+ specific_query_params = {"query": query_text}
101
+
102
+ payload = self._build_api_payload(
103
+ specific_query_params=specific_query_params,
104
+ specific_filters=specific_filters,
105
+ items_per_page=items_per_page,
106
+ page_number=page_number,
107
+ order_by=order_by,
108
+ sort_direction=sort_direction,
109
+ show_posted=show_posted,
110
+ show_forecasted=show_forecasted,
111
+ show_closed=show_closed,
112
+ show_archived=show_archived,
113
+ query_operator=query_operator
114
+ )
115
+ return self._execute_search(payload)
@@ -0,0 +1,134 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any, List
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByMultipleCriteria(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities by combining multiple filter criteria.
14
+ Use this for more complex queries not covered by specialized tools.
15
+ All filter parameters are optional, but at least one filter or query_text should be provided.
16
+ """
17
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_multiple_criteria"):
18
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
19
+ self.name = name
20
+
21
+ @property
22
+ def definition(self):
23
+ base_params = self._get_common_parameter_definitions()
24
+ # Parameters from other specific tools, all optional here
25
+ specific_params = {
26
+ "agency_codes": {
27
+ "type": "array", "items": {"type": "string"},
28
+ "description": "Optional. List of agency codes (e.g., ['USAID', 'DOC'])."
29
+ },
30
+ "funding_instruments": {
31
+ "type": "array", "items": {"type": "string"},
32
+ "description": "Optional. List of funding instruments."
33
+ },
34
+ "funding_categories": {
35
+ "type": "array", "items": {"type": "string"},
36
+ "description": "Optional. List of funding categories."
37
+ },
38
+ "applicant_types": {
39
+ "type": "array", "items": {"type": "string"},
40
+ "description": "Optional. List of applicant types."
41
+ },
42
+ "assistance_listing_numbers": {
43
+ "type": "array", "items": {"type": "string"},
44
+ "description": "Optional. List of Assistance Listing Numbers."
45
+ },
46
+ "requires_cost_sharing": {
47
+ "type": "boolean",
48
+ "description": "Optional. Filter by cost-sharing requirement."
49
+ },
50
+ # For simplicity, award criteria and dates are not individual params here.
51
+ # If needed, they could be added, or a user could use a more specific tool.
52
+ "query_text": {
53
+ "type": "string",
54
+ "description": "Optional. Text to search for within filtered results."
55
+ },
56
+ "query_operator": {
57
+ "type": "string", "enum": ["AND", "OR"],
58
+ "description": "Operator for 'query_text' if provided (default: AND).", "default": "AND"
59
+ }
60
+ }
61
+ all_properties = {**specific_params, **base_params}
62
+
63
+ return {
64
+ "type": "function",
65
+ "function": {
66
+ "name": self.name,
67
+ "description": (
68
+ "Search grant opportunities by combining various filters like agency, funding types, applicant types, etc. "
69
+ "Useful for complex queries. At least one filter criterion or query_text should be provided. "
70
+ f"{self._pagination_description}"
71
+ f"{self._status_filter_description}"
72
+ f"{self._common_search_description_suffix}"
73
+ ),
74
+ "parameters": {
75
+ "type": "object",
76
+ "properties": all_properties,
77
+ "required": [] # Logic in fn
78
+ }
79
+ }
80
+ }
81
+
82
+ def fn(self,
83
+ agency_codes: Optional[List[str]] = None,
84
+ funding_instruments: Optional[List[str]] = None,
85
+ funding_categories: Optional[List[str]] = None,
86
+ applicant_types: Optional[List[str]] = None,
87
+ assistance_listing_numbers: Optional[List[str]] = None,
88
+ requires_cost_sharing: Optional[bool] = None,
89
+ query_text: Optional[str] = None,
90
+ query_operator: str = "AND",
91
+ # Common params
92
+ items_per_page: int = 5, page_number: int = 1, order_by: str = "relevancy",
93
+ sort_direction: str = "descending", show_posted: bool = True, show_forecasted: bool = False,
94
+ show_closed: bool = False, show_archived: bool = False) -> str:
95
+
96
+ self.logger.info(f"Querying by multiple criteria, query='{query_text}'")
97
+
98
+ specific_filters: Dict[str, Any] = {}
99
+ any_filter_provided = False
100
+
101
+ if agency_codes:
102
+ specific_filters["agency"] = {"one_of": agency_codes}
103
+ any_filter_provided = True
104
+ if funding_instruments:
105
+ specific_filters["funding_instrument"] = {"one_of": funding_instruments}
106
+ any_filter_provided = True
107
+ if funding_categories:
108
+ specific_filters["funding_category"] = {"one_of": funding_categories}
109
+ any_filter_provided = True
110
+ if applicant_types:
111
+ specific_filters["applicant_type"] = {"one_of": applicant_types}
112
+ any_filter_provided = True
113
+ if assistance_listing_numbers:
114
+ specific_filters["assistance_listing_number"] = {"one_of": assistance_listing_numbers}
115
+ any_filter_provided = True
116
+ if requires_cost_sharing is not None:
117
+ specific_filters["is_cost_sharing"] = {"one_of": [requires_cost_sharing]}
118
+ any_filter_provided = True
119
+
120
+ if not any_filter_provided and not query_text:
121
+ return json.dumps({"error": "At least one filter criterion or query_text must be specified for this tool.", "success": False})
122
+
123
+ specific_query_params: Optional[Dict[str, Any]] = None
124
+ if query_text:
125
+ specific_query_params = {"query": query_text}
126
+
127
+ payload = self._build_api_payload(
128
+ specific_query_params=specific_query_params,
129
+ specific_filters=specific_filters if specific_filters else None,
130
+ items_per_page=items_per_page, page_number=page_number, order_by=order_by,
131
+ sort_direction=sort_direction, show_posted=show_posted, show_forecasted=show_forecasted,
132
+ show_closed=show_closed, show_archived=show_archived, query_operator=query_operator
133
+ )
134
+ return self._execute_search(payload)
@@ -0,0 +1,93 @@
1
+ import logging
2
+ from typing import Optional, Dict, Any
3
+
4
+ from .search_base import SearchOpportunitiesBase
5
+ from ..config import FunctionRegistry
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ @FunctionRegistry.register
10
+ class QueryOpportunities(SearchOpportunitiesBase):
11
+ """
12
+ Tool to search for grant opportunities using a general text query.
13
+ """
14
+ def __init__(self, api_key: Optional[str] = None,
15
+ base_url: Optional[str] = None,
16
+ name: str = "query_opportunities"):
17
+ super().__init__(api_key=api_key, base_url=base_url)
18
+ self.name = name
19
+
20
+ @property
21
+ def definition(self):
22
+ base_params = self._get_common_parameter_definitions()
23
+ specific_params = {
24
+ "query_text": {
25
+ "type": "string",
26
+ "description": "The text to search for in opportunity titles, descriptions, etc."
27
+ },
28
+ "query_operator": {
29
+ "type": "string",
30
+ "enum": ["AND", "OR"],
31
+ "description": "Operator for combining terms in 'query_text' (default: AND).",
32
+ "default": "AND"
33
+ }
34
+ }
35
+
36
+ # Combine and order parameters as desired for the definition
37
+ # Typically, specific parameters first, then common ones.
38
+ all_properties = {**specific_params, **base_params}
39
+
40
+ return {
41
+ "type": "function",
42
+ "function": {
43
+ "name": self.name,
44
+ "description": (
45
+ "Search for grant opportunities using a text query. "
46
+ f"{self._pagination_description}"
47
+ f"{self._status_filter_description}"
48
+ f"{self._common_search_description_suffix}"
49
+ ),
50
+ "parameters": {
51
+ "type": "object",
52
+ "properties": all_properties,
53
+ "required": ["query_text"] # Only query_text is strictly required for this tool
54
+ }
55
+ }
56
+ }
57
+
58
+ def fn(self,
59
+ query_text: str,
60
+ query_operator: str = "AND",
61
+ # Common params from base, with defaults
62
+ items_per_page: int = 5,
63
+ page_number: int = 1,
64
+ order_by: str = "relevancy",
65
+ sort_direction: str = "descending",
66
+ show_posted: bool = True,
67
+ show_forecasted: bool = False,
68
+ show_closed: bool = False,
69
+ show_archived: bool = False) -> str:
70
+ """
71
+ Executes the general opportunity search.
72
+ """
73
+ self.logger.info(f"Executing general opportunity query: '{query_text}'")
74
+
75
+ specific_query_params = {
76
+ "query": query_text # API expects "query" not "query_text"
77
+ }
78
+
79
+ payload = self._build_api_payload(
80
+ specific_query_params=specific_query_params,
81
+ specific_filters=None, # No other specific filters for this tool
82
+ items_per_page=items_per_page,
83
+ page_number=page_number,
84
+ order_by=order_by,
85
+ sort_direction=sort_direction,
86
+ show_posted=show_posted,
87
+ show_forecasted=show_forecasted,
88
+ show_closed=show_closed,
89
+ show_archived=show_archived,
90
+ query_operator=query_operator
91
+ )
92
+
93
+ return self._execute_search(payload)
@@ -0,0 +1,104 @@
1
+ import logging
2
+ from typing import Optional, Dict, Any, List
3
+
4
+ from .search_base import SearchOpportunitiesBase
5
+ from ..config import FunctionRegistry
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ @FunctionRegistry.register
10
+ class QueryOpportunitiesByAgencyCode(SearchOpportunitiesBase):
11
+ """
12
+ Tool to search for grant opportunities filtered by agency code(s).
13
+ Optionally, a text query can also be provided.
14
+ """
15
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_agency"):
16
+ super().__init__(api_key=api_key, base_url=base_url)
17
+ self.name = name
18
+
19
+ @property
20
+ def definition(self):
21
+ base_params = self._get_common_parameter_definitions()
22
+ specific_params = {
23
+ "agency_codes": {
24
+ "type": "array",
25
+ "items": {"type": "string"},
26
+ "description": "A list of agency codes to filter by (e.g., ['USAID', 'DOC'])."
27
+ },
28
+ "query_text": {
29
+ "type": "string",
30
+ "description": "Optional. Text to search for within the results filtered by agency."
31
+ },
32
+ "query_operator": {
33
+ "type": "string",
34
+ "enum": ["AND", "OR"],
35
+ "description": "Operator for combining terms in 'query_text' if provided (default: AND).",
36
+ "default": "AND"
37
+ }
38
+ }
39
+ all_properties = {**specific_params, **base_params}
40
+
41
+ return {
42
+ "type": "function",
43
+ "function": {
44
+ "name": self.name,
45
+ "description": (
46
+ "Search for grant opportunities filtered by one or more agency codes. "
47
+ "An optional text query can further refine results. "
48
+ f"{self._pagination_description}"
49
+ f"{self._status_filter_description}"
50
+ f"{self._common_search_description_suffix}"
51
+ ),
52
+ "parameters": {
53
+ "type": "object",
54
+ "properties": all_properties,
55
+ "required": ["agency_codes"]
56
+ }
57
+ }
58
+ }
59
+
60
+ def fn(self,
61
+ agency_codes: List[str],
62
+ query_text: Optional[str] = None,
63
+ query_operator: str = "AND",
64
+ # Common params from base
65
+ items_per_page: int = 5,
66
+ page_number: int = 1,
67
+ order_by: str = "relevancy",
68
+ sort_direction: str = "descending",
69
+ show_posted: bool = True,
70
+ show_forecasted: bool = False,
71
+ show_closed: bool = False,
72
+ show_archived: bool = False) -> str:
73
+ """
74
+ Executes the opportunity search filtered by agency codes.
75
+ """
76
+ self.logger.info(f"Executing opportunity query by agency codes: {agency_codes}, query_text: '{query_text}'")
77
+
78
+ if not agency_codes:
79
+ logger.error("agency_codes list cannot be empty.")
80
+ return json.dumps({"error": "agency_codes list cannot be empty.", "success": False})
81
+
82
+ specific_filters: Dict[str, Any] = {
83
+ "agency": {"one_of": agency_codes}
84
+ }
85
+
86
+ specific_query_params: Optional[Dict[str, Any]] = None
87
+ if query_text:
88
+ specific_query_params = {"query": query_text}
89
+
90
+ payload = self._build_api_payload(
91
+ specific_query_params=specific_query_params,
92
+ specific_filters=specific_filters,
93
+ items_per_page=items_per_page,
94
+ page_number=page_number,
95
+ order_by=order_by,
96
+ sort_direction=sort_direction,
97
+ show_posted=show_posted,
98
+ show_forecasted=show_forecasted,
99
+ show_closed=show_closed,
100
+ show_archived=show_archived,
101
+ query_operator=query_operator
102
+ )
103
+
104
+ return self._execute_search(payload)
@@ -0,0 +1,102 @@
1
+ import logging
2
+ from typing import Optional, Dict, Any
3
+ import json
4
+
5
+ from .base import SimplerGrantsGovBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class SearchAgencies(SimplerGrantsGovBase):
12
+ """
13
+ Tool to search for agencies based on query text and filters.
14
+ Corresponds to the POST /v1/agencies/search endpoint.
15
+ """
16
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "search_agencies"):
17
+ super().__init__(api_key=api_key, base_url=base_url)
18
+ self.name = name
19
+
20
+ @property
21
+ def definition(self):
22
+ # Based on AgencySearchRequestSchema
23
+ return {
24
+ "type": "function",
25
+ "function": {
26
+ "name": self.name,
27
+ "description": "Search for agencies using a query string and structured filters like 'has_active_opportunity' or 'is_test_agency'.",
28
+ "parameters": {
29
+ "type": "object",
30
+ "properties": {
31
+ "query": {
32
+ "type": "string",
33
+ "description": "Optional. Query string which searches against agency text fields."
34
+ },
35
+ "query_operator": {
36
+ "type": "string",
37
+ "enum": ["AND", "OR"],
38
+ "description": "Optional. Operator for combining query conditions (default: OR).",
39
+ "default": "OR"
40
+ },
41
+ "filters": {
42
+ "type": "object",
43
+ "description": "Optional. A JSON object for filtering. Keys can be 'has_active_opportunity' or 'is_test_agency'. Each key holds an object like {'one_of': [true/false]} specifying the filter.",
44
+ "properties": {
45
+ "has_active_opportunity": {
46
+ "type": "object",
47
+ "properties": {"one_of": {"type": "array", "items": {"type": "boolean"}}}
48
+ },
49
+ "is_test_agency": {
50
+ "type": "object",
51
+ "properties": {"one_of": {"type": "array", "items": {"type": "boolean"}}}
52
+ }
53
+ }
54
+ },
55
+ "pagination": {
56
+ "type": "object",
57
+ "description": "Required. A JSON object for pagination. Must include 'page_offset', 'page_size', and 'sort_order' (array of objects with 'order_by': ['agency_code', 'agency_name'] and 'sort_direction': ['ascending', 'descending']).",
58
+ "properties": {
59
+ "page_offset": {"type": "integer", "description": "Page number (starts at 1)."},
60
+ "page_size": {"type": "integer", "description": "Results per page."},
61
+ "sort_order": {
62
+ "type": "array",
63
+ "items": {
64
+ "type": "object",
65
+ "properties": {
66
+ "order_by": {"type": "string", "enum": ["agency_code", "agency_name"]},
67
+ "sort_direction": {"type": "string", "enum": ["ascending", "descending"]}
68
+ },
69
+ "required": ["order_by", "sort_direction"]
70
+ }
71
+ }
72
+ },
73
+ "required": ["page_offset", "page_size", "sort_order"]
74
+ }
75
+ },
76
+ "required": ["pagination"]
77
+ }
78
+ }
79
+ }
80
+
81
+ def fn(self, pagination: Dict[str, Any], query: Optional[str] = None, filters: Optional[Dict[str, Any]] = None, query_operator: str = "OR") -> str:
82
+ """
83
+ Executes the search agencies request.
84
+ """
85
+ self.logger.info("Executing Simpler Grants Gov search agencies tool")
86
+ payload: Dict[str, Any] = {
87
+ "pagination": pagination,
88
+ "query_operator": query_operator
89
+ }
90
+ if query:
91
+ payload["query"] = query
92
+ if filters:
93
+ payload["filters"] = filters
94
+
95
+ endpoint = "/v1/agencies/search"
96
+ try:
97
+ result = self._make_request("POST", endpoint, json_payload=payload)
98
+ self.logger.debug(f"Search agencies successful. Response length: {len(result)}")
99
+ return result
100
+ except Exception as e:
101
+ self.logger.error(f"Search agencies failed: {e}", exc_info=True)
102
+ return json.dumps({"error": f"Search agencies failed: {str(e)}", "success": False})