Merge branch 'master' into lida_dev

This commit is contained in:
AnonymousDotNet 2024-10-21 12:02:47 +08:00
commit 2f6c3d410d
54 changed files with 606 additions and 290 deletions

View file

@ -32,6 +32,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
<PackageReference Include="System.Memory.Data" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />

View file

@ -1,5 +1,8 @@
using System.Diagnostics;
namespace BotSharp.Abstraction.Browsing.Models;
[DebuggerStepThrough]
public class ElementLocatingArgs
{
[JsonPropertyName("match_rule")]

View file

@ -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!;

View file

@ -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}";
}
}

View file

@ -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; }
/// <summary>
/// contains, starts, ends, equals
/// </summary>
public string UrlMatchPattern { get; set; } = "contains";
/// <summary>
/// Handle Content-Type: text/x-component
/// </summary>
public Func<string, string>? PartSearch { get; set; } = null;
}

View file

@ -12,6 +12,7 @@ public interface IConversationService
Task<Conversation> GetConversation(string id);
Task<PagedItems<Conversation>> GetConversations(ConversationFilter filter);
Task<Conversation> UpdateConversationTitle(string id, string title);
Task<bool> UpdateConversationTags(string conversationId, List<string> tags);
Task<bool> UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
Task<List<Conversation>> GetLastConversations();
Task<List<string>> GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable<string> excludeAgentIds);

View file

@ -15,10 +15,10 @@ public class Conversation
public string Title { get; set; } = string.Empty;
[JsonIgnore]
public List<DialogElement> Dialogs { get; set; } = new List<DialogElement>();
public List<DialogElement> Dialogs { get; set; } = new();
[JsonIgnore]
public Dictionary<string, string> States { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> 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<string> Tags { get; set; } = new();
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}

View file

@ -4,5 +4,23 @@ namespace BotSharp.Abstraction.Instructs;
public interface IInstructService
{
/// <summary>
/// Execute completion by using specified instruction or template
/// </summary>
/// <param name="agentId">Agent (static agent)</param>
/// <param name="message">Additional message provided by user</param>
/// <param name="templateName">Template name</param>
/// <param name="instruction">System prompt</param>
/// <returns></returns>
Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null);
/// <summary>
/// A generic way to execute completion by using specified instruction or template
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="instruction">Prompt</param>
/// <param name="agentId">Agent id</param>
/// <param name="options">Llm Provider, model, message, prompt data</param>
/// <returns></returns>
Task<T?> Instruct<T>(string instruction, string agentId, InstructOptions options) where T : class;
}

View file

@ -0,0 +1,29 @@
namespace BotSharp.Abstraction.Instructs.Models;
public class InstructOptions
{
/// <summary>
/// Llm provider
/// </summary>
public string Provider { get; set; } = null!;
/// <summary>
/// Llm model
/// </summary>
public string Model { get; set; } = null!;
/// <summary>
/// Conversation id. When this field is not null, it will get dialogs from conversation.
/// </summary>
public string? ConversationId { get; set; }
/// <summary>
/// The single message. It can be append to the whole dialogs or sent alone.
/// </summary>
public string? Message { get; set; }
/// <summary>
/// Data to fill in prompt
/// </summary>
public Dictionary<string, object> Data { get; set; } = new();
}

View file

@ -22,5 +22,7 @@ public class ConversationFilter
/// <summary>
/// Check whether each key in the list is in the conversation states and its value equals to target value if not empty
/// </summary>
public IEnumerable<KeyValue> States { get; set; } = new List<KeyValue>();
public IEnumerable<KeyValue>? States { get; set; } = [];
public IEnumerable<string>? Tags { get; set; } = [];
}

View file

@ -72,6 +72,7 @@ public interface IBotSharpRepository
Conversation GetConversation(string conversationId);
PagedItems<Conversation> GetConversations(ConversationFilter filter);
void UpdateConversationTitle(string conversationId, string title);
bool UpdateConversationTags(string conversationId, List<string> tags);
bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request);
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);

View file

@ -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
{
/// <summary>
/// Interupt the authentication process, and return the user object if the user is authenticated
/// </summary>
/// <param name="id"></param>
/// <param name="password"></param>
/// <returns></returns>
Task<User> Authenticate(string id, string password);
void AddClaims(List<Claim> claims);
void BeforeSending(Token token);
/// <summary>
/// Add extra claims to user
/// </summary>
/// <param name="claims"></param>
/// <returns></returns>
bool AddClaims(List<Claim> claims)
=> true;
/// <summary>
/// User authenticated successfully
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
bool UserAuthenticated(JwtSecurityToken token)
=> true;
/// <summary>
/// Bfore user updating
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task UserUpdating(User user);
/// <summary>
/// After user created
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task UserCreated(User user);
/// <summary>
/// Reset password
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task VerificationCodeResetPassword(User user);
/// <summary>
/// Delete users
/// </summary>
/// <param name="userIds"></param>
/// <returns></returns>
Task DelUsers(List<string> userIds);
}

View file

