feat(config): expose router generation + narration knobs in config file
The router/narration model behaviour was hardcoded (Main built RouterConfig() with defaults). Surface it under the [router] section so it's tunable without a rebuild: [router] conversation_keep_last, retrieval_k, token_budget [router.generation] temperature, top_p, max_tokens (chat/steering) [router.narration] temperature, top_p, max_tokens, max_per_run ConfigLoader parses the new sections (adds asDouble); Main maps the config-layer RouterConfig onto the domain RouterConfig and threads narration.max_per_run into ServerModule. All values default to the previous constants, so behaviour is unchanged when the sections are absent. Documents the block in sample-config.toml and adds parser tests for present/absent cases.
This commit is contained in:
@@ -233,8 +233,11 @@ 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 routerGenerationSection = sections["router.generation"] ?: emptyMap()
|
||||
val routerNarrationSection = sections["router.narration"] ?: emptyMap()
|
||||
val modelsSection = sections["models"] ?: emptyMap()
|
||||
|
||||
val server = ServerConfig(
|
||||
@@ -368,9 +371,27 @@ object ConfigLoader {
|
||||
bitWidth = asInt(routerL3Section["bit_width"], 4),
|
||||
)
|
||||
|
||||
val generation = GenerationSettings(
|
||||
temperature = asDouble(routerGenerationSection["temperature"], 0.7),
|
||||
topP = asDouble(routerGenerationSection["top_p"], 0.9),
|
||||
maxTokens = asInt(routerGenerationSection["max_tokens"], 512),
|
||||
)
|
||||
|
||||
val narration = NarrationSettings(
|
||||
temperature = asDouble(routerNarrationSection["temperature"], 0.7),
|
||||
topP = asDouble(routerNarrationSection["top_p"], 0.9),
|
||||
maxTokens = asInt(routerNarrationSection["max_tokens"], 1024),
|
||||
maxPerRun = asInt(routerNarrationSection["max_per_run"], 100),
|
||||
)
|
||||
|
||||
val router = RouterConfig(
|
||||
embedder = embedder,
|
||||
l3 = l3,
|
||||
conversationKeepLast = asInt(routerSection["conversation_keep_last"], 6),
|
||||
retrievalK = asInt(routerSection["retrieval_k"], 5),
|
||||
tokenBudget = asInt(routerSection["token_budget"], 4096),
|
||||
generation = generation,
|
||||
narration = narration,
|
||||
)
|
||||
|
||||
val models = modelsList.mapNotNull { modelMap ->
|
||||
@@ -457,6 +478,16 @@ object ConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private fun asDouble(value: Any?, default: Double = 0.0): Double {
|
||||
return when (value) {
|
||||
is Double -> value
|
||||
is Int -> value.toDouble()
|
||||
is Long -> value.toDouble()
|
||||
is String -> value.toDoubleOrNull() ?: default
|
||||
else -> default
|
||||
}
|
||||
}
|
||||
|
||||
private fun asBoolean(value: Any?, default: Boolean = false): Boolean {
|
||||
return when (value) {
|
||||
is Boolean -> value
|
||||
|
||||
@@ -89,6 +89,29 @@ data class ProviderConfig(
|
||||
data class RouterConfig(
|
||||
val embedder: EmbedderConfig = EmbedderConfig(),
|
||||
val l3: L3Config = L3Config(),
|
||||
val conversationKeepLast: Int = 6,
|
||||
val retrievalK: Int = 5,
|
||||
val tokenBudget: Int = 4096,
|
||||
val generation: GenerationSettings = GenerationSettings(),
|
||||
val narration: NarrationSettings = NarrationSettings(),
|
||||
)
|
||||
|
||||
/** Sampling/length knobs for router chat + steering replies. */
|
||||
@Serializable
|
||||
data class GenerationSettings(
|
||||
val temperature: Double = 0.7,
|
||||
val topP: Double = 0.9,
|
||||
val maxTokens: Int = 512,
|
||||
)
|
||||
|
||||
/** Router narration knobs. Larger maxTokens than chat so reasoning models can
|
||||
* finish "thinking" and still emit the line; maxPerRun caps narrations per run. */
|
||||
@Serializable
|
||||
data class NarrationSettings(
|
||||
val temperature: Double = 0.7,
|
||||
val topP: Double = 0.9,
|
||||
val maxTokens: Int = 1024,
|
||||
val maxPerRun: Int = 100,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -415,4 +415,51 @@ class ConfigLoaderTest {
|
||||
assertEquals("/home/user/projects", result.tools.allowedWorkspaceRoots[0])
|
||||
assertEquals("/tmp/work", result.tools.allowedWorkspaceRoots[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseToml parses router generation and narration knobs`() {
|
||||
val toml = """
|
||||
[router]
|
||||
conversation_keep_last = 8
|
||||
retrieval_k = 3
|
||||
token_budget = 8192
|
||||
|
||||
[router.generation]
|
||||
temperature = 0.4
|
||||
top_p = 0.8
|
||||
max_tokens = 600
|
||||
|
||||
[router.narration]
|
||||
temperature = 0.55
|
||||
top_p = 0.95
|
||||
max_tokens = 2048
|
||||
max_per_run = 25
|
||||
""".trimIndent()
|
||||
|
||||
val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig
|
||||
|
||||
assertEquals(8, result.router.conversationKeepLast)
|
||||
assertEquals(3, result.router.retrievalK)
|
||||
assertEquals(8192, result.router.tokenBudget)
|
||||
assertEquals(0.4, result.router.generation.temperature)
|
||||
assertEquals(0.8, result.router.generation.topP)
|
||||
assertEquals(600, result.router.generation.maxTokens)
|
||||
assertEquals(0.55, result.router.narration.temperature)
|
||||
assertEquals(2048, result.router.narration.maxTokens)
|
||||
assertEquals(25, result.router.narration.maxPerRun)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseToml uses router defaults when sections absent`() {
|
||||
val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
|
||||
parseTomlMethod.isAccessible = true
|
||||
val result = parseTomlMethod.invoke(ConfigLoader, "[server]\nhost = \"x\"") as CorrexConfig
|
||||
|
||||
assertEquals(6, result.router.conversationKeepLast)
|
||||
assertEquals(512, result.router.generation.maxTokens)
|
||||
assertEquals(1024, result.router.narration.maxTokens)
|
||||
assertEquals(100, result.router.narration.maxPerRun)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user