Skip to content

Commit fa29fd6

Browse files
style: ktlint format pass across kore-llm/core/rabbitmq/spring
Pure formatting (no semantic changes): multi-line lambda bodies for LLM backends, consistent indentation in test classes, single-line vs block expression alignment in RabbitMqEventBus and KoreAutoConfiguration. Verified zero behavioral diff via review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2671238 commit fa29fd6

10 files changed

Lines changed: 823 additions & 696 deletions

File tree

kore-core/src/test/kotlin/dev/unityinflow/kore/core/integration/HeroDemoTest.kt

Lines changed: 90 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -27,97 +27,110 @@ import org.junit.jupiter.api.Test
2727
* T-07-01 mitigation: HeroDemoTest uses identical syntax to the README example.
2828
*/
2929
class HeroDemoTest {
30-
3130
@Test
32-
fun `hero demo - simple text response`() = runTest {
33-
// This is the README hero demo pattern
34-
val runner = agent("demo-agent") {
35-
model = MockLLMBackend("mock")
36-
.whenCalled(
37-
LLMChunk.Text("Hello from kore!"),
38-
LLMChunk.Usage(inputTokens = 20, outputTokens = 10),
39-
LLMChunk.Done,
40-
)
41-
budget(maxTokens = 1_000)
42-
}
31+
fun `hero demo - simple text response`() =
32+
runTest {
33+
// This is the README hero demo pattern
34+
val runner =
35+
agent("demo-agent") {
36+
model =
37+
MockLLMBackend("mock")
38+
.whenCalled(
39+
LLMChunk.Text("Hello from kore!"),
40+
LLMChunk.Usage(inputTokens = 20, outputTokens = 10),
41+
LLMChunk.Done,
42+
)
43+
budget(maxTokens = 1_000)
44+
}
4345

44-
val result = runner.run(AgentTask(id = "demo-1", input = "say hello")).await()
46+
val result = runner.run(AgentTask(id = "demo-1", input = "say hello")).await()
4547

46-
val success = result.shouldBeInstanceOf<AgentResult.Success>()
47-
success.output shouldBe "Hello from kore!"
48-
success.tokenUsage.inputTokens shouldBe 20
49-
success.tokenUsage.outputTokens shouldBe 10
50-
}
48+
val success = result.shouldBeInstanceOf<AgentResult.Success>()
49+
success.output shouldBe "Hello from kore!"
50+
success.tokenUsage.inputTokens shouldBe 20
51+
success.tokenUsage.outputTokens shouldBe 10
52+
}
5153

5254
@Test
53-
fun `hero demo - tool calling loop`() = runTest {
54-
val toolProvider = MockToolProvider()
55-
.withTool("echo", "Echoes the input", "{}")
56-
.returnsFor("echo", "echoed!")
57-
58-
var llmCallNumber = 0
59-
val mockBackend = object : LLMBackend {
60-
override val name = "mock"
61-
override fun call(
62-
messages: List<ConversationMessage>,
63-
tools: List<ToolDefinition>,
64-
config: LLMConfig,
65-
) = flow {
66-
llmCallNumber++
67-
if (llmCallNumber == 1) {
68-
emit(LLMChunk.ToolCall(id = "c1", name = "echo", arguments = """{"text":"hi"}"""))
69-
emit(LLMChunk.Done)
70-
} else {
71-
emit(LLMChunk.Text("Tool returned: echoed!"))
72-
emit(LLMChunk.Done)
55+
fun `hero demo - tool calling loop`() =
56+
runTest {
57+
val toolProvider =
58+
MockToolProvider()
59+
.withTool("echo", "Echoes the input", "{}")
60+
.returnsFor("echo", "echoed!")
61+
62+
var llmCallNumber = 0
63+
val mockBackend =
64+
object : LLMBackend {
65+
override val name = "mock"
66+
67+
override fun call(
68+
messages: List<ConversationMessage>,
69+
tools: List<ToolDefinition>,
70+
config: LLMConfig,
71+
) = flow {
72+
llmCallNumber++
73+
if (llmCallNumber == 1) {
74+
emit(LLMChunk.ToolCall(id = "c1", name = "echo", arguments = """{"text":"hi"}"""))
75+
emit(LLMChunk.Done)
76+
} else {
77+
emit(LLMChunk.Text("Tool returned: echoed!"))
78+
emit(LLMChunk.Done)
79+
}
80+
}
7381
}
74-
}
75-
}
7682

77-
val runner = agent("tool-agent") {
78-
model = mockBackend
79-
tools(toolProvider)
80-
}
83+
val runner =
84+
agent("tool-agent") {
85+
model = mockBackend
86+
tools(toolProvider)
87+
}
8188

82-
val result = runner.run(AgentTask(id = "tool-1", input = "echo hi")).await()
89+
val result = runner.run(AgentTask(id = "tool-1", input = "echo hi")).await()
8390

84-
val success = result.shouldBeInstanceOf<AgentResult.Success>()
85-
success.output shouldBe "Tool returned: echoed!"
86-
llmCallNumber shouldBe 2
87-
}
91+
val success = result.shouldBeInstanceOf<AgentResult.Success>()
92+
success.output shouldBe "Tool returned: echoed!"
93+
llmCallNumber shouldBe 2
94+
}
8895

8996
@Test
90-
fun `fallback chain compiles and wraps backends`() = runTest {
91-
val primary = MockLLMBackend("primary")
92-
// No .whenCalled() on primary → throws on first call → fallback activates
93-
val fallback = MockLLMBackend("fallback")
94-
.whenCalled(LLMChunk.Text("from fallback"), LLMChunk.Done)
95-
96-
val resilient = primary fallbackTo fallback
97-
98-
resilient.name shouldBe "primary"
99-
100-
// Primary has no responses — will fail — fallback should be used
101-
val runner = agent("fallback-agent") {
102-
model = resilient
97+
fun `fallback chain compiles and wraps backends`() =
98+
runTest {
99+
val primary = MockLLMBackend("primary")
100+
// No .whenCalled() on primary → throws on first call → fallback activates
101+
val fallback =
102+
MockLLMBackend("fallback")
103+
.whenCalled(LLMChunk.Text("from fallback"), LLMChunk.Done)
104+
105+
val resilient = primary fallbackTo fallback
106+
107+
resilient.name shouldBe "primary"
108+
109+
// Primary has no responses — will fail — fallback should be used
110+
val runner =
111+
agent("fallback-agent") {
112+
model = resilient
113+
}
114+
val result = runner.run(AgentTask(id = "fb-1", input = "test")).await()
115+
// primary fails (no scripted response) → fallback returns "from fallback"
116+
val success = result.shouldBeInstanceOf<AgentResult.Success>()
117+
success.output shouldBe "from fallback"
103118
}
104-
val result = runner.run(AgentTask(id = "fb-1", input = "test")).await()
105-
// primary fails (no scripted response) → fallback returns "from fallback"
106-
val success = result.shouldBeInstanceOf<AgentResult.Success>()
107-
success.output shouldBe "from fallback"
108-
}
109119

110120
@Test
111-
fun `budget exceeded stops the agent loop`() = runTest {
112-
val backend = MockLLMBackend("mock")
113-
.whenCalled(LLMChunk.Text("response"), LLMChunk.Done)
121+
fun `budget exceeded stops the agent loop`() =
122+
runTest {
123+
val backend =
124+
MockLLMBackend("mock")
125+
.whenCalled(LLMChunk.Text("response"), LLMChunk.Done)
126+
127+
val runner =
128+
agent("budget-agent") {
129+
model = backend
130+
budget(maxTokens = 0L) // zero budget → BudgetExceeded immediately
131+
}
114132

115-
val runner = agent("budget-agent") {
116-
model = backend
117-
budget(maxTokens = 0L) // zero budget → BudgetExceeded immediately
133+
val result = runner.run(AgentTask(id = "budget-1", input = "test")).await()
134+
result.shouldBeInstanceOf<AgentResult.BudgetExceeded>()
118135
}
119-
120-
val result = runner.run(AgentTask(id = "budget-1", input = "test")).await()
121-
result.shouldBeInstanceOf<AgentResult.BudgetExceeded>()
122-
}
123136
}

kore-llm/src/main/kotlin/dev/unityinflow/kore/llm/ClaudeBackend.kt

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -32,88 +32,97 @@ class ClaudeBackend(
3232
private val defaultModel: String = "claude-3-5-sonnet-20241022",
3333
private val semaphore: Semaphore = Semaphore(10),
3434
) : LLMBackend {
35-
3635
override val name: String = "claude"
3736

3837
override fun call(
3938
messages: List<ConversationMessage>,
4039
tools: List<ToolDefinition>,
4140
config: LLMConfig,
42-
): Flow<LLMChunk> = flow {
43-
val systemPrompt = messages
44-
.filter { it.role == ConversationMessage.Role.System }
45-
.joinToString("\n") { it.content }
41+
): Flow<LLMChunk> =
42+
flow {
43+
val systemPrompt =
44+
messages
45+
.filter { it.role == ConversationMessage.Role.System }
46+
.joinToString("\n") { it.content }
4647

47-
val anthropicMessages = messages
48-
.filter { it.role != ConversationMessage.Role.System }
49-
.map { it.toAnthropicMessageParam() }
48+
val anthropicMessages =
49+
messages
50+
.filter { it.role != ConversationMessage.Role.System }
51+
.map { it.toAnthropicMessageParam() }
5052

51-
val anthropicTools = tools.map { tool ->
52-
Tool.builder()
53-
.name(tool.name)
54-
.description(tool.description)
55-
.inputSchema(
56-
Tool.InputSchema.builder()
57-
.type(Tool.InputSchema.Type.OBJECT)
58-
.build(),
59-
)
60-
.build()
61-
}
53+
val anthropicTools =
54+
tools.map { tool ->
55+
Tool
56+
.builder()
57+
.name(tool.name)
58+
.description(tool.description)
59+
.inputSchema(
60+
Tool.InputSchema
61+
.builder()
62+
.type(Tool.InputSchema.Type.OBJECT)
63+
.build(),
64+
).build()
65+
}
6266

63-
// Semaphore acquired only around the HTTP call — not around retry delay (Pitfall 11)
64-
val response = semaphore.withPermit {
65-
// All SDK I/O inside Dispatchers.IO to prevent thread starvation (Pitfall 1, T-05-03)
66-
withContext(Dispatchers.IO) {
67-
val paramsBuilder = MessageCreateParams.builder()
68-
.model(Model.of(config.model.ifBlank { defaultModel }))
69-
.maxTokens(config.maxTokens.toLong())
70-
.messages(anthropicMessages)
67+
// Semaphore acquired only around the HTTP call — not around retry delay (Pitfall 11)
68+
val response =
69+
semaphore.withPermit {
70+
// All SDK I/O inside Dispatchers.IO to prevent thread starvation (Pitfall 1, T-05-03)
71+
withContext(Dispatchers.IO) {
72+
val paramsBuilder =
73+
MessageCreateParams
74+
.builder()
75+
.model(Model.of(config.model.ifBlank { defaultModel }))
76+
.maxTokens(config.maxTokens.toLong())
77+
.messages(anthropicMessages)
7178

72-
if (systemPrompt.isNotBlank()) paramsBuilder.system(systemPrompt)
73-
if (anthropicTools.isNotEmpty()) paramsBuilder.tools(anthropicTools)
79+
if (systemPrompt.isNotBlank()) paramsBuilder.system(systemPrompt)
80+
if (anthropicTools.isNotEmpty()) paramsBuilder.tools(anthropicTools)
7481

75-
client.messages().create(paramsBuilder.build())
76-
}
77-
}
82+
client.messages().create(paramsBuilder.build())
83+
}
84+
}
7885

79-
// Translate provider-specific response to canonical LLMChunk sequence (Pitfall 5, T-05-05)
80-
// Using isText()/isToolUse() pattern as required by SDK 0.1.0 (not instanceof)
81-
for (block in response.content()) {
82-
when {
83-
block.isText() -> emit(LLMChunk.Text(block.asText().text()))
84-
block.isToolUse() -> {
85-
val toolUse = block.asToolUse()
86-
emit(
87-
LLMChunk.ToolCall(
88-
id = toolUse.id(),
89-
name = toolUse.name(),
90-
// _input() returns JsonValue — convert to JSON string (Pitfall 5)
91-
arguments = toolUse._input().toString(),
92-
),
93-
)
86+
// Translate provider-specific response to canonical LLMChunk sequence (Pitfall 5, T-05-05)
87+
// Using isText()/isToolUse() pattern as required by SDK 0.1.0 (not instanceof)
88+
for (block in response.content()) {
89+
when {
90+
block.isText() -> emit(LLMChunk.Text(block.asText().text()))
91+
block.isToolUse() -> {
92+
val toolUse = block.asToolUse()
93+
emit(
94+
LLMChunk.ToolCall(
95+
id = toolUse.id(),
96+
name = toolUse.name(),
97+
// _input() returns JsonValue — convert to JSON string (Pitfall 5)
98+
arguments = toolUse._input().toString(),
99+
),
100+
)
101+
}
102+
// Unknown content block types are silently ignored per SDK guidance
94103
}
95-
// Unknown content block types are silently ignored per SDK guidance
96104
}
97-
}
98105

99-
val usage = response.usage()
100-
emit(
101-
LLMChunk.Usage(
102-
inputTokens = usage.inputTokens().toInt(),
103-
outputTokens = usage.outputTokens().toInt(),
104-
),
105-
)
106-
emit(LLMChunk.Done)
107-
}
106+
val usage = response.usage()
107+
emit(
108+
LLMChunk.Usage(
109+
inputTokens = usage.inputTokens().toInt(),
110+
outputTokens = usage.outputTokens().toInt(),
111+
),
112+
)
113+
emit(LLMChunk.Done)
114+
}
108115

109116
private fun ConversationMessage.toAnthropicMessageParam(): MessageParam {
110-
val role = when (this.role) {
111-
ConversationMessage.Role.User, ConversationMessage.Role.Tool -> MessageParam.Role.USER
112-
ConversationMessage.Role.Assistant -> MessageParam.Role.ASSISTANT
113-
// System messages filtered out before this is called
114-
ConversationMessage.Role.System -> MessageParam.Role.USER
115-
}
116-
return MessageParam.builder()
117+
val role =
118+
when (this.role) {
119+
ConversationMessage.Role.User, ConversationMessage.Role.Tool -> MessageParam.Role.USER
120+
ConversationMessage.Role.Assistant -> MessageParam.Role.ASSISTANT
121+
// System messages filtered out before this is called
122+
ConversationMessage.Role.System -> MessageParam.Role.USER
123+
}
124+
return MessageParam
125+
.builder()
117126
.role(role)
118127
.content(content)
119128
.build()

0 commit comments

Comments
 (0)