Koog를 사용하여 도구 호출 계산기 에이전트 구축
원문: 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입니다.
더 궁금한 점이 있거나 추가 도움이 필요하면 언제든지 문의하세요!