@ -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);

View file

@ -51,6 +51,12 @@ public partial class ConversationService : IConversationService
return conversation;
}
public async Task<bool> UpdateConversationTags(string conversationId, List<string> tags)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
return db.UpdateConversationTags(conversationId, tags);
}
public async Task<bool> UpdateConversationMessage(string conversationId, UpdateMessageRequest request)
{
var db = _services.GetRequiredService<IBotSharpRepository>();

View file

@ -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 _),

View file

@ -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<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
{
var agentService = _services.GetRequiredService<IAgentService>();
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<IInstructHook>();
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<RoleDialogModel>
{
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;
}
}

View file

@ -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<T?> Instruct<T>(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<T>(text, _options.JsonSerializerOptions);
}
}
else
{
var text = response.Content.JsonContent();
if (!string.IsNullOrWhiteSpace(text))
{
result = JsonSerializer.Deserialize<T>(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<string, object> data)
{
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(instruction, data ?? new Dictionary<string, object>());
}
private async Task<RoleDialogModel> GetAiResponse(string agentId, string prompt, InstructOptions options)
{
var agentService = _services.GetRequiredService<IAgentService>();
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<RoleDialogModel> BuildDialogs(InstructOptions options)
{
var messages = new List<RoleDialogModel>();
if (!string.IsNullOrWhiteSpace(options.ConversationId))
{
var conv = _services.GetRequiredService<IConversationService>();
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);
}
}

View file

@ -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<InstructService> _logger;
public InstructService(IServiceProvider services, ILogger<InstructService> logger)
public InstructService(
IServiceProvider services,
BotSharpOptions options,
ILogger<InstructService> logger)
{
_services = services;
_options = options;
_logger = logger;
}
/// <summary>
/// Execute completion by using specified instruction or template
/// </summary>
/// <param name="agentId">Agent (static agent)</param>
/// <param name="message">Additional message provided by user</param>
/// <param name="templateName">Template name</param>
/// <param name="instruction">System prompt</param>
/// <returns></returns>
public async Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
{
var agentService = _services.GetRequiredService<IAgentService>();
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<IInstructHook>();
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<RoleDialogModel>
{
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;
}
}

View file

@ -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<string> tags)
=> throw new NotImplementedException();
public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request)
=> throw new NotImplementedException();

View file

@ -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<string> 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<Conversation>(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())

View file

@ -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;
}
}

View file

@ -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;

View file

@ -275,7 +275,7 @@ public class UserService : IUserService
foreach (var hook in hooks)
{
hook.BeforeSending(token);
hook.UserAuthenticated(jwt);
}
return token;

View file

@ -202,7 +202,7 @@ public class ConversationController : ControllerBase
public async Task<bool> UpdateConversationTitle([FromRoute] string conversationId, [FromBody] UpdateConversationTitleModel newTile)
{
var userService = _services.GetRequiredService<IUserService>();
var conversationService = _services.GetRequiredService<IConversationService>();
var conv = _services.GetRequiredService<IConversationService>();
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<bool> UpdateConversationTags([FromRoute] string conversationId, [FromBody] UpdateConversationRequest request)
{
var conv = _services.GetRequiredService<IConversationService>();
return await conv.UpdateConversationTags(conversationId, request.Tags);
}
[HttpPut("/conversation/{conversationId}/update-message")]
public async Task<bool> UpdateConversationMessage([FromRoute] string conversationId, [FromBody] UpdateMessageModel model)
{

View file

@ -29,6 +29,8 @@ public class ConversationViewModel
public string Status { get; set; }
public Dictionary<string, string> States { get; set; }
public List<string> 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
};

View file

@ -0,0 +1,6 @@
namespace BotSharp.OpenAPI.ViewModels.Conversations;
public class UpdateConversationRequest
{
public List<string> Tags { get; set; } = [];
}

View file

@ -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));
}
}

View file

@ -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)

View file

@ -1 +1,2 @@
Please call handle_excel_request if user wants to load the data from a excel/csv 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.

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
@ -21,7 +21,8 @@
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\confirm_knowledge_persistence.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\memorize_knowledge.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instructions\instruction.liquid" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.liquid" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.plain.liquid" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.refine.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\knowledge_retrieval.fn.liquid" />
</ItemGroup>
@ -38,7 +39,10 @@
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.liquid">
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.refine.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.plain.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\knowledge_retrieval.json">

View file

@ -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<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<ExtractedKnowledge>(message.FunctionArgs ?? "{}");
var agentService = _services.GetRequiredService<IAgentService>();
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<ExtractedKnowledge>();
message.Content = response.Content;
return true;
}
private async Task<string> GetGenerateKnowledgePrompt(string userQuestions, string sqlAnswer)
{
var agentService = _services.GetRequiredService<IAgentService>();
var render = _services.GetRequiredService<ITemplateRender>();
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<string, object>
{
{ "user_questions", userQuestions },
{ "sql_answer", sqlAnswer },
});
}
private async Task<RoleDialogModel> 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<RoleDialogModel> { message });
}
}

