Skip to content

Intent to Instruction

The Intent to Instruction node looks up the intent label written by a preceding ClassifyIntent node in a Map-kind string storage, and attaches the matching value as an InstructionComponent to the current interaction.

The attached instruction is then rendered into the prompt by the downstream Generate node's Mustache template via the {{#instructions}} loop or the {{instruction_<name>}} shorthand.

NodeType: IntentToInstruction.

How it works

  1. Reads IntentionComponent from the current interaction (written by an upstream ClassifyIntent node).
  2. Calls FindFirst(intentLabel) on the configured Map string storage.
  3. If a match is found, attaches InstructionComponent{instructionName, matchedText} and returns "default".
  4. If no IntentionComponent is present, or the key is not found in the map, logs a warning and returns "default" without attaching any component. The downstream Generate node's template renders the instructions loop as empty, producing a general answer.

The node always takes the "default" exit — it never blocks or branches. Missing intents and missing keys are handled silently (with a log warning) so the graph keeps running without hard failures.

Parameters

Param Type Default Range Structural Description
string_storage Optional[str] inherit model default Named session Map-kind string storage. Mutable — rebinds the storage.
inline_keys Optional[Any] inherit model default Inline map keys (equal-length with inline_strings). Structural because they materialise the in-memory Map storage at construction.
inline_strings Optional[Any] inherit model default Inline map values (equal-length with inline_keys). Structural.
instruction_name Optional[str] inherit model default Name used as the Mustache variable suffix (instruction_) on the attached InstructionComponent.

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
default default_exit Default exit target after mapping an intent to an instruction. Empty string = END.

Exit routes

Route Fires when
default Always — the node never branches. Set default_exit to the downstream Generate node name, or leave it empty to terminate the turn (END).

The "default" exit fires regardless of whether an instruction was found. When nothing is found the template's {{#instructions}} loop renders as empty.

