---
title: "ADR-066: Criteria-mode configurations resolve in the service layer"
manual: "TYPO3 LLM Extension"
version: "0.35"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-066@0.35"
source: "Adr/Adr066CriteriaConfigurationResolution.rst"
modified: "2026-09-16T22:09:16+00:00"
---

# ADR-066: Criteria-mode configurations resolve in the service layer

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

## Context

[ADR-055](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-055@0.35) and [ADR-056](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-056@0.35) introduced
`LlmConfiguration` records with two selection modes: `fixed` (a
direct `model_uid` relation) and `criteria` (a stored
`ModelSelectionCriteria` JSON; `model_uid = 0`, the concrete model
chosen at call time). `ModelSelectionService::resolveModel()` was the
resolver — but it had **no production caller**. Every
`*ForConfiguration()` entry point (`embedForConfiguration`,
`chatWithConfiguration`, `chatWithToolsForConfiguration`,
`completeWithConfiguration`, `streamChatWithConfiguration`) obtained
its adapter through `getAdapterFromConfiguration()`, which read
`$configuration->getLlmModel()` — a plain getter — directly. For a
criteria-mode record that relation is `null`, so the call threw
`ProviderException: Configuration "…" has no model assigned`.

This surfaced live: nr_ai_search's `embeddingConfiguration` /
`chatConfiguration` presets are criteria-mode, so the entire retrieval
path failed the moment it reached the provider adapter.

## Decision

`getAdapterFromConfiguration()` — the single adapter choke point for
every `*ForConfiguration()` path — resolves the model through
`ModelSelectionService::resolveModel($configuration)`, which returns
the directly configured model unchanged for `fixed` mode and selects
from the stored criteria for `criteria` mode. It still throws the same
`ProviderException` (code `1735300100`) when resolution yields no
model.

The resolved model is **not** written back onto the configuration. The
configuration is a repository-managed Extbase entity; calling
`setLlmModel()` would mark it dirty and Extbase would persist
`model_uid` at request end, silently converting a criteria-mode record
into a fixed-mode one. Per-model cost analytics for criteria configs
(`UsageMiddleware` reads `getLlmModel()` directly) therefore remain a
separate, non-destructive follow-up.

## Consequences

-   Criteria-mode configurations work across embed / chat / tools /
    complete / stream without the caller pre-resolving a model.
-   `ModelSelectionServiceInterface` is a trailing, nullable constructor
    dependency of `LlmServiceManager` (autowired); when absent the method
    falls back to the raw getter, so existing 5-argument constructions keep
    compiling.
-   Cost analytics for criteria-mode configs fall back to the
    provider-reported model id (no per-model DB pricing) until the
    follow-up threads the resolved model through the call metadata.

See PR #372.
