Merge pull request #539 from hchen2020/master

Fix route to agent.
This commit is contained in:
C. Oceania 2024-07-11 16:45:27 -05:00 committed by GitHub
commit e51bc29895
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 99 additions and 82 deletions

View file

@ -6,4 +6,5 @@ public class BuiltInAgentId
public const string Chatbot = "01e2fc5c-2c89-4ec7-8470-7688608b496c";
public const string HumanSupport = "01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b";
public const string UtilityAssistant = "6745151e-6d46-4a02-8de4-1c4f21c7da95";
public const string Fallback = "01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d";
}

View file

@ -43,7 +43,7 @@ public partial class ConversationService
// Enqueue receiving agent first in case it stop completion by OnMessageReceived
var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.SetMessageId(_conversationId, message.MessageId);
routing.Context.Push(agent.Id);
routing.Context.Push(agent.Id, reason: "request started");
// Save payload
if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))

View file

@ -88,6 +88,8 @@ public partial class RouteToAgentFn : IFunctionCallback
{
// Stack redirection agent
_context.Push(agentId, reason: $"REDIRECTION {reason}");
message.Content = reason;
message.Role = AgentRole.Function;
}
}

View file

@ -6,7 +6,7 @@ namespace BotSharp.Core.Routing.Handlers;
/// <summary>
/// Retrieve information from specific agent
/// </summary>
public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase//, IRoutingHandler
{
public string Name => "retrieve_data_from_agent";

View file

@ -59,7 +59,9 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
message.FunctionArgs = JsonSerializer.Serialize(inst);
if (message.FunctionName != null)
{
var ret = await routing.InvokeFunction(message.FunctionName, message);
var msg = RoleDialogModel.From(message);
var ret = await routing.InvokeFunction(message.FunctionName, msg);
_dialogs.Add(msg);
}
var agentId = routing.Context.GetCurrentAgentId();

View file

@ -59,6 +59,9 @@ public class HFPlanner : IPlaner
}
}
// Fix LLM malformed response
PlannerHelper.FixMalformedResponse(_services, inst);
return inst;
}
@ -71,7 +74,11 @@ public class HFPlanner : IPlaner
var agent = db.GetAgents(filter).FirstOrDefault();
var context = _services.GetRequiredService<IRoutingContext>();
context.Push(agent.Id);
context.Push(agent.Id, reason: inst.NextActionReason);
// Set user content as Planner's question
message.FunctionName = inst.Function;
message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments);
}
return true;

View file

@ -69,7 +69,7 @@ public class NaivePlanner : IPlaner
}
// Fix LLM malformed response
FixMalformedResponse(inst);
PlannerHelper.FixMalformedResponse(_services, inst);
return inst;
}
@ -117,73 +117,4 @@ public class NaivePlanner : IPlaner
{ StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) }
});
}
/// <summary>
/// Sometimes LLM hallucinates and fails to set function names correctly.
/// </summary>
/// <param name="args"></param>
private void FixMalformedResponse(FunctionCallFromLlm args)
{
var agentService = _services.GetRequiredService<IAgentService>();
var agents = agentService.GetAgents(new AgentFilter
{
Type = AgentType.Task
}).Result.Items.ToList();
var malformed = false;
// Sometimes it populate malformed Function in Agent name
if (!string.IsNullOrEmpty(args.Function) &&
args.Function == args.AgentName)
{
args.Function = "route_to_agent";
malformed = true;
}
// Another case of malformed response
if (string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.Function))
{
args.AgentName = args.Function;
args.Function = "route_to_agent";
malformed = true;
}
// It should be Route to agent, but it is used as Response to user.
if (!string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.AgentName) &&
args.Function != "route_to_agent")
{
args.Function = "route_to_agent";
malformed = true;
}
// Function name shouldn't contain dot symbol
if (!string.IsNullOrEmpty(args.Function) &&
args.Function.Contains('.'))
{
args.Function = args.Function.Split('.').Last();
malformed = true;
}
// Agent Name is contaminated.
if (args.Function == "route_to_agent")
{
// Action agent name
if (!agents.Any(x => x.Name == args.AgentName) && !string.IsNullOrEmpty(args.AgentName))
{
args.AgentName = agents.FirstOrDefault(x => args.AgentName.Contains(x.Name))?.Name ?? args.AgentName;
}
// Goal agent name
if (!agents.Any(x => x.Name == args.OriginalAgent) && !string.IsNullOrEmpty(args.OriginalAgent))
{
args.OriginalAgent = agents.FirstOrDefault(x => args.OriginalAgent.Contains(x.Name))?.Name ?? args.OriginalAgent;
}
}
if (malformed)
{
_logger.LogWarning($"Captured LLM malformed response");
}
}
}

