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{ | 	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? | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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"` | ||||||
| @@ -55,6 +69,7 @@ type Bot struct { | |||||||
| 	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
									
								
							
							
						
						
									
										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 | 	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 "" | ||||||
|   | |||||||
							
								
								
									
										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; | - 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; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder