refactor: streamline tool response handling and improve error logging in LiteLLM and BaseTool

pull/904/head
harshalmore31 2 weeks ago
parent 58921ffd84
commit d6fa21af46

@ -2800,18 +2800,13 @@ class Agent:
) )
def execute_tools(self, response: any, loop_count: int): def execute_tools(self, response: any, loop_count: int):
try:
output = ( output = (
self.tool_struct.execute_function_calls_from_api_response( self.tool_struct.execute_function_calls_from_api_response(
response response
) )
) )
# Handle empty or None output
if not output:
logger.info("No tool function calls found in response")
return
self.short_memory.add( self.short_memory.add(
role="Tool Executor", role="Tool Executor",
content=format_data_structure(output), content=format_data_structure(output),
@ -2825,7 +2820,6 @@ class Agent:
# Now run the LLM again without tools - create a temporary LLM instance # Now run the LLM again without tools - create a temporary LLM instance
# instead of modifying the cached one # instead of modifying the cached one
# Create a temporary LLM instance without tools for the follow-up call # Create a temporary LLM instance without tools for the follow-up call
try:
temp_llm = self.temp_llm_instance_for_tool_summary() temp_llm = self.temp_llm_instance_for_tool_summary()
tool_response = temp_llm.run( tool_response = temp_llm.run(
@ -2848,24 +2842,6 @@ class Agent:
f"{tool_response}", f"{tool_response}",
loop_count, loop_count,
) )
except Exception as e:
logger.error(f"Error in tool summary generation: {e}")
# Add a fallback summary
fallback_summary = f"Tool execution completed. Output: {format_data_structure(output)}"
self.short_memory.add(
role=self.agent_name,
content=fallback_summary,
)
self.pretty_print(fallback_summary, loop_count)
except Exception as e:
logger.error(f"Error in tool execution: {e}")
error_message = f"Tool execution failed: {str(e)}"
self.short_memory.add(
role="Tool Executor",
content=error_message,
)
self.pretty_print(error_message, loop_count)
def list_output_types(self): def list_output_types(self):
return OutputType return OutputType

@ -2253,15 +2253,6 @@ class BaseTool(BaseModel):
else: else:
# Convert string to dict if needed # Convert string to dict if needed
if isinstance(api_response, str): if isinstance(api_response, str):
# Handle empty or whitespace-only strings
api_response = api_response.strip()
if not api_response:
self._log_if_verbose(
"warning",
"Empty API response string received, returning empty list"
)
return []
try: try:
api_response = json.loads(api_response) api_response = json.loads(api_response)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
@ -2269,7 +2260,6 @@ class BaseTool(BaseModel):
"error", "error",
f"Failed to parse JSON from API response: {e}. Response: '{api_response[:100]}...'" f"Failed to parse JSON from API response: {e}. Response: '{api_response[:100]}...'"
) )
# If JSON parsing fails, try to continue without function calls
return [] return []
if not isinstance(api_response, dict): if not isinstance(api_response, dict):
@ -2401,10 +2391,11 @@ class BaseTool(BaseModel):
if name: if name:
try: try:
# Parse arguments JSON string # Parse arguments JSON string
if isinstance(arguments_str, str): arguments = (
arguments = self._parse_json_string(arguments_str) json.loads(arguments_str)
else: if isinstance(arguments_str, str)
arguments = arguments_str if arguments_str is not None else {} else arguments_str
)
function_calls.append( function_calls.append(
{ {
@ -2417,16 +2408,7 @@ class BaseTool(BaseModel):
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
self._log_if_verbose( self._log_if_verbose(
"error", "error",
f"Failed to parse arguments for {name}: {e}. Using empty dict instead.", f"Failed to parse arguments for {name}: {e}",
)
# Use empty dict as fallback
function_calls.append(
{
"name": name,
"arguments": {},
"id": response.get("id"),
"type": "openai",
}
) )
# Check for choices[].message.tool_calls format # Check for choices[].message.tool_calls format
@ -2907,15 +2889,12 @@ class BaseTool(BaseModel):
if name: if name:
try: try:
# Parse arguments JSON string with better error handling # Parse arguments JSON string
if isinstance(arguments_str, str): arguments = (
arguments_str = arguments_str.strip() json.loads(arguments_str)
if not arguments_str: if isinstance(arguments_str, str)
arguments = {} else arguments_str
else: )
arguments = json.loads(arguments_str)
else:
arguments = arguments_str if arguments_str is not None else {}
function_calls.append( function_calls.append(
{ {
@ -2930,18 +2909,7 @@ class BaseTool(BaseModel):
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
self._log_if_verbose( self._log_if_verbose(
"error", "error",
f"Failed to parse arguments for {name}: {e}. Using empty dict instead.", f"Failed to parse arguments for {name}: {e}",
)
# Use empty dict as fallback
function_calls.append(
{
"name": name,
"arguments": {},
"id": getattr(
tool_call, "id", None
),
"type": "openai",
}
) )
# Handle dictionary representations of tool calls # Handle dictionary representations of tool calls

@ -152,16 +152,7 @@ class LiteLLM:
) )
def output_for_tools(self, response: any): def output_for_tools(self, response: any):
try:
if self.mcp_call is True: if self.mcp_call is True:
# Validate response structure for MCP calls
if (hasattr(response, 'choices') and
len(response.choices) > 0 and
hasattr(response.choices[0], 'message') and
hasattr(response.choices[0].message, 'tool_calls') and
response.choices[0].message.tool_calls and
len(response.choices[0].message.tool_calls) > 0):
out = response.choices[0].message.tool_calls[0].function out = response.choices[0].message.tool_calls[0].function
output = { output = {
"function": { "function": {
@ -171,26 +162,11 @@ class LiteLLM:
} }
return output return output
else: else:
logger.warning("Invalid MCP response structure, returning empty dict")
return {}
else:
# Validate response structure for regular tool calls
if (hasattr(response, 'choices') and
len(response.choices) > 0 and
hasattr(response.choices[0], 'message') and
hasattr(response.choices[0].message, 'tool_calls')):
out = response.choices[0].message.tool_calls out = response.choices[0].message.tool_calls
if isinstance(out, BaseModel): if isinstance(out, BaseModel):
out = out.model_dump() out = out.model_dump()
return out return out
else:
logger.warning("Invalid tool response structure, returning empty list")
return []
except Exception as e:
logger.error(f"Error processing tool response: {e}")
return {} if self.mcp_call else []
def _prepare_messages( def _prepare_messages(
self, self,
@ -473,29 +449,14 @@ class LiteLLM:
# Make the completion call # Make the completion call
response = completion(**completion_params) response = completion(**completion_params)
# Validate response structure before processing
if not hasattr(response, 'choices') or not response.choices:
logger.error("Invalid response: no choices found")
return "Error: Invalid response from API"
if not hasattr(response.choices[0], 'message'):
logger.error("Invalid response: no message found in first choice")
return "Error: Invalid response structure"
# Handle tool-based response # Handle tool-based response
if (self.tools_list_dictionary is not None and if self.tools_list_dictionary is not None:
hasattr(response.choices[0].message, 'tool_calls') and
response.choices[0].message.tool_calls is not None):
return self.output_for_tools(response) return self.output_for_tools(response)
elif self.return_all is True: elif self.return_all is True:
return response.model_dump() return response.model_dump()
else: else:
# Return standard response content # Return standard response content
content = response.choices[0].message.content return response.choices[0].message.content
if content is None:
logger.warning("Response content is None, returning empty string")
return ""
return content
except LiteLLMException as error: except LiteLLMException as error:
logger.error( logger.error(

Loading…
Cancel
Save