Merge branch 'master' into lida_dev
This commit is contained in:
commit
2f6c3d410d
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
namespace BotSharp.Abstraction.Browsing.Models;
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public class ElementLocatingArgs
|
||||
{
|
||||
[JsonPropertyName("match_rule")]
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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; } = [];
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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 _),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ public class UserService : IUserService
|
|||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.BeforeSending(token);
|
||||
hook.UserAuthenticated(jwt);
|
||||
}
|
||||
|
||||
return token;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.OpenAPI.ViewModels.Conversations;
|
||||
|
||||
public class UpdateConversationRequest
|
||||
{
|
||||
public List<string> Tags { get; set; } = [];
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,5 +6,3 @@ public abstract class MongoBase
|
|||
[BsonId(IdGenerator = typeof(StringGuidIdGenerator))]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
@ -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" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -72,6 +72,7 @@ public class TwilioVoiceController : TwilioController
|
|||
ConversationId = conversationId,
|
||||
SeqNumber = seqNum,
|
||||
Content = messageContent,
|
||||
Digits = request.Digits,
|
||||
From = request.From
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(@"
|
||||
|
|
|
|||
Loading…
Reference in a new issue