From 818f6fdcfbfe296dab032af4023d98a1ac566fcf Mon Sep 17 00:00:00 2001 From: Rikki Date: Mon, 26 May 2025 14:25:17 +0800 Subject: init --- src/blog_search.ts | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/blog_search.ts (limited to 'src/blog_search.ts') diff --git a/src/blog_search.ts b/src/blog_search.ts new file mode 100644 index 0000000..9855cd5 --- /dev/null +++ b/src/blog_search.ts @@ -0,0 +1,77 @@ +import { z } from 'zod' + +export const ROUTE_PATH = '/api/blog_search' + +const EMBEDDING_DIMENSION = 1024 as const +const MAX_TEXT_LENGTH = 1_000 +const MAX_RETRIES = 5 +const WORKERS_AI_MAX_RETRIES = 3 + +const RequestBodySchema = z.object({ + text: z.string(), +}) + +const AIResponseSchema = z.object({ + data: z.array(z.array(z.number()).length(EMBEDDING_DIMENSION)).length(1), +}) + +const textResponse = (msg: string, status = 400) => new Response(msg, { status }) + + +export async function handle(c: HonoContext) { + let json: unknown + try { + json = await c.req.json() + } catch { + return textResponse('Bad Request: Invalid JSON', 400) + } + + const body = RequestBodySchema.safeParse(json) + if (!body.success) { + return textResponse(`Bad Request: ${body.error.message}`, 400) + } + + const text = body.data.text.trim() + if (text.length > MAX_TEXT_LENGTH) { + return textResponse('Bad Request: Text is too long', 400) + } + + let embedding: number[] | null = null + for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { + try { + const raw = await c.env.AI.run( + '@cf/baai/bge-m3', + { text }, + { + gateway: { + id: 'workers-embedding', + retries: { maxAttempts: WORKERS_AI_MAX_RETRIES }, + }, + }, + ) + + const parsed = AIResponseSchema.safeParse(raw) + if (!parsed.success) { + return textResponse(`Internal Server Error: ${parsed.error.message}`, 500) + } + + embedding = parsed.data.data[0] + break + } catch (err) { + console.error(`Embedding attempt ${attempt + 1} failed`, err) + } + } + + if (!embedding) { + return textResponse('Internal Server Error: Embedding failed', 500) + } + + // ─── Respond with binary vector ──────────────────────────────────────────── + const buffer = new Float32Array(embedding).buffer + + const headers: Record = { + 'Content-Type': 'application/octet-stream', + } + + return new Response(buffer, { status: 200, headers }) +} \ No newline at end of file -- cgit v1.2.3