Configuration presets
A consuming extension can declare the LlmConfiguration records it
needs as configuration presets. nr_llm lists declared-but-not-yet
imported presets as pending; a backend admin imports one with a single
confirmation. See ADR-056 for the design rationale.
A preset expresses requirements — model capabilities and constraints
as Model — never a concrete provider, model, or
API key. The imported record runs in criteria selection mode, so
Model resolves the actual model on every run
against whatever the admin has configured.
Declaring presets in your extension
Implement Configuration. The
nr_llm.configuration_preset DI tag is applied automatically when your
extension's Services. has autoconfigure: true (the TYPO3
default):
<?php
declare(strict_types=1);
namespace Vendor\NrAiSearch\Integration;
use Netresearch\NrLlm\Domain\DTO\ModelSelectionCriteria;
use Netresearch\NrLlm\Service\Preset\ConfigurationPreset;
use Netresearch\NrLlm\Service\Preset\ConfigurationPresetProviderInterface;
final class AiSearchPresetProvider implements ConfigurationPresetProviderInterface
{
public function getPresets(): array
{
return [
new ConfigurationPreset(
identifier: 'nr_ai_search.chat',
name: 'AI Search Chat',
description: 'Answers site-search questions with tool support.',
criteria: new ModelSelectionCriteria(
capabilities: ['chat', 'tools'],
minContextLength: 8000,
),
systemPrompt: 'You answer questions about this website.',
temperature: 0.2,
maxTokens: 2000,
),
new ConfigurationPreset(
identifier: 'nr_ai_search.embedding',
name: 'AI Search Embeddings',
description: 'Creates embeddings for the search index.',
criteria: new ModelSelectionCriteria(
capabilities: ['embedding'],
preferLowestCost: true,
),
),
];
}
}
Rules the value object enforces at construction time:
- The identifier is lowercase
[a-z0-9_]segments separated by dots and must be namespaced with your extension key (nr_ai_search.chat) so presets from different extensions cannot collide. Duplicate identifiers across providers fail fast at container build time. - The criteria must require at least one capability.
All other fields (system prompt, temperature, max tokens, the daily
budget ceilings maxRequestsPerDay / maxTokensPerDay /
maxCostPerDay, allowedToolGroups) are optional seeds; null
keeps the column default of the created record.
At runtime, resolve the imported configuration by its identifier as
usual, for example through
Llm.
Import flow
- The admin queries the pending presets (AJAX route
nrllm_preset_list). Each entry carries a preflight result: whether the criteria currently match an active model (satisfiable+matchedModelLabel), or which requirement eliminates every candidate (missingRequirement). - The admin confirms one import (AJAX route
nrllm_preset_importwith the presetidentifier). nr_llm creates an active, criteria-modetx_nrllm_configurationrecord and stores the preset's checksum inpreset_checksum. - The record is a normal configuration from then on — the admin can edit or delete it. A preset whose identifier already has a record is never offered again (and an import attempt is refused), so imports are idempotent; the stored checksum makes a changed declaration detectable.
Both endpoints are restricted to backend administrators
(ADR-037). Admins normally go through the
Configurations backend module, which renders the pending presets —
including each preflight result — above the configuration records and
imports one through nrllm_preset_import with a single click; see
Importing configuration presets.
Change detection
nrllm_preset_list additionally returns a drifted list: imported
presets whose current declaration checksum no longer matches the
preset_checksum stored at import time. Each entry carries
identifier, name, and configurationUid. The Configurations
module flags such records with a non-blocking "Preset changed" hint.
Detection only: nr_llm never updates an imported record automatically. If your extension changes a preset declaration, document what admins should adjust — the checksum-driven update flow (diff + re-confirm) is deliberately deferred (ADR-056).