The Hermes Function-Calling Standard

12 min read Module 3 of 9 Topic 7 of 25

What you'll learn

  • Write a tool definition inside a <tools> block using JSON Schema
  • Trace a full <tool_call> / <tool_response> round trip
  • Control which tools load using toolsets instead of exposing everything at once
  • Diagnose and recover from a malformed or rejected tool call
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Tool calling is the mechanism that turns a model from a text generator into something that can actually act. This topic has four subtopics: how a tool is declared, how the call-and-response cycle actually flows, how toolsets control what the model even sees, and how to debug it when things go wrong. Work through them in order, each builds on the last.


Subtopic 7.1: Anatomy of a Tool Definition

The Hermes function-calling standard places every available tool’s schema inside a <tools> block in the model’s system context, as JSON Schema. The model reads these descriptions the same way you’d read documentation, so the description field is not a formality, it is the single biggest lever you have over whether the model picks the right tool at the right time.

<tools>
[
  {
    "name": "get_current_weather",
    "description": "Get the current weather for a city. Use this when the user asks about weather, temperature, or conditions in a specific location.",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {
          "type": "string",
          "description": "The city name, e.g. 'London' or 'New York'"
        },
        "unit": {
          "type": "string",
          "enum": ["celsius", "fahrenheit"],
          "description": "Temperature unit, defaults to celsius if omitted"
        }
      },
      "required": ["city"]
    }
  }
]
</tools>

Schemas can be generated from a Pydantic model rather than hand-written, which is the more maintainable path for anything beyond a handful of tools:

from pydantic import BaseModel, Field

class GetWeatherParams(BaseModel):
    city: str = Field(..., description="The city name, e.g. 'London' or 'New York'")
    unit: str = Field("celsius", description="Temperature unit")

# .model_json_schema() produces the parameters block Hermes expects inside <tools>
schema = GetWeatherParams.model_json_schema()

Common pitfall: writing a vague description like “gets weather data.” The model has nothing but this text to decide whether a given user request warrants calling this tool over some other one. Write it the way you’d explain the tool to a new teammate: what it does, and when to reach for it.


Subtopic 7.2: The Call-and-Response Cycle

When the model decides a tool is needed, it emits a <tool_call> block instead of a plain-text answer:

<tool_call>
{"name": "get_current_weather", "arguments": {"city": "London", "unit": "celsius"}}
</tool_call>

This is a request, nothing more. The orchestration layer intercepts it, runs the real Python function, and hands the result back inside a <tool_response> block appended to the conversation:

<tool_response>
{"city": "London", "temperature": 14, "unit": "celsius", "condition": "light rain"}
</tool_response>

Only now does the model have the actual data, and it continues reasoning (optionally inside a <think> block, if the model supports extended reasoning) before producing its final answer:

# The execution side: Hermes' orchestration layer, conceptually
import json

TOOL_FUNCTIONS = {
    "get_current_weather": get_current_weather,   # your real implementation
}

def execute_tool_call(raw_call: str) -> str:
    call = json.loads(raw_call)
    name, args = call["name"], call["arguments"]

    if name not in TOOL_FUNCTIONS:
        result = {"error": f"Unknown tool: {name}"}
    else:
        try:
            result = TOOL_FUNCTIONS[name](**args)
        except Exception as e:
            # Surface the error to the model rather than crashing:
            # a well-designed agent can often recover or ask for clarification
            result = {"error": str(e)}

    return json.dumps(result)

A full turn can involve several of these round trips in sequence, the model calls a tool, reads the result, decides it needs another tool, calls that, and only then answers. This loop is exactly what you traced conceptually in Lesson 3’s request-flow diagram; here you’re seeing the actual wire format underneath it.


Subtopic 7.3: Toolsets, Platform Tools, and Selective Loading

Every tool in <tools> costs context budget and adds one more option the model has to correctly choose (or correctly ignore) on every turn. Hermes groups tools into toolsets, letting you load only what a given deployment actually needs, rather than exposing everything by default.

hermes tools    # inspect which toolsets are currently active
# ~/.hermes/config.yaml
agent:
  # explicit allow-list: only these toolsets load, nothing else
  platform_toolsets:
    cli: ["filesystem", "shell", "web_search"]
  # explicit deny-list: these never load even if requested elsewhere
  disabled_toolsets: ["code_execution", "image_generation"]

This is the same platform_toolsets / disabled_toolsets pair you saw in “Blank Slate” setup back in Lesson 2, now with the reasoning behind it made explicit: a support-bot deployment (Module 6) has no business loading a code-execution toolset, and a coding-assistant deployment has little use for image generation. Scoping toolsets to the deployment is the first and cheapest guardrail available to you, well before the more involved sandboxing techniques in Module 7.


Subtopic 7.4: Debugging Malformed or Rejected Tool Calls

Three failure modes account for nearly every tool-calling bug you’ll hit:

SymptomLikely causeFix
Model describes a tool call in plain prose instead of <tool_call>Inference server missing tool-call parser (Lesson 5)Add --tool-call-parser hermes (vLLM) or --jinja (llama.cpp)
<tool_call> present but arguments don’t match the schemaDescription or parameter naming is ambiguousRewrite the description field to be more specific; add an enum to constrain free-text guesses
Tool executes but the model ignores the resultResult wasn’t wrapped correctly, or context length was exceeded (Lesson 6)Verify <tool_response> formatting; check hermes doctor for context warnings
hermes doctor   # your first stop for any of the above

For anything hermes doctor doesn’t catch, inspect the raw conversation to see exactly what the model emitted before deciding whether the problem is the schema, the model, or the inference server:

hermes sessions list
hermes sessions show <session-id> --raw   # see exact <tool_call>/<tool_response> exchanges

Exercise: Write a Pydantic model and JSON Schema for a tool called convert_currency(amount, from_currency, to_currency). Deliberately write a bad, vague description first (“converts money”) and test it with an ambiguous request like “how much is that in euros.” Then rewrite the description to be specific about when to call it, and compare how reliably the model picks it up. This is the fastest way to internalize why subtopic 7.1’s advice about descriptions matters in practice, not just in theory.

Knowledge Check

4 questions to test your understanding

1 In the Hermes function-calling format, what goes inside the <tools> block that a model receives in its system context?

2 After the model emits a <tool_call>, what MUST happen before the model can produce a final answer that uses the tool's result?

3 Why would you deliberately disable a toolset rather than just trusting the model to ignore tools it doesn't need?

4 A self-hosted model keeps emitting tool-call JSON as plain text instead of inside a properly formatted <tool_call> block, and Hermes fails to parse it. What is the most likely root cause, based on Lesson 5?

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan