Unified InvokeFunction.
This commit is contained in:
parent
35f89e9be6
commit
fb0adadf6a
|
|
@ -49,7 +49,7 @@ PS D:\> npm run dev
|
|||
|
||||
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"/>
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ BotSharp uses component design, the kernel is kept to a minimum, and business fu
|
|||
|
||||
### 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -64,9 +64,9 @@ author = 'Haiping Chen'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.23'
|
||||
version = '1.1'
|
||||
# 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
|
||||
# for a list of supported languages.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,4 @@ public interface IConversationService
|
|||
|
||||
List<RoleDialogModel> GetDialogHistory(int lastCount = 50);
|
||||
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;
|
||||
|
||||
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
|
||||
var goalAgentInState = states.GetState("user_goal_agent", string.Empty);
|
||||
bool correctToOriginalAgent = false;
|
||||
if (goalAgentInState == string.Empty)
|
||||
{
|
||||
states.SetState("user_goal_agent", args.OriginalAgent, isNeedVersion: true);
|
||||
|
|
@ -37,6 +38,7 @@ public class RouteToAgentFn : IFunctionCallback
|
|||
{
|
||||
// Correct to original agent
|
||||
args.OriginalAgent = goalAgentInState;
|
||||
correctToOriginalAgent = true;
|
||||
}
|
||||
else if (args.OriginalAgent != args.AgentName && args.OriginalAgent != goalAgentInState)
|
||||
{
|
||||
|
|
@ -49,7 +51,7 @@ public class RouteToAgentFn : IFunctionCallback
|
|||
var originalAgent = db.GetAgents(filter).FirstOrDefault();
|
||||
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);
|
||||
if (missingfield && message.CurrentAgentId != agentId)
|
||||
{
|
||||
// Stack original Agent
|
||||
_context.Push(agentId, reason: "redirection rule");
|
||||
// Stack redirection agent
|
||||
_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;
|
||||
|
||||
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)
|
||||
{
|
||||
var context = _services.GetRequiredService<IRoutingContext>();
|
||||
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == inst.Function);
|
||||
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
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
|
@ -57,15 +55,13 @@ public partial class RoutingService
|
|||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
states.SaveStateByArgs(message.FunctionArgs?.JsonContent<JsonDocument>());
|
||||
|
||||
var conversationService = _services.GetRequiredService<IConversationService>();
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
// Call functions
|
||||
await conversationService.CallFunctions(message);
|
||||
await routing.InvokeFunction(message.FunctionName, message);
|
||||
|
||||
// Pass execution result to LLM to get response
|
||||
if (!message.StopCompletion)
|
||||
{
|
||||
var routing = _services.GetRequiredService<IRoutingContext>();
|
||||
|
||||
// Find response template
|
||||
var templateService = _services.GetRequiredService<IResponseTemplateService>();
|
||||
var responseTemplate = await templateService.RenderFunctionResponse(message.CurrentAgentId, message);
|
||||
|
|
@ -83,7 +79,7 @@ public partial class RoutingService
|
|||
content: message.Content));
|
||||
|
||||
// Send to Next LLM
|
||||
var agentId = routing.GetCurrentAgentId();
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
await InvokeAgent(agentId, dialogs);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,56 @@ public partial class RoutingService
|
|||
public async Task<bool> InvokeFunction(string name, RoleDialogModel message)
|
||||
{
|
||||
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;
|
||||
message.FunctionName = name;
|
||||
message.Role = AgentRole.Function;
|
||||
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
|
||||
if (!message.StopCompletion)
|
||||
if (!message.StopCompletion && message.FunctionName != originalFunctionName)
|
||||
{
|
||||
message.FunctionName = originalFunctionName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
using BotSharp.Abstraction.Conversations;
|
||||
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;
|
||||
|
||||
|
|
@ -6,15 +11,40 @@ public class GetPizzaTypesFn : IFunctionCallback
|
|||
{
|
||||
public string Name => "get_pizza_types";
|
||||
|
||||
private readonly IServiceProvider _services;
|
||||
public GetPizzaTypesFn(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
message.Content = "Pepperoni Pizza, Cheese Pizza, Margherita Pizza";
|
||||
message.Data = new List<string>
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var pizzaTypes = new List<string>
|
||||
{
|
||||
"Pepperoni Pizza",
|
||||
"Cheese 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
|
||||
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
|
||||
"iconUrl": "https://cdn-icons-png.flaticon.com/512/6978/6978255.png",
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "pizza" ],
|
||||
"routingRules": [
|
||||
|
|
|
|||
|
|
@ -4,15 +4,7 @@
|
|||
"createdDateTime": "2023-08-18T14:39:32.2349685Z",
|
||||
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
|
||||
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "pizza" ],
|
||||
"routingRules": [
|
||||
{
|
||||
"field": "order_number",
|
||||
"description": "order number",
|
||||
"type": "string",
|
||||
"redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd"
|
||||
}
|
||||
]
|
||||
"profiles": [ "pizza" ]
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
||||
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
||||
"id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "pizza" ]
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
||||
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
||||
"id": "fe8c60aa-b114-4ef3-93cb-a8efeac80f75",
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "pizza" ],
|
||||
"routingRules": [
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
"description": "order number",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"redirectTo": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd"
|
||||
"redirectTo": "b284db86-e9c2-4c25-a59e-4649797dd130"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue