diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs
index 0669a267..f1d17b93 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs
@@ -4,5 +4,16 @@ namespace BotSharp.Abstraction.Instructs;
public interface IInstructService
{
+ ///
+ /// Execute completion by using specified instruction or template
+ ///
+ /// Agent (static agent)
+ /// Additional message provided by user
+ /// Template name
+ /// System prompt
+ ///
Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null);
+
+
+ Task Instruct(string instruction, string agentId, InstructOptions options) where T : class;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs
new file mode 100644
index 00000000..46c6c900
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructOptions.cs
@@ -0,0 +1,29 @@
+namespace BotSharp.Abstraction.Instructs.Models;
+
+public class InstructOptions
+{
+ ///
+ /// Llm provider
+ ///
+ public string Provider { get; set; } = null!;
+
+ ///
+ /// Llm model
+ ///
+ public string Model { get; set; } = null!;
+
+ ///
+ /// Conversation id. When this field is not null, it will get dialogs from conversation.
+ ///
+ public string? ConversationId { get; set; }
+
+ ///
+ /// The single message. It can be append to the whole dialogs or sent alone.
+ ///
+ public string? Message { get; set; }
+
+ ///
+ /// Data to fill in prompt
+ ///
+ public Dictionary Data { get; set; } = new();
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/GenerateKnowledge.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/GenerateKnowledge.cs
deleted file mode 100644
index a5698dc0..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/GenerateKnowledge.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace BotSharp.Abstraction.Knowledges.Models;
-
-public class GenerateKnowledge
-{
- [JsonPropertyName("question")]
- public string Question { get; set; } = string.Empty;
-
- [JsonPropertyName("answer")]
- public string Answer { get; set; } = string.Empty;
-
- [JsonPropertyName("refined_collection")]
- public string RefinedCollection { get; set; } = string.Empty;
-
- [JsonPropertyName("refine_answer")]
- public Boolean RefineAnswer { get; set; } = false;
-
- [JsonPropertyName("existing_answer")]
- public string ExistingAnswer { get; set; } = string.Empty;
-}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
index d74f71ac..7a3d1d5d 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
@@ -372,10 +372,10 @@ public class ConversationStateService : IConversationStateService, IDisposable
private bool CheckArgType(string name, string value)
{
var agentTypes = AgentService.AgentParameterTypes.SelectMany(p => p.Value).ToList();
- var filed = agentTypes.FirstOrDefault(t => t.Key == name);
- if (filed.Key != null)
+ var found = agentTypes.FirstOrDefault(t => t.Key == name);
+ if (found.Key != null)
{
- return filed.Value switch
+ return found.Value switch
{
"boolean" => bool.TryParse(value, out _),
"number" => long.TryParse(value, out _),
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Execute.cs
new file mode 100644
index 00000000..1d9dd1d0
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Execute.cs
@@ -0,0 +1,100 @@
+using BotSharp.Abstraction.Instructs;
+using BotSharp.Abstraction.Instructs.Models;
+using BotSharp.Abstraction.MLTasks;
+
+namespace BotSharp.Core.Instructs;
+
+public partial class InstructService
+{
+ public async Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
+ {
+ var agentService = _services.GetRequiredService();
+ Agent agent = await agentService.LoadAgent(agentId);
+
+ if (agent.Disabled)
+ {
+ var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
+ return new InstructResult
+ {
+ MessageId = message.MessageId,
+ Text = content
+ };
+ }
+
+ // Trigger before completion hooks
+ var hooks = _services.GetServices();
+ foreach (var hook in hooks)
+ {
+ if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
+ {
+ continue;
+ }
+
+ await hook.BeforeCompletion(agent, message);
+
+ // Interrupted by hook
+ if (message.StopCompletion)
+ {
+ return new InstructResult
+ {
+ MessageId = message.MessageId,
+ Text = message.Content
+ };
+ }
+ }
+
+ // Render prompt
+ var prompt = string.IsNullOrEmpty(templateName) ?
+ agentService.RenderedInstruction(agent) :
+ agentService.RenderedTemplate(agent, templateName);
+
+ var completer = CompletionProvider.GetCompletion(_services,
+ agentConfig: agent.LlmConfig);
+
+ var response = new InstructResult
+ {
+ MessageId = message.MessageId
+ };
+ if (completer is ITextCompletion textCompleter)
+ {
+ var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId);
+ response.Text = result;
+ }
+ else if (completer is IChatCompletion chatCompleter)
+ {
+ if (instruction == "#TEMPLATE#")
+ {
+ instruction = prompt;
+ prompt = message.Content;
+ }
+
+ var result = await chatCompleter.GetChatCompletions(new Agent
+ {
+ Id = agentId,
+ Name = agent.Name,
+ Instruction = instruction
+ }, new List
+ {
+ new RoleDialogModel(AgentRole.User, prompt)
+ {
+ CurrentAgentId = agentId,
+ MessageId = message.MessageId
+ }
+ });
+ response.Text = result.Content;
+ }
+
+
+ foreach (var hook in hooks)
+ {
+ if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
+ {
+ continue;
+ }
+
+ await hook.AfterCompletion(agent, response);
+ }
+
+ return response;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Instruct.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Instruct.cs
new file mode 100644
index 00000000..40297400
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.Instruct.cs
@@ -0,0 +1,110 @@
+using BotSharp.Abstraction.Instructs.Models;
+using BotSharp.Abstraction.Templating;
+using System.Collections;
+using System.Reflection;
+
+namespace BotSharp.Core.Instructs;
+
+public partial class InstructService
+{
+ public async Task Instruct(string instruction, string agentId, InstructOptions options) where T : class
+ {
+ var prompt = GetPrompt(instruction, options.Data);
+ var response = await GetAiResponse(agentId, prompt, options);
+
+ if (string.IsNullOrWhiteSpace(response.Content)) return null;
+
+ var type = typeof(T);
+ T? result = null;
+
+ try
+ {
+ if (IsStringType(type))
+ {
+ result = response.Content as T;
+ }
+ else if (IsListType(type))
+ {
+ var text = response.Content.JsonArrayContent();
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ result = JsonSerializer.Deserialize(text, _options.JsonSerializerOptions);
+ }
+ }
+ else
+ {
+ var text = response.Content.JsonContent();
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ result = JsonSerializer.Deserialize(text, _options.JsonSerializerOptions);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when getting ai response, {ex.Message}\r\n{ex.InnerException}");
+ }
+
+ return result;
+ }
+
+ private string GetPrompt(string instruction, Dictionary data)
+ {
+ var render = _services.GetRequiredService();
+
+ return render.Render(instruction, data ?? new Dictionary());
+ }
+
+ private async Task GetAiResponse(string agentId, string prompt, InstructOptions options)
+ {
+ var agentService = _services.GetRequiredService();
+ var agent = await agentService.LoadAgent(agentId);
+
+ var localAgent = new Agent
+ {
+ Id = agentId,
+ Name = agent.Name,
+ Instruction = prompt,
+ TemplateDict = new()
+ };
+
+ var messages = BuildDialogs(options);
+ var completion = CompletionProvider.GetChatCompletion(_services, provider: options.Provider, model: options.Model);
+
+ return await completion.GetChatCompletions(localAgent, messages);
+ }
+
+ private List BuildDialogs(InstructOptions options)
+ {
+ var messages = new List();
+
+ if (!string.IsNullOrWhiteSpace(options.ConversationId))
+ {
+ var conv = _services.GetRequiredService();
+ var dialogs = conv.GetDialogHistory();
+ messages.AddRange(dialogs);
+ }
+
+ if (!string.IsNullOrWhiteSpace(options.Message))
+ {
+ messages.Add(new RoleDialogModel(AgentRole.User, options.Message));
+ }
+
+ return messages;
+ }
+
+ private bool IsStringType(Type? type)
+ {
+ if (type == null) return false;
+
+ return type == typeof(string);
+ }
+
+ private bool IsListType(Type? type)
+ {
+ if (type == null) return false;
+
+ var interfaces = type.GetTypeInfo().ImplementedInterfaces;
+ return type.IsArray || interfaces.Any(x => x.Name == typeof(IEnumerable).Name);
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs
index b05889e1..a1a31e0e 100644
--- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs
+++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs
@@ -1,118 +1,21 @@
-using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Instructs;
-using BotSharp.Abstraction.Instructs.Models;
-using BotSharp.Abstraction.MLTasks;
+using BotSharp.Abstraction.Options;
namespace BotSharp.Core.Instructs;
public partial class InstructService : IInstructService
{
private readonly IServiceProvider _services;
- private readonly ILogger _logger;
+ private readonly BotSharpOptions _options;
+ private readonly ILogger _logger;
- public InstructService(IServiceProvider services, ILogger logger)
+ public InstructService(
+ IServiceProvider services,
+ BotSharpOptions options,
+ ILogger logger)
{
_services = services;
+ _options = options;
_logger = logger;
}
-
- ///
- /// Execute completion by using specified instruction or template
- ///
- /// Agent (static agent)
- /// Additional message provided by user
- /// Template name
- /// System prompt
- ///
- public async Task Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
- {
- var agentService = _services.GetRequiredService();
- Agent agent = await agentService.LoadAgent(agentId);
-
- if (agent.Disabled)
- {
- var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
- return new InstructResult
- {
- MessageId = message.MessageId,
- Text = content
- };
- }
-
- // Trigger before completion hooks
- var hooks = _services.GetServices();
- foreach (var hook in hooks)
- {
- if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
- {
- continue;
- }
-
- await hook.BeforeCompletion(agent, message);
-
- // Interrupted by hook
- if (message.StopCompletion)
- {
- return new InstructResult
- {
- MessageId = message.MessageId,
- Text = message.Content
- };
- }
- }
-
- // Render prompt
- var prompt = string.IsNullOrEmpty(templateName) ?
- agentService.RenderedInstruction(agent) :
- agentService.RenderedTemplate(agent, templateName);
-
- var completer = CompletionProvider.GetCompletion(_services,
- agentConfig: agent.LlmConfig);
-
- var response = new InstructResult
- {
- MessageId = message.MessageId
- };
- if (completer is ITextCompletion textCompleter)
- {
- var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId);
- response.Text = result;
- }
- else if (completer is IChatCompletion chatCompleter)
- {
- if (instruction == "#TEMPLATE#")
- {
- instruction = prompt;
- prompt = message.Content;
- }
-
- var result = await chatCompleter.GetChatCompletions(new Agent
- {
- Id = agentId,
- Name = agent.Name,
- Instruction = instruction
- }, new List
- {
- new RoleDialogModel(AgentRole.User, prompt)
- {
- CurrentAgentId = agentId,
- MessageId = message.MessageId
- }
- });
- response.Text = result.Content;
- }
-
-
- foreach (var hook in hooks)
- {
- if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
- {
- continue;
- }
-
- await hook.AfterCompletion(agent, response);
- }
-
- return response;
- }
}
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj b/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj
index 3216564e..283d8b32 100644
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj
@@ -1,4 +1,4 @@
-
+
$(TargetFramework)
@@ -21,7 +21,7 @@
-
+
@@ -42,7 +42,7 @@
PreserveNewest
-
+
PreserveNewest
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/GenerateKnowledgeFn.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/GenerateKnowledgeFn.cs
deleted file mode 100644
index 114ce7e4..00000000
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/GenerateKnowledgeFn.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using BotSharp.Abstraction.Templating;
-using BotSharp.Core.Infrastructures;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-
-namespace BotSharp.Plugin.KnowledgeBase.Functions;
-
-public class GenerateKnowledgeFn : IFunctionCallback
-{
- public string Name => "generate_knowledge";
-
- public string Indication => "generating knowledge";
-
- private readonly IServiceProvider _services;
- private readonly KnowledgeBaseSettings _settings;
-
- public GenerateKnowledgeFn(IServiceProvider services, KnowledgeBaseSettings settings)
- {
- _services = services;
- _settings = settings;
- }
-
- public async Task Execute(RoleDialogModel message)
- {
- var args = JsonSerializer.Deserialize(message.FunctionArgs ?? "{}");
- var agentService = _services.GetRequiredService();
- var llmAgent = await agentService.GetAgent(BuiltInAgentId.Planner);
- var refineKnowledge = args.RefinedCollection;
- String generateKnowledgePrompt;
- if (args.RefineAnswer == true)
- {
- generateKnowledgePrompt = await GetRefineKnowledgePrompt(args.Question, args.Answer, args.ExistingAnswer);
- }
- else
- {
- generateKnowledgePrompt = await GetGenerateKnowledgePrompt(args.Question, args.Answer);
- }
- var agent = new Agent
- {
- Id = message.CurrentAgentId ?? string.Empty,
- Name = "knowledge_generator",
- Instruction = generateKnowledgePrompt,
- LlmConfig = llmAgent.LlmConfig
- };
- var response = await GetAiResponse(agent);
- message.Data = response.Content.JsonArrayContent();
- message.Content = response.Content;
- return true;
- }
-
- private async Task GetGenerateKnowledgePrompt(string userQuestions, string sqlAnswer)
- {
- var agentService = _services.GetRequiredService();
- var render = _services.GetRequiredService();
-
- var agent = await agentService.GetAgent(BuiltInAgentId.Learner);
- var template = agent.Templates.FirstOrDefault(x => x.Name == "knowledge.generation")?.Content ?? string.Empty;
-
- return render.Render(template, new Dictionary
- {
- { "user_questions", userQuestions },
- { "sql_answer", sqlAnswer },
- });
- }
- private async Task GetRefineKnowledgePrompt(string userQuestion, string sqlAnswer, string existionAnswer)
- {
- var agentService = _services.GetRequiredService();
- var render = _services.GetRequiredService();
-
- var agent = await agentService.GetAgent(BuiltInAgentId.Learner);
- var template = agent.Templates.FirstOrDefault(x => x.Name == "knowledge.generation.refine")?.Content ?? string.Empty;
-
- return render.Render(template, new Dictionary
- {
- { "user_question", userQuestion },
- { "new_answer", sqlAnswer },
- { "existing_answer", existionAnswer}
- });
- }
- private async Task GetAiResponse(Agent agent)
- {
- var text = "Generate question and answer pair";
- var message = new RoleDialogModel(AgentRole.User, text);
-
- var completion = CompletionProvider.GetChatCompletion(_services,
- provider: agent.LlmConfig.Provider,
- model: agent.LlmConfig.Model);
-
- return await completion.GetChatCompletions(agent, new List { message });
- }
-}
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.liquid b/src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.plain.liquid
similarity index 100%
rename from src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.liquid
rename to src/Plugins/BotSharp.Plugin.KnowledgeBase/data/agents/01acc3e5-0af7-49e6-ad7a-a760bd12dc40/templates/knowledge.generation.plain.liquid