Merge branch 'v4' into 'v4'

Update GPT ChatterBot

See merge request Kwoth/nadekobot!313
This commit is contained in:
Kwoth
2024-01-16 09:12:14 +00:00
9 changed files with 140 additions and 47 deletions

View File

@@ -79,8 +79,12 @@ public class ChatterBotService : IExecOnMessage
case ChatBotImplementation.Gpt3: case ChatBotImplementation.Gpt3:
if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey)) if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey))
return new OfficialGpt3Session(_creds.Gpt3ApiKey, return new OfficialGpt3Session(_creds.Gpt3ApiKey,
_gcs.Data.ChatGpt.Model, _gcs.Data.ChatGpt.ModelName,
_gcs.Data.ChatGpt.ChatHistory,
_gcs.Data.ChatGpt.MaxTokens, _gcs.Data.ChatGpt.MaxTokens,
_gcs.Data.ChatGpt.MinTokens,
_gcs.Data.ChatGpt.PersonalityPrompt,
_client.CurrentUser.Username,
_httpFactory); _httpFactory);
Log.Information("Gpt3 will not work as the api key is missing."); Log.Information("Gpt3 will not work as the api key is missing.");
@@ -199,7 +203,7 @@ public class ChatterBotService : IExecOnMessage
} }
_ = channel.TriggerTypingAsync(); _ = channel.TriggerTypingAsync();
var response = await cbs.Think(message); var response = await cbs.Think(message, usrMsg.Author.ToString());
await channel.SendConfirmAsync(_eb, await channel.SendConfirmAsync(_eb,
title: null, title: null,
response.SanitizeMentions(true) response.SanitizeMentions(true)

View File

@@ -11,7 +11,13 @@ public class Gpt3Response
public class Choice public class Choice
{ {
public string Text { get; set; } [JsonPropertyName("message")]
public Message Message { get; init; }
}
public class Message {
[JsonPropertyName("content")]
public string Content { get; init; }
} }
public class Gpt3ApiRequest public class Gpt3ApiRequest
@@ -19,8 +25,8 @@ public class Gpt3ApiRequest
[JsonPropertyName("model")] [JsonPropertyName("model")]
public string Model { get; init; } public string Model { get; init; }
[JsonPropertyName("prompt")] [JsonPropertyName("messages")]
public string Prompt { get; init; } public List<GPTMessage> Messages { get; init; }
[JsonPropertyName("temperature")] [JsonPropertyName("temperature")]
public int Temperature { get; init; } public int Temperature { get; init; }
@@ -28,3 +34,13 @@ public class Gpt3ApiRequest
[JsonPropertyName("max_tokens")] [JsonPropertyName("max_tokens")]
public int MaxTokens { get; init; } public int MaxTokens { get; init; }
} }
public class GPTMessage
{
[JsonPropertyName("role")]
public string Role {get; init;}
[JsonPropertyName("content")]
public string Content {get; init;}
[JsonPropertyName("name")]
public string Name {get; init;}
}

View File

@@ -3,5 +3,5 @@ namespace NadekoBot.Modules.Games.Common.ChatterBot;
public interface IChatterBotSession public interface IChatterBotSession
{ {
Task<string> Think(string input); Task<string> Think(string input, string username);
} }

View File

