refine global stats
This commit is contained in:
parent
a8fa46eff5
commit
453d4bc38f
|
|
@ -1,7 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
public static class StatCategory
|
||||
public static class StatsCategory
|
||||
{
|
||||
public static string LlmCost = "llm-cost";
|
||||
public static string AgentLlmCost = "agent-llm-cost";
|
||||
public static string AgentCall = "agent-call";
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
public enum StatsOperation
|
||||
{
|
||||
Add = 1,
|
||||
Subtract = 2,
|
||||
Reset = 3
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ public class BotSharpStats
|
|||
public string Group { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public IDictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, double> Data { get; set; } = new Dictionary<string, double>();
|
||||
|
||||
private DateTime innerRecordTime;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
public class BotSharpStatsInput
|
||||
{
|
||||
public string Category { get; set; }
|
||||
public string Group { get; set; }
|
||||
public List<StatsKeyValuePair> Data { get; set; } = [];
|
||||
public DateTime RecordTime { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
public class StatsKeyValuePair
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public double Value { get; set; }
|
||||
public StatsOperation Operation { get; set; }
|
||||
|
||||
public StatsKeyValuePair()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public StatsKeyValuePair(string key, double value, StatsOperation operation = StatsOperation.Add)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Operation = operation;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Key}]: {Value} ({Operation})";
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,5 @@ namespace BotSharp.Abstraction.Statistics.Services;
|
|||
|
||||
public interface IBotSharpStatsService
|
||||
{
|
||||
bool UpdateLlmCost(BotSharpStats stats);
|
||||
bool UpdateAgentCall(BotSharpStats stats);
|
||||
bool UpdateStats(string resourceKey, BotSharpStatsInput input);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,20 +60,19 @@ public class TokenStatistics : ITokenStatistics
|
|||
|
||||
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatsService>();
|
||||
var body = new BotSharpStats
|
||||
var body = new BotSharpStatsInput
|
||||
{
|
||||
Category = StatCategory.LlmCost,
|
||||
Group = $"Agent: {message.CurrentAgentId}",
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "prompt_token_count_total", stats.PromptCount },
|
||||
{ "completion_token_count_total", stats.CompletionCount },
|
||||
{ "prompt_cost_total", deltaPromptCost },
|
||||
{ "completion_cost_total", deltaCompletionCost }
|
||||
},
|
||||
RecordTime = DateTime.UtcNow
|
||||
Category = StatsCategory.AgentLlmCost,
|
||||
Group = message.CurrentAgentId,
|
||||
RecordTime = DateTime.UtcNow,
|
||||
Data = [
|
||||
new StatsKeyValuePair("prompt_token_count_total", stats.PromptCount),
|
||||
new StatsKeyValuePair("completion_token_count_total", stats.CompletionCount),
|
||||
new StatsKeyValuePair("prompt_cost_total", deltaPromptCost),
|
||||
new StatsKeyValuePair("completion_cost_total", deltaCompletionCost)
|
||||
]
|
||||
};
|
||||
globalStats.UpdateLlmCost(body);
|
||||
globalStats.UpdateStats("global-llm-cost", body);
|
||||
}
|
||||
|
||||
public void PrintStatistics()
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ public partial class FileRepository
|
|||
var file = Directory.GetFiles(dir).FirstOrDefault(x => Path.GetFileName(x) == STATS_FILE);
|
||||
if (file == null) return null;
|
||||
|
||||
var time = BuildRecordTime(recordTime);
|
||||
var text = File.ReadAllText(file);
|
||||
var list = JsonSerializer.Deserialize<List<BotSharpStats>>(text, _options);
|
||||
var found = list?.FirstOrDefault(x => x.Category.IsEqualTo(category)
|
||||
&& x.Group.IsEqualTo(group)
|
||||
&& x.RecordTime == recordTime);
|
||||
&& x.RecordTime == time);
|
||||
return found;
|
||||
}
|
||||
|
||||
|
|
@ -38,11 +39,12 @@ public partial class FileRepository
|
|||
}
|
||||
else
|
||||
{
|
||||
var time = BuildRecordTime(body.RecordTime);
|
||||
var text = File.ReadAllText(file);
|
||||
var list = JsonSerializer.Deserialize<List<BotSharpStats>>(text, _options);
|
||||
var found = list?.FirstOrDefault(x => x.Category.IsEqualTo(body.Category)
|
||||
&& x.Group.IsEqualTo(body.Group)
|
||||
&& x.RecordTime == body.RecordTime);
|
||||
&& x.RecordTime == time);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
|
|
@ -65,4 +67,12 @@ public partial class FileRepository
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private DateTime BuildRecordTime(DateTime date)
|
||||
{
|
||||
var recordDate = new DateTime(date.Year, date.Month, date.Day, date.Hour, 0, 0);
|
||||
return DateTime.SpecifyKind(recordDate, DateTimeKind.Utc);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ public class BotSharpStatsService : IBotSharpStatsService
|
|||
private readonly ILogger<BotSharpStatsService> _logger;
|
||||
private readonly StatisticsSettings _settings;
|
||||
|
||||
private const string GLOBAL_LLM_COST = "global-llm-cost";
|
||||
private const string GLOBAL_AGENT_CALL = "global-agent-call";
|
||||
private const int TIMEOUT_SECONDS = 5;
|
||||
|
||||
public BotSharpStatsService(
|
||||
|
|
@ -23,109 +21,71 @@ public class BotSharpStatsService : IBotSharpStatsService
|
|||
_settings = settings;
|
||||
}
|
||||
|
||||
public bool UpdateLlmCost(BotSharpStats stats)
|
||||
|
||||
public bool UpdateStats(string resourceKey, BotSharpStatsInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_settings.Enabled) return false;
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var locker = _services.GetRequiredService<IDistributedLocker>();
|
||||
|
||||
var res = locker.Lock(GLOBAL_LLM_COST, () =>
|
||||
if (!_settings.Enabled
|
||||
|| string.IsNullOrEmpty(resourceKey)
|
||||
|| input == null
|
||||
|| string.IsNullOrEmpty(input.Category)
|
||||
|| string.IsNullOrEmpty(input.Group))
|
||||
{
|
||||
var body = db.GetGlobalStats(stats.Category, stats.Group, stats.RecordTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
var locker = _services.GetRequiredService<IDistributedLocker>();
|
||||
var res = locker.Lock(resourceKey, () =>
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var body = db.GetGlobalStats(input.Category, input.Group, input.RecordTime);
|
||||
if (body == null)
|
||||
{
|
||||
var stats = new BotSharpStats
|
||||
{
|
||||
Category = input.Category,
|
||||
Group = input.Group,
|
||||
RecordTime = input.RecordTime,
|
||||
Data = input.Data.ToDictionary(x => x.Key, x => x.Value)
|
||||
};
|
||||
db.SaveGlobalStats(stats);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in stats.Data)
|
||||
foreach (var item in input.Data)
|
||||
{
|
||||
var curValue = item.Value;
|
||||
if (body.Data.TryGetValue(item.Key, out var preValue))
|
||||
{
|
||||
var preValStr = preValue?.ToString();
|
||||
var curValStr = curValue?.ToString();
|
||||
try
|
||||
switch (item.Operation)
|
||||
{
|
||||
if (int.TryParse(preValStr, out var count))
|
||||
{
|
||||
curValue = int.Parse(curValStr ?? "0") + count;
|
||||
}
|
||||
else if (double.TryParse(preValStr, out var num))
|
||||
{
|
||||
curValue = double.Parse(curValStr ?? "0") + num;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
case StatsOperation.Add:
|
||||
preValue += curValue;
|
||||
break;
|
||||
case StatsOperation.Subtract:
|
||||
preValue -= curValue;
|
||||
break;
|
||||
case StatsOperation.Reset:
|
||||
preValue = 0;
|
||||
break;
|
||||
}
|
||||
body.Data[item.Key] = preValue;
|
||||
}
|
||||
|
||||
body.Data[item.Key] = curValue;
|
||||
}
|
||||
|
||||
db.SaveGlobalStats(body);
|
||||
}, TIMEOUT_SECONDS);
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when updating global llm cost stats {stats}. {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateAgentCall(BotSharpStats stats)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_settings.Enabled) return false;
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var locker = _services.GetRequiredService<IDistributedLocker>();
|
||||
|
||||
var res = locker.Lock(GLOBAL_AGENT_CALL, () =>
|
||||
{
|
||||
var body = db.GetGlobalStats(stats.Category, stats.Group, stats.RecordTime);
|
||||
if (body == null)
|
||||
{
|
||||
db.SaveGlobalStats(stats);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in stats.Data)
|
||||
{
|
||||
var curValue = item.Value;
|
||||
if (body.Data.TryGetValue(item.Key, out var preValue))
|
||||
else
|
||||
{
|
||||
var preValStr = preValue?.ToString();
|
||||
var curValStr = curValue?.ToString();
|
||||
try
|
||||
{
|
||||
if (int.TryParse(preValStr, out var count))
|
||||
{
|
||||
curValue = int.Parse(curValStr ?? "0") + count;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
body.Data[item.Key] = curValue;
|
||||
}
|
||||
body.Data[item.Key] = curValue;
|
||||
}
|
||||
|
||||
db.SaveGlobalStats(body);
|
||||
}, TIMEOUT_SECONDS);
|
||||
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when updating global agent call stats {stats}. {ex.Message}\r\n{ex.InnerException}");
|
||||
_logger.LogError($"Error when updating global stats {input.Category}-{input.Group}. {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,15 @@ public class GlobalStatsConversationHook : ConversationHookBase
|
|||
// record agent call
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatsService>();
|
||||
|
||||
var body = new BotSharpStats
|
||||
var body = new BotSharpStatsInput
|
||||
{
|
||||
Category = StatCategory.AgentCall,
|
||||
Group = $"Agent: {message.CurrentAgentId}",
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "agent_id", message.CurrentAgentId },
|
||||
{ "agent_call_count", 1 }
|
||||
},
|
||||
Category = StatsCategory.AgentCall,
|
||||
Group = message.CurrentAgentId,
|
||||
Data = [
|
||||
new StatsKeyValuePair("agent_call_count", 1)
|
||||
],
|
||||
RecordTime = DateTime.UtcNow
|
||||
};
|
||||
globalStats.UpdateAgentCall(body);
|
||||
globalStats.UpdateStats("global-agent-call", body);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,9 +253,7 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
else if (message.Role == AgentRole.User)
|
||||
{
|
||||
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
|
||||
var textPart = ChatMessageContentPart.CreateTextPart(text);
|
||||
var contentParts = new List<ChatMessageContentPart> { textPart };
|
||||
messages.Add(new UserChatMessage(contentParts));
|
||||
messages.Add(new UserChatMessage(text));
|
||||
}
|
||||
else if (message.Role == AgentRole.Assistant)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ global using System.Linq;
|
|||
global using System.Text.Json;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using DeepSeek.Core;
|
||||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Agents.Models;
|
||||
global using BotSharp.Abstraction.MLTasks;
|
||||
|
|
@ -15,5 +14,4 @@ global using BotSharp.Abstraction.Agents.Enums;
|
|||
global using BotSharp.Abstraction.Conversations;
|
||||
global using BotSharp.Abstraction.Loggers;
|
||||
global using BotSharp.Abstraction.Functions.Models;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Plugin.DeepSeekAI.Models;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
|
|
@ -4,6 +4,6 @@ public class GlobalStatisticsDocument : MongoBase
|
|||
{
|
||||
public string Category { get; set; }
|
||||
public string Group { get; set; }
|
||||
public IDictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, double> Data { get; set; } = new Dictionary<string, double>();
|
||||
public DateTime RecordTime { get; set; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue