Fix: cookie; add llm parser interface
This commit is contained in:
		| @@ -144,17 +144,16 @@ func makeCookie(username string, remote string) (*http.Cookie, error) { | ||||
| 	cookie := &http.Cookie{ | ||||
| 		Name:     cookieName, | ||||
| 		Value:    cookieValue, | ||||
| 		// Secure:   true, | ||||
| 		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? | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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"` | ||||
| @@ -55,6 +69,7 @@ type Bot struct { | ||||
| 	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
									
								
							
							
						
						
									
										88
									
								
								llmapi/parser.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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 "" | ||||
|   | ||||
							
								
								
									
										3
									
								
								todos.md
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								todos.md
									
									
									
									
									
								
							| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder