Skip to content

Tool Call

The Tool Call node uses language-model inference to detect whether the model wants to call an external function, given a set of tool definitions. It does not execute the tool — the server is agnostic to the tool's implementation; the detected call is surfaced to the client, which runs the tool and feeds the result back into the next turn.

This detection-only contract makes the node safe to run on-device with no special sandboxing: the server never touches the client's tool implementation.

NodeType: ToolCall.

Parameters

Param Type Default Range Structural Description
model_name Optional[str] inherit model default Model catalog name. Empty = use the agent's default_model_name.
system_prompt Optional[str] (multiline) inherit model default Prepended before the user turn during projection.
tool_call_format Optional[str] inherit model default Tool-call dialect used for both prompt construction and output parsing. Structural because the format config is baked into the projection strategy.
tools Optional[Any] inherit model default Callable tool definitions. Structural because the tool-call prompt schema and parser contract are materialised at construction.
generate_on_no_tool bool False When no tool call is detected in the response, generate a plain text reply.
notify_client bool False When true, emit an OnNodeEvent("tool_call", …) for each tool call parsed.
sampling Optional[Any] inherit model default Sparse sampling overrides.

Exits

Each exit is a structural string field on the node's params; its value names the target node (empty = END).

Exit Param field Description
tool_called tool_called_exit Exit taken when one or more tool calls are parsed from the response. Empty string = END.
no_tool_called no_tool_called_exit Exit taken when no tool call is parsed (or when generate_on_no_tool produced a plain text reply). Empty string = END.

Exit routes

Route Fires when
tool_called At least one tool call was detected in the model's output.
no_tool_called The model produced no parseable tool call.

Both routes must be wired.

Side effects

  • Records every detected tool call on the current turn (one record per call; a single turn may produce more than one).
  • When generate_on_no_tool = true (experimental) and no tool is detected, additionally appends the model's text as the assistant's reply and fires AnswerText chunks (as a Generate node would).
  • When notify_client = true, fires one NodeEvent (event_type="tool_call") per detected call, out-of-band, before the turn completes.

Tool-call formats

Format Family Typical model
chatml OpenAI / Qwen / default Qwen2.5, OpenAI-compatible chat models.
llama3 Llama 3 Meta Llama 3 / 3.1 / 3.2.
mistral Mistral function-calling Mistral-Instruct v0.3+.
generic JSON-in-text Fallback for models with no native format.

The format controls:

  1. How tool definitions are rendered into the prompt.
  2. How the model's output is parsed to extract calls.

Pick the format matching your model's training. If unsure, start with chatml and inspect debug_info.tool_call_format and the raw model output in diagnostics.

Diagnostics

When enable_diagnostics = true, the node contributes these keys to TurnComplete.debug_info:

Key Meaning
tool_call_format The format config name that was actually used.
tool_count Number of tool definitions passed to the model.
generate_on_no_tool Current value of the param. (Experimental.)
(plus sampling / prompt diagnostics shared with other inference nodes)

Minimum working example

from tryll_client.graph import (
    GraphDescription, ToolCallParams, GenerateParams,
    ToolDefinition, ToolParamDefinition,
)

tools = [
    ToolDefinition(
        name="get_weather",
        description="Get the current weather for a city.",
        parameters=[
            ToolParamDefinition(name="city", type="string",
                description="City name"),
        ],
    ),
]

graph = (
    GraphDescription()
    .add_node("detect", ToolCallParams(
        tools=tools,
        tool_call_format="chatml",
        notify_client=True,
        tool_called_exit="",    # empty = END (client handles the tool)
        no_tool_called_exit="answer",
    ))
    .add_node("answer", GenerateParams(
        default_exit="",   # empty = END
    ))
    .set_start_node("detect")
    .set_default_model_name("Qwen2.5-3B-Instruct")
)

agent = client.create_agent(graph)
using namespace Tryll::Client;
using namespace Tryll::NodeParams;

auto cityParam = std::make_unique<ToolParamDefinitionT>();
cityParam->name        = "city";
cityParam->type        = "string";
cityParam->description = "City name";

