Skip to content

Commit ee8af1d

Browse files
author
Jicheng Lu
committed
add rule flow guide
1 parent 0e287e8 commit ee8af1d

2 files changed

Lines changed: 194 additions & 3 deletions

File tree

src/Infrastructure/BotSharp.Core.Rules/Engines/Frontier.cs renamed to src/Infrastructure/BotSharp.Abstraction/Rules/Frontier.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace BotSharp.Core.Rules.Engines;
44
/// Abstraction over the data structure that drives graph traversal order.
55
/// Stack → DFS, Queue → BFS. Swap the frontier mid-traversal to switch strategy.
66
/// </summary>
7-
internal interface IFrontier<T>
7+
public interface IFrontier<T>
88
{
99
void Add(T item);
1010
T Remove();
@@ -19,7 +19,7 @@ internal interface IFrontier<T>
1919
/// <summary>
2020
/// LIFO frontier – produces depth-first traversal.
2121
/// </summary>
22-
internal sealed class StackFrontier<T> : IFrontier<T>
22+
public sealed class StackFrontier<T> : IFrontier<T>
2323
{
2424
private readonly Stack<T> _stack = new();
2525

@@ -46,7 +46,7 @@ public void DrainTo(IFrontier<T> other)
4646
/// <summary>
4747
/// FIFO frontier – produces breadth-first traversal.
4848
/// </summary>
49-
internal sealed class QueueFrontier<T> : IFrontier<T>
49+
public sealed class QueueFrontier<T> : IFrontier<T>
5050
{
5151
private readonly Queue<T> _queue = new();
5252

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Rule Flow Graph — Build & Modification Guide
2+
3+
## 1. Graph Structure
4+
5+
A rule flow graph consists of **Nodes** (units of work) connected by **Edges** (directed links). The engine loads the graph, applies schemas from node config, validates connection compatibility, and then traverses the graph starting from the root node.
6+
7+
Every graph must have **exactly one root node** and **exactly one end node**. The root node is the entry point where execution begins, and the end node is the exit point where execution concludes.
8+
9+
## 2. Node Types
10+
11+
Every node **must** have a `Type` and a `Name`. The `Name` must match a registered flow unit implementation.
12+
13+
- **Root** (type: `root` or `start`) — The entry point of the graph. It should have **no** input schema but **must** have an output schema that declares the parameters it passes to downstream nodes. When executed, it copies all trigger parameters into its output.
14+
- **End** (type: `end`) — The exit point of the graph. It can have both an input schema and an output schema. When executed, it collects the final context parameters as its result.
15+
- **Action** (type: `action`) — A node that performs work such as sending a chat message, making an HTTP request, or invoking a tool call. Must have both an input schema and an output schema.
16+
- **Condition** (type: `condition`) — A node that evaluates a boolean expression. Its children are only traversed when the evaluation result is `true`; otherwise the branch is skipped. Must have both an input schema and an output schema.
17+
18+
## 3. Required Node Properties
19+
20+
| Property | Required | Description |
21+
|----------|----------|-------------|
22+
| `Id` | Yes | Unique identifier (GUID). |
23+
| `Name` | Yes | Must match a registered flow unit's name (e.g., `"start"`, `"send_message_to_agent"`). |
24+
| `Type` | Yes | One of: `root`, `start`, `action`, `condition`, `end`. |
25+
| `Config` | No | Key-value dictionary passed as parameters to the node at execution time. Also supports reserved keys (see below). |
26+
27+
## 4. Reserved Config Keys
28+
29+
| Key | Purpose |
30+
|-----|---------|
31+
| `input_schema` | A JSON string describing the node's input schema. When provided, this overrides any default input schema. |
32+
| `output_schema` | A JSON string describing the node's output schema. When provided, this overrides any default output schema. |
33+
| `traversal_algorithm` | The graph traversal defaults to depth-first (DFS). Only set this value if you need to switch the traversal strategy mid-graph. Setting it to `"bfs"` on a node will change the traversal to breadth-first from that node onward. |
34+
35+
## 5. Edge Properties
36+
37+
| Property | Required | Description |
38+
|----------|----------|-------------|
39+
| `Id` | Yes | Unique identifier (GUID). |
40+
| `From` | Yes | Reference to the source node. |
41+
| `To` | Yes | Reference to the target node. |
42+
| `Type` | No | Default `"next"`. |
43+
| `Weight` | No | Higher weight = higher priority when multiple children exist. Default `1.0`. |
44+
45+
## 6. Schema Contract
46+
47+
Each node can declare an **input** and **output** schema to describe its data contract. The schema follows a JSON Schema-like structure with two fields:
48+
49+
- **properties** — A dictionary of parameter names. Each entry has a `type` (one of `string`, `number`, `boolean`, `object`, `array`) and an optional `description`.
50+
- **required** — A list of parameter names that must be provided for the node to function correctly.
51+
52+
Example schema (as JSON):
53+
54+
```json
55+
{
56+
"properties": {
57+
"order_id": { "type": "string", "description": "The order identifier" },
58+
"amount": { "type": "number", "description": "Total amount" }
59+
},
60+
"required": ["order_id"]
61+
}
62+
```
63+
64+
**Schema precedence:** A schema defined in the node's config (via `input_schema` / `output_schema` keys) always **overrides** any default schema.
65+
66+
## 7. Connection Validation Rules
67+
68+
The engine validates **every edge** in the graph at load time. For an edge `[A] → [B]` to be valid:
69+
70+
### Rule 1 — Required keys must be available
71+
**Every key listed in the downstream node's required input must exist in the upstream node's output schema properties.**
72+
**Neither the upstream node's output schema nor the downstream node's input schema can be empty — both must be explicitly defined for every connection**.
73+
74+
### Rule 2 — Types must be compatible
75+
When a required key appears in both the upstream node's output properties and the downstream node's input properties, the type must match (case-insensitive). For example:
76+
-`A` outputs `{"order_id": {"type": "string"}}``B` expects `{"order_id": {"type": "string"}}`
77+
-`A` outputs `{"amount": {"type": "number"}}``B` expects `{"amount": {"type": "string"}}`
78+
79+
### Rule 3 — Boundary constraints
80+
- A graph must contain **exactly one** root node and **exactly one** end node.
81+
- **Root nodes** (`root`/`start`): Should have **no** input schema — they are entry points with no upstream. Must have an output schema that declares the parameters available to downstream nodes.
82+
- **Intermediate nodes** (`action`/`condition`): Must have both an input schema and an output schema. The input schema declares what the node expects from upstream, and the output schema declares what it produces for downstream.
83+
- **End nodes** (`end`): Can have both input and output schemas. The input schema validates that upstream nodes provide the required final data. The output schema describes the data the graph produces as its overall result.
84+
85+
## 8. Built-in Node Reference
86+
87+
### 8.1 `http_request` (Action)
88+
89+
An action node that sends an HTTP request and returns the response. The URL supports placeholder substitution — any `{key}` in the URL is replaced with the matching parameter value at runtime.
90+
91+
**Required config / input:**
92+
93+
| Parameter | Type | Required | Description |
94+
|-----------|------|----------|-------------|
95+
| `http_url` | string | Yes | The target URL. May contain `{placeholder}` tokens that are substituted from context parameters. |
96+
| `http_method` | string | Yes | One of: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`. |
97+
| `http_query_params` | array | No | Query parameters as key-value pairs, appended to the URL. |
98+
| `http_request_headers` | array | No | Request headers as key-value pairs. |
99+
| `http_request_body` | string | No | The request body (JSON string). |
100+
101+
**Output:**
102+
103+
| Parameter | Type | Description |
104+
|-----------|------|-------------|
105+
| `http_response` | string | The HTTP response body. |
106+
| `http_response_headers` | string | The HTTP response headers as a JSON string. |
107+
108+
If the request fails (non-success status code or exception), the node returns `Success = false` with an error message.
109+
110+
### 8.2 `logic_gate` (Condition)
111+
112+
A condition node that collects results from multiple parent condition nodes and evaluates a composite logical expression. It waits until **all** parent nodes have been visited before evaluating.
113+
114+
**Required config / input:**
115+
116+
| Parameter | Type | Required | Description |
117+
|-----------|------|----------|-------------|
118+
| `expression` | object | Yes | A JSON-encoded expression tree (see below). |
119+
| `default_value` | string | No | The fallback boolean value (`"true"` or `"false"`) when a referenced parent node or data key is not found. Defaults to `"false"`. |
120+
121+
**Expression tree format:**
122+
123+
There are two kinds of nodes in an expression tree:
124+
125+
- **Leaf node** — References a specific parent node's result.
126+
- `node` (required): The name of a parent condition node.
127+
- `key` (optional): A key in the parent node's output data that holds a boolean string. If omitted, the parent's overall success flag is used.
128+
129+
- **Operator node** — Combines child expressions with a logical operator.
130+
- `op` (required): One of `"and"`, `"or"`, `"not"`.
131+
- `children` (required): A list of child expression nodes.
132+
- For `"not"`, only the first child is evaluated.
133+
134+
**Example expression:**
135+
136+
Given three parent condition nodes — `check_work_order`, `check_client`, and `check_affiliate` — the following expression evaluates `work_order_valid AND (client_name_valid OR NOT affiliate_name_valid)`:
137+
138+
```json
139+
{
140+
"expression": {
141+
"op": "and",
142+
"children": [
143+
{ "node": "check_work_order", "key": "work_order_valid" },
144+
{
145+
"op": "or",
146+
"children": [
147+
{ "node": "check_client", "key": "client_name_valid" },
148+
{
149+
"op": "not",
150+
"children": [
151+
{ "node": "check_affiliate", "key": "affiliate_name_valid" }
152+
]
153+
}
154+
]
155+
}
156+
]
157+
},
158+
"default_value": "false"
159+
}
160+
```
161+
162+
The node produces no output schema — it only controls whether downstream nodes are traversed based on the evaluation result.
163+
164+
## 9. Example: Minimal Valid Graph
165+
166+
```
167+
[start] ──→ [check_order] ──→ [send_message_to_agent] ──→ [end]
168+
root condition action end
169+
```
170+
171+
**Node definitions:**
172+
173+
| Node | Type | Name | Output Schema | Input Schema |
174+
|------|------|------|--------------|--------------|
175+
| Start | `root` | `start` | _(from context: `order_id`, `customer_name`)_ | _(none)_ |
176+
| Check Order | `condition` | `check_order` | `{ order_id: string }` | `{ required: [order_id] }` |
177+
| Send Message | `action` | `send_message_to_agent` | `{ agent_id: string, conversation_id: string }` | _(empty — no required)_ |
178+
| End | `end` | `end` | _(none)_ | _(none)_ |
179+
180+
**Validation at load:**
181+
- `[start] → [check_order]`: `check_order` requires `order_id` — satisfied if `start`'s output or `check_order`'s config provides it. ✅
182+
- `[check_order] → [send_message_to_agent]`: `send_message_to_agent` has no required inputs. ✅
183+
- `[send_message_to_agent] → [end]`: `end` has no input schema. ✅
184+
185+
## 10. How Data Flows at Runtime
186+
187+
1. **Start node** — Copies all context parameters (trigger states, flow options) into its output data.
188+
2. **Subsequent nodes** — Receive merged parameters built from three sources in order: the node's own config, then trigger states, then upstream output data.
189+
3. **Condition nodes** — If the evaluation result is `false`, all children of that node are **skipped**.
190+
4. **Action nodes** — If the action result is marked as delayed, children are **deferred** until the next execution cycle.
191+
5. **End node** — Copies the final context parameters into result data.

0 commit comments

Comments
 (0)