Skip to content

Latest commit

ย 

History

History
1854 lines (1458 loc) ยท 62.2 KB

File metadata and controls

1854 lines (1458 loc) ยท 62.2 KB

Graph Builder ็ณป็ปŸๆžถๆž„ๆ–‡ๆกฃ

ๆœฌๆ–‡ๆกฃๆ•ดๅˆไบ†ๅŽ็ซฏๅ›พๆž„ๅปบ็ณป็ปŸ็š„่ƒฝๅŠ›ๅฎž็ŽฐไธŽๅ‰็ซฏๅฎž็Žฐ่ฟ‡็จ‹๏ผŒๆไพ›็ณป็ปŸๆ€ง็š„ๆžถๆž„่ฏดๆ˜Žใ€‚ๅŒ…ๆ‹ฌๆ ‡ๅ‡† LangGraph ๆž„ๅปบๅ™จๅ’Œ DeepAgents ๆž„ๅปบๅ™จไธค็งๆจกๅผใ€‚


็›ฎๅฝ•

  1. ็ณป็ปŸๆžถๆž„ๆฆ‚่งˆ
  2. ๅŽ็ซฏๆ ธๅฟƒๅฎž็Žฐ
  3. ๅ‰็ซฏๆ ธๅฟƒๅฎž็Žฐ
  4. ๆ•ฐๆฎๆตไธŽไบคไบ’
  5. ่Š‚็‚น็ฑปๅž‹ๅฎŒๆ•ดๅ‚่€ƒ
  6. ่พน้…็ฝฎ่ง„่Œƒ
  7. ็Šถๆ€ๅ˜้‡ๆŒ‡ๅ—
  8. ๆœ€ไฝณๅฎž่ทตไธŽๆจกๆฟ
  9. ้”™่ฏฏๅค„็†ไธŽ่ฐƒ่ฏ•
  10. DeepAgents ๆžถๆž„

1. ็ณป็ปŸๆžถๆž„ๆฆ‚่งˆ

1.1 ๆ•ดไฝ“ๆžถๆž„ๅ›พ

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
Loading

1.2 ๆž„ๅปบๅ™จ้€‰ๆ‹ฉๆœบๅˆถ

็ณป็ปŸๆ”ฏๆŒไธค็งๅ›พๆž„ๅปบๆจกๅผ๏ผŒ้€š่ฟ‡ 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(...)

1.3 ๆ ธๅฟƒ่ƒฝๅŠ›็Ÿฉ้˜ต

่ƒฝๅŠ› ๅŽ็ซฏๅฎž็Žฐ ๅ‰็ซฏๅฎž็Žฐ ่ฏดๆ˜Ž
ๆกไปถ่ทฏ็”ฑ 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 ๆ˜Ÿๅž‹ๆ‹“ๆ‰‘

2. ๅŽ็ซฏๆ ธๅฟƒๅฎž็Žฐ

2.1 GraphBuilder ๅทฅๅŽ‚็ฑป

ๆ–‡ไปถไฝ็ฝฎ: 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
Loading

ไฝฟ็”จ็คบไพ‹

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()

2.2 LanggraphModelBuilder ็ฑป

ๆ–‡ไปถไฝ็ฝฎ: 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
Loading

ๅ…ณ้”ฎๆ–นๆณ•

ๆ–นๆณ• ๅŠŸ่ƒฝ
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() ๅŒ…่ฃ…ๆ‰ง่กŒๅ™จ๏ผˆ่‡ชๅŠจ็Šถๆ€ๆ›ดๆ–ฐ๏ผ‰

2.2 BaseGraphBuilder ๅŸบ็ฑป

ๆ–‡ไปถไฝ็ฝฎ: 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)
    # ... ๅ…ถไป–่Š‚็‚น็ฑปๅž‹

2.3 ่Š‚็‚นๆ‰ง่กŒๅ™จ (NodeExecutors)

ๆ–‡ไปถไฝ็ฝฎ: 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] ๆจกๆฟๆถˆๆฏๅ›žๅค

RouterNodeExecutor ็คบไพ‹

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

2.4 GraphState ็Šถๆ€ๅฎšไน‰

ๆ–‡ไปถไฝ็ฝฎ: 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 ๅ‡ฝๆ•ฐ

