populate if empty context and

add trimming to messages
This commit is contained in:
Kristian 2023-08-10 00:40:21 +02:00
parent a5517264a7
commit aa3d85c10b
5 changed files with 307 additions and 2810 deletions

View File

@ -1,20 +1,19 @@
package bot package bot
import ( import (
"bitbot/pb"
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
openai "github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
) )
var ( var (
CryptoToken string
BotToken string BotToken string
OpenAIToken string OpenAIToken string
CryptoToken string
) )
func Run() { func Run() {
@ -28,7 +27,6 @@ func Run() {
discord.Open() discord.Open()
defer discord.Close() defer discord.Close()
log.Info("BitBot is running...") log.Info("BitBot is running...")
pb.Run()
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
@ -44,16 +42,21 @@ func newMessage(discord *discordgo.Session, message *discordgo.MessageCreate) {
isPrivateChannel := message.GuildID == "" isPrivateChannel := message.GuildID == ""
conversationHistory := conversationHistoryMap[message.Author.ID] userID := message.Author.ID
conversationHistory := conversationHistoryMap[userID]
channelID := message.ChannelID
conversationHistory = populateConversationHistory(discord, channelID, conversationHistory)
userMessage := openai.ChatCompletionMessage{ userMessage := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser, Role: openai.ChatMessageRoleUser,
Content: message.Content, Content: message.Content,
} }
conversationHistory = append(conversationHistory, userMessage) conversationHistory = append(conversationHistory, userMessage)
if strings.Contains(message.Content, "!bit") || isPrivateChannel { if strings.Contains(message.Content, "!bit") || isPrivateChannel {
gptResponse := chatGPT(message.Content, conversationHistory) gptResponse := chatGPT(discord, message.ChannelID, message.Content, conversationHistory)
discord.ChannelTyping(message.ChannelID) discord.ChannelTyping(message.ChannelID)
discord.ChannelMessageSendComplex(message.ChannelID, gptResponse) discord.ChannelMessageSendComplex(message.ChannelID, gptResponse)
@ -67,5 +70,5 @@ func newMessage(discord *discordgo.Session, message *discordgo.MessageCreate) {
discord.ChannelMessageSendComplex(message.ChannelID, currentCryptoPrice) discord.ChannelMessageSendComplex(message.ChannelID, currentCryptoPrice)
} }
conversationHistoryMap[message.Author.ID] = conversationHistory conversationHistoryMap[userID] = conversationHistory
} }

View File

@ -8,53 +8,125 @@ import (
openai "github.com/sashabaranov/go-openai" openai "github.com/sashabaranov/go-openai"
) )
const maxTokens = 3000 const (
const maxContextTokens = 4097 maxTokens = 3000
maxContextTokens = 4097
maxMessageTokens = 1000
systemMessageText = "1. Identify the key points or main ideas of the original answers.\n2. Summarize each answer using concise and informative language.\n3. Prioritize clarity and brevity, capturing the essence of the information provided.\n4. Trim down unnecessary details and avoid elaboration.\n5. Make sure the summarized answers still convey accurate and meaningful information."
)
func chatGPT(message string, conversationHistory []openai.ChatCompletionMessage) *discordgo.MessageSend { func populateConversationHistory(session *discordgo.Session, channelID string, conversationHistory []openai.ChatCompletionMessage) []openai.ChatCompletionMessage {
client := openai.NewClient(OpenAIToken) messages, err := session.ChannelMessages(channelID, 100, "", "", "")
if err != nil {
log.Error("Error retrieving channel history:", err)
return conversationHistory
}
// Calculate the total number of tokens used in the conversation history and completion.
totalTokens := 0 totalTokens := 0
for _, msg := range conversationHistory { for _, msg := range conversationHistory {
totalTokens += len(msg.Content) + len(msg.Role) + 2 // Account for role and content tokens, plus two extra for delimiters. totalTokens += len(msg.Content) + len(msg.Role) + 2
} }
// Calculate the number of tokens used in the completion. maxHistoryTokens := maxTokens - totalTokens
for _, message := range messages {
if message.Author.ID == session.State.User.ID {
continue // Skip the bot's own messages
}
if len(message.Content) > 0 {
tokens := len(message.Content) + 2 // Account for role and content tokens
if totalTokens+tokens <= maxContextTokens && len(conversationHistory) < maxHistoryTokens {
conversationHistory = append(conversationHistory, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: message.Content,
})
totalTokens += tokens
} else {
if totalTokens+tokens > maxContextTokens {
log.Warn("Message token count exceeds maxContextTokens:", len(message.Content), len(message.Content)+2)
} else {
log.Warn("Conversation history length exceeds maxContextTokens:", len(conversationHistory), maxHistoryTokens)
}
break
}
}
}
return conversationHistory
}
func chatGPT(session *discordgo.Session, channelID string, message string, conversationHistory []openai.ChatCompletionMessage) *discordgo.MessageSend {
conversationHistory = populateConversationHistory(session, channelID, conversationHistory)
client := openai.NewClient(OpenAIToken)
// Calculate the total tokens in the conversation history
totalTokens := 0
for _, msg := range conversationHistory {
totalTokens += len(msg.Content) + len(msg.Role) + 2
}
log.Info("Total tokens in conversation history:", totalTokens)
// Calculate the tokens in the completion message
completionTokens := len(message) completionTokens := len(message)
log.Info("Tokens in completion message:", completionTokens)
// If the total tokens (context + completion) exceed the maxTokens limit, truncate the completion first. // Calculate the total tokens including the new message
for totalTokens+completionTokens > maxTokens { totalMessageTokens := len(message) + 2 // Account for role and content tokens
// Remove tokens from the beginning of the completion message.
if completionTokens > maxTokens { // Ensure the total tokens of messages including new message doesn't exceed maxMessageTokens
message = message[:maxTokens] for totalTokens+totalMessageTokens > maxMessageTokens {
completionTokens = maxTokens tokensToRemove := totalTokens + totalMessageTokens - maxMessageTokens
tokensRemoved := 0
trimmedMessages := []openai.ChatCompletionMessage{} // Store trimmed messages
for _, msg := range conversationHistory {
tokens := len(msg.Content) + len(msg.Role) + 2
if tokensRemoved+tokens <= tokensToRemove {
tokensRemoved += tokens
log.Info("Removing message with tokens:", tokens)
} else { } else {
// If removing the last message reduces the context within the limit, remove it. trimmedMessages = append(trimmedMessages, msg)
if totalTokens-len(conversationHistory[len(conversationHistory)-1].Content)-len(conversationHistory[len(conversationHistory)-1].Role)-2 <= maxTokens { }
totalTokens -= len(conversationHistory[len(conversationHistory)-1].Content) + len(conversationHistory[len(conversationHistory)-1].Role) + 2 }
conversationHistory = conversationHistory[:len(conversationHistory)-1] if tokensRemoved > 0 {
conversationHistory = trimmedMessages
totalTokens -= tokensRemoved
} else { } else {
// Otherwise, remove the first message from the conversation history. break
totalTokens -= len(conversationHistory[0].Content) + len(conversationHistory[0].Role) + 2
conversationHistory = conversationHistory[1:]
}
} }
} }
// Add a system message at the beginning of the conversation history with the instructions. // Add user message to conversation history
systemMessage := openai.ChatCompletionMessage{ userMessage := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleSystem,
Content: "1. Identify the key points or main ideas of the original answers.\n2. Summarize each answer using concise and informative language.\n3. Prioritize clarity and brevity, capturing the essence of the information provided.\n4. Trim down unnecessary details and avoid elaboration.\n5. Make sure the summarized answers still convey accurate and meaningful information.",
}
// Combine the system message, previous conversation history, and the current user message.
messages := append([]openai.ChatCompletionMessage{systemMessage}, conversationHistory...)
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser, Role: openai.ChatMessageRoleUser,
Content: message, Content: message,
}) }
conversationHistory = append(conversationHistory, userMessage)
// Construct system message
systemMessage := openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleSystem,
Content: systemMessageText,
}
// Combine messages, ensuring they don't exceed maxTokens
messages := []openai.ChatCompletionMessage{systemMessage}
totalTokens = len(systemMessage.Content) + len(systemMessage.Role) + 2
for _, msg := range conversationHistory {
tokens := len(msg.Content) + len(msg.Role) + 2
if totalTokens+tokens <= maxTokens {
messages = append(messages, msg)
totalTokens += tokens
} else {
break
}
}
// Perform GPT-3.5 Turbo completion
log.Info("Starting GPT-3.5 Turbo completion...")
resp, err := client.CreateChatCompletion( resp, err := client.CreateChatCompletion(
context.Background(), context.Background(),
openai.ChatCompletionRequest{ openai.ChatCompletionRequest{
@ -65,7 +137,9 @@ func chatGPT(message string, conversationHistory []openai.ChatCompletionMessage)
Messages: messages, Messages: messages,
}, },
) )
log.Info("GPT-3.5 Turbo completion done.")
// Handle API errors
if err != nil { if err != nil {
log.Error("Error connecting to the OpenAI API:", err) log.Error("Error connecting to the OpenAI API:", err)
return &discordgo.MessageSend{ return &discordgo.MessageSend{
@ -73,8 +147,8 @@ func chatGPT(message string, conversationHistory []openai.ChatCompletionMessage)
} }
} }
// Construct and return the bot's response
gptResponse := resp.Choices[0].Message.Content gptResponse := resp.Choices[0].Message.Content
embed := &discordgo.MessageSend{ embed := &discordgo.MessageSend{
Content: gptResponse, Content: gptResponse,
} }

