Repository: mem0ai/mem0
Pull request: feat: system prompt
## Description

Allow passing of a system prompt.

Fixes #443 

## Type of change

Please delete options that are not relevant.

- [X] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

Please delete options that are not relevant.

- [X] Unit Test

## Checklist:

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my own code
- [X] I have commented my code, particularly in hard-to-understand areas
- [X] I have made corresponding changes to the documentation
- [X] My changes generate no new warnings
- [X] I have added tests that prove my fix is effective or that my feature works
- [X] New and existing unit tests pass locally with my changes
- [X] Any dependent changes have been merged and published in downstream modules
- [X] I have checked my code and corrected any misspellings

## Maintainer Checklist

- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] Made sure Checks passed


Problem statement:
Add support to pass system prompt
### 🚀 The feature

OpenAI supports passing system prompt. We need to add support for this in embedchain

### Motivation, pitch

System prompts help in better controlling the persona of the bot.

### Alternatives

_No response_

### Additional context

_No response_

Hints from issue discussion:

Commit message: docs: added system_prompt

Patch to review:
diff --git a/docs/advanced/query_configuration.mdx b/docs/advanced/query_configuration.mdx
index 6ab51d3dc4..23f47815b8 100644
--- a/docs/advanced/query_configuration.mdx
+++ b/docs/advanced/query_configuration.mdx
@@ -65,6 +65,8 @@ _coming soon_
 |top_p|Controls the diversity of words. Higher values (closer to 1) make word selection more diverse, lower values make words less diverse.|float|1|
 |history|include conversation history from your client or database.|any (recommendation: list[str])|None|
 |stream|control if response is streamed back to the user.|bool|False|
+|deployment_name|t.b.a.|str|None|
+|system_prompt|System prompt string. Unused if none.|str|None|
 
 ## ChatConfig
 
diff --git a/embedchain/apps/App.py b/embedchain/apps/App.py
index 057ad92c56..3e6537e362 100644
--- a/embedchain/apps/App.py
+++ b/embedchain/apps/App.py
@@ -25,6 +25,8 @@ def __init__(self, config: AppConfig = None):
 
     def get_llm_model_answer(self, prompt, config: ChatConfig):
         messages = []
