(
config: OpenAiEmbedderConfig,
options: OpenAiEmbedderOptions = {},
)
| 126 | } |
| 127 | |
| 128 | export function createOpenAiEmbedder( |
| 129 | config: OpenAiEmbedderConfig, |
| 130 | options: OpenAiEmbedderOptions = {}, |
| 131 | ): Embedder { |
| 132 | const fetchImpl = options.fetchImpl ?? fetch; |
| 133 | const sleep = options.sleep ?? defaultSleep; |
| 134 | const maxBatchSize = options.maxBatchSize ?? DEFAULTS.maxBatchSize; |
| 135 | const maxBatchChars = options.maxBatchChars ?? DEFAULTS.maxBatchChars; |
| 136 | const maxRetries = options.maxRetries ?? DEFAULTS.maxRetries; |
| 137 | const backoffBaseMs = options.backoffBaseMs ?? DEFAULTS.backoffBaseMs; |
| 138 | const docTimeoutMs = options.docTimeoutMs ?? DEFAULTS.docTimeoutMs; |
| 139 | const queryTimeoutMs = options.queryTimeoutMs ?? DEFAULTS.queryTimeoutMs; |
| 140 | |
| 141 | assertSafeEmbeddingsBaseUrl(config.baseUrl); |
| 142 | const dims = config.dimensions ?? DEFAULT_EMBEDDINGS_DIMENSIONS; |
| 143 | const endpoint = `${config.baseUrl.replace(/\/+$/, '')}/embeddings`; |
| 144 | |
| 145 | function batchInputs(texts: readonly string[]): string[][] { |
| 146 | const batches: string[][] = []; |
| 147 | let current: string[] = []; |
| 148 | let chars = 0; |
| 149 | for (const t of texts) { |
| 150 | if ( |
| 151 | current.length > 0 && |
| 152 | (current.length >= maxBatchSize || chars + t.length > maxBatchChars) |
| 153 | ) { |
| 154 | batches.push(current); |
| 155 | current = []; |
| 156 | chars = 0; |
| 157 | } |
| 158 | current.push(t); |
| 159 | chars += t.length; |
| 160 | } |
| 161 | if (current.length > 0) batches.push(current); |
| 162 | return batches; |
| 163 | } |
| 164 | |
| 165 | type AttemptResult = |
| 166 | | { kind: 'ok'; vectors: Float32Array[] } |
| 167 | | { kind: 'retry'; reason: EmbeddingErrorReason; error: Error } |
| 168 | | { kind: 'fatal'; reason: EmbeddingErrorReason; error: Error }; |
| 169 | |
| 170 | async function attemptOnce( |
| 171 | body: string, |
| 172 | expectedCount: number, |
| 173 | roleLabel: 'query' | 'document', |
| 174 | timeoutMs: number, |
| 175 | ): Promise<AttemptResult> { |
| 176 | const controller = new AbortController(); |
| 177 | const timer = setTimeout(() => controller.abort(), timeoutMs); |
| 178 | const startedAt = performance.now(); |
| 179 | try { |
| 180 | const res = await fetchImpl(endpoint, { |
| 181 | method: 'POST', |
| 182 | headers: { |
| 183 | 'Content-Type': 'application/json', |
| 184 | Authorization: `Bearer ${config.apiKey}`, |
| 185 | }, |
no test coverage detected