diff options
author | href <href@random.sh> | 2022-12-03 02:00:37 +0000 |
---|---|---|
committer | Jordan Bracco <href@random.sh> | 2022-12-11 02:03:36 +0000 |
commit | 251ea43c1308eb96e4ada16edf6481a8be1fa765 (patch) | |
tree | 2148e304574c684f7eb1e74607634a907df9085d /lib/lsg_irc | |
parent | new plugin: radio france (diff) |
new plugin: openai gpt
Diffstat (limited to 'lib/lsg_irc')
-rw-r--r-- | lib/lsg_irc/gpt_plugin.ex | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/lib/lsg_irc/gpt_plugin.ex b/lib/lsg_irc/gpt_plugin.ex new file mode 100644 index 0000000..f628f8d --- /dev/null +++ b/lib/lsg_irc/gpt_plugin.ex @@ -0,0 +1,103 @@ +defmodule LSG.IRC.GptPlugin do + require Logger + + def irc_doc() do + """ + # OpenAI GPT + + * **!gpt** list GPT tasks + * **!gpt `[task]` `<task args>`** run a task + * **?offensive `<content>`** is content offensive + """ + end + + @couch_db "bot-plugin-openai-prompts" + @trigger "gpt" + + def start_link() do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + def init(_) do + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "trigger:#{@trigger}", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:offensive", regopts) + {:ok, nil} + end + + def handle_info({:irc, :trigger, @trigger, m = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: [task | args]}}}, state) do + case Couch.get(@couch_db, task) do + {:ok, task} -> task(m, task, Enum.join(args, " ")) + {:error, :not_found} -> m.replyfun.("gpt: no such task: #{task}") + error -> + Logger.info("gpt: task load error: #{inspect error}") + m.replyfun.("gpt: database error") + end + {:noreply, state} + end + + def handle_info({:irc, :trigger, @trigger, m = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: []}}}, state) do + case Couch.get(@couch_db, "_all_docs") do + {:ok, %{"rows" => []}} -> m.replyfun.("gpt: no tasks available") + {:ok, %{"rows" => tasks}} -> + tasks = tasks |> Enum.map(fn(task) -> Map.get(task, "id") end) |> Enum.join(", ") + m.replyfun.("gpt: tasks: #{tasks}") + error -> + Logger.info("gpt: task load error: #{inspect error}") + m.replyfun.("gpt: database error") + end + {:noreply, state} + end + + def handle_info({:irc, :trigger, "offensive", m = %IRC.Message{trigger: %IRC.Trigger{type: :query, args: text}}}, state) do + text = Enum.join(text, " ") + {moderate?, moderation} = moderation(text, m.account.id) + reply = cond do + moderate? -> "⚠️ #{Enum.join(moderation, ", ")}" + !moderate? && moderation -> "👍" + !moderate? -> "☠️ error" + end + m.replyfun.(reply) + {:noreply, state} + end + + def handle_info(_, state) do + {:noreply, state} + end + + defp task(msg, task = %{"type" => "completions", "prompt" => prompt}, content) do + prompt = Tmpl.render(prompt, msg, %{"content" => content}) + args = Map.get(task, "openai_params") + |> Map.put("prompt", prompt) + |> Map.put("user", msg.account.id) + {moderate?, moderation} = moderation(content, msg.account.id) + if moderate?, do: msg.replyfun.("⚠️ offensive input: #{Enum.join(moderation, ", ")}") + Logger.debug("GPT: request #{inspect args}") + case OpenAi.post("/v1/completions", args) do + {:ok, %{"choices" => [%{"text" => text} | _]}} -> + {moderate?, moderation} = moderation(text, msg.account.id) + if moderate?, do: msg.replyfun.("🚨 offensive output: #{Enum.join(moderation, ", ")}") + msg.replyfun.(String.trim(text)) + error -> + Logger.error("gpt error: #{inspect error}") + msg.replyfun.("gpt: ☠️ ") + end + end + + defp moderation(content, user_id) do + case OpenAi.post("/v1/moderations", %{"input" => content, "user" => user_id}) do + {:ok, %{"results" => [%{"flagged" => true, "categories" => categories} | _]}} -> + cat = categories + |> Enum.filter(fn({_key, value}) -> value end) + |> Enum.map(fn({key, _}) -> key end) + {true, cat} + {:ok, moderation} -> + Logger.debug("gpt: moderation: not flagged, #{inspect moderation}") + {false, true} + error -> + Logger.error("gpt: moderation error: #{inspect error}") + {false, false} + end + end + +end |