Files

:vector-index-ksqlite — ANN-индекс поверх SQLite (brute-force)

Что это

KMP-реализация MutableVectorIndexStore поверх ksqlite. Persistence "бесплатно" — обычная SQLite БД.

Стратегия поиска: brute-force cosine similarity. Один SELECT всех записей → декодирование BLOB → cosine sim в Kotlin → sort desc → топ-K. Просто, KMP-совместимо, без sqlite-vec extension.

Используется как persistent ANN-индекс для small-to-medium датасетов (≤10K записей на embedding ~512d). Для больших — переезжать на :vector-index-jvector или sqlite-vec.

Где используется

Standalone-агентом, когда нужна persistence embeddings и масштаб не превышает ~10K записей. Прямо сейчас модуль не подключён в :standalone — это API-готовый компонент, ждущий интеграции.

Что внутри

class KsqliteVectorIndexStore private constructor(
    override val dimension: Int,
    connection: SQLiteConnection,
    ownsConnection: Boolean,
) : MutableVectorIndexStore {

    constructor(dimension: Int, path: String)
    constructor(dimension: Int, connection: SQLiteConnection)

    override suspend fun getSize(): Long
    override suspend fun add(id: String?, embedding: FloatArray, payload: String?): VectorIndex
    override suspend fun delete(id: String): Boolean
    override suspend fun clear(): Long
    override suspend fun search(embedding: FloatArray, limit: Int): List<VectorSearchResult>
    override fun close()

    companion object {
        fun memory(dimension: Int, name: String? = null): KsqliteVectorIndexStore
    }
}

Lifecycle:

  • (dimension, path) — открывает файловое соединение, мигрирует, закрывает в close().
  • (dimension, connection) — внешнее соединение (shared bundle); store НЕ закрывает его.
  • memory(dimension, name) — in-memory, мигрирует, закрывает.

Schema: единственная таблица vector_index (id TEXT PRIMARY KEY, dimension INTEGER, embedding BLOB, payload TEXT). Embedding хранится как raw float32 little-endian через ручной FloatArray.toLittleEndianBytes() / ByteArray.toFloatArray() (hand-rolled в commonMain — без java.nio.ByteBuffer).

Миграции: PRAGMA user_version + BEGIN/COMMIT. Идемпотентен.

UUID: kotlin.uuid.Uuid.random() (commonMain-safe, не JVM-only java.util.UUID).

Цели сборки

Только: jvm(), linuxX64(), linuxArm64(), mingwX64() — ksqlite 0.1.2 публикует нативные бинари только для этих 4 целей (плюс 4 Android NDK). Нет macOS/iOS — для Apple использовать :vector-index-jvector.

Как подключить

dependencies {
    implementation("pw.binom.agentik:vector-index-ksqlite:0.1.0")
    implementation("pw.binom.agentik:vector-index-api:0.1.0")
}

val store = KsqliteVectorIndexStore(dimension = 768, path = "~/.agentik/vectors.db")
store.add(id = "doc-1", embedding = embeddingOf("hello"), payload = """{"src":"chat"}""")
val top = store.search(embedding = queryEmbedding, limit = 10)
store.close()

Тесты

./gradlew :vector-index-ksqlite:jvmTest

Покрывают: explicit/auto id, duplicate throws, delete+idempotency, clear, top-K sort, payload round-trip, BLOB bit-exact round-trip (FloatArray<->BLOB encoding), schema migration idempotency.

Зависимости

  • pw.binom.db:ksqlite:0.1.2
  • pw.binom.agentik:vector-index-api
  • kotlinx-coroutines-core

Чего здесь НЕТ

  • ANN на стороне SQL — нет sqlite-vec. Brute-force в Kotlin.
  • Фильтрации в search() — чистый ANN.
  • Apple targets (см. выше).

Текущий статус

Реализация готова, тесты зелёные. Интеграция в :standalone отложена до момента, когда понадобится.