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", fmt.Sprintf("Bearer %s", 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 }