Merge pull request #345 from SciSharp/master

merge latest code
This commit is contained in:
geffzhang 2024-03-18 07:57:36 +08:00 committed by GitHub
commit dec1fba4b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 99 additions and 35 deletions

View file

@ -59,6 +59,9 @@ public abstract class ConversationHookBase : IConversationHook
public virtual Task OnMessageReceived(RoleDialogModel message) public virtual Task OnMessageReceived(RoleDialogModel message)
=> Task.CompletedTask; => Task.CompletedTask;
public virtual Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg)
=> Task.CompletedTask;
public virtual Task OnResponseGenerated(RoleDialogModel message) public virtual Task OnResponseGenerated(RoleDialogModel message)
=> Task.CompletedTask; => Task.CompletedTask;

View file

@ -46,6 +46,7 @@ public interface IConversationHook
Task OnStateChanged(string name, string preValue, string currentValue); Task OnStateChanged(string name, string preValue, string currentValue);
Task OnMessageReceived(RoleDialogModel message); Task OnMessageReceived(RoleDialogModel message);
Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg);
/// <summary> /// <summary>
/// Triggered before LLM calls function. /// Triggered before LLM calls function.

View file

@ -30,6 +30,7 @@ public interface IConversationService
/// <returns></returns> /// <returns></returns>
Task<bool> SendMessage(string agentId, Task<bool> SendMessage(string agentId,
RoleDialogModel lastDalog, RoleDialogModel lastDalog,
PostbackMessageModel? replyMessage,
Func<RoleDialogModel, Task> onResponseReceived, Func<RoleDialogModel, Task> onResponseReceived,
Func<RoleDialogModel, Task> onFunctionExecuting, Func<RoleDialogModel, Task> onFunctionExecuting,
Func<RoleDialogModel, Task> onFunctionExecuted); Func<RoleDialogModel, Task> onFunctionExecuted);

View file

@ -4,4 +4,9 @@ public class IncomingMessageModel : MessageConfig
{ {
public string Text { get; set; } = string.Empty; public string Text { get; set; } = string.Empty;
public virtual string Channel { get; set; } = string.Empty; public virtual string Channel { get; set; } = string.Empty;
/// <summary>
/// Postback message
/// </summary>
public PostbackMessageModel? Postback { get; set; }
} }

View file

@ -0,0 +1,11 @@
namespace BotSharp.Abstraction.Conversations.Models;
public class PostbackMessageModel
{
public string FunctionName { get; set; } = string.Empty;
public string Payload { get; set; } = string.Empty;
/// <summary>
/// Parent message id
/// </summary>
public string ParentId { get; set; } = string.Empty;
}

View file

@ -31,7 +31,7 @@ public interface IRoutingService
List<RoutingHandlerDef> GetHandlers(Agent router); List<RoutingHandlerDef> GetHandlers(Agent router);
void ResetRecursiveCounter(); void ResetRecursiveCounter();
Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs); Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs);
Task<bool> InvokeFunction(string name, RoleDialogModel message, bool restoreOriginalFunctionName = true); Task<bool> InvokeFunction(string name, RoleDialogModel message);
Task<RoleDialogModel> InstructLoop(RoleDialogModel message); Task<RoleDialogModel> InstructLoop(RoleDialogModel message);
/// <summary> /// <summary>

View file

