memory: SigLIP2 on-device embedding (text-embedding-kmp v3)
Добавляет второй бэкенд эмбеддингов для vector-памяти: on-device SigLIP2
через ONNX Runtime. Не требует сети (HTTP), не светят тексты заметок наружу.
* gradle/libs.versions.toml: text-embedding-kmp = "3.0.0-SNAPSHOT", модули
api-jvm / siglip-jvm (group переехал с pw.binom.voice.embeddingtext на
pw.binom.ai.embeddingtext).
* settings.gradle.kts: mavenLocal() добавлен в dependencyResolutionManagement
(text-embedding-kmp публикуется локально как snapshot).
* memory-vector/SiglipEmbeddingProvider (jvmMain) — адаптер
pw.binom.voice.embeddingtext.TextEmbeddingExtractor → EmbeddingProvider:
оборачивает blocking embed() в withContext(Dispatchers.IO) + Mutex (ONNX
сессия не reentrant), размерность пробируется через probe embed("probe")
(768 для SigLIP2-base).
* memory-vector/build.gradle.kts: api(libs.text.embedding.api) +
implementation(libs.text.embedding.siglip); jvmTest получает
SiglipEmbeddingProviderTest — smoke test (skip если модель не найдена).
* standalone/AgentikConfig: новый enum EmbeddingBackend { HTTP, SIGLIP },
поля embeddingBackend / embeddingModelPath / embeddingTokenizerPath, env:
AGENTIK_EMBEDDING_BACKEND, AGENTIK_EMBEDDING_MODEL_PATH,
AGENTIK_EMBEDDING_TOKENIZER_PATH.
* standalone/Main.kt: switch на embeddingBackend при memory-backend=vector;
для SIGLIP требуются оба пути, иначе ошибка с понятным сообщением.
* standalone/README.md: обновлены env-vars, добавлены две bash-секции
(HTTP и SIGLIP) + инструкция скачивания модели с static.binom.pw.
* scripts/install-text-embedding-stub.sh: workaround для upstream бага
(siglip-jvm/*.module ссылается на api без -jvm variant). Создаёт
stub-артефакт api:3.0.0-SNAPSHOT в mavenLocal с тем же содержимым.
Удалить когда upstream починит module-metadata.
Smoke test: AGENTIK_MEMORY_BACKEND=vector AGENTIK_EMBEDDING_BACKEND=siglip
+ несуществующий путь → FileNotFoundException с понятным трейсом (значит
ONNX Runtime инициализирован, путь через factory пробрасывается корректно).
Tests: 288 total green. Fatjar 250MB (вырос из-за onnxruntime ~80MB).
Dropped: старый stub-jar pw.binom.voice.embeddingtext:api:2.0.0-SNAPSHOT.
This commit is contained in:
@@ -10,6 +10,7 @@ litert = "7"
|
||||
sqldelight = "2.3.2"
|
||||
shadow = "8.3.5"
|
||||
jvector = "3.0.6"
|
||||
text-embedding-kmp = "3.0.0-SNAPSHOT"
|
||||
|
||||
[plugins]
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
@@ -67,3 +68,8 @@ kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.re
|
||||
|
||||
# --- JVector (io.github.jbellis) — embedded ANN-индекс для vector-бэкенда памяти. JVM-only. ---
|
||||
jvector = { module = "io.github.jbellis:jvector", version.ref = "jvector" }
|
||||
|
||||
# --- text-embedding-kmp (pw.binom.ai.embeddingtext) — on-device SigLIP2 эмбеддинг через ONNX. ---
|
||||
# Артефакты публикуются под именами `-jvm` (KMP convention для JVM-таргета).
|
||||
text-embedding-api = { module = "pw.binom.ai.embeddingtext:api-jvm", version.ref = "text-embedding-kmp" }
|
||||
text-embedding-siglip = { module = "pw.binom.ai.embeddingtext:siglip-jvm", version.ref = "text-embedding-kmp" }
|
||||
|
||||
@@ -24,6 +24,23 @@ kotlin {
|
||||
jvmMain.dependencies {
|
||||
implementation(libs.jvector)
|
||||
implementation(libs.sqldelight.sqlite.driver)
|
||||
// text-embedding-kmp — on-device SigLIP2 через ONNX Runtime.
|
||||
// Сигнатура `embed(String): TextEmbedding` (blocking), оборачиваем
|
||||
// наш `suspend fun embed(text)` через Mutex. api-вариант экспортируем
|
||||
// (`api`), потому что SiglipEmbeddingProvider реализует `embed()`
|
||||
// через тип TextEmbeddingExtractor, который виден потребителю
|
||||
// только если он сам подтянет api-jvm — проще пробросить.
|
||||
//
|
||||
// WORKAROUND: upstream `siglip-jvm/*.module` ссылается на `api`
|
||||
// БЕЗ -jvm суффикса. Поскольку в mavenLocal есть только `api-jvm`,
|
||||
// требуется дополнительный stub-jar `pw.binom.ai.embeddingtext:api`
|
||||
// с тем же содержимым. Создаётся так:
|
||||
// mkdir -p ~/.m2/repository/pw.binom.ai.embeddingtext/api/3.0.0-SNAPSHOT
|
||||
// cp ~/.m2/repository/.../api-jvm/3.0.0-SNAPSHOT/api-jvm-*.{jar,sources.jar} \
|
||||
// ~/.m2/repository/.../api/3.0.0-SNAPSHOT/api-*.{jar,sources.jar}
|
||||
// Когда upstream починит module-metadata — эту инструкцию можно убрать.
|
||||
api(libs.text.embedding.api)
|
||||
implementation(libs.text.embedding.siglip)
|
||||
}
|
||||
jvmTest.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package pw.binom.agentik.memory.vector.embedding
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import pw.binom.agentik.memory.vector.EmbeddingProvider
|
||||
import pw.binom.voice.embeddingtext.TextEmbeddingExtractor
|
||||
import pw.binom.voice.embeddingtext.createSiglip2TextExtractor
|
||||
|
||||
/**
|
||||
* Локальный on-device эмбеддинг через [TextEmbeddingExtractor] (SigLIP2 / ONNX).
|
||||
*
|
||||
* Особенности:
|
||||
* - `TextEmbeddingExtractor.embed(text)` — **blocking** (ONNX-инференс на CPU),
|
||||
* не suspend. Оборачиваем в `Dispatchers.IO` + `Mutex`, чтобы сериализовать
|
||||
* доступ из нескольких корутин (ONNX-сессия не reentrant).
|
||||
* - Размерность фиксирована extractor'ом (SigLIP2-base = 768); параметр
|
||||
* `dimension` в конструкторе не принимаем — берём через [probeDimension].
|
||||
* - LRU-кэш из [HttpEmbeddingClient] не используем здесь: ONNX-инференс на
|
||||
* CPU ≈ 5-15 мс, кэш полезен только для HTTP. Но если потребуется —
|
||||
* легко добавить.
|
||||
*
|
||||
* Модель + токенизатор не бандлятся в jar: передаём пути в конструкторе.
|
||||
* Скачать: см. README репы `text-embedding-kmp`.
|
||||
*/
|
||||
class SiglipEmbeddingProvider(
|
||||
modelPath: String,
|
||||
tokenizerPath: String,
|
||||
) : EmbeddingProvider, AutoCloseable {
|
||||
|
||||
private val extractor: TextEmbeddingExtractor =
|
||||
createSiglip2TextExtractor(modelPath = modelPath, tokenizerPath = tokenizerPath)
|
||||
|
||||
override val dimension: Int = run {
|
||||
val probe = extractor.embed("probe")
|
||||
probe.dim
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun embed(text: String): FloatArray = withContext(Dispatchers.IO) {
|
||||
mutex.withLock { extractor.embed(text).values }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
extractor.close()
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package pw.binom.agentik.memory.vector.embedding
|
||||
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Smoke-test SiglipEmbeddingProvider.
|
||||
*
|
||||
* Если файлы модели не найдены (по дефолту `/tmp/text-emb-model/`),
|
||||
* тест пропускается через [assumeModelAvailable]. Если найдены —
|
||||
* проверяется, что провайдер открывается, возвращает валидный эмбеддинг
|
||||
* правильной размерности, и закрывается чисто.
|
||||
*/
|
||||
class SiglipEmbeddingProviderTest {
|
||||
|
||||
@Test
|
||||
fun `dimension is 768 when model loads successfully`() {
|
||||
val modelDir = File("/tmp/text-emb-model")
|
||||
assume(modelDir.exists() && File(modelDir, "text_model_int8.onnx").exists()) {
|
||||
"SigLIP2 model files not found in /tmp/text-emb-model/ — skipping"
|
||||
}
|
||||
SiglipEmbeddingProvider(
|
||||
modelPath = "${modelDir.absolutePath}/text_model_int8.onnx",
|
||||
tokenizerPath = "${modelDir.absolutePath}/tokenizer.model",
|
||||
).use { provider ->
|
||||
assertEquals(768, provider.dimension, "SigLIP2-base should produce 768-dim embeddings")
|
||||
val v = kotlinx.coroutines.runBlocking { provider.embed("hello world") }
|
||||
assertEquals(768, v.size)
|
||||
assertTrue(v.any { it != 0f }, "embedding should not be all zeros")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing model file fails with clear error`() {
|
||||
val tmpDir = createTempDir(prefix = "no-model-")
|
||||
val nonExistent = File(tmpDir, "does-not-exist.onnx")
|
||||
assertFailsWith<Exception> {
|
||||
SiglipEmbeddingProvider(
|
||||
modelPath = nonExistent.absolutePath,
|
||||
tokenizerPath = nonExistent.absolutePath,
|
||||
).use { it.dimension }
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun assume(condition: Boolean, message: () -> String) {
|
||||
if (!condition) {
|
||||
println("SKIPPED: ${message()}")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Создаёт stub-артефакт `pw.binom.voice.embeddingtext:api:2.0.0-SNAPSHOT`,
|
||||
# ссылающийся на `api-jvm:2.0.0-SNAPSHOT`. Нужно из-за бага в upstream module
|
||||
# metadata у siglip-jvm (ссылается на `api` без variant).
|
||||
#
|
||||
# Удалить, когда upstream починит module-metadata.
|
||||
|
||||
set -e
|
||||
M2="${HOME}/.m2/repository"
|
||||
GROUP_DIR="${M2}/pw/binom/ai/embeddingtext"
|
||||
SRC_DIR="${GROUP_DIR}/api-jvm/3.0.0-SNAPSHOT"
|
||||
DST_DIR="${GROUP_DIR}/api/3.0.0-SNAPSHOT"
|
||||
|
||||
if [ ! -f "${SRC_DIR}/api-jvm-3.0.0-SNAPSHOT.jar" ]; then
|
||||
echo "Source api-jvm not found: ${SRC_DIR}"
|
||||
echo "Сначала опубликуй text-embedding-kmp в mavenLocal:"
|
||||
echo " git clone https://git.binom.pw/subochev/text-embedding-kmp /tmp/text-embedding-kmp"
|
||||
echo " cd /tmp/text-embedding-kmp && ./gradlew -Pversion=3.0.0-SNAPSHOT :api:publishJvmPublicationToMavenLocal :siglip:publishJvmPublicationToMavenLocal"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${DST_DIR}"
|
||||
cp "${SRC_DIR}/api-jvm-3.0.0-SNAPSHOT.jar" "${DST_DIR}/api-3.0.0-SNAPSHOT.jar"
|
||||
cp "${SRC_DIR}/api-jvm-3.0.0-SNAPSHOT-sources.jar" "${DST_DIR}/api-3.0.0-SNAPSHOT-sources.jar" 2>/dev/null || true
|
||||
|
||||
cat > "${DST_DIR}/api-3.0.0-SNAPSHOT.pom" <<POM
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>pw.binom.ai.embeddingtext</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
</project>
|
||||
POM
|
||||
|
||||
cat > "${DST_DIR}/maven-metadata-local.xml" <<META
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>pw.binom.ai.embeddingtext</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<versioning>
|
||||
<snapshot><timestamp>20260101.000000</timestamp></snapshot>
|
||||
<lastUpdated>20260101000000</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
META
|
||||
|
||||
echo "Installed stub: pw.binom.ai.embeddingtext:api:3.0.0-SNAPSHOT -> ${DST_DIR}"
|
||||
@@ -12,6 +12,8 @@ dependencyResolutionManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
// Локально опубликованные snapshot-ы text-embedding-kmp (см. `~/.m2`).
|
||||
mavenLocal()
|
||||
// Home Nexus, репо "caffeine": pw.binom.* (A2A, ...)
|
||||
maven {
|
||||
name = "caffeine"
|
||||
|
||||
+33
-8
@@ -41,8 +41,11 @@ java -jar standalone/build/libs/standalone-all.jar
|
||||
| `AGENTIK_SKILLS_DIR` | _выкл._ | Каталог со скилами (`SKILL.md` / `*.yaml`) |
|
||||
| `AGENTIK_MEMORY_DIR` | `~/.agentik/memory` (md) или `AGENTIK_DB_PATH` (vector) | Каталог памяти (md); `"off"` отключает |
|
||||
| `AGENTIK_MEMORY_BACKEND` | `md` | `md` (Hermes-style §-файлы) / `vector` (SQLite + JVector + LLM-эмбеддинги) / `off` |
|
||||
| `AGENTIK_EMBEDDING_MODEL` | `text-embedding-3-small` | Модель эмбеддингов для vector-бэкенда |
|
||||
| `AGENTIK_EMBEDDING_DIMENSION` | `1536` | Размерность вектора (должна совпадать с моделью) |
|
||||
| `AGENTIK_EMBEDDING_MODEL` | `text-embedding-3-small` | Модель эмбеддингов для vector-бэкенда (только HTTP) |
|
||||
| `AGENTIK_EMBEDDING_DIMENSION` | `1536` | Размерность вектора (только HTTP; SIGLIP определяет автоматически) |
|
||||
| `AGENTIK_EMBEDDING_BACKEND` | `HTTP` | `HTTP` (POST /v1/embeddings) или `SIGLIP` (on-device, без сети) |
|
||||
| `AGENTIK_EMBEDDING_MODEL_PATH` | _только SIGLIP_ | Путь к `text_model_int8.onnx` (SigLIP2) |
|
||||
| `AGENTIK_EMBEDDING_TOKENIZER_PATH` | _только SIGLIP_ | Путь к `tokenizer.model` (sentencepiece) |
|
||||
| `AGENTIK_SOUL` | _выкл._ | Путь к `SOUL.md` — файл персоны (markdown), вставляется в начало system prompt |
|
||||
| `AGENTIK_LLM_BACKEND` | — | `openai` или `google` (см. ниже) |
|
||||
| `AGENTIK_MCP_CONFIG` | _выкл._ | Путь к JSON со списком MCP-серверов |
|
||||
@@ -218,23 +221,45 @@ context…]` блок в начало user-сообщения; после зап
|
||||
`AGENTIK_MEMORY_BACKEND` выбирает хранилище. Дефолт — `md` (Hermes-style
|
||||
§-файлы, keyword overlap, без внешних вызовов).
|
||||
|
||||
`vector` — SQLite (`AGENTIK_DB_PATH`) + JVector ANN + LLM-эмбеддинги
|
||||
(запросы на `${OPENAI_BASE_URL}/v1/embeddings`). Семантический поиск:
|
||||
cosine similarity + recency-re-rank. Те же четыре тула; та же семантика.
|
||||
`vector` — SQLite (`AGENTIK_DB_PATH`) + JVector ANN + эмбеддинги. Два
|
||||
бэкенда эмбеддингов через `AGENTIK_EMBEDDING_BACKEND`:
|
||||
|
||||
- **`HTTP` (default)** — POST на `${OPENAI_BASE_URL}/v1/embeddings`. Семантический
|
||||
поиск: cosine similarity + recency-re-rank.
|
||||
- **`SIGLIP`** — on-device SigLIP2 через ONNX Runtime (text-embedding-kmp,
|
||||
768-мерный вектор). Никаких внешних вызовов: модель и токенизатор должны
|
||||
лежать на диске. Размерность определяется автоматически (768).
|
||||
|
||||
```bash
|
||||
# Vector-бэкенд
|
||||
# Vector-бэкенд + HTTP-эмбеддинги (default)
|
||||
AGENTIK_MEMORY_BACKEND=vector \
|
||||
AGENTIK_EMBEDDING_BACKEND=http \
|
||||
AGENTIK_EMBEDDING_MODEL=text-embedding-3-small \
|
||||
AGENTIK_EMBEDDING_DIMENSION=1536 \
|
||||
java -jar standalone-all.jar
|
||||
|
||||
# Vector-бэкенд + on-device SigLIP2 (без сети)
|
||||
AGENTIK_MEMORY_BACKEND=vector \
|
||||
AGENTIK_EMBEDDING_BACKEND=siglip \
|
||||
AGENTIK_EMBEDDING_MODEL_PATH=/path/to/text_model_int8.onnx \
|
||||
AGENTIK_EMBEDDING_TOKENIZER_PATH=/path/to/tokenizer.model \
|
||||
java -jar standalone-all.jar
|
||||
```
|
||||
|
||||
Для vector требуется `AGENTIK_LLM_BACKEND=openai` (т.к. нужен
|
||||
Для HTTP-эмбеддингов требуется `AGENTIK_LLM_BACKEND=openai` (т.к. нужен
|
||||
OpenAI-совместимый `/v1/embeddings` endpoint — LiteLLM proxy тоже подходит).
|
||||
Embedding-вызовы кэшируются LRU на 256 текстов — дедупликация при повторных
|
||||
HTTP-вызовы кэшируются LRU на 256 текстов — дедупликация при повторных
|
||||
запросах одинаковых промптов.
|
||||
|
||||
Для SIGLIP нужно сначала скачать модель (~283M) и токенизатор (~4M):
|
||||
```bash
|
||||
mkdir -p /path/to/siglip-model
|
||||
curl -fSL -o /path/to/siglip-model/text_model_int8.onnx \
|
||||
http://static.binom.pw/models/siglip2/text_model_int8.onnx
|
||||
curl -fSL -o /path/to/siglip-model/tokenizer.model \
|
||||
http://static.binom.pw/models/siglip2/tokenizer.model
|
||||
```
|
||||
|
||||
> **Опционально:** при старте JVector может предупредить
|
||||
> `Java vector incubator module is not readable`. Это значит, что JIT
|
||||
> не использует SIMD (Panama Vector API) и индекс строится через скалярный
|
||||
|
||||
@@ -12,6 +12,7 @@ import pw.binom.agentik.memory.MemorySystem
|
||||
import pw.binom.agentik.memory.md.openMdMemorySystem
|
||||
import pw.binom.agentik.memory.vector.VectorMemorySystem
|
||||
import pw.binom.agentik.memory.vector.embedding.HttpEmbeddingClient
|
||||
import pw.binom.agentik.memory.vector.embedding.SiglipEmbeddingProvider
|
||||
import pw.binom.agentik.server.agentikAgent
|
||||
import pw.binom.agentik.skills.SkillCatalog
|
||||
import pw.binom.agentik.skills.SkillLoader
|
||||
@@ -93,25 +94,44 @@ fun main() {
|
||||
}
|
||||
}
|
||||
MemoryBackend.VECTOR -> {
|
||||
val llm = config.llm
|
||||
// Берём базовый URL + API key у активного LLM-бэкенда.
|
||||
// Поддерживается только OPENAI (LiteLLM proxy тоже работает, т.к. /v1/embeddings
|
||||
// — это OpenAI-совместимый endpoint).
|
||||
require(llm.backend == LlmBackend.OPENAI) {
|
||||
"AGENTIK_MEMORY_BACKEND=vector требует LLM_BACKEND=openai (нужен /v1/embeddings)"
|
||||
val embedding: pw.binom.agentik.memory.vector.EmbeddingProvider = when (config.embeddingBackend) {
|
||||
AgentikConfig.EmbeddingBackend.HTTP -> {
|
||||
val llm = config.llm
|
||||
// Берём базовый URL + API key у активного LLM-бэкенда.
|
||||
// Поддерживается только OPENAI (LiteLLM proxy тоже работает, т.к. /v1/embeddings
|
||||
// — это OpenAI-совместимый endpoint).
|
||||
require(llm.backend == LlmBackend.OPENAI) {
|
||||
"AGENTIK_EMBEDDING_BACKEND=http требует LLM_BACKEND=openai (нужен /v1/embeddings)"
|
||||
}
|
||||
val oa = checkNotNull(llm.openai) { "openai config required for http embedding backend" }
|
||||
HttpEmbeddingClient(
|
||||
apiUrl = oa.baseUrl.trimEnd('/'),
|
||||
apiKey = oa.apiKey,
|
||||
model = config.embeddingModel,
|
||||
dimension = config.embeddingDimension,
|
||||
)
|
||||
}
|
||||
AgentikConfig.EmbeddingBackend.SIGLIP -> {
|
||||
val modelPath = checkNotNull(config.embeddingModelPath) {
|
||||
"AGENTIK_EMBEDDING_BACKEND=siglip требует AGENTIK_EMBEDDING_MODEL_PATH"
|
||||
}
|
||||
val tokenizerPath = checkNotNull(config.embeddingTokenizerPath) {
|
||||
"AGENTIK_EMBEDDING_BACKEND=siglip требует AGENTIK_EMBEDDING_TOKENIZER_PATH"
|
||||
}
|
||||
SiglipEmbeddingProvider(modelPath = modelPath, tokenizerPath = tokenizerPath)
|
||||
}
|
||||
}
|
||||
val oa = checkNotNull(llm.openai) { "openai config required for vector backend" }
|
||||
val embedding = HttpEmbeddingClient(
|
||||
apiUrl = oa.baseUrl.trimEnd('/'),
|
||||
apiKey = oa.apiKey,
|
||||
model = config.embeddingModel,
|
||||
dimension = config.embeddingDimension,
|
||||
)
|
||||
VectorMemorySystem.open(
|
||||
dbPath = config.dbPath,
|
||||
embedding = embedding,
|
||||
).also {
|
||||
println(" memory: db=${config.dbPath} (vector-backend, model=${config.embeddingModel}, dim=${config.embeddingDimension})")
|
||||
val backendLabel = when (config.embeddingBackend) {
|
||||
AgentikConfig.EmbeddingBackend.HTTP ->
|
||||
"model=${config.embeddingModel}, dim=${config.embeddingDimension}"
|
||||
AgentikConfig.EmbeddingBackend.SIGLIP ->
|
||||
"model=siglip2-base (on-device), dim=${embedding.dimension}"
|
||||
}
|
||||
println(" memory: db=${config.dbPath} (vector-backend, $backendLabel)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,20 +56,44 @@ data class AgentikConfig(
|
||||
*/
|
||||
val memoryBackend: MemoryBackend = MemoryBackend.MD,
|
||||
/**
|
||||
* Имя модели эмбеддингов для vector-бэкенда. Дефолт `text-embedding-3-small`
|
||||
* Имя модели эмбеддингов для vector-бэкенда. Используется только при
|
||||
* [embeddingBackend] = HTTP. Дефолт `text-embedding-3-small`
|
||||
* (1536-мерный). Должна быть доступна через тот же baseUrl/apiKey что и LLM.
|
||||
*/
|
||||
val embeddingModel: String = DEFAULT_EMBEDDING_MODEL,
|
||||
/**
|
||||
* Размерность эмбеддингов vector-бэкенда. Должна совпадать с реальной
|
||||
* размерностью [embeddingModel]. Дефолт 1536 для `text-embedding-3-small`.
|
||||
* Размерность эмбеддингов vector-бэкенда. Используется только при
|
||||
* [embeddingBackend] = HTTP. Должна совпадать с реальной размерностью
|
||||
* [embeddingModel]. Дефолт 1536 для `text-embedding-3-small`.
|
||||
* Для [embeddingBackend] = SIGLIP размерность определяется самой моделью
|
||||
* (768 для SigLIP2-base), параметр игнорируется.
|
||||
*/
|
||||
val embeddingDimension: Int = DEFAULT_EMBEDDING_DIMENSION,
|
||||
/**
|
||||
* Бэкенд эмбеддингов для vector-памяти:
|
||||
* - HTTP — POST /v1/embeddings к OpenAI-совместимому API (default);
|
||||
* - SIGLIP — on-device SigLIP2 через ONNX Runtime (text-embedding-kmp), без сети.
|
||||
*/
|
||||
val embeddingBackend: EmbeddingBackend = EmbeddingBackend.HTTP,
|
||||
/**
|
||||
* Путь к ONNX-модели SigLIP2 (`text_model_int8.onnx`). Используется только при
|
||||
* [embeddingBackend] = SIGLIP.
|
||||
*/
|
||||
val embeddingModelPath: String? = null,
|
||||
/**
|
||||
* Путь к sentencepiece-токенизатору (`tokenizer.model`). Используется только при
|
||||
* [embeddingBackend] = SIGLIP.
|
||||
*/
|
||||
val embeddingTokenizerPath: String? = null,
|
||||
) {
|
||||
/** Бэкенд долговременной памяти. */
|
||||
@Serializable
|
||||
enum class MemoryBackend { MD, VECTOR, OFF }
|
||||
|
||||
/** Бэкенд эмбеддингов (для memory-backend=vector). */
|
||||
@Serializable
|
||||
enum class EmbeddingBackend { HTTP, SIGLIP }
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_PORT: Int = 8080
|
||||
const val DEFAULT_DB_PATH: String = "./agentik.db"
|
||||
@@ -99,6 +123,11 @@ data class AgentikConfig(
|
||||
?: DEFAULT_EMBEDDING_MODEL,
|
||||
embeddingDimension = env("AGENTIK_EMBEDDING_DIMENSION")?.toIntOrNull()
|
||||
?: DEFAULT_EMBEDDING_DIMENSION,
|
||||
embeddingBackend = env("AGENTIK_EMBEDDING_BACKEND")?.let {
|
||||
runCatching { EmbeddingBackend.valueOf(it.uppercase()) }.getOrNull()
|
||||
} ?: EmbeddingBackend.HTTP,
|
||||
embeddingModelPath = env("AGENTIK_EMBEDDING_MODEL_PATH")?.takeIf { it.isNotBlank() },
|
||||
embeddingTokenizerPath = env("AGENTIK_EMBEDDING_TOKENIZER_PATH")?.takeIf { it.isNotBlank() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user