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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace BotSharp.Abstraction.Agents;

public interface IInstructionResolver
{
string Name { get; }
string Provider { get; }

Task<string> ResolveAsync(Agent agent, string instruction, IEnumerable<object?> args, IDictionary<string, object?> kwArgs)
=> Task.FromResult(instruction);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
using BotSharp.Abstraction.Rules.Models;
using BotSharp.Abstraction.Graph.Models;

namespace BotSharp.Abstraction.Rules;

public class RuleGraph
public class FlowGraph
{
private string _id = Guid.NewGuid().ToString();
private List<RuleNode> _nodes = [];
private List<RuleEdge> _edges = [];
private List<FlowNode> _nodes = [];
private List<FlowEdge> _edges = [];

public RuleGraph()
public FlowGraph()
{
_id = Guid.NewGuid().ToString();
_nodes = [];
_edges = [];
}

public static RuleGraph Init()
public static FlowGraph Init()
{
return new RuleGraph();
return new FlowGraph();
}

public RuleNode? GetRootNode(string? name = null)
public FlowNode? GetRootNode(string? name = null)
{
if (!string.IsNullOrEmpty(name))
{
Expand All @@ -30,7 +30,7 @@ public static RuleGraph Init()
return _nodes.FirstOrDefault(x => x.Type.IsEqualTo("root") || x.Type.IsEqualTo("start"));
}

public (RuleNode? Node, IEnumerable<RuleEdge> IncomingEdges, IEnumerable<RuleEdge> OutgoingEdges) GetNode(string id)
public (FlowNode? Node, IEnumerable<FlowEdge> IncomingEdges, IEnumerable<FlowEdge> OutgoingEdges) GetNode(string id)
{
var node = _nodes.FirstOrDefault(x => x.Id.IsEqualTo(id));
if (node == null)
Expand All @@ -55,12 +55,12 @@ public string GetGraphId()
return _id;
}

public IEnumerable<RuleNode> GetNodes(Func<RuleNode, bool>? filter = null)
public IEnumerable<FlowNode> GetNodes(Func<FlowNode, bool>? filter = null)
{
return filter == null ? [.. _nodes] : [.. _nodes.Where(filter)];
}

public IEnumerable<RuleEdge> GetEdges(Func<RuleEdge, bool>? filter = null)
public IEnumerable<FlowEdge> GetEdges(Func<FlowEdge, bool>? filter = null)
{
return filter == null ? [.. _edges] : [.. _edges.Where(filter)];
}
Expand All @@ -70,17 +70,17 @@ public void SetGraphId(string id)
_id = id;
}

public void SetNodes(IEnumerable<RuleNode> nodes)
public void SetNodes(IEnumerable<FlowNode> nodes)
{
_nodes = [.. nodes?.ToList() ?? []];
}

public void SetEdges(IEnumerable<RuleEdge> edges)
public void SetEdges(IEnumerable<FlowEdge> edges)
{
_edges = [.. edges?.ToList() ?? []];
}

public void AddNode(RuleNode node)
public void AddNode(FlowNode node)
{
var found = _nodes.Exists(x => x.Id.IsEqualTo(node.Id));
if (!found)
Expand All @@ -89,7 +89,7 @@ public void AddNode(RuleNode node)
}
}

public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)
public void AddEdge(FlowNode from, FlowNode to, EdgeItemPayload payload)
{
var sourceFound = _nodes.Exists(x => x.Id.IsEqualTo(from.Id));
var targetFound = _nodes.Exists(x => x.Id.IsEqualTo(to.Id));
Expand All @@ -107,7 +107,7 @@ public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)

if (!edgeFound)
{
_edges.Add(new RuleEdge(from, to)
_edges.Add(new FlowEdge(from, to)
{
Id = payload.Id,
Name = payload.Name,
Expand All @@ -121,7 +121,7 @@ public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)
}
}

public IEnumerable<(RuleNode, RuleEdge)> GetParentNodes(RuleNode node, bool ascending = false)
public IEnumerable<(FlowNode, FlowEdge)> GetParentNodes(FlowNode node, bool ascending = false)
{
var filtered = _edges.Where(e => e.To != null && e.To.Id.IsEqualTo(node.Id));
var ordered = ascending
Expand All @@ -131,7 +131,7 @@ public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)
return ordered.Select(e => (e.From, e)).ToList();
}

public IEnumerable<(RuleNode, RuleEdge)> GetChildrenNodes(RuleNode node, bool ascending = false)
public IEnumerable<(FlowNode, FlowEdge)> GetChildrenNodes(FlowNode node, bool ascending = false)
{
var filtered = _edges.Where(e => e.From != null && e.From.Id.IsEqualTo(node.Id));
var ordered = ascending
Expand All @@ -141,7 +141,7 @@ public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)
return ordered.Select(e => (e.To, e)).ToList();
}

