PlateERAG Backendμ Node Editorλ μκ°μ μν¬νλ‘μ° νΈμ§κΈ°λ₯Ό μν λ°±μλ μμ€ν μ λλ€. κ° λ Έλλ νΉμ κΈ°λ₯μ μννλ λ 립μ μΈ μ€ν λ¨μμ΄λ©°, μ΄λ€μ μ°κ²°νμ¬ λ³΅μ‘ν λ°μ΄ν° μ²λ¦¬ μν¬νλ‘μ°λ₯Ό ꡬμ±ν μ μμ΅λλ€.
- λ Έλ κΈ°λ° μν€ν μ²: κ° λ Έλλ νλμ κΈ°λ₯μ λ΄λΉνλ λ 립μ μΈ μ€ν λ¨μ
- μλ λ
Έλ λ°κ²¬:
nodes/ν΄λμ λ Έλλ€μ μλμΌλ‘ νμ§νκ³ λ±λ‘ - νμ μμ€ν : κ°λ ₯ν νμ κ²μ¦ λ° ν¬νΈ μμ€ν
- μν¬νλ‘μ° μ€ν: μμ μ λ ¬μ ν΅ν ν¨μ¨μ μΈ λ Έλ μ€ν
- νμ₯μ±: μλ‘μ΄ λ Έλλ₯Ό μ½κ² μΆκ°ν μ μλ νλ¬κ·ΈμΈ μν€ν μ²
- μΉ΄ν κ³ λ¦¬ μμ€ν : λ Έλλ₯Ό κΈ°λ₯λ³λ‘ λΆλ₯νμ¬ κ΄λ¦¬
- λ§€κ°λ³μ κ²μ¦: λ°νμ μ μ λ Έλ λ§€κ°λ³μ μ ν¨μ± κ²μ¦
- JSON μ€ν μμ±: νλ‘ νΈμλλ₯Ό μν λ Έλ μ€ν μλ μμ±
editor/
βββ README.md # π μ΄ λ¬Έμ
βββ __init__.py # π§ μλν° ν¨ν€μ§ μ΄κΈ°ν
βββ model/ # π λ°μ΄ν° λͺ¨λΈ
β βββ __init__.py
β βββ node.py # π λ
Έλ νμ
μ μ λ° κ²μ¦
βββ node_composer.py # πΌ λ
Έλ νμ§ λ° λ±λ‘ μμ€ν
βββ workflow_executor.py # π μν¬νλ‘μ° μ€ν μμ§
βββ nodes/ # π λ
Έλ ꡬν ν΄λ
βββ __init__.py
βββ chat/ # π¬ μ±ν
λͺ¨λΈ λ
Έλ
β βββ __init__.py
β βββ chat_openai.py # π€ OpenAI μ±ν
λ
Έλ
βββ math/ # π’ μν μ°μ° λ
Έλ
β βββ __init__.py
β βββ math_add.py # β λ§μ
λ
Έλ
β βββ math_multiply.py # βοΈ κ³±μ
λ
Έλ
β βββ math_subtract.py # β λΊμ
λ
Έλ
βββ tool/ # π§ μ νΈλ¦¬ν° λ
Έλ
βββ __init__.py
βββ input_int.py # π₯ μ μ μ
λ ₯ λ
Έλ
βββ input_str.py # π₯ λ¬Έμμ΄ μ
λ ₯ λ
Έλ
βββ print_any.py # π€ μΆλ ₯ λ
Έλ
βββ test_validation.py # π§ͺ κ²μ¦ ν
μ€νΈ λ
Έλ
νμ¬ μ§μνλ μΉ΄ν κ³ λ¦¬λ€:
| μΉ΄ν κ³ λ¦¬ ID | μΉ΄ν κ³ λ¦¬ μ΄λ¦ | μμ΄μ½ | μ€λͺ |
|---|---|---|---|
langchain |
LangChain | SiLangchain |
LangChain λΌμ΄λΈλ¬λ¦¬ κΈ°λ° λ Έλ |
polar |
POLAR | POLAR |
POLAR μμ€ν μ μ© λ Έλ |
utilities |
Utilities | LuWrench |
μ νΈλ¦¬ν° λ° λꡬ λ Έλ |
math |
Math | LuWrench |
μν μ°μ° λ Έλ |
κ° μΉ΄ν κ³ λ¦¬ λ΄μμ μ¬μ©ν μ μλ κΈ°λ₯λ€:
| κΈ°λ₯ ID | κΈ°λ₯ μ΄λ¦ | μ€λͺ |
|---|---|---|
agents |
Agent | LangChain μμ΄μ νΈ |
cache |
Cache | μΊμ± μμ€ν |
chain |
Chain | LangChain μ²΄μΈ |
chat_models |
Chat Model | μ±ν λͺ¨λΈ |
document_loaders |
Document Loader | λ¬Έμ λ‘λ |
embeddings |
Embedding | μλ² λ© λͺ¨λΈ |
graph |
Graph | κ·Έλν μ²λ¦¬ |
memory |
Memory | λ©λͺ¨λ¦¬ μμ€ν |
moderation |
Moderation | μ½ν μΈ κ²μ΄ |
output_parsers |
Output Parser | μΆλ ₯ νμ |
tools |
Tool | λꡬ λ Έλ |
arithmetic |
Arithmetic | μν μ°μ° |
endnode |
End Node | μ’ λ£ λ Έλ |
startnode |
Start Node | μμ λ Έλ |
λ Έλ κ° λ°μ΄ν° μ μ‘μ μν ν¬νΈ νμ λ€:
| νμ | μ€λͺ | μμ |
|---|---|---|
INT |
μ μ | 42, -10, 0 |
STR |
λ¬Έμμ΄ | "Hello", "World" |
FLOAT |
λΆλμμμ | 3.14, -0.5, 1.0 |
BOOL |
λΆλ¦° | true, false |
ANY |
λͺ¨λ νμ | λͺ¨λ λ°μ΄ν° νμ νμ© |
λ Έλ μ€μ μ μν λ§€κ°λ³μ νμ λ€:
| νμ | μ€λͺ | μΆκ° μμ± |
|---|---|---|
STRING |
λ¬Έμμ΄ λ§€κ°λ³μ | - |
INTEGER |
μ μ λ§€κ°λ³μ | min, max, step |
FLOAT |
λΆλμμμ λ§€κ°λ³μ | min, max, step |
BOOLEAN |
λΆλ¦° λ§€κ°λ³μ | - |
parameters = [
{
"id": "temperature",
"name": "Temperature",
"type": "FLOAT",
"value": 0.7,
"required": False,
"optional": True, # κ³ κΈ λͺ¨λμμλ§ νμ
"min": 0.0,
"max": 2.0,
"step": 0.1
},
{
"id": "model",
"name": "Model",
"type": "STRING",
"value": "gpt-3.5-turbo",
"required": True,
"options": [ # λλ‘λ€μ΄ μ΅μ
{"value": "gpt-3.5-turbo", "label": "GPT-3.5 Turbo"},
{"value": "gpt-4", "label": "GPT-4"}
]
}
]μλ‘μ΄ λ
Έλλ₯Ό nodes/ ν΄λμ μ μ ν μΉ΄ν
κ³ λ¦¬μ μμ±ν©λλ€.
νμΌ: editor/nodes/tool/string_concat.py
"""
λ¬Έμμ΄ μ°κ²° λ
Έλ
λ κ°μ λ¬Έμμ΄μ μ
λ ₯λ°μ μ°κ²°ν κ²°κ³Όλ₯Ό λ°νν©λλ€.
"""
from editor.node_composer import Node
class StringConcatNode(Node):
# μΉ΄ν
κ³ λ¦¬ λ° κΈ°λ₯ μ μ
categoryId = "utilities" # λ°λμ CATEGORIES_LABEL_MAPμ μ‘΄μ¬ν΄μΌ ν¨
functionId = "tools" # λ°λμ FUNCTION_LABEL_MAPμ μ‘΄μ¬ν΄μΌ ν¨
# λ
Έλ κΈ°λ³Έ μ 보
nodeId = "tool/string_concat" # κ³ μ μλ³μ (μΉ΄ν
κ³ λ¦¬/λ
Έλλͺ
νμ κΆμ₯)
nodeName = "String Concat" # μ¬μ©μμκ² νμλ μ΄λ¦
description = "λ κ°μ λ¬Έμμ΄μ μ
λ ₯λ°μ μ°κ²°ν κ²°κ³Όλ₯Ό λ°νν©λλ€. ꡬλΆμλ₯Ό μ€μ ν μ μμ΅λλ€."
tags = ["string", "concatenation", "text", "join", "utility"] # κ²μ νκ·Έ
# μ
λ ₯ ν¬νΈ μ μ
inputs = [
{
"id": "str1", # ν¬νΈ κ³ μ μλ³μ
"name": "String 1", # ν¬νΈ νμ μ΄λ¦
"type": "STR", # λ°μ΄ν° νμ
"required": True, # νμ μ
λ ₯
"multi": False # λ€μ€ μ°κ²° λΉνμ©
},
{
"id": "str2",
"name": "String 2",
"type": "STR",
"required": True,
"multi": False
}
]
# μΆλ ₯ ν¬νΈ μ μ
outputs = [
{
"id": "result",
"name": "Result",
"type": "STR"
}
]
# λ§€κ°λ³μ μ μ
parameters = [
{
"id": "separator",
"name": "Separator",
"type": "STRING",
"value": " ", # κΈ°λ³Έκ°: 곡백
"required": False, # νμ μλ
"optional": False # κΈ°λ³Έ λͺ¨λμμ νμ
}
]
def execute(self, str1: str, str2: str, separator: str = " ") -> str:
"""
λ
Έλ μ€ν λ©μλ
Args:
str1: 첫 λ²μ§Έ λ¬Έμμ΄
str2: λ λ²μ§Έ λ¬Έμμ΄
separator: ꡬλΆμ (κΈ°λ³Έκ°: 곡백)
Returns:
str: μ°κ²°λ λ¬Έμμ΄
"""
return f"{str1}{separator}{str2}"λ Έλκ° μ μλλ©΄ μλμΌλ‘ λ±λ‘λ©λλ€. λ±λ‘ μ±κ³΅ μ λ€μκ³Ό κ°μ λ©μμ§κ° μΆλ ₯λ©λλ€:
-> λ
Έλ 'String Concat' λ±λ‘ μλ£.
λ±λ‘ μ€ν¨ μ λ€μκ³Ό κ°μ μ€λ₯κ° μΆλ ₯λ©λλ€:
[Node Registration Failed] Node 'StringConcatNode': 'categoryId' is invalid.
-> Assigned value: 'invalid_category' (Allowed values: ['langchain', 'polar', 'utilities', 'math'])
μλ‘μ΄ λ Έλλ₯Ό ν μ€νΈνλ λ°©λ²:
# test_string_concat.py
from editor.nodes.tool.string_concat import StringConcatNode
def test_string_concat():
node = StringConcatNode()
# κΈ°λ³Έ ꡬλΆμ ν
μ€νΈ
result = node.execute("Hello", "World")
assert result == "Hello World"
# μ¬μ©μ μ μ ꡬλΆμ ν
μ€νΈ
result = node.execute("Hello", "World", separator="-")
assert result == "Hello-World"
print("β
String Concat Node ν
μ€νΈ ν΅κ³Ό")
if __name__ == "__main__":
test_string_concat()class CounterNode(Node):
categoryId = "utilities"
functionId = "tools"
nodeId = "tool/counter"
nodeName = "Counter"
description = "νΈμΆλ λλ§λ€ μΉ΄μ΄νΈλ₯Ό μ¦κ°μν€λ λ
Έλμ
λλ€."
tags = ["counter", "state", "increment"]
inputs = [
{
"id": "trigger",
"name": "Trigger",
"type": "ANY",
"required": True,
"multi": False
}
]
outputs = [
{
"id": "count",
"name": "Count",
"type": "INT"
}
]
parameters = [
{
"id": "start_value",
"name": "Start Value",
"type": "INTEGER",
"value": 0,
"required": False
}
]
def __init__(self):
super().__init__()
self.count = 0
def execute(self, trigger: any, start_value: int = 0) -> int:
if self.count == 0:
self.count = start_value
self.count += 1
return self.countclass ConditionalNode(Node):
categoryId = "utilities"
functionId = "tools"
nodeId = "tool/conditional"
nodeName = "Conditional"
description = "쑰건μ λ°λΌ λ€λ₯Έ κ°μ λ°ννλ λ
Έλμ
λλ€."
tags = ["conditional", "if", "logic", "branch"]
inputs = [
{
"id": "condition",
"name": "Condition",
"type": "BOOL",
"required": True,
"multi": False
},
{
"id": "true_value",
"name": "True Value",
"type": "ANY",
"required": True,
"multi": False
},
{
"id": "false_value",
"name": "False Value",
"type": "ANY",
"required": True,
"multi": False
}
]
outputs = [
{
"id": "result",
"name": "Result",
"type": "ANY"
}
]
parameters = []
def execute(self, condition: bool, true_value: any, false_value: any) -> any:
return true_value if condition else false_valuefrom typing import List
class BatchProcessNode(Node):
categoryId = "utilities"
functionId = "tools"
nodeId = "tool/batch_process"
nodeName = "Batch Process"
description = "μ¬λ¬ μ
λ ₯μ λ°°μΉλ‘ μ²λ¦¬νλ λ
Έλμ
λλ€."
tags = ["batch", "process", "list", "multiple"]
inputs = [
{
"id": "items",
"name": "Items",
"type": "ANY",
"required": True,
"multi": True # λ€μ€ μ
λ ₯ νμ©
}
]
outputs = [
{
"id": "results",
"name": "Results",
"type": "ANY"
}
]
parameters = [
{
"id": "batch_size",
"name": "Batch Size",
"type": "INTEGER",
"value": 10,
"required": False,
"min": 1,
"max": 100
}
]
def execute(self, items: List[any], batch_size: int = 10) -> List[any]:
results = []
# λ°°μΉ λ¨μλ‘ μ²λ¦¬
for i in range(0, len(items), batch_size):
batch = items[i:i + batch_size]
# λ°°μΉ μ²λ¦¬ λ‘μ§
processed_batch = [self.process_item(item) for item in batch]
results.extend(processed_batch)
return results
def process_item(self, item: any) -> any:
"""κ°λ³ μμ΄ν
μ²λ¦¬ λ‘μ§"""
return item # μ€μ μ²λ¦¬ λ‘μ§ κ΅¬νimport requests
from typing import Dict, Any
class HttpRequestNode(Node):
categoryId = "utilities"
functionId = "tools"
nodeId = "tool/http_request"
nodeName = "HTTP Request"
description = "HTTP μμ²μ 보λ΄κ³ μλ΅μ λ°λ λ
Έλμ
λλ€."
tags = ["http", "request", "api", "web", "rest"]
inputs = [
{
"id": "url",
"name": "URL",
"type": "STR",
"required": True,
"multi": False
},
{
"id": "data",
"name": "Request Data",
"type": "ANY",
"required": False,
"multi": False
}
]
outputs = [
{
"id": "response",
"name": "Response",
"type": "ANY"
},
{
"id": "status_code",
"name": "Status Code",
"type": "INT"
}
]
parameters = [
{
"id": "method",
"name": "Method",
"type": "STRING",
"value": "GET",
"required": True,
"options": [
{"value": "GET", "label": "GET"},
{"value": "POST", "label": "POST"},
{"value": "PUT", "label": "PUT"},
{"value": "DELETE", "label": "DELETE"}
]
},
{
"id": "timeout",
"name": "Timeout",
"type": "INTEGER",
"value": 30,
"required": False,
"optional": True,
"min": 1,
"max": 300
}
]
def execute(self, url: str, data: any = None, method: str = "GET", timeout: int = 30) -> Dict[str, Any]:
try:
response = requests.request(
method=method,
url=url,
json=data if data else None,
timeout=timeout
)
return {
"response": response.json() if response.headers.get('content-type', '').startswith('application/json') else response.text,
"status_code": response.status_code
}
except Exception as e:
return {
"response": {"error": str(e)},
"status_code": -1
}import json
from typing import Dict, Any
class DataTransformNode(Node):
categoryId = "utilities"
functionId = "tools"
nodeId = "tool/data_transform"
nodeName = "Data Transform"
description = "λ°μ΄ν°λ₯Ό λ€λ₯Έ νμμΌλ‘ λ³ννλ λ
Έλμ
λλ€."
tags = ["transform", "convert", "data", "format"]
inputs = [
{
"id": "data",
"name": "Input Data",
"type": "ANY",
"required": True,
"multi": False
}
]
outputs = [
{
"id": "transformed_data",
"name": "Transformed Data",
"type": "ANY"
}
]
parameters = [
{
"id": "transform_type",
"name": "Transform Type",
"type": "STRING",
"value": "to_json",
"required": True,
"options": [
{"value": "to_json", "label": "To JSON"},
{"value": "to_string", "label": "To String"},
{"value": "to_upper", "label": "To Uppercase"},
{"value": "to_lower", "label": "To Lowercase"}
]
}
]
def execute(self, data: any, transform_type: str = "to_json") -> any:
try:
if transform_type == "to_json":
return json.dumps(data, ensure_ascii=False, indent=2)
elif transform_type == "to_string":
return str(data)
elif transform_type == "to_upper":
return str(data).upper()
elif transform_type == "to_lower":
return str(data).lower()
else:
return data
except Exception as e:
return {"error": str(e)}# test_nodes.py
import pytest
from editor.nodes.tool.string_concat import StringConcatNode
from editor.nodes.tool.conditional import ConditionalNode
class TestStringConcatNode:
def setup_method(self):
self.node = StringConcatNode()
def test_basic_concat(self):
result = self.node.execute("Hello", "World")
assert result == "Hello World"
def test_custom_separator(self):
result = self.node.execute("Hello", "World", separator="-")
assert result == "Hello-World"
def test_empty_strings(self):
result = self.node.execute("", "")
assert result == " "
class TestConditionalNode:
def setup_method(self):
self.node = ConditionalNode()
def test_true_condition(self):
result = self.node.execute(True, "yes", "no")
assert result == "yes"
def test_false_condition(self):
result = self.node.execute(False, "yes", "no")
assert result == "no"# node_validator.py
from editor.node_composer import get_node_registry, run_discovery
def validate_all_nodes():
"""λͺ¨λ λ
Έλμ μ ν¨μ±μ κ²μ¦ν©λλ€."""
run_discovery()
registry = get_node_registry()
print(f"μ΄ {len(registry)}κ°μ λ
Έλκ° λ±λ‘λμμ΅λλ€.")
# μΉ΄ν
κ³ λ¦¬λ³ λΆλ₯
categories = {}
for node in registry:
cat_id = node['categoryId']
if cat_id not in categories:
categories[cat_id] = []
categories[cat_id].append(node)
# κ²μ¦ κ²°κ³Ό μΆλ ₯
for cat_id, nodes in categories.items():
print(f"\nπ {cat_id}: {len(nodes)}κ° λ
Έλ")
for node in nodes:
print(f" - {node['nodeName']} ({node['id']})")
# μ
λ ₯/μΆλ ₯ κ²μ¦
if not node['inputs'] and not node['outputs']:
print(f" β οΈ μ
λ ₯/μΆλ ₯μ΄ λͺ¨λ μμ΅λλ€.")
# λ§€κ°λ³μ κ²μ¦
for param in node['parameters']:
if param.get('required') and param.get('optional'):
print(f" β λ§€κ°λ³μ '{param['id']}': requiredμ optionalμ΄ λͺ¨λ Trueμ
λλ€.")
if __name__ == "__main__":
validate_all_nodes()editor/model/node.pyμμ μλ‘μ΄ μΉ΄ν
κ³ λ¦¬λ₯Ό μΆκ°ν©λλ€:
CATEGORIES_LABEL_MAP = {
'langchain': 'LangChain',
'polar': 'POLAR',
'utilities': 'Utilities',
'math': 'Math',
'database': 'Database', # μλ‘μ΄ μΉ΄ν
κ³ λ¦¬ μΆκ°
'network': 'Network', # μλ‘μ΄ μΉ΄ν
κ³ λ¦¬ μΆκ°
# ...
}
ICON_LABEL_MAP = {
'langchain': 'SiLangchain',
'polar': 'POLAR',
'utilities': 'LuWrench',
'math': 'LuWrench',
'database': 'BiDatabase', # μλ‘μ΄ μμ΄μ½ μΆκ°
'network': 'BiNetwork', # μλ‘μ΄ μμ΄μ½ μΆκ°
# ...
}νμν κ²½μ° μλ‘μ΄ κΈ°λ₯λ μΆκ°ν©λλ€:
FUNCTION_LABEL_MAP = {
# ...κΈ°μ‘΄ κΈ°λ₯λ€...
'sql': 'SQL',
'nosql': 'NoSQL',
'crud': 'CRUD',
'http': 'HTTP',
'websocket': 'WebSocket',
'tcp': 'TCP',
# ...
}μλ‘μ΄ μΉ΄ν κ³ λ¦¬λ₯Ό μν ν΄λλ₯Ό μμ±ν©λλ€:
editor/nodes/
βββ database/
β βββ __init__.py
β βββ mysql_query.py
β βββ mongodb_find.py
β βββ redis_get.py
βββ network/
βββ __init__.py
βββ http_get.py
βββ websocket_send.py
βββ tcp_connect.py
# editor/nodes/database/mysql_query.py
import mysql.connector
from editor.node_composer import Node
class MySQLQueryNode(Node):
categoryId = "database"
functionId = "sql"
nodeId = "database/mysql_query"
nodeName = "MySQL Query"
description = "MySQL λ°μ΄ν°λ² μ΄μ€μ 쿼리λ₯Ό μ€ννλ λ
Έλμ
λλ€."
tags = ["mysql", "database", "sql", "query"]
inputs = [
{
"id": "query",
"name": "SQL Query",
"type": "STR",
"required": True,
"multi": False
}
]
outputs = [
{
"id": "result",
"name": "Query Result",
"type": "ANY"
}
]
parameters = [
{
"id": "host",
"name": "Host",
"type": "STRING",
"value": "localhost",
"required": True
},
{
"id": "port",
"name": "Port",
"type": "INTEGER",
"value": 3306,
"required": True
},
{
"id": "database",
"name": "Database",
"type": "STRING",
"value": "",
"required": True
},
{
"id": "username",
"name": "Username",
"type": "STRING",
"value": "",
"required": True
},
{
"id": "password",
"name": "Password",
"type": "STRING",
"value": "",
"required": True
}
]
def execute(self, query: str, host: str, port: int, database: str, username: str, password: str):
try:
connection = mysql.connector.connect(
host=host,
port=port,
database=database,
user=username,
password=password
)
cursor = connection.cursor()
cursor.execute(query)
if query.strip().upper().startswith('SELECT'):
result = cursor.fetchall()
columns = [desc[0] for desc in cursor.description]
return {
"data": result,
"columns": columns
}
else:
connection.commit()
return {
"affected_rows": cursor.rowcount,
"message": "Query executed successfully"
}
except Exception as e:
return {"error": str(e)}
finally:
if connection.is_connected():
cursor.close()
connection.close()# editor/nodes/network/http_get.py
import requests
from editor.node_composer import Node
class HttpGetNode(Node):
categoryId = "network"
functionId = "http"
nodeId = "network/http_get"
nodeName = "HTTP GET"
description = "HTTP GET μμ²μ 보λ΄λ λ
Έλμ
λλ€."
tags = ["http", "get", "request", "api", "web"]
inputs = [
{
"id": "url",
"name": "URL",
"type": "STR",
"required": True,
"multi": False
}
]
outputs = [
{
"id": "response",
"name": "Response",
"type": "ANY"
},
{
"id": "status_code",
"name": "Status Code",
"type": "INT"
}
]
parameters = [
{
"id": "timeout",
"name": "Timeout",
"type": "INTEGER",
"value": 30,
"required": False,
"min": 1,
"max": 300
},
{
"id": "headers",
"name": "Headers",
"type": "STRING",
"value": "{}",
"required": False,
"optional": True
}
]
def execute(self, url: str, timeout: int = 30, headers: str = "{}"):
try:
import json
headers_dict = json.loads(headers) if headers else {}
response = requests.get(url, timeout=timeout, headers=headers_dict)
try:
response_data = response.json()
except:
response_data = response.text
return {
"response": response_data,
"status_code": response.status_code
}
except Exception as e:
return {
"response": {"error": str(e)},
"status_code": -1
}λͺ¨λ μν¬νλ‘μ°λ λ°λμ λ€μκ³Ό κ°μ ꡬ쑰λ₯Ό κ°μ ΈμΌ ν©λλ€:
μ¬μ©μ μ
λ ₯ (Interaction)
β
[Start Node]
β
[μ€κ° μ²λ¦¬ λ
Έλλ€]
β
[End Node]
β
μ¬μ©μ μΆλ ₯ (Result)
Start Nodeλ μν¬νλ‘μ°μ μ§μ μ μ΄λ©°, μ¬μ©μμ Interactionμ λ°μλ€μ΄λ νΉμν λ Έλμ λλ€.
- λ¨μΌμ±: μν¬νλ‘μ°λΉ λ°λμ νλλ§ μ‘΄μ¬ν΄μΌ ν¨
- μ λ ₯ μμ: μΈλΆ ν¬νΈλ‘λΆν° μ λ ₯μ λ°μ§ μμ
- Interaction μ°κ²°: μ¬μ©μμ μ λ ₯(Interaction)μ μ§μ λ°μ
- κ³ μ κΈ°λ₯:
functionId = "startnode"λ‘ κ³ μ λ¨
class InputStringNode(Node):
categoryId = "utilities"
functionId = "startnode" # κ³ μ κ°: μμ λ
Έλ
nodeId = "tool/input_str"
nodeName = "Input String"
description = "μ¬μ©μκ° μ€μ ν λ¬Έμμ΄ κ°μ μΆλ ₯νλ μ
λ ₯ λ
Έλμ
λλ€. μν¬νλ‘μ°μμ ν
μ€νΈ λ°μ΄ν°μ μμμ μΌλ‘ μ¬μ©λ©λλ€."
tags = ["input", "string", "text", "parameter", "source", "start_node", "user_input"]
inputs = [] # μ
λ ₯ ν¬νΈ μμ
outputs = [
{
"id": "result",
"name": "Result",
"type": "STR"
}
]
parameters = [
{
"id": "input_str",
"name": "String",
"type": "STRING",
"value": "",
"required": True
}
]
def execute(self, input_str: str) -> str:
"""μ¬μ©μ μ
λ ₯μ κ·Έλλ‘ μΆλ ₯"""
return input_strEnd Nodeλ μν¬νλ‘μ°μ μΆκ΅¬μ μ΄λ©°, μ΅μ’ κ²°κ³Όλ₯Ό μ¬μ©μμκ² λ°ννλ νΉμν λ Έλμ λλ€.
- λ¨μΌμ±: μν¬νλ‘μ°λΉ λ°λμ νλλ§ μ‘΄μ¬ν΄μΌ ν¨
- μΆλ ₯ μμ: λ€λ₯Έ λ Έλλ‘ μΆλ ₯μ μ λ¬νμ§ μμ
- κ²°κ³Ό λ°ν: μν¬νλ‘μ°μ μ΅μ’ κ²°κ³Όλ₯Ό μ¬μ©μμκ² λ°ν
- κ³ μ κΈ°λ₯:
functionId = "endnode"λ‘ κ³ μ λ¨
class OutputStringNode(Node):
categoryId = "utilities"
functionId = "endnode" # κ³ μ κ°: μ’
λ£ λ
Έλ
nodeId = "tool/output_str"
nodeName = "Output String"
description = "μ
λ ₯λ°μ κ°μ μ΅μ’
κ²°κ³Όλ‘ μΆλ ₯νλ μ’
λ£ λ
Έλμ
λλ€. μν¬νλ‘μ°μ μ΅μ’
μΆλ ₯μ μΌλ‘ μ¬μ©λ©λλ€."
tags = ["output", "string", "text", "result", "end_node", "final_output"]
inputs = [
{
"id": "input",
"name": "Input",
"type": "ANY",
"required": True,
"multi": False
}
]
outputs = [] # μΆλ ₯ ν¬νΈ μμ
parameters = []
def execute(self, input: any) -> any:
"""μ
λ ₯μ μ΅μ’
κ²°κ³Όλ‘ λ°ν"""
return input# μ¬μ©μ μ
λ ₯ μμ
user_interaction = {
"type": "text",
"content": "μλ
νμΈμ, λ μ¨λ μ΄λ€κ°μ?",
"timestamp": "2025-07-18T10:30:00Z"
}# Start Nodeκ° μ¬μ©μ μ
λ ₯μ μ²λ¦¬
start_node_result = start_node.execute(user_interaction["content"])
# κ²°κ³Ό: "μλ
νμΈμ, λ μ¨λ μ΄λ€κ°μ?"# μ: μ±ν
λͺ¨λΈμ ν΅ν μλ΅ μμ±
chat_node_result = chat_node.execute(start_node_result)
# κ²°κ³Ό: "μλ
νμΈμ! μ€λ λ μ¨λ λ§κ³ κΈ°μ¨μ 25λμ
λλ€."# End Nodeκ° μ΅μ’
κ²°κ³Όλ₯Ό μ¬μ©μμκ² λ°ν
final_result = end_node.execute(chat_node_result)
# κ²°κ³Ό: "μλ
νμΈμ! μ€λ λ μ¨λ λ§κ³ κΈ°μ¨μ 25λμ
λλ€."{
"workflow_name": "Simple Chat",
"workflow_id": "simple_chat",
"nodes": [
{
"id": "start_node",
"type": "tool/input_str",
"data": {
"nodeId": "tool/input_str",
"parameters": {
"input_str": "{{user_input}}" // Interactionμμ μλ μ£Όμ
}
}
},
{
"id": "chat_node",
"type": "chat/openai",
"data": {
"nodeId": "chat/openai",
"parameters": {
"model": "gpt-3.5-turbo",
"temperature": 0.7
}
}
},
{
"id": "end_node",
"type": "tool/output_str",
"data": {
"nodeId": "tool/output_str",
"parameters": {}
}
}
],
"edges": [
{
"id": "edge1",
"source": {"nodeId": "start_node", "portId": "result"},
"target": {"nodeId": "chat_node", "portId": "text"}
},
{
"id": "edge2",
"source": {"nodeId": "chat_node", "portId": "result"},
"target": {"nodeId": "end_node", "portId": "input"}
}
]
}{
"workflow_name": "RAG Chat",
"workflow_id": "rag_chat",
"nodes": [
{
"id": "start_node",
"type": "tool/input_str",
"data": {
"nodeId": "tool/input_str",
"parameters": {
"input_str": "{{user_input}}"
}
}
},
{
"id": "retrieval_node",
"type": "rag/vector_search",
"data": {
"nodeId": "rag/vector_search",
"parameters": {
"collection_name": "knowledge_base",
"top_k": 5
}
}
},
{
"id": "context_merge_node",
"type": "tool/string_concat",
"data": {
"nodeId": "tool/string_concat",
"parameters": {
"separator": "\n\n"
}
}
},
{
"id": "chat_node",
"type": "chat/openai",
"data": {
"nodeId": "chat/openai",
"parameters": {
"model": "gpt-4",
"temperature": 0.3
}
}
},
{
"id": "end_node",
"type": "tool/output_str",
"data": {
"nodeId": "tool/output_str",
"parameters": {}
}
}
],
"edges": [
{
"id": "edge1",
"source": {"nodeId": "start_node", "portId": "result"},
"target": {"nodeId": "retrieval_node", "portId": "query"}
},
{
"id": "edge2",
"source": {"nodeId": "start_node", "portId": "result"},
"target": {"nodeId": "context_merge_node", "portId": "str1"}
},
{
"id": "edge3",
"source": {"nodeId": "retrieval_node", "portId": "result"},
"target": {"nodeId": "context_merge_node", "portId": "str2"}
},
{
"id": "edge4",
"source": {"nodeId": "context_merge_node", "portId": "result"},
"target": {"nodeId": "chat_node", "portId": "text"}
},
{
"id": "edge5",
"source": {"nodeId": "chat_node", "portId": "result"},
"target": {"nodeId": "end_node", "portId": "input"}
}
]
}- λ¨μμ±: 볡μ‘ν λ‘μ§ μ§μ, μ λ ₯ λ³νμ μ§μ€
- μ μ°μ±: λ€μν μ λ ₯ νμ μ§μ
- κ²μ¦: μ λ ₯ λ°μ΄ν° μ ν¨μ± κ²μ¦
- ν¬λ§·ν : μ¬μ©μ μΉνμ μΈ κ²°κ³Ό νμ
- λ©νλ°μ΄ν°: μ€ν μ 보, νμμ€ν¬ν λ± μΆκ°
- μλ¬ μ²λ¦¬: μ€ν μ€λ₯ μ μ μ ν μλ¬ λ©μμ§
- μ νμ±: Start β μ€κ° β End μ ν ꡬ쑰 μ μ§
- κ²μ¦: μ°κ²°μ± λ° κ΅¬μ‘° κ²μ¦
- ν μ€νΈ: λ€μν μ λ ₯μ λν ν μ€νΈ