# RAG diagnostics

`com.tnsai.intelligence.rag.binding.RagDiagnostics` tells you what a Role's
knowledge declarations resolved to, before a bad answer sends you debugging.
It does not change retrieval.

A snapshot reports:

- which `EmbeddingFunction` is in use, and whether it is the bundled hash
  fallback;
- whether a reranker, query expander or graph-store provider is installed
  (absence is a reported fact, not an exception);
- per source: name, type, detected format, document count, chunk count and
  ingest fingerprint;
- the effective `@Search` declarations.

## Take a snapshot

| Factory | Behaviour |
|---|---|
| `of(roleType)` | Snapshots the binding that already exists for the Role. Never ingests, and fails when the Role has not been bound. |
| `of(roleType, true)` | Builds the binding when none exists, which runs ingest. |
| `of(roleType, sources, ingestIfAbsent)` | For a Role bound with `RoleRagBinding.forRole(role, sources)`. Pass the same sources, since the binding cache is keyed on the Role and its sources together. |
| `of(binding)` | Snapshots a `RoleRagBinding` you hold. |

Provider lists come from a fresh SPI discovery of the classpath, not from the
registries the binding resolved against, so an application that injects its
own registries can see names it does not use.

<!-- java-contract: src/main/java/com/example/tnsai/docs/RagDiagnosticsExample.java -->
```java
package com.example.tnsai.docs;

import com.tnsai.annotations.ActionSpec;
import com.tnsai.annotations.Knowledge;
import com.tnsai.annotations.RoleSpec;
import com.tnsai.annotations.Search;
import com.tnsai.enums.ActionType;
import com.tnsai.intelligence.rag.binding.RagDiagnostics;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.roles.Role;

public final class RagDiagnosticsExample {
    private RagDiagnosticsExample() {}

    @RoleSpec(knowledge = @Knowledge(
        name = "product-docs",
        path = "knowledge/products",
        search = @Search(
            strategy = Search.Strategy.SEMANTIC,
            queryParam = "question",
            topK = 5)))
    public static final class FaqRole extends Role {
        @Override
        public RoleIdentity getIdentity() {
            return new RoleIdentity("faq", "FAQ answers", "support");
        }

        @ActionSpec(
            type = ActionType.LLM,
            description = "Answer from product documentation",
            knowledge = @Knowledge(name = "product-docs"))
        public String answer(String question) {
            return question;
        }
    }

    public static String inspectExistingBinding() {
        return RagDiagnostics.of(FaqRole.class).report();
    }

    public static String inspectByBuilding() {
        return RagDiagnostics.of(FaqRole.class, true).report();
    }
}
```

`inspectExistingBinding()` asks what the Role already became.
`inspectByBuilding()` builds the binding to show it. Do not call the
two-argument form from a health endpoint unless you mean to pay for ingest.

## Read the snapshot

`report()` returns human-readable text. Paste it into an issue, but do not
parse it: its format is not stable. Tests and health checks use the typed
accessors:

| Accessor | Returns |
|---|---|
| `roleClass()` | The Role this snapshot describes |
| `embeddingType()` | The fully qualified type of the embedding function in use |
| `bundledHashFallback()` | `true` when retrieval uses the bundled hash embedding |
| `reranker()`, `queryExpander()`, `graphStore()` | A `ProviderStatus`: `names()` in discovery order, and `installed()` |
| `sources()` | One `SourceSnapshot` per source, in declaration order: `name`, `type`, `declaredFormat`, `detectedFormats`, `documentCount`, `chunkCount`, `fingerprints` |
| `retrieval()` | One `RetrievalDeclaration` per `@Search`: `location` (`type` or `method:<name>`), `strategy`, `sources`, `rerank`, `rerankerModel`, `queryExpansion`, `queryExpansionModel` |

## Other ways a downgrade shows

- **Hash embedding.** Without an installed `EmbeddingFunction`, `SEMANTIC`,
  `HYBRID`, `HIERARCHICAL` and `TEMPORAL` log one WARN per Role class and
  rank by token overlap. See
  [Embeddings](embedding-prefixes.md#hash-embedding-fallback).
- **Missing reranker or expander.** TnsAI ships no `Reranker` and no
  `QueryExpander`. The exception names the registered providers and a remedy
  that matches the `(strategy, queryExpansion)` pair.
- **Unservable model names.** `rerankerModel` and `queryExpansionModel` are
  resolved when `Role.create` sets up retrieval. A name no provider serves is a
  configuration error before `@Search.onFailure` applies. A failure a provider
  raises while ranking or expanding still follows `onFailure`. A Role that asks
  for neither rerank nor expansion does not load those providers.

## Related

- [Embeddings](embedding-prefixes.md): installing an embedding provider and the
  hash fallback.
- [Install retrieval providers](providers.md): rerankers, query expanders and
  graph stores.
- [RAG](index.md): `@Knowledge` and `@Search` fields.
- [Annotation runtime status](../annotations/runtime-status.md): wiring versus
  dispatch.
