feat: Enhance response validation and parsing in tool.py (#23456)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from dataclasses import dataclass
|
||||||
from os import getenv
|
from os import getenv
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional, Union
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -20,6 +21,20 @@ API_TOOL_DEFAULT_TIMEOUT = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ParsedResponse:
|
||||||
|
"""Represents a parsed HTTP response with type information"""
|
||||||
|
|
||||||
|
content: Union[str, dict]
|
||||||
|
is_json: bool
|
||||||
|
|
||||||
|
def to_string(self) -> str:
|
||||||
|
"""Convert response to string format for credential validation"""
|
||||||
|
if isinstance(self.content, dict):
|
||||||
|
return json.dumps(self.content, ensure_ascii=False)
|
||||||
|
return str(self.content)
|
||||||
|
|
||||||
|
|
||||||
class ApiTool(Tool):
|
class ApiTool(Tool):
|
||||||
"""
|
"""
|
||||||
Api tool
|
Api tool
|
||||||
@@ -58,7 +73,9 @@ class ApiTool(Tool):
|
|||||||
|
|
||||||
response = self.do_http_request(self.api_bundle.server_url, self.api_bundle.method, headers, parameters)
|
response = self.do_http_request(self.api_bundle.server_url, self.api_bundle.method, headers, parameters)
|
||||||
# validate response
|
# validate response
|
||||||
return self.validate_and_parse_response(response)
|
parsed_response = self.validate_and_parse_response(response)
|
||||||
|
# For credential validation, always return as string
|
||||||
|
return parsed_response.to_string()
|
||||||
|
|
||||||
def tool_provider_type(self) -> ToolProviderType:
|
def tool_provider_type(self) -> ToolProviderType:
|
||||||
return ToolProviderType.API
|
return ToolProviderType.API
|
||||||
@@ -112,23 +129,36 @@ class ApiTool(Tool):
|
|||||||
|
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def validate_and_parse_response(self, response: httpx.Response) -> str:
|
def validate_and_parse_response(self, response: httpx.Response) -> ParsedResponse:
|
||||||
"""
|
"""
|
||||||
validate the response
|
validate the response and return parsed content with type information
|
||||||
|
|
||||||
|
:return: ParsedResponse with content and is_json flag
|
||||||
"""
|
"""
|
||||||
if isinstance(response, httpx.Response):
|
if isinstance(response, httpx.Response):
|
||||||
if response.status_code >= 400:
|
if response.status_code >= 400:
|
||||||
raise ToolInvokeError(f"Request failed with status code {response.status_code} and {response.text}")
|
raise ToolInvokeError(f"Request failed with status code {response.status_code} and {response.text}")
|
||||||
if not response.content:
|
if not response.content:
|
||||||
return "Empty response from the tool, please check your parameters and try again."
|
return ParsedResponse(
|
||||||
|
"Empty response from the tool, please check your parameters and try again.", False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check content type
|
||||||
|
content_type = response.headers.get("content-type", "").lower()
|
||||||
|
is_json_content_type = "application/json" in content_type
|
||||||
|
|
||||||
|
# Try to parse as JSON
|
||||||
try:
|
try:
|
||||||
response = response.json()
|
json_response = response.json()
|
||||||
try:
|
# If content-type indicates JSON, return as JSON object
|
||||||
return json.dumps(response, ensure_ascii=False)
|
if is_json_content_type:
|
||||||
except Exception:
|
return ParsedResponse(json_response, True)
|
||||||
return json.dumps(response)
|
else:
|
||||||
|
# If content-type doesn't indicate JSON, treat as text regardless of content
|
||||||
|
return ParsedResponse(response.text, False)
|
||||||
except Exception:
|
except Exception:
|
||||||
return response.text
|
# Not valid JSON, return as text
|
||||||
|
return ParsedResponse(response.text, False)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid response type {type(response)}")
|
raise ValueError(f"Invalid response type {type(response)}")
|
||||||
|
|
||||||
@@ -369,7 +399,14 @@ class ApiTool(Tool):
|
|||||||
response = self.do_http_request(self.api_bundle.server_url, self.api_bundle.method, headers, tool_parameters)
|
response = self.do_http_request(self.api_bundle.server_url, self.api_bundle.method, headers, tool_parameters)
|
||||||
|
|
||||||
# validate response
|
# validate response
|
||||||
response = self.validate_and_parse_response(response)
|
parsed_response = self.validate_and_parse_response(response)
|
||||||
|
|
||||||
# assemble invoke message
|
# assemble invoke message based on response type
|
||||||
yield self.create_text_message(response)
|
if parsed_response.is_json and isinstance(parsed_response.content, dict):
|
||||||
|
yield self.create_json_message(parsed_response.content)
|
||||||
|
else:
|
||||||
|
# Convert to string if needed and create text message
|
||||||
|
text_response = (
|
||||||
|
parsed_response.content if isinstance(parsed_response.content, str) else str(parsed_response.content)
|
||||||
|
)
|
||||||
|
yield self.create_text_message(text_response)
|
||||||
|
Reference in New Issue
Block a user