Build a Chat Agent with a Graph¶
Create an agent whose graph is a simple
HumanMessageGuardrail → Generate chain, send a message, and get a
streamed reply.
Prerequisites
- A session connected and configured — see Connect and Manage a Session.
- A model listed in
models.jsonthat is either alreadyLoadedor at leastLocal. If not, see Use Your Own Local Model.
Steps¶
// 1. Attach TryllAgentComponent to a GameObject.
// Assign a TryllWorkflowAsset in the Inspector, or build the graph at runtime:
var graph = new TryllGraphBuilder()
.AddHumanMessageGuardrail("guard", new TryllHumanMessageGuardrailParams
{
// TriggeredExit is "" (END) by default — drop jailbreaks silently.
NotTriggeredExit = "answer",
})
.AddGenerate("answer", new TryllGenerateParams())
.SetStartNode("guard")
.SetDefaultModelName("My Local Model")
.Build();
var agentComp = GetComponent<TryllAgentComponent>();
agentComp.InlineGraphDescription = graph;
// 2. Wire events.
agentComp.OnAnswerText.AddListener(
(text, isDelta, isFinal) => chatText.text += text);
agentComp.OnTurnComplete.AddListener(
(status, _, _) => typingIndicator.SetActive(false));
// 3. When the agent is created, send the first message.
agentComp.OnAgentCreated.AddListener(() => agentComp.SendMessage("Hello!"));
- Add a
UTryllAgentComponentto your actor. - In the Details panel, edit Graph on the component, or
reference a
UTryllWorkflowAssetyou authored in the Content Browser. On the guard node, set Triggered Exit to""(END) and Not Triggered Exit to"answer". The answer node's Default Exit can be left empty (END). - In Blueprint:
- Bind On Agent Ready to a handler that calls Send Message.
- Bind On Answer Text to append each chunk to a UI text block.
- Bind On Turn Complete to flip the "typing" indicator off.
#include <tryll/TryllClient.h>
namespace TC = Tryll::Client;
TC::HumanMessageGuardrailParams guardParams;
// guardParams.triggered_exit defaults to "" (END) — drop jailbreaks silently.
guardParams.not_triggered_exit = "answer";
TC::GraphDescription graph;
graph.AddNode("guard", guardParams)
.AddNode("answer", TC::GenerateParams{})
.SetStartNode("guard")
.SetDefaultModelName("My Local Model");
auto agent = client.CreateAgent(graph);
// Register persistent callbacks, then send — fire-and-forget.
agent.SetOnAnswerText([](std::string_view text, bool, bool)
{ std::cout << text << std::flush; });
agent.SetOnTurnComplete([](TC::TurnStatus, std::string_view, std::int32_t)
{ std::cout << "\n"; });
agent.SendText("Hello!");
from tryll_client.graph import GraphDescription, HumanMessageGuardrailParams, GenerateParams
# 1. Build the graph.
guard_params = HumanMessageGuardrailParams(
# triggered_exit defaults to "" (END) — drop jailbreaks silently.
not_triggered_exit = "answer",
)
graph = (
GraphDescription()
.add_node("guard", guard_params)
.add_node("answer", GenerateParams())
.set_start_node("guard")
.set_default_model_name("My Local Model")
)
# 2. Create the agent.
agent = client.create_agent(graph)
# 3. Send a message. send_message blocks and returns the full reply.
reply = agent.send_message("Hello!")
print(reply)
Verify it worked¶
Server-side, at info log level, one turn produces:
[info] Agent 7: turn starting
[info] Node guard: not_triggered
[info] Node answer: default
[info] Agent 7: turn complete (Success, tokens=128)
Client-side, Python returns the full reply string from
send_message, while C++ and Unreal see each streamed chunk arrive
in the SendText callback / OnAnswerText delegate, terminated by
a TurnComplete with status Success.
Common pitfalls¶
- "Model not found" (error
4002) — either the
name is wrong, or the model is not in
models.json. Double-check withclient.list_models(). - "Graph validation failed" (error
3003) — check for a bad
start node name or duplicate node names in the graph. If the message
says
InvalidExitTarget(3008), an exit field on one of the nodes names a node that doesn't exist — fix the string or leave it empty for END. - Silent nothing after
SendMessage. Check that the start node is not an always-ENDbranch (e.g., guardrail that matches everything). The server log will show which route was taken.