Reducer ไฝœ็”จ
add_messages ่ฟฝๅŠ ๆถˆๆฏๅˆ—่กจ
add_task_results ่ฟฝๅŠ ไปปๅŠก็ป“ๆžœ
merge_loop_states ๆทฑๅบฆๅˆๅนถๅพช็Žฏ็Šถๆ€๏ผˆ้ฟๅ…ๅนถๅ‘ๅ†ฒ็ช๏ผ‰
merge_task_states ๆทฑๅบฆๅˆๅนถไปปๅŠก็Šถๆ€
merge_node_contexts ๆทฑๅบฆๅˆๅนถ่Š‚็‚นไธŠไธ‹ๆ–‡

3. ๅ‰็ซฏๆ ธๅฟƒๅฎž็Žฐ

3.1 ่Š‚็‚นๆณจๅ†Œ่กจ (nodeRegistry)

ๆ–‡ไปถไฝ็ฝฎ: 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'],
  }),
}

3.2 ็Šถๆ€็ฎก็† (builderStore)

ๆ–‡ไปถไฝ็ฝฎ: 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'
  }
  // ...
}

3.3 ่พน้…็ฝฎ้ขๆฟ (EdgePropertiesPanel)

ๆ–‡ไปถไฝ็ฝฎ: 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 []
}

3.4 ็ฑปๅž‹ๅฎšไน‰ (graph.ts)

ๆ–‡ไปถไฝ็ฝฎ: 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       // ไผ˜ๅ…ˆ็บง๏ผˆๆ•ฐๅญ—่ถŠๅฐ่ถŠไผ˜ๅ…ˆ๏ผ‰
}

4. ๆ•ฐๆฎๆตไธŽไบคไบ’

4.1 ๅ›พไฟๅญ˜ๆต็จ‹

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
Loading

4.2 ๅ›พๆž„ๅปบๆต็จ‹

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
Loading

4.3 ๅ›พๆ‰ง่กŒๆต็จ‹

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)
Loading

4.4 ๆกไปถ่ทฏ็”ฑๆ•ฐๆฎๆต

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
Loading

5. ่Š‚็‚น็ฑปๅž‹ๅฎŒๆ•ดๅ‚่€ƒ

5.1 Agent ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
model modelSelect โœ… ๆŽจ็†ๆจกๅž‹
systemPrompt textarea ็ณป็ปŸๆŒ‡ไปค
tools toolSelector ่ฟžๆŽฅ็š„ๅทฅๅ…ท
skills skillSelector ่ฟžๆŽฅ็š„ๆŠ€่ƒฝ
useDeepAgents boolean ๅฏ็”จ DeepAgents ๆจกๅผ
description textarea SubAgent ๆ่ฟฐ
enableMemory boolean ๅฏ็”จ้•ฟๆœŸ่ฎฐๅฟ†
memoryModel modelSelect ่ฎฐๅฟ†ๅค„็†ๆจกๅž‹
memoryPrompt textarea ่ฎฐๅฟ†ๆ›ดๆ–ฐๆ็คบ

ๅŽ็ซฏๆ‰ง่กŒๅ™จ: AgentNodeExecutor ่ฟ”ๅ›ž: { messages: [...], current_node: "..." }


5.2 Condition ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
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"

5.3 Router ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
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


5.4 Loop Condition ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
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"

5.5 Tool ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
tool_name text โœ… ๅทฅๅ…ทๅ็งฐ
input_mapping kvList ่พ“ๅ…ฅๅ‚ๆ•ฐๆ˜ ๅฐ„

input_mapping ็คบไพ‹:

{
  "query": "state.get('context', {}).get('user_query')",
  "limit": "10"
}

ๅŽ็ซฏๆ‰ง่กŒๅ™จ: ToolNodeExecutor ่ฟ”ๅ›ž: { tool_output: [...], messages: [...] }


5.6 Function ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
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: [...] }


5.7 Aggregator ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
error_strategy select โœ… fail_fast / best_effort

็ญ–็•ฅ่ฏดๆ˜Ž:

  • fail_fast: ไปปไธ€ๅคฑ่ดฅๅˆ™ๆ•ดไฝ“ๅคฑ่ดฅ
  • best_effort: ๆ”ถ้›†ๆ‰€ๆœ‰ๆˆๅŠŸ็ป“ๆžœ๏ผŒ่ฎฐๅฝ•ๅคฑ่ดฅ

ๅŽ็ซฏๆ‰ง่กŒๅ™จ: AggregatorNodeExecutor ่ฟ”ๅ›ž: { aggregated_results: { status, success_count, error_count, results, errors } }


