feat: switchable router embedder and L3 backends via config

Adds [router.embedder] and [router.l3] sections to CorrexConfig with
backend selectors. Ships LlamaCppEmbedder that hits llama.cpp's
/embedding endpoint (handles OpenAI-compatible, simple, and array
response shapes; validates dimension). InfrastructureModule gains
createEmbedderFromConfig and createL3MemoryStoreFromConfig that
dispatch on backend value.

Defaults preserve current behavior (noop embedder + in-memory L3).
Switching to "llamacpp" / "turbovec" is a config-only change — no code
edits required. For turbovec backend, the bundled python sidecar
script is extracted from classpath to ~/.cache/correx/ on first use.
This commit is contained in:
2026-05-30 01:23:14 +04:00
parent 84a7568e15
commit d68c76ee3c
11 changed files with 496 additions and 2 deletions
@@ -6,6 +6,8 @@ import com.correx.core.approvals.DefaultApprovalRepository
import com.correx.core.artifacts.DefaultArtifactReducer
import com.correx.core.artifacts.repository.ArtifactRepository
import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.config.EmbedderConfig
import com.correx.core.config.L3Config
import com.correx.core.events.EventDispatcher
import com.correx.core.events.stores.EventStore
import com.correx.core.inference.CapabilityScore
@@ -33,7 +35,10 @@ import com.correx.infrastructure.artifactscas.index.SqliteArtifactIndex
import com.correx.infrastructure.inference.DefaultProviderRegistry
import com.correx.infrastructure.inference.commons.ModelDescriptor
import com.correx.infrastructure.inference.commons.ResidencyMode
import com.correx.infrastructure.inference.llama.cpp.LlamaCppEmbedder
import com.correx.infrastructure.inference.llama.cpp.LlamaCppInferenceProvider
import com.correx.infrastructure.router.turbovec.TurboVecL3MemoryStore
import com.correx.infrastructure.router.turbovec.TurboVecSidecarConfig
import com.correx.infrastructure.persistence.SqliteEventStore
import com.correx.infrastructure.persistence.artifact.LiveArtifactRepository
import com.correx.infrastructure.tools.DefaultToolRegistry
@@ -49,6 +54,7 @@ import kotlinx.coroutines.runBlocking
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.sql.DriverManager
@Suppress("TooManyFunctions")
@@ -137,6 +143,70 @@ object InfrastructureModule {
fun createInMemoryL3MemoryStore(): L3MemoryStore = InMemoryL3MemoryStore()
fun createLlamaCppEmbedder(config: EmbedderConfig): Embedder {
val url = config.url ?: throw IllegalArgumentException(
"EmbedderConfig with backend 'llamacpp' requires non-null 'url'"
)
return LlamaCppEmbedder(
url = url,
modelId = config.modelId,
dimension = config.dimension,
)
}
fun createTurboVecL3MemoryStore(config: L3Config): L3MemoryStore {
val scriptPath = config.scriptPath?.let { Paths.get(it) }
?: extractBundledTurboVecScript()
val persistPath = config.persistPath?.let { Paths.get(it) }
val sidecarConfig = TurboVecSidecarConfig(
pythonExecutable = config.pythonExecutable,
scriptPath = scriptPath,
dim = config.dim,
bitWidth = config.bitWidth,
persistPath = persistPath,
)
return TurboVecL3MemoryStore(sidecarConfig)
}
private fun extractBundledTurboVecScript(): Path {
val cacheDir = Paths.get(System.getProperty("user.home"), ".cache", "correx")
Files.createDirectories(cacheDir)
val targetPath = cacheDir.resolve("turbovec_sidecar.py")
val inputStream = InfrastructureModule::class.java.getResourceAsStream("/python/turbovec_sidecar.py")
?: throw IllegalStateException("Could not find bundled turbovec_sidecar.py in classpath")
inputStream.use { input ->
Files.copy(input, targetPath, StandardCopyOption.REPLACE_EXISTING)
}
return targetPath
}
fun createEmbedderFromConfig(config: EmbedderConfig): Embedder {
return when (config.backend) {
"noop" -> createNoopEmbedder(config.dimension)
"llamacpp" -> createLlamaCppEmbedder(config)
else -> throw IllegalArgumentException(
"Unknown embedder backend: '${config.backend}'. Supported: noop, llamacpp"
)
}
}
fun createL3MemoryStoreFromConfig(config: L3Config): L3MemoryStore {
return when (config.backend) {
"in_memory" -> createInMemoryL3MemoryStore()
"turbovec" -> createTurboVecL3MemoryStore(config)
else -> throw IllegalArgumentException(
"Unknown L3 backend: '${config.backend}'. Supported: in_memory, turbovec"
)
}
}
fun createRouterFacade(
eventStore: EventStore,
inferenceRouter: InferenceRouter,