View file

@ -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.

View file

@ -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 }}

View file

@ -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<string> Tags { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}

View file

@ -6,5 +6,3 @@ public abstract class MongoBase
[BsonId(IdGenerator = typeof(StringGuidIdGenerator))]
public string Id { get; set; }
}

View file

@ -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<string> tags)
{
if (string.IsNullOrEmpty(conversationId)) return false;
var filter = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var update = Builders<ConversationDocument>.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<FilterDefinition<ConversationStateDocument>>();
@ -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();

View file

@ -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));
}
}

View file

@ -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<PrimaryStagePlanFn> _logger;
public PrimaryStagePlanFn(IServiceProvider services, ILogger<PrimaryStagePlanFn> logger)
public PrimaryStagePlanFn(
IServiceProvider services,
ILogger<PrimaryStagePlanFn> 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<string, object>(),
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<string>();

View file

@ -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<SecondaryStagePlanFn> _logger;
public SecondaryStagePlanFn(IServiceProvider services, ILogger<SecondaryStagePlanFn> logger)
public SecondaryStagePlanFn(
IServiceProvider services,
ILogger<SecondaryStagePlanFn> 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<SecondaryBreakdownTask>(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<string, object>(),
LlmConfig = currentAgent.LlmConfig
};
@ -63,7 +68,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
return true;
}
private async Task<string> GetSecondStagePlanPrompt(string taskDescription, string planPrimary, string knowledgeResults, RoleDialogModel message)
private async Task<string> GetSecondStagePlanPrompt(string taskDescription, string planResult, string knowledgeResults, RoleDialogModel message)
{
var agentService = _services.GetRequiredService<IAgentService>();
var render = _services.GetRequiredService<ITemplateRender>();
@ -79,7 +84,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
return render.Render(template, new Dictionary<string, object>
{
{ "task_description", taskDescription },
{ "primary_plan", planPrimary },
{ "primary_plan", planResult },
{ "additional_knowledge", knowledgeResults },
{ "response_format", responseFormat }
});

View file

@ -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<SummaryPlanFn> _logger;
@ -35,6 +36,7 @@ public class SummaryPlanFn : IFunctionCallback
var allTables = new List<string>();
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<RoleDialogModel> GetAiResponse(Agent plannerAgent)

View file

@ -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()
{

View file

@ -17,12 +17,12 @@
</ItemGroup>
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_dictionary_lookup.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_select.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_table_definition.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_dictionary_lookup.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\verify_dictionary_term.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_executor.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_table_definition.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\verify_dictionary_term.fn.liquid" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\agent.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\execute_sql.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\lookup_dictionary.json" />
@ -37,10 +37,10 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_table_definition.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_dictionary_lookup.json">
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\verify_dictionary_term.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_dictionary_lookup.fn.liquid">
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\verify_dictionary_term.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_table_definition.json">

View file

@ -99,6 +99,11 @@ public class ExecuteQueryFn : IFunctionCallback
private async Task<ExecuteQueryArgs> RefineSqlStatement(RoleDialogModel message, ExecuteQueryArgs args)
{
if (args.Tables == null || args.Tables.Length == 0)
{
return args;
}
// get table DDL
var fn = _services.GetRequiredService<IRoutingService>();
var msg = RoleDialogModel.From(message);

View file

@ -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;
}

View file

@ -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",

View file

@ -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" ]
}
}

View file

@ -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 }}

View file

@ -72,6 +72,7 @@ public class TwilioVoiceController : TwilioController
ConversationId = conversationId,
SeqNumber = seqNum,
Content = messageContent,
Digits = request.Digits,
From = request.From
};

View file

@ -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<string, string> States { get; set; } = new();

View file

@ -66,23 +66,11 @@ namespace BotSharp.Plugin.Twilio.Services
var sessionManager = sp.GetRequiredService<ITwilioSessionManager>();
var progressService = sp.GetRequiredService<IConversationProgressService>();
InitProgressService(message, sessionManager, progressService);
routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId);
var states = new List<MessageState>
{
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<MessageState>
{
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<string> GetReplySpeechFileName(string conversationId, AssistantMessage reply, IServiceProvider sp)
{
var completion = CompletionProvider.GetAudioCompletion(sp, "openai", "tts-1");
var fileStorage = sp.GetRequiredService<IFileStorageService>();
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<string>(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)

View file

@ -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<IWebPageResponseHook>();
foreach (var hook in webPageResponseHooks)
{

View file

@ -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;
}
}

View file

@ -12,12 +12,20 @@ public partial class PlaywrightWebDriver
if (args.Direction == "down")
{
// Get the total page height
int scrollY = await page.EvaluateAsync<int>("document.body.scrollHeight");
int scrollY = await page.EvaluateAsync<int>("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<int>("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<int>("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<int>("document.body.scrollHeight");
// Scroll to the top
await page.Mouse.WheelAsync(0, -scrollY);
}
else if (args.Direction == "left")
{
await page.EvaluateAsync(@"