mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
feat: skip empty fields in mcp tool call + improve error handling and response
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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},
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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';
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user