Koog 문서 한국어 번역 12: GOAP 에이전트

·10분 읽기

원문: Koog 공식 문서

GOAP 에이전트

GOAP는 A* 탐색을 사용하여 총 비용을 최소화하면서 목표 조건을 충족하는 최적의 액션 시퀀스를 찾는 알고리즘 기반 계획 접근 방식입니다. LLM을 사용해 계획을 생성하는 LLM 기반 플래너와 달리, GOAP 에이전트는 미리 정의된 목표와 액션을 바탕으로 알고리즘적으로 액션 시퀀스를 탐색합니다.

GOAP 플래너는 세 가지 핵심 개념을 다룹니다:

  • 상태(State): 세계의 현재 상태를 나타냅니다.
  • 액션(Actions): 수행 가능한 작업을 정의하며, 사전 조건(precondition), 효과(belief), 비용(cost), 실행 로직을 포함합니다.
  • 목표(Goals): 목표 조건, 휴리스틱 비용, 가치 함수를 정의합니다.

참고: 사전 준비사항

이 페이지의 예제를 실행하려면 사전 준비사항, 의존성, API 키 설정이 필요합니다(빠른 시작 가이드 참조).

이 페이지의 예제는 OPENAI_API_KEY 환경 변수가 설정되어 있다고 가정합니다.

Koog에서는 DSL을 사용하여 목표와 액션을 선언적으로 지정하는 방식으로 GOAP 에이전트를 정의합니다.

GOAP 에이전트를 생성하려면 다음 단계를 따릅니다:

  1. 목표에 맞는 다양한 속성을 나타내는 프로퍼티를 가진 데이터 클래스로 상태를 정의합니다.
  2. goap() 함수를 사용하여 GOAPPlanner 인스턴스를 생성합니다.
    1. action() 함수를 사용하여 사전 조건과 belief를 포함한 액션을 정의합니다.
    2. goal() 함수를 사용하여 완료 조건을 포함한 목표를 정의합니다.
  3. 플래너를 AIAgentPlannerStrategy로 감싸서 PlannerAIAgent 생성자에 전달합니다.

참고

플래너는 개별 액션과 그 순서를 선택합니다. 각 액션에는 실행되기 위해 참이어야 하는 사전 조건(precondition)과 예측 결과를 정의하는 belief가 포함됩니다. belief에 대한 자세한 내용은 실제 실행과 상태 belief 비교를 참조하세요.

다음 예제에서 GOAP는 아티클 생성의 상위 수준 계획(개요 → 초안 → 검토 → 게시)을 담당하고, LLM은 각 액션 내에서 실제 콘텐츠 생성을 수행합니다.

Kotlin

