Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

The embedder ladder

The four ways a semantic rule can turn text into a vector, what each costs per lookup, and which ones make the cache call out.

ReferenceLast updated

On this page

Exact and normalized caching need no embedder. Semantic caching needs one, and choosing it is the one place in the cache where you trade an air-gapped default for recall quality, latency or money. Each rung trades a single property, and you pick the rung rather than inheriting its cost.

Embedders are named instances. A rule names one; several rules can share one.

cache:
  embedders:
    potion: { kind: static }
  rules:
    - id: chat-hybrid
      strategy: [exact, normalized, semantic]
      semantic:
        embedder: potion

The ladder

kind Runs Cost per lookup Calls out
lexical in process, pure Go CPU, microseconds no
static in process, pure Go CPU, microseconds to milliseconds no
http at a loopback or local address your host a network round trip yes, gated
http at an external address a vendor a round trip and real spend yes, gated

The two in-process kinds are what the default build ships with, and a deployment using them dials nothing at all.

lexical

Sparse similarity with no model: min-hash and sim-hash signatures over term frequency. High precision, low recall, and surface-sensitive in a way that turns out to be useful — it notices the negation, polarity and date differences that dense embeddings smooth over. When it is wrong it is usually wrong toward a miss.

    lex: { kind: lexical }

Reach for it when you want a paraphrase tier that will not serve a near-miss, and you can accept catching fewer paraphrases.

static

A token-to-vector table looked up per token and mean-pooled with weights. No transformer pass, so it costs a table lookup per token rather than a forward pass, and it runs in process with no network.

    potion:
      kind: static
      table_file: /etc/tokentriage/potion-base-8M.tt

http

An OpenAI-compatible /embeddings client, serving both the local-network rung (a model you host) and the external rung (a vendor API). The distinction is the address, and both are refused unless the instance carries external: allow.

    local:
      kind: http
      base_url: "http://127.0.0.1:11434/v1"    # a model you run
      model: nomic-embed-text
      external: allow
    vendor:
      kind: http
      base_url: "https://api.openai.com/v1"
      model: text-embedding-3-small
      api_key_env: OPENAI_API_KEY              # names a variable, never the secret
      external: allow

The external rung is metered: the provider reports token usage and it is priced through the same path as every other dollar in the product.

The gate, and what it means

Any embedder that leaves the process is refused at load unless it says so:

cache.embedders["vendor"].external: an external-class embedder (kind http / non-loopback base_url) requires external: allow

Opening the gate is one auditable line, and it produces a warning naming the instance and its class:

warning: cache.embedders["vendor"]: external_api embedder — enabling it makes the cache PHONE OUT for embeddings (disclosed on every semantic trace; the air-gap posture is broken for this embedder)

An api_key_env value must name an environment variable. A literal secret in that field is a validation error.

What a metered embedder costs, and where you see it

An embedding runs on every lookup that reaches the semantic link, whether or not it produces a hit, because the vector is what the search needs. On a miss it runs once: the store write reuses the vector the lookup already computed rather than embedding a second time.

A priced call lands on the row as embed_cost, alongside embed_us:

"embed_cost": { "usd": 0.0000021, "estimated": false, "unpriced": false },
"embed_us": 412

It is estimated: false because it is a real charge against reported usage, not a counterfactual. If the price does not resolve, the field records unpriced: true and makes no dollar claim rather than reporting zero.

The same spend joins the metacost surface under a response_cache line, which is where the cost of measurement is collected across the whole product. What the measurement itself costs owns that surface; this page owns where the number comes from.

An in-process embedder produces no embed_cost field at all. It is counted as free because it is: CPU time on a process you are already running.

One embed per miss, and the exception

The vector computed during a lookup is carried to the store write, so a metered miss pays for one embedding rather than two. The exception is a write with no lookup vector — a forced refresh, which bypasses the read. That embedding has no request row in flight to land on, so it is charged to the metacost line and counted per rule on the evidence surface instead of being dropped.

Changing an embedder repartitions the cache

The embedder’s fingerprint is part of the scope key for a semantic matcher. Swapping the instance a rule names, or pointing table_file at a different table, makes every existing semantic entry unreachable. Vectors from two different embedders are not comparable, so this is the outcome you want; plan for a cold semantic tier after the change.

Embedder definitions themselves reload hot, but the effect is the same partition change.

What can go wrong

Recall is poor and every paraphrase misses. Almost always the bundled fixture table. Check whether table_file is set.

The semantic tier is slower than expected on a large working set. Embedding is only part of the cost; the search over a large per-scope set is the rest. See budgets and performance.

A vendor outage turns into misses, not errors. An embedding failure is a fault that forwards the request live, and the row records it. The client is served either way.