> For the complete documentation index, see [llms.txt](https://programgarden.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://programgarden.gitbook.io/docs/develop/dynamic_node_guide.md).
# 커ì¤í
ë
¸ë 주ì
í기 (Dynamic Node)
## 1. ê°ì
### ëì ë
¸ëë?
\*\*ëì ë
¸ë(Dynamic Node)\*\*ë `programgarden-community` í¨í¤ì§ì PRíì§ ìê³ ë, ë°íìì ì¬ì©ìê° ì§ì ë§ë ë
¸ë를 ìí¬íë¡ì°ì 주ì
íì¬ ì¤íí ì ìë 기ë¥ì
ëë¤.
기존ìë ëì ë¡ì§ì ì¶ê°íë ¤ë©´ `programgarden-community`ì íë¬ê·¸ì¸ì 기ì¬í´ì¼ íìµëë¤. ëì ë
¸ëë ì´ ê³¼ì ìì´ **ìì ë§ì ë
¸ë를 ì¦ì ì¬ì©**í ì ìê² í´ì¤ëë¤.
### 기존 íë¬ê·¸ì¸ vs ëì ë
¸ë
| í목 | 커뮤ëí° íë¬ê·¸ì¸ | ëì ë
¸ë |
| ------------- | -------------------------------- | -------------------- |
| ë±ë¡ ë°©ì | `programgarden-community`ì PR ì ì¶ | ë°íìì ì½ëë¡ ì£¼ì
|
| ë°°í¬ | í¨í¤ì§ ë¦´ë¦¬ì¤ íì | ì¦ì ì¬ì© ê°ë¥ |
| ì¬ì© ë²ì | 모ë ì¬ì©ì ê³µì | 주ì
í ì¸ì
ììë§ ì¬ì© |
| ë¤ì´ë° | ìì | `Dynamic_` prefix íì |
| credential ì ê·¼ | ê°ë¥ | ë¶ê° (ë³´ìì ì°¨ë¨) |
| ì í©í ê²½ì° | ë²ì© ì ëµ/ì§í | ê°ì¸ ì ëµ, íë¡í íì´í, ë
ì ë¡ì§ |
### ì¸ì ì¬ì©íëê°?
* **ê°ì¸ ì ëµ**ì ìí¬íë¡ì°ì íµí©íê³ ì¶ì ë
* ìë¡ì´ ì§íë ë¡ì§ì **ë¹ ë¥´ê² íë¡í íì´í**í ë
* ì¸ë¶ ë¼ì´ë¸ë¬ë¦¬(pandas, numpy ë±)를 íì©í **ë
ìì ì¸ ë¶ì ë
¸ë**ê° íìí ë
* 커뮤ëí° ê¸°ì¬ ì ì **ë¡ì»¬ìì 먼ì í
ì¤í¸**íê³ ì¶ì ë
***
## 2. íµì¬ ê°ë
### ì¤í¤ë§ì í´ëì¤ ë¶ë¦¬ (Lazy Loading)
ëì ë
¸ëë \*\*ì¤í¤ë§(Schema)\*\*ì \*\*í´ëì¤(Class)\*\*를 ë¶ë¦¬íì¬ ê´ë¦¬í©ëë¤.
{% @mermaid/diagram content="flowchart LR
A\["ì¤í¤ë§ ë±ë¡
(UI íìì©)"] -- "ì± ìì ì ââââ ì¤í ì§ì " --> B\["í´ëì¤ ì£¼ì
(ì¤íì©)"]" %}
* **ì¤í¤ë§**: ë
¸ëì ë©íì ë³´(íì
ëª
, ì¹´í
ê³ ë¦¬, ì
ì¶ë ¥ í¬í¸). UIì ë
¸ë 목ë¡ì íìíë ë° ì¬ì©ë©ëë¤. í´ëì¤ê° ìì´ë ë±ë¡í ì ììµëë¤.
* **í´ëì¤**: ì¤ì ì¤í ë¡ì§ì´ ë´ê¸´ Python í´ëì¤. ìí¬íë¡ì° ì¤í ì§ì ì 주ì
í©ëë¤.
ì´ ë¶ë¦¬ ëë¶ì ì± ìì ììë ê°ë²¼ì´ ì¤í¤ë§ë§ ë¡ëíê³ , ì¤íì íìí í´ëì¤ë§ ëì¤ì ë¤ì´ë¡ë/주ì
í ì ììµëë¤.
### Dynamic\_ prefix ë¤ì´ë° ê·ì¹
ëì ë
¸ëì íì
ëª
ì ë°ëì `Dynamic_`ì¼ë¡ ììí´ì¼ í©ëë¤.
```
Dynamic_MyRSI â
Dynamic_MACDCross â
MyCustomNode â (Dynamic_ prefix ìì)
ConditionNode â (기존 ë
¸ëì ì¶©ë ê°ë¥)
```
ì´ ê·ì¹ì:
* 기존 ë´ì¥ ë
¸ëìì **ì´ë¦ ì¶©ëì ë°©ì§**í©ëë¤
* ìí¬íë¡ì°ìì ëì ë
¸ë를 **ì½ê² ìë³**í ì ìê² í©ëë¤
* `get_required_dynamic_types()`ê° íìí ëì íì
ì ìëì¼ë¡ ì°¾ì ì ìê² í©ëë¤
### ì±
ì ë¶ë¦¬
| ìì | ë¼ì´ë¸ë¬ë¦¬ (ProgramGarden) | ì¬ì©ì |
| -------- | ----------------------------------- | ---------------- |
| ì¤í¤ë§ ì ì¥ì | ì ê³µ (`DynamicNodeRegistry`) | ì¤í¤ë§ ì ì ë° ë±ë¡ |
| í´ëì¤ ê²ì¦ | BaseNode ìì, execute() ì¡´ì¬, í¬í¸ ì¼ì¹ ê²ì¦ | í´ëì¤ êµ¬í |
| í´ëì¤ ì ì¥ | ê²ì¦ í ì ì¥ | ë¤ì´ë¡ë/import í 주ì
|
| ìí¬íë¡ì° ì¤í | 주ì
ë í´ëì¤ë¡ ì¤í | ì¤í ìì² |
***
## 3. ì¬ì© íë¦ (4ë¨ê³)
{% @mermaid/diagram content="flowchart LR
A\["Step 1
ì¤í¤ë§ ë±ë¡
(ì± ìì ì)"] --> B\["Step 2
ìí¬íë¡ì° ì ì
(JSON)"] --> C\["Step 3
íì
íì¸ &
í´ëì¤ ì£¼ì
"] --> D\["Step 4
ì¤í
(Job)"]" %}
### Step 1: ì¤í¤ë§ ë±ë¡
ì± ìì ì, ì¬ì©ìê° ì ìí ë
¸ë ì¤í¤ë§ë¥¼ ë±ë¡í©ëë¤.
```python
from programgarden import WorkflowExecutor
executor = WorkflowExecutor()
# ì¤í¤ë§ë¥¼ ëì
ë리 리ì¤í¸ë¡ ì ë¬
executor.register_dynamic_schemas([
{
"node_type": "Dynamic_RSI",
"category": "condition",
"description": "ëì RSI ì§í",
"inputs": [
{"name": "data", "type": "array", "required": True}
],
"outputs": [
{"name": "rsi", "type": "number"},
{"name": "signal", "type": "string"},
],
}
])
```
> ì´ ìì ììë í´ëì¤ê° ìì´ë ë©ëë¤. UIì ë
¸ë 목ë¡ì íìíë ì©ëì
ëë¤.
### Step 2: ìí¬íë¡ì° ì ì
ëì ë
¸ë를 í¬í¨í ìí¬íë¡ì°ë¥¼ JSONì¼ë¡ ì ìí©ëë¤.
```json
{
"nodes": [
{"id": "start", "type": "StartNode"},
{"id": "rsi", "type": "Dynamic_RSI", "period": 14}
],
"edges": [
{"from": "start", "to": "rsi"}
]
}
```
### Step 3: íì íì
íì¸ & í´ëì¤ ì£¼ì
ìí¬íë¡ì°ìì ì¬ì©ëë ëì íì
ì íì¸íê³ , í´ë¹ í´ëì¤ë¥¼ 주ì
í©ëë¤.
```python
# íìí ëì íì
íì¸
required = executor.get_required_dynamic_types(workflow)
# â ["Dynamic_RSI"]
# í´ëì¤ ì£¼ì
executor.inject_node_classes({
"Dynamic_RSI": DynamicRSINode,
})
# ì¤ë¹ ìë£ íì¸ (ì í)
assert executor.is_dynamic_node_ready("Dynamic_RSI")
```
### Step 4: ì¤í & ì 리
```python
# ê²ì¦
validation = executor.validate(workflow)
assert validation.is_valid
# ì¤í
job = await executor.execute(workflow)
# ... ìí¬íë¡ì° ì¤í ...
# ì 리 (ì¤í í ë©ëª¨ë¦¬ í´ì )
await job.stop()
executor.clear_injected_classes()
```
***
## 4. ëì ë
¸ë í´ëì¤ ìì±ë²
### 기본 구조
ëì ë
¸ëë `BaseNode`ì ììíê³ , `execute()` ë©ìë를 구íí´ì¼ í©ëë¤.
```python
from typing import Dict, Any, List
from programgarden_core.nodes.base import BaseNode, NodeCategory, OutputPort
class DynamicRSINode(BaseNode):
# íì: íì
ëª
(ì¤í¤ë§ì node_typeê³¼ ì¼ì¹í´ì¼ í¨)
type: str = "Dynamic_RSI"
# íì: ì¹´í
ê³ ë¦¬
category: NodeCategory = NodeCategory.CONDITION
# ì í: ì¤ì íë (Pydantic íë)
period: int = 14
# íì: ì¶ë ¥ í¬í¸ ì ì (ì¤í¤ë§ì outputsì ì´ë¦ì´ ì¼ì¹í´ì¼ í¨)
_outputs: List[OutputPort] = [
OutputPort(name="rsi", type="number"),
OutputPort(name="signal", type="string"),
]
# íì: ì¤í ë¡ì§
async def execute(self, context) -> Dict[str, Any]:
# contextìì ì
ë ¥ ë°ì´í° ì ê·¼ ê°ë¥
# ê³ì° ë¡ì§ ìí í ì¶ë ¥ í¬í¸ì ë§ë ëì
ë리 ë°í
return {
"rsi": 35.5,
"signal": "oversold",
}
```
### BaseNode ìì
모ë ëì ë
¸ëë `programgarden_core.nodes.base.BaseNode`ì ììí´ì¼ í©ëë¤.
```python
from programgarden_core.nodes.base import BaseNode
```
`BaseNode`ì Pydantic `BaseModel` 기ë°ì´ë¯ë¡, ì¤ì íë를 Pydantic íëë¡ ì ì¸í ì ììµëë¤.
### \_outputs ì ì (OutputPort)
ì¤í¤ë§ì ì ìí ì¶ë ¥ í¬í¸ì **ì´ë¦ì´ ì¼ì¹íë** `OutputPort`를 í´ëì¤ì ì ì¸í´ì¼ í©ëë¤.
```python
from programgarden_core.nodes.base import OutputPort
_outputs: List[OutputPort] = [
OutputPort(name="rsi", type="number"),
OutputPort(name="signal", type="string"),
]
```
`OutputPort` íë:
| íë | íì
| íì | ì¤ëª
|
| -------------- | --- | :-: | ------------------------------------------------ |
| `name` | str | O | í¬í¸ ì´ë¦ (ì¤í¤ë§ì outputsì ì¼ì¹) |
| `type` | str | O | ë°ì´í° íì
(`number`, `string`, `array`, `object` ë±) |
| `display_name` | str | | ì¬ì©ì íìì© ì´ë¦ |
| `description` | str | | í¬í¸ ì¤ëª
|
### \_inputs ì ì (InputPort, ì í)
ì
ë ¥ í¬í¸ë ì ì¸í ì ììµëë¤.
```python
from programgarden_core.nodes.base import InputPort
_inputs: List[InputPort] = [
InputPort(name="data", type="array", required=True),
]
```
`InputPort` íë:
| íë | íì
| íì | ì¤ëª
|
| ------------- | ---- | :-: | ---------------------------- |
| `name` | str | O | í¬í¸ ì´ë¦ |
| `type` | str | O | ë°ì´í° íì
|
| `required` | bool | | íì ì¬ë¶ (기본: `True`) |
| `multiple` | bool | | ì¬ë¬ ì£ì§ ì°ê²° ê°ë¥ ì¬ë¶ (기본: `False`) |
| `description` | str | | í¬í¸ ì¤ëª
|
### execute() ë©ìë 구í
`execute()`ë ë¹ë기 ë©ìë(`async def`)ë¡ êµ¬ííë©°, `Dict[str, Any]`를 ë°íí©ëë¤.
```python
async def execute(self, context) -> Dict[str, Any]:
# 1. ì
ë ¥ ë°ì´í° ì ê·¼
# 2. ê³ì° ë¡ì§ ìí
# 3. ì¶ë ¥ í¬í¸ì ë§ë ëì
ë리 ë°í
return {
"output_port_name": value,
}
```
ë°í ëì
ë리ì **í¤**ë `_outputs`ì ì ìí í¬í¸ ì´ë¦ê³¼ ì¼ì¹í´ì¼ í©ëë¤.
### ì¤ì íë ì¶ê°
Pydantic íëë¡ ì¤ì ê°ì ì ì¸íë©´, ìí¬íë¡ì° JSONìì ê°ì ì ë¬ë°ì ì ììµëë¤.
```python
class DynamicRSINode(BaseNode):
type: str = "Dynamic_RSI"
category: NodeCategory = NodeCategory.CONDITION
# ì¤ì íë
period: int = 14
overbought: float = 70.0
oversold: float = 30.0
# ...
```
ìí¬íë¡ì° JSONìì:
```json
{
"id": "rsi",
"type": "Dynamic_RSI",
"period": 21,
"overbought": 75.0,
"oversold": 25.0
}
```
***
## 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)
```python
executor.register_dynamic_schemas([
{
"node_type": "Dynamic_RSI", # íì, Dynamic_ prefix
"category": "condition", # ì í, 기본ê°: "data"
"description": "ëì RSI", # ì í
"inputs": [...], # ì í, ì
ë ¥ í¬í¸ ì ì
"outputs": [...], # ì í, ì¶ë ¥ í¬í¸ ì ì
"config_schema": {...}, # ì í, ì¤ì íë ì¤í¤ë§
"version": "1.0.0", # ì í, 기본ê°: "1.0.0"
"author": "í길ë", # ì í
}
])
```
**Raises:** `ValueError` - `Dynamic_` prefixê° ìë ê²½ì°
#### get\_required\_dynamic\_types(workflow)
```python
required = executor.get_required_dynamic_types(workflow)
# â ["Dynamic_RSI", "Dynamic_MACD"]
```
ìí¬íë¡ì° JSONì `nodes` ë°°ì´ìì `Dynamic_`ì¼ë¡ ììíë íì
ë§ ì¶ì¶í©ëë¤.
#### inject\_node\_classes(node\_classes)
```python
executor.inject_node_classes({
"Dynamic_RSI": DynamicRSINode,
"Dynamic_MACD": DynamicMACDNode,
})
```
**ê²ì¦ í목 (ìë):**
1. ì¤í¤ë§ ë±ë¡ ì¬ë¶
2. `BaseNode` ìì ì¬ë¶
3. `execute()` ë©ìë ì¡´ì¬ ì¬ë¶
4. ì¤í¤ë§ì í´ëì¤ì ì¶ë ¥ í¬í¸ ì¼ì¹ ì¬ë¶
**Raises:**
* `ValueError` - ì¤í¤ë§ 미ë±ë¡, í¬í¸ ë¶ì¼ì¹
* `TypeError` - BaseNode 미ìì, execute() 미구í
#### is\_dynamic\_node\_ready(node\_type)
```python
if executor.is_dynamic_node_ready("Dynamic_RSI"):
print("ì¤í ê°ë¥!")
```
ì¤í¤ë§ ë±ë¡ **AND** í´ëì¤ ì£¼ì
ì´ ëª¨ë ìë£ëìì ë `True`를 ë°íí©ëë¤.
#### clear\_injected\_classes()
```python
executor.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)
### ì¤í¤ë§ ì ì
```python
schemas = [
{
"node_type": "Dynamic_RSI",
"category": "condition",
"description": "ëì RSI ì§í ë
¸ë",
"inputs": [
{"name": "data", "type": "array", "required": True}
],
"outputs": [
{"name": "rsi", "type": "number"},
{"name": "signal", "type": "string"},
],
"config_schema": {
"period": {"type": "integer", "default": 14, "min": 1, "max": 100}
},
},
{
"node_type": "Dynamic_MACD",
"category": "condition",
"description": "ëì MACD ì§í ë
¸ë",
"outputs": [
{"name": "macd", "type": "number"},
{"name": "signal_line", "type": "number"},
],
"config_schema": {
"fast_period": {"type": "integer", "default": 12},
"slow_period": {"type": "integer", "default": 26},
},
},
]
```
### í´ëì¤ êµ¬í
```python
from typing import Dict, Any, List
from programgarden_core.nodes.base import BaseNode, NodeCategory, OutputPort, InputPort
class DynamicRSINode(BaseNode):
"""ëì RSI ì§í ë
¸ë"""
type: str = "Dynamic_RSI"
category: NodeCategory = NodeCategory.CONDITION
period: int = 14
_inputs: List[InputPort] = [
InputPort(name="data", type="array", required=True),
]
_outputs: List[OutputPort] = [
OutputPort(name="rsi", type="number"),
OutputPort(name="signal", type="string"),
]
async def execute(self, context) -> Dict[str, Any]:
# ì¤ì RSI ê³ì° ë¡ì§ì ì¬ê¸°ì 구í
rsi_value = 35.5 # ìì ê°
if rsi_value < 30:
signal = "oversold"
elif rsi_value > 70:
signal = "overbought"
else:
signal = "neutral"
return {
"rsi": rsi_value,
"signal": signal,
}
class DynamicMACDNode(BaseNode):
"""ëì MACD ì§í ë
¸ë"""
type: str = "Dynamic_MACD"
category: NodeCategory = NodeCategory.CONDITION
fast_period: int = 12
slow_period: int = 26
_outputs: List[OutputPort] = [
OutputPort(name="macd", type="number"),
OutputPort(name="signal_line", type="number"),
]
async def execute(self, context) -> Dict[str, Any]:
# ì¤ì MACD ê³ì° ë¡ì§ì ì¬ê¸°ì 구í
return {
"macd": 1.23,
"signal_line": 1.10,
}
```
### ìí¬íë¡ì° JSON
```json
{
"id": "rsi-macd-workflow",
"name": "RSI + MACD ì ëµ",
"version": "1.0.0",
"nodes": [
{"id": "start", "type": "StartNode"},
{"id": "rsi", "type": "Dynamic_RSI", "period": 14},
{"id": "macd", "type": "Dynamic_MACD", "fast_period": 12, "slow_period": 26}
],
"edges": [
{"from": "start", "to": "rsi"},
{"from": "start", "to": "macd"}
]
}
```
### ì¤í ì½ë
```python
import asyncio
from programgarden import WorkflowExecutor
async def main():
executor = WorkflowExecutor()
# 1. ì¤í¤ë§ ë±ë¡
executor.register_dynamic_schemas(schemas)
# 2. ìí¬íë¡ì° ì ì
workflow = {
"id": "rsi-macd-workflow",
"name": "RSI + MACD ì ëµ",
"version": "1.0.0",
"nodes": [
{"id": "start", "type": "StartNode"},
{"id": "rsi", "type": "Dynamic_RSI", "period": 14},
{"id": "macd", "type": "Dynamic_MACD", "fast_period": 12, "slow_period": 26},
],
"edges": [
{"from": "start", "to": "rsi"},
{"from": "start", "to": "macd"},
],
}
# 3. íì íì
íì¸ & í´ëì¤ ì£¼ì
required = executor.get_required_dynamic_types(workflow)
print(f"íìí ëì íì
: {required}")
# â ["Dynamic_RSI", "Dynamic_MACD"]
executor.inject_node_classes({
"Dynamic_RSI": DynamicRSINode,
"Dynamic_MACD": DynamicMACDNode,
})
# 4. ê²ì¦
validation = executor.validate(workflow)
if not validation.is_valid:
print(f"ê²ì¦ ì¤í¨: {validation.errors}")
return
# 5. ì¤í
job = await executor.execute(workflow)
print(f"Job ìì: {job.job_id}")
# ... ìí¬íë¡ì° ì¤í ...
# 6. ì 리
await job.stop()
executor.clear_injected_classes()
asyncio.run(main())
```
***
## 7. ì ì½ì¬í & ë³´ì
### credential\_id ì¬ì© ë¶ê°
ëì ë
¸ëììë `credential_id`를 ì¬ì©í ì ììµëë¤. ì´ë ë³´ìì ì¸ë¶ ì½ëê° ì¬ì©ìì ì¦ê¶ì¬ ì¸ì¦ ì ë³´ì ì ê·¼íë ê²ì ì°¨ë¨í기 ìí¨ì
ëë¤.
```json
{
"id": "custom",
"type": "Dynamic_RSI",
"credential_id": "my-cred"
}
```
ìì ê°ì´ `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íì¬ ì¬ì©í ì ììµëë¤. ë¤ë§, í´ë¹ ë¼ì´ë¸ë¬ë¦¬ê° ì¤í íê²½ì ì¤ì¹ëì´ ìì´ì¼ í©ëë¤.
```python
async def execute(self, context) -> Dict[str, Any]:
import pandas as pd
import numpy as np
# pandas/numpy를 íì©í ê³ì°
data = pd.DataFrame(...)
rsi = ...
return {"rsi": rsi, "signal": "oversold"}
```
### Q. clear\_injected\_classes() í ì¤í¤ë§ë ì ì§ëëì?
ë¤. `clear_injected_classes()`ë í´ëì¤ë§ ì ê±°í©ëë¤. ì¤í¤ë§ë ê·¸ëë¡ ì ì§ëì´ UIì ë
¸ë 목ë¡ì´ ê³ì íìë©ëë¤. ë¤ì ì¤í ì í´ëì¤ë§ ë¤ì 주ì
íë©´ ë©ëë¤.
### Q. íëì ìí¬íë¡ì°ì ëì ë
¸ëì ë´ì¥ ë
¸ë를 í¨ê» ì¬ì©í ì ìëì?
ë¤. ëì ë
¸ëë ë´ì¥ ë
¸ëì ëì¼í ë°©ìì¼ë¡ ìí¬íë¡ì°ì ë°°ì¹ë©ëë¤. edgesë¡ ì°ê²°íê³ , `{{ }}` ííìì¼ë¡ ë°ì´í°ë¥¼ ë°ì¸ë©í ì ììµëë¤.
```json
{
"nodes": [
{"id": "broker", "type": "OverseasStockBrokerNode", "credential_id": "cred-1", "paper_trading": false},
{"id": "historical", "type": "OverseasStockHistoricalDataNode"},
{"id": "custom_rsi", "type": "Dynamic_RSI", "period": 14}
],
"edges": [
{"from": "broker", "to": "historical"},
{"from": "historical", "to": "custom_rsi"}
]
}
```
### Q. DynamicNodeRegistry를 ì§ì ì¬ì©í´ì¼ íëì?
ëë¶ë¶ì ê²½ì° `WorkflowExecutor`ì ëí¼ ë©ìëë§ì¼ë¡ ì¶©ë¶í©ëë¤. `DynamicNodeRegistry`ë ë´ë¶ì ì¼ë¡ ì±ê¸í¤ í¨í´ì¼ë¡ ê´ë¦¬ëë©°, `WorkflowExecutor`ê° ì´ë¥¼ ìëì¼ë¡ ì¬ì©í©ëë¤.
ì§ì ì¬ì©ì´ íìí ê²½ì°:
```python
from programgarden_core.registry import DynamicNodeRegistry, DynamicNodeSchema
registry = DynamicNodeRegistry()
# Pydantic 모ë¸ë¡ ì§ì ì¤í¤ë§ ë±ë¡
schema = DynamicNodeSchema(
node_type="Dynamic_MyRSI",
category="condition",
outputs=[{"name": "rsi", "type": "number"}],
)
registry.register_schema(schema)
```