Skip to content

[Bug]: Duplicate tool names cause silent 400 failures on strict providers (Google Vertex, Azure, Bedrock) after 4d363499d #18478

@thesunofdog

Description

@thesunofdog

Bug Description

After commit 4d36349 ("feat(plugins): bundled platform plugins auto-load by default", 2026-04-29), sessions that use a context engine plugin (e.g. hermes-lcm) can produce duplicate tool names in self.tools. Providers that enforce tool name uniqueness — Anthropic, Google Vertex AI, Azure, and Amazon Bedrock — reject the request with HTTP 400. When these providers are part of OpenRouter's auto-routing fallback chain, the failure is silent: the agent logs the error, exhausts its fallback list, and either returns a degraded response or fails the turn entirely with no user-visible explanation.

Affected Component

comp/agent, comp/plugins, comp/tools

Root Cause

run_agent.py injects context engine tool schemas unconditionally:

# run_agent.py ~L2007 (upstream, before fix)
for _schema in self.context_compressor.get_tool_schemas():
    _wrapped = {"type": "function", "function": _schema}
    self.tools.append(_wrapped)   # ← no dedup check

The memory manager injection path (added earlier) already has a dedup guard. The context engine path does not. When a context engine plugin like hermes-lcm is active, its 6 tool schemas (lcm_grep, lcm_describe, lcm_expand, lcm_expand_query, lcm_status, lcm_doctor) get appended on every AIAgent.__init__ call. In gateway deployments where agents are re-initialized per session, or in any path that re-enters the injection block, duplicates accumulate.

Additionally, agent/auxiliary_client.py's _build_call_kwargs() passes tools directly to the API with no dedup:

# agent/auxiliary_client.py ~L3167 (upstream, before fix)
if tools:
    kwargs["tools"] = tools   # ← passed as-is, no dedup

This means any upstream duplication reaches every provider call.

Evidence

37 HTTP 400 errors logged across 2026-04-29 23:47 → 2026-05-01 09:48, all with identical message:

tools: Tool names must be unique.

Provider breakdown (via OpenRouter auto-routing):

  • Azure: 20 occurrences
  • Google Vertex AI: 14 occurrences
  • Amazon Bedrock: 1 occurrence (separate error format: {"message":"tools: Tool names must be unique."})

Errors appear in both interactive gateway sessions and cron job sessions. First occurrence correlates with commit 4d36349 landing on 2026-04-29.

Steps to Reproduce

  1. Enable the hermes-lcm context engine plugin (or any context engine plugin that returns tools from get_tool_schemas())
  2. Configure OpenRouter as provider with model: anthropic/claude-sonnet-4.x (or any model whose auto-routing includes Google/Azure/Bedrock as fallbacks)
  3. Start the gateway on the Telegram (or any) platform
  4. Send a message — observe HTTP 400 in errors.log when OpenRouter routes to a strict provider

Expected Behavior

Tool schemas injected by context engine plugins are deduplicated against the existing tool list, matching the behaviour already implemented for memory manager tool injection. Providers never receive duplicate tool names.

Actual Behavior

Duplicate tool names reach the API. Strict providers (Google Vertex, Azure, Bedrock) reject with HTTP 400. The error is non-retryable and the agent silently degrades or fails.

Proposed Fix

Three defensive layers (already applied in our fork, diff below):

1. run_agent.py — dedup at context engine injection point:

_ce_existing_names = {
    t.get("function", {}).get("name")
    for t in self.tools
    if isinstance(t, dict)
}
for _schema in self.context_compressor.get_tool_schemas():
    _tname = _schema.get("name", "")
    if _tname and _tname in _ce_existing_names:
        logger.warning(
            "Context engine tool '%s' already in tool list — skipping duplicate injection",
            _tname,
        )
        continue
    _wrapped = {"type": "function", "function": _schema}
    self.tools.append(_wrapped)
    if _tname:
        self.valid_tool_names.add(_tname)
        self._context_engine_tool_names.add(_tname)
        _ce_existing_names.add(_tname)

2. agent/auxiliary_client.py — dedup in _build_call_kwargs() (all non-Anthropic providers):

if tools:
    _seen: set = set()
    _deduped: list = []
    for _t in tools:
        _tname = (_t.get("function") or {}).get("name", "")
        if _tname and _tname in _seen:
            logger.warning(
                "_build_call_kwargs: duplicate tool name '%s' removed before API call "                "(provider=%s model=%s)",
                _tname, provider, model,
            )
            continue
        if _tname:
            _seen.add(_tname)
        _deduped.append(_t)
    kwargs["tools"] = _deduped

3. agent/anthropic_adapter.py — dedup in convert_tools_to_anthropic():

seen_names: set = set()
for t in tools:
    fn = t.get("function", {})
    name = fn.get("name", "")
    if name and name in seen_names:
        logger.warning(
            "convert_tools_to_anthropic: duplicate tool name '%s' — dropping second occurrence",
            name,
        )
        continue
    if name:
        seen_names.add(name)
    result.append({...})

The fix is intentionally conservative — layers 2 and 3 are last-resort guards at the API call boundary so that any future injection path that forgets dedup doesn't cause provider failures.

Related Issues

Environment

  • Hermes Agent commit: 828d3a320 (main as of 2026-04-29)
  • Plugin: hermes-lcm v0.7.1
  • Provider: OpenRouter (auto-routing to Google Vertex / Azure / Bedrock)
  • Platform: Telegram gateway + CLI sessions + cron jobs (all affected)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High — major feature broken, no workaroundcomp/agentCore agent loop, run_agent.py, prompt buildercomp/pluginsPlugin system and bundled pluginscomp/toolsTool registry, model_tools, toolsetstype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions