Unified InvokeFunction.
This commit is contained in:
parent
35f89e9be6
commit
fb0adadf6a
|
|
@ -49,7 +49,7 @@ PS D:\> npm run dev
|
||||||
|
|
||||||
Access http://localhost:5015/
|
Access http://localhost:5015/
|
||||||
|
|
||||||
[Online Demo with UI](https://botsharp.azurewebsites.net/)
|
[Online Demo with UI](https://botsharp.azurewebsites.net/?wt.mc_id=AI-MVP-5005183)
|
||||||
|
|
||||||
<img src="./docs/static/screenshots/agent-builder-agents.png" height="450px"/>
|
<img src="./docs/static/screenshots/agent-builder-agents.png" height="450px"/>
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ BotSharp uses component design, the kernel is kept to a minimum, and business fu
|
||||||
|
|
||||||
### Documents
|
### Documents
|
||||||
|
|
||||||
Read the docs: https://botsharp.readthedocs.io
|
Read the docs: https://botsharp.readthedocs.io?wt.mc_id=AI-MVP-5005183
|
||||||
|
|
||||||
If you feel that this project is helpful to you, please Star the project, we would be very grateful.
|
If you feel that this project is helpful to you, please Star the project, we would be very grateful.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,9 @@ author = 'Haiping Chen'
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.23'
|
version = '1.1'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.23.0'
|
release = '1.1.0'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,4 @@ public interface IConversationService
|
||||||
|
|
||||||
List<RoleDialogModel> GetDialogHistory(int lastCount = 50);
|
List<RoleDialogModel> GetDialogHistory(int lastCount = 50);
|
||||||
Task CleanHistory(string agentId);
|
Task CleanHistory(string agentId);
|
||||||
|
|
||||||
Task CallFunctions(RoleDialogModel msg);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
using BotSharp.Abstraction.Functions;
|
|
||||||
|
|
||||||
namespace BotSharp.Core.Conversations.Services;
|
|
||||||
|
|
||||||
public partial class ConversationService
|
|
||||||
{
|
|
||||||
public async Task CallFunctions(RoleDialogModel msg)
|
|
||||||
{
|
|
||||||
// Invoke functions
|
|
||||||
var functions = _services.GetServices<IFunctionCallback>()
|
|
||||||
.Where(x => x.Name == msg.FunctionName)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (functions.Count == 0)
|
|
||||||
{
|
|
||||||
msg.Content = $"Can't find function implementation of {msg.FunctionName}.";
|
|
||||||
_logger.LogError(msg.Content);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hooks = _services.GetServices<IConversationHook>()
|
|
||||||
.OrderBy(x => x.Priority)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var fn in functions)
|
|
||||||
{
|
|
||||||
// Before executing functions
|
|
||||||
foreach (var hook in hooks)
|
|
||||||
{
|
|
||||||
await hook.OnFunctionExecuting(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Execute function
|
|
||||||
await fn.Execute(msg);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(msg.Content))
|
|
||||||
{
|
|
||||||
msg.Content = msg.Content ?? JsonSerializer.Serialize(msg.Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
msg.Content = ex.Message;
|
|
||||||
msg.StopCompletion = true;
|
|
||||||
_logger.LogError(msg.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// After functions have been executed
|
|
||||||
foreach (var hook in hooks)
|
|
||||||
{
|
|
||||||
await hook.OnFunctionExecuted(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
using BotSharp.Abstraction.Repositories;
|
|
||||||
using BotSharp.Abstraction.Repositories.Filters;
|
|
||||||
using BotSharp.Abstraction.Users.Enums;
|
|
||||||
|
|
||||||
namespace BotSharp.Core.Conversations.Services;
|
namespace BotSharp.Core.Conversations.Services;
|
||||||
|
|
||||||
public partial class ConversationService : IConversationService
|
public partial class ConversationService : IConversationService
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
using BotSharp.Abstraction.Functions;
|
|
||||||
|
|
||||||
namespace BotSharp.Core.Instructs;
|
|
||||||
|
|
||||||
public partial class InstructService
|
|
||||||
{
|
|
||||||
private async Task CallFunctions(RoleDialogModel msg)
|
|
||||||
{
|
|
||||||
var hooks = _services.GetServices<IConversationHook>()
|
|
||||||
.OrderBy(x => x.Priority).ToList();
|
|
||||||
|
|
||||||
// Invoke functions
|
|
||||||
var functions = _services.GetServices<IFunctionCallback>()
|
|
||||||
.Where(x => x.Name == msg.FunctionName)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (functions.Count == 0)
|
|
||||||
{
|
|
||||||
msg.Content = $"Can't find function implementation of {msg.FunctionName}.";
|
|
||||||
_logger.LogError(msg.Content);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var fn in functions)
|
|
||||||
{
|
|
||||||
// Before executing functions
|
|
||||||
foreach (var hook in hooks)
|
|
||||||
{
|
|
||||||
await hook.OnFunctionExecuting(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Execute function
|
|
||||||
await fn.Execute(msg);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
msg.Content = ex.Message;
|
|
||||||
msg.StopCompletion = true;
|
|
||||||
_logger.LogError(msg.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
// After functions have been executed
|
|
||||||
foreach (var hook in hooks)
|
|
||||||
{
|
|
||||||
await hook.OnFunctionExecuted(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -29,6 +29,7 @@ public class RouteToAgentFn : IFunctionCallback
|
||||||
{
|
{
|
||||||
// Correct user goal agent to keep orignal task
|
// Correct user goal agent to keep orignal task
|
||||||
var goalAgentInState = states.GetState("user_goal_agent", string.Empty);
|
var goalAgentInState = states.GetState("user_goal_agent", string.Empty);
|
||||||
|
bool correctToOriginalAgent = false;
|
||||||
if (goalAgentInState == string.Empty)
|
if (goalAgentInState == string.Empty)
|
||||||
{
|
{
|
||||||
states.SetState("user_goal_agent", args.OriginalAgent, isNeedVersion: true);
|
states.SetState("user_goal_agent", args.OriginalAgent, isNeedVersion: true);
|
||||||
|
|
@ -37,6 +38,7 @@ public class RouteToAgentFn : IFunctionCallback
|
||||||
{
|
{
|
||||||
// Correct to original agent
|
// Correct to original agent
|
||||||
args.OriginalAgent = goalAgentInState;
|
args.OriginalAgent = goalAgentInState;
|
||||||
|
correctToOriginalAgent = true;
|
||||||
}
|
}
|
||||||
else if (args.OriginalAgent != args.AgentName && args.OriginalAgent != goalAgentInState)
|
else if (args.OriginalAgent != args.AgentName && args.OriginalAgent != goalAgentInState)
|
||||||
{
|
{
|
||||||
|
|
@ -49,7 +51,7 @@ public class RouteToAgentFn : IFunctionCallback
|
||||||
var originalAgent = db.GetAgents(filter).FirstOrDefault();
|
var originalAgent = db.GetAgents(filter).FirstOrDefault();
|
||||||
if (originalAgent != null)
|
if (originalAgent != null)
|
||||||
{
|
{
|
||||||
_context.Push(originalAgent.Id, $"user goal agent");
|
_context.Push(originalAgent.Id, $"user goal agent{(correctToOriginalAgent ? " & is corrected" : "")}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,8 +92,8 @@ public class RouteToAgentFn : IFunctionCallback
|
||||||
var missingfield = HasMissingRequiredField(message, out var agentId);
|
var missingfield = HasMissingRequiredField(message, out var agentId);
|
||||||
if (missingfield && message.CurrentAgentId != agentId)
|
if (missingfield && message.CurrentAgentId != agentId)
|
||||||
{
|
{
|
||||||
// Stack original Agent
|
// Stack redirection agent
|
||||||
_context.Push(agentId, reason: "redirection rule");
|
_context.Push(agentId, reason: $"redirection: {message.Content}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
using BotSharp.Abstraction.Functions;
|
|
||||||
using BotSharp.Abstraction.Functions.Models;
|
|
||||||
using BotSharp.Abstraction.Routing;
|
|
||||||
using BotSharp.Abstraction.Routing.Models;
|
|
||||||
using BotSharp.Abstraction.Routing.Settings;
|
using BotSharp.Abstraction.Routing.Settings;
|
||||||
|
|
||||||
namespace BotSharp.Core.Routing.Handlers;
|
namespace BotSharp.Core.Routing.Handlers;
|
||||||
|
|
@ -39,12 +35,10 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||||
|
|
||||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||||
{
|
{
|
||||||
var context = _services.GetRequiredService<IRoutingContext>();
|
|
||||||
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == inst.Function);
|
|
||||||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||||
var ret = await function.Execute(message);
|
var ret = await routing.InvokeFunction(message.FunctionName, message);
|
||||||
|
|
||||||
var agentId = context.GetCurrentAgentId();
|
var agentId = routing.Context.GetCurrentAgentId();
|
||||||
|
|
||||||
// Update next action agent's name
|
// Update next action agent's name
|
||||||
var agentService = _services.GetRequiredService<IAgentService>();
|
var agentService = _services.GetRequiredService<IAgentService>();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using BotSharp.Abstraction.Agents.Models;
|
|
||||||
using BotSharp.Abstraction.Routing.Models;
|
|
||||||
using BotSharp.Abstraction.Templating;
|
using BotSharp.Abstraction.Templating;
|
||||||
|
|
||||||
namespace BotSharp.Core.Routing;
|
namespace BotSharp.Core.Routing;
|
||||||
|
|
@ -57,15 +55,13 @@ public partial class RoutingService
|
||||||
var states = _services.GetRequiredService<IConversationStateService>();
|
var states = _services.GetRequiredService<IConversationStateService>();
|
||||||
states.SaveStateByArgs(message.FunctionArgs?.JsonContent<JsonDocument>());
|
states.SaveStateByArgs(message.FunctionArgs?.JsonContent<JsonDocument>());
|
||||||
|
|
||||||
var conversationService = _services.GetRequiredService<IConversationService>();
|
var routing = _services.GetRequiredService<IRoutingService>();
|
||||||
// Call functions
|
// Call functions
|
||||||
await conversationService.CallFunctions(message);
|
await routing.InvokeFunction(message.FunctionName, message);
|
||||||
|
|
||||||
// Pass execution result to LLM to get response
|
// Pass execution result to LLM to get response
|
||||||
if (!message.StopCompletion)
|
if (!message.StopCompletion)
|
||||||
{
|
{
|
||||||
var routing = _services.GetRequiredService<IRoutingContext>();
|
|
||||||
|
|
||||||
// Find response template
|
// Find response template
|
||||||
var templateService = _services.GetRequiredService<IResponseTemplateService>();
|
var templateService = _services.GetRequiredService<IResponseTemplateService>();
|
||||||
var responseTemplate = await templateService.RenderFunctionResponse(message.CurrentAgentId, message);
|
var responseTemplate = await templateService.RenderFunctionResponse(message.CurrentAgentId, message);
|
||||||
|
|
@ -83,7 +79,7 @@ public partial class RoutingService
|
||||||
content: message.Content));
|
content: message.Content));
|
||||||
|
|
||||||
// Send to Next LLM
|
// Send to Next LLM
|
||||||
var agentId = routing.GetCurrentAgentId();
|
var agentId = routing.Context.GetCurrentAgentId();
|
||||||
await InvokeAgent(agentId, dialogs);
|
await InvokeAgent(agentId, dialogs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,56 @@ public partial class RoutingService
|
||||||
public async Task<bool> InvokeFunction(string name, RoleDialogModel message)
|
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) return false;
|
if (function == null)
|
||||||
|
{
|
||||||
|
message.StopCompletion = true;
|
||||||
|
message.Content = $"Can't find function implementation of {message.FunctionName}.";
|
||||||
|
_logger.LogError(message.Content);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var originalFunctionName = message.FunctionName;
|
var originalFunctionName = message.FunctionName;
|
||||||
message.FunctionName = name;
|
message.FunctionName = name;
|
||||||
message.Role = AgentRole.Function;
|
message.Role = AgentRole.Function;
|
||||||
message.FunctionArgs = message.FunctionArgs;
|
message.FunctionArgs = message.FunctionArgs;
|
||||||
var result = await function.Execute(message);
|
|
||||||
|
var hooks = _services.GetServices<IConversationHook>()
|
||||||
|
.OrderBy(x => x.Priority)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Before executing functions
|
||||||
|
foreach (var hook in hooks)
|
||||||
|
{
|
||||||
|
await hook.OnFunctionExecuting(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = await function.Execute(message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
message.StopCompletion = true;
|
||||||
|
message.Content = ex.Message;
|
||||||
|
_logger.LogError(ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure content has been populated
|
||||||
|
if (string.IsNullOrEmpty(message.Content) && message.Data != null)
|
||||||
|
{
|
||||||
|
message.Content = JsonSerializer.Serialize(message.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// After functions have been executed
|
||||||
|
foreach (var hook in hooks)
|
||||||
|
{
|
||||||
|
await hook.OnFunctionExecuted(message);
|
||||||
|
}
|
||||||
|
|
||||||
// restore original function name
|
// restore original function name
|
||||||
if (!message.StopCompletion)
|
if (!message.StopCompletion && message.FunctionName != originalFunctionName)
|
||||||
{
|
{
|
||||||
message.FunctionName = originalFunctionName;
|
message.FunctionName = originalFunctionName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
|
using BotSharp.Abstraction.Conversations;
|
||||||
using BotSharp.Abstraction.Conversations.Models;
|
using BotSharp.Abstraction.Conversations.Models;
|
||||||
|
using BotSharp.Abstraction.Messaging;
|
||||||
|
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||||
|
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace BotSharp.Plugin.PizzaBot.Functions;
|
namespace BotSharp.Plugin.PizzaBot.Functions;
|
||||||
|
|
||||||
|
|
@ -6,15 +11,40 @@ public class GetPizzaTypesFn : IFunctionCallback
|
||||||
{
|
{
|
||||||
public string Name => "get_pizza_types";
|
public string Name => "get_pizza_types";
|
||||||
|
|
||||||
|
private readonly IServiceProvider _services;
|
||||||
|
public GetPizzaTypesFn(IServiceProvider services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> Execute(RoleDialogModel message)
|
public async Task<bool> Execute(RoleDialogModel message)
|
||||||
{
|
{
|
||||||
message.Content = "Pepperoni Pizza, Cheese Pizza, Margherita Pizza";
|
var states = _services.GetRequiredService<IConversationStateService>();
|
||||||
message.Data = new List<string>
|
var pizzaTypes = new List<string>
|
||||||
{
|
{
|
||||||
"Pepperoni Pizza",
|
"Pepperoni Pizza",
|
||||||
"Cheese Pizza",
|
"Cheese Pizza",
|
||||||
"Margherita Pizza"
|
"Margherita Pizza"
|
||||||
};
|
};
|
||||||
|
message.Data = pizzaTypes;
|
||||||
|
message.RichContent = new RichContent<IRichMessage>
|
||||||
|
{
|
||||||
|
Recipient = new Recipient
|
||||||
|
{
|
||||||
|
Id = states.GetConversationId()
|
||||||
|
},
|
||||||
|
Message = new ButtonTemplateMessage
|
||||||
|
{
|
||||||
|
Text = "Please select a pizza type",
|
||||||
|
Buttons = pizzaTypes.Select(x => new ButtonElement
|
||||||
|
{
|
||||||
|
Type = "text",
|
||||||
|
Title = x,
|
||||||
|
Payload = x
|
||||||
|
}).ToArray()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
|
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
|
||||||
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
|
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
|
||||||
"iconUrl": "https://cdn-icons-png.flaticon.com/512/6978/6978255.png",
|
"iconUrl": "https://cdn-icons-png.flaticon.com/512/6978/6978255.png",
|
||||||
"disabled": true,
|
"disabled": false,
|
||||||
"isPublic": true,
|
"isPublic": true,
|
||||||
"profiles": [ "pizza" ],
|
"profiles": [ "pizza" ],
|
||||||
"routingRules": [
|
"routingRules": [
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,7 @@
|
||||||
"createdDateTime": "2023-08-18T14:39:32.2349685Z",
|
"createdDateTime": "2023-08-18T14:39:32.2349685Z",
|
||||||
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
|
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
|
||||||
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
|
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
|
||||||
"disabled": true,
|
"disabled": false,
|
||||||
"isPublic": true,
|
"isPublic": true,
|
||||||
"profiles": [ "pizza" ],
|
"profiles": [ "pizza" ]
|
||||||
"routingRules": [
|
|
||||||
{
|
|
||||||
"field": "order_number",
|
|
||||||
"description": "order number",
|
|
||||||
"type": "string",
|
|
||||||
"redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
||||||
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
||||||
"id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",
|
"id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",
|
||||||
"disabled": true,
|
"disabled": false,
|
||||||
"isPublic": true,
|
"isPublic": true,
|
||||||
"profiles": [ "pizza" ]
|
"profiles": [ "pizza" ]
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
||||||
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
||||||
"id": "fe8c60aa-b114-4ef3-93cb-a8efeac80f75",
|
"id": "fe8c60aa-b114-4ef3-93cb-a8efeac80f75",
|
||||||
"disabled": true,
|
"disabled": false,
|
||||||
"isPublic": true,
|
"isPublic": true,
|
||||||
"profiles": [ "pizza" ],
|
"profiles": [ "pizza" ],
|
||||||
"routingRules": [
|
"routingRules": [
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"description": "order number",
|
"description": "order number",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true,
|
"required": true,
|
||||||
"redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd"
|
"redirectTo": "b284db86-e9c2-4c25-a59e-4649797dd130"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue