npm package: @pvbgs/rag-sdk
Description: Config-driven, stateless Node.js + TypeScript SDK for building RAG backends with your own infra.
Latest version: 0.1.5

## @pvbgs/rag-sdk

Config-driven, stateless Node.js + TypeScript SDK for building Retrieval-Augmented Generation (RAG) backends on your own infrastructure. No hosted backend or data retention. Plug your vector DB and AI provider, get a unified API.

### Features
- Config-driven adapters (vector DB + AI provider)
- Stateless, pluggable architecture
- Collections, documents, retrieval, chat
- Streaming via AsyncIterable
- Chunking, event hooks, lightweight metrics
- Mocks for testing

### Install
```bash
npm install @pvbgs/rag-sdk
```

### Supported Adapters
- Vector DB: `pinecone`, `chroma`, `mock`
- AI Provider: `openai`, `gemini`, `mock`

---

### Example: Consumer client using this SDK

For a runnable example that demonstrates how to wire this SDK with OpenAI and Pinecone, see the companion repository:

- Example repo: [vijay-veltris/rag-consumer-client](https://github.com/vijay-veltris/rag-consumer-client)

Quick start with the example:
1) Clone the example repo and install deps:
```bash
git clone https://github.com/vijay-veltris/rag-consumer-client
cd rag-consumer-client
npm install
```
2) Copy env and fill in your API keys:
```bash
cp .env.example .env
# Edit .env and set OPENAI_* and PINECONE_* variables
```
3) Run:
```bash
npm run dev
```

The example shows:
- How to configure `RAGClient` for OpenAI + Pinecone
- How to embed documents and chat with retrieval
- Recommended environment variables and defaults

---

### Configuration

```ts
import { RAGClient } from '@pvbgs/rag-sdk'

const client = new RAGClient({
  vectorDb: {
    // 'mock' | 'pinecone' | 'chroma'
    type: 'pinecone',
    // Pinecone specific (example)
    apiKey: process.env.PINECONE_API_KEY!,
    indexName: process.env.PINECONE_INDEX!,
    environment: process.env.PINECONE_ENV!,
    namespace: process.env.PINECONE_NAMESPACE ?? 'default',
    dimension: Number(process.env.PINECONE_DIMENSION ?? 1536),
    metric: (process.env.PINECONE_METRIC as 'cosine' | 'dotproduct' | 'euclidean') ?? 'cosine',
    host: process.env.PINECONE_HOST, // recommended for serverless/Control Plane v2
  },
  aiProvider: {
    // 'openai' | 'gemini' | 'mock'
    type: 'openai',
    apiKey: process.env.OPENAI_API_KEY!,
    model: process.env.OPENAI_MODEL ?? 'gpt-4o-mini',
    embedModel: process.env.OPENAI_EMBED_MODEL ?? 'text-embedding-3-small',
    temperature: Number(process.env.OPENAI_TEMPERATURE ?? 0.2),
    maxTokens: Number(process.env.OPENAI_MAX_TOKENS ?? 512),
    stream: process.env.OPENAI_STREAM === 'true',
  },
  options: {
    defaultCollection: 'docs',
    chunking: {
      maxTokens: 256,
      overlap: 20,
    },
    eventHooks: {
      onDocumentEmbedded: ({ sourceId, collectionId }) => {
        console.log('[hook] onDocumentEmbedded', { sourceId, collectionId })
      },
      onChatRequested: ({ query, collectionId }) => {
        console.log('[hook] onChatRequested', { query, collectionId })
      },
      onRetrievalComplete: ({ retrievedCount }) => {
        console.log('[hook] onRetrievalComplete', { retrievedCount })
      },
    },
  },
})
```

Config types:
- VectorDbConfig:
  - `type`: 'pinecone' | 'chroma' | 'mock'
  - Common: `namespace?`, `dimension?`, `metric?`, `host?`, `apiKey?`, `indexName?`, `environment?`
  - Extra provider-specific keys are allowed
- AIProviderConfig:
  - `type`: 'openai' | 'gemini' | 'mock'
  - Common: `apiKey?`, `model?`, `embedModel?`, `temperature?`, `maxTokens?`, `stream?`
  - Extra provider-specific keys are allowed
- ClientOptions:
  - `defaultCollection?` ('default' if omitted)
  - `chunking?` { `maxTokens?`, `overlap?` }
  - `eventHooks?` { `onDocumentEmbedded?`, `onChatRequested?`, `onRetrievalComplete?` }

---

### Top-level API

The `RAGClient` exposes managers and convenience methods:

```ts
import { RAGClient } from '@pvbgs/rag-sdk'

const client = new RAGClient({ /* config as above */ })

// Collections (convenience pass-throughs)
await client.listCollections()
await client.createCollection({ name: 'docs' })
client.selectCollection('docs')
await client.deleteCollection('old')
await client.renameCollection('docs', 'documents')

// System prompt helpers
await client.updateSystemPrompt('You are helpful.\n=== CONTEXT START ===\n{{CONTEXT}}\n=== CONTEXT END ===', 'documents')
const prompt = await client.getSystemPrompt('documents')

// Managers
client.collectionManager
client.documentManager
client.chatManager
```

---

### Collections

```ts
// List
const collections = await client.collectionManager.listCollections()

// Create & auto-select
const created = await client.collectionManager.createCollection({ name: 'docs' })

// Switch active collection
client.collectionManager.selectCollection(created.id)

// Rename
await client.collectionManager.renameCollection(created.id, 'documents')

// Delete
await client.collectionManager.deleteCollection(created.id)
```

---

### Documents

```ts
// Embed a document (chunks + embeddings handled automatically)
const { sourceId } = await client.documentManager.embedDocument({
  collectionId: 'documents',               // optional; defaults to options.defaultCollection
  content: 'Hello world. This is a test document.',
  metadata: { filename: 'hello.txt' },
})

// List sources within a collection
const sources = await client.documentManager.listSources('documents')

// Get a specific source
const source = await client.documentManager.getSource(sourceId)

// Update a source (re-embeds under the hood)
await client.documentManager.updateSource(sourceId, 'Updated content', { filename: 'hello-v2.txt' })

// Delete a source
await client.documentManager.deleteSource(sourceId)
```

---

### Chat

Chat with a collection (RAG retrieval + generation). Returns either a `ChatResponse` or an `AsyncIterable<string>` when `stream: true`.

```ts
// Non-streaming
const result = await client.chatManager.chatWithCollection({
  collectionId: 'documents',          // optional; defaults to options.defaultCollection
  query: 'Summarize the document.',
  stream: false,
})
console.log(result.text)
console.log(result.chatId)

// Streaming
const stream = (await client.chatManager.chatWithCollection({
  query: 'Explain the content.',
  stream: true,
})) as AsyncIterable<string>
for await (const chunk of stream) {
  process.stdout.write(chunk)
}
```

Source usage and annotations:
```ts
const response = await client.chatManager.chatWithCollection({ query: 'What is this about?' })
const sources = await client.chatManager.getSourcesUsed(response.chatId)
const annotations = await client.chatManager.getAnnotations(response.chatId)
```

System prompt resolution:
- If a stored prompt contains `{{CONTEXT}}`, it is replaced with retrieved chunks.
- If stored prompt does not contain `{{CONTEXT}}`, the SDK appends a context block.
- If no stored prompt, a strict default is generated.

---

### Events

Provide hooks to observe lifecycle events:
- `onDocumentEmbedded({ sourceId, collectionId })`
- `onChatRequested({ query, collectionId? })`
- `onRetrievalComplete({ query, retrievedCount })`

Configure under `options.eventHooks` in the client config.

---

### License
MIT