Skip to content

Retrieval Augmented Generation

The knowledge layer lets you ingest documents, embed them into a vector store, and attach them to an agent so answers are grounded in your content rather than model guesswork.

ts
import {
  KnowledgeEngine,
  createKnowledgeEngine,
  InMemoryVectorStore,   // built-in, good for <10 000 docs
  loadPdf, loadCsv, loadUrl,
} from 'personaforge';

Quick start

ts
import { createAgent } from 'personaforge';
import { createKnowledgeEngine, loadUrl } from 'personaforge';
import { OpenAIEmbeddingProvider } from 'personaforge';

// 1. Build the engine — `embed` is an EmbeddingFn: (text) => Promise<number[]>
const embedder = new OpenAIEmbeddingProvider({ apiKey: process.env.OPENAI_API_KEY! });
const kb = createKnowledgeEngine({
  embed: (text) => embedder.embed(text),
  // default: InMemoryVectorStore (cosine similarity)
});

// 2. Ingest documents
const docs = await loadUrl('https://docs.example.com/api-reference', { recursive: true, maxPages: 20 });
await kb.addDocuments(docs);

// 3. Attach to agent
const agent = createAgent({
  name: 'docs-assistant',
  instructions: 'Answer questions about our product using the provided documentation.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  knowledgebase: kb,
  addKnowledgeToContext: true,   // automatically prepends retrieved chunks to system prompt (default: true when a knowledgebase is set)
});

const result = await agent.run('How do I authenticate API requests?');
console.log(result.text);

Document loaders

Load from URL

ts
import { loadUrl } from 'personaforge';

const docs = await loadUrl('https://example.com/docs', {
  recursive: true,
  maxPages: 50,
  selector: 'main',  // CSS selector to extract content from
});

Load PDF

ts
import { loadPdf } from 'personaforge';

const docs = await loadPdf('./data/handbook.pdf', {
  splitByPage: true,  // one Document per page
  metadata: { source: 'handbook', version: '2.1' },
});

Load CSV

ts
import { loadCsv } from 'personaforge';

const docs = await loadCsv('./data/products.csv', {
  contentColumn: 'description',   // column to use as document content
  metadataColumns: ['sku', 'category', 'price'],
});

Manual documents

ts
import type { Document } from 'personaforge';

const docs: Document[] = [
  {
    id: crypto.randomUUID(),
    content: 'The refund policy allows returns within 30 days of purchase.',
    metadata: { source: 'policy', section: 'refunds' },
  },
];
await kb.addDocuments(docs);

Vector store backends

InMemoryVectorStore (default)

Good for development and up to ~10 000 documents. Data is lost on process restart.

ts
const embedder = new OpenAIEmbeddingProvider({ apiKey: '...' });
const kb = createKnowledgeEngine({
  embed: (text) => embedder.embed(text),
  // InMemoryVectorStore is the default; no extra config needed
});

PgvectorKnowledgeAdapter

Production-ready vector search backed by PostgreSQL + pgvector:

ts
import { PgvectorKnowledgeAdapter, createKnowledgeEngine } from 'personaforge';

const adapter = new PgvectorKnowledgeAdapter({
  connectionString: process.env.DATABASE_URL!,
  tableName: 'knowledge_embeddings',
  dimensions: 1536,  // match your embedding model
});

const kb = createKnowledgeEngine({ embed: myEmbed, store: adapter });

ChromaKnowledgeAdapter

ts
import { ChromaKnowledgeAdapter } from 'personaforge';

const adapter = new ChromaKnowledgeAdapter({
  url: 'http://localhost:8000',
  collectionName: 'my-docs',
  embed: myEmbed,   // EmbeddingFn used to embed docs and queries
});

Neo4jKnowledgeAdapter — graph RAG

ts
import { Neo4jKnowledgeAdapter } from 'personaforge';

const adapter = new Neo4jKnowledgeAdapter({
  uri: process.env.NEO4J_URI!,
  username: process.env.NEO4J_USER!,
  password: process.env.NEO4J_PASSWORD!,
  database: 'docs',
});

DbKnowledgeEngine — zero infra (SQLite-backed)

ts
import { createDbKnowledgeEngine } from 'personaforge';
import { SqliteAgentDb } from 'personaforge/db';

const db = new SqliteAgentDb({ path: './agent.db' });
const kb = createDbKnowledgeEngine({ db, embed: myEmbed });

Retrieval options

ts
// When the engine is attached to an agent via `knowledgebase`, retrieval runs
// automatically before each run. To build the retrieved context manually,
// call buildContext(query, topK?) — it returns the top-k chunks joined into a
// single string, ready to inject into a prompt.
const context = await kb.buildContext('How do I reset my password?', 5);
console.log(context);

Embedding providers

ts
import { OpenAIEmbeddingProvider } from 'personaforge';

const openaiEmbed = new OpenAIEmbeddingProvider({ apiKey: '...', model: 'text-embedding-3-small' });

Custom embedding function

Any async (text: string) => number[] works:

ts
import type { EmbeddingFn } from 'personaforge';

const myEmbed: EmbeddingFn = async (text) => {
  const res = await fetch('https://my-embed-service/embed', {
    method: 'POST', body: JSON.stringify({ text }),
    headers: { 'Content-Type': 'application/json' },
  });
  const { embedding } = await res.json();
  return embedding;
};

Embedding cache

Avoid re-embedding identical text within a process. withEmbeddingCache wraps an EmbeddingFn with an in-process LRU cache — it keeps up to maxSize most-recent embeddings in memory (no external store, no TTL). The cache is cleared on restart.

ts
import { withEmbeddingCache } from 'personaforge';

// Second arg is the max number of cached entries (default: 500).
const cachedEmbed = withEmbeddingCache(myEmbeddingFn, 500);

Where to go next

  • Memory — retain facts across conversations.
  • Eval — measure RAG quality with RAG_CRITERIA.
  • Example 05: RAG — full ingestion-to-answer example.

Released under the MIT License.