One provider integration instead of one per extension
Every extension that wants AI calls the same service. Adding the second AI feature no longer means building the second provider integration, the second key store and the second error handler.
TYPO3 Extension · nr-llm
Connect a provider once, steer access centrally, cap budgets, and make AI features available to every extension on the site — under control.
Four different values. A difference between them is not a contradiction — the release is what you install, main is what is being built, and the review date is when a person last checked this page's wording.
The technical description is further down. These are the effects that decide whether it is worth adopting.
Every extension that wants AI calls the same service. Adding the second AI feature no longer means building the second provider integration, the second key store and the second error handler.
Configurations name a provider and a model. Moving from a commercial API to a self-hosted endpoint changes a backend record, not the code in the consuming extensions.
API keys live in nr-vault as identifiers. They are not in the database in plaintext, not in a configuration file, and not in three extensions at once.
Usage is recorded per provider, model, service and backend user, with an estimated cost. Budgets cap it per user and per period.
Translation, alt text, embeddings and retrieval are services other extensions inject rather than reimplement.
Ollama, vLLM and LocalAI are configured the same way as a commercial API, so a data-residency requirement does not force a different architecture.
What an administrator can actually decide, and where the software enforces it.
Per-capability permissions are granted to backend groups. The configuration module itself is administrator-only.
Budgets cap requests, tokens or estimated cost per backend user, daily or monthly. A run that would exceed the budget fails with a typed exception rather than silently continuing.
Built-in tools are grouped and each group is toggled on or off. Read-only tools are the default; the writing tools ship disabled.
An approval-gated tool suspends the run and waits for a person to decide. There is no timeout that approves by default.
Provider, model, service, user, token counts and estimated cost per request. Prompt content is not part of the usage record.
A configuration can name fallbacks, retried on connection errors, HTTP 5xx and rate limits, in the order declared. An exhausted chain raises a typed exception rather than returning an empty answer.
One request, end to end.
All configured the same way. The choice is an administrative decision, not an architectural one.
| Model | Where data is processed | Fits when | What it costs you |
|---|---|---|---|
| Commercial API (OpenAI, Anthropic, Google, Mistral, Groq) | At the provider | Model quality and choice decide | A contractual and data-protection review per provider |
| EU-hosted provider | At the provider, inside the EU | The processing location is prescribed | A smaller model selection |
| Azure OpenAI or an OpenAI-compatible endpoint | In your own cloud account | A framework agreement already exists | Commitment to the platform |
| Self-hosted (Ollama, vLLM, LocalAI) | In your infrastructure | Data must not leave the network | Your own operations, hardware and model maintenance |
| Mixed | Different per configuration | Sensitive and uncritical cases differ | More configuration and more to explain |
What the extension actually does about cost. No savings figure is claimed here — that depends on volumes we do not know.
Requests, tokens or estimated cost. Each can be capped daily or monthly, per backend user.
Usage analytics break estimated cost down by provider, model, service and user, over a rolling window.
The request is refused with a typed BudgetExceededException. The calling extension decides how to present that.
A configuration can name fallbacks — a smaller model, or a self-hosted endpoint with no per-token cost.
From the per-model pricing recorded in the backend, applied to the token counts the provider returns. It is an estimate, not the provider's invoice.
Each is small enough to finish and specific enough to judge.
Configure one provider, grant the translation capability to the editorial group, set a monthly budget, translate a page tree. Afterwards you can see who translated what, at which model, at what estimated cost.
Point a configuration at a vision-capable model and generate alt text for a media folder. Compare a commercial model against a self-hosted one on the same images.
Enable the retrieval tool group and ask questions about your own site content. Check that every answer names the pages it came from.
The same fields on every Netresearch AI product, so they can be compared rather than admired.
Every TYPO3 extension that wants AI capabilities today has to solve the same infrastructure problems on its own. When a site runs three AI extensions, that means three separate API key configurations, three places to check when something breaks, and no way to switch providers globally.
nr-llm provides the missing shared layer between your extensions and the LLM providers. Extension developers add AI in a few lines of dependency injection; administrators manage every connection, key, and budget from one backend module.
Extensions inject a single service interface and call methods for chat, completion, translation, vision, embeddings, streaming, and tool calling. Provider selection, API keys, caching, and error handling are all managed by nr-llm.
Underneath, a provider abstraction layer maps a common interface onto OpenAI, Anthropic, Gemini, Ollama, OpenRouter, Mistral, Groq, Azure OpenAI, and any OpenAI-compatible endpoint. Switching providers is an admin setting, not a code change.
The Admin Tools > LLM backend module holds encrypted keys, usage and cost tracking, per-user budgets, and a setup wizard — restricted to administrators.
nr-llm is organized around a small set of building blocks. Each one solves a problem you would otherwise re-implement in every AI extension.
All providers implement one common interface. OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Mistral, Groq, Azure OpenAI, and any OpenAI-compatible endpoint (vLLM, LocalAI, LiteLLM) are reachable through the same service calls. Switch provider with a single configuration change — no code edits, no vendor lock-in.
Every API key is stored as a vault identifier (UUID) using nr-vault envelope encryption. nr-llm never stores or logs raw keys in plain text. Error messages are sanitized to strip secret-bearing query parameters before anything is logged.
A Provider holds an endpoint, encrypted key, and adapter type. A Model references a provider and defines its model id, capabilities, and pricing. A Configuration references a model and adds use-case settings — system prompt, temperature, token limits. This lets you keep multiple keys per provider (prod/dev/backup) and reuse model definitions across use cases.
High-level services cover common tasks: CompletionService for text generation with format and creativity control, TranslationService with formality and glossary support, VisionService for alt-text and image analysis, and EmbeddingService for text-to-vector conversion with similarity calculations.
Stream responses chunk by chunk with a single foreach over streamChat() for real-time UIs. Tool/function calling lets the model request functions your code executes, via chatWithTools() — the response reports which tools were called so you can process them.
48 built-in function-calling tools in 9 toggleable groups give the model grounded access to the TYPO3 instance — content search, TCA/FlexForm schema, TypoScript, source and exception reads, FAL files, diagnostics, and backend accounts. 41 of them only read. The rag group returns cited site-content evidence from the installed search index (EXT:solr, ke_search, indexed_search, or a database fallback), and the editing group holds the seven writing tools, which ship disabled and pause the run for human approval before they act.
Cap per-backend-user spending across every preset by requests, tokens, or estimated cost, on a daily or monthly basis. The Analytics view shows cost and usage trends with breakdowns per provider, model, and service, plus per-user consumption against monthly budgets.
Responses are cached automatically through TYPO3's caching framework — using whatever backend the instance configures (Redis, Valkey, Memcached, or the default). Embedding results cache deterministically with a 24-hour default lifetime, and cache lifetimes are configurable per operation type.
nr-llm serves three audiences with the same shared foundation.
Add AI capabilities without building provider integrations, handling API keys, or implementing caching and streaming. Inject one service interface and call it. Register custom providers when you need them.
Manage every AI connection, encrypted key, and provider configuration from a single backend module. Switch from OpenAI to Anthropic without touching extension code. Set per-user budgets and watch cost and usage in one dashboard.
Reduce integration effort across client projects with a consistent AI architecture and no vendor lock-in. Encrypted keys, admin-only access, and SBOM plus SLSA provenance on every release support compliance. Ollama gives a local-first option for data-sensitive environments.
Developers
Add AI to your TYPO3 extension in a few minutes — no API key handling, no HTTP client code, no provider-specific logic.
Install via Composer. Then activate in Admin Tools > Extensions and run Admin Tools > LLM > Setup Wizard.
composer require netresearch/nr-llm
Inject LlmServiceManagerInterface via constructor promotion and call it. Provider selection, API keys, caching, and error handling are all managed by nr-llm.
use Netresearch\NrLlm\Service\LlmServiceManagerInterface;
class MyController
{
public function __construct(
private readonly LlmServiceManagerInterface $llm,
) {}
public function summarizeAction(string $text): string
{
return $this->llm->complete("Summarize: {$text}")->content;
}
}
Chat, completion, streaming, embeddings and tool calling are methods on the injected LlmServiceManagerInterface. Translation and vision alt-text are dedicated feature services — inject them the same way. Tool calling is a request/execute/reply loop; the Streaming & Tool Calling deep dive has the complete example.
use Netresearch\NrLlm\Domain\ValueObject\ChatMessage;
$messages = [
ChatMessage::system('You are a helpful TYPO3 assistant.'),
ChatMessage::user('Explain TYPO3 content elements in one paragraph.'),
];
// Chat & completion (LlmServiceManagerInterface)
$answer = $this->llm->chat($messages)->content;
$answer = $this->llm->complete('Summarize the TYPO3 release cycle.')->content;
// Streaming — yields string chunks
foreach ($this->llm->streamChat($messages) as $chunk) {
echo $chunk;
}
// Embeddings — EmbeddingResponse carries the vector
$embedding = $this->llm->embed('semantic search query');
// Translation — dedicated service, returns a TranslationResult
$german = $this->translationService->translate('Hello world', 'de')->getText();
// Vision alt-text — dedicated service
$altText = $this->visionService->generateAltText($imageUrl);
Every provider error is a typed exception. Catch the ones you care about and show a friendly message; the fallback chain and retries have already run before these surface.
use Netresearch\NrLlm\Exception\BudgetExceededException;
use Netresearch\NrLlm\Provider\Exception\ProviderRateLimitException;
use Netresearch\NrLlm\Provider\Exception\ProviderConnectionException;
use Netresearch\NrLlm\Provider\Exception\FallbackChainExhaustedException;
use Netresearch\NrLlm\Provider\Exception\ProviderResponseException;
try {
return $this->llm->complete("Summarize: {$text}")->content;
} catch (BudgetExceededException) {
return 'The AI budget for this account is exhausted.';
} catch (ProviderRateLimitException) {
return 'The AI provider is rate-limiting requests. Please retry shortly.';
} catch (FallbackChainExhaustedException | ProviderConnectionException) {
return 'Could not reach any AI provider right now.';
} catch (ProviderResponseException $e) {
$this->logger->warning('LLM provider error', ['status' => $e->httpStatus]);
return 'The AI service returned an error.';
}
ChatOptions ships tuned presets — factual, creative, balanced, json, code — plus fluent overrides. CompletionService::completeJson() returns a decoded array, so you extract fields directly.
use Netresearch\NrLlm\Service\Option\ChatOptions;
// Deterministic output, capped length
$options = ChatOptions::factual()->withMaxTokens(200);
$summary = $this->llm->complete('Summarize the changelog.', $options)->content;
// Decoded JSON straight from the model
$data = $this->completionService->completeJson(
'Return {"title": ..., "tags": [...]} for this article: ' . $article,
);
$title = $data['title'];
For admins
The Admin Tools > LLM backend module gives administrators full control over AI on the site — providers, models, configurations, budgets, and analytics in one place.
Register API connections (OpenAI, Anthropic, Gemini, Ollama, and more), define which models are available and their capabilities, and create use-case presets with temperature, system prompts, and token limits.
The Setup Wizard auto-detects your provider type from the endpoint URL, discovers available models, and generates a ready-to-use configuration in five guided steps. Paste your API key and go.
The Task Wizard and Configuration Wizard generate complete tasks and configurations — system prompt, parameters, and model recommendation — from a plain-language description. A Fetch Models button auto-fills capabilities and pricing from the provider API.
Cap per-backend-user spending by requests, tokens, or cost on a daily or monthly basis across every preset. The Analytics view shows estimated cost and usage trends with breakdowns per provider, model, and service, plus per-user consumption against budgets.
48 function-calling tools in 9 toggleable groups let models inspect content, schema, configuration, code, files, system diagnostics, and accounts — with the rag group returning cited evidence from the installed search index. 41 only read; the six writers ship disabled and pause for approval.
The admin-only Playground runs the bounded agent loop against any configuration and streams the whole dialog live — every request, response, and tool execution — plus a dry-run mode that shows the exact prompt without calling the model.
Configurations can list fallback configurations to retry against on connection errors, HTTP 5xx, or rate limits. Per-capability permissions map to native TYPO3 backend-group options. Keys are stored encrypted via nr-vault and the module is restricted to administrators.
On-device AI
Ask a question about nr-llm and get an answer generated entirely in your browser by Chrome's built-in AI (Gemini Nano). Answers are grounded in this site's content.
Runs entirely in your browser. Nothing is sent to any server.
Architecture
nr-llm uses a three-tier configuration hierarchy that separates concerns cleanly. A Configuration (use-case settings such as system prompt, temperature, and max tokens) references a Model (model id, capabilities, pricing), which references a Provider (endpoint, encrypted API key, adapter type). This lets you keep multiple API keys per provider type, point at custom endpoints such as Azure OpenAI or a local Ollama or vLLM instance, and reuse model definitions across configurations. Requests flow through a middleware pipeline that enforces fallback chains and records usage after each successful call. The extension targets PHP 8.2+ and TYPO3 v13.4 LTS or v14.3 LTS, with a PSR-18 HTTP client.
TYPO3 v13.4 LTS or v14.3 LTS, and PHP 8.2 or higher. A PSR-18 compatible HTTP client (such as guzzlehttp/guzzle) is also required. The extension is currently in beta (version 0.32.0).
OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Mistral, Groq, Azure OpenAI, and any OpenAI-compatible endpoint (vLLM, LocalAI, LiteLLM). Capabilities vary by provider — for example OpenAI, Gemini, and OpenRouter support chat, embeddings, vision, streaming, and tools, while Groq focuses on fast chat and streaming.
Keys are stored as vault identifiers (UUIDs) via nr-vault envelope encryption. nr-llm never stores or logs raw keys in plain text, and the backend module is restricted to administrators. nr-vault is a required dependency.
Yes. nr-llm is licensed under GPL-2.0-or-later and developed by Netresearch DTT GmbH. The source is on GitHub and the package is on Packagist.
Yes. Ollama runs models locally and needs no API key, so AI features can work without sending data to external APIs — a local-first option for data-sensitive environments. Ollama supports chat, embeddings, and streaming.
Require netresearch/nr-llm via Composer, inject LlmServiceManagerInterface (or a specific feature service), and call its methods for chat, completion, translation, vision, embeddings, streaming, or tool calling. You can also register custom providers. See the Developer and Integration guides.
Set per-backend-user budgets that cap spending by requests, tokens, or estimated cost on a daily or monthly basis across every preset. Response caching through the TYPO3 caching framework reduces repeat calls, and the Analytics view tracks estimated cost and usage per provider, model, service, and user.
You choose the provider, including a local Ollama instance that keeps data on your own infrastructure. Keys are encrypted at rest, access is admin-only, and error messages are sanitized to strip secrets. Treat LLM responses as untrusted content and sanitize user input before sending it, as with any AI integration.
Yes. All providers implement a common interface, so switching from OpenAI to Anthropic or a local model is a configuration change in the backend, not a code edit. Configurations can also list fallback configurations to retry against on connection errors, HTTP 5xx, or rate limits.
Building your own means re-implementing key encryption, provider switching, caching, streaming, tool calling, cost tracking, budgets, guardrails, and error handling in every extension — and hard-coding one vendor. nr-llm centralizes all of that once, so administrators manage providers and keys in one backend module and any extension on the site reuses them with no vendor lock-in.
You choose the provider per configuration. Running Ollama or another OpenAI-compatible endpoint locally keeps all prompt data on your own infrastructure with no external API calls; for hosted providers you can select EU-region endpoints such as Azure OpenAI. API keys are encrypted at rest via nr-vault, access is admin-only, and error messages are sanitized to strip secrets.
nr-llm is not an end-user AI feature — it is shared infrastructure, like the TYPO3 caching framework, that other extensions build on. It provides one provider abstraction across seven-plus providers, encrypted key storage, and typed services, rather than a single bundled use case.
Encrypted API keys via nr-vault, an admin-only backend module with per-capability backend-group permissions, a guardrail pipeline that redacts secrets across input, output, and streaming, optional human-in-the-loop approval, and per-user budgets with usage analytics. 41 of the 48 built-in tools are read-only; the six that write ship disabled and suspend the run for human approval before they act. The Governance & Security page covers each control in detail.
We can work through which capabilities you need, which operating model fits your constraints, and what a first controlled step looks like.