Skip to content

Repository files navigation

langchainjs-postgres

npm version CI License: MIT

An unofficial LangChain.js port of langchain-postgres — implementations of core LangChain abstractions using Postgres / pgvector, hand-translated from Python to TypeScript.

Note

Not affiliated with or endorsed by LangChain. If you want the official, first-party Postgres integration for LangChain.js, see @langchain/community.

The package is released under the MIT license. Feel free to use the abstractions as provided, or modify / extend them as appropriate for your own application.

Requirements

  • Node.js >= 20
  • Postgres with the pgvector extension available (CREATE EXTENSION IF NOT EXISTS vector is run automatically by initVectorstoreTable)
  • pg (node-postgres) as the driver

Installation

You can use npm, pnpm, or yarn to install langchainjs-postgres:

npm install @yukiharada1228/langchain-postgres @langchain/core pg

pnpm add @yukiharada1228/langchain-postgres @langchain/core pg

yarn add @yukiharada1228/langchain-postgres @langchain/core pg

Vectorstore

Warning

PGVector is a straight port of upstream's deprecated collection/embedding-table schema. Prefer PGVectorStore below for new projects — it's faster and easier to manage. See Legacy PGVector for migrating an existing schema.

Documentation

  • Example — minimal quickstart below
  • Integration tests — runnable examples against a real Postgres + pgvector instance

Tip

For developing, debugging, and deploying AI agents and LLM applications, see LangSmith.

Example

import { PGEngine, PGVectorStore } from "@yukiharada1228/langchain-postgres";
import { OpenAIEmbeddings } from "@langchain/openai";
import { Document } from "@langchain/core/documents";

const engine = PGEngine.fromConnectionString(process.env.DATABASE_URL!);

await engine.initVectorstoreTable("documents", 1536, {
  metadataColumns: [{ name: "category", dataType: "TEXT" }],
});

const vectorStore = await PGVectorStore.initialize(
  engine,
  new OpenAIEmbeddings(),
  "documents",
  {
    metadataColumns: ["category"],
  },
);

await vectorStore.addDocuments([
  new Document({
    pageContent: "pgvector stores embeddings in Postgres.",
    metadata: { category: "docs" },
  }),
]);

const results = await vectorStore.similaritySearch(
  "How are embeddings stored?",
  4,
  {
    category: "docs",
  },
);

await engine.close();

Tip

Since JavaScript has no sync/async split, the Python package's separate PGVectorStore / AsyncPGVectorStore classes are collapsed into a single, always-async PGVectorStore — every method here works the way any other LangChain.js vector store does (similaritySearch, addDocuments, delete, ...), no a-prefixed method names.

Metadata filters

similaritySearch, similaritySearchWithScore, delete, and get all accept a Mongo-style filter object:

await vectorStore.similaritySearch("query", 4, {
  $and: [{ category: "docs" }, { "author.age": { $gt: 30 } }],
});

Supported operators: $eq, $ne, $lt, $lte, $gt, $gte, $in, $nin, $between, $exists, $like, $ilike, $and, $or, $not.

Hybrid Search with PGVectorStore

With PGVectorStore you can use hybrid (dense + sparse/full-text) search for more comprehensive and relevant search results.

import {
  HybridSearchConfig,
  reciprocalRankFusion,
} from "@yukiharada1228/langchain-postgres";

const vectorStore = await PGVectorStore.initialize(
  engine,
  embeddings,
  "documents",
  {
    hybridSearchConfig: new HybridSearchConfig({
      fusionFunction: reciprocalRankFusion,
    }),
  },
);

await vectorStore.applyHybridSearchIndex();
const hybridDocs = await vectorStore.similaritySearch("products", 5);

Vector indexes

import { HNSWIndex } from "@yukiharada1228/langchain-postgres";

await vectorStore.applyVectorIndex(
  new HNSWIndex({ m: 16, efConstruction: 64 }),
);

Database-side embeddings

Embedding providers can implement embedQueryInlineTemplate(placeholder) to compute embeddings inside Postgres. It returns a trusted SQL expression using the supplied $n placeholder; document and query text are passed separately as native pg parameters. The query embedding expression is evaluated at most once per similarity search and reused for ranking and scoring. The SQL embedding function must already be available in your database. For example:

import type { PGVectorStoreEmbeddings } from "@yukiharada1228/langchain-postgres";

