Skip to content
tnsaiDocs
GitHub

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

FactoryBehaviour
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.

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:

AccessorReturns
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.
  • 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.