Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ public class GraphAgentBuilder<Input, Output>(
}
}

/**
* Installs a new feature into the GraphAgentBuilder.
*
* @param featureInstaller A lambda function that utilizes the FeatureContext to define the feature to be installed.
* @return The updated instance of GraphAgentBuilder with the newly added feature.
*/
public fun install(
featureInstaller: FeatureContext.() -> Unit,
): GraphAgentBuilder<Input, Output> = apply {
this.featureInstallers += featureInstaller
}

/**
* Builds and returns an instance of `AIAgent` configured using the parameters
* provided to the `GraphAgentBuilder`.
Expand Down
4 changes: 0 additions & 4 deletions examples/simple-examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ dependencies {
//noinspection UseTomlInstead
implementation("ai.koog:agents-features-chat-memory-sql")
//noinspection UseTomlInstead
implementation("ai.koog:agents-features-chat-history-jdbc")
//noinspection UseTomlInstead
implementation("ai.koog:agents-features-persistence-jdbc")
//noinspection UseTomlInstead
implementation("ai.koog:agents-features-a2a-server")
//noinspection UseTomlInstead
implementation("ai.koog:agents-features-a2a-client")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ import ai.koog.agents.example.ApiKeyService
import ai.koog.agents.example.simpleapi.Switch
import ai.koog.agents.example.simpleapi.SwitchTools
import ai.koog.agents.features.eventHandler.feature.handleEvents
import ai.koog.prompt.dsl.prompt
import ai.koog.prompt.executor.clients.anthropic.AnthropicModels
import ai.koog.prompt.executor.clients.openai.OpenAIModels
import ai.koog.prompt.executor.clients.openai.OpenAIResponsesParams
import ai.koog.prompt.executor.clients.openai.base.models.ReasoningEffort
import ai.koog.prompt.executor.clients.openai.models.ReasoningConfig
import ai.koog.prompt.executor.clients.openai.models.ReasoningSummary
import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor
import ai.koog.prompt.executor.model.PromptExecutor
import ai.koog.prompt.message.Message
Expand All @@ -35,8 +40,14 @@ suspend fun main() {
println("\n🔧 Using ${context.toolName} with ${context.toolArgs}... ")
}
onLLMStreamingFrameReceived { context ->
(context.streamFrame as? StreamFrame.TextDelta)?.let { frame ->
print(frame.text)
when (val frame = context.streamFrame) {
is StreamFrame.ReasoningComplete -> println("Reasoning complete:id=${frame.id}\ntext=${frame.text}\nsummary=${frame.summary}")
is StreamFrame.TextComplete -> println("Text complete")
is StreamFrame.ToolCallComplete -> println("Tool call complete")
is StreamFrame.ReasoningDelta -> println("Reasoning delta:id=${frame.id}\ntext=${frame.text}\nsummary=${frame.summary}")
is StreamFrame.TextDelta -> println("Text delta:\n${frame.text}")
is StreamFrame.ToolCallDelta -> println("Tool call delta:\n${frame.content}")
is StreamFrame.End -> println("End")
}
}
onLLMStreamingFailed {
Expand Down Expand Up @@ -68,15 +79,20 @@ private fun openAiAgent(
toolRegistry: ToolRegistry,
executor: PromptExecutor,
installFeatures: FeatureContext.() -> Unit = {}
) = AIAgent(
promptExecutor = executor,
strategy = streamingWithToolsStrategy(),
llmModel = OpenAIModels.Chat.GPT4oMini,
systemPrompt = "You're responsible for running a Switch and perform operations on it by request",
temperature = 0.0,
toolRegistry = toolRegistry,
installFeatures = installFeatures
)
) = AIAgent.builder()
.graphStrategy { streamingWithToolsStrategy() }
.promptExecutor(executor)
.prompt(prompt("agent", OpenAIResponsesParams(
temperature = 1.0,
reasoning = ReasoningConfig(effort = ReasoningEffort.MEDIUM, summary = ReasoningSummary.AUTO)
))
{
system("You're responsible for running a Switch and perform operations on it by request")
})
.llmModel(OpenAIModels.Chat.GPT5_1)
.toolRegistry(toolRegistry)
.install(installFeatures)
.build()

@Suppress("unused")
private fun anthropicAgent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ abstract class ExecutorIntegrationTestBase {
private val testScope = TestScope()
private val basicLimit = 256
private val extendedLimit = 512
private val reasoningLimit = 10000

@AfterEach
fun cleanup() {
Expand Down Expand Up @@ -136,22 +137,22 @@ abstract class ExecutorIntegrationTestBase {
summary = ReasoningSummary.AUTO
),
include = listOf(OpenAIInclude.REASONING_ENCRYPTED_CONTENT),
maxTokens = basicLimit
maxTokens = reasoningLimit
)

is GoogleLLMProvider -> {
val thinkingConfig = GoogleThinkingConfig(
includeThoughts = true,
thinkingBudget = extendedLimit
thinkingBudget = reasoningLimit
)
GoogleParams(
thinkingConfig = thinkingConfig,
// Slightly higher limit to avoid truncation in multi-step reasoning tests
maxTokens = extendedLimit
maxTokens = reasoningLimit
)
}

else -> LLMParams(maxTokens = basicLimit)
else -> LLMParams(maxTokens = reasoningLimit)
}
}

Expand Down Expand Up @@ -214,8 +215,9 @@ abstract class ExecutorIntegrationTestBase {
)

val executor = getExecutor(model)
val params = createReasoningParams(model)

val prompt = Prompt.build("test-streaming") {
val prompt = Prompt.build("test-streaming", params = params) {
system("You are a helpful assistant.")
user("Count from 1 to 5. Like 1, 2, 3 ...")
}
Expand Down Expand Up @@ -1100,17 +1102,11 @@ abstract class ExecutorIntegrationTestBase {
open fun integration_testReasoningStreamingSummaryDeltas(model: LLModel) = runTest(timeout = 300.seconds) {
Models.assumeAvailable(model.provider)

val params = OpenAIResponsesParams(
reasoning = ReasoningConfig(
effort = ReasoningEffort.MEDIUM,
summary = ReasoningSummary.DETAILED
),
include = listOf(OpenAIInclude.REASONING_ENCRYPTED_CONTENT),
maxTokens = basicLimit
)
val params = createReasoningParams(model)

val prompt = Prompt.build("reasoning-streaming-test", params = params) {
system("You are a helpful assistant.")
user("Think about this step by step: What is 12 * 15?")
user("Reason about what is 8 * 9?. Include summary.")
}

val executor = getExecutor(model)
Expand All @@ -1137,7 +1133,7 @@ abstract class ExecutorIntegrationTestBase {
(reasoningText + reasoningSummary).length shouldBeGreaterThan 0

val finalAnswer = textDeltaFrames.joinToString("") { it.text }
finalAnswer.shouldContain("180")
finalAnswer.shouldContain("72")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import ai.koog.prompt.executor.model.PromptExecutor
import ai.koog.prompt.llm.LLMProvider
import ai.koog.prompt.llm.LLModel
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
Expand Down Expand Up @@ -141,6 +140,18 @@ class MultipleLLMPromptExecutorIntegrationTest : ExecutorIntegrationTestBase() {
super.integration_testExecuteStreamingWithTools(model)
}

@ParameterizedTest
@MethodSource("ai.koog.integration.tests.utils.Models#openAIReasoningModels")
override fun integration_testReasoningStreamingSummaryDeltas(model: LLModel) {
super.integration_testReasoningStreamingSummaryDeltas(model)
}

@ParameterizedTest
@MethodSource("ai.koog.integration.tests.utils.Models#openAIReasoningModels")
override fun integration_testReasoningStreamingWithEncryptedContent(model: LLModel) {
super.integration_testReasoningStreamingWithEncryptedContent(model)
}

@ParameterizedTest
@MethodSource("ai.koog.integration.tests.utils.Models#latestModels")
override fun integration_testToolWithRequiredParams(model: LLModel) {
Expand Down Expand Up @@ -295,19 +306,6 @@ class MultipleLLMPromptExecutorIntegrationTest : ExecutorIntegrationTestBase() {
super.integration_testReasoningWithEncryption(model)
}

@Disabled("KG-767 OpenAILLMClient loses Responses API reasoning summary stream events")
@ParameterizedTest
@MethodSource("ai.koog.integration.tests.utils.Models#openAIReasoningModels")
override fun integration_testReasoningStreamingSummaryDeltas(model: LLModel) {
super.integration_testReasoningStreamingSummaryDeltas(model)
}

@ParameterizedTest
@MethodSource("ai.koog.integration.tests.utils.Models#openAIReasoningModels")
override fun integration_testReasoningStreamingWithEncryptedContent(model: LLModel) {
super.integration_testReasoningStreamingWithEncryptedContent(model)
}

@ParameterizedTest
@MethodSource("ai.koog.integration.tests.utils.Models#reasoningCapableModels")
override fun integration_testReasoningMultiStep(model: LLModel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ object Models {
@JvmStatic
fun openAIReasoningModels(): Stream<LLModel> {
return Stream.of(
OpenAIModels.Chat.GPT5_2,
OpenAIModels.Chat.GPT5_4,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public object AnthropicModels : LLModelDefinitions {
LLMCapability.ToolChoice,
LLMCapability.Vision.Image,
LLMCapability.Document,
LLMCapability.Completion
LLMCapability.Completion,
) + thinkingCapabilities,
contextLength = 200_000,
maxOutputTokens = 64_000,
Expand Down Expand Up @@ -184,7 +184,7 @@ public object AnthropicModels : LLModelDefinitions {
LLMCapability.ToolChoice,
LLMCapability.Vision.Image,
LLMCapability.Document,
LLMCapability.Completion
LLMCapability.Completion,
) + thinkingCapabilities,
contextLength = 200_000,
maxOutputTokens = 32_000,
Expand All @@ -209,7 +209,7 @@ public object AnthropicModels : LLModelDefinitions {
LLMCapability.ToolChoice,
LLMCapability.Vision.Image,
LLMCapability.Document,
LLMCapability.Completion
LLMCapability.Completion,
) + thinkingCapabilities,
contextLength = 200_000,
maxOutputTokens = 32_000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public object DeepSeekModels : LLModelDefinitions {
LLMCapability.Schema.JSON.Basic,
LLMCapability.Schema.JSON.Standard,
LLMCapability.MultipleChoices,
LLMCapability.Thinking,
),
contextLength = 64_000,
maxOutputTokens = 64_000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public object GoogleModels : LLModelDefinitions {
)

/**
* Full capabilities including standard, multimodal, tools and native structured output
* Full capabilities including standard, multimodal, tools, native structured output
*/
private val fullCapabilities: List<LLMCapability> =
standardCapabilities + multimodalCapabilities + toolCapabilities + structuredOutputCapabilities
Expand Down Expand Up @@ -130,7 +130,7 @@ public object GoogleModels : LLModelDefinitions {
public val Gemini2_5Pro: LLModel = LLModel(
provider = LLMProvider.Google,
id = "gemini-2.5-pro",
capabilities = fullCapabilities,
capabilities = fullCapabilities + LLMCapability.Thinking,
contextLength = 1_048_576,
maxOutputTokens = 65_536,
)
Expand All @@ -144,7 +144,7 @@ public object GoogleModels : LLModelDefinitions {
public val Gemini2_5Flash: LLModel = LLModel(
provider = LLMProvider.Google,
id = "gemini-2.5-flash",
capabilities = fullCapabilities,
capabilities = fullCapabilities + LLMCapability.Thinking,
contextLength = 1_048_576,
maxOutputTokens = 65_536,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ public object MistralAIModels : LLModelDefinitions {
LLMCapability.Speculation,
LLMCapability.Vision.Image,
LLMCapability.Document,
LLMCapability.MultipleChoices
LLMCapability.MultipleChoices,
LLMCapability.Thinking,
),
contextLength = 128_000
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,10 @@ public class OllamaClient @JvmOverloads constructor(
val chunk = ollamaJson.decodeFromString<OllamaChatResponseDTO>(line)
chunk.message?.let { message ->
if (message.content.isNotEmpty()) {
emitTextDelta(message.content)
emitTextDelta(text = message.content)
}
if (message.thinking.isNullOrEmpty().not()) {
emitReasoningDelta(message.thinking)
emitReasoningDelta(text = message.thinking)
}
message.toolCalls?.forEachIndexed { index, toolCall ->
val name = toolCall.function.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ public object OllamaModels : LLModelDefinitions {
capabilities = listOf(
LLMCapability.Temperature,
LLMCapability.Schema.JSON.Basic,
LLMCapability.Tools
LLMCapability.Tools,
LLMCapability.Thinking,
),
contextLength = 40_960,
)
Expand All @@ -250,7 +251,8 @@ public object OllamaModels : LLModelDefinitions {
capabilities = listOf(
LLMCapability.Temperature,
LLMCapability.Schema.JSON.Basic,
LLMCapability.Tools
LLMCapability.Tools,
LLMCapability.Thinking,
),
contextLength = 40_960,
)
Expand Down Expand Up @@ -359,7 +361,8 @@ public object OllamaModels : LLModelDefinitions {
capabilities = listOf(
LLMCapability.Temperature,
LLMCapability.Schema.JSON.Basic,
LLMCapability.Tools
LLMCapability.Tools,
LLMCapability.Thinking,
),
contextLength = 32_768,
)
Expand Down
Loading