This commit is contained in:
Jicheng Lu 2025-05-04 23:20:52 -05:00
parent ed39786539
commit 1762675cb0
21 changed files with 56 additions and 287 deletions

View file

@ -16,7 +16,6 @@ public enum AgentField
Instruction,
Function,
Template,
Link,
Response,
Sample,
LlmConfig,

View file

@ -46,12 +46,6 @@ public class Agent
[JsonIgnore]
public List<AgentTemplate> Templates { get; set; } = new();
/// <summary>
/// Links that can be filled into parent prompt
/// </summary>
[JsonIgnore]
public List<AgentLink> Links { get; set; } = new();
/// <summary>
/// Agent tasks
/// </summary>
@ -88,7 +82,7 @@ public class Agent
public PluginDef Plugin { get; set; }
[JsonIgnore]
public bool Installed => Plugin.Enabled;
public bool Installed => Plugin?.Enabled == true;
/// <summary>
/// Default is True, user will enable this by installing appropriate plugin.
@ -175,7 +169,6 @@ public class Agent
Responses = agent.Responses,
Samples = agent.Samples,
Templates = agent.Templates,
Links = agent.Links,
Utilities = agent.Utilities,
McpTools = agent.McpTools,
Knowledges = agent.Knowledges,
@ -212,12 +205,6 @@ public class Agent
return this;
}
public Agent SetLinks(List<AgentLink> links)
{
Links = links ?? [];
return this;
}
public Agent SetTasks(List<AgentTask> tasks)
{
Tasks = tasks ?? [];

View file

@ -1,17 +0,0 @@
namespace BotSharp.Abstraction.Agents.Models;
public class AgentLink : AgentPromptBase
{
public AgentLink() : base()
{
}
public AgentLink(string name, string content) : base(name, content)
{
}
public override string ToString()
{
return base.ToString();
}
}

View file

@ -1,23 +0,0 @@
namespace BotSharp.Abstraction.Agents.Models;
public class AgentPromptBase
{
public string Name { get; set; }
public string Content { get; set; }
public AgentPromptBase()
{
}
public AgentPromptBase(string name, string content)
{
Name = name;
Content = content;
}
public override string ToString()
{
return Name;
}
}

View file

@ -1,17 +1,22 @@
namespace BotSharp.Abstraction.Agents.Models;
public class AgentTemplate : AgentPromptBase
public class AgentTemplate
{
public AgentTemplate() : base()
public string Name { get; set; }
public string Content { get; set; }
public AgentTemplate()
{
}
public AgentTemplate(string name, string content) : base(name, content)
public AgentTemplate(string name, string content)
{
Name = name;
Content = content;
}
public override string ToString()
{
return base.ToString();
return Name;
}
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Templating.Constants;
public static class TemplateRenderConstant
{
public const string RENDER_AGENT = "render_agent";
}

View file

@ -3,22 +3,5 @@ namespace BotSharp.Abstraction.Templating;
public interface ITemplateRender
{
string Render(string template, Dictionary<string, object> dict);
/// <summary>
/// Register tag
/// </summary>
/// <param name="tag"></param>
/// <param name="content">A dictionary whose key is identifier and value is its content to render</param>
/// <param name="data"></param>
/// <returns></returns>
bool RegisterTag(string tag, Dictionary<string, string> content, Dictionary<string, object>? data = null);
/// <summary>
/// Register tags
/// </summary>
/// <param name="tags">A dictionary whose key is tag and value is its identifier and content to render</param>
/// <param name="data"></param>
/// <returns></returns>
bool RegisterTags(Dictionary<string, List<AgentPromptBase>> tags, Dictionary<string, object>? data = null);
void RegisterType(Type type);
}

View file

@ -111,26 +111,6 @@ public partial class AgentService
return templates;
}
private List<AgentLink> GetLinksFromFile(string fileDir)
{
var links = new List<AgentLink>();
var linkDir = Path.Combine(fileDir, "links");
if (!Directory.Exists(linkDir)) return links;
foreach (var file in Directory.GetFiles(linkDir))
{
var extension = Path.GetExtension(file).Substring(1);
if (extension.IsEqualTo(_agentSettings.TemplateFormat))
{
var name = Path.GetFileNameWithoutExtension(file);
var content = File.ReadAllText(file);
links.Add(new AgentLink(name, content));
}
}
return links;
}
private List<FunctionDef> GetFunctionsFromFile(string fileDir)
{
var functions = new List<FunctionDef>();

View file

@ -52,12 +52,10 @@ public partial class AgentService
var functions = GetFunctionsFromFile(dir);
var responses = GetResponsesFromFile(dir);
var templates = GetTemplatesFromFile(dir);
var links = GetLinksFromFile(dir);
var samples = GetSamplesFromFile(dir);
agent.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetTemplates(templates)
.SetLinks(links)
.SetFunctions(functions)
.SetResponses(responses)
.SetSamples(samples);

View file

@ -22,7 +22,7 @@ public partial class AgentService
agent.TemplateDict[t.Key] = t.Value;
}
RenderAgentLinks(agent, agent.TemplateDict);
agent.TemplateDict[TemplateRenderConstant.RENDER_AGENT] = agent;
var res = render.Render(string.Join("\r\n", instructions), agent.TemplateDict);
return res;
}
@ -129,6 +129,7 @@ public partial class AgentService
}
// render liquid template
agent.TemplateDict[TemplateRenderConstant.RENDER_AGENT] = agent;
var content = render.Render(template, agent.TemplateDict);
HookEmitter.Emit<IContentGeneratingHook>(_services, async hook =>
@ -137,26 +138,4 @@ public partial class AgentService
return content;
}
private void RenderAgentLinks(Agent agent, Dictionary<string, object> dict)
{
var render = _services.GetRequiredService<ITemplateRender>();
var links = new Dictionary<string, string>();
agent.Links ??= [];
foreach (var link in agent.Links)
{
if (string.IsNullOrWhiteSpace(link.Name)
|| string.IsNullOrWhiteSpace(link.Content))
{
continue;
}
links[link.Name] = link.Content;
}
render.RegisterTag("link", links, dict);
return;
}
}

