add support for chat GPT
This commit is contained in:
parent
275795a4db
commit
7a5c027f8f
5 changed files with 498 additions and 1 deletions
15
Dockerfile
15
Dockerfile
|
|
@ -101,7 +101,20 @@ ENV basic_Username='' \
|
|||
rlv_HideStatusOutput='false' \
|
||||
relay_count='0' \
|
||||
relay_HideStatusOutput='false' \
|
||||
relay_UseShortEncoder='false'
|
||||
relay_UseShortEncoder='false' \
|
||||
chatgpt_Enabled='false' \
|
||||
chatgpt_ApiKey='none' \
|
||||
chatgpt_OrganizationId='none' \
|
||||
chatgpt_AllowImReplys='false' \
|
||||
chatgpt_ImReplyFriendsOnly='true' \
|
||||
chatgpt_ImReplyRateLimiter='3' \
|
||||
chatgpt_AllowGroupReplys='false' \
|
||||
chatgpt_GroupReplyForGroup='false' \
|
||||
chatgpt_GroupReplyRateLimiter='3' \
|
||||
chatgpt_LocalchatReply='false' \
|
||||
chatgpt_LocalchatRateLimiter='3' \
|
||||
chatgpt_UseModel='gpt3' \
|
||||
chatgpt_ChatHistoryMessages='5'
|
||||
|
||||
|
||||
EXPOSE 80
|
||||
|
|
|
|||
81
SecondBotEvents/Config/ChatGptConfig.cs
Normal file
81
SecondBotEvents/Config/ChatGptConfig.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SecondBotEvents.Config
|
||||
{
|
||||
public class ChatGptConfig : Config
|
||||
{
|
||||
public ChatGptConfig(bool fromENV, string fromFolder = "") : base(fromENV, fromFolder) { }
|
||||
protected override void MakeSettings()
|
||||
{
|
||||
filename = "chatgpt";
|
||||
settings.Add("Enabled");
|
||||
settings.Add("ApiKey");
|
||||
settings.Add("OrganizationId");
|
||||
settings.Add("AllowImReplys");
|
||||
settings.Add("ImReplyFriendsOnly");
|
||||
settings.Add("ImReplyRateLimiter");
|
||||
settings.Add("AllowGroupReplys");
|
||||
settings.Add("GroupReplyForGroup");
|
||||
settings.Add("GroupReplyRateLimiter");
|
||||
settings.Add("LocalchatReply");
|
||||
settings.Add("LocalchatRateLimiter");
|
||||
settings.Add("UseModel");
|
||||
settings.Add("ChatHistoryMessages");
|
||||
}
|
||||
|
||||
public int GetImReplyRateLimiter()
|
||||
{
|
||||
return ReadSettingAsInt("ImReplyRateLimiter", 3);
|
||||
}
|
||||
public int GetGroupReplyRateLimiter()
|
||||
{
|
||||
return ReadSettingAsInt("GroupReplyRateLimiter", 3);
|
||||
}
|
||||
public int GetLocalchatRateLimiter()
|
||||
{
|
||||
return ReadSettingAsInt("LocalchatRateLimiter", 3);
|
||||
}
|
||||
public int GetChatHistoryMessages()
|
||||
{
|
||||
return ReadSettingAsInt("ChatHistoryMessages", 5);
|
||||
}
|
||||
public string GetUseModel()
|
||||
{
|
||||
return ReadSettingAsString("UseModel", "gpt3");
|
||||
}
|
||||
public bool GetEnabled()
|
||||
{
|
||||
return ReadSettingAsBool("Enabled");
|
||||
}
|
||||
public string GetApiKey()
|
||||
{
|
||||
return ReadSettingAsString("ApiKey","none");
|
||||
}
|
||||
public string GetOrganizationId()
|
||||
{
|
||||
return ReadSettingAsString("OrganizationId","none");
|
||||
}
|
||||
public bool GetAllowImReplys()
|
||||
{
|
||||
return ReadSettingAsBool("AllowImReplys");
|
||||
}
|
||||
public bool GetImReplyFriendsOnly()
|
||||
{
|
||||
return ReadSettingAsBool("ImReplyFriendsOnly");
|
||||
}
|
||||
public bool GetAllowGroupReplys()
|
||||
{
|
||||
return ReadSettingAsBool("AllowGroupReplys");
|
||||
}
|
||||
public string GetGroupReplyForGroup()
|
||||
{
|
||||
return ReadSettingAsString("GroupReplyForGroup","none");
|
||||
}
|
||||
public bool GetLocalchatReply()
|
||||
{
|
||||
return ReadSettingAsBool("LocalchatReply");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,8 @@ namespace SecondBotEvents
|
|||
public HomeboundService HomeboundService { get { return (HomeboundService)GetService("HomeboundService"); } }
|
||||
public EventsService EventsService { get { return (EventsService)GetService("EventsService"); } }
|
||||
public DialogService DialogService { get { return (DialogService)GetService("DialogService"); } }
|
||||
|
||||
public ChatGptService ChatGptService { get { return (ChatGptService)GetService("ChatGptService"); } }
|
||||
|
||||
public TriggerOnEventService TriggerOnEventService { get { return (TriggerOnEventService)GetService("TriggerOnEventService"); } }
|
||||
|
||||
|
|
@ -167,6 +169,7 @@ namespace SecondBotEvents
|
|||
RegisterService("CustomCommandsService");
|
||||
RegisterService("TriggerOnEventService");
|
||||
RegisterService("RelayService");
|
||||
RegisterService("ChatGptService");
|
||||
if (BotClient.IsLoaded() == false)
|
||||
{
|
||||
LogFormater.Info("Config is not loaded :(");
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<None Remove="Services\template.html" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Betalgo.OpenAI" Version="8.6.2" />
|
||||
<PackageReference Include="Discord.Net" Version="3.15.3" />
|
||||
<PackageReference Include="Discord.Net.WebSocket" Version="3.15.3" />
|
||||
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
||||
|
|
|
|||
399
SecondBotEvents/Services/ChatGptService.cs
Normal file
399
SecondBotEvents/Services/ChatGptService.cs
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenMetaverse;
|
||||
using SecondBotEvents.Config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Net.Http;
|
||||
using OpenMetaverse.ImportExport.Collada14;
|
||||
using OpenMetaverse.Packets;
|
||||
using OpenAI.Managers;
|
||||
using OpenAI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenAI.ObjectModels.RequestModels;
|
||||
using OpenAI.ObjectModels;
|
||||
using OpenAI.ObjectModels.ResponseModels;
|
||||
|
||||
namespace SecondBotEvents.Services
|
||||
{
|
||||
public class ChatGptService : BotServices
|
||||
{
|
||||
public ChatGptConfig myConfig = null;
|
||||
public ChatGptService(EventsSecondBot setMaster) : base(setMaster)
|
||||
{
|
||||
myConfig = new ChatGptConfig(master.fromEnv, master.fromFolder);
|
||||
if (myConfig.GetEnabled() == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected int chatHistorySize = 5;
|
||||
protected int localchatRateLimit = 3;
|
||||
protected int groupchatRateLimit = 3;
|
||||
protected int imchatRateLimit = 3;
|
||||
public override void Start(bool updateEnabled = false, bool setEnabledTo = false)
|
||||
{
|
||||
if (updateEnabled == true)
|
||||
{
|
||||
myConfig.setEnabled(setEnabledTo);
|
||||
}
|
||||
if (myConfig.GetEnabled() == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
chatHistorySize = inrange(3,10,myConfig.GetChatHistoryMessages());
|
||||
localchatRateLimit = inrange(1, 10, myConfig.GetLocalchatRateLimiter());
|
||||
groupchatRateLimit = inrange(1, 10, myConfig.GetGroupReplyRateLimiter());
|
||||
imchatRateLimit = inrange(1, 10, myConfig.GetImReplyRateLimiter());
|
||||
running = true;
|
||||
master.BotClientNoticeEvent += BotClientRestart;
|
||||
}
|
||||
|
||||
protected int inrange(int value, int min, int max)
|
||||
{
|
||||
if (value < min) return min;
|
||||
else if(value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
|
||||
public override string Status()
|
||||
{
|
||||
if (myConfig == null)
|
||||
{
|
||||
return "No Config";
|
||||
}
|
||||
else if (myConfig.GetEnabled() == false)
|
||||
{
|
||||
return "Disabled";
|
||||
}
|
||||
upkeep();
|
||||
if (chatHistoryAI.Count() > 0)
|
||||
{
|
||||
return "Enabled running: " + chatHistoryAI.Count().ToString() + " chat windows";
|
||||
}
|
||||
return "Enabled No chat history";
|
||||
}
|
||||
|
||||
readonly string[] hard_blocked_agents = new[] { "secondlife", "second life" };
|
||||
protected void BotLocalchat(object o, ChatEventArgs e)
|
||||
{
|
||||
if (e.SourceID == GetClient().Self.AgentID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (myConfig.GetLocalchatReply() == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (e.Type)
|
||||
{
|
||||
case ChatType.OwnerSay:
|
||||
case ChatType.Whisper:
|
||||
case ChatType.Normal:
|
||||
case ChatType.Shout:
|
||||
case ChatType.RegionSayTo:
|
||||
{
|
||||
if (hard_blocked_agents.Contains(e.FromName.ToLowerInvariant()) == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (e.SourceType == ChatSourceType.Object)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (e.SourceType == ChatSourceType.System)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (e.Type == ChatType.OwnerSay)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// trigger localchat
|
||||
GetAiReply(localchatRateLimit, UUID.Zero, e.SourceID, e.FromName, e.Message, false, false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void BotImMessage(object o, InstantMessageEventArgs e)
|
||||
{
|
||||
if (e.IM.FromAgentID == GetClient().Self.AgentID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (e.IM.Dialog)
|
||||
{
|
||||
case InstantMessageDialog.MessageFromObject:
|
||||
{
|
||||
// object IM
|
||||
break;
|
||||
}
|
||||
case InstantMessageDialog.MessageFromAgent: // shared with SessionSend
|
||||
case InstantMessageDialog.SessionSend:
|
||||
{
|
||||
if (master.DataStoreService.GetIsGroup(e.IM.IMSessionID) == false)
|
||||
{
|
||||
// trigger avatar IM
|
||||
if (myConfig.GetAllowImReplys() == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (myConfig.GetImReplyFriendsOnly() == true)
|
||||
{
|
||||
if (GetClient().Friends.FriendList.ContainsKey(e.IM.FromAgentID) == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// request GPT reply to avatar IM
|
||||
GetAiReply(imchatRateLimit, e.IM.FromAgentID, e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message, true);
|
||||
break;
|
||||
}
|
||||
// trigger group IM
|
||||
if (myConfig.GetAllowGroupReplys() == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (myConfig.GetGroupReplyForGroup() != e.IM.IMSessionID.ToString())
|
||||
{
|
||||
break;
|
||||
}
|
||||
GetAiReply(groupchatRateLimit, e.IM.FromAgentID, e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message, false, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<UUID, List<KeyValuePair<string, string>>> chatHistoryAI = new Dictionary<UUID, List<KeyValuePair<string, string>>>();
|
||||
Dictionary<UUID, long> ChatHistoryLastAccessed = new Dictionary<UUID, long>();
|
||||
Dictionary<UUID, long> ChatRateLimiter = new Dictionary<UUID, long>();
|
||||
|
||||
protected long lastUpkeep = 0;
|
||||
protected void upkeep()
|
||||
{
|
||||
long dif = SecondbotHelpers.UnixTimeNow() - lastUpkeep;
|
||||
if (dif < 30)
|
||||
{
|
||||
return; // upkeep not needed
|
||||
}
|
||||
lastUpkeep = SecondbotHelpers.UnixTimeNow();
|
||||
// check for expired chat windows (longer than 2 mins from last message)
|
||||
|
||||
}
|
||||
|
||||
protected async void GetAiReply(int ratelimiter, UUID replyTo, UUID user, string name, string message, bool avatarchat = false, bool groupchat = false)
|
||||
{
|
||||
bool allowedChat = true;
|
||||
lock(ChatRateLimiter)
|
||||
{
|
||||
if (ChatRateLimiter.ContainsKey(user) == false)
|
||||
{
|
||||
ChatRateLimiter.Add(user, 0);
|
||||
}
|
||||
long dif = SecondbotHelpers.UnixTimeNow() - ChatRateLimiter[user];
|
||||
if(dif <= ratelimiter)
|
||||
{
|
||||
allowedChat = false;
|
||||
}
|
||||
}
|
||||
if(allowedChat == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lock (ChatRateLimiter)
|
||||
{
|
||||
ChatRateLimiter[user] = SecondbotHelpers.UnixTimeNow() + 1;
|
||||
}
|
||||
List<ChatMessage> messages = new List<ChatMessage>();
|
||||
lock (chatHistoryAI) lock (ChatHistoryLastAccessed)
|
||||
{
|
||||
if (chatHistoryAI.ContainsKey(user) == false)
|
||||
{
|
||||
chatHistoryAI.Add(user, new List<KeyValuePair<string, string>>());
|
||||
}
|
||||
if (ChatHistoryLastAccessed.ContainsKey(user) == false)
|
||||
{
|
||||
ChatHistoryLastAccessed.Add(user, SecondbotHelpers.UnixTimeNow());
|
||||
}
|
||||
// trim history
|
||||
List<KeyValuePair<string, string>> history = chatHistoryAI[user];
|
||||
if (history.Count() == 0)
|
||||
{
|
||||
if (avatarchat == true)
|
||||
{
|
||||
history.Add(new KeyValuePair<string, string>("system", "You are " + GetClient().Self.FirstName + ", A secondlife assistant. you are talking to " + name + "."));
|
||||
}
|
||||
else if (groupchat == true)
|
||||
{
|
||||
history.Add(new KeyValuePair<string, string>("system", "You are " + GetClient().Self.FirstName + ", A secondlife assistant. you are talking to a group of people."));
|
||||
}
|
||||
else
|
||||
{
|
||||
history.Add(new KeyValuePair<string, string>("system", "You are " + GetClient().Self.FirstName + ", A secondlife assistant. you are talking to people in a public place."));
|
||||
}
|
||||
}
|
||||
history.Add(new KeyValuePair<string, string>("user", "" + name + " says " + message));
|
||||
while (history.Count() > chatHistorySize + 1)
|
||||
{
|
||||
history.RemoveAt(1);
|
||||
}
|
||||
chatHistoryAI[user] = history;
|
||||
ChatHistoryLastAccessed[user] = SecondbotHelpers.UnixTimeNow();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (KeyValuePair<string, string> entry in history)
|
||||
{
|
||||
string role = entry.Key.ToLower();
|
||||
if (role != "system" && role != "user" && role != "assistant")
|
||||
{
|
||||
throw new ArgumentException("Invalid role provided in history. Role must be 'system', 'user', or 'assistant'.");
|
||||
}
|
||||
string thismessage = entry.Value;
|
||||
if(thismessage == null)
|
||||
{
|
||||
throw new ArgumentException("Message can not be empty");
|
||||
}
|
||||
if (role == "system") messages.Add(ChatMessage.FromSystem(thismessage));
|
||||
else if (role == "user") messages.Add(ChatMessage.FromUser(thismessage));
|
||||
else if (role == "assistant") messages.Add(ChatMessage.FromAssistant(thismessage));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogFormater.Warn("error building AI request:" + ex.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(messages.Count() == 0)
|
||||
{
|
||||
LogFormater.Warn("No messages given");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
string replyMessage = "";
|
||||
var openAiService = new OpenAIService(new OpenAiOptions()
|
||||
{
|
||||
ApiKey = myConfig.GetApiKey(),
|
||||
});
|
||||
if((myConfig.GetOrganizationId() != null) && (myConfig.GetOrganizationId() != "none"))
|
||||
{
|
||||
openAiService = new OpenAIService(new OpenAiOptions()
|
||||
{
|
||||
ApiKey = myConfig.GetApiKey(),
|
||||
Organization = myConfig.GetOrganizationId(),
|
||||
});
|
||||
}
|
||||
ChatCompletionCreateResponse completionResult = null;
|
||||
if (myConfig.GetUseModel() == "gpt3")
|
||||
{
|
||||
completionResult = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
|
||||
{
|
||||
Messages = messages,
|
||||
Model = Models.Gpt_3_5_Turbo
|
||||
});
|
||||
}
|
||||
else if (myConfig.GetUseModel() == "gpt-3.5-turbo")
|
||||
{
|
||||
completionResult = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
|
||||
{
|
||||
Messages = messages,
|
||||
Model = Models.Gpt_3_5_Turbo
|
||||
});
|
||||
}
|
||||
else if(myConfig.GetUseModel() == "gpt-4-mini")
|
||||
{
|
||||
completionResult = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
|
||||
{
|
||||
Messages = messages,
|
||||
Model = Models.Gpt_4o_mini
|
||||
});
|
||||
}
|
||||
else if (myConfig.GetUseModel() == "gpt-4-turbo")
|
||||
{
|
||||
completionResult = await openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest
|
||||
{
|
||||
Messages = messages,
|
||||
Model = Models.Gpt_4_turbo
|
||||
});
|
||||
}
|
||||
if(completionResult == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (completionResult.Successful)
|
||||
{
|
||||
replyMessage = completionResult.Choices.First().Message.Content;
|
||||
}
|
||||
if (replyMessage != "")
|
||||
{
|
||||
lock (chatHistoryAI) lock (ChatHistoryLastAccessed)
|
||||
{
|
||||
List<KeyValuePair<string, string>> history = chatHistoryAI[user];
|
||||
history.Add(new KeyValuePair<string, string>("assistant", replyMessage));
|
||||
chatHistoryAI[user] = history;
|
||||
ChatHistoryLastAccessed[user] = SecondbotHelpers.UnixTimeNow();
|
||||
}
|
||||
if (avatarchat == true)
|
||||
{
|
||||
GetClient().Self.InstantMessage(replyTo, replyMessage);
|
||||
}
|
||||
else if (groupchat == true)
|
||||
{
|
||||
GetClient().Self.InstantMessageGroup(replyTo, replyMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetClient().Self.Chat(replyMessage, 0, ChatType.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle other potential errors
|
||||
Console.WriteLine("An error occurred: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void BotClientRestart(object o, BotClientNotice e)
|
||||
{
|
||||
LogFormater.Info("ChatGpt [Attached to new client]");
|
||||
GetClient().Network.LoggedOut += BotLoggedOut;
|
||||
GetClient().Network.SimConnected += BotLoggedIn;
|
||||
}
|
||||
|
||||
protected void BotLoggedOut(object o, LoggedOutEventArgs e)
|
||||
{
|
||||
GetClient().Network.SimConnected += BotLoggedIn;
|
||||
LogFormater.Info("ChatGpt [Waiting for connect]");
|
||||
}
|
||||
|
||||
protected void BotLoggedIn(object o, SimConnectedEventArgs e)
|
||||
{
|
||||
GetClient().Network.SimConnected -= BotLoggedIn;
|
||||
GetClient().Self.IM += BotImMessage;
|
||||
GetClient().Self.ChatFromSimulator += BotLocalchat;
|
||||
LogFormater.Info("ChatGpt [accepting chat input]");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue