feat: Make OpenAPI tool async

Merge https://github.com/google/adk-python/pull/2872

Closes https://github.com/google/adk-python/issues/787

The OpenAPI tool has been ported to the httpx client to make requests truly asynchronous.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/2872 from condorcet:async_openapi_tool bf83f73af93f624126462fb0bd41fef27c53a0b6
PiperOrigin-RevId: 864250822
This commit is contained in:
Vasilii Novikov
2026-02-02 02:15:52 -08:00
committed by Copybara-Service
parent 2770012cec
commit 9290b96626
5 changed files with 56 additions and 40 deletions
@@ -28,9 +28,9 @@ from fastapi.openapi.models import HTTPBearer
from fastapi.openapi.models import OAuth2
from fastapi.openapi.models import OpenIdConnect
from fastapi.openapi.models import Schema
import httpx
from pydantic import BaseModel
from pydantic import ValidationError
import requests
from ....auth.auth_credential import AuthCredential
from ....auth.auth_credential import AuthCredentialTypes
@@ -289,14 +289,14 @@ def openid_url_to_scheme_credential(
Raises:
ValueError: If the OpenID URL is invalid, fetching fails, or required
fields are missing.
requests.exceptions.RequestException: If there's an error during the
httpx.HTTPStatusError or httpx.RequestError: If there's an error during the
HTTP request.
"""
try:
response = requests.get(openid_url, timeout=10)
response = httpx.get(openid_url, timeout=10)
response.raise_for_status()
config_dict = response.json()
except requests.exceptions.RequestException as e:
except httpx.RequestError as e:
raise ValueError(
f"Failed to fetch OpenID configuration from {openid_url}: {e}"
) from e
@@ -28,7 +28,7 @@ from typing import Union
from fastapi.openapi.models import Operation
from fastapi.openapi.models import Schema
from google.genai.types import FunctionDeclaration
import requests
import httpx
from typing_extensions import override
from ....agents.readonly_context import ReadonlyContext
@@ -312,7 +312,7 @@ class RestApiTool(BaseTool):
Returns:
A dictionary containing the request parameters for the API call. This
initializes a requests.request() call.
initializes an httpx.AsyncClient.request() call.
Example:
self._prepare_request_params({"input_id": "test-id"})
@@ -497,17 +497,7 @@ class RestApiTool(BaseTool):
if provider_headers:
request_params.setdefault("headers", {}).update(provider_headers)
# Log the API request
self._logger.debug(
"API Request: %s %s",
request_params.get("method", "").upper(),
request_params.get("url", ""),
)
self._logger.debug("API Request params: %s", request_params.get("params"))
if "json" in request_params:
self._logger.debug("API Request body: %s", request_params.get("json"))
response = requests.request(**request_params)
response = await _request(**request_params)
# Log the API response
self._logger.debug(
@@ -519,11 +509,9 @@ class RestApiTool(BaseTool):
# Parse API response
try:
response.raise_for_status() # Raise HTTPError for bad responses
result = response.json() # Try to decode JSON
self._logger.debug("API Response body: %s", result)
return result
except requests.exceptions.HTTPError:
response.raise_for_status() # Raise HTTPStatusError for bad responses
return response.json() # Try to decode JSON
except httpx.HTTPStatusError:
error_details = response.content.decode("utf-8")
self._logger.warning(
"API call failed for tool %s: Status %d - %s",
@@ -556,3 +544,10 @@ class RestApiTool(BaseTool):
f' auth_scheme="{self.auth_scheme}",'
f' auth_credential="{self.auth_credential}")'
)
async def _request(**request_params) -> httpx.Response:
async with httpx.AsyncClient(
verify=request_params.pop("verify", True)
) as client:
return await client.request(**request_params)