5.8 LLM ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
prompt textarea โœ… Prompt ๆจกๆฟ
model modelSelect โœ… LLM ๆจกๅž‹

ๆจกๆฟๅ˜้‡: {{variable}} ไปŽ context ๆ›ฟๆข๏ผŒ{{state.field}} ไปŽ state ๆ›ฟๆข

ๅŽ็ซฏๆ‰ง่กŒๅ™จ: LLMNodeExecutor ่ฟ”ๅ›ž: { llm_output: [...], messages: [...] }


5.9 JSON Parser ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
jsonpath_query text JSONPath ่กจ่พพๅผ
json_schema textarea JSON Schema ้ชŒ่ฏ

JSONPath ็คบไพ‹: $.data.items[*].name

ๅŽ็ซฏๆ‰ง่กŒๅ™จ: JSONParserNodeExecutor ่ฟ”ๅ›ž: { context: { json_output: ... } }


5.10 HTTP Request ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
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 } } }


5.11 Direct Reply ่Š‚็‚น

ๅญ—ๆฎต ็ฑปๅž‹ ๅฟ…ๅกซ ่ฏดๆ˜Ž
template textarea ๆถˆๆฏๆจกๆฟ

ๆจกๆฟ: ๆ”ฏๆŒ {{variable}} ไปŽ context ๆ›ฟๆข

ๅŽ็ซฏๆ‰ง่กŒๅ™จ: DirectReplyNodeExecutor ่ฟ”ๅ›ž: { messages: [...] }


6. ่พน้…็ฝฎ่ง„่Œƒ

6.1 EdgeData ๅฎŒๆ•ด็ป“ๆž„

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 }>
}

6.2 ่พน็ฑปๅž‹ไธŽๆ ทๅผ

edge_type ้ขœ่‰ฒ ๆ ทๅผ ็”จ้€”
normal #cbd5e1 (็ฐ) ๅฎž็บฟ 1.5px ๆ™ฎ้€š้กบๅบ่ฟžๆŽฅ
conditional #3b82f6 (่“) ๅฎž็บฟ 2px ๆกไปถๅˆ†ๆ”ฏ
loop_back #9333ea (็ดซ) ่™š็บฟ 2.5px ๅพช็Žฏๅ›ž่พน

6.3 ๆกไปถ่Š‚็‚น่พน้…็ฝฎ

Condition ่Š‚็‚น

// True ๅˆ†ๆ”ฏ
{ route_key: "true", edge_type: "conditional" }

// False ๅˆ†ๆ”ฏ
{ route_key: "false", edge_type: "conditional" }

Router ่Š‚็‚น

// ๅ‡่ฎพ 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" }

Loop Condition ่Š‚็‚น

// ็ปง็ปญๅพช็Žฏ๏ผˆๅ›žๅˆฐๅพช็Žฏไฝ“๏ผ‰
{ route_key: "continue_loop", edge_type: "loop_back" }

// ้€€ๅ‡บๅพช็Žฏ
{ route_key: "exit_loop", edge_type: "conditional" }

6.4 ๅŽ็ซฏ่พนๅค„็†้€ป่พ‘

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
    )

7. ็Šถๆ€ๅ˜้‡ๆŒ‡ๅ—

7.1 ๅ…จๅฑ€ๅฏ็”จๅ˜้‡

ๅ˜้‡ ่ทฏๅพ„ ็ฑปๅž‹ ่ฏดๆ˜Ž
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 ๅ…จๅฑ€ไธŠไธ‹ๆ–‡

7.2 ไฝœ็”จๅŸŸๅ˜้‡

ๅพช็Žฏไฝœ็”จๅŸŸ (loop_states)

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_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 vs parallel_results)

ๅญ—ๆฎต ็”จ้€” ็ป“ๆž„
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

7.3 ๆกไปถ่กจ่พพๅผ่ฏ„ไผฐไธŠไธ‹ๆ–‡

ๆ‰€ๆœ‰ๆกไปถ่Š‚็‚น๏ผˆ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)

7.4 ่กจ่พพๅผไฝฟ็”จ็คบไพ‹

# 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', [])

