46 lines
2 KiB
TypeScript
46 lines
2 KiB
TypeScript
// server/supabase/functions/search-knowledge/index.ts
|
|
// Composition root: wires the real adapters into the search-knowledge use case.
|
|
|
|
import { createClient } from '@supabase/supabase-js'
|
|
import { requireUser } from '../_shared/auth.ts'
|
|
import { createServiceRoleClient } from '../_shared/quota.ts'
|
|
import { readProviderKey } from '../_shared/provider-key.ts'
|
|
import { createOpenAIEmbeddingProvider } from '../_shared/openai-embeddings.ts'
|
|
import { createSupabaseEmbeddingUsageStore } from '../_shared/embedding-quota.ts'
|
|
import { type ChunkSearcher, createSearchKnowledgeHandler, SIMILARITY_THRESHOLD } from './handler.ts'
|
|
|
|
let serviceClient: ReturnType<typeof createServiceRoleClient> | null = null
|
|
function service(): ReturnType<typeof createServiceRoleClient> {
|
|
serviceClient ??= createServiceRoleClient()
|
|
return serviceClient
|
|
}
|
|
|
|
/** match_knowledge_chunks runs as the caller so RLS limits results to their own chunks. */
|
|
const searchChunks: ChunkSearcher = async (req, embedding, count) => {
|
|
const authHeader = req.headers.get('Authorization') ?? ''
|
|
const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? ''
|
|
const anonKey = Deno.env.get('SUPABASE_ANON_KEY') ?? ''
|
|
if (!supabaseUrl || !anonKey) return { ok: false, reason: 'unavailable' }
|
|
|
|
const userClient = createClient(supabaseUrl, anonKey, {
|
|
global: { headers: { Authorization: authHeader } },
|
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
})
|
|
const result = await userClient.rpc('match_knowledge_chunks', {
|
|
query_embedding: embedding,
|
|
match_count: count,
|
|
similarity_threshold: SIMILARITY_THRESHOLD,
|
|
})
|
|
if (result.error) return { ok: false, reason: 'failed' }
|
|
return { ok: true, results: Array.isArray(result.data) ? result.data : [] }
|
|
}
|
|
|
|
Deno.serve(createSearchKnowledgeHandler({
|
|
authenticate: requireUser,
|
|
embeddingProvider: () => {
|
|
const key = readProviderKey('OPENAI_API_KEY')
|
|
return key ? createOpenAIEmbeddingProvider(key) : null
|
|
},
|
|
usageStore: () => createSupabaseEmbeddingUsageStore(service()),
|
|
searchChunks,
|
|
}))
|