---
title: "ADR-055: Embeddings join the configuration path; dimensions metadata"
manual: "TYPO3 LLM Extension"
version: "0.35"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-055@0.35"
source: "Adr/Adr055EmbeddingConfigurationPath.rst"
modified: "2026-09-16T22:09:16+00:00"
---

# ADR-055: Embeddings join the configuration path; dimensions metadata

-   *Status:* Accepted
-   *Date:* 2026-07-13
-   *Authors:* Netresearch DTT GmbH

## Context

The three-tier model (Provider → Model → Configuration,
[ADR-001](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-001@0.35)) reaches every chat-shaped capability:
`completeWithConfiguration()`, `chatWithConfiguration()`,
`streamChatWithConfiguration()` and
`chatWithToolsForConfiguration()` all resolve the adapter from a
DB-backed `LlmConfiguration` (vault key + model + pricing) and run
through the middleware pipeline, so budgets are enforced and cost is
attributed per configuration.

Embeddings did not. `LlmServiceManager::embed()` only accepted
`EmbeddingOptions` with raw `provider`/`model` strings, resolved
against ExtensionConfiguration and a model-less transient
configuration. An embedding consumer that persists vectors (a search
index, semantic auto-linking — see the scope boundary in
[ADR-050](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-050@0.35)) therefore had to duplicate provider, model
and dimensionality into its *own* extension configuration, bypassing
per-configuration budgets and cost attribution entirely.

The dimensionality gap made this worse: no record anywhere stated how
many dimensions a model's vectors have. A consumer validating a
persisted vector index against the configured model had to run a live
"calibration probe" — embed a throwaway string and count the floats —
which costs a provider call and fails when the provider is unreachable.

## Decision

**Embeddings join the configuration path.**
`LlmServiceManager::embedForConfiguration()` mirrors
`chatWithToolsForConfiguration()`: it resolves the adapter via
`getAdapterFromConfiguration()`, runs through the middleware
pipeline with `ProviderOperation::Embedding` and the budget
metadata from the options, and guards the `embeddings` feature the
same way `embed()` does (`UnsupportedFeatureException` when the
provider lacks it). Per-call `EmbeddingOptions` take precedence over
the configuration's stored defaults — an options `model` overrides
the configuration's model id. Caching mirrors `embed()`: a positive
`cache_ttl` places a cache key on the call context (keyed on the
configuration identifier plus the *effective* model), so two
configurations pointing at different models never share cache entries.

The high-level feature service follows:
`EmbeddingService::embedForConfiguration()` and
`embedBatchForConfiguration()` delegate to the manager and populate
`beUserUid` via the shared auto-populate wiring, exactly like the
existing `embed()`/`embedBatch()` paths.

**Model records carry dimensions metadata.** `tx_nrllm_model` gains
a `dimensions` column (integer, `0 = unknown`, declared like
`context_length`), surfaced in the TCA next to the other model
limits and on the `Model` entity as
`getDimensions()`/`setDimensions()`. It is descriptive metadata:
nothing in nr_llm enforces it at call time.

## Consequences

-   Embedding consumers select a backend-managed configuration instead
    of duplicating provider + model + dimensions into their own
    extension configuration; per-configuration budgets and cost
    attribution apply to embeddings like to every chat-shaped
    capability.
-   A consumer can validate a persisted vector index against the
    configured model by comparing its stored dimensionality with
    `getLlmModel()->getDimensions()` — no live calibration probe, no
    provider round-trip. A value of `0` means "unknown"; consumers
    fall back to their previous behaviour then.
-   `LlmServiceManagerInterface` and `EmbeddingServiceInterface`
    gained methods — implementers outside this repo must add them.
-   nr_llm's embedding capability remains stateless
    ([ADR-050](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-050@0.35)): the configuration path changes *how the
    call is resolved and accounted*, not what is persisted. Vector
    stores stay out of scope.