View file

@ -38,7 +38,6 @@ public partial class AgentService
record.ChannelInstructions = agent.ChannelInstructions ?? [];
record.Functions = agent.Functions ?? [];
record.Templates = agent.Templates ?? [];
record.Links = agent.Links ?? [];
record.Responses = agent.Responses ?? [];
record.Samples = agent.Samples ?? [];
record.Utilities = agent.Utilities ?? [];
@ -106,7 +105,6 @@ public partial class AgentService
.SetInstruction(foundAgent.Instruction)
.SetChannelInstructions(foundAgent.ChannelInstructions)
.SetTemplates(foundAgent.Templates)
.SetLinks(foundAgent.Links)
.SetFunctions(foundAgent.Functions)
.SetResponses(foundAgent.Responses)
.SetSamples(foundAgent.Samples)
@ -198,12 +196,10 @@ public partial class AgentService
var functions = GetFunctionsFromFile(dir);
var responses = GetResponsesFromFile(dir);
var templates = GetTemplatesFromFile(dir);
var links = GetLinksFromFile(dir);
var samples = GetSamplesFromFile(dir);
return agent.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetTemplates(templates)
.SetLinks(links)
.SetFunctions(functions)
.SetResponses(responses)
.SetSamples(samples);

View file

@ -51,9 +51,6 @@ namespace BotSharp.Core.Repository
case AgentField.Template:
UpdateAgentTemplates(agent.Id, agent.Templates);
break;
case AgentField.Link:
UpdateAgentLinks(agent.Id, agent.Links);
break;
case AgentField.Response:
UpdateAgentResponses(agent.Id, agent.Responses);
break;
@ -328,23 +325,6 @@ namespace BotSharp.Core.Repository
}
}
private void UpdateAgentLinks(string agentId, List<AgentLink> links)
{
if (links == null) return;
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
var linkDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agentId, AGENT_LINKS_FOLDER);
DeleteBeforeCreateDirectory(linkDir);
foreach (var link in links)
{
var file = Path.Combine(linkDir, $"{link.Name}.{_agentSettings.TemplateFormat}");
File.WriteAllText(file, link.Content);
}
}
private void UpdateAgentResponses(string agentId, List<AgentResponse> responses)
{
if (responses == null) return;
@ -424,7 +404,6 @@ namespace BotSharp.Core.Repository
UpdateAgentInstructions(inputAgent.Id, inputAgent.Instruction, inputAgent.ChannelInstructions);
UpdateAgentResponses(inputAgent.Id, inputAgent.Responses);
UpdateAgentTemplates(inputAgent.Id, inputAgent.Templates);
UpdateAgentLinks(inputAgent.Id, inputAgent.Links);
UpdateAgentFunctions(inputAgent.Id, inputAgent.Functions);
UpdateAgentSamples(inputAgent.Id, inputAgent.Samples);
}
@ -468,13 +447,11 @@ namespace BotSharp.Core.Repository
var functions = FetchFunctions(dir);
var samples = FetchSamples(dir);
var templates = FetchTemplates(dir);
var links = FetchLinks(dir);
var responses = FetchResponses(dir);
return record.SetInstruction(defaultInstruction)
.SetChannelInstructions(channelInstructions)
.SetFunctions(functions)
.SetTemplates(templates)
.SetLinks(links)
.SetSamples(samples)
.SetResponses(responses);
}

