커스텀 노드 구현
원문: Koog Documentation — custom-nodes 이 글은 Koog 공식 문서의 custom-nodes 페이지를 한국어로 옮긴 번역본입니다. 문서 구조와 링크 의미를 유지하되, MkDocs 전용 UI 문법은 블로그에서 읽기 좋도록 정리했습니다.
커스텀 노드 구현
이 페이지에서는 Koog 프레임워크에서 사용자 정의 노드를 구현하는 방법에 대한 자세한 지침을 제공합니다. 사용자 정의 노드를 사용하면 특정 작업을 수행하는 재사용 가능한 구성 요소를 생성하여 에이전트 워크플로의 기능을 확장할 수 있습니다. 운영.
그래프 노드가 무엇인지, 사용법, 기존 기본 노드에 대해 자세히 알아보려면 Graph nodes을 참조하세요.
노드 아키텍처 개요
구현 세부 사항을 살펴보기 전에 Koog 프레임워크의 노드 아키텍처를 이해하는 것이 중요합니다. 노드는 에이전트 워크플로의 기본 구성 요소이며, 각 노드는 워크플로의 특정 작업이나 변환을 나타냅니다. 노드 간 실행 흐름을 정의하는 에지를 사용하여 노드를 연결합니다.
각 노드에는 입력을 받아 출력을 생성한 후 워크플로의 다음 노드로 전달되는 execute 메서드가 있습니다.
커스텀 노드 구현
사용자 정의 노드 구현은 입력 데이터에 대한 기본 논리를 수행하고 반환하는 간단한 구현부터 다양합니다. 매개변수를 허용하고 실행 간 상태를 유지하는 보다 복잡한 노드 구현에 대한 출력입니다.
기본 노드 구현
그래프에서 사용자 정의 노드를 구현하고 사용자 정의 로직을 정의하는 가장 간단한 방법은 다음 패턴을 사용하는 것입니다.
코틀린
1val myNode by node<Input, Output>("node_name") { input ->2 // Processing3 returnValue4}자바
1var myNode = AIAgentNode.builder("node_name")2 .withInput(Input.class)3 .withOutput(Output.class)4 .withAction((input, ctx) -> {5 // Processing6 return returnValue;7 })8 .build();위의 코드는 사전 정의된 Input 및 Output 유형과 선택적 이름이 있는 사용자 정의 노드 myNode를 나타냅니다.
문자열 매개변수(node_name). Kotlin에서는 node DSL 기능을 사용합니다. Java에서는 AIAgentNode.builder()를 사용합니다.
패턴.
실제 예에서 문자열 입력을 받아 입력 길이를 반환하는 간단한 노드는 다음과 같습니다.
코틀린
1val myNode by node<String, Int>("node_name") { input ->2 // Processing3 input.length4}자바
1var myNode = AIAgentNode.builder("node_name")2 .withInput(String.class)3 .withOutput(Integer.class)4 .withAction((input, ctx) -> {5 // Processing6 return input.length();7 })8 .build();사용자 정의 노드를 생성하는 또 다른 방법은 재사용 가능한 함수로 추출하는 것입니다. Kotlin에서는 확장 프로그램을 정의합니다.
node 함수를 호출하는 AIAgentSubgraphBuilderBase의 함수입니다. Java에서는 노드 빌더 호출을 다음으로 추출합니다.
도우미 방법.
코틀린
1fun AIAgentSubgraphBuilderBase<*, *>.myCustomNode(2 name: String? = null3): AIAgentNodeDelegate<Input, Output> = node(name) { input ->4 // Custom logic5 input // Return the input as output (pass-through)6}78val myCustomNode by myCustomNode("node_name")자바
1var myCustomNode = AIAgentNode.builder("node_name")2 .withInput(String.class)3 .withOutput(String.class)4 .withAction((input, ctx) -> {5 // Custom logic6 return input; // Return the input as output (pass-through)7 })8 .build();이는 일부 사용자 지정 논리를 수행하지만 입력을 출력으로 반환하는 통과 노드를 생성합니다. 수정.
추가 인수가 있는 노드
동작을 사용자 정의하기 위해 인수를 허용하는 노드를 생성할 수 있습니다.
코틀린
1 fun AIAgentSubgraphBuilderBase<*, *>.myNodeWithArguments(2 name: String? = null,3 arg1: String,4 arg2: Int5): AIAgentNodeDelegate<Input, Output> = node(name) { input ->6 // Use arg1 and arg2 in your custom logic7 input // Return the input as the output8}910val myCustomNode by myNodeWithArguments("node_name", arg1 = "value1", arg2 = 42)자바
1String arg1 = "value1";2int arg2 = 42;34var myCustomNode = AIAgentNode.builder("node_name")5 .withInput(String.class)6 .withOutput(String.class)7 .withAction((input, ctx) -> {8 // Use arg1 and arg2 in your custom logic9 return input; // Return the input as the output10 })11 .build();매개변수화된 노드
일반 입력 및 출력 유형 매개변수를 사용하여 노드를 정의할 수 있습니다. Kotlin에서는 inline 함수를 reified과 함께 사용합니다.
유형 매개변수. Java에서는 노드를 빌드할 때 유형을 명시적으로 지정합니다.
코틀린
1inline fun <reified T> AIAgentSubgraphBuilderBase<*, *>.myParameterizedNode(2 name: String? = null,3): AIAgentNodeDelegate<T, T> = node(name) { input ->4 // Do some additional actions5 // Return the input as the output6 input7}89val strategy = strategy<String, String>("strategy_name") {10 val myCustomNode by myParameterizedNode<String>("node_name")11}자바
1// In Java, specify the types explicitly when building the node2var myCustomNode = AIAgentNode.builder("node_name")3 .withInput(String.class)4 .withOutput(String.class)5 .withAction((input, ctx) -> {6 // Do some additional actions7 // Return the input as the output8 return input;9 })10 .build();상태 저장 노드
노드가 실행 간에 상태를 유지해야 하는 경우 클로저 변수를 사용할 수 있습니다. Kotlin에서는 다음에서 변수를 선언합니다.
둘러싸는 기능. Java에서는 람다 캡처가 다음과 같아야 하므로 AtomicInteger과 같은 스레드로부터 안전한 래퍼를 사용합니다.
효과적으로 최종.
코틀린
1fun AIAgentSubgraphBuilderBase<*, *>.myStatefulNode(2 name: String? = null3): AIAgentNodeDelegate<Input, Output> {4 var counter = 056 return node(name) { input ->7 counter++8 println("Node executed $counter times")9 input10 }11}자바
1// In Java, use AtomicInteger (or similar) since the lambda captures must be effectively final2AtomicInteger counter = new AtomicInteger(0);34var myStatefulNode = AIAgentNode.builder("node_name")5 .withInput(String.class)6 .withOutput(String.class)7 .withAction((input, ctx) -> {8 int count = counter.incrementAndGet();9 System.out.println("Node executed " + count + " times");10 return input;11 })12 .build();노드 입력 및 출력 유형
노드는 다양한 입력 및 출력 유형을 가질 수 있습니다. Kotlin과 Java 모두에서 일반 유형으로 지정됩니다. 매개변수:
코틀린
1val stringToIntNode by node<String, Int>("node_name") { input: String ->2 // Processing3 input.toInt() // Convert string to integer4}자바
1var stringToIntNode = AIAgentNode.builder("node_name")2 .withInput(String.class)3 .withOutput(Integer.class)4 .withAction((input, ctx) -> {5 // Processing6 return Integer.parseInt(input); // Convert string to integer7 })8 .build();참고 입력 및 출력 유형에 따라 노드가 워크플로의 다른 노드에 연결될 수 있는 방법이 결정됩니다. 소스 노드의 출력 유형이 대상 노드의 입력 유형과 호환되는 경우에만 노드를 연결할 수 있습니다.
모범 사례
사용자 정의 노드를 구현할 때 다음 모범 사례를 따르십시오.
- 노드에 집중: 각 노드는 잘 정의된 단일 작업을 수행해야 합니다.
- 설명이 포함된 이름을 사용하세요: 노드 이름은 목적을 명확하게 나타내야 합니다.
- 문서 매개변수: 모든 매개변수에 대한 명확한 문서를 제공합니다.
- 순조롭게 오류 처리: 워크플로 오류를 방지하기 위해 적절한 오류 처리를 구현합니다.
- 노드를 재사용 가능하게 만들기: 다양한 워크플로우에서 노드를 재사용할 수 있도록 설계합니다.
- 유형 매개변수 사용: 적절한 경우 일반 유형 매개변수를 사용하여 노드를 더 유연하게 만듭니다.
- 기본값 제공: 가능하면 매개변수에 적합한 기본값을 제공하세요.
일반적인 패턴
다음 섹션에서는 사용자 정의 노드 구현을 위한 몇 가지 일반적인 패턴을 제공합니다.
통과 노드
작업을 수행하지만 입력을 출력으로 반환하는 노드입니다.
코틀린
1val loggingNode by node<String, String>("node_name") { input ->2 println("Processing input: $input")3 input // Return the input as the output4}자바
1var loggingNode = AIAgentNode.builder("node_name")2 .withInput(String.class)3 .withOutput(String.class)4 .withAction((input, ctx) -> {5 System.out.println("Processing input: " + input);6 return input; // Return the input as the output7 })8 .build();변환 노드
입력 데이터를 변환하고 수정된 출력을 생성하는 노드입니다.
코틀린
1val upperCaseNode by node<String, String>("node_name") { input ->2 println("Processing input: $input")3 input.uppercase() // Transform the input to uppercase4}자바
1var upperCaseNode = AIAgentNode.builder("node_name")2 .withInput(String.class)3 .withOutput(String.class)4 .withAction((input, ctx) -> {5 System.out.println("Processing input: " + input);6 return input.toUpperCase(); // Transform the input to uppercase7 })8 .build();LLM 상호 작용 노드
LLM과 상호 작용하는 노드입니다. Kotlin에서는 LLM 세션을 세밀하게 제어할 수 있습니다. Java에서는 일반적으로
신속한 구성을 자동으로 처리하는 AIAgentNode.llmRequest()과 같은 사전 구축된 팩토리 메서드를 사용하세요.
코틀린
1val summarizeTextNode by node<String, String>("node_name") { input ->2 llm.writeSession {3 appendPrompt {4 user("Please summarize the following text: $input")5 }67 val response = requestLLMWithoutTools()8 response.content9 }10}자바
1// In Java, LLM interaction is handled using pre-built factory nodes.2// AIAgentNode.llmRequest() creates a node that sends the input string as a user3// message to the LLM and returns the response. The prompt text is provided as4// the node's input when it is executed in the graph.5var summarizeTextNode = AIAgentNode.llmRequest(true, "node_name");67// To extract the text content from the LLM response, chain a separate node:8var extractContent = AIAgentNode.builder("extract-content")9 .withInput(Message.Response.class)10 .withOutput(String.class)11 .withAction((response, ctx) -> response.getContent())12 .build();참고 위의 Kotlin 예는 LLM 세션(사용자 정의 프롬프트 구성, 명시적
requestLLMWithoutTools호출)에 대한 세밀한 제어를 보여줍니다. Java API는 입력 문자열이 사용자 메시지가 되는 프롬프트 구성을 자동으로 처리하는AIAgentNode.llmRequest()과 같은 상위 수준 팩토리 메서드를 제공합니다. 고급 프롬프트 사용자 정의를 위해 여러 노드를 구성하거나 사용자 정의 하위 그래프를 사용하십시오.
도구 실행 노드
도구를 실행하는 사용자 정의 노드. Kotlin에서는 도구 호출을 수동으로 구성하고 실행할 수 있습니다. 자바에서는 일반적으로 도구 오케스트레이션을 LLM에 위임하는 하위 그래프를 사용합니다.
코틀린
1val nodeExecuteCustomTool by node<String, String>("node_name") { input ->2 val toolCall = Message.Tool.Call(3 id = UUID.randomUUID().toString(),4 tool = toolName,5 metaInfo = ResponseMetaInfo.create(Clock.System),6 content = Json.encodeToString(ToolArgs(arg1 = input, arg2 = 42)) // Use the input as tool arguments7 )89 val result = environment.executeTool(toolCall)10 result.content11}자바
1// In Java, direct tool execution (as shown in the Kotlin example) is not available2// through the Java builder API. Instead, use a subgraph that delegates tool calls3// to the LLM, which decides when and how to invoke the tools:4var toolSubgraph = AIAgentSubgraph.builder("tool-subgraph")5 .withInput(String.class)6 .withOutput(String.class)7 .withTask(input -> "Use my_tool with input: " + input)8 .build();참고 Kotlin 예제에서는
Message.Tool.Call을 수동으로 구성하고environment.executeTool()을 호출하여 하위 수준 도구 실행을 보여줍니다. Java API는 LLM이 도구 호출을 자동으로 조정하는withTask()와 함께 하위 그래프를 사용하는 더 높은 수준의 접근 방식을 권장합니다. 사용 가능한 도구를 제한하려면.withInput()앞에.limitedTools(List.of(myTool))을 연결하세요.