add agent links

This commit is contained in:
Jicheng Lu 2025-04-28 14:53:32 -05:00
parent 4d9ce27345
commit d05fa40bb7
22 changed files with 298 additions and 21 deletions

View file

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

View file

@ -46,6 +46,12 @@ 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>
@ -168,6 +174,8 @@ public class Agent
Functions = agent.Functions,
Responses = agent.Responses,
Samples = agent.Samples,
Templates = agent.Templates,
Links = agent.Links,
Utilities = agent.Utilities,
McpTools = agent.McpTools,
Knowledges = agent.Knowledges,
@ -204,6 +212,12 @@ 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

@ -0,0 +1,17 @@
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

@ -0,0 +1,23 @@
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,23 +1,17 @@
namespace BotSharp.Abstraction.Agents.Models;
public class AgentTemplate
public class AgentTemplate : AgentPromptBase
{
public string Name { get; set; }
public string Content { get; set; }
public AgentTemplate()
public AgentTemplate() : base()
{
}
public AgentTemplate(string name, string content)
public AgentTemplate(string name, string content) : base(name, content)
{
Name = name;
Content = content;
}
public override string ToString()
{
return Name;
return base.ToString();
}
}

View file

@ -3,5 +3,22 @@ namespace BotSharp.Abstraction.Templating;
public interface ITemplateRender
{
string Render(string template, Dictionary<string, object> dict);
void Register(Type type);
/// <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

@ -45,7 +45,7 @@ public class AgentPlugin : IBotSharpPlugin
{
var settingService = provider.GetRequiredService<ISettingService>();
var render = provider.GetRequiredService<ITemplateRender>();
render.Register(typeof(AgentSettings));
render.RegisterType(typeof(AgentSettings));
return settingService.Bind<AgentSettings>("Agent");
});
}

View file

@ -111,6 +111,26 @@ 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,10 +52,12 @@ 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,6 +22,7 @@ public partial class AgentService
agent.TemplateDict[t.Key] = t.Value;
}
RenderAgentLinks(agent, agent.TemplateDict);
var res = render.Render(string.Join("\r\n", instructions), agent.TemplateDict);
return res;
}
@ -136,4 +137,26 @@ 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,6 +38,7 @@ 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 ?? [];
@ -105,6 +106,7 @@ 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)
@ -196,10 +198,12 @@ 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)
.SetTemplates(templates)
.SetLinks(links)
.SetFunctions(functions)
.SetResponses(responses)
.SetSamples(samples);

View file

@ -31,7 +31,7 @@ public class ConversationPlugin : IBotSharpPlugin
{
var settingService = provider.GetRequiredService<ISettingService>();
var render = provider.GetRequiredService<ITemplateRender>();
render.Register(typeof(ConversationSetting));
render.RegisterType(typeof(ConversationSetting));
return settingService.Bind<ConversationSetting>("Conversation");
});

View file

@ -51,6 +51,9 @@ 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;
@ -325,6 +328,23 @@ 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;
@ -404,6 +424,7 @@ 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);
}
@ -447,11 +468,13 @@ 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,6 +24,7 @@ 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";
@ -228,6 +229,7 @@ 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);
@ -386,7 +388,7 @@ public partial class FileRepository : IBotSharpRepository
foreach (var file in Directory.GetFiles(templateDir))
{
var fileName = file.Split(Path.DirectorySeparatorChar).Last();
var fileName = Path.GetFileName(file);
var splitIdx = fileName.LastIndexOf(".");
var name = fileName.Substring(0, splitIdx);
var extension = fileName.Substring(splitIdx + 1);
@ -400,6 +402,29 @@ 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

@ -3,6 +3,7 @@ using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Templating;
using BotSharp.Abstraction.Translation.Models;
using Fluid;
using Fluid.Ast;
using System.Collections;
using System.Reflection;
@ -40,17 +41,71 @@ public class TemplateRender : ITemplateRender
{
var context = new TemplateContext(dict, _options);
template = t.Render(context);
return template;
}
else
{
_logger.LogWarning(error);
return template;
}
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();
});
public void Register(Type type)
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;
@ -59,7 +114,7 @@ public class TemplateRender : ITemplateRender
if (type.IsGenericType)
{
var genericType = type.GetGenericArguments()[0];
Register(genericType);
RegisterType(genericType);
}
}
else if (IsTrackToNextLevel(type))
@ -68,7 +123,7 @@ public class TemplateRender : ITemplateRender
var props = type.GetProperties();
foreach (var prop in props)
{
Register(prop.PropertyType);
RegisterType(prop.PropertyType);
}
}
}

View file

@ -1 +1 @@
You are a AI Assistant. You can answer user's question.
You are a AI Assistant. You can answer user's question.

View file

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

View file

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

View file

@ -18,6 +18,7 @@ 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; }
@ -87,6 +88,7 @@ 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,6 +15,7 @@ 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

@ -0,0 +1,28 @@
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,6 +52,9 @@ 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;
@ -237,6 +240,19 @@ 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;
@ -356,6 +372,7 @@ 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)
@ -538,6 +555,7 @@ 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() ?? [],
@ -634,6 +652,7 @@ 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() ?? [],