Merge pull request #916 from iceljc/features/add-latest-state

Features/add latest state
This commit is contained in:
iceljc 2025-03-05 17:38:00 -06:00 committed by GitHub
commit d94b5cb297
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 1169 additions and 102 deletions

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>

View file

@ -108,7 +108,8 @@ public class RoleDialogModel : ITrackableMessage
[JsonPropertyName("generated_images")]
public List<ImageGeneration> GeneratedImages { get; set; } = new List<ImageGeneration>();
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string RenderedInstruction { get; set; } = string.Empty;
private RoleDialogModel()
{

View file

@ -7,4 +7,5 @@ public interface IInstructHook
string SelfId { get; }
Task BeforeCompletion(Agent agent, RoleDialogModel message);
Task AfterCompletion(Agent agent, InstructResult result);
Task OnResponseGenerated(InstructResponseModel response);
}

View file

@ -15,4 +15,9 @@ public class InstructHookBase : IInstructHook
{
return;
}
public virtual async Task OnResponseGenerated(InstructResponseModel response)
{
return;
}
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace BotSharp.Abstraction.Instructs.Models;
public class InstructLogFilter : Pagination
{
public List<string>? AgentIds { get; set; }
public List<string>? Providers { get; set; }
public List<string>? Models { get; set; }
public List<string>? TemplateNames { get; set; }
public static InstructLogFilter Empty()
{
return new InstructLogFilter();
}
}

View file

@ -0,0 +1,12 @@
namespace BotSharp.Abstraction.Instructs.Models;
public class InstructResponseModel
{
public string? AgentId { get; set; }
public string Provider { get; set; } = default!;
public string Model { get; set; } = default!;
public string? TemplateName { get; set; }
public string UserMessage { get; set; } = default!;
public string? SystemInstruction { get; set; }
public string CompletionText { get; set; } = default!;
}

View file

@ -0,0 +1,50 @@
using System.Text.Json;
namespace BotSharp.Abstraction.Loggers.Models;
public class InstructionLogModel
{
[JsonPropertyName("id")]
public string Id { get; set; } = default!;
[JsonPropertyName("agent_id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? AgentId { get; set; }
[JsonPropertyName("agent_name")]
[JsonIgnore]
public string? AgentName { get; set; }
[JsonPropertyName("provider")]
public string Provider { get; set; } = default!;
[JsonPropertyName("model")]
public string Model { get; set; } = default!;
[JsonPropertyName("template_name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? TemplateName { get; set; }
[JsonPropertyName("user_message")]
public string UserMessage { get; set; } = string.Empty;
[JsonPropertyName("system_instruction")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? SystemInstruction { get; set; }
[JsonPropertyName("completion_text")]
public string CompletionText { get; set; } = string.Empty;
[JsonPropertyName("user_id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? UserId { get; set; }
[JsonIgnore]
public Dictionary<string, string> States { get; set; } = [];
[JsonPropertyName("states")]
public Dictionary<string, JsonDocument> InnerStates { get; set; } = [];
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}

View file

@ -6,6 +6,8 @@ public interface IAudioCompletion
{
string Provider { get; }
string Model { get; }
Task<string> GenerateTextFromAudioAsync(Stream audio, string audioFileName, string? text = null);
Task<BinaryData> GenerateAudioFromTextAsync(string text);

View file

@ -7,6 +7,8 @@ public interface IChatCompletion
/// </summary>
string Provider { get; }
string Model { get; }
/// <summary>
/// Set model name, one provider can consume different model or version(s)
/// </summary>

View file

@ -9,6 +9,8 @@ public interface IImageCompletion
/// </summary>
string Provider { get; }
string Model { get; }
/// <summary>
/// Set model name, one provider can consume different model or version(s)
/// </summary>

View file

@ -6,7 +6,6 @@ public interface IRealTimeCompletion
{
string Provider { get; }
string Model { get; }
void SetModelName(string model);
Task Connect(RealtimeHubConnection conn,

View file

@ -6,6 +6,7 @@ public interface ITextCompletion
/// The LLM provider like Microsoft Azure, OpenAI, ClaudAI
/// </summary>
string Provider { get; }
string Model { get; }
/// <summary>
/// Set model name, one provider can consume different model or version(s)

View file

@ -6,9 +6,14 @@ public interface ITextEmbedding
/// The Embedding provider like Microsoft Azure, OpenAI, ClaudAI
/// </summary>
string Provider { get; }
string Model { get; }
void SetModelName(string model);
Task<float[]> GetVectorAsync(string text);
Task<List<float[]>> GetVectorsAsync(List<string> texts);
void SetModelName(string model);
void SetDimension(int dimension);
int GetDimension();
}

View file

@ -13,3 +13,17 @@ public class KeyValue
return $"Key: {Key}, Value: {Value}";
}
}
public class KeyValue<T>
{
[JsonPropertyName("key")]
public string Key { get; set; }
[JsonPropertyName("value")]
public T? Value { get; set; }
public override string ToString()
{
return $"Key: {Key}, Value: {Value}";
}
}

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories.Filters;
@ -171,6 +172,14 @@ public interface IBotSharpRepository : IHaveServiceProvider
=> throw new NotImplementedException();
#endregion
#region Instruction Log
bool SaveInstructionLogs(IEnumerable<InstructionLogModel> logs)
=> throw new NotImplementedException();
PagedItems<InstructionLogModel> GetInstructionLogs(InstructLogFilter filter)
=> throw new NotImplementedException();
#endregion
#region Statistics
BotSharpStats? GetGlobalStats(string metric, string dimension, string dimRefVal, DateTime recordTime, StatsInterval interval)
=> throw new NotImplementedException();

View file

@ -92,4 +92,20 @@ public static class StringExtensions
return JsonSerializer.Deserialize<T[]>(text, options);
}
public static bool IsPrimitiveValue(this string value)
{
return int.TryParse(value, out _) ||
long.TryParse(value, out _) ||
double.TryParse(value, out _) ||
float.TryParse(value, out _) ||
bool.TryParse(value, out _) ||
char.TryParse(value, out _) ||
byte.TryParse(value, out _) ||
sbyte.TryParse(value, out _) ||
short.TryParse(value, out _) ||
ushort.TryParse(value, out _) ||
uint.TryParse(value, out _) ||
ulong.TryParse(value, out _);
}
}

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Instructs;
using System.IO;
namespace BotSharp.Core.Files.Services;
@ -6,10 +8,11 @@ public partial class FileInstructService
{
public async Task<string> ReadImages(string? provider, string? model, string text, IEnumerable<InstructFileModel> images, string? agentId = null)
{
var innerAgentId = agentId ?? Guid.Empty.ToString();
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai", model: model ?? "gpt-4o", multiModal: true);
var message = await completion.GetChatCompletions(new Agent()
{
Id = agentId ?? Guid.Empty.ToString(),
Id = innerAgentId,
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, text)
@ -17,16 +20,55 @@ public partial class FileInstructService
Files = images?.Select(x => new BotSharpFile { FileUrl = x.FileUrl, FileData = x.FileData }).ToList() ?? new List<BotSharpFile>()
}
});
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = innerAgentId,
Provider = completion.Provider,
Model = completion.Model,
UserMessage = text,
CompletionText = message.Content
});
}
return message.Content;
}
public async Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text, string? agentId = null)
{
var innerAgentId = agentId ?? Guid.Empty.ToString();
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-3");
var message = await completion.GetImageGeneration(new Agent()
{
Id = agentId ?? Guid.Empty.ToString(),
Id = innerAgentId,
}, new RoleDialogModel(AgentRole.User, text));
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = innerAgentId,
Provider = completion.Provider,
Model = completion.Model,
UserMessage = text,
CompletionText = message.Content
});
}
return message;
}
@ -37,6 +79,7 @@ public partial class FileInstructService
throw new ArgumentException($"Cannot find image url or data!");
}
var innerAgentId = agentId ?? Guid.Empty.ToString();
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
var bytes = await DownloadFile(image);
using var stream = new MemoryStream();
@ -46,10 +89,29 @@ public partial class FileInstructService
var fileName = $"{image.FileName ?? "image"}.{image.FileExtension ?? "png"}";
var message = await completion.GetImageVariation(new Agent()
{
Id = agentId ?? Guid.Empty.ToString()
Id = innerAgentId
}, new RoleDialogModel(AgentRole.User, string.Empty), stream, fileName);
stream.Close();
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = innerAgentId,
Provider = completion.Provider,
Model = completion.Model,
UserMessage = string.Empty,
CompletionText = message.Content
});
}
return message;
}
@ -60,6 +122,7 @@ public partial class FileInstructService
throw new ArgumentException($"Cannot find image url or data!");
}
var innerAgentId = agentId ?? Guid.Empty.ToString();
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
var bytes = await DownloadFile(image);
using var stream = new MemoryStream();
@ -69,10 +132,29 @@ public partial class FileInstructService
var fileName = $"{image.FileName ?? "image"}.{image.FileExtension ?? "png"}";
var message = await completion.GetImageEdits(new Agent()
{
Id = agentId ?? Guid.Empty.ToString()
Id = innerAgentId
}, new RoleDialogModel(AgentRole.User, text), stream, fileName);
stream.Close();
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = innerAgentId,
Provider = completion.Provider,
Model = completion.Model,
UserMessage = text,
CompletionText = message.Content
});
}
return message;
}
@ -84,6 +166,7 @@ public partial class FileInstructService
throw new ArgumentException($"Cannot find image/mask url or data");
}
var innerAgentId = agentId ?? Guid.Empty.ToString();
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
var imageBytes = await DownloadFile(image);
var maskBytes = await DownloadFile(mask);
@ -100,11 +183,30 @@ public partial class FileInstructService
var maskName = $"{mask.FileName ?? "mask"}.{mask.FileExtension ?? "png"}";
var message = await completion.GetImageEdits(new Agent()
{
Id = agentId ?? Guid.Empty.ToString()
Id = innerAgentId
}, new RoleDialogModel(AgentRole.User, text), imageStream, imageName, maskStream, maskName);
imageStream.Close();
maskStream.Close();
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = innerAgentId,
Provider = completion.Provider,
Model = completion.Model,
UserMessage = text,
CompletionText = message.Content
});
}
return message;
}

