Skip to content
Open
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 @@ -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<SimpleState> {
* 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<State>(
private val stateType: KType,
) {
public class GOAPPlannerBuilder<State> @PublishedApi internal constructor() {
private val actions: MutableList<Action<State>> = mutableListOf()
private val goals: MutableList<Goal<State>> = mutableListOf()

Expand Down Expand Up @@ -56,7 +86,25 @@ public class GOAPPlannerBuilder<State>(
/**
* Builds the [GOAPPlanner].
*/
public fun build(): GOAPPlanner<State> = GOAPPlanner(actions, goals, stateType)
public fun build(stateType: KType): GOAPPlanner<State> = 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 <reified T> invoke(init: GOAPPlannerBuilder<T>.() -> Unit): GOAPPlanner<T> {
return GOAPPlannerBuilder<T>()
.apply<GOAPPlannerBuilder<T>> { init() }
.build(typeOf<T>())
}
}
}

/**
Expand All @@ -66,11 +114,12 @@ public class GOAPPlannerBuilder<State>(
* @param init The initialization block for the builder.
* @return A new [GOAPPlanner] instance with the defined actions.
*/
public fun <State> 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 <reified State> goap(
stateType: KType = typeOf<State>(),
init: GOAPPlannerBuilder<State>.() -> Unit
): GOAPPlanner<State> {
val builder = GOAPPlannerBuilder<State>(stateType)
builder.init()
return builder.build()
}
): GOAPPlanner<State> = GOAPPlannerBuilder<State>(init)
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -21,7 +20,7 @@ class GOAPPlannerAgentTest {

@Test
fun testGOAPLinearPath() = runTest {
val planner = goap<SimpleState>(typeOf<SimpleState>()) {
val planner = GOAPPlannerBuilder<SimpleState> {
// Action to get the key
action(
name = "Get key",
Expand Down Expand Up @@ -94,7 +93,7 @@ class GOAPPlannerAgentTest {

@Test
fun testGOAPOptimalPathSelection() = runTest {
val planner = goap<PathState>(typeOf<PathState>()) {
val planner = GOAPPlannerBuilder<PathState> {
// Expensive path: cost 10
action(
name = "Expensive route",
Expand Down Expand Up @@ -166,7 +165,7 @@ class GOAPPlannerAgentTest {

@Test
fun testGOAPComplexDependencies() = runTest {
val planner = goap<ComplexState>(typeOf<ComplexState>()) {
val planner = GOAPPlannerBuilder<ComplexState> {
// Gather wood (no prerequisites)
action(
name = "Gather wood",
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/planner-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -154,7 +154,7 @@ data class ContentState(
)

// Create GOAP planner with LLM-powered actions
val planner = goap<ContentState>(typeOf<ContentState>()) {
val planner = GOAPPlannerBuilder<ContentState> {
action(
name = "Create outline",
precondition = { state -> !state.hasOutline },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -122,7 +122,7 @@ suspend fun AIAgentFunctionalContext.evaluateWordings(
}
}

fun grouperPlanner() = goap<State>(typeOf<State>()) {
fun grouperPlanner() = GOAPPlannerBuilder<State> {
goal(
name = "Needed number of good proposals reached"
) { state ->
Expand Down