Merge pull request #886 from iceljc/features/refine-agent-call-stats

add agent id as ref
This commit is contained in:
iceljc 2025-02-19 12:23:30 -06:00 committed by GitHub
commit 7756b7ad02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 91 additions and 50 deletions

View file

@ -12,6 +12,7 @@ public interface IAgentService
Task<Agent> CreateAgent(Agent agent);
Task<string> RefreshAgents();
Task<PagedItems<Agent>> GetAgents(AgentFilter filter);
Task<List<IdName>> GetAgentOptions();
/// <summary>
/// Load agent configurations and trigger hooks

View file

@ -3,11 +3,11 @@ namespace BotSharp.Abstraction.Files;
public interface IFileInstructService
{
#region Image
Task<string> ReadImages(string? provider, string? model, string text, IEnumerable<InstructFileModel> images);
Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text);
Task<RoleDialogModel> VaryImage(string? provider, string? model, InstructFileModel image);
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image);
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image, InstructFileModel mask);
Task<string> ReadImages(string? provider, string? model, string text, IEnumerable<InstructFileModel> images, string? agentId = null);
Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text, string? agentId = null);
Task<RoleDialogModel> VaryImage(string? provider, string? model, InstructFileModel image, string? agentId = null);
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image, string? agentId = null);
Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image, InstructFileModel mask, string? agentId = null);
#endregion
#region Pdf
@ -17,7 +17,7 @@ public interface IFileInstructService
/// <param name="prompt"></param>
/// <param name="files">Pdf files</param>
/// <returns></returns>
Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<InstructFileModel> files);
Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<InstructFileModel> files, string? agentId = null);
#endregion
#region Audio

View file

@ -0,0 +1,21 @@
namespace BotSharp.Abstraction.Models;
public class IdName
{
[JsonPropertyName("id")]
public string Id { get; set; } = default!;
[JsonPropertyName("name")]
public string Name { get; set; } = default!;
public IdName(string id, string name)
{
Id = id;
Name = name;
}
public override string ToString()
{
return $"{Id}: {Name}";
}
}

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Routing.Settings;
namespace BotSharp.Core.Agents.Services;
@ -26,6 +27,15 @@ public partial class AgentService
};
}
#if !DEBUG
[SharpCache(10, perInstanceCache: true)]
#endif
public async Task<List<IdName>> GetAgentOptions()
{
var agents = _db.GetAgents(AgentFilter.Empty());
return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? [];
}
#if !DEBUG
[SharpCache(10, perInstanceCache: true)]
#endif

View file

@ -4,12 +4,12 @@ namespace BotSharp.Core.Files.Services;
public partial class FileInstructService
{
public async Task<string> ReadImages(string? provider, string? model, string text, IEnumerable<InstructFileModel> images)
public async Task<string> ReadImages(string? provider, string? model, string text, IEnumerable<InstructFileModel> images, string? agentId = null)
{
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai", model: model ?? "gpt-4o", multiModal: true);
var message = await completion.GetChatCompletions(new Agent()
{
Id = Guid.Empty.ToString(),
Id = agentId ?? Guid.Empty.ToString(),
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, text)
@ -20,17 +20,17 @@ public partial class FileInstructService
return message.Content;
}
public async Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text)
public async Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text, string? agentId = null)
{
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-3");
var message = await completion.GetImageGeneration(new Agent()
{
Id = Guid.Empty.ToString(),
Id = agentId ?? Guid.Empty.ToString(),
}, new RoleDialogModel(AgentRole.User, text));
return message;
}
public async Task<RoleDialogModel> VaryImage(string? provider, string? model, InstructFileModel image)
public async Task<RoleDialogModel> VaryImage(string? provider, string? model, InstructFileModel image, string? agentId = null)
{
if (string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData))
{
@ -46,14 +46,14 @@ public partial class FileInstructService
var fileName = $"{image.FileName ?? "image"}.{image.FileExtension ?? "png"}";
var message = await completion.GetImageVariation(new Agent()
{
Id = Guid.Empty.ToString()
Id = agentId ?? Guid.Empty.ToString()
}, new RoleDialogModel(AgentRole.User, string.Empty), stream, fileName);
stream.Close();
return message;
}
public async Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image)
public async Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image, string? agentId = null)
{
if (string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData))
{
@ -69,14 +69,14 @@ public partial class FileInstructService
var fileName = $"{image.FileName ?? "image"}.{image.FileExtension ?? "png"}";
var message = await completion.GetImageEdits(new Agent()
{
Id = Guid.Empty.ToString()
Id = agentId ?? Guid.Empty.ToString()
}, new RoleDialogModel(AgentRole.User, text), stream, fileName);
stream.Close();
return message;
}
public async Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image, InstructFileModel mask)
public async Task<RoleDialogModel> EditImage(string? provider, string? model, string text, InstructFileModel image, InstructFileModel mask, string? agentId = null)
{
if ((string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData)) ||
(string.IsNullOrWhiteSpace(mask?.FileUrl) && string.IsNullOrWhiteSpace(mask?.FileData)))
@ -100,7 +100,7 @@ public partial class FileInstructService
var maskName = $"{mask.FileName ?? "mask"}.{mask.FileExtension ?? "png"}";
var message = await completion.GetImageEdits(new Agent()
{
Id = Guid.Empty.ToString()
Id = agentId ?? Guid.Empty.ToString()
}, new RoleDialogModel(AgentRole.User, text), imageStream, imageName, maskStream, maskName);
imageStream.Close();

