From db7451ad9d9446652de0986c095cc57a323f1354 Mon Sep 17 00:00:00 2001 From: Gordon Shumway Date: Mon, 5 Aug 2024 20:39:58 -0400 Subject: [PATCH] Add Gemini plugin --- Gemini/README.md | 20 ++++++ Gemini/__init__.py | 69 ++++++++++++++++++ Gemini/config.py | 146 +++++++++++++++++++++++++++++++++++++++ Gemini/local/__init__.py | 1 + Gemini/plugin.py | 89 ++++++++++++++++++++++++ Gemini/requirements.txt | 1 + Gemini/setup.py | 35 ++++++++++ Gemini/test.py | 38 ++++++++++ 8 files changed, 399 insertions(+) create mode 100644 Gemini/README.md create mode 100644 Gemini/__init__.py create mode 100644 Gemini/config.py create mode 100644 Gemini/local/__init__.py create mode 100644 Gemini/plugin.py create mode 100644 Gemini/requirements.txt create mode 100644 Gemini/setup.py create mode 100644 Gemini/test.py diff --git a/Gemini/README.md b/Gemini/README.md new file mode 100644 index 0000000..959577e --- /dev/null +++ b/Gemini/README.md @@ -0,0 +1,20 @@ +GoogleAI Gemini Chat Plugin + +This plugin is under development and probably shouldn't be used by anyone... + +Get an API key from https://aistudio.google.com/app/apikey + +@config plugins.gemini.api_key YOUR_KEY_HERE +system prompt: + +@config plugins.gemini.prompt "You are $botnick the IRC bot. Be brief, helpful" +^^ Configurable per channel, etc. get creative + +@config list plugins.gemini +^^ Please take a look at the various options and configure stuff before you do anything. + +@chat +^^ Command to send text to the chatgpt API + +@messageparser add "(?i)(.*BOT_NICK_HERE)(?:[:]*)(.*)" "chat $1$2" +^^ replace BOT_NICK_HERE with your bot nick and add automatic replies to nick mentions diff --git a/Gemini/__init__.py b/Gemini/__init__.py new file mode 100644 index 0000000..cb7e23b --- /dev/null +++ b/Gemini/__init__.py @@ -0,0 +1,69 @@ +### +# Copyright (c) 2024, oddluck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +""" +Gemini: GoogleAI Gemini Chat Plugin +""" + +import sys +import supybot +from supybot import world + +# Use this for the version of this plugin. +__version__ = "" + +# XXX Replace this with an appropriate author or supybot.Author instance. +__author__ = supybot.authors.unknown + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +# This is a url where the most recent plugin package can be downloaded. +__url__ = "" + +from . import config +from . import plugin +from importlib import reload + +# In case we're being reloaded. +reload(config) +reload(plugin) +# Add more reloads here if you add third-party modules and want them to be +# reloaded when this plugin is reloaded. Don't forget to import them as well! + +if world.testing: + from . import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/Gemini/config.py b/Gemini/config.py new file mode 100644 index 0000000..9e98aac --- /dev/null +++ b/Gemini/config.py @@ -0,0 +1,146 @@ +### +# Copyright (c) 2024, oddluck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +from supybot import conf, registry + +try: + from supybot.i18n import PluginInternationalization + + _ = PluginInternationalization("Gemini") +except: + # Placeholder that allows to run the plugin on a bot + # without the i18n module + _ = lambda x: x + + +def configure(advanced): + # This will be called by supybot to configure this module. advanced is + # a bool that specifies whether the user identified themself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + + conf.registerPlugin("Gemini", True) + + +Gemini = conf.registerPlugin("Gemini") +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(Gemini, 'someConfigVariableName', +# registry.Boolean(False, _("""Help for someConfigVariableName."""))) + +conf.registerChannelValue( + Gemini, + "api_key", + registry.String( + "", + _("""Your GoogleAI API Key (required)"""), + private=True, + ), +) + +conf.registerChannelValue( + Gemini, + "model", + registry.String( + "gemini-1.5-flash-latest", + _( + """ + GoogleAI endpoint model, default: "gemini-1.5-flash-latest" + """ + ), + ), +) + +conf.registerChannelValue( + Gemini, + "prompt", + registry.String( + "You are $botnick the IRC bot. Be brief, helpful. Keep each line of reply 400 characters or less. Do not prefix replies with your name.", + _( + """ + The prompt defining your bot's personality. + """ + ), + ), +) + +conf.registerChannelValue( + Gemini, + "max_tokens", + registry.Integer( + 200, + _( + """ + The maximum number of tokens to generate in the response + """ + ), + ), +) + +conf.registerChannelValue( + Gemini, + "max_history", + registry.Integer( + 50, + _( + """ + The maximum number of messages to keep in conversation history. + """ + ), + ), +) + +conf.registerChannelValue( + Gemini, + "nick_include", + registry.Boolean( + True, + _( + """ + Include user nicks in history/queries. Disabled will treat conversation as if from a single user. + """ + ), + ), +) + +conf.registerChannelValue( + Gemini, + "nick_prefix", + registry.Boolean( + False, + _( + """ + Prefix nick on replies true/false... + """ + ), + ), +) + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/Gemini/local/__init__.py b/Gemini/local/__init__.py new file mode 100644 index 0000000..e86e97b --- /dev/null +++ b/Gemini/local/__init__.py @@ -0,0 +1 @@ +# Stub so local is a module, used for third-party modules diff --git a/Gemini/plugin.py b/Gemini/plugin.py new file mode 100644 index 0000000..f123638 --- /dev/null +++ b/Gemini/plugin.py @@ -0,0 +1,89 @@ +### +# Copyright (c) 2024, oddluck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +from supybot import utils, plugins, ircutils, callbacks +from supybot.commands import * +from supybot.i18n import PluginInternationalization +import google.generativeai as genai + +_ = PluginInternationalization("Gemini") + + +class Gemini(callbacks.Plugin): + """GoogleAI Gemini Chat Plugin""" + + threaded = True + + def __init__(self, irc): + self.__parent = super(Gemini, self) + self.__parent.__init__(irc) + self.history = {} + + def chat(self, irc, msg, args, text): + """Chat Call to the Gemini API""" + channel = msg.channel + if not irc.isChannel(channel): + channel = msg.nick + api_key = self.registryValue("api_key", msg.channel) + genai.configure(api_key=api_key) + max_tokens = self.registryValue("max_tokens", msg.channel) + max_history = self.registryValue("max_history", msg.channel) + generation_config = {"max_output_tokens": max_tokens} + prompt = self.registryValue("prompt", msg.channel).replace("$botnick", irc.nick) + ai_model = self.registryValue("model", msg.channel) + prefix = self.registryValue("nick_prefix", msg.channel) + include = self.registryValue("nick_include", msg.channel) + model = genai.GenerativeModel( + ai_model, + generation_config=generation_config, + system_instruction=prompt, + ) + self.history.setdefault(channel, None) + if not self.history[channel]: + self.history[channel] = [] + if len(self.history[channel]) > max_history: + self.history[channel] = [] + chat = model.start_chat(history=self.history[channel][-max_history:]) + if include: + response = chat.send_message("%s: %s" % (msg.nick, text)) + else: + response = chat.send_message(text) + for line in response.text.splitlines(): + if line: + irc.reply(line, prefixNick=prefix) + self.history[channel] = chat.history + + chat = wrap(chat, ["text"]) + + +Class = Gemini + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/Gemini/requirements.txt b/Gemini/requirements.txt new file mode 100644 index 0000000..27f8b2f --- /dev/null +++ b/Gemini/requirements.txt @@ -0,0 +1 @@ +google-generativeai diff --git a/Gemini/setup.py b/Gemini/setup.py new file mode 100644 index 0000000..e193da1 --- /dev/null +++ b/Gemini/setup.py @@ -0,0 +1,35 @@ +### +# Copyright (c) 2024, oddluck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +from supybot.setup import plugin_setup + +plugin_setup( + "Gemini", +) diff --git a/Gemini/test.py b/Gemini/test.py new file mode 100644 index 0000000..dabeaed --- /dev/null +++ b/Gemini/test.py @@ -0,0 +1,38 @@ +### +# Copyright (c) 2024, oddluck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +from supybot.test import * + + +class GeminiTestCase(PluginTestCase): + plugins = ("Gemini",) + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: