diff --git a/.github/workflows/license_tests.yml b/.github/workflows/license_tests.yml index 1404a33..f7a4159 100644 --- a/.github/workflows/license_tests.yml +++ b/.github/workflows/license_tests.yml @@ -9,4 +9,4 @@ jobs: license_tests: uses: neongeckocom/.github/.github/workflows/license_tests.yml@master with: - packages-exclude: '^(neon-llm-chatgpt|tqdm).*' \ No newline at end of file + packages-exclude: '^(neon-llm|tqdm|klat-connector|neon-chatbot|dnspython|attrs|RapidFuzz).*' diff --git a/.github/workflows/update_docker_images.yml b/.github/workflows/update_docker_images.yml new file mode 100644 index 0000000..c777ec0 --- /dev/null +++ b/.github/workflows/update_docker_images.yml @@ -0,0 +1,11 @@ +name: Publish Updated Docker Image +on: + workflow_dispatch: + +jobs: + + build_and_publish_docker: + uses: neongeckocom/.github/.github/workflows/publish_docker.yml@master + secrets: inherit + with: + include_semver: False \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e8477..eb5492b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,32 +1,80 @@ # Changelog -## [0.0.1a4](https://github.com/NeonGeckoCom/neon-llm-chatgpt/tree/0.0.1a4) (2023-06-30) +## [0.0.2a10](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a10) (2025-03-03) -[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-chatgpt/compare/0.0.1a3...0.0.1a4) +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a9...0.0.2a10) **Merged pull requests:** -- Update config handling to use envvars instead of FS overlay [\#6](https://github.com/NeonGeckoCom/neon-llm-chatgpt/pull/6) ([NeonDaniel](https://github.com/NeonDaniel)) +- Update to stable dependencies [\#7](https://github.com/NeonGeckoCom/neon-llm-gemini/pull/7) ([NeonDaniel](https://github.com/NeonDaniel)) -## [0.0.1a3](https://github.com/NeonGeckoCom/neon-llm-chatgpt/tree/0.0.1a3) (2023-06-06) +## [0.0.2a9](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a9) (2025-01-17) -[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-chatgpt/compare/0.0.1a2...0.0.1a3) +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a8...0.0.2a9) **Merged pull requests:** -- Add log for handled requests [\#4](https://github.com/NeonGeckoCom/neon-llm-chatgpt/pull/4) ([NeonDaniel](https://github.com/NeonDaniel)) +- Update Docker configuration and async consumer init [\#6](https://github.com/NeonGeckoCom/neon-llm-gemini/pull/6) ([NeonDaniel](https://github.com/NeonDaniel)) -## [0.0.1a2](https://github.com/NeonGeckoCom/neon-llm-chatgpt/tree/0.0.1a2) (2023-05-16) +## [0.0.2a8](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a8) (2025-01-17) -[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-chatgpt/compare/babd77e0f173fbe3681927677602c72c58774ff0...0.0.1a2) +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a7...0.0.2a8) -**Implemented enhancements:** +**Merged pull requests:** + +- Add Docker container update automation [\#5](https://github.com/NeonGeckoCom/neon-llm-gemini/pull/5) ([NeonDaniel](https://github.com/NeonDaniel)) + +## [0.0.2a7](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a7) (2024-04-04) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a6...0.0.2a7) + +**Merged pull requests:** + +- neon-llm-core version bump [\#4](https://github.com/NeonGeckoCom/neon-llm-gemini/pull/4) ([NeonBohdan](https://github.com/NeonBohdan)) -- \[FEAT\] Python Packaging [\#1](https://github.com/NeonGeckoCom/neon-llm-chatgpt/issues/1) +## [0.0.2a6](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a6) (2024-03-25) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a5...0.0.2a6) **Merged pull requests:** -- Update for packaging and deployment [\#3](https://github.com/NeonGeckoCom/neon-llm-chatgpt/pull/3) ([NeonDaniel](https://github.com/NeonDaniel)) +- neon-llm-core version bump [\#3](https://github.com/NeonGeckoCom/neon-llm-gemini/pull/3) ([NeonBohdan](https://github.com/NeonBohdan)) + +## [0.0.2a5](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a5) (2024-01-17) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a3...0.0.2a5) + +**Merged pull requests:** + +- Use Gemini [\#1](https://github.com/NeonGeckoCom/neon-llm-gemini/pull/1) ([NeonBohdan](https://github.com/NeonBohdan)) + +## [0.0.2a3](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a3) (2023-11-16) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a2...0.0.2a3) + +## [0.0.2a2](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a2) (2023-10-23) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.2a1...0.0.2a2) + +## [0.0.2a1](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.2a1) (2023-10-19) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.1...0.0.2a1) + +## [0.0.1](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.1) (2023-06-30) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.1a4...0.0.1) + +## [0.0.1a4](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.1a4) (2023-06-30) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.1a3...0.0.1a4) + +## [0.0.1a3](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.1a3) (2023-06-06) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/0.0.1a2...0.0.1a3) + +## [0.0.1a2](https://github.com/NeonGeckoCom/neon-llm-gemini/tree/0.0.1a2) (2023-05-16) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-llm-gemini/compare/babd77e0f173fbe3681927677602c72c58774ff0...0.0.1a2) diff --git a/Dockerfile b/Dockerfile index 822d628..2a6fdb5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,18 @@ FROM python:3.9-slim LABEL vendor=neon.ai \ - ai.neon.name="neon-llm-chatgpt" + ai.neon.name="neon-llm-gemini" + +ENV OVOS_CONFIG_BASE_FOLDER=neon +ENV OVOS_CONFIG_FILENAME=diana.yaml +ENV OVOS_DEFAULT_CONFIG=/opt/neon/diana.yaml +ENV XDG_CONFIG_HOME=/config +ENV CHATBOT_VERSION=v2 -ENV OVOS_CONFIG_BASE_FOLDER neon -ENV OVOS_CONFIG_FILENAME diana.yaml -ENV XDG_CONFIG_HOME /config COPY docker_overlay/ / WORKDIR /app COPY . /app RUN pip install /app -CMD [ "neon-llm-chatgpt" ] \ No newline at end of file +CMD [ "neon-llm-gemini" ] \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 525bb37..0fdcf76 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System # All trademark and other rights reserved by their respective owners -# Copyright 2008-2021 Neongecko.com Inc. +# Copyright 2008-2025 Neongecko.com Inc. # BSD-3 License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the diff --git a/README.md b/README.md index 353a84c..de88a49 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# NeonAI LLM ChatGPT -Proxies API calls to ChatGPT. +# NeonAI LLM Gemini +Proxies API calls to Google Gemini. ## Request Format API requests should include `history`, a list of tuples of strings, and the current @@ -25,20 +25,21 @@ MQ: port: server: users: - mq-chatgpt-api: - password: - user: neon_chatgpt -ChatGPT: - key: "" - model: "gpt-3.5-turbo" + neon_llm_gemini: + password: + user: neon_gemini +LLM_GEMINI: + model: "gemini-pro" + key_path: "" role: "You are trying to give a short answer in less than 40 words." context_depth: 3 max_tokens: 100 + num_parallel_processes: 2 ``` For example, if your configuration resides in `~/.config`: ```shell export CONFIG_PATH="/home/${USER}/.config" -docker run -v ${CONFIG_PATH}:/config neon_llm_chatgpt +docker run -v ${CONFIG_PATH}:/config neon_llm_gemini ``` > Note: If connecting to a local MQ server, you may need to specify `--network host` \ No newline at end of file diff --git a/docker_overlay/etc/neon/diana.yaml b/docker_overlay/etc/neon/diana.yaml deleted file mode 100644 index 78e5933..0000000 --- a/docker_overlay/etc/neon/diana.yaml +++ /dev/null @@ -1,21 +0,0 @@ -log_level: INFO -logs: - level_overrides: - error: - - pika - warning: - - filelock - info: [] - debug: [] -MQ: - server: api.neon.ai - port: 5672 - users: - mq_handler: - user: neon_api_utils - password: Klatchat2021 -ChatGPT: - model: "gpt-3.5-turbo" - role: "You are trying to give a short answer in less than 40 words." - context_depth: 3 - max_tokens: 100 \ No newline at end of file diff --git a/docker_overlay/opt/neon/diana.yaml b/docker_overlay/opt/neon/diana.yaml new file mode 100644 index 0000000..6abb49b --- /dev/null +++ b/docker_overlay/opt/neon/diana.yaml @@ -0,0 +1,31 @@ +log_level: INFO +logs: + level_overrides: + error: + - pika + warning: + - filelock + - watchdog + - httpcore + info: + - openai + - asyncio + - matplotlib + debug: [] +MQ: + server: neon-rabbitmq + port: 5672 + users: + mq_handler: + user: neon_api_utils + password: Klatchat2021 +LLM_GEMINI: + model: "gemini-pro" + role: "You are trying to give a short answer in less than 40 words." + context_depth: 3 + max_tokens: 100 + num_parallel_processes: 2 +llm_bots: + gemini: + - description: You are trying to give a short answer in less than 40 words. + name: assistant \ No newline at end of file diff --git a/neon_llm_chatgpt/chatgpt.py b/neon_llm_chatgpt/chatgpt.py deleted file mode 100644 index 5751cfa..0000000 --- a/neon_llm_chatgpt/chatgpt.py +++ /dev/null @@ -1,63 +0,0 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2021 Neongecko.com Inc. -# BSD-3 -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. 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. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# 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 HOLDER 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. - -import openai - - -class ChatGPT: - def __init__(self, config): - self.model = config["model"] - self.role = config["role"] - self.context_depth = config["context_depth"] - self.max_tokens = config["max_tokens"] - openai.api_key = config["key"] - - @staticmethod - def convert_role(role): - if role == "user": - role_chatgpt = "user" - elif role == "llm": - role_chatgpt = "assistant" - return role_chatgpt - - def ask(self, message, chat_history): - messages = [ - {"role": "system", "content": self.role}, - ] - # Context N messages - for role, content in chat_history[-self.context_depth:]: - role_chatgpt = self.convert_role(role) - messages.append({"role": role_chatgpt, "content": content}) - messages.append({"role": "user", "content": message}) - - response = openai.ChatCompletion.create( - model=self.model, - messages=messages, - temperature=0, - max_tokens=self.max_tokens, - ) - bot_message = response.choices[0].message['content'] - return bot_message diff --git a/neon_llm_chatgpt/default_config.json b/neon_llm_chatgpt/default_config.json deleted file mode 100644 index f8887c7..0000000 --- a/neon_llm_chatgpt/default_config.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "MQ": { - "server": "localhost", - "port": "5672", - "users": { - "mq-chatgpt-api": { - "user": "", - "password": "" - } - } - }, - "ChatGPT": { - "key": "", - "model": "gpt-3.5-turbo", - "role": "You are trying to give a short answer in less than 40 words.", - "context_depth": 3, - "max_tokens": 100 - } -} \ No newline at end of file diff --git a/neon_llm_chatgpt/rmq.py b/neon_llm_chatgpt/rmq.py deleted file mode 100644 index 52fcceb..0000000 --- a/neon_llm_chatgpt/rmq.py +++ /dev/null @@ -1,93 +0,0 @@ -# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System -# All trademark and other rights reserved by their respective owners -# Copyright 2008-2021 Neongecko.com Inc. -# BSD-3 -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. 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. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# 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 HOLDER 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. - -import pika - -from neon_mq_connector.connector import MQConnector -from neon_mq_connector.utils.network_utils import dict_to_b64 -from neon_mq_connector.utils.rabbit_utils import create_mq_callback -from ovos_utils.log import LOG - -from neon_llm_chatgpt.chatgpt import ChatGPT -from neon_llm_chatgpt.config import load_config - - -class ChatgptMQ(MQConnector): - """ - Module for processing MQ requests from PyKlatchat to LibreTranslate""" - - def __init__(self): - config = load_config() - chatgpt_config = config.get("ChatGPT", None) - self.chatGPT = ChatGPT(chatgpt_config) - - self.service_name = 'neon_llm_chatgpt' - - mq_config = config.get("MQ", None) - super().__init__(config=mq_config, service_name=self.service_name) - - self.vhost = "/llm" - self.queue = "chat_gpt_input" - self.register_consumer(name=self.service_name, - vhost=self.vhost, - queue=self.queue, - callback=self.handle_request, - on_error=self.default_error_handler, - auto_ack=False) - - @create_mq_callback(include_callback_props=('channel', 'method', 'body')) - def handle_request(self, - channel: pika.channel.Channel, - method: pika.spec.Basic.Return, - body: dict): - """ - Handles requests from MQ to ChatGPT received on queue - "request_chatgpt" - - :param channel: MQ channel object (pika.channel.Channel) - :param method: MQ return method (pika.spec.Basic.Return) - :param body: request body (dict) - """ - message_id = body["message_id"] - routing_key = body["routing_key"] - - query = body["query"] - history = body["history"] - - response = self.chatGPT.ask(message=query, chat_history=history) - - api_response = { - "message_id": message_id, - "response": response - } - - channel.basic_publish(exchange='', - routing_key=routing_key, - body=dict_to_b64(api_response), - properties=pika.BasicProperties( - expiration=str(1000))) - channel.basic_ack(method.delivery_tag) - LOG.info(f"Handled request: {message_id}") diff --git a/neon_llm_chatgpt/__init__.py b/neon_llm_gemini/__init__.py similarity index 97% rename from neon_llm_chatgpt/__init__.py rename to neon_llm_gemini/__init__.py index d782cbb..f6f252a 100644 --- a/neon_llm_chatgpt/__init__.py +++ b/neon_llm_gemini/__init__.py @@ -1,6 +1,6 @@ # NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System # All trademark and other rights reserved by their respective owners -# Copyright 2008-2021 Neongecko.com Inc. +# Copyright 2008-2025 Neongecko.com Inc. # BSD-3 # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: diff --git a/neon_llm_chatgpt/__main__.py b/neon_llm_gemini/__main__.py similarity index 84% rename from neon_llm_chatgpt/__main__.py rename to neon_llm_gemini/__main__.py index e98ff45..8388800 100644 --- a/neon_llm_chatgpt/__main__.py +++ b/neon_llm_gemini/__main__.py @@ -1,6 +1,6 @@ # NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System # All trademark and other rights reserved by their respective owners -# Copyright 2008-2021 Neongecko.com Inc. +# Copyright 2008-2025 Neongecko.com Inc. # BSD-3 # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -24,15 +24,17 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from neon_llm_chatgpt.rmq import ChatgptMQ +from neon_llm_gemini.rmq import GeminiMQ +from neon_utils.log_utils import init_log def main(): + init_log(log_name="gemini") + # Run RabbitMQ - chatgptMQ = ChatgptMQ() - chatgptMQ.run(run_sync=False, run_consumers=True, - daemonize_consumers=True) - chatgptMQ.observer_thread.join() + gemini_mq_service = GeminiMQ() + gemini_mq_service.run(run_sync=False, run_consumers=True, + daemonize_consumers=True) if __name__ == "__main__": diff --git a/neon_llm_gemini/gemini.py b/neon_llm_gemini/gemini.py new file mode 100644 index 0000000..49cb978 --- /dev/null +++ b/neon_llm_gemini/gemini.py @@ -0,0 +1,191 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2025 Neongecko.com Inc. +# BSD-3 +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. 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. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# 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 HOLDER 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. + +import os +from vertexai.preview.generative_models import GenerativeModel, Content, Part +from vertexai.language_models import TextEmbeddingModel +from openai.embeddings_utils import distances_from_embeddings + +from typing import List, Dict +from neon_llm_core.llm import NeonLLM + + +class Gemini(NeonLLM): + + mq_to_llm_role = { + "user": "user", + "llm": "model" + } + + def __init__(self, config): + super().__init__(config) + self._embedding = None + self._context_depth = 0 + + self.model_name = config["model"] + self.role = config["role"] + self.context_depth = config["context_depth"] + self.max_tokens = config["max_tokens"] + self.api_key_path = config["key_path"] + self.warmup() + + @property + def context_depth(self): + return self._context_depth + + @context_depth.setter + def context_depth(self, value): + self._context_depth = value + value % 2 + + @property + def tokenizer(self) -> None: + return self._tokenizer + + @property + def tokenizer_model_name(self) -> str: + return "" + + @property + def model(self) -> GenerativeModel: + if self._model is None: + self._model = GenerativeModel(self.model_name) + return self._model + + @property + def embedding(self) -> TextEmbeddingModel: + if self._embedding is None: + self._embedding = TextEmbeddingModel.from_pretrained("textembedding-gecko@latest") + return self._embedding + + @property + def llm_model_name(self) -> str: + return self.model_name + + @property + def _system_prompt(self) -> str: + return self.role + + def warmup(self): + os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = self.api_key_path + self.model + self.embedding + + def get_sorted_answer_indexes(self, question: str, answers: List[str], persona: dict) -> List[int]: + """ + Creates sorted list of answer indexes with respect to order provided in :param answers based on PPL score + Answers are sorted from best to worst + :param question: incoming question + :param answers: list of answers to rank + :returns list of indexes + """ + if not answers: + return [] + scores = self._score(prompt=question, targets=answers, persona=persona) + sorted_items = sorted(zip(range(len(answers)), scores), key=lambda x: x[1]) + sorted_items_indexes = [x[0] for x in sorted_items] + return sorted_items_indexes + + def _call_model(self, prompt: Dict) -> str: + """ + Wrapper for Gemini Model generation logic + :param prompt: Input messages sequence + :returns: Output text sequence generated by model + """ + + chat = self._model.start_chat( + history=prompt["chat_history"], + ) + response = chat.send_message( + prompt["message"], + generation_config = { + "temperature": 0, + "max_output_tokens": self.max_tokens, + } + ) + text = response.text + + return text + + def _assemble_prompt(self, message: str, chat_history: List[List[str]], persona: dict) -> List[Dict[str, str]]: + """ + Assembles prompt engineering logic + Setup Guidance: + https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/overview + + :param message: Incoming prompt + :param chat_history: History of preceding conversation + :returns: assembled prompt + """ + system_prompt = persona.get("description", self._system_prompt) + # Context N messages + messages = [] + for role, content in chat_history[-self.context_depth:]: + if ((len(messages) == 0) and (role == "user")): + content = self._convert2instruction(content, system_prompt) + role_gemini = self.convert_role(role) + messages.append(Content(parts=[Part.from_text(content)], role = role_gemini)) + if (len(messages) == 0): + message = self._convert2instruction(message, system_prompt) + prompt = { + "chat_history": messages, + "message": message + } + + return prompt + + def _convert2instruction(self, content: str, system_prompt: str): + instruction = f"{system_prompt.strip()}\n\n{content.strip()}" + return instruction + + def _score(self, prompt: str, targets: List[str], persona: dict) -> List[float]: + """ + Calculates logarithmic probabilities for the list of provided text sequences + :param prompt: Input text sequence + :param targets: Output text sequences + :returns: List of calculated logarithmic probabilities per output text sequence + """ + + question_embeddings, answers_embeddings = self._embeddings(question=prompt, answers=targets, persona=persona) + scores_list = distances_from_embeddings(question_embeddings, answers_embeddings) + return scores_list + + def _tokenize(self, prompt: str) -> None: + pass + + def _embeddings(self, question: str, answers: List[str], persona: dict) -> (List[float], List[List[float]]): + """ + Computes embeddings for the list of provided answers + :param question: Question for LLM to response to + :param answers: List of provided answers + :returns ppl values for each answer + """ + response = self.ask(question, [], persona=persona) + texts = [response] + answers + embeddings_obj = self.embedding.get_embeddings(texts) + embeddings = [embedding.values for embedding in embeddings_obj] + question_embeddings = embeddings[0] + answers_embeddings = embeddings[1:] + return question_embeddings, answers_embeddings \ No newline at end of file diff --git a/neon_llm_chatgpt/config.py b/neon_llm_gemini/rmq.py similarity index 67% rename from neon_llm_chatgpt/config.py rename to neon_llm_gemini/rmq.py index 2787463..6ead4c2 100644 --- a/neon_llm_chatgpt/config.py +++ b/neon_llm_gemini/rmq.py @@ -1,6 +1,6 @@ # NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System # All trademark and other rights reserved by their respective owners -# Copyright 2008-2021 Neongecko.com Inc. +# Copyright 2008-2025 Neongecko.com Inc. # BSD-3 # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -23,28 +23,33 @@ # 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 neon_llm_core.rmq import NeonLLMMQConnector -import json +from neon_llm_gemini.gemini import Gemini -from os.path import join, dirname, isfile -from ovos_utils.log import LOG -from ovos_config.config import Configuration - -def load_config() -> dict: +class GeminiMQ(NeonLLMMQConnector): """ - Load and return a configuration object, + Module for processing MQ requests to Gemini """ - legacy_config_path = "/app/app/config.json" - if isfile(legacy_config_path): - LOG.warning(f"Deprecated configuration found at {legacy_config_path}") - with open(legacy_config_path) as f: - config = json.load(f) - return config - config = Configuration() - if not config: - LOG.warning(f"No configuration found! falling back to defaults") - default_config_path = join(dirname(__file__), "default_config.json") - with open(default_config_path) as f: - config = json.load(f) - return config + + def __init__(self): + super().__init__() + self.warmup() + + @property + def name(self): + return "gemini" + + @property + def model(self): + if self._model is None: + self._model = Gemini(self.model_config) + return self._model + + def warmup(self): + self.model + + @staticmethod + def compose_opinion_prompt(respondent_nick: str, question: str, answer: str) -> str: + return f'Why Answer "{answer}" to the Question "{question}" generated by Bot named "{respondent_nick}" is good?' diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a769c8b..9c26836 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,4 +1,5 @@ -openai~=0.27 -neon-mq-connector~=0.7 -ovos-utils~=0.0.32 -ovos-config~=0.0.10 \ No newline at end of file +# model +google-cloud-aiplatform>=1.38 +openai[embeddings]~=0.27 +# networking +neon-llm-core[chatbots]~=0.2 diff --git a/setup.py b/setup.py index 64747b5..5e547db 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ # NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework # All trademark and other rights reserved by their respective owners -# Copyright 2008-2022 Neongecko.com Inc. +# Copyright 2008-2025 Neongecko.com Inc. # Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, # Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo # BSD-3 License @@ -67,12 +67,12 @@ def get_requirements(requirements_filename: str): version = line.split("'")[1] setup( - name='neon-llm-chatgpt', + name='neon-llm-gemini', version=version, - description='LLM service for Chat GPT', + description='LLM service for Gemini', long_description=long_description, long_description_content_type="text/markdown", - url='https://github.com/NeonGeckoCom/neon-llm-chatgpt', + url='https://github.com/NeonGeckoCom/neon-llm-gemini', author='Neongecko', author_email='developers@neon.ai', license='BSD-3.0', @@ -85,7 +85,7 @@ def get_requirements(requirements_filename: str): ], entry_points={ 'console_scripts': [ - 'neon-llm-chatgpt=neon_llm_chatgpt.__main__:main' + 'neon-llm-gemini=neon_llm_gemini.__main__:main' ] } ) diff --git a/version.py b/version.py index ef7228e..3c7b878 100644 --- a/version.py +++ b/version.py @@ -1,6 +1,6 @@ # NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework # All trademark and other rights reserved by their respective owners -# Copyright 2008-2022 Neongecko.com Inc. +# Copyright 2008-2025 Neongecko.com Inc. # Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, # Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo # BSD-3 License @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.0.1" +__version__ = "0.1.0"