diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
index e94cf839..474dcf56 100644
--- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
+++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
@@ -32,6 +32,7 @@
+
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
index 53d72520..b50f2366 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
@@ -1,5 +1,8 @@
+using System.Diagnostics;
+
namespace BotSharp.Abstraction.Browsing.Models;
+[DebuggerStepThrough]
public class ElementLocatingArgs
{
[JsonPropertyName("match_rule")]
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
index aa4154ff..0964389b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
@@ -7,7 +7,7 @@ public class PageActionArgs
public BroswerActionEnum Action { get; set; }
public string? Content { get; set; }
- public string? Direction { get; set; }
+ public string Direction { get; set; } = "down";
public string Url { get; set; } = null!;
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
index 1d03b0b7..14932118 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
@@ -7,4 +7,9 @@ public class WebPageResponseData
public string ResponseData { get; set; } = null!;
public bool ResponseInMemory { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ public override string ToString()
+ {
+ return $"{Url} {ResponseData.Length}";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
index cd97a01d..54951115 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
@@ -1,7 +1,20 @@
+using System.Diagnostics;
+
namespace BotSharp.Abstraction.Browsing.Models;
+[DebuggerStepThrough]
public class WebPageResponseFilter
{
public string Url { get; set; } = null!;
public string[]? QueryParameters { get; set; }
+
+ ///
+ /// contains, starts, ends, equals
+ ///
+ public string UrlMatchPattern { get; set; } = "contains";
+
+ ///
+ /// Handle Content-Type: text/x-component
+ ///
+ public Func? PartSearch { get; set; } = null;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
index 85f41e57..ffb4986a 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
@@ -12,6 +12,7 @@ public interface IConversationService
Task GetConversation(string id);
Task> GetConversations(ConversationFilter filter);
Task UpdateConversationTitle(string id, string title);
+ Task UpdateConversationTags(string conversationId, List tags);
Task UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
Task> GetLastConversations();
Task> GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable excludeAgentIds);
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
index 890f3211..38734d75 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
@@ -15,10 +15,10 @@ public class Conversation
public string Title { get; set; } = string.Empty;
[JsonIgnore]
- public List Dialogs { get; set; } = new List();
+ public List Dialogs { get; set; } = new();
[JsonIgnore]
- public Dictionary States { get; set; } = new Dictionary();
+ public Dictionary States { get; set; } = new();
public string Status { get; set; } = ConversationStatus.Open;
@@ -26,6 +26,8 @@ public class Conversation
public int DialogCount { get; set; }
+ public List Tags { get; set; } = new();
+
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs
index 0669a267..e6a659cc 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs
@@ -4,5 +4,23 @@ namespace BotSharp.Abstraction.Instructs;
public interface IInstructService
{
+ ///
+ /// Execute completion by using specified instruction or template
+ ///
+ /// Agent (static agent)
+ /// Additional message provided by user
+ /// Template name
+ /// System prompt
+ ///
Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null);
+
+ ///
+ /// A generic way to execute completion by using specified instruction or template
+ ///
+ ///
+ /// Prompt
+ /// Agent id
+ /// Llm Provider, model, message, prompt data
+ ///
+ Task Instruct(string instruction, string agentId, InstructOptions options) where T : class;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs
new file mode 100644
index 00000000..46c6c900
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs
@@ -0,0 +1,29 @@
+namespace BotSharp.Abstraction.Instructs.Models;
+
+public class InstructOptions
+{
+ ///
+ /// Llm provider
+ ///
+ public string Provider { get; set; } = null!;
+
+ ///
+ /// Llm model
+ ///
+ public string Model { get; set; } = null!;
+
+ ///
+ /// Conversation id. When this field is not null, it will get dialogs from conversation.
+ ///
+ public string? ConversationId { get; set; }
+
+ ///
+ /// The single message. It can be append to the whole dialogs or sent alone.
+ ///
+ public string? Message { get; set; }
+
+ ///
+ /// Data to fill in prompt
+ ///
+ public Dictionary Data { get; set; } = new();
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
index 12864ac3..6ebedc5b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs
@@ -22,5 +22,7 @@ public class ConversationFilter
///
/// Check whether each key in the list is in the conversation states and its value equals to target value if not empty
///
- public IEnumerable States { get; set; } = new List();
+ public IEnumerable? States { get; set; } = [];
+
+ public IEnumerable? Tags { get; set; } = [];
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
index dd2648d8..1471093b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
@@ -72,6 +72,7 @@ public interface IBotSharpRepository
Conversation GetConversation(string conversationId);
PagedItems GetConversations(ConversationFilter filter);
void UpdateConversationTitle(string conversationId, string title);
+ bool UpdateConversationTags(string conversationId, List tags);
bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
index afb0e40e..36b07f11 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
@@ -1,15 +1,60 @@
using BotSharp.Abstraction.Users.Models;
+using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace BotSharp.Abstraction.Users;
public interface IAuthenticationHook
{
+ ///
+ /// Interupt the authentication process, and return the user object if the user is authenticated
+ ///
+ ///
+ ///
+ ///
Task Authenticate(string id, string password);
- void AddClaims(List claims);
- void BeforeSending(Token token);
+
+ ///
+ /// Add extra claims to user
+ ///
+ ///
+ ///
+ bool AddClaims(List claims)
+ => true;
+
+ ///
+ /// User authenticated successfully
+ ///
+ ///
+ ///
+ bool UserAuthenticated(JwtSecurityToken token)
+ => true;
+
+ ///
+ /// Bfore user updating
+ ///
+ ///
+ ///
Task UserUpdating(User user);
+
+ ///
+ /// After user created
+ ///
+ ///
+ ///
Task UserCreated(User user);
+
+ ///
+ /// Reset password
+ ///
+ ///
+ ///
Task VerificationCodeResetPassword(User user);
+
+ ///
+ /// Delete users
+ ///
+ ///
+ ///
Task DelUsers(List userIds);
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index 59ce88c2..ba739dac 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -40,10 +40,10 @@ public partial class ConversationService
routing.Context.Push(agent.Id, reason: "request started");
// Save payload in order to assign the payload before hook is invoked
- if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
- {
- message.Payload = replyMessage.Payload;
- }
+ // if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
+ // {
+ // message.Payload = replyMessage.Payload;
+ // }
// Before chat completion hook
hooks = ReOrderConversationHooks(hooks);
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
index 4d3ed7af..97b69d44 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
@@ -51,6 +51,12 @@ public partial class ConversationService : IConversationService
return conversation;
}
+ public async Task UpdateConversationTags(string conversationId, List tags)
+ {
+ var db = _services.GetRequiredService();
+ return db.UpdateConversationTags(conversationId, tags);
+ }
+
public async Task UpdateConversationMessage(string conversationId, UpdateMessageRequest request)
{
var db = _services.GetRequiredService();
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
index d74f71ac..7a3d1d5d 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
@@ -372,10 +372,10 @@ public class ConversationStateService : IConversationStateService, IDisposable
private bool CheckArgType(string name, string value)
{
var agentTypes = AgentService.AgentParameterTypes.SelectMany(p => p.Value).ToList();
- var filed = agentTypes.FirstOrDefault(t => t.Key == name);
- if (filed.Key != null)
+ var found = agentTypes.FirstOrDefault(t => t.Key == name);
+ if (found.Key != null)
{
- return filed.Value switch
+ return found.Value switch
{
"boolean" => bool.TryParse(value, out _),
"number" => long.TryParse(value, out _),
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Execute.cs
new file mode 100644
index 00000000..1d9dd1d0
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Execute.cs
@@ -0,0 +1,100 @@
+using BotSharp.Abstraction.Instructs;
+using BotSharp.Abstraction.Instructs.Models;
+using BotSharp.Abstraction.MLTasks;
+
+namespace BotSharp.Core.Instructs;
+
+public partial class InstructService
+{
+ public async Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
+ {
+ var agentService = _services.GetRequiredService();
+ Agent agent = await agentService.LoadAgent(agentId);
+
+ if (agent.Disabled)
+ {
+ var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
+ return new InstructResult
+ {
+ MessageId = message.MessageId,
+ Text = content
+ };
+ }
+
+ // Trigger before completion hooks
+ var hooks = _services.GetServices();
+ foreach (var hook in hooks)
+ {
+ if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
+ {
+ continue;
+ }
+
+ await hook.BeforeCompletion(agent, message);
+
+ // Interrupted by hook
+ if (message.StopCompletion)
+ {
+ return new InstructResult
+ {
+ MessageId = message.MessageId,
+ Text = message.Content
+ };
+ }
+ }
+
+ // Render prompt
+ var prompt = string.IsNullOrEmpty(templateName) ?
+ agentService.RenderedInstruction(agent) :
+ agentService.RenderedTemplate(agent, templateName);
+
+ var completer = CompletionProvider.GetCompletion(_services,
+ agentConfig: agent.LlmConfig);
+
+ var response = new InstructResult
+ {
+ MessageId = message.MessageId
+ };
+ if (completer is ITextCompletion textCompleter)
+ {
+ var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId);
+ response.Text = result;
+ }
+ else if (completer is IChatCompletion chatCompleter)
+ {
+ if (instruction == "#TEMPLATE#")
+ {
+ instruction = prompt;
+ prompt = message.Content;
+ }
+
+ var result = await chatCompleter.GetChatCompletions(new Agent
+ {
+ Id = agentId,
+ Name = agent.Name,
+ Instruction = instruction
+ }, new List
+ {
+ new RoleDialogModel(AgentRole.User, prompt)
+ {
+ CurrentAgentId = agentId,
+ MessageId = message.MessageId
+ }
+ });
+ response.Text = result.Content;
+ }
+
+
+ foreach (var hook in hooks)
+ {
+ if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
+ {
+ continue;
+ }
+
+ await hook.AfterCompletion(agent, response);
+ }
+
+ return response;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Instruct.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Instruct.cs
new file mode 100644
index 00000000..76ad3608
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Instruct.cs
@@ -0,0 +1,109 @@
+using BotSharp.Abstraction.Instructs.Models;
+using BotSharp.Abstraction.Templating;
+using System.Collections;
+using System.Reflection;
+
+namespace BotSharp.Core.Instructs;
+
+public partial class InstructService
+{
+ public async Task Instruct(string instruction, string agentId, InstructOptions options) where T : class
+ {
+ var prompt = GetPrompt(instruction, options.Data);
+ var response = await GetAiResponse(agentId, prompt, options);
+
+ if (string.IsNullOrWhiteSpace(response.Content)) return null;
+
+ var type = typeof(T);
+ T? result = null;
+
+ try
+ {
+ if (IsStringType(type))
+ {
+ result = response.Content as T;
+ }
+ else if (IsListType(type))
+ {
+ var text = response.Content.JsonArrayContent();
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ result = JsonSerializer.Deserialize(text, _options.JsonSerializerOptions);
+ }
+ }
+ else
+ {
+ var text = response.Content.JsonContent();
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ result = JsonSerializer.Deserialize(text, _options.JsonSerializerOptions);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when getting ai response, {ex.Message}\r\n{ex.InnerException}");
+ }
+
+ return result;
+ }
+
+ private string GetPrompt(string instruction, Dictionary data)
+ {
+ var render = _services.GetRequiredService();
+
+ return render.Render(instruction, data ?? new Dictionary());
+ }
+
+ private async Task GetAiResponse(string agentId, string prompt, InstructOptions options)
+ {
+ var agentService = _services.GetRequiredService();
+ var agent = await agentService.LoadAgent(agentId);
+
+ var localAgent = new Agent
+ {
+ Id = agentId,
+ Name = agent?.Name ?? "Unknown",
+ Instruction = prompt,
+ TemplateDict = new()
+ };
+
+ var messages = BuildDialogs(options);
+ var completion = CompletionProvider.GetChatCompletion(_services, provider: options.Provider, model: options.Model);
+ return await completion.GetChatCompletions(localAgent, messages);
+ }
+
+ private List BuildDialogs(InstructOptions options)
+ {
+ var messages = new List();
+
+ if (!string.IsNullOrWhiteSpace(options.ConversationId))
+ {
+ var conv = _services.GetRequiredService();
+ var dialogs = conv.GetDialogHistory();
+ messages.AddRange(dialogs);
+ }
+
+ if (!string.IsNullOrWhiteSpace(options.Message))
+ {
+ messages.Add(new RoleDialogModel(AgentRole.User, options.Message));
+ }
+
+ return messages;
+ }
+
+ private bool IsStringType(Type? type)
+ {
+ if (type == null) return false;
+
+ return type == typeof(string);
+ }
+
+ private bool IsListType(Type? type)
+ {
+ if (type == null) return false;
+
+ var interfaces = type.GetTypeInfo().ImplementedInterfaces;
+ return type.IsArray || interfaces.Any(x => x.Name == typeof(IEnumerable).Name);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs
index b05889e1..a1a31e0e 100644
--- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs
+++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs
@@ -1,118 +1,21 @@
-using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Instructs;
-using BotSharp.Abstraction.Instructs.Models;
-using BotSharp.Abstraction.MLTasks;
+using BotSharp.Abstraction.Options;
namespace BotSharp.Core.Instructs;
public partial class InstructService : IInstructService
{
private readonly IServiceProvider _services;
- private readonly ILogger _logger;
+ private readonly BotSharpOptions _options;
+ private readonly ILogger _logger;
- public InstructService(IServiceProvider services, ILogger logger)
+ public InstructService(
+ IServiceProvider services,
+ BotSharpOptions options,
+ ILogger logger)
{
_services = services;
+ _options = options;
_logger = logger;
}
-
- ///
- /// Execute completion by using specified instruction or template
- ///
- /// Agent (static agent)
- /// Additional message provided by user
- /// Template name
- /// System prompt
- ///
- public async Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
- {
- var agentService = _services.GetRequiredService();
- Agent agent = await agentService.LoadAgent(agentId);
-
- if (agent.Disabled)
- {
- var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
- return new InstructResult
- {
- MessageId = message.MessageId,
- Text = content
- };
- }
-
- // Trigger before completion hooks
- var hooks = _services.GetServices();
- foreach (var hook in hooks)
- {
- if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
- {
- continue;
- }
-
- await hook.BeforeCompletion(agent, message);
-
- // Interrupted by hook
- if (message.StopCompletion)
- {
- return new InstructResult
- {
- MessageId = message.MessageId,
- Text = message.Content
- };
- }
- }
-
- // Render prompt
- var prompt = string.IsNullOrEmpty(templateName) ?
- agentService.RenderedInstruction(agent) :
- agentService.RenderedTemplate(agent, templateName);
-
- var completer = CompletionProvider.GetCompletion(_services,
- agentConfig: agent.LlmConfig);
-
- var response = new InstructResult
- {
- MessageId = message.MessageId
- };
- if (completer is ITextCompletion textCompleter)
- {
- var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId);
- response.Text = result;
- }
- else if (completer is IChatCompletion chatCompleter)
- {
- if (instruction == "#TEMPLATE#")
- {
- instruction = prompt;
- prompt = message.Content;
- }
-
- var result = await chatCompleter.GetChatCompletions(new Agent
- {
- Id = agentId,
- Name = agent.Name,
- Instruction = instruction
- }, new List
- {
- new RoleDialogModel(AgentRole.User, prompt)
- {
- CurrentAgentId = agentId,
- MessageId = message.MessageId
- }
- });
- response.Text = result.Content;
- }
-
-
- foreach (var hook in hooks)
- {
- if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
- {
- continue;
- }
-
- await hook.AfterCompletion(agent, response);
- }
-
- return response;
- }
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
index 8e647694..a4131e64 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
@@ -161,6 +161,9 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public void UpdateConversationTitle(string conversationId, string title)
=> throw new NotImplementedException();
+ public bool UpdateConversationTags(string conversationId, List tags)
+ => throw new NotImplementedException();
+
public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request)
=> throw new NotImplementedException();
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
index d61fda41..bc9912b3 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs
@@ -1,8 +1,5 @@
using BotSharp.Abstraction.Loggers.Models;
-using BotSharp.Abstraction.Repositories.Models;
-using System.Globalization;
using System.IO;
-using System.Xml.Linq;
namespace BotSharp.Core.Repository
{
@@ -13,6 +10,7 @@ namespace BotSharp.Core.Repository
var utcNow = DateTime.UtcNow;
conversation.CreatedTime = utcNow;
conversation.UpdatedTime = utcNow;
+ conversation.Tags = conversation.Tags ?? new();
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversation.Id);
if (!Directory.Exists(dir))
@@ -134,6 +132,24 @@ namespace BotSharp.Core.Repository
}
}
+ public bool UpdateConversationTags(string conversationId, List tags)
+ {
+ if (string.IsNullOrEmpty(conversationId)) return false;
+
+ var convDir = FindConversationDirectory(conversationId);
+ if (string.IsNullOrEmpty(convDir)) return false;
+
+ var convFile = Path.Combine(convDir, CONVERSATION_FILE);
+ if (!File.Exists(convFile)) return false;
+
+ var json = File.ReadAllText(convFile);
+ var conv = JsonSerializer.Deserialize(json, _options);
+ conv.Tags = tags ?? new();
+ conv.UpdatedTime = DateTime.UtcNow;
+ File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options));
+ return true;
+ }
+
public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request)
{
if (string.IsNullOrEmpty(conversationId)) return false;
@@ -354,6 +370,10 @@ namespace BotSharp.Core.Repository
{
matched = matched && record.CreatedTime >= filter.StartTime.Value;
}
+ if (filter?.Tags != null && filter.Tags.Any())
+ {
+ matched = matched && !record.Tags.IsNullOrEmpty() && record.Tags.Exists(t => filter.Tags.Contains(t));
+ }
// Check states
if (filter != null && !filter.States.IsNullOrEmpty())
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs
index 31caa932..a77397d0 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs
@@ -42,11 +42,10 @@ public class RoutingContext : IRoutingContext
_routerAgentIds = agentService.GetAgents(new AgentFilter
{
Type = AgentType.Routing
- }).Result.Items
- .Select(x => x.Id).ToArray();
+ }).Result.Items.Select(x => x.Id).ToArray();
}
- return _stack.Where(x => !_routerAgentIds.Contains(x)).Last();
+ return _stack.Where(x => !_routerAgentIds.Contains(x)).LastOrDefault() ?? string.Empty;
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs
index f95b3f37..f8efab46 100644
--- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs
+++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.GetConversationContent.cs
@@ -16,15 +16,7 @@ public partial class RoutingService
role = agent.Name;
}
- if (role == AgentRole.User)
- {
- conversation += $"{role}: {dialog.Payload ?? dialog.Content}\r\n";
- }
- else
- {
- // Assistant reply deosn't need help with payload
- conversation += $"{role}: {dialog.Content}\r\n";
- }
+ conversation += $"{role}: {dialog.Payload ?? dialog.Content}\r\n";
}
return conversation;
diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
index 10dbe8de..5d08c51f 100644
--- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
+++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
@@ -275,7 +275,7 @@ public class UserService : IUserService
foreach (var hook in hooks)
{
- hook.BeforeSending(token);
+ hook.UserAuthenticated(jwt);
}
return token;
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
index 600c0dec..f5cfc7c8 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs
@@ -202,7 +202,7 @@ public class ConversationController : ControllerBase
public async Task UpdateConversationTitle([FromRoute] string conversationId, [FromBody] UpdateConversationTitleModel newTile)
{
var userService = _services.GetRequiredService();
- var conversationService = _services.GetRequiredService();
+ var conv = _services.GetRequiredService();
var user = await userService.GetUser(_user.Id);
var filter = new ConversationFilter
@@ -210,17 +210,24 @@ public class ConversationController : ControllerBase
Id = conversationId,
UserId = user.Role != UserRole.Admin ? user.Id : null
};
- var conversations = await conversationService.GetConversations(filter);
+ var conversations = await conv.GetConversations(filter);
if (conversations.Items.IsNullOrEmpty())
{
return false;
}
- var response = await conversationService.UpdateConversationTitle(conversationId, newTile.NewTitle);
+ var response = await conv.UpdateConversationTitle(conversationId, newTile.NewTitle);
return response != null;
}
+ [HttpPut("/conversation/{conversationId}/update-tags")]
+ public async Task UpdateConversationTags([FromRoute] string conversationId, [FromBody] UpdateConversationRequest request)
+ {
+ var conv = _services.GetRequiredService();
+ return await conv.UpdateConversationTags(conversationId, request.Tags);
+ }
+
[HttpPut("/conversation/{conversationId}/update-message")]
public async Task UpdateConversationMessage([FromRoute] string conversationId, [FromBody] UpdateMessageModel model)
{
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs
index c50ecc88..05f8cb89 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/ConversationViewModel.cs
@@ -29,6 +29,8 @@ public class ConversationViewModel
public string Status { get; set; }
public Dictionary States { get; set; }
+ public List Tags { get; set; } = new();
+
[JsonPropertyName("updated_time")]
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
[JsonPropertyName("created_time")]
@@ -48,6 +50,7 @@ public class ConversationViewModel
Channel = sess.Channel,
Status = sess.Status,
TaskId = sess.TaskId,
+ Tags = sess.Tags ?? new(),
CreatedTime = sess.CreatedTime,
UpdatedTime = sess.UpdatedTime
};
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/UpdateConversationRequest.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/UpdateConversationRequest.cs
new file mode 100644
index 00000000..c9b89747
--- /dev/null
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/UpdateConversationRequest.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.OpenAPI.ViewModels.Conversations;
+
+public class UpdateConversationRequest
+{
+ public List Tags { get; set; } = [];
+}
diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs
index 3384e3b7..b77a47c1 100644
--- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/Chat/ChatCompletionProvider.cs
@@ -312,7 +312,7 @@ public class ChatCompletionProvider : IChatCompletion
}
else if (message.Role == AgentRole.Assistant)
{
- messages.Add(new AssistantChatMessage(message.Content));
+ messages.Add(new AssistantChatMessage(message.Payload ?? message.Content));
}
}
diff --git a/src/Plugins/BotSharp.Plugin.ExcelHandler/Services/MySqlService.cs b/src/Plugins/BotSharp.Plugin.ExcelHandler/Services/MySqlService.cs
index 70ce855e..f85b5ace 100644
--- a/src/Plugins/BotSharp.Plugin.ExcelHandler/Services/MySqlService.cs
+++ b/src/Plugins/BotSharp.Plugin.ExcelHandler/Services/MySqlService.cs
@@ -139,11 +139,11 @@ namespace BotSharp.Plugin.ExcelHandler.Services
string insertDataSql = ProcessInsertSqlQuery(dataSql);
ExecuteSqlQueryForInsertion(insertDataSql);
- return (true, $"{_currentFileName}: \r\n {_excelRowSize} records have been successfully inserted into `{_tableName}` table");
+ return (true, $"{_currentFileName}: \r\n {_excelRowSize} records have been successfully inserted into `{_database}`.`{_tableName}` table");
}
catch (Exception ex)
{
- return (false, $"{_currentFileName}: Failed to parse excel data into `{_tableName}` table. ####Error: {ex.Message}");
+ return (false, $"{_currentFileName}: Failed to parse excel data into `{_database}`.`{_tableName}` table. ####Error: {ex.Message}");
}
}
private string ParseSheetData(ISheet singleSheet)
diff --git a/src/Plugins/BotSharp.Plugin.ExcelHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/handle_excel_request.fn.liquid b/src/Plugins/BotSharp.Plugin.ExcelHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/handle_excel_request.fn.liquid
index b9444c61..a3f27694 100644
--- a/src/Plugins/BotSharp.Plugin.ExcelHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/handle_excel_request.fn.liquid
+++ b/src/Plugins/BotSharp.Plugin.ExcelHandler/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/handle_excel_request.fn.liquid
@@ -1 +1,2 @@
-Please call handle_excel_request if user wants to load the data from a excel/csv file.
\ No newline at end of file
+Please call handle_excel_request if user wants to load the data from a excel/csv file.
+handle_excel_request can NOT generate excel/csv.
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj b/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj
index 1cd8144f..283d8b32 100644
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj
@@ -1,4 +1,4 @@
-
+
$(TargetFramework)
@@ -21,7 +21,8 @@
-
+
+
@@ -38,7 +39,10 @@
PreserveNewest
-
+
+ PreserveNewest
+
+
PreserveNewest
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/GenerateKnowledgeFn.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/GenerateKnowledgeFn.cs
deleted file mode 100644
index cd43fca1..00000000
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/GenerateKnowledgeFn.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using BotSharp.Abstraction.Templating;
-using BotSharp.Core.Infrastructures;
-
-namespace BotSharp.Plugin.KnowledgeBase.Functions;
-
-public class GenerateKnowledgeFn : IFunctionCallback
-{
- public string Name => "generate_knowledge";
-
- public string Indication => "generating knowledge";
-
- private readonly IServiceProvider _services;
- private readonly KnowledgeBaseSettings _settings;
-
- public GenerateKnowledgeFn(IServiceProvider services, KnowledgeBaseSettings settings)
- {
- _services = services;
- _settings = settings;
- }
-
- public async Task Execute(RoleDialogModel message)
- {
- var args = JsonSerializer.Deserialize(message.FunctionArgs ?? "{}");
- var agentService = _services.GetRequiredService();
- var llmAgent = await agentService.GetAgent(BuiltInAgentId.Planner);
- var generateKnowledgePrompt = await GetGenerateKnowledgePrompt(args.Question, args.Answer);
- var agent = new Agent
- {
- Id = message.CurrentAgentId ?? string.Empty,
- Name = "sqlDriver_DictionarySearch",
- Instruction = generateKnowledgePrompt,
- LlmConfig = llmAgent.LlmConfig
- };
- var response = await GetAiResponse(agent);
- message.Data = response.Content.JsonArrayContent();
- message.Content = response.Content;
- return true;
- }
-
- private async Task GetGenerateKnowledgePrompt(string userQuestions, string sqlAnswer)
- {
- var agentService = _services.GetRequiredService();
- var render = _services.GetRequiredService();
-
- var agent = await agentService.GetAgent(BuiltInAgentId.Learner);
- var template = agent.Templates.FirstOrDefault(x => x.Name == "knowledge.generation")?.Content ?? string.Empty;
-
- return render.Render(template, new Dictionary
- {
- { "user_questions", userQuestions },
- { "sql_answer", sqlAnswer },
- });
- }
- private async Task GetAiResponse(Agent agent)
- {
- var text = "Generate question and answer pair";
- var message = new RoleDialogModel(AgentRole.User, text);
-
- var completion = CompletionProvider.GetChatCompletion(_services,
- provider: agent.LlmConfig.Provider,
- model: agent.LlmConfig.Model);
-
- return await completion.GetChatCompletions(agent, new List { message });
- }
-}
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.liquid b/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.plain.liquid
similarity index 86%
rename from src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.liquid
rename to src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.plain.liquid
index 49e374a1..2792595a 100644
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.liquid
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.plain.liquid
@@ -1,4 +1,4 @@
-You are a knowledge generator for knowledge base. Extract the answer in "SQL Answer" to answer the User Questions.
+You are a knowledge extractor for knowledge base. Extract the answer in "SQL Answer" to answer the User Questions.
* Replace alias with the actual table name. Output json array only, formatting as [{"question":"string", "answer":""}].
* Skip the question/answer for tmp table.
* Don't include tmp table in the answer.
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.refine.liquid b/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.refine.liquid
new file mode 100644
index 00000000..4a7af975
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.refine.liquid
@@ -0,0 +1,15 @@
+You are a knowledge extractor for knowledge base. Utilize the new answer and existing answer to generate the final integrated answer.
+Output json array only, formatting as [{"question":"", "answer":""}]. Replace the new line with \r\n.
+* Don't loss any knowledge in the existing answer.
+
+=====
+User Question:
+{{ user_question }}
+
+=====
+New Answer:
+{{ new_answer }}
+
+=====
+Existing Answer:
+{{ existing_answer }}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs
index e684e34d..609d637d 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationDocument.cs
@@ -9,6 +9,7 @@ public class ConversationDocument : MongoBase
public string Channel { get; set; }
public string Status { get; set; }
public int DialogCount { get; set; }
+ public List Tags { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoBase.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoBase.cs
index 0af038a5..0c3c12b0 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoBase.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoBase.cs
@@ -6,5 +6,3 @@ public abstract class MongoBase
[BsonId(IdGenerator = typeof(StringGuidIdGenerator))]
public string Id { get; set; }
}
-
-
diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
index f710a237..ff7cefcd 100644
--- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
+++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs
@@ -19,6 +19,7 @@ public partial class MongoRepository
Channel = conversation.Channel,
TaskId = conversation.TaskId,
Status = conversation.Status,
+ Tags = conversation.Tags ?? new(),
CreatedTime = utcNow,
UpdatedTime = utcNow
};
@@ -108,6 +109,19 @@ public partial class MongoRepository
_dc.Conversations.UpdateOne(filterConv, updateConv);
}
+ public bool UpdateConversationTags(string conversationId, List tags)
+ {
+ if (string.IsNullOrEmpty(conversationId)) return false;
+
+ var filter = Builders.Filter.Eq(x => x.Id, conversationId);
+ var update = Builders.Update
+ .Set(x => x.Tags, tags ?? new())
+ .Set(x => x.UpdatedTime, DateTime.UtcNow);
+
+ var res = _dc.Conversations.UpdateOne(filter, update);
+ return res.ModifiedCount > 0;
+ }
+
public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request)
{
if (string.IsNullOrEmpty(conversationId)) return false;
@@ -254,6 +268,7 @@ public partial class MongoRepository
Dialogs = dialogElements,
States = curStates,
DialogCount = conv.DialogCount,
+ Tags = conv.Tags,
CreatedTime = conv.CreatedTime,
UpdatedTime = conv.UpdatedTime
};
@@ -297,6 +312,10 @@ public partial class MongoRepository
{
convFilters.Add(convBuilder.Gte(x => x.CreatedTime, filter.StartTime.Value));
}
+ if (filter?.Tags != null && filter.Tags.Any())
+ {
+ convFilters.Add(convBuilder.AnyIn(x => x.Tags, filter.Tags));
+ }
// Filter states
var stateFilters = new List>();
@@ -349,6 +368,7 @@ public partial class MongoRepository
Channel = x.Channel,
Status = x.Status,
DialogCount = x.DialogCount,
+ Tags = x.Tags ?? new(),
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime
}).ToList();
@@ -375,6 +395,7 @@ public partial class MongoRepository
Channel = c.Channel,
Status = c.Status,
DialogCount = c.DialogCount,
+ Tags = c.Tags ?? new(),
CreatedTime = c.CreatedTime,
UpdatedTime = c.UpdatedTime
}).ToList();
diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs
index 50151513..1c9ca641 100644
--- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs
@@ -290,7 +290,7 @@ public class ChatCompletionProvider : IChatCompletion
}
else if (message.Role == AgentRole.Assistant)
{
- messages.Add(new AssistantChatMessage(message.Content));
+ messages.Add(new AssistantChatMessage(message.Payload ?? message.Content));
}
}
diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs
index f854a51d..0024c916 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/PrimaryStagePlanFn.cs
@@ -6,10 +6,13 @@ public class PrimaryStagePlanFn : IFunctionCallback
{
public string Name => "plan_primary_stage";
public string Indication => "Currently analyzing and breaking down user requirements.";
+
private readonly IServiceProvider _services;
private readonly ILogger _logger;
- public PrimaryStagePlanFn(IServiceProvider services, ILogger logger)
+ public PrimaryStagePlanFn(
+ IServiceProvider services,
+ ILogger logger)
{
_services = services;
_logger = logger;
@@ -35,15 +38,17 @@ public class PrimaryStagePlanFn : IFunctionCallback
}
}
knowledges = knowledges.Distinct().ToList();
+ var knowledgeState = String.Join("\r\n", knowledges);
+ state.SetState("relevant_knowledges", knowledgeState);
// Get first stage planning prompt
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
- var firstPlanningPrompt = await GetFirstStagePlanPrompt(message, task.Requirements, knowledges);
+ var prompt = await GetFirstStagePlanPrompt(message, task.Requirements, knowledges);
var plannerAgent = new Agent
{
Id = BuiltInAgentId.Planner,
- Name = "planning_1st",
- Instruction = firstPlanningPrompt,
+ Name = "FirstStagePlanner",
+ Instruction = prompt,
TemplateDict = new Dictionary(),
LlmConfig = currentAgent.LlmConfig
};
@@ -64,11 +69,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
var agent = await agentService.GetAgent(BuiltInAgentId.Planner);
var template = agent.Templates.FirstOrDefault(x => x.Name == "two_stage.1st.plan")?.Content ?? string.Empty;
- var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
- {
- Parameters = [ JsonDocument.Parse("{}") ],
- Results = [ string.Empty ]
- });
+ var responseFormat = JsonSerializer.Serialize(new FirstStagePlan{});
// Get global knowledges
var globalKnowledges = new List();
diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs
index e228d607..df4a879f 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SecondaryStagePlanFn.cs
@@ -1,5 +1,4 @@
using BotSharp.Plugin.Planner.TwoStaging.Models;
-using System.Threading.Tasks;
namespace BotSharp.Plugin.Planner.Functions;
@@ -7,10 +6,13 @@ public class SecondaryStagePlanFn : IFunctionCallback
{
public string Name => "plan_secondary_stage";
public string Indication => "Further analyzing and breaking down user sub-needs.";
+
private readonly IServiceProvider _services;
private readonly ILogger _logger;
- public SecondaryStagePlanFn(IServiceProvider services, ILogger logger)
+ public SecondaryStagePlanFn(
+ IServiceProvider services,
+ ILogger logger)
{
_services = services;
_logger = logger;
@@ -25,7 +27,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
var msgSecondary = RoleDialogModel.From(message);
var collectionName = knowledgeSettings.Default.CollectionName;
- var planPrimary = states.GetState("planning_result");
+ var planResult = states.GetState("planning_result");
var taskSecondary = JsonSerializer.Deserialize(msgSecondary.FunctionArgs);
@@ -38,19 +40,22 @@ public class SecondaryStagePlanFn : IFunctionCallback
knowledges.AddRange(k);
}
knowledges = knowledges.Distinct().ToList();
-
var knowledgeResults = string.Join("\r\n\r\n=====\r\n", knowledges);
+ var knowledgeState = states.GetState("relevant_knowledges");
+ knowledgeState += String.Join("\r\n", knowledges);
+ states.SetState("relevant_knowledges", knowledgeState);
+
// Get second stage planning prompt
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
- var secondPlanningPrompt = await GetSecondStagePlanPrompt(taskSecondary.TaskDescription, planPrimary, knowledgeResults, message);
- _logger.LogInformation(secondPlanningPrompt);
+ var prompt = await GetSecondStagePlanPrompt(taskSecondary.TaskDescription, planResult, knowledgeResults, message);
+ _logger.LogInformation(prompt);
var plannerAgent = new Agent
{
Id = BuiltInAgentId.Planner,
- Name = "planning_2nd",
- Instruction = secondPlanningPrompt,
+ Name = "SecondStagePlanner",
+ Instruction = prompt,
TemplateDict = new Dictionary(),
LlmConfig = currentAgent.LlmConfig
};
@@ -63,7 +68,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
return true;
}
- private async Task GetSecondStagePlanPrompt(string taskDescription, string planPrimary, string knowledgeResults, RoleDialogModel message)
+ private async Task GetSecondStagePlanPrompt(string taskDescription, string planResult, string knowledgeResults, RoleDialogModel message)
{
var agentService = _services.GetRequiredService();
var render = _services.GetRequiredService();
@@ -79,7 +84,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
return render.Render(template, new Dictionary
{
{ "task_description", taskDescription },
- { "primary_plan", planPrimary },
+ { "primary_plan", planResult },
{ "additional_knowledge", knowledgeResults },
{ "response_format", responseFormat }
});
diff --git a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
index e08e6f6f..bcf7588a 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/Functions/SummaryPlanFn.cs
@@ -8,6 +8,7 @@ public class SummaryPlanFn : IFunctionCallback
{
public string Name => "plan_summary";
public string Indication => "Organizing and summarizing the final output results.";
+
private readonly IServiceProvider _services;
private readonly ILogger _logger;
@@ -35,6 +36,7 @@ public class SummaryPlanFn : IFunctionCallback
var allTables = new List();
var ddlStatements = string.Empty;
var relevantKnowledge = states.GetState("planning_result");
+ relevantKnowledge += "\r\n" + states.GetState("relevant_knowledges");
var dictionaryItems = states.GetState("dictionary_items");
var excelImportResult = states.GetState("excel_import_result");
@@ -53,14 +55,14 @@ public class SummaryPlanFn : IFunctionCallback
ddlStatements += "\r\n" + msgCopy.Content;
// Summarize and generate query
- var summaryPlanPrompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, relevantKnowledge, dictionaryItems, ddlStatements, excelImportResult);
- _logger.LogInformation($"Summary plan prompt:\r\n{summaryPlanPrompt}");
+ var prompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, relevantKnowledge, dictionaryItems, ddlStatements, excelImportResult);
+ _logger.LogInformation($"Summary plan prompt:\r\n{prompt}");
var plannerAgent = new Agent
{
Id = BuiltInAgentId.Planner,
- Name = "Planner Summary",
- Instruction = summaryPlanPrompt,
+ Name = "SummaryPlanner",
+ Instruction = prompt,
LlmConfig = currentAgent.LlmConfig
};
@@ -105,7 +107,7 @@ public class SummaryPlanFn : IFunctionCallback
{ "relevant_knowledges", relevantKnowledge },
{ "dictionary_items", dictionaryItems },
{ "table_structure", ddlStatement },
- { "excel_import_result",excelImportResult }
+ { "excel_import_result", excelImportResult }
});
}
private async Task GetAiResponse(Agent plannerAgent)
diff --git a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/FirstStagePlan.cs b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/FirstStagePlan.cs
index 9588b811..10d26e05 100644
--- a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/FirstStagePlan.cs
+++ b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Models/FirstStagePlan.cs
@@ -5,8 +5,8 @@ public class FirstStagePlan
[JsonPropertyName("task_detail")]
public string Task { get; set; } = "";
- [JsonPropertyName("reason")]
- public string Reason { get; set; } = "";
+ //[JsonPropertyName("reason")]
+ //public string Reason { get; set; } = "";
[JsonPropertyName("step")]
public int Step { get; set; } = -1;
@@ -20,14 +20,14 @@ public class FirstStagePlan
[JsonPropertyName("related_tables")]
public string[] Tables { get; set; } = [];
- [JsonPropertyName("related_urls")]
- public string[] Urls { get; set; } = [];
+ //[JsonPropertyName("related_urls")]
+ //public string[] Urls { get; set; } = [];
- [JsonPropertyName("input_args")]
- public JsonDocument[] Parameters { get; set; } = [];
+ //[JsonPropertyName("input_args")]
+ //public JsonDocument[] Parameters { get; set; } = [];
- [JsonPropertyName("output_results")]
- public string[] Results { get; set; } = [];
+ //[JsonPropertyName("output_results")]
+ //public string[] Results { get; set; } = [];
public override string ToString()
{
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
index d4d0a243..c0b755f6 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj
@@ -17,12 +17,12 @@
-
-
+
+
@@ -37,10 +37,10 @@
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
index e3d8e57e..99b40a65 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs
@@ -99,6 +99,11 @@ public class ExecuteQueryFn : IFunctionCallback
private async Task RefineSqlStatement(RoleDialogModel message, ExecuteQueryArgs args)
{
+ if (args.Tables == null || args.Tables.Length == 0)
+ {
+ return args;
+ }
+
// get table DDL
var fn = _services.GetRequiredService();
var msg = RoleDialogModel.From(message);
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/LookupDictionaryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/VerifyDictionaryTerm.cs
similarity index 96%
rename from src/Plugins/BotSharp.Plugin.SqlDriver/Functions/LookupDictionaryFn.cs
rename to src/Plugins/BotSharp.Plugin.SqlDriver/Functions/VerifyDictionaryTerm.cs
index c94c12db..134ef8e4 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/LookupDictionaryFn.cs
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/VerifyDictionaryTerm.cs
@@ -7,12 +7,15 @@ using static Dapper.SqlMapper;
namespace BotSharp.Plugin.SqlDriver.Functions;
-public class LookupDictionaryFn : IFunctionCallback
+public class VerifyDictionaryTerm : IFunctionCallback
{
public string Name => "verify_dictionary_term";
+ public string Indication => "Verifying dictionary term";
+
+
private readonly IServiceProvider _services;
- public LookupDictionaryFn(IServiceProvider services)
+ public VerifyDictionaryTerm(IServiceProvider services)
{
_services = services;
}
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/sql_dictionary_lookup.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json
similarity index 77%
rename from src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/sql_dictionary_lookup.json
rename to src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json
index 6ad2a917..3dc21e01 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/sql_dictionary_lookup.json
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/verify_dictionary_term.json
@@ -1,6 +1,6 @@
{
"name": "verify_dictionary_term",
- "description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is True",
+ "description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is true and is_insert is false",
"parameters": {
"type": "object",
"properties": {
@@ -12,6 +12,10 @@
"type": "string",
"description": "the reason why you need to call verify_dictionary_term"
},
+ "is_insert": {
+ "type": "boolean",
+ "description": "if SQL statement is inserting."
+ },
"tables": {
"type": "array",
"description": "all related tables",
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/sql_dictionary_lookup.fn.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/verify_dictionary_term.fn.liquid
similarity index 100%
rename from src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/sql_dictionary_lookup.fn.liquid
rename to src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/verify_dictionary_term.fn.liquid
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json
index 9cdafc04..6587cef5 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/execute_sql.json
@@ -20,13 +20,13 @@
"tables": {
"type": "array",
- "description": "all related tables",
+ "description": "all related tables in the sql statements",
"items": {
"type": "string",
"description": "table name"
}
}
},
- "required": [ "sql_statement", "tables", "formatting_result" ]
+ "required": [ "sql_statements", "tables", "formatting_result" ]
}
}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid
index 5d07a941..5c6ad05c 100644
--- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid
+++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/templates/query_result_formatting.liquid
@@ -1,5 +1,5 @@
Output in human readable format. If there is large amount of rows, shape it in tabular, otherwise, output in plain text.
-Put user task description in the first line in the same language, for example, user is using Chinese, you have to output the result in Chinese.
+Put user task description in the first line in the same language, for example, if user is using Chinese, you have to output the result in Chinese.
User Task Description:
{{ requirement_detail }}
\ No newline at end of file
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
index d9ff3402..cf318e35 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
@@ -72,6 +72,7 @@ public class TwilioVoiceController : TwilioController
ConversationId = conversationId,
SeqNumber = seqNum,
Content = messageContent,
+ Digits = request.Digits,
From = request.From
};
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs b/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs
index a6339c7b..c74addd0 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs
@@ -5,6 +5,7 @@ namespace BotSharp.Plugin.Twilio.Models
public string ConversationId { get; set; }
public int SeqNumber { get; set; }
public string Content { get; set; }
+ public string Digits { get; set; }
public string From { get; set; }
public Dictionary States { get; set; } = new();
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs
index e3476ea2..fe63ba82 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs
@@ -66,23 +66,11 @@ namespace BotSharp.Plugin.Twilio.Services
var sessionManager = sp.GetRequiredService();
var progressService = sp.GetRequiredService();
InitProgressService(message, sessionManager, progressService);
-
- routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId);
- var states = new List
- {
- new MessageState("channel", ConversationChannel.Phone),
- new MessageState("calling_phone", message.From)
- };
-
- foreach (var kvp in message.States)
- {
- states.Add(new MessageState(kvp.Key, kvp.Value));
- }
- conv.SetConversationId(message.ConversationId, states);
-
+ InitConversation(message, inputMsg, conv, routing);
+
var result = await conv.SendMessage(config.AgentId,
inputMsg,
- replyMessage: null,
+ replyMessage: BuildPostbackMessageModel(conv, message),
async msg =>
{
reply = new AssistantMessage()
@@ -94,13 +82,50 @@ namespace BotSharp.Plugin.Twilio.Services
};
}
);
+ reply.SpeechFileName = await GetReplySpeechFileName(message.ConversationId, reply, sp);
+ reply.Hints = GetHints(reply);
+ reply.Content = null;
+ await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply);
+ }
+ private PostbackMessageModel BuildPostbackMessageModel(IConversationService conv, CallerMessage message)
+ {
+ var messages = conv.GetDialogHistory(1);
+ if (!messages.Any()) return null;
+ var lastMessage = messages[0];
+ if (string.IsNullOrEmpty(lastMessage.PostbackFunctionName)) return null;
+ return new PostbackMessageModel
+ {
+ FunctionName = lastMessage.PostbackFunctionName,
+ ParentId = lastMessage.MessageId,
+ Payload = message.Digits
+ };
+ }
+
+ private static void InitConversation(CallerMessage message, RoleDialogModel inputMsg, IConversationService conv, IRoutingService routing)
+ {
+ routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId);
+ var states = new List
+ {
+ new("channel", ConversationChannel.Phone),
+ new("calling_phone", message.From)
+ };
+ states.AddRange(message.States.Select(kvp => new MessageState(kvp.Key, kvp.Value)));
+ conv.SetConversationId(message.ConversationId, states);
+ }
+
+ private static async Task GetReplySpeechFileName(string conversationId, AssistantMessage reply, IServiceProvider sp)
+ {
var completion = CompletionProvider.GetAudioCompletion(sp, "openai", "tts-1");
var fileStorage = sp.GetRequiredService();
var data = await completion.GenerateAudioFromTextAsync(reply.Content);
var fileName = $"reply_{reply.MessageId}.mp3";
- fileStorage.SaveSpeechFile(message.ConversationId, fileName, data);
- reply.SpeechFileName = fileName;
+ fileStorage.SaveSpeechFile(conversationId, fileName, data);
+ return fileName;
+ }
+
+ private static string GetHints(AssistantMessage reply)
+ {
var phrases = reply.Content.Split(',', StringSplitOptions.RemoveEmptyEntries);
int capcity = 100;
var hints = new List(capcity);
@@ -122,9 +147,7 @@ namespace BotSharp.Plugin.Twilio.Services
}
// add frequency short words
hints.AddRange(["yes", "no", "correct", "right"]);
- reply.Hints = string.Join(", ", hints.Select(x => x.ToLower()).Distinct().Reverse());
- reply.Content = null;
- await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply);
+ return string.Join(", ", hints.Select(x => x.ToLower()).Distinct().Reverse());
}
private static void InitProgressService(CallerMessage message, ITwilioSessionManager sessionManager, IConversationProgressService progressService)
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs
index 4a67ffeb..2bbb0acf 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs
@@ -146,37 +146,41 @@ public class PlaywrightInstance : IDisposable
{
if (e.Status != 204 &&
e.Headers.ContainsKey("content-type") &&
- e.Headers["content-type"].Contains("application/json") &&
(e.Request.ResourceType == "fetch" || e.Request.ResourceType == "xhr") &&
(excludeResponseUrls == null || !excludeResponseUrls.Any(url => e.Url.ToLower().Contains(url))) &&
(includeResponseUrls == null || includeResponseUrls.Any(url => e.Url.ToLower().Contains(url))))
{
Serilog.Log.Information($"{e.Request.Method}: {e.Url}");
- JsonElement? json = null;
+
try
{
- if (e.Status == 200 && e.Ok)
- {
- json = await e.JsonAsync();
- }
- else
- {
- Serilog.Log.Warning($"Response status: {e.Status} {e.StatusText}, OK: {e.Ok}");
- }
-
var result = new WebPageResponseData
{
Url = e.Url.ToLower(),
PostData = e.Request?.PostData ?? string.Empty,
- ResponseData = JsonSerializer.Serialize(json),
ResponseInMemory = responseInMemory
};
+ if (e.Headers["content-type"].Contains("application/json"))
+ {
+ if (e.Status == 200 && e.Ok)
+ {
+ var json = await e.JsonAsync();
+ result.ResponseData = JsonSerializer.Serialize(json);
+ }
+ }
+ else
+ {
+ var html = await e.TextAsync();
+ result.ResponseData = html;
+ }
+
if (responseContainer != null && responseInMemory)
{
responseContainer.Add(result);
}
+ Serilog.Log.Warning($"Response status: {e.Status} {e.StatusText}, OK: {e.Ok}");
var webPageResponseHooks = _services.GetServices();
foreach (var hook in webPageResponseHooks)
{
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs
index a39da9c0..ff43864e 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs
@@ -128,7 +128,15 @@ public partial class PlaywrightWebDriver
}
else
{
- result.Selector = locator.ToString().Split("Locator@").Last();
+ foreach (var element in await locator.AllAsync())
+ {
+ var html = await element.InnerHTMLAsync();
+ _logger.LogWarning(html);
+ // fix if html has &
+ result.Body = HttpUtility.HtmlDecode(html);
+ break;
+ }
+
result.IsSuccess = true;
}
}
diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ScrollPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ScrollPage.cs
index c50bf48b..48538ce8 100644
--- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ScrollPage.cs
+++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.ScrollPage.cs
@@ -12,12 +12,20 @@ public partial class PlaywrightWebDriver
if (args.Direction == "down")
{
// Get the total page height
- int scrollY = await page.EvaluateAsync("document.body.scrollHeight");
+ int scrollY = await page.EvaluateAsync("window.screen.height");
- // Scroll to the bottom
+ // Scroll a page down
await page.Mouse.WheelAsync(0, scrollY);
}
else if (args.Direction == "up")
+ {
+ // Get the total page height
+ int scrollY = await page.EvaluateAsync("window.screen.height");
+
+ // Scroll a page up
+ await page.Mouse.WheelAsync(0, -scrollY);
+ }
+ else if (args.Direction == "bottom")
{
// Get the total page height
int scrollY = await page.EvaluateAsync("document.body.scrollHeight");
@@ -25,6 +33,14 @@ public partial class PlaywrightWebDriver
// Scroll to the bottom
await page.Mouse.WheelAsync(0, -scrollY);
}
+ else if (args.Direction == "top")
+ {
+ // Get the total page height
+ int scrollY = await page.EvaluateAsync("document.body.scrollHeight");
+
+ // Scroll to the top
+ await page.Mouse.WheelAsync(0, -scrollY);
+ }
else if (args.Direction == "left")
{
await page.EvaluateAsync(@"