Koog를 사용하여 AI 뱅킹 도우미 구축
원문: Koog Documentation — Banking 이 글은 Koog 공식 문서의 Banking 페이지를 한국어로 옮긴 번역본입니다. 문서 구조와 링크 의미를 유지하되, MkDocs 전용 UI 문법은 블로그에서 읽기 좋도록 정리했습니다.
Koog를 사용하여 AI 뱅킹 도우미 구축
:material-github: Open on GitHub{ .md-button .md-button--기본 } :material-download: Download .ipynb{ .md-버튼 }
이 튜토리얼에서는 Kotlin에서 Koog 에이전트를 사용하여 소규모 은행 보조원을 구축하겠습니다. 다음 방법을 배우게 됩니다.
- 도메인 모델 및 샘플 데이터 정의
- 송금 및 거래 분석을 위한 기능 중심 도구 공개
- 사용자 의도 분류(전송 및 분석)
- 두 가지 스타일로 통화를 조정합니다.
- 그래프/하위 그래프 전략
- "도구로서의 에이전트"
결국에는 자유 형식의 사용자 요청을 올바른 도구로 라우팅하고 유용하고 감사 가능한 응답을 생성할 수 있게 됩니다.
설정 및 종속성
Kotlin Notebook 커널을 사용하겠습니다. Maven Central에서 Koog 아티팩트를 확인할 수 있는지 확인하세요.
LLM 제공업체 키는 OPENAI_API_KEY을 통해 확인할 수 있습니다.
1%useLatestDescriptors2%use datetime34// uncomment this for using koog from Maven Central5// %use koog1import ai.koog.prompt.executor.llms.all.simpleOpenAIExecutor23val apiKey = System.getenv("OPENAI_API_KEY") ?: error("Please set OPENAI_API_KEY environment variable")4val openAIExecutor = simpleOpenAIExecutor(apiKey)시스템 프롬프트 정의
잘 만들어진 시스템 프롬프트는 AI가 자신의 역할과 제약 조건을 이해하는 데 도움이 됩니다. 이 프롬프트는 모든 상담원의 행동을 안내합니다.
1val bankingAssistantSystemPrompt = """2 |You are a banking assistant interacting with a user (userId=123).3 |Your goal is to understand the user's request and determine whether it can be fulfilled using the available tools.4 |5 |If the task can be accomplished with the provided tools, proceed accordingly,6 |at the end of the conversation respond with: "Task completed successfully."7 |If the task cannot be performed with the tools available, respond with: "Can't perform the task."8""".trimMargin()도메인 모델 및 샘플 데이터
먼저 도메인 모델과 샘플 데이터를 정의해 보겠습니다. 직렬화를 지원하는 Kotlin의 데이터 클래스를 사용하겠습니다.
1import kotlinx.serialization.Serializable23@Serializable4data class Contact(5 val id: Int,6 val name: String,7 val surname: String? = null,8 val phoneNumber: String9)1011val contactList = listOf(12 Contact(100, "Alice", "Smith", "+1 415 555 1234"),13 Contact(101, "Bob", "Johnson", "+49 151 23456789"),14 Contact(102, "Charlie", "Williams", "+36 20 123 4567"),15 Contact(103, "Daniel", "Anderson", "+46 70 123 45 67"),16 Contact(104, "Daniel", "Garcia", "+34 612 345 678"),17)1819val contactById = contactList.associateBy(Contact::id)도구: 송금
도구는 순수하고 예측 가능해야 합니다.
우리는 두 가지 "소프트 계약"을 모델링합니다.
chooseRecipient은 모호성이 감지되면 후보를 반환합니다.sendMoney은confirmed플래그를 지원합니다.false인 경우 에이전트에게 사용자에게 확인을 요청합니다.
1import ai.koog.agents.core.tools.annotations.LLMDescription2import ai.koog.agents.core.tools.annotations.Tool3import ai.koog.agents.core.tools.reflect.ToolSet45@LLMDescription("Tools for money transfer operations.")6class MoneyTransferTools : ToolSet {78 @Tool9 @LLMDescription(10 """11 Returns the list of contacts for the given user.12 The user in this demo is always userId=123.13 """14 )15 fun getContacts(16 @LLMDescription("The unique identifier of the user whose contact list is requested.") userId: Int17 ): String = buildString {18 contactList.forEach { c ->19 appendLine("${c.id}: ${c.name} ${c.surname ?: ""} (${c.phoneNumber})")20 }21 }.trimEnd()2223 @Tool24 @LLMDescription("Returns the current balance (demo value).")25 fun getBalance(26 @LLMDescription("The unique identifier of the user.") userId: Int27 ): String = "Balance: 200.00 EUR"2829 @Tool30 @LLMDescription("Returns the default user currency (demo value).")31 fun getDefaultCurrency(32 @LLMDescription("The unique identifier of the user.") userId: Int33 ): String = "EUR"3435 @Tool36 @LLMDescription("Returns a demo FX rate between two ISO currencies (e.g. EUR→USD).")37 fun getExchangeRate(38 @LLMDescription("Base currency (e.g., EUR).") from: String,39 @LLMDescription("Target currency (e.g., USD).") to: String40 ): String = when (from.uppercase() to to.uppercase()) {41 "EUR" to "USD" -> "1.10"42 "EUR" to "GBP" -> "0.86"43 "GBP" to "EUR" -> "1.16"44 "USD" to "EUR" -> "0.90"45 else -> "No information about exchange rate available."46 }4748 @Tool49 @LLMDescription(50 """51 Returns a ranked list of possible recipients for an ambiguous name.52 The agent should ask the user to pick one and then use the selected contact id.53 """54 )55 fun chooseRecipient(56 @LLMDescription("An ambiguous or partial contact name.") confusingRecipientName: String57 ): String {58 val matches = contactList.filter { c ->59 c.name.contains(confusingRecipientName, ignoreCase = true) ||60 (c.surname?.contains(confusingRecipientName, ignoreCase = true) ?: false)61 }62 if (matches.isEmpty()) {63 return "No candidates found for '$confusingRecipientName'. Use getContacts and ask the user to choose."64 }65 return matches.mapIndexed { idx, c ->66 "${idx + 1}. ${c.id}: ${c.name} ${c.surname ?: ""} (${c.phoneNumber})"67 }.joinToString("\n")68 }6970 @Tool71 @LLMDescription(72 """73 Sends money from the user to a contact.74 If confirmed=false, return "REQUIRES_CONFIRMATION" with a human-readable summary.75 The agent should confirm with the user before retrying with confirmed=true.76 """77 )78 fun sendMoney(79 @LLMDescription("Sender user id.") senderId: Int,80 @LLMDescription("Amount in sender's default currency.") amount: Double,81 @LLMDescription("Recipient contact id.") recipientId: Int,82 @LLMDescription("Short purpose/description.") purpose: String,83 @LLMDescription("Whether the user already confirmed this transfer.") confirmed: Boolean = false84 ): String {85 val recipient = contactById[recipientId] ?: return "Invalid recipient."86 val summary = "Transfer €%.2f to %s %s (%s) for \"%s\"."87 .format(amount, recipient.name, recipient.surname ?: "", recipient.phoneNumber, purpose)8889 if (!confirmed) {90 return "REQUIRES_CONFIRMATION: $summary"91 }9293 // In a real system this is where you'd call a payment API.94 return "Money was sent. $summary"95 }96}첫 번째 에이전트 만들기
이제 송금 도구를 사용하는 에이전트를 만들어 보겠습니다. 에이전트는 LLM과 도구를 결합하여 작업을 수행합니다.
1import ai.koog.agents.core.agent.AIAgent2import ai.koog.agents.core.agent.AIAgentService3import ai.koog.agents.core.tools.ToolRegistry4import ai.koog.agents.core.tools.reflect.asTools5import ai.koog.agents.ext.tool.AskUser6import ai.koog.prompt.executor.clients.openai.OpenAIModels7import kotlinx.coroutines.runBlocking89val transferAgentService = AIAgentService(10 executor = openAIExecutor,11 llmModel = OpenAIModels.Chat.GPT4oMini,12 systemPrompt = bankingAssistantSystemPrompt,13 temperature = 0.0, // Use deterministic responses for financial operations14 toolRegistry = ToolRegistry {15 tool(AskUser)16 tools(MoneyTransferTools().asTools())17 }18)1920// Test the agent with various scenarios21println("Banking Assistant started")22val message = "Send 25 euros to Daniel for dinner at the restaurant."2324// Other test messages you can try:25// - "Send 50 euros to Alice for the concert tickets"26// - "What's my current balance?"27// - "Transfer 100 euros to Bob for the shared vacation expenses"2829runBlocking {30 val result = transferAgentService.createAgentAndRun(message)31 result32}뱅킹 어시스턴트 시작됨 다니엘이라는 연락처가 2개 있습니다. 어느 사람에게 송금할지 확인하세요.
- 다니엘 앤더슨 (+46 70 123 45 67)
- 다니엘 가르시아(+34 612 345 678) "레스토랑에서의 저녁 식사"를 위해 Daniel Garcia(+34 612 345 678)에게 €25.00 이체를 확인하시기 바랍니다.
작업이 성공적으로 완료되었습니다.
거래 분석 추가
거래 분석 도구를 활용해 어시스턴트의 역량을 확장해 봅시다. 먼저 트랜잭션 도메인 모델을 정의하겠습니다.
1@Serializable2enum class TransactionCategory(val title: String) {3 FOOD_AND_DINING("Food & Dining"),4 SHOPPING("Shopping"),5 TRANSPORTATION("Transportation"),6 ENTERTAINMENT("Entertainment"),7 GROCERIES("Groceries"),8 HEALTH("Health"),9 UTILITIES("Utilities"),10 HOME_IMPROVEMENT("Home Improvement");1112 companion object {13 fun fromString(value: String): TransactionCategory? =14 entries.find { it.title.equals(value, ignoreCase = true) }1516 fun availableCategories(): String =17 entries.joinToString(", ") { it.title }18 }19}2021@Serializable22data class Transaction(23 val merchant: String,24 val amount: Double,25 val category: TransactionCategory,26 val date: LocalDateTime27)샘플 거래 데이터
1val transactionAnalysisPrompt = """2Today is 2025-05-22.3Available categories for transactions: ${TransactionCategory.availableCategories()}4"""56val sampleTransactions = listOf(7 Transaction("Starbucks", 5.99, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 22, 8, 30, 0, 0)),8 Transaction("Amazon", 129.99, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 22, 10, 15, 0, 0)),9 Transaction(10 "Shell Gas Station",11 45.50,12 TransactionCategory.TRANSPORTATION,13 LocalDateTime(2025, 5, 21, 18, 45, 0, 0)14 ),15 Transaction("Netflix", 15.99, TransactionCategory.ENTERTAINMENT, LocalDateTime(2025, 5, 21, 12, 0, 0, 0)),16 Transaction("AMC Theaters", 32.50, TransactionCategory.ENTERTAINMENT, LocalDateTime(2025, 5, 20, 19, 30, 0, 0)),17 Transaction("Whole Foods", 89.75, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 20, 16, 20, 0, 0)),18 Transaction("Target", 67.32, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 20, 14, 30, 0, 0)),19 Transaction("CVS Pharmacy", 23.45, TransactionCategory.HEALTH, LocalDateTime(2025, 5, 19, 11, 25, 0, 0)),20 Transaction("Subway", 12.49, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 19, 13, 15, 0, 0)),21 Transaction("Spotify Premium", 9.99, TransactionCategory.ENTERTAINMENT, LocalDateTime(2025, 5, 19, 14, 15, 0, 0)),22 Transaction("AT&T", 85.00, TransactionCategory.UTILITIES, LocalDateTime(2025, 5, 18, 9, 0, 0, 0)),23 Transaction("Home Depot", 156.78, TransactionCategory.HOME_IMPROVEMENT, LocalDateTime(2025, 5, 18, 15, 45, 0, 0)),24 Transaction("Amazon", 129.99, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 17, 10, 15, 0, 0)),25 Transaction("Starbucks", 5.99, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 17, 8, 30, 0, 0)),26 Transaction("Whole Foods", 89.75, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 16, 16, 20, 0, 0)),27 Transaction("CVS Pharmacy", 23.45, TransactionCategory.HEALTH, LocalDateTime(2025, 5, 15, 11, 25, 0, 0)),28 Transaction("AT&T", 85.00, TransactionCategory.UTILITIES, LocalDateTime(2025, 5, 14, 9, 0, 0, 0)),29 Transaction("Xbox Game Pass", 14.99, TransactionCategory.ENTERTAINMENT, LocalDateTime(2025, 5, 14, 16, 45, 0, 0)),30 Transaction("Aldi", 76.45, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 13, 17, 30, 0, 0)),31 Transaction("Chipotle", 15.75, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 13, 12, 45, 0, 0)),32 Transaction("Best Buy", 299.99, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 12, 14, 20, 0, 0)),33 Transaction("Olive Garden", 89.50, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 12, 19, 15, 0, 0)),34 Transaction("Whole Foods", 112.34, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 11, 10, 30, 0, 0)),35 Transaction("Old Navy", 45.99, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 11, 13, 45, 0, 0)),36 Transaction("Panera Bread", 18.25, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 10, 11, 30, 0, 0)),37 Transaction("Costco", 245.67, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 10, 15, 20, 0, 0)),38 Transaction("Five Guys", 22.50, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 9, 18, 30, 0, 0)),39 Transaction("Macy's", 156.78, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 9, 14, 15, 0, 0)),40 Transaction("Hulu Plus", 12.99, TransactionCategory.ENTERTAINMENT, LocalDateTime(2025, 5, 8, 20, 0, 0, 0)),41 Transaction("Whole Foods", 94.23, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 8, 16, 45, 0, 0)),42 Transaction("Texas Roadhouse", 78.90, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 8, 19, 30, 0, 0)),43 Transaction("Walmart", 167.89, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 7, 11, 20, 0, 0)),44 Transaction("Chick-fil-A", 14.75, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 7, 12, 30, 0, 0)),45 Transaction("Aldi", 82.45, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 6, 15, 45, 0, 0)),46 Transaction("TJ Maxx", 67.90, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 6, 13, 20, 0, 0)),47 Transaction("P.F. Chang's", 95.40, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 5, 19, 15, 0, 0)),48 Transaction("Whole Foods", 78.34, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 4, 14, 30, 0, 0)),49 Transaction("H&M", 89.99, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 3, 16, 20, 0, 0)),50 Transaction("Red Lobster", 112.45, TransactionCategory.FOOD_AND_DINING, LocalDateTime(2025, 5, 2, 18, 45, 0, 0)),51 Transaction("Whole Foods", 67.23, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 2, 11, 30, 0, 0)),52 Transaction("Marshalls", 123.45, TransactionCategory.SHOPPING, LocalDateTime(2025, 5, 1, 15, 20, 0, 0)),53 Transaction(54 "Buffalo Wild Wings",55 45.67,56 TransactionCategory.FOOD_AND_DINING,57 LocalDateTime(2025, 5, 1, 19, 30, 0, 0)58 ),59 Transaction("Aldi", 145.78, TransactionCategory.GROCERIES, LocalDateTime(2025, 5, 1, 10, 15, 0, 0))60)거래 분석 도구
1@LLMDescription("Tools for analyzing transaction history")2class TransactionAnalysisTools : ToolSet {34 @Tool5 @LLMDescription(6 """7 Retrieves transactions filtered by userId, category, start date, and end date.8 All parameters are optional. If no parameters are provided, all transactions are returned.9 Dates should be in the format YYYY-MM-DD.10 """11 )12 fun getTransactions(13 @LLMDescription("The ID of the user whose transactions to retrieve.")14 userId: String? = null,15 @LLMDescription("The category to filter transactions by (e.g., 'Food & Dining').")16 category: String? = null,17 @LLMDescription("The start date to filter transactions by, in the format YYYY-MM-DD.")18 startDate: String? = null,19 @LLMDescription("The end date to filter transactions by, in the format YYYY-MM-DD.")20 endDate: String? = null21 ): String {22 var filteredTransactions = sampleTransactions2324 // Validate userId (in production, this would query a real database)25 if (userId != null && userId != "123") {26 return "No transactions found for user $userId."27 }2829 // Apply category filter30 category?.let { cat ->31 val categoryEnum = TransactionCategory.fromString(cat)32 ?: return "Invalid category: $cat. Available: ${TransactionCategory.availableCategories()}"33 filteredTransactions = filteredTransactions.filter { it.category == categoryEnum }34 }3536 // Apply date range filters37 startDate?.let { date ->38 val startDateTime = parseDate(date, startOfDay = true)39 filteredTransactions = filteredTransactions.filter { it.date >= startDateTime }40 }4142 endDate?.let { date ->43 val endDateTime = parseDate(date, startOfDay = false)44 filteredTransactions = filteredTransactions.filter { it.date <= endDateTime }45 }4647 if (filteredTransactions.isEmpty()) {48 return "No transactions found matching the specified criteria."49 }5051 return filteredTransactions.joinToString("\n") { transaction ->52 "${transaction.date}: ${transaction.merchant} - " +53 "$${transaction.amount} (${transaction.category.title})"54 }55 }5657 @Tool58 @LLMDescription("Calculates the sum of an array of double numbers.")59 fun sumArray(60 @LLMDescription("Comma-separated list of double numbers to sum (e.g., '1.5,2.3,4.7').")61 numbers: String62 ): String {63 val numbersList = numbers.split(",")64 .mapNotNull { it.trim().toDoubleOrNull() }65 val sum = numbersList.sum()66 return "Sum: $%.2f".format(sum)67 }6869 // Helper function to parse dates70 private fun parseDate(dateStr: String, startOfDay: Boolean): LocalDateTime {71 val parts = dateStr.split("-").map { it.toInt() }72 require(parts.size == 3) { "Invalid date format. Use YYYY-MM-DD" }7374 return if (startOfDay) {75 LocalDateTime(parts[0], parts[1], parts[2], 0, 0, 0, 0)76 } else {77 LocalDateTime(parts[0], parts[1], parts[2], 23, 59, 59, 999999999)78 }79 }80}1val analysisAgentService = AIAgentService(2 executor = openAIExecutor,3 llmModel = OpenAIModels.Chat.GPT4oMini,4 systemPrompt = "$bankingAssistantSystemPrompt\n$transactionAnalysisPrompt",5 temperature = 0.0,6 toolRegistry = ToolRegistry {7 tools(TransactionAnalysisTools().asTools())8 }9)1011println("Transaction Analysis Assistant started")12val analysisMessage = "How much have I spent on restaurants this month?"1314// Other queries to try:15// - "What's my maximum check at a restaurant this month?"16// - "How much did I spend on groceries in the first week of May?"17// - "What's my total spending on entertainment in May?"18// - "Show me all transactions from last week"1920runBlocking {21 val result = analysisAgentService.createAgentAndRun(analysisMessage)22 result23}거래 분석 도우미가 시작되었습니다.
이번 달에 식당에 총 $517.64를 지출했습니다.
작업이 성공적으로 완료되었습니다.
그래프를 사용하여 에이전트 구축
이제 요청을 적절한 핸들러로 라우팅할 수 있는 그래프 에이전트로 특수 에이전트를 결합해 보겠습니다.
요청 분류
먼저, 들어오는 요청을 분류하는 방법이 필요합니다.
1import ai.koog.agents.core.tools.annotations.LLMDescription2import kotlinx.serialization.SerialName3import kotlinx.serialization.Serializable45@Suppress("unused")6@SerialName("UserRequestType")7@Serializable8@LLMDescription("Type of user request: Transfer or Analytics")9enum class RequestType { Transfer, Analytics }1011@Serializable12@LLMDescription("The bank request that was classified by the agent.")13data class ClassifiedBankRequest(14 @property:LLMDescription("Type of request: Transfer or Analytics")15 val requestType: RequestType,16 @property:LLMDescription("Actual request to be performed by the banking application")17 val userRequest: String18)19공유 도구 레지스트리
1// Create a comprehensive tool registry for the multi-agent system2val toolRegistry = ToolRegistry {3 tool(AskUser) // Allow agents to ask for clarification4 tools(MoneyTransferTools().asTools())5 tools(TransactionAnalysisTools().asTools())6}에이전트 전략
이제 여러 노드를 조정하는 전략을 작성하겠습니다.
1import ai.koog.agents.core.dsl.builder.forwardTo2import ai.koog.agents.core.dsl.builder.strategy3import ai.koog.agents.core.dsl.extension.*4import ai.koog.agents.ext.agent.subgraphWithTask5import ai.koog.prompt.structure.StructureFixingParser67val strategy = strategy<String, String>("banking assistant") {89 // Subgraph for classifying user requests10 val classifyRequest by subgraph<String, ClassifiedBankRequest>(11 tools = listOf(AskUser)12 ) {13 // Use structured output to ensure proper classification14 val requestClassification by nodeLLMRequestStructured<ClassifiedBankRequest>(15 examples = listOf(16 ClassifiedBankRequest(17 requestType = RequestType.Transfer,18 userRequest = "Send 25 euros to Daniel for dinner at the restaurant."19 ),20 ClassifiedBankRequest(21 requestType = RequestType.Analytics,22 userRequest = "Provide transaction overview for the last month"23 )24 ),25 fixingParser = StructureFixingParser(26 model = OpenAIModels.Chat.GPT4oMini,27 retries = 2,28 )29 )3031 val callLLM by nodeLLMRequest()32 val callAskUserTool by nodeExecuteTool()3334 // Define the flow35 edge(nodeStart forwardTo requestClassification)3637 edge(38 requestClassification forwardTo nodeFinish39 onCondition { it.isSuccess }40 transformed { it.getOrThrow().data }41 )4243 edge(44 requestClassification forwardTo callLLM45 onCondition { it.isFailure }46 transformed { "Failed to understand the user's intent" }47 )4849 edge(callLLM forwardTo callAskUserTool onToolCall { true })5051 edge(52 callLLM forwardTo callLLM onAssistantMessage { true }53 transformed { "Please call `${AskUser.name}` tool instead of chatting" }54 )5556 edge(callAskUserTool forwardTo requestClassification57 transformed { it.result.toString() })58 }5960 // Subgraph for handling money transfers61 val transferMoney by subgraphWithTask<ClassifiedBankRequest, String>(62 tools = MoneyTransferTools().asTools() + AskUser,63 llmModel = OpenAIModels.Chat.GPT4o // Use more capable model for transfers64 ) { request ->65 """66 $bankingAssistantSystemPrompt67 Specifically, you need to help with the following request:68 ${request.userRequest}69 """.trimIndent()70 }7172 // Subgraph for transaction analysis73 val transactionAnalysis by subgraphWithTask<ClassifiedBankRequest, String>(74 tools = TransactionAnalysisTools().asTools() + AskUser,75 ) { request ->76 """77 $bankingAssistantSystemPrompt78 $transactionAnalysisPrompt79 Specifically, you need to help with the following request:80 ${request.userRequest}81 """.trimIndent()82 }8384 // Connect the subgraphs85 edge(nodeStart forwardTo classifyRequest)8687 edge(classifyRequest forwardTo transferMoney88 onCondition { it.requestType == RequestType.Transfer })8990 edge(classifyRequest forwardTo transactionAnalysis91 onCondition { it.requestType == RequestType.Analytics })9293 // Route results to finish node94 edge(transferMoney forwardTo nodeFinish)95 edge(transactionAnalysis forwardTo nodeFinish)96}1import ai.koog.agents.core.agent.config.AIAgentConfig2import ai.koog.prompt.dsl.prompt34val agentConfig = AIAgentConfig(5 prompt = prompt(id = "banking assistant") {6 system("$bankingAssistantSystemPrompt\n$transactionAnalysisPrompt")7 },8 model = OpenAIModels.Chat.GPT4o,9 maxAgentIterations = 50 // Allow for complex multi-step operations10)1112val agent = AIAgent<String, String>(13 promptExecutor = openAIExecutor,14 strategy = strategy,15 agentConfig = agentConfig,16 toolRegistry = toolRegistry,17)그래프 에이전트 실행
1println("Banking Assistant started")2val testMessage = "Send 25 euros to Daniel for dinner at the restaurant."34// Test various scenarios:5// Transfer requests:6// - "Send 50 euros to Alice for the concert tickets"7// - "Transfer 100 to Bob for groceries"8// - "What's my current balance?"9//10// Analytics requests:11// - "How much have I spent on restaurants this month?"12// - "What's my maximum check at a restaurant this month?"13// - "How much did I spend on groceries in the first week of May?"14// - "What's my total spending on entertainment in May?"1516runBlocking {17 val result = agent.run(testMessage)18 "Result: $result"19}뱅킹 어시스턴트 시작됨 이름이 Daniel인 연락처를 여러 개 찾았습니다. 올바른 것을 선택하십시오:
- 다니엘 앤더슨 (+46 70 123 45 67)
- 다니엘 가르시아(+34 612 345 678) 정확한 수령인의 번호를 지정해 주세요. "레스토랑에서의 저녁 식사"를 위해 Daniel Garcia에게 €25를 보낼지 여부를 확인하세요.
결과: 작업이 성공적으로 완료되었습니다.
에이전트 구성 - 에이전트를 도구로 사용
Koog를 사용하면 에이전트를 다른 에이전트 내의 도구로 사용하여 강력한 구성 패턴을 구현할 수 있습니다.
1import ai.koog.agents.core.agent.createAgentTool2import ai.koog.agents.core.tools.ToolParameterDescriptor3import ai.koog.agents.core.tools.ToolParameterType45val classifierAgent = AIAgent(6 executor = openAIExecutor,7 llmModel = OpenAIModels.Chat.GPT4oMini,8 toolRegistry = ToolRegistry {9 tool(AskUser)1011 // Convert agents into tools12 tool(13 transferAgentService.createAgentTool(14 agentName = "transferMoney",15 agentDescription = "Transfers money and handles all related operations",16 inputDescriptor = ToolParameterDescriptor(17 name = "request",18 description = "Transfer request from the user",19 type = ToolParameterType.String20 )21 )22 )2324 tool(25 analysisAgentService.createAgentTool(26 agentName = "analyzeTransactions",27 agentDescription = "Performs analytics on user transactions",28 inputDescriptor = ToolParameterDescriptor(29 name = "request",30 description = "Transaction analytics request",31 type = ToolParameterType.String32 )33 )34 )35 },36 systemPrompt = "$bankingAssistantSystemPrompt\n$transactionAnalysisPrompt"37)구성된 에이전트 실행
1println("Banking Assistant started")2val composedMessage = "Send 25 euros to Daniel for dinner at the restaurant."34runBlocking {5 val result = classifierAgent.run(composedMessage)6 "Result: $result"7}뱅킹 어시스턴트 시작됨 다니엘이라는 연락처가 2개 있습니다. 어느 사람에게 송금할지 확인하세요.
- 다니엘 앤더슨 (+46 70 123 45 67)
- 다니엘 가르시아(+34 612 345 678) "레스토랑에서 저녁 식사"를 위해 Daniel Anderson(+46 70 123 45 67)에게 €25.00 이체를 확인하시기 바랍니다.
결과: 작업을 수행할 수 없습니다.
요약
이 튜토리얼에서는 다음 방법을 배웠습니다.
- AI가 언제, 어떻게 사용하는지 이해하는 데 도움이 되는 명확한 설명이 포함된 LLM 기반 도구를 만듭니다.
- 특정 작업을 수행하기 위해 LLM과 도구를 결합하는 단일 목적 에이전트 구축
- 복잡한 워크플로우에 대한 전략 및 하위 그래프를 사용하여 그래프 에이전트 구현
- 다른 에이전트 내의 도구로 사용하여 에이전트 구성
- 확인 및 명확성을 포함한 사용자 상호 작용 처리
모범 사례
- 명확한 도구 설명: AI가 도구 사용법을 이해하는 데 도움이 되도록 자세한 LLMDescription 주석을 작성합니다.
- 관용적인 Kotlin: 데이터 클래스, 확장 함수, 범위 함수와 같은 Kotlin 기능을 사용합니다.
- 오류 처리: 항상 입력의 유효성을 검사하고 의미 있는 오류 메시지를 제공합니다.
- 사용자 경험: 송금과 같은 중요한 작업에 대한 확인 단계를 포함합니다.
- 모듈성: 더 나은 유지 관리를 위해 문제를 다양한 도구와 에이전트로 분리합니다.