Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/Infrastructure/BotSharp.Abstraction/Rules/RuleGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)
Type = payload.Type,
Labels = [.. payload.Labels ?? []],
Weight = payload.Weight,
Purpose = payload.Purpose,
Alias = payload.Alias,
Description = payload.Description,
Config = new(payload.Config ?? [])
});
Expand Down Expand Up @@ -234,9 +234,15 @@ public class GraphItem
public virtual string Type { get; set; } = null!;
public virtual IEnumerable<string> Labels { get; set; } = [];
public virtual double Weight { get; set; } = 1.0;
public virtual string? Purpose { get; set; }
public virtual string? Description { get; set; }
public virtual Dictionary<string, string?> Config { get; set; } = [];

private string? _alias;
public virtual string Alias
{
get => string.IsNullOrEmpty(_alias) ? Name : _alias;
set => _alias = value;
}
}

public class NodeItem : GraphItem
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text.Json;
using BotSharp.Core.Rules.Models;

namespace BotSharp.Core.Rules.Conditions;
Expand All @@ -16,11 +15,13 @@ namespace BotSharp.Core.Rules.Conditions;
/// (A AND B) OR (C AND NOT D)
///
/// Leaf node format:
/// { "node": "node_name", "key": "data_key" }
/// - "node": The Name of a parent condition node whose result to inspect.
/// - "key": The key in the parent node's RuleNodeResult.Data dictionary that holds
/// a boolean string ("true"/"false"). If omitted, falls back to the parent
/// node's RuleNodeResult.Success flag.
/// { "node_alias": "node_alias", "key": "data_key" }
/// - "node_alias": The Alias of a parent condition node whose result to inspect.
/// Using Alias instead of Name avoids collisions when multiple nodes
/// share the same Name (e.g. several "http_request" nodes).
/// - "key": The key in the parent node's RuleNodeResult.Data dictionary that holds
/// a boolean string ("true"/"false"). If omitted, falls back to the parent
/// node's RuleNodeResult.Success flag.
///
/// Node config:
/// "expression" - A JSON-encoded LogicExpression tree.
Expand All @@ -30,20 +31,20 @@ namespace BotSharp.Core.Rules.Conditions;
/// Example: work_order_valid AND (client_name_valid OR NOT affiliate_name_valid)
///
/// Given three parent condition nodes:
/// - Node A ("check_work_order") returns Data["work_order_valid"] = "true"
/// - Node B ("check_client") returns Data["client_name_valid"] = "false"
/// - Node C ("check_affiliate") returns Data["affiliate_name_valid"] = "false"
/// - Node A (node_alias "check_work_order") returns Data["work_order_valid"] = "true"
/// - Node B (node_alias "check_client") returns Data["client_name_valid"] = "false"
/// - Node C (node_alias "check_affiliate") returns Data["affiliate_name_valid"] = "false"
///
/// The gate node config would be:
/// {
/// "expression": {
/// "op": "and",
/// "children": [
/// { "node": "check_work_order", "key": "work_order_valid" },
/// { "node_alias": "check_work_order", "key": "work_order_valid" },
/// { "op": "or", "children": [
/// { "node": "check_client", "key": "client_name_valid" },
/// { "node_alias": "check_client", "key": "client_name_valid" },
/// { "op": "not", "children": [
/// { "node": "check_affiliate", "key": "affiliate_name_valid" }
/// { "node_alias": "check_affiliate", "key": "affiliate_name_valid" }
/// ]}
/// ]}
/// ]
Expand Down Expand Up @@ -142,10 +143,10 @@ public async Task<RuleNodeResult> EvaluateAsync(

var defaultValue = currentNode.Config?.GetValueOrDefault("default_value") ?? "false";

// 3. Build lookup: parent node name → its latest RuleFlowStepResult
// 3. Build lookup: parent node alias → its latest RuleFlowStepResult
var parentResults = (context.PrevStepResults ?? [])
.Where(r => parentNodeIds.Contains(r.Node.Id))
.GroupBy(r => r.Node.Name, StringComparer.OrdinalIgnoreCase)
.GroupBy(r => r.Node.Alias, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);

// 4. Evaluate the expression tree
Expand All @@ -167,13 +168,13 @@ private bool Evaluate(
Dictionary<string, RuleFlowStepResult> parentResults,
string defaultValue)
{
// Leaf node: look up a specific parent's result
if (!string.IsNullOrEmpty(expr.Node))
// Leaf node: look up a specific parent's result by alias
if (!string.IsNullOrEmpty(expr.NodeAlias))
{
if (!parentResults.TryGetValue(expr.Node, out var stepResult))
if (!parentResults.TryGetValue(expr.NodeAlias, out var stepResult))
{
_logger.LogWarning("Logic gate: parent node '{Node}' not found in results, using default '{Default}'.",
expr.Node, defaultValue);
_logger.LogWarning("Logic gate: parent node alias '{Alias}' not found in results, using default '{Default}'.",
expr.NodeAlias, defaultValue);
return ParseBool(defaultValue);
}

Expand Down
8 changes: 1 addition & 7 deletions src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ private async Task ExecuteGraphTraversal(
Edge = nextEdge,
Graph = graph,
Text = text,
Parameters = BuildParameters(nextNode.Config, states, innerData),
Parameters = BuildParameters(states, innerData),
PrevStepResults = results,
JsonOptions = options?.JsonOptions
};
Expand Down Expand Up @@ -702,17 +702,11 @@ private void ValidateGraphSchema(RuleGraph graph)

#region Private methods
private Dictionary<string, string?> BuildParameters(
Dictionary<string, string?>? config,
IEnumerable<MessageState>? states,
Dictionary<string, string?>? param = null)
{
var dict = new Dictionary<string, string?>();

if (config != null)
{
dict = new(config);
}

if (!states.IsNullOrEmpty())
{
foreach (var state in states!)
Expand Down
15 changes: 8 additions & 7 deletions src/Infrastructure/BotSharp.Core.Rules/Models/LogicExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ namespace BotSharp.Core.Rules.Models;

/// <summary>
/// Represents a node in a logic expression tree used by LogicGateCondition.
///
/// Leaf node: references a parent node's result by name and an optional custom data key.
/// e.g. { "node": "check_work_order", "key": "work_order_valid" }
///
///
/// Leaf node: references a parent node's result by alias and an optional custom data key.
/// e.g. { "node_alias": "check_work_order", "key": "work_order_valid" }
///
/// Operator node: combines children with "and", "or", or "not".
/// e.g. { "op": "and", "children": [ ... ] }
/// </summary>
public class LogicExpression
{
/// <summary>
/// For leaf nodes: the parent node name whose result to inspect.
/// For leaf nodes: the parent node alias whose result to inspect.
/// Using Alias avoids collisions when multiple nodes share the same Name.
/// </summary>
[JsonPropertyName("node")]
[JsonPropertyName("node_alias")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Node { get; set; }
public string? NodeAlias { get; set; }

/// <summary>
/// For leaf nodes: the key in the parent node's Data dictionary that holds the boolean value.
Expand Down
6 changes: 3 additions & 3 deletions src/Plugins/BotSharp.Plugin.Membase/Services/DemoRuleGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ private RuleGraph BuildGraph(GraphQueryResult result)
Weight = sourceNodeWeight,
Name = GetGraphItemAttribute(sourceNodeProps, key: "name", defaultValue: "node"),
Type = GetGraphItemAttribute(sourceNodeProps, key: "type", defaultValue: "action"),
Purpose = GetGraphItemAttribute(sourceNodeProps, key: "purpose", defaultValue: ""),
Alias = GetGraphItemAttribute(sourceNodeProps, key: "alias", defaultValue: ""),
Description = GetGraphItemAttribute(sourceNodeProps, key: "description", defaultValue: ""),
Config = GetConfig(sourceNodeProps)
};
Expand All @@ -181,7 +181,7 @@ private RuleGraph BuildGraph(GraphQueryResult result)
Weight = targetNodeWeight,
Name = GetGraphItemAttribute(targetNodeProps, key: "name", defaultValue: "node"),
Type = GetGraphItemAttribute(targetNodeProps, key: "type", defaultValue: "action"),
Purpose = GetGraphItemAttribute(targetNodeProps, key: "purpose", defaultValue: ""),
Alias = GetGraphItemAttribute(targetNodeProps, key: "alias", defaultValue: ""),
Description = GetGraphItemAttribute(targetNodeProps, key: "description", defaultValue: ""),
Config = GetConfig(targetNodeProps)
};
Expand All @@ -192,7 +192,7 @@ private RuleGraph BuildGraph(GraphQueryResult result)
Id = edgeId ?? Guid.NewGuid().ToString(),
Name = GetGraphItemAttribute(edgeProps, key: "name", defaultValue: "edge"),
Type = GetGraphItemAttribute(edgeProps, key: "type", defaultValue: "next"),
Purpose = GetGraphItemAttribute(edgeProps, key: "purpose", defaultValue: ""),
Alias = GetGraphItemAttribute(edgeProps, key: "alias", defaultValue: ""),
Description = GetGraphItemAttribute(edgeProps, key: "description", defaultValue: ""),
Weight = edgeWeight,
Config = GetConfig(edgeProps)
Expand Down
Loading