Fix: cookie; add llm parser interface
This commit is contained in:
		| @@ -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
									
								
							
							
						
						
									
										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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder