Koog를 사용하여 도구 호출 계산기 에이전트 구축

·3분 읽기

원문: Koog Documentation — Calculator 이 글은 Koog 공식 문서의 Calculator 페이지를 한국어로 옮긴 번역본입니다. 문서 구조와 링크 의미를 유지하되, MkDocs 전용 UI 문법은 블로그에서 읽기 좋도록 정리했습니다.

Koog를 사용하여 도구 호출 계산기 에이전트 구축

:material-github: Open on GitHub{ .md-button .md-button--기본 } :material-download: Download .ipynb{ .md-버튼 }

이 미니 튜토리얼에서는 Koog 도구 호출을 통해 구동되는 계산기 에이전트를 구축합니다. 다음 방법을 배우게 됩니다.

  • 연산을 위한 작고 순수한 도구 설계
  • Koog의 다중 호출 전략으로 병렬 도구 호출을 조정합니다.
  • 투명성을 위해 경량 이벤트 로깅 추가
  • OpenAI(및 선택적으로 Ollama)로 실행

우리는 API를 깔끔하고 관용적인 Kotlin으로 유지하여 예측 가능한 결과를 반환하고 극단적인 경우(예: 0으로 나누기)를 적절하게 처리할 것입니다.

설정

Koog를 사용할 수 있는 Kotlin Notebook 환경에 있다고 가정합니다. LLM 실행자 제공

1%useLatestDescriptors2%use koog345val OPENAI_API_KEY = System.getenv("OPENAI_API_KEY")6    ?: error("Please set the OPENAI_API_KEY environment variable")78val executor = simpleOpenAIExecutor(OPENAI_API_KEY)

계산기 도구

도구는 계약이 명확한 작고 순수한 기능입니다. 더 나은 정밀도와 일관된 출력 형식을 위해 Double을 사용합니다.

1import ai.koog.agents.core.tools.annotations.Tool23// Format helper: integers render cleanly, decimals keep reasonable precision.4private fun Double.pretty(): String =5    if (abs(this % 1.0) < 1e-9) this.toLong().toString() else "%.10g".format(this)67@LLMDescription("Tools for basic calculator operations")8class CalculatorTools : ToolSet {910    @Tool11    @LLMDescription("Adds two numbers and returns the sum as text.")12    fun plus(13        @LLMDescription("First addend.") a: Double,14        @LLMDescription("Second addend.") b: Double15    ): String = (a + b).pretty()1617    @Tool18    @LLMDescription("Subtracts the second number from the first and returns the difference as text.")19    fun minus(20        @LLMDescription("Minuend.") a: Double,21        @LLMDescription("Subtrahend.") b: Double22    ): String = (a - b).pretty()2324    @Tool25    @LLMDescription("Multiplies two numbers and returns the product as text.")26    fun multiply(27        @LLMDescription("First factor.") a: Double,28        @LLMDescription("Second factor.") b: Double29    ): String = (a * b).pretty()3031    @Tool32    @LLMDescription("Divides the first number by the second and returns the quotient as text. Returns an error message on division by zero.")33    fun divide(34        @LLMDescription("Dividend.") a: Double,35        @LLMDescription("Divisor (must not be zero).") b: Double36    ): String = if (abs(b) < 1e-12) {37        "ERROR: Division by zero"38    } else {39        (a / b).pretty()40    }41}

도구 레지스트리

우리의 도구를 노출하세요(상호작용/로깅을 위한 두 가지 내장 기능 포함).

