Merge branch 'master' of https://github.com/ywang1110/BotSharp
This commit is contained in:
commit
7429480035
|
|
@ -4,5 +4,6 @@ namespace BotSharp.Abstraction.Browsing;
|
|||
|
||||
public interface IWebDriverHook
|
||||
{
|
||||
Task<List<string>> GetUploadFiles(MessageInfo message);
|
||||
Task<List<string>> GetUploadFiles(MessageInfo message) => Task.FromResult(new List<string>());
|
||||
Task OnLocateElement(MessageInfo message, string content) => Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,24 @@ public class MessageState
|
|||
[JsonPropertyName("active_rounds")]
|
||||
public int ActiveRounds { get; set; } = -1;
|
||||
|
||||
[JsonPropertyName("global")]
|
||||
public bool Global { get; set; }
|
||||
|
||||
public MessageState()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public MessageState(string key, object value, int activeRounds = -1)
|
||||
public MessageState(string key, object value, int activeRounds = -1, bool isGlobal = false)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
ActiveRounds = activeRounds;
|
||||
Global = isGlobal;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Key: {Key} => Value: {Value}, ActiveRounds: {ActiveRounds}";
|
||||
return $"Key: {Key} => Value: {Value}, ActiveRounds: {ActiveRounds}, Global: {Global}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ public interface IRealtimeHub
|
|||
|
||||
IRealTimeCompletion Completer { get; }
|
||||
|
||||
Task ConnectToModel(Func<string, Task>? responseToUser = null, Func<string, Task>? init = null);
|
||||
Task ConnectToModel(Func<string, Task>? responseToUser = null, Func<string, Task>? init = null, List<MessageState>? initStates = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ public class ConversationFilter
|
|||
public string? Title { get; set; }
|
||||
public string? TitleAlias { get; set; }
|
||||
public string? AgentId { get; set; }
|
||||
public List<string>? AgentIds { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? Channel { get; set; }
|
||||
public string? ChannelId { get; set; }
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public class SideCarAttribute : AsyncMoAttribute
|
|||
object? res = null;
|
||||
var isHandled = false;
|
||||
|
||||
var enabled = instance != null && instance.IsEnabled() && method != null;
|
||||
var enabled = instance != null && instance.IsEnabled && method != null;
|
||||
if (!enabled)
|
||||
{
|
||||
return (isHandled, value);
|
||||
|
|
@ -112,7 +112,7 @@ public class SideCarAttribute : AsyncMoAttribute
|
|||
object? value = null;
|
||||
var isHandled = false;
|
||||
|
||||
var enabled = instance != null && instance.IsEnabled() && method != null;
|
||||
var enabled = instance != null && instance.IsEnabled && method != null;
|
||||
if (!enabled)
|
||||
{
|
||||
return (isHandled, value);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
using BotSharp.Abstraction.SideCar.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.SideCar;
|
||||
|
||||
public interface IConversationSideCar
|
||||
{
|
||||
string Provider { get; }
|
||||
bool IsEnabled { get; }
|
||||
|
||||
bool IsEnabled();
|
||||
void AppendConversationDialogs(string conversationId, List<DialogElement> messages);
|
||||
List<DialogElement> GetConversationDialogs(string conversationId);
|
||||
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
|
||||
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
|
||||
void UpdateConversationStates(string conversationId, List<StateKeyValue> states);
|
||||
Task<RoleDialogModel> SendMessage(string agentId, string text,
|
||||
PostbackMessageModel? postback = null, List<MessageState>? states = null, List<DialogElement>? dialogs = null);
|
||||
PostbackMessageModel? postback = null,
|
||||
List<MessageState>? states = null,
|
||||
List<DialogElement>? dialogs = null,
|
||||
SideCarOptions? options = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
namespace BotSharp.Abstraction.SideCar.Models;
|
||||
|
||||
public class SideCarOptions
|
||||
{
|
||||
public bool IsInheritStates { get; set; }
|
||||
public IEnumerable<string>? InheritStateKeys { get; set; }
|
||||
|
||||
public static SideCarOptions Empty()
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
public static SideCarOptions InheritStates(IEnumerable<string>? targetStates = null)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
IsInheritStates = true,
|
||||
InheritStateKeys = targetStates
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Hooks;
|
||||
using BotSharp.Abstraction.Models;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
||||
|
|
@ -22,10 +23,10 @@ public class RealtimeHub : IRealtimeHub
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ConnectToModel(Func<string, Task>? responseToUser = null, Func<string, Task>? init = null)
|
||||
public async Task ConnectToModel(Func<string, Task>? responseToUser = null, Func<string, Task>? init = null, List<MessageState>? initStates = null)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
convService.SetConversationId(_conn.ConversationId, []);
|
||||
convService.SetConversationId(_conn.ConversationId, initStates ?? []);
|
||||
var conversation = await convService.GetConversation(_conn.ConversationId);
|
||||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ public class BotSharpConversationSideCar : IConversationSideCar
|
|||
private readonly ILogger<BotSharpConversationSideCar> _logger;
|
||||
|
||||
private Stack<ConversationContext> _contextStack = new();
|
||||
private SideCarOptions? _sideCarOptions;
|
||||
|
||||
private bool _enabled = false;
|
||||
private string _conversationId = string.Empty;
|
||||
|
||||
public string Provider => "botsharp";
|
||||
public bool IsEnabled => _enabled;
|
||||
|
||||
public BotSharpConversationSideCar(
|
||||
IServiceProvider services,
|
||||
|
|
@ -38,11 +40,6 @@ public class BotSharpConversationSideCar : IConversationSideCar
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool IsEnabled()
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
public void AppendConversationDialogs(string conversationId, List<DialogElement> messages)
|
||||
{
|
||||
if (!IsValid(conversationId))
|
||||
|
|
@ -97,12 +94,22 @@ public class BotSharpConversationSideCar : IConversationSideCar
|
|||
top.State = new ConversationState(states);
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> SendMessage(string agentId, string text,
|
||||
PostbackMessageModel? postback = null, List<MessageState>? states = null, List<DialogElement>? dialogs = null)
|
||||
public async Task<RoleDialogModel> SendMessage(
|
||||
string agentId,
|
||||
string text,
|
||||
PostbackMessageModel? postback = null,
|
||||
List<MessageState>? states = null,
|
||||
List<DialogElement>? dialogs = null,
|
||||
SideCarOptions? options = null)
|
||||
{
|
||||
_sideCarOptions = options;
|
||||
_logger.LogInformation($"Entering side car conversation...");
|
||||
|
||||
BeforeExecute(dialogs);
|
||||
var response = await InnerExecute(agentId, text, postback, states);
|
||||
AfterExecute();
|
||||
|
||||
_logger.LogInformation($"Existing side car conversation...");
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
@ -160,13 +167,11 @@ public class BotSharpConversationSideCar : IConversationSideCar
|
|||
|
||||
private void AfterExecute()
|
||||
{
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
|
||||
var node = _contextStack.Pop();
|
||||
|
||||
// Recover
|
||||
state.SetCurrentState(node.State);
|
||||
RestoreStates(node.State);
|
||||
routing.Context.SetRecursiveCounter(node.RecursiveCounter);
|
||||
routing.Context.SetAgentStack(node.RoutingStack);
|
||||
routing.Context.SetDialogs(node.RoutingDialogs);
|
||||
|
|
@ -181,4 +186,43 @@ public class BotSharpConversationSideCar : IConversationSideCar
|
|||
&& !string.IsNullOrEmpty(conversationId)
|
||||
&& !string.IsNullOrEmpty(_conversationId);
|
||||
}
|
||||
|
||||
private void RestoreStates(ConversationState prevStates)
|
||||
{
|
||||
var innerStates = prevStates;
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
if (_sideCarOptions?.IsInheritStates == true)
|
||||
{
|
||||
var curStates = state.GetCurrentState();
|
||||
foreach (var pair in curStates)
|
||||
{
|
||||
var endNode = pair.Value.Values.LastOrDefault();
|
||||
if (endNode == null) continue;
|
||||
|
||||
if (_sideCarOptions?.InheritStateKeys?.Any() == true
|
||||
&& !_sideCarOptions.InheritStateKeys.Contains(pair.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (innerStates.ContainsKey(pair.Key))
|
||||
{
|
||||
innerStates[pair.Key].Values.Add(endNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
innerStates[pair.Key] = new StateKeyValue
|
||||
{
|
||||
Key = pair.Key,
|
||||
Versioning = pair.Value.Versioning,
|
||||
Readonly = pair.Value.Readonly,
|
||||
Values = [endNode]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.SetCurrentState(innerStates);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,5 +16,6 @@ global using BotSharp.Abstraction.Conversations.Models;
|
|||
global using BotSharp.Abstraction.Models;
|
||||
global using BotSharp.Abstraction.Routing;
|
||||
global using BotSharp.Abstraction.SideCar;
|
||||
global using BotSharp.Abstraction.SideCar.Models;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Core.SideCar.Settings;
|
||||
|
|
@ -178,7 +178,7 @@ public partial class ConversationService : IConversationService
|
|||
{
|
||||
_conversationId = conversationId;
|
||||
_state.Load(_conversationId, isReadOnly);
|
||||
states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
|
||||
states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, isNeedVersion: !x.Global, source: StateSource.External));
|
||||
}
|
||||
|
||||
public async Task<Conversation> GetConversationRecordOrCreateNew(string agentId)
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ public class ConversationStateService : IConversationStateService
|
|||
Reset();
|
||||
|
||||
var endNodes = new Dictionary<string, string>();
|
||||
if (_sidecar?.IsEnabled() == true)
|
||||
if (_sidecar?.IsEnabled == true)
|
||||
{
|
||||
return endNodes;
|
||||
}
|
||||
|
|
@ -234,7 +234,7 @@ public class ConversationStateService : IConversationStateService
|
|||
|
||||
public void Save()
|
||||
{
|
||||
if (_conversationId == null || _sidecar?.IsEnabled() == true)
|
||||
if (_conversationId == null || _sidecar?.IsEnabled == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@ public partial class FileRepository
|
|||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
if (filter?.AgentId != null)
|
||||
{
|
||||
filter.AgentIds ??= [];
|
||||
filter.AgentIds.Add(filter.AgentId);
|
||||
}
|
||||
|
||||
var totalDirs = Directory.GetDirectories(dir);
|
||||
foreach (var d in totalDirs)
|
||||
{
|
||||
|
|
@ -419,9 +425,9 @@ public partial class FileRepository
|
|||
{
|
||||
matched = matched && record.TitleAlias.Contains(filter.TitleAlias);
|
||||
}
|
||||
if (filter?.AgentId != null)
|
||||
if (filter?.AgentIds != null && filter.AgentIds.Any())
|
||||
{
|
||||
matched = matched && record.AgentId == filter.AgentId;
|
||||
matched = matched && filter.AgentIds.Contains(record.AgentId);
|
||||
}
|
||||
if (filter?.Status != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
using BotSharp.Abstraction.Statistics.Models;
|
||||
using BotSharp.Abstraction.Statistics.Services;
|
||||
using BotSharp.Abstraction.Users;
|
||||
|
||||
namespace BotSharp.Logger.Hooks;
|
||||
|
|
@ -25,7 +22,10 @@ public class RateLimitConversationHook : ConversationHookBase
|
|||
public override async Task OnMessageReceived(RoleDialogModel message)
|
||||
{
|
||||
var settings = _services.GetRequiredService<ConversationSetting>();
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
var rateLimit = settings.RateLimit;
|
||||
var channel = states.GetState("channel");
|
||||
|
||||
// Check max input length
|
||||
var charCount = message.Content.Length;
|
||||
|
|
@ -45,7 +45,7 @@ public class RateLimitConversationHook : ConversationHookBase
|
|||
var userSents = Dialogs.Where(x => x.Role == AgentRole.User)
|
||||
.TakeLast(2).ToList();
|
||||
|
||||
if (userSents.Count > 1)
|
||||
if (channel != ConversationChannel.Phone && userSents.Count > 1)
|
||||
{
|
||||
var seconds = (DateTime.UtcNow - userSents.First().CreatedAt).TotalSeconds;
|
||||
if (seconds < rateLimit.MinTimeSecondsBetweenMessages)
|
||||
|
|
@ -56,9 +56,6 @@ public class RateLimitConversationHook : ConversationHookBase
|
|||
}
|
||||
}
|
||||
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var channel = states.GetState("channel");
|
||||
|
||||
// Check the number of conversations
|
||||
if (channel != ConversationChannel.Phone && channel != ConversationChannel.Email && channel != ConversationChannel.Database)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Models;
|
||||
using BotSharp.Abstraction.Realtime.Models.Session;
|
||||
using BotSharp.Core.Session;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -62,9 +63,11 @@ public class ChatStreamMiddleware
|
|||
var hub = services.GetRequiredService<IRealtimeHub>();
|
||||
var conn = hub.SetHubConnection(conversationId);
|
||||
conn.CurrentAgentId = agentId;
|
||||
InitEvents(conn);
|
||||
|
||||
// load conversation and state
|
||||
var convService = services.GetRequiredService<IConversationService>();
|
||||
var state = services.GetRequiredService<IConversationStateService>();
|
||||
convService.SetConversationId(conversationId, []);
|
||||
await convService.GetConversationRecordOrCreateNew(agentId);
|
||||
|
||||
|
|
@ -79,7 +82,8 @@ public class ChatStreamMiddleware
|
|||
var (eventType, data) = MapEvents(conn, receivedText);
|
||||
if (eventType == "start")
|
||||
{
|
||||
await ConnectToModel(hub, webSocket);
|
||||
var request = InitRequest(data);
|
||||
await ConnectToModel(hub, webSocket, request?.States);
|
||||
}
|
||||
else if (eventType == "media")
|
||||
{
|
||||
|
|
@ -95,25 +99,26 @@ public class ChatStreamMiddleware
|
|||
}
|
||||
}
|
||||
|
||||
convService.SaveStates();
|
||||
await _session.DisconnectAsync();
|
||||
_session.Dispose();
|
||||
}
|
||||
|
||||
private async Task ConnectToModel(IRealtimeHub hub, WebSocket webSocket)
|
||||
private async Task ConnectToModel(IRealtimeHub hub, WebSocket webSocket, List<MessageState>? states = null)
|
||||
{
|
||||
await hub.ConnectToModel(async data =>
|
||||
await hub.ConnectToModel(responseToUser: async data =>
|
||||
{
|
||||
if (_session != null)
|
||||
{
|
||||
await _session.SendEventAsync(data);
|
||||
}
|
||||
});
|
||||
}, initStates: states);
|
||||
}
|
||||
|
||||
private (string, string) MapEvents(RealtimeHubConnection conn, string receivedText)
|
||||
{
|
||||
var response = JsonSerializer.Deserialize<ChatStreamEventResponse>(receivedText);
|
||||
string data = string.Empty;
|
||||
var data = response?.Body?.Payload ?? string.Empty;
|
||||
|
||||
switch (response.Event)
|
||||
{
|
||||
|
|
@ -121,13 +126,16 @@ public class ChatStreamMiddleware
|
|||
conn.ResetStreamState();
|
||||
break;
|
||||
case "media":
|
||||
var mediaResponse = JsonSerializer.Deserialize<ChatStreamMediaEventResponse>(receivedText);
|
||||
data = mediaResponse?.Body?.Payload ?? string.Empty;
|
||||
break;
|
||||
case "disconnect":
|
||||
break;
|
||||
}
|
||||
|
||||
return (response.Event, data);
|
||||
}
|
||||
|
||||
private void InitEvents(RealtimeHubConnection conn)
|
||||
{
|
||||
conn.OnModelMessageReceived = message =>
|
||||
JsonSerializer.Serialize(new
|
||||
{
|
||||
|
|
@ -147,7 +155,17 @@ public class ChatStreamMiddleware
|
|||
{
|
||||
@event = "clear"
|
||||
});
|
||||
}
|
||||
|
||||
return (response.Event, data);
|
||||
private ChatStreamRequest? InitRequest(string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<ChatStreamRequest>(data, BotSharpOptions.defaultJsonOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
private bool AllowSendingMessage()
|
||||
{
|
||||
var sidecar = _services.GetService<IConversationSideCar>();
|
||||
return sidecar == null || !sidecar.IsEnabled();
|
||||
return sidecar == null || !sidecar.IsEnabled;
|
||||
}
|
||||
|
||||
private async Task InitClientConversation(string conversationId, ConversationDto conversation)
|
||||
|
|
|
|||
|
|
@ -6,15 +6,12 @@ internal class ChatStreamEventResponse
|
|||
{
|
||||
[JsonPropertyName("event")]
|
||||
public string Event { get; set; }
|
||||
}
|
||||
|
||||
internal class ChatStreamMediaEventResponse : ChatStreamEventResponse
|
||||
{
|
||||
[JsonPropertyName("body")]
|
||||
public MediaEventResponseBody Body { get; set; }
|
||||
public ChatStreamEventResponseBody Body { get; set; }
|
||||
}
|
||||
|
||||
internal class MediaEventResponseBody
|
||||
internal class ChatStreamEventResponseBody
|
||||
{
|
||||
[JsonPropertyName("payload")]
|
||||
public string Payload { get; set; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using BotSharp.Abstraction.Models;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Plugin.ChatHub.Models.Stream;
|
||||
|
||||
public class ChatStreamRequest
|
||||
{
|
||||
[JsonPropertyName("states")]
|
||||
public List<MessageState> States { get; set; } = [];
|
||||
}
|
||||
|
|
@ -270,7 +270,7 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
{
|
||||
messages.Add(new AssistantChatMessage(new List<ChatToolCall>
|
||||
{
|
||||
ChatToolCall.CreateFunctionToolCall(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty))
|
||||
ChatToolCall.CreateFunctionToolCall(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? "{}"))
|
||||
}));
|
||||
|
||||
messages.Add(new ToolChatMessage(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.Content));
|
||||
|
|
|
|||
|
|
@ -329,17 +329,13 @@ public class GoogleRealTimeProvider : IRealTimeCompletion
|
|||
var words = new List<string>();
|
||||
HookEmitter.Emit<IRealtimeHook>(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)), agent.Id);
|
||||
|
||||
var functions = request.Tools?.SelectMany(s => s.FunctionDeclarations).Select(x =>
|
||||
{
|
||||
var fn = new FunctionDef
|
||||
var functions = request.Tools?.SelectMany(s => s.FunctionDeclarations).Select(x => new FunctionDef
|
||||
{
|
||||
Name = x.Name ?? string.Empty,
|
||||
Description = x.Description ?? string.Empty,
|
||||
Parameters = x.Parameters != null
|
||||
? JsonSerializer.Deserialize<FunctionParametersDef>(JsonSerializer.Serialize(x.Parameters))
|
||||
: null
|
||||
};
|
||||
return fn;
|
||||
}).ToArray();
|
||||
|
||||
await HookEmitter.Emit<IContentGeneratingHook>(_services,
|
||||
|
|
|
|||
|
|
@ -346,6 +346,12 @@ public partial class MongoRepository
|
|||
var convBuilder = Builders<ConversationDocument>.Filter;
|
||||
var convFilters = new List<FilterDefinition<ConversationDocument>>() { convBuilder.Empty };
|
||||
|
||||
if (filter?.AgentId != null)
|
||||
{
|
||||
filter.AgentIds ??= [];
|
||||
filter.AgentIds.Add(filter.AgentId);
|
||||
}
|
||||
|
||||
// Filter conversations
|
||||
if (!string.IsNullOrEmpty(filter?.Id))
|
||||
{
|
||||
|
|
@ -359,9 +365,9 @@ public partial class MongoRepository
|
|||
{
|
||||
convFilters.Add(convBuilder.Regex(x => x.Title, new BsonRegularExpression(filter.TitleAlias, "i")));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(filter?.AgentId))
|
||||
if (filter?.AgentIds != null && filter.AgentIds.Any())
|
||||
{
|
||||
convFilters.Add(convBuilder.Eq(x => x.AgentId, filter.AgentId));
|
||||
convFilters.Add(convBuilder.In(x => x.AgentId, filter.AgentIds));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(filter?.Status))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -326,15 +326,11 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
var (prompt, messages, options) = PrepareOptions(agent, []);
|
||||
|
||||
var instruction = messages.FirstOrDefault()?.Content.FirstOrDefault()?.Text ?? agent?.Description ?? string.Empty;
|
||||
var functions = options.Tools.Select(x =>
|
||||
{
|
||||
var fn = new FunctionDef
|
||||
var functions = options.Tools.Select(x => new FunctionDef
|
||||
{
|
||||
Name = x.FunctionName,
|
||||
Description = x.FunctionDescription
|
||||
};
|
||||
fn.Parameters = JsonSerializer.Deserialize<FunctionParametersDef>(x.FunctionParameters);
|
||||
return fn;
|
||||
Description = x.FunctionDescription,
|
||||
Parameters = JsonSerializer.Deserialize<FunctionParametersDef>(x.FunctionParameters)
|
||||
}).ToArray();
|
||||
|
||||
var realtimeModelSettings = _services.GetRequiredService<RealtimeModelSettings>();
|
||||
|
|
@ -615,10 +611,10 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
{
|
||||
messages.Add(new AssistantChatMessage(new List<ChatToolCall>
|
||||
{
|
||||
ChatToolCall.CreateFunctionToolCall(message.ToolCallId, message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty))
|
||||
ChatToolCall.CreateFunctionToolCall(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? "{}"))
|
||||
}));
|
||||
|
||||
messages.Add(new ToolChatMessage(message.ToolCallId, message.Content));
|
||||
messages.Add(new ToolChatMessage(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.Content));
|
||||
}
|
||||
else if (message.Role == AgentRole.User)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class ExecuteQueryFn : IFunctionCallback
|
|||
var results = dbType.ToLower() switch
|
||||
{
|
||||
"mysql" => RunQueryInMySql(args.SqlStatements),
|
||||
"sqlserver" => RunQueryInSqlServer(args.SqlStatements),
|
||||
"sqlserver" or "mssql" => RunQueryInSqlServer(args.SqlStatements),
|
||||
"redshift" => RunQueryInRedshift(args.SqlStatements),
|
||||
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class GetTableDefinitionFn : IFunctionCallback
|
|||
var tableDdls = dbType switch
|
||||
{
|
||||
"mysql" => GetDdlFromMySql(tables),
|
||||
"sqlserver" => GetDdlFromSqlServer(tables),
|
||||
"sqlserver" or "mssql" => GetDdlFromSqlServer(tables),
|
||||
"redshift" => GetDdlFromRedshift(tables),
|
||||
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class SqlSelect : IFunctionCallback
|
|||
var result = dbType switch
|
||||
{
|
||||
"mysql" => RunQueryInMySql(args),
|
||||
"sqlserver" => RunQueryInSqlServer(args),
|
||||
"sqlserver" or "mssql" => RunQueryInSqlServer(args),
|
||||
"redshift" => RunQueryInRedshift(args),
|
||||
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class SqlValidateFn : IFunctionCallback
|
|||
var validateSql = dbType.ToLower() switch
|
||||
{
|
||||
"mysql" => $"EXPLAIN\r\n{sql.Replace("SET ", "-- SET ", StringComparison.InvariantCultureIgnoreCase).Replace(";", "; EXPLAIN ").TrimEnd("EXPLAIN ".ToCharArray())}",
|
||||
"sqlserver" => $"SET PARSEONLY ON;\r\n{sql}\r\nSET PARSEONLY OFF;",
|
||||
"sqlserver" or "mssql" => $"SET PARSEONLY ON;\r\n{sql}\r\nSET PARSEONLY OFF;",
|
||||
"redshift" => $"explain\r\n{sql}",
|
||||
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class GetTableDefinitionFn : IFunctionCallback
|
|||
var tableDdls = dbType switch
|
||||
{
|
||||
"mysql" => GetDdlFromMySql(tables),
|
||||
"sqlserver" => GetDdlFromSqlServer(tables),
|
||||
"sqlserver" or "mssql" => GetDdlFromSqlServer(tables),
|
||||
"redshift" => GetDdlFromRedshift(tables,schema),
|
||||
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class SqlSelect : IFunctionCallback
|
|||
var result = dbType switch
|
||||
{
|
||||
"mysql" => RunQueryInMySql(args),
|
||||
"sqlserver" => RunQueryInSqlServer(args),
|
||||
"sqlserver" or "mssql" => RunQueryInSqlServer(args),
|
||||
"redshift" => RunQueryInRedshift(args),
|
||||
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -53,20 +53,29 @@ public class TwilioInboundController : TwilioController
|
|||
instruction.SpeechPaths.Add(request.InitAudioFile);
|
||||
}
|
||||
|
||||
// Before creating session
|
||||
await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
|
||||
{
|
||||
await hook.OnSessionCreating(request, instruction);
|
||||
}, request.AgentId);
|
||||
|
||||
|
||||
var (agent, conversationId) = await InitConversation(request);
|
||||
request.ConversationId = conversationId.Id;
|
||||
instruction.AgentId = request.AgentId;
|
||||
instruction.ConversationId = request.ConversationId;
|
||||
|
||||
|
||||
// After creating session
|
||||
await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
|
||||
{
|
||||
await hook.OnSessionCreated(request);
|
||||
}, request.AgentId);
|
||||
|
||||
|
||||
if (twilio.MachineDetected(request))
|
||||
{
|
||||
response = new VoiceResponse();
|
||||
|
||||
await HookEmitter.Emit<ITwilioCallStatusHook>(_services,
|
||||
async hook => await hook.OnVoicemailStarting(request), request.AgentId);
|
||||
|
||||
|
|
@ -116,11 +125,6 @@ public class TwilioInboundController : TwilioController
|
|||
});
|
||||
}
|
||||
|
||||
await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
|
||||
{
|
||||
await hook.OnSessionCreated(request);
|
||||
}, request.AgentId);
|
||||
|
||||
return TwiML(response);
|
||||
}
|
||||
|
||||
|
|
@ -162,16 +166,16 @@ public class TwilioInboundController : TwilioController
|
|||
|
||||
var states = new List<MessageState>
|
||||
{
|
||||
new("channel", ConversationChannel.Phone),
|
||||
new("calling_phone", request.From),
|
||||
new("phone_direction", request.Direction),
|
||||
new("twilio_call_sid", request.CallSid),
|
||||
new("channel", ConversationChannel.Phone, isGlobal: true),
|
||||
new("calling_phone", request.From, isGlobal: true),
|
||||
new("phone_direction", request.Direction, isGlobal: true),
|
||||
new("twilio_call_sid", request.CallSid, isGlobal: true),
|
||||
};
|
||||
|
||||
if (request.Direction == "inbound")
|
||||
{
|
||||
states.Add(new MessageState("calling_phone_from", request.From));
|
||||
states.Add(new MessageState("calling_phone_to", request.To));
|
||||
states.Add(new MessageState("calling_phone_from", request.From, isGlobal: true));
|
||||
states.Add(new MessageState("calling_phone_to", request.To, isGlobal: true));
|
||||
}
|
||||
|
||||
var requestStates = ParseStates(request.States);
|
||||
|
|
@ -204,7 +208,7 @@ public class TwilioInboundController : TwilioController
|
|||
|
||||
storage.Append(conversation.Id, new RoleDialogModel(AgentRole.User, request.Intent)
|
||||
{
|
||||
CurrentAgentId = conversation.Id,
|
||||
CurrentAgentId = agent.Id,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using BotSharp.Plugin.Twilio.Interfaces;
|
|||
using BotSharp.Plugin.Twilio.Models;
|
||||
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
|
||||
using Twilio.Rest.Api.V2010.Account;
|
||||
using Twilio.TwiML.Messaging;
|
||||
using Twilio.Types;
|
||||
using Conversation = BotSharp.Abstraction.Conversations.Models.Conversation;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
|
@ -176,7 +177,7 @@ public class OutboundPhoneCallFn : IFunctionCallback
|
|||
});
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
var excludStates = new List<string>
|
||||
var excludeStates = new List<string>
|
||||
{
|
||||
"provider",
|
||||
"model",
|
||||
|
|
@ -185,22 +186,34 @@ public class OutboundPhoneCallFn : IFunctionCallback
|
|||
"llm_total_cost"
|
||||
};
|
||||
|
||||
var curStates = state.GetStates().Select(x => new MessageState(x.Key, x.Value)).ToList();
|
||||
var curConvStates = state.GetStates().Select(x => new MessageState(x.Key, x.Value)).ToList();
|
||||
var subConvStates = new List<MessageState>
|
||||
{
|
||||
new(StateConst.ORIGIN_CONVERSATION_ID, originConversationId),
|
||||
new("channel", "phone"),
|
||||
new("phone_from", call.From),
|
||||
new("phone_direction", call.Direction),
|
||||
new("phone_number", call.To),
|
||||
new("twilio_call_sid", call.Sid)
|
||||
new(StateConst.ORIGIN_CONVERSATION_ID, originConversationId, isGlobal: true),
|
||||
new("channel", "phone", isGlobal: true),
|
||||
new("phone_from", call.From, isGlobal: true),
|
||||
new("phone_direction", call.Direction, isGlobal: true),
|
||||
new("phone_number", call.To, isGlobal: true),
|
||||
new("twilio_call_sid", call.Sid, isGlobal: true)
|
||||
};
|
||||
var subStateKeys = subConvStates.Select(x => x.Key).ToList();
|
||||
var included = curStates.Where(x => !subStateKeys.Contains(x.Key) && !excludStates.Contains(x.Key));
|
||||
var newStates = subConvStates.Concat(included).Select(x => new StateKeyValue
|
||||
var included = curConvStates.Where(x => !subStateKeys.Contains(x.Key) && !excludeStates.Contains(x.Key));
|
||||
|
||||
var mappedCurConvStates = MapStates(included, messageId, utcNow);
|
||||
var mappedSubConvStates = MapStates(subConvStates, messageId, utcNow);
|
||||
var allStates = mappedCurConvStates.Concat(mappedSubConvStates).ToList();
|
||||
|
||||
db.UpdateConversationStates(newConversationId, allStates);
|
||||
}
|
||||
|
||||
private IEnumerable<StateKeyValue> MapStates(IEnumerable<MessageState> states, string messageId, DateTime updateTime)
|
||||
{
|
||||
if (states.IsNullOrEmpty()) return [];
|
||||
|
||||
return states.Select(x => new StateKeyValue
|
||||
{
|
||||
Key = x.Key,
|
||||
Versioning = true,
|
||||
Versioning = !x.Global,
|
||||
Values = [
|
||||
new StateValue
|
||||
{
|
||||
|
|
@ -209,11 +222,9 @@ public class OutboundPhoneCallFn : IFunctionCallback
|
|||
Active = true,
|
||||
ActiveRounds = x.ActiveRounds,
|
||||
Source = StateSource.Application,
|
||||
UpdateTime = utcNow
|
||||
UpdateTime = updateTime
|
||||
}
|
||||
]
|
||||
}).ToList();
|
||||
|
||||
db.UpdateConversationStates(newConversationId, newStates);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,11 @@ public partial class PlaywrightWebDriver
|
|||
// fix if html has &
|
||||
result.Body = HttpUtility.HtmlDecode(html);
|
||||
result.IsSuccess = true;
|
||||
var hooks = _services.GetServices<IWebDriverHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnLocateElement(message, result.Body);
|
||||
}
|
||||
}
|
||||
else if (count > 1)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class UtilWebCloseBrowserFn : IFunctionCallback
|
|||
ContextId = webDriverService.GetMessageContext(message)
|
||||
};
|
||||
|
||||
await browser.CloseBrowser(message.CurrentAgentId);
|
||||
await browser.CloseBrowser(msg.ContextId);
|
||||
|
||||
message.Content = $"Browser closed.";
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public class UtilWebLocateElementFn : IFunctionCallback
|
|||
MessageId = message.MessageId,
|
||||
ContextId = webDriverService.GetMessageContext(message)
|
||||
};
|
||||
browser.SetServiceProvider(_services);
|
||||
var result = await browser.LocateElement(msg, locatorArgs);
|
||||
|
||||
message.Content = $"Locating element {(result.IsSuccess ? "success" : "failed")}. ";
|
||||
|
|
|
|||
Loading…
Reference in a new issue