How to Construct DAG-Based Chat Flows Using the AWEL Flow Builder

The AWEL flow builder in OpenDeRisk lets you visually design directed-acyclic-graph (DAG) chat workflows by connecting trigger, operator, and resource nodes in a React-based UI, which serializes to JSON and compiles into an executable DAG via FlowFactory.from_dict().

Constructing DAG-based chat flows using the AWEL flow builder is the primary method for orchestrating complex LLM interactions in the OpenDeRisk platform. This visual workflow system allows you to stitch together triggers, operators, and resources into a runnable pipeline without writing boilerplate orchestration code. The following guide explains the architecture, implementation details, and practical code examples for building these flows based on the actual source code in the derisk-ai/openderisk repository.

AWEL Flow Builder Architecture Overview

The AWEL (Adaptive Workflow Execution Language) system consists of several coordinated components that transform a visual canvas into a running Python DAG.

Component Role Source File
Flow UI React-based visual editor for dragging nodes, connecting edges, and setting parameters. Serializes canvas state to JSON. packages/derisk-core/src/derisk/core/awel/flow/ui.py
Flow JSON Template Serialized DAG representation containing nodes, edges, viewport, and metadata. Consumed by the back-end to reconstruct the workflow. packages/derisk-serve/src/derisk_serve/flow/templates/en/rag-chat-awel-flow-template.json
Flow Factory Parses the JSON template and instantiates derisk.core.awel.dag.base.DAG objects. Validates node types and resolves operator/resource classes. packages/derisk-core/src/derisk/core/awel/flow/flow_factory.py
Triggers Entry points such as HTTP endpoints that initiate DAG execution. Implemented as FastAPI routers that inject the current DAG instance. packages/derisk-core/src/derisk/core/awel/trigger/http_trigger.py
Operators & Resources Work units including LLM calls, knowledge retrieval, and prompt templates. Implemented as subclasses of BaseOperator or BaseResource. packages/derisk-core/src/derisk/core/awel/operators/ and packages/derisk-core/src/derisk/core/awel/resource/
Flow Service CRUD API for persisting flows to the database and registering them with the DAGManager. packages/derisk-serve/src/derisk_serve/flow/service/service.py

When a user clicks Save Flow in the UI, the JSON payload is posted to /api/v2/serve/awel/flows. The flow service validates the payload, invokes FlowFactory.from_dict to construct a DAG, registers it with the global DAGManager, and exposes a trigger URL such as /api/v1/awel/trigger/templates/<dag_id>.

Building a DAG-Based Chat Flow Step-by-Step

Follow these steps to construct a functional chat flow using the visual AWEL flow builder:

  1. Create a new flow – Click Create Flow in the OpenDeRisk UI to initialize a blank canvas.

  2. Add a trigger node – Drag the Common LLM Http Trigger onto the canvas. This creates an HTTP POST endpoint that parses incoming request bodies into CommonLLMHttpRequestBody objects.

  3. Add operator nodes – Drag operators to perform specific tasks:

    • Higher-order Knowledge Operator – Performs RAG retrieval against a knowledge base.
    • Higher-order Streaming LLM Operator – Calls the LLM and streams output tokens.
    • Output Parser – Converts raw LLM output into structured chat responses.
  4. Add resource nodes – Include Prompt Template resources to hold system prompts or few-shot examples that operators can reference.

  5. Connect edges – Define data flow by dragging connector lines between nodes. Edge metadata (source_order, target_order) determines which output port feeds into which input port.

  6. Configure node parameters – Click each node to open a form and set fields such as API endpoint, model name, and temperature. Validation logic in packages/derisk-core/src/derisk/core/awel/flow/ui.py enforces constraints.

  7. Save the flow – The UI serializes the canvas into a JSON structure matching the RAG Chat template format and posts it to the back-end.

  8. Execute the flow – Send a POST request to the generated trigger URL with a payload matching CommonLLMHttpRequestBody. The back-end creates a DAGContext, executes the graph asynchronously, and streams responses if sse_output or streaming_output are enabled.

How the Back-End Turns JSON into an Executable DAG

