/** * `similaritySearch({ model, column, embedWith, ... })` — agent-tool * factory that wraps an ORM Model + a vector column into a * drop-in `Tool` an agent can call to retrieve semantically similar * rows (#B7 Phase 2). * * Composes B7 Phase 1's ORM primitives (`whereVectorSimilarTo` + * `selectVectorDistance`) with `AI.embed()`. The model emits a * natural-language `query`; the tool embeds it, runs a vector search, * and returns the top-K rows ranked by similarity. * * @example * ```ts * import { similaritySearch } from '@gemstack/ai-sdk' * import { Document } from './app/Models/Document.js' * * class KnowledgeAgent extends Agent { * tools() { * return [ * similaritySearch({ * model: Document, * column: 'embedding', * embedWith: 'openai/text-embedding-3-small', * minSimilarity: 0.7, * limit: 10, * }), * ] * } * } * ``` * * Phase 2 limitations (lifted in Phase 2.5): * - **Standalone queries only.** No `scope`/`.where()` chaining yet — * the underlying `whereVectorSimilarTo` query must run alone. Agents * see every row in the corpus that matches the vector. Phase 2.5 * adds a `scope: (q) => q.where(...)` callback. * - **Cosine-centric similarity.** `similarity` is reported as * `1 - distance` regardless of `metric`. For non-cosine metrics * (`l2`, `inner-product`) the value is internally consistent with * the `minSimilarity` filter applied by the adapter but isn't a * meaningful normalized score on its own. */ import type { ServerToolBuilder } from './tool.js'; /** * Structural type for the model class similaritySearch accepts. * * Declared locally instead of importing an ORM `Model` type * so the main entry stays free of any ORM runtime — the tool calls * `model.query()` and never references an ORM package * itself. The user's app brings its own Model class. */ export interface SimilaritySearchModel { readonly name: string; query(): SimilaritySearchQueryBuilder; } /** * WhereOperator strings the `scope` callback may pass to `.where()`. * Mirrors a query builder's where-operator set. Duplicated here so the * main entry has no compile-time query-builder dep. */ export type SimilaritySearchWhereOperator = '=' | '!=' | '>' | '>=' | '<' | '<=' | 'LIKE' | 'NOT LIKE' | 'IN' | 'NOT IN'; /** * Structural type for the QueryBuilder methods similaritySearch needs. * Mirrors a subset of a query builder's `QueryBuilder` so apps * writing a `scope` callback get autocomplete on the methods that actually * compose with `whereVectorSimilarTo` (#B7 Phase 2.5): * * - `.where()` / `.orWhere()` chains compose into the SQL with positional * parameter binding. * - `.with()` / `.orderBy()` / `.whereGroup()` / `.whereHas()` still throw * when paired with a vector clause — out of scope for `scope`. */ export interface SimilaritySearchQueryBuilder { where(column: string, value: unknown): SimilaritySearchQueryBuilder; where(column: string, operator: SimilaritySearchWhereOperator, value: unknown): SimilaritySearchQueryBuilder; orWhere(column: string, value: unknown): SimilaritySearchQueryBuilder; orWhere(column: string, operator: SimilaritySearchWhereOperator, value: unknown): SimilaritySearchQueryBuilder; withTrashed?(): SimilaritySearchQueryBuilder; onlyTrashed?(): SimilaritySearchQueryBuilder; whereVectorSimilarTo?(column: string, query: number[] | string, opts?: { metric?: 'cosine' | 'l2' | 'inner-product'; minSimilarity?: number; embedWith?: string; }): SimilaritySearchQueryBuilder; selectVectorDistance?(column: string, query: number[], alias: string): SimilaritySearchQueryBuilder; limit(n: number): SimilaritySearchQueryBuilder; get(): Promise; } export interface SimilarityHit { readonly row: TInstance; /** * Higher = closer. For `metric: 'cosine'` (default) this is * `1 - distance` ∈ [-1, 1]. For other metrics see the JSDoc on * {@link similaritySearch}. */ readonly similarity: number; } export interface SimilaritySearchOptions { /** The Model class whose rows will be searched. */ model: SimilaritySearchModel; /** The column on `model` declared with the `vector({ dimensions })` cast. */ column: string; /** * Embedding model id (`/`). Required — fails loud * at factory construction time if missing so apps don't accidentally * route to whatever `AiRegistry.getDefault()` happens to be. */ embedWith: string; /** Default `'cosine'`. */ metric?: 'cosine' | 'l2' | 'inner-product'; /** * Drops rows whose `1 - distance` falls below this threshold. Cosine * range is `[-1, 1]` so values near `0.7–0.9` are typical for * "relevant" documents. */ minSimilarity?: number; /** Default `10`. */ limit?: number; /** * Pre-filter the query before the vector clause attaches. Receives a * fresh `model.query()` builder; return the chained builder. Use for * tenancy (`.where('tenantId', userId)`), publication state * (`.where('published', true)`), or any flat predicate that should * narrow the search corpus. * * **Phase 2.5 limits:** flat `.where()` / `.orWhere()` chains compose * into the SQL. `whereGroup` / `whereHas` / `with()` still throw at * terminal time — keep the scope flat. * * @example * ```ts * similaritySearch({ * model: KnowledgeArticle, * column: 'embedding', * embedWith: 'openai/text-embedding-3-small', * scope: q => q.where('tenantId', currentTenant).where('published', true), * }) * ``` */ scope?: (q: SimilaritySearchQueryBuilder) => SimilaritySearchQueryBuilder; /** * Override the tool name. Default * `similarity_search_`. */ name?: string; /** Override the tool description. */ description?: string; /** * Custom string projection for each hit, replaces the default * `(0.87) {"id":1,"content":"..."}` shape that the model sees on its * next step. The structured array still flows to the UI via the * `tool-result` chunk. */ projectResult?: (row: TInstance, similarity: number) => string; } /** * Build a `Tool` that embeds a natural-language query and returns the * top-K similar rows from `model.column`. */ export declare function similaritySearch(opts: SimilaritySearchOptions): ServerToolBuilder<{ query: string; }, SimilarityHit[]>; //# sourceMappingURL=similarity-search.d.ts.map