테스트

·3분 읽기

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

테스트

개요

테스트 기능은 AI 에이전트 파이프라인, 하위 그래프 및 도구 상호 작용을 테스트하기 위한 포괄적인 프레임워크를 제공합니다. Koog 프레임워크에서 이를 통해 개발자는 모의 LLM(Large)을 사용하여 제어된 테스트 환경을 만들 수 있습니다. 언어 모델) 실행자, 도구 레지스트리 및 에이전트 환경.

목적

이 기능의 주요 목적은 다음을 통해 에이전트 기반 AI 기능의 테스트를 용이하게 하는 것입니다.

  • 특정 프롬프트에 대한 LLM 응답 모의
  • 도구 호출 및 결과 시뮬레이션
  • 에이전트 파이프라인 하위 그래프 및 해당 구조 테스트
  • 에이전트 노드를 통한 올바른 데이터 흐름 확인
  • 예상되는 동작에 대한 어설션 제공

구성 및 초기화

테스트 종속성 설정

테스트 환경을 설정하기 전에 다음 종속성을 추가했는지 확인하세요.

1// build.gradle.kts2dependencies {3   testImplementation("ai.koog:agents-test:LATEST_VERSION")4   testImplementation(kotlin("test"))5}

LLM 응답 모의

테스트의 기본 형태에는 결정적 동작을 보장하기 위해 LLM 응답을 모의하는 것이 포함됩니다. MockLLMBuilder 및 관련 유틸리티를 사용하여 이 작업을 수행할 수 있습니다.

코틀린

1// Create a mock LLM executor2val mockLLMApi = getMockExecutor {3  // Mock a simple text response4  mockLLMAnswer("Hello!") onRequestContains "Hello"56  // Mock a default response7  mockLLMAnswer("I don't know how to answer that.").asDefaultResponse8}

자바

1import ai.koog.agents.core.tools.ToolRegistry;2import ai.koog.agents.testing.tools.MockExecutor;3import ai.koog.prompt.executor.model.PromptExecutor;45// Create a tool registry (empty)6ToolRegistry toolRegistry = ToolRegistry.builder().build();78// Create a mock LLM executor9PromptExecutor mockLLMApi = MockExecutor.builder()10    .toolRegistry(toolRegistry)11    .mockLLMAnswer("Hello!").onRequestContains("Hello")12    .mockLLMAnswer("I don't know how to answer that.").asDefaultResponse()13    .build();

모의 도구 호출

LLM을 모의하여 입력 패턴에 따라 특정 도구를 호출할 수 있습니다.

코틀린

1// Mock a tool call response2mockLLMToolCall(CreateTool, CreateTool.Args("solve")) onRequestEquals "Solve task"34// Mock tool behavior - simplest form without lambda5mockTool(PositiveToneTool) alwaysReturns "The text has a positive tone."67// Using lambda when you need to perform extra actions8mockTool(NegativeToneTool) alwaysTells {9  // Perform some extra action10  println("Negative tone tool called")1112  // Return the result13  "The text has a negative tone."14}1516// Mock tool behavior based on specific arguments17mockTool(AnalyzeTool) returns "Detailed analysis" onArguments AnalyzeTool.Args("analyze deeply")1819// Mock tool behavior with conditional argument matching20mockTool(SearchTool) returns "Found results" onArgumentsMatching { args ->21  args.query.contains("important")22}

자바

1

위의 예는 간단한 것부터 복잡한 것까지 도구를 모의하는 다양한 방법을 보여줍니다.

  1. alwaysReturns: 가장 간단한 형식으로 람다 없이 직접 값을 반환합니다.
  2. alwaysTells: 추가 작업을 수행해야 할 때 람다를 사용합니다.
  3. returns...onArguments: 정확한 인수 일치에 대한 특정 결과를 반환합니다.
  4. returns...onArgumentsMatching: 사용자 정의 인수 조건에 따라 결과를 반환합니다.

테스트 모드 활성화

에이전트에서 테스트 모드를 활성화하려면 AIAgent 생성자 블록 내에서 withTesting() 함수를 사용하세요.

코틀린

1// Create the agent with testing enabled2AIAgent(3    promptExecutor = mockLLMApi,4    toolRegistry = toolRegistry,5    llmModel = llmModel6) {7    // Enable testing mode8    withTesting()9}

자바

1

고급 테스트

그래프 구조 테스트

자세한 노드 동작과 에지 연결을 테스트하기 전에 에이전트 그래프의 전체 구조를 확인하는 것이 중요합니다. 여기에는 필요한 모든 노드가 존재하고 예상 하위 그래프에 올바르게 연결되어 있는지 확인하는 작업이 포함됩니다.

