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:
@@ -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