7.5 ๅฎ‰ๅ…จๆณจๆ„ไบ‹้กน

  • ่กจ่พพๅผๅœจๆฒ™็ฎฑ็Žฏๅขƒไธญๆ‰ง่กŒ๏ผŒ__builtins__ ่ขซ็ฆ็”จ
  • ๅช่ƒฝ่ฎฟ้—ฎ่ฏ„ไผฐไธŠไธ‹ๆ–‡ไธญๆไพ›็š„ๅ˜้‡
  • ไธๆ”ฏๆŒๅฏผๅ…ฅๆจกๅ—ๆˆ–ๆ‰ง่กŒไปปๆ„ไปฃ็ 
  • ๅคๆ‚้€ป่พ‘ๅบ”ไฝฟ็”จ Function ่Š‚็‚น๏ผˆๆ”ฏๆŒๅฎŒๆ•ด Python ๆฒ™็ฎฑ๏ผ‰

7.4 ๆจกๆฟๅ˜้‡ๆ›ฟๆข

# Direct Reply / LLM ่Š‚็‚นๆจกๆฟ
"Hello {{user_name}}, your score is {{score}}"

# ๅฏนๅบ” context
context = {
    "user_name": "Alice",
    "score": 95
}

# ไนŸๆ”ฏๆŒ state ่ฎฟ้—ฎ
"Current node: {{state.current_node}}"

8. ๆœ€ไฝณๅฎž่ทตไธŽๆจกๆฟ

8.1 ๆกไปถๅˆ†ๆ”ฏๆจกๆฟ

                    โ”Œโ”€โ”€โ”€ [Yes] โ”€โ†’ Agent A
User Input โ†’ Condition
                    โ””โ”€โ”€โ”€ [No] โ”€โ”€โ†’ Agent B

้…็ฝฎ่ฆ็‚น:

  1. Condition ่Š‚็‚น่ฎพ็ฝฎ expression
  2. True ๅˆ†ๆ”ฏ่พน่ฎพ็ฝฎ route_key: "true"
  3. False ๅˆ†ๆ”ฏ่พน่ฎพ็ฝฎ route_key: "false"

8.2 ๅคš่ง„ๅˆ™่ทฏ็”ฑๆจกๆฟ

             โ”Œโ”€โ”€โ”€ [High] โ”€โ”€โ”€โ”€โ†’ Premium Agent
             โ”‚
Router โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€ [Medium] โ”€โ”€โ†’ Standard Agent
             โ”‚
             โ””โ”€โ”€โ”€ [Default] โ”€โ†’ Basic Agent

้…็ฝฎ่ฆ็‚น:

  1. Router ่Š‚็‚น้…็ฝฎ routes ๆ•ฐ็ป„๏ผˆๆŒ‰ไผ˜ๅ…ˆ็บงๆŽ’ๅบ๏ผ‰
  2. ๆฏๆก่พน็š„ route_key ๅŒน้…ๅฏนๅบ”่ง„ๅˆ™็š„ targetEdgeKey
  3. ็กฎไฟๆœ‰้ป˜่ฎค่ทฏ็”ฑๅค„็†ๆœชๅŒน้…ๆƒ…ๅ†ต

8.3 ๅพช็Žฏๅค„็†ๆจกๆฟ

                  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                  โ”‚                      โ”‚
                  โ†“                      โ”‚
Start โ†’ Loop Condition โ”€[continue]โ”€โ†’ Process โ”€โ”˜
              โ”‚
              โ””โ”€[exit]โ”€โ†’ End

้…็ฝฎ่ฆ็‚น:

  1. Loop Condition ่ฎพ็ฝฎ conditionTypeใ€conditionใ€maxIterations
  2. continue ่พน: route_key: "continue_loop", edge_type: "loop_back"
  3. exit ่พน: route_key: "exit_loop", edge_type: "conditional"

8.4 ๅนถ่กŒ่šๅˆๆจกๆฟ

         โ”Œโ”€โ†’ Task A โ”€โ”
         โ”‚           โ”‚
Start โ”€โ”€โ”€โ”ผโ”€โ†’ Task B โ”€โ”ผโ”€โ†’ Aggregator โ†’ End
         โ”‚           โ”‚
         โ””โ”€โ†’ Task C โ”€โ”˜

้…็ฝฎ่ฆ็‚น:

  1. Fan-Out: ไธ€ไธช่Š‚็‚น่ฟžๆŽฅๅคšไธช็›ฎๆ ‡๏ผˆๆ™ฎ้€š่พน๏ผ‰
  2. Aggregator ่Š‚็‚น่ฎพ็ฝฎ error_strategy
  3. ไปปๅŠก็ป“ๆžœ้€š่ฟ‡ task_results ็Šถๆ€ไผ ้€’

