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( cookieValue := base64.URLEncoding.EncodeToString([]byte(
string(signature) + sessionToken)) string(signature) + sessionToken))
cookie := &http.Cookie{ cookie := &http.Cookie{
Name: cookieName, Name: cookieName,
Value: cookieValue, Value: cookieValue,
// Secure: true, Secure: true,
HttpOnly: true, HttpOnly: true,
SameSite: http.SameSiteNoneMode, SameSite: http.SameSiteNoneMode,
// Domain: cfg.ServerConfig.Host,
} }
log.Info("check remote addr for cookie set", log.Info("check remote addr for cookie set",
"remote", remote, "session", session) "remote", remote, "session", session)
if strings.Contains(remote, "192.168.0") { if strings.Contains(remote, "192.168.0") {
// cookie.Domain = "192.168.0.101" cookie.Domain = "192.168.0.106"
cookie.Domain = ""
cookie.SameSite = http.SameSiteLaxMode cookie.SameSite = http.SameSiteLaxMode
cookie.Secure = false
log.Info("changing cookie domain", "domain", cookie.Domain) log.Info("changing cookie domain", "domain", cookie.Domain)
} }
// set ctx? // 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.ActionHistory = append(fi.Room.ActionHistory, action)
fi.Room.MimeDone = true fi.Room.MimeDone = true
notify(models.NotifyBacklogPrefix+fi.Room.ID, clue+num) notify(models.NotifyBacklogPrefix+fi.Room.ID, clue+num)
notifyBotIfNeeded(fi)
if err := saveFullInfo(fi); err != nil { if err := saveFullInfo(fi); err != nil {
abortWithError(w, err.Error()) abortWithError(w, err.Error())
return return

View File

@ -21,7 +21,7 @@ var (
DoneChanMap = make(map[string]chan bool) DoneChanMap = make(map[string]chan bool)
// got prompt: control character (\\u0000-\\u001F) found while parsing a string at line 4 column 0 // 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` 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 { type DSResp struct {
@ -37,6 +37,20 @@ type DSResp struct {
Object string `json:"object"` 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 { type MimeResp struct {
Clue string `json:"clue"` Clue string `json:"clue"`
Number string `json:"number"` Number string `json:"number"`
@ -49,12 +63,13 @@ type GusserResp struct {
} }
type Bot struct { type Bot struct {
Role string // gueeser | mime Role string // gueeser | mime
Team string Team string
cfg *config.Config cfg *config.Config
RoomID string // can we get a room from here? RoomID string // can we get a room from here?
BotName string BotName string
log *slog.Logger log *slog.Logger
LLMParser RespParser
// channels for communicaton // channels for communicaton
// channels are not serializable // channels are not serializable
// SignalsCh chan bool // SignalsCh chan bool
@ -82,43 +97,16 @@ func (b *Bot) StartBot() {
b.log.Error("bot loop", "error", err) b.log.Error("bot loop", "error", err)
return return
} }
dsResp := DSResp{} tempMap, err := b.LLMParser.ParseBytes(llmResp)
if err := json.Unmarshal(llmResp, &dsResp); err != nil { if err != nil {
b.log.Error("failed to unmarshall", "error", err) b.log.Error("bot loop", "error", err)
return 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 eventName := models.NotifyBacklogPrefix + room.ID
eventPayload := "" eventPayload := ""
tempMap := make(map[string]any)
switch b.Role { switch b.Role {
case models.UserRoleMime: case models.UserRoleMime:
// respMap := make(map[string]any)
mimeResp := MimeResp{} 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) b.log.Info("mime resp log", "mimeResp", tempMap)
mimeResp.Clue = tempMap["clue"].(string) mimeResp.Clue = tempMap["clue"].(string)
mimeResp.Number = tempMap["number"].(string) mimeResp.Number = tempMap["number"].(string)
@ -186,7 +174,7 @@ func (b *Bot) StartBot() {
eventName = models.NotifyRoomUpdatePrefix + room.ID eventName = models.NotifyRoomUpdatePrefix + room.ID
eventPayload = "" eventPayload = ""
default: default:
b.log.Error("unexpected role", "role", b.Role, "llmResp", sj) b.log.Error("unexpected role", "role", b.Role, "resp-map", tempMap)
return return
} }
// save room // save room
@ -223,6 +211,11 @@ func NewBot(role, team, name, roomID string, cfg *config.Config) (*Bot, error) {
AddSource: true, 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 // add to room
room, err := getRoomByID(bot.RoomID) room, err := getRoomByID(bot.RoomID)
if err != nil { if err != nil {
@ -321,7 +314,7 @@ func (b *Bot) BuildPrompt(room *models.Room) string {
toText["cards"] = room.Cards toText["cards"] = room.Cards
} }
if b.Role == models.UserRoleGuesser { if b.Role == models.UserRoleGuesser {
copiedCards := []models.WordCard{} copiedCards := make([]models.WordCard, len(room.Cards))
copy(copiedCards, room.Cards) copy(copiedCards, room.Cards)
for i, card := range copiedCards { for i, card := range copiedCards {
if !card.Revealed { 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) b.log.Error("failed to read resp body", "error", err, "url", b.cfg.LLMConfig.URL)
return nil, err 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 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 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 // WhichBotToMove returns bot name that have to move or empty string
func (r *Room) WhichBotToMove() 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) 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 { if ok {
return r.BlueTeam.Mime return r.BlueTeam.Mime
} }
} else {
return getGuesser(r.BotMap, UserTeamBlue)
} }
// check gussers
case UserTeamRed: case UserTeamRed:
if !r.MimeDone { if !r.MimeDone {
_, ok := r.BotMap[r.RedTeam.Mime] _, ok := r.BotMap[r.RedTeam.Mime]
if ok { if ok {
return r.RedTeam.Mime return r.RedTeam.Mime
} }
} else {
return getGuesser(r.BotMap, UserTeamRed)
} }
// check gussers
default: default:
// how did we got here? // how did we got here?
return "" return ""

View File

@ -9,6 +9,9 @@
- there two places for bot to check if its its move: start-game; end-turn; - there two places for bot to check if its its move: start-game; end-turn;
- remove bot button (if game is not running); - 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; - 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 #### sse points
- clue sse update; - clue sse update;