테스트 기능은 에이전트의 그래프 구조를 테스트하는 포괄적인 방법을 제공합니다. 이 접근 방식은 여러 하위 그래프와 상호 연결된 노드가 있는 복잡한 에이전트에 특히 유용합니다.

기본 구조 테스트

에이전트 그래프의 기본 구조를 검증하는 것부터 시작하세요.

코틀린

1AIAgent(2    // Constructor arguments3    promptExecutor = mockLLMApi,4    toolRegistry = toolRegistry,5    llmModel = llmModel6) {7    testGraph<String, String>("test") {8        val firstSubgraph = assertSubgraphByName<String, String>("first")9        val secondSubgraph = assertSubgraphByName<String, String>("second")1011        // Assert subgraph connections12        assertEdges {13            startNode() alwaysGoesTo firstSubgraph14            firstSubgraph alwaysGoesTo secondSubgraph15            secondSubgraph alwaysGoesTo finishNode()16        }1718        // Verify the first subgraph19        verifySubgraph(firstSubgraph) {20            val start = startNode()21            val finish = finishNode()2223            // Assert nodes by name24            val askLLM = assertNodeByName<String, Message.Response>("callLLM")25            val callTool = assertNodeByName<Message.Tool.Call, ReceivedToolResult>("executeTool")2627            // Assert node reachability28            assertReachable(start, askLLM)29            assertReachable(askLLM, callTool)30        }31    }32}

자바

1

노드 동작 테스트

노드 동작 테스트를 통해 에이전트 그래프의 노드가 지정된 입력에 대해 예상되는 출력을 생성하는지 확인할 수 있습니다. 이는 에이전트의 논리가 다양한 시나리오에서 올바르게 작동하는지 확인하는 데 중요합니다.

기본 노드 테스트

개별 노드에 대한 간단한 입력 및 출력 검증으로 시작하십시오.

코틀린

1assertNodes {23    // Test basic text responses4    askLLM withInput "Hello" outputs assistantMessage("Hello!")56    // Test tool call responses7    askLLM withInput "Solve task" outputs toolCallMessage(CreateTool, CreateTool.Args("solve"))8}

자바

1

위의 예에서는 다음 동작을 테스트하는 방법을 보여줍니다.

  1. LLM 노드가 Hello을 입력으로 받으면 간단한 텍스트 메시지로 응답합니다.
  2. Solve task을 수신하면 도구 호출로 응답합니다.

테스트 도구 실행 노드

도구를 실행하는 노드를 테스트할 수도 있습니다.

코틀린

1assertNodes {2    // Test tool runs with specific arguments3    callTool withInput toolCallMessage(4        SolveTool,5        SolveTool.Args("solve")6    ) outputs toolResult(SolveTool, SolveTool.Args("solve"), "solved")7}

자바

1

이는 도구 실행 노드가 특정 도구 호출 서명을 수신하면 예상된 도구 결과를 생성하는지 확인합니다.

고급 노드 테스트

보다 복잡한 시나리오의 경우 구조화된 입력 및 출력을 사용하여 노드를 테스트할 수 있습니다.

코틀린

1assertNodes {2    // Test with different inputs to the same node3    askLLM withInput "Simple query" outputs assistantMessage("Simple response")45    // Test with complex parameters6    askLLM withInput "Complex query with parameters" outputs toolCallMessage(7        AnalyzeTool,8        AnalyzeTool.Args(query = "parameters", depth = 3)9    )10}

자바

1

자세한 결과 구조를 사용하여 복잡한 도구 호출 시나리오를 테스트할 수도 있습니다.

코틀린

1assertNodes {2    // Test a complex tool call with a structured result3    callTool withInput toolCallMessage(4        AnalyzeTool,5        AnalyzeTool.Args(query = "complex", depth = 5)6    ) outputs toolResult(AnalyzeTool, AnalyzeTool.Args(query = "complex", depth = 5), AnalyzeTool.Result(7        analysis = "Detailed analysis",8        confidence = 0.95,9        metadata = mapOf("source" to "database", "timestamp" to "2023-06-15")10    ))11}

자바

1

이러한 고급 테스트는 노드가 정교한 에이전트 동작에 필수적인 복잡한 데이터 구조를 올바르게 처리하는지 확인하는 데 도움이 됩니다.

에지 연결 테스트

에지 연결 테스트를 통해 에이전트 그래프가 한 노드에서 적절한 다음 노드로 출력을 올바르게 라우팅하는지 확인할 수 있습니다. 이렇게 하면 에이전트가 다양한 출력을 기반으로 의도한 워크플로 경로를 따르게 됩니다.

기본 에지 테스트

간단한 에지 연결 테스트로 시작하세요.

코틀린

1assertEdges {2    // Test text message routing3    askLLM withOutput assistantMessage("Hello!") goesTo giveFeedback45    // Test tool call routing6    askLLM withOutput toolCallMessage(CreateTool, CreateTool.Args("solve")) goesTo callTool7}

자바

1

이 예에서는 다음 동작을 확인합니다.

  1. LLM 노드가 간단한 텍스트 메시지를 출력하면 흐름이 giveFeedback 노드로 전달됩니다.
  2. 도구 호출을 출력하면 흐름이 callTool 노드로 전달됩니다.

조건부 라우팅 테스트

출력 내용을 기반으로 보다 복잡한 라우팅 논리를 테스트할 수 있습니다.

코틀린

1assertEdges {2    // Different text responses can route to different nodes3    askLLM withOutput assistantMessage("Need more information") goesTo askForInfo4    askLLM withOutput assistantMessage("Ready to proceed") goesTo processRequest5}

자바

1

고급 에지 테스트

정교한 에이전트의 경우 도구 결과의 구조화된 데이터를 기반으로 조건부 라우팅을 테스트할 수 있습니다.

코틀린

1assertEdges {2    // Test routing based on tool result content3    callTool withOutput toolResult(4        AnalyzeTool,5        AnalyzeTool.Args(query = "parameters", depth = 3),6        AnalyzeTool.Result(analysis = "Needs more processing", confidence = 0.5)7    ) goesTo processResult8}

자바

1

다양한 결과 속성을 기반으로 복잡한 결정 경로를 테스트할 수도 있습니다.

코틀린

1assertEdges {2    // Route to different nodes based on confidence level3    callTool withOutput toolResult(4        AnalyzeTool,5        AnalyzeTool.Args(query = "parameters", depth = 3),6        AnalyzeTool.Result(analysis = "Complete", confidence = 0.9)7    ) goesTo finish89    callTool withOutput toolResult(10        AnalyzeTool,11        AnalyzeTool.Args(query = "parameters", depth = 3),12        AnalyzeTool.Result(analysis = "Uncertain", confidence = 0.3)13    ) goesTo verifyResult14}

자바

1

이러한 고급 에지 테스트는 지능적인 상황 인식 워크플로를 만드는 데 필수적인 노드 출력의 내용과 구조를 기반으로 에이전트가 올바른 결정을 내리는 데 도움이 됩니다.

전체 테스트 예시

다음은 완전한 테스트 시나리오를 보여주는 사용자 스토리입니다.

텍스트의 어조를 분석하고 피드백을 제공하는 어조 분석 에이전트를 개발 중입니다. 에이전트는 긍정적, 부정적, 중립적 톤을 감지하는 도구를 사용합니다.

이 에이전트를 테스트하는 방법은 다음과 같습니다.

코틀린

1@Test2fun testToneAgent() = runTest {3    // Create a list to track tool calls4    var toolCalls = mutableListOf<String>()5    var result: String? = null67    // Create a tool registry8    val toolRegistry = ToolRegistry {9        // A special tool, required with this type of agent10        tool(SayToUser)1112        with(ToneTools) {13            tools()14        }15    }1617    // Create an event handler18    val eventHandler = EventHandler {19        onToolCallStarting { tool, args ->20            println("[DEBUG_LOG] Tool called: tool ${tool.name}, args $args")21            toolCalls.add(tool.name)22        }2324        handleError {25            println("[DEBUG_LOG] An error occurred: ${it.message}\n${it.stackTraceToString()}")26            true27        }2829        handleResult {30            println("[DEBUG_LOG] Result: $it")31            result = it32        }33    }3435    val positiveText = "I love this product!"36    val negativeText = "Awful service, hate the app."37    val defaultText = "I don't know how to answer this question."3839    val positiveResponse = "The text has a positive tone."40    val negativeResponse = "The text has a negative tone."41    val neutralResponse = "The text has a neutral tone."4243    val mockLLMApi = getMockExecutor(toolRegistry, eventHandler) {44        // Set up LLM responses for different input texts45        mockLLMToolCall(NeutralToneTool, ToneTool.Args(defaultText)) onRequestEquals defaultText46        mockLLMToolCall(PositiveToneTool, ToneTool.Args(positiveText)) onRequestEquals positiveText47        mockLLMToolCall(NegativeToneTool, ToneTool.Args(negativeText)) onRequestEquals negativeText4849        // Mock the behavior where the LLM responds with just tool responses when the tools return results50        mockLLMAnswer(positiveResponse) onRequestContains positiveResponse51        mockLLMAnswer(negativeResponse) onRequestContains negativeResponse52        mockLLMAnswer(neutralResponse) onRequestContains neutralResponse5354        mockLLMAnswer(defaultText).asDefaultResponse5556        // Tool mocks57        mockTool(PositiveToneTool) alwaysTells {58            toolCalls += "Positive tone tool called"59            positiveResponse60        }61        mockTool(NegativeToneTool) alwaysTells {62            toolCalls += "Negative tone tool called"63            negativeResponse64        }65        mockTool(NeutralToneTool) alwaysTells {66            toolCalls += "Neutral tone tool called"67            neutralResponse68        }69    }7071    // Create a strategy72    val strategy = toneStrategy("tone_analysis")7374    // Create an agent configuration75    val agentConfig = AIAgentConfig(76        prompt = prompt("test-agent") {77            system(78                """79                You are an question answering agent with access to the tone analysis tools.80                You need to answer 1 question with the best of your ability.81                Be as concise as possible in your answers.82                DO NOT ANSWER ANY QUESTIONS THAT ARE BESIDES PERFORMING TONE ANALYSIS!83                DO NOT HALLUCINATE!84            """.trimIndent()85            )86        },87        model = mockk<LLModel>(relaxed = true),88        maxAgentIterations = 1089    )9091    // Create an agent with testing enabled92    val agent = AIAgent(93        promptExecutor = mockLLMApi,94        toolRegistry = toolRegistry,95        strategy = strategy,96        eventHandler = eventHandler,97        agentConfig = agentConfig,98    ) {99        withTesting()100    }101102    // Test the positive text103    agent.run(positiveText)104    assertEquals("The text has a positive tone.", result, "Positive tone result should match")105    assertEquals(1, toolCalls.size, "One tool is expected to be called")106107    // Test the negative text108    agent.run(negativeText)109    assertEquals("The text has a negative tone.", result, "Negative tone result should match")110    assertEquals(2, toolCalls.size, "Two tools are expected to be called")111112    //Test the neutral text113    agent.run(defaultText)114    assertEquals("The text has a neutral tone.", result, "Neutral tone result should match")115    assertEquals(3, toolCalls.size, "Three tools are expected to be called")116}

자바

1

여러 하위 그래프가 있는 보다 복잡한 에이전트의 경우 그래프 구조를 테스트할 수도 있습니다.

코틀린

1@Test2fun testMultiSubgraphAgentStructure() = runTest {3    val strategy = strategy("test") {4        val firstSubgraph by subgraph(5            "first",6            tools = listOf(DummyTool, CreateTool, SolveTool)7        ) {8            val callLLM by nodeLLMRequest(allowToolCalls = false)9            val executeTool by nodeExecuteTool()10            val sendToolResult by nodeLLMSendToolResult()11            val giveFeedback by node<String, String> { input ->12                llm.writeSession {13                    appendPrompt {14                        user("Call tools! Don't chat!")15                    }16                }17                input18            }1920            edge(nodeStart forwardTo callLLM)21            edge(callLLM forwardTo executeTool onToolCall { true })22            edge(callLLM forwardTo giveFeedback onAssistantMessage { true })23            edge(giveFeedback forwardTo giveFeedback onAssistantMessage { true })24            edge(giveFeedback forwardTo executeTool onToolCall { true })25            edge(executeTool forwardTo nodeFinish transformed { it.content })26        }2728        val secondSubgraph by subgraph<String, String>("second") {29            edge(nodeStart forwardTo nodeFinish)30        }3132        edge(nodeStart forwardTo firstSubgraph)33        edge(firstSubgraph forwardTo secondSubgraph)34        edge(secondSubgraph forwardTo nodeFinish)35    }3637    val toolRegistry = ToolRegistry {38        tool(DummyTool)39        tool(CreateTool)40        tool(SolveTool)41    }4243    val mockLLMApi = getMockExecutor(toolRegistry) {44        mockLLMAnswer("Hello!") onRequestContains "Hello"45        mockLLMToolCall(CreateTool, CreateTool.Args("solve")) onRequestEquals "Solve task"46    }4748    val basePrompt = prompt("test") {}4950    AIAgent(51        toolRegistry = toolRegistry,52        strategy = strategy,53        eventHandler = EventHandler {},54        agentConfig = AIAgentConfig(prompt = basePrompt, model = OpenAIModels.Chat.GPT4o, maxAgentIterations = 100),55        promptExecutor = mockLLMApi,56    ) {57        testGraph("test") {58            val firstSubgraph = assertSubgraphByName<String, String>("first")59            val secondSubgraph = assertSubgraphByName<String, String>("second")6061            assertEdges {62                startNode() alwaysGoesTo firstSubgraph63                firstSubgraph alwaysGoesTo secondSubgraph64                secondSubgraph alwaysGoesTo finishNode()65            }6667            verifySubgraph(firstSubgraph) {68                val start = startNode()69                val finish = finishNode()7071                val askLLM = assertNodeByName<String, Message.Response>("callLLM")72                val callTool = assertNodeByName<Message.Tool.Call, ReceivedToolResult>("executeTool")73                val giveFeedback = assertNodeByName<Any?, Any?>("giveFeedback")7475                assertReachable(start, askLLM)76                assertReachable(askLLM, callTool)7778                assertNodes {79                    askLLM withInput "Hello" outputs Message.Assistant("Hello!")80                    askLLM withInput "Solve task" outputs toolCallMessage(CreateTool, CreateTool.Args("solve"))8182                    callTool withInput toolCallSignature(83                        SolveTool,84                        SolveTool.Args("solve")85                    ) outputs toolResult(SolveTool, "solved")8687                    callTool withInput toolCallSignature(88                        CreateTool,89                        CreateTool.Args("solve")90                    ) outputs toolResult(CreateTool, "created")91                }9293                assertEdges {94                    askLLM withOutput Message.Assistant("Hello!") goesTo giveFeedback95                    askLLM withOutput toolCallMessage(CreateTool, CreateTool.Args("solve")) goesTo callTool96                }97            }98        }99    }100}

자바

1

API 참조

테스트 기능과 관련된 전체 API 참조는 agents-test 모듈에 대한 참조 문서를 참조하세요.

FAQ 및 문제 해결

특정 도구 응답을 어떻게 모의합니까?

MockLLMBuilder에서 mockTool 방법을 사용합니다.

코틀린

1val mockExecutor = getMockExecutor {2    mockTool(myTool) alwaysReturns myResult34    // Or with conditions5    mockTool(myTool) returns myResult onArguments myArgs6}

자바

1

복잡한 그래프 구조를 어떻게 테스트할 수 있나요?

하위 그래프 어설션, verifySubgraph 및 노드 참조를 사용합니다.

코틀린

1testGraph<Unit, String>("test") {2    val mySubgraph = assertSubgraphByName<Unit, String>("mySubgraph")34    verifySubgraph(mySubgraph) {5        // Get references to nodes6        val nodeA = assertNodeByName<Unit, String>("nodeA")7        val nodeB = assertNodeByName<String, String>("nodeB")89        // Assert reachability10        assertReachable(nodeA, nodeB)1112        // Assert edge connections13        assertEdges {14            nodeA.withOutput("result") goesTo nodeB15        }16    }17}

자바

1

입력에 따라 다양한 LLM 응답을 어떻게 시뮬레이션합니까?

패턴 일치 방법을 사용합니다.

코틀린

1getMockExecutor {2    mockLLMAnswer("Response A") onRequestContains "topic A"3    mockLLMAnswer("Response B") onRequestContains "topic B"4    mockLLMAnswer("Exact response") onRequestEquals "exact question"5    mockLLMAnswer("Conditional response") onCondition { it.contains("keyword") && it.length > 10 }6}

자바

1import ai.koog.agents.testing.tools.MockExecutor;2import ai.koog.prompt.executor.model.PromptExecutor;34PromptExecutor promptExecutor = MockExecutor.builder()5    .mockLLMAnswer("Response A").onRequestContains("topic A")6    .mockLLMAnswer("Response B").onRequestContains("topic B")7    .mockLLMAnswer("Exact response").onRequestEquals("exact question")8    .mockLLMAnswer("Conditional response").onCondition(s -> s.contains("keyword") && s.length() > 10)9    .build();

문제 해결

모의 실행자는 항상 기본 응답을 반환합니다.

패턴 일치가 올바른지 확인하세요. 패턴은 대소문자를 구분하며 다음과 정확히 일치해야 합니다. 지정.

도구 호출이 차단되지 않습니다.

다음 사항을 확인하세요.

  1. 도구 레지스트리가 올바르게 설정되었습니다.
  2. 도구 이름이 정확히 일치합니다.
  3. 도구 작업이 올바르게 구성되었습니다.

그래프 어설션이 실패함

  1. 노드 이름이 올바른지 확인하십시오.
  2. 그래프 구조가 예상한 것과 일치하는지 확인하세요.
  3. 올바른 진입점과 종료점을 얻으려면 startNode()finishNode() 메서드를 사용하세요.