---
title: "KeywordSearch"
manual: "TYPO3 LLM Extension"
version: "0.35"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:api-keyword-search@0.35"
source: "Api/KeywordSearch.rst"
modified: "2026-09-16T22:09:16+00:00"
---

# KeywordSearch

-   **interface KeywordSearchInterface**

    -   *Fully qualified name:* `\Netresearch\NrLlm\Service\Retrieval\KeywordSearchInterface`

    Public keyword-search facade over the site-search retrieval cascade
    ([ADR-071: Public keyword-search facade over the retrieval cascade](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-071@0.35)). Searches the first available backend (Solr,
    ke_search, indexed_search, database fallback), always filtered
    public-only — hits are what the anonymous visitor could read.

    Input is clamped, never rejected, and any backend failure degrades
    to an empty result: the facade never throws.

    -   **search(string $query, int $limit, ?int $languageId = null) : array**

        Run a public-only keyword search. The query is trimmed and
        truncated to 200 characters; a query shorter than 2 characters
        returns an empty list. The limit is clamped to 1–20; a negative
        language id is clamped to 0. Hits are deduplicated by URL and
        capped at the limit.

        -   *param string $query:* Free-text query
        -   *param int $limit:* Maximum number of hits (clamped to 1–20)
        -   *param ?int $languageId:* sys_language uid; null means default (0)

        *Returns:* list\<KeywordHit> — empty when nothing matched, the query is too short, or no backend is available

    -   **isAvailable() : bool**

        Whether at least one search backend of this variant can answer
        right now. Never throws.

-   **class KeywordHit**

    -   *Fully qualified name:* `\Netresearch\NrLlm\Service\Retrieval\KeywordHit`

    One keyword-search hit. Final readonly DTO.

    -   **sourceId**

        string — Stable source id; format is backend-internal.

    -   **title**

        string — Result title.

    -   **url**

        string — Public URL of the hit (may be empty when the backend
        cannot resolve one).

    -   **excerpt**

        string — Short indexed-content excerpt.

    -   **languageId**

        int — sys_language uid the hit belongs to.

    -   **score**

        ?float — Backend-native relevance score; not comparable across
        backends; null when the backend reports none.

    -   **pageUid**

        ?int — Page uid when the answering backend can resolve the hit
        to a page, null otherwise.

## Service variants

Two container registrations exist ([ADR-071: Public keyword-search facade over the retrieval cascade](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-071@0.35)):

-   `KeywordSearchInterface` — the full cascade including the
    database LIKE fallback. Wire it via constructor type hint or resolve
    it from the container.
-   `nr_llm.keyword_search.index_backed` — a named variant that
    excludes the fallback tier. Use it when "index unavailable" must
    yield an empty result instead of LIKE hits (e.g. hybrid dense+sparse
    fusion). Its `isAvailable()` answers for index-backed engines
    only.

## Usage

```php
use Netresearch\NrLlm\Service\Retrieval\KeywordSearchInterface;

final class PageFinder
{
    public function __construct(
        private readonly KeywordSearchInterface $keywordSearch,
    ) {}

    public function findCandidates(string $topic): array
    {
        if (!$this->keywordSearch->isAvailable()) {
            return [];
        }

        return $this->keywordSearch->search($topic, 10);
    }
}
```

Wiring the index-backed-only variant:

```yaml
Vendor\Ext\Search\SparseArm:
  arguments:
    $keywordSearch: '@nr_llm.keyword_search.index_backed'
```
