Skip to content

Commit de2f4ee

Browse files
authored
Merge pull request #365 from iceljc/features/refine-breakpoint
refine state
2 parents 22b82b9 + 03f041a commit de2f4ee

File tree

13 files changed

+111
-78
lines changed

13 files changed

+111
-78
lines changed

src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationState.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace BotSharp.Abstraction.Conversations.Models;
22

3-
public class ConversationState : Dictionary<string, List<StateValue>>
3+
public class ConversationState : Dictionary<string, StateKeyValue>
44
{
55
public ConversationState()
66
{
@@ -11,7 +11,7 @@ public ConversationState(List<StateKeyValue> pairs)
1111
{
1212
foreach (var pair in pairs)
1313
{
14-
this[pair.Key] = pair.Values;
14+
this[pair.Key] = pair;
1515
}
1616
}
1717
}

src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace BotSharp.Abstraction.Conversations.Models;
33
public class StateKeyValue
44
{
55
public string Key { get; set; }
6+
public bool Versioning { get; set; }
67
public List<StateValue> Values { get; set; } = new List<StateValue>();
78

89
public StateKeyValue()
@@ -20,6 +21,13 @@ public StateKeyValue(string key, List<StateValue> values)
2021
public class StateValue
2122
{
2223
public string Data { get; set; }
24+
25+
[JsonPropertyName("message_id")]
26+
public string MessageId { get; set; }
27+
28+
public bool Active { get; set; }
29+
30+
[JsonPropertyName("update_time")]
2331
public DateTime UpdateTime { get; set; }
2432

2533
public StateValue()

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public async Task<bool> SendMessage(string agentId,
2727
#endif
2828

2929
message.CurrentAgentId = agent.Id;
30+
message.CreatedAt = DateTime.UtcNow;
3031
if (string.IsNullOrEmpty(message.SenderId))
3132
{
3233
message.SenderId = _user.Id;

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using BotSharp.Abstraction.Conversations.Models;
2+
13
namespace BotSharp.Core.Conversations.Services;
24

35
/// <summary>
@@ -42,32 +44,42 @@ public IConversationStateService SetState<T>(string name, T value, bool isNeedVe
4244
var currentValue = value.ToString();
4345
var hooks = _services.GetServices<IConversationHook>();
4446

45-
if (_states.TryGetValue(name, out var values))
47+
if (ContainsState(name) && _states.TryGetValue(name, out var pair))
4648
{
47-
preValue = values?.LastOrDefault()?.Data ?? string.Empty;
49+
preValue = pair?.Values.LastOrDefault()?.Data ?? string.Empty;
4850
}
4951

50-
if (!_states.ContainsKey(name) || preValue != currentValue)
52+
if (!ContainsState(name) || preValue != currentValue)
5153
{
5254
_logger.LogInformation($"[STATE] {name} = {value}");
5355
foreach (var hook in hooks)
5456
{
5557
hook.OnStateChanged(name, preValue, currentValue).Wait();
5658
}
5759

58-
var stateValue = new StateValue
60+
var routingCtx = _services.GetRequiredService<IRoutingContext>();
61+
var newPair = new StateKeyValue
62+
{
63+
Key = name,
64+
Versioning = isNeedVersion
65+
};
66+
67+
var newValue = new StateValue
5968
{
6069
Data = currentValue,
61-
UpdateTime = DateTime.UtcNow
70+
MessageId = routingCtx.MessageId,
71+
Active = true,
72+
UpdateTime = DateTime.UtcNow,
6273
};
6374

64-
if (!_states.ContainsKey(name) || !isNeedVersion)
75+
if (!isNeedVersion || !_states.ContainsKey(name))
6576
{
66-
_states[name] = new List<StateValue> { stateValue };
77+
newPair.Values = new List<StateValue> { newValue };
78+
_states[name] = newPair;
6779
}
6880
else
6981
{
70-
_states[name].Add(stateValue);
82+
_states[name].Values.Add(newValue);
7183
}
7284
}
7385

@@ -85,9 +97,12 @@ public Dictionary<string, string> Load(string conversationId)
8597
{
8698
foreach (var state in _states)
8799
{
88-
var value = state.Value?.LastOrDefault()?.Data ?? string.Empty;
89-
curStates[state.Key] = value;
90-
_logger.LogInformation($"[STATE] {state.Key} : {value}");
100+
var value = state.Value?.Values?.LastOrDefault();
101+
if (value == null || !value.Active) continue;
102+
103+
var data = value.Data ?? string.Empty;
104+
curStates[state.Key] = data;
105+
_logger.LogInformation($"[STATE] {state.Key} : {data}");
91106
}
92107
}
93108

@@ -112,7 +127,7 @@ public void Save()
112127

113128
foreach (var dic in _states)
114129
{
115-
states.Add(new StateKeyValue(dic.Key, dic.Value));
130+
states.Add(dic.Value);
116131
}
117132

118133
_db.UpdateConversationStates(_conversationId, states);
@@ -121,27 +136,46 @@ public void Save()
121136

122137
public void CleanStates()
123138
{
124-
_states.Clear();
139+
var utcNow = DateTime.UtcNow;
140+
foreach (var key in _states.Keys)
141+
{
142+
var value = _states[key];
143+
if (value == null || !value.Versioning || value.Values.IsNullOrEmpty()) continue;
144+
145+
var lastValue = value.Values.LastOrDefault();
146+
if (lastValue == null || !lastValue.Active) continue;
147+
148+
value.Values.Add(new StateValue
149+
{
150+
Data = lastValue.Data,
151+
MessageId = lastValue.MessageId,
152+
Active = false,
153+
UpdateTime = utcNow
154+
});
155+
}
125156
}
126157

127158
public Dictionary<string, string> GetStates()
128159
{
129160
var curStates = new Dictionary<string, string>();
130161
foreach (var state in _states)
131162
{
132-
curStates[state.Key] = state.Value?.LastOrDefault()?.Data ?? string.Empty;
163+
var value = state.Value?.Values?.LastOrDefault();
164+
if (value == null || !value.Active) continue;
165+
166+
curStates[state.Key] = value.Data ?? string.Empty;
133167
}
134168
return curStates;
135169
}
136170

137171
public string GetState(string name, string defaultValue = "")
138172
{
139-
if (!_states.ContainsKey(name) || _states[name].IsNullOrEmpty())
173+
if (!_states.ContainsKey(name) || _states[name].Values.IsNullOrEmpty() || !_states[name].Values.Last().Active)
140174
{
141175
return defaultValue;
142176
}
143177

144-
return _states[name].Last().Data;
178+
return _states[name].Values.Last().Data;
145179
}
146180

147181
public void Dispose()
@@ -152,8 +186,9 @@ public void Dispose()
152186
public bool ContainsState(string name)
153187
{
154188
return _states.ContainsKey(name)
155-
&& !_states[name].IsNullOrEmpty()
156-
&& !string.IsNullOrEmpty(_states[name].Last().Data);
189+
&& !_states[name].Values.IsNullOrEmpty()
190+
&& _states[name].Values.LastOrDefault()?.Active == true
191+
&& !string.IsNullOrEmpty(_states[name].Values.Last().Data);
157192
}
158193

159194
public void SaveStateByArgs(JsonDocument args)

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void Append(string conversationId, RoleDialogModel dialog)
4646
AgentId = agentId,
4747
MessageId = dialog.MessageId,
4848
FunctionName = dialog.FunctionName,
49-
CreateTime = DateTime.UtcNow
49+
CreateTime = dialog.CreatedAt
5050
};
5151

5252
var content = dialog.Content.RemoveNewLine();
@@ -65,7 +65,7 @@ public void Append(string conversationId, RoleDialogModel dialog)
6565
MessageId = dialog.MessageId,
6666
SenderId = dialog.SenderId,
6767
FunctionName = dialog.FunctionName,
68-
CreateTime = DateTime.UtcNow
68+
CreateTime = dialog.CreatedAt
6969
};
7070

7171
var content = dialog.Content.RemoveNewLine();

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ public bool TruncateConversation(string conversationId, string messageId, bool c
481481
var refTime = dialogs.ElementAt(foundIdx).MetaData.CreateTime;
482482
var stateDir = Path.Combine(convDir, STATE_FILE);
483483
var states = CollectConversationStates(stateDir);
484-
isSaved = HandleTruncatedStates(stateDir, states, refTime);
484+
isSaved = HandleTruncatedStates(stateDir, states, messageId, refTime);
485485

486486
// Handle truncated breakpoints
487487
var breakpointDir = Path.Combine(convDir, BREAKPOINT_FILE);
@@ -597,12 +597,20 @@ private bool HandleTruncatedDialogs(string convDir, string dialogDir, List<Dialo
597597
return isSaved;
598598
}
599599

600-
private bool HandleTruncatedStates(string stateDir, List<StateKeyValue> states, DateTime refTime)
600+
private bool HandleTruncatedStates(string stateDir, List<StateKeyValue> states, string refMsgId, DateTime refTime)
601601
{
602602
var truncatedStates = new List<StateKeyValue>();
603603
foreach (var state in states)
604604
{
605-
var values = state.Values.Where(x => x.UpdateTime < refTime).ToList();
605+
if (!state.Versioning)
606+
{
607+
truncatedStates.Add(state);
608+
continue;
609+
}
610+
611+
var values = state.Values.Where(x => x.MessageId != refMsgId)
612+
.Where(x => x.UpdateTime < refTime)
613+
.ToList();
606614
if (values.Count == 0) continue;
607615

608616
state.Values = values;

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,7 @@ public void SaveConversationContentLog(ContentLogOutputModel log)
7171
log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
7272

7373
var convDir = FindConversationDirectory(log.ConversationId);
74-
if (string.IsNullOrEmpty(convDir))
75-
{
76-
convDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, log.ConversationId);
77-
Directory.CreateDirectory(convDir);
78-
}
74+
if (string.IsNullOrEmpty(convDir)) return;
7975

8076
var logDir = Path.Combine(convDir, "content_log");
8177
if (!Directory.Exists(logDir))
@@ -120,11 +116,7 @@ public void SaveConversationStateLog(ConversationStateLogModel log)
120116
log.MessageId = log.MessageId.IfNullOrEmptyAs(Guid.NewGuid().ToString());
121117

122118
var convDir = FindConversationDirectory(log.ConversationId);
123-
if (string.IsNullOrEmpty(convDir))
124-
{
125-
convDir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, log.ConversationId);
126-
Directory.CreateDirectory(convDir);
127-
}
119+
if (string.IsNullOrEmpty(convDir)) return;
128120

129121
var logDir = Path.Combine(convDir, "state_log");
130122
if (!Directory.Exists(logDir))

src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ private async Task DoWork(CancellationToken stoppingToken)
3636
try
3737
{
3838
await CleanIdleConversationsAsync();
39-
await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10));
4039
}
4140
catch (Exception ex)
4241
{
@@ -54,37 +53,6 @@ public override async Task StopAsync(CancellationToken stoppingToken)
5453
await base.StopAsync(stoppingToken);
5554
}
5655

57-
private async Task CloseIdleConversationsAsync(TimeSpan conversationIdleTimeout)
58-
{
59-
using var scope = _services.CreateScope();
60-
var conversationService = scope.ServiceProvider.GetRequiredService<IConversationService>();
61-
var hooks = scope.ServiceProvider.GetServices<IConversationHook>()
62-
.OrderBy(x => x.Priority)
63-
.ToList();
64-
var moment = DateTime.UtcNow.Add(-conversationIdleTimeout);
65-
var conversations = (await conversationService.GetLastConversations()).Where(c => c.CreatedTime <= moment);
66-
foreach (var conversation in conversations)
67-
{
68-
try
69-
{
70-
var response = new RoleDialogModel(AgentRole.Assistant, "End the conversation due to timeout.")
71-
{
72-
StopCompletion = true,
73-
FunctionName = "conversation_end"
74-
};
75-
76-
foreach (var hook in hooks)
77-
{
78-
await hook.OnConversationEnding(response);
79-
}
80-
}
81-
catch (Exception ex)
82-
{
83-
_logger.LogError(ex, $"Error occurred closing conversation #{conversation.Id}.");
84-
}
85-
}
86-
}
87-
8856
private async Task CleanIdleConversationsAsync()
8957
{
9058
using var scope = _services.CreateScope();

src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using BotSharp.Abstraction.Routing;
12
using BotSharp.Abstraction.Users.Models;
23

34
namespace BotSharp.OpenAPI.Controllers;
@@ -161,6 +162,9 @@ public async Task<ChatResponseModel> SendMessage([FromRoute] string agentId,
161162
}
162163

163164
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
165+
var routing = _services.GetRequiredService<IRoutingService>();
166+
routing.Context.SetMessageId(conversationId, inputMsg.MessageId);
167+
164168
conv.SetConversationId(conversationId, input.States);
165169
conv.States.SetState("channel", input.Channel)
166170
.SetState("provider", input.Provider)

src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ private string BuildContentLog(ContentLogInputModel input)
345345
Role = input.Message.Role,
346346
Content = input.Log,
347347
Source = input.Source,
348-
CreateTime = DateTime.UtcNow
348+
CreateTime = input.Message.CreatedAt
349349
};
350350

351351
var json = JsonSerializer.Serialize(output, _options.JsonSerializerOptions);
@@ -367,7 +367,7 @@ private string BuildStateLog(string conversationId, Dictionary<string, string> s
367367
ConversationId = conversationId,
368368
MessageId = message.MessageId,
369369
States = states,
370-
CreateTime = DateTime.UtcNow
370+
CreateTime = message.CreatedAt
371371
};
372372

373373
var convSettings = _services.GetRequiredService<ConversationSetting>();

src/Plugins/BotSharp.Plugin.MongoStorage/Models/StateMongoElement.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ namespace BotSharp.Plugin.MongoStorage.Models;
55
public class StateMongoElement
66
{
77
public string Key { get; set; }
8+
public bool Versioning { get; set; }
89
public List<StateValueMongoElement> Values { get; set; }
910

1011
public static StateMongoElement ToMongoElement(StateKeyValue state)
1112
{
1213
return new StateMongoElement
1314
{
1415
Key = state.Key,
16+
Versioning = state.Versioning,
1517
Values = state.Values?.Select(x => StateValueMongoElement.ToMongoElement(x))?.ToList() ?? new List<StateValueMongoElement>()
1618
};
1719
}
@@ -21,6 +23,7 @@ public static StateKeyValue ToDomainElement(StateMongoElement state)
2123
return new StateKeyValue
2224
{
2325
Key = state.Key,
26+
Versioning = state.Versioning,
2427
Values = state.Values?.Select(x => StateValueMongoElement.ToDomainElement(x))?.ToList() ?? new List<StateValue>()
2528
};
2629
}
@@ -29,13 +32,17 @@ public static StateKeyValue ToDomainElement(StateMongoElement state)
2932
public class StateValueMongoElement
3033
{
3134
public string Data { get; set; }
35+
public string MessageId { get; set; }
36+
public bool Active { get; set; }
3237
public DateTime UpdateTime { get; set; }
3338

3439
public static StateValueMongoElement ToMongoElement(StateValue element)
3540
{
3641
return new StateValueMongoElement
3742
{
3843
Data = element.Data,
44+
MessageId = element.MessageId,
45+
Active = element.Active,
3946
UpdateTime = element.UpdateTime
4047
};
4148
}
@@ -45,6 +52,8 @@ public static StateValue ToDomainElement(StateValueMongoElement element)
4552
return new StateValue
4653
{
4754
Data = element.Data,
55+
MessageId = element.MessageId,
56+
Active = element.Active,
4857
UpdateTime = element.UpdateTime
4958
};
5059
}

0 commit comments

Comments
 (0)