@ -10,6 +10,7 @@ public partial class ConversationService
{ {
public async Task<bool> SendMessage(string agentId, public async Task<bool> SendMessage(string agentId,
RoleDialogModel message, RoleDialogModel message,
PostbackMessageModel? replyMessage,
Func<RoleDialogModel, Task> onMessageReceived, Func<RoleDialogModel, Task> onMessageReceived,
Func<RoleDialogModel, Task> onFunctionExecuting, Func<RoleDialogModel, Task> onFunctionExecuting,
Func<RoleDialogModel, Task> onFunctionExecuted) Func<RoleDialogModel, Task> onFunctionExecuted)
@ -51,7 +52,14 @@ public partial class ConversationService
hook.SetAgent(agent) hook.SetAgent(agent)
.SetConversation(conversation); .SetConversation(conversation);
await hook.OnMessageReceived(message); if (replyMessage == null)
{
await hook.OnMessageReceived(message);
}
else
{
await hook.OnPostbackMessageReceived(message, replyMessage);
}
// Interrupted by hook // Interrupted by hook
if (message.StopCompletion) if (message.StopCompletion)

View file

@ -97,9 +97,10 @@ public class EvaluatingService : IEvaluatingService
await conv.SendMessage(agentId, await conv.SendMessage(agentId,
new RoleDialogModel(AgentRole.User, text), new RoleDialogModel(AgentRole.User, text),
replyMessage: null,
async msg => response = msg, async msg => response = msg,
fnExecuting => Task.CompletedTask, _ => Task.CompletedTask,
fnExecuted => Task.CompletedTask); _ => Task.CompletedTask);
return response; return response;
} }

View file

@ -10,11 +10,11 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef> public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{ {
new ParameterPropertyDef("next_action_reason", "the reason why route to this agent") new ParameterPropertyDef("next_action_reason", "the reason why route to this agent, if user is replying last agent's question, you must route to this agent")
{ {
Required = true Required = true
}, },
new ParameterPropertyDef("next_action_agent", "agent for next action based on user latest response") new ParameterPropertyDef("next_action_agent", "agent for next action based on user latest response, if user is replying last agent's question, you must route to this agent")
{ {
Required = true Required = true
}, },

View file

@ -1,3 +1,4 @@
using Amazon.Runtime.Internal.Transform;
using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Repositories.Filters;
@ -111,9 +112,11 @@ public class NaivePlanner : IPlaner
{ {
var template = router.Templates.First(x => x.Name == "planner_prompt.naive").Content; var template = router.Templates.First(x => x.Name == "planner_prompt.naive").Content;
var states = _services.GetRequiredService<IConversationStateService>();
var render = _services.GetRequiredService<ITemplateRender>(); var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object> return render.Render(template, new Dictionary<string, object>
{ {
{ "expected_next_action_agent", states.GetState("expected_next_action_agent")}
}); });
} }

View file

@ -4,7 +4,7 @@ namespace BotSharp.Core.Routing;
public partial class RoutingService public partial class RoutingService
{ {
public async Task<bool> InvokeFunction(string name, RoleDialogModel message, bool restoreOriginalFunctionName = true) public async Task<bool> InvokeFunction(string name, RoleDialogModel message)
{ {
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == name); var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == name);
if (function == null) if (function == null)
@ -57,8 +57,7 @@ public partial class RoutingService
// restore original function name // restore original function name
if (!message.StopCompletion && if (!message.StopCompletion &&
message.FunctionName != originalFunctionName && message.FunctionName != originalFunctionName)
restoreOriginalFunctionName)
{ {
message.FunctionName = originalFunctionName; message.FunctionName = originalFunctionName;
} }

View file

@ -1,3 +1,7 @@
What is the next step based on the CONVERSATION? What is the next step based on the CONVERSATION?
Route to the Agent that last handled the conversation if necessary. Route to the appropriate agent last handled agent based on the context.
{% if expected_next_action_agent != empty -%}
Expected next action agent is {{ expected_next_action_agent }}.
{%- endif %}
Try to keep the User Goal Agent be consistent as previous goal agent.
If user wants to speak to customer service, use function human_intervention_needed. If user wants to speak to customer service, use function human_intervention_needed.

View file

@ -171,6 +171,7 @@ public class ConversationController : ControllerBase
var response = new ChatResponseModel(); var response = new ChatResponseModel();
await conv.SendMessage(agentId, inputMsg, await conv.SendMessage(agentId, inputMsg,
replyMessage: input.Postback,
async msg => async msg =>
{ {
response.Text = msg.Content; response.Text = msg.Content;
@ -179,14 +180,8 @@ public class ConversationController : ControllerBase
response.Instruction = msg.Instruction; response.Instruction = msg.Instruction;
response.Data = msg.Data; response.Data = msg.Data;
}, },
async fnExecuting => _ => Task.CompletedTask,
{ _ => Task.CompletedTask);
},
async fnExecuted =>
{
});
var state = _services.GetRequiredService<IConversationStateService>(); var state = _services.GetRequiredService<IConversationStateService>();
response.States = state.GetStates(); response.States = state.GetStates();

View file

@ -59,6 +59,22 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
} }
public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg)
{
var conversationId = _state.GetConversationId();
var log = $"{message.Content}";
var replyContent = JsonSerializer.Serialize(replyMsg, _serializerOptions);
log += $"\r\n```json\r\n{replyContent}\r\n```";
var input = new ContentLogInputModel(conversationId, message)
{
Name = _user.UserName,
Source = ContentLogSource.UserInput,
Log = log
};
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
}
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations) public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
{ {
if (!_convSettings.ShowVerboseLog) return; if (!_convSettings.ShowVerboseLog) return;

View file

@ -80,13 +80,12 @@ public class ChatbotUiController : ControllerBase
.SetState("sampling_factor", input.SamplingFactor); .SetState("sampling_factor", input.SamplingFactor);
var result = await conv.SendMessage(input.AgentId, var result = await conv.SendMessage(input.AgentId,
message, message,
replyMessage: null,
async msg => async msg =>
await OnChunkReceived(outputStream, msg), await OnChunkReceived(outputStream, msg),
async fn _ => Task.CompletedTask,
=> await Task.CompletedTask, _ => Task.CompletedTask);
async fn
=> await Task.CompletedTask);
await OnEventCompleted(outputStream); await OnEventCompleted(outputStream);
} }