8.5 ้”™่ฏฏ้‡่ฏ•ๆจกๆฟ

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚                     โ”‚
                    โ†“                     โ”‚
Start โ†’ API Call โ†’ Check Error โ”€[retry]โ”€โ”€โ”˜
                        โ”‚
                        โ””โ”€[success]โ”€โ†’ End

้…็ฝฎ่ฆ็‚น:

  1. ไฝฟ็”จ Loop Condition + doWhile ๆจกๅผ
  2. ๆกไปถ: loop_count < 3 and state.get('context', {}).get('has_error', False)
  3. ๅœจๅพช็Žฏไฝ“ๅ†…ๆ›ดๆ–ฐ has_error ็Šถๆ€

9. ้”™่ฏฏๅค„็†ไธŽ่ฐƒ่ฏ•

9.1 ๅ›พ้ชŒ่ฏ

BaseGraphBuilder ๆไพ›็ผ–่ฏ‘ๆ—ถ้ชŒ่ฏ๏ผš

# ้ชŒ่ฏๅ›พ็ป“ๆž„
errors = builder.validate_graph_structure()
# ๆฃ€ๆŸฅ: ๅญค็ซ‹่Š‚็‚นใ€Router ๆ— ๅ‡บ่พนใ€Loop ็ผบๅฐ‘่พน

# ้ชŒ่ฏ Handle ID ๆ˜ ๅฐ„
mapping_errors = builder.validate_handle_to_route_mapping()
# ๆฃ€ๆŸฅ: Handle ID ๅˆฐ route_key ็š„ไธ€่‡ดๆ€ง

9.2 ๅธธ่ง้”™่ฏฏ

้”™่ฏฏ ๅŽŸๅ›  ่งฃๅ†ณๆ–นๆกˆ
Router ๆ— ่พ“ๅ‡บ ๆฒกๆœ‰้…็ฝฎ routes ๆทปๅŠ ่ทฏ็”ฑ่ง„ๅˆ™
่พน็ผบๅฐ‘ route_key ๆกไปถ่พนๆœช้…็ฝฎ ๅœจ EdgePropertiesPanel ่ฎพ็ฝฎ
Loop ๆญปๅพช็Žฏ maxIterations ๅคชๅคงๆˆ–ๆกไปถๆฐธ็œŸ ๆฃ€ๆŸฅๆกไปถ่กจ่พพๅผๅ’Œ้™ๅˆถ
่กจ่พพๅผๆฑ‚ๅ€ผๅคฑ่ดฅ ๅ˜้‡ๆœชๅฎšไน‰ๆˆ–่ฏญๆณ•้”™่ฏฏ ไฝฟ็”จ context ้ข„ๅฎšไน‰ๅ˜้‡

9.3 ่ฐƒ่ฏ•ๆ—ฅๅฟ—

ๅŽ็ซฏไฝฟ็”จ 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_loop

9.4 ๅ‰็ซฏ้ชŒ่ฏ

edgeValidator.ts ๆไพ›ๅฎžๆ—ถ่พน้…็ฝฎ้ชŒ่ฏ๏ผš

const errors = validateEdgeData(edge, sourceNode, targetNode)
// ่ฟ”ๅ›ž: ValidationError[] = [{ field, message, severity }]

10. DeepAgents ๆžถๆž„

10.1 GraphBuilder ๅทฅๅŽ‚ๆจกๅผ

ๆ–‡ไปถไฝ็ฝฎ: backend/app/core/graph/graph_builder.py

GraphBuilder ไฝœไธบๅทฅๅŽ‚็ฑป๏ผŒ่‡ชๅŠจๆฃ€ๆต‹ๅ›พ้…็ฝฎๅนถ้€‰ๆ‹ฉๅˆ้€‚็š„ๆž„ๅปบๅ™จ๏ผš

  • ๆฃ€ๆต‹ๆœบๅˆถ๏ผšๆ‰ซๆๆ‰€ๆœ‰่Š‚็‚น๏ผŒๆŸฅๆ‰พ config.useDeepAgents === true
  • ้€‰ๆ‹ฉ้€ป่พ‘๏ผšๅฆ‚ๆžœๆฃ€ๆต‹ๅˆฐ DeepAgents ่Š‚็‚น๏ผŒไฝฟ็”จ DeepAgentsGraphBuilder๏ผ›ๅฆๅˆ™ไฝฟ็”จ LanggraphModelBuilder
  • ้€ๆ˜Žๅˆ‡ๆข๏ผš็”จๆˆทๆ— ้œ€ๅ…ณๅฟƒๅบ•ๅฑ‚ๅฎž็Žฐ๏ผŒ็ณป็ปŸ่‡ชๅŠจ้€‰ๆ‹ฉ
