refactor: rename the router subsystem to Talkie
The 'router' name was misleading — it's the always-on conversational front-end
(CHAT triage + STEERING into a running workflow), not a routing layer, and it's
distinct from InferenceRouter. Rename core:router -> core:talkie, package
com.correx.core.router -> com.correx.core.talkie, and RouterFacade/Config/State/
Repository/ContextBuilder/Projector/Reducer/Response -> Talkie*. Config section
[router] -> [talkie] (legacy [router] still read as fallback).
Persisted wire formats are preserved: ChatTurnRole.ROUTER and the
RouterNarrationEvent @SerialName("RouterNarration") stay, so existing event
logs still replay. RouterNarrationEvent the class is now TalkieNarrationEvent.
This commit is contained in:
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
testImplementation(project(":core:events"))
|
||||
testImplementation(project(":core:router"))
|
||||
testImplementation(project(":core:talkie"))
|
||||
testImplementation(project(":core:sessions"))
|
||||
testImplementation(project(":core:transitions"))
|
||||
testImplementation(project(":core:validation"))
|
||||
|
||||
@@ -8,12 +8,12 @@ import com.correx.core.events.events.StoredEvent
|
||||
import com.correx.core.events.stores.EventStore
|
||||
import com.correx.core.events.types.EventId
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3Hit
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MetadataRehydrator
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.l3.RehydratableL3MemoryStore
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Hit
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MetadataRehydrator
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.l3.RehydratableL3MemoryStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
+120
-120
@@ -5,15 +5,15 @@ import com.correx.core.context.model.TokenBudget
|
||||
import com.correx.core.events.events.L3RetrievedHit
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterContextBuilder
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.router.model.RouterL2Entry
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.RouterTurn
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.TurnRole
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieContextBuilder
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import com.correx.core.talkie.model.RouterL2Entry
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.TalkieTurn
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.TurnRole
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
@@ -24,12 +24,12 @@ import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterContextBuilderTest {
|
||||
class TalkieContextBuilderTest {
|
||||
|
||||
private val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200))
|
||||
private val builder = DefaultRouterContextBuilder(config)
|
||||
private val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 200))
|
||||
private val builder = DefaultTalkieContextBuilder(config)
|
||||
|
||||
private fun buildPack(state: RouterState, budget: TokenBudget): ContextPack = runBlocking {
|
||||
private fun buildPack(state: TalkieState, budget: TokenBudget): ContextPack = runBlocking {
|
||||
builder.build(state, budget)
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build fits within budget when all entries are small`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hi", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hi", clock.now()),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -60,14 +60,14 @@ class RouterContextBuilderTest {
|
||||
fun `build drops entries when budget is exceeded`() {
|
||||
// Build a state where conversation + L2 entries together exceed the tight budget
|
||||
val longContent = "x".repeat(800) // ~400 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longContent, clock.now()),
|
||||
RouterTurn(TurnRole.ROUTER, longContent, clock.now()),
|
||||
RouterTurn(TurnRole.USER, longContent, clock.now()),
|
||||
TalkieTurn(TurnRole.USER, longContent, clock.now()),
|
||||
TalkieTurn(TurnRole.ROUTER, longContent, clock.now()),
|
||||
TalkieTurn(TurnRole.USER, longContent, clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -82,10 +82,10 @@ class RouterContextBuilderTest {
|
||||
fun `build drops entries oldest-first for L2 memory when budget forces partial drop`() {
|
||||
// Each L2 entry: "Stage sN (SUCCESS): " + 200-char summary ≈ 55 tokens each.
|
||||
// Use a config with conversationKeepLast=0 so only L0 + L2 compete for budget.
|
||||
val configNoConv = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderNoConv = DefaultRouterContextBuilder(configNoConv)
|
||||
val configNoConv = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderNoConv = DefaultTalkieContextBuilder(configNoConv)
|
||||
val summary = "x".repeat(200) // ~50 tokens; with prefix "Stage sN (SUCCESS): " ~55 tokens total
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -115,18 +115,18 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build drops conversation turns oldest-first when conversationKeepLast exceeded`() {
|
||||
val configWide = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderWide = DefaultRouterContextBuilder(configWide)
|
||||
val configWide = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderWide = DefaultTalkieContextBuilder(configWide)
|
||||
val longContent = "x".repeat(400)
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-04T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-03T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-04T00:00:00Z")),
|
||||
),
|
||||
)
|
||||
// Budget allows L0 + 2 long conversation turns; oldest 2 turns are dropped.
|
||||
@@ -144,7 +144,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 system prompt is always present`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
currentStageId = null,
|
||||
@@ -156,7 +156,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 workflow status is always present`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.FAILED,
|
||||
currentStageId = StageId("failed-stage"),
|
||||
@@ -171,7 +171,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 entries survive zero budget`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.COMPLETED,
|
||||
currentStageId = null,
|
||||
@@ -185,12 +185,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L0 entries survive budget that cannot cover L1 or L2`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "x".repeat(500), clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "x".repeat(500), clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -215,10 +215,10 @@ class RouterContextBuilderTest {
|
||||
// Each L2 entry: "Stage sN (SUCCESS): " + 100-char summary ≈ 30 tokens each.
|
||||
// L0 ~30 tokens; budget 90 leaves ~60. Two entries (~30 each = ~60) fit; oldest dropped.
|
||||
val summary = "x".repeat(100) // ~25 tokens; with prefix ~30 tokens total
|
||||
val configTight = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderTight = DefaultRouterContextBuilder(configTight)
|
||||
val configTight = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderTight = DefaultTalkieContextBuilder(configTight)
|
||||
val baseTime = Instant.parse("2026-01-01T00:00:00Z")
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -251,9 +251,9 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L2 entries fit within budget are all retained`() {
|
||||
val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -274,7 +274,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `system prompt is classified as L0`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
)
|
||||
@@ -286,7 +286,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `workflow status is classified as L0`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -298,13 +298,13 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversation turns are classified as L1`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello", clock.now()),
|
||||
RouterTurn(TurnRole.ROUTER, "hi", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello", clock.now()),
|
||||
TalkieTurn(TurnRole.ROUTER, "hi", clock.now()),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -317,7 +317,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `l2 memory entries are classified as L2`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -335,12 +335,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `no entries other than L0 L1 L2 appear in pack`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello", clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -359,18 +359,18 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversationHistory respects conversationKeepLast from config`() {
|
||||
val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "one", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "two", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "three", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "four", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "five", Instant.parse("2026-01-05T00:00:00Z")),
|
||||
),
|
||||
)
|
||||
val pack = runBlocking { builder.build(state, TokenBudget(limit = 10000)) }
|
||||
@@ -381,17 +381,17 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversationHistory takeLast preserves ordering`() {
|
||||
val config = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "first", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "second", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "third", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "fourth", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -409,7 +409,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build with empty state produces L0 only`() {
|
||||
val state = RouterState()
|
||||
val state = TalkieState()
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
val l0Entries = pack.layers[ContextLayer.L0]
|
||||
assertNotNull(l0Entries)
|
||||
@@ -420,7 +420,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `pack contains correct metadata on empty state`() {
|
||||
val state = RouterState()
|
||||
val state = TalkieState()
|
||||
val pack = buildPack(state, TokenBudget(limit = 5000))
|
||||
assertEquals(5000, pack.budgetLimit)
|
||||
assertEquals(0, pack.compressionMetadata.entriesDropped)
|
||||
@@ -430,7 +430,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `pack has correct context pack id and session info`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -447,7 +447,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `system prompt has expected content`() {
|
||||
val state = RouterState()
|
||||
val state = TalkieState()
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
val systemEntry = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" }
|
||||
assertNotNull(systemEntry)
|
||||
@@ -461,7 +461,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `workflow status entry contains status and stage`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.COMPLETED,
|
||||
currentStageId = null,
|
||||
@@ -475,12 +475,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversation turn entry is formatted with role prefix`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "user message", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "user message", clock.now()),
|
||||
),
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 10000))
|
||||
@@ -492,7 +492,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `l2 entry is formatted with stage outcome and summary`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -516,12 +516,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `budgetUsed equals sum of tokenEstimate of all retained entries`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello world", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello world", clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -535,14 +535,14 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `compressionMetadata entriesDropped reflects dropped count`() {
|
||||
val config = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "x".repeat(200), clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "x".repeat(200), clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "x".repeat(500), StageOutcomeKind.SUCCESS, clock.now()),
|
||||
@@ -561,17 +561,17 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build with full state including all layers`() {
|
||||
val config = RouterConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultRouterContextBuilder(config)
|
||||
val state = RouterState(
|
||||
val config = TalkieConfig(conversationKeepLast = 2, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder = DefaultTalkieContextBuilder(config)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
RouterTurn(TurnRole.USER, "second turn", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
RouterTurn(TurnRole.ROUTER, "second reply", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "first turn", Instant.parse("2026-01-01T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "first reply", Instant.parse("2026-01-02T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.USER, "second turn", Instant.parse("2026-01-03T00:00:00Z")),
|
||||
TalkieTurn(TurnRole.ROUTER, "second reply", Instant.parse("2026-01-04T00:00:00Z")),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(
|
||||
@@ -614,7 +614,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `build with null sessionId produces unknown pack id`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
)
|
||||
val pack = buildPack(state, TokenBudget(limit = 1000))
|
||||
@@ -627,7 +627,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L3 hits injected into pack ordered by score descending`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -667,7 +667,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `L3 entries carry recalled memory label in content`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
lastRetrievedMemory = listOf(
|
||||
@@ -694,7 +694,7 @@ class RouterContextBuilderTest {
|
||||
fun `L3 hits dropped when budget exhausted and counted in entriesDropped`() {
|
||||
// Budget tight enough for L0 but not L3 entries with long text
|
||||
val longText = "x".repeat(800) // ~200 tokens
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
lastRetrievedMemory = listOf(
|
||||
@@ -721,14 +721,14 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
fun `system prompt and current user turn survive tight budget`() {
|
||||
val longContent = "x".repeat(800) // ~200 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z"), turnId = "old-turn"),
|
||||
RouterTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z"), turnId = "mid-turn"),
|
||||
RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "current-turn"),
|
||||
TalkieTurn(TurnRole.USER, longContent, Instant.parse("2026-01-01T00:00:00Z"), turnId = "old-turn"),
|
||||
TalkieTurn(TurnRole.ROUTER, longContent, Instant.parse("2026-01-02T00:00:00Z"), turnId = "mid-turn"),
|
||||
TalkieTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "current-turn"),
|
||||
),
|
||||
)
|
||||
// Budget 60: L0 frame ~30 tokens -> remaining ~30; current user turn ~4 tokens -> protected.
|
||||
@@ -748,17 +748,17 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `oldest conversation turn is dropped before newest when budget is tight`() {
|
||||
val config3 = RouterConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder3 = DefaultRouterContextBuilder(config3)
|
||||
val config3 = TalkieConfig(conversationKeepLast = 3, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builder3 = DefaultTalkieContextBuilder(config3)
|
||||
val longContent = "x".repeat(800) // ~200 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "oldest turn $longContent", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
|
||||
RouterTurn(TurnRole.ROUTER, "middle turn $longContent", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
|
||||
RouterTurn(TurnRole.USER, "newest turn", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
|
||||
TalkieTurn(TurnRole.USER, "oldest turn $longContent", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
|
||||
TalkieTurn(TurnRole.ROUTER, "middle turn $longContent", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
|
||||
TalkieTurn(TurnRole.USER, "newest turn", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
|
||||
),
|
||||
)
|
||||
// Budget 100: L0 frame ~30 tokens -> remaining ~70; newest turn ~3 tokens -> protected.
|
||||
@@ -776,12 +776,12 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
fun `L3 and L2 dropped before recent conversation when budget is tight`() {
|
||||
val longContent = "x".repeat(200) // ~50 tokens each
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"),
|
||||
TalkieTurn(TurnRole.USER, "current user message", clock.now(), turnId = "curr"),
|
||||
),
|
||||
lastRetrievedMemory = listOf(
|
||||
L3RetrievedHit(
|
||||
@@ -816,7 +816,7 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
fun `available workflows entry is injected into L0 when non-empty`() {
|
||||
val workflows = listOf(WorkflowSummary("healthcheck", "Runs the health check pipeline", listOf("collect", "report")))
|
||||
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), availableWorkflows = workflows) }
|
||||
val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), availableWorkflows = workflows) }
|
||||
val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" }
|
||||
assertNotNull(entry)
|
||||
assertTrue(entry!!.content.contains("## Available workflows"))
|
||||
@@ -825,27 +825,27 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `available workflows entry is omitted when list is empty`() {
|
||||
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000)) }
|
||||
val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000)) }
|
||||
assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "availableWorkflows" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `project profile text is injected into L0 when non-null`() {
|
||||
val text = "## Project profile\nA Kotlin service.\n### Conventions\n- Use runCatching"
|
||||
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), projectProfileText = text) }
|
||||
val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), projectProfileText = text) }
|
||||
val entry = (pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" }
|
||||
assertEquals(text, entry!!.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `project profile text is omitted when null or blank`() {
|
||||
val pack = runBlocking { builder.build(RouterState(), TokenBudget(limit = 10000), projectProfileText = " ") }
|
||||
val pack = runBlocking { builder.build(TalkieState(), TokenBudget(limit = 10000), projectProfileText = " ") }
|
||||
assertNull((pack.layers[ContextLayer.L0] ?: emptyList()).find { it.sourceType == "projectProfile" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `system prompt contains triage directive not bare refusal`() {
|
||||
val pack = buildPack(RouterState(), TokenBudget(limit = 10000))
|
||||
val pack = buildPack(TalkieState(), TokenBudget(limit = 10000))
|
||||
val content = pack.layers[ContextLayer.L0]?.find { it.sourceType == "systemPrompt" }!!.content
|
||||
assertFalse(content.contains("if something is not in context, say you do not have it"))
|
||||
assertTrue(content.contains("Available workflows", ignoreCase = true))
|
||||
@@ -855,13 +855,13 @@ class RouterContextBuilderTest {
|
||||
@Test
|
||||
fun `truncatedLayers populated with affected layers when drops occur`() {
|
||||
val longText = "x".repeat(800)
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longText, clock.now(), turnId = "old"),
|
||||
RouterTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"),
|
||||
TalkieTurn(TurnRole.USER, longText, clock.now(), turnId = "old"),
|
||||
TalkieTurn(TurnRole.USER, "current", clock.now(), turnId = "curr"),
|
||||
),
|
||||
lastRetrievedMemory = listOf(
|
||||
L3RetrievedHit(
|
||||
@@ -889,12 +889,12 @@ class RouterContextBuilderTest {
|
||||
// The current user turn must still be included; budgetUsed must report the true total;
|
||||
// appliedStrategies must contain "BudgetExceeded".
|
||||
val longUserTurn = "x".repeat(200) // ~50 tokens
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, longUserTurn, clock.now(), turnId = "current"),
|
||||
TalkieTurn(TurnRole.USER, longUserTurn, clock.now(), turnId = "current"),
|
||||
),
|
||||
)
|
||||
// Budget 1: system prompt + workflow status alone exceed this.
|
||||
@@ -928,16 +928,16 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `conversationKeepLast zero still includes the current user turn`() {
|
||||
val configZero = RouterConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderZero = DefaultRouterContextBuilder(configZero)
|
||||
val state = RouterState(
|
||||
val configZero = TalkieConfig(conversationKeepLast = 0, tokenBudget = TokenBudget(limit = 10000))
|
||||
val builderZero = DefaultTalkieContextBuilder(configZero)
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "old turn 1", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
|
||||
RouterTurn(TurnRole.ROUTER, "old reply", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
|
||||
RouterTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
|
||||
TalkieTurn(TurnRole.USER, "old turn 1", Instant.parse("2026-01-01T00:00:00Z"), turnId = "t1"),
|
||||
TalkieTurn(TurnRole.ROUTER, "old reply", Instant.parse("2026-01-02T00:00:00Z"), turnId = "t2"),
|
||||
TalkieTurn(TurnRole.USER, "current user input", Instant.parse("2026-01-03T00:00:00Z"), turnId = "t3"),
|
||||
),
|
||||
)
|
||||
val pack = runBlocking { builderZero.build(state, TokenBudget(limit = 10000)) }
|
||||
@@ -955,7 +955,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `appliedStrategies includes L3Recall when lastRetrievedMemory is non-empty`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
@@ -978,7 +978,7 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `appliedStrategies does not include L3Recall when lastRetrievedMemory is empty`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
)
|
||||
@@ -995,12 +995,12 @@ class RouterContextBuilderTest {
|
||||
|
||||
@Test
|
||||
fun `layers map is sorted by ContextLayer ordinal`() {
|
||||
val state = RouterState(
|
||||
val state = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
conversationHistory = listOf(
|
||||
RouterTurn(TurnRole.USER, "hello", clock.now()),
|
||||
TalkieTurn(TurnRole.USER, "hello", clock.now()),
|
||||
),
|
||||
l2Memory = listOf(
|
||||
RouterL2Entry(StageId("s1"), "summary", StageOutcomeKind.SUCCESS, clock.now()),
|
||||
+211
-211
@@ -29,26 +29,26 @@ import com.correx.core.inference.NoopEmbedder
|
||||
import com.correx.core.inference.ProviderHealth
|
||||
import com.correx.core.inference.ResponseFormat
|
||||
import com.correx.core.inference.TokenUsage
|
||||
import com.correx.core.router.ChatMode
|
||||
import com.correx.core.router.DefaultRouterContextBuilder
|
||||
import com.correx.core.router.DefaultRouterFacade
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.IdeaReader
|
||||
import com.correx.core.router.RouterContextBuilder
|
||||
import com.correx.core.router.RouterFacade
|
||||
import com.correx.core.router.RouterProjector
|
||||
import com.correx.core.router.RouterRepository
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.RouterResponse
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.RouterTurn
|
||||
import com.correx.core.router.model.TurnRole
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.ChatMode
|
||||
import com.correx.core.talkie.DefaultTalkieContextBuilder
|
||||
import com.correx.core.talkie.DefaultTalkieFacade
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.IdeaReader
|
||||
import com.correx.core.talkie.TalkieContextBuilder
|
||||
import com.correx.core.talkie.TalkieFacade
|
||||
import com.correx.core.talkie.TalkieProjector
|
||||
import com.correx.core.talkie.TalkieRepository
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.TalkieResponse
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.TalkieTurn
|
||||
import com.correx.core.talkie.model.TurnRole
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
||||
import com.correx.testing.fixtures.EventFixtures
|
||||
import com.correx.testing.fixtures.inference.MockTokenizer
|
||||
@@ -63,7 +63,7 @@ import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterFacadeTest {
|
||||
class TalkieFacadeTest {
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// CHAT mode
|
||||
@@ -135,21 +135,21 @@ class RouterFacadeTest {
|
||||
fun `STEERING mode appends SteeringNoteAddedEvent with current stageId`(): Unit = runBlocking {
|
||||
val mockStore = mockEventStore()
|
||||
val stageId = StageId("stage-A")
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("steering response"),
|
||||
eventStore = mockStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -166,26 +166,26 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
fun `conversation history grows per call - user and router turns appended`(): Unit = runBlocking {
|
||||
val capturedStates = mutableListOf<RouterState>()
|
||||
val capturedStates = mutableListOf<TalkieState>()
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedStates.add(state)
|
||||
return emptyContextPack()
|
||||
}
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -206,26 +206,26 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
fun `conversation history is session-scoped - different sessions do not share history`(): Unit = runBlocking {
|
||||
val capturedStates = mutableListOf<RouterState>()
|
||||
val capturedStates = mutableListOf<TalkieState>()
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedStates.add(state)
|
||||
return emptyContextPack()
|
||||
}
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -246,17 +246,17 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
fun `state is passed through to context builder`(): Unit = runBlocking {
|
||||
val capturedState = mutableListOf<RouterState>()
|
||||
val mockContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
val capturedState = mutableListOf<TalkieState>()
|
||||
val mockContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedState.add(state)
|
||||
return emptyContextPack()
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("s1"),
|
||||
@@ -265,7 +265,7 @@ class RouterFacadeTest {
|
||||
routerContextBuilder = mockContextBuilder,
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -278,20 +278,20 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `budget is passed through to context builder`(): Unit = runBlocking {
|
||||
val capturedBudget = mutableListOf<TokenBudget>()
|
||||
val mockContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
val mockContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedBudget.add(budget)
|
||||
return emptyContextPack()
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = mockContextBuilder,
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 4200)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 4200)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -313,20 +313,20 @@ class RouterFacadeTest {
|
||||
}
|
||||
}
|
||||
val stateStageId = StageId("state-stage")
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stateStageId,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -346,20 +346,20 @@ class RouterFacadeTest {
|
||||
return mockProvider("response")
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
currentStageId = null,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -378,16 +378,16 @@ class RouterFacadeTest {
|
||||
return mockProviderWithCapture("response", capturedRequestIds)
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -408,16 +408,16 @@ class RouterFacadeTest {
|
||||
return mockProviderWithRequestCapture("response", capturedRequests)
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -431,12 +431,12 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `context pack is passed to inference provider`(): Unit = runBlocking {
|
||||
val capturedContextPacks = mutableListOf<ContextPack>()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
val pack = ContextPack(
|
||||
id = ContextPackId("test-pack"),
|
||||
sessionId = state.sessionId ?: SessionId("unknown"),
|
||||
@@ -478,7 +478,7 @@ class RouterFacadeTest {
|
||||
}
|
||||
},
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -488,12 +488,12 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `responseFormat defaults to Text`(): Unit = runBlocking {
|
||||
val capturedRequests = mutableListOf<InferenceRequest>()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = object : InferenceRouter {
|
||||
override suspend fun route(
|
||||
@@ -504,7 +504,7 @@ class RouterFacadeTest {
|
||||
}
|
||||
},
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -514,7 +514,7 @@ class RouterFacadeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onUserInput returns RouterResponse with content and mode flag`(): Unit = runBlocking {
|
||||
fun `onUserInput returns TalkieResponse with content and mode flag`(): Unit = runBlocking {
|
||||
val mockStore = mockEventStore()
|
||||
val facade = facadeWithMocks(eventStore = mockStore, chatMode = ChatMode.STEERING)
|
||||
val response = facade.onUserInput(sessionId = SessionId("test-session"), input = "Hello!")
|
||||
@@ -576,19 +576,19 @@ class RouterFacadeTest {
|
||||
override fun capabilities(): Set<CapabilityScore> = setOf(CapabilityScore(ModelCapability.General, 1.0))
|
||||
}
|
||||
val eventStore = mockEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = object : InferenceRouter {
|
||||
override suspend fun route(stageId: StageId, requiredCapabilities: Set<ModelCapability>): InferenceProvider =
|
||||
metricsProvider
|
||||
},
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -717,16 +717,16 @@ class RouterFacadeTest {
|
||||
store: EventStore,
|
||||
reply: String,
|
||||
workflows: List<WorkflowSummary>,
|
||||
): RouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId)
|
||||
): TalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter(reply),
|
||||
eventStore = store,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
workflowSummaryProvider = { workflows },
|
||||
@@ -739,28 +739,28 @@ class RouterFacadeTest {
|
||||
chatMode: ChatMode = ChatMode.CHAT,
|
||||
embedder: Embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(),
|
||||
): RouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
): TalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("s1"),
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = embedder,
|
||||
l3MemoryStore = l3MemoryStore,
|
||||
).let { impl ->
|
||||
object : RouterFacade {
|
||||
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): RouterResponse =
|
||||
object : TalkieFacade {
|
||||
override suspend fun onUserInput(sessionId: SessionId, input: String, mode: ChatMode): TalkieResponse =
|
||||
impl.onUserInput(sessionId = sessionId, input = input, mode = chatMode)
|
||||
override suspend fun narrate(sessionId: SessionId, trigger: com.correx.core.router.model.NarrationTrigger) =
|
||||
override suspend fun narrate(sessionId: SessionId, trigger: com.correx.core.talkie.model.NarrationTrigger) =
|
||||
impl.narrate(sessionId = sessionId, trigger = trigger)
|
||||
}
|
||||
}
|
||||
@@ -872,21 +872,21 @@ class RouterFacadeTest {
|
||||
)
|
||||
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
embedder = stubEmbedder,
|
||||
l3MemoryStore = l3Store,
|
||||
)
|
||||
@@ -928,9 +928,9 @@ class RouterFacadeTest {
|
||||
)
|
||||
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
|
||||
// Bootstrap session with a ChatTurnEvent whose turnId matches the L3 entry
|
||||
@@ -954,17 +954,17 @@ class RouterFacadeTest {
|
||||
)
|
||||
)
|
||||
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000), retrievalK = 5),
|
||||
embedder = stubEmbedder,
|
||||
l3MemoryStore = l3Store,
|
||||
)
|
||||
@@ -986,16 +986,16 @@ class RouterFacadeTest {
|
||||
throw RuntimeException("embedding service unavailable")
|
||||
}
|
||||
val eventStore = mockEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = throwingEmbedder,
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1014,9 +1014,9 @@ class RouterFacadeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in RouterState`(): Unit = runBlocking {
|
||||
val reducer = DefaultRouterReducer()
|
||||
val projector = RouterProjector(reducer)
|
||||
fun `replay of L3MemoryRetrievedEvent populates lastRetrievedMemory in TalkieState`(): Unit = runBlocking {
|
||||
val reducer = DefaultTalkieReducer()
|
||||
val projector = TalkieProjector(reducer)
|
||||
val sessionId = SessionId("replay-session")
|
||||
val hits = listOf(
|
||||
L3RetrievedHit(
|
||||
@@ -1048,28 +1048,28 @@ class RouterFacadeTest {
|
||||
|
||||
@Test
|
||||
fun `ContextTruncatedEvent emitted when context builder drops entries`(): Unit = runBlocking {
|
||||
// Use the real DefaultRouterContextBuilder with a tight budget and large conversation
|
||||
// Use the real DefaultTalkieContextBuilder with a tight budget and large conversation
|
||||
val longContent = "x".repeat(800) // ~200 tokens each
|
||||
val sessionId = SessionId("truncation-session")
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(
|
||||
conversationKeepLast = 4,
|
||||
tokenBudget = TokenBudget(limit = 80),
|
||||
),
|
||||
),
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(
|
||||
config = TalkieConfig(
|
||||
conversationKeepLast = 4,
|
||||
tokenBudget = TokenBudget(limit = 80),
|
||||
),
|
||||
@@ -1112,16 +1112,16 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `no ContextTruncatedEvent when budget is generous`(): Unit = runBlocking {
|
||||
val eventStore = mockEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
),
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1136,9 +1136,9 @@ class RouterFacadeTest {
|
||||
val longContent = "x".repeat(800)
|
||||
val sessionId = SessionId("turnid-session")
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
|
||||
// Pre-seed with a large turn so the budget overflows on the next call
|
||||
@@ -1162,17 +1162,17 @@ class RouterFacadeTest {
|
||||
)
|
||||
)
|
||||
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
),
|
||||
inferenceRouter = mockInferenceRouter("response"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
config = TalkieConfig(conversationKeepLast = 4, tokenBudget = TokenBudget(limit = 80)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1192,28 +1192,28 @@ class RouterFacadeTest {
|
||||
// Each turn must emit L3MemoryRetrievedEvent (with empty hits), so that
|
||||
// lastRetrievedMemory is always current-turn-scoped and never stale.
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val capturedStates = mutableListOf<RouterState>()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val capturedStates = mutableListOf<TalkieState>()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
private val real = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
private val real = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
)
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack {
|
||||
capturedStates.add(state)
|
||||
return real.build(state, budget)
|
||||
}
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -1265,25 +1265,25 @@ class RouterFacadeTest {
|
||||
|
||||
val capturedPacks = mutableListOf<ContextPack>()
|
||||
val eventStore = mockEventStore()
|
||||
val replayer = DefaultEventReplayer<RouterState>(
|
||||
val replayer = DefaultEventReplayer<TalkieState>(
|
||||
store = eventStore,
|
||||
projection = RouterProjector(DefaultRouterReducer()),
|
||||
projection = TalkieProjector(DefaultTalkieReducer()),
|
||||
)
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
replayer.rebuild(sessionId)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
private val real = DefaultRouterContextBuilder(
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
private val real = DefaultTalkieContextBuilder(
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000)),
|
||||
)
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack =
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack =
|
||||
real.build(state, budget).also { capturedPacks.add(it) }
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("router reply"),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 10000), retrievalK = 5),
|
||||
embedder = stubEmbedder,
|
||||
l3MemoryStore = l3Store,
|
||||
)
|
||||
@@ -1321,16 +1321,16 @@ class RouterFacadeTest {
|
||||
return mockProvider("narration text")
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(
|
||||
config = TalkieConfig(
|
||||
tokenBudget = TokenBudget(limit = 5000),
|
||||
narrationModelId = "llama-cpp:phi-3-mini",
|
||||
),
|
||||
@@ -1339,7 +1339,7 @@ class RouterFacadeTest {
|
||||
)
|
||||
facade.narrate(
|
||||
sessionId = SessionId("test-session"),
|
||||
trigger = com.correx.core.router.model.NarrationTrigger(
|
||||
trigger = com.correx.core.talkie.model.NarrationTrigger(
|
||||
kind = "test",
|
||||
instruction = "describe what happened",
|
||||
),
|
||||
@@ -1366,16 +1366,16 @@ class RouterFacadeTest {
|
||||
return mockProvider("response")
|
||||
}
|
||||
}
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState()
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState()
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter,
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(
|
||||
config = TalkieConfig(
|
||||
tokenBudget = TokenBudget(limit = 5000),
|
||||
narrationModelId = "llama-cpp:phi-3-mini",
|
||||
),
|
||||
@@ -1393,9 +1393,9 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `workflowSummaryProvider is called and forwarded to context builder on each turn`(): Unit = runBlocking {
|
||||
val captured = mutableListOf<List<WorkflowSummary>>()
|
||||
val capturingBuilder = object : RouterContextBuilder {
|
||||
val capturingBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary>,
|
||||
projectProfileText: String?,
|
||||
@@ -1413,9 +1413,9 @@ class RouterFacadeTest {
|
||||
@Test
|
||||
fun `sessionProfileProvider is called and forwarded to context builder`(): Unit = runBlocking {
|
||||
val capturedProfiles = mutableListOf<String?>()
|
||||
val capturingBuilder = object : RouterContextBuilder {
|
||||
val capturingBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary>,
|
||||
projectProfileText: String?,
|
||||
@@ -1433,9 +1433,9 @@ class RouterFacadeTest {
|
||||
}
|
||||
|
||||
private fun facadeWith(
|
||||
contextBuilder: RouterContextBuilder = object : RouterContextBuilder {
|
||||
contextBuilder: TalkieContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(
|
||||
state: RouterState,
|
||||
state: TalkieState,
|
||||
budget: TokenBudget,
|
||||
availableWorkflows: List<WorkflowSummary>,
|
||||
projectProfileText: String?,
|
||||
@@ -1443,14 +1443,14 @@ class RouterFacadeTest {
|
||||
},
|
||||
workflowSummaryProvider: () -> List<WorkflowSummary> = { emptyList() },
|
||||
sessionProfileProvider: suspend (SessionId) -> String? = { null },
|
||||
): RouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState = RouterState(sessionId = sessionId)
|
||||
): TalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState = TalkieState(sessionId = sessionId)
|
||||
},
|
||||
routerContextBuilder = contextBuilder,
|
||||
inferenceRouter = mockInferenceRouter("inference response"),
|
||||
eventStore = mockEventStore(),
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
workflowSummaryProvider = workflowSummaryProvider,
|
||||
+52
-52
@@ -4,7 +4,7 @@ import com.correx.core.context.model.TokenBudget
|
||||
import com.correx.core.events.events.ChatTurnEvent
|
||||
import com.correx.core.events.events.EventMetadata
|
||||
import com.correx.core.events.events.NewEvent
|
||||
import com.correx.core.events.events.RouterNarrationEvent
|
||||
import com.correx.core.events.events.TalkieNarrationEvent
|
||||
import com.correx.core.events.events.StoredEvent
|
||||
import com.correx.core.events.stores.EventStore
|
||||
import com.correx.core.events.types.ContextPackId
|
||||
@@ -25,19 +25,19 @@ import com.correx.core.inference.NoopEmbedder
|
||||
import com.correx.core.inference.ProviderHealth
|
||||
import com.correx.core.inference.ResponseFormat
|
||||
import com.correx.core.inference.TokenUsage
|
||||
import com.correx.core.router.DefaultRouterContextBuilder
|
||||
import com.correx.core.router.DefaultRouterFacade
|
||||
import com.correx.core.router.RouterContextBuilder
|
||||
import com.correx.core.router.RouterRepository
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.router.l3.L3MemoryEntry
|
||||
import com.correx.core.router.l3.L3MemoryStore
|
||||
import com.correx.core.router.l3.L3Query
|
||||
import com.correx.core.router.model.NarrationTrigger
|
||||
import com.correx.core.router.model.RouterConfig
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.router.model.WorkflowSummary
|
||||
import com.correx.core.talkie.DefaultTalkieContextBuilder
|
||||
import com.correx.core.talkie.DefaultTalkieFacade
|
||||
import com.correx.core.talkie.TalkieContextBuilder
|
||||
import com.correx.core.talkie.TalkieRepository
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3MemoryEntry
|
||||
import com.correx.core.talkie.l3.L3MemoryStore
|
||||
import com.correx.core.talkie.l3.L3Query
|
||||
import com.correx.core.talkie.model.NarrationTrigger
|
||||
import com.correx.core.talkie.model.TalkieConfig
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.core.talkie.model.WorkflowSummary
|
||||
import com.correx.testing.fixtures.inference.MockTokenizer
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -47,14 +47,14 @@ import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterNarrationTest {
|
||||
class TalkieNarrationTest {
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// narrate() — event emission
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
fun `narrate emits RouterNarrationEvent with inference content and metrics`(): Unit = runBlocking {
|
||||
fun `narrate emits TalkieNarrationEvent with inference content and metrics`(): Unit = runBlocking {
|
||||
val knownLatencyMs = 55L
|
||||
val knownUsage = TokenUsage(promptTokens = 8, completionTokens = 3)
|
||||
val eventStore = MapBackedEventStore()
|
||||
@@ -68,7 +68,7 @@ class RouterNarrationTest {
|
||||
|
||||
facade.narrate(SessionId("sess-1"), trigger)
|
||||
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>()
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
|
||||
assertEquals(1, narrationEvents.size)
|
||||
val event = narrationEvents[0]
|
||||
assertEquals("narration line", event.content)
|
||||
@@ -112,8 +112,8 @@ class RouterNarrationTest {
|
||||
|
||||
facade.narrate(SessionId("sess-4"), trigger)
|
||||
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "blank inference must not emit RouterNarrationEvent")
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "blank inference must not emit TalkieNarrationEvent")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,30 +124,30 @@ class RouterNarrationTest {
|
||||
|
||||
facade.narrate(SessionId("sess-5"), trigger)
|
||||
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "empty inference must not emit RouterNarrationEvent")
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
|
||||
assertTrue(narrationEvents.isEmpty(), "empty inference must not emit TalkieNarrationEvent")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `narrate RouterNarrationEvent carries sessionId and stageId from state`(): Unit = runBlocking {
|
||||
fun `narrate TalkieNarrationEvent carries sessionId and stageId from state`(): Unit = runBlocking {
|
||||
val stageId = StageId("my-stage")
|
||||
val eventStore = MapBackedEventStore()
|
||||
val facade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
val facade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = stageId,
|
||||
)
|
||||
},
|
||||
routerContextBuilder = object : RouterContextBuilder {
|
||||
override suspend fun build(state: RouterState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
override suspend fun buildNarrationContext(state: RouterState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack()
|
||||
routerContextBuilder = object : TalkieContextBuilder {
|
||||
override suspend fun build(state: TalkieState, budget: TokenBudget, availableWorkflows: List<WorkflowSummary>, projectProfileText: String?): ContextPack = emptyContextPack()
|
||||
override suspend fun buildNarrationContext(state: TalkieState, trigger: NarrationTrigger, budget: TokenBudget): ContextPack = emptyContextPack()
|
||||
},
|
||||
inferenceRouter = mockInferenceRouter("stage narration", latencyMs = 10L, tokensUsed = TokenUsage(1, 1)),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = InMemoryL3MemoryStore(),
|
||||
)
|
||||
@@ -155,7 +155,7 @@ class RouterNarrationTest {
|
||||
|
||||
facade.narrate(SessionId("session-x"), trigger)
|
||||
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<RouterNarrationEvent>()
|
||||
val narrationEvents = eventStore.appendedEvents.map { it.payload }.filterIsInstance<TalkieNarrationEvent>()
|
||||
assertEquals(1, narrationEvents.size)
|
||||
assertEquals(SessionId("session-x"), narrationEvents[0].sessionId)
|
||||
assertEquals("my-stage", narrationEvents[0].stageId)
|
||||
@@ -167,8 +167,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext includes system entry in L0`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val trigger = NarrationTrigger(kind = "test", instruction = "Do something.")
|
||||
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
|
||||
|
||||
@@ -179,8 +179,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext includes workflow status entry`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(
|
||||
sessionId = SessionId("s"),
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("my-stage"),
|
||||
@@ -196,8 +196,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext includes trigger instruction as final entry`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val instruction = "Narrate the completed action now."
|
||||
val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction)
|
||||
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
|
||||
@@ -214,8 +214,8 @@ class RouterNarrationTest {
|
||||
// Regression: the trigger instruction must render as a user turn. When it was filed
|
||||
// under L0 it folded into the system message, leaving zero user messages and the chat
|
||||
// template rejected the request ("No user query found in messages").
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.RUNNING)
|
||||
val instruction = "Tell the user the stage just completed."
|
||||
val trigger = NarrationTrigger(kind = "stage_completed", instruction = instruction)
|
||||
val pack = builder.buildNarrationContext(state, trigger, TokenBudget(limit = 5000))
|
||||
@@ -231,8 +231,8 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext stays within budget`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(sessionId = SessionId("s"), workflowStatus = WorkflowStatus.IDLE)
|
||||
val trigger = NarrationTrigger(kind = "test", instruction = "Short.")
|
||||
val budget = TokenBudget(limit = 5000)
|
||||
val pack = builder.buildNarrationContext(state, trigger, budget)
|
||||
@@ -242,13 +242,13 @@ class RouterNarrationTest {
|
||||
|
||||
@Test
|
||||
fun `buildNarrationContext does not include conversationHistory turns`(): Unit = runBlocking {
|
||||
val builder = DefaultRouterContextBuilder(RouterConfig())
|
||||
val state = RouterState(
|
||||
val builder = DefaultTalkieContextBuilder(TalkieConfig())
|
||||
val state = TalkieState(
|
||||
sessionId = SessionId("s"),
|
||||
workflowStatus = WorkflowStatus.IDLE,
|
||||
conversationHistory = listOf(
|
||||
com.correx.core.router.model.RouterTurn(
|
||||
role = com.correx.core.router.model.TurnRole.USER,
|
||||
com.correx.core.talkie.model.TalkieTurn(
|
||||
role = com.correx.core.talkie.model.TurnRole.USER,
|
||||
content = "secret conversation turn",
|
||||
timestamp = kotlinx.datetime.Clock.System.now(),
|
||||
),
|
||||
@@ -274,19 +274,19 @@ class RouterNarrationTest {
|
||||
latencyMs: Long = 10L,
|
||||
tokensUsed: TokenUsage = TokenUsage(5, 5),
|
||||
l3MemoryStore: L3MemoryStore = InMemoryL3MemoryStore(),
|
||||
): DefaultRouterFacade = DefaultRouterFacade(
|
||||
routerRepository = object : RouterRepository {
|
||||
override suspend fun getRouterState(sessionId: SessionId): RouterState =
|
||||
RouterState(
|
||||
): DefaultTalkieFacade = DefaultTalkieFacade(
|
||||
routerRepository = object : TalkieRepository {
|
||||
override suspend fun getTalkieState(sessionId: SessionId): TalkieState =
|
||||
TalkieState(
|
||||
sessionId = sessionId,
|
||||
workflowStatus = WorkflowStatus.RUNNING,
|
||||
currentStageId = StageId("s1"),
|
||||
)
|
||||
},
|
||||
routerContextBuilder = DefaultRouterContextBuilder(RouterConfig()),
|
||||
routerContextBuilder = DefaultTalkieContextBuilder(TalkieConfig()),
|
||||
inferenceRouter = mockInferenceRouter(responseText, latencyMs, tokensUsed),
|
||||
eventStore = eventStore,
|
||||
config = RouterConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
config = TalkieConfig(tokenBudget = TokenBudget(limit = 5000)),
|
||||
embedder = NoopEmbedder(dimension = 8),
|
||||
l3MemoryStore = l3MemoryStore,
|
||||
)
|
||||
@@ -372,7 +372,7 @@ class RouterNarrationTest {
|
||||
storedEntries.add(entry)
|
||||
}
|
||||
|
||||
override suspend fun query(query: L3Query): List<com.correx.core.router.l3.L3Hit> = emptyList()
|
||||
override suspend fun query(query: L3Query): List<com.correx.core.talkie.l3.L3Hit> = emptyList()
|
||||
|
||||
override suspend fun existsByTurnIdPrefix(prefix: String): Boolean = false
|
||||
|
||||
+10
-10
@@ -9,27 +9,27 @@ import com.correx.core.events.events.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.RouterProjector
|
||||
import com.correx.core.router.model.RouterState
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.TalkieProjector
|
||||
import com.correx.core.talkie.model.TalkieState
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterProjectorTest {
|
||||
class TalkieProjectorTest {
|
||||
|
||||
private val reducer = DefaultRouterReducer()
|
||||
private val projector = RouterProjector(reducer)
|
||||
private val reducer = DefaultTalkieReducer()
|
||||
private val projector = TalkieProjector(reducer)
|
||||
private val sessionId = SessionId("s1")
|
||||
private val stageId = StageId("stage-1")
|
||||
private val workflowId = "workflow-test"
|
||||
|
||||
@Test
|
||||
fun `initial() returns RouterState with IDLE status and empty collections`() {
|
||||
fun `initial() returns TalkieState with IDLE status and empty collections`() {
|
||||
val state = projector.initial()
|
||||
assertEquals(WorkflowStatus.IDLE, state.workflowStatus)
|
||||
assertNull(state.sessionId)
|
||||
@@ -190,7 +190,7 @@ class RouterProjectorTest {
|
||||
|
||||
@Test
|
||||
fun `full lifecycle through projector mirrors reducer pipeline`() {
|
||||
var state: RouterState = projector.initial()
|
||||
var state: TalkieState = projector.initial()
|
||||
|
||||
state = projector.apply(
|
||||
state,
|
||||
+5
-5
@@ -9,9 +9,9 @@ import com.correx.core.events.events.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import kotlinx.datetime.Instant
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@@ -20,9 +20,9 @@ import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterReducerTest {
|
||||
class TalkieReducerTest {
|
||||
|
||||
private val reducer = DefaultRouterReducer()
|
||||
private val reducer = DefaultTalkieReducer()
|
||||
private val sessionId = SessionId("s1")
|
||||
private val stageId = StageId("stage-1")
|
||||
private val workflowId = "workflow-test"
|
||||
@@ -29,6 +29,6 @@ dependencies {
|
||||
testImplementation(project(":infrastructure:tools"))
|
||||
testImplementation(project(":infrastructure:tools:filesystem"))
|
||||
testImplementation(project(":apps:server"))
|
||||
testImplementation(project(":core:router"))
|
||||
testImplementation(project(":core:talkie"))
|
||||
testImplementation(project(":core:config"))
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,7 @@ import com.correx.core.inference.Embedder
|
||||
import com.correx.core.journal.DecisionJournalProjector
|
||||
import com.correx.core.journal.DefaultDecisionJournalReducer
|
||||
import com.correx.core.journal.DefaultDecisionJournalRepository
|
||||
import com.correx.core.router.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.talkie.l3.InMemoryL3MemoryStore
|
||||
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
|
||||
import com.correx.infrastructure.persistence.InMemoryEventStore
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@@ -12,7 +12,7 @@ dependencies {
|
||||
testImplementation(project(":core:inference"))
|
||||
testImplementation(project(":core:kernel"))
|
||||
testImplementation(project(":core:artifacts"))
|
||||
testImplementation(project(":core:router"))
|
||||
testImplementation(project(":core:talkie"))
|
||||
testImplementation(project(":core:journal"))
|
||||
implementation(project(":testing:fixtures"))
|
||||
}
|
||||
+7
-7
@@ -8,19 +8,19 @@ import com.correx.core.events.events.WorkflowFailedEvent
|
||||
import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.router.DefaultRouterReducer
|
||||
import com.correx.core.router.RouterProjector
|
||||
import com.correx.core.router.model.StageOutcomeKind
|
||||
import com.correx.core.router.model.WorkflowStatus
|
||||
import com.correx.core.talkie.DefaultTalkieReducer
|
||||
import com.correx.core.talkie.TalkieProjector
|
||||
import com.correx.core.talkie.model.StageOutcomeKind
|
||||
import com.correx.core.talkie.model.WorkflowStatus
|
||||
import com.correx.testing.fixtures.EventFixtures.stored
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RouterProjectorTest {
|
||||
class TalkieProjectorTest {
|
||||
|
||||
private val reducer = DefaultRouterReducer()
|
||||
private val projector = RouterProjector(reducer)
|
||||
private val reducer = DefaultTalkieReducer()
|
||||
private val projector = TalkieProjector(reducer)
|
||||
private val sessionId = SessionId("s1")
|
||||
private val stageId = StageId("stage-1")
|
||||
private val workflowId = "workflow-test"
|
||||
Reference in New Issue
Block a user