remove :storage-ksqlite (conversation/message) and related tests; decouple schema from journal
ci / JVM build + tests (push) Successful in 6m6s

This commit is contained in:
2026-09-22 16:01:05 +03:00
parent 639c7d1748
commit 84f5fd84f3
39 changed files with 1569 additions and 616 deletions
+26
View File
@@ -0,0 +1,26 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
kotlin {
jvmToolchain(21)
jvm()
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
sourceSets {
commonMain.dependencies {
api(libs.kotlinx.coroutines.core)
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
}
}
}
@@ -0,0 +1,29 @@
package pw.binom.agentik.vectorindex
interface MutableVectorIndexStore : VectorIndexStore {
/**
* Добавляет запись в индекс.
*
* @param id Идентификатор записи. Если `null` — реализация генерирует
* его сама (формат остаётся на усмотрение реализации).
* @param embedding Вектор. Размер должен совпадать с [VectorIndexStore.dimension].
* @param payload Пользовательские данные (любая сериализуемая структура —
* реализация хранит opaque string и не интерпретирует).
* @return Добавленная запись (с сгенерированным `id`, если был передан `null`).
* @throws VectorIndexAlreadyExistsException запись с указанным `id` уже есть в индексе.
*/
suspend fun add(id: String?, embedding: FloatArray, payload: String?): VectorIndex
/**
* Удаляет запись из индекса.
* @return `true`, если запись была удалена, `false`, если записи с таким `id` не было.
*/
suspend fun delete(id: String): Boolean
/**
* Полностью очищает индекс.
* @return количество удалённых записей.
*/
suspend fun clear(): Long
}
@@ -0,0 +1,37 @@
package pw.binom.agentik.vectorindex
/**
* Запись в vector-индексе: идентификатор + embedding + опциональный
* пользовательский payload.
*
* НЕ `data class` потому что [embedding] это `FloatArray`, а дефолтные
* `equals`/`hashCode` data-class'а сравнивают массивы по reference (а не
* по содержимому) — два `VectorIndex` с идентичными числами оказались бы
* `!=`. Вместо этого [equals]/[hashCode]/[toString] переопределены руками.
*/
class VectorIndex(
val id: String,
val embedding: FloatArray,
val payload: String?,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as VectorIndex
if (id != other.id) return false
if (!embedding.contentEquals(other.embedding)) return false
if (payload != other.payload) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + embedding.contentHashCode()
result = 31 * result + (payload?.hashCode() ?: 0)
return result
}
override fun toString(): String =
"VectorIndex(id='$id', embedding.size=${embedding.size}, payload=$payload)"
}
@@ -0,0 +1,3 @@
package pw.binom.agentik.vectorindex
class VectorIndexAlreadyExistsException : Exception()
@@ -0,0 +1,22 @@
package pw.binom.agentik.vectorindex
/**
* Интерфейс read-only доступа к ANN-индексу. Реализации могут держать
* native resources (mmap'нутые файлы, sqlite handles) — поэтому [close]
* обязателен и наследуется от [AutoCloseable].
*/
interface VectorIndexStore : AutoCloseable {
/** Размерность embeddings, которые принимает этот индекс. */
val dimension: Int
/** Текущее количество записей в индексе. */
suspend fun getSize(): Long
/**
* Возвращает top-[limit] ближайших к [embedding] записей, отсортированных
* по убыванию [VectorSearchResult.score]. Если в индексе меньше [limit]
* записей — возвращается меньший список.
*/
suspend fun search(embedding: FloatArray, limit: Int): List<VectorSearchResult>
}
@@ -0,0 +1,6 @@
package pw.binom.agentik.vectorindex
data class VectorSearchResult(
val index: VectorIndex,
val score: Float,
)