Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public interface IAgentService
/// <param name="id"></param>
/// <returns>Original agent information</returns>
Task<Agent> GetAgent(string id);

Task<AgentTemplate?> GetAgentTemplateDetail(string agentId, string templateName);

Task<bool> DeleteAgent(string id, AgentDeleteOptions? options = null);
Task UpdateAgent(Agent agent, AgentField updateField);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
namespace BotSharp.Abstraction.Agents.Models;

public class AgentTemplate
public class AgentTemplate : AgentTemplateConfig
{
public string Name { get; set; }
public string Content { get; set; }
public string Content { get; set; } = string.Empty;

public AgentTemplate()
{
Expand All @@ -20,3 +19,23 @@ public override string ToString()
return Name;
}
}

public class AgentTemplateConfig
{
public string Name { get; set; }

/// <summary>
/// Response format: json, xml, markdown, yaml, etc.
/// </summary>
[JsonPropertyName("response_format")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? ResponseFormat { get; set; }

[JsonPropertyName("llm_config")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AgentTemplateLlmConfig? LlmConfig { get; set; }
}

public class AgentTemplateLlmConfig : LlmConfigBase
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ public class LlmConfigBase : LlmProviderModel
/// <summary>
/// Llm maximum output tokens
/// </summary>
[JsonPropertyName("max_output_tokens")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? MaxOutputTokens { get; set; }

/// <summary>
/// Llm reasoning effort level
/// Llm reasoning effort level, thinking level
/// </summary>
[JsonPropertyName("reasoning_effort_level")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? ReasoningEffortLevel { get; set; }
}

Expand All @@ -22,4 +26,7 @@ public class LlmProviderModel
[JsonPropertyName("model")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Model { get; set; }

[JsonIgnore]
public bool IsValid => !string.IsNullOrEmpty(Provider) && !string.IsNullOrEmpty(Model);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
Task<List<User>> GetUsersByAffiliateId(string affiliateId) => throw new NotImplementedException();
Task<User?> GetUserByUserName(string userName) => throw new NotImplementedException();
Task UpdateUserName(string userId, string userName) => throw new NotImplementedException();
Task<Dashboard?> GetDashboard(string id = null) => throw new NotImplementedException();

Check warning on line 51 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 51 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 51 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 51 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 51 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 51 in src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.
Task CreateUser(User user) => throw new NotImplementedException();
Task UpdateExistUser(string userId, User user) => throw new NotImplementedException();
Task UpdateUserVerified(string userId) => throw new NotImplementedException();
Expand Down Expand Up @@ -88,6 +88,8 @@
=> throw new NotImplementedException();
Task<string> GetAgentTemplate(string agentId, string templateName)
=> throw new NotImplementedException();
Task<AgentTemplate> GetAgentTemplateDetail(string agentId, string templateName)
=> throw new NotImplementedException();
Task<bool> PatchAgentTemplate(string agentId, AgentTemplate template)
=> throw new NotImplementedException();
Task<bool> UpdateAgentLabels(string agentId, List<string> labels)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task<Agent> CreateAgent(Agent agent)
var userService = _services.GetRequiredService<IUserService>();
var auth = await userService.GetUserAuthorizations();

await _db.BulkInsertAgents(new List<Agent> { agentRecord });
await _db.BulkInsertAgents([agentRecord]);
if (auth.IsAdmin || auth.Permissions.Contains(UserPermission.CreateAgent))
{
await _db.BulkInsertUserAgents(new List<UserAgent>
Expand All @@ -39,7 +39,7 @@ await _db.BulkInsertUserAgents(new List<UserAgent>
{
UserId = user.Id,
AgentId = agentRecord.Id,
Actions = new List<string> { UserAction.Edit, UserAction.Train, UserAction.Evaluate, UserAction.Chat },
Actions = [UserAction.Edit, UserAction.Train, UserAction.Evaluate, UserAction.Chat],
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
}
Expand Down Expand Up @@ -98,20 +98,51 @@ private List<AgentTemplate> GetTemplatesFromFile(string fileDir)
var templateDir = Path.Combine(fileDir, "templates");
if (!Directory.Exists(templateDir)) return templates;

// Load template configs
var configs = GetAgentTemplateConfigs(fileDir);

foreach (var file in Directory.GetFiles(templateDir))
{
var extension = Path.GetExtension(file).Substring(1);
if (extension.IsEqualTo(_agentSettings.TemplateFormat))
{
var name = Path.GetFileNameWithoutExtension(file);
var content = File.ReadAllText(file);
templates.Add(new AgentTemplate(name, content));
var template = new AgentTemplate(name, content);
var config = configs.FirstOrDefault(x => x.Name.IsEqualTo(name));
if (config != null)
{
template.ResponseFormat = config.ResponseFormat;
template.LlmConfig = config.LlmConfig;
}
templates.Add(template);
}
}

return templates;
}

private IEnumerable<AgentTemplateConfig> GetAgentTemplateConfigs(string baseDir)
{
var configFile = Path.Combine(baseDir, "template_configs.json");
var configs = new List<AgentTemplateConfig>();

try
{
if (File.Exists(configFile))
{
var configJson = File.ReadAllText(configFile);
configs = JsonSerializer.Deserialize<List<AgentTemplateConfig>>(configJson, _options.JsonSerializerOptions) ?? [];
}
return configs;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error when loading template configs in {configFile}", configFile);
return configs;
}
}

private List<FunctionDef> GetFunctionsFromFile(string fileDir)
{
var functions = new List<FunctionDef>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@ private void AddDefaultInstruction(Agent agent, string instruction)
agent.ChannelInstructions = instructions;
}

#if !DEBUG
[SharpCache(10, perInstanceCache: true)]
#endif
public async Task<AgentTemplate?> GetAgentTemplateDetail(string agentId, string templateName)
{
var template = await _db.GetAgentTemplateDetail(agentId, templateName);
if (template == null)
{
return template;
}

if (template.LlmConfig == null)
{
var agent = await _db.GetAgent(agentId);
if (!string.IsNullOrEmpty(agent?.LlmConfig?.Provider)
&& !string.IsNullOrEmpty(agent?.LlmConfig?.Model))
{
template.LlmConfig = new AgentTemplateLlmConfig
{
Provider = agent.LlmConfig.Provider,
Model = agent.LlmConfig.Model,
MaxOutputTokens = agent.LlmConfig.MaxOutputTokens,
ReasoningEffortLevel = agent.LlmConfig.ReasoningEffortLevel
};
}
}

return template;
}

public async Task InheritAgent(Agent agent)
{
if (string.IsNullOrWhiteSpace(agent?.InheritAgentId))
Expand Down
4 changes: 4 additions & 0 deletions src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions\human_intervention_needed.json" />

<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\template_configs.json" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instructions\instruction.liquid" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\functions\get_weather.json" />
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\functions\get_fun_events.json" />
Expand Down Expand Up @@ -131,6 +132,9 @@
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\agent.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\template_configs.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,31 @@ private async Task<InstructResult> RunLlm(
var result = string.Empty;

// Render prompt
var prompt = string.IsNullOrEmpty(templateName) ?
agentService.RenderInstruction(agent) :
agentService.RenderTemplate(agent, templateName);
var prompt = string.Empty;
var llmConfig = agent.LlmConfig;

if (!string.IsNullOrEmpty(templateName))
{
prompt = agentService.RenderTemplate(agent, templateName);
var templateLlmConfig = agent.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName))?.LlmConfig;
if (templateLlmConfig?.IsValid == true)
{
llmConfig = new AgentLlmConfig
{
Provider = templateLlmConfig.Provider,
Model = templateLlmConfig.Model,
MaxOutputTokens = templateLlmConfig.MaxOutputTokens,
ReasoningEffortLevel = templateLlmConfig.ReasoningEffortLevel
};
}
}
else
{
prompt = agentService.RenderInstruction(agent);
}

var completer = CompletionProvider.GetCompletion(_services,
agentConfig: agent.LlmConfig);
agentConfig: llmConfig);

if (completer is ITextCompletion textCompleter)
{
Expand Down Expand Up @@ -292,7 +311,7 @@ private async Task<InstructResult> RunLlm(
}
else
{
result = await GetChatCompletion(chatCompleter, agent, instruction, prompt, message.MessageId, files);
result = await GetChatCompletion(chatCompleter, agent, instruction, prompt, message.MessageId, llmConfig, files);
}

// Repair JSON format if needed
Expand Down Expand Up @@ -343,14 +362,15 @@ private async Task<string> GetChatCompletion(
string instruction,
string text,
string messageId,
AgentLlmConfig? llmConfig = null,
IEnumerable<InstructFileModel>? files = null)
{
var result = await chatCompleter.GetChatCompletions(new Agent
{
Id = agent.Id,
Name = agent.Name,
Instruction = instruction,
LlmConfig = agent.LlmConfig
LlmConfig = llmConfig ?? agent.LlmConfig
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public partial class InstructService
private async Task<Agent> BuildInnerAgent(InstructOptions? options)
{
Agent? agent = null;
AgentLlmConfig? llmConfig = null;
string? instruction = null;

if (!string.IsNullOrWhiteSpace(options?.AgentId))
Expand All @@ -65,8 +66,19 @@ private async Task<Agent> BuildInnerAgent(InstructOptions? options)

if (!string.IsNullOrWhiteSpace(options?.TemplateName))
{
var template = agent?.Templates?.FirstOrDefault(x => x.Name == options.TemplateName)?.Content ?? string.Empty;
instruction = BuildInstruction(template, options?.Data ?? []);
var template = agent?.Templates?.FirstOrDefault(x => x.Name == options.TemplateName);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Case-sensitive templatename match 📘 Rule violation ≡ Correctness

Template selection compares identifier-like names with ==, which is case-sensitive and can behave
inconsistently for identifier matching. This violates the requirement to use ordinal,
case-insensitive comparisons for identifiers.
Agent Prompt
## Issue description
Template lookup uses a case-sensitive equality operator for an identifier-like string (`TemplateName`), risking mismatches.

## Issue Context
Identifier comparisons must be culture-invariant and case-insensitive.

## Fix Focus Areas
- src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs[69-69]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

instruction = BuildInstruction(template?.Content ?? string.Empty, options?.Data ?? []);
var templateLlmConfig = template?.LlmConfig;
if (templateLlmConfig?.IsValid == true)
{
llmConfig = new AgentLlmConfig
{
Provider = templateLlmConfig.Provider,
Model = templateLlmConfig.Model,
MaxOutputTokens = templateLlmConfig.MaxOutputTokens,
ReasoningEffortLevel = templateLlmConfig.ReasoningEffortLevel
};
}
}
}

Expand All @@ -75,7 +87,7 @@ private async Task<Agent> BuildInnerAgent(InstructOptions? options)
Id = agent?.Id ?? Guid.Empty.ToString(),
Name = agent?.Name ?? "Unknown",
Instruction = instruction,
LlmConfig = agent?.LlmConfig ?? new()
LlmConfig = llmConfig ?? agent?.LlmConfig ?? new()
};
}

Expand Down
Loading
Loading