View file

@ -4,7 +4,7 @@ namespace BotSharp.Core.Files.Services;
public partial class FileInstructService
{
public async Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<InstructFileModel> files)
public async Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<InstructFileModel> files, string? agentId = null)
{
var content = string.Empty;
@ -14,7 +14,6 @@ public partial class FileInstructService
}
var guid = Guid.NewGuid().ToString();
var sessionDir = _fileStorage.BuildDirectory(SESSION_FOLDER, guid);
DeleteIfExistDirectory(sessionDir, true);
@ -28,7 +27,7 @@ public partial class FileInstructService
model: model, modelId: modelId ?? "gpt-4", multiModal: true);
var message = await completion.GetChatCompletions(new Agent()
{
Id = Guid.Empty.ToString(),
Id = agentId ?? Guid.Empty.ToString(),
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, prompt)

View file

@ -13,8 +13,8 @@ public static class BotSharpLoggerExtensions
services.AddScoped<IContentGeneratingHook, CommonContentGeneratingHook>();
services.AddScoped<IContentGeneratingHook, TokenStatsConversationHook>();
services.AddScoped<IContentGeneratingHook, VerboseLogHook>();
services.AddScoped<IContentGeneratingHook, GlobalStatsConversationHook>();
services.AddScoped<IConversationHook, RateLimitConversationHook>();
services.AddScoped<IConversationHook, GlobalStatsConversationHook>();
return services;
}
}

View file

