gmf_forge_ai_shared_core.tools.builtin_tools

Built-in tools for search, calculation, and API calls.

 1"""Built-in tools for search, calculation, and API calls."""
 2
 3from gmf_forge_ai_shared_core.tools.builtin_tools.search_tool import SearchTool
 4from gmf_forge_ai_shared_core.tools.builtin_tools.calculation_tool import CalculationTool
 5from gmf_forge_ai_shared_core.tools.builtin_tools.api_tool import APITool
 6
 7__all__ = [
 8    "SearchTool",
 9    "CalculationTool",
10    "APITool",
11]
class SearchTool:
 27class SearchTool:
 28    """
 29    Search tool for web and document search.
 30    
 31    Provides a unified interface for searching across different sources:
 32    - Web search (via search APIs)
 33    - Document search (local or vector store)
 34    - Custom search endpoints
 35    
 36    Example:
 37        >>> tool = SearchTool()
 38        >>> 
 39        >>> # Search the web
 40        >>> results = await tool.search(
 41        ...     query="What is RAG?",
 42        ...     max_results=5
 43        ... )
 44        >>> 
 45        >>> for result in results:
 46        ...     print(f"{result.title}: {result.snippet}")
 47    """
 48    
 49    def __init__(
 50        self,
 51        api_key: Optional[str] = None,
 52        search_endpoint: Optional[str] = None,
 53        timeout: int = 30
 54    ):
 55        """
 56        Initialize the search tool.
 57        
 58        Args:
 59            api_key: Optional API key for search service
 60            search_endpoint: Optional custom search endpoint URL
 61            timeout: Request timeout in seconds (default: 30)
 62        """
 63        self.api_key = api_key
 64        self.search_endpoint = search_endpoint
 65        self.timeout = timeout
 66        self._client: Optional[httpx.AsyncClient] = None
 67    
 68    async def _get_client(self) -> httpx.AsyncClient:
 69        """Get or create HTTP client."""
 70        if self._client is None:
 71            self._client = httpx.AsyncClient(timeout=self.timeout)
 72        return self._client
 73    
 74    async def search(
 75        self,
 76        query: str,
 77        max_results: int = 10,
 78        search_type: str = "web",
 79        filters: Optional[Dict[str, Any]] = None
 80    ) -> List[SearchResult]:
 81        """
 82        Perform a search query.
 83        
 84        Args:
 85            query: Search query string
 86            max_results: Maximum number of results to return
 87            search_type: Type of search ("web", "documents", "hybrid")
 88            filters: Optional filters to apply to search
 89            
 90        Returns:
 91            List of SearchResult objects
 92            
 93        Example:
 94            >>> results = await tool.search(
 95            ...     query="machine learning tutorials",
 96            ...     max_results=5,
 97            ...     filters={"language": "en", "date_range": "1y"}
 98            ... )
 99        """
