feat: skip empty fields in mcp tool call + improve error handling and response

This commit is contained in:
Siddhant Rai
2025-09-11 19:04:10 +05:30
parent 09b9576eef
commit 641cf5a4c1
8 changed files with 69 additions and 37 deletions

View File

@@ -142,28 +142,28 @@ class BaseAgent(ABC):
tool_id, action_name, call_args = parser.parse_args(call) tool_id, action_name, call_args = parser.parse_args(call)
call_id = getattr(call, "id", None) or str(uuid.uuid4()) call_id = getattr(call, "id", None) or str(uuid.uuid4())
# Check if parsing failed # Check if parsing failed
if tool_id is None or action_name is None: if tool_id is None or action_name is None:
error_message = f"Error: Failed to parse LLM tool call. Tool name: {getattr(call, 'name', 'unknown')}" error_message = f"Error: Failed to parse LLM tool call. Tool name: {getattr(call, 'name', 'unknown')}"
logger.error(error_message) logger.error(error_message)
tool_call_data = { tool_call_data = {
"tool_name": "unknown", "tool_name": "unknown",
"call_id": call_id, "call_id": call_id,
"action_name": getattr(call, 'name', 'unknown'), "action_name": getattr(call, "name", "unknown"),
"arguments": call_args or {}, "arguments": call_args or {},
"result": f"Failed to parse tool call. Invalid tool name format: {getattr(call, 'name', 'unknown')}", "result": f"Failed to parse tool call. Invalid tool name format: {getattr(call, 'name', 'unknown')}",
} }
yield {"type": "tool_call", "data": {**tool_call_data, "status": "error"}} yield {"type": "tool_call", "data": {**tool_call_data, "status": "error"}}
self.tool_calls.append(tool_call_data) self.tool_calls.append(tool_call_data)
return f"Failed to parse tool call.", call_id return f"Failed to parse tool call.", call_id
# Check if tool_id exists in available tools # Check if tool_id exists in available tools
if tool_id not in tools_dict: if tool_id not in tools_dict:
error_message = f"Error: Tool ID '{tool_id}' extracted from LLM call not found in available tools_dict. Available IDs: {list(tools_dict.keys())}" error_message = f"Error: Tool ID '{tool_id}' extracted from LLM call not found in available tools_dict. Available IDs: {list(tools_dict.keys())}"
logger.error(error_message) logger.error(error_message)
# Return error result # Return error result
tool_call_data = { tool_call_data = {
"tool_name": "unknown", "tool_name": "unknown",
@@ -175,7 +175,7 @@ class BaseAgent(ABC):
yield {"type": "tool_call", "data": {**tool_call_data, "status": "error"}} yield {"type": "tool_call", "data": {**tool_call_data, "status": "error"}}
self.tool_calls.append(tool_call_data) self.tool_calls.append(tool_call_data)
return f"Tool with ID {tool_id} not found.", call_id return f"Tool with ID {tool_id} not found.", call_id
tool_call_data = { tool_call_data = {
"tool_name": tools_dict[tool_id]["name"], "tool_name": tools_dict[tool_id]["name"],
"call_id": call_id, "call_id": call_id,

View File

@@ -283,7 +283,14 @@ class MCPTool(Tool):
""" """
self._ensure_valid_session() self._ensure_valid_session()
call_params = {"name": action_name, "arguments": kwargs} # Skipping empty/None values - letting the server use defaults
cleaned_kwargs = {}
for key, value in kwargs.items():
if value == "" or value is None:
continue
cleaned_kwargs[key] = value
call_params = {"name": action_name, "arguments": cleaned_kwargs}
try: try:
result = self._make_mcp_request("tools/call", call_params) result = self._make_mcp_request("tools/call", call_params)
return result return result

View File

@@ -26,7 +26,7 @@ class Settings(BaseSettings):
"gpt-4o-mini": 128000, "gpt-4o-mini": 128000,
"gpt-3.5-turbo": 4096, "gpt-3.5-turbo": 4096,
"claude-2": 1e5, "claude-2": 1e5,
"gemini-2.0-flash-exp": 1e6, "gemini-2.5-flash": 1e6,
} }
UPLOAD_FOLDER: str = "inputs" UPLOAD_FOLDER: str = "inputs"
PARSE_PDF_AS_IMAGE: bool = False PARSE_PDF_AS_IMAGE: bool = False

View File

@@ -151,15 +151,8 @@ class GoogleLLM(BaseLLM):
if role == "assistant": if role == "assistant":
role = "model" role = "model"
elif role == "system":
continue
elif role == "tool": elif role == "tool":
continue role = "model"
elif role not in ["user", "model"]:
logging.warning(
f"GoogleLLM: Converting unsupported role '{role}' to 'user'"
)
role = "user"
parts = [] parts = []
if role and content is not None: if role and content is not None:

View File

@@ -205,7 +205,6 @@ class LLMHandler(ABC):
except StopIteration as e: except StopIteration as e:
tool_response, call_id = e.value tool_response, call_id = e.value
break break
updated_messages.append( updated_messages.append(
{ {
"role": "assistant", "role": "assistant",
@@ -222,17 +221,36 @@ class LLMHandler(ABC):
) )
updated_messages.append(self.create_tool_message(call, tool_response)) updated_messages.append(self.create_tool_message(call, tool_response))
except Exception as e: except Exception as e:
logger.error(f"Error executing tool: {str(e)}", exc_info=True) logger.error(f"Error executing tool: {str(e)}", exc_info=True)
updated_messages.append( error_call = ToolCall(
{ id=call.id, name=call.name, arguments=call.arguments
"role": "tool",
"content": f"Error executing tool: {str(e)}",
"tool_call_id": call.id,
}
) )
error_response = f"Error executing tool: {str(e)}"
error_message = self.create_tool_message(error_call, error_response)
updated_messages.append(error_message)
call_parts = call.name.split("_")
if len(call_parts) >= 2:
tool_id = call_parts[-1] # Last part is tool ID (e.g., "1")
action_name = "_".join(call_parts[:-1])
tool_name = tools_dict.get(tool_id, {}).get("name", "unknown_tool")
full_action_name = f"{action_name}_{tool_id}"
else:
tool_name = "unknown_tool"
action_name = call.name
full_action_name = call.name
yield {
"type": "tool_call",
"data": {
"tool_name": tool_name,
"call_id": call.id,
"action_name": full_action_name,
"arguments": call.arguments,
"error": error_response,
"status": "error",
},
}
return updated_messages return updated_messages
def handle_non_streaming( def handle_non_streaming(
@@ -263,13 +281,11 @@ class LLMHandler(ABC):
except StopIteration as e: except StopIteration as e:
messages = e.value messages = e.value
break break
response = agent.llm.gen( response = agent.llm.gen(
model=agent.gpt_model, messages=messages, tools=agent.tools model=agent.gpt_model, messages=messages, tools=agent.tools
) )
parsed = self.parse_response(response) parsed = self.parse_response(response)
self.llm_calls.append(build_stack_data(agent.llm)) self.llm_calls.append(build_stack_data(agent.llm))
return parsed.content return parsed.content
def handle_streaming( def handle_streaming(

View File

@@ -17,7 +17,6 @@ class GoogleLLMHandler(LLMHandler):
finish_reason="stop", finish_reason="stop",
raw_response=response, raw_response=response,
) )
if hasattr(response, "candidates"): if hasattr(response, "candidates"):
parts = response.candidates[0].content.parts if response.candidates else [] parts = response.candidates[0].content.parts if response.candidates else []
tool_calls = [ tool_calls = [
@@ -41,7 +40,6 @@ class GoogleLLMHandler(LLMHandler):
finish_reason="tool_calls" if tool_calls else "stop", finish_reason="tool_calls" if tool_calls else "stop",
raw_response=response, raw_response=response,
) )
else: else:
tool_calls = [] tool_calls = []
if hasattr(response, "function_call"): if hasattr(response, "function_call"):
@@ -61,14 +59,16 @@ class GoogleLLMHandler(LLMHandler):
def create_tool_message(self, tool_call: ToolCall, result: Any) -> Dict: def create_tool_message(self, tool_call: ToolCall, result: Any) -> Dict:
"""Create Google-style tool message.""" """Create Google-style tool message."""
from google.genai import types
return { return {
"role": "tool", "role": "model",
"content": [ "content": [
types.Part.from_function_response( {
name=tool_call.name, response={"result": result} "function_response": {
).to_json_dict() "name": tool_call.name,
"response": {"result": result},
}
}
], ],
} }

View File

@@ -1,6 +1,6 @@
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import { forwardRef, Fragment, useRef, useState, useEffect } from 'react'; import { forwardRef, Fragment, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
@@ -12,12 +12,13 @@ import {
import rehypeKatex from 'rehype-katex'; import rehypeKatex from 'rehype-katex';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math'; import remarkMath from 'remark-math';
import DocumentationDark from '../assets/documentation-dark.svg';
import ChevronDown from '../assets/chevron-down.svg'; import ChevronDown from '../assets/chevron-down.svg';
import Cloud from '../assets/cloud.svg'; import Cloud from '../assets/cloud.svg';
import DocsGPT3 from '../assets/cute_docsgpt3.svg'; import DocsGPT3 from '../assets/cute_docsgpt3.svg';
import Dislike from '../assets/dislike.svg?react'; import Dislike from '../assets/dislike.svg?react';
import Document from '../assets/document.svg'; import Document from '../assets/document.svg';
import DocumentationDark from '../assets/documentation-dark.svg';
import Edit from '../assets/edit.svg'; import Edit from '../assets/edit.svg';
import Like from '../assets/like.svg?react'; import Like from '../assets/like.svg?react';
import Link from '../assets/link.svg'; import Link from '../assets/link.svg';
@@ -761,7 +762,11 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
Response Response
</span>{' '} </span>{' '}
<CopyButton <CopyButton
textToCopy={JSON.stringify(toolCall.result, null, 2)} textToCopy={
toolCall.status === 'error'
? toolCall.error || 'Unknown error'
: JSON.stringify(toolCall.result, null, 2)
}
/> />
</p> </p>
{toolCall.status === 'pending' && ( {toolCall.status === 'pending' && (
@@ -779,6 +784,16 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
</span> </span>
</p> </p>
)} )}
{toolCall.status === 'error' && (
<p className="dark:bg-raisin-black rounded-b-2xl p-2 font-mono text-sm break-words">
<span
className="leading-[23px] text-red-500 dark:text-red-400"
style={{ fontFamily: 'IBMPlexMono-Medium' }}
>
{toolCall.error}
</span>
</p>
)}
</div> </div>
</div> </div>
</Accordion> </Accordion>

View File

@@ -4,5 +4,6 @@ export type ToolCallsType = {
call_id: string; call_id: string;
arguments: Record<string, any>; arguments: Record<string, any>;
result?: Record<string, any>; result?: Record<string, any>;
status?: 'pending' | 'completed'; error?: string;
status?: 'pending' | 'completed' | 'error';
}; };