1val toolRegistry = ToolRegistry {2    tool(AskUser)   // enables explicit user clarification when needed3    tool(SayToUser) // allows the agent to present the final message to the user4    tools(CalculatorTools())5}

전략: 다중 도구 호출(선택적 압축 포함)

이 전략을 통해 LLM은 한 번에 여러 도구 호출(예: plus, minus, multiply, divide)을 제안한 다음 결과를 다시 보낼 수 있습니다. 토큰 사용량이 너무 커지면 계속하기 전에 도구 결과 기록을 압축합니다.

1import ai.koog.agents.core.environment.ReceivedToolResult23object CalculatorStrategy {4    private const val MAX_TOKENS_THRESHOLD = 100056    val strategy = strategy<String, String>("test") {7        val callLLM by nodeLLMRequestMultiple()8        val executeTools by nodeExecuteMultipleTools(parallelTools = true)9        val sendToolResults by nodeLLMSendMultipleToolResults()10        val compressHistory by nodeLLMCompressHistory<List<ReceivedToolResult>>()1112        edge(nodeStart forwardTo callLLM)1314        // If the assistant produced a final answer, finish.15        edge((callLLM forwardTo nodeFinish) transformed { it.first() } onAssistantMessage { true })1617        // Otherwise, run the tools LLM requested (possibly several in parallel).18        edge((callLLM forwardTo executeTools) onMultipleToolCalls { true })1920        // If we’re getting large, compress past tool results before continuing.21        edge(22            (executeTools forwardTo compressHistory)23                onCondition { llm.readSession { prompt.latestTokenUsage > MAX_TOKENS_THRESHOLD } }24        )25        edge(compressHistory forwardTo sendToolResults)2627        // Normal path: send tool results back to the LLM.28        edge(29            (executeTools forwardTo sendToolResults)30                onCondition { llm.readSession { prompt.latestTokenUsage <= MAX_TOKENS_THRESHOLD } }31        )3233        // LLM might request more tools after seeing results.34        edge((sendToolResults forwardTo executeTools) onMultipleToolCalls { true })3536        // Or it can produce the final answer.37        edge((sendToolResults forwardTo nodeFinish) transformed { it.first() } onAssistantMessage { true })38    }39}

에이전트 구성

최소한의 도구 전달 프롬프트가 잘 작동합니다. 결정론적 수학을 위해 온도를 낮게 유지하십시오.

1val agentConfig = AIAgentConfig(2    prompt = prompt("calculator") {3        system("You are a calculator. Always use the provided tools for arithmetic.")4    },5    model = OpenAIModels.Chat.GPT4o,6    maxAgentIterations = 507)
1import ai.koog.agents.features.eventHandler.feature.handleEvents23val agent = AIAgent(4    promptExecutor = executor,5    strategy = CalculatorStrategy.strategy,6    agentConfig = agentConfig,7    toolRegistry = toolRegistry8) {9    handleEvents {10        onToolCallStarting { e ->11            println("Tool called: ${e.tool.name}, args=${e.toolArgs}")12        }13        onAgentExecutionFailed { e ->14            println("Agent error: ${e.throwable.message}")15        }16        onAgentCompleted { e ->17            println("Final result: ${e.result}")18        }19    }20}

시도해 보세요

에이전트는 표현식을 병렬 도구 호출로 분해하고 깔끔한 형식의 결과를 반환해야 합니다.

1import kotlinx.coroutines.runBlocking23runBlocking {4    agent.run("(10 + 20) * (5 + 5) / (2 - 11)")5}6// Expected final value ≈ -33.333...

호출된 도구: plus, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=10.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): 코틀린.문자열=20.0}) 호출된 도구: plus, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=5.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): 코틀린.문자열=5.0}) 호출된 도구: minus, args=VarArgs(args={재미 Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double)의 매개변수 #1 a: kotlin.String=2.0, fun Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double)의 매개변수 #2 b: 코틀린.문자열=11.0}) 호출된 도구: 곱하기, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): kotlin.String=30.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): 코틀린.문자열=10.0}) 호출된 도구: Divide, args=VarArgs(args={재미 Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double)의 매개변수 #1 a: kotlin.String=1.0, fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double)의 매개변수 #2 b: kotlin.String=-9.0}) 호출된 도구: Divide, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=300.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=-9.0}) 최종 결과: ((10 + 20) * (5 + 5) / (2 - 11)) 표현식의 결과는 대략 (-33.33)입니다.

((10 + 20) * (5 + 5) / (2 - 11)) 표현식의 결과는 대략 (-33.33)입니다.

강제로 병렬 호출을 시도해보세요

필요한 모든 도구를 한 번에 호출하도록 모델에 요청하세요. 여전히 올바른 계획과 안정적인 실행을 볼 수 있습니다.

1runBlocking {2    agent.run("Use tools to calculate (10 + 20) * (5 + 5) / (2 - 11). Please call all the tools at once.")3}

호출된 도구: plus, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=10.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): 코틀린.문자열=20.0}) 호출된 도구: plus, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): kotlin.String=5.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.plus(kotlin.Double, kotlin.Double): 코틀린.문자열=5.0}) 호출된 도구: minus, args=VarArgs(args={재미 Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double)의 매개변수 #1 a: kotlin.String=2.0, fun Line_4_jupyter.CalculatorTools.minus(kotlin.Double, kotlin.Double)의 매개변수 #2 b: 코틀린.문자열=11.0}) 호출된 도구: 곱하기, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): kotlin.String=30.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.multiply(kotlin.Double, kotlin.Double): 코틀린.문자열=10.0}) 호출된 도구: Divide, args=VarArgs(args={매개변수 #1 a of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=30.0, 매개변수 #2 b of fun Line_4_jupyter.CalculatorTools.divide(kotlin.Double, kotlin.Double): kotlin.String=-9.0}) 최종 결과: ((10 + 20) * (5 + 5) / (2 - 11))의 결과는 대략 (-3.33)입니다.

((10 + 20) * (5 + 5) / (2 - 11))의 결과는 대략 (-3.33)입니다.

올라마와 함께 달리다

로컬 추론을 선호하는 경우 실행기와 모델을 교환하세요.

1val ollamaExecutor: PromptExecutor = simpleOllamaAIExecutor()23val ollamaAgentConfig = AIAgentConfig(4    prompt = prompt("calculator", LLMParams(temperature = 0.0)) {5        system("You are a calculator. Always use the provided tools for arithmetic.")6    },7    model = OllamaModels.Meta.LLAMA_3_2,8    maxAgentIterations = 509)101112val ollamaAgent = AIAgent(13    promptExecutor = ollamaExecutor,14    strategy = CalculatorStrategy.strategy,15    agentConfig = ollamaAgentConfig,16    toolRegistry = toolRegistry17)1819runBlocking {20    ollamaAgent.run("(10 + 20) * (5 + 5) / (2 - 11)")21}

상담원의 말: (10 + 20) * (5 + 5) / (2 - 11) 표현식의 결과는 대략 -33.33입니다.

더 궁금한 점이 있거나 추가 도움이 필요하면 언제든지 문의하세요!