ๆฌๆๆกฃๆดๅไบๅ็ซฏๅพๆๅปบ็ณป็ป็่ฝๅๅฎ็ฐไธๅ็ซฏๅฎ็ฐ่ฟ็จ๏ผๆไพ็ณป็ปๆง็ๆถๆ่ฏดๆใๅ ๆฌๆ ๅ LangGraph ๆๅปบๅจๅ DeepAgents ๆๅปบๅจไธค็งๆจกๅผใ
- ็ณป็ปๆถๆๆฆ่ง
- ๅ็ซฏๆ ธๅฟๅฎ็ฐ
- ๅ็ซฏๆ ธๅฟๅฎ็ฐ
- ๆฐๆฎๆตไธไบคไบ
- ่็น็ฑปๅๅฎๆดๅ่
- ่พน้ ็ฝฎ่ง่
- ็ถๆๅ้ๆๅ
- ๆไฝณๅฎ่ทตไธๆจกๆฟ
- ้่ฏฏๅค็ไธ่ฐ่ฏ
- DeepAgents ๆถๆ
flowchart TB
subgraph Frontend["ๅ็ซฏๅฑ React/TypeScript"]
UI[BuilderCanvas<br/>็ปๅธ็ปไปถ]
Store[builderStore<br/>็ถๆ็ฎก็]
Registry[nodeRegistry<br/>่็นๆณจๅ่กจ]
EdgePanel[EdgePropertiesPanel<br/>่พน้
็ฝฎ้ขๆฟ]
PropsPanel[PropertiesPanel<br/>่็น้
็ฝฎ้ขๆฟ]
Types[graph.ts<br/>็ฑปๅๅฎไน]
end
subgraph Factory["GraphBuilder ๅทฅๅๅฑ"]
GB[GraphBuilder<br/>่ชๅจ้ๆฉๆๅปบๅจ]
end
subgraph Builders["ๆๅปบๅจๅฑ"]
LGB[LanggraphModelBuilder<br/>ๆ ๅ LangGraph]
DAGB[DeepAgentsGraphBuilder<br/>DeepAgents ๆๅ็ปๆ]
end
subgraph DeepAgents["DeepAgents ็ปไปถ"]
BM[BackendManager<br/>ๅ
ฑไบซๅ็ซฏ็ฎก็]
SM[SkillsManager<br/>ๆ่ฝ็ฎก็]
NF[NodeFactory<br/>่็นๅทฅๅ]
AC[AgentConfig<br/>้
็ฝฎ่งฃๆ]
end
subgraph Backend["ๅ็ซฏๅฑ Python/FastAPI"]
BaseBuilder[BaseGraphBuilder<br/>ๅบ็กๆๅปบๅจ]
Executors[NodeExecutors<br/>่็นๆง่กๅจ]
State[GraphState<br/>็ถๆๅฎไน]
Validator[NodeConfigValidator<br/>้
็ฝฎ้ช่ฏๅจ]
end
subgraph Runtime["่ฟ่กๆถๅฑ"]
LG[LangGraph<br/>StateGraph]
DA[DeepAgents<br/>Manager/SubAgent]
Checkpointer[Checkpointer<br/>็ถๆๆไน
ๅ]
end
subgraph Data["ๆฐๆฎๅฑ"]
DB[(PostgreSQL<br/>ๅพๅญๅจ)]
API[REST API<br/>GraphRouter]
end
UI --> Store
Store --> API
API --> GB
GB -->|ๆฃๆต useDeepAgents| DAGB
GB -->|้ป่ฎค| LGB
DAGB --> BM
DAGB --> SM
DAGB --> NF
NF --> AC
LGB --> BaseBuilder
DAGB --> BaseBuilder
BaseBuilder --> Executors
LGB --> State
DAGB --> State
LGB --> LG
DAGB --> DA
LG --> Checkpointer
DA --> Checkpointer
Registry --> UI
EdgePanel --> Store
PropsPanel --> Store
Types --> UI
Types --> EdgePanel
็ณป็ปๆฏๆไธค็งๅพๆๅปบๆจกๅผ๏ผ้่ฟ GraphBuilder ๅทฅๅ็ฑป่ชๅจ้ๆฉ๏ผ
| ๆๅปบๆจกๅผ | ่งฆๅๆกไปถ | ๆๅปบๅจ็ฑป | ้็จๅบๆฏ |
|---|---|---|---|
| ๆ ๅ LangGraph | ่็นๆชๅฏ็จ useDeepAgents |
LanggraphModelBuilder |
ไผ ็ปๅทฅไฝๆตใๆกไปถ่ทฏ็ฑใๅพช็ฏๆงๅถ |
| DeepAgents | ่ณๅฐไธไธช่็นๅฏ็จ useDeepAgents |
DeepAgentsGraphBuilder |
ๅค Agent ๅไฝใManager-Worker ๆจกๅผ |
้ๆฉ้ป่พ๏ผ
# backend/app/core/graph/graph_builder.py
def _has_deep_agents_nodes(self) -> bool:
"""ๆฃๆฅๆฏๅฆๆ่็นๅฏ็จไบ DeepAgents"""
for node in self.nodes:
if node.data.get("config", {}).get("useDeepAgents", False):
return True
return False
def _create_builder(self) -> BaseGraphBuilder:
if self._has_deep_agents_nodes():
return DeepAgentsGraphBuilder(...)
else:
return LanggraphModelBuilder(...)| ่ฝๅ | ๅ็ซฏๅฎ็ฐ | ๅ็ซฏๅฎ็ฐ | ่ฏดๆ |
|---|---|---|---|
| ๆกไปถ่ทฏ็ฑ | RouterNodeExecutor + add_conditional_edges |
router_node + EdgePropertiesPanel |
ๅค่งๅ่ทฏ็ฑ๏ผๆฏๆไผๅ ็บง |
| ไบๅ ๆกไปถ | ConditionNodeExecutor |
condition ่็น |
True/False ๅๆฏ |
| ๅพช็ฏๆงๅถ | LoopConditionNodeExecutor |
loop_condition_node |
forEach/while/doWhile |
| ๅนถ่กๆง่ก | Fan-Out + AggregatorNodeExecutor |
aggregator_node |
ๆฏๆ fail_fast/best_effort |
| ็ถๆ้็ฆป | loop_states / task_states |
่ชๅจ็ฎก็ | ้ฒๆญขๅนถ่ก/ๅตๅฅๅพช็ฏๅฒ็ช |
| ๅทฅๅ ท่ฐ็จ | ToolNodeExecutor |
tool_node |
ๆณจๅ่กจๅทฅๅ ทๆง่ก |
| ่ชๅฎไนๅฝๆฐ | FunctionNodeExecutor + Sandbox |
function_node |
ๆฒ็ฎฑๆง่ก Python ไปฃ็ |
| LLM ่ฐ็จ | LLMNodeExecutor |
llm_node |
ๆจกๆฟๅ้ๆฟๆข |
| HTTP ่ฏทๆฑ | HttpRequestNodeExecutor |
http_request_node |
้่ฏ/่ฎค่ฏ/่ถ ๆถ |
| JSON ่งฃๆ | JSONParserNodeExecutor |
json_parser_node |
JSONPath + Schema ้ช่ฏ |
| DeepAgents | DeepAgentsGraphBuilder + CompiledSubAgent |
useDeepAgents ้
็ฝฎ |
Manager-Worker ๆๅๆๆ |
ๆไปถไฝ็ฝฎ: backend/app/core/graph/graph_builder.py
GraphBuilder ๆฏๅพๆๅปบ็ๅ
ฅๅฃๅทฅๅ็ฑป๏ผ่ด่ดฃ่ชๅจๆฃๆตๅพ้
็ฝฎๅนถ้ๆฉๅ้็ๆๅปบๅจๅฎ็ฐใ
class GraphBuilder:
"""Factory class that selects appropriate builder based on graph configuration.
Automatically detects if DeepAgents mode should be used and delegates
to the appropriate builder implementation.
"""
def _has_deep_agents_nodes(self) -> bool:
"""ๆฃๆฅๆฏๅฆๆ่็นๅฏ็จไบ DeepAgents"""
for node in self.nodes:
config = node.data.get("config", {})
if config.get("useDeepAgents", False) is True:
return True
return False
def _create_builder(self) -> BaseGraphBuilder:
"""ๅๅปบๅ้็ๆๅปบๅจๅฎไพ"""
if self._has_deep_agents_nodes():
return DeepAgentsGraphBuilder(...)
else:
return LanggraphModelBuilder(...)
async def build(self) -> CompiledStateGraph:
"""ๅผๆญฅๆๅปบๅนถ็ผ่ฏ StateGraph"""
builder = self._create_builder()
return await builder.build()flowchart TD
Start[GraphBuilder.build] --> Check{ๆฃๆฅ่็น้
็ฝฎ}
Check -->|ๆ useDeepAgents=True| DeepAgents[DeepAgentsGraphBuilder]
Check -->|ๆ useDeepAgents| LangGraph[LanggraphModelBuilder]
DeepAgents --> BuildDA[ๆๅปบๆๅๆๆ]
LangGraph --> BuildLG[ๆๅปบๆ ๅ LangGraph]
BuildDA --> Return[่ฟๅ CompiledStateGraph]
BuildLG --> Return
from app.core.graph.graph_builder import GraphBuilder
# ๅๅปบๆๅปบๅจ๏ผ่ชๅจ้ๆฉๅฎ็ฐ๏ผ
builder = GraphBuilder(
graph=graph,
nodes=nodes,
edges=edges,
llm_model="gpt-4",
user_id=user_id,
)
# ๆๅปบๅพ๏ผ่ชๅจไฝฟ็จๅ้็ๆๅปบๅจ๏ผ
compiled_graph = await builder.build()ๆไปถไฝ็ฝฎ: backend/app/core/graph/langgraph_model_builder.py
LanggraphModelBuilder ๆฏๅพๆๅปบ็ๆ ธๅฟ็ฑป๏ผ็ปงๆฟ่ช BaseGraphBuilder๏ผ่ด่ดฃๅฐๅ็ซฏๅฎไน็่็นๅ่พน่ฝฌๆขไธบ LangGraph ็ StateGraphใ
class LanggraphModelBuilder(BaseGraphBuilder):
"""ๆๅปบๆ ๅ LangGraph๏ผๆฏๆ START/END ่็นใ
ๆฏๆ:
- ๆกไปถ่ทฏ็ฑ (RouterNodeExecutor, ConditionNodeExecutor)
- ๅพช็ฏ (LoopConditionNodeExecutor)
- ๅนถ่กๆง่ก (Fan-Out/Fan-In)
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._conditional_nodes: Set[str] = set() # ๆกไปถ่พน่็น
self._handle_to_route_maps: Dict[str, Dict[str, str]] = {} # Handle ID ๆ ๅฐ
self._executor_cache: Dict[str, Any] = {} # ๆง่กๅจ็ผๅญ
self._loop_body_map: Dict[str, str] = {} # ๅพช็ฏไฝๆ ๅฐ
self._parallel_nodes: Set[str] = set() # ๅนถ่ก่็นflowchart LR
subgraph Build["build() ๆนๆณ"]
V[้ช่ฏๅพ็ปๆ]
L[่ฏๅซๅพช็ฏไฝ]
P[่ฏๅซๅนถ่ก่็น]
N[ๆทปๅ ๆๆ่็น]
C[ๆๅปบๆกไปถ่พน]
S[ๆทปๅ START ่พน]
R[ๆทปๅ ๅธธ่ง่พน]
E[ๆทปๅ END ่พน]
CO[็ผ่ฏๅพ]
end
V --> L --> P --> N --> C --> S --> R --> E --> CO
| ๆนๆณ | ๅ่ฝ |
|---|---|
build() |
ๅผๆญฅๆๅปบๅนถ็ผ่ฏ StateGraph |
_build_conditional_edges_for_router() |
ไธบ Router ่็นๆๅปบๆกไปถ่พน |
_build_conditional_edges_for_condition() |
ไธบ Condition ่็นๆๅปบๆกไปถ่พน |
_build_conditional_edges_for_loop() |
ไธบ Loop ่็นๆๅปบๆกไปถ่พน |
_identify_loop_bodies() |
่ฏๅซๅพช็ฏไฝ่็น |
_identify_parallel_nodes() |
่ฏๅซ Fan-Out ่็น |
_wrap_node_executor() |
ๅ ่ฃ ๆง่กๅจ๏ผ่ชๅจ็ถๆๆดๆฐ๏ผ |
ๆไปถไฝ็ฝฎ: backend/app/core/graph/base_builder.py
ๆไพ่็น/่พน็ฎก็ใๆง่กๅจๅๅปบ็ญๅบ็กๅ่ฝใ
class BaseGraphBuilder(ABC):
def __init__(
self,
graph: AgentGraph,
nodes: List[GraphNode],
edges: List[GraphEdge],
llm_model: Optional[str] = None,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
max_tokens: int = 4096,
user_id: Optional[Any] = None,
checkpointer: Optional[Any] = None,
model_service: Optional[Any] = None,
):
# ๆๅปบๆฅๆพๆ ๅฐ
self._node_map: Dict[uuid.UUID, GraphNode] = {n.id: n for n in nodes}
self._node_id_to_name: Dict[uuid.UUID, str] = {}
self._outgoing_edges: Dict[uuid.UUID, List[uuid.UUID]] = {}
self._incoming_edges: Dict[uuid.UUID, List[uuid.UUID]] = {}async def _create_node_executor(self, node: GraphNode, node_name: str) -> Any:
"""ๆ นๆฎ่็น็ฑปๅๅๅปบๅฏนๅบ็ๆง่กๅจ"""
node_type = self._get_node_type(node)
if node_type == "agent":
return AgentNodeExecutor(node, node_name, ...)
elif node_type == "condition":
return ConditionNodeExecutor(node, node_name)
elif node_type == "router_node":
return RouterNodeExecutor(node, node_name)
elif node_type == "loop_condition_node":
return LoopConditionNodeExecutor(node, node_name)
# ... ๅ
ถไป่็น็ฑปๅๆไปถไฝ็ฝฎ: backend/app/core/graph/node_executors.py
| ๆง่กๅจ | ่ฟๅ็ฑปๅ | ๆ ธๅฟๅ่ฝ |
|---|---|---|
AgentNodeExecutor |
Dict[str, Any] |
LLM Agent ่ฐ็จ๏ผๆฏๆๅทฅๅ ท |
ConditionNodeExecutor |
Dict[str, Any] โ str (route_key) |
ๆกไปถ่กจ่พพๅผๆฑๅผ |
RouterNodeExecutor |
str (route_key) |
ๅค่งๅ่ทฏ็ฑ |
LoopConditionNodeExecutor |
str (continue_loop/exit_loop) |
ๅพช็ฏๆกไปถๆงๅถ |
ToolNodeExecutor |
Dict[str, Any] |
ๆง่กๆณจๅๅทฅๅ ท |
FunctionNodeExecutor |
Dict[str, Any] |
ๆฒ็ฎฑๆง่ก่ชๅฎไนไปฃ็ |
AggregatorNodeExecutor |
Dict[str, Any] |
่ๅๅนถ่ก็ปๆ |
LLMNodeExecutor |
Dict[str, Any] |
ๅๆฌก LLM ่ฐ็จ |
JSONParserNodeExecutor |
Dict[str, Any] |
JSON ่งฃๆ/่ฝฌๆข |
HttpRequestNodeExecutor |
Dict[str, Any] |
HTTP ่ฏทๆฑ๏ผๅธฆ้่ฏ๏ผ |
DirectReplyNodeExecutor |
Dict[str, Any] |
ๆจกๆฟๆถๆฏๅๅค |
class RouterNodeExecutor:
"""ๅค่งๅ่ทฏ็ฑๆง่กๅจ"""
def __init__(self, node: GraphNode, node_id: str):
self.node = node
self.node_id = node_id
self.rules = self._get_rules() # ๆไผๅ
็บงๆๅบ
self.handle_to_route_map: Dict[str, str] = {}
async def __call__(self, state: GraphState) -> str:
"""่ฟๅ route_key๏ผๆ ๅฐๅฐๆกไปถ่พน"""
for rule in self.rules:
if self._evaluate_rule(rule, state):
route_key = rule.get("targetEdgeKey", "default")
return route_key
return self.default_routeๆไปถไฝ็ฝฎ: backend/app/core/graph/graph_state.py
class GraphState(MessagesState):
"""ๅทฅไฝๆตๅพ็ถๆ๏ผๆฏๆๅคๆๆต็จๆจกๅผ"""
# ==================== ๅบ็กๅญๆฎต ====================
messages: Annotated[List[BaseMessage], add_messages]
current_node: Optional[str]
context: Dict[str, Any]
todos: NotRequired[Annotated[List[Dict[str, Any]], add_todos]]
# ==================== ่ทฏ็ฑๆงๅถ ====================
route_decision: NotRequired[str]
route_history: NotRequired[Annotated[List[str], operator.add]]
# ==================== ๅพช็ฏๆงๅถ ====================
loop_count: NotRequired[int]
loop_condition_met: NotRequired[bool]
max_loop_iterations: NotRequired[int]
# ==================== ๅนถ่กๆง่ก ====================
task_results: NotRequired[Annotated[List[Dict[str, Any]], add_task_results]]
parallel_results: NotRequired[Annotated[List[Any], operator.add]]
# ==================== ไฝ็จๅ็ถๆ ====================
loop_states: NotRequired[Annotated[Dict[str, Dict[str, Any]], merge_loop_states]]
task_states: NotRequired[Annotated[Dict[str, Dict[str, Any]], merge_task_states]]
node_contexts: NotRequired[Annotated[Dict[str, Dict[str, Any]], merge_node_contexts]]| Reducer | ไฝ็จ |
|---|---|
add_messages |
่ฟฝๅ ๆถๆฏๅ่กจ |
add_task_results |
่ฟฝๅ ไปปๅก็ปๆ |
merge_loop_states |
ๆทฑๅบฆๅๅนถๅพช็ฏ็ถๆ๏ผ้ฟๅ ๅนถๅๅฒ็ช๏ผ |
merge_task_states |
ๆทฑๅบฆๅๅนถไปปๅก็ถๆ |
merge_node_contexts |
ๆทฑๅบฆๅๅนถ่็นไธไธๆ |
ๆไปถไฝ็ฝฎ: frontend/app/workspace/[workspaceId]/[agentId]/services/nodeRegistry.tsx
interface NodeDefinition {
type: string // ่็น็ฑปๅๆ ่ฏ
label: string // ๆพ็คบๅ็งฐ
subLabel?: string // ๅฏๆ ้ข
icon: LucideIcon // ๅพๆ ็ปไปถ
style: {
color: string // ๆๅญ้ข่ฒ็ฑป
bg: string // ่ๆฏ้ข่ฒ็ฑป
}
defaultConfig: Record<string, unknown> // ้ป่ฎค้
็ฝฎ
schema: FieldSchema[] // ้
็ฝฎ่กจๅ Schema
}
interface FieldSchema {
key: string
label: string
type: FieldType // text/textarea/select/number/modelSelect/toolSelector/...
placeholder?: string
options?: string[]
required?: boolean
description?: string
// number ็ฑปๅไธ็จ
min?: number
max?: number
step?: number
// conditionExpr ็ฑปๅไธ็จ
variables?: string[] // ๅฏ็จๅ้ๆ็คบ
}export const nodeRegistry = {
getGrouped: () => ({
Agents: ['agent', 'llm_node'],
'Flow Control': ['condition', 'condition_agent', 'router_node', 'loop_condition_node'],
Actions: ['custom_function', 'http', 'http_request_node', 'human_input', 'direct_reply',
'tool_node', 'function_node', 'json_parser_node'],
Aggregation: ['aggregator_node'],
}),
}ๆไปถไฝ็ฝฎ: frontend/app/workspace/[workspaceId]/[agentId]/stores/builderStore.ts
interface BuilderState {
// Canvas ็ถๆ
nodes: Node[]
edges: Edge[]
rfInstance: ReactFlowInstance | null
selectedNodeId: string | null
selectedEdgeId: string | null
// ๅๅฒ็ถๆ (Undo/Redo)
past: HistoryState[]
future: HistoryState[]
// ๆไน
ๅ็ถๆ
graphId: string | null
graphName: string | null
workspaceId: string | null
lastAutoSaveTime: number | null
hasPendingChanges: boolean
}| ๆนๆณ | ๅ่ฝ |
|---|---|
addNode(type, position, label, config) |
ๆทปๅ ่็น |
updateNodeConfig(id, config) |
ๆดๆฐ่็น้ ็ฝฎ |
updateEdge(id, data) |
ๆดๆฐ่พน้ ็ฝฎ๏ผEdgeData๏ผ |
selectNode(id) / selectEdge(id) |
้ๆฉ่็น/่พน |
onConnect(connection) |
่ฟๆฅๅค็๏ผ่ชๅจๆจๆญ edge_type๏ผ |
syncEdgesWithRouteRules(nodeId, routes) |
ๅๆญฅ Router ่ทฏ็ฑ่งๅๅฐ่พน |
autoSave() |
่ชๅจไฟๅญ๏ผ้ฒๆ 2s๏ผ |
undo() / redo() |
ๆค้/้ๅ |
onConnect: (connection: Connection) => {
const sourceType = sourceNode?.data?.type || ''
const isConditionalSource = ['router_node', 'condition', 'loop_condition_node']
.includes(sourceType)
// ่ชๅจ่ฎพ็ฝฎ edge_type
let edgeType: EdgeData['edge_type'] = isConditionalSource ? 'conditional' : 'normal'
// ่ชๅจๆจๆญ route_key
if (sourceType === 'condition') {
const hasTrueEdge = edges.some(e => e.data?.route_key === 'true')
defaultRouteKey = hasTrueEdge ? 'false' : 'true'
} else if (sourceType === 'loop_condition_node') {
const hasContinueEdge = edges.some(e => e.data?.route_key === 'continue_loop')
defaultRouteKey = hasContinueEdge ? 'exit_loop' : 'continue_loop'
}
// ...
}ๆไปถไฝ็ฝฎ: frontend/app/workspace/[workspaceId]/[agentId]/components/EdgePropertiesPanel.tsx
- ่พน็ฑปๅ้ๆฉ: Normal / Conditional / Loop Back
- Route Key ้ ็ฝฎ: ๅน้ ๅ็ซฏ่ทฏ็ฑๅณ็ญ
- ๅฟซ้้ๆฉ: ๆ นๆฎๆบ่็น็ฑปๅๆไพๅปบ่ฎฎ
- ๅฎๆถ้ช่ฏ: ๆพ็คบ้ ็ฝฎ้่ฏฏ
// ่ชๅจ็ๆ Handle ID ๅปบ่ฎฎ
const getHandleIdSuggestions = () => {
if (sourceNodeType === 'router_node') {
const routes = sourceNode?.data?.config?.routes || []
return routes.map((r) => ({
handleId: r.targetEdgeKey,
routeKey: r.targetEdgeKey,
}))
}
if (sourceNodeType === 'loop_condition_node') {
return [
{ handleId: 'continue_loop_handle', routeKey: 'continue_loop' },
{ handleId: 'exit_loop_handle', routeKey: 'exit_loop' },
]
}
if (sourceNodeType === 'condition') {
return [
{ handleId: 'true_handle', routeKey: 'true' },
{ handleId: 'false_handle', routeKey: 'false' },
]
}
return []
}ๆไปถไฝ็ฝฎ: frontend/app/workspace/[workspaceId]/[agentId]/types/graph.ts
/**
* ่พนๆฐๆฎ็ปๆ - ๅญๅจๅจ Edge.data ไธญ
* ไธๅ็ซฏ GraphEdge.data JSONB ๅญๆฎตๅฏนๅบ
*/
export interface EdgeData {
/** ่ทฏ็ฑ้ฎ๏ผๅน้
Executor ่ฟๅๅผ */
route_key?: string
/** React Flow Handle ID */
source_handle_id?: string
/** ่พน็ฑปๅ */
edge_type?: 'normal' | 'conditional' | 'loop_back'
/** ๆพ็คบๆ ็ญพ */
label?: string
/** ๆกไปถ่กจ่พพๅผ๏ผ่พน็บงๅซๆกไปถ๏ผ */
condition?: string
/** ่ทฏๅพๆงๅถ็น๏ผloop_back ่พน๏ผ */
waypoints?: Array<{ x: number; y: number }>
}
/**
* Router ่็น่ทฏ็ฑ่งๅ
*/
export interface RouteRule {
id: string
condition: string // Python ่กจ่พพๅผ
targetEdgeKey: string // ๅน้
่พน็ route_key
label: string // ๆพ็คบๅ็งฐ
priority?: number // ไผๅ
็บง๏ผๆฐๅญ่ถๅฐ่ถไผๅ
๏ผ
}sequenceDiagram
participant User as ็จๆท
participant Canvas as BuilderCanvas
participant Store as builderStore
participant API as REST API
participant DB as PostgreSQL
User->>Canvas: ็ผ่พ่็น/่พน
Canvas->>Store: updateNodeConfig / updateEdge
Store->>Store: triggerAutoSave (้ฒๆ 2s)
Store->>API: PUT /api/graphs/{id}/state
Note over API: ไฟๅญ nodes, edges, viewport
API->>DB: UPDATE agent_graphs
DB-->>API: OK
API-->>Store: 200 OK
Store->>Store: ๆดๆฐ lastSavedStateHash
sequenceDiagram
participant API as REST API
participant Builder as LanggraphModelBuilder
participant Base as BaseGraphBuilder
participant Exec as NodeExecutors
participant LG as LangGraph
API->>Builder: build(graph, nodes, edges)
Builder->>Builder: validate_graph_structure()
Builder->>Builder: _identify_loop_bodies()
Builder->>Builder: _identify_parallel_nodes()
loop ๆฏไธช่็น
Builder->>Base: _create_node_executor(node)
Base->>Exec: new XxxNodeExecutor(node, name)
Exec-->>Builder: executor
Builder->>LG: workflow.add_node(name, executor)
end
loop ๆกไปถ่็น
Builder->>Builder: _build_conditional_edges_for_xxx()
Builder->>LG: workflow.add_conditional_edges()
end
Builder->>LG: workflow.add_edge(START, first_node)
Builder->>LG: workflow.add_edge(last_node, END)
Builder->>LG: workflow.compile(checkpointer)
LG-->>Builder: CompiledStateGraph
Builder-->>API: compiled_graph
sequenceDiagram
participant Client as ๅ็ซฏ
participant API as REST API
participant Graph as CompiledGraph
participant Exec as NodeExecutor
participant State as GraphState
Client->>API: POST /api/chat (SSE)
API->>Graph: ainvoke({"messages": [...]})
loop ๆฏไธช่็น
Graph->>State: ่ฏปๅๅฝๅ็ถๆ
Graph->>Exec: __call__(state)
Exec->>Exec: ๆง่ก้ป่พ
Exec-->>State: ่ฟๅ็ถๆๆดๆฐ
State-->>Graph: ๅๅนถๆดๆฐ
Graph-->>API: SSE event (node_start/node_end)
API-->>Client: ๆตๅผๆจ้
end
Graph-->>API: ๆ็ป็ปๆ
API-->>Client: SSE event (end)
flowchart LR
subgraph Frontend["ๅ็ซฏ้
็ฝฎ"]
R1["Router ่็น้
็ฝฎ<br/>routes: [{condition, targetEdgeKey}]"]
E1["่พน 1 ้
็ฝฎ<br/>route_key: 'high'"]
E2["่พน 2 ้
็ฝฎ<br/>route_key: 'low'"]
end
subgraph Backend["ๅ็ซฏๅค็"]
B1["_build_conditional_edges_for_router()"]
B2["conditional_map = {<br/>'high': 'node_a',<br/>'low': 'node_b'}"]
B3["add_conditional_edges(<br/>router_name,<br/>executor,<br/>conditional_map)"]
end
subgraph Runtime["่ฟ่กๆถ"]
R2["RouterNodeExecutor.__call__()"]
R3["่ฟๅ route_key: 'high'"]
R4["LangGraph ้ๆฉ่พน<br/>โ node_a"]
end
R1 --> B1
E1 --> B1
E2 --> B1
B1 --> B2
B2 --> B3
B3 --> R2
R2 --> R3
R3 --> R4
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
model |
modelSelect | โ | ๆจ็ๆจกๅ |
systemPrompt |
textarea | ็ณป็ปๆไปค | |
tools |
toolSelector | ่ฟๆฅ็ๅทฅๅ ท | |
skills |
skillSelector | ่ฟๆฅ็ๆ่ฝ | |
useDeepAgents |
boolean | ๅฏ็จ DeepAgents ๆจกๅผ | |
description |
textarea | SubAgent ๆ่ฟฐ | |
enableMemory |
boolean | ๅฏ็จ้ฟๆ่ฎฐๅฟ | |
memoryModel |
modelSelect | ่ฎฐๅฟๅค็ๆจกๅ | |
memoryPrompt |
textarea | ่ฎฐๅฟๆดๆฐๆ็คบ |
ๅ็ซฏๆง่กๅจ: AgentNodeExecutor
่ฟๅ: { messages: [...], current_node: "..." }
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
expression |
conditionExpr | โ | Python ๆกไปถ่กจ่พพๅผ |
trueLabel |
text | True ๅๆฏๆ ็ญพ | |
falseLabel |
text | False ๅๆฏๆ ็ญพ |
ๅฏ็จๅ้: state, messages, context, current_node, loop_count
ๅ็ซฏๆง่กๅจ: ConditionNodeExecutor
่ฟๅ: { route_decision: "true" | "false", route_history: [...] }
่พน้ ็ฝฎ่ฆๆฑ:
- True ๅๆฏ:
route_key: "true" - False ๅๆฏ:
route_key: "false"
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
routes |
routeList | โ | ่ทฏ็ฑ่งๅๅ่กจ |
defaultRoute |
text | ้ป่ฎค่ทฏ็ฑ้ฎ |
่ทฏ็ฑ่งๅ็ปๆ:
interface RouteRule {
id: string
condition: string // "state.get('score', 0) > 80"
targetEdgeKey: string // "high_score"
label: string // "้ซๅ่ทฏ็ฑ"
priority?: number // 0 (ๆ้ซไผๅ
)
}ๅ็ซฏๆง่กๅจ: RouterNodeExecutor
่ฟๅ: route_key (ๅญ็ฌฆไธฒ)
่พน้
็ฝฎ่ฆๆฑ: ๆฏๆก่พน็ route_key ๅฟ
้กปๅน้
ๆไธช่งๅ็ targetEdgeKey
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
conditionType |
select | โ | forEach / while / doWhile |
listVariable |
text | forEach: ๅ่กจๅ้ๅ | |
condition |
conditionExpr | while/doWhile: ๆกไปถ่กจ่พพๅผ | |
maxIterations |
number | โ | ๆๅคง่ฟญไปฃๆฌกๆฐ๏ผๅฎๅ จ้ๅถ๏ผ |
ๅฏ็จๅ้: state, loop_count, loop_state, context
ๅ็ซฏๆง่กๅจ: LoopConditionNodeExecutor
่ฟๅ: "continue_loop" | "exit_loop"
่พน้ ็ฝฎ่ฆๆฑ:
- ็ปง็ปญๅพช็ฏ:
route_key: "continue_loop",edge_type: "loop_back" - ้ๅบๅพช็ฏ:
route_key: "exit_loop",edge_type: "conditional"
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
tool_name |
text | โ | ๅทฅๅ ทๅ็งฐ |
input_mapping |
kvList | ่พๅ ฅๅๆฐๆ ๅฐ |
input_mapping ็คบไพ:
{
"query": "state.get('context', {}).get('user_query')",
"limit": "10"
}ๅ็ซฏๆง่กๅจ: ToolNodeExecutor
่ฟๅ: { tool_output: [...], messages: [...] }
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
function_name |
select | ้ขๅฎไนๅฝๆฐๅ | |
function_code |
textarea | ่ชๅฎไน Python ไปฃ็ |
้ขๅฎไนๅฝๆฐ: math_add, math_multiply, string_concat, dict_get, dict_set
่ชๅฎไนไปฃ็ ็คบไพ:
# ไฝฟ็จ result ๅ้่ฟๅ็ปๆ
result = {
"output": state.get("value", 0) * 2,
"status": "success"
}ๅ็ซฏๆง่กๅจ: FunctionNodeExecutor (ๆฒ็ฎฑๆง่ก)
่ฟๅ: { function_results: [...] }
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
error_strategy |
select | โ | fail_fast / best_effort |
็ญ็ฅ่ฏดๆ:
fail_fast: ไปปไธๅคฑ่ดฅๅๆดไฝๅคฑ่ดฅbest_effort: ๆถ้ๆๆๆๅ็ปๆ๏ผ่ฎฐๅฝๅคฑ่ดฅ
ๅ็ซฏๆง่กๅจ: AggregatorNodeExecutor
่ฟๅ: { aggregated_results: { status, success_count, error_count, results, errors } }
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
prompt |
textarea | โ | Prompt ๆจกๆฟ |
model |
modelSelect | โ | LLM ๆจกๅ |
ๆจกๆฟๅ้: {{variable}} ไป context ๆฟๆข๏ผ{{state.field}} ไป state ๆฟๆข
ๅ็ซฏๆง่กๅจ: LLMNodeExecutor
่ฟๅ: { llm_output: [...], messages: [...] }
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
jsonpath_query |
text | JSONPath ่กจ่พพๅผ | |
json_schema |
textarea | JSON Schema ้ช่ฏ |
JSONPath ็คบไพ: $.data.items[*].name
ๅ็ซฏๆง่กๅจ: JSONParserNodeExecutor
่ฟๅ: { context: { json_output: ... } }
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
method |
select | โ | GET/POST/PUT/DELETE/PATCH |
url |
text | โ | URL ๆจกๆฟ |
headers |
kvList | HTTP ๅคด | |
auth |
select | none/bearer/basic | |
max_retries |
number | ๆๅคง้่ฏๆฌกๆฐ | |
timeout |
number | ่ถ ๆถ็งๆฐ |
URL ๆจกๆฟ: ๆฏๆ {{variable}} ๅ้ๆฟๆข
ๅ็ซฏๆง่กๅจ: HttpRequestNodeExecutor
่ฟๅ: { context: { http_response: { status, headers, data, success } } }
| ๅญๆฎต | ็ฑปๅ | ๅฟ ๅกซ | ่ฏดๆ |
|---|---|---|---|
template |
textarea | ๆถๆฏๆจกๆฟ |
ๆจกๆฟ: ๆฏๆ {{variable}} ไป context ๆฟๆข
ๅ็ซฏๆง่กๅจ: DirectReplyNodeExecutor
่ฟๅ: { messages: [...] }
interface EdgeData {
/**
* ่ทฏ็ฑ้ฎ - ๅน้
่็นๆง่กๅจ่ฟๅ็่ทฏ็ฑๅณ็ญ
*
* ็คบไพ:
* - Condition ่็น: "true" | "false"
* - Router ่็น: ่ชๅฎไน้ฎๅฆ "high_score", "default"
* - Loop ่็น: "continue_loop" | "exit_loop"
*/
route_key?: string
/**
* React Flow Handle ID
* ๅฏ้๏ผ็จไบ้ซ็บงๅบๆฏ็็ฒพ็กฎ Handle ็ปๅฎ
*/
source_handle_id?: string
/**
* ่พน็ฑปๅ
* - normal: ๆฎ้่ฟๆฅ๏ผ็ฐ่ฒ๏ผ
* - conditional: ๆกไปถ่พน๏ผ่่ฒ๏ผ
* - loop_back: ๅพช็ฏๅ่พน๏ผ็ดซ่ฒ่็บฟ๏ผ
*/
edge_type?: 'normal' | 'conditional' | 'loop_back'
/**
* ๆพ็คบๆ ็ญพ
*/
label?: string
/**
* ๆกไปถ่กจ่พพๅผ๏ผ่พน็บงๅซๆกไปถ๏ผ่พๅฐไฝฟ็จ๏ผ
*/
condition?: string
/**
* ่ทฏๅพๆงๅถ็น๏ผloop_back ่พนๅฏ่ฐๆด่ทฏๅพ๏ผ
*/
waypoints?: Array<{ x: number; y: number }>
}| edge_type | ้ข่ฒ | ๆ ทๅผ | ็จ้ |
|---|---|---|---|
normal |
#cbd5e1 (็ฐ) |
ๅฎ็บฟ 1.5px | ๆฎ้้กบๅบ่ฟๆฅ |
conditional |
#3b82f6 (่) |
ๅฎ็บฟ 2px | ๆกไปถๅๆฏ |
loop_back |
#9333ea (็ดซ) |
่็บฟ 2.5px | ๅพช็ฏๅ่พน |
// True ๅๆฏ
{ route_key: "true", edge_type: "conditional" }
// False ๅๆฏ
{ route_key: "false", edge_type: "conditional" }// ๅ่ฎพ routes = [
// { condition: "score > 80", targetEdgeKey: "high" },
// { condition: "score > 60", targetEdgeKey: "medium" },
// { condition: "True", targetEdgeKey: "low" }
// ]
// ่พน 1
{ route_key: "high", edge_type: "conditional" }
// ่พน 2
{ route_key: "medium", edge_type: "conditional" }
// ่พน 3
{ route_key: "low", edge_type: "conditional" }// ็ปง็ปญๅพช็ฏ๏ผๅๅฐๅพช็ฏไฝ๏ผ
{ route_key: "continue_loop", edge_type: "loop_back" }
// ้ๅบๅพช็ฏ
{ route_key: "exit_loop", edge_type: "conditional" }def _build_conditional_edges_for_router(self, workflow, router_node, router_node_name, router_executor):
conditional_map = {}
handle_to_route_map = {}
for edge in self.edges:
if edge.source_node_id == router_node.id:
edge_data = edge.data or {}
source_handle_id = edge_data.get("source_handle_id")
route_key = edge_data.get("route_key", "default")
# ๆๅปบ Handle ID ๅฐ route_key ็ๆ ๅฐ
if source_handle_id:
handle_to_route_map[source_handle_id] = route_key
# ๆๅปบ route_key ๅฐ็ฎๆ ่็น็ๆ ๅฐ
target_name = self._node_id_to_name.get(edge.target_node_id)
if target_name:
conditional_map[route_key] = target_name
# ่ฎพ็ฝฎ Handle ๆ ๅฐๅฐๆง่กๅจ
router_executor.set_handle_to_route_map(handle_to_route_map)
# ๆทปๅ ๆกไปถ่พน
workflow.add_conditional_edges(
router_node_name,
router_executor, # ่ฟๅ route_key
conditional_map, # route_key -> target_node_name
)| ๅ้ | ่ทฏๅพ | ็ฑปๅ | ่ฏดๆ |
|---|---|---|---|
current_node |
state.current_node |
string | ๅฝๅ่็น ID |
route_decision |
state.route_decision |
string | ๆๆฐ่ทฏ็ฑๅณ็ญ |
loop_count |
state.loop_count |
number | ๅ จๅฑๅพช็ฏ่ฎกๆฐ |
messages |
state.messages |
array | ๆถๆฏๅ่กจ |
context |
state.context |
object | ๅ จๅฑไธไธๆ |
loop_states = {
"loop_node_id_1": {
"loop_count": 3,
"current_index": 2, # forEach ๆจกๅผ
"data": {...},
"items": [...] # forEach ๅ่กจ
}
}
# ่ฎฟ้ฎๆนๅผ
loop_states.get('loop_node_id').get('loop_count', 0)task_states = {
"task_node_id_1": {
"status": "success",
"result": {...},
"error_msg": None,
"task_id": "task_123"
}
}
# ่ฎฟ้ฎๆนๅผ
task_states.get('task_node_id').get('result')| ๅญๆฎต | ็จ้ | ็ปๆ |
|---|---|---|
task_results |
Aggregator ่็น่ๅ | ๆ ๅๅ็ปๆ๏ผๅ ๅซ status/result/error_msg/task_id |
parallel_results |
้็จๅนถ่ก็ปๆ | ไปปๆ็ฑปๅๅ่กจ๏ผ็จไบๆชๆฅๆฉๅฑ |
task_results ๆ ๅ็ปๆ:
task_results = [
{
"status": "success", # "success" | "error"
"result": {...}, # ไปปๅก็ปๆ
"error_msg": None, # ้่ฏฏไฟกๆฏ๏ผๅฆๆๆ๏ผ
"task_id": "node_id" # ไปปๅก่็น ID
},
...
]
# Aggregator ่็นไฝฟ็จ
errors = [r for r in task_results if r.get("status") == "error"]
successes = [r for r in task_results if r.get("status") == "success"]่ชๅจๅกซๅ
: NodeExecutionWrapper ๅจๅนถ่ก่็นๆง่กๅฎๆๅ่ชๅจๅกซๅ
task_results
ๆๆๆกไปถ่็น๏ผRouterใConditionใLoop๏ผๅจ่ฏไผฐ่กจ่พพๅผๆถ๏ผ้ฝไผๅๅปบไธไธชๅฎๅ จ็่ฏไผฐไธไธๆ๏ผ
# ๅ็ซฏ NodeExecutors ไธญ็่ฏไผฐไธไธๆ
eval_context = {
"state": dict(state), # ๅฎๆด็ถๆๅญๅ
ธ
"context": state.get("context", {}), # ็จๆทๅฎไน็ไธไธๆๅ้
"messages": state.get("messages", []), # ๆถๆฏๅ่กจ
"current_node": state.get("current_node"),
"loop_count": state.get("loop_count", 0),
"route_decision": state.get("route_decision"),
# ๅพช็ฏ่็น้ขๅคๅ้
"loop_state": loop_states.get(self.node_id, {}), # ๅฝๅๅพช็ฏ็ถๆ
}
# ่กจ่พพๅผๅจๆฒ็ฎฑไธญๆง่ก๏ผๆ __builtins__๏ผ
result = eval(expression, {"__builtins__": {}}, eval_context)# Router ๆกไปถ - ่ฎฟ้ฎ context ๅ้
state.get('context', {}).get('score', 0) > 80
context.get('user_type') == 'vip'
# Condition ่กจ่พพๅผ - ๆฃๆฅๆถๆฏๆฐ้
len(state.get('messages', [])) > 5
len(messages) > 5 # ็ฎๅๅฝขๅผ
# Loop ๆกไปถ - ๅคๅๆกไปถ
loop_count < 3 and state.get('context', {}).get('has_error') == False
loop_count < 3 and context.get('has_error', False) == False
# forEach ๆกไปถ - ๆฃๆฅ่ฟญไปฃ่ฟๅบฆ
loop_states.get('loop_condition_node_id', {}).get('current_index', 0) < len(loop_states.get('loop_condition_node_id', {}).get('items', []))
# ่ฎฟ้ฎๆๅไธๆกๆถๆฏๅ
ๅฎน
messages[-1].content if messages else ''
# ๆฃๆฅ่ทฏ็ฑๅๅฒ
'high' in state.get('route_history', [])- ่กจ่พพๅผๅจๆฒ็ฎฑ็ฏๅขไธญๆง่ก๏ผ
__builtins__่ขซ็ฆ็จ - ๅช่ฝ่ฎฟ้ฎ่ฏไผฐไธไธๆไธญๆไพ็ๅ้
- ไธๆฏๆๅฏผๅ ฅๆจกๅๆๆง่กไปปๆไปฃ็
- ๅคๆ้ป่พๅบไฝฟ็จ Function ่็น๏ผๆฏๆๅฎๆด Python ๆฒ็ฎฑ๏ผ
# Direct Reply / LLM ่็นๆจกๆฟ
"Hello {{user_name}}, your score is {{score}}"
# ๅฏนๅบ context
context = {
"user_name": "Alice",
"score": 95
}
# ไนๆฏๆ state ่ฎฟ้ฎ
"Current node: {{state.current_node}}" โโโโ [Yes] โโ Agent A
User Input โ Condition
โโโโ [No] โโโ Agent B
้ ็ฝฎ่ฆ็น:
- Condition ่็น่ฎพ็ฝฎ
expression - True ๅๆฏ่พน่ฎพ็ฝฎ
route_key: "true" - False ๅๆฏ่พน่ฎพ็ฝฎ
route_key: "false"
โโโโ [High] โโโโโ Premium Agent
โ
Router โโโโโโโผโโโ [Medium] โโโ Standard Agent
โ
โโโโ [Default] โโ Basic Agent
้ ็ฝฎ่ฆ็น:
- Router ่็น้
็ฝฎ
routesๆฐ็ป๏ผๆไผๅ ็บงๆๅบ๏ผ - ๆฏๆก่พน็
route_keyๅน้ ๅฏนๅบ่งๅ็targetEdgeKey - ็กฎไฟๆ้ป่ฎค่ทฏ็ฑๅค็ๆชๅน้ ๆ ๅต
โโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โ
Start โ Loop Condition โ[continue]โโ Process โโ
โ
โโ[exit]โโ End
้ ็ฝฎ่ฆ็น:
- Loop Condition ่ฎพ็ฝฎ
conditionTypeใconditionใmaxIterations - continue ่พน:
route_key: "continue_loop",edge_type: "loop_back" - exit ่พน:
route_key: "exit_loop",edge_type: "conditional"
โโโ Task A โโ
โ โ
Start โโโโผโโ Task B โโผโโ Aggregator โ End
โ โ
โโโ Task C โโ
้ ็ฝฎ่ฆ็น:
- Fan-Out: ไธไธช่็น่ฟๆฅๅคไธช็ฎๆ ๏ผๆฎ้่พน๏ผ
- Aggregator ่็น่ฎพ็ฝฎ
error_strategy - ไปปๅก็ปๆ้่ฟ
task_results็ถๆไผ ้
โโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โ
Start โ API Call โ Check Error โ[retry]โโโ
โ
โโ[success]โโ End
้ ็ฝฎ่ฆ็น:
- ไฝฟ็จ Loop Condition +
doWhileๆจกๅผ - ๆกไปถ:
loop_count < 3 and state.get('context', {}).get('has_error', False) - ๅจๅพช็ฏไฝๅ
ๆดๆฐ
has_error็ถๆ
BaseGraphBuilder ๆไพ็ผ่ฏๆถ้ช่ฏ๏ผ
# ้ช่ฏๅพ็ปๆ
errors = builder.validate_graph_structure()
# ๆฃๆฅ: ๅญค็ซ่็นใRouter ๆ ๅบ่พนใLoop ็ผบๅฐ่พน
# ้ช่ฏ Handle ID ๆ ๅฐ
mapping_errors = builder.validate_handle_to_route_mapping()
# ๆฃๆฅ: Handle ID ๅฐ route_key ็ไธ่ดๆง| ้่ฏฏ | ๅๅ | ่งฃๅณๆนๆก |
|---|---|---|
| Router ๆ ่พๅบ | ๆฒกๆ้ ็ฝฎ routes | ๆทปๅ ่ทฏ็ฑ่งๅ |
| ่พน็ผบๅฐ route_key | ๆกไปถ่พนๆช้ ็ฝฎ | ๅจ EdgePropertiesPanel ่ฎพ็ฝฎ |
| Loop ๆญปๅพช็ฏ | maxIterations ๅคชๅคงๆๆกไปถๆฐธ็ | ๆฃๆฅๆกไปถ่กจ่พพๅผๅ้ๅถ |
| ่กจ่พพๅผๆฑๅผๅคฑ่ดฅ | ๅ้ๆชๅฎไนๆ่ฏญๆณ้่ฏฏ | ไฝฟ็จ context ้ขๅฎไนๅ้ |
ๅ็ซฏไฝฟ็จ loguru ่พๅบ่ฏฆ็ปๆฅๅฟ๏ผ
# ่็นๆง่กๆฅๅฟ
[AgentNodeExecutor] >>> Executing node 'agent_1' | input_messages_count=3
[AgentNodeExecutor] <<< Node 'agent_1' completed | elapsed=1234.56ms | new_messages=1
# Router ๅณ็ญๆฅๅฟ
[RouterNodeExecutor] >>> Evaluating router node 'router_1' | rules_count=3
[RouterNodeExecutor] <<< Route selected | route_key=high | elapsed=5.23ms
# Loop ็ถๆๆฅๅฟ
[LoopConditionNodeExecutor] >>> Evaluating loop | loop_count=2 | maxIterations=5
[LoopConditionNodeExecutor] <<< Loop condition evaluated | route=continue_loopedgeValidator.ts ๆไพๅฎๆถ่พน้
็ฝฎ้ช่ฏ๏ผ
const errors = validateEdgeData(edge, sourceNode, targetNode)
// ่ฟๅ: ValidationError[] = [{ field, message, severity }]ๆไปถไฝ็ฝฎ: backend/app/core/graph/graph_builder.py
GraphBuilder ไฝไธบๅทฅๅ็ฑป๏ผ่ชๅจๆฃๆตๅพ้
็ฝฎๅนถ้ๆฉๅ้็ๆๅปบๅจ๏ผ
- ๆฃๆตๆบๅถ๏ผๆซๆๆๆ่็น๏ผๆฅๆพ
config.useDeepAgents === true - ้ๆฉ้ป่พ๏ผๅฆๆๆฃๆตๅฐ DeepAgents ่็น๏ผไฝฟ็จ
DeepAgentsGraphBuilder๏ผๅฆๅไฝฟ็จLanggraphModelBuilder - ้ๆๅๆข๏ผ็จๆทๆ ้ๅ ณๅฟๅบๅฑๅฎ็ฐ๏ผ็ณป็ป่ชๅจ้ๆฉ
# ่ชๅจ้ๆฉๆๅปบๅจ
builder = GraphBuilder(graph, nodes, edges, ...)
compiled_graph = await builder.build() # ๅ
้จ่ชๅจ้ๆฉๅฎ็ฐๆไปถไฝ็ฝฎ: backend/app/core/graph/deep_agents_builder.py
DeepAgentsGraphBuilder ๅฎ็ฐๆๅๆๆ็ปๆ๏ผไธไธช Manager๏ผๆ น่็น๏ผ็ฎก็ๅคไธช Worker๏ผๅญ่็น๏ผใ
class DeepAgentsGraphBuilder(BaseGraphBuilder):
"""Two-level star structure: Root (DeepAgent) โ Children (CompiledSubAgent)."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._backend_manager = DeepAgentsBackendManager(self.nodes)
self._skills_manager = DeepAgentsSkillsManager(self.user_id)
self._node_builder = DeepAgentsNodeBuilder(builder=self)flowchart TB
Manager[Manager Agent<br/>ๆ น่็น DeepAgent]
Manager -->|task| Worker1[Worker 1<br/>CompiledSubAgent]
Manager -->|task| Worker2[Worker 2<br/>CompiledSubAgent]
Manager -->|task| Worker3[Worker 3<br/>CompiledSubAgent]
Manager -->|task| CodeAgent[CodeAgent<br/>CompiledSubAgent]
style Manager fill:#e1f5ff
style Worker1 fill:#fff4e1
style Worker2 fill:#fff4e1
style Worker3 fill:#fff4e1
style CodeAgent fill:#fff4e1
็น็น๏ผ
- Manager ๆฏๅฏไธ็ๆ น่็น๏ผDeepAgent๏ผ
- ๆๆ Worker ้ฝๆฏ Manager ็็ดๆฅๅญ่็น๏ผCompiledSubAgent๏ผ
- Manager ้่ฟ
task()ๅทฅๅ ทๅงๆไปปๅก็ป Worker - Worker ไน้ดไธ็ดๆฅ้ไฟก๏ผ้่ฟ Manager ๅ่ฐ
flowchart TD
Start[ๅผๅงๆๅปบ] --> Setup[่ฎพ็ฝฎๅ
ฑไบซๅ็ซฏ]
Setup --> Select[้ๆฉๅนถ้ช่ฏๆ น่็น]
Select --> Check{ๆฏๅฆๆๅญ่็น?}
Check -->|ๆ | Standalone[ๆๅปบ็ฌ็ซ DeepAgent]
Check -->|ๆ| BuildWorkers[ๆๅปบๆๆ Worker ่็น]
BuildWorkers --> BuildManager[ๆๅปบ Manager ่็น<br/>ๅ
ๅซๆๆ Worker]
BuildManager --> Finalize[ๆ็ปๅ Agent]
Standalone --> Finalize
Finalize --> End[่ฟๅ CompiledStateGraph]
ๆไปถไฝ็ฝฎ: backend/app/core/graph/deep_agents/backend_manager.py
่่ดฃ๏ผ็ฎก็ๅ ฑไบซ Docker ๅ็ซฏ๏ผ็จไบๆ่ฝๅ CodeAgent ๆง่กใ
ๆ ธๅฟๅ่ฝ๏ผ
- ๆฃๆตๆฏๅฆ้่ฆๅ ฑไบซๅ็ซฏ๏ผๆๆ่ฝ้ ็ฝฎๆ CodeAgent ไฝฟ็จ Docker๏ผ
- ๅๅปบๅ็ฎก็ๅ ฑไบซ Docker ๅฎนๅจ
- ไธบ่็นๆไพๅ็ซฏๅฎไพ๏ผๅ ฑไบซๆ่็นไธ็จ๏ผ
- ๆธ ็่ตๆบ
class DeepAgentsBackendManager:
"""Manages shared Docker backend for DeepAgents graph."""
async def create_shared_backend(self) -> PydanticSandboxAdapter:
"""ๅๅปบๅ
ฑไบซ Docker ๅ็ซฏ"""
async def get_backend_for_node(
self, node: GraphNode, has_skills: bool,
create_backend_for_node: callable
) -> Optional[Any]:
"""่ทๅ่็น็ๅ็ซฏ๏ผไผๅ
ๅ
ฑไบซๅ็ซฏ๏ผ"""ๆไปถไฝ็ฝฎ: backend/app/core/graph/deep_agents/skills_manager.py
่่ดฃ๏ผ็ฎก็ๆ่ฝ็้ขๅ ่ฝฝๅ่ทฏๅพ้ ็ฝฎใ
ๆ ธๅฟๅ่ฝ๏ผ
- ้ช่ฏๆ่ฝ้ ็ฝฎ
- ้ขๅ ่ฝฝๆ่ฝๅฐๅ็ซฏๆฒ็ฎฑ
- ๆไพๆ่ฝ่ทฏๅพๅ่กจ
- ่ฏๆญๆ่ฝๅ ่ฝฝ็ถๆ
class DeepAgentsSkillsManager:
"""Manages skills for DeepAgents graph."""
async def preload_skills_to_backend(
self, node: GraphNode, backend: Any
) -> None:
"""้ขๅ ่ฝฝๆ่ฝๅฐๅ็ซฏๆฒ็ฎฑ"""
@staticmethod
def get_skills_paths(
has_skills: bool, backend: Optional[Any]
) -> Optional[list[str]]:
"""่ทๅๆ่ฝ่ทฏๅพๅ่กจ"""ๆไปถไฝ็ฝฎ: backend/app/core/graph/deep_agents/node_factory.py
่่ดฃ๏ผๆๅปบๅ็ง็ฑปๅ็่็น๏ผRootใManagerใWorkerใCodeAgent๏ผใ
ๆ ธๅฟๅ่ฝ๏ผ
- ๆๅปบๆ น่็น๏ผRoot DeepAgent๏ผ
- ๆๅปบ็ฎก็ๅจ่็น๏ผManager DeepAgent with SubAgents๏ผ
- ๆๅปบๅทฅไฝ่็น๏ผWorker CompiledSubAgent๏ผ
- ๆๅปบ CodeAgent ่็น๏ผCodeAgent CompiledSubAgent๏ผ
class DeepAgentsNodeBuilder:
"""Builds nodes for DeepAgents graph."""
async def build_root_node(
self, node: GraphNode, node_name: str
) -> Any:
"""ๆๅปบๆ น่็น๏ผ็ฌ็ซ DeepAgent๏ผ"""
async def build_manager_node(
self, node: GraphNode, node_name: str,
subagents: list[Any], is_root: bool = False
) -> Any:
"""ๆๅปบ็ฎก็ๅจ่็น๏ผๅธฆ SubAgents ็ DeepAgent๏ผ"""
async def build_worker_node(self, node: GraphNode) -> Any:
"""ๆๅปบๅทฅไฝ่็น๏ผCompiledSubAgent๏ผ"""
async def build_code_agent_node(self, node: GraphNode) -> Any:
"""ๆๅปบ CodeAgent ่็น๏ผCompiledSubAgent๏ผ"""ๆไปถไฝ็ฝฎ: backend/app/core/graph/deep_agents/node_config.py
่่ดฃ๏ผ็ปไธ่งฃๆ่็น้ ็ฝฎ๏ผ็ๆๆ ๅๅ็ Agent ้ ็ฝฎๅฏน่ฑกใ
ๆ ธๅฟๅ่ฝ๏ผ
- ่งฃๆ่็นๆฐๆฎ๏ผtoolsใskillsใbackendใmiddleware ็ญ๏ผ
- ็ปไธ้ ็ฝฎๆ ผๅผ๏ผManager ๅ SubAgent ๅ ฑ็จ๏ผ
- ๆฏๆ CodeAgent ๆฉๅฑ้ ็ฝฎ
@dataclass
class AgentConfig:
"""Unified agent configuration - works for both Manager and SubAgent."""
name: str
label: str
node_type: str
description: Optional[str]
system_prompt: Optional[str]
model: Any
tools: list[Any]
middleware: list[Any]
skills: Optional[list[str]]
backend: Optional[Any]
@classmethod
async def from_node(
cls, node: GraphNode, builder: Any,
node_id_to_name: dict
) -> "AgentConfig":
"""ไป่็น่งฃๆ้
็ฝฎ"""
@dataclass
class CodeAgentConfig(AgentConfig):
"""Extended config for CodeAgent nodes."""
agent_mode: str = "autonomous"
executor_type: str = "local"
enable_data_analysis: bool = True
# ... ๅ
ถไป CodeAgent ็นๅฎ้
็ฝฎSubAgent ็ runnable ๆฅๆถ่พๅ
ฅ๏ผๆฏๆๅค็งๆ ผๅผ๏ผ
# ๆฏๆๆ ผๅผ 1: ็ดๆฅไปปๅกๅญ็ฌฆไธฒ
{"task": "ๆง่กไปปๅก"}
# ๆฏๆๆ ผๅผ 2: LangChain ๆถๆฏๅฏน่ฑก
{"messages": [HumanMessage(content="ไปปๅกๅ
ๅฎน")]}
# ๆฏๆๆ ผๅผ 3: ๅญๅ
ธๆ ผๅผ๏ผfallback๏ผ
{"messages": [{"role": "user", "content": "ไปปๅกๅ
ๅฎน"}]}ๅฎ็ฐ๏ผnode_factory.py๏ผ๏ผ
async def code_agent_invoke(inputs: dict) -> dict:
# ๆๅไปปๅก - ๆฏๆๅค็งๆ ผๅผ
task = inputs.get("task")
if not task:
messages = inputs.get("messages", [])
if messages:
last_msg = messages[-1]
if hasattr(last_msg, "content"): # BaseMessage ๅฏน่ฑก
task = last_msg.content
elif isinstance(last_msg, dict): # ๅญๅ
ธๆ ผๅผ
task = last_msg.get("content", "")SubAgent ๅฟ
้กป่ฟๅ AIMessage ๅฏน่ฑก๏ผ็ฌฆๅ DeepAgents ๆกๆถ่ฆๆฑ๏ผ
# ๆญฃ็กฎๆ ผๅผ
return {
"messages": [AIMessage(content="ๆง่ก็ปๆ")],
"result": result,
}
# DeepAgents ๆกๆถไผ่ฎฟ้ฎ result["messages"][-1].textsequenceDiagram
participant User as ็จๆท
participant Manager as Manager Agent
participant SubAgent as SubAgent
User->>Manager: ่พๅ
ฅไปปๅก
Manager->>Manager: ๅๆไปปๅก
Manager->>SubAgent: task("ๅ
ทไฝๅญไปปๅก")
SubAgent->>SubAgent: ๆง่กไปปๅก
SubAgent-->>Manager: ่ฟๅ็ปๆ (AIMessage)
Manager->>Manager: ๆดๅ็ปๆ
Manager-->>User: ่ฟๅๆ็ป็ปๆ
้ๅไฝฟ็จ DeepAgents ็ๅบๆฏ๏ผ
- ้่ฆๅคไธชไธไธ Agent ๅไฝๅฎๆๅคๆไปปๅก
- ไปปๅกๅฏไปฅๅ่งฃไธบ็ฌ็ซ็ๅญไปปๅก
- ้่ฆ Manager ๅ่ฐๅๅณ็ญ
- ้่ฆๆ่ฝๅ ฑไบซๅไปฃ็ ๆง่ก่ฝๅ
ไธ้ๅไฝฟ็จ DeepAgents ็ๅบๆฏ๏ผ
- ็ฎๅ็็บฟๆงๅทฅไฝๆต
- ๅไธ Agent ๅฏไปฅๅฎๆ็ไปปๅก
- ไธ้่ฆ Agent ้ดๅไฝ็ๅบๆฏ
Manager ่็น้ ็ฝฎ๏ผ
{
"type": "agent",
"config": {
"useDeepAgents": true,
"systemPrompt": "ไฝ ๆฏๅข้ๅ่ฐ่
๏ผ่ด่ดฃๅ้
ไปปๅก็ปไปฅไธไธๅฎถ๏ผ\n- ไธๅฎถA๏ผ่ด่ดฃ...\n- ไธๅฎถB๏ผ่ด่ดฃ...",
"description": "ๅข้ๅ่ฐ่
๏ผ็ฎก็ๅคไธชไธไธ Agent"
}
}Worker ่็น้ ็ฝฎ๏ผ
{
"type": "agent",
"config": {
"useDeepAgents": false,
"description": "ไธไธๅค็ X ไปปๅก็ไธๅฎถ",
"systemPrompt": "ไฝ ๆฏ X ้ขๅ็ไธๅฎถ๏ผไธๆณจไบ..."
}
}CodeAgent ่็น้ ็ฝฎ๏ผ
{
"type": "code_agent",
"config": {
"useDeepAgents": false,
"description": "ๆง่ก Python ไปฃ็ ็ไธๅฎถ",
"agent_mode": "autonomous",
"executor_type": "auto"
}
}- ๅ ฑไบซๅ็ซฏ๏ผๅคไธช่็นไฝฟ็จๆ่ฝๆถ๏ผ็ณป็ป่ชๅจๅๅปบๅ ฑไบซ Docker ๅ็ซฏ๏ผ้ฟๅ ้ๅคๅๅปบ
- ๆ่ฝ้ขๅ ่ฝฝ๏ผๅจๆๅปบๆถ้ขๅ ่ฝฝๆ่ฝๅฐๅ็ซฏ๏ผ่ฟ่กๆถ็ดๆฅ่ฏปๅๆไปถ
- ่็นๅค็จ๏ผManager ๅฏไปฅๅค็จ Worker ๅค็ๅคไธชไปปๅก
- ่ตๆบๆธ ็๏ผๅพๆง่กๅฎๆๅ่ชๅจๆธ ็ๅ ฑไบซๅ็ซฏ่ตๆบ
Q: ไธบไปไน Worker ่็นไธ้่ฆ่ฎพ็ฝฎ useDeepAgents: true๏ผ
A: ๅชๆ Manager ่็น้่ฆ่ฎพ็ฝฎไธบ trueใWorker ่็นไฝไธบ SubAgent๏ผ็ฑ Manager ็ฎก็๏ผไธ้่ฆๅ็ฌๅฏ็จใ
Q: ๅฆไฝ็กฎไฟ Manager ่ฝๆญฃ็กฎ่ฐ็จ Worker๏ผ
A: Manager ็ systemPrompt ไธญๅฟ
้กปๅๅบๆๆ Worker ็ description๏ผ่ฟๆ ท Manager ๆ่ฝ็ฅ้ๅฆไฝ้ๆฉๅ้็ Workerใ
Q: CodeAgent ไฝไธบ SubAgent ๆถ้่ฆๆณจๆไปไน๏ผ
A: CodeAgent ็ runnable ๅฟ
้กป่ฟๅ AIMessage ๅฏน่ฑก๏ผ่ไธๆฏๅญๅ
ธใ็ณป็ปๅทฒ่ชๅจๅค็ๆญคๆ ผๅผ่ฝฌๆขใ
| ๆไปถ | ่ทฏๅพ |
|---|---|
| GraphBuilder | backend/app/core/graph/graph_builder.py |
| LanggraphModelBuilder | backend/app/core/graph/langgraph_model_builder.py |
| DeepAgentsGraphBuilder | backend/app/core/graph/deep_agents_builder.py |
| BaseGraphBuilder | backend/app/core/graph/base_builder.py |
| NodeExecutors | backend/app/core/graph/node_executors.py |
| GraphState | backend/app/core/graph/graph_state.py |
| NodeConfigValidator | backend/app/core/graph/node_config_validator.py |
| NodeConfigHelper | backend/app/core/graph/node_config_helper.py |
| DeepAgentsBackendManager | backend/app/core/graph/deep_agents/backend_manager.py |
| DeepAgentsSkillsManager | backend/app/core/graph/deep_agents/skills_manager.py |
| DeepAgentsNodeBuilder | backend/app/core/graph/deep_agents/node_factory.py |
| AgentConfig | backend/app/core/graph/deep_agents/node_config.py |
| BackendFactory | backend/app/core/graph/deep_agents/backend_factory.py |
| ๆไปถ | ่ทฏๅพ |
|---|---|
| nodeRegistry | frontend/app/workspace/[workspaceId]/[agentId]/services/nodeRegistry.tsx |
| builderStore | frontend/app/workspace/[workspaceId]/[agentId]/stores/builderStore.ts |
| EdgePropertiesPanel | frontend/app/workspace/[workspaceId]/[agentId]/components/EdgePropertiesPanel.tsx |
| graph.ts Types | frontend/app/workspace/[workspaceId]/[agentId]/types/graph.ts |
| edgeValidator | frontend/app/workspace/[workspaceId]/[agentId]/services/edgeValidator.ts |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๅ็ซฏ (React Flow) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ GraphSettingsPanel โ โ ConditionExprField โ โ
โ โ (ๅพๅ
จๅฑ่ฎพ็ฝฎ) โ โ (ๆกไปถ่กจ่พพๅผ็ผ่พๅจ) โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Context Variables โ โ โ โ Variables hints include: โ โ โ
โ โ โ - retry_count: 3 โ โโโโโถโ โ - state, messages, context โ โ โ
โ โ โ - user_type: "vip" โ โ โ โ - context.get('retry_count') โ โ โ
โ โ โ - max_items: 10 โ โ โ โ - context.get('user_type') โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ builderStore โ โ
โ โ graphVariables: { โ โ
โ โ viewport: { x: 0, y: 0, zoom: 1 }, โ โ
โ โ context: { retry_count: 3, user_type: "vip", max_items: 10 } โ โ
โ โ } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โ autoSave() / saveGraph() โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ agentService โ โ
โ โ saveGraphState({ nodes, edges, viewport, variables }) โ โ
โ โ loadGraphState() โ { nodes, edges, viewport, variables } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ HTTP API
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๅ็ซฏ (FastAPI + LangGraph) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ graphs.py API โ โ
โ โ GraphStatePayload.variables โ graph_service.save_graph_state() โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ graph_service.py โ โ
โ โ save_graph_state(): graph.variables["context"] = variables โ โ
โ โ load_graph_state(): return { variables: graph.variables } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ AgentGraph Model โ โ
โ โ variables: JSONB { โ โ
โ โ "viewport": {...}, โ โ
โ โ "context": {"retry_count": 3, "user_type": "vip", ...} โ โ
โ โ } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โ (่ฟ่กๆถ) โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ chat.py โ โ
โ โ get_user_config(graph_id) โ initial_context โ โ
โ โ graph.ainvoke({ โ โ
โ โ "messages": [...], โ โ
โ โ "context": initial_context โโโ ไป graph.variables.context โ โ
โ โ }) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ GraphState โ โ
โ โ messages: List[BaseMessage] โ โ
โ โ current_node: Optional[str] โ โ
โ โ context: Dict[str, Any] โ ็จๆทๅฎไน็ๅ้็ฐๅจๆๅผไบ๏ผ โ โ
โ โ loop_count: int โ โ
โ โ route_decision: str โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Node Executors โ โ
โ โ RouterNodeExecutor: โ โ
โ โ eval_context = { โ โ
โ โ "state": dict(state), โ โ
โ โ "context": state.get("context", {}), โ
ๆๅผ โ โ
โ โ "loop_count": ..., โ โ
โ โ } โ โ
โ โ eval("context.get('retry_count', 0) < 3") โ
ๆญฃๅธธๅทฅไฝ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1. ็จๆทๅจ GraphSettingsPanel ้
็ฝฎๅ้
โโโถ builderStore.updateGraphContext('retry_count', 3)
2. ่ชๅจไฟๅญ่งฆๅ
โโโถ agentService.saveGraphState({ variables: { context: {...} } })
3. ๅ็ซฏไฟๅญ
โโโถ graph_service.save_graph_state() โ graph.variables = {...}
4. ็จๆท่ฟ่กๅพ
โโโถ chat.py: get_user_config(graph_id) โ initial_context
โโโถ graph.ainvoke({ "context": initial_context })
5. ๆกไปถ่็นๆง่ก
โโโถ RouterNodeExecutor: eval("context.get('retry_count') < 3") โ True โ
| ๆฃๆฅ็น | ็ถๆ | ่ฏดๆ |
|---|---|---|
| ๅ็ซฏ้ ็ฝฎไฟๅญ | โ | GraphSettingsPanel โ builderStore โ agentService โ API |
| ๅ็ซฏๆไน ๅ | โ | API โ graph_service โ AgentGraph.variables |
| ่ฟ่กๆถๅ ่ฝฝ | โ | chat.py ไป graph.variables.context ๅๅงๅ GraphState.context |
| ่กจ่พพๅผๆฑๅผ | โ | RouterNodeExecutor.eval_context ๅ ๅซ context |
| ๅ้ๆ็คบ | โ | ConditionExprField ๅจๆๆพ็คบ็จๆทๅฎไน็ context ๅ้ |
| ๅๆปๆฏๆ | โ | DeploymentHistoryPanel ๅ ่ฝฝๆถๅๆญฅ graphVariables |
| ็ๆฌ | ๆฅๆ | ่ฏดๆ |
|---|---|---|
| 1.0 | 2026-01-02 | ๅๅง็ๆฌ๏ผๆดๅๆๆ่ฟ็จ่ฎพ่ฎกๆๆกฃ |
| 1.1 | 2026-01-02 | ๅฎๅๅๅ็ซฏ State ๅๅๆถๆ่ฏดๆ |
| 1.2 | 2026-01-22 | ๆทปๅ GraphBuilder ๅทฅๅๆจกๅผๅ DeepAgents ๆถๆๅฎๆดๆๆกฃ |