add groq cloud instead of opnai for chatbot
fix uppercase only for crypto
This commit is contained in:
parent
5db6a70961
commit
d3f6deb6df
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/sashabaranov/go-openai"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -44,7 +43,7 @@ func Run() {
|
|||||||
<-c
|
<-c
|
||||||
}
|
}
|
||||||
|
|
||||||
var conversationHistoryMap = make(map[string][]openai.ChatCompletionMessage)
|
var conversationHistoryMap = make(map[string][]map[string]interface{})
|
||||||
var sshConnections = make(map[string]*SSHConnection)
|
var sshConnections = make(map[string]*SSHConnection)
|
||||||
|
|
||||||
func hasAdminRole(roles []string) bool {
|
func hasAdminRole(roles []string) bool {
|
||||||
@ -69,13 +68,11 @@ func newMessage(discord *discordgo.Session, message *discordgo.MessageCreate) {
|
|||||||
channelID := message.ChannelID
|
channelID := message.ChannelID
|
||||||
conversationHistory = populateConversationHistory(discord, channelID, conversationHistory)
|
conversationHistory = populateConversationHistory(discord, channelID, conversationHistory)
|
||||||
|
|
||||||
log.Info("Conversation history from bot.go:", conversationHistory)
|
|
||||||
|
|
||||||
if strings.HasPrefix(message.Content, "!cry") {
|
if strings.HasPrefix(message.Content, "!cry") {
|
||||||
currentCryptoPrice := getCurrentCryptoPrice(message.Content)
|
currentCryptoPrice := getCurrentCryptoPrice(message.Content)
|
||||||
discord.ChannelMessageSendComplex(message.ChannelID, currentCryptoPrice)
|
discord.ChannelMessageSendComplex(message.ChannelID, currentCryptoPrice)
|
||||||
} else if strings.HasPrefix(message.Content, "!bit") || isPrivateChannel {
|
} else if strings.HasPrefix(message.Content, "!bit") || isPrivateChannel {
|
||||||
chatGPT(discord, message.ChannelID, message.Content, conversationHistory)
|
chatGPT(discord, message.ChannelID, conversationHistory)
|
||||||
} else if strings.HasPrefix(message.Content, "!genkey") {
|
} else if strings.HasPrefix(message.Content, "!genkey") {
|
||||||
if hasAdminRole(message.Member.Roles) {
|
if hasAdminRole(message.Member.Roles) {
|
||||||
err := GenerateAndSaveSSHKeyPairIfNotExist()
|
err := GenerateAndSaveSSHKeyPairIfNotExist()
|
||||||
|
|||||||
246
bot/chat.go
246
bot/chat.go
@ -1,182 +1,160 @@
|
|||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"bytes"
|
||||||
"fmt"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
openai "github.com/sashabaranov/go-openai"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxTokens = 2000
|
maxTokens = 2000
|
||||||
maxContextTokens = 4000
|
maxContextTokens = 2000
|
||||||
maxMessageTokens = 2000
|
maxMessageTokens = 2000
|
||||||
systemMessageText = "your name is !bit you are a discord bot"
|
systemMessageText = "your name is !bit you are a discord bot, you use brief answers untill asked to elaborate or explain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func populateConversationHistory(session *discordgo.Session, channelID string, conversationHistory []openai.ChatCompletionMessage) []openai.ChatCompletionMessage {
|
func populateConversationHistory(session *discordgo.Session, channelID string, conversationHistory []map[string]interface{}) []map[string]interface{} {
|
||||||
|
// Retrieve recent messages from the Discord channel
|
||||||
messages, err := session.ChannelMessages(channelID, 20, "", "", "")
|
messages, err := session.ChannelMessages(channelID, 20, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error retrieving channel history:", err)
|
log.Error("Error retrieving channel history:", err)
|
||||||
return conversationHistory
|
return conversationHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define max tokens for the conversation history
|
||||||
|
maxTokens := 2000
|
||||||
totalTokens := 0
|
totalTokens := 0
|
||||||
maxHistoryTokens := maxTokens
|
|
||||||
|
|
||||||
// Calculate total tokens without removing any messages
|
// Calculate current token count in conversation history
|
||||||
for _, msg := range conversationHistory {
|
for _, msg := range conversationHistory {
|
||||||
totalTokens += len(msg.Content) + len(msg.Role) + 2
|
content, okContent := msg["Content"].(string)
|
||||||
}
|
role, okRole := msg["Role"].(string)
|
||||||
|
if okContent && okRole {
|
||||||
log.Info("Total Tokens Before Trimming:", totalTokens)
|
tokens := len(content) + len(role) + 2 // Account for tokens in content and role
|
||||||
|
totalTokens += tokens
|
||||||
// Iterate from the beginning of conversationHistory (oldest messages)
|
log.Infof("Existing message tokens: %d", tokens)
|
||||||
for i := 0; i < len(conversationHistory); i++ {
|
|
||||||
msg := conversationHistory[i]
|
|
||||||
tokens := len(msg.Content) + len(msg.Role) + 2 // Account for role and content tokens
|
|
||||||
|
|
||||||
if totalTokens-tokens >= maxHistoryTokens {
|
|
||||||
// Remove the oldest message
|
|
||||||
log.Info("Removing Oldest Message:", msg.Content)
|
|
||||||
conversationHistory = conversationHistory[i+1:]
|
|
||||||
i-- // Adjust index after removal
|
|
||||||
} else {
|
|
||||||
totalTokens -= tokens
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Total Tokens After Trimming:", totalTokens)
|
// Process messages in reverse order (newest to oldest)
|
||||||
|
|
||||||
// Add new messages from the channel
|
|
||||||
for i := len(messages) - 1; i >= 0; i-- {
|
for i := len(messages) - 1; i >= 0; i-- {
|
||||||
message := messages[i]
|
message := messages[i]
|
||||||
if len(message.Content) > 0 {
|
|
||||||
tokens := len(message.Content) + 2 // Account for role and content tokens
|
// Check if the message is older than 30 minutes
|
||||||
if totalTokens+tokens <= maxContextTokens {
|
if time.Since(message.Timestamp) < 30*time.Minute {
|
||||||
conversationHistory = append(conversationHistory, openai.ChatCompletionMessage{
|
tokens := len(message.Content) + 2
|
||||||
Role: openai.ChatMessageRoleUser,
|
if totalTokens+tokens <= maxTokens {
|
||||||
Content: message.Content,
|
// Append as map[string]interface{} instead of map[string]string
|
||||||
|
conversationHistory = append(conversationHistory, map[string]interface{}{
|
||||||
|
"role": "user",
|
||||||
|
"content": message.Content,
|
||||||
})
|
})
|
||||||
totalTokens += tokens
|
totalTokens += tokens
|
||||||
log.Info("Adding New Message:", message.Content)
|
log.Infof("Adding message with tokens: %d", tokens)
|
||||||
} else {
|
} else {
|
||||||
if totalTokens+tokens > maxContextTokens {
|
log.Warnf("Skipping message with tokens: %d", tokens)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Infof("Skipping message, older than 30 minutes: %s", message.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the current message is included (regardless of token limit)
|
||||||
|
conversationHistory = append(conversationHistory, map[string]interface{}{
|
||||||
|
"role": "user",
|
||||||
|
"content": message.Content,
|
||||||
|
})
|
||||||
|
totalTokens += len(message.Content) + 2
|
||||||
|
log.Infof("Adding message with tokens: %d", totalTokens)
|
||||||
|
|
||||||
|
// Now check if the token limit is exceeded and trim older messages
|
||||||
|
if totalTokens > maxTokens && len(conversationHistory) > 1 {
|
||||||
|
// Remove the oldest message from the history
|
||||||
|
conversationHistory = conversationHistory[1:]
|
||||||
|
content, okContent := conversationHistory[0]["Content"].(string)
|
||||||
|
role, okRole := conversationHistory[0]["Role"].(string)
|
||||||
|
if okContent && okRole {
|
||||||
|
tokens := len(content) + len(role) + 2
|
||||||
|
totalTokens -= tokens
|
||||||
|
log.Infof("Trimming message with tokens: %d", tokens)
|
||||||
|
}
|
||||||
|
log.Info("Trimming oldest message to maintain token limit")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the final order of conversation history
|
log.Info("Final Conversation History Order: %s", conversationHistory)
|
||||||
log.Info("Final Conversation History Order:", conversationHistory)
|
|
||||||
|
|
||||||
return conversationHistory
|
return conversationHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatGPT(session *discordgo.Session, channelID string, message string, conversationHistory []openai.ChatCompletionMessage) {
|
// Function to handle Groq API requests and pagination
|
||||||
client := openai.NewClient(OpenAIToken)
|
func chatGPT(session *discordgo.Session, channelID string, conversationHistory []map[string]interface{}) {
|
||||||
|
OpenAIToken := OpenAIToken
|
||||||
|
GroqBaseURL := "https://api.groq.com/openai/v1"
|
||||||
|
GroqModel := "llama-3.1-70b-versatile"
|
||||||
|
|
||||||
// Perform GPT-4 completion
|
// Add system message at the start of conversation history
|
||||||
log.Info("Starting completion...", conversationHistory)
|
conversationHistory = append([]map[string]interface{}{
|
||||||
resp, err := client.CreateChatCompletion(
|
{"role": "system", "content": systemMessageText},
|
||||||
context.Background(),
|
}, conversationHistory...)
|
||||||
openai.ChatCompletionRequest{
|
|
||||||
MaxTokens: maxTokens,
|
|
||||||
FrequencyPenalty: 0.3,
|
|
||||||
PresencePenalty: 0.6,
|
|
||||||
Model: openai.GPT3Dot5Turbo,
|
|
||||||
Messages: conversationHistory, // Use trimmed conversation history
|
|
||||||
},
|
|
||||||
)
|
|
||||||
log.Info("completion done.")
|
|
||||||
|
|
||||||
// Handle API errors
|
client := http.Client{}
|
||||||
if err != nil {
|
requestBody, err := json.Marshal(map[string]interface{}{
|
||||||
log.Error("Error connecting to the OpenAI API:", err)
|
"model": GroqModel,
|
||||||
return
|
"messages": conversationHistory,
|
||||||
}
|
"max_tokens": maxTokens,
|
||||||
|
"frequency_penalty": 0.3,
|
||||||
// Paginate the response and send as separate messages with clickable emojis
|
"presence_penalty": 0.6,
|
||||||
gptResponse := resp.Choices[0].Message.Content
|
|
||||||
pageSize := maxMessageTokens
|
|
||||||
|
|
||||||
// Split the response into pages
|
|
||||||
var pages []string
|
|
||||||
for i := 0; i < len(gptResponse); i += pageSize {
|
|
||||||
end := i + pageSize
|
|
||||||
if end > len(gptResponse) {
|
|
||||||
end = len(gptResponse)
|
|
||||||
}
|
|
||||||
pages = append(pages, gptResponse[i:end])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the first page
|
|
||||||
currentPage := 0
|
|
||||||
totalPages := len(pages)
|
|
||||||
embed := &discordgo.MessageEmbed{
|
|
||||||
Title: fmt.Sprintf("Page %d of %d", currentPage+1, totalPages),
|
|
||||||
Description: pages[currentPage],
|
|
||||||
Color: 0x00ff00, // Green color
|
|
||||||
}
|
|
||||||
msg, err := session.ChannelMessageSendEmbed(channelID, embed)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error sending embed message:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add reaction emojis for pagination if there are multiple pages
|
|
||||||
if totalPages > 1 { // Only add reactions if there are multiple pages
|
|
||||||
err = session.MessageReactionAdd(channelID, msg.ID, "⬅️")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error adding reaction emoji:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = session.MessageReactionAdd(channelID, msg.ID, "➡️")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error adding reaction emoji:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a reaction handler function
|
|
||||||
session.AddHandler(func(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
|
|
||||||
// Call the reactionHandler function and pass totalPages
|
|
||||||
reactionHandler(s, r, currentPage, msg, pages, totalPages)
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
}
|
log.Errorf("Failed to marshal request body: %v", err)
|
||||||
|
|
||||||
func reactionHandler(session *discordgo.Session, r *discordgo.MessageReactionAdd, currentPage int, msg *discordgo.Message, pages []string, totalPages int) {
|
|
||||||
// Check if the reaction is from the same user and message
|
|
||||||
if r.UserID == session.State.User.ID || r.MessageID != msg.ID {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle pagination based on reaction
|
req, err := http.NewRequest("POST", GroqBaseURL+"/chat/completions", bytes.NewBuffer(requestBody))
|
||||||
if r.Emoji.Name == "⬅️" {
|
if err != nil {
|
||||||
if currentPage > 0 {
|
log.Errorf("Failed to create request: %v", err)
|
||||||
currentPage--
|
return
|
||||||
}
|
}
|
||||||
} else if r.Emoji.Name == "➡️" {
|
req.Header.Set("Content-Type", "application/json")
|
||||||
if currentPage < len(pages)-1 {
|
req.Header.Set("Authorization", "Bearer "+OpenAIToken)
|
||||||
currentPage++
|
|
||||||
}
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to make request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var groqResp struct {
|
||||||
|
Choices []struct {
|
||||||
|
Message struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"message"`
|
||||||
|
} `json:"choices"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the message with the new page
|
body, err := io.ReadAll(resp.Body)
|
||||||
updatedEmbed := &discordgo.MessageEmbed{
|
|
||||||
Title: fmt.Sprintf("Page %d of %d", currentPage+1, len(pages)),
|
|
||||||
Description: pages[currentPage],
|
|
||||||
Color: 0x00ff00, // Green color
|
|
||||||
}
|
|
||||||
_, err := session.ChannelMessageEditEmbed(r.ChannelID, r.MessageID, updatedEmbed)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error editing embed message:", err)
|
log.Errorf("Failed to read response body: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &groqResp)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to decode response: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(groqResp.Choices) > 0 {
|
||||||
|
gptResponse := groqResp.Choices[0].Message.Content
|
||||||
|
_, err := session.ChannelMessageSend(channelID, gptResponse)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error sending message:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -20,17 +19,17 @@ type CryptoData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getCurrentCryptoPrice(message string) *discordgo.MessageSend {
|
func getCurrentCryptoPrice(message string) *discordgo.MessageSend {
|
||||||
r, _ := regexp.Compile(`\s?[A-Z]{3,5}\s?`)
|
message = strings.ToUpper(strings.TrimSpace(message)) // convert to uppercase and remove leading/trailing spaces
|
||||||
currency := r.FindString(message)
|
message = strings.ReplaceAll(message, "!CRY ", "") // remove "!cry" prefix
|
||||||
curr := strings.ReplaceAll(currency, " ", "")
|
currency := message[:]
|
||||||
|
|
||||||
if curr == "" {
|
if len(currency) < 3 || len(currency) > 5 { // check length of currency code
|
||||||
return &discordgo.MessageSend{
|
return &discordgo.MessageSend{
|
||||||
Content: "Sorry, cant recognize crypto currency shortcode try uppercase and with spaces around",
|
Content: "Sorry, cant recognize crypto currency shortcode try uppercase and with length between 3 to 5",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoURL := fmt.Sprintf("%s?fsym=%s&tsyms=USD&api_key=%s", URL, curr, CryptoToken)
|
cryptoURL := fmt.Sprintf("%s?fsym=%s&tsyms=USD&api_key=%s", URL, currency, CryptoToken)
|
||||||
fmt.Println(cryptoURL)
|
fmt.Println(cryptoURL)
|
||||||
client := http.Client{Timeout: 5 * time.Second}
|
client := http.Client{Timeout: 5 * time.Second}
|
||||||
|
|
||||||
@ -54,10 +53,10 @@ func getCurrentCryptoPrice(message string) *discordgo.MessageSend {
|
|||||||
Embeds: []*discordgo.MessageEmbed{{
|
Embeds: []*discordgo.MessageEmbed{{
|
||||||
Type: discordgo.EmbedTypeRich,
|
Type: discordgo.EmbedTypeRich,
|
||||||
Title: "Current Price",
|
Title: "Current Price",
|
||||||
Description: "Price for " + curr,
|
Description: "Price for " + currency,
|
||||||
Fields: []*discordgo.MessageEmbedField{
|
Fields: []*discordgo.MessageEmbedField{
|
||||||
{
|
{
|
||||||
Name: "1 " + curr,
|
Name: "1 " + currency,
|
||||||
Value: usd + " USD",
|
Value: usd + " USD",
|
||||||
Inline: true,
|
Inline: true,
|
||||||
},
|
},
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -101,7 +101,6 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/sashabaranov/go-openai v1.15.4
|
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.14.0
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -194,8 +194,6 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
|||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sashabaranov/go-openai v1.15.4 h1:BXCR0Uxk5RipeY4yBC7g6pBVfcjh8jwrMNOYdie6yuk=
|
|
||||||
github.com/sashabaranov/go-openai v1.15.4/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
|
||||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user