RSS Git Download  Clone
Raw Blame History 2kB 56 lines
/**
 * FT.HYBRID — hybrid search combining text + vector similarity (Redis 8.4+)
 * Requires a RediSearch index with a VECTOR field.
 */
export default async (options) => {
    const { socket, payload } = options

    try {
        const redis = socket.p3xrs.ioredis
        const { index, query, vectorField, vectorValues, count = 10, offset = 0, limit = 20 } = payload

        if (!index || !query || !vectorField || !vectorValues) {
            socket.emit(options.responseEvent, {
                status: 'error',
                error: 'Index, query, vectorField, and vectorValues are required',
            })
            return
        }

        const vecBlob = Buffer.from(new Float32Array(vectorValues.map(Number)).buffer)
        const args = [
            index, query,
            'LIMIT', offset, limit,
            'VECTOR', vectorField, count, vecBlob,
        ]

        const result = await redis.call('FT.HYBRID', ...args)

        // Parse like FT.SEARCH: [totalCount, key1, [field, value, ...], ...]
        const total = result[0]
        const docs = []
        for (let i = 1; i < result.length; i += 2) {
            const key = result[i]
            const fields = result[i + 1]
            const doc = { _key: key }
            if (Array.isArray(fields)) {
                for (let j = 0; j < fields.length; j += 2) {
                    doc[fields[j]] = fields[j + 1]
                }
            }
            docs.push(doc)
        }

        socket.emit(options.responseEvent, {
            status: 'ok',
            data: { total, docs },
        })
    } catch (e) {
        console.error(e)
        socket.emit(options.responseEvent, {
            status: 'error',
            error: e.message,
        })
    }
}