View file

@ -1,4 +1,6 @@
using BotSharp.Abstraction.Files.Converters;
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Instructs;
namespace BotSharp.Core.Files.Services;
@ -23,11 +25,12 @@ public partial class FileInstructService
var images = await ConvertPdfToImages(pdfFiles);
if (images.IsNullOrEmpty()) return content;
var innerAgentId = agentId ?? Guid.Empty.ToString();
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai",
model: model, modelId: modelId ?? "gpt-4", multiModal: true);
var message = await completion.GetChatCompletions(new Agent()
{
Id = agentId ?? Guid.Empty.ToString(),
Id = innerAgentId,
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, prompt)
@ -35,6 +38,25 @@ public partial class FileInstructService
Files = images.Select(x => new BotSharpFile { FileStorageUrl = x }).ToList()
}
});
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = innerAgentId,
Provider = completion.Provider,
Model = completion.Model,
UserMessage = prompt,
CompletionText = message.Content
});
}
return message.Content;
}
catch (Exception ex)

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Users.Enums;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Instructs;
@ -17,7 +18,15 @@ public class InsturctionPlugin : IBotSharpPlugin
public bool AttachMenu(List<PluginMenuDef> menu)
{
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("Instruction", link: "page/instruction", icon: "bx bx-book-content", weight: section.Weight + 5));
menu.Add(new PluginMenuDef("Instruction", icon: "bx bx-book-content", weight: section.Weight + 5)
{
SubMenu = new List<PluginMenuDef>
{
new PluginMenuDef("Instruction", link: "page/instruction"),
new PluginMenuDef("Log", link: "page/instruction/log") { Roles = [UserRole.Root, UserRole.Admin] }
}
});
return true;
}
}

View file

@ -43,6 +43,9 @@ public partial class InstructService
}
}
var provider = string.Empty;
var model = string.Empty;
// Render prompt
var prompt = string.IsNullOrEmpty(templateName) ?
agentService.RenderedInstruction(agent) :
@ -57,11 +60,18 @@ public partial class InstructService
};
if (completer is ITextCompletion textCompleter)
{
instruction = null;
provider = textCompleter.Provider;
model = textCompleter.Model;
var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId);
response.Text = result;
}
else if (completer is IChatCompletion chatCompleter)
{
provider = chatCompleter.Provider;
model = chatCompleter.Model;
if (instruction == "#TEMPLATE#")
{
instruction = prompt;
@ -93,6 +103,16 @@ public partial class InstructService
}
await hook.AfterCompletion(agent, response);
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = agentId,
Provider = provider,
Model = model,
TemplateName = templateName,
UserMessage = prompt,
SystemInstruction = instruction,
CompletionText = response.Text
});
}
return response;

View file

