From 7bae11dca8dfd1341f98858454a00b41c3341333 Mon Sep 17 00:00:00 2001 From: Mehdi Samsami Date: Thu, 30 Jan 2025 17:07:03 +0330 Subject: [PATCH 1/7] add support for specifying additional system prompts for the llm --- src/clonellm/_prompt.py | 14 ++++++++++---- src/clonellm/core.py | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/clonellm/_prompt.py b/src/clonellm/_prompt.py index aaa6b4a..9f3ae41 100644 --- a/src/clonellm/_prompt.py +++ b/src/clonellm/_prompt.py @@ -1,6 +1,8 @@ +from typing import Optional + from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -__all__ = ("summarize_context_prompt", "context_prompt", "user_profile_prompt", "history_prompt", "question_prompt") +__all__ = ("summarize_context_prompt", "get_context_prompt", "user_profile_prompt", "history_prompt", "question_prompt") summarize_context_prompt = ChatPromptTemplate.from_messages( @@ -13,8 +15,8 @@ ) -context_prompt = ChatPromptTemplate.from_messages( - [ +def get_context_prompt(additional_system_prompts: Optional[list[str]] = None) -> ChatPromptTemplate: + prompts = [ ("system", "You are my human clone. Act as I would. Do not ask questions about me. If anyone asks, you are me."), ("system", "You are having a conversation with someone who wants to learn more about you."), ("system", "You MUST speak in first person. If you do not, you will be terminated."), @@ -27,10 +29,14 @@ ), ), ("system", "Use the personality traits and communication samples (if provided) to know how to craft your responses."), + ] + prompts += [("system", prompt) for prompt in additional_system_prompts or []] + prompts += [ ("system", "Here is some relevant context you have related to the question:\n {context}"), ("system", "ALWAYS ensure your responses are aligned with the provided context (and personal information)."), ] -) + return ChatPromptTemplate.from_messages(prompts) + user_profile_prompt = ChatPromptTemplate.from_messages( [ diff --git a/src/clonellm/core.py b/src/clonellm/core.py index f875e00..7685066 100644 --- a/src/clonellm/core.py +++ b/src/clonellm/core.py @@ -26,7 +26,7 @@ from ._base import LiteLLMMixin from ._prompt import ( - context_prompt, + get_context_prompt, history_prompt, question_prompt, summarize_context_prompt, @@ -52,6 +52,7 @@ class CloneLLM(LiteLLMMixin): user_profile (Optional[UserProfile | dict[str, Any] | str]): The profile of the user to be cloned by the language model. Defaults to None. memory (Optional[bool | int]): Maximum number of messages in conversation memory. Defaults to None (or 0) for no memory. -1 or `True` means infinite memory. api_key (Optional[str]): The API key to use. Defaults to None. + system_prompts (Optional[list[str]]): Additional system prompts (instructions) for the language model. Defaults to None. **kwargs (Any): Additional keyword arguments supported by the `langchain_community.chat_models.ChatLiteLLM` class. """ @@ -70,6 +71,7 @@ def __init__( user_profile: Optional[UserProfile | dict[str, Any] | str] = None, memory: Optional[bool | int] = None, api_key: Optional[str] = None, + system_prompts: Optional[list[str]] = None, **kwargs: Any, ) -> None: self.embedding = embedding @@ -77,6 +79,7 @@ def __init__( self.documents = documents self.user_profile = user_profile self.memory = memory + self.system_prompts = system_prompts from_class_method: Optional[dict[str, Any]] = kwargs.pop(self._FROM_CLASS_METHOD_KWARG, None) super().__init__(model, api_key, **kwargs) @@ -134,6 +137,7 @@ def from_persist_directory( user_profile: Optional[UserProfile | dict[str, Any] | str] = None, memory: Optional[bool | int] = None, api_key: Optional[str] = None, + system_prompts: Optional[list[str]] = None, **kwargs: Any, ) -> Self: """Creates an instance of CloneLLM by loading a Chroma vector store from a persistent directory. @@ -145,6 +149,7 @@ def from_persist_directory( user_profile (Optional[UserProfile | dict[str, Any] | str]): The profile of the user to be cloned by the language model. Defaults to None. memory (Optional[bool | int]): Maximum number of messages in conversation memory. Defaults to None (or 0) for no memory. -1 or `True` means infinite memory. api_key (Optional[str]): The API key to use. Defaults to None. + system_prompts (Optional[list[str]]): Additional system prompts (instructions) for the language model. Defaults to None. **kwargs (Any): Additional keyword arguments supported by the `langchain_community.chat_models.ChatLiteLLM` class. Returns: @@ -170,6 +175,7 @@ def from_persist_directory( user_profile=user_profile, memory=memory, api_key=api_key, + system_prompts=system_prompts, **kwargs, ) @@ -181,6 +187,7 @@ def from_context( user_profile: Optional[UserProfile | dict[str, Any] | str] = None, memory: Optional[bool | int] = None, api_key: Optional[str] = None, + system_prompts: Optional[list[str]] = None, **kwargs: Any, ) -> Self: """Creates an instance of CloneLLM using a summarized context string instead of documents. @@ -191,6 +198,7 @@ def from_context( user_profile (Optional[UserProfile | dict[str, Any] | str]): The profile of the user to be cloned by the language model. Defaults to None. memory (Optional[bool | int]): Maximum number of messages in conversation memory. Defaults to None (or 0) for no memory. -1 or `True` means infinite memory. api_key (Optional[str]): The API key to use. Defaults to None. + system_prompts (Optional[list[str]]): Additional system prompts (instructions) for the language model. Defaults to None. **kwargs (Any): Additional keyword arguments supported by the `langchain_community.chat_models.ChatLiteLLM` class. Returns: @@ -203,6 +211,7 @@ def from_context( user_profile=user_profile, memory=memory, api_key=api_key, + system_prompts=system_prompts, **kwargs, ) @@ -339,7 +348,7 @@ def _get_retriever(self, k: int = 1) -> VectorStoreRetriever: return self.db.as_retriever(search_kwargs={"k": k}) def _get_rag_chain(self) -> RunnableSerializable[Any, str]: - prompt = context_prompt.copy() + prompt = get_context_prompt(self.system_prompts) if self.user_profile: prompt += user_profile_prompt.format_messages(user_profile=self._user_profile) prompt += question_prompt @@ -348,7 +357,7 @@ def _get_rag_chain(self) -> RunnableSerializable[Any, str]: return {"context": context, "input": RunnablePassthrough()} | prompt | self._llm | StrOutputParser() def _get_rag_chain_with_history(self) -> RunnableWithMessageHistory: - prompt = context_prompt + prompt = get_context_prompt(self.system_prompts) if self.user_profile: prompt += user_profile_prompt.format_messages(user_profile=self._user_profile) prompt += history_prompt From f26a4eceeacb111295b332bdd68df9030f858c6b Mon Sep 17 00:00:00 2001 From: Mehdi Samsami Date: Thu, 30 Jan 2025 17:07:28 +0330 Subject: [PATCH 2/7] update readme and examples to include the usage of additional system prompts --- README.md | 12 ++++++++++++ examples/advanced_clone.py | 1 + examples/advanced_clone_async.py | 1 + 3 files changed, 14 insertions(+) diff --git a/README.md b/README.md index bee15c7..ae3eb8e 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,18 @@ clone.clear_memory() # clone.reset_memory() ``` +### Additional system prompts +You can pass additional system prompts (instructions) to the clone to guide its behavior. +```python +from clonellm import CloneLLM + +clone = CloneLLM( + model="gpt-4o", + documents=documents, + system_prompts=["Keep your responses brief and concise, and always respond in first person."], +) +``` + ### Streaming CloneLLM supports streaming responses from the LLM, allowing for real-time processing of text as it is being generated, rather than receiving the whole output at once. ```python diff --git a/examples/advanced_clone.py b/examples/advanced_clone.py index 48f28a5..2a8d1df 100644 --- a/examples/advanced_clone.py +++ b/examples/advanced_clone.py @@ -64,6 +64,7 @@ def main() -> None: vector_store=RagVectorStore.Chroma, user_profile=profile, memory=MAX_MEMORY_SIZE, + system_prompts=["Keep your responses brief and concise, and always respond in first person."], request_timeout=5, temperature=0.3, max_tokens=256, diff --git a/examples/advanced_clone_async.py b/examples/advanced_clone_async.py index 26e1b00..dbc06da 100644 --- a/examples/advanced_clone_async.py +++ b/examples/advanced_clone_async.py @@ -65,6 +65,7 @@ async def main() -> None: vector_store=RagVectorStore.Chroma, user_profile=profile, memory=MAX_MEMORY_SIZE, + system_prompts=["Keep your responses brief and concise, and always respond in first person."], request_timeout=5, temperature=0.5, max_tokens=256, From 4b17283efffc882ed6ceb867b8b1d85a3fb3ce19 Mon Sep 17 00:00:00 2001 From: Mehdi Samsami Date: Thu, 30 Jan 2025 17:12:28 +0330 Subject: [PATCH 3/7] bump version -> v0.4.0 --- README.md | 2 +- pyproject.toml | 2 +- src/clonellm/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae3eb8e..c542c04 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

- Latest Release + Latest Release PyPI Version diff --git a/pyproject.toml b/pyproject.toml index bac235b..4ad782d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "clonellm" -version = "0.3.0" +version = "0.4.0" description = "Python package to create an AI clone of yourself using LLMs." packages = [{ from = "src", include = "clonellm" }] include = ["src/clonellm/py.typed"] diff --git a/src/clonellm/__init__.py b/src/clonellm/__init__.py index 33e75b0..37d044c 100644 --- a/src/clonellm/__init__.py +++ b/src/clonellm/__init__.py @@ -1,5 +1,5 @@ __author__ = "Mehdi Samsami" -__version__ = "0.3.0" +__version__ = "0.4.0" from .core import CloneLLM from .embed import LiteLLMEmbeddings From 4d65c95a4b6390b56a596c387ce8f218097d6d75 Mon Sep 17 00:00:00 2001 From: Mehdi Samsami Date: Thu, 30 Jan 2025 17:16:06 +0330 Subject: [PATCH 4/7] run tests on python 3.13 --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ed29567..99578e2 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} From 958123f804c8cffb69ba792f9eb91de04bc19d33 Mon Sep 17 00:00:00 2001 From: Mehdi Samsami Date: Thu, 30 Jan 2025 17:23:34 +0330 Subject: [PATCH 5/7] add support for python 3.13 --- README.md | 2 +- pyproject.toml | 2 +- setup.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c542c04..9470dfc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ PyPI Version - Python Versions + Python Versions PyPI License diff --git a/pyproject.toml b/pyproject.toml index 4ad782d..5e839d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ keywords = ["python", "ai", "llm", "rag"] repository = "https://github.com/msamsami/clonellm" [tool.poetry.dependencies] -python = ">=3.10,<3.13" +python = ">=3.10,<3.14" litellm = "^1.36.0" langchain = "^0.1.17" pydantic = {version = ">=2.8.0", python = ">=3.12.4"} diff --git a/setup.py b/setup.py index 4c3647e..aa9485f 100644 --- a/setup.py +++ b/setup.py @@ -36,9 +36,10 @@ def get_requirements(filename: str) -> list[str]: "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", ], - python_requires=">=3.10,<3.13", + python_requires=">=3.10,<3.14", install_requires=get_requirements("requirements.txt"), extras_require={"chroma": ["langchain-chroma"], "faiss": ["faiss-cpu"]}, ) From 6059e8730850ef1b6c81ca06a2f7826d10f89184 Mon Sep 17 00:00:00 2001 From: Mehdi Samsami Date: Thu, 30 Jan 2025 17:27:50 +0330 Subject: [PATCH 6/7] update poetry lock file --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index c4a7d4a..0e55e34 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1098,13 +1098,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "huggingface-hub" -version = "0.28.0" +version = "0.28.1" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.28.0-py3-none-any.whl", hash = "sha256:71cff4e500efe68061d94b7f6d3114e183715088be7a90bf4dd84af83b5f5cdb"}, - {file = "huggingface_hub-0.28.0.tar.gz", hash = "sha256:c2b18c02a47d4384763caddb4d0ab2a8fc6c16e0800d6de4d55d0a896244aba3"}, + {file = "huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7"}, + {file = "huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae"}, ] [package.dependencies] @@ -4037,5 +4037,5 @@ faiss = ["faiss-cpu"] [metadata] lock-version = "2.0" -python-versions = ">=3.10,<3.13" -content-hash = "c7c2386e623f01441004e21a887e19555def34b51f71c97cf89cd4b658871554" +python-versions = ">=3.10,<3.14" +content-hash = "a6e6b8176a0a6ceb97af7f55ab7d5949507f8a2cb17fad7db8cf73a13b9e2574" From e31d272223cb229476ad630f7739ce7c6e37cda8 Mon Sep 17 00:00:00 2001 From: Mehdi Samsami Date: Thu, 30 Jan 2025 17:42:23 +0330 Subject: [PATCH 7/7] install legacy-cgi for python 3.13 and later (deprecated in PEP-594) --- poetry.lock | 19 +++++++++++++++---- pyproject.toml | 1 + requirements.txt | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0e55e34..45f86db 100644 --- a/poetry.lock +++ b/poetry.lock @@ -605,13 +605,13 @@ packaging = "*" [[package]] name = "fastapi" -version = "0.115.7" +version = "0.115.8" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = true python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.7-py3-none-any.whl", hash = "sha256:eb6a8c8bf7f26009e8147111ff15b5177a0e19bb4a45bc3486ab14804539d21e"}, - {file = "fastapi-0.115.7.tar.gz", hash = "sha256:0f106da6c01d88a6786b3248fb4d7a940d071f6f488488898ad5d354b25ed015"}, + {file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"}, + {file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"}, ] [package.dependencies] @@ -1578,6 +1578,17 @@ requests-toolbelt = ">=1.0.0,<2.0.0" [package.extras] langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] +[[package]] +name = "legacy-cgi" +version = "2.6.2" +description = "Fork of the standard library cgi and cgitb modules, being deprecated in PEP-594" +optional = false +python-versions = ">=3.10" +files = [ + {file = "legacy_cgi-2.6.2-py3-none-any.whl", hash = "sha256:a7b83afb1baf6ebeb56522537c5943ef9813cf933f6715e88a803f7edbce0bff"}, + {file = "legacy_cgi-2.6.2.tar.gz", hash = "sha256:9952471ceb304043b104c22d00b4f333cac27a6abe446d8a528fc437cf13c85f"}, +] + [[package]] name = "litellm" version = "1.59.9" @@ -4038,4 +4049,4 @@ faiss = ["faiss-cpu"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.14" -content-hash = "a6e6b8176a0a6ceb97af7f55ab7d5949507f8a2cb17fad7db8cf73a13b9e2574" +content-hash = "035376ea783c72423f20b56dcdf1c4c1edaf35c96b36e32140b50fc478d8437f" diff --git a/pyproject.toml b/pyproject.toml index 5e839d0..55c1701 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ python = ">=3.10,<3.14" litellm = "^1.36.0" langchain = "^0.1.17" pydantic = {version = ">=2.8.0", python = ">=3.12.4"} +legacy-cgi = {version = ">=2.6.2", python = ">=3.13"} langchain-chroma = {version = "*", optional = true} faiss-cpu = {version = "*", optional = true} diff --git a/requirements.txt b/requirements.txt index 30a6117..4890ba8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ litellm>=1.36.0,<2.0.0 langchain>=0.1.17,<1.0.0 pydantic>=2.8.0; python_version >= '3.12.4' +legacy-cgi>=2.6.2; python_version >= '3.13'