auto getWeather = std::make_unique<ToolDefinitionT>();
getWeather->name        = "get_weather";
getWeather->description = "Get the current weather for a city.";
getWeather->parameters.push_back(std::move(cityParam));

ToolCallParamsT dp;
dp.tool_call_format  = "chatml";
dp.notify_client     = true;
dp.tool_called_exit  = "";       // empty = END (client handles the tool)
dp.no_tool_called_exit = "answer";
dp.tools.push_back(std::move(getWeather));

GenerateParamsT answerP;
// answerP.default_exit = ""; // empty = END (the default)

GraphDescription graph;
graph.AddToolCall("detect", std::move(dp))
     .AddGenerate("answer", std::move(answerP))
     .SetStartNode("detect")
     .SetDefaultModelName("Qwen2.5-3B-Instruct");

auto agent = client.CreateAgent(graph);
using Tryll.Client;
using System.Collections.Generic;

var tools = new List<TryllToolDefinition>
{
    new TryllToolDefinition
    {
        Name        = "get_weather",
        Description = "Get the current weather for a city.",
        Parameters  = new List<TryllToolParamDefinition>
        {
            new TryllToolParamDefinition
                { Name = "city", Type = "string", Description = "City name" },
        },
    },
};

var graph = new TryllGraphBuilder()
    .AddToolCall("detect", new TryllToolCallParams
    {
        Tools           = tools,
        ToolCallFormat  = "chatml",
        NotifyClient    = true,
        ToolCalledExit  = "",       // empty = END (client handles the tool)
        NoToolCalledExit = "answer",
    })
    .AddGenerate("answer", new TryllGenerateParams())
    .SetStartNode("detect")
    .SetDefaultModelName("Qwen2.5-3B-Instruct")
    .Build();
#include "Generated/TryllGraphBuilder.Nodes.h"
#include "Generated/TryllNodeParamsFactory.h"

UTryllToolCallParams* DetectP = UTryllNodeParamsFactory::MakeToolCallParams(this);
DetectP->bOverrideToolCallFormat = true;
DetectP->ToolCallFormat  = TEXT("chatml");
DetectP->NotifyClient    = true;
DetectP->ToolCalledExit  = TEXT("");       // empty = END
DetectP->NoToolCalledExit = TEXT("answer");
// Author tool definitions via DetectP->Tools (TArray<FTryllToolDefinition>)
FTryllToolDefinition GetWeather;
GetWeather.Name        = TEXT("get_weather");
GetWeather.Description = TEXT("Get the current weather for a city.");
GetWeather.Parameters.Add({ TEXT("city"), TEXT("string"), TEXT("City name") });
DetectP->Tools.Add(GetWeather);

FTryllGraphDescription Graph = FTryllGraphBuilder()
    .AddNode(TEXT("detect"), DetectP)
    .AddNode(TEXT("answer"), UTryllNodeParamsFactory::MakeGenerateParams(this))
    .SetStartNode(TEXT("detect"))
    .SetDefaultModelName(TEXT("Qwen2.5-3B-Instruct"))
    .Build();

Or author UTryllToolCallParams (with tool definitions) inside a UTryllWorkflowAsset; bind UTryllSubsystem::OnToolCall to receive the detection.

Receive the notification in your client with agent.set_on_tool_call(cb) (Python), agent.SetOnToolCall(cb) (C++), or UTryllSubsystem::OnToolCall (Unreal).

See the full flow (including client-side execution and feeding the result back) in How to define and handle tool calls.

Client bindings

  • C++: GraphDescription::AddToolCall(name, ToolCallParamsT)GraphDescription.h
  • Python: GraphDescription.add_node(name, ToolCallParams(...))tryll_client.graph
  • Unity: TryllGraphBuilder.AddToolCall(name, new TryllToolCallParams{...})Runtime/Generated/TryllGraphBuilder.Nodes.cs
  • Unreal: AddToolCallNode(builder, name, UTryllToolCallParams*)Generated/TryllGraphBuilder.Nodes.h