For the complete documentation index, see llms.txt. This page is also available as Markdown.

커스텀 노드 주입하기 (Dynamic Node)

1. 개요

동적 노드란?

**동적 노드(Dynamic Node)**는 programgarden-community 패키지에 PR하지 않고도, 런타임에 사용자가 직접 만든 노드를 워크플로우에 주입하여 실행할 수 있는 기능입니다.

기존에는 동적 로직을 추가하려면 programgarden-community에 플러그인을 기여해야 했습니다. 동적 노드는 이 과정 없이 자신만의 노드를 즉시 사용할 수 있게 해줍니다.

기존 플러그인 vs 동적 노드

항목
커뮤니티 플러그인
동적 노드

등록 방식

programgarden-community에 PR 제출

런타임에 코드로 주입

배포

패키지 릴리스 필요

즉시 사용 가능

사용 범위

모든 사용자 공유

주입한 세션에서만 사용

네이밍

자유

Dynamic_ prefix 필수

credential 접근

가능

불가 (보안상 차단)

적합한 경우

범용 전략/지표

개인 전략, 프로토타이핑, 독자 로직

언제 사용하는가?

  • 개인 전략을 워크플로우에 통합하고 싶을 때

  • 새로운 지표나 로직을 빠르게 프로토타이핑할 때

  • 외부 라이브러리(pandas, numpy 등)를 활용한 독자적인 분석 노드가 필요할 때

  • 커뮤니티 기여 전에 로컬에서 먼저 테스트하고 싶을 때


2. 핵심 개념

스키마와 클래스 분리 (Lazy Loading)

동적 노드는 **스키마(Schema)**와 **클래스(Class)**를 분리하여 관리합니다.

spinner
  • 스키마: 노드의 메타정보(타입명, 카테고리, 입출력 포트). UI에 노드 목록을 표시하는 데 사용됩니다. 클래스가 없어도 등록할 수 있습니다.

  • 클래스: 실제 실행 로직이 담긴 Python 클래스. 워크플로우 실행 직전에 주입합니다.

이 분리 덕분에 앱 시작 시에는 가벼운 스키마만 로드하고, 실행에 필요한 클래스만 나중에 다운로드/주입할 수 있습니다.

Dynamic_ prefix 네이밍 규칙

동적 노드의 타입명은 반드시 Dynamic_으로 시작해야 합니다.

이 규칙은:

  • 기존 내장 노드와의 이름 충돌을 방지합니다

  • 워크플로우에서 동적 노드를 쉽게 식별할 수 있게 합니다

  • get_required_dynamic_types()가 필요한 동적 타입을 자동으로 찾을 수 있게 합니다

책임 분리

영역
라이브러리 (ProgramGarden)
사용자

스키마 저장소

제공 (DynamicNodeRegistry)

스키마 정의 및 등록

클래스 검증

BaseNode 상속, execute() 존재, 포트 일치 검증

클래스 구현

클래스 저장

검증 후 저장

다운로드/import 후 주입

워크플로우 실행

주입된 클래스로 실행

실행 요청


3. 사용 흐름 (4단계)

spinner

Step 1: 스키마 등록

앱 시작 시, 사용자가 정의한 노드 스키마를 등록합니다.

이 시점에서는 클래스가 없어도 됩니다. UI에 노드 목록을 표시하는 용도입니다.

Step 2: 워크플로우 정의

동적 노드를 포함한 워크플로우를 JSON으로 정의합니다.

Step 3: 필요 타입 확인 & 클래스 주입

워크플로우에서 사용되는 동적 타입을 확인하고, 해당 클래스를 주입합니다.

Step 4: 실행 & 정리


4. 동적 노드 클래스 작성법

기본 구조

동적 노드는 BaseNode을 상속하고, execute() 메서드를 구현해야 합니다.

BaseNode 상속

모든 동적 노드는 programgarden_core.nodes.base.BaseNode을 상속해야 합니다.

BaseNode은 Pydantic BaseModel 기반이므로, 설정 필드를 Pydantic 필드로 선언할 수 있습니다.

_outputs 정의 (OutputPort)

스키마에 정의한 출력 포트와 이름이 일치하는 OutputPort를 클래스에 선언해야 합니다.

OutputPort 필드:

필드
타입
필수
설명

name

str

O

포트 이름 (스키마의 outputs와 일치)

type

str

O

데이터 타입 (number, string, array, object 등)

display_name

str

사용자 표시용 이름

description

str

포트 설명

_inputs 정의 (InputPort, 선택)

입력 포트도 선언할 수 있습니다.

InputPort 필드:

필드
타입
필수
설명

name

str

O

포트 이름

type

str

O

데이터 타입

required

bool

필수 여부 (기본: True)

multiple

bool

여러 엣지 연결 가능 여부 (기본: False)

description

str

포트 설명

execute() 메서드 구현

execute()는 비동기 메서드(async def)로 구현하며, Dict[str, Any]를 반환합니다.

반환 딕셔너리의 _outputs에 정의한 포트 이름과 일치해야 합니다.

설정 필드 추가

Pydantic 필드로 설정값을 선언하면, 워크플로우 JSON에서 값을 전달받을 수 있습니다.

워크플로우 JSON에서:


5. API 레퍼런스

WorkflowExecutor 메서드

메서드
설명
반환

register_dynamic_schemas(schemas)

스키마 딕셔너리 리스트 등록

None

get_required_dynamic_types(workflow)

워크플로우에 필요한 동적 타입 목록

List[str]

inject_node_classes(node_classes)

노드 클래스 주입 ({타입명: 클래스})

None

