Merge branch 'master' of https://github.com/SciSharp/BotSharp into features/call-dummy-func
This commit is contained in:
commit
369ca68f5b
|
|
@ -20,7 +20,7 @@ public class PageActionArgs
|
|||
public bool OpenNewTab { get; set; } = true;
|
||||
[JsonPropertyName("open_blank_page")]
|
||||
public bool OpenBlankPage { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("enable_response_callback")]
|
||||
public bool EnableResponseCallback { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,20 @@ public class WebPageResponseData
|
|||
public string ResponseData { get; set; } = null!;
|
||||
public bool ResponseInMemory { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public string Method { get; set; }
|
||||
public List<WebPageCookieData> Cookies { get; set; }
|
||||
public int ResponseCode { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Url} {ResponseData.Length}";
|
||||
}
|
||||
}
|
||||
public class WebPageCookieData
|
||||
{
|
||||
public string Name { get; set; } = null!;
|
||||
public string Value { get; set; } = null!;
|
||||
public string Domain { get; set; } = null!;
|
||||
public string Path { get; set; } = null!;
|
||||
public float Expires { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,13 +116,14 @@ public class RealtimeHub : IRealtimeHub
|
|||
}
|
||||
else
|
||||
{
|
||||
// Push dialogs into model context
|
||||
// Append dialogs into model context
|
||||
var history = "[CONVERSATION HISTORY]\r\n";
|
||||
foreach (var message in dialogs)
|
||||
{
|
||||
await _completer.InsertConversationItem(message);
|
||||
history += $"{message.Role}: {message.Content}\r\n";
|
||||
}
|
||||
|
||||
await _completer.TriggerModelInference($"{instruction}\r\n\r\nAssist user without repeating your previous statement.");
|
||||
await _completer.TriggerModelInference($"{instruction}\r\n\r\n{history}\r\n\r\nAssist user without repeating your previous statement.");
|
||||
}
|
||||
},
|
||||
onModelAudioDeltaReceived: async (audioDeltaData, itemId) =>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@
|
|||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-transfer_phone_call.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-hangup_phone_call.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-text_message.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-outbound_phone_call.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-hangup_phone_call.fn.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-outbound_phone_call.fn.liquid" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-transfer_phone_call.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
|
@ -22,6 +31,12 @@
|
|||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-outbound_phone_call.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-hangup_phone_call.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-outbound_phone_call.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ public class TwilioVoiceController : TwilioController
|
|||
OnlyOnce = true
|
||||
});
|
||||
|
||||
response = twilio.HangUp(null);
|
||||
response = twilio.HangUp(string.Empty);
|
||||
}
|
||||
// keep waiting for user response
|
||||
else
|
||||
|
|
@ -433,8 +433,18 @@ public class TwilioVoiceController : TwilioController
|
|||
[HttpPost("twilio/voice/hang-up")]
|
||||
public async Task<TwiMLResult> Hangup(ConversationalVoiceRequest request)
|
||||
{
|
||||
var instruction = new ConversationalVoiceResponse
|
||||
{
|
||||
ConversationId = request.ConversationId
|
||||
};
|
||||
|
||||
if (request.InitAudioFile != null)
|
||||
{
|
||||
instruction.SpeechPaths.Add(request.InitAudioFile);
|
||||
}
|
||||
|
||||
var twilio = _services.GetRequiredService<TwilioService>();
|
||||
var response = twilio.HangUp("twilio/bye.mp3");
|
||||
var response = twilio.HangUp(instruction);
|
||||
return TwiML(response);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
|
||||
using Twilio.Rest.Api.V2010.Account;
|
||||
|
||||
|
|
@ -27,6 +29,7 @@ public class HangupPhoneCallFn : IFunctionCallback
|
|||
{
|
||||
var args = JsonSerializer.Deserialize<HangupPhoneCallArgs>(message.FunctionArgs);
|
||||
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var conversationId = routing.Context.ConversationId;
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
|
|
@ -39,21 +42,28 @@ public class HangupPhoneCallFn : IFunctionCallback
|
|||
return false;
|
||||
}
|
||||
|
||||
if (args.AnythingElseToHelp)
|
||||
{
|
||||
message.Content = "Tell me how I can help.";
|
||||
}
|
||||
else
|
||||
{
|
||||
var call = CallResource.Update(
|
||||
url: new Uri($"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?conversation-id={conversationId}"),
|
||||
pathSid: callSid
|
||||
);
|
||||
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?conversation-id={conversationId}";
|
||||
|
||||
message.Content = "The call is ending.";
|
||||
message.StopCompletion = true;
|
||||
// Generate initial assistant audio
|
||||
string initAudioFile = null;
|
||||
if (!string.IsNullOrEmpty(args.ResponseContent))
|
||||
{
|
||||
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
|
||||
var data = await completion.GenerateAudioFromTextAsync(args.ResponseContent);
|
||||
initAudioFile = "ending.mp3";
|
||||
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);
|
||||
|
||||
processUrl += $"&init-audio-file={initAudioFile}";
|
||||
}
|
||||
|
||||
var call = CallResource.Update(
|
||||
url: new Uri(processUrl),
|
||||
pathSid: callSid
|
||||
);
|
||||
|
||||
message.Content = args.Reason;
|
||||
message.StopCompletion = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,44 +35,35 @@ public class TransferPhoneCallFn : IFunctionCallback
|
|||
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var conversationId = routing.Context.ConversationId;
|
||||
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/transfer-call?conversation-id={conversationId}&transfer-to={args.PhoneNumber}";
|
||||
|
||||
// Generate initial assistant audio
|
||||
string initAudioFile = null;
|
||||
if (!string.IsNullOrEmpty(args.TransitionMessage))
|
||||
{
|
||||
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
|
||||
var data = await completion.GenerateAudioFromTextAsync(args.TransitionMessage);
|
||||
initAudioFile = "transfer.mp3";
|
||||
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);
|
||||
|
||||
processUrl += $"&init-audio-file={initAudioFile}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(initAudioFile))
|
||||
{
|
||||
processUrl += $"&init-audio-file={initAudioFile}";
|
||||
}
|
||||
|
||||
// Forward call
|
||||
var sid = states.GetState("twilio_call_sid");
|
||||
|
||||
if (string.IsNullOrEmpty(sid))
|
||||
{
|
||||
_logger.LogError("Twilio call sid is empty.");
|
||||
message.Content = "There is an error when transferring the phone call.";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var call = CallResource.Update(
|
||||
pathSid: sid,
|
||||
url: new Uri(processUrl));
|
||||
|
||||
message.Content = args.TransitionMessage;
|
||||
return true;
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var conversationId = routing.Context.ConversationId;
|
||||
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/transfer-call?conversation-id={conversationId}&transfer-to={args.PhoneNumber}";
|
||||
|
||||
// Generate initial assistant audio
|
||||
if (!string.IsNullOrEmpty(args.TransitionMessage))
|
||||
{
|
||||
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
|
||||
var data = await completion.GenerateAudioFromTextAsync(args.TransitionMessage);
|
||||
var initAudioFile = "transfer.mp3";
|
||||
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);
|
||||
|
||||
processUrl += $"&init-audio-file={initAudioFile}";
|
||||
}
|
||||
|
||||
// Transfer call
|
||||
var call = CallResource.Update(
|
||||
pathSid: sid,
|
||||
url: new Uri(processUrl));
|
||||
|
||||
message.Content = args.TransitionMessage;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
|
|||
|
||||
public class HangupPhoneCallArgs
|
||||
{
|
||||
[JsonPropertyName("anything_else_to_help")]
|
||||
public bool AnythingElseToHelp { get; set; } = true;
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("response_content")]
|
||||
public string ResponseContent { get; set; } = null!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,6 +149,22 @@ public class TwilioService
|
|||
return response;
|
||||
}
|
||||
|
||||
public VoiceResponse HangUp(ConversationalVoiceResponse voiceResponse)
|
||||
{
|
||||
var response = new VoiceResponse();
|
||||
var conversationId = voiceResponse.ConversationId;
|
||||
if (voiceResponse.SpeechPaths != null && voiceResponse.SpeechPaths.Any())
|
||||
{
|
||||
foreach (var speechPath in voiceResponse.SpeechPaths)
|
||||
{
|
||||
var uri = GetSpeechPath(conversationId, speechPath);
|
||||
response.Play(new Uri(uri));
|
||||
}
|
||||
}
|
||||
response.Hangup();
|
||||
return response;
|
||||
}
|
||||
|
||||
public VoiceResponse DialCsrAgent(string speechPath)
|
||||
{
|
||||
var response = new VoiceResponse();
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
"type": "string",
|
||||
"description": "The reason why user wants to end the phone call."
|
||||
},
|
||||
"anything_else_to_help": {
|
||||
"type": "boolean",
|
||||
"description": "Check if user has anything else to help."
|
||||
"response_content": {
|
||||
"type": "string",
|
||||
"description": "A statement said to the user when politely ending a conversation."
|
||||
}
|
||||
},
|
||||
"required": [ "reason", "anything_else_to_help" ]
|
||||
"required": [ "reason", "response_content" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using Azure;
|
||||
using BotSharp.Abstraction.Browsing.Settings;
|
||||
using Microsoft.Playwright;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
||||
|
|
@ -131,7 +132,6 @@ public class PlaywrightInstance : IDisposable
|
|||
{
|
||||
return page;
|
||||
}
|
||||
|
||||
page.Request += async (sender, e) =>
|
||||
{
|
||||
await HandleFetchRequest(e, message, args);
|
||||
|
|
@ -154,7 +154,7 @@ public class PlaywrightInstance : IDisposable
|
|||
|
||||
public async Task HandleFetchResponse(IResponse response, MessageInfo message, PageActionArgs args)
|
||||
{
|
||||
if (response.Status != 204 &&
|
||||
if (response.Status != 204 && response.Status != 302 &&
|
||||
response.Headers.ContainsKey("content-type") &&
|
||||
(response.Request.ResourceType == "fetch" || response.Request.ResourceType == "xhr") &&
|
||||
(args.ExcludeResponseUrls == null || !args.ExcludeResponseUrls.Any(url => response.Url.ToLower().Contains(url))) &&
|
||||
|
|
@ -164,11 +164,23 @@ public class PlaywrightInstance : IDisposable
|
|||
|
||||
try
|
||||
{
|
||||
var context = await GetContext(message.ContextId);
|
||||
var cookies = await context.CookiesAsync(new string[] { response.Url });
|
||||
var result = new WebPageResponseData
|
||||
{
|
||||
Url = response.Url.ToLower(),
|
||||
PostData = response.Request?.PostData ?? string.Empty,
|
||||
ResponseInMemory = args.ResponseInMemory
|
||||
ResponseInMemory = args.ResponseInMemory,
|
||||
Method = response.Request.Method,
|
||||
ResponseCode = response.Status,
|
||||
Cookies = cookies.Select(x => new WebPageCookieData
|
||||
{
|
||||
Name = x.Name,
|
||||
Value = x.Value,
|
||||
Domain = x.Domain,
|
||||
Path = x.Path,
|
||||
Expires = x.Expires
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
var html = await response.TextAsync();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
"open_blank_page": {
|
||||
"type": "boolean",
|
||||
"description": "Open blank page"
|
||||
},
|
||||
"enable_response_callback": {
|
||||
"type": "boolean",
|
||||
"description": "Enable response callback"
|
||||
}
|
||||
},
|
||||
"required": [ "url" ]
|
||||
|
|
|
|||
Loading…
Reference in a new issue