Retrieve¶
The Retrieve node performs the R of RAG:
vector similarity search over an
embedded string storage, attaching
the top matches to the current interaction
as a knowledge component. A filter
that does not compile, changed via ChangeAgentParam, returns
3007 InvalidParamValue and leaves the previous filter in force.
It does not modify the prompt directly. Rendering is delegated to
the downstream Generate node's Mustache template,
which decides where and how the attached components appear in the
prompt.
NodeType: Retrieve.
Parameters¶
Input¶
| Param | Type | Default | Range | Structural | Localizable | Description |
|---|---|---|---|---|---|---|
input |
str | empty | — | ✓ | — | Text to search with: the player's message or an earlier node's output. Empty = the player's message. |
Storage¶
| Param | Type | Default | Range | Structural | Localizable | Description |
|---|---|---|---|---|---|---|
source_kind |
KnowledgeSourceKind | KnowledgeSourceKind.FromFile | — | ✓ | — | From File = a prebuilt knowledge file. From Name = a memory store the game fills during play (Write To Memory). |
embedded_string_storage |
str | empty | — | ✓ | — | Embedded knowledge file to search. Used when Source Kind is From File; leave empty with From Name. |
filter |
str | empty | — | — | — | JSON filter on chunk metadata; only matching chunks are searched. Empty = no filter. |
storage_name |
str | empty | — | ✓ | — | Memory store to search, by name. Used when Source Kind is From Name. May include agent variables (var.*). |
schema |
str | empty | — | ✓ | — | Record schema of the store. Required with From Name; with From File, checked against the file. |
Behavior¶
| Param | Type | Default | Range | Structural | Localizable | Description |
|---|---|---|---|---|---|---|
retrieval_mode |
RetrievalMode | RetrievalMode.Dense | — | — | — | Dense = match by meaning. Lexical = match by keywords (BM25). Hybrid = both, merged. |
top_k |
int | 2 | 1.0 – 1000.0 | — | — | How many matching chunks to return. |
threshold |
float | 0.5 | 0.0 – 2.0 | — | — | Max cosine distance for a chunk to count (0 = identical, 2 = opposite). 0 = no limit. Ignored in Lexical mode. |
rrf_k |
int | 60 | 1.0 – 1000.0 | — | — | Hybrid only: how strongly the merge favours top-ranked hits. Higher = flatter. 60 is the standard value. |
Output¶
| Param | Type | Default | Range | Structural | Localizable | Description |
|---|---|---|---|---|---|---|
source |
str | empty | — | — | — | Name templates use for these results: the knowledge_ |
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 |
|---|---|---|
found |
found_exit |
Node to run when at least one chunk is found. Empty = end the turn. |
not_found |
not_found_exit |
Node to run when nothing is found. Empty = end the turn. |
Schema picker (Unity)¶
schema is a catalog name, not a free-form path. In the Unity Inspector the
[TryllSchema] drawer reads the selected bundled server's data/schemas.json,
offers (none), and keeps an unknown value as Unregistered. An external
server may expose a different catalog — the server remains authoritative.
Keep both address fields visible: File mode still uses
embedded_string_storage; Name mode uses storage_name + schema.
Exit routes¶
| Route | Fires when |
|---|---|
found |
At least one chunk survived threshold filtering. |
not_found |
The resolved input text is empty, or zero chunks survived filtering. |
Each route can point at a different target or at the same target. An
empty exit field routes to END and is valid. A non-empty exit that does
not name a node in the graph fails agent creation with
3003 GraphCompilationFailed.
Recommended Hybrid defaults¶
Default retrieval_mode remains Dense (no silent behaviour change). To
opt in to hybrid retrieval:
| Param | Suggested value |
|---|---|
retrieval_mode |
Hybrid |
top_k |
3 |
threshold |
0.5 (dense-leg gate only — BM25-only hits are not filtered by it) |
rrf_k |
60 (Cormack default; 10 scored slightly higher on the PZ eval sweep) |
Release note: switching an existing graph to Hybrid changes how
threshold behaves — it no longer acts as a post-fusion precision guard.
Prefer top_k for result-count control in Hybrid.
Side effects¶
- In Dense / Hybrid modes, embeds the resolved
inputtext (an emptyinputmeans the player's message) using the embedding model recorded in the KB config file referenced byembedded_string_storage. Lexical mode skips the embedding call. - Queries the storage: Dense uses the HNSW cosine
index; Lexical uses an in-process BM25 index over record text (Path A
load-or-builds a
.bm25sidecar; Unicode casefold via utf8proc, format version 2); Hybrid runs both and fuses with Reciprocal Rank Fusion (rrf_k). The embedding model is owned by the KB config, not by this node. - Applies
thresholdas a maximum cosine distance on the dense leg (unchanged in Dense mode). In Hybrid, threshold gates dense candidates before fusion — BM25-only hits are not gated. In Lexical, threshold is inert. Prefertop_kas the primary precision control in Hybrid. - Attaches a knowledge block (
source+ surviving chunks) to the current turn. If nothing survives filtering, an empty block is still attached (the Mustache{{#knowledge_<source>}}section simply renders nothing). Lexical-only hits reportdistanceas JSONnull(no cosine distance).
The attached knowledge is rendered into the prompt on the next
Generate node, via the template and placement params on that node.
Diagnostics¶
When enable_diagnostics = true, this node contributes to
debug_info.nodes[].diagnostics. See
Turn Diagnostics JSON for the envelope.
No model runs here, so there is no engine section.
| Key | Meaning | Absent when |
|---|---|---|
parameters.source |
The source label attached to the component. |
— |
parameters.embedded_string_storage |
Configured storage name. | Not set. |
parameters.top_k |
Number of chunks requested. | — |
parameters.threshold |
Max cosine distance used for dense-leg filtering, or "inf" when disabled. |
— |
parameters.retrieval_mode |
Dense / Lexical / Hybrid. |
— |
parameters.rrf_k |
RRF constant (Hybrid only). | — |
parameters.filter |
Raw JSON of the active filter. Empty string when no filter is set. | — |
parameters.raw_result_count |
Dense: chunks from the index before threshold. Hybrid: dense_raw_count + sparse_raw_count. Lexical: sparse hit count. |
— |
parameters.filtered_count |
Dense/Hybrid: chunks removed by the dense-leg threshold. Lexical: always 0. |
— |
parameters.dense_raw_count |
Dense hits before threshold (0 for Lexical). | The run was pure Dense — the per-leg counts appear only on the Hybrid/Lexical path. |
parameters.dense_filtered_count |
Dense hits removed by threshold. | As above. |
parameters.sparse_raw_count |
BM25 hits before fusion / top-K trim. | As above. |
parameters.fused_candidate_count |
Hybrid: unique record ids across both legs before top-K trim (0 for Lexical). | As above. |
parameters.result_count |
Chunks actually attached to the component. | — |
input.query |
The text that was embedded / searched. | The resolved text was empty. |
output.results[] |
Ranked hits, in server order (RRF rank for Hybrid). Always present — an empty array on not_found, never a missing key. |
— |
output.results[].id |
Chunk id. | — |
output.results[].distance |
Cosine distance (lower = more similar), or JSON null for lexical-only hits. |
— |
output.results[].bm25_score |
Okapi BM25 score (stable public diagnostic). | The sparse leg did not contribute to this hit. |
output.results[].rank_dense |
1-based dense-leg rank (stable public diagnostic). | This hit did not come from the dense leg. |
output.results[].rank_sparse |
1-based BM25-leg rank (stable public diagnostic). | This hit did not come from the sparse leg. |
output.results[].text |
Chunk text that will be rendered into the prompt. | — |
The count chain raw_result_count → filtered_count → result_count is the funnel to read
when retrieval returns too little: it separates "the index had nothing" from "the threshold
rejected it". Note that output.results[] lists only the surviving chunks — the near-misses
are visible as the gap between the counts, and in the turn inspector's dimmed rows.
Minimum working example¶
from tryll_client.graph import GraphDescription, GenerateParams, RetrieveParams, Placement
RAG_TEMPLATE = (
"{{#knowledge}}"
"{{name}}:\n{{#chunks}}- {{text}}\n{{/chunks}}\n"
"{{/knowledge}}"
)
graph = (
GraphDescription()
.add_node("knowledge", RetrieveParams(
embedded_string_storage="aquarium/aquarium_all_mini.json",
top_k=3,
threshold=0.6,
found_exit="answer",
not_found_exit="answer",
))
.add_node("answer", GenerateParams(
template=RAG_TEMPLATE,
placement=Placement.BeforeUserAsSystem,
default_exit="", # empty = END
))
.set_start_node("knowledge")
.set_default_model_name("My Local Model")
)
agent = client.create_agent(graph)
using namespace Tryll::Client;
using namespace Tryll::NodeParams;
RetrieveParamsT kp;
kp.embedded_string_storage = "aquarium/aquarium_all_mini.json";
kp.top_k = 3;
kp.threshold = 0.6f;
kp.found_exit = "answer";
kp.not_found_exit = "answer";
GenerateParamsT gp;
gp.template_ = "{{#knowledge}}{{name}}:\n{{#chunks}}- {{text}}\n{{/chunks}}\n{{/knowledge}}";
gp.placement = ::Tryll::Placement_BeforeUserAsSystem;
// gp.default_exit = ""; // empty = END (the default)
GraphDescription graph;
graph.AddRetrieve("knowledge", std::move(kp))
.AddGenerate("answer", std::move(gp))
.SetStartNode("knowledge")
.SetDefaultModelName("My Local Model");
auto agent = client.CreateAgent(graph);
using Tryll.Client;
var graph = new TryllGraphBuilder()
.AddRetrieve("knowledge", new TryllRetrieveParams
{
EmbeddedStringStorage = "aquarium/aquarium_all_mini.json",
TopK = 3,
Threshold = 0.6f,
FoundExit = "answer",
NotFoundExit = "answer",
})
.AddGenerate("answer", new TryllGenerateParams
{
Template = "{{#knowledge}}{{name}}:\n{{#chunks}}- {{text}}\n{{/chunks}}\n{{/knowledge}}",
Placement = TryllPlacement.BeforeUserAsSystem,
})
.SetStartNode("knowledge")
.SetDefaultModelName("My Local Model")
.Build();
#include "Generated/TryllGraphBuilder.Nodes.h"
#include "Generated/TryllNodeParamsFactory.h"
UTryllRetrieveParams* KP = UTryllNodeParamsFactory::MakeRetrieveParams(this);
KP->EmbeddedStringStorage.Path = TEXT("aquarium/aquarium_all_mini.json");
KP->TopK = 3;
KP->Threshold = 0.6f;
KP->FoundExit = TEXT("answer");
KP->NotFoundExit = TEXT("answer");
UTryllGenerateParams* GP = UTryllNodeParamsFactory::MakeGenerateParams(this);
GP->bOverrideTemplate = true;
GP->Template = TEXT("{{#knowledge}}{{name}}:\n{{#chunks}}- {{text}}\n{{/chunks}}\n{{/knowledge}}");
GP->Placement = ETryllPlacement::BeforeUserAsSystem;
FTryllGraphDescription Graph = FTryllGraphBuilder()
.AddNode(TEXT("knowledge"), KP)
.AddNode(TEXT("answer"), GP)
.SetStartNode(TEXT("knowledge"))
.SetDefaultModelName(TEXT("My Local Model"))
.Build();
See the full walkthrough in How to create a simple RAG assistant.
Client bindings¶
- C++:
GraphDescription::AddRetrieve(name, RetrieveParamsT)—GraphDescription.h - Python:
GraphDescription.add_node(name, RetrieveParams(...))—tryll_client.graph - Unity:
TryllGraphBuilder.AddRetrieve(name, new TryllRetrieveParams{...})—Runtime/Generated/TryllGraphBuilder.Nodes.cs - Unreal:
AddRetrieveNode(builder, name, UTryllRetrieveParams*)—Generated/TryllGraphBuilder.Nodes.h