Skip to main content
The @ooneex/rag package is a Retrieval-Augmented Generation toolkit. It turns your documents into searchable knowledge: convert PDFs into clean text chunks, embed them with OpenAI models, store them in a LanceDB vector database, and retrieve the most relevant passages with hybrid search. You define a typed vector database class, open a table, add records, and search — every record carries an id, the searchable text, and a metadata object of your own typed fields.

Why this package

  • Hybrid retrieval. Full-text and vector search run together and are merged with RRF reranking, so you get both keyword precision and semantic recall.
  • Typed end to end. Your metadata shape drives the types for records, filters, and selected fields.
  • PDF in, chunks out. The Convertor parses PDFs into heading-aware chunks with page metadata, ready to embed.
  • Local-first storage. LanceDB stores vectors on disk — no separate database server to run.
  • Composable filters. Combine field conditions with AND, OR, and NOT to narrow results.
  • Container-friendly. Register databases with a decorator and resolve them from the DI container.

The building blocks

BlockWhat it isPage
Vector DatabaseA typed class describing where data lives, which embedding model to use, and the schema.Vector Database
Vector TableThe handle you add records to, look them up, and index.Vector Table
ConvertorTurns PDFs into structured, embeddable chunks.Convertor
EmbeddingsThe OpenAI models that turn text into vectors.Embeddings
SearchHybrid full-text + vector search with RRF reranking.Search
FilteringComposable conditions to scope queries.Filtering

Installation

bun add @ooneex/rag
Embeddings are generated with OpenAI, so set your API key in the environment:
OPENAI_API_KEY=sk-...

The record shape

Every row in a table has the same three top-level fields:
{
  id: string;        // your identifier
  text: string;      // the content that gets embedded and full-text indexed
  metadata: { ... }; // your custom, typed fields used for filtering and selection
}
You describe metadata with a DataType and the database carries it through everywhere — added records, search results, filters, and selected columns are all typed against it.

End-to-end example

import { AbstractVectorDatabase } from "@ooneex/rag";
import type {
  EmbeddingModelType,
  EmbeddingProviderType,
  FieldValueType,
} from "@ooneex/rag";
import { Utf8 } from "apache-arrow";

type ArticleData = {
  metadata: {
    title: string;
    category: string;
  };
};

class ArticleVectorDatabase extends AbstractVectorDatabase<ArticleData> {
  public getDatabaseUri = (): string => "./data/articles.lance";

  public getEmbeddingModel = (): {
    provider: EmbeddingProviderType;
    model: EmbeddingModelType["model"];
  } => ({ provider: "openai", model: "text-embedding-3-small" });

  public getSchema = (): { [K in keyof ArticleData]: FieldValueType } => ({
    metadata: new Utf8(),
  });
}

// Connect and open a table (created with indexes on first open).
const db = new ArticleVectorDatabase();
await db.connect();
const table = await db.open("articles");

// Add records.
await table.add([
  {
    id: "1",
    text: "An introduction to retrieval-augmented generation systems.",
    metadata: { title: "RAG Intro", category: "AI" },
  },
  {
    id: "2",
    text: "How vector databases store and search embeddings.",
    metadata: { title: "Vector DBs", category: "Database" },
  },
]);

// Search — hybrid full-text + vector, with an optional filter.
const results = await table.search("retrieval augmented generation", {
  limit: 5,
  select: ["title", "category"],
  filter: { field: "category", op: "=", value: "AI" },
});

How retrieval works

When you call search(), the table:
  1. Runs a vector search over the embedded text and a full-text search over the same column.
  2. Merges both result sets with an RRF reranker (Reciprocal Rank Fusion).
  3. Applies your filter and select, then returns the top limit records, typed against your metadata.
This hybrid approach catches results that pure keyword search misses (semantic matches) and results that pure vector search ranks poorly (exact terms, names, codes).
Tables are created with three indexes on first open() — a btree index on id, a full-text (FTS) index on text, and an IVF-PQ vector index on vector — so search is fast out of the box. See Vector Database for details.