From 3a3d46e4789a6521b904282e6c9434754af6a0d5 Mon Sep 17 00:00:00 2001 From: dougollerenshaw Date: Wed, 9 Oct 2024 10:42:16 -0700 Subject: [PATCH 1/5] working on adding gemini --- .github/workflows/python-tests.yml | 4 +- README.md | 2 +- requirements.txt | 2 + sandbox/gemini_sandbox.ipynb | 144 +++++++++++++++++++++++++++++ sandbox/prototype_gemini.py | 39 ++++++++ 5 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 sandbox/gemini_sandbox.ipynb create mode 100644 sandbox/prototype_gemini.py diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index c06786d..eeb4fcc 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, '3.10'] + python-version: [3.9, '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 @@ -32,4 +32,4 @@ jobs: run: | sudo apt-get update sudo apt-get install -y xvfb - xvfb-run -a pytest -v tests/ui/test_chat_window.py || echo "UI tests failed but continuing" \ No newline at end of file + xvfb-run -a pytest -v tests/ui/test_chat_window.py || echo "UI tests failed but continuing" diff --git a/README.md b/README.md index c35cf58..55ae1f4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ https://github.com/user-attachments/assets/8aa729ff-c431-4a61-a9ef-d17050a27d02 ### Prerequisites -- Python 3.8 or higher +- Python 3.9 or higher ### Setup diff --git a/requirements.txt b/requirements.txt index 883cb12..a828fdf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ anthropic==0.34.2 +google-generativeai==0.8.3 python-decouple==3.8 virtualenv==20.16.2 +numpy==1.26.4 openai hjson pyyaml diff --git a/sandbox/gemini_sandbox.ipynb b/sandbox/gemini_sandbox.ipynb new file mode 100644 index 0000000..4dff9b5 --- /dev/null +++ b/sandbox/gemini_sandbox.ipynb @@ -0,0 +1,144 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "genai.configure(api_key=config('GEMINI_API_KEY'))\n", + "\n", + "model = genai.GenerativeModel(\"gemini-1.5-pro\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mSignature:\u001b[0m\n", + "\u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgenerate_content\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcontents\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'content_types.ContentsType'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mgeneration_config\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'generation_types.GenerationConfigType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msafety_settings\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'safety_types.SafetySettingOptions | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstream\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtools\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'content_types.FunctionLibraryType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtool_config\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'content_types.ToolConfigType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mrequest_options\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'helper_types.RequestOptionsType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'generation_types.GenerateContentResponse'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDocstring:\u001b[0m\n", + "A multipurpose function to generate responses from the model.\n", + "\n", + "This `GenerativeModel.generate_content` method can handle multimodal input, and multi-turn\n", + "conversations.\n", + "\n", + ">>> model = genai.GenerativeModel('models/gemini-pro')\n", + ">>> response = model.generate_content('Tell me a story about a magic backpack')\n", + ">>> response.text\n", + "\n", + "### Streaming\n", + "\n", + "This method supports streaming with the `stream=True`. The result has the same type as the non streaming case,\n", + "but you can iterate over the response chunks as they become available:\n", + "\n", + ">>> response = model.generate_content('Tell me a story about a magic backpack', stream=True)\n", + ">>> for chunk in response:\n", + "... print(chunk.text)\n", + "\n", + "### Multi-turn\n", + "\n", + "This method supports multi-turn chats but is **stateless**: the entire conversation history needs to be sent with each\n", + "request. This takes some manual management but gives you complete control:\n", + "\n", + ">>> messages = [{'role':'user', 'parts': ['hello']}]\n", + ">>> response = model.generate_content(messages) # \"Hello, how can I help\"\n", + ">>> messages.append(response.candidates[0].content)\n", + ">>> messages.append({'role':'user', 'parts': ['How does quantum physics work?']})\n", + ">>> response = model.generate_content(messages)\n", + "\n", + "For a simpler multi-turn interface see `GenerativeModel.start_chat`.\n", + "\n", + "### Input type flexibility\n", + "\n", + "While the underlying API strictly expects a `list[protos.Content]` objects, this method\n", + "will convert the user input into the correct type. The hierarchy of types that can be\n", + "converted is below. Any of these objects can be passed as an equivalent `dict`.\n", + "\n", + "* `Iterable[protos.Content]`\n", + "* `protos.Content`\n", + "* `Iterable[protos.Part]`\n", + "* `protos.Part`\n", + "* `str`, `Image`, or `protos.Blob`\n", + "\n", + "In an `Iterable[protos.Content]` each `content` is a separate message.\n", + "But note that an `Iterable[protos.Part]` is taken as the parts of a single message.\n", + "\n", + "Arguments:\n", + " contents: The contents serving as the model's prompt.\n", + " generation_config: Overrides for the model's generation config.\n", + " safety_settings: Overrides for the model's safety settings.\n", + " stream: If True, yield response chunks as they are generated.\n", + " tools: `protos.Tools` more info coming soon.\n", + " request_options: Options for the request.\n", + "\u001b[0;31mFile:\u001b[0m ~/opt/anaconda3/envs/codeaide/lib/python3.11/site-packages/google/generativeai/generative_models.py\n", + "\u001b[0;31mType:\u001b[0m method" + ] + } + ], + "source": [ + "# Define the system prompt (context)\n", + "system_prompt = \"\"\"You are an AI assistant specialized in explaining complex topics in simple terms.\n", + "Your responses should be clear, concise, and easy to understand for a general audience.\"\"\"\n", + "\n", + "# Define the user's question\n", + "user_question = \"Can you explain quantum computing in simple terms?\"\n", + "\n", + "# Set up the chat\n", + "chat = model.start_chat(context=system_prompt)\n", + "\n", + "# Generate a response\n", + "response = chat.send_message(user_question)\n", + "\n", + "# Print the response\n", + "print(response.text)\n", + "\n", + "# You can continue the conversation\n", + "follow_up = chat.send_message(\"How does that compare to classical computing?\")\n", + "print(follow_up.text)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "codeaide", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/sandbox/prototype_gemini.py b/sandbox/prototype_gemini.py new file mode 100644 index 0000000..bd4fe62 --- /dev/null +++ b/sandbox/prototype_gemini.py @@ -0,0 +1,39 @@ +""" +This is a little sandbox script to test out the Gemini API. +""" + +import argparse +from decouple import config +import google.generativeai as genai + +genai.configure(api_key=config("GEMINI_API_KEY")) + +model = genai.GenerativeModel("gemini-1.5-flash") + + +def generate_a_story(): + response = model.generate_content("Write a story about a magic backpack.") + print(response.text) + + +def request_code(): + response = model.generate_content( + "Write a Python function to calculate the Fibonacci sequence." + ) + print(response.text) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Gemini API prototype script") + parser.add_argument( + "action", + choices=["story", "code"], + help="Action to perform: generate a story or request code", + ) + + args = parser.parse_args() + + if args.action == "story": + generate_a_story() + elif args.action == "code": + request_code() From a10b43866e3f536f9667cd9cf82d9983d13c5a19 Mon Sep 17 00:00:00 2001 From: dougollerenshaw Date: Thu, 10 Oct 2024 11:14:04 -0700 Subject: [PATCH 2/5] Added egg info to gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6fc6be0..0e92774 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,7 @@ codeaide.spec dist/ # Ignore .dmg files -*.dmg \ No newline at end of file +*.dmg + +# Ignore .egg-info directories +*.egg-info/ From 0b5a9a2afd00ceb2f807b8f9c52436daa0748623 Mon Sep 17 00:00:00 2001 From: dougollerenshaw Date: Thu, 10 Oct 2024 11:28:41 -0700 Subject: [PATCH 3/5] All changes to get gemini working --- codeaide/logic/chat_handler.py | 16 +++- codeaide/ui/chat_window.py | 14 +++- codeaide/utils/api_utils.py | 38 +++++++-- codeaide/utils/constants.py | 18 ++++- sandbox/gemini_sandbox.ipynb | 144 --------------------------------- sandbox/prototype_gemini.py | 3 +- 6 files changed, 72 insertions(+), 161 deletions(-) delete mode 100644 sandbox/gemini_sandbox.ipynb diff --git a/codeaide/logic/chat_handler.py b/codeaide/logic/chat_handler.py index 474e8b0..ea76ff6 100644 --- a/codeaide/logic/chat_handler.py +++ b/codeaide/logic/chat_handler.py @@ -11,7 +11,6 @@ from codeaide.utils.constants import ( MAX_RETRIES, AI_PROVIDERS, - DEFAULT_MODEL, DEFAULT_PROVIDER, INITIAL_MESSAGE, ) @@ -57,7 +56,9 @@ def __init__(self): self.api_client = None self.api_key_set = False self.current_provider = DEFAULT_PROVIDER - self.current_model = DEFAULT_MODEL + self.current_model = list(AI_PROVIDERS[self.current_provider]["models"].keys())[ + 0 + ] self.max_tokens = AI_PROVIDERS[self.current_provider]["models"][ self.current_model ]["max_tokens"] @@ -346,8 +347,17 @@ def update_conversation_history(self, response): self.conversation_history.append( {"role": "assistant", "content": response.choices[0].message.content} ) + elif self.current_provider.lower() == "google": + self.conversation_history.append( + { + "role": "assistant", + "content": response.candidates[0].content.parts[0].text, + } + ) else: - raise ValueError(f"Unsupported provider: {self.current_provider}") + raise ValueError( + f"In update_conversation_history, unsupported provider: {self.current_provider}" + ) self.file_handler.save_chat_history(self.conversation_history) def create_questions_response(self, text, questions): diff --git a/codeaide/ui/chat_window.py b/codeaide/ui/chat_window.py index 6a10798..f97614e 100644 --- a/codeaide/ui/chat_window.py +++ b/codeaide/ui/chat_window.py @@ -194,7 +194,7 @@ def setup_ui(self): # Model dropdown self.model_dropdown = QComboBox() - self.update_model_dropdown(DEFAULT_PROVIDER) + self.update_model_dropdown(DEFAULT_PROVIDER, add_message_to_chat=False) self.model_dropdown.currentTextChanged.connect(self.update_chat_handler) dropdown_layout.addWidget(QLabel("Model:")) dropdown_layout.addWidget(self.model_dropdown) @@ -474,17 +474,23 @@ def force_close(self): def sigint_handler(self, *args): QApplication.quit() - def update_model_dropdown(self, provider): + def update_model_dropdown(self, provider, add_message_to_chat=False): self.model_dropdown.clear() models = AI_PROVIDERS[provider]["models"].keys() self.model_dropdown.addItems(models) - # Set the current item to the first model in the list + # Set the current item to the first model in the list (default) if models: - self.model_dropdown.setCurrentText(list(models)[0]) + default_model = list(models)[0] + self.model_dropdown.setCurrentText(default_model) + self.logger.info(f"Set default model for {provider} to {default_model}") else: self.logger.info(f"No models available for provider {provider}") + # Update the chat handler with the selected model if add_message_to_chat is True + if add_message_to_chat: + self.update_chat_handler() + def update_chat_handler(self): provider = self.provider_dropdown.currentText() model = self.model_dropdown.currentText() diff --git a/codeaide/utils/api_utils.py b/codeaide/utils/api_utils.py index 35621f4..3e32046 100644 --- a/codeaide/utils/api_utils.py +++ b/codeaide/utils/api_utils.py @@ -1,12 +1,13 @@ import os import anthropic import openai +import google.generativeai as genai from decouple import AutoConfig import hjson +from google.generativeai.types import GenerationConfig from codeaide.utils.constants import ( AI_PROVIDERS, - DEFAULT_MODEL, DEFAULT_PROVIDER, SYSTEM_PROMPT, ) @@ -23,7 +24,7 @@ def __init__(self, service): ) -def get_api_client(provider=DEFAULT_PROVIDER, model=DEFAULT_MODEL): +def get_api_client(provider=DEFAULT_PROVIDER, model=None): try: root_dir = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -47,8 +48,12 @@ def get_api_client(provider=DEFAULT_PROVIDER, model=DEFAULT_MODEL): return anthropic.Anthropic(api_key=api_key) elif provider.lower() == "openai": return openai.OpenAI(api_key=api_key) + elif provider.lower() == "google": + genai.configure(api_key=api_key) + client = genai.GenerativeModel(model, system_instruction=SYSTEM_PROMPT) + return client else: - raise ValueError(f"Unsupported provider: {provider}") + raise ValueError(f"In get_api_client, unsupported provider: {provider}") except Exception as e: logger.error(f"Error initializing {provider.capitalize()} API client: {str(e)}") return None @@ -112,6 +117,25 @@ def send_api_request(api_client, conversation_history, max_tokens, model, provid ) if not response.choices: return None + elif provider.lower() == "google": + # Convert conversation history to the format expected by Google Gemini + prompt = "" + for message in conversation_history: + role = message["role"] + content = message["content"] + prompt += f"{role.capitalize()}: {content}\n\n" + + # Create a GenerationConfig object + generation_config = GenerationConfig( + max_output_tokens=max_tokens, + temperature=0.7, # You can adjust this as needed + top_p=0.95, # You can adjust this as needed + top_k=40, # You can adjust this as needed + ) + + response = api_client.generate_content( + contents=prompt, generation_config=generation_config + ) else: raise NotImplementedError(f"API request for {provider} not implemented") @@ -137,8 +161,10 @@ def parse_response(response, provider): if not response.choices: raise ValueError("Empty or invalid response received") json_str = response.choices[0].message.content + elif provider.lower() == "google": + json_str = response.candidates[0].content.parts[0].text else: - raise ValueError(f"Unsupported provider: {provider}") + raise ValueError(f"In parse_response, unsupported provider: {provider}") # Remove the triple backticks and language identifier if present if json_str.startswith("```json"): @@ -172,8 +198,10 @@ def check_api_connection(): if client is None: return False, "API key is missing or invalid" try: + provider = DEFAULT_PROVIDER + model = list(AI_PROVIDERS[provider]["models"].keys())[0] response = client.messages.create( - model=DEFAULT_MODEL, + model=model, max_tokens=100, messages=[{"role": "user", "content": "Are we communicating?"}], ) diff --git a/codeaide/utils/constants.py b/codeaide/utils/constants.py index a4816db..b19b133 100644 --- a/codeaide/utils/constants.py +++ b/codeaide/utils/constants.py @@ -1,5 +1,15 @@ # API Configuration +# This dictionary defines the supported API providers and the supported models for each. +# The max_tokens argument is the max output tokens, which is generally specified in the API documentation +# The default model for each provider will be the first model in the list AI_PROVIDERS = { + "google": { + "api_key_name": "GEMINI_API_KEY", + "models": { + "gemini-1.5-pro": {"max_tokens": 8192}, + "gemini-1.5-flash": {"max_tokens": 8192}, + }, + }, "anthropic": { "api_key_name": "ANTHROPIC_API_KEY", "models": { @@ -19,9 +29,8 @@ }, } -# Default model -DEFAULT_MODEL = "claude-3-5-sonnet-20240620" -DEFAULT_PROVIDER = "anthropic" +# This sets the default provider when the application launches +DEFAULT_PROVIDER = "google" # Other existing constants remain unchanged MAX_RETRIES = 3 @@ -104,10 +113,11 @@ * Ensure that newlines in string literals are properly contained within the string delimiters and do not break the code structure. * Add inline comments to explain complex parts of the code or to provide additional context where necessary. However, avoid excessive commenting that may clutter the code. * All code must be contained within a single file. If the code requires multiple classes or functions, include them all in the same code block. +* Do not include triple backticks ("```") or language identifiers in the code block. Remember, the goal is to provide valuable, working code solutions while maintaining a balance between making reasonable assumptions and seeking clarification when truly necessary. Format your responses as a JSON object with six keys: -* 'text': a string that contains any natural language explanations or comments that you think are helpful for the user. This should never be null or incomplete. If you mention providing a list or explanation, ensure it is fully included here. If you have no text response, provide a brief explanation of the code or the assumptions made. +* 'text': a string that contains any natural language explanations or comments that you think are helpful for the user. This should never be null or incomplete. If you mention providing a list or explanation, ensure it is fully included here. If you have no text response, provide a brief explanation of the code or the assumptions made. Use plain text, not markdown. * 'questions': an array of strings that pose necessary follow-up questions to the user * 'code': a string with the properly formatted, complete code block. This must include all necessary components for the code to run, including any previously implemented methods or classes. This should be null only if you have questions or text responses but no code to provide. * 'code_version': a string that represents the version of the code. Start at 1.0 and increment for each new version of the code you provide. Use your judgement on whether to increment the minor or major component of the version. It is critical that version numbers never be reused during a chat and that the numbers always increment upward. This field should be null if you have no code to provide. diff --git a/sandbox/gemini_sandbox.ipynb b/sandbox/gemini_sandbox.ipynb deleted file mode 100644 index 4dff9b5..0000000 --- a/sandbox/gemini_sandbox.ipynb +++ /dev/null @@ -1,144 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "genai.configure(api_key=config('GEMINI_API_KEY'))\n", - "\n", - "model = genai.GenerativeModel(\"gemini-1.5-pro\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[0;31mSignature:\u001b[0m\n", - "\u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgenerate_content\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcontents\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'content_types.ContentsType'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mgeneration_config\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'generation_types.GenerationConfigType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0msafety_settings\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'safety_types.SafetySettingOptions | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstream\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtools\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'content_types.FunctionLibraryType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtool_config\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'content_types.ToolConfigType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mrequest_options\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'helper_types.RequestOptionsType | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'generation_types.GenerateContentResponse'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m\n", - "A multipurpose function to generate responses from the model.\n", - "\n", - "This `GenerativeModel.generate_content` method can handle multimodal input, and multi-turn\n", - "conversations.\n", - "\n", - ">>> model = genai.GenerativeModel('models/gemini-pro')\n", - ">>> response = model.generate_content('Tell me a story about a magic backpack')\n", - ">>> response.text\n", - "\n", - "### Streaming\n", - "\n", - "This method supports streaming with the `stream=True`. The result has the same type as the non streaming case,\n", - "but you can iterate over the response chunks as they become available:\n", - "\n", - ">>> response = model.generate_content('Tell me a story about a magic backpack', stream=True)\n", - ">>> for chunk in response:\n", - "... print(chunk.text)\n", - "\n", - "### Multi-turn\n", - "\n", - "This method supports multi-turn chats but is **stateless**: the entire conversation history needs to be sent with each\n", - "request. This takes some manual management but gives you complete control:\n", - "\n", - ">>> messages = [{'role':'user', 'parts': ['hello']}]\n", - ">>> response = model.generate_content(messages) # \"Hello, how can I help\"\n", - ">>> messages.append(response.candidates[0].content)\n", - ">>> messages.append({'role':'user', 'parts': ['How does quantum physics work?']})\n", - ">>> response = model.generate_content(messages)\n", - "\n", - "For a simpler multi-turn interface see `GenerativeModel.start_chat`.\n", - "\n", - "### Input type flexibility\n", - "\n", - "While the underlying API strictly expects a `list[protos.Content]` objects, this method\n", - "will convert the user input into the correct type. The hierarchy of types that can be\n", - "converted is below. Any of these objects can be passed as an equivalent `dict`.\n", - "\n", - "* `Iterable[protos.Content]`\n", - "* `protos.Content`\n", - "* `Iterable[protos.Part]`\n", - "* `protos.Part`\n", - "* `str`, `Image`, or `protos.Blob`\n", - "\n", - "In an `Iterable[protos.Content]` each `content` is a separate message.\n", - "But note that an `Iterable[protos.Part]` is taken as the parts of a single message.\n", - "\n", - "Arguments:\n", - " contents: The contents serving as the model's prompt.\n", - " generation_config: Overrides for the model's generation config.\n", - " safety_settings: Overrides for the model's safety settings.\n", - " stream: If True, yield response chunks as they are generated.\n", - " tools: `protos.Tools` more info coming soon.\n", - " request_options: Options for the request.\n", - "\u001b[0;31mFile:\u001b[0m ~/opt/anaconda3/envs/codeaide/lib/python3.11/site-packages/google/generativeai/generative_models.py\n", - "\u001b[0;31mType:\u001b[0m method" - ] - } - ], - "source": [ - "# Define the system prompt (context)\n", - "system_prompt = \"\"\"You are an AI assistant specialized in explaining complex topics in simple terms.\n", - "Your responses should be clear, concise, and easy to understand for a general audience.\"\"\"\n", - "\n", - "# Define the user's question\n", - "user_question = \"Can you explain quantum computing in simple terms?\"\n", - "\n", - "# Set up the chat\n", - "chat = model.start_chat(context=system_prompt)\n", - "\n", - "# Generate a response\n", - "response = chat.send_message(user_question)\n", - "\n", - "# Print the response\n", - "print(response.text)\n", - "\n", - "# You can continue the conversation\n", - "follow_up = chat.send_message(\"How does that compare to classical computing?\")\n", - "print(follow_up.text)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "codeaide", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.10" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/sandbox/prototype_gemini.py b/sandbox/prototype_gemini.py index bd4fe62..77cf358 100644 --- a/sandbox/prototype_gemini.py +++ b/sandbox/prototype_gemini.py @@ -5,10 +5,11 @@ import argparse from decouple import config import google.generativeai as genai +from codeaide.utils.constants import SYSTEM_PROMPT genai.configure(api_key=config("GEMINI_API_KEY")) -model = genai.GenerativeModel("gemini-1.5-flash") +model = genai.GenerativeModel("gemini-1.5-pro", system_instruction=SYSTEM_PROMPT) def generate_a_story(): From 8c67bbfa01ee479c91127cc7fe868fe27e46cf89 Mon Sep 17 00:00:00 2001 From: dougollerenshaw Date: Thu, 10 Oct 2024 11:31:08 -0700 Subject: [PATCH 4/5] Updated tests --- tests/ui/test_chat_window.py | 7 +------ tests/utils/test_api_utils.py | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/tests/ui/test_chat_window.py b/tests/ui/test_chat_window.py index 2f3fcca..7a226b1 100644 --- a/tests/ui/test_chat_window.py +++ b/tests/ui/test_chat_window.py @@ -12,7 +12,6 @@ from codeaide.utils.constants import ( AI_PROVIDERS, DEFAULT_PROVIDER, - DEFAULT_MODEL, MODEL_SWITCH_MESSAGE, ) @@ -82,11 +81,7 @@ def test_model_switching(chat_window, mock_chat_handler, caplog): test_provider = next( provider for provider in AI_PROVIDERS.keys() if provider != DEFAULT_PROVIDER ) - test_model = next( - model - for model in AI_PROVIDERS[test_provider]["models"].keys() - if model != DEFAULT_MODEL - ) + test_model = list(AI_PROVIDERS[test_provider]["models"].keys())[0] window.provider_dropdown.setCurrentText(test_provider) window.model_dropdown.setCurrentText(test_model) diff --git a/tests/utils/test_api_utils.py b/tests/utils/test_api_utils.py index c56ed19..a014caa 100644 --- a/tests/utils/test_api_utils.py +++ b/tests/utils/test_api_utils.py @@ -12,8 +12,6 @@ get_api_client, ) from codeaide.utils.constants import ( - DEFAULT_MODEL, - DEFAULT_PROVIDER, SYSTEM_PROMPT, AI_PROVIDERS, ) @@ -28,8 +26,7 @@ pytest.mark.api_connection, ] -# Get the max_tokens value from the AI_PROVIDERS dictionary -MAX_TOKENS = AI_PROVIDERS[DEFAULT_PROVIDER]["models"][DEFAULT_MODEL]["max_tokens"] +MAX_TOKENS = 100 # Define this at the top of the file for use in tests @pytest.fixture @@ -222,12 +219,13 @@ def test_send_api_request_success_openai(self, mock_openai): mock_client.chat.completions.create.return_value = mock_response mock_openai.return_value = mock_client + model = list(AI_PROVIDERS["openai"]["models"].keys())[0] result = send_api_request( - mock_client, conversation_history, MAX_TOKENS, DEFAULT_MODEL, "openai" + mock_client, conversation_history, MAX_TOKENS, model, "openai" ) mock_client.chat.completions.create.assert_called_once_with( - model=DEFAULT_MODEL, + model=model, max_tokens=MAX_TOKENS, messages=[{"role": "system", "content": SYSTEM_PROMPT}] + conversation_history, @@ -247,12 +245,13 @@ def test_send_api_request_empty_response(self, mock_anthropic): mock_client.messages.create.return_value = mock_response mock_anthropic.return_value = mock_client + model = list(AI_PROVIDERS["anthropic"]["models"].keys())[0] result = send_api_request( - mock_client, conversation_history, MAX_TOKENS, DEFAULT_MODEL, "anthropic" + mock_client, conversation_history, MAX_TOKENS, model, "anthropic" ) mock_client.messages.create.assert_called_once_with( - model=DEFAULT_MODEL, + model=model, max_tokens=MAX_TOKENS, messages=conversation_history, system=SYSTEM_PROMPT, @@ -283,11 +282,12 @@ def test_send_api_request_api_error(self, mock_anthropic_client): body={"error": {"message": "API Error"}}, ) + model = list(AI_PROVIDERS["anthropic"]["models"].keys())[0] result = send_api_request( mock_anthropic_client, conversation_history, MAX_TOKENS, - DEFAULT_MODEL, + model, "anthropic", ) @@ -318,16 +318,17 @@ def test_send_api_request_custom_max_tokens(self, mock_anthropic_client): mock_response.content = [Mock(text="Hello! How can I assist you today?")] mock_anthropic_client.messages.create.return_value = mock_response + model = list(AI_PROVIDERS["anthropic"]["models"].keys())[0] result = send_api_request( mock_anthropic_client, conversation_history, custom_max_tokens, - DEFAULT_MODEL, + model, "anthropic", ) mock_anthropic_client.messages.create.assert_called_once_with( - model=DEFAULT_MODEL, + model=model, max_tokens=custom_max_tokens, messages=conversation_history, system=SYSTEM_PROMPT, From a320f045fa713175fce4b18efa2757e0913c0777 Mon Sep 17 00:00:00 2001 From: dougollerenshaw Date: Thu, 10 Oct 2024 12:49:07 -0700 Subject: [PATCH 5/5] Added a code cleaning method to deal with triple backticks --- codeaide/ui/chat_window.py | 3 --- codeaide/utils/api_utils.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/codeaide/ui/chat_window.py b/codeaide/ui/chat_window.py index f97614e..1cfbba6 100644 --- a/codeaide/ui/chat_window.py +++ b/codeaide/ui/chat_window.py @@ -335,9 +335,6 @@ def call_process_input_async(self, user_input): f"ChatWindow: call_process_input_async called with input: {user_input[:50]}..." ) response = self.chat_handler.process_input(user_input) - self.logger.info( - f"ChatWindow: Received response from chat handler: {str(response)[:50]}..." - ) self.handle_response(response) def on_modify(self): diff --git a/codeaide/utils/api_utils.py b/codeaide/utils/api_utils.py index 3e32046..2a01aae 100644 --- a/codeaide/utils/api_utils.py +++ b/codeaide/utils/api_utils.py @@ -4,6 +4,7 @@ import google.generativeai as genai from decouple import AutoConfig import hjson +import re from google.generativeai.types import GenerationConfig from codeaide.utils.constants import ( @@ -151,7 +152,7 @@ def parse_response(response, provider): if not response: raise ValueError("Empty or invalid response received") - logger.debug(f"Received response: {response}") + logger.info(f"Received response: {response}") if provider.lower() == "anthropic": if not response.content: @@ -190,9 +191,35 @@ def parse_response(response, provider): requirements = outer_json.get("requirements", []) questions = outer_json.get("questions", []) + # Clean the code if it exists + if code: + code = clean_code(code) + return text, questions, code, code_version, version_description, requirements +def clean_code(code): + """ + Clean the code by removing triple backticks and language identifiers. + + Args: + code (str): The code string to clean. + + Returns: + str: The cleaned code string. + """ + # Remove triple backticks and language identifier at the start + code = re.sub(r"^```[\w-]*\n", "", code, flags=re.MULTILINE) + + # Remove triple backticks at the end + code = re.sub(r"\n```$", "", code, flags=re.MULTILINE) + + # Trim any leading or trailing whitespace + code = code.strip() + + return code + + def check_api_connection(): client = get_api_client() if client is None: