Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | 28x 28x 28x 12x 12x 12x 5x 2x 1x 1x 1x 6x 6x 5x 1x | /**
* @module
*
* @author Bayu Dwiyan Satria
* @version 1.0.0
* @since 1.3.0
*/
import { Binding } from '@/core/bindings/Binding'
import { lazySettings } from '@/utils/lazySettings'
import { AiSearchSettings } from '@/types/AiSearchSettings'
import type { CloudflareEnv } from '@/types/CloudflareEnv'
/**
* Accessor for a single-instance AI Search binding.
*
* @remarks
* AI Search is managed retrieval: Cloudflare indexes a source, and a request
* asks it a question in words rather than in vectors. That is the difference
* from {@link VectorizeBinding}, which this package has had since 1.0.0 — with
* Vectorize a Worker owns the embedding model, the chunking, and the index
* lifecycle; here it owns none of them. Both are worth having, and which one is
* right turns on whether the embedding pipeline is something you want to run.
*
* ## Not `env.AI.autorag()`
*
* The older path to the same product hung off the Workers AI binding, as
* `env.AI.autorag(id)` and `env.AI.aiSearch()`. Both are deprecated in the
* `@cloudflare/workers-types` version this package pins, in favour of the
* standalone bindings this class and {@link AiSearchNamespaceBinding} wrap. So
* although {@link AIBinding} already exists, routing AI Search through it would
* have adopted a deprecated surface on the day it was written.
*
* ## Why there is no `AiSearchService`
*
* The kernel names no retrieval capability, and the same reasoning applies as
* for {@link WorkflowBinding}: an interface with one implementation and no
* consumers is ceremony. Managed retrieval is a plausible candidate later —
* "ask a corpus a question" is not Cloudflare vocabulary — but the seam should
* be drawn by a second implementation, not guessed at ahead of one.
*
* @example
* ```ts
* const filings = new AiSearchBinding()
*
* const { chunks } = await filings.search(env, { query: 'dividend policy changes in 2026' })
* ```
*
* @class
*
* @author Bayu Dwiyan Satria
* @version 1.0.0
* @since 1.3.0
*/
export class AiSearchBinding<E extends CloudflareEnv = CloudflareEnv> extends Binding<AiSearchInstance, E> {
/**
* This accessor's settings, resolved on first read.
*/
private readonly settings: () => AiSearchSettings
/**
* Constructs an AiSearchBinding instance.
*
* @param settings Resolved settings. Defaults to `resolve('aiSearch')`, so callers
* normally construct this with no arguments at all.
*/
constructor(settings?: AiSearchSettings) {
const settle = lazySettings<AiSearchSettings>('aiSearch', settings)
super(() => settle().binding)
this.settings = settle
}
/**
* Retrieves the chunks matching a query.
*
* @remarks
* Retrieval only — this returns passages and their scores, and generates no
* prose. Use it when the Worker does its own reasoning over the results, and
* {@link chat} when it wants an answer written.
*
* The request takes either a `query` string or a `messages` conversation,
* never both; the platform's own types enforce that.
*
* @param env The Worker environment.
* @param params The search request, as a query or a conversation.
* @returns The matching chunks, each with its score and source item.
* @throws {@link MissingBindingError} When the AI Search binding is absent.
*/
public async search(env: E, params: AiSearchSearchRequest): Promise<AiSearchSearchResponse> {
return await this.resolve(env).search(this.withOptions(params))
}
/**
* Answers a conversation, grounded in the indexed corpus.
*
* @remarks
* Retrieval and generation in one call. The response carries the `chunks` the
* answer was drawn from alongside the answer itself, which is what makes a
* citation possible — return them, or a reader has no way to check the model.
*
* @param env The Worker environment.
* @param params The chat request.
* @returns The generated answer, plus the chunks it was grounded in.
* @throws {@link MissingBindingError} When the AI Search binding is absent.
*/
public async chat(env: E, params: AiSearchChatCompletionsRequest): Promise<AiSearchChatCompletionsResponse> {
return await this.resolve(env).chatCompletions(this.withOptions({ ...params, stream: false }))
}
/**
* Answers a conversation as a stream of server-sent events.
*
* @remarks
* Separate from {@link chat} rather than a `stream: true` flag on it, because
* the two return different things — a parsed response and a `ReadableStream`
* — and a boolean argument that changes a return type is a discriminated
* union pretending to be an option.
*
* @param env The Worker environment.
* @param params The chat request.
* @returns The event stream, ready to return as a response body.
* @throws {@link MissingBindingError} When the AI Search binding is absent.
*/
public async chatStream(env: E, params: AiSearchChatCompletionsRequest): Promise<ReadableStream> {
return await this.resolve(env).chatCompletions(this.withOptions({ ...params, stream: true }))
}
/**
* Reads the instance's metadata.
*
* @param env The Worker environment.
* @returns What this instance is and how it is configured.
* @throws {@link MissingBindingError} When the AI Search binding is absent.
*/
public async info(env: E): Promise<AiSearchInstanceInfo> {
return await this.resolve(env).info()
}
/**
* Reads the instance's indexing statistics.
*
* @remarks
* Item counts by status and the last activity time — the answer to "is the
* corpus behind?", which is the question a search over stale content raises
* and a search response cannot answer.
*
* @param env The Worker environment.
* @returns Counts per status, last activity, and engine details.
* @throws {@link MissingBindingError} When the AI Search binding is absent.
*/
public async stats(env: E): Promise<AiSearchStatsResponse> {
return await this.resolve(env).stats()
}
/**
* Applies the configured retrieval options to a request that names none.
*
* @remarks
* All or nothing, for the reason {@link AiSearchSettings.options} gives: a
* request naming `ai_search_options` owns the whole object, because merging
* four nested sub-objects field by field would give a call site an effective
* configuration it could not predict from reading itself.
*
* @typeParam T The request shape, either a search or a chat request.
*
* @param params The caller-supplied request.
* @returns The request with configured options filled in.
*/
private withOptions<
T extends {
/**
* The request's own retrieval options, when it names any.
*/
ai_search_options?: AiSearchOptions
}
>(params: T): T {
const configured = this.settings().options
if (configured === undefined || params.ai_search_options !== undefined) {
return params
}
return { ...params, ai_search_options: configured } as T
}
}
|