View file

@ -24,7 +24,6 @@ public partial class FileRepository : IBotSharpRepository
private const string AGENT_INSTRUCTIONS_FOLDER = "instructions";
private const string AGENT_FUNCTIONS_FOLDER = "functions";
private const string AGENT_TEMPLATES_FOLDER = "templates";
private const string AGENT_LINKS_FOLDER = "links";
private const string AGENT_RESPONSES_FOLDER = "responses";
private const string AGENT_TASKS_FOLDER = "tasks";
private const string AGENT_TASK_PREFIX = "#metadata";
@ -229,7 +228,6 @@ public partial class FileRepository : IBotSharpRepository
.SetChannelInstructions(channelInstructions)
.SetFunctions(FetchFunctions(d))
.SetTemplates(FetchTemplates(d))
.SetLinks(FetchLinks(d))
.SetResponses(FetchResponses(d))
.SetSamples(FetchSamples(d));
_agents.Add(agent);
@ -402,29 +400,6 @@ public partial class FileRepository : IBotSharpRepository
return templates;
}
private List<AgentLink> FetchLinks(string fileDir)
{
var links = new List<AgentLink>();
var linkDir = Path.Combine(fileDir, AGENT_LINKS_FOLDER);
if (!Directory.Exists(linkDir)) return links;
foreach (var file in Directory.GetFiles(linkDir))
{
var fileName = Path.GetFileName(file);
var splitIdx = fileName.LastIndexOf(".");
var name = fileName.Substring(0, splitIdx);
var extension = fileName.Substring(splitIdx + 1);
if (extension.Equals(_agentSettings.TemplateFormat, StringComparison.OrdinalIgnoreCase))
{
var content = File.ReadAllText(file);
links.Add(new AgentLink(name, content));
}
}
return links;
}
private List<AgentTask> FetchTasks(string fileDir)
{
var tasks = new List<AgentTask>();

View file

@ -5,7 +5,9 @@ using BotSharp.Abstraction.Translation.Models;
using Fluid;
using Fluid.Ast;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text.Encodings.Web;
namespace BotSharp.Core.Templating;
@ -33,6 +35,8 @@ public class TemplateRender : ITemplateRender
_options.MemberAccessStrategy.Register<FunctionParametersDef>();
_options.MemberAccessStrategy.Register<UserIdentity>();
_options.MemberAccessStrategy.Register<TranslationInput>();
_parser.RegisterIdentifierTag("link", RenderIdentifierTag);
}
public string Render(string template, Dictionary<string, object> dict)
@ -50,61 +54,6 @@ public class TemplateRender : ITemplateRender
return template;
}
public bool RegisterTag(string tag, Dictionary<string, string> content, Dictionary<string, object>? data = null)
{
_parser.RegisterIdentifierTag(tag, (identifier, writer, encoder, context) =>
{
if (content?.TryGetValue(identifier, out var value) == true)
{
var str = Render(value, data ?? []);
writer.Write(str);
}
else
{
writer.Write(string.Empty);
}
return Statement.Normal();
});
return true;
}
public bool RegisterTags(Dictionary<string, List<AgentPromptBase>> tags, Dictionary<string, object>? data = null)
{
if (tags.IsNullOrEmpty()) return false;
foreach (var item in tags)
{
var tag = item.Key;
if (string.IsNullOrWhiteSpace(tag)
|| item.Value.IsNullOrEmpty())
{
continue;
}
foreach (var prompt in item.Value)
{
_parser.RegisterIdentifierTag(tag, (identifier, writer, encoder, context) =>
{
var found = item.Value.FirstOrDefault(x => x.Name.IsEqualTo(identifier));
if (found != null)
{
var str = Render(found.Content, data ?? []);
writer.Write(str);
}
else
{
writer.Write(string.Empty);
}
return Statement.Normal();
});
}
}
return true;
}
public void RegisterType(Type type)
{
if (type == null || IsStringType(type)) return;
@ -130,6 +79,39 @@ public class TemplateRender : ITemplateRender
#region Private methods
private static async ValueTask<Completion> RenderIdentifierTag(string identifier, TextWriter writer, TextEncoder encoder, TemplateContext context)
{
try
{
var value = await context.Model.GetValueAsync(TemplateRenderConstant.RENDER_AGENT, context);
var agent = value?.ToObjectValue() as Agent;
var found = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(identifier));
var key = $"{agent?.Id} | {identifier}";
if (found == null || context.AmbientValues.TryGetValue(key, out var visited) && (bool)visited)
{
writer.Write(string.Empty);
}
else if (_parser.TryParse(found.Content, out var t, out _))
{
context.AmbientValues[key] = true;
var rendered = t.Render(context);
writer.Write(rendered);
context.AmbientValues.Remove(key);
}
else
{
writer.Write(string.Empty);
}
}
catch
{
writer.Write(string.Empty);
}
return Completion.Normal;
}
private static bool IsStringType(Type type)
{
return type == typeof(string);

View file

@ -41,6 +41,7 @@ global using BotSharp.Abstraction.Statistics.Enums;
global using BotSharp.Abstraction.Statistics.Services;
global using BotSharp.Abstraction.Loggers.Services;
global using BotSharp.Abstraction.Infrastructures.Events;
global using BotSharp.Abstraction.Templating.Constants;
global using BotSharp.Core.Repository;
global using BotSharp.Core.Routing;
global using BotSharp.Core.Agents.Services;

View file

@ -24,8 +24,6 @@ public class AgentCreationModel
/// </summary>
public List<AgentTemplate> Templates { get; set; } = new();
public List<AgentLink> Links { get; set; } = new();
/// <summary>
/// LLM callable function definition
/// </summary>
@ -72,7 +70,6 @@ public class AgentCreationModel
Instruction = Instruction,
ChannelInstructions = ChannelInstructions,
Templates = Templates,
Links = Links,
Functions = Functions,
Responses = Responses,
Samples = Samples,

View file

@ -25,11 +25,6 @@ public class AgentUpdateModel
/// </summary>
public List<AgentTemplate>? Templates { get; set; }
/// <summary>
/// Links
/// </summary>
public List<AgentLink>? Links { get; set; }
/// <summary>
/// Samples
/// </summary>
@ -110,7 +105,6 @@ public class AgentUpdateModel
Instruction = Instruction ?? string.Empty,
ChannelInstructions = ChannelInstructions ?? [],
Templates = Templates ?? [],
Links = Links ?? [],
Functions = Functions ?? [],
Responses = Responses ?? [],
Utilities = Utilities ?? [],

View file

@ -18,7 +18,6 @@ public class AgentViewModel
[JsonPropertyName("channel_instructions")]
public List<ChannelInstruction> ChannelInstructions { get; set; }
public List<AgentTemplate> Templates { get; set; }
public List<AgentLink> Links { get; set; }
public List<FunctionDef> Functions { get; set; }
public List<AgentResponse> Responses { get; set; }
public List<string> Samples { get; set; }
@ -88,7 +87,6 @@ public class AgentViewModel
Instruction = agent.Instruction,
ChannelInstructions = agent.ChannelInstructions ?? [],
Templates = agent.Templates ?? [],
Links = agent.Links ?? [],
Functions = agent.Functions ?? [],
Responses = agent.Responses ?? [],
Samples = agent.Samples ?? [],

View file

@ -15,7 +15,6 @@ public class AgentDocument : MongoBase
public int? MaxMessageCount { get; set; }
public List<ChannelInstructionMongoElement> ChannelInstructions { get; set; }
public List<AgentTemplateMongoElement> Templates { get; set; }
public List<AgentLinkMongoElement> Links { get; set; }
public List<FunctionDefMongoElement> Functions { get; set; }
public List<AgentResponseMongoElement> Responses { get; set; }
public List<string> Samples { get; set; }

View file

@ -1,28 +0,0 @@
using BotSharp.Abstraction.Agents.Models;
namespace BotSharp.Plugin.MongoStorage.Models;
[BsonIgnoreExtraElements(Inherited = true)]
public class AgentLinkMongoElement
{
public string Name { get; set; } = default!;
public string Content { get; set; } = default!;
public static AgentLinkMongoElement ToMongoElement(AgentLink link)
{
return new AgentLinkMongoElement
{
Name = link.Name,
Content = link.Content
};
}
public static AgentLink ToDomainElement(AgentLinkMongoElement mongoLink)
{
return new AgentLink
{
Name = mongoLink.Name,
Content = mongoLink.Content
};
}
}

View file

@ -52,9 +52,6 @@ public partial class MongoRepository
case AgentField.Template:
UpdateAgentTemplates(agent.Id, agent.Templates);
break;
case AgentField.Link:
UpdateAgentLinks(agent.Id, agent.Links);
break;
case AgentField.Response:
UpdateAgentResponses(agent.Id, agent.Responses);
break;
@ -240,19 +237,6 @@ public partial class MongoRepository
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentLinks(string agentId, List<AgentLink> links)
{
if (links == null) return;
var linksToUpdate = links.Select(t => AgentLinkMongoElement.ToMongoElement(t)).ToList();
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var update = Builders<AgentDocument>.Update
.Set(x => x.Links, linksToUpdate)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentResponses(string agentId, List<AgentResponse> responses)
{
if (responses == null || string.IsNullOrWhiteSpace(agentId)) return;
@ -372,7 +356,6 @@ public partial class MongoRepository
.Set(x => x.Instruction, agent.Instruction)
.Set(x => x.ChannelInstructions, agent.ChannelInstructions.Select(i => ChannelInstructionMongoElement.ToMongoElement(i)).ToList())
.Set(x => x.Templates, agent.Templates.Select(t => AgentTemplateMongoElement.ToMongoElement(t)).ToList())
.Set(x => x.Links, agent.Links.Select(t => AgentLinkMongoElement.ToMongoElement(t)).ToList())
.Set(x => x.Functions, agent.Functions.Select(f => FunctionDefMongoElement.ToMongoElement(f)).ToList())
.Set(x => x.Responses, agent.Responses.Select(r => AgentResponseMongoElement.ToMongoElement(r)).ToList())
.Set(x => x.Samples, agent.Samples)
@ -555,7 +538,6 @@ public partial class MongoRepository
LlmConfig = AgentLlmConfigMongoElement.ToMongoElement(x.LlmConfig),
ChannelInstructions = x.ChannelInstructions?.Select(i => ChannelInstructionMongoElement.ToMongoElement(i))?.ToList() ?? [],
Templates = x.Templates?.Select(t => AgentTemplateMongoElement.ToMongoElement(t))?.ToList() ?? [],
Links = x.Links?.Select(l => AgentLinkMongoElement.ToMongoElement(l))?.ToList() ?? [],
Functions = x.Functions?.Select(f => FunctionDefMongoElement.ToMongoElement(f))?.ToList() ?? [],
Responses = x.Responses?.Select(r => AgentResponseMongoElement.ToMongoElement(r))?.ToList() ?? [],
RoutingRules = x.RoutingRules?.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?.ToList() ?? [],
@ -652,7 +634,6 @@ public partial class MongoRepository
LlmConfig = AgentLlmConfigMongoElement.ToDomainElement(agentDoc.LlmConfig),
ChannelInstructions = agentDoc.ChannelInstructions?.Select(i => ChannelInstructionMongoElement.ToDomainElement(i))?.ToList() ?? [],
Templates = agentDoc.Templates?.Select(t => AgentTemplateMongoElement.ToDomainElement(t))?.ToList() ?? [],
Links = agentDoc.Links?.Select(l => AgentLinkMongoElement.ToDomainElement(l))?.ToList() ?? [],
Functions = agentDoc.Functions?.Select(f => FunctionDefMongoElement.ToDomainElement(f)).ToList() ?? [],
Responses = agentDoc.Responses?.Select(r => AgentResponseMongoElement.ToDomainElement(r))?.ToList() ?? [],
RoutingRules = agentDoc.RoutingRules?.Select(r => RoutingRuleMongoElement.ToDomainElement(agentDoc.Id, agentDoc.Name, r))?.ToList() ?? [],