View file

@ -60,7 +60,9 @@ public class MessageHandleService
var replies = new List<IRichMessage>(); var replies = new List<IRichMessage>();
var result = await conv.SendMessage(agentId, var result = await conv.SendMessage(agentId,
new RoleDialogModel(AgentRole.User, message), async msg => new RoleDialogModel(AgentRole.User, message),
replyMessage: null,
async msg =>
{ {
if (msg.RichContent != null) if (msg.RichContent != null)
{ {

View file

@ -59,6 +59,7 @@ public class TwilioVoiceController : TwilioController
var result = await conv.SendMessage(agentId, var result = await conv.SendMessage(agentId,
new RoleDialogModel(AgentRole.User, input.SpeechResult), new RoleDialogModel(AgentRole.User, input.SpeechResult),
replyMessage: null,
async msg => async msg =>
{ {
response = twilio.ReturnInstructions(msg.Content); response = twilio.ReturnInstructions(msg.Content);

View file

@ -61,16 +61,15 @@ namespace BotSharp.Plugin.WeChat
AgentId = AgentId AgentId = AgentId
}))?.Id; }))?.Id;
var result = await conversationService.SendMessage(AgentId, new RoleDialogModel("user", message), async msg => var result = await conversationService.SendMessage(AgentId,
{ new RoleDialogModel("user", message),
await ReplyTextMessageAsync(openid, msg.Content); replyMessage: null,
}, async functionExecuting => async msg =>
{ {
await ReplyTextMessageAsync(openid, msg.Content);
}, async functionExecuted => },
{ _ => Task.CompletedTask,
_ => Task.CompletedTask);
});
} }
private async Task<User> GetWeChatAccountUserAsync(string openId, IServiceProvider service) private async Task<User> GetWeChatAccountUserAsync(string openId, IServiceProvider service)

View file

@ -0,0 +1,16 @@
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Conversations.Models;
namespace BotSharp.Plugin.PizzaBot.Hooks;
public class PizzaTypeConversationHook : ConversationHookBase
{
public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg)
{
if (replyMsg.FunctionName == "get_pizza_types")
{
// message.StopCompletion = true;
}
return;
}
}