# RAG

## What it does

Retrieval-Augmented Generation (RAG) grounds chat turns or action calls in
application-owned knowledge before the LLM runs. TnsAI keeps corpus loading,
retrieval policy, and invocation wiring separate so applications can choose the
entry point and strategy independently.

## Where RAG lives in the framework

RAG is intentionally split across three modules so each layer can evolve independently:

| Layer | Module | Provides |
|---|---|---|
| **Primitives** | [tnsai-core](https://github.com/TnsAI-Framework/TnsAI/tree/main/tnsai-core) (`com.tnsai.knowledge`, `com.tnsai.memory.advanced`) | `KnowledgeBase`, `Document`, `EmbeddingFunction`, `BM25Index`, `VectorMemoryStore`, `HybridMemoryRetriever` |
| **Strategies** | [tnsai-intelligence](https://github.com/TnsAI-Framework/TnsAI/tree/main/tnsai-intelligence) (`com.tnsai.intelligence.rag`) | `RAGPipeline`, `RAGStrategy`, `RetrievedDocument`, `RAGContext` |
| **Service / HTTP** | [tnsai-server](https://github.com/TnsAI-Framework/TnsAI/tree/main/tnsai-server) (`com.tnsai.server.rag`) | `RagService`, `FileIndexer`, `CodeChunker`, `HybridRetriever`, retrieval streams |

Embedded agents typically use Core primitives plus Intelligence strategies.
TnsAI.Server adds HTTP endpoints, indexing, and stream-based retrieval.

## Declarative (annotations)

Chat-level RAG combines `@ChatKnowledge` with a named `@KnowledgeSource` on
the same agent class. Action-level RAG places `@KnowledgeSource` on the Role
or on a non-`Role` SCOP object; `@Retrieval` may sit on the class or an
action method. Maven Central `0.14.1` runs that action path for a
non-`Role` target through [`SCOPBridge.executeAction`](../../integrate/scop.md).

SCOP **chat** grounding is the additive owner-aware
[`SCOPBridge.sendToLLM`](../../integrate/scop.md) overload (also on
`0.14.1`). The three-argument path remains ungrounded. Do not use
`sendToLLM` as a stand-in for action `@Retrieval`.

The published-release checks for this section use the Maven Central version
shown on [Installation](../../start/installation.md). Separate source checks
keep the examples compatible with the immutable framework commit pinned by
this repository.

Named `queryParam` selection requires consumer classes compiled with Java's
`-parameters` flag. In Maven, set `<maven.compiler.parameters>true</maven.compiler.parameters>`;
with `javac`, pass `-parameters`. A class-level `@Retrieval(queryParam = "question")`
also requires every action on that Role to expose a parameter named `question`.
If that contract differs by action, put `@Retrieval` on each method instead.
Missing names are configuration errors before `onFailure` applies.

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

import com.tnsai.agents.Agent;
import com.tnsai.annotations.ActionSpec;
import com.tnsai.annotations.ChatKnowledge;
import com.tnsai.annotations.KnowledgeSource;
import com.tnsai.annotations.Retrieval;
import com.tnsai.enums.ActionType;
import com.tnsai.llm.LLMClient;
import com.tnsai.llm.providers.AnthropicClient;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.roles.Role;

import java.util.List;

public final class RagTopicAnnotationsExample {
    private RagTopicAnnotationsExample() {}

    @ChatKnowledge(source = "faq", topK = 5)
    @KnowledgeSource(name = "faq", path = "knowledge/faq")
    public static final class SupportAgent extends Agent {
        @Override
        protected LLMClient getLLM() {
            return new AnthropicClient("claude-sonnet-4-20250514");
        }

        @Override
        protected List<Role> getRoles() {
            return List.of(Role.create(SupportRole.class));
        }
    }

    @KnowledgeSource(
        name = "product-docs",
        type = KnowledgeSource.KnowledgeType.FILE,
        path = "knowledge/products")
    @Retrieval(
        strategy = Retrieval.Strategy.HYBRID,
        sources = "product-docs",
        queryParam = "question",
        topK = 5)
    public static final class SupportRole extends Role {
        @Override
        public RoleIdentity getIdentity() {
            return new RoleIdentity("support", "Product support", "support");
        }

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

## Programmatic (builder)

For the equivalent programmatic paths, provide an existing `KnowledgeBase` or
immutable source/retrieval configs. The application composition root also
supplies the required principal, liability sink, and authority scope:

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

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.accountability.LiabilitySink;
import com.tnsai.agents.Agent;
import com.tnsai.agents.AgentBuilder;
import com.tnsai.annotations.Retrieval;
import com.tnsai.identity.AgentPrincipal;
import com.tnsai.llm.LLMClient;
import com.tnsai.rag.KnowledgeSourceConfig;
import com.tnsai.rag.RetrievalConfig;
import com.tnsai.roles.Role;

public final class RagTopicBuilderExample {
    private RagTopicBuilderExample() {}

    public static Agent buildActionAgent(
        LLMClient llmClient,
        Role supportRole,
        AgentPrincipal principal,
        LiabilitySink liabilitySink,
        AuthorityScope authorityScope
    ) {
        return AgentBuilder.create()
            .llm(llmClient)
            .role(supportRole)
            .addKnowledgeSource(
                KnowledgeSourceConfig.file(
                    "product-docs", "knowledge/products"))
            .retrieval(RetrievalConfig.builder()
                .strategy(Retrieval.Strategy.HYBRID)
                .sources("product-docs")
                .queryParam("question")
                .topK(5)
                .build())
            .principal(principal)
            .liabilitySink(liabilitySink)
            .authorityScope(authorityScope)
            .build();
    }

    public static Agent buildCanonicalChatAgent(
        LLMClient llmClient,
        Role supportRole,
        AgentPrincipal principal,
        LiabilitySink liabilitySink,
        AuthorityScope authorityScope
    ) {
        return AgentBuilder.create()
            .llm(llmClient)
            .role(supportRole)
            .addKnowledgeSource(
                KnowledgeSourceConfig.file("faq", "knowledge/faq"))
            .retrieval(RetrievalConfig.builder()
                .sources("faq")
                .topK(5)
                .build())
            .chatKnowledge("faq")
            .principal(principal)
            .liabilitySink(liabilitySink)
            .authorityScope(authorityScope)
            .build();
    }
}
```

`chatKnowledge(sourceName)` (`@since 0.15.0`) grounds chat in one named
builder source. Three calls do the work together: `addKnowledgeSource`
declares the source, `retrieval` supplies the retrieval behaviour and
top-K, and `chatKnowledge` names which declared source grounds
conversation.

The chat binding receives a **source-scoped copy** of that retrieval
configuration. The action-level source list and retrieval configuration
are left unchanged, so one agent can retrieve across several sources for
its actions while chat stays bound to exactly one.

Naming the source is required rather than a convenience: action
retrieval may list several sources, and the builder never guesses which
of them should ground chat. `build()` rejects the combinations that
would otherwise leave chat quietly ungrounded:

| Condition | `IllegalStateException` |
| --- | --- |
| combined with `knowledgeBase(...)` | `chatKnowledge cannot be combined with legacy knowledgeBase` |
| no `retrieval(...)` supplied | `chatKnowledge requires a retrieval configuration` |
| name matches no declared source | `chatKnowledge references unknown builder source` |
| name declared more than once | `must be declared exactly once` |
| source declared but disabled | `must be enabled` |
| retrieval `topK` not positive | `chatKnowledge retrieval topK must be positive` |
| retrieval `contextWindow` not positive | `chatKnowledge retrieval contextWindow must be positive` |

The first row is the one to know when reading the older builder story
below: the two chat paths are mutually exclusive, not layered. For how
`AgentBuilder.knowledgeBase(KnowledgeBase)` behaved before it was
removed in `0.16.0`, see [Which one to use](#which-one-to-use).

## Three ways to ground chat

What separates them is **what describes the source**, and when it is resolved:

| path | source described by | resolved |
| --- | --- | --- |
| `@ChatKnowledge` + `@KnowledgeSource` | annotations | at init |
| `chatKnowledge(name)` | a declared `KnowledgeSourceConfig` + `retrieval(...)` | at init; needs an installed `RetrievalEngineProvider` |
| `liveChatKnowledge(name)` | nothing — the application supplies the binding | never; chat grounds when the binding arrives |

`KnowledgeSourceConfig` is a **declarative descriptor** — path, format,
connection, query. It cannot wrap a live in-memory object, which is the
whole reason the third row exists.

⚠️ Reaching for row two with a runtime corpus does **not** leave chat
ungrounded — it makes construction throw. `chatKnowledge(name)` demands
a matching declared source, a retrieval configuration and an installed
provider, and `build()` rejects the agent when any is missing. The
answer in that case is row three, not a workaround for row two.

### Application-supplied live sources

`AgentBuilder.liveChatKnowledge(sourceName)` and
`ChatKnowledgeBinding.live(...)` are `@since 0.16.0`. See
[Installation](../../start/installation.md) for the current published
coordinates.

`liveChatKnowledge` declares that chat grounds on a source the
application supplies at runtime. Initialization resolves nothing for it
and builds no retrieval engine, so no `RetrievalEngineProvider` is
required on its account. The agent starts ungrounded and stays that way
until a binding arrives. Action retrieval is untouched: declare
`knowledgeSources(...)` and `retrieval(...)` as usual if actions also
retrieve — that declaration does not feed chat, and this one does not
feed actions.

`ChatKnowledgeBinding.live(ownerId, sourceName, topK, knowledgeBase)`
adapts an application-owned corpus. It **holds** the corpus rather than
copying it, so documents added after binding are visible to retrieval,
and closing the binding does not close the corpus — that stays the
application's to manage, which matters when the same corpus is rebound
on every message. Retrieval is keyword-strategy, fail-open and uncached,
matching what `KnowledgeBase.search` actually does.

🔑 **The name is load-bearing, and both halves must agree.** The binding
must be owned by that agent and carry exactly the declared
`sourceName`, or the agent refuses it — the same check the declarative
path applies, so chat never grounds on a source nobody named. Write the
two halves together:

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

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.accountability.LiabilitySink;
import com.tnsai.agents.Agent;
import com.tnsai.agents.AgentBuilder;
import com.tnsai.identity.AgentPrincipal;
import com.tnsai.knowledge.KnowledgeBase;
import com.tnsai.llm.LLMClient;
import com.tnsai.rag.ChatKnowledgeBinding;
import com.tnsai.roles.Role;

public final class LiveChatKnowledgeExample {
    private LiveChatKnowledgeExample() {}

    // Both halves must name the same source, or the agent refuses the
    // binding. Declaring it once is the cheapest way to keep them equal.
    private static final String SOURCE = "session-corpus";

    public static Agent buildAgent(
        LLMClient llmClient,
        Role supportRole,
        AgentPrincipal principal,
        LiabilitySink liabilitySink,
        AuthorityScope authorityScope
    ) {
        return AgentBuilder.create()
            .llm(llmClient)
            .role(supportRole)
            .liveChatKnowledge(SOURCE)
            .principal(principal)
            .liabilitySink(liabilitySink)
            .authorityScope(authorityScope)
            .build();
    }

    public static void groundChat(
        Agent agent,
        String ownerId,
        KnowledgeBase applicationOwnedCorpus
    ) {
        agent.setChatKnowledgeBinding(
            ChatKnowledgeBinding.live(
                ownerId, SOURCE, 5, applicationOwnedCorpus));
    }
}
```

A refused binding is quiet by design: the agent keeps answering, just
ungrounded. If chat replies without using the corpus you attached, check
that the declared name and the bound name are the same string before
looking anywhere else.

## Which one to use

Use chat-level RAG when every conversational turn should be grounded in the
same knowledge base. It retrieves before the LLM call, fences retrieved
content, and adds the matching disclosure to the system prompt.

Use action-level RAG when retrieval belongs to specialized actions. It runs
during action dispatch and writes bounded context to `_rag_context`.

Explicit builder values win over annotations. A builder source replaces an
annotation source with the same name; other named sources remain available.
`AgentBuilder.knowledgeBase(KnowledgeBase)` and
`knowledgeBaseTopK(int)` are **removed in `0.16.0`**, with
`Agent.setKnowledgeBase` / `getKnowledgeBase`, their orchestrator
equivalents, and `ChatKnowledgeBinding.snapshot`. They remain in Maven
Central `0.15.1`, where they installed a snapshot binding under
`source=knowledge-base` and won over `@ChatKnowledge` at init.

The `KnowledgeBase` type is **not** removed — only the bridge that
attached one to chat. Declare the source and name it with
`chatKnowledge(...)`, or, for a corpus the application owns and keeps
mutating, use `liveChatKnowledge(...)` with `ChatKnowledgeBinding.live(...)`
(`@since 0.16.0`).

## TnsAI 0.14.0 field reference

`live` means the runtime consumes the field, `partial` means only the named
subset is available, and `not implemented` means changing the field has no
runtime effect. Every field carries the `@since` version that introduced
it, and the pages that introduce them repeat it — read the `@since`
rather than assuming the table tracks one release.

### Chat-level fields

| Annotation field | Effect | Status |
|---|---|---|
| `@ChatKnowledge.source` | Selects the named source on the same agent class. | `live` |
| `@ChatKnowledge.topK` | Limits documents retrieved for each chat turn. | `live` |
| `@ChatKnowledge.enabled` | Enables or bypasses chat-level retrieval. | `live` |

### Knowledge-source fields

| `@KnowledgeSource` field | Effect | Status |
|---|---|---|
| `name` | Defines the source key used by retrieval policies. | `live` |
| `type` | Selects a `SourceLoader`; only `FILE` is bundled. | `partial` |
| `format` | Selects AUTO detection or strict per-file parsing for FILE sources. | `live` |
| `path` | Supplies the file/directory path, or a provider-defined URL. | `live` |
| `connection` | Supplies provider-defined database connection data. | `partial` |
| `query` | Supplies a provider-defined database ingestion query. | `partial` |
| `enabled` | Includes or skips the source during binding. | `live` |
| `include` | Includes matching FILE paths before ingestion. | `live` |
| `exclude` | Excludes matching FILE paths before ingestion. | `live` |
| `unit` | Selects `AUTO`, `DOCUMENT`, `LINE`, `HEADING`, or `CHUNK` slicing at ingest. `@since 0.15.0`. | `live` |
| `chunkContext` | Selects `NONE`, `STRUCTURAL`, or `LLM` context prepended for indexing. `LINE` rejects non-`NONE`. `@since 0.15.0`. | `live` |
| `chunkContextModel` | Selects the generator model for `chunkContext = LLM`; ignored for `NONE` and `STRUCTURAL`. `@since 0.15.0`. | `live` |

### Retrieval-policy fields

| `@Retrieval` field | Effect | Status |
|---|---|---|
| `strategy` | Selects semantic, keyword, hybrid, graph, multi-query, hierarchical, temporal, or reasoning retrieval. `REASONING` is `@since 0.14.0` and is in Maven Central `0.14.1`. | `live` |
| `sources` | Selects named sources; empty means all configured sources. | `live` |
| `queryParam` | Selects a named action parameter when compiled with `-parameters`; blank preserves positional inference. | `live` |
| `rerank` | Enables provider-backed reranking. | `live` |
| `rerankerModel` | Selects the installed reranker provider and model. | `live` |
| `contextWindow` | Caps the rendered retrieval context in tokens. | `live` |
| `topK` | Caps total initial results from the selected strategy call. | `live` |
| `topN` | Caps results after reranking. | `live` |
| `minScore` | Drops results below the relevance threshold. | `live` |
| `deduplicate` | Enables post-fusion duplicate removal. | `live` |
| `dedupeThreshold` | Sets the duplicate similarity threshold. | `live` |
| `queryExpansion` | Selects the query-expansion mode. | `live` |
| `queryExpansionModel` | Selects the installed query-expansion provider and model. | `live` |
| `navigatorModel` | Selects the installed `TreeNavigator` when `strategy` is `REASONING`. Required on that path; blank or unmatched identifiers fail before `onFailure`. `@since 0.14.0`. | `live` |
| `expandedQueries` | Caps generated query variants. | `live` |
| `includeSpec` | Controls source/score metadata in rendered context. | `live` |
| `contextFormat` | Defines the validated context template. | `live` |
| `cache` | Enables the bounded per-Role retrieval cache. | `live` |
| `cacheTTL` | Sets cache lifetime in seconds. | `live` |
| `maxStaleness` | Caps stale-cache age for `USE_CACHE`. | `live` |
| `onFailure` | Selects the transport-failure policy. | `live` |

## Pages

- [Knowledge Base](knowledge-base.md) — Create, populate, query a `KnowledgeBase`. `@VectorMemory` fail-loud SPI.
- [File formats](formats.md) — `DocumentFormat` vs `KnowledgeType.FILE`, selectors, ignore/Limits, optional PDF/Office.
- [Embeddings](embeddings.md) Matryoshka prefix helper (`@since 0.14.0`). Hash-fallback warning (`@since 0.15.0`).
- [Diagnostics](diagnostics.md) resolved RAG config (`RagDiagnostics`, `@since 0.15.0`).
- [Ordered consumption](sequential-consumption.md) shared target/source cursor
  (`@Sequential`, `@since 0.15.0`).
- [Agent-level action RAG](agent-knowledge.md) `@AgentSpec.knowledge()` / `retrieval()` (`@since 0.14.0`).
- [Strategies](strategies.md) — Swap retrieval algorithms via the RAG SPI.
- [Hybrid graph stream](hybrid-graph.md) third HYBRID ranking (`@since 0.14.0`).
- [Vectorless reasoning](reasoning.md) — Root-to-leaf `REASONING` descent (`@since 0.14.0`).
- [Smart segmentation](segmentation.md) heading-aware paper splitter (`@since 0.14.0`). Not the FILE chunker.
- [Controlled write-back](write-back.md) package-private overlay (`TnsAI@4b57a851`). Default off. Not a public write API.
- [Selective re-embed](selective-reembed.md) hash-keyed local index (`@since 0.14.0`). Not Qdrant/pgvector.
- [Pipeline](pipeline.md) — Server `FileIndexer` over shared `FileIngestionService`, ignore rules, admission caps, hybrid search.
