@mastra/libsql 1.21.1-alpha.0 → 1.21.1-alpha.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @mastra/libsql
2
2
 
3
+ ## 1.21.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Added HTTP endpoints for caller-driven experiments: `POST /datasets/:datasetId/experiments` now accepts `start: false` to create an experiment without spawning the runner (with an optional target and run-level `scorerIds`), and new routes `POST /datasets/:datasetId/experiments/:experimentId/items/:itemId/run`, `POST /datasets/:datasetId/experiments/:experimentId/results`, and `POST /datasets/:datasetId/experiments/:experimentId/finalize` let external orchestrators run one item server-side, submit externally computed per-item results (idempotent upsert on retries), and finalize the run with server-computed counts. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
8
+
9
+ - Added `createDatasetExperiment()`, `runExperimentItem()`, `submitExperimentResult()`, and `finalizeExperiment()` methods so a caller-owned orchestrator (for example Temporal) can drive an experiment loop while Mastra either executes each item server-side or ingests externally computed results. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
10
+
11
+ - Fixed versioned dataset item lookups to return the item visible in the requested dataset snapshot. ([#21979](https://github.com/mastra-ai/mastra/pull/21979))
12
+
13
+ - Added `upsertExperimentResult()` to the experiments storage domain plus an `attempt` column on experiment results and a nullable target with an optional `scorerIds` column on experiments, enabling retry-safe result writes for caller-driven experiments (retried submissions with the same `(experimentId, itemId, attempt)` key converge on a single row). `saveScore()` now accepts an optional caller-supplied `id` and upserts on it, so retried experiment submissions replace their previous score rows (latest wins) instead of accumulating duplicates. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
14
+
15
+ - Added caller-driven experiments so an external orchestrator (for example Temporal workers) can own the experiment loop while Mastra stays the system of record. ([#21888](https://github.com/mastra-ai/mastra/pull/21888))
16
+
17
+ Create an experiment with `dataset.createExperiment()` (idempotent when you pass your own id). With a target, Mastra runs each item for you: call `dataset.runExperimentItem()` per item and Mastra executes the registered agent or workflow, resolves scorers (experiment `scorers`, falling back to item `scorerIds`, then dataset `scorerIds`), and upserts the result. Without a target, run everything yourself and report per-item results with `dataset.submitExperimentResult()` (upsert semantics on `(experimentId, itemId, attempt)` so retried workers converge on a single row). Either way, close the run with `dataset.finalizeExperiment()` and Mastra computes per-item succeeded/failed/skipped counts from the persisted rows. Results go into the same storage as native runs, so Studio views, comparisons, and review summaries work unchanged.
18
+
19
+ ```typescript
20
+ // Caller drives the loop, Mastra runs each item
21
+ const { experimentId } = await dataset.createExperiment({
22
+ id: workflowRunId,
23
+ targetType: 'agent',
24
+ targetId: 'support-agent',
25
+ scorers: ['accuracy'],
26
+ });
27
+
28
+ await dataset.runExperimentItem({ experimentId, itemId });
29
+
30
+ // Or: caller runs everything, Mastra ingests results
31
+ const ingest = await dataset.createExperiment({ id: workflowRunId });
32
+ await dataset.submitExperimentResult({
33
+ experimentId: ingest.experimentId,
34
+ itemId,
35
+ output,
36
+ scores: [{ scorerId: 'accuracy', score: 0.92 }],
37
+ });
38
+
39
+ const experiment = await dataset.finalizeExperiment({ experimentId });
40
+ ```
41
+
42
+ - Updated dependencies [[`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`acc3471`](https://github.com/mastra-ai/mastra/commit/acc3471de5f3fde8027ee4e355af292b2bc1bc30), [`b6a771e`](https://github.com/mastra-ai/mastra/commit/b6a771ef23d203ddb348efca8065eff65def8191), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`26d4016`](https://github.com/mastra-ai/mastra/commit/26d40160ff7f7d8bf95fee2039a52cbc83863533), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`9267e9b`](https://github.com/mastra-ai/mastra/commit/9267e9b3d9c2fcf16936050495a787054c2431ab), [`57c5103`](https://github.com/mastra-ai/mastra/commit/57c51035a2a36e3df3c4f32f46bb789a66ed5946)]:
43
+ - @mastra/core@1.61.0-alpha.3
44
+
3
45
  ## 1.21.1-alpha.0
4
46
 
5
47
  ### Patch Changes
@@ -3,7 +3,7 @@ name: mastra-libsql
3
3
  description: Documentation for @mastra/libsql. Use when working with @mastra/libsql APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/libsql"
6
- version: "1.21.1-alpha.0"
6
+ version: "1.21.1-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.21.1-alpha.0",
2
+ "version": "1.21.1-alpha.1",
3
3
  "package": "@mastra/libsql",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -77,9 +77,19 @@ For detailed information about available operators and syntax, see the [Metadata
77
77
 
78
78
  Basic filtering examples:
79
79
 
80
+ **MongoDB**:
81
+
80
82
  ```ts
83
+ import { MongoDBVector } from '@mastra/mongodb'
84
+
85
+ const mongoVector = new MongoDBVector({
86
+ id: 'mongodb-vector',
87
+ uri: process.env.MONGODB_URI,
88
+ dbName: process.env.MONGODB_DB_NAME,
89
+ })
90
+
81
91
  // Simple equality filter
82
- const results = await pgVector.query({
92
+ const equalityResults = await mongoVector.query({
83
93
  indexName: 'embeddings',
84
94
  queryVector: embedding,
85
95
  topK: 10,
@@ -89,7 +99,7 @@ const results = await pgVector.query({
89
99
  })
90
100
 
91
101
  // Numeric comparison
92
- const results = await pgVector.query({
102
+ const priceResults = await mongoVector.query({
93
103
  indexName: 'embeddings',
94
104
  queryVector: embedding,
95
105
  topK: 10,
@@ -99,7 +109,7 @@ const results = await pgVector.query({
99
109
  })
100
110
 
101
111
  // Multiple conditions
102
- const results = await pgVector.query({
112
+ const compoundResults = await mongoVector.query({
103
113
  indexName: 'embeddings',
104
114
  queryVector: embedding,
105
115
  topK: 10,
@@ -111,7 +121,7 @@ const results = await pgVector.query({
111
121
  })
112
122
 
113
123
  // Array operations
114
- const results = await pgVector.query({
124
+ const tagResults = await mongoVector.query({
115
125
  indexName: 'embeddings',
116
126
  queryVector: embedding,
117
127
  topK: 10,
@@ -121,7 +131,64 @@ const results = await pgVector.query({
121
131
  })
122
132
 
123
133
  // Logical operators
124
- const results = await pgVector.query({
134
+ const categoryResults = await mongoVector.query({
135
+ indexName: 'embeddings',
136
+ queryVector: embedding,
137
+ topK: 10,
138
+ filter: {
139
+ $or: [{ category: 'electronics' }, { category: 'accessories' }],
140
+ $and: [{ price: { $gt: 50 } }, { price: { $lt: 200 } }],
141
+ },
142
+ })
143
+ ```
144
+
145
+ **pgVector**:
146
+
147
+ ```ts
148
+ // Simple equality filter
149
+ const equalityResults = await pgVector.query({
150
+ indexName: 'embeddings',
151
+ queryVector: embedding,
152
+ topK: 10,
153
+ filter: {
154
+ source: 'article1.txt',
155
+ },
156
+ })
157
+
158
+ // Numeric comparison
159
+ const priceResults = await pgVector.query({
160
+ indexName: 'embeddings',
161
+ queryVector: embedding,
162
+ topK: 10,
163
+ filter: {
164
+ price: { $gt: 100 },
165
+ },
166
+ })
167
+
168
+ // Multiple conditions
169
+ const compoundResults = await pgVector.query({
170
+ indexName: 'embeddings',
171
+ queryVector: embedding,
172
+ topK: 10,
173
+ filter: {
174
+ category: 'electronics',
175
+ price: { $lt: 1000 },
176
+ inStock: true,
177
+ },
178
+ })
179
+
180
+ // Array operations
181
+ const tagResults = await pgVector.query({
182
+ indexName: 'embeddings',
183
+ queryVector: embedding,
184
+ topK: 10,
185
+ filter: {
186
+ tags: { $in: ['sale', 'new'] },
187
+ },
188
+ })
189
+
190
+ // Logical operators
191
+ const categoryResults = await pgVector.query({
125
192
  indexName: 'embeddings',
126
193
  queryVector: embedding,
127
194
  topK: 10,
@@ -141,6 +208,47 @@ Common use cases for metadata filtering:
141
208
  - Combine multiple conditions for precise querying
142
209
  - Filter by document attributes (e.g., language, author)
143
210
 
211
+ ### Where the filter is applied
212
+
213
+ Vector stores differ in _when_ they apply a metadata filter, which affects how filtered queries scale.
214
+
215
+ MongoDB can evaluate the filter inside the vector index itself. This keeps the query on a single round trip to `$vectorSearch`, so it avoids the pre-filter pass that collects matching document IDs and the 16 MB BSON limit that pass is subject to. Declaring the fields you filter on in `filterFields` when you create the index is what enables it:
216
+
217
+ ```ts
218
+ // Declare the metadata fields you want to filter on
219
+ await mongoVector.createIndex({
220
+ indexName: 'embeddings',
221
+ dimension: 1536,
222
+ filterFields: ['source', 'price', 'category', 'inStock', 'tags'],
223
+ })
224
+
225
+ // createIndex() returns before the index finishes building
226
+ await mongoVector.waitForIndexReady({ indexName: 'embeddings' })
227
+
228
+ // The filter is applied during the index search
229
+ const results = await mongoVector.query({
230
+ indexName: 'embeddings',
231
+ queryVector: embedding,
232
+ topK: 10,
233
+ filter: { source: 'article1.txt' },
234
+ })
235
+ ```
236
+
237
+ Mastra passes the filter to the index only when every field it references is declared in `filterFields` and every operator is one the index accepts: `$and`, `$or`, `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, and `$nin`. A filter that uses an undeclared field or any other operator takes a fallback path: Mastra matches the collection first and passes the matching document IDs into the vector search. That fallback holds only while the ID set fits within MongoDB's 16 MB BSON document limit. On large collections the query fails once that limit is exceeded, so declare your filter fields when you expect selective filters over large data sets.
238
+
239
+ pgVector applies the filter as an ordinary query condition:
240
+
241
+ ```ts
242
+ const results = await pgVector.query({
243
+ indexName: 'embeddings',
244
+ queryVector: embedding,
245
+ topK: 10,
246
+ filter: { source: 'article1.txt' },
247
+ })
248
+ ```
249
+
250
+ 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.
251
+
144
252
  ### Vector Query Tool
145
253
 
146
254
  Sometimes you want to give your agent the ability to query a vector database directly. The Vector Query Tool allows your agent to be in charge of retrieval decisions, combining semantic search with optional filtering and reranking based on the agent's understanding of the user's needs.
package/dist/index.cjs CHANGED
@@ -4136,8 +4136,12 @@ var DatasetsLibSQL = class extends _mastra_core_storage.DatasetsStorage {
4136
4136
  try {
4137
4137
  let result;
4138
4138
  if (args.datasetVersion !== void 0) result = await this.#client.execute({
4139
- sql: `SELECT ${buildSelectColumns(_mastra_core_storage.TABLE_DATASET_ITEMS)} FROM ${_mastra_core_storage.TABLE_DATASET_ITEMS} WHERE id = ? AND datasetVersion = ? AND isDeleted = 0`,
4140
- args: [args.id, args.datasetVersion]
4139
+ sql: `SELECT ${buildSelectColumns(_mastra_core_storage.TABLE_DATASET_ITEMS)} FROM ${_mastra_core_storage.TABLE_DATASET_ITEMS} WHERE id = ? AND datasetVersion <= ? AND (validTo IS NULL OR validTo > ?) AND isDeleted = 0 ORDER BY datasetVersion DESC LIMIT 1`,
4140
+ args: [
4141
+ args.id,
4142
+ args.datasetVersion,
4143
+ args.datasetVersion
4144
+ ]
4141
4145
  });
4142
4146
  else result = await this.#client.execute({
4143
4147
  sql: `SELECT ${buildSelectColumns(_mastra_core_storage.TABLE_DATASET_ITEMS)} FROM ${_mastra_core_storage.TABLE_DATASET_ITEMS} WHERE id = ? AND validTo IS NULL AND isDeleted = 0`,
@@ -4601,7 +4605,8 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4601
4605
  "experimentSetId",
4602
4606
  "comparisonId",
4603
4607
  "variantId",
4604
- "trialIndex"
4608
+ "trialIndex",
4609
+ "scorerIds"
4605
4610
  ]
4606
4611
  });
4607
4612
  await this.#db.alterTable({
@@ -4613,7 +4618,8 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4613
4618
  "comment",
4614
4619
  "toolMockReport",
4615
4620
  "organizationId",
4616
- "projectId"
4621
+ "projectId",
4622
+ "attempt"
4617
4623
  ]
4618
4624
  });
4619
4625
  await this.#client.batch([
@@ -4630,7 +4636,11 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4630
4636
  args: []
4631
4637
  },
4632
4638
  {
4633
- sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_experiment_results_exp_item ON "${_mastra_core_storage.TABLE_EXPERIMENT_RESULTS}" ("experimentId", "itemId")`,
4639
+ sql: `DROP INDEX IF EXISTS idx_experiment_results_exp_item`,
4640
+ args: []
4641
+ },
4642
+ {
4643
+ sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_experiment_results_exp_item_attempt ON "${_mastra_core_storage.TABLE_EXPERIMENT_RESULTS}" ("experimentId", "itemId", "attempt")`,
4634
4644
  args: []
4635
4645
  },
4636
4646
  {
@@ -4719,8 +4729,9 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4719
4729
  agentVersion: row.agentVersion ?? null,
4720
4730
  organizationId: row.organizationId ?? null,
4721
4731
  projectId: row.projectId ?? null,
4722
- targetType: row.targetType,
4723
- targetId: row.targetId,
4732
+ targetType: row.targetType ?? null,
4733
+ targetId: row.targetId ?? null,
4734
+ scorerIds: row.scorerIds ? (0, _mastra_core_storage.safelyParseJSON)(row.scorerIds) : null,
4724
4735
  name: row.name ?? void 0,
4725
4736
  description: row.description ?? void 0,
4726
4737
  metadata: row.metadata ? (0, _mastra_core_storage.safelyParseJSON)(row.metadata) : void 0,
@@ -4756,6 +4767,7 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4756
4767
  startedAt: (0, _mastra_core_storage.ensureDate)(row.startedAt),
4757
4768
  completedAt: (0, _mastra_core_storage.ensureDate)(row.completedAt),
4758
4769
  retryCount: row.retryCount,
4770
+ attempt: row.attempt != null ? Number(row.attempt) : 0,
4759
4771
  traceId: row.traceId ?? null,
4760
4772
  status: row.status ?? null,
4761
4773
  tags: row.tags ? (0, _mastra_core_storage.safelyParseJSON)(row.tags) : null,
@@ -4778,8 +4790,9 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4778
4790
  agentVersion: input.agentVersion ?? null,
4779
4791
  organizationId: input.organizationId ?? null,
4780
4792
  projectId: input.projectId ?? null,
4781
- targetType: input.targetType,
4782
- targetId: input.targetId,
4793
+ targetType: input.targetType ?? null,
4794
+ targetId: input.targetId ?? null,
4795
+ scorerIds: input.scorerIds ?? null,
4783
4796
  name: input.name ?? null,
4784
4797
  description: input.description ?? null,
4785
4798
  metadata: input.metadata ?? null,
@@ -4807,8 +4820,9 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
4807
4820
  agentVersion: input.agentVersion ?? null,
4808
4821
  organizationId: input.organizationId ?? null,
4809
4822
  projectId: input.projectId ?? null,
4810
- targetType: input.targetType,
4811
- targetId: input.targetId,
4823
+ targetType: input.targetType ?? null,
4824
+ targetId: input.targetId ?? null,
4825
+ scorerIds: input.scorerIds ?? null,
4812
4826
  name: input.name,
4813
4827
  description: input.description,
4814
4828
  metadata: input.metadata,
@@ -5055,6 +5069,7 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
5055
5069
  startedAt: input.startedAt.toISOString(),
5056
5070
  completedAt: input.completedAt.toISOString(),
5057
5071
  retryCount: input.retryCount,
5072
+ attempt: input.attempt ?? 0,
5058
5073
  traceId: input.traceId ?? null,
5059
5074
  status: input.status ?? null,
5060
5075
  tags: input.tags !== void 0 && input.tags !== null ? JSON.stringify(input.tags) : null,
@@ -5076,6 +5091,7 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
5076
5091
  startedAt: input.startedAt,
5077
5092
  completedAt: input.completedAt,
5078
5093
  retryCount: input.retryCount,
5094
+ attempt: input.attempt ?? 0,
5079
5095
  traceId: input.traceId ?? null,
5080
5096
  status: input.status ?? null,
5081
5097
  tags: input.tags ?? null,
@@ -5090,6 +5106,81 @@ var ExperimentsLibSQL = class extends _mastra_core_storage.ExperimentsStorage {
5090
5106
  }, error);
5091
5107
  }
5092
5108
  }
5109
+ async upsertExperimentResult(input) {
5110
+ try {
5111
+ const attempt = input.attempt ?? 0;
5112
+ let existingId = (await this.#client.execute({
5113
+ sql: `SELECT "id" FROM ${_mastra_core_storage.TABLE_EXPERIMENT_RESULTS} WHERE "experimentId" = ? AND "itemId" = ? AND COALESCE("attempt", 0) = ?`,
5114
+ args: [
5115
+ input.experimentId,
5116
+ input.itemId,
5117
+ attempt
5118
+ ]
5119
+ })).rows[0]?.id;
5120
+ if (!existingId) try {
5121
+ return await this.addExperimentResult({
5122
+ ...input,
5123
+ attempt
5124
+ });
5125
+ } catch (insertError) {
5126
+ if (!(0, _mastra_core_storage.hasErrorCode)(insertError, /* @__PURE__ */ new Set([
5127
+ "SQLITE_CONSTRAINT",
5128
+ "SQLITE_CONSTRAINT_PRIMARYKEY",
5129
+ "SQLITE_CONSTRAINT_UNIQUE"
5130
+ ]))) throw insertError;
5131
+ existingId = (await this.#client.execute({
5132
+ sql: `SELECT "id" FROM ${_mastra_core_storage.TABLE_EXPERIMENT_RESULTS} WHERE "experimentId" = ? AND "itemId" = ? AND COALESCE("attempt", 0) = ?`,
5133
+ args: [
5134
+ input.experimentId,
5135
+ input.itemId,
5136
+ attempt
5137
+ ]
5138
+ })).rows[0]?.id;
5139
+ if (!existingId) throw insertError;
5140
+ }
5141
+ await this.#client.execute({
5142
+ sql: `UPDATE ${_mastra_core_storage.TABLE_EXPERIMENT_RESULTS} SET
5143
+ "itemDatasetVersion" = ?, "organizationId" = ?, "projectId" = ?,
5144
+ "input" = ?, "output" = ?, "groundTruth" = ?, "error" = ?,
5145
+ "startedAt" = ?, "completedAt" = ?, "retryCount" = ?, "attempt" = ?,
5146
+ "traceId" = ?, "status" = ?, "tags" = ?, "toolMockReport" = ?
5147
+ WHERE "id" = ?`,
5148
+ args: [
5149
+ input.itemDatasetVersion ?? null,
5150
+ input.organizationId ?? null,
5151
+ input.projectId ?? null,
5152
+ JSON.stringify(input.input),
5153
+ input.output != null ? JSON.stringify(input.output) : null,
5154
+ input.groundTruth != null ? JSON.stringify(input.groundTruth) : null,
5155
+ input.error != null ? JSON.stringify(input.error) : null,
5156
+ input.startedAt.toISOString(),
5157
+ input.completedAt.toISOString(),
5158
+ input.retryCount,
5159
+ attempt,
5160
+ input.traceId ?? null,
5161
+ input.status ?? null,
5162
+ input.tags != null ? JSON.stringify(input.tags) : null,
5163
+ input.toolMockReport != null ? JSON.stringify(input.toolMockReport) : null,
5164
+ existingId
5165
+ ]
5166
+ });
5167
+ const result = await this.getExperimentResultById({ id: existingId });
5168
+ if (!result) throw new _mastra_core_error.MastraError({
5169
+ id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "UPSERT_EXPERIMENT_RESULT", "NOT_FOUND"),
5170
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
5171
+ category: _mastra_core_error.ErrorCategory.USER,
5172
+ details: { resultId: existingId }
5173
+ });
5174
+ return result;
5175
+ } catch (error) {
5176
+ if (error instanceof _mastra_core_error.MastraError) throw error;
5177
+ throw new _mastra_core_error.MastraError({
5178
+ id: (0, _mastra_core_storage.createStorageErrorId)("LIBSQL", "UPSERT_EXPERIMENT_RESULT", "FAILED"),
5179
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
5180
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY
5181
+ }, error);
5182
+ }
5183
+ }
5093
5184
  async updateExperimentResult(input) {
5094
5185
  try {
5095
5186
  const setClauses = [];
@@ -11609,8 +11700,13 @@ var ScoresLibSQL = class ScoresLibSQL extends _mastra_core_storage.ScoresStorage
11609
11700
  }, error);
11610
11701
  }
11611
11702
  try {
11612
- const id = crypto.randomUUID();
11703
+ const suppliedId = parsedScore.id;
11704
+ const id = suppliedId ?? crypto.randomUUID();
11613
11705
  const now = /* @__PURE__ */ new Date();
11706
+ if (suppliedId) await this.#client.execute({
11707
+ sql: `DELETE FROM ${_mastra_core_storage.TABLE_SCORERS} WHERE id = ?`,
11708
+ args: [suppliedId]
11709
+ });
11614
11710
  await this.#db.insert({
11615
11711
  tableName: _mastra_core_storage.TABLE_SCORERS,
11616
11712
  record: {