From 23389c3a6217b70d1902f2be4ed27e525096ed98 Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Mon, 28 Oct 2024 21:22:15 -0600 Subject: [PATCH 01/22] Add back Ollama integration --- .envrc.template | 1 + .gitignore | 1 + Dockerfile | 2 +- README.md | 6 +++ docker-compose.yaml | 14 ++++++- pyproject.toml | 2 +- simplemind/providers/__init__.py | 3 +- simplemind/providers/ollama.py | 63 ++++++++++++++++++++++++++++++++ simplemind/settings.py | 1 + test_ollama.py | 58 +++++++++++++++++++++++++++++ 10 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 simplemind/providers/ollama.py create mode 100644 test_ollama.py diff --git a/.envrc.template b/.envrc.template index 8438939..07f7d8f 100644 --- a/.envrc.template +++ b/.envrc.template @@ -1,3 +1,4 @@ export OPENAI_API_KEY="" export ANTHROPIC_API_KEY="" export XAI_API_KEY="" +export OLLAMA_HOST_URL="" diff --git a/.gitignore b/.gitignore index b66e3ef..e43c919 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,4 @@ cython_debug/ .env src/** +requirements.txt diff --git a/Dockerfile b/Dockerfile index 5d0954e..7599519 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,4 @@ WORKDIR /src RUN pip install -r requirements.txt -ENTRYPOINT ["python", "build.py"] \ No newline at end of file +ENTRYPOINT ["python", "main.py"] \ No newline at end of file diff --git a/README.md b/README.md index 95d75d9..adebc02 100644 --- a/README.md +++ b/README.md @@ -159,10 +159,16 @@ To get started: 4. Submit a pull request. ## Building + 1. Clone the repository. 2. `cd` to the root directory. +3. Generate the requirements.txt file `python -m piptools compile pyproject.toml` 3. Run `docker-compose up --build` +Two containers will run in sequence: +1) `simplemind` - Builds and runs the tests +2) `simplemind-test` - Runs the tests again, if the 1st container suceeds. + ## License SimpleMind is licensed under the Apache 2.0 License. diff --git a/docker-compose.yaml b/docker-compose.yaml index d756f99..85d363c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,10 +1,22 @@ services: simplemind: + depends_on: + simplemind-test: + condition: service_completed_successfully build: context: . dockerfile: Dockerfile volumes: - ./simplemind:/src/simplemind - - ./build.py:/src/build.py + - ./test_ollama.py:/src/main.py + env_file: + - .env + simplemind-test: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./simplemind:/src/simplemind + - ./test_ollama.py:/src/main.py env_file: - .env \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 1c4cebc..e71e648 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.1.0" description = "An experimental client for AI providers that intends to replace LangChain and LangGraph for most common use cases." readme = "README.md" requires-python = ">=3.11" -dependencies = ["pydantic", "pydantic-settings", "instructor", "openai", "anthropic"] +dependencies = ["pydantic", "pydantic-settings", "instructor", "openai", "anthropic", "ollama"] [build-system] requires = ["hatchling"] diff --git a/simplemind/providers/__init__.py b/simplemind/providers/__init__.py index 87787ea..c3112c6 100644 --- a/simplemind/providers/__init__.py +++ b/simplemind/providers/__init__.py @@ -1,5 +1,6 @@ from .openai import OpenAI from .anthropic import Anthropic from .xai import XAI +from .ollama import Ollama -providers = [OpenAI, Anthropic, XAI] +providers = [OpenAI, Anthropic, XAI, Ollama] diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py new file mode 100644 index 0000000..de1bfdc --- /dev/null +++ b/simplemind/providers/ollama.py @@ -0,0 +1,63 @@ +import ollama as ol + +from ..settings import settings + +PROVIDER_NAME = "ollama" +DEFAULT_MODEL = "llama3.2" +TIMEOUT = 60 +NOT_IMPLEMENTED_REASON = """ +# TODO: instructor does not natively support ollama. +# Alternate python dependency may be required +""" +class Ollama: + __name__ = PROVIDER_NAME + DEFAULT_MODEL = DEFAULT_MODEL + + def __init__(self, host_url: str = None): + self.host_url = host_url or settings.OLLAMA_HOST_URL + + @property + def client(self): + """The raw Ollama client.""" + return ol.Client( + timeout=TIMEOUT, + host=self.host_url) + + @property + def structured_client(self): + """A client patched with Instructor.""" + raise NotImplementedError(NOT_IMPLEMENTED_REASON) + + def send_conversation(self, conversation: "Conversation"): + """Send a conversation to the Ollama API.""" + from ..models import Message + messages = [ + {"role": msg.role, "content": msg.text} for msg in conversation.messages + ] + response = self.client.chat( + model=conversation.llm_model or DEFAULT_MODEL, messages=messages + ) + assistant_message = response.get("message") + + # Create and return a properly formatted Message instance + return Message( + role="assistant", + text=assistant_message.get("content"), + raw=response, + llm_model=conversation.llm_model or DEFAULT_MODEL, + llm_provider=PROVIDER_NAME, + ) + + def structured_response(self, *args, **kwargs): + raise NotImplementedError(NOT_IMPLEMENTED_REASON) + + def generate_text(self, prompt, *, llm_model): + messages = [ + {"role": "user", "content": prompt}, + ] + + response = self.client.chat( + messages=messages, model=llm_model + ) + + return response.get("message").get("content") diff --git a/simplemind/settings.py b/simplemind/settings.py index 7828508..8b8e6f3 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -6,6 +6,7 @@ class Settings(BaseSettings): OPENAI_API_KEY: str = Field(..., env="OPENAI_API_KEY") ANTHROPIC_API_KEY: str = Field(..., env="ANTHROPIC_API_KEY") XAI_API_KEY: str = Field(..., env="XAI_API_KEY") + OLLAMA_HOST_URL: str = Field(..., env="OLLAMA_HOST_URL") settings = Settings() diff --git a/test_ollama.py b/test_ollama.py new file mode 100644 index 0000000..4d40d38 --- /dev/null +++ b/test_ollama.py @@ -0,0 +1,58 @@ +import unittest +import simplemind as sm +from pydantic import BaseModel + +class TestOllama(unittest.TestCase): + + def test_generate_text(self): + result = sm.generate_text(prompt="What is the meaning of life?", llm_provider="ollama", llm_model="llama3.2") + self.assertIsNotNone(result) + + def test_create_conversation(self): + conversation = sm.create_conversation(llm_provider="ollama", llm_model="llama3.2") + conversation.add_message("user", "Remember the number 42.") + result = conversation.send() + self.assertIsNotNone(result) + self.assertIsInstance(result, sm.models.Message) + + def test_memory(self): + conversation = sm.create_conversation(llm_provider="ollama", llm_model="llama3.2") + class SimpleMemoryPlugin: + def __init__(self): + self.memories = [ + "the earth has fictionally beeen destroyed.", + "the moon is made of cheese.", + ] + + def yield_memories(self): + return (m for m in self.memories) + + def send_hook(self, conversation: sm.Conversation): + for m in self.yield_memories(): + conversation.add_message(role="system", text=m) + + conversation.add_plugin(SimpleMemoryPlugin()) + conversation.add_message( + role="user", + text="Write a poem about the moon", + ) + result = conversation.send() + self.assertIsNotNone(result) + self.assertIsInstance(result, sm.models.Message) + + def test_structure_response(self): + class Poem(BaseModel): + title: str + content: str + with self.assertRaises(NotImplementedError): + data_obj = sm.generate_data( + prompt="Write a poem about love", + llm_provider="ollama", + llm_model="llama3.2", + response_model=Poem) + self.assertIsNotNone(data_obj) + self.assertIsInstance(data_obj, Poem) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From cb6d5540cbd46355b19db9d0ef84ebee7bebe4fb Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Mon, 28 Oct 2024 21:22:59 -0600 Subject: [PATCH 02/22] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index adebc02..72d7d3a 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,6 @@ To get started: 4. Submit a pull request. ## Building - 1. Clone the repository. 2. `cd` to the root directory. 3. Generate the requirements.txt file `python -m piptools compile pyproject.toml` From fb2460f9074874d1b42f963336346ede42296df3 Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Mon, 28 Oct 2024 21:45:25 -0600 Subject: [PATCH 03/22] prepend system memories for plugin --- simplemind/models.py | 3 +++ test_ollama.py | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/simplemind/models.py b/simplemind/models.py index 4a85c99..dcc7be1 100644 --- a/simplemind/models.py +++ b/simplemind/models.py @@ -46,6 +46,9 @@ class Conversation(SMBaseModel): def __str__(self): return f"" + def prepend_system_message(self, role: str, text: str, meta: Dict[str, Any] = {}): + self.messages = [Message(role=role, text=text, meta=meta)] + self.messages + def add_message(self, role: str, text: str, meta: Dict[str, Any] = {}): """Add a new message to the conversation.""" self.messages.append(Message(role=role, text=text, meta=meta)) diff --git a/test_ollama.py b/test_ollama.py index 4d40d38..deb4fbd 100644 --- a/test_ollama.py +++ b/test_ollama.py @@ -6,6 +6,7 @@ class TestOllama(unittest.TestCase): def test_generate_text(self): result = sm.generate_text(prompt="What is the meaning of life?", llm_provider="ollama", llm_model="llama3.2") + self.assertGreater(len(result), 0) self.assertIsNotNone(result) def test_create_conversation(self): @@ -13,14 +14,15 @@ class TestOllama(unittest.TestCase): conversation.add_message("user", "Remember the number 42.") result = conversation.send() self.assertIsNotNone(result) + self.assertGreaterEqual(len(result.text), 0) self.assertIsInstance(result, sm.models.Message) + # @unittest.skip('Not working...') def test_memory(self): - conversation = sm.create_conversation(llm_provider="ollama", llm_model="llama3.2") class SimpleMemoryPlugin: def __init__(self): self.memories = [ - "the earth has fictionally beeen destroyed.", + "the earth has fictionally been destroyed.", "the moon is made of cheese.", ] @@ -29,15 +31,21 @@ class TestOllama(unittest.TestCase): def send_hook(self, conversation: sm.Conversation): for m in self.yield_memories(): - conversation.add_message(role="system", text=m) + conversation.prepend_system_message(role="system", text=m) - conversation.add_plugin(SimpleMemoryPlugin()) + conversation = sm.create_conversation(llm_provider="ollama", llm_model="llama3.2") + conversation.add_message( role="user", text="Write a poem about the moon", ) + self.assertGreater(len(conversation.messages), 0) + conversation.add_plugin(SimpleMemoryPlugin()) result = conversation.send() + self.assertGreater(len(conversation.messages), 2) self.assertIsNotNone(result) + self.assertIsNotNone(result.text) + self.assertGreater(len(result.text), 0) self.assertIsInstance(result, sm.models.Message) def test_structure_response(self): From 4c8cb49a582b8b5c68b7d02aac236a2d5585b66d Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Mon, 28 Oct 2024 21:46:52 -0600 Subject: [PATCH 04/22] Update test_ollama.py --- test_ollama.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test_ollama.py b/test_ollama.py index deb4fbd..76a7cc2 100644 --- a/test_ollama.py +++ b/test_ollama.py @@ -17,7 +17,6 @@ class TestOllama(unittest.TestCase): self.assertGreaterEqual(len(result.text), 0) self.assertIsInstance(result, sm.models.Message) - # @unittest.skip('Not working...') def test_memory(self): class SimpleMemoryPlugin: def __init__(self): From 83d430a310b04bc0a2ea49612fce0de70558d63a Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Mon, 28 Oct 2024 21:59:36 -0600 Subject: [PATCH 05/22] Update test_ollama.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- test_ollama.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/test_ollama.py b/test_ollama.py index 76a7cc2..f4b721c 100644 --- a/test_ollama.py +++ b/test_ollama.py @@ -48,17 +48,21 @@ class TestOllama(unittest.TestCase): self.assertIsInstance(result, sm.models.Message) def test_structure_response(self): - class Poem(BaseModel): - title: str - content: str - with self.assertRaises(NotImplementedError): - data_obj = sm.generate_data( - prompt="Write a poem about love", - llm_provider="ollama", - llm_model="llama3.2", - response_model=Poem) - self.assertIsNotNone(data_obj) - self.assertIsInstance(data_obj, Poem) + class Poem(BaseModel): + title: str + content: str + # Test for NotImplementedError + with self.assertRaises(NotImplementedError): + sm.generate_data( + prompt="Write a poem about love", + llm_provider="ollama", + llm_model="llama3.2", + response_model=Poem) + + # If implementation is added later, uncomment and modify: + # data_obj = sm.generate_data(...) + # self.assertIsNotNone(data_obj) + # self.assertIsInstance(data_obj, Poem) if __name__ == '__main__': From 87d636ca55c888babf850ea0b1fd7f89dbae8c71 Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Mon, 28 Oct 2024 22:00:29 -0600 Subject: [PATCH 06/22] Update README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 72d7d3a..97a04f5 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,8 @@ To get started: 3. Run `docker-compose up --build` Two containers will run in sequence: -1) `simplemind` - Builds and runs the tests -2) `simplemind-test` - Runs the tests again, if the 1st container suceeds. +1) `simplemind-test` - Builds and runs the tests +2) `simplemind` - Runs if the test container succeeds ## License SimpleMind is licensed under the Apache 2.0 License. From dcb9c14d30c01e50ce2a6bd249c0381e5e564388 Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Mon, 28 Oct 2024 22:05:55 -0600 Subject: [PATCH 07/22] Update simplemind/models.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- simplemind/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/simplemind/models.py b/simplemind/models.py index 47c57e8..9019de1 100644 --- a/simplemind/models.py +++ b/simplemind/models.py @@ -79,9 +79,8 @@ class Conversation(SMBaseModel): def __str__(self): return f"" - def prepend_system_message(self, role: str, text: str, meta: Dict[str, Any] = {}): - self.messages = [Message(role=role, text=text, meta=meta)] + self.messages - + def prepend_system_message(self, role: str, text: str, meta: Optional[Dict[str, Any]] = None): + self.messages = [Message(role=role, text=text, meta=meta or {})] + self.messages def add_message(self, role: str, text: str, meta: Dict[str, Any] = {}): """Add a new message to the conversation.""" self.messages.append(Message(role=role, text=text, meta=meta)) From d0a76d753234e84d48478b2df7a48b00732d1563 Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Tue, 29 Oct 2024 08:29:26 -0600 Subject: [PATCH 08/22] Update ollama.py --- simplemind/providers/ollama.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index de1bfdc..2373e06 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -10,7 +10,7 @@ NOT_IMPLEMENTED_REASON = """ # Alternate python dependency may be required """ class Ollama: - __name__ = PROVIDER_NAME + NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL def __init__(self, host_url: str = None): @@ -19,6 +19,8 @@ class Ollama: @property def client(self): """The raw Ollama client.""" + if not self.host_url: + raise ValueError("No ollama host url provided") return ol.Client( timeout=TIMEOUT, host=self.host_url) From 1ce27595642610597ffa516c491f9c42e23cfaaa Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Tue, 29 Oct 2024 08:29:29 -0600 Subject: [PATCH 09/22] Update settings.py --- simplemind/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplemind/settings.py b/simplemind/settings.py index 18544c7..2545f93 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -12,7 +12,7 @@ class Settings(BaseSettings): ) GROQ_API_KEY: Optional[SecretStr] = Field(None, description="API key for Groq") OPENAI_API_KEY: Optional[SecretStr] = Field(None, description="API key for OpenAI") - OLLAMA_HOST_URL: Optional[SecretStr] = Field(None, description="Fully qualified host URL for Ollama") + OLLAMA_HOST_URL: Optional[str] = Field(None, description="Fully qualified host URL for Ollama") XAI_API_KEY: Optional[SecretStr] = Field(None, description="API key for xAI") DEFAULT_LLM_PROVIDER: str = Field("openai", description="The default LLM provider") From abdac66fee36c316b21ddabe104d02f880c5d737 Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Tue, 29 Oct 2024 08:42:29 -0600 Subject: [PATCH 10/22] Update test_ollama.py --- test_ollama.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test_ollama.py b/test_ollama.py index f4b721c..0a5b560 100644 --- a/test_ollama.py +++ b/test_ollama.py @@ -1,4 +1,6 @@ +import os import unittest +from unittest import mock import simplemind as sm from pydantic import BaseModel @@ -58,11 +60,6 @@ class TestOllama(unittest.TestCase): llm_provider="ollama", llm_model="llama3.2", response_model=Poem) - - # If implementation is added later, uncomment and modify: - # data_obj = sm.generate_data(...) - # self.assertIsNotNone(data_obj) - # self.assertIsInstance(data_obj, Poem) if __name__ == '__main__': From 8ed065836a19dd65340a890a14dfbb8fa154ab84 Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Tue, 29 Oct 2024 08:44:19 -0600 Subject: [PATCH 11/22] Update ollama.py --- simplemind/providers/ollama.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index 2373e06..e4b45fd 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -1,5 +1,6 @@ import ollama as ol +from ._base import BaseProvider from ..settings import settings PROVIDER_NAME = "ollama" @@ -9,7 +10,8 @@ NOT_IMPLEMENTED_REASON = """ # TODO: instructor does not natively support ollama. # Alternate python dependency may be required """ -class Ollama: + +class Ollama(BaseProvider): NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL From b04c68f57d809c40b1334a2f354d7f773617639b Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Tue, 29 Oct 2024 09:37:48 -0600 Subject: [PATCH 12/22] Remove docker components --- Dockerfile | 12 ------------ docker-compose.yaml | 22 ---------------------- 2 files changed, 34 deletions(-) delete mode 100644 Dockerfile delete mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 7599519..0000000 --- a/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM python:3.12.0 - -RUN apt-get update -y && apt-get upgrade -y -RUN pip install --upgrade pip - -COPY requirements.txt /src/requirements.txt - -WORKDIR /src - -RUN pip install -r requirements.txt - -ENTRYPOINT ["python", "main.py"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 85d363c..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,22 +0,0 @@ -services: - simplemind: - depends_on: - simplemind-test: - condition: service_completed_successfully - build: - context: . - dockerfile: Dockerfile - volumes: - - ./simplemind:/src/simplemind - - ./test_ollama.py:/src/main.py - env_file: - - .env - simplemind-test: - build: - context: . - dockerfile: Dockerfile - volumes: - - ./simplemind:/src/simplemind - - ./test_ollama.py:/src/main.py - env_file: - - .env \ No newline at end of file From d9f0d21e53ad0c760f30e849231ad8064274d58e Mon Sep 17 00:00:00 2001 From: Kurt Heiden Date: Tue, 29 Oct 2024 13:48:52 -0600 Subject: [PATCH 13/22] Update README.md --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 1e14ca4..331683f 100644 --- a/README.md +++ b/README.md @@ -181,16 +181,6 @@ To get started: 3. Make your changes. 4. Submit a pull request. -## Building -1. Clone the repository. -2. `cd` to the root directory. -3. Generate the requirements.txt file `python -m piptools compile pyproject.toml` -3. Run `docker-compose up --build` - -Two containers will run in sequence: -1) `simplemind-test` - Builds and runs the tests -2) `simplemind` - Runs if the test container succeeds - ## License SimpleMind is licensed under the Apache 2.0 License. From 3e2801a1ac808b0320f46e10749636c60c04b328 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 07:54:59 -0400 Subject: [PATCH 14/22] Refactor Groq provider to use the correct client method --- simplemind/providers/groq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplemind/providers/groq.py b/simplemind/providers/groq.py index ee1175d..3e0dbb4 100644 --- a/simplemind/providers/groq.py +++ b/simplemind/providers/groq.py @@ -83,7 +83,7 @@ class Groq(BaseProvider): {"role": "user", "content": prompt}, ] - response = self.structured_client.chat.completions.create( + response = self.client.chat.completions.create( messages=messages, model=llm_model, **kwargs, From 3090ade9e31710a2a06362c1f96b2b7be99bb15e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 07:55:28 -0400 Subject: [PATCH 15/22] Fix Groq provider in CHANGELOG.md and update version in pyproject.toml --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccff72f..cf8d8fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Release History =============== +## 0.1.1 (2024-10-29) + +- Fix Groq provider. + ## 0.1.0 (2024-10-29) - Initial release. diff --git a/pyproject.toml b/pyproject.toml index dd07ea8..03250bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "simplemind" -version = "0.1.0" +version = "0.1.1" description = "An experimental client for AI providers that intends to replace LangChain and LangGraph for most common use cases." readme = "README.md" requires-python = ">=3.11" From 0100ad0163e6c2e612a9c9f2514cf6c89c11ca0a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 12:18:51 -0400 Subject: [PATCH 16/22] Refactor translate_to_french function to use the correct conversation setup --- examples/translate_text.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/translate_text.py b/examples/translate_text.py index e7d1d3e..b9e38d2 100644 --- a/examples/translate_text.py +++ b/examples/translate_text.py @@ -1,9 +1,13 @@ from _context import sm -conversation = sm.create_conversation(llm_model="gpt-4o", llm_provider="openai") -conversation.add_message( - "user", "Translate the following text to French: 'Hello, world!'" -) +def translate_to_french(text: str) -> str: + conversation = sm.create_conversation(llm_model="gpt-4o", llm_provider="openai") -print(conversation.send().text) + conversation.add_message( + "user", f"Translate the following text to French: {text!r}" + ) + return conversation.send().text + + +print(translate_to_french("Hello, world!")) From c1115ccf47948af128ef547f6932b5ddaa40901f Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 12:19:29 -0400 Subject: [PATCH 17/22] Refactor translate_to_french function to use the correct conversation setup --- examples/translate_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/translate_text.py b/examples/translate_text.py index b9e38d2..ff07c2a 100644 --- a/examples/translate_text.py +++ b/examples/translate_text.py @@ -10,4 +10,4 @@ def translate_to_french(text: str) -> str: return conversation.send().text -print(translate_to_french("Hello, world!")) +print(translate_to_french("an omlette with cheese")) From 0fa4b60412ecafec373d8a1be96bea01ecfc8db5 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 12:35:37 -0400 Subject: [PATCH 18/22] ask nicely --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 331683f..a6f6e62 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ conversation.add_plugin(SimpleMemoryPlugin()) conversation.add_message( role="user", - text="Write a poem about the moon", + text="Please write a poem about the moon", ) ``` ```pycon From dd2b08b4cf78dbefa4fe15b30402e90084fb93cc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 12:36:13 -0400 Subject: [PATCH 19/22] Refactor generate_data.py to use correct conversation setup and formatting --- examples/generate_data.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/generate_data.py b/examples/generate_data.py index fb15882..6cfbd3f 100644 --- a/examples/generate_data.py +++ b/examples/generate_data.py @@ -9,21 +9,32 @@ class Movie(BaseModel): title: str year: int + class MovieCharecter(BaseModel): name: str actor: str + class MovieQuote(BaseModel): quote: str movie: Movie charecter: MovieCharecter + class QuotesList(BaseModel): quotes: List[MovieQuote] theme: str -quotes = sm.generate_data(llm_provider="openai", llm_model="gpt-4o-mini", prompt="Generate 20 quotes from famous movies", response_model=QuotesList) +quotes = sm.generate_data( + llm_provider="openai", + llm_model="gpt-4o-mini", + prompt="Generate 20 quotes from famous movies", + response_model=QuotesList, +) -for quote in quotes.quotes: - print(f"{quote.charecter.name} from {quote.movie.title} ({quote.movie.year}): {quote.quote!r}") +if __name__ == "__main__": + for quote in quotes.quotes: + print( + f"{quote.charecter.name} from {quote.movie.title} ({quote.movie.year}): {quote.quote!r}" + ) From 593d6c8e076d85d62f1aa17ede2d1dbe358e8c09 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 12:37:26 -0400 Subject: [PATCH 20/22] proper manners --- examples/simple_memory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_memory.py b/examples/simple_memory.py index fbb79c1..12efd10 100644 --- a/examples/simple_memory.py +++ b/examples/simple_memory.py @@ -21,7 +21,7 @@ conversation.add_plugin(SimpleMemoryPlugin()) conversation.add_message( role="user", - text="Write a poem about the moon", + text="Please write a poem about the moon", ) r = conversation.send() From 4f3fcac02df473bebd3d8be748c687963c2f873d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 14:29:46 -0400 Subject: [PATCH 21/22] Refactor generate_data.py to use correct conversation setup and formatting --- examples/generate_data.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/examples/generate_data.py b/examples/generate_data.py index 6cfbd3f..888d694 100644 --- a/examples/generate_data.py +++ b/examples/generate_data.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Iterator from pydantic import BaseModel @@ -23,18 +23,22 @@ class MovieQuote(BaseModel): class QuotesList(BaseModel): quotes: List[MovieQuote] - theme: str -quotes = sm.generate_data( - llm_provider="openai", - llm_model="gpt-4o-mini", - prompt="Generate 20 quotes from famous movies", - response_model=QuotesList, -) +def gen_quotes(n=10) -> Iterator[MovieQuote]: + """Generate a list of quotes from famous movies.""" + + for q in sm.generate_data( + llm_provider="openai", + llm_model="gpt-4o-mini", + prompt=f"Generate {n} quotes from famous movies", + response_model=QuotesList, + ).quotes: + yield q + if __name__ == "__main__": - for quote in quotes.quotes: + for quote in gen_quotes(n=20): print( f"{quote.charecter.name} from {quote.movie.title} ({quote.movie.year}): {quote.quote!r}" ) From 3dccac85ff50d2e41c2e5b4009635560b3c10b4a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 29 Oct 2024 16:18:02 -0400 Subject: [PATCH 22/22] Refactor Ollama provider to use default timeout and add support for structured responses --- simplemind/providers/ollama.py | 38 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index e4b45fd..ce96503 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -1,19 +1,19 @@ import ollama as ol +import instructor +from openai import OpenAI from ._base import BaseProvider from ..settings import settings PROVIDER_NAME = "ollama" DEFAULT_MODEL = "llama3.2" -TIMEOUT = 60 -NOT_IMPLEMENTED_REASON = """ -# TODO: instructor does not natively support ollama. -# Alternate python dependency may be required -""" +DEFAULT_TIMEOUT = 60 + class Ollama(BaseProvider): NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL + TIMEOUT = DEFAULT_TIMEOUT def __init__(self, host_url: str = None): self.host_url = host_url or settings.OLLAMA_HOST_URL @@ -23,18 +23,23 @@ class Ollama(BaseProvider): """The raw Ollama client.""" if not self.host_url: raise ValueError("No ollama host url provided") - return ol.Client( - timeout=TIMEOUT, - host=self.host_url) + return ol.Client(timeout=self.TIMEOUT, host=self.host_url) @property def structured_client(self): """A client patched with Instructor.""" - raise NotImplementedError(NOT_IMPLEMENTED_REASON) + return instructor.from_openai( + OpenAI( + base_url=f"{self.host_url}/v1", + api_key="ollama", + ), + mode=instructor.Mode.JSON, + ) def send_conversation(self, conversation: "Conversation"): """Send a conversation to the Ollama API.""" from ..models import Message + messages = [ {"role": msg.role, "content": msg.text} for msg in conversation.messages ] @@ -52,16 +57,21 @@ class Ollama(BaseProvider): llm_provider=PROVIDER_NAME, ) - def structured_response(self, *args, **kwargs): - raise NotImplementedError(NOT_IMPLEMENTED_REASON) + def structured_response(self, prompt, response_model, *, llm_model: str, **kwargs): + messages = [ + {"role": "user", "content": prompt}, + ] + + response = self.structured_client.chat.completions.create( + messages=messages, model=llm_model, response_model=response_model, **kwargs + ) + return response def generate_text(self, prompt, *, llm_model): messages = [ {"role": "user", "content": prompt}, ] - response = self.client.chat( - messages=messages, model=llm_model - ) + response = self.client.chat(messages=messages, model=llm_model) return response.get("message").get("content")