@ -4,7 +4,7 @@ using BotSharp.Abstraction.Statistics.Services;
namespace BotSharp.Logger.Hooks;
public class GlobalStatsConversationHook : ConversationHookBase
public class GlobalStatsConversationHook : IContentGeneratingHook
{
private readonly IServiceProvider _services;
@ -14,14 +14,10 @@ public class GlobalStatsConversationHook : ConversationHookBase
_services = services;
}
public override async Task OnMessageReceived(RoleDialogModel message)
{
UpdateAgentCall(message);
}
public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg)
public async Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenStats)
{
UpdateAgentCall(message);
await Task.CompletedTask;
}
private void UpdateAgentCall(RoleDialogModel message)
@ -33,7 +29,7 @@ public class GlobalStatsConversationHook : ConversationHookBase
{
Metric = StatsMetric.AgentCall,
Dimension = "agent",
DimRefVal = message.CurrentAgentId,
DimRefVal = message.CurrentAgentId ?? string.Empty,
RecordTime = DateTime.UtcNow,
IntervalType = StatsInterval.Day,
Data = [

View file

@ -149,6 +149,12 @@ public class AgentController : ControllerBase
return await _agentService.DeleteAgent(agentId);
}
[HttpGet("/agent/options")]
public async Task<List<IdName>> GetAgentOptions()
{
return await _agentService.GetAgentOptions();
}
[HttpGet("/agent/utility/options")]
public IEnumerable<AgentUtility> GetAgentUtilityOptions()
{

View file

@ -88,7 +88,7 @@ public class InstructModeController : ControllerBase
try
{
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var content = await fileInstruct.ReadImages(input.Provider, input.Model, input.Text, input.Files);
var content = await fileInstruct.ReadImages(input.Provider, input.Model, input.Text, input.Files, input.AgentId);
return content;
}
catch (Exception ex)
@ -101,7 +101,7 @@ public class InstructModeController : ControllerBase
[HttpPost("/instruct/multi-modal/upload")]
public async Task<MultiModalViewModel> MultiModalCompletion(IFormFile file, [FromForm] string text, [FromForm] string? provider = null,
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null)
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null, [FromForm] string? agentId = null)
{
var state = _services.GetRequiredService<IConversationStateService>();
states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
@ -115,7 +115,7 @@ public class InstructModeController : ControllerBase
new InstructFileModel { FileData = data }
};
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var content = await fileInstruct.ReadImages(provider, model, text, files);
var content = await fileInstruct.ReadImages(provider, model, text, files, agentId);
viewModel.Content = content;
return viewModel;
}
@ -140,7 +140,7 @@ public class InstructModeController : ControllerBase
try
{
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var message = await fileInstruct.GenerateImage(input.Provider, input.Model, input.Text);
var message = await fileInstruct.GenerateImage(input.Provider, input.Model, input.Text, input.AgentId);
imageViewModel.Content = message.Content;
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
return imageViewModel;
@ -171,7 +171,7 @@ public class InstructModeController : ControllerBase
}
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var message = await fileInstruct.VaryImage(input.Provider, input.Model, input.File);
var message = await fileInstruct.VaryImage(input.Provider, input.Model, input.File, input.AgentId);
imageViewModel.Content = message.Content;
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
@ -188,7 +188,7 @@ public class InstructModeController : ControllerBase
[HttpPost("/instruct/image-variation/upload")]
public async Task<ImageGenerationViewModel> ImageVariation(IFormFile file, [FromForm] string? provider = null,
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null)
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null, [FromForm] string? agentId = null)
{
var state = _services.GetRequiredService<IConversationStateService>();
states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
@ -203,7 +203,7 @@ public class InstructModeController : ControllerBase
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
var message = await completion.GetImageVariation(new Agent()
{
Id = Guid.Empty.ToString()
Id = agentId ?? Guid.Empty.ToString()
}, new RoleDialogModel(AgentRole.User, string.Empty), stream, file.FileName);
imageViewModel.Content = message.Content;
@ -235,7 +235,7 @@ public class InstructModeController : ControllerBase
{
return new ImageGenerationViewModel { Message = "Error! Cannot find a valid image file!" };
}
var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, input.File);
var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, input.File, input.AgentId);
imageViewModel.Content = message.Content;
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
return imageViewModel;
@ -251,7 +251,7 @@ public class InstructModeController : ControllerBase
[HttpPost("/instruct/image-edit/upload")]
public async Task<ImageGenerationViewModel> ImageEdit(IFormFile file, [FromForm] string text, [FromForm] string? provider = null,
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null)
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null, [FromForm] string? agentId = null)
{
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var state = _services.GetRequiredService<IConversationStateService>();
@ -267,7 +267,7 @@ public class InstructModeController : ControllerBase
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
var message = await completion.GetImageEdits(new Agent()
{
Id = Guid.Empty.ToString()
Id = agentId ?? Guid.Empty.ToString()
}, new RoleDialogModel(AgentRole.User, text), stream, file.FileName);
imageViewModel.Content = message.Content;
@ -301,7 +301,7 @@ public class InstructModeController : ControllerBase
{
return new ImageGenerationViewModel { Message = "Error! Cannot find a valid image or mask!" };
}
var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, image, mask);
var message = await fileInstruct.EditImage(input.Provider, input.Model, input.Text, image, mask, input.AgentId);
imageViewModel.Content = message.Content;
imageViewModel.Images = message.GeneratedImages.Select(x => ImageViewModel.ToViewModel(x)).ToList();
return imageViewModel;
@ -317,7 +317,7 @@ public class InstructModeController : ControllerBase
[HttpPost("/instruct/image-mask-edit/upload")]
public async Task<ImageGenerationViewModel> ImageMaskEdit(IFormFile image, IFormFile mask, [FromForm] string text, [FromForm] string? provider = null,
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null)
[FromForm] string? model = null, [FromForm] List<MessageState>? states = null, [FromForm] string? agentId = null)
{
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var state = _services.GetRequiredService<IConversationStateService>();
@ -337,7 +337,7 @@ public class InstructModeController : ControllerBase
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
var message = await completion.GetImageEdits(new Agent()
{
Id = Guid.Empty.ToString()
Id = agentId ?? Guid.Empty.ToString()
}, new RoleDialogModel(AgentRole.User, text), imageStream, image.FileName, maskStream, mask.FileName);
imageViewModel.Content = message.Content;
@ -368,7 +368,7 @@ public class InstructModeController : ControllerBase
try
{
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var content = await fileInstruct.ReadPdf(input.Provider, input.Model, input.ModelId, input.Text, input.Files);
var content = await fileInstruct.ReadPdf(input.Provider, input.Model, input.ModelId, input.Text, input.Files, input.AgentId);
viewModel.Content = content;
return viewModel;
}
@ -383,7 +383,7 @@ public class InstructModeController : ControllerBase
[HttpPost("/instruct/pdf-completion/upload")]
public async Task<PdfCompletionViewModel> PdfCompletion(IFormFile file, [FromForm] string text, [FromForm] string? provider = null,
[FromForm] string? model = null, [FromForm] string? modelId = null, [FromForm] List<MessageState>? states = null)
[FromForm] string? model = null, [FromForm] string? modelId = null, [FromForm] List<MessageState>? states = null, [FromForm] string? agentId = null)
{
var state = _services.GetRequiredService<IConversationStateService>();
states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
@ -398,7 +398,7 @@ public class InstructModeController : ControllerBase
};
var fileInstruct = _services.GetRequiredService<IFileInstructService>();
var content = await fileInstruct.ReadPdf(provider, model, modelId, text, files);
var content = await fileInstruct.ReadPdf(provider, model, modelId, text, files, agentId);
viewModel.Content = content;
return viewModel;
}