@ -33,13 +33,19 @@ public partial class FileRepository
var stateFile = Path.Combine(dir, STATE_FILE);
if (!File.Exists(stateFile))
{
File.WriteAllText(stateFile, JsonSerializer.Serialize(new List<StateKeyValue>(), _options));
File.WriteAllText(stateFile, "[]");
}
var latestStateFile = Path.Combine(dir, CONV_LATEST_STATE_FILE);
if (!File.Exists(latestStateFile))
{
File.WriteAllText(latestStateFile, "{}");
}
var breakpointFile = Path.Combine(dir, BREAKPOINT_FILE);
if (!File.Exists(breakpointFile))
{
File.WriteAllText(breakpointFile, JsonSerializer.Serialize(new List<ConversationBreakpoint>(), _options));
File.WriteAllText(breakpointFile, "[]");
}
}
@ -300,14 +306,21 @@ public partial class FileRepository
if (states.IsNullOrEmpty()) return;
var convDir = FindConversationDirectory(conversationId);
if (!string.IsNullOrEmpty(convDir))
if (string.IsNullOrEmpty(convDir)) return;
var stateFile = Path.Combine(convDir, STATE_FILE);
if (File.Exists(stateFile))
{
var stateFile = Path.Combine(convDir, STATE_FILE);
if (File.Exists(stateFile))
{
var stateStr = JsonSerializer.Serialize(states, _options);
File.WriteAllText(stateFile, stateStr);
}
var stateStr = JsonSerializer.Serialize(states, _options);
File.WriteAllText(stateFile, stateStr);
}
var latestStateFile = Path.Combine(convDir, CONV_LATEST_STATE_FILE);
if (File.Exists(latestStateFile))
{
var latestStates = BuildLatestStates(states);
var stateStr = JsonSerializer.Serialize(latestStates, _options);
File.WriteAllText(latestStateFile, stateStr);
}
}
@ -427,25 +440,63 @@ public partial class FileRepository
}
// Check states
if (filter != null && !filter.States.IsNullOrEmpty())
if (matched && filter != null && !filter.States.IsNullOrEmpty())
{
var stateFile = Path.Combine(d, STATE_FILE);
var convStates = CollectConversationStates(stateFile);
foreach (var pair in filter.States)
var latestStateFile = Path.Combine(d, CONV_LATEST_STATE_FILE);
var convStates = CollectConversationLatestStates(latestStateFile);
if (convStates.IsNullOrEmpty())
{
if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue;
var foundState = convStates.FirstOrDefault(x => x.Key.IsEqualTo(pair.Key));
if (foundState == null)
matched = false;
}
else
{
foreach (var pair in filter.States)
{
matched = false;
break;
}
if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue;
if (!string.IsNullOrWhiteSpace(pair.Value))
{
var curValue = foundState.Values.LastOrDefault()?.Data;
matched = matched && pair.Value.IsEqualTo(curValue);
var components = pair.Key.Split(".").ToList();
var primaryKey = components[0];
if (convStates.TryGetValue(primaryKey, out var doc))
{
var elem = doc.RootElement.GetProperty("data");
if (components.Count < 2)
{
if (!string.IsNullOrWhiteSpace(pair.Value))
{
if (elem.ValueKind == JsonValueKind.Null)
{
matched = false;
}
else if (elem.ValueKind == JsonValueKind.Array)
{
matched = elem.EnumerateArray().Where(x => x.ValueKind != JsonValueKind.Null)
.Select(x => x.ToString())
.Any(x => x == pair.Value);
}
else if (elem.ValueKind == JsonValueKind.String)
{
matched = elem.GetString() == pair.Value;
}
else
{
matched = elem.GetRawText() == pair.Value;
}
}
}
else
{
var paths = components.Where((_, idx) => idx > 0);
var found = FindState(elem, paths, pair.Value);
matched = found != null;
}
}
else
{
matched = false;
}
if (!matched) break;
}
}
}
@ -575,8 +626,9 @@ public partial class FileRepository
// Handle truncated states
var refTime = dialogs.ElementAt(foundIdx).MetaData.CreatedTime;
var stateDir = Path.Combine(convDir, STATE_FILE);
var latestStateDir = Path.Combine(convDir, CONV_LATEST_STATE_FILE);
var states = CollectConversationStates(stateDir);
isSaved = HandleTruncatedStates(stateDir, states, messageId, refTime);
isSaved = HandleTruncatedStates(stateDir, latestStateDir, states, messageId, refTime);
// Handle truncated breakpoints
var breakpointDir = Path.Combine(convDir, BREAKPOINT_FILE);
@ -703,7 +755,7 @@ public partial class FileRepository
return isSaved;
}
private bool HandleTruncatedStates(string stateDir, List<StateKeyValue> states, string refMsgId, DateTime refTime)
private bool HandleTruncatedStates(string stateDir, string latestStateDir, List<StateKeyValue> states, string refMsgId, DateTime refTime)
{
var truncatedStates = new List<StateKeyValue>();
foreach (var state in states)
@ -724,6 +776,10 @@ public partial class FileRepository
}
var isSaved = SaveTruncatedStates(stateDir, truncatedStates);
if (isSaved)
{
SaveTruncatedLatestStates(latestStateDir, truncatedStates);
}
return isSaved;
}
@ -794,6 +850,17 @@ public partial class FileRepository
return true;
}
private bool SaveTruncatedLatestStates(string latestStateDir, List<StateKeyValue> states)
{
if (string.IsNullOrEmpty(latestStateDir) || states == null) return false;
if (!File.Exists(latestStateDir)) File.Create(latestStateDir);
var latestStates = BuildLatestStates(states);
var stateStr = JsonSerializer.Serialize(latestStates, _options);
File.WriteAllText(latestStateDir, stateStr);
return true;
}
private bool SaveTruncatedBreakpoints(string breakpointDir, List<ConversationBreakpoint> breakpoints)
{
if (string.IsNullOrEmpty(breakpointDir) || breakpoints == null) return false;
@ -804,22 +871,108 @@ public partial class FileRepository
return true;
}
private string? EncodeText(string? text)
private Dictionary<string, JsonDocument> CollectConversationLatestStates(string latestStateDir)
{
if (string.IsNullOrEmpty(text)) return text;
if (string.IsNullOrEmpty(latestStateDir) || !File.Exists(latestStateDir)) return [];
var bytes = Encoding.UTF8.GetBytes(text);
var encoded = Convert.ToBase64String(bytes);
return encoded;
var str = File.ReadAllText(latestStateDir);
var states = JsonSerializer.Deserialize<Dictionary<string, JsonDocument>>(str, _options);
return states ?? [];
}
private string? DecodeText(string? text)
private Dictionary<string, JsonDocument> BuildLatestStates(List<StateKeyValue> states)
{
if (string.IsNullOrEmpty(text)) return text;
var endNodes = new Dictionary<string, JsonDocument>();
foreach (var pair in states)
{
var value = pair.Values?.LastOrDefault();
if (value == null || !value.Active) continue;
var decoded = Convert.FromBase64String(text);
var origin = Encoding.UTF8.GetString(decoded);
return origin;
try
{
var jsonStr = JsonSerializer.Serialize(new { Data = JsonDocument.Parse(value.Data) }, _options);
var json = JsonDocument.Parse(jsonStr);
endNodes[pair.Key] = json;
}
catch
{
var str = JsonSerializer.Serialize(new { Data = value.Data }, _options);
var json = JsonDocument.Parse(str);
endNodes[pair.Key] = json;
}
}
return endNodes;
}
private JsonElement? FindState(JsonElement? root, IEnumerable<string> paths, string? targetValue)
{
var elem = root;
if (elem == null || paths.IsNullOrEmpty())
{
return null;
}
for (int i = 0; i < paths.Count(); i++)
{
if (elem == null) return null;
var field = paths.ElementAt(i);
if (elem.Value.ValueKind == JsonValueKind.Array)
{
if (!elem.Value.EnumerateArray().IsNullOrEmpty())
{
foreach (var item in elem.Value.EnumerateArray())
{
var subPaths = paths.Where((_, idx) => idx >= i);
elem = FindState(item, subPaths, targetValue);
if (elem != null)
{
return elem;
}
}
}
else
{
return null;
}
}
else if (elem.Value.ValueKind == JsonValueKind.Object && elem.Value.TryGetProperty(field, out var prop))
{
elem = prop;
}
else
{
return null;
}
}
if (elem != null && !string.IsNullOrWhiteSpace(targetValue))
{
if (elem.Value.ValueKind == JsonValueKind.Null)
{
return null;
}
else if (elem.Value.ValueKind == JsonValueKind.Array)
{
var isInArray = elem.Value.EnumerateArray().Where(x => x.ValueKind != JsonValueKind.Null)
.Select(x => x.ToString())
.Any(x => x == targetValue);
return isInArray ? elem : null;
}
else if ((elem.Value.ValueKind == JsonValueKind.String && elem.Value.GetString() == targetValue)
|| (elem.Value.ValueKind != JsonValueKind.String && elem.Value.GetRawText() == targetValue))
{
return elem;
}
else
{
return null;
}
}
return elem;
}
#endregion
}

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using System.IO;
@ -122,6 +123,99 @@ namespace BotSharp.Core.Repository
}
#endregion
#region Instruction Log
public bool SaveInstructionLogs(IEnumerable<InstructionLogModel> logs)
{
if (logs.IsNullOrEmpty()) return false;
var baseDir = Path.Combine(_dbSettings.FileRepository, INSTRUCTION_LOG_FOLDER);
if (!Directory.Exists(baseDir))
{
Directory.CreateDirectory(baseDir);
}
foreach (var log in logs)
{
var file = Path.Combine(baseDir, $"{Guid.NewGuid()}.json");
log.InnerStates = BuildLogStates(log.States);
var text = JsonSerializer.Serialize(log, _options);
File.WriteAllText(file, text);
}
return true;
}
public PagedItems<InstructionLogModel> GetInstructionLogs(InstructLogFilter filter)
{
if (filter == null)
{
filter = InstructLogFilter.Empty();
}
var baseDir = Path.Combine(_dbSettings.FileRepository, INSTRUCTION_LOG_FOLDER);
if (!Directory.Exists(baseDir))
{
return new();
}
var logs = new List<InstructionLogModel>();
var files = Directory.GetFiles(baseDir);
foreach (var file in files)
{
var json = File.ReadAllText(file);
var log = JsonSerializer.Deserialize<InstructionLogModel>(json, _options);
if (log == null) continue;
var matched = true;
if (!filter.AgentIds.IsNullOrEmpty())
{
matched = matched && filter.AgentIds.Contains(log.AgentId);
}
if (!filter.Providers.IsNullOrEmpty())
{
matched = matched && filter.Providers.Contains(log.Provider);
}
if (!filter.Models.IsNullOrEmpty())
{
matched = matched && filter.Models.Contains(log.Model);
}
if (!filter.TemplateNames.IsNullOrEmpty())
{
matched = matched && filter.TemplateNames.Contains(log.TemplateName);
}
if (!matched) continue;
log.Id = Path.GetFileNameWithoutExtension(file);
logs.Add(log);
}
var records = logs.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size);
var agentIds = records.Where(x => !string.IsNullOrEmpty(x.AgentId)).Select(x => x.AgentId).ToList();
var agents = GetAgents(new AgentFilter
{
AgentIds = agentIds
});
records = records.Select(x =>
{
var states = x.InnerStates.ToDictionary(p => p.Key, p =>
{
var data = p.Value.RootElement.GetProperty("data");
return data.ValueKind != JsonValueKind.Null ? data.ToString() : null;
});
x.AgentName = !string.IsNullOrEmpty(x.AgentId) ? agents.FirstOrDefault(a => a.Id == x.AgentId)?.Name : null;
x.States = states ?? [];
return x;
}).ToList();
return new PagedItems<InstructionLogModel>
{
Items = records,
Count = logs.Count()
};
}
#endregion
#region Private methods
private int GetNextLogIndex(string logDir, string id)
{
@ -141,6 +235,28 @@ namespace BotSharp.Core.Repository
return logIndexes.IsNullOrEmpty() ? 0 : logIndexes.Max() + 1;
}
private Dictionary<string, JsonDocument> BuildLogStates(Dictionary<string, string> states)
{
var dic = new Dictionary<string, JsonDocument>();
foreach (var pair in states)
{
try
{
var jsonStr = JsonSerializer.Serialize(new { Data = JsonDocument.Parse(pair.Value) }, _options);
var json = JsonDocument.Parse(jsonStr);
dic[pair.Key] = json;
}
catch
{
var str = JsonSerializer.Serialize(new { Data = pair.Value }, _options);
var json = JsonDocument.Parse(str);
dic[pair.Key] = json;
}
}
return dic;
}
#endregion
}
}

View file

