Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,17 @@ async def call(
)
except ValueError:
self._logger.debug("API Response (non-JSON): %s", response.text)
return {"text": response.text} # Return text if not JSON
# A bare "text" leaves the model unable to tell an empty or non-JSON
# body from data, so say which one came back. The call still succeeded,
# so this goes under "message", never "error".
if response.text.strip():
message = "The response body was not valid JSON."
else:
message = "The response body was empty."
return {
"text": response.text, # Return text if not JSON
"message": f"{message} Status Code: {response.status_code}",
}

def _format_error_response(self, error_message: str) -> dict[str, str]:
"""Formats a tool execution error response dict."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,113 @@ async def test_call_http_failure_decodes_body_with_declared_charset(
"Status Code: 404, Commande introuvable : échec"
)

@pytest.mark.parametrize(
"content",
[b"", b" \n\t "],
ids=["empty", "whitespace"],
)
@patch(
"google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool._request"
)
@pytest.mark.asyncio
async def test_call_empty_body_says_the_body_was_empty(
self,
mock_request,
content,
mock_tool_context,
sample_endpoint,
sample_operation,
):
"""An empty 2xx body is labelled so the model does not read it as data."""
mock_request.return_value = httpx.Response(
status_code=200,
request=httpx.Request("GET", "https://example.com/test"),
content=content,
)
tool = RestApiTool(
name="test_tool",
description="Test Tool",
endpoint=sample_endpoint,
operation=sample_operation,
)

result = await tool.call(args={}, tool_context=mock_tool_context)

assert result == {
"text": content.decode(),
"message": "The response body was empty. Status Code: 200",
}

@pytest.mark.parametrize(
"content, content_type",
[
(b"<html><body>Saved</body></html>", "text/html"),
(b"Saved", "text/plain"),
],
ids=["html", "plain_text"],
)
@patch(
"google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool._request"
)
@pytest.mark.asyncio
async def test_call_non_json_body_says_the_body_was_not_json(
self,
mock_request,
content,
content_type,
mock_tool_context,
sample_endpoint,
sample_operation,
):
"""A non-JSON 2xx body keeps its text and is labelled as not JSON."""
mock_request.return_value = httpx.Response(
status_code=200,
request=httpx.Request("GET", "https://example.com/test"),
content=content,
headers={"content-type": content_type},
)
tool = RestApiTool(
name="test_tool",
description="Test Tool",
endpoint=sample_endpoint,
operation=sample_operation,
)

result = await tool.call(args={}, tool_context=mock_tool_context)

assert result == {
"text": content.decode(),
"message": "The response body was not valid JSON. Status Code: 200",
}

@patch(
"google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool._request"
)
@pytest.mark.asyncio
async def test_call_json_body_is_returned_unchanged(
self,
mock_request,
mock_tool_context,
sample_endpoint,
sample_operation,
):
"""A JSON 2xx body reaches the model as decoded, with nothing added."""
mock_request.return_value = httpx.Response(
status_code=200,
request=httpx.Request("GET", "https://example.com/test"),
json={"result": "ok"},
)
tool = RestApiTool(
name="test_tool",
description="Test Tool",
endpoint=sample_endpoint,
operation=sample_operation,
)

result = await tool.call(args={}, tool_context=mock_tool_context)

assert result == {"result": "ok"}

@patch(
"google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool._request"
)
Expand Down
Loading