SPB Git
2.2 KB · 30 lines typescript
1import { AI_PROVIDER } from "@/lib/env";2/** Optional OpenAI embeddings. Model availability is verified at runtime (GET /v1/models/<id>) before first use; failures degrade to lexical only. */3const MODEL = process.env.OPENAI_EMBEDDING_MODEL ?? "text-embedding-3-small";4export const EMBEDDING_DIM = 1536;5let verified: boolean | null = null;6export function embeddingsEnabled() { return AI_PROVIDER === "openai" && !!process.env.OPENAI_API_KEY; }7export async function verifyEmbeddingModel(): Promise<{ ok: boolean; model: string; reason?: string }> {8  if (!embeddingsEnabled()) return { ok: false, model: MODEL, reason: "AI provider disabled" };9  if (verified !== null) return { ok: verified, model: MODEL };10  try {11    const r = await fetch(`https://api.openai.com/v1/models/${encodeURIComponent(MODEL)}`, { headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, signal: AbortSignal.timeout(15000) });12    verified = r.ok; return { ok: r.ok, model: MODEL, reason: r.ok ? undefined : `HTTP ${r.status}` };13  } catch (e) { verified = false; return { ok: false, model: MODEL, reason: (e as Error).message }; }14}15/** Embed up to 64 inputs per call; inputs are truncated to ~8,000 characters (model limit 8,191 tokens). Returns null when disabled. */16export async function embed(inputs: string[]): Promise<number[][] | null> {17  if (!embeddingsEnabled()) return null;18  const v = await verifyEmbeddingModel(); if (!v.ok) return null;19  const out: number[][] = [];20  for (let i = 0; i < inputs.length; i += 64) {21    const batch = inputs.slice(i, i + 64).map((s) => s.slice(0, 8000));22    const r = await fetch("https://api.openai.com/v1/embeddings", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ model: MODEL, input: batch, dimensions: EMBEDDING_DIM }), signal: AbortSignal.timeout(60000) });23    if (!r.ok) throw new Error(`embeddings HTTP ${r.status}: ${(await r.text()).slice(0, 200)}`);24    const j = (await r.json()) as { data: { index: number; embedding: number[] }[] };25    for (const d of j.data.sort((a, b) => a.index - b.index)) out.push(d.embedding);26  }27  return out;28}29export const EMBEDDING_MODEL = MODEL;30
Files · main
Menu

Theme