@ -6,7 +6,6 @@ using BotSharp.Abstraction.Users.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Tasks.Models;
namespace BotSharp.Core.Repository;
public partial class FileRepository : IBotSharpRepository
@ -34,6 +33,7 @@ public partial class FileRepository : IBotSharpRepository
private const string DIALOG_FILE = "dialogs.json";
private const string STATE_FILE = "state.json";
private const string BREAKPOINT_FILE = "breakpoint.json";
private const string CONV_LATEST_STATE_FILE = "latest-state.json";
private const string TRANSLATION_MEMORY_FILE = "memory.json";
private const string USERS_FOLDER = "users";
@ -54,6 +54,7 @@ public partial class FileRepository : IBotSharpRepository
private const string STATS_FILE = "stats.json";
private const string CRON_FILE = "cron.json";
private const string INSTRUCTION_LOG_FOLDER = "instruction-logs";
public FileRepository(
IServiceProvider services,

View file

@ -15,6 +15,7 @@ public static class BotSharpLoggerExtensions
services.AddScoped<IContentGeneratingHook, VerboseLogHook>();
services.AddScoped<IContentGeneratingHook, GlobalStatsConversationHook>();
services.AddScoped<IConversationHook, RateLimitConversationHook>();
services.AddScoped<IInstructHook, InstructionLogHook>();
return services;
}
}

View file

@ -0,0 +1,50 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Users;
namespace BotSharp.Logger.Hooks;
public class InstructionLogHook : InstructHookBase
{
private readonly IServiceProvider _services;
private readonly ILogger<InstructionLogHook> _logger;
private readonly IUserIdentity _user;
public override string SelfId => string.Empty;
public InstructionLogHook(
IServiceProvider services,
ILogger<InstructionLogHook> logger,
IUserIdentity user)
{
_services = services;
_logger = logger;
_user = user;
}
public override async Task OnResponseGenerated(InstructResponseModel response)
{
if (response == null) return;
var db = _services.GetRequiredService<IBotSharpRepository>();
var state = _services.GetRequiredService<IConversationStateService>();
var user = db.GetUserById(_user.Id);
db.SaveInstructionLogs(new List<InstructionLogModel>
{
new InstructionLogModel
{
AgentId = response.AgentId,
Provider = response.Provider,
Model = response.Model,
TemplateName = response.TemplateName,
UserMessage = response.UserMessage,
SystemInstruction = response.SystemInstruction,
CompletionText = response.CompletionText,
States = state.GetStates(),
UserId = user?.Id
}
});
return;
}
}

View file

@ -12,4 +12,5 @@ global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Conversations;
global using BotSharp.Abstraction.Repositories;
global using BotSharp.Abstraction.Conversations.Settings;
global using BotSharp.Abstraction.Instructs;
global using BotSharp.Logger.Hooks;

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>

View file

@ -52,8 +52,29 @@ public class InstructModeController : ControllerBase
.SetState("model", input.Model, source: StateSource.External)
.SetState("model_id", input.ModelId, source: StateSource.External);
var agentId = input.AgentId ?? Guid.Empty.ToString();
var textCompletion = CompletionProvider.GetTextCompletion(_services);
return await textCompletion.GetCompletion(input.Text, input.AgentId ?? Guid.Empty.ToString(), Guid.NewGuid().ToString());
var response = await textCompletion.GetCompletion(input.Text, agentId, Guid.NewGuid().ToString());
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = agentId,
Provider = textCompletion.Provider,
Model = textCompletion.Model,
TemplateName = input.Template,
UserMessage = input.Text,
CompletionText = response
});
}
return response;
}
#region Chat
@ -66,15 +87,36 @@ public class InstructModeController : ControllerBase
.SetState("model", input.Model, source: StateSource.External)
.SetState("model_id", input.ModelId, source: StateSource.External);
var agentId = input.AgentId ?? Guid.Empty.ToString();
var completion = CompletionProvider.GetChatCompletion(_services);
var message = await completion.GetChatCompletions(new Agent()
{
Id = input.AgentId ?? Guid.Empty.ToString(),
Id = agentId,
Instruction = input.Instruction
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, input.Text)
});
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.OnResponseGenerated(new InstructResponseModel
{
AgentId = agentId,
Provider = completion.Provider,
Model = completion.Model,
TemplateName = input.Template,
UserMessage = input.Text,
SystemInstruction = message.RenderedInstruction,
CompletionText = message.Content
});
}
return message.Content;
}
#endregion

View file

@ -1,4 +1,7 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Repositories;
using BotSharp.OpenAPI.ViewModels.Instructs;
using Microsoft.AspNetCore.Hosting;
namespace BotSharp.OpenAPI.Controllers;
@ -45,4 +48,16 @@ public class LoggerController : ControllerBase
var conversationService = _services.GetRequiredService<IConversationService>();
return await conversationService.GetConversationStateLogs(conversationId);
}
[HttpGet("/logger/instruction/log")]
public async Task<PagedItems<InstructionLogViewModel>> GetInstructionLogs([FromQuery] InstructLogFilter request)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var logs = db.GetInstructionLogs(request);
return new PagedItems<InstructionLogViewModel>
{
Items = logs.Items.Select(x => InstructionLogViewModel.From(x)),
Count = logs.Count
};
}
}

View file

@ -15,4 +15,5 @@ public class IncomingInstructRequest : IncomingMessageModel
{
public string? AgentId { get; set; }
public string? Instruction { get; set; }
public string? Template { get; set; }
}

View file

