ragpack-js 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -41,7 +41,7 @@ console.log(answer);
41
41
  // Semantic search (without LLM)
42
42
  const results = await collection.findSimilar({ query: "how does auth work?", topK: 5 });
43
43
  for (const r of results) {
44
- console.log(r.similarity, r.chunk_text);
44
+ console.log(r.vector_similarity, r.chunk_text);
45
45
  }
46
46
  ```
47
47
 
@@ -72,12 +72,15 @@ Returns a `CollectionClient` scoped to that collection.
72
72
  | `ingest(file, filename?)` | Upload a file directly |
73
73
  | `ingest({ uri, mimeType? })` | Ingest from a URL or S3 URI, MIME type auto-detected if omitted |
74
74
  | `rag(options)` | Full RAG pipeline — retrieves chunks and returns an LLM answer |
75
- | `findSimilar({ query, topK? })` | Semantic search without LLM, returns ranked chunks |
75
+ | `findSimilar(options)` | Hybrid (vector + keyword) search without LLM, returns ranked chunks |
76
76
  | `jobs.list()` | List ingestion jobs for this collection |
77
77
  | `jobs.get(id)` | Get a single job by ID |
78
78
  | `jobs.waitUntilComplete(id)` | Poll until job is `complete` or `failed` |
79
79
  | `documents.list(options?)` | List indexed documents |
80
+ | `documents.get(id)` | Get a single document by ID |
81
+ | `documents.metadata(id)` | Get a document's typed metadata field values |
80
82
  | `documents.rename(id, name)` | Set the display name of a document |
83
+ | `documents.update(id, options)` | Update a document's name, `extraJson`, and/or typed metadata |
81
84
  | `documents.delete(id)` | Delete a document and its chunks |
82
85
 
83
86
  #### `rag(options)`
@@ -89,18 +92,54 @@ const { answer, chunks } = await collection.rag({
89
92
  query: "How do I reset my password?",
90
93
  // All options below are optional
91
94
  promptSlug: "basic_rag", // defaults to "basic_rag" if omitted
92
- topK: 5, // number of chunks to retrieve (default: 5)
95
+ topK: 2, // number of chunks to retrieve (default: 2)
93
96
  model: "gpt-4o", // LLM model; falls back to server default
94
97
  minSimilarity: 70, // drop chunks below this similarity score (0–100)
98
+ filters: { mime_type: "application/pdf" },
95
99
  });
96
100
 
97
101
  console.log(answer);
98
102
 
99
103
  for (const chunk of chunks) {
100
- console.log(chunk.similarity, chunk.chunk_text);
104
+ console.log(chunk.vector_similarity, chunk.chunk_text);
101
105
  }
102
106
  ```
103
107
 