The conversion from visual design to runnable Python code happens in FlowFactory. When the front-end sends a JSON payload, the following process occurs:

from derisk.core.awel.flow.flow_factory import FlowFactory
from derisk.core.awel.dag.base import DAG

# `flow_json` is the payload received from the front-end.

dag: DAG = FlowFactory.from_dict(flow_json)          # validates nodes/edges

dag.register()                                      # registers with DAGManager

The FlowFactory performs three critical tasks:

  1. Validation – It reads FlowNodeData and FlowEdgeData models to ensure all required fields (such as operator_type, flow_type, and connection IDs) are present.

  2. Class Resolution – It resolves concrete Python classes using internal methods like _get_operator_class and _get_resource_class, mapping JSON node types to actual implementations in packages/derisk-core/src/derisk/core/awel/operators/.

  3. DAG Construction – It builds a DAG object where each JSON node becomes a DAGNode. The metadata.triggers section is parsed to automatically create an HTTP router (AWELHttpTrigger) that forwards incoming requests to dag.run().

Practical Code Examples

Programmatic Construction (Python)

You can construct flows entirely in Python without using the visual editor. This is useful for version-controlled workflows or automated deployments:

from derisk.core.awel.flow.flow_factory import FlowFactory
from derisk.core.awel.dag.base import DAG

# 1️⃣ Define the flow as a Python dict (same schema as the UI JSON)

flow_json = {
    "flow": {
        "uid": "demo-flow-001",
        "label": "Demo Chat Flow",
        "flow_category": "chat_flow",
        "metadata": {
            "triggers": [
                {
                    "trigger_type": "http",
                    "path": "/api/v1/awel/trigger/demo_flow",
                    "methods": ["POST"],
                    "trigger_mode": "chat"
                }
            ],
            "sse_output": True,
            "streaming_output": True,
        },
        "flow_data": {
            "nodes": [
                # ── Trigger

                {
                    "width": 260,
                    "height": 140,
                    "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0",
                    "position": {"x": 0, "y": 0, "zoom": 0},
                    "type": "customNode",
                    "data": {
                        "label": "Common LLM Http Trigger",
                        "flow_type": "operator",
                        "operator_type": "input",
                        "parameters": [
                            {"name": "endpoint", "value": "/demo/{dag_id}"},
                            {"name": "methods", "value": "POST"}
                        ],
                    },
                },
                # ── Knowledge Retrieval

                {
                    "width": 300,
                    "height": 200,
                    "id": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0",
                    "position": {"x": 400, "y": -100, "zoom": 0},
                    "type": "customNode",
                    "data": {
                        "label": "RAG Knowledge Operator",
                        "flow_type": "operator",
                        "operator_type": "process",
                    },
                },
                # ── Streaming LLM

                {
                    "width": 300,
                    "height": 200,
                    "id": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0",
                    "position": {"x": 800, "y": -100, "zoom": 0},
                    "type": "customNode",
                    "data": {
                        "label": "Streaming LLM Operator",
                        "flow_type": "operator",
                        "operator_type": "process",
                        "parameters": [
                            {"name": "model_name", "value": "gpt-4o"},
                            {"name": "temperature", "value": 0.7}
                        ],
                    },
                },
                # ── Output Parser

                {
                    "width": 260,
                    "height": 140,
                    "id": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0",
                    "position": {"x": 1200, "y": -100, "zoom": 0},
                    "type": "customNode",
                    "data": {
                        "label": "OpenAI Streaming Output",
                        "flow_type": "operator",
                        "operator_type": "output",
                    },
                },
            ],
            "edges": [
                # Trigger → Knowledge

                {
                    "source": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0",
                    "source_order": 0,
                    "target": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0",
                    "target_order": 0,
                    "id": "t|k",
                    "type": "buttonedge",
                },
                # Knowledge → LLM

                {
                    "source": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0",
                    "source_order": 0,
                    "target": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0",
                    "target_order": 0,
                    "id": "k|l",
                    "type": "buttonedge",
                },
                # LLM → Output

                {
                    "source": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0",
                    "source_order": 0,
                    "target": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0",
                    "target_order": 0,
                    "id": "l|o",
                    "type": "buttonedge",
                },
            ],
            "viewport": {"x": 0, "y": 0, "zoom": 1.0},
        },
    }
}

