summaryrefslogtreecommitdiff
path: root/src/blog_search.ts
blob: 9855cd5896504dd81eaff3f4c0d3b4d5c88b9ae8 (plain)
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
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<string, string> = {
        'Content-Type': 'application/octet-stream',
    }

    return new Response(buffer, { status: 200, headers })
}