+        if config.system_prompt:
+            messages.append({"role": "system", "content": config.system_prompt})
         messages.append({"role": "user", "content": prompt})
         response = openai.ChatCompletion.create(
             model=config.model or "gpt-3.5-turbo-0613",
diff --git a/embedchain/apps/CustomApp.py b/embedchain/apps/CustomApp.py
index 6b3cf30e6a..1cb6574251 100644
--- a/embedchain/apps/CustomApp.py
+++ b/embedchain/apps/CustomApp.py
@@ -1,5 +1,5 @@
 import logging
-from typing import List
+from typing import List, Optional
 
 from langchain.schema import BaseMessage
 
@@ -84,7 +84,7 @@ def _get_openai_answer(prompt: str, config: ChatConfig) -> str:
         if config.top_p and config.top_p != 1:
             logging.warning("Config option `top_p` is not supported by this model.")
 
-        messages = CustomApp._get_messages(prompt)
+        messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
 
         return chat(messages).content
 
@@ -97,7 +97,7 @@ def _get_athrophic_answer(prompt: str, config: ChatConfig) -> str:
         if config.max_tokens and config.max_tokens != 1000:
             logging.warning("Config option `max_tokens` is not supported by this model.")
 
-        messages = CustomApp._get_messages(prompt)
+        messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
 
         return chat(messages).content
 
@@ -110,7 +110,7 @@ def _get_vertex_answer(prompt: str, config: ChatConfig) -> str:
         if config.top_p and config.top_p != 1:
             logging.warning("Config option `top_p` is not supported by this model.")
 
-        messages = CustomApp._get_messages(prompt)
+        messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
 
         return chat(messages).content
 
@@ -133,15 +133,18 @@ def _get_azure_openai_answer(prompt: str, config: ChatConfig) -> str:
         if config.top_p and config.top_p != 1:
             logging.warning("Config option `top_p` is not supported by this model.")
 
-        messages = CustomApp._get_messages(prompt)
+        messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
 
         return chat(messages).content
 
     @staticmethod
-    def _get_messages(prompt: str) -> List[BaseMessage]:
+    def _get_messages(prompt: str, system_prompt: Optional[str] = None) -> List[BaseMessage]:
         from langchain.schema import HumanMessage, SystemMessage
 
-        return [SystemMessage(content="You are a helpful assistant."), HumanMessage(content=prompt)]
+        return [
+            SystemMessage(content=system_prompt if system_prompt else "You are a helpful assistant."),
+            HumanMessage(content=prompt),
+        ]
 
     def _stream_llm_model_response(self, response):
         """
diff --git a/embedchain/apps/Llama2App.py b/embedchain/apps/Llama2App.py
index b1a133415c..b9615cf83b 100644
--- a/embedchain/apps/Llama2App.py
+++ b/embedchain/apps/Llama2App.py
@@ -2,7 +2,7 @@
 
 from langchain.llms import Replicate
 
-from embedchain.config import AppConfig
+from embedchain.config import AppConfig, ChatConfig
 from embedchain.embedchain import EmbedChain
 
 
@@ -27,8 +27,10 @@ def __init__(self, config: AppConfig = None):
 
         super().__init__(config)
 
-    def get_llm_model_answer(self, prompt, config: AppConfig = None):
+    def get_llm_model_answer(self, prompt, config: ChatConfig = None):
         # TODO: Move the model and other inputs into config
+        if config.system_prompt:
+            raise ValueError("Llama2App does not support `system_prompt`")
         llm = Replicate(
             model="a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
             input={"temperature": 0.75, "max_length": 500, "top_p": 1},
diff --git a/embedchain/apps/OpenSourceApp.py b/embedchain/apps/OpenSourceApp.py
index 803cf13334..eaf04cab8c 100644
--- a/embedchain/apps/OpenSourceApp.py
+++ b/embedchain/apps/OpenSourceApp.py
@@ -55,6 +55,9 @@ def _get_gpt4all_answer(self, prompt: str, config: ChatConfig) -> Union[str, Ite
                 "OpenSourceApp does not support switching models at runtime. Please create a new app instance."
             )
 
+        if config.system_prompt:
+            raise ValueError("OpenSourceApp does not support `system_prompt`")
+
         response = self.instance.generate(
             prompt=prompt,
             streaming=config.stream,
diff --git a/embedchain/config/ChatConfig.py b/embedchain/config/ChatConfig.py
index 0149fd3a3b..fd195a9083 100644
--- a/embedchain/config/ChatConfig.py
+++ b/embedchain/config/ChatConfig.py
@@ -1,4 +1,5 @@
 from string import Template
+from typing import Optional
 
 from embedchain.config.QueryConfig import QueryConfig
 
@@ -34,6 +35,7 @@ def __init__(
         top_p=None,
         stream: bool = False,
         deployment_name=None,
+        system_prompt: Optional[str] = None,
     ):
         """
         Initializes the ChatConfig instance.
@@ -51,6 +53,8 @@ def __init__(
         (closer to 1) make word selection more diverse, lower values make words less
         diverse.
         :param stream: Optional. Control if response is streamed back to the user
+        :param deployment_name: t.b.a.
+        :param system_prompt: Optional. System prompt string.
         :raises ValueError: If the template is not valid as template should contain
         $context and $query and $history
         """
@@ -70,6 +74,7 @@ def __init__(
             history=[0],
             stream=stream,
             deployment_name=deployment_name,
+            system_prompt=system_prompt,
         )
 
     def set_history(self, history):
diff --git a/embedchain/config/QueryConfig.py b/embedchain/config/QueryConfig.py
index ef06f597dd..3a0ceef75f 100644
--- a/embedchain/config/QueryConfig.py
+++ b/embedchain/config/QueryConfig.py
@@ -1,5 +1,6 @@
 import re
 from string import Template
+from typing import Optional
 
 from embedchain.config.BaseConfig import BaseConfig
 
@@ -63,6 +64,7 @@ def __init__(
         history=None,
         stream: bool = False,
         deployment_name=None,
+        system_prompt: Optional[str] = None,
     ):
         """
         Initializes the QueryConfig instance.
@@ -81,6 +83,8 @@ def __init__(
         diverse.
         :param history: Optional. A list of strings to consider as history.
         :param stream: Optional. Control if response is streamed back to user
+        :param deployment_name: t.b.a.
+        :param system_prompt: Optional. System prompt string.
         :raises ValueError: If the template is not valid as template should
         contain $context and $query (and optionally $history).
         """
@@ -108,6 +112,7 @@ def __init__(
         self.model = model
         self.top_p = top_p if top_p else 1
         self.deployment_name = deployment_name
+        self.system_prompt = system_prompt
 
         if self.validate_template(template):
             self.template = template
diff --git a/tests/embedchain/test_query.py b/tests/embedchain/test_query.py
index 5c47ce8c4a..da8f1b2603 100644
--- a/tests/embedchain/test_query.py
+++ b/tests/embedchain/test_query.py
@@ -41,3 +41,20 @@ def test_query(self):
         self.assertEqual(mock_retrieve.call_args[0][0], "Test query")
         self.assertIsInstance(mock_retrieve.call_args[0][1], QueryConfig)
         mock_answer.assert_called_once()
+
+    @patch("openai.ChatCompletion.create")
+    def test_query_config_passing(self, mock_create):
+        mock_create.return_value = {"choices": [{"message": {"content": "response"}}]}  # Mock response
+
+        config = AppConfig()
+        chat_config = QueryConfig(system_prompt="Test system prompt")
+        app = App(config=config)
+
+        app.get_llm_model_answer("Test query", chat_config)
+
+        # Test systemp_prompt: Check that the 'create' method was called with the correct 'messages' argument
+        messages_arg = mock_create.call_args.kwargs["messages"]
+        self.assertEqual(messages_arg[0]["role"], "system")
+        self.assertEqual(messages_arg[0]["content"], "Test system prompt")
+
+        # TODO: Add tests for other config variables


Code review comments:
diff_hunk: @@ -133,15 +133,18 @@ def _get_azure_openai_answer(prompt: str, config: ChatConfig) -> str:
         if config.top_p and config.top_p != 1:
             logging.warning("Config option `top_p` is not supported by this model.")
 
-        messages = CustomApp._get_messages(prompt)
+        messages = CustomApp._get_messages(prompt, system_prompt=config.system_prompt)
 
         return chat(messages).content
 
     @staticmethod
-    def _get_messages(prompt: str) -> List[BaseMessage]:
+    def _get_messages(prompt: str, system_prompt: Optional[str] = None) -> List[BaseMessage]:
         from langchain.schema import HumanMessage, SystemMessage
 
-        return [SystemMessage(content="You are a helpful assistant."), HumanMessage(content=prompt)]
+        return [
+            SystemMessage(content=system_prompt if system_prompt else "You are a helpful assistant."),
path: embedchain/apps/CustomApp.py
text: @user1:
We should only add if system prompt is present.
Can you update?

@author:
fixed