Merge branch 'master' of https://github.com/hchen2020/BotSharp
This commit is contained in:
commit
2d710d4f50
|
|
@ -6,6 +6,7 @@ public class WebBrowsingSettings
|
|||
public bool Headless { get; set; }
|
||||
// Default timeout in milliseconds
|
||||
public float DefaultTimeout { get; set; } = 30000;
|
||||
public float DefaultNavigationTimeout { get; set; } = 30000;
|
||||
public bool IsEnableScreenshot { get; set; }
|
||||
// Default wait time in seconds after page is opened
|
||||
public int DefaultWaitTime { get; set; } = 5;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public interface IConversationService
|
|||
string ConversationId { get; }
|
||||
Task<Conversation> NewConversation(Conversation conversation);
|
||||
void SetConversationId(string conversationId, List<MessageState> states, bool isReadOnly = false);
|
||||
Task<Conversation> GetConversation(string id);
|
||||
Task<Conversation> GetConversation(string id, bool isLoadStates = false);
|
||||
Task<PagedItems<Conversation>> GetConversations(ConversationFilter filter);
|
||||
Task<Conversation> UpdateConversationTitle(string id, string title);
|
||||
Task<Conversation> UpdateConversationTitleAlias(string id, string titleAlias);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ public class TokenStatsModel
|
|||
public string Model { get; set; }
|
||||
public string Prompt { get; set; }
|
||||
public int PromptCount { get; set; }
|
||||
public int CachedPromptCount { get; set; }
|
||||
public int CompletionCount { get; set; }
|
||||
public AgentLlmConfig LlmConfig { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ public class FunctionDef
|
|||
[JsonPropertyName("parameters")]
|
||||
public FunctionParametersDef Parameters { get; set; } = new FunctionParametersDef();
|
||||
|
||||
[JsonPropertyName("output")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Output { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}: {Description}";
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@ public class InstructHookBase : IInstructHook
|
|||
|
||||
public virtual async Task BeforeCompletion(Agent agent, RoleDialogModel message)
|
||||
{
|
||||
return;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task AfterCompletion(Agent agent, InstructResult result)
|
||||
{
|
||||
return;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task OnResponseGenerated(InstructResponseModel response)
|
||||
{
|
||||
return;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Instructs.Models;
|
||||
|
||||
public class ExecuteTemplateArgs
|
||||
{
|
||||
[JsonPropertyName("template_name")]
|
||||
public string? TemplateName { get; set; }
|
||||
}
|
||||
|
|
@ -2,5 +2,11 @@ namespace BotSharp.Abstraction.Instructs.Settings;
|
|||
|
||||
public class InstructionSettings
|
||||
{
|
||||
public bool EnableLog { get; set; }
|
||||
public InstructionLogSetting Logging { get; set; } = new();
|
||||
}
|
||||
|
||||
public class InstructionLogSetting
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public List<string> ExcludedAgentIds { get; set; } = [];
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ namespace BotSharp.Abstraction.Loggers.Models;
|
|||
public class InstructionLogModel
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Id { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName("agent_id")]
|
||||
|
|
|
|||
|
|
@ -62,12 +62,22 @@ public class LlmModelSetting
|
|||
/// </summary>
|
||||
public int Dimension { get; set; }
|
||||
|
||||
public LlmCost AdditionalCost { get; set; } = new();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Type}] {Name} {Endpoint}";
|
||||
}
|
||||
}
|
||||
|
||||
public class LlmCost
|
||||
{
|
||||
public float CachedPromptCost { get; set; } = 0f;
|
||||
public float AudioPromptCost { get; set; } = 0f;
|
||||
public float ReasoningCompletionCost { get; } = 0f;
|
||||
public float AudioCompletionCost { get; } = 0f;
|
||||
}
|
||||
|
||||
public enum LlmModelType
|
||||
{
|
||||
Text = 1,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ public class ConversationFilter
|
|||
|
||||
public List<string>? Tags { get; set; }
|
||||
|
||||
public bool IsLoadLatestStates { get; set; }
|
||||
|
||||
public static ConversationFilter Empty()
|
||||
{
|
||||
return new ConversationFilter();
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
=> throw new NotImplementedException();
|
||||
void UpdateConversationStatus(string conversationId, string status)
|
||||
=> throw new NotImplementedException();
|
||||
Conversation GetConversation(string conversationId)
|
||||
Conversation GetConversation(string conversationId, bool isLoadStates = false)
|
||||
=> throw new NotImplementedException();
|
||||
PagedItems<Conversation> GetConversations(ConversationFilter filter)
|
||||
=> throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.metrics.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.reviewer.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.simulator.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.simulator.liquid" />
|
||||
<None Remove="data\plugins\config.json" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -191,6 +192,12 @@
|
|||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.metrics.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-instruct-execute_template.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-instruct-execute_template.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\plugins\config.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public partial class ConversationService : IConversationService
|
|||
return db.UpdateConversationMessage(conversationId, request);
|
||||
}
|
||||
|
||||
public async Task<Conversation> GetConversation(string id)
|
||||
public async Task<Conversation> GetConversation(string id, bool isLoadStates = false)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var conversation = db.GetConversation(id);
|
||||
|
|
@ -80,6 +80,11 @@ public partial class ConversationService : IConversationService
|
|||
|
||||
public async Task<PagedItems<Conversation>> GetConversations(ConversationFilter filter)
|
||||
{
|
||||
if (filter == null)
|
||||
{
|
||||
filter = ConversationFilter.Empty();
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var conversations = db.GetConversations(filter);
|
||||
return conversations;
|
||||
|
|
|
|||
|
|
@ -69,23 +69,34 @@ public class ConversationStateService : IConversationStateService
|
|||
return this;
|
||||
}
|
||||
|
||||
var defaultRound = -1;
|
||||
var preValue = string.Empty;
|
||||
var currentValue = value.ToString();
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
var curActiveRounds = activeRounds > 0 ? activeRounds : -1;
|
||||
int? preActiveRounds = null;
|
||||
var curActive = true;
|
||||
StateKeyValue? pair = null;
|
||||
StateValue? prevLeafNode = null;
|
||||
var curActiveRounds = activeRounds > 0 ? activeRounds : defaultRound;
|
||||
|
||||
if (ContainsState(name) && _curStates.TryGetValue(name, out var pair))
|
||||
if (ContainsState(name) && _curStates.TryGetValue(name, out pair))
|
||||
{
|
||||
var leafNode = pair?.Values?.LastOrDefault();
|
||||
preActiveRounds = leafNode?.ActiveRounds;
|
||||
preValue = leafNode?.Data ?? string.Empty;
|
||||
prevLeafNode = pair?.Values?.LastOrDefault();
|
||||
preValue = prevLeafNode?.Data ?? string.Empty;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"[STATE] {name} = {value}");
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
|
||||
if (!ContainsState(name) || preValue != currentValue || preActiveRounds != curActiveRounds)
|
||||
var isNoChange = ContainsState(name)
|
||||
&& preValue == currentValue
|
||||
&& prevLeafNode?.ActiveRounds == curActiveRounds
|
||||
&& curActiveRounds == defaultRound
|
||||
&& prevLeafNode?.Source == source
|
||||
&& prevLeafNode?.DataType == valueType
|
||||
&& prevLeafNode?.Active == curActive
|
||||
&& pair?.Readonly == readOnly;
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
if (!ContainsState(name) || preValue != currentValue || prevLeafNode?.ActiveRounds != curActiveRounds)
|
||||
{
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
|
|
@ -95,7 +106,7 @@ public class ConversationStateService : IConversationStateService
|
|||
MessageId = routingCtx.MessageId,
|
||||
Name = name,
|
||||
BeforeValue = preValue,
|
||||
BeforeActiveRounds = preActiveRounds,
|
||||
BeforeActiveRounds = prevLeafNode?.ActiveRounds,
|
||||
AfterValue = currentValue,
|
||||
AfterActiveRounds = curActiveRounds,
|
||||
DataType = valueType,
|
||||
|
|
@ -116,7 +127,7 @@ public class ConversationStateService : IConversationStateService
|
|||
{
|
||||
Data = currentValue,
|
||||
MessageId = routingCtx.MessageId,
|
||||
Active = true,
|
||||
Active = curActive,
|
||||
ActiveRounds = curActiveRounds,
|
||||
DataType = valueType,
|
||||
Source = source,
|
||||
|
|
@ -128,6 +139,10 @@ public class ConversationStateService : IConversationStateService
|
|||
newPair.Values = new List<StateValue> { newValue };
|
||||
_curStates[name] = newPair;
|
||||
}
|
||||
else if (isNoChange)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
_curStates[name].Values.Add(newValue);
|
||||
|
|
@ -415,14 +430,14 @@ public class ConversationStateService : IConversationStateService
|
|||
{
|
||||
var values = _curStates.Values.ToList();
|
||||
var copy = JsonSerializer.Deserialize<List<StateKeyValue>>(JsonSerializer.Serialize(values));
|
||||
return new ConversationState(copy ?? new());
|
||||
return new ConversationState(copy ?? []);
|
||||
}
|
||||
|
||||
public void SetCurrentState(ConversationState state)
|
||||
{
|
||||
var values = _curStates.Values.ToList();
|
||||
var copy = JsonSerializer.Deserialize<List<StateKeyValue>>(JsonSerializer.Serialize(values));
|
||||
_curStates = new ConversationState(copy ?? new());
|
||||
_curStates = new ConversationState(copy ?? []);
|
||||
}
|
||||
|
||||
public void ResetCurrentState()
|
||||
|
|
|
|||
|
|
@ -41,9 +41,11 @@ public class TokenStatistics : ITokenStatistics
|
|||
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
||||
var settings = settingsService.GetSetting(stats.Provider, _model);
|
||||
|
||||
var deltaPromptCost = stats.PromptCount / 1000f * settings.PromptCost;
|
||||
var deltaPromptCost = (stats.PromptCount - stats.CachedPromptCount) / 1000f * settings.PromptCost;
|
||||
var deltaCachedPromptCost = stats.CachedPromptCount / 1000f * (settings.AdditionalCost?.CachedPromptCost ?? 0f);
|
||||
var deltaCompletionCost = stats.CompletionCount / 1000f * settings.CompletionCost;
|
||||
var deltaTotal = deltaPromptCost + deltaCompletionCost;
|
||||
|
||||
var deltaTotal = deltaPromptCost + deltaCachedPromptCost + deltaCompletionCost;
|
||||
_promptCost += deltaPromptCost;
|
||||
_completionCost += deltaCompletionCost;
|
||||
|
||||
|
|
@ -53,6 +55,8 @@ public class TokenStatistics : ITokenStatistics
|
|||
stat.SetState("prompt_total", stats.PromptCount + inputCount, isNeedVersion: false, source: StateSource.Application);
|
||||
var outputCount = int.Parse(stat.GetState("completion_total", "0"));
|
||||
stat.SetState("completion_total", stats.CompletionCount + outputCount, isNeedVersion: false, source: StateSource.Application);
|
||||
var cachedCount = int.Parse(stat.GetState("cached_prompt_total", "0"));
|
||||
stat.SetState("cached_prompt_total", stats.CachedPromptCount + cachedCount, isNeedVersion: false, source: StateSource.Application);
|
||||
|
||||
// Total cost
|
||||
var total_cost = float.Parse(stat.GetState("llm_total_cost", "0"));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Instructs;
|
||||
using BotSharp.Abstraction.Instructs.Models;
|
||||
|
||||
namespace BotSharp.Core.Instructs.Functions;
|
||||
|
||||
public class ExecuteTemplateFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "util-instruct-execute_template";
|
||||
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<ExecuteTemplateFn> _logger;
|
||||
|
||||
public ExecuteTemplateFn(
|
||||
IServiceProvider services,
|
||||
ILogger<ExecuteTemplateFn> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<ExecuteTemplateArgs>(message.FunctionArgs);
|
||||
if (string.IsNullOrEmpty(args.TemplateName))
|
||||
{
|
||||
message.Content = $"Invalid template name.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.GetAgent(message.CurrentAgentId);
|
||||
var template = agent.Templates.FirstOrDefault(x => x.Name.IsEqualTo(args.TemplateName));
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
message.Content = $"Cannot find template ({args.TemplateName}) in agent {agent.Name}";
|
||||
return false;
|
||||
}
|
||||
|
||||
var response = await GetAiResponse(agent, args.TemplateName);
|
||||
message.Content = response;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<string> GetAiResponse(Agent agent, string templateName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var text = agentService.RenderedTemplate(agent, templateName);
|
||||
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: agent.LlmConfig?.Provider, model: agent.LlmConfig?.Model);
|
||||
var response = await completion.GetChatCompletions(new Agent()
|
||||
{
|
||||
Id = agent.Id
|
||||
},
|
||||
new List<RoleDialogModel>
|
||||
{
|
||||
new(AgentRole.User, text)
|
||||
});
|
||||
|
||||
var hooks = _services.GetServices<IInstructHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agent.Id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await hook.OnResponseGenerated(new InstructResponseModel
|
||||
{
|
||||
AgentId = agent.Id,
|
||||
TemplateName = templateName,
|
||||
Provider = completion.Provider,
|
||||
Model = completion.Model,
|
||||
UserMessage = text,
|
||||
CompletionText = response.Content
|
||||
});
|
||||
}
|
||||
|
||||
return response.Content;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Error when getting agent {agent.Name} instruction response.";
|
||||
_logger.LogWarning($"{error} {ex.Message}\r\n{ex.InnerException}");
|
||||
return error;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
namespace BotSharp.Core.Instructs.Hooks;
|
||||
|
||||
public class InstructUtilityHook : IAgentUtilityHook
|
||||
{
|
||||
private static string PREFIX = "util-instruct-";
|
||||
private static string EXECUTE_TEMPLATE = $"{PREFIX}execute_template";
|
||||
|
||||
public void AddUtilities(List<AgentUtility> utilities)
|
||||
{
|
||||
utilities.Add(new AgentUtility
|
||||
{
|
||||
Name = "instruct.template",
|
||||
Functions = [new($"{EXECUTE_TEMPLATE}")],
|
||||
Templates = [new($"{EXECUTE_TEMPLATE}.fn")]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Instructs.Settings;
|
||||
using BotSharp.Abstraction.Plugins.Models;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Core.Instructs.Hooks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BotSharp.Core.Instructs;
|
||||
|
|
@ -18,6 +19,8 @@ public class InsturctionPlugin : IBotSharpPlugin
|
|||
var settingService = provider.GetRequiredService<ISettingService>();
|
||||
return settingService.Bind<InstructionSettings>("Instruction");
|
||||
});
|
||||
|
||||
services.AddScoped<IAgentUtilityHook, InstructUtilityHook>();
|
||||
}
|
||||
|
||||
public bool AttachMenu(List<PluginMenuDef> menu)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
|
|||
public bool DeleteConversations(IEnumerable<string> conversationIds)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Conversation GetConversation(string conversationId)
|
||||
public Conversation GetConversation(string conversationId, bool isLoadStates = false)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public PagedItems<Conversation> GetConversations(ConversationFilter filter)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using BotSharp.Abstraction.Loggers.Models;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Repository;
|
||||
|
|
@ -346,7 +347,7 @@ public partial class FileRepository
|
|||
}
|
||||
}
|
||||
|
||||
public Conversation GetConversation(string conversationId)
|
||||
public Conversation GetConversation(string conversationId, bool isLoadStates = false)
|
||||
{
|
||||
var convDir = FindConversationDirectory(conversationId);
|
||||
if (string.IsNullOrEmpty(convDir)) return null;
|
||||
|
|
@ -361,18 +362,20 @@ public partial class FileRepository
|
|||
record.Dialogs = CollectDialogElements(dialogFile);
|
||||
}
|
||||
|
||||
var stateFile = Path.Combine(convDir, STATE_FILE);
|
||||
if (record != null)
|
||||
if (isLoadStates)
|
||||
{
|
||||
var states = CollectConversationStates(stateFile);
|
||||
var curStates = new Dictionary<string, string>();
|
||||
states.ForEach(x =>
|
||||
var latestStateFile = Path.Combine(convDir, CONV_LATEST_STATE_FILE);
|
||||
if (record != null && File.Exists(latestStateFile))
|
||||
{
|
||||
curStates[x.Key] = x.Values?.LastOrDefault()?.Data ?? string.Empty;
|
||||
});
|
||||
record.States = curStates;
|
||||
var stateJson = File.ReadAllText(latestStateFile);
|
||||
var states = JsonSerializer.Deserialize<Dictionary<string, JsonDocument>>(stateJson, _options) ?? [];
|
||||
record.States = states.ToDictionary(x => x.Key, x =>
|
||||
{
|
||||
var elem = x.Value.RootElement.GetProperty("data");
|
||||
return elem.ValueKind != JsonValueKind.Null ? elem.ToString() : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
|
|
@ -508,6 +511,21 @@ public partial class FileRepository
|
|||
|
||||
if (!matched) continue;
|
||||
|
||||
if (filter.IsLoadLatestStates)
|
||||
{
|
||||
var latestStateFile = Path.Combine(d, CONV_LATEST_STATE_FILE);
|
||||
if (File.Exists(latestStateFile))
|
||||
{
|
||||
var stateJson = File.ReadAllText(latestStateFile);
|
||||
var states = JsonSerializer.Deserialize<Dictionary<string, JsonDocument>>(stateJson, _options) ?? [];
|
||||
record.States = states.ToDictionary(x => x.Key, x =>
|
||||
{
|
||||
var elem = x.Value.RootElement.GetProperty("data");
|
||||
return elem.ValueKind != JsonValueKind.Null ? elem.ToString() : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
records.Add(record);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ public class RoutingUtilityHook : IAgentUtilityHook
|
|||
|
||||
public void AddUtilities(List<AgentUtility> utilities)
|
||||
{
|
||||
var utility = new AgentUtility
|
||||
utilities.Add(new AgentUtility
|
||||
{
|
||||
Name = "routing.tools",
|
||||
Functions = [new($"{REDIRECT_TO_AGENT}"), new($"{FALLBACK_TO_ROUTER}")],
|
||||
Templates = [new($"{REDIRECT_TO_AGENT}.fn"), new($"{FALLBACK_TO_ROUTER}.fn")]
|
||||
};
|
||||
|
||||
utilities.Add(utility);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@ public partial class RoutingService
|
|||
Context.SetDialogs(dialogs);
|
||||
|
||||
// Send to Next LLM
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
await InvokeAgent(agentId, dialogs);
|
||||
var curAgentId = routing.Context.GetCurrentAgentId();
|
||||
await InvokeAgent(curAgentId, dialogs);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
||||
public partial class RoutingService
|
||||
|
|
@ -6,12 +8,20 @@ public partial class RoutingService
|
|||
public async Task<bool> InvokeFunction(string name, RoleDialogModel message)
|
||||
{
|
||||
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == name);
|
||||
|
||||
var isFillDummyContent = false;
|
||||
var dummyFuncResponse = string.Empty;
|
||||
if (function == null)
|
||||
{
|
||||
message.StopCompletion = true;
|
||||
message.Content = $"Can't find function implementation of {name}.";
|
||||
_logger.LogError(message.Content);
|
||||
return false;
|
||||
dummyFuncResponse = await GetDummyFunctionOutput(name, message);
|
||||
isFillDummyContent = !string.IsNullOrEmpty(dummyFuncResponse);
|
||||
if (!isFillDummyContent)
|
||||
{
|
||||
message.StopCompletion = true;
|
||||
message.Content = $"Can't find function implementation of {name}.";
|
||||
_logger.LogError(message.Content);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Clone message
|
||||
|
|
@ -25,7 +35,15 @@ public partial class RoutingService
|
|||
var progressService = _services.GetService<IConversationProgressService>();
|
||||
|
||||
// Before executing functions
|
||||
clonedMessage.Indication = await function.GetIndication(message);
|
||||
if (!isFillDummyContent)
|
||||
{
|
||||
clonedMessage.Indication = await function.GetIndication(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
clonedMessage.Indication = "Running";
|
||||
}
|
||||
|
||||
if (progressService?.OnFunctionExecuting != null)
|
||||
{
|
||||
await progressService.OnFunctionExecuting(clonedMessage);
|
||||
|
|
@ -40,7 +58,15 @@ public partial class RoutingService
|
|||
|
||||
try
|
||||
{
|
||||
result = await function.Execute(clonedMessage);
|
||||
if (!isFillDummyContent)
|
||||
{
|
||||
result = await function.Execute(clonedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
clonedMessage.Content = dummyFuncResponse;
|
||||
result = true;
|
||||
}
|
||||
|
||||
// After functions have been executed
|
||||
foreach (var hook in hooks)
|
||||
|
|
@ -87,4 +113,32 @@ public partial class RoutingService
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<string?> GetDummyFunctionOutput(string functionName, RoleDialogModel message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message.CurrentAgentId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.GetAgent(message.CurrentAgentId);
|
||||
var found = agent?.Functions?.FirstOrDefault(x => x.Name == functionName);
|
||||
if (string.IsNullOrWhiteSpace(found?.Output))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
var dict = new Dictionary<string, object>();
|
||||
foreach (var item in state.GetStates())
|
||||
{
|
||||
dict[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
var text = render.Render(found.Output, dict);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "util-instruct-execute_template",
|
||||
"description": "Select a specific template that can handle the user's request.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"template_name": {
|
||||
"type": "string",
|
||||
"description": "The template name that is selected for handling the request."
|
||||
}
|
||||
},
|
||||
"required": [ "template_name" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
please call function util-routing-execute_template if user wants to use a template to fulfill a specific task.
|
||||
Please ensure each template is executed only once.
|
||||
Please output the template response directly without changing anthything.
|
||||
|
|
@ -26,7 +26,14 @@ public class InstructionLogHook : InstructHookBase
|
|||
public override async Task OnResponseGenerated(InstructResponseModel response)
|
||||
{
|
||||
var settings = _services.GetRequiredService<InstructionSettings>();
|
||||
if (!settings.EnableLog || response == null) return;
|
||||
if (response == null
|
||||
|| string.IsNullOrWhiteSpace(response.AgentId)
|
||||
|| settings == null
|
||||
|| !settings.Logging.Enabled
|
||||
|| settings.Logging.ExcludedAgentIds.Contains(response.AgentId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
|
|
@ -49,6 +56,7 @@ public class InstructionLogHook : InstructHookBase
|
|||
UserId = user?.Id
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
await base.OnResponseGenerated(response);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ public class ConversationController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("/conversation/{conversationId}")]
|
||||
public async Task<ConversationViewModel?> GetConversation([FromRoute] string conversationId)
|
||||
public async Task<ConversationViewModel?> GetConversation([FromRoute] string conversationId, [FromQuery] bool isLoadStates = false)
|
||||
{
|
||||
var service = _services.GetRequiredService<IConversationService>();
|
||||
var userService = _services.GetRequiredService<IUserService>();
|
||||
|
|
@ -151,7 +151,8 @@ public class ConversationController : ControllerBase
|
|||
var filter = new ConversationFilter
|
||||
{
|
||||
Id = conversationId,
|
||||
UserId = !isAdmin ? user.Id : null
|
||||
UserId = !isAdmin ? user.Id : null,
|
||||
IsLoadLatestStates = isLoadStates
|
||||
};
|
||||
var conversations = await service.GetConversations(filter);
|
||||
if (conversations.Items.IsNullOrEmpty())
|
||||
|
|
@ -161,7 +162,6 @@ public class ConversationController : ControllerBase
|
|||
|
||||
var result = ConversationViewModel.FromSession(conversations.Items.First());
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
result.States = state.Load(conversationId, isReadOnly: true);
|
||||
user = await userService.GetUser(result.User.Id);
|
||||
result.User = UserViewModel.FromUser(user);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class ConversationViewModel
|
|||
public string? TaskId { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
public Dictionary<string, string> States { get; set; }
|
||||
public Dictionary<string, string> States { get; set; } = [];
|
||||
|
||||
public List<string> Tags { get; set; } = new();
|
||||
|
||||
|
|
@ -55,7 +55,8 @@ public class ConversationViewModel
|
|||
Channel = sess.Channel,
|
||||
Status = sess.Status,
|
||||
TaskId = sess.TaskId,
|
||||
Tags = sess.Tags ?? new(),
|
||||
Tags = sess.Tags ?? [],
|
||||
States = sess.States ?? [],
|
||||
CreatedTime = sess.CreatedTime,
|
||||
UpdatedTime = sess.UpdatedTime
|
||||
};
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
if (!AllowSendingMessage()) return;
|
||||
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var json = JsonSerializer.Serialize(new ChatResponseModel()
|
||||
{
|
||||
ConversationId = conv.ConversationId,
|
||||
|
|
@ -114,6 +115,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
Function = message.FunctionName,
|
||||
RichContent = message.SecondaryRichContent ?? message.RichContent,
|
||||
Data = message.Data,
|
||||
States = state.GetStates(),
|
||||
Sender = new UserViewModel()
|
||||
{
|
||||
FirstName = "AI",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public class FunctionDefMongoElement
|
|||
public string? VisibilityExpression { get; set; }
|
||||
public string? Impact { get; set; }
|
||||
public FunctionParametersDefMongoElement Parameters { get; set; } = new();
|
||||
public string? Output { get; set; }
|
||||
|
||||
public static FunctionDefMongoElement ToMongoElement(FunctionDef function)
|
||||
{
|
||||
|
|
@ -27,7 +28,8 @@ public class FunctionDefMongoElement
|
|||
Type = function.Parameters.Type,
|
||||
Properties = JsonSerializer.Serialize(function.Parameters.Properties),
|
||||
Required = function.Parameters.Required,
|
||||
}
|
||||
},
|
||||
Output = function.Output
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +47,8 @@ public class FunctionDefMongoElement
|
|||
Type = function.Parameters.Type,
|
||||
Properties = JsonSerializer.Deserialize<JsonDocument>(function.Parameters.Properties.IfNullOrEmptyAs("{}")),
|
||||
Required = function.Parameters.Required,
|
||||
}
|
||||
},
|
||||
Output = function.Output
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public partial class MongoRepository
|
|||
UpdateAgentProfiles(agent.Id, agent.Profiles);
|
||||
break;
|
||||
case AgentField.Label:
|
||||
UpdateAgentLabels(agent.Id, agent.Profiles);
|
||||
UpdateAgentLabels(agent.Id, agent.Labels);
|
||||
break;
|
||||
case AgentField.RoutingRule:
|
||||
UpdateAgentRoutingRules(agent.Id, agent.RoutingRules);
|
||||
|
|
|
|||
|
|
@ -299,26 +299,25 @@ public partial class MongoRepository
|
|||
_dc.Conversations.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
public Conversation GetConversation(string conversationId)
|
||||
public Conversation GetConversation(string conversationId, bool isLoadStates = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId)) return null;
|
||||
|
||||
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
|
||||
var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
var filterState = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
|
||||
|
||||
var conv = _dc.Conversations.Find(filterConv).FirstOrDefault();
|
||||
var dialog = _dc.ConversationDialogs.Find(filterDialog).FirstOrDefault();
|
||||
var states = _dc.ConversationStates.Find(filterState).FirstOrDefault();
|
||||
|
||||
if (conv == null) return null;
|
||||
|
||||
var dialogElements = dialog?.Dialogs?.Select(x => DialogMongoElement.ToDomainElement(x))?.ToList() ?? new List<DialogElement>();
|
||||
var curStates = new Dictionary<string, string>();
|
||||
states.States.ForEach(x =>
|
||||
var curStates = conv.LatestStates?.ToDictionary(x => x.Key, x =>
|
||||
{
|
||||
curStates[x.Key] = x.Values?.LastOrDefault()?.Data ?? string.Empty;
|
||||
});
|
||||
var jsonDoc = JsonDocument.Parse(x.Value.ToJson());
|
||||
var data = jsonDoc.RootElement.GetProperty("data");
|
||||
return data.ValueKind != JsonValueKind.Null ? data.ToString() : null;
|
||||
}) ?? [];
|
||||
|
||||
return new Conversation
|
||||
{
|
||||
|
|
@ -456,19 +455,34 @@ public partial class MongoRepository
|
|||
var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDef).Skip(pager.Offset).Limit(pager.Size).ToList();
|
||||
var count = _dc.Conversations.CountDocuments(filterDef);
|
||||
|
||||
var conversations = conversationDocs.Select(x => new Conversation
|
||||
var conversations = conversationDocs.Select(x =>
|
||||
{
|
||||
Id = x.Id.ToString(),
|
||||
AgentId = x.AgentId.ToString(),
|
||||
UserId = x.UserId.ToString(),
|
||||
TaskId = x.TaskId,
|
||||
Title = x.Title,
|
||||
Channel = x.Channel,
|
||||
Status = x.Status,
|
||||
DialogCount = x.DialogCount,
|
||||
Tags = x.Tags ?? new(),
|
||||
CreatedTime = x.CreatedTime,
|
||||
UpdatedTime = x.UpdatedTime
|
||||
var states = new Dictionary<string, string>();
|
||||
if (filter.IsLoadLatestStates)
|
||||
{
|
||||
states = x.LatestStates.ToDictionary(p => p.Key, p =>
|
||||
{
|
||||
var jsonDoc = JsonDocument.Parse(p.Value.ToJson());
|
||||
var data = jsonDoc.RootElement.GetProperty("data");
|
||||
return data.ValueKind != JsonValueKind.Null ? data.ToString() : null;
|
||||
});
|
||||
}
|
||||
|
||||
return new Conversation
|
||||
{
|
||||
Id = x.Id.ToString(),
|
||||
AgentId = x.AgentId.ToString(),
|
||||
UserId = x.UserId.ToString(),
|
||||
TaskId = x.TaskId,
|
||||
Title = x.Title,
|
||||
Channel = x.Channel,
|
||||
Status = x.Status,
|
||||
DialogCount = x.DialogCount,
|
||||
Tags = x.Tags ?? [],
|
||||
States = states,
|
||||
CreatedTime = x.CreatedTime,
|
||||
UpdatedTime = x.UpdatedTime
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return new PagedItems<Conversation>
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
Provider = Provider,
|
||||
Model = _model,
|
||||
PromptCount = response.Value?.Usage?.InputTokenCount ?? 0,
|
||||
CachedPromptCount = response.Value?.Usage?.InputTokenDetails?.CachedTokenCount ?? 0,
|
||||
CompletionCount = response.Value?.Usage?.OutputTokenCount ?? 0
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ public class PlaywrightInstance : IDisposable
|
|||
|
||||
public async Task<IBrowserContext> InitContext(string ctxId, BrowserActionArgs args)
|
||||
{
|
||||
var _webDriver = _services.GetRequiredService<WebBrowsingSettings>();
|
||||
if (_contexts.ContainsKey(ctxId))
|
||||
return _contexts[ctxId];
|
||||
|
||||
|
|
@ -83,6 +84,8 @@ public class PlaywrightInstance : IDisposable
|
|||
// "--start-maximized"
|
||||
]
|
||||
});
|
||||
_contexts[ctxId].SetDefaultTimeout(_webDriver.DefaultTimeout);
|
||||
_contexts[ctxId].SetDefaultNavigationTimeout(_webDriver.DefaultNavigationTimeout);
|
||||
}
|
||||
|
||||
_pages[ctxId] = new List<IPage>();
|
||||
|
|
|
|||
|
|
@ -223,7 +223,10 @@
|
|||
},
|
||||
|
||||
"Instruction": {
|
||||
"EnableLog": true
|
||||
"Logging": {
|
||||
"Enabled": true,
|
||||
"ExcludedAgentIds": []
|
||||
}
|
||||
},
|
||||
|
||||
"ChatHub": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue