@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
package/src/query.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation between the browser-safe search contract and the Databricks
|
|
3
|
+
* Vector Search query API, kept in one place so the client, the tools, and the
|
|
4
|
+
* routes never hand-roll a `filters_json` string or unpack a `data_array` by
|
|
5
|
+
* hand.
|
|
6
|
+
*
|
|
7
|
+
* - {@link toQueryType} maps the friendly {@link SearchMode} onto the API's
|
|
8
|
+
* `query_type` (`HYBRID` / `ANN`). Keyword-only search rides on `ANN` with
|
|
9
|
+
* text but no vector, which Databricks answers with its BM25 path.
|
|
10
|
+
* - {@link compileFilter} turns the `{ column: value }` filter object (with
|
|
11
|
+
* optional operator maps like `{ ">=": 10 }`) into the `filters_json`
|
|
12
|
+
* string the API expects, so a caller never learns Databricks' filter
|
|
13
|
+
* spelling.
|
|
14
|
+
* - {@link toHits} unpacks the columnar `{ manifest, result }` response into
|
|
15
|
+
* `{ id, score, fields }` hits, pulling the score out of the reserved
|
|
16
|
+
* `__db_score` / `score` column and the id out of the primary-key column.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { ValidationError } from "@databricks/appkit";
|
|
22
|
+
import type { SearchHit, SearchMode } from "@dbx-tools/shared-search";
|
|
23
|
+
import { json, object } from "@dbx-tools/shared-core";
|
|
24
|
+
|
|
25
|
+
/** The column name Databricks Vector Search returns the relevance score under. */
|
|
26
|
+
const SCORE_COLUMN = "__db_score";
|
|
27
|
+
|
|
28
|
+
/** Map a {@link SearchMode} onto the serving API `query_type`. */
|
|
29
|
+
export function toQueryType(mode: SearchMode): string {
|
|
30
|
+
return mode === "hybrid" ? "HYBRID" : "ANN";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compile a `{ column: value }` filter object into the `filters_json` string
|
|
35
|
+
* the query API expects. A scalar becomes an equality; an array becomes an
|
|
36
|
+
* IN-style match; an operator map (`{ ">=": 10, "<": 20 }`) expands to the
|
|
37
|
+
* `column operator` keys Databricks uses. Returns `undefined` for an empty
|
|
38
|
+
* filter so the field is omitted rather than sent as `{}`.
|
|
39
|
+
*/
|
|
40
|
+
export function compileFilter(filter: Record<string, unknown> | undefined): string | undefined {
|
|
41
|
+
if (!filter || Object.keys(filter).length === 0) return undefined;
|
|
42
|
+
const compiled: Record<string, unknown> = {};
|
|
43
|
+
for (const [column, value] of Object.entries(filter)) {
|
|
44
|
+
if (object.isRecord(value)) {
|
|
45
|
+
for (const [op, operand] of Object.entries(value)) {
|
|
46
|
+
compiled[`${column} ${op}`] = operand;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
compiled[column] = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return JSON.stringify(compiled);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A minimal structural view of the Vector Search query response. */
|
|
56
|
+
export interface QueryResponseLike {
|
|
57
|
+
manifest?: { columns?: Array<{ name?: string }> };
|
|
58
|
+
result?: { data_array?: Array<Array<unknown>> };
|
|
59
|
+
next_page_token?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Unpack a columnar query response into {@link SearchHit}s. The manifest names
|
|
64
|
+
* the columns in order; each row is a positional array. The score comes from
|
|
65
|
+
* the reserved score column and the id from `primaryKey` (falling back to the
|
|
66
|
+
* first column when the key is unknown). The score column is stripped from
|
|
67
|
+
* `fields` so a hit's fields are just the document.
|
|
68
|
+
*/
|
|
69
|
+
export function toHits(
|
|
70
|
+
response: QueryResponseLike,
|
|
71
|
+
primaryKey: string | undefined,
|
|
72
|
+
indexName?: string,
|
|
73
|
+
): SearchHit[] {
|
|
74
|
+
const columns = (response.manifest?.columns ?? []).map((c) => c.name ?? "");
|
|
75
|
+
const rows = response.result?.data_array ?? [];
|
|
76
|
+
const scoreIdx = columns.indexOf(SCORE_COLUMN);
|
|
77
|
+
const keyIdx = primaryKey ? columns.indexOf(primaryKey) : -1;
|
|
78
|
+
return rows.map((row, rowIndex) => {
|
|
79
|
+
const fields: Record<string, unknown> = {};
|
|
80
|
+
columns.forEach((name, i) => {
|
|
81
|
+
if (i === scoreIdx || !name) return;
|
|
82
|
+
fields[name] = row[i];
|
|
83
|
+
});
|
|
84
|
+
const scoreRaw = scoreIdx >= 0 ? Number(row[scoreIdx]) : NaN;
|
|
85
|
+
const idRaw =
|
|
86
|
+
keyIdx >= 0 ? row[keyIdx] : primaryKey ? fields[primaryKey] : (row[0] ?? rowIndex);
|
|
87
|
+
return {
|
|
88
|
+
id: String(idRaw ?? rowIndex),
|
|
89
|
+
score: Number.isFinite(scoreRaw) ? scoreRaw : 0,
|
|
90
|
+
fields,
|
|
91
|
+
...(indexName ? { index: indexName } : {}),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The columns to request from an index for a search. When neither the request
|
|
98
|
+
* nor the index config names columns, the score column alone is requested and
|
|
99
|
+
* the primary key is appended so a hit always has an id. Callers that want the
|
|
100
|
+
* whole document should pass the index's own column list.
|
|
101
|
+
*/
|
|
102
|
+
export function toRequestColumns(
|
|
103
|
+
requested: readonly string[] | undefined,
|
|
104
|
+
fallback: readonly string[] | undefined,
|
|
105
|
+
primaryKey: string | undefined,
|
|
106
|
+
): string[] {
|
|
107
|
+
const base = requested && requested.length > 0 ? requested : (fallback ?? []);
|
|
108
|
+
const columns = new Set<string>(base);
|
|
109
|
+
if (primaryKey) columns.add(primaryKey);
|
|
110
|
+
if (columns.size === 0 && primaryKey) columns.add(primaryKey);
|
|
111
|
+
return [...columns];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parse a JSON document payload the model / a route supplied for a write.
|
|
116
|
+
* Accepts an already-parsed array/object or a JSON string, and always returns
|
|
117
|
+
* an array so a single document and a batch are handled the same way. Throws a
|
|
118
|
+
* {@link ValidationError} on unparseable input so the caller can surface it.
|
|
119
|
+
*/
|
|
120
|
+
export function toDocumentArray(input: unknown): Array<Record<string, unknown>> {
|
|
121
|
+
const value = typeof input === "string" ? json.parse(input, undefined) : input;
|
|
122
|
+
if (value === undefined) {
|
|
123
|
+
throw new ValidationError("documents must be a JSON object, array, or string");
|
|
124
|
+
}
|
|
125
|
+
const list = Array.isArray(value) ? value : [value];
|
|
126
|
+
return list.filter(object.isRecord);
|
|
127
|
+
}
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared AI Search runtime: the resolved config plus a single
|
|
3
|
+
* {@link SearchClient} instance the plugin, the Mastra tools, and the routes
|
|
4
|
+
* all read. Mirrors the web-search / email runtime pattern - the plugin primes
|
|
5
|
+
* it from its config at setup, and everything else reads the same instance so
|
|
6
|
+
* a tool invoked outside the agent still sees the deployment's config.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createSearchClient, SearchClient } from "./client.ts";
|
|
12
|
+
import {
|
|
13
|
+
resolveSearchConfig,
|
|
14
|
+
type SearchPluginConfig,
|
|
15
|
+
type ResolvedSearchConfig,
|
|
16
|
+
} from "./config.ts";
|
|
17
|
+
import { LakebaseSearchBackend } from "./lakebase.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How the runtime is built. `lakebase` (when present) is the Postgres full-text
|
|
21
|
+
* FALLBACK backend the plugin wires up when no Vector Search endpoint is
|
|
22
|
+
* configured but the AppKit `lakebase` plugin is registered. Every read/write
|
|
23
|
+
* returns the same shape either way.
|
|
24
|
+
*/
|
|
25
|
+
export interface SearchRuntimeOptions {
|
|
26
|
+
config?: SearchPluginConfig;
|
|
27
|
+
lakebase?: LakebaseSearchBackend;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The shared resolved config plus the client reads run through. */
|
|
31
|
+
export interface SearchRuntime {
|
|
32
|
+
config: ResolvedSearchConfig;
|
|
33
|
+
client: SearchClient;
|
|
34
|
+
/** The Lakebase fallback backend, when the runtime is backed by Postgres. */
|
|
35
|
+
lakebase?: LakebaseSearchBackend;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let runtime: SearchRuntime | undefined;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Return the shared runtime, building it on first use from the supplied config
|
|
42
|
+
* layered over environment defaults. Overrides are only read when the runtime
|
|
43
|
+
* is first created, so prime it from the plugin's config at setup; subsequent
|
|
44
|
+
* calls pass nothing and get the same instance.
|
|
45
|
+
*/
|
|
46
|
+
export function getSearchRuntime(options?: SearchRuntimeOptions): SearchRuntime {
|
|
47
|
+
if (!runtime) {
|
|
48
|
+
const config = resolveSearchConfig(options?.config);
|
|
49
|
+
const lakebase = options?.lakebase;
|
|
50
|
+
runtime = {
|
|
51
|
+
config,
|
|
52
|
+
client: createSearchClient(config, undefined, lakebase),
|
|
53
|
+
...(lakebase ? { lakebase } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return runtime;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Drop the memoized runtime so the next {@link getSearchRuntime} rebuilds it. */
|
|
60
|
+
export function resetSearchRuntime(): void {
|
|
61
|
+
const backend = runtime?.lakebase;
|
|
62
|
+
runtime = undefined;
|
|
63
|
+
if (backend) void backend.close();
|
|
64
|
+
}
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-facing descriptions and the request schemas the Mastra tools + AppKit
|
|
3
|
+
* tool provider validate against. The wire shapes themselves live in
|
|
4
|
+
* `@dbx-tools/shared-search`; this module adds the model-readable
|
|
5
|
+
* descriptions so both hosts describe the tools identically.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { search } from "@dbx-tools/shared-search";
|
|
11
|
+
import { string } from "@dbx-tools/shared-core";
|
|
12
|
+
|
|
13
|
+
/** Description the model reads for the `search` tool. */
|
|
14
|
+
export const SEARCH_TOOL_DESCRIPTION = string.toDescription(`
|
|
15
|
+
Search a Databricks AI Search (Vector Search) index for the documents most
|
|
16
|
+
relevant to a query. Pass a natural-language question or keywords; hybrid
|
|
17
|
+
matching combines semantic similarity with keyword ranking, so exact terms
|
|
18
|
+
(product names, error codes) and paraphrases both work. Optionally name an
|
|
19
|
+
index (defaults to the app's configured index), a result limit, and attribute
|
|
20
|
+
filters. Use it to look up docs, knowledge-base articles, products, or any
|
|
21
|
+
indexed content before answering.
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
/** Description the model reads for the `universal_search` tool. */
|
|
25
|
+
export const UNIVERSAL_SEARCH_TOOL_DESCRIPTION = string.toDescription(`
|
|
26
|
+
Search across every configured AI Search index at once and return the best
|
|
27
|
+
matches from all of them, merged and ranked. Use it when the right index
|
|
28
|
+
isn't known in advance or when an answer may live in any of several
|
|
29
|
+
collections (docs, tickets, products).
|
|
30
|
+
`);
|
|
31
|
+
|
|
32
|
+
/** Description the model reads for the `add_documents` tool. */
|
|
33
|
+
export const ADD_DOCUMENTS_TOOL_DESCRIPTION = string.toDescription(`
|
|
34
|
+
Add or update documents in a direct-access AI Search index. Pass an array of
|
|
35
|
+
documents as JSON objects; each MUST include the index's primary-key column.
|
|
36
|
+
Only available when the app enables the write surface. Use it to index new
|
|
37
|
+
content the user provides.
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
/** Description the model reads for the `create_index` tool. */
|
|
41
|
+
export const CREATE_INDEX_TOOL_DESCRIPTION = string.toDescription(`
|
|
42
|
+
Create a Databricks AI Search (Vector Search) index. For the common case pass
|
|
43
|
+
a Delta source table (catalog.schema.table): Databricks computes embeddings
|
|
44
|
+
from its text column and keeps the index synced. To create a direct-access
|
|
45
|
+
index you write vectors to yourself, pass an embedding dimension instead of a
|
|
46
|
+
source table. The endpoint, embedding model, primary key, and text column are
|
|
47
|
+
inferred when omitted. Only available when the app enables the write surface.
|
|
48
|
+
Creating an index provisions infrastructure - do this only when the user
|
|
49
|
+
explicitly asks to set up a new index.
|
|
50
|
+
`);
|
|
51
|
+
|
|
52
|
+
/** Description the model reads for the `sync_index` tool. */
|
|
53
|
+
export const SYNC_INDEX_TOOL_DESCRIPTION = string.toDescription(`
|
|
54
|
+
Refresh a Delta Sync AI Search index from its source table so newly added or
|
|
55
|
+
changed rows become searchable. Optionally name the index (defaults to the
|
|
56
|
+
app's default index). Only available when the app enables the write surface.
|
|
57
|
+
`);
|
|
58
|
+
|
|
59
|
+
/** Schema for the `search` tool input (the shared request schema). */
|
|
60
|
+
export const searchToolSchema = search.searchRequestSchema;
|
|
61
|
+
|
|
62
|
+
/** Schema for the `universal_search` tool input. */
|
|
63
|
+
export const universalSearchToolSchema = search.universalSearchRequestSchema;
|
|
64
|
+
|
|
65
|
+
/** Schema for the `search` / `universal_search` tool output. */
|
|
66
|
+
export const searchResultSchema = search.searchResultSchema;
|
|
67
|
+
|
|
68
|
+
/** Schema for the `create_index` tool input. */
|
|
69
|
+
export const createIndexToolSchema = search.createIndexRequestSchema;
|
|
70
|
+
|
|
71
|
+
/** Schema for the `create_index` tool output (a resolved index definition). */
|
|
72
|
+
export const indexInfoSchema = search.indexInfoSchema;
|
|
73
|
+
|
|
74
|
+
/** Schema for the `sync_index` tool input. */
|
|
75
|
+
export const syncIndexToolSchema = search.syncIndexRequestSchema;
|
package/src/tool.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `search`, `universal_search`, and (opt-in) `add_documents`,
|
|
3
|
+
* `create_index`, and `sync_index` Mastra tools.
|
|
4
|
+
*
|
|
5
|
+
* All three read the shared runtime primed by the plugin, so a tool spread
|
|
6
|
+
* into an agent uses the deployment's default index, columns, page size, and
|
|
7
|
+
* mode without any per-tool wiring. They run under the caller's OBO scope (the
|
|
8
|
+
* client resolves the execution context's workspace client), so search runs as
|
|
9
|
+
* the requesting user and Unity Catalog ACLs apply.
|
|
10
|
+
*
|
|
11
|
+
* The same tools are exposed to AppKit's own agents through the plugin's
|
|
12
|
+
* `ToolProvider` (see `plugin.ts`); this module is the Mastra half.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { search as searchContract, type UpsertResult } from "@dbx-tools/shared-search";
|
|
18
|
+
import { createTool } from "@mastra/core/tools";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { toCreateIndexOptions } from "./index-tools.ts";
|
|
21
|
+
import { toDocumentArray } from "./query.ts";
|
|
22
|
+
import { getSearchRuntime } from "./runtime.ts";
|
|
23
|
+
import {
|
|
24
|
+
ADD_DOCUMENTS_TOOL_DESCRIPTION,
|
|
25
|
+
CREATE_INDEX_TOOL_DESCRIPTION,
|
|
26
|
+
SEARCH_TOOL_DESCRIPTION,
|
|
27
|
+
SYNC_INDEX_TOOL_DESCRIPTION,
|
|
28
|
+
UNIVERSAL_SEARCH_TOOL_DESCRIPTION,
|
|
29
|
+
createIndexToolSchema,
|
|
30
|
+
indexInfoSchema,
|
|
31
|
+
searchResultSchema,
|
|
32
|
+
searchToolSchema,
|
|
33
|
+
syncIndexToolSchema,
|
|
34
|
+
universalSearchToolSchema,
|
|
35
|
+
} from "./schema.ts";
|
|
36
|
+
|
|
37
|
+
/** Common option accepted by every tool factory: override the tool id. */
|
|
38
|
+
export interface SearchToolOptions {
|
|
39
|
+
/** Override the tool id (defaults per tool). */
|
|
40
|
+
id?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the `search` tool. Spread it into any agent that should be able to look
|
|
45
|
+
* things up in an index.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { searchTool } from "@dbx-tools/search";
|
|
50
|
+
* import { createAgent } from "@dbx-tools/appkit-mastra";
|
|
51
|
+
*
|
|
52
|
+
* const support = createAgent({
|
|
53
|
+
* instructions: "Answer from the docs. Use `search` to find them.",
|
|
54
|
+
* tools: () => ({ search: searchTool() }),
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function searchTool(options: SearchToolOptions = {}) {
|
|
59
|
+
return createTool({
|
|
60
|
+
id: options.id ?? "search",
|
|
61
|
+
description: SEARCH_TOOL_DESCRIPTION,
|
|
62
|
+
inputSchema: searchToolSchema,
|
|
63
|
+
outputSchema: searchResultSchema,
|
|
64
|
+
execute: async (input, context) => {
|
|
65
|
+
const request = searchToolSchema.parse(input);
|
|
66
|
+
const { client } = getSearchRuntime();
|
|
67
|
+
return client.search(request.query, {
|
|
68
|
+
...(request.index ? { index: request.index } : {}),
|
|
69
|
+
...(request.limit ? { limit: request.limit } : {}),
|
|
70
|
+
...(request.mode ? { mode: request.mode } : {}),
|
|
71
|
+
...(request.columns ? { columns: request.columns } : {}),
|
|
72
|
+
...(request.filter ? { filter: request.filter } : {}),
|
|
73
|
+
...(request.scoreThreshold !== undefined ? { scoreThreshold: request.scoreThreshold } : {}),
|
|
74
|
+
...(context?.abortSignal ? { signal: context.abortSignal } : {}),
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Build the `universal_search` tool (federated search across every index). */
|
|
81
|
+
export function universalSearchTool(options: SearchToolOptions = {}) {
|
|
82
|
+
return createTool({
|
|
83
|
+
id: options.id ?? "universal_search",
|
|
84
|
+
description: UNIVERSAL_SEARCH_TOOL_DESCRIPTION,
|
|
85
|
+
inputSchema: universalSearchToolSchema,
|
|
86
|
+
outputSchema: searchResultSchema,
|
|
87
|
+
execute: async (input, context) => {
|
|
88
|
+
const request = universalSearchToolSchema.parse(input);
|
|
89
|
+
const { client } = getSearchRuntime();
|
|
90
|
+
return client.universalSearch(request.query, {
|
|
91
|
+
...(request.indexes ? { indexes: request.indexes } : {}),
|
|
92
|
+
...(request.limit ? { limit: request.limit } : {}),
|
|
93
|
+
...(request.mode ? { mode: request.mode } : {}),
|
|
94
|
+
...(context?.abortSignal ? { signal: context.abortSignal } : {}),
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build the opt-in `add_documents` tool (write into a direct-access index).
|
|
102
|
+
* Only install it when the plugin's write surface is enabled.
|
|
103
|
+
*/
|
|
104
|
+
export function addDocumentsTool(options: SearchToolOptions = {}) {
|
|
105
|
+
const inputSchema = searchContract.searchDocumentSchema
|
|
106
|
+
.array()
|
|
107
|
+
.describe("Documents to add or update. Each must include the index primary key.");
|
|
108
|
+
return createTool({
|
|
109
|
+
id: options.id ?? "add_documents",
|
|
110
|
+
description: ADD_DOCUMENTS_TOOL_DESCRIPTION,
|
|
111
|
+
inputSchema: searchContract.searchRequestSchema
|
|
112
|
+
.pick({ index: true })
|
|
113
|
+
.extend({ documents: inputSchema }),
|
|
114
|
+
outputSchema: searchContract.upsertResultSchema,
|
|
115
|
+
execute: async (input, context): Promise<UpsertResult> => {
|
|
116
|
+
const { client, config } = getSearchRuntime();
|
|
117
|
+
const record = input as { index?: string; documents: unknown };
|
|
118
|
+
const documents = toDocumentArray(record.documents);
|
|
119
|
+
const index = record.index ?? config.defaultIndex ?? "";
|
|
120
|
+
return client.addDocuments(index, documents, context?.abortSignal);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build the opt-in `create_index` tool (provision a Vector Search index).
|
|
127
|
+
* Only install it when the plugin's write surface is enabled. Delegates to
|
|
128
|
+
* {@link SearchClient.createIndex}, inferring the endpoint, embedding model,
|
|
129
|
+
* key, and columns from the request + plugin config.
|
|
130
|
+
*/
|
|
131
|
+
export function createIndexTool(options: SearchToolOptions = {}) {
|
|
132
|
+
return createTool({
|
|
133
|
+
id: options.id ?? "create_index",
|
|
134
|
+
description: CREATE_INDEX_TOOL_DESCRIPTION,
|
|
135
|
+
inputSchema: createIndexToolSchema,
|
|
136
|
+
outputSchema: indexInfoSchema,
|
|
137
|
+
execute: async (input, context) => {
|
|
138
|
+
const request = createIndexToolSchema.parse(input);
|
|
139
|
+
const { client } = getSearchRuntime();
|
|
140
|
+
return client.createIndex(request.name, toCreateIndexOptions(request, context?.abortSignal));
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Output schema for the `sync_index` tool. */
|
|
146
|
+
const syncIndexResultSchema = z.object({
|
|
147
|
+
index: z.string().describe("The index that was synced."),
|
|
148
|
+
synced: z.boolean().describe("True once the sync was triggered."),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build the opt-in `sync_index` tool (refresh a Delta Sync index from its
|
|
153
|
+
* source table). Only install it when the plugin's write surface is enabled.
|
|
154
|
+
*/
|
|
155
|
+
export function syncIndexTool(options: SearchToolOptions = {}) {
|
|
156
|
+
return createTool({
|
|
157
|
+
id: options.id ?? "sync_index",
|
|
158
|
+
description: SYNC_INDEX_TOOL_DESCRIPTION,
|
|
159
|
+
inputSchema: syncIndexToolSchema,
|
|
160
|
+
outputSchema: syncIndexResultSchema,
|
|
161
|
+
execute: async (input, context) => {
|
|
162
|
+
const request = syncIndexToolSchema.parse(input);
|
|
163
|
+
const { client, config } = getSearchRuntime();
|
|
164
|
+
const index = request.index ?? config.defaultIndex ?? "";
|
|
165
|
+
await client.syncIndex(index, context?.abortSignal);
|
|
166
|
+
return { index, synced: true };
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|