# Tutorial: answer questions from your documents (RAG)
Source: https://sovaria.in/docs/tutorial-rag-knowledge-base

Build a help-center assistant: load articles into a knowledge base, search it from your app with citations, add an agent that answers from it, and let each user add private files that only they can search.

A knowledge base splits your documents into sections, embeds them, and keeps them in a table in your project's Postgres with row-level security ([Knowledge bases (RAG)](https://sovaria.in/docs/knowledge-bases)). In this tutorial you load a help center, search it from a web app, and put an agent on top that answers with numbered citations. You need a project and `@sovaria/sdk` ([Quickstart](https://sovaria.in/docs/quickstart)).

## 1. Create the knowledge base

Run this as a team member (a signed-in console session or a server key), for example in a small Node script. `app_users` means signed-in app users can search what your team adds.

setup.ts:
```ts
import { createClient } from '@sovaria/sdk'

// Server-only: the service role key comes from your secret store, never from the app.
const admin = createClient(process.env.SOVARIA_URL!, process.env.SOVARIA_SERVICE_ROLE_KEY!)

await admin.knowledge.create('help_center', { access: 'app_users', description: 'Public help articles' })
```

## 2. Load your articles

load.ts:
```ts
// Web pages: fetched and split by heading.
for (const url of ['https://example.com/help/refunds', 'https://example.com/help/shipping']) {
  await admin.knowledge.addUrl('help_center', url)
}

// Your own text or Markdown, with a stable id and metadata to filter on.
const res = await admin.knowledge.addText('help_center', '# Gift cards\nGift cards can\'t be returned or exchanged…', {
  documentId: 'gift-cards',
  metadata: { area: 'billing' },
})
console.log(res)   // sections, tokens, new / unchanged, still embedding, estimated_cost_usd
```

- Re-adding a document with the same `documentId` replaces it, and only changed sections are embedded again. Run the loader from CI whenever your help center changes.
- Sections are searchable by exact words at once and by meaning within seconds, once they are embedded.

## 3. Search from your app

search.ts:
```ts
// In the app, as the signed-in user.
const { results } = await sovaria.knowledge.search('help_center', 'can I return a gift card?', {
  k: 5,
  mode: 'hybrid',                      // meaning + exact words
  metadataFilter: { area: 'billing' }, // optional
}) as any

for (const r of results) {
  console.log(r.citation, r.title, '›', r.heading)
  console.log(r.content.slice(0, 200))
}
```

Showing the passages directly, with links to the source, is often all a help widget needs, with no model call and no AI cost per search.

## 4. An agent that answers from it

1. **Create the agent** — Open **Agents** › **New agent**. Instructions: "Answer questions about our store from the help center. Cite sources like [1]. If the help center doesn't cover it, say so and offer to contact support." Choose your model provider and key.
2. **Give it the knowledge base** — In **Knowledge**, pick `help_center`. Under **Data access**, choose **No direct SQL**: this agent only needs the documents.
3. **Open it to your users** — **Who can use it**: your app's signed-in users. Set **Runs per user per day** (for example 30) so one person can't spend your model budget.

assistant.ts:
```ts
let threadId: string | undefined

export async function ask(question: string, onText: (t: string) => void) {
  const reply = await sovaria.agents.run(AGENT_ID, {
    message: question,
    threadId,                                         // continue the same conversation
    onEvent: (e: any) => { if (e.type === 'text_delta') onText(e.text) },
  })
  threadId = reply.threadId
  return reply
}
```

The agent searches as the user who asked, so it only quotes passages that user may read. When the help center has no answer, it says it doesn't know instead of guessing.

## 5. Private files per user (optional)

For "chat with my files", create a knowledge base with user uploads. Each file an app user adds is private to them: the table's row rules stop anyone else, the agent included, from retrieving it.

TypeScript:
```ts
// Once, as a team member:
await admin.knowledge.create('my_files', { access: 'user_uploads', description: 'Files users upload' })

// In the app, as the signed-in user (a File from <input type="file">):
await sovaria.knowledge.addFile('my_files', file)
const { results } = await sovaria.knowledge.search('my_files', 'what does my lease say about pets?') as any
```

## 6. Keep the cost down

- You pay to embed each section once, and again only when it changes. Stable `documentId`s keep unchanged sections from being re-embedded.
- Sections of 300 to 600 tokens and a small embedding model with 512 dimensions are cheap and accurate enough for most help centers.
- Search alone uses no model. Only agent runs do, and they count against your Sovariq credits or your own provider key.
