Merge pull request #2102 from NousResearch/hermes/hermes-6757a563
fix(tools,cli): normalise MCP schemas + expand session list columns
This commit is contained in:
commit
d8081790f3
3 changed files with 61 additions and 12 deletions
|
|
@ -3721,20 +3721,20 @@ For more help on a command:
|
||||||
return
|
return
|
||||||
has_titles = any(s.get("title") for s in sessions)
|
has_titles = any(s.get("title") for s in sessions)
|
||||||
if has_titles:
|
if has_titles:
|
||||||
print(f"{'Title':<22} {'Preview':<40} {'Last Active':<13} {'ID'}")
|
print(f"{'Title':<32} {'Preview':<40} {'Last Active':<13} {'ID'}")
|
||||||
print("─" * 100)
|
print("─" * 110)
|
||||||
else:
|
else:
|
||||||
print(f"{'Preview':<50} {'Last Active':<13} {'Src':<6} {'ID'}")
|
print(f"{'Preview':<50} {'Last Active':<13} {'Src':<6} {'ID'}")
|
||||||
print("─" * 90)
|
print("─" * 95)
|
||||||
for s in sessions:
|
for s in sessions:
|
||||||
last_active = _relative_time(s.get("last_active"))
|
last_active = _relative_time(s.get("last_active"))
|
||||||
preview = s.get("preview", "")[:38] if has_titles else s.get("preview", "")[:48]
|
preview = s.get("preview", "")[:38] if has_titles else s.get("preview", "")[:48]
|
||||||
if has_titles:
|
if has_titles:
|
||||||
title = (s.get("title") or "—")[:20]
|
title = (s.get("title") or "—")[:30]
|
||||||
sid = s["id"][:20]
|
sid = s["id"]
|
||||||
print(f"{title:<22} {preview:<40} {last_active:<13} {sid}")
|
print(f"{title:<32} {preview:<40} {last_active:<13} {sid}")
|
||||||
else:
|
else:
|
||||||
sid = s["id"][:20]
|
sid = s["id"]
|
||||||
print(f"{preview:<50} {last_active:<13} {s['source']:<6} {sid}")
|
print(f"{preview:<50} {last_active:<13} {s['source']:<6} {sid}")
|
||||||
|
|
||||||
elif action == "export":
|
elif action == "export":
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,18 @@ class TestSchemaConversion:
|
||||||
assert schema["parameters"]["type"] == "object"
|
assert schema["parameters"]["type"] == "object"
|
||||||
assert schema["parameters"]["properties"] == {}
|
assert schema["parameters"]["properties"] == {}
|
||||||
|
|
||||||
|
def test_object_schema_without_properties_gets_normalized(self):
|
||||||
|
from tools.mcp_tool import _convert_mcp_schema
|
||||||
|
|
||||||
|
mcp_tool = _make_mcp_tool(
|
||||||
|
name="ask",
|
||||||
|
description="Ask Crawl4AI",
|
||||||
|
input_schema={"type": "object"},
|
||||||
|
)
|
||||||
|
schema = _convert_mcp_schema("crawl4ai", mcp_tool)
|
||||||
|
|
||||||
|
assert schema["parameters"] == {"type": "object", "properties": {}}
|
||||||
|
|
||||||
def test_tool_name_prefix_format(self):
|
def test_tool_name_prefix_format(self):
|
||||||
from tools.mcp_tool import _convert_mcp_schema
|
from tools.mcp_tool import _convert_mcp_schema
|
||||||
|
|
||||||
|
|
@ -1893,6 +1905,33 @@ class TestSamplingCallbackText:
|
||||||
messages = call_args.kwargs["messages"]
|
messages = call_args.kwargs["messages"]
|
||||||
assert messages[0] == {"role": "system", "content": "Be helpful"}
|
assert messages[0] == {"role": "system", "content": "Be helpful"}
|
||||||
|
|
||||||
|
def test_server_tools_with_object_schema_are_normalized(self):
|
||||||
|
"""Server-provided tools should gain empty properties for object schemas."""
|
||||||
|
fake_client = MagicMock()
|
||||||
|
fake_client.chat.completions.create.return_value = _make_llm_response()
|
||||||
|
server_tool = SimpleNamespace(
|
||||||
|
name="ask",
|
||||||
|
description="Ask Crawl4AI",
|
||||||
|
inputSchema={"type": "object"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"agent.auxiliary_client.call_llm",
|
||||||
|
return_value=fake_client.chat.completions.create.return_value,
|
||||||
|
) as mock_call:
|
||||||
|
params = _make_sampling_params(tools=[server_tool])
|
||||||
|
asyncio.run(self.handler(None, params))
|
||||||
|
|
||||||
|
tools = mock_call.call_args.kwargs["tools"]
|
||||||
|
assert tools == [{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "ask",
|
||||||
|
"description": "Ask Crawl4AI",
|
||||||
|
"parameters": {"type": "object", "properties": {}},
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
def test_length_stop_reason(self):
|
def test_length_stop_reason(self):
|
||||||
"""finish_reason='length' maps to stopReason='maxTokens'."""
|
"""finish_reason='length' maps to stopReason='maxTokens'."""
|
||||||
fake_client = MagicMock()
|
fake_client = MagicMock()
|
||||||
|
|
|
||||||
|
|
@ -605,7 +605,9 @@ class SamplingHandler:
|
||||||
"function": {
|
"function": {
|
||||||
"name": getattr(t, "name", ""),
|
"name": getattr(t, "name", ""),
|
||||||
"description": getattr(t, "description", "") or "",
|
"description": getattr(t, "description", "") or "",
|
||||||
"parameters": getattr(t, "inputSchema", {}) or {},
|
"parameters": _normalize_mcp_input_schema(
|
||||||
|
getattr(t, "inputSchema", None)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for t in server_tools
|
for t in server_tools
|
||||||
|
|
@ -1213,6 +1215,17 @@ def _make_check_fn(server_name: str):
|
||||||
# Discovery & registration
|
# Discovery & registration
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _normalize_mcp_input_schema(schema: dict | None) -> dict:
|
||||||
|
"""Normalize MCP input schemas for LLM tool-calling compatibility."""
|
||||||
|
if not schema:
|
||||||
|
return {"type": "object", "properties": {}}
|
||||||
|
|
||||||
|
if schema.get("type") == "object" and "properties" not in schema:
|
||||||
|
return {**schema, "properties": {}}
|
||||||
|
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
def _convert_mcp_schema(server_name: str, mcp_tool) -> dict:
|
def _convert_mcp_schema(server_name: str, mcp_tool) -> dict:
|
||||||
"""Convert an MCP tool listing to the Hermes registry schema format.
|
"""Convert an MCP tool listing to the Hermes registry schema format.
|
||||||
|
|
||||||
|
|
@ -1231,10 +1244,7 @@ def _convert_mcp_schema(server_name: str, mcp_tool) -> dict:
|
||||||
return {
|
return {
|
||||||
"name": prefixed_name,
|
"name": prefixed_name,
|
||||||
"description": mcp_tool.description or f"MCP tool {mcp_tool.name} from {server_name}",
|
"description": mcp_tool.description or f"MCP tool {mcp_tool.name} from {server_name}",
|
||||||
"parameters": mcp_tool.inputSchema if mcp_tool.inputSchema else {
|
"parameters": _normalize_mcp_input_schema(mcp_tool.inputSchema),
|
||||||
"type": "object",
|
|
||||||
"properties": {},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue