zylon-ai/private-gpt · error · ValueError
Duplicate tool names found in the tools list. Provided tools
Error message
Duplicate tool names found in the tools list. Provided tools: {self.tools} Unique tool names: {set(tool_names)} What it means
ChatBody validates that tool names are unique: two tools with the same name in the tools list make tool resolution ambiguous, so the validator raises with both the full tools list and the unique name set included in the message.
Source
Thrown at private_gpt/server/chat/chat_models.py:378
"Tools are not supported when response_format is set to json_schema"
)
raise ValueError(
"Tools are not supported when structured output is enabled."
)
if self.mcp_servers:
raise ValueError(
"MCP servers are not supported when structured output is enabled."
)
if system.citations.enabled:
raise ValueError(
"Citations are not supported when structured output is enabled."
)
# Check unique tools
if self.tools:
tool_names = [tool.name for tool in self.tools]
if len(tool_names) != len(set(tool_names)):
raise ValueError(
"Duplicate tool names found in the tools list."
f" Provided tools: {self.tools}"
f" Unique tool names: {set(tool_names)}"
)
# Check tool use and result blocks
tool_uses_ids: set[str] = set()
tool_results_ids: set[str] = set()
for message in self.messages:
if isinstance(message.content, list):
for block in message.content:
if block is None:
raise ValueError("Block cannot be None")
elif isinstance(block, ToolUseBlock):
if message.role != "assistant":
raise ValueError(
f"Tool use blocks can only be used in assistant messages: {message}"
)View on GitHub (pinned to 4a030776a3)
Solutions
- Rename one of the colliding tools (names must be unique within a request).
- Namespace tools by source when aggregating, e.g. 'mcp_fs_search' vs 'local_search'.
- Deduplicate before sending: keep first occurrence per name.
Example fix
# before tools = mcp_tools + local_tools # both contain 'search' # after seen = set() tools = [t for t in (mcp_tools + local_tools) if not (t.name in seen or seen.add(t.name))]
Defensive patterns
Strategy: validation
Validate before calling
names = [t['name'] for t in tools]
assert len(names) == len(set(names)), f'duplicate tools: {set(n for n in names if names.count(n) > 1)}' Type guard
def tool_names_unique(tools: list[dict]) -> bool:
names = [t.get('name') for t in tools]
return len(names) == len(set(names)) Try / catch
except ValidationError as e:
if 'Duplicate tool names' in str(e):
seen = set(); tools = [t for t in tools if not (t['name'] in seen or seen.add(t['name']))]
retry()
else:
raise Prevention
- Namespace tools by source (mcp_fs_search, local_search)
- Deduplicate when aggregating tools from multiple providers
- Add a startup check that registered tool names are unique
When it happens
Trigger: Registering both a built-in tool and a custom tool named 'database_query'; aggregating tools from multiple providers that share names (e.g. two 'search' tools); duplicating a tool entry when merging configs.
Common situations: Combining MCP server tools with local tools that collide on names; copy-paste tool definitions; version upgrades that add a tool whose name already exists in user config.
Related errors
- Tool choice '{self.tool_choice}' is not in the provided tool
- Tool context is provided, but no tools are specified. Please
- Tools are not supported when response_format is set to json_
- TOOL_NAME_CONFLICT
- mistral-common only supports function tools.
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/dda9f5e83ee9901d.
Report an issue: GitHub.