const dbEmbeddings: PGVectorStoreEmbeddings = {
  embedDocuments: (texts) => embeddings.embedDocuments(texts),
  embedQuery: (text) => embeddings.embedQuery(text),
  embedQueryInlineTemplate: (placeholder) =>
    `embedding('model_id', ${placeholder})::vector`,
};

const store = await PGVectorStore.initialize(engine, dbEmbeddings, "documents");

addDocuments, addTexts, and text similarity searches use the inline hook instead of calling the provider's client embedding methods. Explicit, non-empty vectors are used as provided. Maximal marginal relevance search still uses embedQuery for its client-side reranking step, matching upstream.

The legacy embedQueryInline(text) hook is also supported; its provider is responsible for escaping the text in the returned SQL. When both hooks exist, embedQueryInlineTemplate takes precedence. These camelCase names correspond to upstream's embed_query_inline_template and embed_query_inline methods.

Legacy PGVector

For parity with the original langchain_pg_collection / langchain_pg_embedding schema:

import { PGVector } from "@yukiharada1228/langchain-postgres";

const store = await PGVector.initialize(engine, embeddings, {
  collectionName: "my-collection",
});

Use migratePgvectorCollection(engine, "my-collection", newStore) to move data from a legacy collection into a PGVectorStore table.

ChatMessageHistory

The chat message history abstraction helps persist chat message history in a Postgres table.

PostgresChatMessageHistory is parameterized using a tableName and a sessionId.

The tableName is the name of the table in the database where the chat messages will be stored.

The sessionId is a unique identifier for the chat session. It can be assigned by the caller using crypto.randomUUID().

import { Pool } from "pg";
import { PostgresChatMessageHistory } from "@yukiharada1228/langchain-postgres";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

// Create the table schema (only needs to be done once)
await PostgresChatMessageHistory.createTables(pool, "chat_history");

const history = new PostgresChatMessageHistory({
  tableName: "chat_history",
  sessionId: crypto.randomUUID(),
  pool,
});

await history.addUserMessage("Hello!");
console.log(await history.getMessages());

What's included

Export Ported from (Python) Purpose
PGEngine langchain_postgres.v2.engine.PGEngine Connection pool + table setup (initVectorstoreTable)
PGVectorStore langchain_postgres.v2.async_vectorstore.AsyncPGVectorStore Modern, per-table vector store (metadata filters, hybrid search, indexes)
PostgresChatMessageHistory langchain_postgres.chat_message_histories.PostgresChatMessageHistory Chat history backed by a simple (session_id, message) table
PGVector langchain_postgres.vectorstores.PGVector Legacy collection/embedding-table vector store
PGVectorTranslator langchain_postgres.translator.PGVectorTranslator Self-query retriever filter translator
HNSWIndex, IVFFlatIndex, ExactNearestNeighbor, ... langchain_postgres.v2.indexes Vector index management
HybridSearchConfig, weightedSumRanking, reciprocalRankFusion langchain_postgres.v2.hybrid_search_config Dense + sparse (full-text) hybrid search
migratePgvectorCollection, listPgvectorCollectionNames langchain_postgres.utils.pgvector_migrator Migrate data from the legacy PGVector schema to PGVectorStore

Known gaps vs. upstream

  • The self-query translator only supports the comparators @langchain/core's structured-query IR defines (eq/ne/lt/gt/lte/gte); Python's IR additionally has in/nin/contain/like.
  • migratePgvectorCollection inserts batch-by-batch sequentially instead of with bounded concurrency.

These (and any newly-introduced upstream behavior) are tracked via the upstream-sync workflow described below.

Development

npm install
npm run build       # tsup -> dist/
npm test            # vitest (unit tests against a mocked pg.Pool, no DB required)
npm run typecheck
npm run lint

Integration tests

tests/integration/ runs the same code paths against a real Postgres + pgvector instance (no mocking). It requires DATABASE_URL and is not part of npm test:

docker run --rm -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres pgvector/pgvector:pg17
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres npm run test:integration

CI runs this automatically against a pgvector/pgvector:pg17 service container.

Staying in sync with upstream

This package tracks langchain-ai/langchain-postgres (Python) as a git submodule at upstream/langchain-postgres, pinned to the commit this port was last synced against. Since porting Python to TypeScript can't be automated, a daily GitHub Actions workflow compares the pinned commit against upstream's latest commit and, when langchain_postgres/ has changed, opens (or refreshes) a tracking issue labeled upstream summarizing what moved. You can also run the check manually:

git submodule update --init --recursive
npm run diff:upstream

About

LangChain.js abstractions backed by Postgres Backend

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages