Skip to content

Commit ea6cc04

Browse files
authored
Add multi-LLM support with configurable compression model and file logging (#1394)
This PR adds support for using different AI models for different tasks: Claude Sonnet 4.5 for the main agent work and a cheaper model (GPT-4.1-mini) for compressing conversation history. It also adds file logging to track what the agent is doing. --- #### Type of the changes - [ ] New feature (non-breaking change which adds functionality) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [x] Refactoring - [ ] CI/CD changes - [ ] Dependencies update #### Checklist - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [ ] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [ ] An issue describing the proposed change exists - [ ] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated
1 parent 5f76fbd commit ea6cc04

File tree

6 files changed

+64
-15
lines changed

6 files changed

+64
-15
lines changed

agents/agents-core/src/commonMain/kotlin/ai/koog/agents/ext/agent/SingleRunStrategyWithHistoryCompression.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import ai.koog.agents.core.dsl.extension.onMultipleToolCalls
1818
import ai.koog.agents.core.dsl.extension.onToolCall
1919
import ai.koog.agents.core.environment.ReceivedToolResult
2020
import ai.koog.prompt.dsl.Prompt
21+
import ai.koog.prompt.llm.LLModel
2122
import ai.koog.prompt.message.Message
2223

2324
/**
@@ -27,10 +28,12 @@ import ai.koog.prompt.message.Message
2728
* when the message count or token size exceeds a threshold
2829
* @property compressionStrategy [HistoryCompressionStrategy] implementation that defines
2930
* how to compress the conversation history
31+
* @property retrievalModel Optional [LLModel] to use for compression (defaults to agent's model)
3032
*/
3133
public data class HistoryCompressionConfig(
3234
val isHistoryTooBig: (Prompt) -> Boolean,
33-
val compressionStrategy: HistoryCompressionStrategy
35+
val compressionStrategy: HistoryCompressionStrategy,
36+
val retrievalModel: LLModel? = null
3437
)
3538

3639
/**
@@ -40,8 +43,8 @@ public data class HistoryCompressionConfig(
4043
* if the conversation history becomes too large (based on [HistoryCompressionConfig.isHistoryTooBig]),
4144
* it compresses the message list to essential facts before continuing.
4245
*
43-
* @param config specifies when to trigger compression (size threshold) and how to compress
44-
* (fact extraction strategy)
46+
* @param config specifies when to trigger compression (size threshold), how to compress
47+
* (fact extraction strategy), and optionally which model to use for compression
4548
* @param runMode how tools are executed: [ToolCalls.SINGLE_RUN_SEQUENTIAL] (one tool per LLM call),
4649
* [ToolCalls.SEQUENTIAL] (multiple tools per call, executed sequentially), or [ToolCalls.PARALLEL]
4750
* (multiple tools per call, executed concurrently)
@@ -64,7 +67,10 @@ private fun singleRunWithHistoryCompressionParallelAbility(
6467
val nodeCallLLM by nodeLLMRequestMultiple()
6568
val nodeExecuteTool by nodeExecuteMultipleTools(parallelTools = parallelTools)
6669
val nodeSendToolResult by nodeLLMSendMultipleToolResults()
67-
val nodeCompressHistory by nodeLLMCompressHistory<List<ReceivedToolResult>>(strategy = config.compressionStrategy)
70+
val nodeCompressHistory by nodeLLMCompressHistory<List<ReceivedToolResult>>(
71+
strategy = config.compressionStrategy,
72+
retrievalModel = config.retrievalModel
73+
)
6874
val nodeSendCompressedHistory by node<List<ReceivedToolResult>, List<Message.Response>> {
6975
llm.writeSession {
7076
requestLLMMultiple()
@@ -104,7 +110,10 @@ private fun singleRunWithHistoryCompressionModeStrategy(config: HistoryCompressi
104110
val nodeCallLLM by nodeLLMRequest()
105111
val nodeExecuteTool by nodeExecuteTool()
106112
val nodeSendToolResult by nodeLLMSendToolResult()
107-
val compressHistory by nodeLLMCompressHistory<ReceivedToolResult>(strategy = config.compressionStrategy)
113+
val compressHistory by nodeLLMCompressHistory<ReceivedToolResult>(
114+
strategy = config.compressionStrategy,
115+
retrievalModel = config.retrievalModel
116+
)
108117
val nodeSendCompressedHistory by node<ReceivedToolResult, Message.Response> {
109118
llm.writeSession {
110119
requestLLM()

examples/code-agent/step-05-history/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ A code agent with history compression that handles long-running tasks without hi
55
## Prerequisites
66

77
- Java 17+
8-
- OpenAI API key
8+
- Anthropic API key (main agent)
9+
- OpenAI API key (for history compression and find sub-agent)
910
- (Optional) Langfuse credentials for observability
1011

1112
## Setup
1213

1314
```bash
15+
export ANTHROPIC_API_KEY=your_anthropic_key
1416
export OPENAI_API_KEY=your_openai_key
1517
```
1618

examples/code-agent/step-05-history/src/main/kotlin/CodeAgentHistoryCompressionConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ val CODE_AGENT_HISTORY_TOO_BIG: (Prompt) -> Boolean = { prompt ->
1616
* Extracts key facts from conversation history.
1717
* LLM answers these questions, and the answers become the compressed history.
1818
*/
19-
val CODE_AGENT_COMPRESSION = RetrieveFactsFromHistory(
19+
val CODE_AGENT_COMPRESSION_STRATEGY = RetrieveFactsFromHistory(
2020
Concept(
2121
"project-structure",
2222
"What is the structure of this project?",

examples/code-agent/step-05-history/src/main/kotlin/Main.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,22 @@ import ai.koog.agents.ext.tool.shell.ExecuteShellCommandTool
1212
import ai.koog.agents.ext.tool.shell.JvmShellCommandExecutor
1313
import ai.koog.agents.ext.tool.shell.PrintShellCommandConfirmationHandler
1414
import ai.koog.agents.ext.tool.shell.ShellCommandConfirmation
15+
import ai.koog.prompt.executor.clients.anthropic.AnthropicLLMClient
16+
import ai.koog.prompt.executor.clients.anthropic.AnthropicModels
17+
import ai.koog.prompt.executor.clients.openai.OpenAILLMClient
1518
import ai.koog.prompt.executor.clients.openai.OpenAIModels
16-
import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor
19+
import ai.koog.prompt.executor.llms.MultiLLMPromptExecutor
20+
import ai.koog.prompt.llm.LLMProvider
1721
import ai.koog.rag.base.files.JVMFileSystemProvider
1822

19-
val executor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY"))
23+
val multiExecutor = MultiLLMPromptExecutor(
24+
LLMProvider.Anthropic to AnthropicLLMClient(System.getenv("ANTHROPIC_API_KEY")),
25+
LLMProvider.OpenAI to OpenAILLMClient(System.getenv("OPENAI_API_KEY"))
26+
)
27+
2028
val agent = AIAgent(
21-
promptExecutor = executor,
22-
llmModel = OpenAIModels.Chat.GPT5Codex,
29+
promptExecutor = multiExecutor,
30+
llmModel = AnthropicModels.Sonnet_4_5,
2331
toolRegistry = ToolRegistry {
2432
tool(ListDirectoryTool(JVMFileSystemProvider.ReadOnly))
2533
tool(ReadFileTool(JVMFileSystemProvider.ReadOnly))
@@ -43,7 +51,8 @@ val agent = AIAgent(
4351
strategy = singleRunStrategyWithHistoryCompression(
4452
config = HistoryCompressionConfig(
4553
isHistoryTooBig = CODE_AGENT_HISTORY_TOO_BIG,
46-
compressionStrategy = CODE_AGENT_COMPRESSION
54+
compressionStrategy = CODE_AGENT_COMPRESSION_STRATEGY,
55+
retrievalModel = OpenAIModels.Chat.GPT4_1Mini
4756
)
4857
),
4958
maxIterations = 400
@@ -72,6 +81,6 @@ suspend fun main(args: Array<String>) {
7281
val result = agent.run(input)
7382
println(result)
7483
} finally {
75-
executor.close()
84+
multiExecutor.close()
7685
}
7786
}

examples/code-agent/step-05-history/src/main/kotlin/Observability.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import ai.koog.agents.features.eventHandler.feature.handleEvents
55
import ai.koog.agents.features.opentelemetry.attribute.CustomAttribute
66
import ai.koog.agents.features.opentelemetry.feature.OpenTelemetry
77
import ai.koog.agents.features.opentelemetry.integration.langfuse.addLangfuseExporter
8+
import io.github.oshai.kotlinlogging.KotlinLogging
9+
10+
private val logger = KotlinLogging.logger("code-agent-events")
811

912
/**
1013
* Extracted observability setup used by agents in this module.
@@ -21,7 +24,19 @@ fun GraphAIAgent.FeatureContext.setupObservability(agentName: String) {
2124
}
2225
handleEvents {
2326
onToolCallStarting { ctx ->
24-
println("[$agentName] Tool '${ctx.toolName}' called with args: ${ctx.toolArgs.toString().take(100)}")
27+
logger.info { "[$agentName] Tool '${ctx.toolName}' called with args: ${ctx.toolArgs.toString().take(100)}" }
28+
}
29+
onNodeExecutionStarting { ctx ->
30+
if (ctx.node.name == "compressHistory") {
31+
val messages = ctx.context.llm.prompt.messages
32+
logger.info { "[$agentName] Pre-compression: ${messages.size} msgs, ${messages.sumOf { it.content.length }} chars" }
33+
}
34+
}
35+
onNodeExecutionCompleted { ctx ->
36+
if (ctx.node.name == "compressHistory") {
37+
val messages = ctx.context.llm.prompt.messages
38+
logger.info { "[$agentName] Post-compression: ${messages.size} msgs, ${messages.sumOf { it.content.length }} chars" }
39+
}
2540
}
2641
}
2742
}

examples/code-agent/step-05-history/src/main/resources/logback.xml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,21 @@
55
</encoder>
66
</appender>
77

8-
<root level="ERROR">
8+
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
9+
<file>${user.dir}/logs/code-agent.log</file>
10+
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
11+
<fileNamePattern>${user.dir}/logs/code-agent.%d{yyyy-MM-dd}.log</fileNamePattern>
12+
<maxHistory>30</maxHistory>
13+
<totalSizeCap>3GB</totalSizeCap>
14+
<cleanHistoryOnStart>true</cleanHistoryOnStart>
15+
</rollingPolicy>
16+
<encoder>
17+
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
18+
</encoder>
19+
</appender>
20+
21+
<root level="INFO">
922
<appender-ref ref="STDOUT" />
23+
<appender-ref ref="FILE" />
1024
</root>
1125
</configuration>

0 commit comments

Comments
 (0)