108
+ #### Hybrid (vector + keyword) search
109
+
110
+ `findSimilar` and `rag` run hybrid search by default: a vector pass and a keyword/BM25 pass are fused server-side via weighted RRF (semantic-favored 7:3). Each result carries `vector_similarity`, `keyword_bm25_score`, `rrf_score`, and `rrf_score_normalized` (0–100, normalized so the batch's top result is always 100).
111
+
112
+ ```ts
113
+ const results = await collection.findSimilar({
114
+ query: "password reset",
115
+ vectorSearchOnly: false, // set true to skip the keyword pass entirely
116
+ hybridSettings: {
117
+ semanticWeight: 0.7,
118
+ fullTextWeight: 0.3,
119
+ rrfK: 60,
120
+ fullTextLimit: 200,
121
+ },
122
+ });
123
+ ```
124
+
125
+ #### Filtering by metadata
126
+
127
+ Both `findSimilar` and `rag` accept a `filters` option — a MongoDB-style expression compiled server-side into a predicate over a document's built-in (`mime_type`, `source_name`, `file_uri`, `created_at`, `updated_at`, …) and registered metadata fields.
128
+
129
+ ```ts
130
+ await collection.findSimilar({
131
+ query: "refund policy",
132
+ filters: {
133
+ $and: [
134
+ { mime_type: "application/pdf" },
135
+ { $or: [{ tags: { $contains: "billing" } }, { created_at: { $gte: "7 days ago" } }] },
136
+ ],
137
+ },
138
+ });
139
+ ```
140
+
141
+ Supported operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$exists`, `$like`/`$ilike` (string fields), `$contains`/`$containsAny`/`$containsAll` (array fields), plus `$and`/`$or` for nesting.
142
+
104
143
  #### Prompt templates
105
144
 
106
145
  Three built-in prompts are always available: `basic_rag`, `rag_with_citations`, and `concise_rag`. List all available prompts (including any you've created in the admin UI) with:
package/dist/index.cjs CHANGED
@@ -118,6 +118,25 @@ var DocumentsResource = class {
118
118
  );
119
119
  return r.documents;
120
120
  }
121
+ /**
122
+ * Get a single document by ID.
123
+ * @param id - The document ID.
124
+ */
125
+ get(id) {
126
+ return this.req(`/collections/${this.slug}/documents/${id}`);
127
+ }
128
+ /**
129
+ * Get this document's typed metadata field values.
130
+ * A field is only included if its value is identical across every chunk
131
+ * of the document — this avoids showing a value that's mid-sync.
132
+ * @param id - The document ID.
133
+ */
134
+ async metadata(id) {
135
+ const r = await this.req(
136
+ `/collections/${this.slug}/documents/${id}/metadata`
137
+ );
138
+ return r.metadata;
139
+ }
121
140
  /**
122
141
  * Rename a document.
123
142
  * @param id - The document ID.
@@ -129,6 +148,32 @@ var DocumentsResource = class {
129
148
  body: JSON.stringify({ name })
130
149
  });
131
150
  }
151
+ /**
152
+ * Update a document's name, extra_json, and/or typed metadata.
153
+ * `metadata` keys that aren't registered fields on this collection (or whose
154
+ * value doesn't match the field's declared type) are silently dropped by
155
+ * the server — the request still succeeds, with no signal for which keys
156
+ * landed. Register fields first via the metadata-fields admin API.
157
+ * @param id - The document ID.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * await collection.documents.update(id, {
162
+ * metadata: { reviewed: true },
163
+ * extraJson: JSON.stringify({ source: "zendesk" }),
164
+ * });
165
+ * ```
166
+ */
167
+ update(id, options) {
168
+ return this.req(`/collections/${this.slug}/documents/${id}`, {
169
+ method: "PATCH",
170
+ body: JSON.stringify({
171
+ ...options.name !== void 0 && { name: options.name },
172
+ ...options.extraJson !== void 0 && { extra_json: options.extraJson },
173
+ ...options.metadata !== void 0 && { metadata: options.metadata }
174
+ })
175
+ });
176
+ }
132
177
  /**
133
178
  * Delete a document and all its chunks from this collection.
134
179
  * @param id - The document ID.
@@ -140,6 +185,29 @@ var DocumentsResource = class {
140
185
  }
141
186
  };
142
187
 
188
+ // src/hybrid.ts
189
+ function buildHybridBody(options) {
190
+ const { filters, vectorSearchOnly, hybridSettings } = options;
191
+ return {
192
+ ...filters !== void 0 && { filters },
193
+ ...vectorSearchOnly !== void 0 && { vector_search_only: vectorSearchOnly },
194
+ ...hybridSettings !== void 0 && {
195
+ hybrid_settings: {
196
+ ...hybridSettings.fullTextWeight !== void 0 && {
197
+ full_text_weight: hybridSettings.fullTextWeight
198
+ },
199
+ ...hybridSettings.semanticWeight !== void 0 && {
200
+ semantic_weight: hybridSettings.semanticWeight
201
+ },
202
+ ...hybridSettings.rrfK !== void 0 && { rrf_k: hybridSettings.rrfK },
203
+ ...hybridSettings.fullTextLimit !== void 0 && {
204
+ full_text_limit: hybridSettings.fullTextLimit
205
+ }
206
+ }
207
+ }
208
+ };
209
+ }
210
+
143
211
  // src/resources/query.ts
144
212
  var QueryResource = class {
145
213
  constructor(req, slug) {
@@ -155,7 +223,11 @@ var QueryResource = class {
155
223
  `/collections/${this.slug}/query`,
156
224
  {
157
225
  method: "POST",
158
- body: JSON.stringify({ query: options.query, top_k: options.topK ?? 5 })
226
+ body: JSON.stringify({
227
+ query: options.query,
228
+ top_k: options.topK ?? 5,
229
+ ...buildHybridBody(options)
230
+ })
159
231
  }
160
232
  );
161
233
  return r.results;
@@ -215,7 +287,8 @@ var CollectionClient = class {
215
287
  top_k: options.topK ?? 5,
216
288
  ...options.promptSlug ? { prompt_slug: options.promptSlug } : {},
217
289
  ...options.model ? { model: options.model } : {},
218
- ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {}
290
+ ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {},
291
+ ...buildHybridBody(options)
219
292
  })
220
293
  });
221
294
  }
package/dist/index.d.cts CHANGED
@@ -74,7 +74,14 @@ interface RagChunk {
74
74
  chunk_index: number;
75
75
  chunk_header: string | null;
76
76
  chunk_text: string | null;
77
- similarity: number;
77
+ /** Cosine similarity score between 0 and 100. Higher is more relevant. */
78
+ vector_similarity: number;
79
+ /** Raw BM25 score from the keyword channel; present only for hybrid results. */
80
+ keyword_bm25_score?: number;
81
+ /** Weighted RRF fusion score, normalized so this batch's top result is 100. */
82
+ rrf_score_normalized?: number;
83
+ /** Raw RRF fusion score; only comparable within this query's own weights/k. */
84
+ rrf_score?: number;
78
85
  }