Side effects

  • Attaches InstructionComponent{instructionName, text} to the current interaction when a matching key is found. The component is rendered into the prompt by the downstream GenerateNode via:
    • {{#instructions}}{{text}}{{/instructions}} — iterates all attached instruction components.
    • {{instruction_<name>}} — renders only the component with the matching name.
  • When no instruction is attached (missing IntentionComponent or missing key), {{#instructions}} renders as empty and the model produces a general response.

Diagnostics

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

Key Meaning
string_storage The name of the map storage used.
instruction_name The name given to the emitted component.
intent The intent label read from IntentionComponent (empty if no component was present).
found "true" when the key was matched and the component was attached; "false" otherwise.
result_text The instruction text that was attached (empty when found = "false").

Minimum working example

from tryll_client.graph import (
    GraphDescription, ClassifyIntentParams, IntentToInstructionParams,
    GenerateParams, Placement,
)

# Create a Map storage: key = intent label, value = instruction text
client.create_keyed_string_storage(
    name="npc_instructions",
    keys=["insult", "pleasing", "joking"],
    values=[
        "The user insulted you. Respond curtly and insult them back.",
        "The user is being polite. Show grudging satisfaction.",
        'The user told a joke. Laugh once ("Ha."), then refocus on duty.',
    ],
)

# The IntentToInstruction node is wired after a ClassifyIntent node
graph = (
    GraphDescription()
    .add_node("classify", ClassifyIntentParams(
        embedded_string_storage="intent_kb",
        embedding_model="All-MiniLM-L6-v2 (Q4_K_M)",
        metadata_field="intent",
        threshold=0.6,
        found_exit="pick",
        not_found_exit="respond",
    ))
    .add_node("pick", IntentToInstructionParams(
        string_storage="npc_instructions",
        instruction_name="tone",
        default_exit="respond",
    ))
    .add_node("respond", GenerateParams(
        template="{{#instructions}}[Tone: {{text}}]\n{{/instructions}}{{user_message}}",
        placement=Placement.InPlaceOfUser,
        default_exit="",   # empty = END
    ))
    .set_start_node("classify")
    .set_default_model_name("My Local Model")
)

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

ClassifyIntentParamsT cp;
cp.embedded_string_storage = "intent_kb";
cp.embedding_model         = "All-MiniLM-L6-v2 (Q4_K_M)";
cp.metadata_field          = "intent";
cp.threshold               = 0.6f;
cp.found_exit              = "pick";
cp.not_found_exit          = "respond";

IntentToInstructionParamsT pp;
pp.string_storage   = "npc_instructions";
pp.instruction_name = "tone";
pp.default_exit     = "respond";

GenerateParamsT gp;
gp.template_  = "{{#instructions}}[Tone: {{text}}]\n{{/instructions}}{{user_message}}";
gp.placement  = ::Tryll::Placement::InPlaceOfUser;
// gp.default_exit = ""; // empty = END (the default)

GraphDescription graph;
graph.AddClassifyIntent("classify", std::move(cp))
     .AddIntentToInstruction("pick",     std::move(pp))
     .AddGenerate("respond",             std::move(gp))
     .SetStartNode("classify")
     .SetDefaultModelName("My Local Model");

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

var graph = new TryllGraphBuilder()
    .AddClassifyIntent("classify", new TryllClassifyIntentParams
    {
        EmbeddedStringStorage = "intent_kb",
        EmbeddingModel        = "All-MiniLM-L6-v2 (Q4_K_M)",
        MetadataField         = "intent",
        Threshold             = 0.6f,
        FoundExit             = "pick",
        NotFoundExit          = "respond",
    })
    .AddIntentToInstruction("pick", new TryllIntentToInstructionParams
    {
        StringStorage   = "npc_instructions",
        InstructionName = "tone",
        DefaultExit     = "respond",
    })
    .AddGenerate("respond", new TryllGenerateParams
    {
        Template  = "{{#instructions}}[Tone: {{text}}]\n{{/instructions}}{{user_message}}",
        Placement = TryllPlacement.InPlaceOfUser,
    })
    .SetStartNode("classify")
    .SetDefaultModelName("My Local Model")
    .Build();
#include "Generated/TryllGraphBuilder.Nodes.h"
#include "Generated/TryllNodeParamsFactory.h"

UTryllClassifyIntentParams* CP = UTryllNodeParamsFactory::MakeClassifyIntentParams(this);
CP->bOverrideEmbeddedStringStorage = true;
CP->EmbeddedStringStorage = TEXT("intent_kb");
CP->bOverrideEmbeddingModel = true;
CP->EmbeddingModel = TEXT("All-MiniLM-L6-v2 (Q4_K_M)");
CP->bOverrideMetadataField = true;
CP->MetadataField = TEXT("intent");
CP->Threshold  = 0.6f;
CP->FoundExit    = TEXT("pick");
CP->NotFoundExit = TEXT("respond");

UTryllIntentToInstructionParams* PP = UTryllNodeParamsFactory::MakeIntentToInstructionParams(this);
PP->bOverrideStringStorage = true;
PP->StringStorage   = TEXT("npc_instructions");
PP->bOverrideInstructionName = true;
PP->InstructionName = TEXT("tone");
PP->DefaultExit     = TEXT("respond");

UTryllGenerateParams* GP = UTryllNodeParamsFactory::MakeGenerateParams(this);
GP->bOverrideTemplate = true;
GP->Template  = TEXT("{{#instructions}}[Tone: {{text}}]\n{{/instructions}}{{user_message}}");
GP->Placement = ETryllPlacement::InPlaceOfUser;

FTryllGraphDescription Graph = FTryllGraphBuilder()
    .AddNode(TEXT("classify"), CP)
    .AddNode(TEXT("pick"),     PP)
    .AddNode(TEXT("respond"),  GP)
    .SetStartNode(TEXT("classify"))
    .SetDefaultModelName(TEXT("My Local Model"))
    .Build();

See the end-to-end walkthrough in How to build an intent-driven NPC.

Client bindings

  • C++: GraphDescription::AddIntentToInstruction(name, IntentToInstructionParamsT)GraphDescription.h
  • Python: GraphDescription.add_node(name, IntentToInstructionParams(...))tryll_client.graph
  • Unity: TryllGraphBuilder.AddIntentToInstruction(name, new TryllIntentToInstructionParams{...})Runtime/Generated/TryllGraphBuilder.Nodes.cs
  • Unreal: AddIntentToInstructionNode(builder, name, UTryllIntentToInstructionParams*)Generated/TryllGraphBuilder.Nodes.h
  • Unreal (Blueprint): author UTryllIntentToInstructionParams on a node description in a UTryllWorkflowAsset