ConversationBreakpoint.Reason
This commit is contained in:
parent
4dece4b4d4
commit
ca262e7ec3
|
|
@ -44,6 +44,9 @@ public abstract class ConversationHookBase : IConversationHook
|
|||
public virtual Task OnConversationEnding(RoleDialogModel message)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public virtual Task OnNewTaskDetected(RoleDialogModel message, string reason)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public virtual Task OnTaskCompleted(RoleDialogModel message)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,13 @@ 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.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public interface IConversationService
|
|||
/// Use this feature when you want to hide some context from LLM.
|
||||
/// </summary>
|
||||
/// <param name="resetStates">Whether to reset all states</param>
|
||||
/// <param name="reason">Append user init words</param>
|
||||
/// <returns></returns>
|
||||
Task UpdateBreakpoint(bool resetStates = false);
|
||||
Task UpdateBreakpoint(bool resetStates = false, string? reason = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,7 @@ public class ConversationBreakpoint
|
|||
|
||||
[JsonPropertyName("created_time")]
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Routing.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Functions.Models;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ namespace BotSharp.Abstraction.Functions.Models;
|
|||
|
||||
public class ParameterPropertyDef : NameDesc
|
||||
{
|
||||
public ParameterPropertyDef(string name, string description, string type = "string")
|
||||
public ParameterPropertyDef(string name, string description, string type = "string", bool required = false)
|
||||
: base(name, description)
|
||||
{
|
||||
Type = type;
|
||||
Required = required;
|
||||
}
|
||||
|
||||
[JsonPropertyName("required")]
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@ public class StateConst
|
|||
public const string EXPECTED_ACTION_AGENT = "expected_next_action_agent";
|
||||
public const string EXPECTED_GOAL_AGENT = "expected_user_goal_agent";
|
||||
public const string NEXT_ACTION_AGENT = "next_action_agent";
|
||||
public const string NEXT_ACTION_REASON = "next_action_reason";
|
||||
public const string USER_GOAL_AGENT = "user_goal_agent";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ public interface IBotSharpRepository
|
|||
Conversation GetConversation(string conversationId);
|
||||
PagedItems<Conversation> GetConversations(ConversationFilter filter);
|
||||
void UpdateConversationTitle(string conversationId, string title);
|
||||
void UpdateConversationBreakpoint(string conversationId, string messageId, DateTime breakpoint);
|
||||
DateTime GetConversationBreakpoint(string conversationId);
|
||||
void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
|
||||
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
|
||||
List<Conversation> GetLastConversations();
|
||||
List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
|
||||
bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ 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>
|
||||
|
|
|
|||
|
|
@ -156,27 +156,31 @@ public partial class ConversationService
|
|||
response.FunctionName = response.PostbackFunctionName;
|
||||
}
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
|
||||
if (response.Instruction != null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnConversationEnding(response);
|
||||
}
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnConversationEnding(response)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnResponseGenerated(response);
|
||||
}
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnResponseGenerated(response)
|
||||
);
|
||||
|
||||
await onResponseReceived(response);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ namespace BotSharp.Core.Conversations.Services;
|
|||
|
||||
public partial class ConversationService : IConversationService
|
||||
{
|
||||
public async Task UpdateBreakpoint(bool resetStates = false)
|
||||
public async Task UpdateBreakpoint(bool resetStates = false, string? reason = null)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var messageId = routingCtx.MessageId;
|
||||
db.UpdateConversationBreakpoint(_conversationId, messageId, DateTime.UtcNow);
|
||||
|
||||
db.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint
|
||||
{
|
||||
MessageId = messageId,
|
||||
Breakpoint = DateTime.UtcNow,
|
||||
Reason = reason
|
||||
});
|
||||
|
||||
// Reset states
|
||||
if (resetStates)
|
||||
|
|
|
|||
|
|
@ -115,7 +115,14 @@ public partial class ConversationService : IConversationService
|
|||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var breakpoint = db.GetConversationBreakpoint(_conversationId);
|
||||
dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint).ToList();
|
||||
if (breakpoint != null)
|
||||
{
|
||||
dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint.Breakpoint).ToList();
|
||||
if (!string.IsNullOrEmpty(breakpoint.Reason))
|
||||
{
|
||||
dialogs.Insert(0, new RoleDialogModel(AgentRole.User, breakpoint.Reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dialogs
|
||||
|
|
|
|||
|
|
@ -189,10 +189,10 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
public void UpdateConversationTitle(string conversationId, string title)
|
||||
=> new NotImplementedException();
|
||||
|
||||
public void UpdateConversationBreakpoint(string conversationId, string messageId, DateTime breakpoint)
|
||||
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
|
||||
=> new NotImplementedException();
|
||||
|
||||
public DateTime GetConversationBreakpoint(string conversationId)
|
||||
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void UpdateConversationStates(string conversationId, List<StateKeyValue> states)
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ namespace BotSharp.Core.Repository
|
|||
}
|
||||
}
|
||||
|
||||
public void UpdateConversationBreakpoint(string conversationId, string messageId, DateTime breakpoint)
|
||||
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
|
||||
{
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
if (!string.IsNullOrEmpty(convDir))
|
||||
|
|
@ -178,9 +178,10 @@ namespace BotSharp.Core.Repository
|
|||
{
|
||||
new ConversationBreakpoint
|
||||
{
|
||||
MessageId = messageId,
|
||||
Breakpoint = breakpoint,
|
||||
CreatedTime = DateTime.UtcNow
|
||||
MessageId = breakpoint.MessageId,
|
||||
Breakpoint = breakpoint.Breakpoint,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
Reason = breakpoint.Reason,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -197,12 +198,12 @@ namespace BotSharp.Core.Repository
|
|||
}
|
||||
}
|
||||
|
||||
public DateTime GetConversationBreakpoint(string conversationId)
|
||||
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
|
||||
{
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
if (string.IsNullOrEmpty(convDir))
|
||||
{
|
||||
return default;
|
||||
return null;
|
||||
}
|
||||
|
||||
var breakpointFile = Path.Combine(convDir, BREAKPOINT_FILE);
|
||||
|
|
@ -214,7 +215,7 @@ namespace BotSharp.Core.Repository
|
|||
var content = File.ReadAllText(breakpointFile);
|
||||
var records = JsonSerializer.Deserialize<List<ConversationBreakpoint>>(content, _options);
|
||||
|
||||
return records?.LastOrDefault()?.Breakpoint ?? default;
|
||||
return records?.LastOrDefault();
|
||||
}
|
||||
|
||||
public ConversationState GetConversationStates(string conversationId)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
|
@ -58,7 +59,7 @@ public partial class RouteToAgentFn : IFunctionCallback
|
|||
if (!string.IsNullOrEmpty(args.AgentName) && args.AgentName.Length < 32)
|
||||
{
|
||||
_context.Push(args.AgentName, args.NextActionReason);
|
||||
states.SetState("next_action_agent", args.AgentName, isNeedVersion: true);
|
||||
states.SetState(StateConst.NEXT_ACTION_AGENT, args.AgentName, isNeedVersion: true);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(args.AgentName))
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class ConversationEndRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "conversation_end";
|
||||
|
||||
public string Description => "User completed his task and wants to end the conversation.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("reason", "why end conversation"),
|
||||
new ParameterPropertyDef("response", "response content to user")
|
||||
};
|
||||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
};
|
||||
|
||||
public ConversationEndRoutingHandler(IServiceProvider services, ILogger<ConversationEndRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
|
||||
{
|
||||
CurrentAgentId = message.CurrentAgentId,
|
||||
MessageId = message.MessageId,
|
||||
StopCompletion = true,
|
||||
FunctionName = inst.Function
|
||||
};
|
||||
|
||||
_dialogs.Add(response);
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>()
|
||||
.OrderBy(x => x.Priority)
|
||||
.ToList();
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnConversationEnding(response);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,8 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
{
|
||||
new ParameterPropertyDef("reason", "why response to user directly without go to other agents"),
|
||||
new ParameterPropertyDef("response", "response content to user in courteous words. If the user wants to end the conversation, you must set conversation_end to true and response politely."),
|
||||
new ParameterPropertyDef("conversation_end", "whether to end this conversation, true or false", type: "boolean")
|
||||
new ParameterPropertyDef("conversation_end", "whether to end this conversation", type: "boolean"),
|
||||
new ParameterPropertyDef("task_completed ", "whether the user's task request has been completed.", type: "boolean")
|
||||
};
|
||||
|
||||
public ResponseToUserRoutingHandler(IServiceProvider services, ILogger<ResponseToUserRoutingHandler> logger, RoutingSettings settings)
|
||||
|
|
|
|||
|
|
@ -11,26 +11,24 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
|
||||
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("user_goal_description", "user goal based on user initial task.")
|
||||
{
|
||||
Required = true
|
||||
},
|
||||
new ParameterPropertyDef("user_goal_agent", "agent who can acheive user initial task, must align with user_goal_description ")
|
||||
{
|
||||
Required = true
|
||||
},
|
||||
new ParameterPropertyDef("args", "useful parameters of next action agent, format: { }")
|
||||
{
|
||||
Type = "object"
|
||||
}
|
||||
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("user_goal_description",
|
||||
"user goal based on user initial task.",
|
||||
required: true),
|
||||
new ParameterPropertyDef("user_goal_agent",
|
||||
"agent who can acheive user initial task, must align with user_goal_description.",
|
||||
required: true),
|
||||
new ParameterPropertyDef("args",
|
||||
"useful parameters of next action agent, format: { }",
|
||||
type: "object"),
|
||||
new ParameterPropertyDef("is_new_task",
|
||||
"whether the user is requesting a new task that is different from the previous topic.",
|
||||
type: "boolean")
|
||||
};
|
||||
|
||||
public RouteToAgentRoutingHandler(IServiceProvider services, ILogger<RouteToAgentRoutingHandler> logger, RoutingSettings settings)
|
||||
|
|
@ -51,6 +49,13 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
);
|
||||
}
|
||||
|
||||
if (inst.IsNewTask)
|
||||
{
|
||||
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
||||
await hook.OnNewTaskDetected(message, inst.NextActionReason)
|
||||
);
|
||||
}
|
||||
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||
var ret = await routing.InvokeFunction(message.FunctionName, message);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Core.Routing.Planning;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
||||
public class TaskCompletedRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
||||
{
|
||||
public string Name => "task_completed";
|
||||
|
||||
public string Description => "User task is completed.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("reason", "why the task is completed")
|
||||
{
|
||||
Required = true
|
||||
},
|
||||
new ParameterPropertyDef("response", "polite response when the task is completed")
|
||||
{
|
||||
Required = true
|
||||
},
|
||||
new ParameterPropertyDef("conversation_end", "whether to end this conversation, true or false")
|
||||
{
|
||||
Required = true,
|
||||
Type = "boolean"
|
||||
},
|
||||
new ParameterPropertyDef("abandoned_arguments", "the arguments next task can't reuse")
|
||||
};
|
||||
|
||||
public List<string> Planers => new List<string>
|
||||
{
|
||||
nameof(HFPlanner)
|
||||
};
|
||||
|
||||
public TaskCompletedRoutingHandler(IServiceProvider services, ILogger<TaskCompletedRoutingHandler> logger, RoutingSettings settings)
|
||||
: base(services, logger, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
var response = new RoleDialogModel(AgentRole.Assistant, inst.Response)
|
||||
{
|
||||
CurrentAgentId = message.CurrentAgentId,
|
||||
MessageId = message.MessageId,
|
||||
StopCompletion = true,
|
||||
FunctionName = inst.Function,
|
||||
Instruction = inst,
|
||||
};
|
||||
|
||||
_dialogs.Add(response);
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>()
|
||||
.OrderBy(x => x.Priority)
|
||||
.ToList();
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnTaskCompleted(response);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,4 +5,5 @@ public class BreakpointMongoElement
|
|||
public string? MessageId { get; set; }
|
||||
public DateTime Breakpoint { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,21 +44,12 @@ public partial class MongoRepository
|
|||
}
|
||||
}).ToList();
|
||||
|
||||
var initialBreakpoints = new List<BreakpointMongoElement>()
|
||||
{
|
||||
new BreakpointMongoElement
|
||||
{
|
||||
Breakpoint = utcNow.AddMilliseconds(-100),
|
||||
CreatedTime = utcNow
|
||||
}
|
||||
};
|
||||
|
||||
var stateDoc = new ConversationStateDocument
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ConversationId = convDoc.Id,
|
||||
States = initialStates,
|
||||
Breakpoints = initialBreakpoints
|
||||
Breakpoints = new List<BreakpointMongoElement>()
|
||||
};
|
||||
|
||||
_dc.Conversations.InsertOne(convDoc);
|
||||
|
|
@ -152,15 +143,16 @@ public partial class MongoRepository
|
|||
_dc.Conversations.UpdateOne(filterConv, updateConv);
|
||||
}
|
||||
|
||||
public void UpdateConversationBreakpoint(string conversationId, string messageId, DateTime breakpoint)
|
||||
public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId)) return;
|
||||
|
||||
var newBreakpoint = new BreakpointMongoElement()
|
||||
{
|
||||
MessageId = messageId,
|
||||
Breakpoint = breakpoint,
|
||||
CreatedTime = DateTime.UtcNow
|
||||
MessageId = breakpoint.MessageId,
|
||||
Breakpoint = breakpoint.Breakpoint,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
Reason = breakpoint.Reason
|
||||
};
|
||||
var filterState = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
var updateState = Builders<ConversationStateDocument>.Update.Push(x => x.Breakpoints, newBreakpoint);
|
||||
|
|
@ -168,11 +160,11 @@ public partial class MongoRepository
|
|||
_dc.ConversationStates.UpdateOne(filterState, updateState);
|
||||
}
|
||||
|
||||
public DateTime GetConversationBreakpoint(string conversationId)
|
||||
public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId))
|
||||
{
|
||||
return default;
|
||||
return null;
|
||||
}
|
||||
|
||||
var filter = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
|
|
@ -180,10 +172,16 @@ public partial class MongoRepository
|
|||
|
||||
if (state == null || state.Breakpoints.IsNullOrEmpty())
|
||||
{
|
||||
return default;
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.Breakpoints.LastOrDefault()?.Breakpoint ?? default;
|
||||
return state.Breakpoints.Select(x => new ConversationBreakpoint
|
||||
{
|
||||
Breakpoint = x.Breakpoint,
|
||||
CreatedTime = x.CreatedTime,
|
||||
MessageId = x.MessageId,
|
||||
Reason = x.Reason,
|
||||
}).LastOrDefault();
|
||||
}
|
||||
|
||||
public ConversationState GetConversationStates(string conversationId)
|
||||
|
|
|
|||
Loading…
Reference in a new issue