# ่‡ชๅŠจ้€‰ๆ‹ฉๆž„ๅปบๅ™จ
builder = GraphBuilder(graph, nodes, edges, ...)
compiled_graph = await builder.build()  # ๅ†…้ƒจ่‡ชๅŠจ้€‰ๆ‹ฉๅฎž็Žฐ

10.2 DeepAgentsGraphBuilder

ๆ–‡ไปถไฝ็ฝฎ: 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
Loading

็‰น็‚น๏ผš

  • 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]
Loading

10.3 ็ป„ไปถๆžถๆž„

10.3.1 DeepAgentsBackendManager

ๆ–‡ไปถไฝ็ฝฎ: 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]:
        """่Žทๅ–่Š‚็‚น็š„ๅŽ็ซฏ๏ผˆไผ˜ๅ…ˆๅ…ฑไบซๅŽ็ซฏ๏ผ‰"""

10.3.2 DeepAgentsSkillsManager

ๆ–‡ไปถไฝ็ฝฎ: 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]]:
        """่Žทๅ–ๆŠ€่ƒฝ่ทฏๅพ„ๅˆ—่กจ"""

10.3.3 DeepAgentsNodeBuilder (NodeFactory)

ๆ–‡ไปถไฝ็ฝฎ: 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๏ผ‰"""

10.3.4 AgentConfig / CodeAgentConfig

ๆ–‡ไปถไฝ็ฝฎ: 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 ็‰นๅฎš้…็ฝฎ

10.4 ๆ•ฐๆฎๆต

10.4.1 ่พ“ๅ…ฅๅค„็†

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", "")

10.4.2 ่พ“ๅ‡บๆ ผๅผ

SubAgent ๅฟ…้กป่ฟ”ๅ›ž AIMessage ๅฏน่ฑก๏ผŒ็ฌฆๅˆ DeepAgents ๆก†ๆžถ่ฆๆฑ‚๏ผš

# ๆญฃ็กฎๆ ผๅผ
return {
    "messages": [AIMessage(content="ๆ‰ง่กŒ็ป“ๆžœ")],
    "result": result,
}

# DeepAgents ๆก†ๆžถไผš่ฎฟ้—ฎ result["messages"][-1].text

10.4.3 Manager ไธŽ SubAgent ้€šไฟก

sequenceDiagram
    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: ่ฟ”ๅ›žๆœ€็ปˆ็ป“ๆžœ
Loading

10.5 ๆœ€ไฝณๅฎž่ทต

10.5.1 ไฝ•ๆ—ถไฝฟ็”จ DeepAgents

้€‚ๅˆไฝฟ็”จ DeepAgents ็š„ๅœบๆ™ฏ๏ผš

  • ้œ€่ฆๅคšไธชไธ“ไธš Agent ๅไฝœๅฎŒๆˆๅคๆ‚ไปปๅŠก
  • ไปปๅŠกๅฏไปฅๅˆ†่งฃไธบ็‹ฌ็ซ‹็š„ๅญไปปๅŠก
  • ้œ€่ฆ Manager ๅ่ฐƒๅ’Œๅ†ณ็ญ–
  • ้œ€่ฆๆŠ€่ƒฝๅ…ฑไบซๅ’Œไปฃ็ ๆ‰ง่กŒ่ƒฝๅŠ›

ไธ้€‚ๅˆไฝฟ็”จ DeepAgents ็š„ๅœบๆ™ฏ๏ผš

  • ็ฎ€ๅ•็š„็บฟๆ€งๅทฅไฝœๆต
  • ๅ•ไธ€ Agent ๅฏไปฅๅฎŒๆˆ็š„ไปปๅŠก
  • ไธ้œ€่ฆ Agent ้—ดๅไฝœ็š„ๅœบๆ™ฏ

10.5.2 ่Š‚็‚น้…็ฝฎๅปบ่ฎฎ

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"
  }
}

10.5.3 ๆ€ง่ƒฝไผ˜ๅŒ–

  1. ๅ…ฑไบซๅŽ็ซฏ๏ผšๅคšไธช่Š‚็‚นไฝฟ็”จๆŠ€่ƒฝๆ—ถ๏ผŒ็ณป็ปŸ่‡ชๅŠจๅˆ›ๅปบๅ…ฑไบซ Docker ๅŽ็ซฏ๏ผŒ้ฟๅ…้‡ๅคๅˆ›ๅปบ
  2. ๆŠ€่ƒฝ้ข„ๅŠ ่ฝฝ๏ผšๅœจๆž„ๅปบๆ—ถ้ข„ๅŠ ่ฝฝๆŠ€่ƒฝๅˆฐๅŽ็ซฏ๏ผŒ่ฟ่กŒๆ—ถ็›ดๆŽฅ่ฏปๅ–ๆ–‡ไปถ
  3. ่Š‚็‚นๅค็”จ๏ผšManager ๅฏไปฅๅค็”จ Worker ๅค„็†ๅคšไธชไปปๅŠก
  4. ่ต„ๆบๆธ…็†๏ผšๅ›พๆ‰ง่กŒๅฎŒๆˆๅŽ่‡ชๅŠจๆธ…็†ๅ…ฑไบซๅŽ็ซฏ่ต„ๆบ

10.5.4 ๅธธ่ง้—ฎ้ข˜

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 ๅฏน่ฑก๏ผŒ่€Œไธๆ˜ฏๅญ—ๅ…ธใ€‚็ณป็ปŸๅทฒ่‡ชๅŠจๅค„็†ๆญคๆ ผๅผ่ฝฌๆขใ€‚


้™„ๅฝ• A: ๆ–‡ไปถ็ดขๅผ•

ๅŽ็ซฏๆ–‡ไปถ

ๆ–‡ไปถ ่ทฏๅพ„
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

้™„ๅฝ• B: ๅ‰ๅŽ็ซฏ State ๅๅŒๆžถๆž„

B.1 ๅๅŒๆžถๆž„ๅ›พ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                             ๅ‰็ซฏ (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")  โœ… ๆญฃๅธธๅทฅไฝœ       โ”‚      โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

B.2 ๆ•ฐๆฎๆตๅฎŒๆ•ดๆ€ง

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 โœ…

B.3 ๆžถๆž„่‡ชๆดฝๆ€งๆฃ€ๆŸฅ

ๆฃ€ๆŸฅ็‚น ็Šถๆ€ ่ฏดๆ˜Ž
ๅ‰็ซฏ้…็ฝฎไฟๅญ˜ โœ… GraphSettingsPanel โ†’ builderStore โ†’ agentService โ†’ API
ๅŽ็ซฏๆŒไน…ๅŒ– โœ… API โ†’ graph_service โ†’ AgentGraph.variables
่ฟ่กŒๆ—ถๅŠ ่ฝฝ โœ… chat.py ไปŽ graph.variables.context ๅˆๅง‹ๅŒ– GraphState.context
่กจ่พพๅผๆฑ‚ๅ€ผ โœ… RouterNodeExecutor.eval_context ๅŒ…ๅซ context
ๅ˜้‡ๆ็คบ โœ… ConditionExprField ๅŠจๆ€ๆ˜พ็คบ็”จๆˆทๅฎšไน‰็š„ context ๅ˜้‡
ๅ›žๆปšๆ”ฏๆŒ โœ… DeploymentHistoryPanel ๅŠ ่ฝฝๆ—ถๅŒๆญฅ graphVariables

้™„ๅฝ• C: ็‰ˆๆœฌๅކๅฒ

็‰ˆๆœฌ ๆ—ฅๆœŸ ่ฏดๆ˜Ž
1.0 2026-01-02 ๅˆๅง‹็‰ˆๆœฌ๏ผŒๆ•ดๅˆๆ‰€ๆœ‰่ฟ‡็จ‹่ฎพ่ฎกๆ–‡ๆกฃ
1.1 2026-01-02 ๅฎŒๅ–„ๅ‰ๅŽ็ซฏ State ๅๅŒๆžถๆž„่ฏดๆ˜Ž
1.2 2026-01-22 ๆทปๅŠ  GraphBuilder ๅทฅๅŽ‚ๆจกๅผๅ’Œ DeepAgents ๆžถๆž„ๅฎŒๆ•ดๆ–‡ๆกฃ