Skip to content
tnsaijava agent framework

pgvector vector index

TnsAI 0.14.0 (TnsAI@816ed1b9, TAN-3096 / PR #141) ships an optional pgvector backend for VectorMemoryStore. The types are @since 0.14.0. They ship in Maven Central 0.14.1.

Role RAG uses pgvector when a JDBC URL is configured and otherwise keeps the in-memory index. No @Retrieval member turns this on. @VectorMemory(provider = "pgvector") is a different path: fail-loud SPI lookup, not this JDBC-URL adapter (TAN-2911). Qdrant and pgvector cannot both be configured in one process — VectorBackends fails startup rather than picking a winner.

Enable

export TNSAI_VECTOR_PGVECTOR_URL=jdbc:postgresql://127.0.0.1:5432/tnsai
# or -Dtnsai.vector.pgvector.url=jdbc:postgresql://127.0.0.1:5432/tnsai

Optional knobs, all system properties only:

PropertyDefaultRole
tnsai.vector.pgvector.useremptyJDBC user
tnsai.vector.pgvector.passwordemptyJDBC password
tnsai.vector.pgvector.tabletnsai_vectorsTable prefix
tnsai.vector.pgvector.indexhnswhnsw or ivfflat / ivf

The PostgreSQL JDBC driver is not a tnsai-core or required tnsai-intelligence dependency. Put org.postgresql:postgresql on the runtime classpath yourself. Production talks SQL through JDK DriverManager (JdbcPgvectorTransport loads org.postgresql.Driver reflectively). A missing driver is an IllegalStateException, not an empty index.

The first upsert runs CREATE EXTENSION IF NOT EXISTS vector and creates the scoped table plus a cosine index (vector_cosine_ops). HNSW is the default; IVFFlat uses lists = 100. A second start is a no-op (IF NOT EXISTS).

Role bindings

RoleRagBinding asks VectorBackends.configured(scope) while building the scoped vector store. That helper opens Qdrant or pgvector — never both. A blank pgvector URL (and no Qdrant URL) returns empty and the binding constructs new VectorMemoryStore(embedding) as before.

The scope is the Role class plus the selected source-name set. The table becomes prefix + _ + the first sixteen hex digits of the SHA-256 of that scope (fingerprint walks eight bytes and writes two hex chars each), so two Roles never share a table. Each index instance also tags every row with an owner derived from the same fingerprint (TAN-5821). A restart therefore still sees the previous corpus. clear() deletes those owner rows and never drops the table.

A putIfAbsent race loser calls abandon() / discard() — it does not clear() the remote store. Upserts under the deterministic owner are idempotent (ON CONFLICT (point_id) DO UPDATE).

Transport faults on size() and search are errors, not an empty index. A missing table is the one quiet case: size is 0 and search is empty.

@KnowledgeSource(name = "handbook", path = "knowledge/handbook")
@Retrieval(strategy = Retrieval.Strategy.SEMANTIC)
public final class HandbookRole extends Role {
}

With the JDBC URL set, that Role's semantic (and hybrid) index lands in Postgres and survives a process restart. Without the URL, the same declaration stays in-process.

Standalone store

Programmatic callers can open the same adapter without a Role:

PgvectorVectorMemoryStore.configured(embedding).ifPresentOrElse(
        store -> store.add(entry),
        () -> new VectorMemoryStore(embedding).add(entry));

Or name the table and index kind explicitly:

PgvectorVectorMemoryStore store = PgvectorVectorMemoryStore.builder()
        .jdbcUrl("jdbc:postgresql://127.0.0.1:5432/tnsai")
        .table("tnsai_vectors")
        .indexKind(PgvectorTransport.IndexKind.HNSW)
        .embeddingFunction(embedding)
        .build();

VectorMemoryStore now accepts a VectorIndex. PgvectorVectorIndex is the shipped JDBC adapter; InMemoryVectorIndex remains the no-URL default. Search ranks with cosine distance (1 - (embedding <=> query)).

Not this page

  • @VectorMemory(provider = "pgvector") — after TAN-2911 (TnsAI@31f3c503) this string is fail-loud unless a VectorStoreProvider named pgvector is registered. Core only ships inmemory. This page's adapter is still the JDBC-URL PgvectorVectorIndex path, not that annotation.
  • The optional Qdrant REST adapter — same VectorIndex seam, different URL (tnsai.vector.qdrant.url). Setting both URLs is a deployment fault.
  • Maven Central 0.14.1 ships VectorIndex and this adapter.
  • A new parent-POM module — TAN-3096 shipped inside tnsai-intelligence.
  • Selective re-embed — local hash-keyed index, not this JDBC adapter.