public RuleGraphInfo GetGraphInfo()
public FlowGraphInfo GetGraphInfo()
{
return new()
{
Expand All @@ -157,9 +157,9 @@ public void Clear()
_edges = [];
}

public static RuleGraph FromGraphInfo(RuleGraphInfo graphInfo)
public static FlowGraph FromGraphInfo(FlowGraphInfo graphInfo)
{
var graph = new RuleGraph();
var graph = new FlowGraph();
graph.SetGraphId(graphInfo.GraphId.IfNullOrEmptyAs(Guid.NewGuid().ToString())!);
graph.SetNodes(graphInfo.Nodes);
graph.SetEdges(graphInfo.Edges);
Expand All @@ -172,7 +172,7 @@ public override string ToString()
}
}

public class RuleNode : GraphItem
public class FlowNode : GraphItem
{
/// <summary>
/// Node type: root, criteria, action, etc.
Expand Down Expand Up @@ -200,22 +200,22 @@ public override string ToString()
}
}

public class RuleEdge : GraphItem
public class FlowEdge : GraphItem
{
/// <summary>
/// Edge type: is_next, etc.
/// </summary>
public override string Type { get; set; } = "next";

public RuleNode From { get; set; }
public RuleNode To { get; set; }
public FlowNode From { get; set; }
public FlowNode To { get; set; }

public RuleEdge() : base()
public FlowEdge() : base()
{

}

public RuleEdge(RuleNode from, RuleNode to) : base()
public FlowEdge(FlowNode from, FlowNode to) : base()
{
Id = Guid.NewGuid().ToString();
From = from;
Expand Down Expand Up @@ -279,11 +279,11 @@ public class EdgeItemPayload : GraphItem

}

public class RuleGraphInfo
public class FlowGraphInfo
{
[JsonPropertyName("graph_id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string GraphId { get; set; }
public IEnumerable<RuleNode> Nodes { get; set; } = [];
public IEnumerable<RuleEdge> Edges { get; set; } = [];
public IEnumerable<FlowNode> Nodes { get; set; } = [];
public IEnumerable<FlowEdge> Edges { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Text.Json.Serialization;

namespace BotSharp.Abstraction.Rules.Models;
namespace BotSharp.Abstraction.Graph.Models;

/// <summary>
/// Describes the input or output contract of a rule flow unit (action or condition).
Expand Down
141 changes: 141 additions & 0 deletions src/Infrastructure/BotSharp.Abstraction/Graph/Utils/GraphBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using BotSharp.Abstraction.Graph.Models;
using BotSharp.Abstraction.Rules;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace BotSharp.Abstraction.Graph.Utils;

public static class GraphBuilder
{
public static FlowGraph Build(GraphQueryResult result, IDictionary<string, string>? data = null)
{
var graph = FlowGraph.Init();
if (result.Values.IsNullOrEmpty())
{
return graph;
}

foreach (var item in result.Values)
{
// Try to deserialize nodes and edge from the dictionary
if (!item.TryGetValue<JsonElement>("sourceNode", out var sourceNodeElement) ||
!item.TryGetValue<JsonElement>("targetNode", out var targetNodeElement) ||
!item.TryGetValue<JsonElement>("edge", out var edgeElement))
{
continue;
}

// Parse source node
var sourceNodeId = sourceNodeElement.TryGetProperty("id", out var sId) ? sId.GetString() : null;
var sourceNodeLabels = sourceNodeElement.TryGetProperty("labels", out var sLabels)
? sLabels.EnumerateArray().Select(x => x.GetString() ?? "").ToList()
: [];
var sourceNodeProps = sourceNodeElement.TryGetProperty("properties", out var sProps)
? sProps
: default;

// Parse target node
var targetNodeId = targetNodeElement.TryGetProperty("id", out var tId) ? tId.GetString() : null;
var targetNodeLabels = targetNodeElement.TryGetProperty("labels", out var tLabels)
? tLabels.EnumerateArray().Select(x => x.GetString() ?? "").ToList()
: [];
var targetNodeProps = targetNodeElement.TryGetProperty("properties", out var tProps)
? tProps
: default;

// Parse edge
var edgeId = edgeElement.TryGetProperty("id", out var eId) ? eId.GetString() : null;
var edgeProps = edgeElement.TryGetProperty("properties", out var eProps)
? eProps
: default;

// Create source node
var sourceNode = new FlowNode()
{
Id = sourceNodeId ?? Guid.NewGuid().ToString(),
Labels = sourceNodeLabels,
Name = GetGraphItemAttribute(sourceNodeProps, key: "name", data: data),
Type = GetGraphItemAttribute(sourceNodeProps, key: "type", data: data),
Description = GetGraphItemAttribute(sourceNodeProps, key: "description", data: data),
Config = GetConfig(sourceNodeProps, data)
};

// Create target node
var targetNode = new FlowNode()
{
Id = targetNodeId ?? Guid.NewGuid().ToString(),
Labels = targetNodeLabels,
Name = GetGraphItemAttribute(targetNodeProps, key: "name", data: data),
Type = GetGraphItemAttribute(targetNodeProps, key: "type", data: data),
Description = GetGraphItemAttribute(targetNodeProps, key: "description", data: data),
Config = GetConfig(targetNodeProps, data)
};

// Create edge payload
var edgePayload = new EdgeItemPayload()
{
Id = edgeId ?? Guid.NewGuid().ToString(),
Name = GetGraphItemAttribute(edgeProps, key: "name", data: data),
Type = GetGraphItemAttribute(edgeProps, key: "type", defaultValue: "NEXT", data: data),
Description = GetGraphItemAttribute(edgeProps, key: "description", data: data),
Config = GetConfig(edgeProps, data)
};

// Add edge to graph
graph.AddEdge(sourceNode, targetNode, edgePayload);
}

return graph;
}

private static string GetGraphItemAttribute(JsonElement? properties, string key, string defaultValue = null, IDictionary<string, string>? data = null)

Check warning on line 91 in src/Infrastructure/BotSharp.Abstraction/Graph/Utils/GraphBuilder.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 91 in src/Infrastructure/BotSharp.Abstraction/Graph/Utils/GraphBuilder.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 91 in src/Infrastructure/BotSharp.Abstraction/Graph/Utils/GraphBuilder.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 91 in src/Infrastructure/BotSharp.Abstraction/Graph/Utils/GraphBuilder.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 91 in src/Infrastructure/BotSharp.Abstraction/Graph/Utils/GraphBuilder.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 91 in src/Infrastructure/BotSharp.Abstraction/Graph/Utils/GraphBuilder.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Cannot convert null literal to non-nullable reference type.
{
if (properties == null || properties.Value.ValueKind == JsonValueKind.Undefined)
{
return defaultValue;
}

if (properties.Value.TryGetProperty(key, out var name) && name.ValueKind == JsonValueKind.String)
{
return ReplacePlaceholders(name.GetString(), data) ?? defaultValue;
}

return defaultValue;
}

private static Dictionary<string, string?> GetConfig(JsonElement? properties, IDictionary<string, string>? data = null)
{
var config = new Dictionary<string, string?>();

if (properties == null || properties.Value.ValueKind == JsonValueKind.Undefined)
{
return config;
}

// Convert all properties to config dictionary
foreach (var prop in properties.Value.EnumerateObject())
{
config[prop.Name] = ReplacePlaceholders(prop.Value.ConvertToString(), data);
}

return config;
}

private static readonly Regex PlaceholderRegex = new(@"\{\{(\w+)\}\}", RegexOptions.Compiled);

private static string? ReplacePlaceholders(string? value, IDictionary<string, string>? data)
{
if (data == null || string.IsNullOrEmpty(value) || !value.Contains("{{"))
{
return value;
}

return PlaceholderRegex.Replace(value, match =>
{
var key = match.Groups[1].Value;
return data.TryGetValue(key, out var replacement) && replacement != null
? replacement
: match.Value;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using BotSharp.Abstraction.Graph;
using BotSharp.Abstraction.Hooks;
using BotSharp.Abstraction.Rules.Models;

namespace BotSharp.Abstraction.Rules.Hooks;

public interface IRuleTriggerHook : IHookBase
{
Task BeforeRuleConditionExecuting(Agent agent, RuleNode conditionNode, RuleEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context) => Task.CompletedTask;
Task AfterRuleConditionExecuted(Agent agent, RuleNode conditionNode, RuleEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context, RuleNodeResult result) => Task.CompletedTask;
Task BeforeRuleConditionExecuting(Agent agent, FlowNode conditionNode, FlowEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context) => Task.CompletedTask;
Task AfterRuleConditionExecuted(Agent agent, FlowNode conditionNode, FlowEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context, RuleNodeResult result) => Task.CompletedTask;

Task BeforeRuleActionExecuting(Agent agent, RuleNode actionNode, RuleEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context) => Task.CompletedTask;
Task AfterRuleActionExecuted(Agent agent, RuleNode actionNode, RuleEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context, RuleNodeResult result) => Task.CompletedTask;
Task BeforeRuleActionExecuting(Agent agent, FlowNode actionNode, FlowEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context) => Task.CompletedTask;
Task AfterRuleActionExecuted(Agent agent, FlowNode actionNode, FlowEdge incomingEdge, IRuleTrigger trigger, RuleFlowContext context, RuleNodeResult result) => Task.CompletedTask;
}
3 changes: 2 additions & 1 deletion src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEngine.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using BotSharp.Abstraction.Graph;
using BotSharp.Abstraction.Rules.Models;

namespace BotSharp.Abstraction.Rules;
Expand Down Expand Up @@ -25,5 +26,5 @@ Task<IEnumerable<string>> Triggered(IRuleTrigger trigger, string text, IEnumerab
/// <param name="trigger"></param>
/// <param name="options"></param>
/// <returns></returns>
Task ExecuteGraphNode(RuleNode node, RuleGraph graph, string agentId, IRuleTrigger trigger, RuleNodeExecutionOptions options);
Task ExecuteGraphNode(FlowNode node, FlowGraph graph, string agentId, IRuleTrigger trigger, RuleNodeExecutionOptions options);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using BotSharp.Abstraction.Rules.Models;
using BotSharp.Abstraction.Graph.Models;

namespace BotSharp.Abstraction.Rules;

Expand Down
Loading
Loading