Fix: cookie; add llm parser interface

This commit is contained in:
Grail Finder
2025-06-12 11:21:39 +03:00
parent 74b10b8395
commit 60d62773b8
6 changed files with 144 additions and 49 deletions

View File

@ -142,19 +142,18 @@ func makeCookie(username string, remote string) (*http.Cookie, error) {
cookieValue := base64.URLEncoding.EncodeToString([]byte(
string(signature) + sessionToken))
cookie := &http.Cookie{
Name: cookieName,
Value: cookieValue,
// Secure: true,
Name: cookieName,
Value: cookieValue,
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteNoneMode,
// Domain: cfg.ServerConfig.Host,
}
log.Info("check remote addr for cookie set",
"remote", remote, "session", session)
if strings.Contains(remote, "192.168.0") {
// cookie.Domain = "192.168.0.101"
cookie.Domain = ""
cookie.Domain = "192.168.0.106"
cookie.SameSite = http.SameSiteLaxMode
cookie.Secure = false
log.Info("changing cookie domain", "domain", cookie.Domain)
}
// set ctx?

View File

@ -254,6 +254,7 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) {
fi.Room.ActionHistory = append(fi.Room.ActionHistory, action)
fi.Room.MimeDone = true
notify(models.NotifyBacklogPrefix+fi.Room.ID, clue+num)
notifyBotIfNeeded(fi)
if err := saveFullInfo(fi); err != nil {
abortWithError(w, err.Error())
return

View File

@ -21,7 +21,7 @@ var (
DoneChanMap = make(map[string]chan bool)
// got prompt: control character (\\u0000-\\u001F) found while parsing a string at line 4 column 0
MimePrompt = `we are playing alias;\nyou are a mime (player who gives a clue of one noun word and number of cards you expect them to open) of the %s team (people who would guess by your clue want open the %s cards);\nplease return your clue, number of cards to open and what words you mean them to find using that clue in json like:\n{\n\"clue\": \"one-word-noun\",\n\"number\": \"number-from-0-to-9\",\n\"words_I_mean_my_team_to_open\": [\"this\", \"that\", ...]\n}\nthe team who openes all their cards first wins.\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;\nhere is the game info in json:\n%s`
GuesserPrompt = `we are playing alias;\nyou are to guess words of the %s team (people who would guess by your clue want open the %s cards) by given clue and a number of meant guesses;\nplease return your guesses and words that you did not wish to open, but think that they could be also meant by the clue in json like:\n{\n\"guesses\": [\"word1\", \"word2\", ...],\n\"could_be\": [\"this\", \"that\", ...]\n}\nthe team who openes all their cards first wins.\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;\nhere is the game info in json:\n%s`
GuesserPrompt = `we are playing alias;\nyou are to guess words of the %s team (you want open %s cards) by given clue and a number of meant guesses;\nplease return your guesses and words that could be meant by the clue, but you do not wish to open yet, in json like:\n{\n\"guesses\": [\"word1\", \"word2\", ...],\n\"could_be\": [\"this\", \"that\", ...]\n}\nthe team who openes all their cards first wins.\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;\nhere is the game info in json:\n%s`
)
type DSResp struct {
@ -37,6 +37,20 @@ type DSResp struct {
Object string `json:"object"`
}
type LLMResp struct {
Choices []struct {
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
Message struct {
Content string `json:"content"`
Role string `json:"role"`
} `json:"message"`
} `json:"choices"`
Created int `json:"created"`
Model string `json:"model"`
Object string `json:"object"`
}
type MimeResp struct {
Clue string `json:"clue"`
Number string `json:"number"`
@ -49,12 +63,13 @@ type GusserResp struct {
}
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
Role string // gueeser | mime
Team string
cfg *config.Config
RoomID string // can we get a room from here?
BotName string
log *slog.Logger
LLMParser RespParser
// channels for communicaton
// channels are not serializable
// SignalsCh chan bool
@ -82,43 +97,16 @@ func (b *Bot) StartBot() {
b.log.Error("bot loop", "error", err)
return
}
dsResp := DSResp{}
if err := json.Unmarshal(llmResp, &dsResp); err != nil {
b.log.Error("failed to unmarshall", "error", err)
return
tempMap, err := b.LLMParser.ParseBytes(llmResp)
if err != nil {
b.log.Error("bot loop", "error", err)
continue
}
if len(dsResp.Choices) == 0 {
b.log.Error("empty choices", "dsResp", dsResp)
return
}
text := dsResp.Choices[0].Text
li := strings.Index(text, "{")
ri := strings.LastIndex(text, "}")
if li < 0 || ri < 1 {
b.log.Error("not a json", "msg", text)
return
}
sj := text[li : ri+1]
// jb, err := json.Marshal(sj)
// if err != nil {
// b.log.Error("failed to marshal", "error", err, "string-json", sj)
// return
// }
// parse response
// if mime -> give clue
// if guesser -> open card (does opening one card prompting new loop?)
// send notification to sse broker
eventName := models.NotifyBacklogPrefix + room.ID
eventPayload := ""
tempMap := make(map[string]any)
switch b.Role {
case models.UserRoleMime:
// respMap := make(map[string]any)
mimeResp := MimeResp{}
if err := json.Unmarshal([]byte(sj), &tempMap); err != nil {
b.log.Error("failed to unmarshal mime resp", "error", err, "string-json", sj)
return
}
b.log.Info("mime resp log", "mimeResp", tempMap)
mimeResp.Clue = tempMap["clue"].(string)
mimeResp.Number = tempMap["number"].(string)
@ -186,7 +174,7 @@ func (b *Bot) StartBot() {
eventName = models.NotifyRoomUpdatePrefix + room.ID
eventPayload = ""
default:
b.log.Error("unexpected role", "role", b.Role, "llmResp", sj)
b.log.Error("unexpected role", "role", b.Role, "resp-map", tempMap)
return
}
// save room
@ -223,6 +211,11 @@ func NewBot(role, team, name, roomID string, cfg *config.Config) (*Bot, error) {
AddSource: true,
})),
}
// there might be a better way
bot.LLMParser = NewLCPRespParser(bot.log)
if strings.Contains(cfg.LLMConfig.URL, "api.deepseek.com") {
bot.LLMParser = NewDeepSeekParser(bot.log)
}
// add to room
room, err := getRoomByID(bot.RoomID)
if err != nil {
@ -321,7 +314,7 @@ func (b *Bot) BuildPrompt(room *models.Room) string {
toText["cards"] = room.Cards
}
if b.Role == models.UserRoleGuesser {
copiedCards := []models.WordCard{}
copiedCards := make([]models.WordCard, len(room.Cards))
copy(copiedCards, room.Cards)
for i, card := range copiedCards {
if !card.Revealed {
@ -383,6 +376,6 @@ func (b *Bot) CallLLM(prompt string) ([]byte, error) {
b.log.Error("failed to read resp body", "error", err, "url", b.cfg.LLMConfig.URL)
return nil, err
}
b.log.Debug("llm resp", "body", string(body))
b.log.Debug("llm resp", "body", string(body), "url", b.cfg.LLMConfig.URL)
return body, nil
}

88
llmapi/parser.go Normal file
View File

@ -0,0 +1,88 @@
package llmapi
import (
"encoding/json"
"fmt"
"log/slog"
"strings"
)
type RespParser interface {
ParseBytes(body []byte) (map[string]any, error)
}
// DeepSeekParser: deepseek implementation of RespParser
type deepSeekParser struct {
log *slog.Logger
}
func NewDeepSeekParser(log *slog.Logger) *deepSeekParser {
return &deepSeekParser{log: log}
}
func (p *deepSeekParser) ParseBytes(body []byte) (map[string]any, error) {
// parsing logic here
dsResp := DSResp{}
if err := json.Unmarshal(body, &dsResp); err != nil {
p.log.Error("failed to unmarshall", "error", err)
return nil, err
}
if len(dsResp.Choices) == 0 {
p.log.Error("empty choices", "dsResp", dsResp)
err := fmt.Errorf("empty choices in dsResp")
return nil, err
}
text := dsResp.Choices[0].Text
li := strings.Index(text, "{")
ri := strings.LastIndex(text, "}")
if li < 0 || ri < 1 {
p.log.Error("not a json", "msg", text)
err := fmt.Errorf("fn: ParseBytes, not a json")
return nil, err
}
sj := text[li : ri+1]
respMap := make(map[string]any)
if err := json.Unmarshal([]byte(sj), &respMap); err != nil {
p.log.Error("failed to unmarshal response", "error", err, "string-json", sj)
return nil, err
}
return respMap, nil
}
// llama.cpp implementation of RespParser
type lcpRespParser struct {
log *slog.Logger
}
func NewLCPRespParser(log *slog.Logger) *lcpRespParser {
return &lcpRespParser{log: log}
}
func (p *lcpRespParser) ParseBytes(body []byte) (map[string]any, error) {
// parsing logic here
resp := LLMResp{}
if err := json.Unmarshal(body, &resp); err != nil {
p.log.Error("failed to unmarshal", "error", err)
return nil, err
}
if len(resp.Choices) == 0 {
p.log.Error("empty choices", "resp", resp)
err := fmt.Errorf("empty choices in resp")
return nil, err
}
text := resp.Choices[0].Message.Content
li := strings.Index(text, "{")
ri := strings.LastIndex(text, "}")
if li < 0 || ri < 1 {
p.log.Error("not a json", "msg", text)
err := fmt.Errorf("fn: ParseBytes, not a json")
return nil, err
}
sj := text[li : ri+1]
respMap := make(map[string]any)
if err := json.Unmarshal([]byte(sj), &respMap); err != nil {
p.log.Error("failed to unmarshal response", "error", err, "string-json", sj)
return nil, err
}
return respMap, nil
}

View File

@ -103,6 +103,15 @@ func (r *Room) CanStart() error {
return nil
}
func getGuesser(m map[string]BotPlayer, team UserTeam) string {
for k, v := range m {
if v.Team == team && v.Role == UserRoleGuesser {
return k
}
}
return ""
}
// WhichBotToMove returns bot name that have to move or empty string
func (r *Room) WhichBotToMove() string {
fmt.Println("looking for bot to move", "team-turn", r.TeamTurn, "mime-done", r.MimeDone, "bot-map", r.BotMap, "is_running", r.IsRunning)
@ -116,16 +125,18 @@ func (r *Room) WhichBotToMove() string {
if ok {
return r.BlueTeam.Mime
}
} else {
return getGuesser(r.BotMap, UserTeamBlue)
}
// check gussers
case UserTeamRed:
if !r.MimeDone {
_, ok := r.BotMap[r.RedTeam.Mime]
if ok {
return r.RedTeam.Mime
}
} else {
return getGuesser(r.BotMap, UserTeamRed)
}
// check gussers
default:
// how did we got here?
return ""

View File

@ -9,6 +9,9 @@
- there two places for bot to check if its its move: start-game; end-turn;
- remove bot button (if game is not running);
- show in backlog (and with that in prompt to llm) how many cards are left to open, also additional comment: if guess was right;
- if bot already added; remove add bot button;
- hide clue input for mime when it's not their turn;
- needs resend to llm btn;
#### sse points
- clue sse update;