Services SDK
Knowledge Base

Knowledge Base

RAG (Retrieval-Augmented Generation), embeddings, and semantic search for document retrieval.

Installation

pnpm add @aicr/knowledge-base

Configuration

Set the OPENAI_API_KEY environment variable for embeddings:

OPENAI_API_KEY=sk-xxxxxxxxxxxx

Quick Start

import { createKnowledgeBase } from '@aicr/knowledge-base';
 
// Create knowledge base
const kb = createKnowledgeBase({
  openaiApiKey: process.env.OPENAI_API_KEY,
  embeddingModel: 'text-embedding-3-small',
  chunkSize: 1000,
  chunkOverlap: 200
});
 
// Ingest documents
await kb.ingest({
  id: 'doc-1',
  content: 'Your document content here...',
  title: 'Product Manual',
  source: 'https://example.com/manual.pdf'
});
 
// Query
const results = await kb.query('How do I reset the device?');
console.log(results[0].content);  // Most relevant chunk
console.log(results[0].score);    // Similarity score (0-1)

API Reference

createKnowledgeBase(config)

Create a knowledge base client.

interface KnowledgeBaseConfig {
  openaiApiKey?: string;
  embeddingModel?: 'text-embedding-3-small' | 'text-embedding-3-large' | 'text-embedding-ada-002';
  chunkSize?: number;      // Default: 1000 characters
  chunkOverlap?: number;   // Default: 200 characters
  vectorStore?: VectorStoreAdapter;  // Custom vector store
  debug?: boolean;
}

kb.ingest(document)

Add a document to the knowledge base.

interface Document {
  id: string;              // Unique document ID
  content: string;         // Document content
  title?: string;          // Document title
  source?: string;         // Source URL or path
  metadata?: Record<string, unknown>;  // Custom metadata
  tenantId?: string;       // For multi-tenant isolation
}
 
interface IngestResult {
  documentId: string;
  chunkCount: number;
  durationMs: number;
  warnings?: string[];
}

Example:

const result = await kb.ingest({
  id: 'policy-2024',
  content: policyDocument,
  title: 'Company Policy 2024',
  source: '/documents/policy-2024.pdf',
  metadata: {
    department: 'HR',
    version: '2.0',
    effectiveDate: '2024-01-01'
  },
  tenantId: 'tenant-123'
});
 
console.log(`Ingested ${result.chunkCount} chunks in ${result.durationMs}ms`);

kb.query(queryText, options)

Search the knowledge base.

interface QueryOptions {
  limit?: number;          // Max results (default: 10)
  minScore?: number;       // Min similarity score (default: 0.5)
  filter?: Record<string, unknown>;  // Metadata filter
  tenantId?: string;       // Tenant isolation
  includeContent?: boolean;  // Include chunk content (default: true)
}
 
interface QueryResult {
  id: string;              // Chunk ID
  documentId: string;      // Parent document ID
  content: string;         // Chunk content
  score: number;           // Similarity score (0-1)
  title?: string;          // Document title
  source?: string;         // Document source
  metadata?: Record<string, unknown>;
}

Example:

const results = await kb.query('compensation policy for remote workers', {
  limit: 5,
  minScore: 0.7,
  filter: { department: 'HR' },
  tenantId: 'tenant-123'
});
 
results.forEach(r => {
  console.log(`[${r.score.toFixed(2)}] ${r.title}`);
  console.log(r.content.substring(0, 200) + '...');
});

kb.deleteDocument(documentId)

Remove a document and its chunks.

await kb.deleteDocument('policy-2024');

kb.getDocument(documentId)

Retrieve a document by ID.

const doc = await kb.getDocument('policy-2024');
if (doc) {
  console.log(doc.title, doc.source);
}

kb.listDocuments(options)

List all documents.

const docs = await kb.listDocuments({
  tenantId: 'tenant-123',
  limit: 20,
  offset: 0
});

kb.embed(text)

Generate an embedding vector for text.

const embedding = await kb.embed('sample text');
console.log(embedding.length);  // 1536 for text-embedding-3-small

Chunking

Documents are automatically split into chunks for optimal retrieval:

// Default chunking
const kb = createKnowledgeBase({
  chunkSize: 1000,     // ~1000 characters per chunk
  chunkOverlap: 200    // 200 character overlap between chunks
});
 
// Custom chunking is automatic:
// - Preserves paragraph boundaries
// - Preserves sentence boundaries
// - Maintains overlap for context

Vector Store Adapters

In-Memory (Default)

Good for development and small datasets:

const kb = createKnowledgeBase({
  openaiApiKey: process.env.OPENAI_API_KEY
});
// Uses built-in in-memory store

Custom Adapter

Implement VectorStoreAdapter for production:

interface VectorStoreAdapter {
  upsert(chunks: Chunk[]): Promise<void>;
  search(embedding: number[], options: QueryOptions): Promise<QueryResult[]>;
  deleteByDocument(documentId: string): Promise<void>;
  deleteByFilter(filter: Record<string, unknown>): Promise<void>;
}
 
// Example: Pinecone adapter
const pineconeStore: VectorStoreAdapter = {
  async upsert(chunks) {
    // Store chunks in Pinecone
  },
  async search(embedding, options) {
    // Query Pinecone
  },
  // ...
};
 
const kb = createKnowledgeBase({
  openaiApiKey: process.env.OPENAI_API_KEY,
  vectorStore: pineconeStore
});

Multi-Tenant Isolation

Use tenantId for data isolation:

// Ingest for specific tenant
await kb.ingest({
  id: 'doc-1',
  content: '...',
  tenantId: 'tenant-A'
});
 
// Query only returns tenant's data
const results = await kb.query('question', {
  tenantId: 'tenant-A'
});

Embedding Models

ModelDimensionsBest For
text-embedding-3-small1536General use, cost-effective
text-embedding-3-large3072Higher accuracy
text-embedding-ada-0021536Legacy compatibility

Error Handling

try {
  const results = await kb.query('question');
} catch (error) {
  if (error.message === 'No embedding API key configured') {
    console.error('OpenAI API key required for embeddings');
  }
}