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
@@ -0,0 +1,32 @@
namespace BotSharp.Abstraction.Rules.Constants;

public static class RuleConstant
{
public const int MAX_GRAPH_RECURSION = 1000;

public const string INPUT_SCHEMA_KEY = "input_schema";
public const string OUTPUT_SCHEMA_KEY = "output_schema";

public static IEnumerable<string> CONDITION_NODE_TYPES = new List<string>
{
"condition",
"criteria"
};

public static IEnumerable<string> ACTION_NODE_TYPES = new List<string>
{
"action"
};

public static IEnumerable<string> ROOT_NODE_TYPES = new List<string>
{
"root",
"start"
};

public static IEnumerable<string> END_NODE_TYPES = new List<string>
{
"end",
"terminal"
};
}
66 changes: 66 additions & 0 deletions src/Infrastructure/BotSharp.Abstraction/Rules/Frontier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace BotSharp.Core.Rules.Engines;

/// <summary>
/// Abstraction over the data structure that drives graph traversal order.
/// Stack → DFS, Queue → BFS. Swap the frontier mid-traversal to switch strategy.
/// </summary>
public interface IFrontier<T>
{
void Add(T item);
T Remove();
int Count { get; }

/// <summary>
/// Drain every remaining item into <paramref name="other"/>, preserving order.
/// </summary>
void DrainTo(IFrontier<T> other);
}

/// <summary>
/// LIFO frontier – produces depth-first traversal.
/// </summary>
public sealed class StackFrontier<T> : IFrontier<T>
{
private readonly Stack<T> _stack = new();

public int Count => _stack.Count;

public void Add(T item) => _stack.Push(item);

public T Remove() => _stack.Pop();

public void DrainTo(IFrontier<T> other)
{
// Reverse so the item that was on top is added first and
// therefore ends up at the same "priority" position in the target.
var items = _stack.ToList();
items.Reverse();
_stack.Clear();
foreach (var item in items)
{
other.Add(item);
}
}
}

/// <summary>
/// FIFO frontier – produces breadth-first traversal.
/// </summary>
public sealed class QueueFrontier<T> : IFrontier<T>
{
private readonly Queue<T> _queue = new();

public int Count => _queue.Count;

public void Add(T item) => _queue.Enqueue(item);

public T Remove() => _queue.Dequeue();

public void DrainTo(IFrontier<T> other)
{
while (_queue.Count > 0)
{
other.Add(_queue.Dequeue());
}
}
}
5 changes: 5 additions & 0 deletions src/Infrastructure/BotSharp.Abstraction/Rules/IRuleEnd.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace BotSharp.Abstraction.Rules;

public interface IRuleEnd : IRuleFlowUnit
{
}
12 changes: 12 additions & 0 deletions src/Infrastructure/BotSharp.Abstraction/Rules/IRuleFlowUnit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,16 @@ public interface IRuleFlowUnit
/// The trigger names
/// </summary>
IEnumerable<string>? Triggers => null;

/// <summary>
/// Schema describing the expected input parameters.
/// Used for validating that upstream nodes produce the required fields.
/// </summary>
FlowUnitSchema? InputSchema => null;

/// <summary>
/// Schema describing the output this unit produces.
/// Used for validating that downstream nodes receive the expected fields.
/// </summary>
FlowUnitSchema? OutputSchema => null;
}
5 changes: 5 additions & 0 deletions src/Infrastructure/BotSharp.Abstraction/Rules/IRuleRoot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace BotSharp.Abstraction.Rules;

public interface IRuleRoot : IRuleFlowUnit
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Text.Json.Serialization;

namespace BotSharp.Abstraction.Rules.Models;

/// <summary>
/// Describes the input or output contract of a rule flow unit (action or condition).
/// Follows a JSON Schema-like structure with "properties" and "required" fields.
/// </summary>
public class FlowUnitSchema
{
/// <summary>
/// Property definitions keyed by parameter name.
/// </summary>
[JsonPropertyName("properties")]
public Dictionary<string, FlowUnitSchemaProperty> Properties { get; set; } = [];

/// <summary>
/// List of required property names.
/// </summary>
[JsonPropertyName("required")]
public List<string> Required { get; set; } = [];

public FlowUnitSchema() { }

public FlowUnitSchema(
Dictionary<string, FlowUnitSchemaProperty> properties,
List<string>? required = null)
{
Properties = properties;
Required = required ?? [];
}
}

