package llmapi import ( "encoding/json" "errors" "fmt" "io" "log/slog" "strings" ) type RespParser interface { ParseBytes(body []byte) (map[string]any, error) MakePayload(prompt string) io.Reader } // 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 := errors.New("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; data: %s", text) 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 } func (p *deepSeekParser) MakePayload(prompt string) io.Reader { return strings.NewReader(fmt.Sprintf(`{ "model": "deepseek-chat", "prompt": "%s", "echo": false, "frequency_penalty": 0, "logprobs": 0, "max_tokens": 1024, "presence_penalty": 0, "stop": null, "stream": false, "stream_options": null, "suffix": null, "temperature": 1, "top_p": 1 }`, prompt)) } // 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 } text := resp.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; data: %s", text) 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 } func (p *lcpRespParser) MakePayload(prompt string) io.Reader { return strings.NewReader(fmt.Sprintf(`{ "model": "local-model", "prompt": "%s", "frequency_penalty": 0, "max_tokens": 1024, "stop": null, "stream": false, "temperature": 0.4, "top_p": 1 }`, prompt)) } type openRouterParser struct { log *slog.Logger modelIndex uint32 } func NewOpenRouterParser(log *slog.Logger) *openRouterParser { return &openRouterParser{ log: log, modelIndex: 0, } } func (p *openRouterParser) ParseBytes(body []byte) (map[string]any, error) { // parsing logic here resp := OpenRouterResp{} 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 := errors.New("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; data: %s", text) 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 } func (p *openRouterParser) MakePayload(prompt string) io.Reader { // Models to rotate through models := []string{ "google/gemini-2.0-flash-exp:free", "deepseek/deepseek-chat-v3-0324:free", "mistralai/mistral-small-3.2-24b-instruct:free", "qwen/qwen3-14b:free", "deepseek/deepseek-r1:free", "google/gemma-3-27b-it:free", "meta-llama/llama-3.3-70b-instruct:free", } // Get next model index using atomic addition for thread safety p.modelIndex++ model := models[int(p.modelIndex)%len(models)] strPayload := fmt.Sprintf(`{ "model": "%s", "messages": [ { "role": "user", "content": "%s" } ] }`, model, prompt) p.log.Debug("made openrouter payload", "model", model, "payload", strPayload) return strings.NewReader(strPayload) }