colpali-backend-api / hono-proxy /src /routes /colpali-search-vespa.ts
vk98's picture
Initial backend deployment - Hono proxy + ColPali embedding API
5dfbe50
import { Hono } from 'hono';
import { z } from 'zod';
import { config } from '../config';
import { cache } from '../services/cache';
const colpaliSearchApp = new Hono();
// Search request schema
const searchQuerySchema = z.object({
query: z.string().min(1).max(500),
ranking: z.enum(['hybrid', 'colpali', 'bm25']).optional().default('hybrid'),
});
// Main search endpoint - direct to Vespa
colpaliSearchApp.get('/', async (c) => {
try {
const query = c.req.query('query');
const ranking = c.req.query('ranking') || 'hybrid';
const validation = searchQuerySchema.safeParse({ query, ranking });
if (!validation.success) {
return c.json({ error: 'Invalid request', details: validation.error.issues }, 400);
}
const validatedData = validation.data;
// Check cache
const cacheKey = `search:${validatedData.query}:${validatedData.ranking}`;
const cachedResult = cache.get(cacheKey);
if (cachedResult) {
c.header('X-Cache', 'HIT');
return c.json(cachedResult);
}
// Prepare YQL query based on ranking type
let yql = '';
switch (validatedData.ranking) {
case 'colpali':
yql = `select * from linqto where userQuery() limit 20`;
break;
case 'bm25':
yql = `select * from linqto where userQuery() order by bm25_score desc limit 20`;
break;
case 'hybrid':
default:
yql = `select * from linqto where userQuery() | rank (reciprocal_rank_fusion(bm25_score, max_sim)) limit 20`;
break;
}
// Query Vespa directly
const searchUrl = `${config.vespaAppUrl}/search/`;
const searchParams = new URLSearchParams({
yql,
query: validatedData.query,
ranking: validatedData.ranking === 'colpali' ? 'colpali' : 'default',
'summary': 'default',
'format': 'json'
});
// For now, using direct fetch without certificate authentication
// In production, you would use a proxy or configure certificates properly
const response = await fetch(`${searchUrl}?${searchParams}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
}
});
if (!response.ok) {
throw new Error(`Vespa returned ${response.status}`);
}
const data = await response.json();
// Transform to match expected format (add sim_map if needed)
const transformedData = {
...data,
root: {
...data.root,
children: data.root?.children?.map((hit: any, idx: number) => ({
...hit,
fields: {
...hit.fields,
// Add sim_map field if not present (for compatibility)
sim_map: hit.fields.sim_map || `sim_map_${idx}`,
}
})) || []
}
};
// Cache the result
cache.set(cacheKey, transformedData);
c.header('X-Cache', 'MISS');
return c.json(transformedData);
} catch (error) {
console.error('Search error:', error);
return c.json({
error: 'Search failed',
message: error instanceof Error ? error.message : 'Unknown error'
}, 500);
}
});
export { colpaliSearchApp };