1// Define a state for content creation2data class ContentState(3    val topic: String,4    val hasOutline: Boolean = false,5    val outline: String = "",6    val hasDraft: Boolean = false,7    val draft: String = "",8    val hasReview: Boolean = false,9    val isPublished: Boolean = false10): GoapAgentState<String, String>(topic) {11    override fun provideOutput(): String = draft12}1314// Create GOAP planner with LLM-powered actions15val planner = AIAgentPlannerStrategy.goap("content-planner", ::ContentState) {16    // Define actions with preconditions and beliefs17    action(18        name = "Create outline",19        precondition = { state -> !state.hasOutline },20        belief = { state -> state.copy(hasOutline = true, outline = "Outline") },21        cost = { 1.0 }22    ) { ctx, state ->23        // Use LLM to create the outline24        val response = ctx.llm.writeSession {25            appendPrompt {26                user("Create a detailed outline for an article about: ${state.topic}")27            }28            requestLLM()29        }30        state.copy(hasOutline = true, outline = response.content)31    }3233    action(34        name = "Write draft",35        precondition = { state -> state.hasOutline && !state.hasDraft },36        belief = { state -> state.copy(hasDraft = true, draft = "Draft") },37        cost = { 2.0 }38    ) { ctx, state ->39        // Use LLM to write the draft40        val response = ctx.llm.writeSession {41            appendPrompt {42                user("Write an article based on this outline:\n${state.outline}")43            }44            requestLLM()45        }46        state.copy(hasDraft = true, draft = response.content)47    }4849    action(50        name = "Review content",51        precondition = { state -> state.hasDraft && !state.hasReview },52        belief = { state -> state.copy(hasReview = true) },53        cost = { 1.0 }54    ) { ctx, state ->55        // Use LLM to review the draft56        val response = ctx.llm.writeSession {57            appendPrompt {58                user("Review this article and suggest improvements:\n${state.draft}")59            }60            requestLLM()61        }62        println("Review feedback: ${response.content}")63        state.copy(hasReview = true)64    }6566    action(67        name = "Publish",68        precondition = { state -> state.hasReview && !state.isPublished },69        belief = { state -> state.copy(isPublished = true) },70        cost = { 1.0 }71    ) { ctx, state ->72        println("Publishing article...")73        state.copy(isPublished = true)74    }7576    // Define the goal with a completion condition77    goal(78        name = "Published article",79        description = "Complete and publish the article",80        condition = { state -> state.isPublished }81    )82}8384// Create and run the agent85val agentConfig = AIAgentConfig(86    prompt = prompt("writer") {87        system("You are a professional content writer.")88    },89    model = OpenAIModels.Chat.GPT4o,90    maxAgentIterations = 2091)9293val agent = AIAgent(94    promptExecutor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")),95    strategy = planner,96    agentConfig = agentConfig97)9899suspend fun main() {100    val result = agent.run("The Future of AI in Software Development")101    println("Final state: $result")102}

Java

1// Define a state for content creation2static class ContentState extends GoapAgentState<String, String> {3    public String topic;4    public boolean hasOutline = false;5    public String outline = "";6    public boolean hasDraft = false;7    public String draft = "";8    public boolean hasReview = false;9    public boolean isPublished = false;1011    public ContentState(String topic) {12        super(topic);13        this.topic = topic;14    }1516    public ContentState copy(boolean hasOutline, String outline, boolean hasDraft,17                             String draft, boolean hasReview, boolean isPublished) {18        ContentState state = new ContentState(topic);19        state.hasOutline = hasOutline;20        state.outline = outline;21        state.hasDraft = hasDraft;22        state.draft = draft;23        state.hasReview = hasReview;24        state.isPublished = isPublished;25        return state;26    }2728    @Override29    public String provideOutput() {30        return draft;31    }32}3334public static void main(String[] args) {35    var promptExecutor = PromptExecutor.builder()36        .openAI("OPENAI_API_KEY")37        .build();3839    var strategy = AIAgentPlannerStrategy.builder("content-planner")40        .goap(ContentState::new)41        .action("Create outline", builder -> builder42            .precondition(state -> !state.hasOutline)43            .belief(state -> state.copy(true, "Outline", false, "", false, false))44            .cost(state -> 1.0)45            .execute((context, state) -> {46                String response = context.llm().writeSession(session -> {47                    session.appendPrompt(prompt -> {48                        prompt.user("Create a detailed outline for an article about: " + state.topic);49                        return null;50                    });51                    return session.requestLLM().getContent();52                });53                return state.copy(true, response, state.hasDraft, state.draft,54                                state.hasReview, state.isPublished);55            })56        )57        .action("Write draft", builder -> builder58            .precondition(state -> state.hasOutline && !state.hasDraft)59            .belief(state -> state.copy(state.hasOutline, state.outline, true, "Draft", false, false))60            .cost(state -> 2.0)61            .execute((context, state) -> {62                String response = context.llm().writeSession(session -> {63                    session.appendPrompt(prompt -> {64                        prompt.user("Write an article based on this outline:\n" + state.outline);65                        return null;66                    });67                    return session.requestLLM().getContent();68                });69                return state.copy(state.hasOutline, state.outline, true, response,70                                state.hasReview, state.isPublished);71            })72        )73        .action("Review content", builder -> builder74            .precondition(state -> state.hasDraft && !state.hasReview)75            .belief(state -> state.copy(state.hasOutline, state.outline, state.hasDraft,76                                       state.draft, true, false))77            .cost(state -> 1.0)78            .execute((context, state) -> {79                String response = context.llm().writeSession(session -> {80                    session.appendPrompt(prompt -> {81                        prompt.user("Review this article and suggest improvements:\n" + state.draft);82                        return null;83                    });84                    return session.requestLLM().getContent();85                });86                System.out.println("Review feedback: " + response);87                return state.copy(state.hasOutline, state.outline, state.hasDraft,88                                state.draft, true, state.isPublished);89            })90        )91        .action("Publish", builder -> builder92            .precondition(state -> state.hasReview && !state.isPublished)93            .belief(state -> state.copy(state.hasOutline, state.outline, state.hasDraft,94                                       state.draft, state.hasReview, true))95            .cost(state -> 1.0)96            .execute((context, state) -> {97                System.out.println("Publishing article...");98                return state.copy(state.hasOutline, state.outline, state.hasDraft,99                                state.draft, state.hasReview, true);100            })101        )102        .goal("Published article", builder -> builder103            .description("Complete and publish the article")104            .condition(state -> state.isPublished)105        )106        .build();107108    var agent = AIAgent.builder()109        .plannerStrategy(strategy)110        .promptExecutor(promptExecutor)111        .llmModel(OpenAIModels.Chat.GPT4o)112        .systemPrompt("You are a professional content writer.")113        .maxIterations(20)114        .build();115116    String result = agent.run("The Future of AI in Software Development");117    System.out.println("Final state: " + result);118}

