개요
원문: Koog Documentation — parallel-node-execution 이 글은 Koog 공식 문서의 parallel-node-execution 페이지를 한국어로 옮긴 번역본입니다. 문서 구조와 링크 의미를 유지하되, MkDocs 전용 UI 문법은 블로그에서 읽기 좋도록 정리했습니다.
개요
병렬 노드 실행을 사용하면 여러 AI 에이전트 노드를 동시에 실행하여 성능을 향상하고 복잡한 워크플로를 활성화할 수 있습니다. 이 기능은 다음과 같은 경우에 특히 유용합니다.
- 서로 다른 모델이나 접근 방식을 통해 동일한 입력을 동시에 처리합니다.
- 여러 개의 독립적인 작업을 병렬로 수행
- 여러 솔루션을 생성한 후 비교하는 경쟁력 있는 평가 패턴을 구현합니다.
주요 구성 요소
Koog의 병렬 노드 실행은 아래에 설명된 방법과 데이터 구조로 구성됩니다.
행동 양식
parallel(): 여러 노드를 병렬로 실행하고 결과를 수집합니다.
데이터 구조
ParallelResult: 병렬 노드 실행의 완료된 결과를 나타냅니다.NodeExecutionResult: 노드 실행의 출력과 컨텍스트를 포함합니다.
기본 사용법
노드를 병렬로 실행
노드의 병렬 실행을 시작하려면 다음 형식으로 parallel 메서드를 사용합니다.
1val nodeName by parallel<Input, Output>(2 firstNode, secondNode, thirdNode /* Add more nodes if needed */3) {4 // Merge strategy goes here, for example: 5 selectByMax { it.length }6}다음은 세 개의 노드를 병렬로 실행하고 최대 길이의 결과를 선택하는 실제 예입니다.
1val calc by parallel<String, Int>(2 nodeCalcTokens, nodeCalcSymbols, nodeCalcWords,3) {4 selectByMax { it }5}위 코드는 nodeCalcTokens, nodeCalcSymbols, nodeCalcWords 노드를 병렬로 실행하여 최대값의 결과를 반환하는 코드입니다.
병합 전략
노드를 병렬로 실행한 후 결과를 병합하는 방법을 지정해야 합니다. Koog는 다음 병합을 제공합니다. 전략:
selectBy(): 조건자 함수를 기반으로 결과를 선택합니다.selectByMax(): 비교 함수를 기반으로 최대값을 갖는 결과를 선택합니다.selectByIndex(): 선택 함수에서 반환된 인덱스를 기반으로 결과를 선택합니다.fold(): 연산 함수를 사용하여 결과를 단일 값으로 접습니다.
선택
조건자 함수를 기반으로 결과를 선택합니다.
1val nodeSelectJoke by parallel<String, String>(2 nodeOpenAI, nodeAnthropicSonnet, nodeAnthropicOpus,3) {4 selectBy { it.contains("programmer") }5}그러면 "programmer"라는 단어가 포함된 첫 번째 농담이 선택됩니다.
selectByMax
비교 함수를 기반으로 최대값이 있는 결과를 선택합니다.
1val nodeLongestJoke by parallel<String, String>(2 nodeOpenAI, nodeAnthropicSonnet, nodeAnthropicOpus,3) {4 selectByMax { it.length }5}최대 길이의 농담을 선택합니다.
selectByIndex
선택 함수에 의해 반환된 인덱스를 기반으로 결과를 선택합니다.
1val nodeBestJoke by parallel<String, String>(2 nodeOpenAI, nodeAnthropicSonnet, nodeAnthropicOpus,3) {4 selectByIndex { jokes ->5 // Use another LLM to determine the best joke6 llm.writeSession {7 model = OpenAIModels.Chat.GPT4o8 appendPrompt {9 system("You are a comedy critic. Select the best joke.")10 user("Here are three jokes: ${jokes.joinToString("\n\n")}")11 }12 val response = requestLLMStructured<JokeRating>()13 response.getOrNull()!!.data.bestJokeIndex14 }15 }16}이것은 최고의 농담의 색인을 결정하기 위해 또 다른 LLM 호출을 사용합니다.
겹
작업 함수를 사용하여 결과를 단일 값으로 접습니다.
1val nodeAllJokes by parallel<String, String>(2 nodeOpenAI, nodeAnthropicSonnet, nodeAnthropicOpus,3) {4 fold("Jokes:\n") { result, joke -> "$result\n$joke" }5}이것은 모든 농담을 하나의 문자열로 결합합니다.
예: 최고의 농담 에이전트
다음은 병렬 실행을 사용하여 다양한 LLM 모델에서 농담을 생성하고 가장 좋은 모델을 선택하는 완전한 예입니다.
1val strategy = strategy("best-joke") {2 // Define nodes for different LLM models3 val nodeOpenAI by node<String, String> { topic ->4 llm.writeSession {5 model = OpenAIModels.Chat.GPT4o6 appendPrompt {7 system("You are a comedian. Generate a funny joke about the given topic.")8 user("Tell me a joke about $topic.")9 }10 val response = requestLLMWithoutTools()11 response.content12 }13 }1415 val nodeAnthropicSonnet by node<String, String> { topic ->16 llm.writeSession {17 model = AnthropicModels.Sonnet_4_518 appendPrompt {19 system("You are a comedian. Generate a funny joke about the given topic.")20 user("Tell me a joke about $topic.")21 }22 val response = requestLLMWithoutTools()23 response.content24 }25 }2627 val nodeAnthropicOpus by node<String, String> { topic ->28 llm.writeSession {29 model = AnthropicModels.Opus_4_630 appendPrompt {31 system("You are a comedian. Generate a funny joke about the given topic.")32 user("Tell me a joke about $topic.")33 }34 val response = requestLLMWithoutTools()35 response.content36 }37 }3839 // Execute joke generation in parallel and select the best joke40 val nodeGenerateBestJoke by parallel(41 nodeOpenAI, nodeAnthropicSonnet, nodeAnthropicOpus,42 ) {43 selectByIndex { jokes ->44 // Another LLM (e.g., GPT4o) would find the funniest joke:45 llm.writeSession {46 model = OpenAIModels.Chat.GPT4o47 appendPrompt {48 prompt("best-joke-selector") {49 system("You are a comedy critic. Give a critique for the given joke.")50 user(51 """52 Here are three jokes about the same topic:5354 ${jokes.mapIndexed { index, joke -> "Joke $index:\n$joke" }.joinToString("\n\n")}5556 Select the best joke and explain why it's the best.57 """.trimIndent()58 )59 }60 }6162 val response = requestLLMStructured<JokeRating>()63 val bestJoke = response.getOrNull()!!.data64 bestJoke.bestJokeIndex65 }66 }67 }6869 // Connect the nodes70 nodeStart then nodeGenerateBestJoke then nodeFinish71}모범 사례
리소스 제약 고려: 노드를 병렬로 실행할 때, 특히 여러 LLM API를 동시에 호출할 때 리소스 사용량에 주의하세요.
컨텍스트 관리: 각 병렬 실행은 분기된 컨텍스트를 생성합니다. 결과를 병합할 때 보존할 컨텍스트를 선택하거나 다양한 실행의 컨텍스트를 결합하는 방법을 선택하세요.
사용 사례에 맞게 최적화:
- 경쟁적 평가(예: 농담 예)의 경우
selectByIndex를 사용하여 최상의 결과를 선택합니다. - 최대값을 찾으려면
selectByMax을 사용하세요. - 조건을 기준으로 필터링하려면
selectBy을 사용하세요. - 집계의 경우
fold을 사용하여 모든 결과를 복합 출력으로 결합합니다.
성능 고려 사항
병렬 실행은 처리량을 크게 향상시킬 수 있지만 약간의 오버헤드가 발생합니다.
- 각 병렬 노드는 새로운 코루틴을 생성합니다.
- 컨텍스트 분기 및 병합으로 인해 일부 계산 비용이 추가됩니다.
- 많은 병렬 실행으로 인해 리소스 경합이 발생할 수 있음
최적의 성능을 위해 다음과 같은 작업을 병렬화합니다.
- 서로 독립적임
- 상당한 실행 시간을 가짐
- 변경 가능한 상태를 공유하지 마세요