---
title: "ADR-018: Multi-Provider Model Discovery"
manual: "TYPO3 LLM Extension"
version: "0.35"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-018@0.35"
source: "Adr/Adr018MultiProviderModelDiscovery.rst"
modified: "2026-09-16T22:09:16+00:00"
---

# ADR-018: Multi-Provider Model Discovery

-   *Status:* Accepted
-   *Date:* 2025-12
-   *Authors:* Netresearch DTT GmbH

## Context

Different LLM providers expose different model listing APIs. OpenAI offers
`GET /v1/models`, Ollama uses `GET /api/tags`, Anthropic has no public
listing endpoint, and Gemini uses a different URL structure entirely. The
setup wizard needs a unified way to discover available models regardless of
provider.

### Problem statement

1.  **Heterogeneous APIs:** No standard protocol for model listing.
1.  **Authentication variance:** Bearer tokens, API key headers, URL parameters.
1.  **Response format divergence:** Each provider returns
    different JSON structures.
1.  **Offline providers:** Some providers (Anthropic,
    Azure) lack public model list APIs.
1.  **Endpoint normalization:** Users enter URLs
    with/without trailing slashes, versions, schemes.

## Decision

Abstract model discovery behind
`ModelDiscoveryInterface` with two operations:

**ModelDiscoveryInterface contract**

```php
interface ModelDiscoveryInterface
{
    /** @return array{success: bool, message: string} */
    public function testConnection(DetectedProvider $provider, string $apiKey): array;

    /** @return array<DiscoveredModel> */
    public function discover(DetectedProvider $provider, string $apiKey): array;
}
```

The `ModelDiscovery` implementation routes per adapter type to one
discoverer class per provider (extracted from the original single-class
implementation in 2026-08; behaviour unchanged):

**Provider-specific dispatch**

```php
public function discover(DetectedProvider $provider, string $apiKey): array
{
    $discoverer = $this->discoverers[$provider->adapterType] ?? null;
    if (!$discoverer instanceof AbstractModelDiscoverer) {
        return $this->getDefaultModels($provider->adapterType);
    }

    $result = $discoverer->discover($endpoint, $apiKey);
    $this->lastDiscoveryUsedFallback = $result->usedFallback;

    return $result->models;
}
```

Each provider's listing, filtering, enrichment and fallback catalog live in
`Classes/Service/SetupWizard/Discovery/` as a
`*ModelDiscoverer extends AbstractModelDiscoverer`; whether a result is
live API data or a canned catalog travels on the returned
`DiscoveryResult` rather than on shared service state.

Key design elements:

-   **API-driven discovery** for providers with listing endpoints (OpenAI, Ollama,
    Mistral, Groq, OpenRouter, Gemini).
-   **Static fallback catalogs** for providers without
    listing endpoints (Anthropic, Azure, unknown).
    Maintained with current model information.
-   **Provider detection** via `ProviderDetector`
    using URL pattern matching with confidence scores
    (1.0 for exact match, 0.3 for unknown).
-   **Normalized DTOs:** `DiscoveredModel` unifies
    model metadata across providers (modelId, name,
    capabilities, contextLength, costs, recommended
    flag).
-   **Authentication dispatch:** Per-provider header
    format (`Authorization: Bearer`,
    `x-api-key`, `x-goog-api-key`, none for
    Ollama).

### Provider detection patterns

`ProviderDetector` matches endpoint URLs against known patterns:

| Pattern | Adapter Type | Confidence |
| --- | --- | --- |
| api.openai.com | openai | 1.0 |
| api.anthropic.com | anthropic | 1.0 |
| generativelanguage.googleapis.com | gemini | 1.0 |
| \\\*.openai.azure.com | azure_openai | 1.0 |
| localhost:11434 | ollama | 1.0 |
| \\\*/v1/chat/completions (path match) | openai | 0.6 |
| Unknown endpoint | openai (fallback) | 0.3 |

## Consequences

**Positive:**

-   ●● Unified model discovery across seven provider types.
-   ● Static catalogs ensure discovery works even without API access.
-   ● Confidence scoring lets the UI warn about uncertain detections.
-   ◐ PSR HTTP interfaces allow testing with mock HTTP clients.
-   ◐ Endpoint normalization handles common user input variations.

**Negative:**

-   ◑ Static catalogs require periodic updates as providers release new models.
-   ◑ API-based discovery may expose all models, including deprecated ones.
-   ✕ Rate limiting on model listing endpoints not handled.

**Net Score:** +5.0 (Strong positive)

## Files changed

**Added:**

-   `Classes/Service/SetupWizard/ModelDiscoveryInterface.php`
-   `Classes/Service/SetupWizard/ModelDiscovery.php`
    (since 2026-08 a facade over `Classes/Service/SetupWizard/Discovery/`,
    one discoverer class per provider)
-   `Classes/Service/SetupWizard/ProviderDetector.php`
-   `Classes/Service/SetupWizard/DTO/DetectedProvider.php`
-   `Classes/Service/SetupWizard/DTO/DiscoveredModel.php`