is_dynamic_node_ready(node_type)

실행 준비 완료 여부 (스키마 등록 + 클래스 주입)

bool

list_dynamic_node_types()

등록된 동적 노드 타입 목록

List[str]

clear_injected_classes()

주입된 클래스 초기화 (메모리 정리)

None

register_dynamic_schemas(schemas)

Raises: ValueError - Dynamic_ prefix가 없는 경우

get_required_dynamic_types(workflow)

워크플로우 JSON의 nodes 배열에서 Dynamic_으로 시작하는 타입만 추출합니다.

inject_node_classes(node_classes)

검증 항목 (자동):

  1. 스키마 등록 여부

  2. BaseNode 상속 여부

  3. execute() 메서드 존재 여부

  4. 스키마와 클래스의 출력 포트 일치 여부

Raises:

  • ValueError - 스키마 미등록, 포트 불일치

  • TypeError - BaseNode 미상속, execute() 미구현

is_dynamic_node_ready(node_type)

스키마 등록 AND 클래스 주입이 모두 완료되었을 때 True를 반환합니다.

clear_injected_classes()

주입된 클래스만 제거합니다. 스키마는 유지됩니다. 워크플로우 실행 후 메모리 정리 용도로 사용합니다.

DynamicNodeSchema 필드

필드
타입
필수
기본값
설명

node_type

str

O

-

고유 타입명 (Dynamic_ prefix 필수)

category

str

"data"

노드 카테고리

description

str

None

노드 설명

inputs

List[Dict]

[]

입력 포트 [{name, type, required, description}]

outputs

List[Dict]

[]

출력 포트 [{name, type, description}]

config_schema

Dict

{}

설정 필드 스키마

version

str

"1.0.0"

노드 버전

author

str

None

작성자


6. 완전한 예제 (RSI + MACD)

스키마 정의

클래스 구현

워크플로우 JSON

실행 코드


7. 제약사항 & 보안

credential_id 사용 불가

동적 노드에서는 credential_id를 사용할 수 없습니다. 이는 보안상 외부 코드가 사용자의 증권사 인증 정보에 접근하는 것을 차단하기 위함입니다.

위와 같이 credential_id를 설정하면 검증 단계에서 실패합니다.

증권사 API 호출이 필요한 경우, 내장 노드(OverseasStockBrokerNode 등)를 사용하고 그 출력을 동적 노드의 입력으로 연결하세요.

검증 규칙

클래스 주입 시(inject_node_classes) 자동으로 4가지 검증이 수행됩니다.

#
검증 항목
실패 시 예외

1

스키마 등록 여부

ValueError: "스키마가 등록되지 않은 타입: Dynamic_XXX"

2

BaseNode 상속 여부

TypeError: "BaseNode를 상속해야 함: ClassName"

3

execute() 메서드 존재

TypeError: "execute() 메서드가 없음: ClassName"

4

출력 포트 일치 (스키마 vs 클래스)

ValueError: "스키마에 정의된 output 포트가 클래스에 없음: {포트명}"

에러 메시지 목록

상황
에러 메시지

Dynamic_ prefix 없이 스키마 등록

동적 노드는 'Dynamic_' prefix 필수: {타입명}

스키마 없이 클래스 주입

스키마가 등록되지 않은 타입: {타입명}

BaseNode 미상속

BaseNode를 상속해야 함: {클래스명}

execute() 미구현

execute() 메서드가 없음: {클래스명}

출력 포트 불일치

스키마에 정의된 output 포트가 클래스에 없음: {포트명}

credential_id 사용 시도

credential_id를 사용할 수 없습니다

클래스 미주입 상태에서 실행

주입되지 않음


8. FAQ

Q. 스키마 등록과 클래스 주입을 왜 분리했나요?

Lazy Loading 패턴입니다. 앱 시작 시 모든 동적 노드의 Python 코드를 로드하면 시작 시간이 길어집니다. 스키마만 먼저 등록하면 UI에 노드 목록을 빠르게 보여줄 수 있고, 실제 실행이 필요할 때만 해당 클래스를 로드합니다.

Q. 기존 노드 타입명(예: ConditionNode)을 동적 노드 타입으로 사용할 수 있나요?

아니요. 반드시 Dynamic_ prefix를 사용해야 합니다. 기존 노드와 이름이 충돌하면 예측할 수 없는 동작이 발생할 수 있습니다.

Q. 동적 노드에서 외부 라이브러리(pandas 등)를 사용할 수 있나요?

네. execute() 메서드 안에서 자유롭게 import하여 사용할 수 있습니다. 다만, 해당 라이브러리가 실행 환경에 설치되어 있어야 합니다.

Q. clear_injected_classes() 후 스키마는 유지되나요?

네. clear_injected_classes()는 클래스만 제거합니다. 스키마는 그대로 유지되어 UI에 노드 목록이 계속 표시됩니다. 다음 실행 시 클래스만 다시 주입하면 됩니다.

Q. 하나의 워크플로우에 동적 노드와 내장 노드를 함께 사용할 수 있나요?

네. 동적 노드는 내장 노드와 동일한 방식으로 워크플로우에 배치됩니다. edges로 연결하고, {{ }} 표현식으로 데이터를 바인딩할 수 있습니다.

Q. DynamicNodeRegistry를 직접 사용해야 하나요?

대부분의 경우 WorkflowExecutor의 래퍼 메서드만으로 충분합니다. DynamicNodeRegistry는 내부적으로 싱글톤 패턴으로 관리되며, WorkflowExecutor가 이를 자동으로 사용합니다.

직접 사용이 필요한 경우:

마지막 업데이트