fix(router): make the real embedder + TurboVec L3 actually work end-to-end

- LlamaCppEmbedder: parse llama.cpp --pooling mean nested response [[...]] (was
  silently failing every embed with 'unexpected response shape')
- qa-stack: embedder -b/-ub 8192 so docs >512 tokens don't 500; fix default path
- TurboVecL3MemoryStore: send init handshake on startup (was 'Index not initialized'
  on every add/query)
- turbovec_sidecar.py: rewrite add/search/save/load against the real turbovec API
  (batched ndarray, L2-normalize so IP=cosine, prepare() before search, classmethod
  load). The sidecar was scaffolding never run against the real lib.
This commit is contained in:
2026-07-03 13:16:05 +04:00
parent a0b3a1865a
commit 6437d29914
5 changed files with 102 additions and 30 deletions
@@ -116,6 +116,15 @@ class LlamaCppEmbedder(
return arrayResult
}
// llama.cpp --pooling mean: [{"embedding": [[...]]}] (nested list-of-one-vector).
val nestedResult = runCatching {
json.decodeFromString<List<EmbeddingNestedArrayResponse>>(responseBody)
.firstOrNull()?.embedding?.firstOrNull().orEmpty()
}.getOrNull()
if (nestedResult != null && nestedResult.isNotEmpty()) {
return nestedResult
}
throw IllegalStateException(
"Unexpected embedding response shape from $url. " +
"Response body (first 200 chars): ${responseBody.take(200)}"
@@ -19,6 +19,13 @@ data class EmbeddingArrayResponse(
val embedding: List<Float>,
)
// llama.cpp /embedding with --pooling mean returns a list-of-one-vector per item:
// [{"index":0,"embedding":[[floats...]]}]. embedding is nested, not flat.
@Serializable
data class EmbeddingNestedArrayResponse(
val embedding: List<List<Float>>,
)
@Serializable
data class OpenAiEmbeddingData(
val embedding: List<Float>,
@@ -79,6 +79,28 @@ class LlamaCppEmbedderTest {
assert(kotlin.math.abs(result[2] - 0.7f) < 0.0001f)
}
@Test
fun `embed parses llama-cpp nested pooling-mean array format`(): Unit = runBlocking {
// llama.cpp /embedding with --pooling mean: [{"index":0,"embedding":[[...]]}]
val mockEngine = MockEngine {
respond(
content = """[{"index":0,"embedding":[[0.5,0.6,0.7]]}]""",
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
)
}
val embedder = LlamaCppEmbedder(
url = "http://localhost:11000",
dimension = 3,
httpClient = HttpClient(mockEngine),
)
val result = embedder.embed("test text")
assert(result.size == 3)
assert(kotlin.math.abs(result[0] - 0.5f) < 0.0001f)
assert(kotlin.math.abs(result[2] - 0.7f) < 0.0001f)
}
@Test
fun `embed throws on dimension mismatch`(): Unit = runBlocking {
val mockEngine = MockEngine { request ->