Bug Description
When using kimi-k2.6 (or any Moonshot model), any tool whose JSON Schema uses a union type array ("type": ["number", "string"]) causes a crash inside sanitize_moonshot_tools():
TypeError: unhashable type: 'list'
This is a hard crash — the agent cannot call any tool that has a union-typed parameter.
Affected Tool Example
The built-in lcm_grep tool has parameters like:
{
"time_from": {
"anyOf": [{"type": "number"}, {"type": "string"}],
"description": "Unix seconds or ISO 8601"
},
"limit": {
"type": ["number", "string"],
"description": "Max results"
}
}
Both forms are valid JSON Schema, but the "type": ["number", "string"] form hits the bug directly.
Root Cause
_fill_missing_type() in agent/moonshot_schema.py line ~169:
# BROKEN:
if "type" in node and node["type"] not in {None, ""}:
return node
When node["type"] is a Python list (e.g. ["number", "string"]), the not in {None, ""} set membership test raises:
TypeError: unhashable type: 'list'
because Python lists are not hashable and cannot be tested for set membership.
Proposed Fix
Detect list types before the set membership test and normalise to the first concrete (non-null) type:
def _fill_missing_type(node: Dict[str, Any]) -> Dict[str, Any]:
"""Infer a reasonable ``type`` if this schema node has none.
Handles JSON Schema union types where ``type`` is a list like
``["number", "string"]``. Moonshot does not accept union type arrays —
we keep the first concrete (non-null) type and discard the rest.
"""
node_type = node.get("type")
# Guard: list types (e.g. ["number", "string"]) are not hashable and
# cannot be tested with `not in {None, ""}`. Normalise to the first
# non-null element so the rest of the pipeline sees a plain string type.
if isinstance(node_type, list):
concrete = next((t for t in node_type if t not in (None, "null", "")), "string")
return {**node, "type": concrete}
if node_type not in {None, ""}:
return node
# ... rest of function unchanged
Moonshot rejects union type arrays anyway (it requires a single scalar type), so normalising to the first concrete type is both correct and safe.
Steps to Reproduce
- Configure Hermes with
kimi-k2.6 as the model (via Ollama Cloud or direct Moonshot API)
- Enable any toolset that includes
lcm_grep (e.g. hermes-cli)
- Send any message that triggers tool use
- Observe
TypeError: unhashable type: 'list' in the agent loop
Environment
- Hermes version: 0.14.0
- Model:
kimi-k2.6 (moonshotai/kimi-k2.6)
- Provider: Ollama Cloud / any provider routing to Moonshot
- Python: 3.12
Notes
- The
anyOf: [{type: number}, {type: string}] form is handled correctly by the existing anyOf collapse logic — only the type: ["number", "string"] array form hits this bug.
- The fix is a 3-line guard at the top of
_fill_missing_type() with no impact on existing behaviour for string types.
Bug Description
When using
kimi-k2.6(or any Moonshot model), any tool whose JSON Schema uses a union type array ("type": ["number", "string"]) causes a crash insidesanitize_moonshot_tools():This is a hard crash — the agent cannot call any tool that has a union-typed parameter.
Affected Tool Example
The built-in
lcm_greptool has parameters like:{ "time_from": { "anyOf": [{"type": "number"}, {"type": "string"}], "description": "Unix seconds or ISO 8601" }, "limit": { "type": ["number", "string"], "description": "Max results" } }Both forms are valid JSON Schema, but the
"type": ["number", "string"]form hits the bug directly.Root Cause
_fill_missing_type()inagent/moonshot_schema.pyline ~169:When
node["type"]is a Pythonlist(e.g.["number", "string"]), thenot in {None, ""}set membership test raises:because Python lists are not hashable and cannot be tested for set membership.
Proposed Fix
Detect list types before the set membership test and normalise to the first concrete (non-null) type:
Moonshot rejects union type arrays anyway (it requires a single scalar type), so normalising to the first concrete type is both correct and safe.
Steps to Reproduce
kimi-k2.6as the model (via Ollama Cloud or direct Moonshot API)lcm_grep(e.g.hermes-cli)TypeError: unhashable type: 'list'in the agent loopEnvironment
kimi-k2.6(moonshotai/kimi-k2.6)Notes
anyOf: [{type: number}, {type: string}]form is handled correctly by the existinganyOfcollapse logic — only thetype: ["number", "string"]array form hits this bug._fill_missing_type()with no impact on existing behaviour for string types.