merge: integrate sonnet-vikunja (#299,#300,#297,#301) into codex handoff HEAD
# Conflicts: # core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/OrchestrationTuning.kt # examples/workflows/prompts/analyst_freestyle.md
This commit is contained in:
+80
@@ -13,8 +13,10 @@ import com.correx.core.inference.RoutingStrategy
|
||||
import com.correx.testing.fixtures.inference.MockInferenceProvider
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertSame
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class DefaultInferenceRouterTest {
|
||||
|
||||
@@ -157,4 +159,82 @@ class DefaultInferenceRouterTest {
|
||||
val result = router.route(stage, setOf(ModelCapability.General), "llama-cpp:sick")
|
||||
assertSame(healthy, result)
|
||||
}
|
||||
|
||||
// ── bounded wait for a briefly-absent provider (#299) ─────────────────────
|
||||
|
||||
@Test
|
||||
fun `waits for the sole capable provider to recover instead of failing immediately`(): Unit = runBlocking {
|
||||
var healthChecks = 0
|
||||
val recovering = object : InferenceProvider by provider("a", ModelCapability.ToolCalling) {
|
||||
override suspend fun healthCheck(): ProviderHealth {
|
||||
healthChecks++
|
||||
return if (healthChecks < 3) ProviderHealth.Unavailable("connection dropped") else ProviderHealth.Healthy
|
||||
}
|
||||
}
|
||||
val router = DefaultInferenceRouter(
|
||||
registryOf(recovering),
|
||||
firstStrategy(),
|
||||
unavailableRetryAttempts = 5,
|
||||
unavailableRetryDelay = 5.milliseconds,
|
||||
)
|
||||
val result = router.route(stage, setOf(ModelCapability.ToolCalling))
|
||||
assertSame(recovering, result)
|
||||
assertTrue(healthChecks >= 3) { "expected at least 3 health checks, got $healthChecks" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `still throws NoEligibleProviderException if the sole provider never recovers within the bound`() {
|
||||
val neverRecovers = MockInferenceProvider(
|
||||
id = ProviderId("a"),
|
||||
declaredCapabilities = setOf(CapabilityScore(ModelCapability.ToolCalling, 1.0)),
|
||||
health = ProviderHealth.Unavailable("still down"),
|
||||
)
|
||||
val router = DefaultInferenceRouter(
|
||||
registryOf(neverRecovers),
|
||||
throwingStrategy(),
|
||||
unavailableRetryAttempts = 2,
|
||||
unavailableRetryDelay = 5.milliseconds,
|
||||
)
|
||||
assertThrows<NoEligibleProviderException> {
|
||||
runBlocking { router.route(stage, setOf(ModelCapability.ToolCalling)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fails fast without waiting when the capability was never configured on any provider`(): Unit = runBlocking {
|
||||
val p = provider("a", ModelCapability.General) // does not declare ToolCalling
|
||||
val router = DefaultInferenceRouter(
|
||||
registryOf(p),
|
||||
throwingStrategy(),
|
||||
unavailableRetryAttempts = 5,
|
||||
unavailableRetryDelay = 10_000.milliseconds, // would time the test out if the wait loop ran
|
||||
)
|
||||
assertThrows<NoEligibleProviderException> {
|
||||
router.route(stage, setOf(ModelCapability.ToolCalling))
|
||||
}
|
||||
}
|
||||
|
||||
// ── event-driven health gating (#300) ─────────────────────────────────────
|
||||
|
||||
@Test
|
||||
fun `reportFailure gates the very next route call without waiting for a health poll`(): Unit = runBlocking {
|
||||
var healthCheckCount = 0
|
||||
val alwaysClaimsHealthy = object : InferenceProvider by provider("a", ModelCapability.General) {
|
||||
override suspend fun healthCheck(): ProviderHealth {
|
||||
healthCheckCount++
|
||||
return ProviderHealth.Healthy // the provider's own health probe hasn't caught up yet
|
||||
}
|
||||
}
|
||||
val backup = provider("b", ModelCapability.General)
|
||||
val router = DefaultInferenceRouter(registryOf(alwaysClaimsHealthy, backup), firstStrategy())
|
||||
|
||||
// Sanity: before reportFailure, routes to the first (still "healthy") provider.
|
||||
assertSame(alwaysClaimsHealthy, router.route(stage, setOf(ModelCapability.General)))
|
||||
|
||||
router.reportFailure(alwaysClaimsHealthy.id, "connection reset")
|
||||
|
||||
// Immediately after — no delay, no waiting for the next poll — routing must avoid it.
|
||||
val result = router.route(stage, setOf(ModelCapability.General))
|
||||
assertSame(backup, result)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user