diff --git a/agents/agents-planner/src/commonMain/kotlin/ai/koog/agents/planner/goap/GOAPPlannerBuilder.kt b/agents/agents-planner/src/commonMain/kotlin/ai/koog/agents/planner/goap/GOAPPlannerBuilder.kt index 5f99926be4..07387099cd 100644 --- a/agents/agents-planner/src/commonMain/kotlin/ai/koog/agents/planner/goap/GOAPPlannerBuilder.kt +++ b/agents/agents-planner/src/commonMain/kotlin/ai/koog/agents/planner/goap/GOAPPlannerBuilder.kt @@ -3,13 +3,43 @@ package ai.koog.agents.planner.goap import ai.koog.agents.core.agent.context.AIAgentFunctionalContext import kotlin.math.exp import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * [GOAPPlanner] DSL builder. + * + * This builder provides a fluent API for defining actions and goals for a Goal-Oriented Action Planning (GOAP) agent. + * It allows you to declaratively specify the available actions, their preconditions and effects, as well as the goals + * the agent should strive to achieve. + * + * Example usage: + * ```kotlin + * data class SimpleState( + * val hasKey: Boolean = false, + * val doorUnlocked: Boolean = false, + * val treasureFound: Boolean = false + * ) + * + * val planner = GOAPPlannerBuilder { + * action( + * name = "Get key", + * precondition = { state -> !state.hasKey }, + * belief = { state -> state.copy(hasKey = true) } + * ) { context, state -> + * // Execution logic + * state.copy(hasKey = true) + * } + * + * goal( + * name = "Find treasure", + * condition = { state -> state.treasureFound } + * ) + * } + * ``` + * + * @param State The type of the state object used by the planner. */ -public class GOAPPlannerBuilder( - private val stateType: KType, -) { +public class GOAPPlannerBuilder @PublishedApi internal constructor() { private val actions: MutableList> = mutableListOf() private val goals: MutableList> = mutableListOf() @@ -56,7 +86,25 @@ public class GOAPPlannerBuilder( /** * Builds the [GOAPPlanner]. */ - public fun build(): GOAPPlanner = GOAPPlanner(actions, goals, stateType) + public fun build(stateType: KType): GOAPPlanner = GOAPPlanner(actions, goals, stateType) + + /** + * Companion object providing factory methods for [GOAPPlannerBuilder]. + */ + public companion object { + /** + * Creates a [GOAPPlanner] instance using the provided configuration block. + * + * @param T The type of the state object. + * @param init The configuration block for the builder. + * @return A new [GOAPPlanner] instance. + */ + public inline operator fun invoke(init: GOAPPlannerBuilder.() -> Unit): GOAPPlanner { + return GOAPPlannerBuilder() + .apply> { init() } + .build(typeOf()) + } + } } /** @@ -66,11 +114,12 @@ public class GOAPPlannerBuilder( * @param init The initialization block for the builder. * @return A new [GOAPPlanner] instance with the defined actions. */ -public fun goap( - stateType: KType, +@Deprecated( + message = "Use GOAPPlannerBuilder directly. This function is deprecated to promote consistent usage of the builder pattern.", + replaceWith = ReplaceWith("GOAPPlannerBuilder(init)"), + level = DeprecationLevel.HIDDEN +) +public inline fun goap( + stateType: KType = typeOf(), init: GOAPPlannerBuilder.() -> Unit -): GOAPPlanner { - val builder = GOAPPlannerBuilder(stateType) - builder.init() - return builder.build() -} +): GOAPPlanner = GOAPPlannerBuilder(init) diff --git a/agents/agents-planner/src/commonTest/kotlin/ai/koog/agents/planner/GOAPPlannerAgentTest.kt b/agents/agents-planner/src/commonTest/kotlin/ai/koog/agents/planner/GOAPPlannerAgentTest.kt index 336b5bf711..544c7f14ed 100644 --- a/agents/agents-planner/src/commonTest/kotlin/ai/koog/agents/planner/GOAPPlannerAgentTest.kt +++ b/agents/agents-planner/src/commonTest/kotlin/ai/koog/agents/planner/GOAPPlannerAgentTest.kt @@ -1,12 +1,11 @@ package ai.koog.agents.planner import ai.koog.agents.core.agent.config.AIAgentConfig -import ai.koog.agents.planner.goap.goap +import ai.koog.agents.planner.goap.GOAPPlannerBuilder import ai.koog.agents.testing.tools.getMockExecutor import ai.koog.prompt.dsl.prompt import ai.koog.prompt.llm.OllamaModels import kotlinx.coroutines.test.runTest -import kotlin.reflect.typeOf import kotlin.test.Test import kotlin.test.assertTrue @@ -21,7 +20,7 @@ class GOAPPlannerAgentTest { @Test fun testGOAPLinearPath() = runTest { - val planner = goap(typeOf()) { + val planner = GOAPPlannerBuilder { // Action to get the key action( name = "Get key", @@ -94,7 +93,7 @@ class GOAPPlannerAgentTest { @Test fun testGOAPOptimalPathSelection() = runTest { - val planner = goap(typeOf()) { + val planner = GOAPPlannerBuilder { // Expensive path: cost 10 action( name = "Expensive route", @@ -166,7 +165,7 @@ class GOAPPlannerAgentTest { @Test fun testGOAPComplexDependencies() = runTest { - val planner = goap(typeOf()) { + val planner = GOAPPlannerBuilder { // Gather wood (no prerequisites) action( name = "Gather wood", diff --git a/docs/docs/planner-agents.md b/docs/docs/planner-agents.md index d976810cfd..659a9ef1d2 100644 --- a/docs/docs/planner-agents.md +++ b/docs/docs/planner-agents.md @@ -129,7 +129,7 @@ import ai.koog.agents.core.agent.context.AIAgentFunctionalContext import ai.koog.agents.core.dsl.extension.requestLLM import ai.koog.agents.planner.AIAgentPlannerStrategy import ai.koog.agents.planner.PlannerAIAgent -import ai.koog.agents.planner.goap.goap +import ai.koog.agents.planner.goap.GOAPPlannerBuilder import ai.koog.prompt.dsl.prompt import ai.koog.prompt.executor.clients.openai.OpenAIModels import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor @@ -154,7 +154,7 @@ data class ContentState( ) // Create GOAP planner with LLM-powered actions -val planner = goap(typeOf()) { +val planner = GOAPPlannerBuilder { action( name = "Create outline", precondition = { state -> !state.hasOutline }, diff --git a/examples/simple-examples/src/main/kotlin/ai/koog/agents/example/goap/GrouperAgent.kt b/examples/simple-examples/src/main/kotlin/ai/koog/agents/example/goap/GrouperAgent.kt index d93983dc13..f392d1c9f3 100644 --- a/examples/simple-examples/src/main/kotlin/ai/koog/agents/example/goap/GrouperAgent.kt +++ b/examples/simple-examples/src/main/kotlin/ai/koog/agents/example/goap/GrouperAgent.kt @@ -5,7 +5,7 @@ import ai.koog.agents.core.agent.context.AIAgentFunctionalContext import ai.koog.agents.example.ApiKeyService import ai.koog.agents.planner.AIAgentPlannerStrategy import ai.koog.agents.planner.PlannerAIAgent -import ai.koog.agents.planner.goap.goap +import ai.koog.agents.planner.goap.GOAPPlannerBuilder import ai.koog.prompt.dsl.prompt import ai.koog.prompt.executor.clients.anthropic.AnthropicLLMClient import ai.koog.prompt.executor.clients.anthropic.AnthropicModels @@ -122,7 +122,7 @@ suspend fun AIAgentFunctionalContext.evaluateWordings( } } -fun grouperPlanner() = goap(typeOf()) { +fun grouperPlanner() = GOAPPlannerBuilder { goal( name = "Needed number of good proposals reached" ) { state ->