From fda4f5ad271af8b26610443f41a4428aaa1d1237 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 6 May 2026 11:44:06 -0500 Subject: [PATCH 1/2] add pgt simulate --- .../Controllers/MembaseController.cs | 47 +++++++++++ .../Interfaces/IMembaseApi.cs | 5 ++ .../Models/Requests/PgtSimulationRequest.cs | 47 +++++++++++ .../Models/Responses/PgtSimulationResponse.cs | 79 +++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtSimulationRequest.cs create mode 100644 src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs diff --git a/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs b/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs index caaf3ab39..751e152fc 100644 --- a/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs +++ b/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs @@ -448,4 +448,51 @@ public async Task DeleteEdge(string graphId, string edgeId) new { message = "An error occurred while deleting the edge.", error = ex.Message }); } } + + /// + /// Simulate a PGT definition + /// + /// The graph identifier + /// The PGT definition identifier + /// The simulation request containing start node and options + /// Simulation result with trace log and visited nodes +#if DEBUG + [AllowAnonymous] +#endif + [HttpPost("/membase/{graphId}/pgt-definitions/{definitionId}/simulate")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task SimulatePgtDefinition(string graphId, string definitionId, [FromBody] PgtSimulationRequest request) + { + if (string.IsNullOrWhiteSpace(graphId)) + { + return BadRequest("Graph ID cannot be empty."); + } + + if (string.IsNullOrWhiteSpace(definitionId)) + { + return BadRequest("Definition ID cannot be empty."); + } + + if (string.IsNullOrWhiteSpace(request?.StartId)) + { + return BadRequest("Start ID cannot be empty."); + } + + try + { + request.Options ??= new(); + request.Options.RunId = $"sim-{Guid.NewGuid()}"; + var result = await _membaseApi.SimulatePgtDefinitionAsync(graphId, definitionId, request); + result.RunId = request.Options.RunId; + return Ok(result); + } + catch (Exception ex) + { + return StatusCode( + StatusCodes.Status500InternalServerError, + new { message = "An error occurred while simulating the PGT definition.", error = ex.Message }); + } + } } diff --git a/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs b/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs index 1c294060a..36f808eb6 100644 --- a/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs +++ b/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs @@ -45,4 +45,9 @@ public interface IMembaseApi [Delete("/graph/{graphId}/edge/{edgeId}")] Task DeleteEdgeAsync(string graphId, string edgeId); #endregion + + #region PGT + [Post("/graph/{graphId}/pgt-definitions/{definitionId}/simulate")] + Task SimulatePgtDefinitionAsync(string graphId, string definitionId, [Body] PgtSimulationRequest request); + #endregion } diff --git a/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtSimulationRequest.cs b/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtSimulationRequest.cs new file mode 100644 index 000000000..d3ad02e13 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtSimulationRequest.cs @@ -0,0 +1,47 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Membase.Models; + +public class PgtSimulationRequest +{ + public string StartId { get; set; } = string.Empty; + + [JsonPropertyName("options")] + public PgtSimulationOptions? Options { get; set; } +} + +public class PgtSimulationOptions +{ + [JsonPropertyName("max_depth")] + public int? MaxDepth { get; set; } + + [JsonPropertyName("timeout_ms")] + public int? TimeoutMs { get; set; } + + [JsonPropertyName("strategy")] + public string? Strategy { get; set; } + + [JsonPropertyName("initial_context")] + public Dictionary? InitialContext { get; set; } + + [JsonPropertyName("stream")] + public bool Stream { get; set; } + + [JsonPropertyName("run_id")] + public string? RunId { get; set; } + + [JsonPropertyName("persist_run")] + public bool PersistRun { get; set; } + + [JsonPropertyName("debug")] + public bool Debug { get; set; } + + [JsonPropertyName("pause_on")] + public string[]? PauseOn { get; set; } + + [JsonPropertyName("debug_idle_timeout_ms")] + public int? DebugIdleTimeoutMs { get; set; } + + [JsonPropertyName("node_execute_hooks")] + public Dictionary? NodeExecuteHooks { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs new file mode 100644 index 000000000..997c05c09 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs @@ -0,0 +1,79 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Membase.Models; + +public class PgtSimulationResponse +{ + public string[] Columns { get; set; } = []; + public PgtSimulationDataItem[] Data { get; set; } = []; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public PgtSimulationStatistics? Statistics { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public object? ExecutionPlan { get; set; } + public CypherNotification[] Notifications { get; set; } = []; + public int RowCount { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? ExecutedAt { get; set; } + + public string RunId { get; set; } +} + +public class PgtSimulationDataItem +{ + [JsonPropertyName("final_context")] + public Dictionary? FinalContext { get; set; } + + [JsonPropertyName("visited")] + public string[] Visited { get; set; } = []; + + [JsonPropertyName("trace_log")] + public PgtTraceLogEntry[] TraceLog { get; set; } = []; + + [JsonPropertyName("halted")] + public string Halted { get; set; } = string.Empty; + + [JsonPropertyName("halt_reason")] + public string HaltReason { get; set; } = string.Empty; +} + +public class PgtTraceLogEntry +{ + public string Event { get; set; } = string.Empty; + + [JsonPropertyName("node_id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? NodeId { get; set; } + + [JsonPropertyName("edge_id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? EdgeId { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Source { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Target { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Type { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Allowed { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? Depth { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? Output { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? Input { get; set; } +} + +public class PgtSimulationStatistics +{ + public long ExecutionTimeMs { get; set; } +} From 9a66edf71663dba942ff2494e1e8d996ad3d885f Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Tue, 19 May 2026 21:40:56 -0500 Subject: [PATCH 2/2] integrate membase pgt --- .../Controllers/MembaseController.cs | 101 +++++++++++++++++- .../Interfaces/IMembaseApi.cs | 6 ++ .../Models/Requests/PgtTraversalRequest.cs | 80 ++++++++++++++ .../Models/Requests/PgtValidationRequest.cs | 47 ++++++++ .../Models/Responses/PgtSimulationResponse.cs | 4 +- .../Models/Responses/PgtTraversalResponse.cs | 40 +++++++ .../Models/Responses/PgtValidationResponse.cs | 35 ++++++ 7 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtTraversalRequest.cs create mode 100644 src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtValidationRequest.cs create mode 100644 src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtTraversalResponse.cs create mode 100644 src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtValidationResponse.cs diff --git a/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs b/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs index 751e152fc..8e9cf1b22 100644 --- a/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs +++ b/src/Plugins/BotSharp.Plugin.Membase/Controllers/MembaseController.cs @@ -483,7 +483,11 @@ public async Task SimulatePgtDefinition(string graphId, string de try { request.Options ??= new(); - request.Options.RunId = $"sim-{Guid.NewGuid()}"; + if (string.IsNullOrEmpty(request.Options.RunId)) + { + request.Options.RunId = $"sim-{Guid.NewGuid()}"; + } + var result = await _membaseApi.SimulatePgtDefinitionAsync(graphId, definitionId, request); result.RunId = request.Options.RunId; return Ok(result); @@ -495,4 +499,99 @@ public async Task SimulatePgtDefinition(string graphId, string de new { message = "An error occurred while simulating the PGT definition.", error = ex.Message }); } } + + /// + /// Traverse a PGT definition + /// + /// The graph identifier + /// The PGT definition identifier + /// The traversal request containing start node and options + /// Traversal result with context, visited nodes, and trace log +#if DEBUG + [AllowAnonymous] +#endif + [HttpPost("/membase/{graphId}/pgt-definitions/{definitionId}/traverse")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task TraversePgtDefinition(string graphId, string definitionId, [FromBody] PgtTraversalRequest request) + { + if (string.IsNullOrWhiteSpace(graphId)) + { + return BadRequest("Graph ID cannot be empty."); + } + + if (string.IsNullOrWhiteSpace(definitionId)) + { + return BadRequest("Definition ID cannot be empty."); + } + + if (string.IsNullOrWhiteSpace(request?.StartId)) + { + return BadRequest("Start ID cannot be empty."); + } + + try + { + request.Options ??= new(); + if (string.IsNullOrEmpty(request.Options.RunId)) + { + request.Options.RunId = $"trv-{Guid.NewGuid()}"; + } + + var result = await _membaseApi.TraversePgtDefinitionAsync(graphId, definitionId, request); + result.RunId = request.Options.RunId; + return Ok(result); + } + catch (Exception ex) + { + return StatusCode( + StatusCodes.Status500InternalServerError, + new { message = "An error occurred while traversing the PGT definition.", error = ex.Message }); + } + } + + /// + /// Validate a PGT definition + /// + /// The graph identifier + /// The PGT definition identifier + /// The validation request containing start node and options + /// Validation result with valid flag, errors, warnings, and stats +#if DEBUG + [AllowAnonymous] +#endif + [HttpPost("/membase/{graphId}/pgt-definitions/{definitionId}/validate")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task ValidatePgtDefinition(string graphId, string definitionId, [FromBody] PgtValidationRequest request) + { + if (string.IsNullOrWhiteSpace(graphId)) + { + return BadRequest("Graph ID cannot be empty."); + } + + if (string.IsNullOrWhiteSpace(definitionId)) + { + return BadRequest("Definition ID cannot be empty."); + } + + if (string.IsNullOrWhiteSpace(request?.StartId)) + { + return BadRequest("Start ID cannot be empty."); + } + + try + { + var result = await _membaseApi.ValidatePgtDefinitionAsync(graphId, definitionId, request); + return Ok(result); + } + catch (Exception ex) + { + return StatusCode( + StatusCodes.Status500InternalServerError, + new { message = "An error occurred while validating the PGT definition.", error = ex.Message }); + } + } } diff --git a/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs b/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs index 36f808eb6..290cee488 100644 --- a/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs +++ b/src/Plugins/BotSharp.Plugin.Membase/Interfaces/IMembaseApi.cs @@ -49,5 +49,11 @@ public interface IMembaseApi #region PGT [Post("/graph/{graphId}/pgt-definitions/{definitionId}/simulate")] Task SimulatePgtDefinitionAsync(string graphId, string definitionId, [Body] PgtSimulationRequest request); + + [Post("/graph/{graphId}/pgt-definitions/{definitionId}/traverse")] + Task TraversePgtDefinitionAsync(string graphId, string definitionId, [Body] PgtTraversalRequest request); + + [Post("/graph/{graphId}/pgt-definitions/{definitionId}/validate")] + Task ValidatePgtDefinitionAsync(string graphId, string definitionId, [Body] PgtValidationRequest request); #endregion } diff --git a/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtTraversalRequest.cs b/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtTraversalRequest.cs new file mode 100644 index 000000000..ba804f6de --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtTraversalRequest.cs @@ -0,0 +1,80 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Membase.Models; + +public class PgtTraversalRequest +{ + public string StartId { get; set; } = string.Empty; + + [JsonPropertyName("options")] + public PgtTraversalOptions? Options { get; set; } +} + +public class PgtTraversalOptions +{ + [JsonPropertyName("max_depth")] + public int? MaxDepth { get; set; } + + [JsonPropertyName("max_subgraph_nesting")] + public int? MaxSubGrapNesting { get; set; } + + [JsonPropertyName("max_visits_per_node")] + public int? MaxVisitsPerNode { get; set; } + + [JsonPropertyName("strategy")] + public string? Strategy { get; set; } + + [JsonPropertyName("timeout_ms")] + public int? TimeoutMs { get; set; } + + [JsonPropertyName("node_validation_hooks")] + public Dictionary? NodeValidationHooks { get; set; } + + [JsonPropertyName("edge_validation_hooks")] + public Dictionary? EdgeValidationHooks { get; set; } + + [JsonPropertyName("edge_evaluate_hooks")] + public Dictionary? EdgeEvaluateHooks { get; set; } + + [JsonPropertyName("node_execute_hooks")] + public Dictionary? NodeExecuteHooks { get; set; } + + [JsonPropertyName("traits")] + public Dictionary? Traits { get; set; } + + [JsonPropertyName("interfaces")] + public Dictionary? Interfaces { get; set; } + + [JsonPropertyName("actors")] + public Dictionary? Actors { get; set; } + + [JsonPropertyName("initial_context")] + public Dictionary? InitialContext { get; set; } + + [JsonPropertyName("environment")] + public Dictionary? Environment { get; set; } + + [JsonPropertyName("functions")] + public Dictionary? Functions { get; set; } + + [JsonPropertyName("record_trace")] + public bool RecordTrace { get; set; } + + [JsonPropertyName("stream")] + public bool Stream { get; set; } + + [JsonPropertyName("persist_run")] + public bool PersistRun { get; set; } + + [JsonPropertyName("debug")] + public bool Debug { get; set; } + + [JsonPropertyName("pause_on")] + public string[]? PauseOn { get; set; } + + [JsonPropertyName("debug_idle_timeout_ms")] + public int? DebugIdleTimeoutMs { get; set; } + + [JsonPropertyName("run_id")] + public string? RunId { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtValidationRequest.cs b/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtValidationRequest.cs new file mode 100644 index 000000000..4c8b84d6e --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Membase/Models/Requests/PgtValidationRequest.cs @@ -0,0 +1,47 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Membase.Models; + +public class PgtValidationRequest +{ + public string StartId { get; set; } = string.Empty; + + [JsonPropertyName("options")] + public PgtValidationOptions? Options { get; set; } +} + +public class PgtValidationOptions +{ + [JsonPropertyName("max_depth")] + public int? MaxDepth { get; set; } + + [JsonPropertyName("max_nodes")] + public int? MaxNodes { get; set; } + + [JsonPropertyName("allow_cycles")] + public bool? AllowCycles { get; set; } + + [JsonPropertyName("target_node_ids")] + public string[]? TargetNodeIds { get; set; } + + [JsonPropertyName("edge_types")] + public string[]? EdgeTypes { get; set; } + + [JsonPropertyName("node_validation_hooks")] + public Dictionary? NodeValidationHooks { get; set; } + + [JsonPropertyName("edge_validation_hooks")] + public Dictionary? EdgeValidationHooks { get; set; } + + [JsonPropertyName("traits")] + public Dictionary? Traits { get; set; } + + [JsonPropertyName("interfaces")] + public Dictionary? Interfaces { get; set; } + + [JsonPropertyName("actors")] + public Dictionary? Actors { get; set; } + + [JsonPropertyName("initial_context")] + public Dictionary? InitialContext { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs index 997c05c09..7a689fd9f 100644 --- a/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs +++ b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtSimulationResponse.cs @@ -8,7 +8,7 @@ public class PgtSimulationResponse public PgtSimulationDataItem[] Data { get; set; } = []; [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public PgtSimulationStatistics? Statistics { get; set; } + public PgtStatistics? Statistics { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object? ExecutionPlan { get; set; } @@ -73,7 +73,7 @@ public class PgtTraceLogEntry public Dictionary? Input { get; set; } } -public class PgtSimulationStatistics +public class PgtStatistics { public long ExecutionTimeMs { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtTraversalResponse.cs b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtTraversalResponse.cs new file mode 100644 index 000000000..e4a1a86b5 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtTraversalResponse.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Membase.Models; + +public class PgtTraversalResponse +{ + public string[] Columns { get; set; } = []; + public PgtTraversalDataItem[] Data { get; set; } = []; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public PgtStatistics? Statistics { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public object? ExecutionPlan { get; set; } + public CypherNotification[] Notifications { get; set; } = []; + public int RowCount { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? ExecutedAt { get; set; } + + public string RunId { get; set; } +} + +public class PgtTraversalDataItem +{ + [JsonPropertyName("final_context")] + public Dictionary? FinalContext { get; set; } + + [JsonPropertyName("visited")] + public string[] Visited { get; set; } = []; + + [JsonPropertyName("trace_log")] + public PgtTraceLogEntry[] TraceLog { get; set; } = []; + + [JsonPropertyName("halted")] + public string Halted { get; set; } = string.Empty; + + [JsonPropertyName("halt_reason")] + public string HaltReason { get; set; } = string.Empty; +} diff --git a/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtValidationResponse.cs b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtValidationResponse.cs new file mode 100644 index 000000000..71e952a58 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Membase/Models/Responses/PgtValidationResponse.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Membase.Models; + +public class PgtValidationResponse +{ + public string[] Columns { get; set; } = []; + public PgtValidationDataItem[] Data { get; set; } = []; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public PgtStatistics? Statistics { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public object? ExecutionPlan { get; set; } + public CypherNotification[] Notifications { get; set; } = []; + public int RowCount { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? ExecutedAt { get; set; } +} + +public class PgtValidationDataItem +{ + [JsonPropertyName("valid")] + public bool Valid { get; set; } + + [JsonPropertyName("errors")] + public object[] Errors { get; set; } = []; + + [JsonPropertyName("warnings")] + public object[] Warnings { get; set; } = []; + + [JsonPropertyName("stats")] + public Dictionary? Stats { get; set; } +}