Deprecate routing handler.
This commit is contained in:
parent
d02cae9222
commit
6fa6aa180d
|
|
@ -69,21 +69,6 @@ public interface IConversationHook
|
|||
|
||||
Task OnResponseGenerated(RoleDialogModel message);
|
||||
|
||||
/// <summary>
|
||||
/// LLM detected user requested a new task different from previous topic.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
Task OnNewTaskDetected(RoleDialogModel message, string reason);
|
||||
|
||||
/// <summary>
|
||||
/// LLM detected the current task is completed.
|
||||
/// It's useful for the situation of multiple tasks in the same conversation.
|
||||
/// </summary>
|
||||
/// <param name="conversation"></param>
|
||||
/// <returns></returns>
|
||||
Task OnTaskCompleted(RoleDialogModel message);
|
||||
|
||||
/// <summary>
|
||||
/// LLM detected the whole conversation is going to be end.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -36,13 +36,6 @@ public class FunctionCallFromLlm : RoutingArgs
|
|||
{
|
||||
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {NextActionReason}>";
|
||||
|
||||
if (string.IsNullOrEmpty(Response))
|
||||
{
|
||||
return $"[{Function} {route} {JsonSerializer.Serialize(Arguments)}]: {Question}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"[{Function} {route} {JsonSerializer.Serialize(Arguments)}]: {Question} => {Response}";
|
||||
}
|
||||
return $"[{Function} {route} {JsonSerializer.Serialize(Arguments)}]: {Question}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// The routing handler will be injected to Router's FUNCTIONS section of the system prompt
|
||||
/// So the handler will be invoked by LLM autonomously.
|
||||
/// </summary>
|
||||
public interface IRoutingHandler
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
List<string> Planers => null;
|
||||
bool Enabled => true;
|
||||
List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>();
|
||||
|
||||
void SetDialogs(List<RoleDialogModel> dialogs);
|
||||
|
||||
Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message);
|
||||
}
|
||||
|
|
@ -26,8 +26,6 @@ public interface IRoutingService
|
|||
/// <returns></returns>
|
||||
RoutingRule[] GetRulesByAgentId(string id);
|
||||
|
||||
List<RoutingHandlerDef> GetHandlers(Agent router);
|
||||
|
||||
//void ResetRecursiveCounter();
|
||||
//int GetRecursiveCounter();
|
||||
//void SetRecursiveCounter(int counter);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class ResponseUserArgs
|
||||
{
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -15,19 +15,6 @@ public class RoutingArgs
|
|||
[JsonPropertyName("conversation_end")]
|
||||
public bool ConversationEnd { get; set; }
|
||||
|
||||
[JsonPropertyName("task_completed")]
|
||||
public bool TaskCompleted { get; set; }
|
||||
|
||||
[JsonPropertyName("is_new_task")]
|
||||
public bool IsNewTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The content of replying to user
|
||||
/// </summary>
|
||||
[JsonPropertyName("response")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Response { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Agent for next action based on user latest response
|
||||
/// </summary>
|
||||
|
|
@ -38,10 +25,12 @@ public class RoutingArgs
|
|||
/// <summary>
|
||||
/// Agent who can achieve user original goal
|
||||
/// </summary>
|
||||
[Obsolete("Will be replaced by dedicate Reasoner")]
|
||||
[JsonPropertyName("user_goal_agent")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string OriginalAgent { get; set; } = string.Empty;
|
||||
|
||||
[Obsolete("Will be replaced by dedicate Reasoner")]
|
||||
[JsonPropertyName("user_goal_description")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string UserGoal { get; set; } = string.Empty;
|
||||
|
|
@ -50,13 +39,6 @@ public class RoutingArgs
|
|||
{
|
||||
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {NextActionReason}>";
|
||||
|
||||
if (string.IsNullOrEmpty(Response))
|
||||
{
|
||||
return $"[{Function} {route}]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"[{Function} {route}] => {Response}";
|
||||
}
|
||||
return $"[{Function} {route}]";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,17 +150,6 @@ public static class BotSharpCoreExtensions
|
|||
var loader = new PluginLoader(services, config, pluginSettings);
|
||||
loader.Load(assembly =>
|
||||
{
|
||||
// Register routing handlers
|
||||
var handlers = assembly.GetTypes()
|
||||
.Where(x => x.IsClass)
|
||||
.Where(x => x.GetInterface(nameof(IRoutingHandler)) != null)
|
||||
.ToArray();
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
services.AddScoped(typeof(IRoutingHandler), handler);
|
||||
}
|
||||
|
||||
// Register function callback
|
||||
var functions = assembly.GetTypes()
|
||||
.Where(x => x.IsClass
|
||||
|
|
|
|||
|
|
@ -155,14 +155,6 @@ public partial class ConversationService
|
|||
var conversation = _services.GetRequiredService<IConversationService>();
|
||||
var updatedConversation = await conversation.UpdateConversationTitle(_conversationId, response.Instruction.NextActionReason);
|
||||
|
||||
// Emit conversation task completed hook
|
||||
if (response.Instruction.TaskCompleted)
|
||||
{
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnTaskCompleted(response)
|
||||
);
|
||||
}
|
||||
|
||||
// Emit conversation ending hook
|
||||
if (response.Instruction.ConversationEnd)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ public class ResponseToUserFn : IFunctionCallback
|
|||
|
||||
public Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
|
||||
message.Content = args.Response;
|
||||
var args = JsonSerializer.Deserialize<ResponseUserArgs>(message.FunctionArgs);
|
||||
message.Content = args.Content;
|
||||
message.Handled = true;
|
||||
message.StopCompletion = true;
|
||||
return Task.FromResult(true);
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Routing.Reasoning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class ContinueExecuteTaskRoutingHandler : RoutingHandlerBase//, IRoutingHandler
|
||||
{
|
||||
public string Name => "continue_execute_task";
|
||||
|
||||
public string Description => "Continue to execute user's request without further information retrival.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("next_action_agent", "agent for next action based on user latest response"),
|
||||
new ParameterPropertyDef("user_goal_agent", "agent who can achieve user original goal"),
|
||||
new ParameterPropertyDef("reason", "why continue to execute current task"),
|
||||
new ParameterPropertyDef("args", "required parameters extracted from question")
|
||||
{
|
||||
Type = "object"
|
||||
}
|
||||
};
|
||||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
nameof(HFReasoner)
|
||||
};
|
||||
|
||||
public ContinueExecuteTaskRoutingHandler(IServiceProvider services, ILogger<ContinueExecuteTaskRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var filter = new AgentFilter { AgentNames = [inst.AgentName] };
|
||||
var record = db.GetAgents(filter).FirstOrDefault();
|
||||
|
||||
message.FunctionName = inst.Function;
|
||||
message.CurrentAgentId = record.Id;
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst.Arguments);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Routing.Reasoning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class InterruptTaskExecutionRoutingHandler : RoutingHandlerBase//, IRoutingHandler
|
||||
{
|
||||
public string Name => "interrupt_task_execution";
|
||||
|
||||
public string Description => "Can't continue user's request becauase the requirements are not met.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("reason", "the reason why the request is interrupted"),
|
||||
new ParameterPropertyDef("answer", "the content response to user")
|
||||
};
|
||||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
nameof(HFReasoner)
|
||||
};
|
||||
|
||||
public InterruptTaskExecutionRoutingHandler(IServiceProvider services, ILogger<InterruptTaskExecutionRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
message.FunctionName = inst.Function;
|
||||
message.StopCompletion = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Routing.Reasoning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve information from specific agent
|
||||
/// </summary>
|
||||
public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase//, IRoutingHandler
|
||||
{
|
||||
public string Name => "retrieve_data_from_agent";
|
||||
|
||||
public string Description => "Retrieve data from appropriate agent.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("reason", "why choose this function"),
|
||||
new ParameterPropertyDef("question", "the question you will ask the next action agent to get the necessary information"),
|
||||
new ParameterPropertyDef("next_action_agent", "agent that can handle the question"),
|
||||
new ParameterPropertyDef("user_goal_agent", "agent that can achieve user original goal"),
|
||||
new ParameterPropertyDef("args", "required parameters extracted from question and hand over to the next agent")
|
||||
{
|
||||
Type = "object"
|
||||
}
|
||||
};
|
||||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
nameof(HFReasoner)
|
||||
};
|
||||
|
||||
public RetrieveDataFromAgentRoutingHandler(IServiceProvider services, ILogger<RetrieveDataFromAgentRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
var context = _services.GetRequiredService<IRoutingContext>();
|
||||
var agentId = context.GetCurrentAgentId();
|
||||
var dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, inst.Question)
|
||||
{
|
||||
CurrentAgentId = agentId,
|
||||
MessageId = message.MessageId
|
||||
}
|
||||
};
|
||||
|
||||
var ret = await routing.InvokeAgent(agentId, dialogs);
|
||||
var response = dialogs.Last();
|
||||
inst.Response = response.Content;
|
||||
|
||||
// Add final response to parent dialog
|
||||
_dialogs.Add(response);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "route_to_agent";
|
||||
|
||||
public string Description => "Route request to appropriate virtual agent.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("next_action_reason",
|
||||
"the reason why route to this virtual agent.",
|
||||
required: true),
|
||||
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),
|
||||
new ParameterPropertyDef("args",
|
||||
"useful parameters of next action agent, format: { }",
|
||||
type: "object"),
|
||||
new ParameterPropertyDef("user_goal_description",
|
||||
"user goal based on user initial task.",
|
||||
required: true),
|
||||
new ParameterPropertyDef("user_goal_agent",
|
||||
"agent who can acheive user initial task.",
|
||||
required: true),
|
||||
new ParameterPropertyDef("conversation_end",
|
||||
"user is ending the conversation.",
|
||||
type: "boolean",
|
||||
required: true),
|
||||
new ParameterPropertyDef("is_new_task",
|
||||
"whether the user is requesting a new task that is different from the previous topic. Set the first round of conversation to false.",
|
||||
type: "boolean")
|
||||
};
|
||||
|
||||
public RouteToAgentRoutingHandler(IServiceProvider services, ILogger<RouteToAgentRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var goalAgent = states.GetState(StateConst.EXPECTED_GOAL_AGENT);
|
||||
if (!string.IsNullOrEmpty(goalAgent) && inst.OriginalAgent != goalAgent)
|
||||
{
|
||||
inst.OriginalAgent = goalAgent;
|
||||
// Emit hook
|
||||
await HookEmitter.Emit<IRoutingHook>(_services, async hook =>
|
||||
await hook.OnRoutingInstructionRevised(inst, message)
|
||||
);
|
||||
}
|
||||
|
||||
if (inst.IsNewTask)
|
||||
{
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnNewTaskDetected(message, inst.NextActionReason)
|
||||
);
|
||||
}
|
||||
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||
if (message.FunctionName != null)
|
||||
{
|
||||
var msg = RoleDialogModel.From(message, role: AgentRole.Function);
|
||||
await routing.InvokeFunction(message.FunctionName, msg);
|
||||
}
|
||||
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
// Update next action agent's name
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.GetAgent(agentId);
|
||||
inst.AgentName = agent.Name;
|
||||
|
||||
if (inst.ExecutingDirectly)
|
||||
{
|
||||
message.Content = inst.Question;
|
||||
}
|
||||
|
||||
if (agent.Disabled)
|
||||
{
|
||||
var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
|
||||
|
||||
message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: content);
|
||||
_dialogs.Add(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ret = await routing.InvokeAgent(agentId, _dialogs);
|
||||
}
|
||||
|
||||
var response = _dialogs.Last();
|
||||
inst.Response = response.Content;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,6 @@ public class RoutingAgentHook : AgentHookBase
|
|||
}
|
||||
|
||||
dict["routing_agents"] = agents;
|
||||
dict["routing_handlers"] = routing.GetHandlers(_agent);
|
||||
|
||||
return base.OnInstructionLoaded(template, dict);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,43 +38,21 @@ public class HFReasoner : IRoutingReasoner
|
|||
{
|
||||
var next = GetNextStepPrompt(router);
|
||||
|
||||
RoleDialogModel response = default;
|
||||
var inst = new FunctionCallFromLlm();
|
||||
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: router?.LlmConfig?.Provider,
|
||||
model: router?.LlmConfig?.Model);
|
||||
|
||||
int retryCount = 0;
|
||||
while (retryCount < 3)
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
try
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
FunctionName = nameof(HFReasoner),
|
||||
MessageId = messageId
|
||||
}
|
||||
};
|
||||
response = await completion.GetChatCompletions(router, dialogs);
|
||||
FunctionName = nameof(HFReasoner),
|
||||
MessageId = messageId
|
||||
}
|
||||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
|
||||
inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"{ex.Message}: {response.Content}");
|
||||
inst.Function = "response_to_user";
|
||||
inst.Response = ex.Message;
|
||||
inst.AgentName = "Router";
|
||||
}
|
||||
finally
|
||||
{
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
var inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
|
||||
// Fix LLM malformed response
|
||||
ReasonerHelper.FixMalformedResponse(_services, inst);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Planning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Reasoning;
|
||||
|
|
@ -18,17 +19,50 @@ public class InstructExecutor : IExecutor
|
|||
RoleDialogModel message,
|
||||
List<RoleDialogModel> dialogs)
|
||||
{
|
||||
message.Instruction = inst;
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var goalAgent = states.GetState(StateConst.EXPECTED_GOAL_AGENT);
|
||||
if (!string.IsNullOrEmpty(goalAgent) && inst.OriginalAgent != goalAgent)
|
||||
{
|
||||
inst.OriginalAgent = goalAgent;
|
||||
// Emit hook
|
||||
await HookEmitter.Emit<IRoutingHook>(_services, async hook =>
|
||||
await hook.OnRoutingInstructionRevised(inst, message)
|
||||
);
|
||||
}
|
||||
|
||||
var handlers = _services.GetServices<IRoutingHandler>();
|
||||
var handler = handlers.FirstOrDefault(x => x.Name == inst.Function);
|
||||
handler.SetDialogs(dialogs);
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||
if (message.FunctionName != null)
|
||||
{
|
||||
var msg = RoleDialogModel.From(message, role: AgentRole.Function);
|
||||
await routing.InvokeFunction(message.FunctionName, msg);
|
||||
}
|
||||
|
||||
var handled = await handler.Handle(routing, inst, message);
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
// Update next action agent's name
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.GetAgent(agentId);
|
||||
inst.AgentName = agent.Name;
|
||||
|
||||
if (inst.ExecutingDirectly)
|
||||
{
|
||||
message.Content = inst.Question;
|
||||
}
|
||||
|
||||
if (agent.Disabled)
|
||||
{
|
||||
var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
|
||||
|
||||
message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: content);
|
||||
dialogs.Add(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ret = await routing.InvokeAgent(agentId, dialogs);
|
||||
}
|
||||
|
||||
// For client display purpose
|
||||
var response = dialogs.Last();
|
||||
response.MessageId = message.MessageId;
|
||||
|
||||
response.Instruction = inst;
|
||||
|
||||
return response;
|
||||
|
|
|
|||
|
|
@ -41,53 +41,22 @@ public class NaiveReasoner : IRoutingReasoner
|
|||
{
|
||||
var next = GetNextStepPrompt(router);
|
||||
|
||||
var inst = new FunctionCallFromLlm();
|
||||
|
||||
// text completion
|
||||
/*var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var instruction = agentService.RenderedInstruction(router);
|
||||
var content = $"{instruction}\r\n###\r\n{next}";
|
||||
content = content + "\r\nResponse: ";
|
||||
var completion = CompletionProvider.GetTextCompletion(_services);*/
|
||||
|
||||
// chat completion
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: router?.LlmConfig?.Provider,
|
||||
model: router?.LlmConfig?.Model);
|
||||
|
||||
int retryCount = 0;
|
||||
while (retryCount < 3)
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
string text = string.Empty;
|
||||
try
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
// text completion
|
||||
// text = await completion.GetCompletion(content, router.Id, messageId);
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
FunctionName = nameof(NaiveReasoner),
|
||||
MessageId = messageId
|
||||
}
|
||||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
FunctionName = nameof(NaiveReasoner),
|
||||
MessageId = messageId
|
||||
}
|
||||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
|
||||
inst = (response.FunctionArgs ?? response.Content).JsonContent<FunctionCallFromLlm>();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"{ex.Message}: {text}");
|
||||
inst.Function = "response_to_user";
|
||||
inst.Response = ex.Message;
|
||||
inst.AgentName = "Router";
|
||||
}
|
||||
finally
|
||||
{
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
var inst = (response.FunctionArgs ?? response.Content).JsonContent<FunctionCallFromLlm>();
|
||||
|
||||
// Fix LLM malformed response
|
||||
ReasonerHelper.FixMalformedResponse(_services, inst);
|
||||
|
|
|
|||
|
|
@ -44,46 +44,26 @@ public class OneStepForwardReasoner : IRoutingReasoner
|
|||
{
|
||||
var next = GetNextStepPrompt(router);
|
||||
|
||||
var inst = new FunctionCallFromLlm();
|
||||
|
||||
// chat completion
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: router?.LlmConfig?.Provider,
|
||||
model: router?.LlmConfig?.Model);
|
||||
|
||||
int retryCount = 0;
|
||||
while (retryCount < 3)
|
||||
{
|
||||
string text = string.Empty;
|
||||
try
|
||||
{
|
||||
// text completion
|
||||
// text = await completion.GetCompletion(content, router.Id, messageId);
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
FunctionName = Name,
|
||||
MessageId = messageId
|
||||
}
|
||||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
string text = string.Empty;
|
||||
|
||||
inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
// text completion
|
||||
// text = await completion.GetCompletion(content, router.Id, messageId);
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
_logger.LogError($"{ex.Message}: {text}");
|
||||
inst.Function = "response_to_user";
|
||||
inst.Response = ex.Message;
|
||||
inst.AgentName = "Router";
|
||||
FunctionName = Name,
|
||||
MessageId = messageId
|
||||
}
|
||||
finally
|
||||
{
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
|
||||
var inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
|
||||
// Fix LLM malformed response
|
||||
ReasonerHelper.FixMalformedResponse(_services, inst);
|
||||
|
|
|
|||
|
|
@ -58,11 +58,8 @@ public partial class RoutingService
|
|||
// Save states
|
||||
states.SaveStateByArgs(inst.Arguments);
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine($"*** Next Instruction *** {inst}");
|
||||
#else
|
||||
_logger.LogInformation($"*** Next Instruction *** {inst}");
|
||||
#endif
|
||||
_logger.LogDebug($"*** Next Instruction *** {inst}");
|
||||
|
||||
await reasoner.AgentExecuting(_router, inst, message, dialogs);
|
||||
|
||||
// Handover to Task Agent
|
||||
|
|
@ -79,7 +76,7 @@ public partial class RoutingService
|
|||
|
||||
await reasoner.AgentExecuted(_router, inst, response, dialogs);
|
||||
|
||||
if (loopCount >= reasoner.MaxLoopCount || _context.IsEmpty)
|
||||
if (loopCount >= reasoner.MaxLoopCount || _context.IsEmpty || response.StopCompletion)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,17 +28,12 @@ public partial class RoutingService : IRoutingService
|
|||
|
||||
public async Task<RoleDialogModel> InstructDirect(Agent agent, RoleDialogModel message)
|
||||
{
|
||||
var handlers = _services.GetServices<IRoutingHandler>();
|
||||
|
||||
var handler = handlers.FirstOrDefault(x => x.Name == "route_to_agent");
|
||||
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var storage = _services.GetRequiredService<IConversationStorage>();
|
||||
storage.Append(conv.ConversationId, message);
|
||||
|
||||
var dialogs = conv.GetDialogHistory();
|
||||
Context.SetDialogs(dialogs);
|
||||
handler.SetDialogs(dialogs);
|
||||
|
||||
var inst = new FunctionCallFromLlm
|
||||
{
|
||||
|
|
@ -50,7 +45,8 @@ public partial class RoutingService : IRoutingService
|
|||
ExecutingDirectly = true
|
||||
};
|
||||
|
||||
var result = await handler.Handle(this, inst, message);
|
||||
message.Instruction = inst;
|
||||
var result = await InvokeFunction("route_to_agent", message);
|
||||
|
||||
var response = dialogs.Last();
|
||||
response.MessageId = message.MessageId;
|
||||
|
|
@ -59,21 +55,6 @@ public partial class RoutingService : IRoutingService
|
|||
return response;
|
||||
}
|
||||
|
||||
public List<RoutingHandlerDef> GetHandlers(Agent router)
|
||||
{
|
||||
var reasoner = GetReasoner(router);
|
||||
|
||||
return _services.GetServices<IRoutingHandler>()
|
||||
.Where(x => x.Planers == null || x.Planers.Contains(reasoner.GetType().Name))
|
||||
.Where(x => !string.IsNullOrEmpty(x.Description))
|
||||
.Select((x, i) => new RoutingHandlerDef
|
||||
{
|
||||
Name = x.Name,
|
||||
Description = x.Description,
|
||||
Parameters = x.Parameters
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
[SharpCache(10)]
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
{
|
||||
"name": "response_to_user",
|
||||
"description": "Response to user without routing to any other agent",
|
||||
"visibility_expression": "{% if states.routing_mode == 'lazy' %}visible{% endif %}",
|
||||
"description": "Response to user without routing to any other agent, user has no specific request.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"response": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Response content"
|
||||
}
|
||||
},
|
||||
"required": [ "response" ]
|
||||
"required": [ "content" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"name": "route_to_agent",
|
||||
"description": "Route request to appropriate AI agent.",
|
||||
"visibility_expression": "{% if states.routing_mode == 'lazy' %}visible{% endif %}",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -18,21 +18,6 @@ Follow these steps to handle user request:
|
|||
{%- endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if routing_mode != 'lazy' %}
|
||||
[FUNCTIONS]
|
||||
{% for handler in routing_handlers -%}
|
||||
# {{ handler.description}}
|
||||
{% if handler.parameters and handler.parameters != empty -%}
|
||||
Parameters:
|
||||
- function: {{ handler.name }}
|
||||
{% for p in handler.parameters -%}
|
||||
- {{ p.name }} {% if p.required -%}(required){%- endif %}: {{ p.description }}{{ "\r\n " }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{{ "\r\n" }}
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
|
||||
[AGENTS]
|
||||
{% for agent in routing_agents -%}
|
||||
* Agent: {{ agent.name }}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public class SequentialPlanner : ITaskPlanner
|
|||
var decomposation = await GetDecomposedStepAsync(router, messageId, dialogs);
|
||||
if (decomposation.TotalRemainingSteps > 0 && _lastInst != null)
|
||||
{
|
||||
_lastInst.Response = decomposation.Description;
|
||||
// _lastInst.Response = decomposation.Description;
|
||||
_lastInst.NextActionReason = $"Having {decomposation.TotalRemainingSteps} steps left.";
|
||||
return _lastInst;
|
||||
}
|
||||
|
|
@ -62,8 +62,6 @@ public class SequentialPlanner : ITaskPlanner
|
|||
|
||||
var next = GetNextStepPrompt(router);
|
||||
|
||||
var inst = new FunctionCallFromLlm();
|
||||
|
||||
// text completion
|
||||
/*var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var instruction = agentService.RenderedInstruction(router);
|
||||
|
|
@ -76,43 +74,26 @@ public class SequentialPlanner : ITaskPlanner
|
|||
provider: router?.LlmConfig?.Provider,
|
||||
model: router?.LlmConfig?.Model);
|
||||
|
||||
int retryCount = 0;
|
||||
while (retryCount < 3)
|
||||
{
|
||||
string text = string.Empty;
|
||||
try
|
||||
{
|
||||
// text completion
|
||||
// text = await completion.GetCompletion(content, router.Id, messageId);
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
FunctionName = nameof(SequentialPlanner),
|
||||
MessageId = messageId
|
||||
}
|
||||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
|
||||
inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
string text = string.Empty;
|
||||
|
||||
// text completion
|
||||
// text = await completion.GetCompletion(content, router.Id, messageId);
|
||||
dialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, next)
|
||||
{
|
||||
_logger.LogError($"{ex.Message}: {text}");
|
||||
inst.Function = "response_to_user";
|
||||
inst.Response = ex.Message;
|
||||
inst.AgentName = "Router";
|
||||
FunctionName = nameof(SequentialPlanner),
|
||||
MessageId = messageId
|
||||
}
|
||||
finally
|
||||
{
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
|
||||
var inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
|
||||
if (decomposation.TotalRemainingSteps > 0)
|
||||
{
|
||||
inst.Response = decomposation.Description;
|
||||
// inst.Response = decomposation.Description;
|
||||
inst.NextActionReason = $"{decomposation.TotalRemainingSteps} steps left.";
|
||||
inst.HandleDialogsByPlanner = true;
|
||||
}
|
||||
|
|
@ -125,10 +106,10 @@ public class SequentialPlanner : ITaskPlanner
|
|||
{
|
||||
var taskAgentDialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, inst.Response)
|
||||
/*new RoleDialogModel(AgentRole.User, inst.Response)
|
||||
{
|
||||
MessageId = message.MessageId,
|
||||
}
|
||||
}*/
|
||||
};
|
||||
|
||||
return taskAgentDialogs;
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@ public class SqlGenerationPlanner : ITaskPlanner
|
|||
|
||||
public List<RoleDialogModel> BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var question = inst.Response;
|
||||
// var question = inst.Response;
|
||||
|
||||
var taskAgentDialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, question)
|
||||
/*new RoleDialogModel(AgentRole.User, question)
|
||||
{
|
||||
MessageId = message.MessageId,
|
||||
}
|
||||
}*/
|
||||
};
|
||||
|
||||
return taskAgentDialogs;
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ public partial class TwoStageTaskPlanner : ITaskPlanner
|
|||
|
||||
public List<RoleDialogModel> BeforeHandleContext(FunctionCallFromLlm inst, RoleDialogModel message, List<RoleDialogModel> dialogs)
|
||||
{
|
||||
var question = inst.Response;
|
||||
// var question = inst.Response;
|
||||
|
||||
var taskAgentDialogs = new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, question)
|
||||
/*new RoleDialogModel(AgentRole.User, question)
|
||||
{
|
||||
MessageId = message.MessageId,
|
||||
}
|
||||
}*/
|
||||
};
|
||||
|
||||
return taskAgentDialogs;
|
||||
|
|
|
|||
|
|
@ -254,8 +254,8 @@
|
|||
"HostAgentId": "01e2fc5c-2c89-4ec7-8470-7688608b496c",
|
||||
"EnableTranslator": false,
|
||||
"LlmConfig": {
|
||||
"Provider": "azure-openai",
|
||||
"Model": "gpt-4o-mini"
|
||||
"Provider": "openai",
|
||||
"Model": "gpt-4.1-nano"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,7 @@
|
|||
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\agent.json" />
|
||||
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\get_pizza_price.json" />
|
||||
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\get_pizza_types.json" />
|
||||
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\place_an_order.json" />
|
||||
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\responses\func.get_pizza_price.0.liquid" />
|
||||
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\agent.json" />
|
||||
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\functions\make_payment.json" />
|
||||
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instructions\instruction.liquid" />
|
||||
|
|
@ -63,9 +61,6 @@
|
|||
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\instructions\instruction.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\responses\func.get_pizza_price.0.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
@ -93,7 +88,7 @@
|
|||
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\get_pizza_types.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\place_an_order.json">
|
||||
<Content Include="data\agents\c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd\functions\place_order.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\functions\make_payment.json">
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using BotSharp.Abstraction.Conversations;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
|
||||
namespace BotSharp.Plugin.PizzaBot.Functions;
|
||||
|
||||
public class PlaceOrderFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "place_an_order";
|
||||
public string Name => "place_order";
|
||||
|
||||
private readonly IServiceProvider _service;
|
||||
public PlaceOrderFn(IServiceProvider service)
|
||||
|
|
@ -19,6 +20,9 @@ public class PlaceOrderFn : IFunctionCallback
|
|||
var state = _service.GetRequiredService<IConversationStateService>();
|
||||
state.SetState("order_number", "P123-01");
|
||||
|
||||
// Set the next action agent to Payment
|
||||
state.SetState(StateConst.EXPECTED_ACTION_AGENT, "Payment", activeRounds: 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "8970b1e5-d260-4e2c-90b1-f1415a257c18",
|
||||
"name": "Pizza Bot",
|
||||
"description": "AI assistant that can help customer place pizza order, make payment or inquiry order status.",
|
||||
"description": "AI assistant that can help customer place pizza order, make payment or inquiry existing order.",
|
||||
"type": "routing",
|
||||
"inheritAgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
|
||||
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
|
||||
|
|
@ -11,6 +11,10 @@
|
|||
"isPublic": true,
|
||||
"profiles": [ "pizza" ],
|
||||
"labels": [ "experiment" ],
|
||||
"llmConfig": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4.1-nano"
|
||||
},
|
||||
"routingRules": [
|
||||
{
|
||||
"type": "reasoner",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Order Inquiry",
|
||||
"description": "Check the order status like payment, delivery or baking.",
|
||||
"description": "Check the existing order status like payment, delivery or baking.",
|
||||
"createdDateTime": "2023-08-18T14:39:32.2349685Z",
|
||||
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
|
||||
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"properties": {
|
||||
"order_number": {
|
||||
"type": "string",
|
||||
"description": "order number."
|
||||
"description": "order number, value must be provided by user."
|
||||
}
|
||||
},
|
||||
"required": [ "order_number" ]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "Ordering",
|
||||
"name": "Order Placement",
|
||||
"description": "Provide types of pizza available, pizza unit price, total cost and place the order.",
|
||||
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
||||
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "place_an_order",
|
||||
"name": "place_order",
|
||||
"description": "Place an order when user has confirmed the pizza type and quantity.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
You are now a Pizza Ordering agent, and you can help customers order a pizza according to the user's preferences.
|
||||
|
||||
Follow below step to place order:
|
||||
1: Ask user preferences, call function get_pizza_types to provide the variety of pizza options.
|
||||
2: Confirm with user the pizza type and quantity.
|
||||
3: Call function place_an_order to purchase.
|
||||
4: Ask user how to pay for this order.
|
||||
|
||||
Use below information to help ordering process:
|
||||
* Today is {{current_date}}, the time now is {{current_time}}, day of week is {{current_weekday}}.
|
||||
|
||||
Follow below step to response:
|
||||
1: Ask user preferences, call function get_pizza_types to provide the variety of pizza options.
|
||||
2: Call get_pizza_price to tell customer the price per unit, then ask user for the quantity.
|
||||
3: Confirm the order with total price, call function place_order to place the order.
|
||||
4: Ask user how to pay for this order.
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
{% assign pizza_type = pizza_type | downcase %}
|
||||
{% if pizza_type contains "cheese" -%}
|
||||
The price for a slice of {{pizza_type}} pizza is ${{ cheese_unit_price }}. Would you like to proceed the order?
|
||||
{%- elsif pizza_type contains "pepperoni" -%}
|
||||
The price for a slice of {{pizza_type}} pizza is ${{ pepperoni_unit_price }}. Would you like to proceed the order?
|
||||
{%- elsif pizza_type contains "margherita" -%}
|
||||
The price for a slice of {{pizza_type}} pizza is ${{ margherita_unit_price }}. Would you like to proceed the order?
|
||||
{%- else -%}
|
||||
We don't have {{pizza_type}} pizza, would you like something else?
|
||||
{%- endif %}
|
||||
{% if quantity == nil -%}
|
||||
How many slices would you like to order?
|
||||
{%- endif %}
|
||||
Loading…
Reference in a new issue