decouple second step
This commit is contained in:
parent
2836650823
commit
7453868901
|
@ -4,10 +4,10 @@ RUN apt update && apt install -y libolm3 libolm-dev
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . ./
|
COPY . ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
RUN go build -o /matrix-bots ./main.go
|
RUN go build -o /matrix-gptzoo ./main.go
|
||||||
|
|
||||||
FROM debian:bullseye
|
FROM debian:bullseye
|
||||||
RUN apt update && apt install -y libolm3 ca-certificates openssl
|
RUN apt update && apt install -y libolm3 ca-certificates openssl
|
||||||
|
|
||||||
COPY --from=build /matrix-bots /matrix-bots
|
COPY --from=build /matrix-gptzoo /matrix-gptzoo
|
||||||
CMD /matrix-bots
|
CMD /matrix-gptzoo
|
|
@ -0,0 +1,69 @@
|
||||||
|
package bot
|
||||||
|
|
||||||
|
import "github.com/sashabaranov/go-openai"
|
||||||
|
|
||||||
|
const systemPrompt = "You are a chatbot that helps people by responding to their questions with short messages."
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
EventID string
|
||||||
|
Role string
|
||||||
|
Content string
|
||||||
|
ReplyToID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conversation struct {
|
||||||
|
Messages []Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConversation(question string) *Conversation {
|
||||||
|
return &Conversation{
|
||||||
|
Messages: []Message{
|
||||||
|
{
|
||||||
|
Role: openai.ChatMessageRoleSystem,
|
||||||
|
Content: systemPrompt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: openai.ChatMessageRoleUser,
|
||||||
|
Content: question,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conversation) Contains(EventID string) bool {
|
||||||
|
for _, m := range c.Messages {
|
||||||
|
if m.EventID == EventID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conversation) Add(msg Message) {
|
||||||
|
c.Messages = append(c.Messages, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conversations []*Conversation
|
||||||
|
|
||||||
|
func (cs Conversations) Contains(EventID string) bool {
|
||||||
|
for _, c := range cs {
|
||||||
|
if c.Contains(EventID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs Conversations) Add(msg Message) {
|
||||||
|
for _, c := range cs {
|
||||||
|
if c.Contains(msg.EventID) {
|
||||||
|
c.Add(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := NewConversation(msg.Content)
|
||||||
|
cs = append(cs, c)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sashabaranov/go-openai"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GPT struct {
|
||||||
|
client *openai.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGPT(apiKey string) *GPT {
|
||||||
|
return &GPT{
|
||||||
|
client: openai.NewClient(apiKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GPT) Complete(conv *Conversation) (string, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
msg := []openai.ChatCompletionMessage{}
|
||||||
|
for _, m := range conv.Messages {
|
||||||
|
msg = append(msg, openai.ChatCompletionMessage{
|
||||||
|
Role: m.Role,
|
||||||
|
Content: m.Content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
req := openai.ChatCompletionRequest{
|
||||||
|
Model: openai.GPT4,
|
||||||
|
Messages: msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := g.client.CreateChatCompletion(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Choices[len(resp.Choices)-1].Message.Content, nil
|
||||||
|
}
|
|
@ -1,13 +1,11 @@
|
||||||
package matrix
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/sashabaranov/go-openai"
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/crypto/cryptohelper"
|
"maunium.net/go/mautrix/crypto/cryptohelper"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
|
@ -22,6 +20,7 @@ type Config struct {
|
||||||
UserPassword string
|
UserPassword string
|
||||||
DBPath string
|
DBPath string
|
||||||
Pickle string
|
Pickle string
|
||||||
|
OpenAIKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Matrix struct {
|
type Matrix struct {
|
||||||
|
@ -29,6 +28,8 @@ type Matrix struct {
|
||||||
readline *readline.Instance
|
readline *readline.Instance
|
||||||
client *mautrix.Client
|
client *mautrix.Client
|
||||||
cryptoHelper *cryptohelper.CryptoHelper
|
cryptoHelper *cryptohelper.CryptoHelper
|
||||||
|
conversations Conversations
|
||||||
|
gptClient *GPT
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg Config) *Matrix {
|
func New(cfg Config) *Matrix {
|
||||||
|
@ -38,13 +39,6 @@ func New(cfg Config) *Matrix {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matrix) Init() error {
|
func (m *Matrix) Init() error {
|
||||||
rl, err := readline.New("[no room]> ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.readline = rl
|
|
||||||
defer m.readline.Close()
|
|
||||||
|
|
||||||
client, err := mautrix.NewClient(m.config.Homeserver, id.UserID(m.config.UserID), m.config.UserAccessKey)
|
client, err := mautrix.NewClient(m.config.Homeserver, id.UserID(m.config.UserID), m.config.UserAccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -54,7 +48,6 @@ func (m *Matrix) Init() error {
|
||||||
m.client = client
|
m.client = client
|
||||||
|
|
||||||
m.client.Log = zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
m.client.Log = zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
||||||
w.Out = rl.Stdout()
|
|
||||||
w.TimeFormat = time.Stamp
|
w.TimeFormat = time.Stamp
|
||||||
})).With().Timestamp().Logger()
|
})).With().Timestamp().Logger()
|
||||||
|
|
||||||
|
@ -72,6 +65,10 @@ func (m *Matrix) Init() error {
|
||||||
}
|
}
|
||||||
m.client.Crypto = m.cryptoHelper
|
m.client.Crypto = m.cryptoHelper
|
||||||
|
|
||||||
|
m.gptClient = NewGPT(m.config.OpenAIKey)
|
||||||
|
|
||||||
|
m.conversations = make(Conversations, 0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +101,6 @@ func (m *Matrix) InviteHandler() (event.Type, mautrix.EventHandler) {
|
||||||
if evt.GetStateKey() == m.client.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
|
if evt.GetStateKey() == m.client.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
|
||||||
_, err := m.client.JoinRoomByID(evt.RoomID)
|
_, err := m.client.JoinRoomByID(evt.RoomID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
m.readline.SetPrompt(fmt.Sprintf("%s> ", evt.RoomID))
|
|
||||||
m.client.Log.Info().
|
m.client.Log.Info().
|
||||||
Str("room_id", evt.RoomID.String()).
|
Str("room_id", evt.RoomID.String()).
|
||||||
Str("inviter", evt.Sender.String()).
|
Str("inviter", evt.Sender.String()).
|
||||||
|
@ -119,36 +115,15 @@ func (m *Matrix) InviteHandler() (event.Type, mautrix.EventHandler) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matrix) RespondHandler(gpt *openai.Client) (event.Type, mautrix.EventHandler) {
|
func (m *Matrix) RespondHandler() (event.Type, mautrix.EventHandler) {
|
||||||
return event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
|
return event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
|
||||||
m.readline.SetPrompt(fmt.Sprintf("%s> ", evt.RoomID))
|
|
||||||
m.client.Log.Info().
|
m.client.Log.Info().
|
||||||
Str("sender", evt.Sender.String()).
|
|
||||||
Str("type", evt.Type.String()).
|
|
||||||
Str("id", evt.ID.String()).
|
|
||||||
Str("body", evt.Content.AsMessage().Body).
|
Str("body", evt.Content.AsMessage().Body).
|
||||||
Msg("Received message")
|
Msg("Received message")
|
||||||
|
|
||||||
if evt.Sender != id.UserID(m.config.UserID) {
|
if evt.Sender != id.UserID(m.config.UserID) {
|
||||||
msgBody := evt.Content.AsMessage().Body
|
msgBody := evt.Content.AsMessage().Body
|
||||||
|
resp, err := m.gptClient.Complete(NewConversation(msgBody))
|
||||||
// Generate a message with OpenAI API
|
|
||||||
openAiResp, err := gpt.CreateChatCompletion(
|
|
||||||
context.Background(),
|
|
||||||
openai.ChatCompletionRequest{
|
|
||||||
Model: openai.GPT4,
|
|
||||||
Messages: []openai.ChatCompletionMessage{
|
|
||||||
{
|
|
||||||
Role: openai.ChatMessageRoleSystem,
|
|
||||||
Content: "You are a chatbot that helps people by responding to their questions with short messages.",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Role: openai.ChatMessageRoleUser,
|
|
||||||
Content: msgBody,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("OpenAI API returned with ", err)
|
fmt.Println("OpenAI API returned with ", err)
|
||||||
|
@ -156,9 +131,8 @@ func (m *Matrix) RespondHandler(gpt *openai.Client) (event.Type, mautrix.EventHa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the OpenAI response back to the chat
|
// Send the OpenAI response back to the chat
|
||||||
responseMarkdown := openAiResp.Choices[len(openAiResp.Choices)-1].Message.Content
|
formattedResp := format.RenderMarkdown(resp, true, false)
|
||||||
responseMessage := format.RenderMarkdown(responseMarkdown, true, false)
|
m.client.SendMessageEvent(evt.RoomID, event.EventMessage, &formattedResp)
|
||||||
m.client.SendMessageEvent(evt.RoomID, event.EventMessage, &responseMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1 +0,0 @@
|
||||||
package gpt
|
|
17
main.go
17
main.go
|
@ -4,33 +4,24 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
"ewintr.nl/matrix-bots/matrix"
|
"ewintr.nl/matrix-bots/bot"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/sashabaranov/go-openai"
|
|
||||||
"golang.org/x/exp/slog"
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
||||||
|
|
||||||
matrixClient := matrix.New(matrix.Config{
|
matrixClient := bot.New(bot.Config{
|
||||||
Homeserver: getParam("MATRIX_HOMESERVER", "http://localhost"),
|
Homeserver: getParam("MATRIX_HOMESERVER", "http://localhost"),
|
||||||
UserID: getParam("MATRIX_USER_ID", "@bot:localhost"),
|
UserID: getParam("MATRIX_USER_ID", "@bot:localhost"),
|
||||||
UserPassword: getParam("MATRIX_PASSWORD", "secret"),
|
UserPassword: getParam("MATRIX_PASSWORD", "secret"),
|
||||||
UserAccessKey: getParam("MATRIX_ACCESS_KEY", "secret"),
|
UserAccessKey: getParam("MATRIX_ACCESS_KEY", "secret"),
|
||||||
DBPath: getParam("BOT_DB_PATH", "bot.db"),
|
DBPath: getParam("BOT_DB_PATH", "bot.db"),
|
||||||
Pickle: getParam("BOT_PICKLE", "scrambled"),
|
Pickle: getParam("BOT_PICKLE", "scrambled"),
|
||||||
|
OpenAIKey: getParam("OPENAI_API_KEY", "no key"),
|
||||||
})
|
})
|
||||||
|
|
||||||
OpenaiAPIKey, ok := os.LookupEnv("OPENAI_API_KEY")
|
|
||||||
if !ok {
|
|
||||||
logger.Error("OPENAI_API_KEY is not set")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new OpenAI client
|
|
||||||
openaiClient := openai.NewClient(OpenaiAPIKey)
|
|
||||||
|
|
||||||
if err := matrixClient.Init(); err != nil {
|
if err := matrixClient.Init(); err != nil {
|
||||||
logger.Error(err.Error())
|
logger.Error(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -38,7 +29,7 @@ func main() {
|
||||||
go matrixClient.Run()
|
go matrixClient.Run()
|
||||||
|
|
||||||
matrixClient.AddEventHandler(matrixClient.InviteHandler())
|
matrixClient.AddEventHandler(matrixClient.InviteHandler())
|
||||||
matrixClient.AddEventHandler(matrixClient.RespondHandler(openaiClient))
|
matrixClient.AddEventHandler(matrixClient.RespondHandler())
|
||||||
|
|
||||||
done := make(chan os.Signal)
|
done := make(chan os.Signal)
|
||||||
signal.Notify(done, os.Interrupt)
|
signal.Notify(done, os.Interrupt)
|
||||||
|
|
Loading…
Reference in New Issue