> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runanywhere.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# RAG

> Answer questions over documents the user provides

Open a session with an embedding model, add documents, ask questions.

```ts theme={null}
const session = await RunAnywhere.rag.open({ id: 'all-minilm-l6-v2' }, { id: 'qwen3-0.6b' })

await session.ingest(RagDocument.text(contents))

const answer = await session.query('What is the refund window?')
console.log(answer.text)

await session.close()
```

<Warning>
  **The web index is process-wide.** Opening a second session while one is open throws
  `invalidState` with "A RAG session is already open. Close it before opening another". No other SDK
  has this constraint.
</Warning>

## Opening

```ts theme={null}
rag.open(embeddingModel: ModelRef, llmModel?: ModelRef, config?: RagConfig): Promise<RagSession>
```

Omit `llmModel` for a retrieval-only session: `search` works, `query` does not.

## RagSession

```ts theme={null}
session.ingest(document: RagDocument): Promise<void>
session.ingest(documents: readonly RagDocument[]): Promise<void>
session.search(query: string, topK?: number): Promise<Match[]>
session.query(question: string, options?: RagQueryOptions): Promise<RagResult>
session.queryStream(question: string, options?: RagQueryOptions): AsyncIterable<RagEvent>
session.stats(): Promise<RagStats>
session.clear(): Promise<void>
session.close(): Promise<void>
```

## Ingesting a dropped file

```ts theme={null}
dropZone.addEventListener('drop', async (event) => {
  event.preventDefault()

  for (const file of event.dataTransfer?.files ?? []) {
    const text = await file.text()
    await session.ingest(RagDocument.text(text, { sourceUri: file.name }))
  }
})
```

Ingestion chunks, embeds, and indexes. It is the slow part, so do it once and keep the session
rather than re-ingesting per question. Show progress for anything larger than a page.

## Retrieval without generation

```ts theme={null}
const matches = await session.search('refund policy', 5)
```

Useful for showing sources, or for feeding matches into your own prompt.

## Streaming an answer

```ts theme={null}
for await (const event of session.queryStream('What is the refund window?')) {
  if (event.type === 'token') answer += event.text
  if (event.type === 'sources') renderSources(event.matches)
}
```

Sources usually arrive before the first token, which is what lets you show what the answer is
based on while it is still being written.

## Configuration

```ts theme={null}
const session = await RunAnywhere.rag.open(
  { id: 'all-minilm-l6-v2' },
  { id: 'qwen3-0.6b' },
  { retrievalTopK: 5, chunkSize: 512, chunkOverlap: 64 }
)
```

| Field                 | Meaning                                          |
| --------------------- | ------------------------------------------------ |
| `retrievalTopK`       | Chunks retrieved per question                    |
| `chunkSize`           | Characters per chunk                             |
| `chunkOverlap`        | Overlap between neighbouring chunks              |
| `similarityThreshold` | Drop matches below this score                    |
| `persistPath`         | Where the index lives, for reuse across sessions |

Overlap matters more than it looks. Without it, a sentence split across a chunk boundary is
retrievable from neither half.

## Two models at once

A RAG session holds an embedding model **and** a language model. In a tab that is real memory
pressure, so prefer a small embedding model: `all-minilm-l6-v2` is a fraction of the size of
the language model beside it.

## Closing

```ts theme={null}
await session.close()
```

Close before opening another, and close on `pagehide`. Because the index is process-wide, a
session you forget to close blocks every later one for the lifetime of the page.

```ts theme={null}
window.addEventListener('pagehide', () => {
  void session?.close()
})
```
