> ## 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

> Retrieval sessions with grounded answers

`rag.open` returns a session that holds a corpus. You ingest documents into it, then either retrieve
chunks or ask a question that gets answered from them.

## Open a session

```typescript theme={null}
import { RunAnywhere } from '@runanywhere/core'

const session = await RunAnywhere.rag.open({
  embeddingModel: { id: 'all-minilm-l6-v2' },
  llmModel: { id: 'smollm2-360m-q8_0' },
})
```

| Field            | Type         | Notes                                      |
| ---------------- | ------------ | ------------------------------------------ |
| `embeddingModel` | `ModelRef`   | Required. `{ id, voice? }`                 |
| `llmModel`       | `ModelRef?`  | Omit for a retrieval-only session          |
| `config`         | `RagConfig?` | Chunking, retrieval, and persistence knobs |

`open` loads both models, downloading them first when needed, then creates the pipeline. It throws when
a model fails to load.

<Warning>
  The native core keeps one RAG pipeline per process, so React Native allows one open session at a
  time. A second `open()` throws rather than silently replacing the first. Call `close()` before
  opening another.
</Warning>

## RagConfig

| Field                 | Type     | Default | Notes                                 |
| --------------------- | -------- | ------- | ------------------------------------- |
| `topK`                | `number` | `5`     | Chunks retrieved per query            |
| `chunkSize`           | `number` | `512`   |                                       |
| `chunkOverlap`        | `number` | `64`    |                                       |
| `similarityThreshold` | `number` | unset   | Unset keeps every retrieved chunk     |
| `persistPath`         | `string` | unset   | Setting it turns on index persistence |

## Ingest

```typescript theme={null}
await session.ingest({ text: 'RunAnywhere runs models on device.' })

await session.ingest([
  { text: 'The first document.', metadata: { source: 'notes' } },
  { text: 'The second document.', id: 'doc-2' },
])

await session.ingestAll(documents) // same work as ingest on an array
```

`RagDocument` is `{ text?, filePath?, id?, metadata? }`. Ingesting a document with neither text nor a
readable file throws.

<Note>
  `filePath` needs the optional `react-native-fs` dependency, since the SDK core has no JavaScript
  filesystem. Without it, pass the text instead.
</Note>

## Search and query

```typescript theme={null}
// Retrieval only
const matches = await session.search('battery life', 3)
for (const match of matches) {
  console.log(match.score, match.text, match.metadata)
}

// Grounded answer
const result = await session.query('How long does the battery last?')
console.log(result.answer)
console.log(result.sources.map((s) => s.score))
```

`RagResult` extends `GenerationResult`, so it carries `answer`, `sources`, and the same metrics block as
any generation: `inputTokens`, `outputTokens`, `timeToFirstTokenMs`, `tokensPerSecond`, `requestId`,
`model`.

`query` takes optional `LlmOptions`, so reasoning and sampling work the same as on `llm.generate`.

```typescript theme={null}
const result = await session.query(question, { reasoning: { mode: 'off' } })
```

## Streaming a query

```typescript theme={null}
const iterator = session.queryStream(question)[Symbol.asyncIterator]()
let answer = ''
try {
  let step = await iterator.next()
  while (!step.done) {
    const event = step.value
    if (event.type === 'retrieved') setSources(event.matches)
    if (event.type === 'token') {
      answer += event.text
      setAnswer(answer)
    }
    if (event.type === 'completed') console.log(event.result.tokensPerSecond)
    step = await iterator.next()
  }
} finally {
  await iterator.return?.()
}
```

| Event                            | When                                  |
| -------------------------------- | ------------------------------------- |
| `{ type: 'retrieved', matches }` | Retrieval finished, before generation |
| `{ type: 'token', text, kind }`  | One answer token                      |
| `{ type: 'completed', result }`  | Terminal, carrying a full `RagResult` |

Hermes cannot iterate this with `for await...of`. Use the manual loop above.

## Stats, clear, close

```typescript theme={null}
const stats = await session.stats()
console.log(stats.documentCount, stats.chunkCount, stats.indexSizeBytes)

await session.clear() // drops every chunk, session stays open
await session.close() // releases the session and its index
```

Every verb throws once the session is closed, except `close()` itself, which is idempotent.

## One document at a time

Because only one session can be open, a document viewer that queries one file at a time should close the
previous session first. Otherwise the second `open()` throws.

```typescript theme={null}
import type { RagSession } from '@runanywhere/core'

const sessionRef = useRef<RagSession | null>(null)

const loadDocument = async (text: string) => {
  await sessionRef.current?.close()
  sessionRef.current = null

  const session = await RunAnywhere.rag.open({
    embeddingModel: { id: embeddingId },
    llmModel: { id: llmId },
  })
  sessionRef.current = session
  await session.ingest({ text })
}
```

## Related

<CardGroup cols={2}>
  <Card title="Other capabilities" icon="layer-group" href="/react-native/capabilities">
    Embeddings and rerank on their own
  </Card>

  <Card title="Voice sessions" icon="robot" href="/react-native/voice-agent">
    The other session type
  </Card>

  <Card title="Generate" icon="brain" href="/react-native/llm/generate">
    Sampling and reasoning options
  </Card>

  <Card title="Error handling" icon="triangle-exclamation" href="/react-native/error-handling">
    SDKException reference
  </Card>
</CardGroup>
