Unified InvokeFunction.

This commit is contained in:
Haiping Chen 2024-02-29 23:44:57 -06:00
parent 35f89e9be6
commit fb0adadf6a
15 changed files with 95 additions and 155 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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);
} }

View file

@ -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);
}
}
}
}

View file

@ -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

View file

@ -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);
}
}
}
}

View file

@ -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}");
} }
} }

View file

@ -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>();

View file

@ -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);
} }
} }

View file

@ -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;
} }

View file

@ -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;
} }
} }

View file

@ -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": [

View file

@ -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"
}
]
} }

View file

@ -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" ]
} }

View file

@ -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"
} }
] ]
} }