Repository: mem0ai/mem0
Pull request: Implemented Gemini (#1490)
## Description

Added support for Gemini using `google-generativeai` module. (sorry it took so long 👍)

Fixes #1490 

## Type of change

- [✅] New feature (non-breaking change which adds functionality)
- [✅] Documentation update

## How Has This Been Tested?

Also added unit test for Gemini in `tests/llms/test_gemini_llm.py`

Please delete options that are not relevant.

- [✅] Unit Test

## Checklist:

- [✅] My code follows the style guidelines of this project
- [✅] I have performed a self-review of my own code
- [✅] I have commented my code, particularly in hard-to-understand areas
- [✅] I have made corresponding changes to the documentation
- [✅] My changes generate no new warnings
- [✅] I have added tests that prove my fix is effective or that my feature works
- [✅] New and existing unit tests pass locally with my changes
- [?] Any dependent changes have been merged and published in downstream modules
- [✅] I have checked my code and corrected any misspellings

## Maintainer Checklist

- [x] closes #1490  (Replace xxxx with the GitHub issue number)
- [x] Made sure Checks passed


Problem statement:
Gemini Support?
### 🚀 The feature

Add support for Google's Gemini.

### Motivation, pitch

I would like to use this in the ongoing Gemini API developer competition in which I'm making a pretty straight-forward agentic AI for Windows. Upon discovering this, I believed it would be useful to me, but then I realized that there is no support for Gemini. Just wanted to know if there is a reason it isn't out yet? or how long it might take? Is it something that is open for contributions?

Hints from issue discussion:
Hi @into-the-night, We do support Gemini in Embedchain: https://docs.embedchain.ai/components/llms#google-ai.

Do you need Gemini support for Mem0?
Yes, yes for mem0. That's what I was trying to make the issue about.
Alright, so for now you can use Gemini models from [Litellm](https://docs.mem0.ai/llms#litellm). I'll inform here once we have added standalone support for Gemini.

Let me know if you face any issue with this.
Got it! I'll try using that.
Also, just one more thing... Is this project open-source? I would like to work on the Gemini integration if I can.
@into-the-night Please feel free to raise a PR.
@Dev-Khant Can i pick this ? I see no one is assigned this issue , so i'm willing to take it on
Sure @Divyanshu9822 but let's ask if @into-the-night is working on this?
umm.. yea sorry about that @Divyanshu9822 I'm already working on this (albeit it _is_ a bit delayed due to some personal stuff catching up). I'll get this up soon.
Hey, its me again (after a long time i know) and I have made some progress with this. But I have ran into a pretty big problem. Gemini doesn't support "system" prompts (I tried changing system to "model" prompts but this has inconsistent results and doesn't make sense). And as far as I see, mem0 uses a lot of system prompts.

A possible solution that I found was to use "user" prompts paired with something like "this is a system instruction" but I wanted to consult here if this would be fine...? 

Thanks in advance! @Dev-Khant 
> Hey, its me again (after a long time i know) and I have made some progress with this. But I have ran into a pretty big problem. Gemini doesn't support "system" prompts (I tried changing system to "model" prompts but this has inconsistent results and doesn't make sense). And as far as I see, mem0 uses a lot of system prompts.
> 
> A possible solution that I found was to use "user" prompts paired with something like "this is a system instruction" but I wanted to consult here if this would be fine...?
> 
> Thanks in advance! @Dev-Khant

Gemini does take system prompt, example is shown below from aistudio :
```python
model = genai.GenerativeModel(
  model_name="gemini-1.5-flash-002",
  generation_config=generation_config,
  # safety_settings = Adjust safety settings
  # See https://ai.google.dev/gemini-api/docs/safety-settings
  system_instruction="you are an ai assitant ",
)
```

Try out gemini on `https://aistudio.google.com/` and click on get code, you'll get model initialisation

Commit message: Merge pull request #1 from mem0ai/main

merge main from mem0ai/mem0

Patch to review:
diff --git a/docs/components/llms/models/gemini.mdx b/docs/components/llms/models/gemini.mdx
new file mode 100644
index 0000000000..f020a2dd23
--- /dev/null
+++ b/docs/components/llms/models/gemini.mdx
@@ -0,0 +1,33 @@
+---
+title: Gemini
+---
+
+To use Gemini model, you have to set the `GEMINI_API_KEY` environment variable. You can obtain the Gemini API key from the [Google AI Studio](https://aistudio.google.com/app/apikey)
+
+## Usage
+
+```python
+import os
+from mem0 import Memory
+
+os.environ["OPENAI_API_KEY"] = "your-api-key" # used for embedding model
+os.environ["GEMINI_API_KEY"] = "your-api-key"
+
+config = {
+    "llm": {
+        "provider": "gemini",
+        "config": {
+            "model": "gemini-1.5-flash-latest",
+            "temperature": 0.2,
+            "max_tokens": 1500,
+        }
+    }
+}
+
+m = Memory.from_config(config)
+m.add("Likes to play cricket on weekends", user_id="alice", metadata={"category": "hobbies"})
+```
+
+## Config
+
+All available parameters for the `Gemini` config are present in [Master List of All Params in Config](../config).
\ No newline at end of file
diff --git a/mem0/llms/gemini.py b/mem0/llms/gemini.py
new file mode 100644
index 0000000000..8dbe6e79a1
--- /dev/null
+++ b/mem0/llms/gemini.py
@@ -0,0 +1,139 @@
+import os
+from typing import Dict, List, Optional
+
+import google.generativeai as genai
+from google.generativeai import GenerativeModel
+from google.generativeai.types import content_types
+
+from mem0.configs.llms.base import BaseLlmConfig
+from mem0.llms.base import LLMBase
+
+
+class GeminiLLM(LLMBase):
+    def __init__(self, config: Optional[BaseLlmConfig] = None):
+        super().__init__(config)
+
+        if not self.config.model:
+            self.config.model = "gemini-1.5-flash-latest"
+
+        api_key = self.config.api_key or os.getenv("GEMINI_API_KEY")
+        genai.configure(api_key=api_key)
+        self.client = GenerativeModel(model_name=self.config.model)
+
+    def _parse_response(self, response, tools):
+        """
+        Process the response based on whether tools are used or not.
+
+        Args:
+            response: The raw response from API.
+            tools: The list of tools provided in the request.
+
+        Returns:
+            str or dict: The processed response.
+        """
+        if tools:
+            processed_response = {
+                "content": content if (content := response.candidates[0].content.parts[0].text) else None,
+                "tool_calls": [],
+            }
+
+            for part in response.candidates[0].content.parts:
+                if fn := part.function_call:
+                    processed_response["tool_calls"].append(
+                        {
+                                "name": fn.name,
+                                "arguments": {key:val for key, val in fn.args.items()},
+                        }
+                    )
+
+            return processed_response
+        else:
+            return response.candidates[0].content.parts[0].text
+
+    def _reformat_messages(self, messages : List[Dict[str, str]]):
+        """
+        Reformat messages for Gemini.
+
+        Args:
+            messages: The list of messages provided in the request.
+
+        Returns:
+            list: The list of messages in the required format.
+        """
+        new_messages = []
+
+        for message in messages:
+            if message["role"] == "system":
+                content = "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: " + message["content"]
+
+            else:
+                content = message["content"]
+            
+            new_messages.append({"parts": content,
+                                "role": "model" if message["role"] == "model" else "user"})
+
+        return new_messages
+
+    def _reformat_tools(self, tools: Optional[List[Dict]]):
+        """
+        Reformat tools for Gemini.
+
+        Args:
+            tools: The list of tools provided in the request.
+
+        Returns:
+            list: The list of tools in the required format.
+        """
+        if tools:
+            new_tools = []
+
+            for tool in tools:
+                func = tool["function"].copy()
+                func["parameters"].pop("additionalProperties", None)
+                new_tools.append({"function_declarations":[func]})
+
+            return new_tools
+        else:
+            return None
+
+    def generate_response(
+        self,
+        messages: List[Dict[str, str]],
+        response_format=None,
+        tools: Optional[List[Dict]] = None,
+        tool_choice: str = "auto",
+    ):
+        """
+        Generate a response based on the given messages using Gemini.
+
+        Args:
+            messages (list): List of message dicts containing 'role' and 'content'.
+            response_format (str or object, optional): Format for the response. Defaults to "text".
+            tools (list, optional): List of tools that the model can call. Defaults to None.
+            tool_choice (str, optional): Tool choice method. Defaults to "auto".
+
+        Returns:
+            str: The generated response.
+        """
+
+        params = {
+            "temperature": self.config.temperature,
+            "max_output_tokens": self.config.max_tokens,
+            "top_p": self.config.top_p,
+        }
+
+        if response_format:
+            params["response_mime_type"] = "application/json"
+            params["response_schema"] = list[response_format]
+        if tool_choice:
+            tool_config = content_types.to_tool_config(
+            {"function_calling_config":
+                {"mode": tool_choice, "allowed_function_names": [tool['function']['name'] for tool in tools] if tool_choice == "any" else None}
+            })
+
+        response = self.client.generate_content(contents = self._reformat_messages(messages),
+                                                tools = self._reformat_tools(tools),
+                                                generation_config = genai.GenerationConfig(**params),
+                                                tool_config = tool_config)
+
+        return self._parse_response(response, tools)
diff --git a/mem0/utils/factory.py b/mem0/utils/factory.py
index 43f853c689..b7606c69a8 100644
--- a/mem0/utils/factory.py
+++ b/mem0/utils/factory.py
@@ -22,6 +22,7 @@ class LlmFactory:
         "openai_structured": "mem0.llms.openai_structured.OpenAIStructuredLLM",
         "anthropic": "mem0.llms.anthropic.AnthropicLLM",
         "azure_openai_structured": "mem0.llms.azure_openai_structured.AzureOpenAIStructuredLLM",
+        "gemini": "mem0.llms.gemini.GeminiLLM",
     }
 
     @classmethod
diff --git a/poetry.lock b/poetry.lock
index 66b08af481..5e9d319f4d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
 
 [[package]]
 name = "aiohappyeyeballs"
@@ -211,6 +211,17 @@ files = [
     {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"},
 ]
 
+[[package]]
+name = "cachetools"
+version = "5.5.0"
+description = "Extensible memoizing collections and decorators"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"},
+    {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"},
+]
+
 [[package]]
 name = "certifi"
 version = "2024.7.4"
@@ -458,6 +469,151 @@ files = [
     {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"},
 ]
 
+[[package]]
+name = "google-ai-generativelanguage"
+version = "0.6.10"
+description = "Google Ai Generativelanguage API client library"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "google_ai_generativelanguage-0.6.10-py3-none-any.whl", hash = "sha256:854a2bf833d18be05ad5ef13c755567b66a4f4a870f099b62c61fe11bddabcf4"},
+    {file = "google_ai_generativelanguage-0.6.10.tar.gz", hash = "sha256:6fa642c964d8728006fe7e8771026fc0b599ae0ebeaf83caf550941e8e693455"},
+]
+
+[package.dependencies]
+google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
+google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev"
+proto-plus = ">=1.22.3,<2.0.0dev"
+protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev"
+
+[[package]]
+name = "google-api-core"
+version = "2.21.0"
+description = "Google API client core library"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "google_api_core-2.21.0-py3-none-any.whl", hash = "sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d"},
+    {file = "google_api_core-2.21.0.tar.gz", hash = "sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81"},
+]
+
+[package.dependencies]
+google-auth = ">=2.14.1,<3.0.dev0"
+googleapis-common-protos = ">=1.56.2,<2.0.dev0"
+grpcio = [
+    {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
+    {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
+]
+grpcio-status = [
+    {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
+    {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
+]
+proto-plus = ">=1.22.3,<2.0.0dev"
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0"
+requests = ">=2.18.0,<3.0.0.dev0"
+
+[package.extras]
+async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"]
+grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"]
+grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
+grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
+
+[[package]]
+name = "google-api-python-client"
+version = "2.149.0"
+description = "Google API Client Library for Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "google_api_python_client-2.149.0-py2.py3-none-any.whl", hash = "sha256:1a5232e9cfed8c201799d9327e4d44dc7ea7daa3c6e1627fca41aa201539c0da"},
+    {file = "google_api_python_client-2.149.0.tar.gz", hash = "sha256:b9d68c6b14ec72580d66001bd33c5816b78e2134b93ccc5cf8f624516b561750"},
+]
+
+[package.dependencies]
+google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0"
+google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0"
+google-auth-httplib2 = ">=0.2.0,<1.0.0"
+httplib2 = ">=0.19.0,<1.dev0"
+uritemplate = ">=3.0.1,<5"
+
+[[package]]
+name = "google-auth"
+version = "2.35.0"
+description = "Google Authentication Library"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"},
+    {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"},
+]
+
+[package.dependencies]
+cachetools = ">=2.0.0,<6.0"
+pyasn1-modules = ">=0.2.1"
+rsa = ">=3.1.4,<5"
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"]
+enterprise-cert = ["cryptography", "pyopenssl"]
+pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"]
+reauth = ["pyu2f (>=0.1.5)"]
+requests = ["requests (>=2.20.0,<3.0.0.dev0)"]
+
+[[package]]
+name = "google-auth-httplib2"
+version = "0.2.0"
+description = "Google Authentication Library: httplib2 transport"
+optional = false
+python-versions = "*"
+files = [
+    {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"},
+    {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"},
+]
+
+[package.dependencies]
+google-auth = "*"
+httplib2 = ">=0.19.0"
+
+[[package]]
+name = "google-generativeai"
+version = "0.8.3"
+description = "Google Generative AI High level API client library and tools."
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "google_generativeai-0.8.3-py3-none-any.whl", hash = "sha256:1108ff89d5b8e59f51e63d1a8bf84701cd84656e17ca28d73aeed745e736d9b7"},
+]
+
+[package.dependencies]
+google-ai-generativelanguage = "0.6.10"
+google-api-core = "*"
+google-api-python-client = "*"
+google-auth = ">=2.15.0"
+protobuf = "*"
+pydantic = "*"
+tqdm = "*"
+typing-extensions = "*"
+
+[package.extras]
+dev = ["Pillow", "absl-py", "black", "ipython", "nose2", "pandas", "pytype", "pyyaml"]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.65.0"
+description = "Common protobufs used in Google APIs"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"},
+    {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"},
+]
+
+[package.dependencies]
+protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0"
+
+[package.extras]
+grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"]
+
 [[package]]
 name = "greenlet"
 version = "3.0.3"
@@ -587,6 +743,22 @@ files = [
 [package.extras]
 protobuf = ["grpcio-tools (>=1.64.1)"]
 
+[[package]]
+name = "grpcio-status"
+version = "1.62.3"
+description = "Status proto mapping for gRPC"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"},
+    {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"},
+]
+
+[package.dependencies]
+googleapis-common-protos = ">=1.5.5"
+grpcio = ">=1.62.3"
+protobuf = ">=4.21.6"
+
 [[package]]
 name = "grpcio-tools"
 version = "1.62.2"
@@ -713,6 +885,20 @@ http2 = ["h2 (>=3,<5)"]
 socks = ["socksio (==1.*)"]
 trio = ["trio (>=0.22.0,<0.26.0)"]
 
+[[package]]
+name = "httplib2"
+version = "0.22.0"
+description = "A comprehensive HTTP client library."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+    {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"},
+    {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"},
+]
+
+[package.dependencies]
+pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""}
+
 [[package]]
 name = "httpx"
 version = "0.27.0"
@@ -1282,6 +1468,23 @@ dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"]
 sentry = ["django", "sentry-sdk"]
 test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest", "pytest-timeout"]
 
+[[package]]
+name = "proto-plus"
+version = "1.24.0"
+description = "Beautiful, Pythonic protocol buffers."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"},
+    {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"},
+]
+
+[package.dependencies]
+protobuf = ">=3.19.0,<6.0.0dev"
+
+[package.extras]
+testing = ["google-api-core (>=1.31.5)"]
+
 [[package]]
 name = "protobuf"
 version = "4.25.4"
@@ -1302,6 +1505,31 @@ files = [
     {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"},
 ]
 
+[[package]]
+name = "pyasn1"
+version = "0.6.1"
+description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
+    {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
+]
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.4.1"
+description = "A collection of ASN.1-based protocols modules"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"},
+    {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"},
+]
+
+[package.dependencies]
+pyasn1 = ">=0.4.6,<0.7.0"
+
 [[package]]
 name = "pydantic"
 version = "2.8.2"
@@ -1445,6 +1673,20 @@ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0
 toml = ["tomli (>=2.0.1)"]
 yaml = ["pyyaml (>=6.0.1)"]
 
+[[package]]
+name = "pyparsing"
+version = "3.2.0"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"},
+    {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"},
+]
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
 [[package]]
 name = "pytest"
 version = "8.2.2"
@@ -1656,6 +1898,20 @@ urllib3 = ">=1.21.1,<3"
 socks = ["PySocks (>=1.5.6,!=1.5.7)"]
 use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
+[[package]]
+name = "rsa"
+version = "4.9"
+description = "Pure-Python RSA implementation"
+optional = false
+python-versions = ">=3.6,<4"
+files = [
+    {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
+    {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
+]
+
+[package.dependencies]
+pyasn1 = ">=0.1.3"
+
 [[package]]
 name = "ruff"
 version = "0.6.5"
@@ -1779,7 +2035,7 @@ files = [
 ]
 
 [package.dependencies]
-greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"}
+greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
 typing-extensions = ">=4.6.0"
 
 [package.extras]
@@ -1879,6 +2135,17 @@ files = [
 mypy-extensions = ">=0.3.0"
 typing-extensions = ">=3.7.4"
 
+[[package]]
+name = "uritemplate"
+version = "4.1.1"
+description = "Implementation of RFC 6570 URI Templates"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
+    {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
+]
+
 [[package]]
 name = "urllib3"
 version = "2.2.2"
@@ -2002,4 +2269,4 @@ multidict = ">=4.0"
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.9,<4.0"
-content-hash = "0e30230a6a904ff2cbd84720f7685886d65dab497f9d5b9d767f22ac9819badd"
+content-hash = "f9e62672b38c40cdf3c86542834d00ac9b0ad4e5499bfe8d9cf92474356b4fcb"
diff --git a/pyproject.toml b/pyproject.toml
index 4bc9648910..5c05b53b6e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,6 +22,7 @@ openai = "^1.33.0"
 posthog = "^3.5.0"
 pytz = "^2024.1"
 sqlalchemy = "^2.0.31"
+google-generativeai = "^0.8.3"
 
 [tool.poetry.group.graph.dependencies]
 langchain-community = "^0.3.1"
diff --git a/tests/llms/test_gemini_llm.py b/tests/llms/test_gemini_llm.py
new file mode 100644
index 0000000000..c6244aeb24
--- /dev/null
+++ b/tests/llms/test_gemini_llm.py
@@ -0,0 +1,118 @@
+from unittest.mock import Mock, patch
+
+import pytest
+from google.generativeai import GenerationConfig
+from google.generativeai.types import content_types
+
+from mem0.configs.llms.base import BaseLlmConfig
+from mem0.llms.gemini import GeminiLLM
+
+
+@pytest.fixture
+def mock_gemini_client():
+    with patch("mem0.llms.gemini.GenerativeModel") as mock_gemini:
+        mock_client = Mock()
+        mock_gemini.return_value = mock_client
+        yield mock_client
+
+
+def test_generate_response_without_tools(mock_gemini_client: Mock):
+    config = BaseLlmConfig(model="gemini-1.5-flash-latest", temperature=0.7, max_tokens=100, top_p=1.0)
+    llm = GeminiLLM(config)
+    messages = [
+        {"role": "system", "content": "You are a helpful assistant."},
+        {"role": "user", "content": "Hello, how are you?"},
+    ]
+
+    mock_part = Mock(text="I'm doing well, thank you for asking!")
+    mock_content = Mock(parts=[mock_part])
+    mock_message = Mock(content=mock_content)
+    mock_response = Mock(candidates=[mock_message])
+    mock_gemini_client.generate_content.return_value = mock_response
+
+    response = llm.generate_response(messages)
+
+    mock_gemini_client.generate_content.assert_called_once_with(
+        contents = [
+            {"parts": "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: You are a helpful assistant.", "role": "user"}, 
+            {"parts": "Hello, how are you?", "role": "user"}
+        ],
+        generation_config = GenerationConfig(temperature=0.7, max_output_tokens=100, top_p=1.0),
+        tools = None,
+        tool_config = content_types.to_tool_config(
+            {"function_calling_config":
+                {"mode": 'auto', "allowed_function_names": None}
+            })
+    )
+    assert response == "I'm doing well, thank you for asking!"
+    
+def test_generate_response_with_tools(mock_gemini_client: Mock):
+    config = BaseLlmConfig(model="gemini-1.5-flash-latest", temperature=0.7, max_tokens=100, top_p=1.0)
+    llm = GeminiLLM(config)
+    messages = [
+        {"role": "system", "content": "You are a helpful assistant."},
+        {"role": "user", "content": "Add a new memory: Today is a sunny day."},
+    ]
+    tools = [
+        {
+            "type": "function",
+            "function": {
+                "name": "add_memory",
+                "description": "Add a memory",
+                "parameters": {
+                    "type": "object",
+                    "properties": {"data": {"type": "string", "description": "Data to add to memory"}},
+                    "required": ["data"],
+                },
+            },
+        }
+    ]
+
+    mock_tool_call = Mock()
+    mock_tool_call.name = "add_memory"
+    mock_tool_call.args = {"data": "Today is a sunny day."}
+
+    mock_part = Mock()
+    mock_part.function_call = mock_tool_call
+    mock_part.text="I've added the memory for you."
+
+    mock_content = Mock()
+    mock_content.parts=[mock_part]
+
+    mock_message = Mock()
+    mock_message.content=mock_content
+
+    mock_response = Mock(candidates=[mock_message])
+    mock_gemini_client.generate_content.return_value = mock_response
+
+    response = llm.generate_response(messages, tools=tools)
+
+    mock_gemini_client.generate_content.assert_called_once_with(
+        contents = [
+            {"parts": "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: You are a helpful assistant.", "role": "user"}, 
+            {"parts": "Add a new memory: Today is a sunny day.", "role": "user"}
+        ],
+        generation_config = GenerationConfig(temperature=0.7, max_output_tokens=100, top_p=1.0),
+        tools = [
+            {
+                "function_declarations": [{
+                    "name": "add_memory", 
+                    "description": "Add a memory",
+                    "parameters": {
+                        "type": "object", 
+                        "properties": {"data": {"type": "string", "description": "Data to add to memory"}},
+                        "required": ["data"]
+                    }
+                }]
+            }
+        ],
+        tool_config = content_types.to_tool_config(
+            {"function_calling_config":
+                {"mode": 'auto', "allowed_function_names": None}
+            })
+    )
+
+    assert response["content"] == "I've added the memory for you."
+    assert len(response["tool_calls"]) == 1
+    assert response["tool_calls"][0]["name"] == "add_memory"
+    assert response["tool_calls"][0]["arguments"] == {"data": "Today is a sunny day."}


Code review comments:
diff_hunk: @@ -22,6 +22,7 @@ openai = "^1.33.0"
 posthog = "^3.5.0"
 pytz = "^2024.1"
 sqlalchemy = "^2.0.31"
+google-generativeai = "^0.8.3"
path: pyproject.toml
text: @user1:
Hey make a optional dependency. You can check how we have done for other LLMs

@author:
Done!

diff_hunk: @@ -0,0 +1,139 @@
+import os
+from typing import Dict, List, Optional
+
+import google.generativeai as genai
+from google.generativeai import GenerativeModel
+from google.generativeai.types import content_types
+
+from mem0.configs.llms.base import BaseLlmConfig
+from mem0.llms.base import LLMBase
+
+
+class GeminiLLM(LLMBase):
+    def __init__(self, config: Optional[BaseLlmConfig] = None):
+        super().__init__(config)
+
+        if not self.config.model:
+            self.config.model = "gemini-1.5-flash-latest"
+
+        api_key = self.config.api_key or os.getenv("GEMINI_API_KEY")
+        genai.configure(api_key=api_key)
+        self.client = GenerativeModel(model_name=self.config.model)
+
+    def _parse_response(self, response, tools):
+        """
+        Process the response based on whether tools are used or not.
+
+        Args:
+            response: The raw response from API.
+            tools: The list of tools provided in the request.
+
+        Returns:
+            str or dict: The processed response.
+        """
+        if tools:
+            processed_response = {
+                "content": content if (content := response.candidates[0].content.parts[0].text) else None,
+                "tool_calls": [],
+            }
+
+            for part in response.candidates[0].content.parts:
+                if fn := part.function_call:
+                    processed_response["tool_calls"].append(
+                        {
+                                "name": fn.name,
+                                "arguments": {key:val for key, val in fn.args.items()},
+                        }
+                    )
+
+            return processed_response
+        else:
+            return response.candidates[0].content.parts[0].text
+
+    def _reformat_messages(self, messages : List[Dict[str, str]]):
+        """
+        Reformat messages for Gemini.
+
+        Args:
+            messages: The list of messages provided in the request.
+
+        Returns:
+            list: The list of messages in the required format.
+        """
+        new_messages = []
+
+        for message in messages:
+            if message["role"] == "system":
+                content = "THIS IS A SYSTEM PROMPT. YOU MUST OBEY THIS: " + message["content"]
+
+            else:
+                content = message["content"]
+            
+            new_messages.append({"parts": content,
+                                "role": "model" if message["role"] == "model" else "user"})
+
+        return new_messages
+
+    def _reformat_tools(self, tools: Optional[List[Dict]]):
+        """
+        Reformat tools for Gemini.
+
+        Args:
+            tools: The list of tools provided in the request.
+
+        Returns:
+            list: The list of tools in the required format.
+        """
+        if tools:
+            new_tools = []
+
+            for tool in tools:
+                func = tool["function"].copy()
+                func["parameters"].pop("additionalProperties", None)
path: mem0/llms/gemini.py
text: @user1:
Are you sure we don't have to change the underlying tools dict structure here?

@author:
oh yea I didn't consider nested object properties in the tools dict. In that case, some more changes would need to be made. I'll do that asap.