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:
@@ -210,6 +210,9 @@ object ConfigLoader {
|
||||
val toolsFileReadSection = sections["tools.file_read"] ?: emptyMap()
|
||||
val toolsFileWriteSection = sections["tools.file_write"] ?: emptyMap()
|
||||
val toolsFileEditSection = sections["tools.file_edit"] ?: emptyMap()
|
||||
val routerSection = sections["router"] ?: emptyMap()
|
||||
val routerEmbedderSection = sections["router.embedder"] ?: emptyMap()
|
||||
val routerL3Section = sections["router.l3"] ?: emptyMap()
|
||||
|
||||
val server = ServerConfig(
|
||||
host = asString(serverSection["host"], "localhost"),
|
||||
@@ -322,6 +325,27 @@ object ConfigLoader {
|
||||
)
|
||||
}
|
||||
|
||||
val embedder = EmbedderConfig(
|
||||
backend = asString(routerEmbedderSection["backend"], "noop"),
|
||||
dimension = asInt(routerEmbedderSection["dimension"], 1536),
|
||||
url = asStringOrNull(routerEmbedderSection["url"]),
|
||||
modelId = asStringOrNull(routerEmbedderSection["model_id"]),
|
||||
)
|
||||
|
||||
val l3 = L3Config(
|
||||
backend = asString(routerL3Section["backend"], "in_memory"),
|
||||
persistPath = asStringOrNull(routerL3Section["persist_path"]),
|
||||
pythonExecutable = asString(routerL3Section["python_executable"], "python3"),
|
||||
scriptPath = asStringOrNull(routerL3Section["script_path"]),
|
||||
dim = asInt(routerL3Section["dim"], 1536),
|
||||
bitWidth = asInt(routerL3Section["bit_width"], 4),
|
||||
)
|
||||
|
||||
val router = RouterConfig(
|
||||
embedder = embedder,
|
||||
l3 = l3,
|
||||
)
|
||||
|
||||
return CorrexConfig(
|
||||
server = server,
|
||||
tui = tui,
|
||||
@@ -329,6 +353,7 @@ object ConfigLoader {
|
||||
approval = approval,
|
||||
tools = tools,
|
||||
providers = providers,
|
||||
router = router,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ data class CorrexConfig(
|
||||
val approval: ApprovalConfig = ApprovalConfig(),
|
||||
val tools: ToolsConfig = ToolsConfig(),
|
||||
val providers: List<ProviderConfig> = emptyList(),
|
||||
val router: RouterConfig = RouterConfig(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -55,3 +56,27 @@ data class ProviderConfig(
|
||||
val url: String = "http://127.0.0.1:10000",
|
||||
val capabilities: Map<String, Double> = emptyMap(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RouterConfig(
|
||||
val embedder: EmbedderConfig = EmbedderConfig(),
|
||||
val l3: L3Config = L3Config(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EmbedderConfig(
|
||||
val backend: String = "noop",
|
||||
val dimension: Int = 1536,
|
||||
val url: String? = null,
|
||||
val modelId: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class L3Config(
|
||||
val backend: String = "in_memory",
|
||||
val persistPath: String? = null,
|
||||
val pythonExecutable: String = "python3",
|
||||
val scriptPath: String? = null,
|
||||
val dim: Int = 1536,
|
||||
val bitWidth: Int = 4,
|
||||
)
|
||||
|
||||
@@ -217,4 +217,62 @@ class ConfigLoaderTest {
|
||||
assertEquals(1.0, result.providers[0].capabilities["General"])
|
||||
assertEquals(0.7, result.providers[0].capabilities["Coding"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseToml parses router embedder and l3 config`() {
|
||||
val toml = """
|
||||
[router.embedder]
|
||||
backend = "llamacpp"
|
||||
dimension = 1536
|
||||
url = "http://localhost:11000"
|
||||
model_id = "nomic-embed-text"
|
||||
|
||||
[router.l3]
|
||||
backend = "turbovec"
|
||||
persist_path = "/tmp/router/l3.tq"
|
||||
python_executable = "python3"
|
||||
dim = 1536
|
||||
bit_width = 4
|
||||
""".trimIndent()
|
||||
|
||||
val loader = ConfigLoader::class.java
|
||||
val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
|
||||
|
||||
assertEquals("llamacpp", result.router.embedder.backend)
|
||||
assertEquals(1536, result.router.embedder.dimension)
|
||||
assertEquals("http://localhost:11000", result.router.embedder.url)
|
||||
assertEquals("nomic-embed-text", result.router.embedder.modelId)
|
||||
|
||||
assertEquals("turbovec", result.router.l3.backend)
|
||||
assertEquals("/tmp/router/l3.tq", result.router.l3.persistPath)
|
||||
assertEquals("python3", result.router.l3.pythonExecutable)
|
||||
assertEquals(1536, result.router.l3.dim)
|
||||
assertEquals(4, result.router.l3.bitWidth)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseToml applies router defaults when sections absent`() {
|
||||
val toml = """
|
||||
[server]
|
||||
host = "localhost"
|
||||
""".trimIndent()
|
||||
|
||||
val loader = ConfigLoader::class.java
|
||||
val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
|
||||
|
||||
assertEquals("noop", result.router.embedder.backend)
|
||||
assertEquals(1536, result.router.embedder.dimension)
|
||||
assertEquals(null, result.router.embedder.url)
|
||||
assertEquals(null, result.router.embedder.modelId)
|
||||
|
||||
assertEquals("in_memory", result.router.l3.backend)
|
||||
assertEquals(null, result.router.l3.persistPath)
|
||||
assertEquals("python3", result.router.l3.pythonExecutable)
|
||||
assertEquals(1536, result.router.l3.dim)
|
||||
assertEquals(4, result.router.l3.bitWidth)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user