@@ -18,7 +18,7 @@ public class OfficialCleverbotSession : IChatterBotSession
_httpFactory = factory; _httpFactory = factory;
} }
public async Task<string> Think(string input) public async Task<string> Think(string input, string username)
{ {
using var http = _httpFactory.CreateClient(); using var http = _httpFactory.CreateClient();
var dataString = await http.GetStringAsync(string.Format(QueryString, input, cs ?? "")); var dataString = await http.GetStringAsync(string.Format(QueryString, input, cs ?? ""));

View File

@@ -1,63 +1,101 @@
#nullable disable #nullable disable
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Net.Http.Json; using System.Net.Http.Json;
using SharpToken;
using Antlr.Runtime;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace NadekoBot.Modules.Games.Common.ChatterBot; namespace NadekoBot.Modules.Games.Common.ChatterBot;
public class OfficialGpt3Session : IChatterBotSession public class OfficialGpt3Session : IChatterBotSession
{ {
private string Uri private string Uri
=> $"https://api.openai.com/v1/completions"; => $"https://api.openai.com/v1/chat/completions";
private readonly string _apiKey; private readonly string _apiKey;
private readonly string _model; private readonly string _model;
private readonly int _maxHistory;
private readonly int _maxTokens; private readonly int _maxTokens;
private readonly int _minTokens;
private readonly string _nadekoUsername;
private readonly GptEncoding _encoding;
private List<GPTMessage> messages = new();
private readonly IHttpClientFactory _httpFactory; private readonly IHttpClientFactory _httpFactory;
public OfficialGpt3Session( public OfficialGpt3Session(
string apiKey, string apiKey,
Gpt3Model model, ChatGptModel model,
int chatHistory,
int maxTokens, int maxTokens,
int minTokens,
string personality,
string nadekoUsername,
IHttpClientFactory factory) IHttpClientFactory factory)
{ {
_apiKey = apiKey; _apiKey = apiKey;
_httpFactory = factory; _httpFactory = factory;
switch (model) switch (model)
{ {
case Gpt3Model.Ada001: case ChatGptModel.Gpt35Turbo:
_model = "text-ada-001"; _model = "gpt-3.5-turbo";
break; break;
case Gpt3Model.Babbage001: case ChatGptModel.Gpt4:
_model = "text-babbage-001"; _model = "gpt-4";
break; break;
case Gpt3Model.Curie001: case ChatGptModel.Gpt432k:
_model = "text-curie-001"; _model = "gpt-4-32k";
break;
case Gpt3Model.Davinci003:
_model = "text-davinci-003";
break; break;
} }
_maxHistory = chatHistory;
_maxTokens = maxTokens; _maxTokens = maxTokens;
_minTokens = minTokens;
_nadekoUsername = nadekoUsername;
_encoding = GptEncoding.GetEncodingForModel(_model);
messages.Add(new GPTMessage(){Role = "user", Content = personality, Name = _nadekoUsername});
} }
public async Task<string> Think(string input) public async Task<string> Think(string input, string username)
{ {
messages.Add(new GPTMessage(){Role = "user", Content = input, Name = username});
while(messages.Count > _maxHistory + 2){
messages.RemoveAt(1);
}
int tokensUsed = 0;
foreach(GPTMessage message in messages){
tokensUsed += _encoding.Encode(message.Content).Count;
}
tokensUsed *= 2; //Unsure why this is the case, but the token count chatgpt reports back is double what I calculate.
//check if we have the minimum number of tokens available to use. Remove messages until we have enough, otherwise exit out and inform the user why.
while(_maxTokens - tokensUsed <= _minTokens){
if(messages.Count > 2){
int tokens = _encoding.Encode(messages[1].Content).Count * 2;
tokensUsed -= tokens;
messages.RemoveAt(1);
}
else{
return "Token count exceeded, please increase the number of tokens in the bot config and restart.";
}
}
using var http = _httpFactory.CreateClient(); using var http = _httpFactory.CreateClient();
http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey); http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey);
var data = await http.PostAsJsonAsync(Uri, new Gpt3ApiRequest() var data = await http.PostAsJsonAsync(Uri, new Gpt3ApiRequest()
{ {
Model = _model, Model = _model,
Prompt = input, Messages = messages,
MaxTokens = _maxTokens, MaxTokens = _maxTokens - tokensUsed,
Temperature = 1, Temperature = 1,
}); });
var dataString = await data.Content.ReadAsStringAsync(); var dataString = await data.Content.ReadAsStringAsync();
try try
{ {
var response = JsonConvert.DeserializeObject<Gpt3Response>(dataString); var response = JsonConvert.DeserializeObject<Gpt3Response>(dataString);
string message = response?.Choices[0]?.Message?.Content;
return response?.Choices[0]?.Text; //Can't rely on the return to except, now that we need to add it to the messages list.
_ = message ?? throw new ArgumentNullException(nameof(message));
messages.Add(new GPTMessage(){Role = "assistant", Content = message, Name = _nadekoUsername});
return message;
} }
catch catch
{ {

View File

@@ -8,7 +8,7 @@ namespace NadekoBot.Modules.Games.Common;
public sealed partial class GamesConfig : ICloneable<GamesConfig> public sealed partial class GamesConfig : ICloneable<GamesConfig>
{ {
[Comment("DO NOT CHANGE")] [Comment("DO NOT CHANGE")]
public int Version { get; set; } = 2; public int Version { get; set; } = 3;
[Comment("Hangman related settings (.hangman command)")] [Comment("Hangman related settings (.hangman command)")]
public HangmanConfig Hangman { get; set; } = new() public HangmanConfig Hangman { get; set; } = new()
@@ -108,14 +108,22 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
public sealed partial class ChatGptConfig public sealed partial class ChatGptConfig
{ {
[Comment(@"Which GPT-3 Model should bot use. [Comment(@"Which GPT-3 Model should bot use.
'ada001' - cheapest and fastest gpt35turbo - cheapest
'babbage001' - 2nd option gpt4 - 30x more expensive, higher quality
'curie001' - 3rd option gp432k - same model as above, but with a 32k token limit")]
'davinci003' - Most expensive, slowest")] public ChatGptModel ModelName { get; set; } = ChatGptModel.Gpt35Turbo;
public Gpt3Model Model { get; set; } = Gpt3Model.Ada001;
[Comment(@"How should the chat bot behave, what's its personality? (Usage of this counts towards the max tokens)")]
public string PersonalityPrompt { get; set; } = "You are a chat bot willing to have a conversation with anyone about anything.";
[Comment(@"The maximum number of messages in a conversation that can be remembered. (This will increase the number of tokens used)")]
public int ChatHistory { get; set; } = 5;
[Comment(@"The maximum number of tokens to use per GPT-3 API call")] [Comment(@"The maximum number of tokens to use per GPT-3 API call")]
public int MaxTokens { get; set; } = 100; public int MaxTokens { get; set; } = 100;
[Comment(@"The minimum number of tokens to use per GPT-3 API call, such that chat history is removed to make room.")]
public int MinTokens { get; set; } = 30;
} }
[Cloneable] [Cloneable]
@@ -149,10 +157,9 @@ public enum ChatBotImplementation
Gpt3 Gpt3
} }
public enum Gpt3Model public enum ChatGptModel
{ {
Ada001, Gpt35Turbo,
Babbage001, Gpt4,
Curie001, Gpt432k
Davinci003
} }

View File

@@ -28,20 +28,33 @@ public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
long.TryParse, long.TryParse,
ConfigPrinters.ToString, ConfigPrinters.ToString,
val => val >= 0); val => val >= 0);
AddParsedProp("chatbot", AddParsedProp("chatbot",
gs => gs.ChatBot, gs => gs.ChatBot,
ConfigParsers.InsensitiveEnum, ConfigParsers.InsensitiveEnum,
ConfigPrinters.ToString); ConfigPrinters.ToString);
AddParsedProp("gpt.model", AddParsedProp("gpt.modelName",
gs => gs.ChatGpt.Model, gs => gs.ChatGpt.ModelName,
ConfigParsers.InsensitiveEnum, ConfigParsers.InsensitiveEnum,
ConfigPrinters.ToString); ConfigPrinters.ToString);
AddParsedProp("gpt.personality",
gs => gs.ChatGpt.PersonalityPrompt,
ConfigParsers.String,
ConfigPrinters.ToString);
AddParsedProp("gpt.chathistory",
gs => gs.ChatGpt.ChatHistory,
int.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("gpt.max_tokens", AddParsedProp("gpt.max_tokens",
gs => gs.ChatGpt.MaxTokens, gs => gs.ChatGpt.MaxTokens,
int.TryParse, int.TryParse,
ConfigPrinters.ToString, ConfigPrinters.ToString,
val => val > 0); val => val > 0);
AddParsedProp("gpt.min_tokens",
gs => gs.ChatGpt.MinTokens,
int.TryParse,
ConfigPrinters.ToString,
val => val > 0);
Migrate(); Migrate();
} }
@@ -68,5 +81,14 @@ public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
c.ChatBot = ChatBotImplementation.Cleverbot; c.ChatBot = ChatBotImplementation.Cleverbot;
}); });
} }
if (data.Version < 3)
{
ModifyConfig(c =>
{
c.Version = 3;
c.ChatGpt.ModelName = ChatGptModel.Gpt35Turbo;
});
}
} }
} }

