Files
golias/llmapi/main.go
2025-05-22 07:49:07 +03:00

206 lines
4.5 KiB
Go

package llmapi
import (
"encoding/json"
"errors"
"fmt"
"golias/config"
"golias/models"
"golias/pkg/cache"
"io"
"log/slog"
"net/http"
"strings"
)
// TODO: config for url and token
// completion prompt
// MIME: llm needs to know all the cards, colors and previous actions
// GUESSER: llm needs to know all the cards and previous actions
type Bot struct {
Role string // gueeser | mime
Team string
cfg *config.Config
RoomID string // can we get a room from here?
BotName string
log *slog.Logger
// channels for communicaton
SignalsCh chan bool
DoneCh chan bool
}
// StartBot
func (b *Bot) StartBot() {
for {
select {
case <-b.SignalsCh:
// get room cards and actions
room, err := getRoomByID(b.RoomID)
if err != nil {
b.log.Error("bot loop", "error", err)
return
}
// form prompt
prompt := b.BuildPrompt(room)
b.log.Debug("got prompt", "prompt", prompt)
// call llm
if err := b.CallLLM(prompt); err != nil {
b.log.Error("bot loop", "error", err)
return
}
// parse response
// if mime -> give clue
// if guesser -> open card (does opening one card prompting new loop?)
// send notification to sse broker
case <-b.DoneCh:
b.log.Debug("got done signal", "bot-name", b.BotName)
return
}
}
}
// EndBot
func NewBot(role, team, name, roomID string, cfg *config.Config) (*Bot, error) {
bot := &Bot{
Role: role,
RoomID: roomID,
BotName: name,
Team: team,
cfg: cfg,
}
// add to room
room, err := getRoomByID(bot.RoomID)
if err != nil {
return nil, err
}
// check if not running
if role == "mime" && room.IsRunning {
return nil, errors.New("cannot join after game started")
}
room.PlayerList = append(room.PlayerList, name)
bp := models.BotPlayer{
Role: models.StrToUserRole(role),
Team: models.StrToUserTeam(team),
}
room.BotMap[name] = bp
switch team {
case "red":
if role == "mime" {
room.RedTeam.Mime = name
} else if role == "guesser" {
room.RedTeam.Guessers = append(room.RedTeam.Guessers, name)
} else {
return nil, fmt.Errorf("uknown role: %s", role)
}
case "blue":
if role == "mime" {
room.BlueTeam.Mime = name
} else if role == "guesser" {
room.BlueTeam.Guessers = append(room.BlueTeam.Guessers, name)
} else {
return nil, fmt.Errorf("uknown role: %s", role)
}
default:
return nil, fmt.Errorf("uknown team: %s", team)
}
if err := saveRoom(room); err != nil {
return nil, err
}
go bot.StartBot() // run bot routine
return bot, nil
}
func getRoomByID(roomID string) (*models.Room, error) {
roomBytes, err := cache.MemCache.Get(models.CacheRoomPrefix + roomID)
if err != nil {
return nil, err
}
resp := &models.Room{}
if err := json.Unmarshal(roomBytes, &resp); err != nil {
return nil, err
}
return resp, nil
}
func saveRoom(room *models.Room) error {
key := models.CacheRoomPrefix + room.ID
data, err := json.Marshal(room)
if err != nil {
return err
}
cache.MemCache.Set(key, data)
return nil
}
func (b *Bot) BuildPrompt(room *models.Room) string {
if b.Role == "" {
return ""
}
toText := make(map[string]any)
toText["backlog"] = room.ActionHistory
// mime sees all colors;
// guesser sees only revealed ones
if b.Role == models.UserRoleMime {
toText["cards"] = room.Cards
}
if b.Role == models.UserRoleGuesser {
copiedCards := []models.WordCard{}
copy(copiedCards, room.Cards)
for i, card := range copiedCards {
if !card.Revealed {
copiedCards[i].Color = models.WordColorUknown
}
}
toText["cards"] = copiedCards
}
data, err := json.MarshalIndent(toText, "", " ")
if err != nil {
// log
return ""
}
return string(data)
}
func (b *Bot) CallLLM(prompt string) error {
method := "POST"
payload := strings.NewReader(fmt.Sprintf(`{
"model": "deepseek-chat",
"prompt": "%s",
"echo": false,
"frequency_penalty": 0,
"logprobs": 0,
"max_tokens": 1024,
"presence_penalty": 0,
"stop": null,
"stream": false,
"stream_options": null,
"suffix": null,
"temperature": 1,
"top_p": 1
}`, prompt))
client := &http.Client{}
req, err := http.NewRequest(method, b.cfg.LLMConfig.URL, payload)
if err != nil {
fmt.Println(err)
return err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", "Bearer "+b.cfg.LLMConfig.TOKEN)
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return err
}
fmt.Println(string(body))
return nil
}