/// <summary>
/// Describes a single property in a FlowUnitSchema.
/// </summary>
public class FlowUnitSchemaProperty
{
/// <summary>
/// JSON type: "string", "number", "boolean", "object", "array"
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = "string";

/// <summary>
/// A brief explanation of the property's purpose.
/// </summary>
[JsonPropertyName("description")]
public string? Description { get; set; }

public FlowUnitSchemaProperty() { }

public FlowUnitSchemaProperty(string type, string? description = null)
{
Type = type;
Description = description;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ public class RuleFlowOptions
[JsonPropertyName("traversal_algorithm")]
public string TraversalAlgorithm { get; set; } = "dfs";

/// <summary>
/// Whether to skip validation when loading the graph
/// </summary>
[JsonPropertyName("skip_validation")]
public bool SkipValidation { get; set; }

/// <summary>
/// Maximum number of nodes to visit
/// </summary>
[JsonPropertyName("max_recursion")]
public int? MaxRecursion { get; set; }

/// <summary>
/// Additional custom parameters, e.g., root_node_name, max_recursion
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions src/Infrastructure/BotSharp.Abstraction/Rules/RuleGraph.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using BotSharp.Abstraction.Rules.Models;

namespace BotSharp.Abstraction.Rules;

public class RuleGraph
Expand Down Expand Up @@ -173,6 +175,20 @@ public class RuleNode : GraphItem
/// </summary>
public override string Type { get; set; } = "action";

/// <summary>
/// Input schema loaded from node config. Overrides the code-defined schema on IRuleFlowUnit.
/// </summary>
[JsonPropertyName("input_schema")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FlowUnitSchema? InputSchema { get; set; }

/// <summary>
/// Output schema loaded from node config. Overrides the code-defined schema on IRuleFlowUnit.
/// </summary>
[JsonPropertyName("output_schema")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FlowUnitSchema? OutputSchema { get; set; }

public override string ToString()
{
return $"Node ({Id}): {Name} ({Type} => {Description})";
Expand Down
13 changes: 12 additions & 1 deletion src/Infrastructure/BotSharp.Core.Rules/Actions/ChatAction.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace BotSharp.Core.Rules.Actions;

public sealed class ChatAction : IRuleAction
public class ChatAction : IRuleAction
{
private readonly IServiceProvider _services;
private readonly ILogger<ChatAction> _logger;
Expand All @@ -15,6 +15,17 @@ public ChatAction(

public string Name => "send_message_to_agent";

public FlowUnitSchema? InputSchema => new();

public FlowUnitSchema? OutputSchema => new(
properties: new()
{
["agent_id"] = new("string", "The agent ID"),
["conversation_id"] = new("string", "The created conversation ID")
},
required: ["agent_id", "conversation_id"]
);

public async Task<RuleNodeResult> ExecuteAsync(
Agent agent,
IRuleTrigger trigger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace BotSharp.Core.Rules.Actions;

public sealed class HttpRequestAction : IRuleAction
public class HttpRequestAction : IRuleAction
{
private readonly IServiceProvider _services;
private readonly ILogger<HttpRequestAction> _logger;
Expand All @@ -22,11 +22,26 @@ public HttpRequestAction(

public string Name => "http_request";

// Default configuration example:
// {
// "http_url": "https://dummy.example.com/api/v1/employees",
// "http_method": "GET"
// }
public FlowUnitSchema? InputSchema => new(
properties: new()
{
["http_url"] = new("string", "The HTTP URL to request"),
["http_method"] = new("string", "HTTP method: GET, POST, PUT, DELETE, PATCH"),
["http_query_params"] = new("array", "Query parameters as key-value pairs"),
["http_request_headers"] = new("array", "Request headers as key-value pairs"),
["http_request_body"] = new("string", "Request body (JSON string)")
},
required: ["http_url", "http_method"]
);

public FlowUnitSchema? OutputSchema => new(
properties: new()
{
["http_response"] = new("string", "The HTTP response body"),
["http_response_headers"] = new("string", "The HTTP response headers as JSON")
},
required: ["http_response", "http_response_headers"]
);

public async Task<RuleNodeResult> ExecuteAsync(
Agent agent,
Expand Down
21 changes: 20 additions & 1 deletion src/Infrastructure/BotSharp.Core.Rules/Actions/ToolCallAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace BotSharp.Core.Rules.Actions;

public sealed class ToolCallAction : IRuleAction
public class ToolCallAction : IRuleAction
{
private readonly IServiceProvider _services;
private readonly ILogger<ToolCallAction> _logger;
Expand All @@ -17,6 +17,25 @@ public ToolCallAction(

public string Name => "tool_call";

public FlowUnitSchema? InputSchema => new(
properties: new()
{
["function_name"] = new("string", "The name of the function to call"),
["function_argument"] = new("object", "The function argument as a RoleDialogModel JSON")
},
required: ["function_name"]
);

public FlowUnitSchema? OutputSchema => new(
properties: new()
{
["function_name"] = new("string", "The executed function name"),
["function_argument"] = new("string", "The function argument as JSON string"),
["function_call_result"] = new("string", "The function call result text")
},
required: ["function_name", "function_argument", "function_call_result"]
);

public async Task<RuleNodeResult> ExecuteAsync(
Agent agent,
IRuleTrigger trigger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public AllVisitedRuleCondition(

public string Name => "all_visited";

public FlowUnitSchema? InputSchema => new();

public FlowUnitSchema? OutputSchema => new();

public async Task<RuleNodeResult> EvaluateAsync(
Agent agent,
IRuleTrigger trigger,
Expand Down
Loading
Loading