View File

@@ -58,6 +58,7 @@
<PackageReference Include="Scrutor" Version="4.2.0" /> <PackageReference Include="Scrutor" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.1.1" /> <PackageReference Include="Serilog.Sinks.Seq" Version="5.1.1" />
<PackageReference Include="SharpToken" Version="1.2.14" />
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17" /> <PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" /> <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14" />

View File

@@ -1,5 +1,5 @@
# DO NOT CHANGE # DO NOT CHANGE
version: 2 version: 3
# Hangman related settings (.hangman command) # Hangman related settings (.hangman command)
hangman: hangman:
# The amount of currency awarded to the winner of a hangman game # The amount of currency awarded to the winner of a hangman game
@@ -57,14 +57,19 @@ raceAnimals:
# Which chatbot API should bot use. # Which chatbot API should bot use.
# 'cleverbot' - bot will use Cleverbot API. # 'cleverbot' - bot will use Cleverbot API.
# 'gpt3' - bot will use GPT-3 API # 'gpt3' - bot will use GPT-3 API
chatBot: gpt3 chatBot: Gpt3
chatGpt: chatGpt:
# Which GPT-3 Model should bot use. # Which GPT-3 Model should bot use.
# 'ada001' - cheapest and fastest # gpt35turbo - cheapest
# 'babbage001' - 2nd option # gpt4 - 30x more expensive, higher quality
# 'curie001' - 3rd option # gp432k - same model as above, but with a 32k token limit
# 'davinci003' - Most expensive, slowest modelName: Gpt35Turbo
model: davinci003 # How should the chat bot behave, whats its personality? (Usage of this counts towards the max tokens)
personalityPrompt: You are a chat bot willing to have a conversation with anyone about anything.
# The maximum number of messages in a conversation that can be remembered. (This will increase the number of tokens used)
chatHistory: 5
# The maximum number of tokens to use per GPT-3 API call # The maximum number of tokens to use per GPT-3 API call
maxTokens: 100 maxTokens: 100
# The minimum number of tokens to use per GPT-3 API call, such that chat history is removed to make room.
minTokens: 30