Add user_goal_description to router.

Improve stream log.
This commit is contained in:
Haiping Chen 2024-03-05 11:36:48 -06:00
parent 2c4af439f4
commit 96bf9d2fa0
8 changed files with 69 additions and 20 deletions

View file

@ -6,6 +6,7 @@ namespace BotSharp.Abstraction.Functions.Models;
public class FunctionCallFromLlm : RoutingArgs
{
[JsonPropertyName("question")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Question { get; set; }
[JsonPropertyName("args")]
@ -29,6 +30,7 @@ public class FunctionCallFromLlm : RoutingArgs
/// Conversation summary
/// </summary>
[JsonPropertyName("summary")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Summary { get; set; }
public override string ToString()

View file

@ -10,13 +10,21 @@ public interface IRoutingHook
/// <param name="instruct">routing instruction</param>
/// <param name="message">message</param>
/// <returns></returns>
Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message);
Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message)
=> Task.CompletedTask;
Task OnAgentEnqueued(string agentId, string preAgentId, string? reason = null);
Task OnRoutingInstructionRevised(FunctionCallFromLlm instruct, RoleDialogModel message)
=> Task.CompletedTask;
Task OnAgentDequeued(string agentId, string currentAgentId, string? reason = null);
Task OnAgentEnqueued(string agentId, string preAgentId, string? reason = null)
=> Task.CompletedTask;
Task OnAgentReplaced(string fromAgentId, string toAgentId, string? reason = null);
Task OnAgentDequeued(string agentId, string currentAgentId, string? reason = null)
=> Task.CompletedTask;
Task OnAgentQueueEmptied(string agentId, string? reason = null);
Task OnAgentReplaced(string fromAgentId, string toAgentId, string? reason = null)
=> Task.CompletedTask;
Task OnAgentQueueEmptied(string agentId, string? reason = null)
=> Task.CompletedTask;
}

View file

@ -8,13 +8,15 @@ public class RoutingArgs
/// <summary>
/// The reason why you select this function or agent
/// </summary>
[JsonPropertyName("reason")]
[JsonPropertyName("next_action_reason")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Reason { get; set; } = string.Empty;
/// <summary>
/// The content of replying to user
/// </summary>
[JsonPropertyName("response")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Response { get; set; }
/// <summary>
@ -31,6 +33,10 @@ public class RoutingArgs
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string OriginalAgent { get; set; }
[JsonPropertyName("user_goal_description")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string UserGoal { get; set; }
public override string ToString()
{
var route = string.IsNullOrEmpty(AgentName) ? "" : $"<Route to {AgentName.ToUpper()} because {Reason}>";

View file

@ -58,15 +58,8 @@ public class RouteToAgentFn : IFunctionCallback
// Push next action agent
if (!string.IsNullOrEmpty(args.AgentName) && args.AgentName.Length < 32)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var filter = new AgentFilter { AgentName = args.AgentName };
var actionAgent = db.GetAgents(filter).FirstOrDefault();
if (actionAgent != null)
{
_context.Push(actionAgent.Id, args.Reason);
}
states.SetState("last_action_agent", args.AgentName, isNeedVersion: true);
_context.Push(args.AgentName, args.Reason);
states.SetState("next_action_agent", args.AgentName, isNeedVersion: true);
}
if (string.IsNullOrEmpty(args.AgentName))
@ -93,7 +86,7 @@ public class RouteToAgentFn : IFunctionCallback
if (missingfield && message.CurrentAgentId != agentId)
{
// Stack redirection agent
_context.Push(agentId, reason: $"redirection: {message.Content}");
_context.Push(agentId, reason: $"REDIRECTION {message.Content}");
}
}
@ -155,7 +148,7 @@ public class RouteToAgentFn : IFunctionCallback
{
// Add field to args
message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "missing_fields", missingFields);
message.Content = $"missing some information: {string.Join(',', missingFields)}";
message.Content = $"missing some information: {string.Join(", ", missingFields)}";
// Handle redirect
var routingRule = routingRules.FirstOrDefault(x => missingFields.Contains(x.Field));

View file

@ -10,7 +10,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
{
new ParameterPropertyDef("reason", "why route to agent")
new ParameterPropertyDef("next_action_reason", "the reason why route to this agent")
{
Required = true
},
@ -18,6 +18,10 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
{
Required = true
},
new ParameterPropertyDef("user_goal_description", "user original goal")
{
Required = true
},
new ParameterPropertyDef("user_goal_agent", "agent who can achieve user original goal")
{
Required = true
@ -38,6 +42,16 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
message.FunctionArgs = JsonSerializer.Serialize(inst);
var ret = await routing.InvokeFunction(message.FunctionName, message);
var states = _services.GetRequiredService<IConversationStateService>();
var goalAgent = states.GetState("user_goal_agent");
if (!string.IsNullOrEmpty(goalAgent))
{
inst.OriginalAgent = goalAgent;
}
await HookEmitter.Emit<IRoutingHook>(_services, async hook =>
await hook.OnRoutingInstructionRevised(inst, message)
);
var agentId = routing.Context.GetCurrentAgentId();
// Update next action agent's name

View file

@ -102,6 +102,7 @@ public class NaivePlanner : IPlaner
else
{
context.Empty(reason: $"Agent queue is cleared by {nameof(NaivePlanner)}");
// context.Push(inst.OriginalAgent, "Push user goal agent");
}
return true;
}

View file

@ -61,8 +61,23 @@ public class RoutingContext : IRoutingContext
return _stack.Peek();
}
/// <summary>
/// Push agent
/// </summary>
/// <param name="agentId">Id or Name</param>
/// <param name="reason"></param>
public void Push(string agentId, string? reason = null)
{
// Convert id to name
if (!Guid.TryParse(agentId, out _))
{
var agentService = _services.GetRequiredService<IAgentService>();
agentId = agentService.GetAgents(new AgentFilter
{
AgentName = agentId
}).Result.Items.First().Id;
}
if (_stack.Count == 0 || _stack.Peek() != agentId)
{
var preAgentId = _stack.Count == 0 ? agentId : _stack.Peek();

View file

@ -41,7 +41,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
AllowTrailingCommas = true,
WriteIndented = true
WriteIndented = true,
};
}
@ -66,6 +66,11 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
public override async Task OnFunctionExecuted(RoleDialogModel message)
{
if (message.FunctionName == "route_to_agent")
{
return;
}
var conversationId = _state.GetConversationId();
var agent = await _agentService.LoadAgent(message.CurrentAgentId);
message.FunctionArgs = message.FunctionArgs ?? "{}";
@ -128,6 +133,11 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
log += $"\r\n```json\r\n{richContent}\r\n```";
}
if (!string.IsNullOrEmpty(message.FunctionName))
{
log += $"\r\n\r\n**{message.FunctionName}**";
}
var input = new ContentLogInputModel(conv.ConversationId, message)
{
Name = agent?.Name,
@ -223,7 +233,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
}
public async Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message)
public async Task OnRoutingInstructionRevised(FunctionCallFromLlm instruct, RoleDialogModel message)
{
var conversationId = _state.GetConversationId();
var agent = await _agentService.LoadAgent(message.CurrentAgentId);