Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add instruct #688

Merged
merged 3 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -4,5 +4,23 @@ namespace BotSharp.Abstraction.Instructs;

public interface IInstructService
{
/// <summary>
/// Execute completion by using specified instruction or template
/// </summary>
/// <param name="agentId">Agent (static agent)</param>
/// <param name="message">Additional message provided by user</param>
/// <param name="templateName">Template name</param>
/// <param name="instruction">System prompt</param>
/// <returns></returns>
Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null);

/// <summary>
/// A generic way to execute completion by using specified instruction or template
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="instruction"></param>
/// <param name="agentId"></param>
/// <param name="options"></param>
/// <returns></returns>
Task<T?> Instruct<T>(string instruction, string agentId, InstructOptions options) where T : class;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace BotSharp.Abstraction.Instructs.Models;

public class InstructOptions
{
/// <summary>
/// Llm provider
/// </summary>
public string Provider { get; set; } = null!;

/// <summary>
/// Llm model
/// </summary>
public string Model { get; set; } = null!;

/// <summary>
/// Conversation id. When this field is not null, it will get dialogs from conversation.
/// </summary>
public string? ConversationId { get; set; }

/// <summary>
/// The single message. It can be append to the whole dialogs or sent alone.
/// </summary>
public string? Message { get; set; }

/// <summary>
/// Data to fill in prompt
/// </summary>
public Dictionary<string, object> Data { get; set; } = new();
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ public void SaveStateByArgs(JsonDocument args)
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 _),
Expand Down
100 changes: 100 additions & 0 deletions src/Infrastructure/BotSharp.Core/Instructs/InstructService.Execute.cs
Original file line number Diff line number Diff line change
@@ -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<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
{
var agentService = _services.GetRequiredService<IAgentService>();
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<IInstructHook>();
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<RoleDialogModel>
{
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;
}
}
110 changes: 110 additions & 0 deletions src/Infrastructure/BotSharp.Core/Instructs/InstructService.Instruct.cs
Original file line number Diff line number Diff line change
@@ -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<T?> Instruct<T>(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<T>(text, _options.JsonSerializerOptions);
}
}
else
{
var text = response.Content.JsonContent();
if (!string.IsNullOrWhiteSpace(text))
{
result = JsonSerializer.Deserialize<T>(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<string, object> data)
{
var render = _services.GetRequiredService<ITemplateRender>();

return render.Render(instruction, data ?? new Dictionary<string, object>());
}

private async Task<RoleDialogModel> GetAiResponse(string agentId, string prompt, InstructOptions options)
{
var agentService = _services.GetRequiredService<IAgentService>();
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<RoleDialogModel> BuildDialogs(InstructOptions options)
{
var messages = new List<RoleDialogModel>();

if (!string.IsNullOrWhiteSpace(options.ConversationId))
{
var conv = _services.GetRequiredService<IConversationService>();
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);
}
}
Loading