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

> Learn to build DAG-based chat flows with the AWEL flow builder in OpenDeRisk. Visually design workflows by connecting nodes and serialize to JSON.

- Repository: [derisk-ai/openderisk](https://github.com/derisk-ai/openderisk)
- Tags: how-to-guide
- Published: 2026-02-28

---

**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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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:

```python
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:

```python
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:

```tsx
// 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:

```bash
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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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`](https://github.com/derisk-ai/openderisk/blob/main/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.