# KnowledgeBase API

`KnowledgeBase`, in package `com.tnsai.knowledge`, is a programmatic document store that your own code fills and searches. `InMemoryKnowledgeBase` is the only implementation TnsAI ships; you can write your own.

It is not how agents are grounded. Agent chat and actions retrieve through declared `@Knowledge` sources and `ChatKnowledgeBinding` (see [RAG](index.md)), and nothing in the orchestrator calls `KnowledgeBase.search`. Role and action indexes are `VectorMemoryStore` instances, optionally in [remote vector stores](remote-vector-stores.md). `WeaviateTools` and `QdrantTools` in `tnsai-tools` are LLM-callable HTTP helpers, not `KnowledgeBase` backends.

## Methods

| Method | Description |
|--------|-------------|
| `void addDocument(Document document)` | Adds one document. Throws `KnowledgeBaseException` on failure. |
| `default void addDocuments(List<Document> documents)` | Adds several. The default calls `addDocument` for each. |
| `Optional<Document> getDocument(String id)` | The document with that ID, or empty |
| `boolean removeDocument(String id)` | `true` if a document was removed |
| `default void replaceDocuments(Collection<String> removeIds, List<Document> add)` | Removes, then adds, as one generation. The default runs the steps in order. `InMemoryKnowledgeBase` holds its write lock throughout and undoes both steps if an add fails, so readers never see a mix. |
| `List<SearchResult> search(String query, int topK)` | Text search, highest score first |
| `List<SearchResult> search(String query, int topK, Map<String, Object> filters)` | Text search over documents whose metadata matches every filter entry |
| `List<SearchResult> searchByEmbedding(float[] embedding, int topK)` | Similarity search with a vector you computed |
| `int size()`, `default boolean isEmpty()` | Document count |
| `void clear()` | Removes every document |
| `default boolean contains(String id)` | `getDocument(id).isPresent()` |

## Document

`Document` is immutable. It has an ID (a UUID when you don't set one), non-empty content, a metadata map and an optional embedding. Two documents are equal when their IDs are equal.

```java
Document simple = Document.of("This is the document content");
Document tagged = Document.of("Product docs...", "source", "docs/product.md");

Document full = Document.builder()
    .id("doc-001")
    .content("Product documentation...")
    .metadata("source", "docs/product.md")
    .metadata(Map.of("category", "documentation"))
    .embedding(embeddingVector)
    .build();

Document withVector = simple.withEmbedding(embeddingVector); // a new Document
```

`content(null)` throws `NullPointerException`, and `build()` throws `IllegalStateException` when the content is null or empty.

| Accessor | Returns |
|--------|-------------|
| `getId()`, `getContent()` | The ID and the text |
| `getSpec()` | The metadata as an unmodifiable map |
| `getSpec(key)`, `getSpec(key, type)` | One metadata value, or `null` when it is missing or, for the typed form, of another type |
| `hasEmbedding()`, `getEmbedding()`, `getEmbeddingDimension()` | Whether there is an embedding, a copy of it (or `null`), and its length (or `0`) |

## SearchResult

`new SearchResult(document, score)` pairs a document with a score; the document must not be null. Results sort by score, highest first. `getDocument()`, `getScore()`, `getContent()` and `getDocumentId()` read it.

## InMemoryKnowledgeBase

`InMemoryKnowledgeBase` keeps documents in a concurrent map behind a read-write lock:

- `search` scores documents by TF-IDF cosine similarity after removing stop words, and leaves out documents that score 0.
- `searchByEmbedding` scores only documents that have an embedding.
- Filters compare metadata values for equality; every entry must match.

```java
KnowledgeBase kb = new InMemoryKnowledgeBase();

kb.addDocument(Document.of("Java is a programming language"));
kb.addDocument(Document.builder()
    .content("Rust is a systems programming language")
    .metadata("category", "systems")
    .build());

List<SearchResult> results = kb.search("programming language", 5);
List<SearchResult> filtered = kb.search("programming", 5, Map.of("category", "systems"));
List<SearchResult> similar = kb.searchByEmbedding(queryEmbedding, 3);
```

`FileIndexer` in `tnsai-server` uses `replaceDocuments` to swap in a new generation of a file's chunks. See [Pipeline: incremental indexing](pipeline.md#incremental-indexing).

## Related

- [RAG](index.md): declared `@Knowledge` sources, which ground agents.
- [Pipeline](pipeline.md): `RagService` and `FileIndexer`, which build on a `KnowledgeBase`.
- [Embeddings](embedding-prefixes.md): embedding functions for vector search.
- [File formats](file-formats.md): what file ingestion reads.
