Deprecate routing handler.

This commit is contained in:
Haiping Chen 2025-04-26 13:05:15 -05:00
parent d02cae9222
commit 6fa6aa180d
36 changed files with 136 additions and 564 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.Routing.Models;
public class ResponseUserArgs
{
[JsonPropertyName("content")]
public string Content { get; set; } = string.Empty;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -59,7 +59,6 @@ public class RoutingAgentHook : AgentHookBase
}
dict["routing_agents"] = agents;
dict["routing_handlers"] = routing.GetHandlers(_agent);
return base.OnInstructionLoaded(template, dict);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
"properties": {
"order_number": {
"type": "string",
"description": "order number."
"description": "order number, value must be provided by user."
}
},
"required": [ "order_number" ]

View file

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

View file

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

View file

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

View file

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