@mastra/pg 1.34.0-alpha.1 → 1.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ name: mastra-pg
3
3
  description: Documentation for @mastra/pg. Use when working with @mastra/pg APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/pg"
6
- version: "1.34.0-alpha.1"
6
+ version: "1.34.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.34.0-alpha.1",
2
+ "version": "1.34.0",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -435,6 +435,8 @@ When using PostgreSQL as your vector store, you can optimize semantic recall per
435
435
 
436
436
  PostgreSQL supports both IVFFlat and HNSW indexes. By default, Mastra creates an IVFFlat index, but HNSW indexes typically provide better performance, especially with OpenAI embeddings which use inner product distance.
437
437
 
438
+ Semantic recall always filters by thread or resource. PgVector uses the vector index for these searches only with HNSW on pgvector 0.8.0 or newer. With IVFFlat, each search compares the query against every message in the thread or resource.
439
+
438
440
  ```typescript
439
441
  import { Memory } from '@mastra/memory'
440
442
  import { PostgresStore, PgVector } from '@mastra/pg'
@@ -251,7 +251,11 @@ const results = await pgVector.query({
251
251
  })
252
252
  ```
253
253
 
254
- Postgres vector indexes (HNSW and IVFFlat) can't restrict that search to rows matching a condition. When a filter is present, pgVector instead compares the query vector against every matching row and returns the closest `topK`. Results are exact, but the work grows with the number of rows the filter matches. Indexing the metadata column speeds up row retrieval. The distance comparisons still happen per row.
254
+ With an HNSW index on pgvector 0.8.0 or newer, PostgreSQL searches the index and skips rows that don't match the filter. When the index search can't fill `topK`, pgVector reruns the query as an exact search.
255
+
256
+ With IVFFlat, or HNSW on older pgvector versions, pgVector compares the query vector against every matching row. Results are exact, but the work grows with the number of matching rows.
257
+
258
+ Indexing the metadata fields you filter on speeds up both cases and gives PostgreSQL the row counts it needs to choose between them.
255
259
 
256
260
  ### Vector Query Tool
257
261
 
package/dist/index.cjs CHANGED
@@ -1103,42 +1103,43 @@ var PgVector = class extends _mastra_core_vector.MastraVector {
1103
1103
  const scoreExpr = ops.scoreExpr(distanceExpr);
1104
1104
  const filterClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
1105
1105
  const hasFilter = filterClause.length > 0;
1106
- const useIndexedOrder = indexInfo.type === "hnsw" && !hasFilter && minScore <= 0;
1107
1106
  const namespaceParam = filterValues.length + 1;
1108
- const query = useIndexedOrder ? `
1107
+ const queryValues = [...filterValues, namespace];
1108
+ const selectColumns = `vector_id as id, ${scoreExpr} as score, metadata${includeVector ? ", embedding" : ""}`;
1109
+ const whereClause = `WHERE namespace = $${namespaceParam}${filterClause ? ` AND (${filterClause})` : ""}`;
1110
+ const useIndexedOrder = indexInfo.type === "hnsw" && (!hasFilter || this.supportsIterativeScan());
1111
+ let rows;
1112
+ if (useIndexedOrder) {
1113
+ const indexedQuery = `
1109
1114
  WITH vector_scores AS (
1110
- SELECT
1111
- vector_id as id,
1112
- ${scoreExpr} as score,
1113
- metadata
1114
- ${includeVector ? ", embedding" : ""}
1115
+ SELECT ${selectColumns}
1115
1116
  FROM ${tableName}
1116
- WHERE namespace = $${namespaceParam}
1117
+ ${whereClause}
1117
1118
  ORDER BY ${distanceExpr}
1118
1119
  LIMIT $2
1119
1120
  )
1120
- SELECT *
1121
+ SELECT *, score > $1 AS above_min_score
1121
1122
  FROM vector_scores
1122
- WHERE score > $1
1123
- ORDER BY score DESC` : `
1123
+ ORDER BY score DESC`;
1124
+ const candidates = (await client.query(indexedQuery, queryValues)).rows;
1125
+ if (candidates.length >= topK) rows = candidates.filter((row) => row.above_min_score);
1126
+ }
1127
+ if (!rows) {
1128
+ const exactQuery = `
1124
1129
  WITH vector_scores AS (
1125
- SELECT
1126
- vector_id as id,
1127
- ${scoreExpr} as score,
1128
- metadata
1129
- ${includeVector ? ", embedding" : ""}
1130
+ SELECT ${selectColumns}
1130
1131
  FROM ${tableName}
1131
- WHERE namespace = $${namespaceParam}
1132
- ${filterClause ? `AND (${filterClause})` : ""}
1132
+ ${whereClause}
1133
1133
  )
1134
1134
  SELECT *
1135
1135
  FROM vector_scores
1136
1136
  WHERE score > $1
1137
1137
  ORDER BY score DESC
1138
1138
  LIMIT $2`;
1139
- const result = await client.query(query, [...filterValues, namespace]);
1139
+ rows = (await client.query(exactQuery, queryValues)).rows;
1140
+ }
1140
1141
  await client.query("COMMIT");
1141
- return result.rows.map(({ id, score, metadata, embedding }) => ({
1142
+ return rows.map(({ id, score, metadata, embedding }) => ({
1142
1143
  id,
1143
1144
  score,
1144
1145
  metadata,
@@ -1405,7 +1406,7 @@ var PgVector = class extends _mastra_core_vector.MastraVector {
1405
1406
  indexConfig,
1406
1407
  vectorType
1407
1408
  }, client);
1408
- if (metadataIndexes?.length) await this.createMetadataIndexes(tableName, indexName, metadataIndexes);
1409
+ if (metadataIndexes?.length) await this.createMetadataIndexes(client, tableName, indexName, metadataIndexes);
1409
1410
  } catch (error) {
1410
1411
  this.createdIndexes.delete(indexName);
1411
1412
  this.namespaceReadyIndexes.delete(indexName);
@@ -1555,13 +1556,13 @@ var PgVector = class extends _mastra_core_vector.MastraVector {
1555
1556
  await client.query(indexSQL);
1556
1557
  });
1557
1558
  }
1558
- async createMetadataIndexes(tableName, indexName, metadataFields) {
1559
+ async createMetadataIndexes(client, tableName, indexName, metadataFields) {
1559
1560
  const hasher = await this.hasher;
1560
1561
  for (const field of metadataFields) {
1561
1562
  const fieldHash = hasher.h32(field).toString(16);
1562
1563
  const metadataIdxName = `"${indexName.slice(0, 55 - fieldHash.length)}_md_${fieldHash}_idx"`;
1563
1564
  const escapedField = field.replace(/'/g, "''");
1564
- await this.pool.query(`
1565
+ await client.query(`
1565
1566
  CREATE INDEX CONCURRENTLY IF NOT EXISTS ${metadataIdxName}
1566
1567
  ON ${tableName} ((metadata->>'${escapedField}'))
1567
1568
  `);