View file

@ -0,0 +1,73 @@
namespace BotSharp.Core.Routing.Planning;
public static class PlannerHelper
{
/// <summary>
/// Sometimes LLM hallucinates and fails to set function names correctly.
/// </summary>
/// <param name="args"></param>
public static void FixMalformedResponse(IServiceProvider services, FunctionCallFromLlm args)
{
var agentService = services.GetRequiredService<IAgentService>();
var agents = agentService.GetAgents(new AgentFilter
{
Type = AgentType.Task
}).Result.Items.ToList();
var malformed = false;
// Sometimes it populate malformed Function in Agent name
if (!string.IsNullOrEmpty(args.Function) &&
args.Function == args.AgentName)
{
args.Function = "route_to_agent";
malformed = true;
}
// Another case of malformed response
if (string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.Function))
{
args.AgentName = args.Function;
args.Function = "route_to_agent";
malformed = true;
}
// It should be Route to agent, but it is used as Response to user.
if (!string.IsNullOrEmpty(args.AgentName) &&
agents.Select(x => x.Name).Contains(args.AgentName) &&
args.Function != "route_to_agent")
{
args.Function = "route_to_agent";
malformed = true;
}
// Function name shouldn't contain dot symbol
if (!string.IsNullOrEmpty(args.Function) &&
args.Function.Contains('.'))
{
args.Function = args.Function.Split('.').Last();
malformed = true;
}
// Agent Name is contaminated.
if (args.Function == "route_to_agent")
{
// Action agent name
if (!agents.Any(x => x.Name == args.AgentName) && !string.IsNullOrEmpty(args.AgentName))
{
args.AgentName = agents.FirstOrDefault(x => args.AgentName.Contains(x.Name))?.Name ?? args.AgentName;
}
// Goal agent name
if (!agents.Any(x => x.Name == args.OriginalAgent) && !string.IsNullOrEmpty(args.OriginalAgent))
{
args.OriginalAgent = agents.FirstOrDefault(x => args.OriginalAgent.Contains(x.Name))?.Name ?? args.OriginalAgent;
}
}
if (malformed)
{
Console.WriteLine($"Captured LLM malformed response");
}
}
}

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Templating;
@ -13,11 +12,12 @@ public partial class TwoStagePlanner
var plan = new FirstStagePlan[0];
var llmProviderService = _services.GetRequiredService<ILlmProviderService>();
var model = llmProviderService.GetProviderModel("azure-openai", "gpt-4");
var provider = router.LlmConfig.Provider ?? "openai";
var model = llmProviderService.GetProviderModel(provider, router.LlmConfig.Model ?? "gpt-4o");
// chat completion
var completion = CompletionProvider.GetChatCompletion(_services,
provider: "azure-openai",
provider: provider,
model: model.Name);
string text = string.Empty;

View file

@ -48,6 +48,7 @@ public partial class RoutingService
}
// Set result to original message
message.Role = clonedMessage.Role;
message.PostbackFunctionName = clonedMessage.PostbackFunctionName;
message.CurrentAgentId = clonedMessage.CurrentAgentId;
message.Content = clonedMessage.Content;

View file

@ -1,9 +1,9 @@
You're {{router.name}} ({{router.description}}).
You can understand messages sent by users in different languages.
You can understand messages sent by users in different languages, and route the request to appropriate agent.
Follow these steps to handle user request:
1. Read the [CONVERSATION] content.
2. Determine which agent is suitable to handle this conversation.
3. For agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments.
2. Determine which agent is suitable to handle this conversation. Try to minimize the routing of human service.
3. Extract and populate agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments.
4. You must include all required args for the selected agent, but you must not make up any parameters when there is no exact value provided, those parameters must set value as null if not declared.
5. Response must be in JSON format.

View file

@ -1 +1 @@
Break down the users most recent needs and figure out the next steps.
Break down the users most recent needs and figure out the instruction of next step.

View file

@ -14,7 +14,7 @@
},
"request_content": {
"type": "string",
"description": "The http request content. It must be in json format.."
"description": "The http request content. It must be in serialized json string."
}
},
"required": [ "request_url", "http_method" ]