Skip to content

Commit bd3e90c

Browse files
author
Jicheng Lu
committed
Merge branch 'master' of https://github.com/SciSharp/BotSharp into features/integrate-response-api
2 parents 1386e03 + f89a155 commit bd3e90c

22 files changed

Lines changed: 1112 additions & 166 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace BotSharp.Abstraction.Rules.Constants;
2+
3+
public static class RuleConstant
4+
{
5+
public const int MAX_GRAPH_RECURSION = 1000;
6+
7+
public const string INPUT_SCHEMA_KEY = "input_schema";
8+
public const string OUTPUT_SCHEMA_KEY = "output_schema";
9+
10+
public static IEnumerable<string> CONDITION_NODE_TYPES = new List<string>
11+
{
12+
"condition",
13+
"criteria"
14+
};
15+
16+
public static IEnumerable<string> ACTION_NODE_TYPES = new List<string>
17+
{
18+
"action"
19+
};
20+
21+
public static IEnumerable<string> ROOT_NODE_TYPES = new List<string>
22+
{
23+
"root",
24+
"start"
25+
};
26+
27+
public static IEnumerable<string> END_NODE_TYPES = new List<string>
28+
{
29+
"end",
30+
"terminal"
31+
};
32+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
namespace BotSharp.Core.Rules.Engines;
2+
3+
/// <summary>
4+
/// Abstraction over the data structure that drives graph traversal order.
5+
/// Stack → DFS, Queue → BFS. Swap the frontier mid-traversal to switch strategy.
6+
/// </summary>
7+
public interface IFrontier<T>
8+
{
9+
void Add(T item);
10+
T Remove();
11+
int Count { get; }
12+
13+
/// <summary>
14+
/// Drain every remaining item into <paramref name="other"/>, preserving order.
15+
/// </summary>
16+
void DrainTo(IFrontier<T> other);
17+
}
18+
19+
/// <summary>
20+
/// LIFO frontier – produces depth-first traversal.
21+
/// </summary>
22+
public sealed class StackFrontier<T> : IFrontier<T>
23+
{
24+
private readonly Stack<T> _stack = new();
25+
26+
public int Count => _stack.Count;
27+
28+
public void Add(T item) => _stack.Push(item);
29+
30+
public T Remove() => _stack.Pop();
31+
32+
public void DrainTo(IFrontier<T> other)
33+
{
34+
// Pop gives items in priority order (highest-weight first).
35+
var items = new List<T>();
36+
while (_stack.Count > 0)
37+
{
38+
items.Add(_stack.Pop());
39+
}
40+
41+
if (other is StackFrontier<T>)
42+
{
43+
items.Reverse();
44+
}
45+
46+
foreach (var item in items)
47+
{
48+
other.Add(item);
49+
}
50+
}
51+
}
52+
53+
/// <summary>
54+
/// FIFO frontier – produces breadth-first traversal.
55+
/// </summary>
56+
public sealed class QueueFrontier<T> : IFrontier<T>
57+
{
58+
private readonly Queue<T> _queue = new();
59+
60+
public int Count => _queue.Count;
61+
62+
public void Add(T item) => _queue.Enqueue(item);
63+
64+
public T Remove() => _queue.Dequeue();
65+
66+
public void DrainTo(IFrontier<T> other)
67+
{
68+
// Dequeue gives items in priority order (highest-weight first).
69+
var items = new List<T>();
70+
while (_queue.Count > 0)
71+
{
72+
items.Add(_queue.Dequeue());
73+
}
74+
75+
if (other is StackFrontier<T>)
76+
{
77+
items.Reverse();
78+
}
79+
80+
foreach (var item in items)
81+
{
82+
other.Add(item);
83+
}
84+
}
85+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace BotSharp.Abstraction.Rules;
2+
3+
public interface IRuleEnd : IRuleFlowUnit
4+
{
5+
}

src/Infrastructure/BotSharp.Abstraction/Rules/IRuleFlowUnit.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,16 @@ public interface IRuleFlowUnit
1818
/// The trigger names
1919
/// </summary>
2020
IEnumerable<string>? Triggers => null;
21+
22+
/// <summary>
23+
/// Schema describing the expected input parameters.
24+
/// Used for validating that upstream nodes produce the required fields.
25+
/// </summary>
26+
FlowUnitSchema? InputSchema => null;
27+
28+
/// <summary>
29+
/// Schema describing the output this unit produces.
30+
/// Used for validating that downstream nodes receive the expected fields.
31+
/// </summary>
32+
FlowUnitSchema? OutputSchema => null;
2133
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace BotSharp.Abstraction.Rules;
2+
3+
public interface IRuleRoot : IRuleFlowUnit
4+
{
5+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace BotSharp.Abstraction.Rules.Models;
4+
5+
/// <summary>
6+
/// Describes the input or output contract of a rule flow unit (action or condition).
7+
/// Follows a JSON Schema-like structure with "properties" and "required" fields.
8+
/// </summary>
9+
public class FlowUnitSchema
10+
{
11+
/// <summary>
12+
/// Property definitions keyed by parameter name.
13+
/// </summary>
14+
[JsonPropertyName("properties")]
15+
public Dictionary<string, FlowUnitSchemaProperty> Properties { get; set; } = [];
16+
17+
/// <summary>
18+
/// List of required property names.
19+
/// </summary>
20+
[JsonPropertyName("required")]
21+
public List<string> Required { get; set; } = [];
22+
23+
public FlowUnitSchema() { }
24+
25+
public FlowUnitSchema(
26+
Dictionary<string, FlowUnitSchemaProperty> properties,
27+
List<string>? required = null)
28+
{
29+
Properties = properties;
30+
Required = required ?? [];
31+
}
32+
}
33+
34+
/// <summary>
35+
/// Describes a single property in a FlowUnitSchema.
36+
/// </summary>
37+
public class FlowUnitSchemaProperty
38+
{
39+
/// <summary>
40+
/// JSON type: "string", "number", "boolean", "object", "array"
41+
/// </summary>
42+
[JsonPropertyName("type")]
43+
public string Type { get; set; } = "string";
44+
45+
/// <summary>
46+
/// A brief explanation of the property's purpose.
47+
/// </summary>
48+
[JsonPropertyName("description")]
49+
public string? Description { get; set; }
50+
51+
public FlowUnitSchemaProperty() { }
52+
53+
public FlowUnitSchemaProperty(string type, string? description = null)
54+
{
55+
Type = type;
56+
Description = description;
57+
}
58+
}

src/Infrastructure/BotSharp.Abstraction/Rules/Options/RuleFlowOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ public class RuleFlowOptions
2020
[JsonPropertyName("traversal_algorithm")]
2121
public string TraversalAlgorithm { get; set; } = "dfs";
2222

23+
/// <summary>
24+
/// Whether to skip validation when loading the graph
25+
/// </summary>
26+
[JsonPropertyName("skip_validation")]
27+
public bool SkipValidation { get; set; }
28+
29+
/// <summary>
30+
/// Maximum number of nodes to visit
31+
/// </summary>
32+
[JsonPropertyName("max_recursion")]
33+
public int? MaxRecursion { get; set; }
34+
2335
/// <summary>
2436
/// Additional custom parameters, e.g., root_node_name, max_recursion
2537
/// </summary>

src/Infrastructure/BotSharp.Abstraction/Rules/RuleGraph.cs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using BotSharp.Abstraction.Rules.Models;
2+
13
namespace BotSharp.Abstraction.Rules;
24

35
public class RuleGraph
@@ -119,20 +121,24 @@ public void AddEdge(RuleNode from, RuleNode to, EdgeItemPayload payload)
119121
}
120122
}
121123

122-
public IEnumerable<(RuleNode, RuleEdge)> GetParentNodes(RuleNode node)
124+
public IEnumerable<(RuleNode, RuleEdge)> GetParentNodes(RuleNode node, bool ascending = false)
123125
{
124-
return _edges.Where(e => e.To != null && e.To.Id.IsEqualTo(node.Id))
125-
.OrderByDescending(e => e.Weight)
126-
.Select(e => (e.From, e))
127-
.ToList();
126+
var filtered = _edges.Where(e => e.To != null && e.To.Id.IsEqualTo(node.Id));
127+
var ordered = ascending
128+
? filtered.OrderBy(e => e.Weight)
129+
: filtered.OrderByDescending(e => e.Weight);
130+
131+
return ordered.Select(e => (e.From, e)).ToList();
128132
}
129133

130-
public IEnumerable<(RuleNode, RuleEdge)> GetChildrenNodes(RuleNode node)
134+
public IEnumerable<(RuleNode, RuleEdge)> GetChildrenNodes(RuleNode node, bool ascending = false)
131135
{
132-
return _edges.Where(e => e.From != null && e.From.Id.IsEqualTo(node.Id))
133-
.OrderByDescending(e => e.Weight)
134-
.Select(e => (e.To, e))
135-
.ToList();
136+
var filtered = _edges.Where(e => e.From != null && e.From.Id.IsEqualTo(node.Id));
137+
var ordered = ascending
138+
? filtered.OrderBy(e => e.Weight)
139+
: filtered.OrderByDescending(e => e.Weight);
140+
141+
return ordered.Select(e => (e.To, e)).ToList();
136142
}
137143

138144
public RuleGraphInfo GetGraphInfo()
@@ -173,6 +179,20 @@ public class RuleNode : GraphItem
173179
/// </summary>
174180
public override string Type { get; set; } = "action";
175181

182+
/// <summary>
183+
/// Input schema loaded from node config. Overrides the code-defined schema on IRuleFlowUnit.
184+
/// </summary>
185+
[JsonPropertyName("input_schema")]
186+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
187+
public FlowUnitSchema? InputSchema { get; set; }
188+
189+
/// <summary>
190+
/// Output schema loaded from node config. Overrides the code-defined schema on IRuleFlowUnit.
191+
/// </summary>
192+
[JsonPropertyName("output_schema")]
193+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
194+
public FlowUnitSchema? OutputSchema { get; set; }
195+
176196
public override string ToString()
177197
{
178198
return $"Node ({Id}): {Name} ({Type} => {Description})";

src/Infrastructure/BotSharp.Core.Rules/Actions/ChatAction.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace BotSharp.Core.Rules.Actions;
22

3-
public sealed class ChatAction : IRuleAction
3+
public class ChatAction : IRuleAction
44
{
55
private readonly IServiceProvider _services;
66
private readonly ILogger<ChatAction> _logger;
@@ -15,6 +15,17 @@ public ChatAction(
1515

1616
public string Name => "send_message_to_agent";
1717

18+
public FlowUnitSchema? InputSchema => new();
19+
20+
public FlowUnitSchema? OutputSchema => new(
21+
properties: new()
22+
{
23+
["agent_id"] = new("string", "The agent ID"),
24+
["conversation_id"] = new("string", "The created conversation ID")
25+
},
26+
required: ["agent_id", "conversation_id"]
27+
);
28+
1829
public async Task<RuleNodeResult> ExecuteAsync(
1930
Agent agent,
2031
IRuleTrigger trigger,

src/Infrastructure/BotSharp.Core.Rules/Actions/HttpRequestAction.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace BotSharp.Core.Rules.Actions;
66

7-
public sealed class HttpRequestAction : IRuleAction
7+
public class HttpRequestAction : IRuleAction
88
{
99
private readonly IServiceProvider _services;
1010
private readonly ILogger<HttpRequestAction> _logger;
@@ -22,11 +22,26 @@ public HttpRequestAction(
2222

2323
public string Name => "http_request";
2424

25-
// Default configuration example:
26-
// {
27-
// "http_url": "https://dummy.example.com/api/v1/employees",
28-
// "http_method": "GET"
29-
// }
25+
public FlowUnitSchema? InputSchema => new(
26+
properties: new()
27+
{
28+
["http_url"] = new("string", "The HTTP URL to request"),
29+
["http_method"] = new("string", "HTTP method: GET, POST, PUT, DELETE, PATCH"),
30+
["http_query_params"] = new("array", "Query parameters as key-value pairs"),
31+
["http_request_headers"] = new("array", "Request headers as key-value pairs"),
32+
["http_request_body"] = new("string", "Request body (JSON string)")
33+
},
34+
required: ["http_url", "http_method"]
35+
);
36+
37+
public FlowUnitSchema? OutputSchema => new(
38+
properties: new()
39+
{
40+
["http_response"] = new("string", "The HTTP response body"),
41+
["http_response_headers"] = new("string", "The HTTP response headers as JSON")
42+
},
43+
required: ["http_response", "http_response_headers"]
44+
);
3045

3146
public async Task<RuleNodeResult> ExecuteAsync(
3247
Agent agent,

0 commit comments

Comments
 (0)