diff options
| author | Xe Iaso <me@xeiaso.net> | 2023-09-26 06:18:56 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2023-09-26 06:18:56 -0400 |
| commit | e260e2b17bbecd34afb49c50cd7dbf4d3b43fed3 (patch) | |
| tree | 64f9ab425c6037509ae13b9f1a7762837150c5c4 | |
| parent | 2654099b2645423152de4e790ea658ff0ab632e4 (diff) | |
| download | x-e260e2b17bbecd34afb49c50cd7dbf4d3b43fed3.tar.xz x-e260e2b17bbecd34afb49c50cd7dbf4d3b43fed3.zip | |
cmd/sapientwindex: add reddit bot
Signed-off-by: Xe Iaso <me@xeiaso.net>
| -rw-r--r-- | cmd/sapientwindex/foo.json | 11 | ||||
| -rw-r--r-- | cmd/sapientwindex/llama.go | 112 | ||||
| -rw-r--r-- | cmd/sapientwindex/main.go | 163 | ||||
| -rw-r--r-- | cmd/sapientwindex/prompts/helper.txt | 11 | ||||
| -rw-r--r-- | cmd/sapientwindex/prompts/moderation.txt | 7 | ||||
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | go.sum | 8 | ||||
| -rw-r--r-- | gomod2nix.toml | 9 |
8 files changed, 324 insertions, 0 deletions
diff --git a/cmd/sapientwindex/foo.json b/cmd/sapientwindex/foo.json new file mode 100644 index 0000000..3b01251 --- /dev/null +++ b/cmd/sapientwindex/foo.json @@ -0,0 +1,11 @@ +{ + "temperature": 0.8, + "top_k": 40, + "top_p": 0.9, + "stream": false, + "prompt": "<s>[INST] <<SYS>>\nYou are an expert in creating tulpas, also known as tulpamancy. When you are given questions from users, you will answer questions in one paragraph like a redditor with casual language. ONLY reply in plain text. DO NOT return anything but your response. DO NOT use emoji.\n\nBegin your answer with ANSWER:\n<</SYS>>\nAnswer this question:\n\nHow I can understand that tulpa is really answering me and I'm not imagining his answer?\n\nSometimes I'm really not sure with this. Maybe someone can help me?\n[/INST]", + "repeat_penalty": 1.15, + "repeat_last_n": 512, + "mirostat": 2, + "n_predict": 2048 +} diff --git a/cmd/sapientwindex/llama.go b/cmd/sapientwindex/llama.go new file mode 100644 index 0000000..7895832 --- /dev/null +++ b/cmd/sapientwindex/llama.go @@ -0,0 +1,112 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "io" + "net/http" + + "within.website/x/web" +) + +var ( + llamaServer = flag.String("llama-server", "http://kos-mos:8080/completion", "API server for LLAMA 2") +) + +func Predict(opts *LLAMAOpts) (*LLAMAResponse, error) { + jsonData, err := json.Marshal(opts) + if err != nil { + return nil, err + } + // Make a POST request to the server + resp, err := http.Post(*llamaServer, "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + // Check the response status code + if resp.StatusCode != http.StatusOK { + return nil, web.NewError(http.StatusOK, resp) + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var result LLAMAResponse + + if err := json.Unmarshal(data, &result); err != nil { + return nil, err + } + + return &result, nil +} + +type LLAMAOpts struct { + Temperature float64 `json:"temperature"` + TopK int `json:"top_k"` + TopP float64 `json:"top_p"` + Stream bool `json:"stream"` + Prompt string `json:"prompt"` + RepeatPenalty float64 `json:"repeat_penalty"` + RepeatLastN int `json:"repeat_last_n"` + Mirostat int `json:"mirostat"` + NPredict int `json:"n_predict"` +} + +type LLAMAResponse struct { + Content string `json:"content"` + GenerationSettings GenerationSettings `json:"generation_settings"` + Model string `json:"model"` + Prompt string `json:"prompt"` + Stop bool `json:"stop"` + StoppedEos bool `json:"stopped_eos"` + StoppedLimit bool `json:"stopped_limit"` + StoppedWord bool `json:"stopped_word"` + StoppingWord string `json:"stopping_word"` + Timings Timings `json:"timings"` + TokensCached int `json:"tokens_cached"` + TokensEvaluated int `json:"tokens_evaluated"` + TokensPredicted int `json:"tokens_predicted"` + Truncated bool `json:"truncated"` +} + +type GenerationSettings struct { + FrequencyPenalty float64 `json:"frequency_penalty"` + Grammar string `json:"grammar"` + IgnoreEos bool `json:"ignore_eos"` + LogitBias []any `json:"logit_bias"` + Mirostat int `json:"mirostat"` + MirostatEta float64 `json:"mirostat_eta"` + MirostatTau float64 `json:"mirostat_tau"` + Model string `json:"model"` + NCtx int `json:"n_ctx"` + NKeep int `json:"n_keep"` + NPredict int `json:"n_predict"` + NProbs int `json:"n_probs"` + PenalizeNl bool `json:"penalize_nl"` + PresencePenalty float64 `json:"presence_penalty"` + RepeatLastN int `json:"repeat_last_n"` + RepeatPenalty float64 `json:"repeat_penalty"` + Seed int64 `json:"seed"` + Stop []any `json:"stop"` + Stream bool `json:"stream"` + Temp float64 `json:"temp"` + TfsZ float64 `json:"tfs_z"` + TopK int `json:"top_k"` + TopP float64 `json:"top_p"` + TypicalP float64 `json:"typical_p"` +} + +type Timings struct { + PredictedMs float64 `json:"predicted_ms"` + PredictedN int `json:"predicted_n"` + PredictedPerSecond float64 `json:"predicted_per_second"` + PredictedPerTokenMs float64 `json:"predicted_per_token_ms"` + PromptMs float64 `json:"prompt_ms"` + PromptN int `json:"prompt_n"` + PromptPerSecond float64 `json:"prompt_per_second"` + PromptPerTokenMs float64 `json:"prompt_per_token_ms"` +} diff --git a/cmd/sapientwindex/main.go b/cmd/sapientwindex/main.go new file mode 100644 index 0000000..dc825ba --- /dev/null +++ b/cmd/sapientwindex/main.go @@ -0,0 +1,163 @@ +package main + +import ( + "bytes" + "embed" + "flag" + "fmt" + "log" + "log/slog" + "strings" + "text/template" + "time" + + "github.com/Marcel-ICMC/graw" + "github.com/Marcel-ICMC/graw/reddit" + "within.website/x/internal" +) + +var ( + redditUsername = flag.String("reddit-username", "", "reddit username") + redditPassword = flag.String("reddit-password", "", "reddit password") + redditAppID = flag.String("reddit-app-id", "", "reddit app id") + redditAppSecret = flag.String("reddit-app-secret", "", "reddit app secret") + subreddit = flag.String("subreddit", "shadowh511", "subreddit to post to") + scanDuration = flag.Duration("scan-duration", 30*time.Second, "how long to scan for") + + //go:embed prompts/*.txt + prompts embed.FS +) + +func main() { + internal.HandleStartup() + + slog.Info("starting up", "username", *redditUsername, "subreddit", *subreddit, "scan_duration", (*scanDuration).String()) + + cfg := reddit.BotConfig{ + Agent: "graw:sapientwindex:0.0.1 by /u/shadowh511", + App: reddit.App{ + ID: *redditAppID, + Secret: *redditAppSecret, + Username: *redditUsername, + Password: *redditPassword, + }, + } + + bot, err := reddit.NewBot(cfg) + if err != nil { + log.Fatal(err) + } + + handle, err := reddit.NewScript(cfg.Agent, *scanDuration) + if err != nil { + log.Fatal(err) + } + announce := &announcer{bot: bot} + + scriptCfg := graw.Config{Subreddits: []string{*subreddit, "shadowh511"}} + + stop, wait, err := graw.Scan(announce, handle, scriptCfg) + if err != nil { + log.Fatal(err) + } + + defer stop() + + wait() +} + +type announcer struct { + bot reddit.Bot +} + +func makePrompt(kind, title, body string) (string, error) { + data, err := prompts.ReadFile("prompts/" + kind + ".txt") + if err != nil { + return "", fmt.Errorf("read prompt: %w", err) + } + + tmpl, err := template.New("prompt").Parse(string(data)) + if err != nil { + return "", fmt.Errorf("parse prompts: %w", err) + } + + var prompt bytes.Buffer + err = tmpl.Execute(&prompt, struct { + Title string + Body string + }{ + Title: title, + Body: body, + }) + if err != nil { + return "", fmt.Errorf("execute template: %w", err) + } + + return prompt.String(), nil +} + +func (a *announcer) Post(post *reddit.Post) error { + if post.LinkFlairText == "Personal" { + return nil + } + + slog.Info("got post", "title", post.Title, "body", post.SelfText) + + prompt, err := makePrompt("moderation", post.Title, post.SelfText) + if err != nil { + slog.Error("make prompt", "err", err) + return nil + } + + opts := &LLAMAOpts{ + Temperature: 0.8, + TopK: 40, + TopP: 0.9, + Stream: false, + Prompt: prompt, + RepeatPenalty: 1.15, + RepeatLastN: 512, + Mirostat: 2, + NPredict: 2048, + } + + resp, err := Predict(opts) + if err != nil { + slog.Error("predict", "err", err) + return nil + } + + if !strings.HasPrefix(strings.ToUpper(strings.TrimSpace(resp.Content)), "YES") { + slog.Info("not a question, skipping", "title", post.Title, "body", post.SelfText, "response", resp.Content) + return nil + } + + prompt, err = makePrompt("helper", post.Title, post.SelfText) + if err != nil { + slog.Error("make prompt", "err", err) + return nil + } + + opts.Prompt = prompt + + resp, err = Predict(opts) + if err != nil { + slog.Error("predict", "err", err) + return nil + } + + body := massageAnswer(resp.Content) + + if err := a.bot.Reply(post.Name, body); err != nil { + slog.Error("reply", "err", err) + return nil + } + + return nil +} + +func massageAnswer(answer string) string { + answer = strings.TrimSpace(answer) + answer = strings.TrimPrefix(answer, "ANSWER: ") + return answer +} diff --git a/cmd/sapientwindex/prompts/helper.txt b/cmd/sapientwindex/prompts/helper.txt new file mode 100644 index 0000000..4fe2a47 --- /dev/null +++ b/cmd/sapientwindex/prompts/helper.txt @@ -0,0 +1,11 @@ +<s>[INST] <<SYS>> +You are an expert in creating tulpas, also known as tulpamancy. When you are given questions from users, you will answer questions in one paragraph like a redditor with casual language. ONLY reply in plain text. DO NOT return anything but your response. DO NOT use emoji. + +Begin your answer with ANSWER: +<</SYS>> +Answer this question: + +{{.Title}} + +{{.Body}} +[/INST] diff --git a/cmd/sapientwindex/prompts/moderation.txt b/cmd/sapientwindex/prompts/moderation.txt new file mode 100644 index 0000000..e6e9620 --- /dev/null +++ b/cmd/sapientwindex/prompts/moderation.txt @@ -0,0 +1,7 @@ +<s>[INST] <<SYS>> +You are the content moderator for a subreddit. Does this look like a question about tulpas or tulpamancy? Respond YES or NO. +<</SYS>> +{{.Title}} + +{{.Body}} +[/INST]
\ No newline at end of file @@ -50,6 +50,7 @@ require ( ) require ( + github.com/Marcel-ICMC/graw v0.0.0-20230411090719-e24cd8592d25 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect github.com/antonmedv/expr v1.15.0 // indirect @@ -65,8 +66,10 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/miekg/dns v1.1.55 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/turnage/redditproto v0.0.0-20151223012412-afedf1b6eddb // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.48.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 // indirect @@ -64,6 +64,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Marcel-ICMC/graw v0.0.0-20230411090719-e24cd8592d25 h1:2y0Jf51U3toefyF80qgCNfd8qZEjkmWSvBX/DNhM6Mw= +github.com/Marcel-ICMC/graw v0.0.0-20230411090719-e24cd8592d25/go.mod h1:Tc1Bv6CivnFGhW5kjO2ZN9/PMlGJ6O4cVFYkaTNWSY8= github.com/McKael/madon/v2 v2.4.0 h1:u5bwEs7r3ek2L1KFOZ27wzHY1vY9bnG4oQDzcrbJyK4= github.com/McKael/madon/v2 v2.4.0/go.mod h1:fSjqeQzvbKPjWQOv0VV3SPKvPCmj6Y+meOqkvQbXEnU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -414,6 +416,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -440,6 +443,9 @@ github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mndrix/golog v0.0.0-20170330170653-a28e2a269775 h1:KPqf9x/eMg3ZnHATLXcM1OgQMNVkPUv1QcGv6zTRMRg= github.com/mndrix/golog v0.0.0-20170330170653-a28e2a269775/go.mod h1:Q4YHYl483MNk6wwg3g8YsINpKe5S2UzUJCRSRlFaSU0= github.com/mndrix/ps v0.0.0-20170330174427-18e65badd6ab h1:fPrYMvMnWuED0MLhLyrny1fLaHhtiXK30pNyBGrk9Gs= @@ -566,6 +572,8 @@ github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 h1:l/T7dYuJEQZOwV github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64/go.mod h1:Q1NAJOuRdQCqN/VIWdnaaEhV8LpeO2rtlBP7/iDJNII= github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY= github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef/go.mod h1:WLFStEdnJXpjK8kd4qKLwQKX/1vrDzp5BcDyiZJBHJM= +github.com/turnage/redditproto v0.0.0-20151223012412-afedf1b6eddb h1:qR56NGRvs2hTUbkn6QF8bEJzxPIoMw3Np3UigBeJO5A= +github.com/turnage/redditproto v0.0.0-20151223012412-afedf1b6eddb/go.mod h1:GyqJdEoZSNoxKDb7Z2Lu/bX63jtFukwpaTP9ZIS5Ei0= github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8= github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY= diff --git a/gomod2nix.toml b/gomod2nix.toml index 612fe51..cd01831 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -7,6 +7,9 @@ schema = 3 [mod."filippo.io/edwards25519"] version = "v1.0.0" hash = "sha256-APnPAcmItvtJ5Zsy863lzR2TjEBF9Y66TY1e4M1ap98=" + [mod."github.com/Marcel-ICMC/graw"] + version = "v0.0.0-20230411090719-e24cd8592d25" + hash = "sha256-oKeE+BQHXpFEU3g21Z41PeSFJicABcRYsGEs807oo40=" [mod."github.com/McKael/madon/v2"] version = "v2.4.0" hash = "sha256-bEkpnDMyJtzU5epFNrjLhgJW+cp5Tf4YL7lk99q0ymM=" @@ -262,6 +265,9 @@ schema = 3 [mod."github.com/mitchellh/go-ps"] version = "v1.0.0" hash = "sha256-HzxVHNLHZpnsBuPcub0G+9jjDcDOsxM/6wifbsxf7EY=" + [mod."github.com/mitchellh/mapstructure"] + version = "v1.5.0" + hash = "sha256-ztVhGQXs67MF8UadVvG72G3ly0ypQW0IRDdOOkjYwoE=" [mod."github.com/mndrix/golog"] version = "v0.0.0-20170330170653-a28e2a269775" hash = "sha256-JD5kY0Krc1yHXT+0b3XzkwqFKpIvY3VFSok4/yHGOP8=" @@ -373,6 +379,9 @@ schema = 3 [mod."github.com/tmc/scp"] version = "v0.0.0-20170824174625-f7b48647feef" hash = "sha256-qHwQb3JA43VVhJdB18/2zXbxqW/bgw1yvr8YhqFyd74=" + [mod."github.com/turnage/redditproto"] + version = "v0.0.0-20151223012412-afedf1b6eddb" + hash = "sha256-ageZnAhwB2i8VP9Qa3ChUQG2a1cJu/89e6tVFdM6qyM=" [mod."github.com/u-root/uio"] version = "v0.0.0-20230305220412-3e8cd9d6bf63" hash = "sha256-y0VT9PLROozi6wNMgnX706ifumQxlMc8y4/XZDhdfMY=" |
