@dbx-tools/search 0.6.9
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 +297 -0
- package/index.ts +28 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +22 -0
- package/lib/src/client.d.ts +304 -0
- package/lib/src/client.js +598 -0
- package/lib/src/config.d.ts +154 -0
- package/lib/src/config.js +225 -0
- package/lib/src/index-tools.d.ts +19 -0
- package/lib/src/index-tools.js +34 -0
- package/lib/src/lakebase.d.ts +86 -0
- package/lib/src/lakebase.js +218 -0
- package/lib/src/plugin.d.ts +216 -0
- package/lib/src/plugin.js +506 -0
- package/lib/src/query.d.ts +64 -0
- package/lib/src/query.js +109 -0
- package/lib/src/runtime.d.ts +38 -0
- package/lib/src/runtime.js +38 -0
- package/lib/src/schema.d.ts +83 -0
- package/lib/src/schema.js +64 -0
- package/lib/src/tool.d.ts +116 -0
- package/lib/src/tool.js +145 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +65 -0
- package/src/client.ts +848 -0
- package/src/config.ts +356 -0
- package/src/index-tools.ts +40 -0
- package/src/lakebase.ts +299 -0
- package/src/plugin.ts +582 -0
- package/src/query.ts +127 -0
- package/src/runtime.ts +64 -0
- package/src/schema.ts +75 -0
- package/src/tool.ts +169 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, Meilisearch-shaped client over Databricks AI Search (Vector
|
|
3
|
+
* Search). The whole point of this module is ergonomics: the Databricks SDK's
|
|
4
|
+
* `vectorSearchIndexes.queryIndex({ index_name, columns, query_text,
|
|
5
|
+
* query_type, num_results, filters_json })` is powerful but verbose, and the
|
|
6
|
+
* response is columnar. This client hides all of that behind two objects:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const client = createSearchClient();
|
|
10
|
+
* const index = client.index("main.support.docs");
|
|
11
|
+
* const { hits } = await index.search("reset my password", { limit: 5 });
|
|
12
|
+
* await index.addDocuments([{ id: "42", title: "Reset", body: "..." }]);
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* `client.search(query, opts)` searches the default index; `client.index(name)`
|
|
16
|
+
* returns a handle bound to one index; `client.universalSearch(query)` fans a
|
|
17
|
+
* query across several indexes and merges the results (Meilisearch's federated
|
|
18
|
+
* / multi-search, the "universal search" the caller asked for). Everything is
|
|
19
|
+
* async and cancellable, resolves the OBO workspace client from the active
|
|
20
|
+
* AppKit execution context (falling back to a service-principal client outside
|
|
21
|
+
* a request), and returns the browser-safe shapes from
|
|
22
|
+
* `@dbx-tools/shared-search`.
|
|
23
|
+
*
|
|
24
|
+
* Autocomplete is just a search with a small `limit` and the raw query text -
|
|
25
|
+
* hybrid mode already prefix-matches, so no separate endpoint is needed; the
|
|
26
|
+
* {@link SearchIndex.autocomplete} helper is a thin, self-documenting alias.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { appkit } from "@dbx-tools/appkit";
|
|
31
|
+
import type { SearchDocument, SearchMode, SearchResult, UpsertResult } from "@dbx-tools/shared-search";
|
|
32
|
+
import { type ResolvedSearchConfig } from "./config.ts";
|
|
33
|
+
import type { LakebaseSearchBackend } from "./lakebase.ts";
|
|
34
|
+
type WorkspaceClientLike = appkit.WorkspaceClientLike;
|
|
35
|
+
/** Options accepted by a single-index search. */
|
|
36
|
+
export interface SearchOptions {
|
|
37
|
+
/** Maximum hits to return. Defaults to the configured page size. */
|
|
38
|
+
limit?: number;
|
|
39
|
+
/** Match mode. Defaults to the configured mode (hybrid). */
|
|
40
|
+
mode?: SearchMode;
|
|
41
|
+
/** Columns to return per hit. Defaults to the index's configured columns. */
|
|
42
|
+
columns?: readonly string[];
|
|
43
|
+
/** Attribute filters as `{ column: value }` or `{ column: { ">=": n } }`. */
|
|
44
|
+
filter?: Record<string, unknown>;
|
|
45
|
+
/** Drop hits below this score. */
|
|
46
|
+
scoreThreshold?: number;
|
|
47
|
+
/** External cancellation. */
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
}
|
|
50
|
+
/** Options accepted by a universal (federated) search. */
|
|
51
|
+
export interface UniversalSearchOptions {
|
|
52
|
+
/** Indexes to search. Defaults to every known index. */
|
|
53
|
+
indexes?: readonly string[];
|
|
54
|
+
/** Maximum hits per index before merging. */
|
|
55
|
+
limit?: number;
|
|
56
|
+
/** Match mode. */
|
|
57
|
+
mode?: SearchMode;
|
|
58
|
+
/** External cancellation. */
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A handle bound to one index. Modeled on Meilisearch's `client.index(uid)`:
|
|
63
|
+
* `search` / `autocomplete` read, `addDocuments` / `deleteDocuments` write (to
|
|
64
|
+
* a direct-access index), and `info` fetches the live definition.
|
|
65
|
+
*/
|
|
66
|
+
export declare class SearchIndex {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
private readonly client;
|
|
69
|
+
constructor(name: string, client: SearchClient);
|
|
70
|
+
/** Search this index. See {@link SearchClient.search}. */
|
|
71
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult>;
|
|
72
|
+
/**
|
|
73
|
+
* Autocomplete against this index: a search with a small default `limit` and
|
|
74
|
+
* the raw prefix as the query. Hybrid mode already handles prefixes, so this
|
|
75
|
+
* is a self-documenting alias rather than a separate code path.
|
|
76
|
+
*/
|
|
77
|
+
autocomplete(prefix: string, options?: SearchOptions): Promise<SearchResult>;
|
|
78
|
+
/** Add or update documents in this (direct-access) index. */
|
|
79
|
+
addDocuments(documents: SearchDocument[], signal?: AbortSignal): Promise<UpsertResult>;
|
|
80
|
+
/** Delete documents from this (direct-access) index by primary key. */
|
|
81
|
+
deleteDocuments(ids: Array<string | number>, signal?: AbortSignal): Promise<UpsertResult>;
|
|
82
|
+
/** Fetch this index's live definition (primary key, columns, readiness). */
|
|
83
|
+
info(signal?: AbortSignal): Promise<IndexInfo>;
|
|
84
|
+
/** Trigger a sync of this (Delta Sync) index from its source table. */
|
|
85
|
+
sync(signal?: AbortSignal): Promise<void>;
|
|
86
|
+
/** Delete this index. */
|
|
87
|
+
delete(signal?: AbortSignal): Promise<void>;
|
|
88
|
+
/** Create this index if it does not exist, otherwise return the existing one. */
|
|
89
|
+
ensure(options?: CreateIndexOptions): Promise<IndexInfo>;
|
|
90
|
+
}
|
|
91
|
+
/** A resolved live index definition. */
|
|
92
|
+
export interface IndexInfo {
|
|
93
|
+
name: string;
|
|
94
|
+
endpoint?: string;
|
|
95
|
+
primaryKey?: string;
|
|
96
|
+
columns: string[];
|
|
97
|
+
ready: boolean;
|
|
98
|
+
rowCount?: number;
|
|
99
|
+
/** True for a DIRECT_ACCESS index (you supply vectors; queries embed client-side). */
|
|
100
|
+
directAccess?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Options for {@link SearchClient.createIndex} / {@link SearchClient.ensureIndex}.
|
|
104
|
+
* The goal is the same as the rest of the client: name a source table and a
|
|
105
|
+
* text column, and everything else - the endpoint, the embedding model, the
|
|
106
|
+
* index type, the sync mode - has a sensible default that infers from the
|
|
107
|
+
* workspace, overridable when a deployment needs to go deeper.
|
|
108
|
+
*/
|
|
109
|
+
export interface CreateIndexOptions {
|
|
110
|
+
/**
|
|
111
|
+
* Source Delta table (catalog.schema.table) for a Delta Sync index. Provide
|
|
112
|
+
* this for the common case: Databricks computes and syncs embeddings from it.
|
|
113
|
+
* Omit it (and pass {@link embeddingDimension}) to create a direct-access
|
|
114
|
+
* index you write vectors to yourself.
|
|
115
|
+
*/
|
|
116
|
+
sourceTable?: string;
|
|
117
|
+
/** Primary-key column. Defaults to `id`. */
|
|
118
|
+
primaryKey?: string;
|
|
119
|
+
/**
|
|
120
|
+
* The text column embeddings are computed from (Delta Sync). Defaults to the
|
|
121
|
+
* first of `text` / `content` / `body` present, else the caller must set it.
|
|
122
|
+
*/
|
|
123
|
+
embeddingSourceColumn?: string;
|
|
124
|
+
/**
|
|
125
|
+
* Embedding model endpoint. A loose name is fuzzy-matched; when omitted the
|
|
126
|
+
* best embedding endpoint in the workspace is chosen ({@link resolveEmbeddingModel}).
|
|
127
|
+
*/
|
|
128
|
+
embeddingModel?: string;
|
|
129
|
+
/** Vector Search endpoint to host the index on. Defaults to the plugin's `endpoint`. */
|
|
130
|
+
endpoint?: string;
|
|
131
|
+
/** For a direct-access index: the embedding vector dimension (self-managed vectors, no source table). */
|
|
132
|
+
embeddingDimension?: number;
|
|
133
|
+
/** For a direct-access index: the column the vector is stored in. Defaults to `embedding`. */
|
|
134
|
+
embeddingVectorColumn?: string;
|
|
135
|
+
/**
|
|
136
|
+
* The column-name -> type map used to build a direct-access index's
|
|
137
|
+
* `schema_json` (types: `string`, `int`, `long`, `float`, `double`,
|
|
138
|
+
* `boolean`, `date`, `timestamp`). Must include the primary key and any
|
|
139
|
+
* columns you upsert. Defaults to `{ id: "string", text: "string" }`. The
|
|
140
|
+
* embedding vector column is added automatically.
|
|
141
|
+
*/
|
|
142
|
+
schema?: Record<string, string>;
|
|
143
|
+
/** Sync mode for a Delta Sync index. `TRIGGERED` (default) syncs on demand; `CONTINUOUS` keeps fresh. */
|
|
144
|
+
pipelineType?: "TRIGGERED" | "CONTINUOUS";
|
|
145
|
+
/** Extra columns to sync alongside the embedding source (Delta Sync). */
|
|
146
|
+
columnsToSync?: string[];
|
|
147
|
+
/** External cancellation. */
|
|
148
|
+
signal?: AbortSignal;
|
|
149
|
+
}
|
|
150
|
+
/** Options for {@link SearchClient.ensureEndpoint}. */
|
|
151
|
+
export interface EnsureEndpointOptions {
|
|
152
|
+
/** Wait for the endpoint to come online before returning. Defaults to false. */
|
|
153
|
+
wait?: boolean;
|
|
154
|
+
/** External cancellation. */
|
|
155
|
+
signal?: AbortSignal;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Options for {@link SearchClient.provision} - a one-call "make this index real
|
|
159
|
+
* and searchable" used at boot or in a seed script. It ensures the endpoint,
|
|
160
|
+
* ensures the index, and (optionally) seeds documents when the index is empty.
|
|
161
|
+
*/
|
|
162
|
+
export interface ProvisionOptions extends CreateIndexOptions {
|
|
163
|
+
/** Documents to seed when the index has no rows yet. Skipped if it already has data. */
|
|
164
|
+
seed?: SearchDocument[];
|
|
165
|
+
/**
|
|
166
|
+
* Wait for the endpoint AND index to come online before returning (needed
|
|
167
|
+
* before seeding). Defaults to true. Endpoint creation can take many minutes.
|
|
168
|
+
*/
|
|
169
|
+
wait?: boolean;
|
|
170
|
+
/** How long to wait for readiness before giving up. Defaults to 20 minutes. */
|
|
171
|
+
timeoutMs?: number;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The AI Search client. Construct it with {@link createSearchClient} (which
|
|
175
|
+
* reads a resolved config) or directly for one-off use. All reads resolve the
|
|
176
|
+
* OBO workspace client from the active execution context.
|
|
177
|
+
*/
|
|
178
|
+
export declare class SearchClient {
|
|
179
|
+
private readonly config;
|
|
180
|
+
private readonly workspaceClientFactory;
|
|
181
|
+
/**
|
|
182
|
+
* Optional Lakebase full-text FALLBACK backend. Present only when no Vector
|
|
183
|
+
* Search endpoint is configured but a Lakebase pool is available; when set,
|
|
184
|
+
* search / provision / write operations delegate to it and return the exact
|
|
185
|
+
* same shapes, so nothing downstream can tell which backend answered.
|
|
186
|
+
*/
|
|
187
|
+
private readonly lakebase?;
|
|
188
|
+
constructor(config?: ResolvedSearchConfig, workspaceClientFactory?: () => WorkspaceClientLike,
|
|
189
|
+
/**
|
|
190
|
+
* Optional Lakebase full-text FALLBACK backend. Present only when no Vector
|
|
191
|
+
* Search endpoint is configured but a Lakebase pool is available; when set,
|
|
192
|
+
* search / provision / write operations delegate to it and return the exact
|
|
193
|
+
* same shapes, so nothing downstream can tell which backend answered.
|
|
194
|
+
*/
|
|
195
|
+
lakebase?: LakebaseSearchBackend | undefined);
|
|
196
|
+
/** True when this client is answering out of the Lakebase fallback backend. */
|
|
197
|
+
get usesLakebase(): boolean;
|
|
198
|
+
/** A handle bound to one index (by full UC name or configured alias). */
|
|
199
|
+
index(reference: string): SearchIndex;
|
|
200
|
+
/**
|
|
201
|
+
* Search one index. `index` may be a full UC name, a configured alias, or
|
|
202
|
+
* omitted to use the default index. Returns hits sorted most-relevant-first.
|
|
203
|
+
*/
|
|
204
|
+
search(query: string, options?: SearchOptions & {
|
|
205
|
+
index?: string;
|
|
206
|
+
}): Promise<SearchResult>;
|
|
207
|
+
/** Cache of index-name -> is-DIRECT_ACCESS, so a search embeds its query only when needed. */
|
|
208
|
+
private readonly directAccessCache;
|
|
209
|
+
/** Whether an index is DIRECT_ACCESS (memoized); a lookup failure assumes Delta Sync. */
|
|
210
|
+
private isDirectAccess;
|
|
211
|
+
/**
|
|
212
|
+
* Fan a query across several indexes and merge the hits, sorted by score -
|
|
213
|
+
* the "universal search" a single box over many collections needs. Each
|
|
214
|
+
* index is searched concurrently; an index that errors is logged and skipped
|
|
215
|
+
* so one bad index does not sink the whole search.
|
|
216
|
+
*/
|
|
217
|
+
universalSearch(query: string, options?: UniversalSearchOptions): Promise<SearchResult>;
|
|
218
|
+
/**
|
|
219
|
+
* Resolve an index reference (full UC name or configured alias) to the name
|
|
220
|
+
* the API expects, failing fast when it resolves to nothing. Without this a
|
|
221
|
+
* blank reference - what an omitted `index` becomes when no default is
|
|
222
|
+
* configured - reaches the SDK as an empty `index_name`, which builds a
|
|
223
|
+
* URL with the name segment missing and comes back as a confusing
|
|
224
|
+
* `ENDPOINT_NOT_FOUND` instead of naming the real problem.
|
|
225
|
+
*/
|
|
226
|
+
private requireIndexName;
|
|
227
|
+
/** Fetch an index's live definition. */
|
|
228
|
+
getIndex(reference: string, signal?: AbortSignal): Promise<IndexInfo>;
|
|
229
|
+
/** Add or update documents in a direct-access index. */
|
|
230
|
+
addDocuments(reference: string, documents: SearchDocument[], signal?: AbortSignal): Promise<UpsertResult>;
|
|
231
|
+
/** Delete documents from a direct-access index by primary key. */
|
|
232
|
+
deleteDocuments(reference: string, ids: Array<string | number>, signal?: AbortSignal): Promise<UpsertResult>;
|
|
233
|
+
/**
|
|
234
|
+
* Resolve an embedding endpoint id for creating a Delta Sync index. Reuses
|
|
235
|
+
* the model resolver: a configured / passed name is fuzzy-matched against the
|
|
236
|
+
* live catalogue, otherwise the highest-ranked embedding endpoint is chosen.
|
|
237
|
+
*/
|
|
238
|
+
resolveEmbeddingModel(requested?: string, signal?: AbortSignal): Promise<string | null>;
|
|
239
|
+
/**
|
|
240
|
+
* Embed text via a Databricks embedding serving endpoint, returning one
|
|
241
|
+
* vector per input. Used to seed a direct-access index and to turn a search
|
|
242
|
+
* query into a query vector - Databricks only manages embeddings for Delta
|
|
243
|
+
* Sync indexes, so a direct-access index (no Delta table, no warehouse) needs
|
|
244
|
+
* the client to embed on write and on query. The endpoint is resolved the
|
|
245
|
+
* same way as for index creation when not named.
|
|
246
|
+
*/
|
|
247
|
+
embed(texts: string[], model?: string, signal?: AbortSignal): Promise<number[][]>;
|
|
248
|
+
/** The vector dimension a model produces (embeds a probe string once). */
|
|
249
|
+
private embeddingDimension;
|
|
250
|
+
/**
|
|
251
|
+
* Create an AI Search index with as little ceremony as possible. Two shapes:
|
|
252
|
+
*
|
|
253
|
+
* - **Delta Sync** (the default): pass `sourceTable`; Databricks computes
|
|
254
|
+
* embeddings from the text column and keeps the index synced. The
|
|
255
|
+
* embedding model is resolved automatically when not named.
|
|
256
|
+
* - **Direct Access**: omit `sourceTable` and pass `embeddingDimension`;
|
|
257
|
+
* you write vectors yourself via {@link addDocuments}.
|
|
258
|
+
*
|
|
259
|
+
* Everything else infers: the endpoint from the plugin config, the primary
|
|
260
|
+
* key (`id`), the text column (`text` / `content` / `body`), and the vector
|
|
261
|
+
* column (`embedding`). Returns the created index's {@link IndexInfo}.
|
|
262
|
+
*/
|
|
263
|
+
createIndex(name: string, options?: CreateIndexOptions): Promise<IndexInfo>;
|
|
264
|
+
/**
|
|
265
|
+
* Create the index if it does not already exist, otherwise return the
|
|
266
|
+
* existing one. Idempotent - safe to call on every boot to guarantee an
|
|
267
|
+
* index is present.
|
|
268
|
+
*/
|
|
269
|
+
ensureIndex(name: string, options?: CreateIndexOptions): Promise<IndexInfo>;
|
|
270
|
+
/**
|
|
271
|
+
* Ensure an index exists, is online, and (optionally) holds seed data - the
|
|
272
|
+
* "wire up a real index on boot" path. Idempotent and cheap to call every
|
|
273
|
+
* boot: it creates the endpoint and index only if missing, waits for them to
|
|
274
|
+
* come online, and seeds documents ONLY when the index is still empty.
|
|
275
|
+
*
|
|
276
|
+
* For the demo/dummy-data case this needs no Delta table and no warehouse:
|
|
277
|
+
* the default is a MANAGED direct-access index (Databricks embeds the `text`
|
|
278
|
+
* column on write and query), so seeding is just an `addDocuments` of plain
|
|
279
|
+
* rows and search-by-text works immediately.
|
|
280
|
+
*/
|
|
281
|
+
provision(name: string, options?: ProvisionOptions): Promise<IndexInfo>;
|
|
282
|
+
/** Poll an index until it reports ready, or throw after {@link timeoutMs}. */
|
|
283
|
+
private waitForIndexReady;
|
|
284
|
+
/** Trigger a sync of a Delta Sync index from its source table. */
|
|
285
|
+
syncIndex(reference: string, signal?: AbortSignal): Promise<void>;
|
|
286
|
+
/** Delete an index. */
|
|
287
|
+
deleteIndex(reference: string, signal?: AbortSignal): Promise<void>;
|
|
288
|
+
/** List the indexes hosted on a Vector Search endpoint (name + type only). */
|
|
289
|
+
listIndexes(endpoint?: string, signal?: AbortSignal): Promise<string[]>;
|
|
290
|
+
/**
|
|
291
|
+
* Ensure a Vector Search endpoint exists, creating a `STANDARD` one when it
|
|
292
|
+
* does not. Optionally wait for it to come online. Idempotent.
|
|
293
|
+
*/
|
|
294
|
+
ensureEndpoint(name?: string, options?: EnsureEndpointOptions): Promise<void>;
|
|
295
|
+
/**
|
|
296
|
+
* Resolve the workspace client and run one call under a bounded timeout. The
|
|
297
|
+
* caller's signal (if any) and a timeout are merged into one SDK `Context`
|
|
298
|
+
* via {@link appkit.databricks.toContext}, so either unwinds the request.
|
|
299
|
+
*/
|
|
300
|
+
private withClient;
|
|
301
|
+
}
|
|
302
|
+
/** Construct a {@link SearchClient} from a resolved config. */
|
|
303
|
+
export declare function createSearchClient(config?: ResolvedSearchConfig, workspaceClientFactory?: () => WorkspaceClientLike, lakebase?: LakebaseSearchBackend): SearchClient;
|
|
304
|
+
export {};
|