사용자 정의 비용 함수

A* 탐색은 최적의 액션 시퀀스를 찾을 때 비용을 기준으로 삼기 때문에, 플래너를 유도하기 위해 액션과 목표에 사용자 정의 비용 함수를 정의할 수 있습니다:

Kotlin

1action(2    name = "Expensive operation",3    precondition = { true },4    belief = { state -> state.copy(operationDone = true) },5    cost = { state ->6        // Dynamic cost based on state7        if (state.hasOptimization) 1.0 else 10.08    }9) { ctx, state ->10    // Execute action11    state.copy(operationDone = true)12}

Java

1.action("Expensive operation", builder -> builder2    .precondition(state -> true)3    .belief(state -> state.copy(true))4    .cost(state -> {5        // Dynamic cost based on state6        return state.hasOptimization ? 1.0 : 10.0;7    })8    .execute((context, state) -> {9        // Execute action10        return state.copy(true);11    })12)

실제 실행과 상태 belief 비교

GOAP는 belief(낙관적 예측)와 실제 실행을 구분합니다:

  • Belief: 플래너가 일어날 것이라고 예상하는 내용으로, 계획 수립에 사용됩니다.
  • 실행(Execution): 실제로 일어나는 내용으로, 실제 상태 업데이트에 사용됩니다.

이를 통해 플래너는 예상 결과를 기반으로 계획을 세우면서도 실제 결과를 올바르게 처리할 수 있습니다:

Kotlin

1action(2    name = "Attempt complex task",3    precondition = { state -> !state.taskComplete },4    belief = { state ->5        // Optimistic belief: task will succeed6        state.copy(taskComplete = true)7    },8    cost = { 5.0 }9) { ctx, state ->10    // Actual execution might fail or have different results11    val success = performComplexTask()12    state.copy(13        taskComplete = success,14        attempts = state.attempts + 115    )16}

Java

1.action("Attempt complex task", builder -> builder2    .precondition(state -> !state.taskComplete)3    .belief(state -> {4        // Optimistic belief: task will succeed5        return state.copy(true, state.attempts);6    })7    .cost(state -> 5.0)8    .execute((context, state) -> {9        // Actual execution might fail or have different results10        boolean success = performComplexTask();11        return state.copy(success, state.attempts + 1);12    })13)