100        if not query or not query.strip():
101            return []
102        
103        if search_type == "web":
104            return await self._web_search(query, max_results, filters)
105        elif search_type == "documents":
106            return await self._document_search(query, max_results, filters)
107        elif search_type == "hybrid":
108            # Combine web and document search
109            web_results = await self._web_search(query, max_results // 2, filters)
110            doc_results = await self._document_search(query, max_results // 2, filters)
111            return web_results + doc_results
112        else:
113            raise ValueError(f"Unknown search type: {search_type}")
114    
115    async def _web_search(
116        self,
117        query: str,
118        max_results: int,
119        filters: Optional[Dict[str, Any]]
120    ) -> List[SearchResult]:
121        """
122        Perform web search.
123        
124        This is a placeholder implementation. In production, integrate with:
125        - Bing Search API
126        - Google Custom Search API
127        - Azure Cognitive Search
128        - Brave Search API
129        """
130        # Placeholder: Return mock results
131        # TODO: Integrate with actual search API
132        results = [
133            SearchResult(
134                title=f"Result {i+1} for: {query}",
135                url=f"https://example.com/result{i+1}",
136                snippet=f"This is a sample search result snippet for query: {query}",
137                score=1.0 - (i * 0.1),
138                metadata={"source": "web", "rank": i+1}
139            )
140            for i in range(min(max_results, 3))
141        ]
142        return results
143    
144    async def _document_search(
145        self,
146        query: str,
147        max_results: int,
148        filters: Optional[Dict[str, Any]]
149    ) -> List[SearchResult]:
150        """
151        Perform document search.
152        
153        This is a placeholder implementation. In production, integrate with:
154        - Azure AI Search
155        - Pinecone
156        - Weaviate
157        - Local vector database
158        """
159        # Placeholder: Return mock results
160        # TODO: Integrate with vector store
161        results = [
162            SearchResult(
163                title=f"Document {i+1}",
164                url=f"document://{i+1}",
165                snippet=f"Document content related to: {query}",
166                score=0.9 - (i * 0.1),
167                metadata={"source": "documents", "rank": i+1}
168            )
169            for i in range(min(max_results, 2))
170        ]
171        return results
172    
173    async def close(self) -> None:
174        """Close the HTTP client."""
175        if self._client:
176            await self._client.aclose()
177            self._client = None
178    
179    def __del__(self):
180        """Cleanup on deletion."""
181        if self._client:
182            import asyncio
183            try:
184                loop = asyncio.get_event_loop()
185                if loop.is_running():
186                    loop.create_task(self.close())
187                else:
188                    loop.run_until_complete(self.close())
189            except Exception:
190                pass  # Ignore errors during cleanup

Search tool for web and document search.

Provides a unified interface for searching across different sources:

  • Web search (via search APIs)
  • Document search (local or vector store)
  • Custom search endpoints

Example:

tool = SearchTool()

Search the web

results = await tool.search( ... query="What is RAG?", ... max_results=5 ... )

for result in results: ... print(f"{result.title}: {result.snippet}")

SearchTool( api_key: Optional[str] = None, search_endpoint: Optional[str] = None, timeout: int = 30)
49    def __init__(
50        self,
51        api_key: Optional[str] = None,
52        search_endpoint: Optional[str] = None,
53        timeout: int = 30
54    ):
55        """
56        Initialize the search tool.
57        
58        Args:
59            api_key: Optional API key for search service
60            search_endpoint: Optional custom search endpoint URL
61            timeout: Request timeout in seconds (default: 30)
62        """
63        self.api_key = api_key
64        self.search_endpoint = search_endpoint
65        self.timeout = timeout
66        self._client: Optional[httpx.AsyncClient] = None

Initialize the search tool.

Args: api_key: Optional API key for search service search_endpoint: Optional custom search endpoint URL timeout: Request timeout in seconds (default: 30)

api_key
search_endpoint
timeout
async def search( self, query: str, max_results: int = 10, search_type: str = 'web', filters: Optional[Dict[str, Any]] = None) -> List[gmf_forge_ai_shared_core.tools.builtin_tools.search_tool.SearchResult]:
 74    async def search(
 75        self,
 76        query: str,
 77        max_results: int = 10,
 78        search_type: str = "web",
 79        filters: Optional[Dict[str, Any]] = None
 80    ) -> List[SearchResult]:
 81        """
 82        Perform a search query.
 83        
 84        Args:
 85            query: Search query string
 86            max_results: Maximum number of results to return
 87            search_type: Type of search ("web", "documents", "hybrid")
 88            filters: Optional filters to apply to search
 89            
 90        Returns:
 91            List of SearchResult objects
 92            
 93        Example:
 94            >>> results = await tool.search(
 95            ...     query="machine learning tutorials",
 96            ...     max_results=5,
 97            ...     filters={"language": "en", "date_range": "1y"}
 98            ... )
 99        """
100        if not query or not query.strip():
101            return []
102        
103        if search_type == "web":
104            return await self._web_search(query, max_results, filters)
105        elif search_type == "documents":
106            return await self._document_search(query, max_results, filters)
107        elif search_type == "hybrid":
108            # Combine web and document search
109            web_results = await self._web_search(query, max_results // 2, filters)
110            doc_results = await self._document_search(query, max_results // 2, filters)
111            return web_results + doc_results
112        else:
113            raise ValueError(f"Unknown search type: {search_type}")

Perform a search query.

Args: query: Search query string max_results: Maximum number of results to return search_type: Type of search ("web", "documents", "hybrid") filters: Optional filters to apply to search

Returns: List of SearchResult objects

Example:

results = await tool.search( ... query="machine learning tutorials", ... max_results=5, ... filters={"language": "en", "date_range": "1y"} ... )

async def close(self) -> None:
173    async def close(self) -> None:
174        """Close the HTTP client."""
175        if self._client:
176            await self._client.aclose()
177            self._client = None

Close the HTTP client.

class CalculationTool:
 15class CalculationTool:
 16    """
 17    Safe mathematical calculation tool.
 18    
 19    Provides sandboxed evaluation of mathematical expressions with support for:
 20    - Basic arithmetic (+, -, *, /, //, %, **)
 21    - Common math functions (sin, cos, sqrt, log, etc.)
 22    - Safe expression parsing (no code execution)
 23    
 24    Example:
 25        >>> calc = CalculationTool()
 26        >>> 
 27        >>> result = calc.calculate("2 + 2 * 3")
 28        >>> print(result)  # 8
 29        >>> 
 30        >>> result = calc.calculate("sqrt(16) + log(100, 10)")
 31        >>> print(result)  # 6.0
 32        >>> 
 33        >>> result = calc.calculate("sin(pi / 2)")
 34        >>> print(result)  # 1.0
 35    """
 36    
 37    # Allowed operators
 38    _OPERATORS = {
 39        ast.Add: operator.add,
 40        ast.Sub: operator.sub,
 41        ast.Mult: operator.mul,
 42        ast.Div: operator.truediv,
 43        ast.FloorDiv: operator.floordiv,
 44        ast.Mod: operator.mod,
 45        ast.Pow: operator.pow,
 46        ast.USub: operator.neg,
 47        ast.UAdd: operator.pos,
 48    }
 49    
 50    # Allowed functions from math module
 51    _FUNCTIONS = {
 52        # Trigonometric
 53        'sin': math.sin,
 54        'cos': math.cos,
 55        'tan': math.tan,
 56        'asin': math.asin,
 57        'acos': math.acos,
 58        'atan': math.atan,
 59        'atan2': math.atan2,
 60        
 61        # Hyperbolic
 62        'sinh': math.sinh,
 63        'cosh': math.cosh,
 64        'tanh': math.tanh,
 65        
 66        # Exponential and logarithmic
 67        'exp': math.exp,
 68        'log': math.log,
 69        'log10': math.log10,
 70        'log2': math.log2,
 71        'sqrt': math.sqrt,
 72        
 73        # Power and rounding
 74        'pow': pow,
 75        'abs': abs,
 76        'round': round,
 77        'ceil': math.ceil,
 78        'floor': math.floor,
 79        
 80        # Other
 81        'factorial': math.factorial,
 82        'gcd': math.gcd,
 83        'degrees': math.degrees,
 84        'radians': math.radians,
 85    }
 86    
 87    # Math constants
 88    _CONSTANTS = {
 89        'pi': math.pi,
 90        'e': math.e,
 91        'tau': math.tau,
 92        'inf': math.inf,
 93        'nan': math.nan,
 94    }
 95    
 96    def __init__(self, precision: int = 10):
 97        """
 98        Initialize the calculation tool.
 99        
100        Args:
101            precision: Number of decimal places for results (default: 10)
102        """
103        self.precision = precision
104    
105    def calculate(self, expression: str) -> Union[float, int]:
106        """
107        Evaluate a mathematical expression safely.
108        
109        Args:
110            expression: Mathematical expression as a string
111            
112        Returns:
113            Calculated result as float or int
114            
115        Raises:
116            ValueError: If expression is invalid or contains unsafe operations
117            ZeroDivisionError: If division by zero occurs
118            
119        Example:
120            >>> calc = CalculationTool()
121            >>> calc.calculate("(5 + 3) * 2")
122            16
123            >>> calc.calculate("sqrt(144)")
124            12.0
125            >>> calc.calculate("2 ** 10")
126            1024
127        """
128        if not expression or not isinstance(expression, str):
129            raise ValueError("Expression must be a non-empty string")
130        
131        try:
132            # Parse the expression into an AST
133            node = ast.parse(expression, mode='eval').body
134            
135            # Evaluate the AST
136            result = self._eval_node(node)
137            
138            # Round to precision
139            if isinstance(result, float):
140                return round(result, self.precision)
141            return result
142            
143        except SyntaxError as e:
144            raise ValueError(f"Invalid expression syntax: {e}")
145        except Exception as e:
146            raise ValueError(f"Calculation error: {e}")
147    
148    def _eval_node(self, node: ast.AST) -> Union[float, int]:
149        """
150        Recursively evaluate an AST node.
151        
152        Args:
153            node: AST node to evaluate
154            
155        Returns:
156            Evaluated result
157            
158        Raises:
159            ValueError: If node type is not allowed
160        """
161        if isinstance(node, ast.Constant):
162            # Python 3.8+ uses ast.Constant
163            return node.value
164        
165        elif isinstance(node, ast.Num):
166            # Fallback for older Python versions
167            return node.n
168        
169        elif isinstance(node, ast.BinOp):
170            # Binary operation (e.g., a + b)
171            left = self._eval_node(node.left)
172            right = self._eval_node(node.right)
173            op_type = type(node.op)
174            
175            if op_type not in self._OPERATORS:
176                raise ValueError(f"Operator {op_type.__name__} not allowed")
177            
178            return self._OPERATORS[op_type](left, right)
179        
180        elif isinstance(node, ast.UnaryOp):
181            # Unary operation (e.g., -a, +a)
182            operand = self._eval_node(node.operand)
183            op_type = type(node.op)
184            
185            if op_type not in self._OPERATORS:
186                raise ValueError(f"Operator {op_type.__name__} not allowed")
187            
188            return self._OPERATORS[op_type](operand)
189        
190        elif isinstance(node, ast.Call):
191            # Function call (e.g., sqrt(4))
192            func_name = node.func.id if isinstance(node.func, ast.Name) else None
193            
194            if func_name not in self._FUNCTIONS:
195                raise ValueError(f"Function '{func_name}' not allowed")
196            
197            func = self._FUNCTIONS[func_name]
198            args = [self._eval_node(arg) for arg in node.args]
199            
200            return func(*args)
201        
202        elif isinstance(node, ast.Name):
203            # Variable/constant (e.g., pi, e)
204            if node.id not in self._CONSTANTS:
205                raise ValueError(f"Variable '{node.id}' not allowed")
206            
207            return self._CONSTANTS[node.id]
208        
209        else:
210            raise ValueError(f"Node type {type(node).__name__} not allowed")
211    
212    def evaluate_multiple(self, expressions: list[str]) -> Dict[str, Union[float, int, str]]:
213        """
214        Evaluate multiple expressions and return results.
215        
216        Args:
217            expressions: List of expression strings
218            
219        Returns:
220            Dictionary mapping expressions to their results or error messages
221            
222        Example:
223            >>> calc = CalculationTool()
224            >>> results = calc.evaluate_multiple([
225            ...     "2 + 2",
226            ...     "sqrt(16)",
227            ...     "1 / 0"  # Will error
228            ... ])
229            >>> print(results)
230            {'2 + 2': 4, 'sqrt(16)': 4.0, '1 / 0': 'Error: ...'}
231        """
232        results = {}
233        for expr in expressions:
234            try:
235                results[expr] = self.calculate(expr)
236            except Exception as e:
237                results[expr] = f"Error: {str(e)}"
238        return results

Safe mathematical calculation tool.

Provides sandboxed evaluation of mathematical expressions with support for:

  • Basic arithmetic (+, -, *, /, //, %, **)
  • Common math functions (sin, cos, sqrt, log, etc.)
  • Safe expression parsing (no code execution)

Example:

calc = CalculationTool()

result = calc.calculate("2 + 2 * 3") print(result) # 8

result = calc.calculate("sqrt(16) + log(100, 10)") print(result) # 6.0

result = calc.calculate("sin(pi / 2)") print(result) # 1.0

CalculationTool(precision: int = 10)
 96    def __init__(self, precision: int = 10):
 97        """
 98        Initialize the calculation tool.
 99        
100        Args:
101            precision: Number of decimal places for results (default: 10)
102        """
103        self.precision = precision

Initialize the calculation tool.

Args: precision: Number of decimal places for results (default: 10)

precision
def calculate(self, expression: str) -> Union[float, int]:
105    def calculate(self, expression: str) -> Union[float, int]:
106        """
107        Evaluate a mathematical expression safely.
108        
109        Args:
110            expression: Mathematical expression as a string
111            
112        Returns:
113            Calculated result as float or int
114            
115        Raises:
116            ValueError: If expression is invalid or contains unsafe operations
117            ZeroDivisionError: If division by zero occurs
118            
119        Example:
120            >>> calc = CalculationTool()
121            >>> calc.calculate("(5 + 3) * 2")
122            16
123            >>> calc.calculate("sqrt(144)")
124            12.0
125            >>> calc.calculate("2 ** 10")
126            1024
127        """
128        if not expression or not isinstance(expression, str):
129            raise ValueError("Expression must be a non-empty string")
130        
131        try:
132            # Parse the expression into an AST
133            node = ast.parse(expression, mode='eval').body
134            
135            # Evaluate the AST
136            result = self._eval_node(node)
137            
138            # Round to precision
139            if isinstance(result, float):
140                return round(result, self.precision)
141            return result
142            
143        except SyntaxError as e:
144            raise ValueError(f"Invalid expression syntax: {e}")
145        except Exception as e:
146            raise ValueError(f"Calculation error: {e}")

Evaluate a mathematical expression safely.

Args: expression: Mathematical expression as a string

Returns: Calculated result as float or int

Raises: ValueError: If expression is invalid or contains unsafe operations ZeroDivisionError: If division by zero occurs

Example:

calc = CalculationTool() calc.calculate("(5 + 3) * 2") 16 calc.calculate("sqrt(144)") 12.0 calc.calculate("2 ** 10") 1024

def evaluate_multiple(self, expressions: list[str]) -> Dict[str, Union[float, int, str]]:
212    def evaluate_multiple(self, expressions: list[str]) -> Dict[str, Union[float, int, str]]:
213        """
214        Evaluate multiple expressions and return results.
215        
216        Args:
217            expressions: List of expression strings
218            
219        Returns:
220            Dictionary mapping expressions to their results or error messages
221            
222        Example:
223            >>> calc = CalculationTool()
224            >>> results = calc.evaluate_multiple([
225            ...     "2 + 2",
226            ...     "sqrt(16)",
227            ...     "1 / 0"  # Will error
228            ... ])
229            >>> print(results)
230            {'2 + 2': 4, 'sqrt(16)': 4.0, '1 / 0': 'Error: ...'}
231        """
232        results = {}
233        for expr in expressions:
234            try:
235                results[expr] = self.calculate(expr)
236            except Exception as e:
237                results[expr] = f"Error: {str(e)}"
238        return results

Evaluate multiple expressions and return results.

Args: expressions: List of expression strings

Returns: Dictionary mapping expressions to their results or error messages

Example:

calc = CalculationTool() results = calc.evaluate_multiple([ ... "2 + 2", ... "sqrt(16)", ... "1 / 0" # Will error ... ]) print(results) {'2 + 2': 4, 'sqrt(16)': 4.0, '1 / 0': 'Error: ...'}

class APITool:
 24class APITool:
 25    """
 26    Tool for making REST API calls.
 27    
 28    Supports all standard HTTP methods (GET, POST, PUT, DELETE, PATCH, etc.)
 29    with authentication, headers, and request body support.
 30    
 31    Example:
 32        >>> tool = APITool()
 33        >>> 
 34        >>> # Simple GET request
 35        >>> response = await tool.get("https://api.example.com/data")
 36        >>> print(response.body)
 37        >>> 
 38        >>> # POST with JSON body
 39        >>> response = await tool.post(
 40        ...     "https://api.example.com/users",
 41        ...     json={"name": "John", "email": "john@example.com"}
 42        ... )
 43        >>> 
 44        >>> # With authentication
 45        >>> response = await tool.get(
 46        ...     "https://api.example.com/secure",
 47        ...     headers={"Authorization": "Bearer token123"}
 48        ... )
 49    """
 50    
 51    def __init__(
 52        self,
 53        base_url: Optional[str] = None,
 54        default_headers: Optional[Dict[str, str]] = None,
 55        timeout: int = 30,
 56        follow_redirects: bool = True
 57    ):
 58        """
 59        Initialize the API tool.
 60        
 61        Args:
 62            base_url: Optional base URL to prepend to all requests
 63            default_headers: Default headers to include in all requests
 64            timeout: Request timeout in seconds (default: 30)
 65            follow_redirects: Whether to follow redirects (default: True)
 66        """
 67        self.base_url = base_url.rstrip('/') if base_url else None
 68        self.default_headers = default_headers or {}
 69        self.timeout = timeout
 70        self.follow_redirects = follow_redirects
 71        self._client: Optional[httpx.AsyncClient] = None
 72    
 73    async def _get_client(self) -> httpx.AsyncClient:
 74        """Get or create HTTP client."""
 75        if self._client is None:
 76            self._client = httpx.AsyncClient(
 77                timeout=self.timeout,
 78                follow_redirects=self.follow_redirects
 79            )
 80        return self._client
 81    
 82    def _build_url(self, path: str) -> str:
 83        """Build full URL from path."""
 84        if self.base_url and not path.startswith(('http://', 'https://')):
 85            return f"{self.base_url}/{path.lstrip('/')}"
 86        return path
 87    
 88    def _merge_headers(self, headers: Optional[Dict[str, str]]) -> Dict[str, str]:
 89        """Merge default headers with request-specific headers."""
 90        merged = self.default_headers.copy()
 91        if headers:
 92            merged.update(headers)
 93        return merged
 94    
 95    async def request(
 96        self,
 97        method: str,
 98        url: str,
 99        headers: Optional[Dict[str, str]] = None,
100        params: Optional[Dict[str, Any]] = None,
101        json: Optional[Dict[str, Any]] = None,
102        data: Optional[Union[Dict[str, Any], str]] = None,
103        **kwargs: Any
104    ) -> APIResponse:
105        """
106        Make an HTTP request.
107        
108        Args:
109            method: HTTP method (GET, POST, PUT, DELETE, etc.)
110            url: URL or path (combined with base_url if set)
111            headers: Optional request headers
112            params: Optional query parameters
113            json: Optional JSON body
114            data: Optional form data or raw body
115            **kwargs: Additional arguments passed to httpx
116            
117        Returns:
118            APIResponse object with status, body, and headers
119            
120        Example:
121            >>> response = await tool.request(
122            ...     "POST",
123            ...     "/api/endpoint",
124            ...     json={"key": "value"},
125            ...     headers={"X-Custom": "header"}
126            ... )
127        """
128        client = await self._get_client()
129        full_url = self._build_url(url)
130        merged_headers = self._merge_headers(headers)
131        
132        try:
133            response = await client.request(
134                method=method.upper(),
135                url=full_url,
136                headers=merged_headers,
137                params=params,
138                json=json,
139                data=data,
140                **kwargs
141            )
142            
143            # Try to parse response as JSON, fallback to text
144            try:
145                body = response.json()
146            except Exception:
147                body = response.text
148            
149            return APIResponse(
150                status_code=response.status_code,
151                body=body,
152                headers=dict(response.headers),
153                success=response.is_success,
154                error=None if response.is_success else response.text
155            )
156            
157        except httpx.TimeoutException as e:
158            return APIResponse(
159                status_code=408,
160                body=None,
161                success=False,
162                error=f"Request timeout: {str(e)}"
163            )
164        except httpx.RequestError as e:
165            return APIResponse(
166                status_code=0,
167                body=None,
168                success=False,
169                error=f"Request error: {str(e)}"
170            )
171        except Exception as e:
172            return APIResponse(
173                status_code=0,
174                body=None,
175                success=False,
176                error=f"Unexpected error: {str(e)}"
177            )
178    
179    async def get(
180        self,
181        url: str,
182        params: Optional[Dict[str, Any]] = None,
183        headers: Optional[Dict[str, str]] = None,
184        **kwargs: Any
185    ) -> APIResponse:
186        """
187        Make a GET request.
188        
189        Args:
190            url: URL or path
191            params: Optional query parameters
192            headers: Optional headers
193            **kwargs: Additional arguments
194            
195        Returns:
196            APIResponse object
197        """
198        return await self.request("GET", url, params=params, headers=headers, **kwargs)
199    
200    async def post(
201        self,
202        url: str,
203        json: Optional[Dict[str, Any]] = None,
204        data: Optional[Union[Dict[str, Any], str]] = None,
205        headers: Optional[Dict[str, str]] = None,
206        **kwargs: Any
207    ) -> APIResponse:
208        """
209        Make a POST request.
210        
211        Args:
212            url: URL or path
213            json: Optional JSON body
214            data: Optional form data
215            headers: Optional headers
216            **kwargs: Additional arguments
217            
218        Returns:
219            APIResponse object
220        """
221        return await self.request("POST", url, json=json, data=data, headers=headers, **kwargs)
222    
223    async def put(
224        self,
225        url: str,
226        json: Optional[Dict[str, Any]] = None,
227        data: Optional[Union[Dict[str, Any], str]] = None,
228        headers: Optional[Dict[str, str]] = None,
229        **kwargs: Any
230    ) -> APIResponse:
231        """
232        Make a PUT request.
233        
234        Args:
235            url: URL or path
236            json: Optional JSON body
237            data: Optional form data
238            headers: Optional headers
239            **kwargs: Additional arguments
240            
241        Returns:
242            APIResponse object
243        """
244        return await self.request("PUT", url, json=json, data=data, headers=headers, **kwargs)
245    
246    async def delete(
247        self,
248        url: str,
249        headers: Optional[Dict[str, str]] = None,
250        **kwargs: Any
251    ) -> APIResponse:
252        """
253        Make a DELETE request.
254        
255        Args:
256            url: URL or path
257            headers: Optional headers
258            **kwargs: Additional arguments
259            
260        Returns:
261            APIResponse object
262        """
263        return await self.request("DELETE", url, headers=headers, **kwargs)
264    
265    async def patch(
266        self,
267        url: str,
268        json: Optional[Dict[str, Any]] = None,
269        data: Optional[Union[Dict[str, Any], str]] = None,
270        headers: Optional[Dict[str, str]] = None,
271        **kwargs: Any
272    ) -> APIResponse:
273        """
274        Make a PATCH request.
275        
276        Args:
277            url: URL or path
278            json: Optional JSON body
279            data: Optional form data
280            headers: Optional headers
281            **kwargs: Additional arguments
282            
283        Returns:
284            APIResponse object
285        """
286        return await self.request("PATCH", url, json=json, data=data, headers=headers, **kwargs)
287    
288    async def close(self) -> None:
289        """Close the HTTP client."""
290        if self._client:
291            await self._client.aclose()
292            self._client = None
293    
294    def __del__(self):
295        """Cleanup on deletion."""
296        if self._client:
297            import asyncio
298            try:
299                loop = asyncio.get_event_loop()
300                if loop.is_running():
301                    loop.create_task(self.close())
302                else:
303                    loop.run_until_complete(self.close())
304            except Exception:
305                pass  # Ignore errors during cleanup

Tool for making REST API calls.

Supports all standard HTTP methods (GET, POST, PUT, DELETE, PATCH, etc.) with authentication, headers, and request body support.

Example:

tool = APITool()

Simple GET request

response = await tool.get("https://api.example.com/data") print(response.body)

POST with JSON body

response = await tool.post( ... "https://api.example.com/users", ... json={"name": "John", "email": "john@example.com"} ... )

With authentication

response = await tool.get( ... "https://api.example.com/secure", ... headers={"Authorization": "Bearer token123"} ... )

APITool( base_url: Optional[str] = None, default_headers: Optional[Dict[str, str]] = None, timeout: int = 30, follow_redirects: bool = True)
51    def __init__(
52        self,
53        base_url: Optional[str] = None,
54        default_headers: Optional[Dict[str, str]] = None,
55        timeout: int = 30,
56        follow_redirects: bool = True
57    ):
58        """
59        Initialize the API tool.
60        
61        Args:
62            base_url: Optional base URL to prepend to all requests
63            default_headers: Default headers to include in all requests
64            timeout: Request timeout in seconds (default: 30)
65            follow_redirects: Whether to follow redirects (default: True)
66        """
67        self.base_url = base_url.rstrip('/') if base_url else None
68        self.default_headers = default_headers or {}
69        self.timeout = timeout
70        self.follow_redirects = follow_redirects
71        self._client: Optional[httpx.AsyncClient] = None

Initialize the API tool.

Args: base_url: Optional base URL to prepend to all requests default_headers: Default headers to include in all requests timeout: Request timeout in seconds (default: 30) follow_redirects: Whether to follow redirects (default: True)

base_url
default_headers
timeout
follow_redirects
async def request( self, method: str, url: str, headers: Optional[Dict[str, str]] = None, params: Optional[Dict[str, Any]] = None, json: Optional[Dict[str, Any]] = None, data: Union[str, Dict[str, Any], NoneType] = None, **kwargs: Any) -> gmf_forge_ai_shared_core.tools.builtin_tools.api_tool.APIResponse:
 95    async def request(
 96        self,
 97        method: str,
 98        url: str,
 99        headers: Optional[Dict[str, str]] = None,
100        params: Optional[Dict[str, Any]] = None,
101        json: Optional[Dict[str, Any]] = None,
102        data: Optional[Union[Dict[str, Any], str]] = None,
103        **kwargs: Any
104    ) -> APIResponse:
105        """
106        Make an HTTP request.
107        
108        Args:
109            method: HTTP method (GET, POST, PUT, DELETE, etc.)
110            url: URL or path (combined with base_url if set)
111            headers: Optional request headers
112            params: Optional query parameters
113            json: Optional JSON body
114            data: Optional form data or raw body
115            **kwargs: Additional arguments passed to httpx
116            
117        Returns:
118            APIResponse object with status, body, and headers
119            
120        Example:
121            >>> response = await tool.request(
122            ...     "POST",
123            ...     "/api/endpoint",
124            ...     json={"key": "value"},
125            ...     headers={"X-Custom": "header"}
126            ... )
127        """
128        client = await self._get_client()
129        full_url = self._build_url(url)
130        merged_headers = self._merge_headers(headers)
131        
132        try:
133            response = await client.request(
134                method=method.upper(),
135                url=full_url,
136                headers=merged_headers,
137                params=params,
138                json=json,
139                data=data,
140                **kwargs
141            )
142            
143            # Try to parse response as JSON, fallback to text
144            try:
145                body = response.json()
146            except Exception:
147                body = response.text
148            
149            return APIResponse(
150                status_code=response.status_code,
151                body=body,
152                headers=dict(response.headers),
153                success=response.is_success,
154                error=None if response.is_success else response.text
155            )
156            
157        except httpx.TimeoutException as e:
158            return APIResponse(
159                status_code=408,
160                body=None,
161                success=False,
162                error=f"Request timeout: {str(e)}"
163            )
164        except httpx.RequestError as e:
165            return APIResponse(
166                status_code=0,
167                body=None,
168                success=False,
169                error=f"Request error: {str(e)}"
170            )
171        except Exception as e:
172            return APIResponse(
173                status_code=0,
174                body=None,
175                success=False,
176                error=f"Unexpected error: {str(e)}"
177            )

Make an HTTP request.

Args: method: HTTP method (GET, POST, PUT, DELETE, etc.) url: URL or path (combined with base_url if set) headers: Optional request headers params: Optional query parameters json: Optional JSON body data: Optional form data or raw body **kwargs: Additional arguments passed to httpx

Returns: APIResponse object with status, body, and headers

Example:

response = await tool.request( ... "POST", ... "/api/endpoint", ... json={"key": "value"}, ... headers={"X-Custom": "header"} ... )

async def get( self, url: str, params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> gmf_forge_ai_shared_core.tools.builtin_tools.api_tool.APIResponse:
179    async def get(
180        self,
181        url: str,
182        params: Optional[Dict[str, Any]] = None,
183        headers: Optional[Dict[str, str]] = None,
184        **kwargs: Any
185    ) -> APIResponse:
186        """
187        Make a GET request.
188        
189        Args:
190            url: URL or path
191            params: Optional query parameters
192            headers: Optional headers
193            **kwargs: Additional arguments
194            
195        Returns:
196            APIResponse object
197        """
198        return await self.request("GET", url, params=params, headers=headers, **kwargs)

Make a GET request.

Args: url: URL or path params: Optional query parameters headers: Optional headers **kwargs: Additional arguments

Returns: APIResponse object

async def post( self, url: str, json: Optional[Dict[str, Any]] = None, data: Union[str, Dict[str, Any], NoneType] = None, headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> gmf_forge_ai_shared_core.tools.builtin_tools.api_tool.APIResponse:
200    async def post(
201        self,
202        url: str,
203        json: Optional[Dict[str, Any]] = None,
204        data: Optional[Union[Dict[str, Any], str]] = None,
205        headers: Optional[Dict[str, str]] = None,
206        **kwargs: Any
207    ) -> APIResponse:
208        """
209        Make a POST request.
210        
211        Args:
212            url: URL or path
213            json: Optional JSON body
214            data: Optional form data
215            headers: Optional headers
216            **kwargs: Additional arguments
217            
218        Returns:
219            APIResponse object
220        """
221        return await self.request("POST", url, json=json, data=data, headers=headers, **kwargs)

Make a POST request.

Args: url: URL or path json: Optional JSON body data: Optional form data headers: Optional headers **kwargs: Additional arguments

Returns: APIResponse object

async def put( self, url: str, json: Optional[Dict[str, Any]] = None, data: Union[str, Dict[str, Any], NoneType] = None, headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> gmf_forge_ai_shared_core.tools.builtin_tools.api_tool.APIResponse:
223    async def put(
224        self,
225        url: str,
226        json: Optional[Dict[str, Any]] = None,
227        data: Optional[Union[Dict[str, Any], str]] = None,
228        headers: Optional[Dict[str, str]] = None,
229        **kwargs: Any
230    ) -> APIResponse:
231        """
232        Make a PUT request.
233        
234        Args:
235            url: URL or path
236            json: Optional JSON body
237            data: Optional form data
238            headers: Optional headers
239            **kwargs: Additional arguments
240            
241        Returns:
242            APIResponse object
243        """
244        return await self.request("PUT", url, json=json, data=data, headers=headers, **kwargs)

Make a PUT request.

Args: url: URL or path json: Optional JSON body data: Optional form data headers: Optional headers **kwargs: Additional arguments

Returns: APIResponse object

async def delete( self, url: str, headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> gmf_forge_ai_shared_core.tools.builtin_tools.api_tool.APIResponse:
246    async def delete(
247        self,
248        url: str,
249        headers: Optional[Dict[str, str]] = None,
250        **kwargs: Any
251    ) -> APIResponse:
252        """
253        Make a DELETE request.
254        
255        Args:
256            url: URL or path
257            headers: Optional headers
258            **kwargs: Additional arguments
259            
260        Returns:
261            APIResponse object
262        """
263        return await self.request("DELETE", url, headers=headers, **kwargs)

Make a DELETE request.

Args: url: URL or path headers: Optional headers **kwargs: Additional arguments

Returns: APIResponse object

async def patch( self, url: str, json: Optional[Dict[str, Any]] = None, data: Union[str, Dict[str, Any], NoneType] = None, headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> gmf_forge_ai_shared_core.tools.builtin_tools.api_tool.APIResponse:
265    async def patch(
266        self,
267        url: str,
268        json: Optional[Dict[str, Any]] = None,
269        data: Optional[Union[Dict[str, Any], str]] = None,
270        headers: Optional[Dict[str, str]] = None,
271        **kwargs: Any
272    ) -> APIResponse:
273        """
274        Make a PATCH request.
275        
276        Args:
277            url: URL or path
278            json: Optional JSON body
279            data: Optional form data
280            headers: Optional headers
281            **kwargs: Additional arguments
282            
283        Returns:
284            APIResponse object
285        """
286        return await self.request("PATCH", url, json=json, data=data, headers=headers, **kwargs)

Make a PATCH request.

Args: url: URL or path json: Optional JSON body data: Optional form data headers: Optional headers **kwargs: Additional arguments

Returns: APIResponse object

async def close(self) -> None:
288    async def close(self) -> None:
289        """Close the HTTP client."""
290        if self._client:
291            await self._client.aclose()
292            self._client = None

Close the HTTP client.