114
go.mod
View File

@ -3,39 +3,38 @@ module bitbot
go 1.18 go 1.18
require ( require (
github.com/bwmarrin/discordgo v0.26.1 github.com/bwmarrin/discordgo v0.27.1
github.com/charmbracelet/log v0.2.1 github.com/charmbracelet/log v0.2.3
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198 github.com/pocketbase/pocketbase v0.17.3
github.com/pocketbase/pocketbase v0.14.3
) )
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.44.234 // indirect github.com/aws/aws-sdk-go v1.44.320 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect github.com/aws/aws-sdk-go-v2 v1.20.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.19 // indirect github.com/aws/aws-sdk-go-v2/config v1.18.33 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.32 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.60 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.77 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.1 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 // indirect
github.com/aws/smithy-go v1.13.5 // indirect github.com/aws/smithy-go v1.14.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.7.1 // indirect github.com/charmbracelet/lipgloss v0.7.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.5.0 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.15.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
@ -47,58 +46,61 @@ require (
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/google/wire v0.5.0 // indirect github.com/google/wire v0.5.0 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/pocketbase/dbx v1.10.0 // indirect github.com/pocketbase/dbx v1.10.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
gocloud.dev v0.29.0 // indirect gocloud.dev v0.33.0 // indirect
golang.org/x/image v0.6.0 // indirect golang.org/x/image v0.11.0 // indirect
golang.org/x/mod v0.9.0 // indirect golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.8.0 // indirect golang.org/x/net v0.14.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/term v0.6.0 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.8.0 // indirect golang.org/x/term v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect golang.org/x/tools v0.12.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.114.0 // indirect google.golang.org/api v0.136.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 // indirect google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 // indirect
google.golang.org/grpc v1.54.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect
google.golang.org/protobuf v1.30.0 // indirect google.golang.org/grpc v1.57.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/ccgo/v3 v3.16.14 // indirect
modernc.org/libc v1.22.3 // indirect modernc.org/libc v1.24.1 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.5.0 // indirect modernc.org/memory v1.6.0 // indirect
modernc.org/opt v0.1.3 // indirect modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.21.1 // indirect modernc.org/sqlite v1.25.0 // indirect
modernc.org/strutil v1.1.3 // indirect modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.1.0 // indirect modernc.org/token v1.1.0 // indirect
) )
require ( require (
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/joho/godotenv v1.4.0 github.com/joho/godotenv v1.5.1
github.com/sashabaranov/go-openai v1.10.1 github.com/sashabaranov/go-openai v1.14.1
golang.org/x/crypto v0.7.0 // indirect golang.org/x/crypto v0.12.0 // indirect
golang.org/x/sys v0.6.0 // indirect golang.org/x/sys v0.11.0 // indirect
) )

2844
go.sum

File diff suppressed because it is too large Load Diff

Binary file not shown.