@ -0,0 +1,67 @@
using BotSharp.Abstraction.Loggers.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Instructs;
public class InstructionLogViewModel
{
[JsonPropertyName("id")]
public string Id { get; set; } = default!;
[JsonPropertyName("agent_id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? AgentId { get; set; }
[JsonPropertyName("agent_name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? AgentName { get; set; }
[JsonPropertyName("provider")]
public string Provider { get; set; } = default!;
[JsonPropertyName("model")]
public string Model { get; set; } = default!;
[JsonPropertyName("template_name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? TemplateName { get; set; }
[JsonPropertyName("user_message")]
public string UserMessage { get; set; } = string.Empty;
[JsonPropertyName("system_instruction")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? SystemInstruction { get; set; }
[JsonPropertyName("completion_text")]
public string CompletionText { get; set; } = string.Empty;
[JsonPropertyName("user_id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? UserId { get; set; }
[JsonPropertyName("states")]
public Dictionary<string, string> States { get; set; } = [];
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public static InstructionLogViewModel From(InstructionLogModel log)
{
return new InstructionLogViewModel
{
Id = log.Id,
AgentId = log.AgentId,
AgentName = log.AgentName,
Provider = log.Provider,
Model = log.Model,
TemplateName = log.TemplateName,
UserMessage = log.UserMessage,
SystemInstruction = log.SystemInstruction,
CompletionText = log.CompletionText,
UserId = log.UserId,
States = log.States,
CreatedTime = log.CreatedTime
};
}
}

View file

@ -9,10 +9,12 @@ namespace BotSharp.Plugin.AnthropicAI.Providers;
public class ChatCompletionProvider : IChatCompletion
{
public string Provider => "anthropic";
public string Model => _model;
protected readonly AnthropicSettings _settings;
protected readonly IServiceProvider _services;
protected readonly ILogger _logger;
private List<string> renderedInstructions = [];
protected string _model;
@ -57,6 +59,7 @@ public class ChatCompletionProvider : IChatCompletion
ToolCallId = toolResult.Id,
FunctionName = toolResult.Name,
FunctionArgs = JsonSerializer.Serialize(toolResult.Input),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
else
@ -66,6 +69,7 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -98,12 +102,15 @@ public class ChatCompletionProvider : IChatCompletion
private (string, MessageParameters) PrepareOptions(Agent agent, List<RoleDialogModel> conversations, LlmModelSetting settings)
{
var instruction = "";
renderedInstructions = [];
var agentService = _services.GetRequiredService<IAgentService>();
if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty())
{
instruction += agentService.RenderedInstruction(agent);
var text = agentService.RenderedInstruction(agent);
instruction += text;
renderedInstructions.Add(text);
}
/*var routing = _services.GetRequiredService<IRoutingService>();

View file

@ -15,6 +15,8 @@ public class NativeWhisperProvider : IAudioCompletion
private readonly ILogger<NativeWhisperProvider> _logger;
public string Provider => "native-whisper";
public string Model => _model;
private string _model;
public NativeWhisperProvider(
BotSharpDatabaseSettings dbSettings,
@ -56,11 +58,13 @@ public class NativeWhisperProvider : IAudioCompletion
{
if (Enum.TryParse(model, true, out GgmlType ggmlType))
{
_model = model;
LoadWhisperModel(ggmlType);
}
else
{
_logger.LogWarning($"Unsupported model type: {model}. Use Tiny model instead!");
_model = "Tiny";
LoadWhisperModel(GgmlType.Tiny);
}
}

View file

@ -5,6 +5,8 @@ public partial class AudioCompletionProvider : IAudioCompletion
private readonly IServiceProvider _services;
public string Provider => "openai";
public string Model => _model;
private string _model;
public AudioCompletionProvider(IServiceProvider service)

View file

@ -9,10 +9,12 @@ public class ChatCompletionProvider : IChatCompletion
protected readonly AzureOpenAiSettings _settings;
protected readonly IServiceProvider _services;
protected readonly ILogger<ChatCompletionProvider> _logger;
private List<string> renderedInstructions = [];
protected string _model;
public virtual string Provider => "azure-openai";
public string Model => _model;
public ChatCompletionProvider(
AzureOpenAiSettings settings,
@ -58,7 +60,8 @@ public class ChatCompletionProvider : IChatCompletion
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
FunctionName = toolCall?.FunctionName,
FunctionArgs = toolCall?.FunctionArguments?.ToString()
FunctionArgs = toolCall?.FunctionArguments?.ToString(),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// Somethings LLM will generate a function name with agent name.
@ -73,6 +76,7 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
}
@ -83,6 +87,7 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
catch (Exception ex)
@ -92,6 +97,7 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -136,7 +142,8 @@ public class ChatCompletionProvider : IChatCompletion
var msg = new RoleDialogModel(AgentRole.Assistant, text)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// After chat completion hook
@ -163,7 +170,8 @@ public class ChatCompletionProvider : IChatCompletion
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
ToolCallId = toolCall?.Id,
FunctionName = toolCall?.FunctionName,
FunctionArgs = toolCall?.FunctionArguments?.ToString()
FunctionArgs = toolCall?.FunctionArguments?.ToString(),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// Somethings LLM will generate a function name with agent name.
@ -199,7 +207,10 @@ public class ChatCompletionProvider : IChatCompletion
var update = choice.ToolCallUpdates?.FirstOrDefault()?.FunctionArgumentsUpdate?.ToString() ?? string.Empty;
Console.Write(update);
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update));
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update)
{
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
continue;
}
@ -207,7 +218,10 @@ public class ChatCompletionProvider : IChatCompletion
_logger.LogInformation(choice.ContentUpdate[0]?.Text);
await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty));
await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty)
{
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
}
return true;
@ -221,6 +235,7 @@ public class ChatCompletionProvider : IChatCompletion
var settingsService = _services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(Provider, _model);
var allowMultiModal = settings != null && settings.MultiModal;
renderedInstructions = [];
var messages = new List<ChatMessage>();
@ -251,6 +266,7 @@ public class ChatCompletionProvider : IChatCompletion
if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty())
{
var instruction = agentService.RenderedInstruction(agent);
renderedInstructions.Add(instruction);
messages.Add(new SystemChatMessage(instruction));
}

View file

@ -13,6 +13,7 @@ public class TextEmbeddingProvider : ITextEmbedding
protected int _dimension;
public virtual string Provider => "azure-openai";
public string Model => _model;
public TextEmbeddingProvider(
AzureOpenAiSettings settings,
@ -49,6 +50,7 @@ public class TextEmbeddingProvider : ITextEmbedding
_model = model;
}
public void SetDimension(int dimension)
{
_dimension = dimension > 0 ? dimension : DEFAULT_DIMENSION;

View file

@ -14,6 +14,7 @@ public partial class ImageCompletionProvider : IImageCompletion
protected string _model;
public virtual string Provider => "azure-openai";
public string Model => _model;
public ImageCompletionProvider(
AzureOpenAiSettings settings,

View file

@ -22,6 +22,7 @@ public class TextCompletionProvider : ITextCompletion
};
public virtual string Provider => "azure-openai";
public string Model => _model;
public TextCompletionProvider(
AzureOpenAiSettings settings,

View file

@ -9,9 +9,11 @@ public class ChatCompletionProvider : IChatCompletion
{
protected readonly IServiceProvider _services;
protected readonly ILogger<ChatCompletionProvider> _logger;
private List<string> renderedInstructions = [];
protected string _model;
public virtual string Provider => "deepseek-ai";
public string Model => _model;
public ChatCompletionProvider(
IServiceProvider services,
@ -51,7 +53,8 @@ public class ChatCompletionProvider : IChatCompletion
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
ToolCallId = toolCall?.Id,
FunctionName = toolCall?.FunctionName,
FunctionArgs = toolCall?.FunctionArguments?.ToString()
FunctionArgs = toolCall?.FunctionArguments?.ToString(),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// Somethings LLM will generate a function name with agent name.
@ -66,6 +69,7 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -107,7 +111,8 @@ public class ChatCompletionProvider : IChatCompletion
var msg = new RoleDialogModel(AgentRole.Assistant, text)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// After chat completion hook
@ -134,7 +139,8 @@ public class ChatCompletionProvider : IChatCompletion
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
ToolCallId = toolCall?.Id,
FunctionName = toolCall?.FunctionName,
FunctionArgs = toolCall?.FunctionArguments?.ToString()
FunctionArgs = toolCall?.FunctionArguments?.ToString(),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// Somethings LLM will generate a function name with agent name.
@ -170,7 +176,10 @@ public class ChatCompletionProvider : IChatCompletion
var update = choice.ToolCallUpdates?.FirstOrDefault()?.FunctionArgumentsUpdate?.ToString() ?? string.Empty;
_logger.LogInformation(update);
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update));
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update)
{
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
continue;
}
@ -178,7 +187,10 @@ public class ChatCompletionProvider : IChatCompletion
_logger.LogInformation(choice.ContentUpdate[0]?.Text);
await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty));
await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty)
{
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
}
return true;
@ -197,6 +209,7 @@ public class ChatCompletionProvider : IChatCompletion
var settingsService = _services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(Provider, _model);
var allowMultiModal = settings != null && settings.MultiModal;
renderedInstructions = [];
var messages = new List<ChatMessage>();
@ -226,6 +239,7 @@ public class ChatCompletionProvider : IChatCompletion
if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty())
{
var text = agentService.RenderedInstruction(agent);
renderedInstructions.Add(text);
messages.Add(new SystemChatMessage(text));
}

View file

@ -10,6 +10,7 @@ public class TextCompletionProvider : ITextCompletion
protected string _model;
public string Provider => "deepseek-ai";
public string Model => _model;
public TextCompletionProvider(
IServiceProvider services,

View file

@ -2,7 +2,6 @@ using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Agents.Enums;
using BotSharp.Abstraction.Conversations;
using BotSharp.Abstraction.Loggers;
using Google.Protobuf.WellKnownTypes;
using Microsoft.Extensions.Logging;
using Mscc.GenerativeAI;
@ -12,10 +11,12 @@ public class GeminiChatCompletionProvider : IChatCompletion
{
private readonly IServiceProvider _services;
private readonly ILogger<GeminiChatCompletionProvider> _logger;
private List<string> renderedInstructions = [];
private string _model;
public string Provider => "google-ai";
public string Model => _model;
public GeminiChatCompletionProvider(
IServiceProvider services,
@ -53,7 +54,8 @@ public class GeminiChatCompletionProvider : IChatCompletion
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
ToolCallId = part.FunctionCall.Name,
FunctionName = part.FunctionCall.Name,
FunctionArgs = part.FunctionCall.Args?.ToString()
FunctionArgs = part.FunctionCall.Args?.ToString(),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
else
@ -62,6 +64,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -98,6 +101,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
{
var agentService = _services.GetRequiredService<IAgentService>();
var googleSettings = _services.GetRequiredService<GoogleAiSettings>();
renderedInstructions = [];
// Add settings
aiModel.UseGoogleSearch = googleSettings.Gemini.UseGoogleSearch;
@ -117,6 +121,7 @@ public class GeminiChatCompletionProvider : IChatCompletion
Role = AgentRole.User
});
renderedInstructions.Add(instruction);
systemPrompts.Add(instruction);
}

View file

@ -13,10 +13,12 @@ public class PalmChatCompletionProvider : IChatCompletion
{
private readonly IServiceProvider _services;
private readonly ILogger<PalmChatCompletionProvider> _logger;
private List<string> renderedInstructions = [];
private string _model;
public string Provider => "google-palm";
public string Model => _model;
public PalmChatCompletionProvider(
IServiceProvider services,
@ -61,7 +63,8 @@ public class PalmChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
FunctionName = llmResponse.FunctionName,
FunctionArgs = JsonSerializer.Serialize(llmResponse.Args)
FunctionArgs = JsonSerializer.Serialize(llmResponse.Args),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
else
@ -75,7 +78,8 @@ public class PalmChatCompletionProvider : IChatCompletion
msg = new RoleDialogModel(llmResponse.Role, llmResponse.Content ?? message.Content)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}

View file

@ -14,6 +14,7 @@ public class GeminiTextCompletionProvider : ITextCompletion
private string _model;
public string Provider => "google-ai";
public string Model => _model;
public GeminiTextCompletionProvider(
IServiceProvider services,
@ -74,7 +75,6 @@ public class GeminiTextCompletionProvider : ITextCompletion
_model = model;
}
private void PrepareOptions(GenerativeModel aiModel)
{
var settings = _services.GetRequiredService<GoogleAiSettings>();

View file

@ -14,6 +14,7 @@ public class PalmTextCompletionProvider : ITextCompletion
private string _model;
public string Provider => "google-palm";
public string Model => _model;
public PalmTextCompletionProvider(
IServiceProvider services,

View file

@ -10,10 +10,12 @@ namespace BotSharp.Plugin.HuggingFace.Providers;
public class ChatCompletionProvider : IChatCompletion
{
public string Provider => "huggingface";
public string Model => _model;
private readonly IServiceProvider _services;
private readonly HuggingFaceSettings _settings;
private readonly ILogger _logger;
private List<string> renderedInstructions = [];
private string _model;
public ChatCompletionProvider(IServiceProvider services,

View file

@ -8,6 +8,7 @@ public class ChatCompletionProvider : IChatCompletion
private readonly IServiceProvider _services;
private readonly ILogger _logger;
private readonly LlamaSharpSettings _settings;
private List<string> renderedInstructions = [];
private string _model;
public ChatCompletionProvider(IServiceProvider services,
@ -20,6 +21,7 @@ public class ChatCompletionProvider : IChatCompletion
}
public string Provider => "llama-sharp";
public string Model => _model;
public async Task<RoleDialogModel> GetChatCompletions(Agent agent, List<RoleDialogModel> conversations)
{
@ -64,7 +66,8 @@ public class ChatCompletionProvider : IChatCompletion
var msg = new RoleDialogModel(AgentRole.Assistant, totalResponse)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = instruction
};
// After chat completion hook
@ -146,7 +149,8 @@ public class ChatCompletionProvider : IChatCompletion
var msg = new RoleDialogModel(AgentRole.Assistant, totalResponse)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = agent.Instruction
};
// Text response received

View file

@ -10,6 +10,7 @@ public class TextCompletionProvider : ITextCompletion
private readonly ITokenStatistics _tokenStatistics;
private string _model;
public string Provider => "llama-sharp";
public string Model => _model;
public TextCompletionProvider(IServiceProvider services,
ILogger<TextCompletionProvider> logger,

View file

@ -1,6 +1,4 @@
using System.IO;
using System.Xml.Linq;
using static System.Net.Mime.MediaTypeNames;
namespace BotSharp.Plugin.LLamaSharp.Providers;
@ -14,6 +12,7 @@ public class TextEmbeddingProvider : ITextEmbedding
protected int _dimension = DEFAULT_DIMENSION;
public string Provider => "llama-sharp";
public string Model => string.Empty;
public TextEmbeddingProvider(IServiceProvider services, LlamaSharpSettings settings)
{

View file

@ -20,6 +20,8 @@ namespace BotSharp.Plugin.VertexAI.Providers
IServiceProvider services) : IChatCompletion
{
public string Provider => "vertexai";
public string Model => _model;
private readonly VertexAIConfiguration _config = config;
private readonly ChatSettings? _settings = settings;
private readonly IServiceProvider _services = services;

View file

@ -19,6 +19,8 @@ namespace BotSharp.Plugin.VertexAI.Providers
IServiceProvider services) : ITextCompletion
{
public string Provider => "vertexai";
public string Model => _model;
private readonly VertexAIConfiguration _config = config;
private readonly ChatSettings? _settings = settings;
private readonly IServiceProvider _services = services;

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Models;
using BotSharp.Plugin.MetaAI.Settings;
using FastText.NetWrapper;
using Microsoft.Extensions.DependencyInjection;
@ -17,6 +18,7 @@ public class fastTextEmbeddingProvider : ITextEmbedding
private int _dimension;
public string Provider => "meta-ai";
public string Model => string.Empty;
public fastTextEmbeddingProvider(IServiceProvider services)
{

View file

@ -5,11 +5,13 @@ namespace BotSharp.Plugin.MetaGLM.Providers;
public class ChatCompletionProvider : IChatCompletion
{
public string Provider => "metaglm";
public string Model => _model;
private readonly MetaGLMSettings _settings;
private readonly IServiceProvider _services;
private readonly ILogger _logger;
private readonly MetaGLMClient metaGLMClient;
private List<string> renderedInstructions = [];
private string _model;
public ChatCompletionProvider(IServiceProvider services,
@ -49,7 +51,8 @@ public class ChatCompletionProvider : IChatCompletion
responseMessage = new RoleDialogModel(AgentRole.Assistant, response?.choices.FirstOrDefault()?.message.content)
{
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId
MessageId = conversations.Last().MessageId,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -61,7 +64,8 @@ public class ChatCompletionProvider : IChatCompletion
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId,
FunctionName = toolcall.function.name,
FunctionArgs = toolcall.function.arguments
FunctionArgs = toolcall.function.arguments,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -87,10 +91,12 @@ public class ChatCompletionProvider : IChatCompletion
var agentService = _services.GetRequiredService<IAgentService>();
List<MessageItem> messages = new List<MessageItem>();
List<FunctionTool> toolcalls = new List<FunctionTool>();
renderedInstructions = [];
if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty())
{
var instruction = agentService.RenderedInstruction(agent);
renderedInstructions.Add(instruction);
messages.Add(new MessageItem("system", instruction));
}

View file

@ -27,6 +27,7 @@ public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletio
private readonly IChatClient _client;
private readonly ILogger<MicrosoftExtensionsAIChatCompletionProvider> _logger;
private readonly IServiceProvider _services;
private List<string> renderedInstructions = [];
private string? _model;
/// <summary>
@ -45,6 +46,7 @@ public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletio
/// <inheritdoc/>
public string Provider => "microsoft.extensions.ai";
public string Model => _model;
/// <inheritdoc/>
public void SetModelName(string model) => _model = model;
@ -54,6 +56,7 @@ public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletio
{
// Before chat completion hook
var hooks = _services.GetServices<IContentGeneratingHook>().ToArray();
renderedInstructions = [];
await Task.WhenAll(hooks.Select(hook => hook.BeforeGenerating(agent, conversations)));
// Configure options
@ -82,6 +85,7 @@ public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletio
if (_services.GetRequiredService<IAgentService>().RenderedInstruction(agent) is string instruction &&
instruction.Length > 0)
{
renderedInstructions.Add(instruction);
messages.Add(new(ChatRole.System, instruction));
}
@ -143,7 +147,8 @@ public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletio
RoleDialogModel result = new(AgentRole.Assistant, string.Concat(completion.Message.Contents.OfType<TextContent>()))
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
if (completion.Message.Contents.OfType<FunctionCallContent>().FirstOrDefault() is { } fcc)

View file

@ -24,6 +24,7 @@ public sealed class MicrosoftExtensionsAITextCompletionProvider : ITextCompletio
/// <inheritdoc/>
public string Provider => "microsoft-extensions-ai";
public string Model => _model;
/// <summary>
/// Creates an instance of the <see cref="MicrosoftExtensionsAITextCompletionProvider"/> class.

View file

@ -23,6 +23,7 @@ public sealed class MicrosoftExtensionsAITextEmbeddingProvider : ITextEmbedding
/// <inheritdoc/>
public string Provider => "microsoft-extensions-ai";
public string Model => _model;
/// <inheritdoc/>
public async Task<float[]> GetVectorAsync(string text) =>

View file

@ -14,4 +14,5 @@ public class ConversationDocument : MongoBase
public List<string> Tags { get; set; } = [];
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public Dictionary<string, BsonDocument> LatestStates { get; set; } = new();
}

View file

@ -0,0 +1,50 @@
using BotSharp.Abstraction.Loggers.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class InstructionLogBetaDocument : MongoBase
{
public string? AgentId { get; set; }
public string Provider { get; set; } = default!;
public string Model { get; set; } = default!;
public string? TemplateName { get; set; }
public string UserMessage { get; set; } = default!;
public string? SystemInstruction { get; set; }
public string CompletionText { get; set; } = default!;
public string? UserId { get; set; }
public Dictionary<string, BsonDocument> States { get; set; } = new();
public DateTime CreatedTime { get; set; }
public static InstructionLogBetaDocument ToMongoModel(InstructionLogModel log)
{
return new InstructionLogBetaDocument
{
AgentId = log.AgentId,
Provider = log.Provider,
Model = log.Model,
TemplateName = log.TemplateName,
UserMessage = log.UserMessage,
SystemInstruction = log.SystemInstruction,
CompletionText = log.CompletionText,
UserId = log.UserId,
CreatedTime = log.CreatedTime
};
}
public static InstructionLogModel ToDomainModel(InstructionLogBetaDocument log)
{
return new InstructionLogModel
{
Id = log.Id,
AgentId = log.AgentId,
Provider = log.Provider,
Model = log.Model,
TemplateName = log.TemplateName,
UserMessage = log.UserMessage,
SystemInstruction = log.SystemInstruction,
CompletionText = log.CompletionText,
UserId = log.UserId,
CreatedTime = log.CreatedTime
};
}
}

View file

@ -193,4 +193,6 @@ public class MongoDbContext
public IMongoCollection<GlobalStatisticsDocument> GlobalStatistics
=> GetCollectionOrCreate<GlobalStatisticsDocument>("GlobalStatistics");
public IMongoCollection<InstructionLogBetaDocument> InstructionLogs
=> GetCollectionOrCreate<InstructionLogBetaDocument>("InstructionLogs");
}

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Repositories.Filters;
using System.Text.Json;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -23,7 +24,8 @@ public partial class MongoRepository
Status = conversation.Status,
Tags = conversation.Tags ?? new(),
CreatedTime = utcNow,
UpdatedTime = utcNow
UpdatedTime = utcNow,
LatestStates = []
};
var dialogDoc = new ConversationDialogDocument
@ -72,8 +74,9 @@ public partial class MongoRepository
var cronDeleted = _dc.CrontabItems.DeleteMany(conbTabItems);
var convDeleted = _dc.Conversations.DeleteMany(filterConv);
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0 || promptLogDeleted.DeletedCount > 0
|| contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0 || convDeleted.DeletedCount > 0;
return convDeleted.DeletedCount > 0 || dialogDeleted.DeletedCount > 0 || statesDeleted.DeletedCount > 0
|| promptLogDeleted.DeletedCount > 0 || contentLogDeleted.DeletedCount > 0
|| stateLogDeleted.DeletedCount > 0 || convDeleted.DeletedCount > 0;
}
[SideCar]
@ -266,6 +269,14 @@ public partial class MongoRepository
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationStates.UpdateOne(filterStates, updateStates);
// Update latest states
var endNodes = BuildLatestStates(saveStates);
var filter = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var update = Builders<ConversationDocument>.Update.Set(x => x.LatestStates, endNodes)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Conversations.UpdateOne(filter, update);
}
public void UpdateConversationStatus(string conversationId, string status)
@ -371,21 +382,47 @@ public partial class MongoRepository
}
// Filter states
var stateFilters = new List<FilterDefinition<ConversationStateDocument>>();
if (filter != null && string.IsNullOrEmpty(filter.Id) && !filter.States.IsNullOrEmpty())
{
foreach (var pair in filter.States)
{
var elementFilters = new List<FilterDefinition<StateMongoElement>> { Builders<StateMongoElement>.Filter.Eq(x => x.Key, pair.Key) };
if (!string.IsNullOrEmpty(pair.Value))
{
elementFilters.Add(Builders<StateMongoElement>.Filter.Eq("Values.Data", pair.Value));
}
stateFilters.Add(Builders<ConversationStateDocument>.Filter.ElemMatch(x => x.States, Builders<StateMongoElement>.Filter.And(elementFilters)));
}
if (string.IsNullOrWhiteSpace(pair.Key)) continue;
var targetConvIds = _dc.ConversationStates.Find(Builders<ConversationStateDocument>.Filter.And(stateFilters)).ToEnumerable().Select(x => x.ConversationId).Distinct().ToList();
convFilters.Add(convBuilder.In(x => x.Id, targetConvIds));
// Format key
var keys = pair.Key.Split(".").ToList();
keys.Insert(1, "data");
keys.Insert(0, "LatestStates");
var formattedKey = string.Join(".", keys);
if (string.IsNullOrWhiteSpace(pair.Value))
{
convFilters.Add(convBuilder.Exists(formattedKey));
}
else if (bool.TryParse(pair.Value, out var boolValue))
{
convFilters.Add(convBuilder.Eq(formattedKey, boolValue));
}
else if (int.TryParse(pair.Value, out var intValue))
{
convFilters.Add(convBuilder.Eq(formattedKey, intValue));
}
else if (decimal.TryParse(pair.Value, out var decimalValue))
{
convFilters.Add(convBuilder.Eq(formattedKey, decimalValue));
}
else if (float.TryParse(pair.Value, out var floatValue))
{
convFilters.Add(convBuilder.Eq(formattedKey, floatValue));
}
else if (double.TryParse(pair.Value, out var doubleValue))
{
convFilters.Add(convBuilder.Eq(formattedKey, doubleValue));
}
else
{
convFilters.Add(convBuilder.Eq(formattedKey, pair.Value));
}
}
}
// Sort and paginate
@ -527,6 +564,7 @@ public partial class MongoRepository
var stateFilter = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundStates = _dc.ConversationStates.Find(stateFilter).FirstOrDefault();
var endNodes = new Dictionary<string, BsonDocument>();
if (foundStates != null)
{
// Truncate states
@ -550,6 +588,7 @@ public partial class MongoRepository
truncatedStates.Add(state);
}
foundStates.States = truncatedStates;
endNodes = BuildLatestStates(truncatedStates);
}
// Truncate breakpoints
@ -573,6 +612,7 @@ public partial class MongoRepository
// Update conversation
var convFilter = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow)
.Set(x => x.LatestStates, endNodes)
.Set(x => x.DialogCount, truncatedDialogs.Count);
_dc.Conversations.UpdateOne(convFilter, updateConv);
@ -621,8 +661,6 @@ public partial class MongoRepository
return keys;
}
private string ConvertSnakeCaseToPascalCase(string snakeCase)
{
string[] words = snakeCase.Split('_');
@ -640,4 +678,29 @@ public partial class MongoRepository
return pascalCase.ToString();
}
private Dictionary<string, BsonDocument> BuildLatestStates(List<StateMongoElement> states)
{
var endNodes = new Dictionary<string, BsonDocument>();
foreach (var pair in states)
{
var value = pair.Values?.LastOrDefault();
if (value == null || !value.Active) continue;
try
{
var jsonStr = JsonSerializer.Serialize(new { Data = JsonDocument.Parse(value.Data) }, _botSharpOptions.JsonSerializerOptions);
var json = BsonDocument.Parse(jsonStr);
endNodes[pair.Key] = json;
}
catch
{
var str = JsonSerializer.Serialize(new { Data = value.Data }, _botSharpOptions.JsonSerializerOptions);
var json = BsonDocument.Parse(str);
endNodes[pair.Key] = json;
}
}
return endNodes;
}
}

View file

@ -1,4 +1,7 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Repositories.Filters;
using System.Text.Json;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -110,4 +113,96 @@ public partial class MongoRepository
return logs;
}
#endregion
#region Instruction Log
public bool SaveInstructionLogs(IEnumerable<InstructionLogModel> logs)
{
if (logs.IsNullOrEmpty()) return false;
var docs = new List<InstructionLogBetaDocument>();
foreach (var log in logs)
{
var doc = InstructionLogBetaDocument.ToMongoModel(log);
foreach (var pair in log.States)
{
try
{
var jsonStr = JsonSerializer.Serialize(new { Data = JsonDocument.Parse(pair.Value) }, _botSharpOptions.JsonSerializerOptions);
var json = BsonDocument.Parse(jsonStr);
doc.States[pair.Key] = json;
}
catch
{
var jsonStr = JsonSerializer.Serialize(new { Data = pair.Value }, _botSharpOptions.JsonSerializerOptions);
var json = BsonDocument.Parse(jsonStr);
doc.States[pair.Key] = json;
}
}
docs.Add(doc);
}
_dc.InstructionLogs.InsertMany(docs);
return true;
}
public PagedItems<InstructionLogModel> GetInstructionLogs(InstructLogFilter filter)
{
if (filter == null)
{
filter = InstructLogFilter.Empty();
}
var builder = Builders<InstructionLogBetaDocument>.Filter;
var filters = new List<FilterDefinition<InstructionLogBetaDocument>>() { builder.Empty };
// Filter logs
if (!filter.AgentIds.IsNullOrEmpty())
{
filters.Add(builder.In(x => x.AgentId, filter.AgentIds));
}
if (!filter.Providers.IsNullOrEmpty())
{
filters.Add(builder.In(x => x.Provider, filter.Providers));
}
if (!filter.Models.IsNullOrEmpty())
{
filters.Add(builder.In(x => x.Model, filter.Models));
}
if (!filter.TemplateNames.IsNullOrEmpty())
{
filters.Add(builder.In(x => x.TemplateName, filter.TemplateNames));
}
var filterDef = builder.And(filters);
var sortDef = Builders<InstructionLogBetaDocument>.Sort.Descending(x => x.CreatedTime);
var docs = _dc.InstructionLogs.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList();
var count = _dc.InstructionLogs.CountDocuments(filterDef);
var agentIds = docs.Where(x => !string.IsNullOrEmpty(x.AgentId)).Select(x => x.AgentId).ToList();
var agents = GetAgents(new AgentFilter
{
AgentIds = agentIds
});
var logs = docs.Select(x =>
{
var log = InstructionLogBetaDocument.ToDomainModel(x);
log.AgentName = !string.IsNullOrEmpty(x.AgentId) ? agents.FirstOrDefault(a => a.Id == x.AgentId)?.Name : null;
log.States = x.States.ToDictionary(p => p.Key, p =>
{
var jsonStr = p.Value.ToJson();
var jsonDoc = JsonDocument.Parse(jsonStr);
var data = jsonDoc.RootElement.GetProperty("data");
return data.ValueKind != JsonValueKind.Null ? data.ToString() : null;
});
return log;
}).ToList();
return new PagedItems<InstructionLogModel>
{
Items = logs,
Count = (int)count
};
}
#endregion
}

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Options;
using Microsoft.Extensions.Logging;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -7,16 +8,19 @@ public partial class MongoRepository : IBotSharpRepository
private readonly MongoDbContext _dc;
private readonly IServiceProvider _services;
private readonly ILogger<MongoRepository> _logger;
private readonly BotSharpOptions _botSharpOptions;
private UpdateOptions _options;
public MongoRepository(
MongoDbContext dc,
IServiceProvider services,
ILogger<MongoRepository> logger)
ILogger<MongoRepository> logger,
BotSharpOptions botSharpOptions)
{
_dc = dc;
_services = services;
_logger = logger;
_botSharpOptions = botSharpOptions;
_options = new UpdateOptions
{
IsUpsert = true,

View file

@ -7,6 +7,8 @@ public partial class AudioCompletionProvider : IAudioCompletion
private readonly IServiceProvider _services;
public string Provider => "openai";
public string Model => _model;
private string _model;
public AudioCompletionProvider(IServiceProvider service)

View file

@ -10,8 +10,10 @@ public class ChatCompletionProvider : IChatCompletion
protected readonly ILogger<ChatCompletionProvider> _logger;
protected string _model;
private List<string> renderedInstructions = [];
public virtual string Provider => "openai";
public string Model => _model;
public ChatCompletionProvider(
OpenAiSettings settings,
@ -53,7 +55,8 @@ public class ChatCompletionProvider : IChatCompletion
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
ToolCallId = toolCall?.Id,
FunctionName = toolCall?.FunctionName,
FunctionArgs = toolCall?.FunctionArguments?.ToString()
FunctionArgs = toolCall?.FunctionArguments?.ToString(),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// Somethings LLM will generate a function name with agent name.
@ -68,6 +71,7 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -112,7 +116,8 @@ public class ChatCompletionProvider : IChatCompletion
var msg = new RoleDialogModel(AgentRole.Assistant, text)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// After chat completion hook
@ -139,7 +144,8 @@ public class ChatCompletionProvider : IChatCompletion
MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty,
ToolCallId = toolCall?.Id,
FunctionName = toolCall?.FunctionName,
FunctionArgs = toolCall?.FunctionArguments?.ToString()
FunctionArgs = toolCall?.FunctionArguments?.ToString(),
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// Somethings LLM will generate a function name with agent name.
@ -175,7 +181,10 @@ public class ChatCompletionProvider : IChatCompletion
var update = choice.ToolCallUpdates?.FirstOrDefault()?.FunctionArgumentsUpdate?.ToString() ?? string.Empty;
_logger.LogInformation(update);
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update));
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update)
{
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
continue;
}
@ -183,7 +192,10 @@ public class ChatCompletionProvider : IChatCompletion
_logger.LogInformation(choice.ContentUpdate[0]?.Text);
await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty));
await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty)
{
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
}
return true;
@ -198,6 +210,7 @@ public class ChatCompletionProvider : IChatCompletion
var settingsService = _services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(Provider, _model);
var allowMultiModal = settings != null && settings.MultiModal;
renderedInstructions = [];
var messages = new List<ChatMessage>();
@ -227,6 +240,7 @@ public class ChatCompletionProvider : IChatCompletion
if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty())
{
var text = agentService.RenderedInstruction(agent);
renderedInstructions.Add(text);
messages.Add(new SystemChatMessage(text));
}

View file

@ -13,6 +13,7 @@ public class TextEmbeddingProvider : ITextEmbedding
protected int _dimension = DEFAULT_DIMENSION;
public virtual string Provider => "openai";
public string Model => _model;
public TextEmbeddingProvider(
OpenAiSettings settings,

View file

@ -14,6 +14,7 @@ public partial class ImageCompletionProvider : IImageCompletion
protected string _model;
public virtual string Provider => "openai";
public string Model => _model;
public ImageCompletionProvider(
OpenAiSettings settings,

View file

@ -15,6 +15,7 @@ public class TextCompletionProvider : ITextCompletion
protected string _model;
public virtual string Provider => "openai";
public string Model => _model;
public TextCompletionProvider(
OpenAiSettings settings,

View file

@ -26,6 +26,7 @@ namespace BotSharp.Plugin.SemanticKernel
/// <inheritdoc/>
public string Provider => "semantic-kernel";
public string Model => _model;
/// <summary>
/// Create a new instance of <see cref="SemanticKernelChatCompletionProvider"/>
@ -74,7 +75,8 @@ namespace BotSharp.Plugin.SemanticKernel
var response = chatMessageContent != null ? chatMessageContent.Content :string.Empty;
var msg = new RoleDialogModel(AgentRole.Assistant, response)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = instruction
};
// After chat completion hook

View file

@ -23,6 +23,7 @@ namespace BotSharp.Plugin.SemanticKernel
/// <inheritdoc/>
public string Provider => "semantic-kernel";
public string Model => _model;
/// <summary>
/// Create a new instance of <see cref="SemanticKernelTextCompletionProvider"/>

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel.Embeddings;
using System.Collections.Generic;
@ -33,6 +34,7 @@ namespace BotSharp.Plugin.SemanticKernel
protected int _dimension;
public string Provider => "semantic-kernel";
public string Model => string.Empty;
/// <inheritdoc/>
public async Task<float[]> GetVectorAsync(string text)

View file

@ -7,10 +7,12 @@ namespace BotSharp.Plugin.SparkDesk.Providers;
public class ChatCompletionProvider : IChatCompletion
{
public string Provider => "sparkdesk";
public string Model => _model;
private readonly SparkDeskSettings _settings;
private readonly IServiceProvider _services;
private readonly ILogger _logger;
private List<string> renderedInstructions = [];
private string _model;
public ChatCompletionProvider(IServiceProvider services,
@ -42,7 +44,8 @@ public class ChatCompletionProvider : IChatCompletion
var responseMessage = new RoleDialogModel(AgentRole.Assistant, response.Text)
{
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId
MessageId = conversations.Last().MessageId,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
if (response.FunctionCall != null)
@ -52,7 +55,8 @@ public class ChatCompletionProvider : IChatCompletion
CurrentAgentId = agent.Id,
MessageId = conversations.Last().MessageId,
FunctionName = response.FunctionCall.Name,
FunctionArgs = response.FunctionCall.Arguments
FunctionArgs = response.FunctionCall.Arguments,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
@ -92,7 +96,8 @@ public class ChatCompletionProvider : IChatCompletion
var msg = new RoleDialogModel(AgentRole.Assistant, response.Text)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// After chat completion hook
@ -116,7 +121,8 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
FunctionName = response.FunctionCall.Name,
FunctionArgs = response.FunctionCall.Arguments
FunctionArgs = response.FunctionCall.Arguments,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
// Somethings LLM will generate a function name with agent name.
@ -150,14 +156,16 @@ public class ChatCompletionProvider : IChatCompletion
{
CurrentAgentId = agent.Id,
FunctionName = response.FunctionCall.Name,
FunctionArgs = response.FunctionCall.Arguments
FunctionArgs = response.FunctionCall.Arguments,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
continue;
}
await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, response.Text)
{
CurrentAgentId = agent.Id
CurrentAgentId = agent.Id,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
});
}
@ -175,10 +183,12 @@ public class ChatCompletionProvider : IChatCompletion
var functions = new List<FunctionDef>();
var agentService = _services.GetRequiredService<IAgentService>();
var messages = new List<ChatMessage>();
renderedInstructions = [];
if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty())
{
var instruction = agentService.RenderedInstruction(agent);
renderedInstructions.Add(instruction);
messages.Add(ChatMessage.FromSystem(instruction));
}
if (!string.IsNullOrEmpty(agent.Knowledges))