View file

@ -13,6 +13,9 @@ public class InstructBaseRequest
[JsonPropertyName("model_id")]
public virtual string? ModelId { get; set; } = null;
[JsonPropertyName("agent_id")]
public virtual string? AgentId { get; set; }
[JsonPropertyName("states")]
public List<MessageState> States { get; set; } = new();
}

View file

@ -42,6 +42,7 @@ public class DialogMongoElement
}
}
[BsonIgnoreExtraElements(Inherited = true)]
public class DialogMetaDataMongoElement
{
public string Role { get; set; } = default!;
@ -50,7 +51,7 @@ public class DialogMetaDataMongoElement
public string MessageType { get; set; } = default!;
public string? FunctionName { get; set; }
public string? SenderId { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime CreateTime { get; set; }
public static DialogMetaData ToDomainElement(DialogMetaDataMongoElement meta)
{
@ -62,7 +63,7 @@ public class DialogMetaDataMongoElement
MessageType = meta.MessageType,
FunctionName = meta.FunctionName,
SenderId = meta.SenderId,
CreatedTime = meta.CreatedTime,
CreatedTime = meta.CreateTime,
};
}
@ -76,7 +77,7 @@ public class DialogMetaDataMongoElement
MessageType = meta.MessageType,
FunctionName = meta.FunctionName,
SenderId = meta.SenderId,
CreatedTime = meta.CreatedTime,
CreateTime = meta.CreatedTime,
};
}
}

View file

@ -48,10 +48,11 @@ public class MongoDbContext
private IMongoDatabase Database => _mongoClient.GetDatabase(_mongoDbDatabaseName);
#region Private methods
private bool CollectionExists(IMongoDatabase database, string collectionName)
{
var filter = Builders<BsonDocument>.Filter.Eq("name", collectionName);
var collections = database.ListCollections(new ListCollectionsOptions { Filter = filter });
var collections = database.ListCollectionNames(new ListCollectionNamesOptions { Filter = filter }).ToList();
return collections.Any();
}
@ -60,9 +61,11 @@ public class MongoDbContext
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException($"The collection {name} cannot be empty.");
string collectionName = $"{_collectionPrefix}_{name}";
var collectionName = $"{_collectionPrefix}_{name}";
if (!CollectionExists(Database, collectionName))
{
Database.CreateCollection(collectionName);
}
var collection = Database.GetCollection<TDocument>(collectionName);
return collection;
@ -134,6 +137,7 @@ public class MongoDbContext
return collection;
}
#endregion
#endregion
public IMongoCollection<AgentDocument> Agents
=> GetCollectionOrCreate<AgentDocument>("Agents");

View file

@ -534,7 +534,7 @@ public partial class MongoRepository
var truncatedDialogs = foundDialog.Dialogs.Where((x, idx) => idx < foundIdx).ToList();
// Handle truncated states
var refTime = foundDialog.Dialogs.ElementAt(foundIdx).MetaData.CreatedTime;
var refTime = foundDialog.Dialogs.ElementAt(foundIdx).MetaData.CreateTime;
var stateFilter = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundStates = _dc.ConversationStates.Find(stateFilter).FirstOrDefault();