# 2️⃣ Build the DAG

dag: DAG = FlowFactory.from_dict(flow_json)

# 3️⃣ Register – makes the trigger URL live

dag.register()

# 4️⃣ Test – send a request to the generated endpoint:

# POST /api/v1/awel/trigger/demo_flow

# Body: {"messages": [{"role": "user", "content": "Hello"}]}

Front-End Interaction (React)

When users save flows via the React UI, the canvas serializes to the same JSON structure:

// Save button in the flow editor (simplified)
const onSave = async () => {
  const payload = serializeCanvas(); // returns the JSON structure like the file above
  await api.flow.createFlow(payload); // POST /api/v2/serve/awel/flows
  message.success(t('save_flow_success'));
};

Triggering a Saved Flow (cURL)

Once registered, trigger the flow via the auto-generated HTTP endpoint:

curl -X POST "http://localhost:8000/api/v1/awel/trigger/templates/flow_dag_rag_chat_awel_flow_template_21eb87d5-b63a-4f41-b2aa-28d01033344d" \
  -H "Content-Type: application/json" \
  -d '{
        "messages": [
          {"role": "user", "content": "What is the risk of investing in AI startups?"}
        ]
      }'

The response streams back as Server-Sent Events if sse_output is enabled in the flow metadata.

Summary

  • AWEL (Adaptive Workflow Execution Language) provides a visual, DAG-based approach to building chat flows in OpenDeRisk, combining React-based UI components with a Python back-end factory pattern.
  • FlowFactory in packages/derisk-core/src/derisk/core/awel/flow/flow_factory.py converts JSON canvas definitions into executable DAG objects by resolving operator classes and validating edge connections.
  • HTTP triggers defined in packages/derisk-core/src/derisk/core/awel/trigger/http_trigger.py expose registered flows as REST endpoints that accept CommonLLMHttpRequestBody and stream responses via SSE.
  • Programmatic construction is fully supported—developers can define flows as Python dictionaries, build them with FlowFactory.from_dict(), and register them without using the visual editor.

Frequently Asked Questions

What is the AWEL flow builder in OpenDeRisk?

The AWEL flow builder is a visual workflow editor integrated into the OpenDeRisk platform that allows users to construct directed-acyclic-graph (DAG) based chat flows by dragging and connecting nodes on a React canvas. It serializes the visual design into a JSON template that the back-end converts into an executable Python DAG using the FlowFactory class.

How does the AWEL flow builder convert visual designs into executable code?

When you click Save Flow in the UI, the React front-end serializes the canvas into a JSON payload containing FlowNodeData and FlowEdgeData objects. This payload is sent to /api/v2/serve/awel/flows, where the FlowService invokes FlowFactory.from_dict() in packages/derisk-core/src/derisk/core/awel/flow/flow_factory.py. The factory resolves JSON node types to concrete Python classes (such as BaseOperator subclasses), validates connections, and constructs a DAG object that gets registered with the global DAGManager.

Can I trigger DAG-based chat flows via HTTP endpoints?

Yes. Every saved flow automatically exposes an HTTP trigger endpoint defined in packages/derisk-core/src/derisk/core/awel/trigger/http_trigger.py. The trigger path is specified in the flow metadata (e.g., /api/v1/awel/trigger/templates/<dag_id>) and accepts POST requests containing a CommonLLMHttpRequestBody JSON payload. If sse_output is enabled in the flow metadata, the endpoint streams responses as Server-Sent Events; otherwise, it returns the complete response after execution.

Where are AWEL flow templates stored in the OpenDeRisk repository?

Reference templates and example flows are stored in packages/derisk-serve/src/derisk_serve/flow/templates/, with specific localized versions like the RAG chat template located at packages/derisk-serve/src/derisk_serve/flow/templates/en/rag-chat-awel-flow-template.json. The core operator definitions and resource classes used by these templates reside in packages/derisk-core/src/derisk/core/awel/operators/ and packages/derisk-core/src/derisk/core/awel/resource/ respectively.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →