Training examples live in labeled datasets. Runtime routing does not accept a
train: DSL flag; local classifier artifacts and semantic-cache dataset
mirroring are separate systems.
For modern labeled datasets, write rows with text and label/intent and let
the classifier trainer consume them directly:
[
{"text": "how much does it cost?", "label": "PRICING"},
{"text": "my API key is failing", "intent": "TECHNICAL_SUPPORT"}
]Classifier artifacts route by label when :classifier can see the route.
Semantic cache mirrors labeled dataset rows by default unless the route uses
cache: false.
You can configure those sources globally:
config :spectre, :classifier,
dataset_path: "training/dataset.json",
artifact_dir: "artifacts/spectre"Do not put training examples in the DSL. train:, training:,
train: [...], train: "file.jsonl", and similar inline training forms are
invalid.
Dataset files can be:
.jsonlist of objects.jsonlone object per line
Rows can use either label or intent. Labeled .json and .jsonl rows are
also mirrored into exact semantic-cache lookup by default for cacheable routes:
[
{"text": "how much does it cost?", "label": "PRICING"},
{"text": "my API key is failing", "intent": "TECHNICAL_SUPPORT"}
]Export rows from an agent:
mix spectre.classifier.dataset MyApp.SupportAgent \
training/support/dataset.json \
--source training/raw/support.jsonl
Train a local classifier artifact:
mix spectre.classifier.download_model --model intfloat/multilingual-e5-small
mix spectre.classifier.train training/support/dataset.json priv/spectre/support
Training writes a semantic_cache.jsonl companion into the artifact directory.
It contains each training row together with the embedding already created
during training. At runtime, Spectre filters it to cacheable routes and loads
the saved vectors; it does not call the embedding provider once per dataset
row.
Configure the classifier:
config :spectre, :classifier,
artifact_dir: "priv/spectre/support",
encoder_model: "intfloat/multilingual-e5-small",
embedding_adapter: Spectre.Classifier.Embeddings.ExFastembedClassifier artifacts use compact centroids by default. At runtime, the local
classifier indexes centroids with Vettore, while semantic-cache search uses the
saved row vectors in semantic_cache.jsonl.
For larger datasets, nearest-example routing can be worth the extra memory:
config :spectre, :classifier,
local_classifier_mode: :examples,
local_classifier_index: :hnsw,
local_classifier_index_options: [ef_search: 64],
local_example_score: :maxUse :mean for local_example_score if you prefer averaging returned hits per
label.
Online learning review
Rows mirrored from an offline labeled dataset are trusted immediately. Rows
created at runtime through SemanticCache.put/3 are different: they remain
editable but unverified and are excluded from exact and semantic routing until
SemanticCache.verify/3 or SemanticCache.relabel/4 approves them.
{:ok, row} =
Spectre.Router.SemanticCache.put(
"need a custom quote",
%{label: :SALES, strategy: :llm_classifier},
spectre_agent: MyApp.SupportAgent
)
{:ok, pending_review} =
Spectre.Router.SemanticCache.examples(MyApp.SupportAgent)
{:ok, verified} =
Spectre.Router.SemanticCache.verify(MyApp.SupportAgent, row.id)This prevents an unverified classifier or LLM decision from immediately becoming ground truth. Review APIs still list quarantined rows, and snapshots preserve both their verification state and embedding.
The runtime request invariant is:
- an accepted exact-cache hit makes zero embedding calls;
- a non-exact semantic lookup embeds only the incoming text once;
- a newly learned row stores its embedding for later snapshots; and
- loading a snapshot never regenerates every stored embedding.
Built-in semantic indexes are bounded to four cached revisions per agent by default. Override this only when an agent intentionally uses several embedding configurations:
config :spectre, :semantic_cache, index_capacity: 8Use :unlimited explicitly if unbounded index retention is truly intended.