ragpack-js 0.1.2 → 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,11 +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 |
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 |
80
84
  | `documents.delete(id)` | Delete a document and its chunks |
81
85
 
82
86
  #### `rag(options)`
@@ -88,18 +92,54 @@ const { answer, chunks } = await collection.rag({
88
92
  query: "How do I reset my password?",
89
93
  // All options below are optional
90
94
  promptSlug: "basic_rag", // defaults to "basic_rag" if omitted
91
- topK: 5, // number of chunks to retrieve (default: 5)
95
+ topK: 2, // number of chunks to retrieve (default: 2)
92
96
  model: "gpt-4o", // LLM model; falls back to server default
93
97
  minSimilarity: 70, // drop chunks below this similarity score (0–100)
98
+ filters: { mime_type: "application/pdf" },
94
99
  });
95
100
 
96
101
  console.log(answer);
97
102
 
98
103
  for (const chunk of chunks) {
99
- console.log(chunk.similarity, chunk.chunk_text);
104
+ console.log(chunk.vector_similarity, chunk.chunk_text);
100
105
  }
101
106
  ```
102
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
+
103
143
  #### Prompt templates
104
144
 
105
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,62 @@ 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
+ }
140
+ /**
141
+ * Rename a document.
142
+ * @param id - The document ID.
143
+ * @param name - The new display name.
144
+ */
145
+ rename(id, name) {
146
+ return this.req(`/collections/${this.slug}/documents/${id}`, {
147
+ method: "PATCH",
148
+ body: JSON.stringify({ name })
149
+ });
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
+ }
121
177
  /**
122
178
  * Delete a document and all its chunks from this collection.
123
179
  * @param id - The document ID.
@@ -129,6 +185,29 @@ var DocumentsResource = class {
129
185
  }
130
186
  };
131
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
+
132
211
  // src/resources/query.ts
133
212
  var QueryResource = class {
134
213
  constructor(req, slug) {
@@ -144,7 +223,11 @@ var QueryResource = class {
144
223
  `/collections/${this.slug}/query`,
145
224
  {
146
225
  method: "POST",
147
- 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
+ })
148
231
  }
149
232
  );
150
233
  return r.results;
@@ -204,7 +287,8 @@ var CollectionClient = class {
204
287
  top_k: options.topK ?? 5,
205
288
  ...options.promptSlug ? { prompt_slug: options.promptSlug } : {},
206
289
  ...options.model ? { model: options.model } : {},
207
- ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {}
290
+ ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {},
291
+ ...buildHybridBody(options)
208
292
  })
209
293
  });
210
294
  }
package/dist/index.d.cts CHANGED
@@ -39,6 +39,7 @@ interface Job {
39
39
  collection_id: string;
40
40
  file_uri: string;
41
41
  mime_type: string;
42
+ display_name?: string;
42
43
  status: "pending" | "processing" | "complete" | "failed";
43
44
  error?: string;
44
45
  created_at: string;
@@ -50,6 +51,7 @@ interface Document {
50
51
  job_id: string;
51
52
  file_uri: string;
52
53
  mime_type: string;
54
+ name?: string;
53
55
  chunk_count: number;
54
56
  status: "ingesting" | "complete" | "failed";
55
57
  error?: string;
@@ -72,7 +74,14 @@ interface RagChunk {
72
74
  chunk_index: number;
73
75
  chunk_header: string | null;
74
76
  chunk_text: string | null;
75
- 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;
76
85
  }
77
86
  interface RagResult {
78
87
  formatted_prompt: string;
@@ -85,13 +94,73 @@ interface QueryResult {
85
94
  file_uri: string;
86
95
  mime_type: string;
87
96
  chunk_index: number;
97
+ chunk_header: string | null;
88
98
  /** The matched text chunk. */
89
99
  chunk_text: string | null;
90
100
  /** Optional metadata attached at ingest time. */
91
101
  extra_json: string | null;
92
- distance: number;
93
- /** Cosine similarity score between 0 and 1. Higher is more relevant. */
94
- 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;
95
164
  }
96
165
 
97
166
  type Requester = <T>(path: string, init?: RequestInit) => Promise<T>;
@@ -131,6 +200,14 @@ declare class JobsResource {
131
200
  waitUntilComplete(id: string, options?: WaitOptions): Promise<Job>;
132
201
  }
133
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
+ }
134
211
  declare class DocumentsResource {
135
212
  private readonly req;
136
213
  private readonly slug?;
@@ -144,6 +221,41 @@ declare class DocumentsResource {
144
221
  limit?: number;
145
222
  offset?: number;
146
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>>;
236
+ /**
237
+ * Rename a document.
238
+ * @param id - The document ID.
239
+ * @param name - The new display name.
240
+ */
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>;
147
259
  /**
148
260
  * Delete a document and all its chunks from this collection.
149
261
  * @param id - The document ID.
@@ -151,23 +263,36 @@ declare class DocumentsResource {
151
263
  delete(id: string): Promise<void>;
152
264
  }
153
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
+
154
279
  interface RagOptions {
155
280
  /** Slug of the prompt template to use. Defaults to `"basic_rag"` if omitted. */
156
281
  promptSlug?: string;
157
282
  /** The user's question. Substituted into `{{question}}`. */
158
283
  query: string;
159
- /** Number of chunks to retrieve. Defaults to 5. */
284
+ /** Number of chunks to retrieve. Defaults to 2, since RAG chunks feed an LLM prompt. */
160
285
  topK?: number;
161
286
  /** LLM model name to use (e.g. `"gpt-4o"`, `"claude-opus-4-8"`). Falls back to server default. */
162
287
  model?: string;
163
288
  /** Minimum similarity score (0–100) a chunk must meet to be included in context. Omit to include all top_k results. */
164
289
  minSimilarity?: number;
165
- }
166
- interface FindSimilarOptions {
167
- /** The search query text. */
168
- query: string;
169
- /** Number of results to return. Defaults to 5. */
170
- 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;
171
296
  }
172
297
  interface IngestUriOptions {
173
298
  /** Remote file URI (e.g. `s3://bucket/file.pdf`). */
@@ -347,4 +472,4 @@ declare class RagPack {
347
472
  collection(slug: string): CollectionClient;
348
473
  }
349
474
 
350
- 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
@@ -39,6 +39,7 @@ interface Job {
39
39
  collection_id: string;
40
40
  file_uri: string;
41
41
  mime_type: string;
42
+ display_name?: string;
42
43
  status: "pending" | "processing" | "complete" | "failed";
43
44
  error?: string;
44
45
  created_at: string;
@@ -50,6 +51,7 @@ interface Document {
50
51
  job_id: string;
51
52
  file_uri: string;
52
53
  mime_type: string;
54
+ name?: string;
53
55
  chunk_count: number;
54
56
  status: "ingesting" | "complete" | "failed";
55
57
  error?: string;
@@ -72,7 +74,14 @@ interface RagChunk {
72
74
  chunk_index: number;
73
75
  chunk_header: string | null;
74
76
  chunk_text: string | null;
75
- 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;
76
85
  }
77
86
  interface RagResult {
78
87
  formatted_prompt: string;
@@ -85,13 +94,73 @@ interface QueryResult {
85
94
  file_uri: string;
86
95
  mime_type: string;
87
96
  chunk_index: number;
97
+ chunk_header: string | null;
88
98
  /** The matched text chunk. */
89
99
  chunk_text: string | null;
90
100
  /** Optional metadata attached at ingest time. */
91
101
  extra_json: string | null;
92
- distance: number;
93
- /** Cosine similarity score between 0 and 1. Higher is more relevant. */
94
- 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;
95
164
  }
96
165
 
97
166
  type Requester = <T>(path: string, init?: RequestInit) => Promise<T>;
@@ -131,6 +200,14 @@ declare class JobsResource {
131
200
  waitUntilComplete(id: string, options?: WaitOptions): Promise<Job>;
132
201
  }
133
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
+ }
134
211
  declare class DocumentsResource {
135
212
  private readonly req;
136
213
  private readonly slug?;
@@ -144,6 +221,41 @@ declare class DocumentsResource {
144
221
  limit?: number;
145
222
  offset?: number;
146
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>>;
236
+ /**
237
+ * Rename a document.
238
+ * @param id - The document ID.
239
+ * @param name - The new display name.
240
+ */
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>;
147
259
  /**
148
260
  * Delete a document and all its chunks from this collection.
149
261
  * @param id - The document ID.
@@ -151,23 +263,36 @@ declare class DocumentsResource {
151
263
  delete(id: string): Promise<void>;
152
264
  }
153
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
+
154
279
  interface RagOptions {
155
280
  /** Slug of the prompt template to use. Defaults to `"basic_rag"` if omitted. */
156
281
  promptSlug?: string;
157
282
  /** The user's question. Substituted into `{{question}}`. */
158
283
  query: string;
159
- /** Number of chunks to retrieve. Defaults to 5. */
284
+ /** Number of chunks to retrieve. Defaults to 2, since RAG chunks feed an LLM prompt. */
160
285
  topK?: number;
161
286
  /** LLM model name to use (e.g. `"gpt-4o"`, `"claude-opus-4-8"`). Falls back to server default. */
162
287
  model?: string;
163
288
  /** Minimum similarity score (0–100) a chunk must meet to be included in context. Omit to include all top_k results. */
164
289
  minSimilarity?: number;
165
- }
166
- interface FindSimilarOptions {
167
- /** The search query text. */
168
- query: string;
169
- /** Number of results to return. Defaults to 5. */
170
- 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;
171
296
  }
172
297
  interface IngestUriOptions {
173
298
  /** Remote file URI (e.g. `s3://bucket/file.pdf`). */
@@ -347,4 +472,4 @@ declare class RagPack {
347
472
  collection(slug: string): CollectionClient;
348
473
  }
349
474
 
350
- 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,62 @@ 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
+ }
113
+ /**
114
+ * Rename a document.
115
+ * @param id - The document ID.
116
+ * @param name - The new display name.
117
+ */
118
+ rename(id, name) {
119
+ return this.req(`/collections/${this.slug}/documents/${id}`, {
120
+ method: "PATCH",
121
+ body: JSON.stringify({ name })
122
+ });
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
+ }
94
150
  /**
95
151
  * Delete a document and all its chunks from this collection.
96
152
  * @param id - The document ID.
@@ -102,6 +158,29 @@ var DocumentsResource = class {
102
158
  }
103
159
  };
104
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
+
105
184
  // src/resources/query.ts
106
185
  var QueryResource = class {
107
186
  constructor(req, slug) {
@@ -117,7 +196,11 @@ var QueryResource = class {
117
196
  `/collections/${this.slug}/query`,
118
197
  {
119
198
  method: "POST",
120
- 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
+ })
121
204
  }
122
205
  );
123
206
  return r.results;
@@ -177,7 +260,8 @@ var CollectionClient = class {
177
260
  top_k: options.topK ?? 5,
178
261
  ...options.promptSlug ? { prompt_slug: options.promptSlug } : {},
179
262
  ...options.model ? { model: options.model } : {},
180
- ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {}
263
+ ...options.minSimilarity != null ? { min_similarity: options.minSimilarity } : {},
264
+ ...buildHybridBody(options)
181
265
  })
182
266
  });
183
267
  }
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "ragpack-js",
3
- "version": "0.1.2",
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": [