테스트
원문: 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위의 예는 간단한 것부터 복잡한 것까지 도구를 모의하는 다양한 방법을 보여줍니다.
alwaysReturns: 가장 간단한 형식으로 람다 없이 직접 값을 반환합니다.alwaysTells: 추가 작업을 수행해야 할 때 람다를 사용합니다.returns...onArguments: 정확한 인수 일치에 대한 특정 결과를 반환합니다.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위의 예에서는 다음 동작을 테스트하는 방법을 보여줍니다.
- LLM 노드가
Hello을 입력으로 받으면 간단한 텍스트 메시지로 응답합니다. 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이 예에서는 다음 동작을 확인합니다.
- LLM 노드가 간단한 텍스트 메시지를 출력하면 흐름이
giveFeedback노드로 전달됩니다. - 도구 호출을 출력하면 흐름이
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}자바
1API 참조
테스트 기능과 관련된 전체 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();문제 해결
모의 실행자는 항상 기본 응답을 반환합니다.
패턴 일치가 올바른지 확인하세요. 패턴은 대소문자를 구분하며 다음과 정확히 일치해야 합니다. 지정.
도구 호출이 차단되지 않습니다.
다음 사항을 확인하세요.
- 도구 레지스트리가 올바르게 설정되었습니다.
- 도구 이름이 정확히 일치합니다.
- 도구 작업이 올바르게 구성되었습니다.
그래프 어설션이 실패함
- 노드 이름이 올바른지 확인하십시오.
- 그래프 구조가 예상한 것과 일치하는지 확인하세요.
- 올바른 진입점과 종료점을 얻으려면
startNode()및finishNode()메서드를 사용하세요.