79
86
  interface RagResult {
80
87
  formatted_prompt: string;
@@ -87,13 +94,73 @@ interface QueryResult {
87
94
  file_uri: string;
88
95
  mime_type: string;
89
96
  chunk_index: number;
97
+ chunk_header: string | null;
90
98
  /** The matched text chunk. */
91
99
  chunk_text: string | null;
92
100
  /** Optional metadata attached at ingest time. */
93
101
  extra_json: string | null;
94
- distance: number;
95
- /** Cosine similarity score between 0 and 1. Higher is more relevant. */
96
- similarity: number;
102
+ /** Typed metadata field values registered for this collection. */
103
+ metadata?: Record<string, unknown>;
104
+ vector_distance: number;
105
+ /** Cosine similarity score between 0 and 100. Higher is more relevant. */
106
+ vector_similarity: number;
107
+ /** Raw BM25 score from the keyword channel; present only for hybrid results. */
108
+ keyword_bm25_score?: number;
109
+ /** Weighted RRF fusion score, normalized so this batch's top result is 100. */
110
+ rrf_score_normalized?: number;
111
+ /** Raw RRF fusion score; only comparable within this query's own weights/k. */
112
+ rrf_score?: number;
113
+ }
114
+ type FilterValue = string | number | boolean;
115
+ /** Operators usable against a single field in a {@link FilterExpression}. */
116
+ interface FilterOps {
117
+ $eq?: FilterValue;
118
+ $ne?: FilterValue;
119
+ $gt?: FilterValue;
120
+ $gte?: FilterValue;
121
+ $lt?: FilterValue;
122
+ $lte?: FilterValue;
123
+ $in?: FilterValue[];
124
+ $nin?: FilterValue[];
125
+ $exists?: boolean;
126
+ /** str/timestamp fields only. */
127
+ $like?: string;
128
+ /** str fields only. */
129
+ $ilike?: string;
130
+ /** arr fields only. */
131
+ $contains?: string;
132
+ /** arr fields only. */
133
+ $containsAny?: string[];
134
+ /** arr fields only. */
135
+ $containsAll?: string[];
136
+ }
137
+ /**
138
+ * MongoDB-style filter expression, compiled server-side into a predicate over
139
+ * a document's built-in and registered metadata fields.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * { mime_type: "application/pdf", score: { $gte: 4 } }
144
+ * { $or: [{ tags: { $contains: "urgent" } }, { created_at: { $gte: "7 days ago" } }] }
145
+ * ```
146
+ */
147
+ type FilterExpression = {
148
+ $and: FilterExpression[];
149
+ } | {
150
+ $or: FilterExpression[];
151
+ } | {
152
+ [field: string]: FilterValue | FilterOps;
153
+ };
154
+ /**
155
+ * Per-request override of the weighted RRF merge between vector and keyword
156
+ * search. Unset fields fall back to server defaults (semantic-favored 7:3).
157
+ */
158
+ interface HybridSettings {
159
+ fullTextWeight?: number;
160
+ semanticWeight?: number;
161
+ rrfK?: number;
162
+ /** Caps FTS candidates considered before fusion; FTS search has no native limit. */
163
+ fullTextLimit?: number;
97
164
  }
98
165
 
99
166
  type Requester = <T>(path: string, init?: RequestInit) => Promise<T>;
@@ -133,6 +200,14 @@ declare class JobsResource {
133
200
  waitUntilComplete(id: string, options?: WaitOptions): Promise<Job>;
134
201
  }
135
202
 
203
+ interface UpdateDocumentOptions {
204
+ /** New display name. */
205
+ name?: string;
206
+ /** Replaces the document's freeform extra_json metadata. */
207
+ extraJson?: string;
208
+ /** Merges values into the document's typed metadata fields. */
209
+ metadata?: Record<string, unknown>;
210
+ }
136
211
  declare class DocumentsResource {
137
212
  private readonly req;
138
213
  private readonly slug?;
@@ -146,12 +221,41 @@ declare class DocumentsResource {
146
221
  limit?: number;
147
222
  offset?: number;
148
223
  }): Promise<Document[]>;
224
+ /**
225
+ * Get a single document by ID.
226
+ * @param id - The document ID.
227
+ */
228
+ get(id: string): Promise<Document>;
229
+ /**
230
+ * Get this document's typed metadata field values.
231
+ * A field is only included if its value is identical across every chunk
232
+ * of the document — this avoids showing a value that's mid-sync.
233
+ * @param id - The document ID.
234
+ */
235
+ metadata(id: string): Promise<Record<string, unknown>>;
149
236
  /**
150
237
  * Rename a document.
151
238
  * @param id - The document ID.
152
239
  * @param name - The new display name.
153
240
  */
154
241
  rename(id: string, name: string): Promise<Document>;
242
+ /**
243
+ * Update a document's name, extra_json, and/or typed metadata.
244
+ * `metadata` keys that aren't registered fields on this collection (or whose
245
+ * value doesn't match the field's declared type) are silently dropped by
246
+ * the server — the request still succeeds, with no signal for which keys
247
+ * landed. Register fields first via the metadata-fields admin API.
248
+ * @param id - The document ID.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * await collection.documents.update(id, {
253
+ * metadata: { reviewed: true },
254
+ * extraJson: JSON.stringify({ source: "zendesk" }),
255
+ * });
256
+ * ```
257
+ */
258
+ update(id: string, options: UpdateDocumentOptions): Promise<Document>;
155
259
  /**
156
260
  * Delete a document and all its chunks from this collection.
157
261
  * @param id - The document ID.
@@ -159,23 +263,36 @@ declare class DocumentsResource {
159
263
  delete(id: string): Promise<void>;
160
264
  }
161
265
 
266
+ interface FindSimilarOptions {
267
+ /** The search query text. */
268
+ query: string;
269
+ /** Number of results to return. Defaults to 5. */
270
+ topK?: number;
271
+ /** Restrict results to chunks whose document matches this filter expression. */
272
+ filters?: FilterExpression;
273
+ /** Skip the keyword/BM25 pass and use pure vector search. Hybrid search runs by default. */
274
+ vectorSearchOnly?: boolean;
275
+ /** Per-request override of the weighted RRF merge between vector and keyword search. */
276
+ hybridSettings?: HybridSettings;
277
+ }
278
+
162
279
  interface RagOptions {
163
280
  /** Slug of the prompt template to use. Defaults to `"basic_rag"` if omitted. */
164
281
  promptSlug?: string;
165
282
  /** The user's question. Substituted into `{{question}}`. */
166
283
  query: string;
167
- /** Number of chunks to retrieve. Defaults to 5. */
284
+ /** Number of chunks to retrieve. Defaults to 2, since RAG chunks feed an LLM prompt. */
168
285
  topK?: number;
169
286
  /** LLM model name to use (e.g. `"gpt-4o"`, `"claude-opus-4-8"`). Falls back to server default. */
170
287
  model?: string;
171
288
  /** Minimum similarity score (0–100) a chunk must meet to be included in context. Omit to include all top_k results. */
172
289
  minSimilarity?: number;
173
- }
174
- interface FindSimilarOptions {
175
- /** The search query text. */
176
- query: string;
177
- /** Number of results to return. Defaults to 5. */
178
- topK?: number;
290
+ /** Restrict retrieval to chunks whose document matches this filter expression. */
291
+ filters?: FilterExpression;
292
+ /** Skip the keyword/BM25 pass and use pure vector search. Hybrid search runs by default. */
293
+ vectorSearchOnly?: boolean;
294
+ /** Per-request override of the weighted RRF merge between vector and keyword search. */
295
+ hybridSettings?: HybridSettings;
179
296
  }
180
297
  interface IngestUriOptions {
181
298
  /** Remote file URI (e.g. `s3://bucket/file.pdf`). */
@@ -355,4 +472,4 @@ declare class RagPack {
355
472
  collection(slug: string): CollectionClient;
356
473
  }
357
474
 
358
- export { type Collection, type Document, type EmbedderInfo, type FindSimilarOptions, type IngestUriOptions, type Job, type LLMInfo, type Prompt, type QueryResult, type RagChunk, type RagOptions, RagPack, type RagPackConfig, RagPackError, type RagResult };
475
+ export { type Collection, type Document, type EmbedderInfo, type FilterExpression, type FilterOps, type FilterValue, type FindSimilarOptions, type HybridSettings, type IngestUriOptions, type Job, type LLMInfo, type Prompt, type QueryResult, type RagChunk, type RagOptions, RagPack, type RagPackConfig, RagPackError, type RagResult, type UpdateDocumentOptions };
package/dist/index.d.ts CHANGED
@@ -74,7 +74,14 @@ interface RagChunk {
74
74
  chunk_index: number;
75
75
  chunk_header: string | null;
76
76
  chunk_text: string | null;
77
- similarity: number;
77
+ /** Cosine similarity score between 0 and 100. Higher is more relevant. */
78
+ vector_similarity: number;
79
+ /** Raw BM25 score from the keyword channel; present only for hybrid results. */
80
+ keyword_bm25_score?: number;
81
+ /** Weighted RRF fusion score, normalized so this batch's top result is 100. */
82
+ rrf_score_normalized?: number;
83
+ /** Raw RRF fusion score; only comparable within this query's own weights/k. */
84
+ rrf_score?: number;
78
85
  }
79
86
  interface RagResult {
80
87
  formatted_prompt: string;
@@ -87,13 +94,73 @@ interface QueryResult {
87
94
  file_uri: string;
88
95
  mime_type: string;
89
96
  chunk_index: number;
97
+ chunk_header: string | null;
90
98
  /** The matched text chunk. */
91
99
  chunk_text: string | null;
92
100
  /** Optional metadata attached at ingest time. */
93
101
  extra_json: string | null;
94
- distance: number;
95
- /** Cosine similarity score between 0 and 1. Higher is more relevant. */
96
- similarity: number;
102
+ /** Typed metadata field values registered for this collection. */
103
+ metadata?: Record<string, unknown>;
104
+ vector_distance: number;
105
+ /** Cosine similarity score between 0 and 100. Higher is more relevant. */
106
+ vector_similarity: number;
107
+ /** Raw BM25 score from the keyword channel; present only for hybrid results. */
108
+ keyword_bm25_score?: number;
109
+ /** Weighted RRF fusion score, normalized so this batch's top result is 100. */
110
+ rrf_score_normalized?: number;
111
+ /** Raw RRF fusion score; only comparable within this query's own weights/k. */
112
+ rrf_score?: number;
113
+ }
114
+ type FilterValue = string | number | boolean;
115
+ /** Operators usable against a single field in a {@link FilterExpression}. */
116
+ interface FilterOps {
117
+ $eq?: FilterValue;
118
+ $ne?: FilterValue;
119
+ $gt?: FilterValue;
120
+ $gte?: FilterValue;
121
+ $lt?: FilterValue;
122
+ $lte?: FilterValue;
123
+ $in?: FilterValue[];
124
+ $nin?: FilterValue[];
125
+ $exists?: boolean;
126
+ /** str/timestamp fields only. */
127
+ $like?: string;
128
+ /** str fields only. */
129
+ $ilike?: string;
130
+ /** arr fields only. */
131
+ $contains?: string;
132
+ /** arr fields only. */
133
+ $containsAny?: string[];
134
+ /** arr fields only. */
135
+ $containsAll?: string[];
136
+ }
137
+ /**
138
+ * MongoDB-style filter expression, compiled server-side into a predicate over
139
+ * a document's built-in and registered metadata fields.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * { mime_type: "application/pdf", score: { $gte: 4 } }
144
+ * { $or: [{ tags: { $contains: "urgent" } }, { created_at: { $gte: "7 days ago" } }] }
145
+ * ```
146
+ */
147
+ type FilterExpression = {
148
+ $and: FilterExpression[];
149
+ } | {
150
+ $or: FilterExpression[];
151
+ } | {
152
+ [field: string]: FilterValue | FilterOps;
153
+ };
154
+ /**
155
+ * Per-request override of the weighted RRF merge between vector and keyword
156
+ * search. Unset fields fall back to server defaults (semantic-favored 7:3).
157
+ */
158
+ interface HybridSettings {
159
+ fullTextWeight?: number;
160
+ semanticWeight?: number;
161
+ rrfK?: number;
162
+ /** Caps FTS candidates considered before fusion; FTS search has no native limit. */
163
+ fullTextLimit?: number;
97
164
  }
98
165
 
99
166
  type Requester = <T>(path: string, init?: RequestInit) => Promise<T>;
@@ -133,6 +200,14 @@ declare class JobsResource {
133
200
  waitUntilComplete(id: string, options?: WaitOptions): Promise<Job>;
134
201
  }
135
202
 
203
+ interface UpdateDocumentOptions {
204
+ /** New display name. */
205
+ name?: string;
206
+ /** Replaces the document's freeform extra_json metadata. */
207
+ extraJson?: string;
208
+ /** Merges values into the document's typed metadata fields. */
209
+ metadata?: Record<string, unknown>;
210
+ }
136
211
  declare class DocumentsResource {
137
212
  private readonly req;
138
213
  private readonly slug?;
@@ -146,12 +221,41 @@ declare class DocumentsResource {
146
221
  limit?: number;
147
222
  offset?: number;
148
223
  }): Promise<Document[]>;
224
+ /**
225
+ * Get a single document by ID.
226
+ * @param id - The document ID.
227
+ */
228
+ get(id: string): Promise<Document>;
229
+ /**
230
+ * Get this document's typed metadata field values.
231
+ * A field is only included if its value is identical across every chunk
232
+ * of the document — this avoids showing a value that's mid-sync.
233
+ * @param id - The document ID.
234
+ */
235
+ metadata(id: string): Promise<Record<string, unknown>>;
149
236
  /**
150
237
  * Rename a document.
151
238
  * @param id - The document ID.
152
239
  * @param name - The new display name.
153
240
  */
154
241
  rename(id: string, name: string): Promise<Document>;
242
+ /**
243
+ * Update a document's name, extra_json, and/or typed metadata.
244
+ * `metadata` keys that aren't registered fields on this collection (or whose
245
+ * value doesn't match the field's declared type) are silently dropped by
246
+ * the server — the request still succeeds, with no signal for which keys
247
+ * landed. Register fields first via the metadata-fields admin API.
248
+ * @param id - The document ID.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * await collection.documents.update(id, {
253
+ * metadata: { reviewed: true },
254
+ * extraJson: JSON.stringify({ source: "zendesk" }),
255
+ * });
256
+ * ```
257
+ */
258
+ update(id: string, options: UpdateDocumentOptions): Promise<Document>;
155
259
  /**
156
260
  * Delete a document and all its chunks from this collection.
157
261
  * @param id - The document ID.
@@ -159,23 +263,36 @@ declare class DocumentsResource {
159
263
  delete(id: string): Promise<void>;
160
264
  }
161
265
 
266
+ interface FindSimilarOptions {
267
+ /** The search query text. */
268
+ query: string;
269
+ /** Number of results to return. Defaults to 5. */
270
+ topK?: number;
271
+ /** Restrict results to chunks whose document matches this filter expression. */
272
+ filters?: FilterExpression;
273
+ /** Skip the keyword/BM25 pass and use pure vector search. Hybrid search runs by default. */
274
+ vectorSearchOnly?: boolean;
275
+ /** Per-request override of the weighted RRF merge between vector and keyword search. */
276
+ hybridSettings?: HybridSettings;
277
+ }
278
+
162
279
  interface RagOptions {
163
280
  /** Slug of the prompt template to use. Defaults to `"basic_rag"` if omitted. */
164
281
  promptSlug?: string;
165
282
  /** The user's question. Substituted into `{{question}}`. */
166
283
  query: string;
167
- /** Number of chunks to retrieve. Defaults to 5. */
284
+ /** Number of chunks to retrieve. Defaults to 2, since RAG chunks feed an LLM prompt. */
168
285
  topK?: number;
169
286
  /** LLM model name to use (e.g. `"gpt-4o"`, `"claude-opus-4-8"`). Falls back to server default. */
170
287
  model?: string;
171
288
  /** Minimum similarity score (0–100) a chunk must meet to be included in context. Omit to include all top_k results. */
172
289
  minSimilarity?: number;
173
- }
174
- interface FindSimilarOptions {
175
- /** The search query text. */
176
- query: string;
177
- /** Number of results to return. Defaults to 5. */
178
- topK?: number;
290
+ /** Restrict retrieval to chunks whose document matches this filter expression. */
291
+ filters?: FilterExpression;
292
+ /** Skip the keyword/BM25 pass and use pure vector search. Hybrid search runs by default. */
293
+ vectorSearchOnly?: boolean;
294
+ /** Per-request override of the weighted RRF merge between vector and keyword search. */
295
+ hybridSettings?: HybridSettings;
179
296
  }
180
297
  interface IngestUriOptions {
181
298
  /** Remote file URI (e.g. `s3://bucket/file.pdf`). */
@@ -355,4 +472,4 @@ declare class RagPack {
355
472
  collection(slug: string): CollectionClient;
356
473
  }
357
474
 
358
- export { type Collection, type Document, type EmbedderInfo, type FindSimilarOptions, type IngestUriOptions, type Job, type LLMInfo, type Prompt, type QueryResult, type RagChunk, type RagOptions, RagPack, type RagPackConfig, RagPackError, type RagResult };
475
+ export { type Collection, type Document, type EmbedderInfo, type FilterExpression, type FilterOps, type FilterValue, type FindSimilarOptions, type HybridSettings, type IngestUriOptions, type Job, type LLMInfo, type Prompt, type QueryResult, type RagChunk, type RagOptions, RagPack, type RagPackConfig, RagPackError, type RagResult, type UpdateDocumentOptions };
package/dist/index.js CHANGED
@@ -91,6 +91,25 @@ var DocumentsResource = class {
91
91
  );
92
92
  return r.documents;
93
93
  }
94
+ /**
95
+ * Get a single document by ID.
96
+ * @param id - The document ID.
97
+ */
98
+ get(id) {
99
+ return this.req(`/collections/${this.slug}/documents/${id}`);
100
+ }
101
+ /**
102
+ * Get this document's typed metadata field values.
103
+ * A field is only included if its value is identical across every chunk
104
+ * of the document — this avoids showing a value that's mid-sync.
105
+ * @param id - The document ID.
106
+ */
107
+ async metadata(id) {
108
+ const r = await this.req(
109
+ `/collections/${this.slug}/documents/${id}/metadata`
110
+ );
111
+ return r.metadata;
112
+ }
94
113
  /**
95
114
  * Rename a document.
96
115
  * @param id - The document ID.
@@ -102,6 +121,32 @@ var DocumentsResource = class {
102
121
  body: JSON.stringify({ name })
103
122
  });
104
123
  }
124
+ /**
125
+ * Update a document's name, extra_json, and/or typed metadata.
126
+ * `metadata` keys that aren't registered fields on this collection (or whose
127
+ * value doesn't match the field's declared type) are silently dropped by
128
+ * the server — the request still succeeds, with no signal for which keys
129
+ * landed. Register fields first via the metadata-fields admin API.
130
+ * @param id - The document ID.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * await collection.documents.update(id, {
135
+ * metadata: { reviewed: true },
136
+ * extraJson: JSON.stringify({ source: "zendesk" }),
137
+ * });
138
+ * ```
139
+ */
140
+ update(id, options) {
141
+ return this.req(`/collections/${this.slug}/documents/${id}`, {
142
+ method: "PATCH",
143
+ body: JSON.stringify({
144
+ ...options.name !== void 0 && { name: options.name },
145
+ ...options.extraJson !== void 0 && { extra_json: options.extraJson },
146
+ ...options.metadata !== void 0 && { metadata: options.metadata }
147
+ })
148
+ });
149
+ }
105
150
  /**
106
151
  * Delete a document and all its chunks from this collection.
107
152
  * @param id - The document ID.
@@ -113,6 +158,29 @@ var DocumentsResource = class {
113
158
  }
114
159
  };
115
160
 
161
+ // src/hybrid.ts
162
+ function buildHybridBody(options) {
163
+ const { filters, vectorSearchOnly, hybridSettings } = options;
164
+ return {
165
+ ...filters !== void 0 && { filters },
166
+ ...vectorSearchOnly !== void 0 && { vector_search_only: vectorSearchOnly },
167
+ ...hybridSettings !== void 0 && {
168
+ hybrid_settings: {
169
+ ...hybridSettings.fullTextWeight !== void 0 && {
170
+ full_text_weight: hybridSettings.fullTextWeight
171
+ },
172
+ ...hybridSettings.semanticWeight !== void 0 && {
173
+ semantic_weight: hybridSettings.semanticWeight
174
+ },
175
+ ...hybridSettings.rrfK !== void 0 && { rrf_k: hybridSettings.rrfK },
176
+ ...hybridSettings.fullTextLimit !== void 0 && {
177
+ full_text_limit: hybridSettings.fullTextLimit
178
+ }
179
+ }
180
+ }
181
+ };
182
+ }
183
+
116
184
  // src/resources/query.ts
117
185
  var QueryResource = class {
118
186
  constructor(req, slug) {
@@ -128,7 +196,11 @@ var QueryResource = class {
128
196
  `/collections/${this.slug}/query`,
129
197
  {
130
198
  method: "POST",
131
- body: JSON.stringify({ query: options.query, top_k: options.topK ?? 5 })
199
+ body: JSON.stringify({
200
+ query: options.query,
201
+ top_k: options.topK ?? 5,
202
+ ...buildHybridBody(options)
203
+ })
132
204
  }
133
205
  );
134
206
  return r.results;
@@ -188,7 +260,8 @@ var CollectionClient = class {
188
260
  top_k: options.topK ?? 5,
189
261
  ...options.promptSlug ? { prompt_slug: options.promptSlug } : {},
190
262
  ...options.model ? { model: options.model } : {},
191
- ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {}
263
+ ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {},
264
+ ...buildHybridBody(options)
192
265
  })
193
266
  });
194
267
  }
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "ragpack-js",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "TypeScript client for the Ragpack self-hosted RAG engine",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.js"
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
13
18
  }
14
19
  },
15
20
  "files": [