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:
-
Create a new flow – Click Create Flow in the OpenDeRisk UI to initialize a blank canvas.
-
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
CommonLLMHttpRequestBodyobjects. -
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.
-
Add resource nodes – Include Prompt Template resources to hold system prompts or few-shot examples that operators can reference.
-
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. -
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.pyenforces constraints. -
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.
-
Execute the flow – Send a POST request to the generated trigger URL with a payload matching
CommonLLMHttpRequestBody. The back-end creates aDAGContext, executes the graph asynchronously, and streams responses ifsse_outputorstreaming_outputare 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:
-
Validation – It reads
FlowNodeDataandFlowEdgeDatamodels to ensure all required fields (such asoperator_type,flow_type, and connection IDs) are present. -
Class Resolution – It resolves concrete Python classes using internal methods like
_get_operator_classand_get_resource_class, mapping JSON node types to actual implementations inpackages/derisk-core/src/derisk/core/awel/operators/. -
DAG Construction – It builds a
DAGobject where each JSON node becomes aDAGNode. Themetadata.triggerssection is parsed to automatically create an HTTP router (AWELHttpTrigger) that forwards incoming requests todag.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.pyconverts JSON canvas definitions into executableDAGobjects by resolving operator classes and validating edge connections. - HTTP triggers defined in
packages/derisk-core/src/derisk/core/awel/trigger/http_trigger.pyexpose registered flows as REST endpoints that acceptCommonLLMHttpRequestBodyand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →