From cbc3739411331233f9b8bb702ab8008a44750645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20=C3=96zmen?= Date: Fri, 1 Nov 2024 00:14:41 +0300 Subject: [PATCH 01/26] added default_kwargs logic to Groq provider --- simplemind/providers/groq.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/simplemind/providers/groq.py b/simplemind/providers/groq.py index 42d8090..5a033be 100644 --- a/simplemind/providers/groq.py +++ b/simplemind/providers/groq.py @@ -10,6 +10,8 @@ from ._base import BaseProvider PROVIDER_NAME = "groq" DEFAULT_MODEL = "llama3-8b-8192" +DEFAULT_MAX_TOKENS = 1_000 +DEFAULT_KWARGS = {"max_tokens": DEFAULT_MAX_TOKENS} T = TypeVar("T", bound=BaseModel) @@ -17,6 +19,7 @@ T = TypeVar("T", bound=BaseModel) class Groq(BaseProvider): NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL + DEFAULT_KWARGS = DEFAULT_KWARGS def __init__(self, api_key: str | None = None): self.api_key = api_key or settings.get_api_key(PROVIDER_NAME) @@ -48,7 +51,7 @@ class Groq(BaseProvider): response = self.client.chat.completions.create( model=conversation.llm_model or self.DEFAULT_MODEL, messages=messages, - **kwargs, + **{**self.DEFAULT_KWARGS, **kwargs}, ) # Get the response content from the Groq response @@ -73,7 +76,7 @@ class Groq(BaseProvider): messages=messages, response_model=response_model, model=kwargs.pop("llm_model", self.DEFAULT_MODEL), - **kwargs, + **{**self.DEFAULT_KWARGS, **kwargs}, ) return response @@ -91,7 +94,7 @@ class Groq(BaseProvider): response = self.client.chat.completions.create( messages=messages, model=llm_model or self.DEFAULT_MODEL, - **kwargs, + **{**self.DEFAULT_KWARGS, **kwargs}, ) return response.choices[0].message.content From 37a9333be30eaba1ef066b6ce55c9e9596e0e08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20=C3=96zmen?= Date: Fri, 1 Nov 2024 00:15:49 +0300 Subject: [PATCH 02/26] added default_kwargs logic to OpenAI provider --- simplemind/providers/openai.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/simplemind/providers/openai.py b/simplemind/providers/openai.py index b6fa568..a756835 100644 --- a/simplemind/providers/openai.py +++ b/simplemind/providers/openai.py @@ -12,11 +12,14 @@ T = TypeVar("T", bound=BaseModel) PROVIDER_NAME = "openai" DEFAULT_MODEL = "gpt-4o-mini" +DEFAULT_MAX_TOKENS = 1_000 +DEFAULT_KWARGS = {"max_tokens": DEFAULT_MAX_TOKENS} class OpenAI(BaseProvider): NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL + DEFAULT_KWARGS = DEFAULT_KWARGS def __init__(self, api_key: str | None = None): self.api_key = api_key or settings.get_api_key(PROVIDER_NAME) @@ -42,7 +45,9 @@ class OpenAI(BaseProvider): ] response = self.client.chat.completions.create( - model=conversation.llm_model or DEFAULT_MODEL, messages=messages, **kwargs + model=conversation.llm_model or DEFAULT_MODEL, + messages=messages, + **{**self.DEFAULT_KWARGS, **kwargs} ) # Get the response content from the OpenAI response @@ -74,7 +79,7 @@ class OpenAI(BaseProvider): messages=messages, model=llm_model or self.DEFAULT_MODEL, response_model=response_model, - **kwargs, + **{**self.DEFAULT_KWARGS, **kwargs} ) return response @@ -84,6 +89,8 @@ class OpenAI(BaseProvider): {"role": "user", "content": prompt}, ] response = self.client.chat.completions.create( - messages=messages, model=llm_model or self.DEFAULT_MODEL, **kwargs + messages=messages, + model=llm_model or self.DEFAULT_MODEL, + **{**self.DEFAULT_KWARGS, **kwargs} ) return response.choices[0].message.content From e648292cb3b8bdc7f758afe32db2062659332335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20=C3=96zmen?= Date: Fri, 1 Nov 2024 00:17:22 +0300 Subject: [PATCH 03/26] added default_kwargs logic to Ollama provider --- simplemind/providers/ollama.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index 12f241c..6eceaa3 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -15,11 +15,17 @@ T = TypeVar("T", bound=BaseModel) PROVIDER_NAME = "ollama" DEFAULT_MODEL = "llama3.2" DEFAULT_TIMEOUT = 60 +DEFAULT_MAX_TOKENS = 1_000 +DEFAULT_KWARGS = { + "max_tokens": DEFAULT_MAX_TOKENS, + "timeout": DEFAULT_TIMEOUT, +} class Ollama(BaseProvider): NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL + DEFAULT_KWARGS = DEFAULT_KWARGS TIMEOUT = DEFAULT_TIMEOUT def __init__(self, host_url: str | None = None): @@ -43,7 +49,7 @@ class Ollama(BaseProvider): mode=instructor.Mode.JSON, ) - def send_conversation(self, conversation: "Conversation") -> "Message": + def send_conversation(self, conversation: "Conversation", **kwargs) -> "Message": """Send a conversation to the Ollama API.""" from ..models import Message @@ -51,7 +57,9 @@ class Ollama(BaseProvider): {"role": msg.role, "content": msg.text} for msg in conversation.messages ] response = self.client.chat( - model=conversation.llm_model or DEFAULT_MODEL, messages=messages + model=conversation.llm_model or DEFAULT_MODEL, + messages=messages, + **{**self.DEFAULT_KWARGS, **kwargs} ) assistant_message = response.get("message") @@ -81,18 +89,20 @@ class Ollama(BaseProvider): messages=messages, model=llm_model or self.DEFAULT_MODEL, response_model=response_model, - **kwargs, + **{**self.DEFAULT_KWARGS, **kwargs} ) return response - def generate_text(self, prompt: str, *, llm_model: str | None = None) -> str: + def generate_text(self, prompt: str, *, llm_model: str | None = None, **kwargs) -> str: """Generate text using the Ollama API.""" messages = [ {"role": "user", "content": prompt}, ] response = self.client.chat( - messages=messages, model=llm_model or self.DEFAULT_MODEL + messages=messages, + model=llm_model or self.DEFAULT_MODEL, + **{**self.DEFAULT_KWARGS, **kwargs} ) return response.get("message", {}).get("content", "") From d82effdfb1ca33ba78d1b86ba375f63f60232838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20=C3=96zmen?= Date: Fri, 1 Nov 2024 00:18:57 +0300 Subject: [PATCH 04/26] added default_kwargs logic to xAI provider --- simplemind/providers/xai.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/simplemind/providers/xai.py b/simplemind/providers/xai.py index dd32e0b..28ea14d 100644 --- a/simplemind/providers/xai.py +++ b/simplemind/providers/xai.py @@ -10,11 +10,13 @@ PROVIDER_NAME = "xai" DEFAULT_MODEL = "grok-beta" BASE_URL = "https://api.x.ai/v1" DEFAULT_MAX_TOKENS = 1000 +DEFAULT_KWARGS = {"max_tokens": DEFAULT_MAX_TOKENS} class XAI(BaseProvider): NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL + DEFAULT_KWARGS = DEFAULT_KWARGS def __init__(self, api_key: str | None = None): self.api_key = api_key or settings.get_api_key(PROVIDER_NAME) @@ -45,7 +47,7 @@ class XAI(BaseProvider): response = self.client.chat.completions.create( model=conversation.llm_model or self.DEFAULT_MODEL, messages=messages, - **kwargs, + **{**self.DEFAULT_KWARGS, **kwargs}, ) # Get the response content from the OpenAI response @@ -71,7 +73,7 @@ class XAI(BaseProvider): response = self.client.chat.completions.create( messages=messages, model=llm_model or self.DEFAULT_MODEL, - **kwargs, + **{**self.DEFAULT_KWARGS, **kwargs}, ) return response.choices[0].message.content From caceba381d97fadff68f99f39c3812b09e261339 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 31 Oct 2024 19:49:33 -0400 Subject: [PATCH 05/26] Refactor default_kwargs logic in Ollama provider --- simplemind/providers/ollama.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index 6eceaa3..72e000d 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -15,11 +15,7 @@ T = TypeVar("T", bound=BaseModel) PROVIDER_NAME = "ollama" DEFAULT_MODEL = "llama3.2" DEFAULT_TIMEOUT = 60 -DEFAULT_MAX_TOKENS = 1_000 -DEFAULT_KWARGS = { - "max_tokens": DEFAULT_MAX_TOKENS, - "timeout": DEFAULT_TIMEOUT, -} +DEFAULT_KWARGS = {} class Ollama(BaseProvider): @@ -59,7 +55,7 @@ class Ollama(BaseProvider): response = self.client.chat( model=conversation.llm_model or DEFAULT_MODEL, messages=messages, - **{**self.DEFAULT_KWARGS, **kwargs} + **{**self.DEFAULT_KWARGS, **kwargs}, ) assistant_message = response.get("message") @@ -89,11 +85,13 @@ class Ollama(BaseProvider): messages=messages, model=llm_model or self.DEFAULT_MODEL, response_model=response_model, - **{**self.DEFAULT_KWARGS, **kwargs} + **{**self.DEFAULT_KWARGS, **kwargs}, ) return response - def generate_text(self, prompt: str, *, llm_model: str | None = None, **kwargs) -> str: + def generate_text( + self, prompt: str, *, llm_model: str | None = None, **kwargs + ) -> str: """Generate text using the Ollama API.""" messages = [ {"role": "user", "content": prompt}, @@ -102,7 +100,7 @@ class Ollama(BaseProvider): response = self.client.chat( messages=messages, model=llm_model or self.DEFAULT_MODEL, - **{**self.DEFAULT_KWARGS, **kwargs} + **{**self.DEFAULT_KWARGS, **kwargs}, ) return response.get("message", {}).get("content", "") From 92c10fc41ebe36455e2258f995c75dd77936e7e0 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 11:07:04 +0530 Subject: [PATCH 06/26] added logging --- simplemind/logging.py | 28 ++++++++++++++++++++++++++++ simplemind/providers/anthropic.py | 4 ++++ simplemind/providers/gemini.py | 4 ++++ simplemind/providers/groq.py | 4 ++++ simplemind/providers/ollama.py | 4 ++++ simplemind/providers/openai.py | 10 +++++++--- simplemind/providers/xai.py | 12 +++++++++++- 7 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 simplemind/logging.py diff --git a/simplemind/logging.py b/simplemind/logging.py new file mode 100644 index 0000000..6dcf779 --- /dev/null +++ b/simplemind/logging.py @@ -0,0 +1,28 @@ +import time +from typing import Any, Callable + +import logfire + +from .settings import settings + + +def logger(func: Callable[..., Any]) -> Callable[..., Any]: + """A @logger decorator that logs the function parameters, function returns, and exceptions raised if logging is enabled.""" + is_logging_enabled = settings.logging.enabled + + def wrapper(*args, **kwargs) -> Any: + if not is_logging_enabled: + return func(*args, **kwargs) + logfire.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") + t1 = time.perf_counter() + try: + result = func(*args, **kwargs) + t2 = time.perf_counter() + logfire.info(f"{func.__name__} returned: {result} in {t2-t1} seconds") + return result + except Exception as e: + t2 = time.perf_counter() + logfire.error(f"Error in {func.__name__}: {e} in {t2-t1} seconds") + raise e + + return wrapper diff --git a/simplemind/providers/anthropic.py b/simplemind/providers/anthropic.py index 5c76160..9978a17 100644 --- a/simplemind/providers/anthropic.py +++ b/simplemind/providers/anthropic.py @@ -5,6 +5,7 @@ import anthropic import instructor from pydantic import BaseModel +from ..logging import logger from ..settings import settings from ._base import BaseProvider @@ -37,6 +38,7 @@ class Anthropic(BaseProvider): """A client patched with Instructor.""" return instructor.from_anthropic(self.client) + @logger def send_conversation(self, conversation: "Conversation", **kwargs): """Send a conversation to the Anthropic API.""" from ..models import Message @@ -63,6 +65,7 @@ class Anthropic(BaseProvider): llm_provider=PROVIDER_NAME, ) + @logger def structured_response( self, response_model: Type[T], *, llm_model: str | None = None, **kwargs ) -> T: @@ -82,6 +85,7 @@ class Anthropic(BaseProvider): ) return response + @logger def generate_text(self, prompt: str, *, llm_model: str, **kwargs): messages = [ {"role": "user", "content": prompt}, diff --git a/simplemind/providers/gemini.py b/simplemind/providers/gemini.py index c902141..4a20262 100644 --- a/simplemind/providers/gemini.py +++ b/simplemind/providers/gemini.py @@ -8,6 +8,7 @@ import google.generativeai as genai import instructor from pydantic import BaseModel +from ..logging import logger from ..settings import settings from ._base import BaseProvider @@ -38,6 +39,7 @@ class Gemini(BaseProvider): """A Gemini client patched with Instructor.""" return instructor.from_gemini(self.client) + @logger def send_conversation(self, conversation: "Conversation") -> "Message": """Send a conversation to the Gemini API.""" from ..models import Message @@ -64,6 +66,7 @@ class Gemini(BaseProvider): llm_provider=PROVIDER_NAME, ) + @logger def structured_response(self, prompt: str, response_model: Type[T], **kwargs) -> T: """Send a structured response to the Gemini API.""" llm_model = kwargs.pop("llm_model", self.model_name) @@ -81,6 +84,7 @@ class Gemini(BaseProvider): ) from e return response + @logger def generate_text(self, prompt: str, **kwargs) -> str: """Generate text using the Gemini API.""" llm_model = kwargs.pop("llm_model", self.model_name) diff --git a/simplemind/providers/groq.py b/simplemind/providers/groq.py index 5a033be..c3a82f8 100644 --- a/simplemind/providers/groq.py +++ b/simplemind/providers/groq.py @@ -5,6 +5,7 @@ import groq import instructor from pydantic import BaseModel +from ..logging import logger from ..settings import settings from ._base import BaseProvider @@ -36,6 +37,7 @@ class Groq(BaseProvider): """A client patched with Instructor.""" return instructor.from_groq(self.client) + @logger def send_conversation( self, conversation: "Conversation", @@ -66,6 +68,7 @@ class Groq(BaseProvider): llm_provider=PROVIDER_NAME, ) + @logger def structured_response(self, prompt: str, response_model: Type[T], **kwargs) -> T: # Ensure messages are provided in kwargs messages = [ @@ -80,6 +83,7 @@ class Groq(BaseProvider): ) return response + @logger def generate_text( self, prompt: str, diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index 72e000d..e9c3220 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -6,6 +6,7 @@ import ollama as ol from openai import OpenAI from pydantic import BaseModel +from ..logging import logger from ..settings import settings from ._base import BaseProvider @@ -45,6 +46,7 @@ class Ollama(BaseProvider): mode=instructor.Mode.JSON, ) + @logger def send_conversation(self, conversation: "Conversation", **kwargs) -> "Message": """Send a conversation to the Ollama API.""" from ..models import Message @@ -68,6 +70,7 @@ class Ollama(BaseProvider): llm_provider=PROVIDER_NAME, ) + @logger def structured_response( self, prompt: str, @@ -89,6 +92,7 @@ class Ollama(BaseProvider): ) return response + @logger def generate_text( self, prompt: str, *, llm_model: str | None = None, **kwargs ) -> str: diff --git a/simplemind/providers/openai.py b/simplemind/providers/openai.py index a756835..f99e9b7 100644 --- a/simplemind/providers/openai.py +++ b/simplemind/providers/openai.py @@ -5,6 +5,7 @@ import instructor import openai as oa from pydantic import BaseModel +from ..logging import logger from ..settings import settings from ._base import BaseProvider @@ -36,6 +37,7 @@ class OpenAI(BaseProvider): """A OpenAI client with Instructor.""" return instructor.from_openai(self.client) + @logger def send_conversation(self, conversation: "Conversation", **kwargs): """Send a conversation to the OpenAI API.""" from ..models import Message @@ -47,7 +49,7 @@ class OpenAI(BaseProvider): response = self.client.chat.completions.create( model=conversation.llm_model or DEFAULT_MODEL, messages=messages, - **{**self.DEFAULT_KWARGS, **kwargs} + **{**self.DEFAULT_KWARGS, **kwargs}, ) # Get the response content from the OpenAI response @@ -62,6 +64,7 @@ class OpenAI(BaseProvider): llm_provider=PROVIDER_NAME, ) + @logger def structured_response( self, prompt: str, @@ -79,10 +82,11 @@ class OpenAI(BaseProvider): messages=messages, model=llm_model or self.DEFAULT_MODEL, response_model=response_model, - **{**self.DEFAULT_KWARGS, **kwargs} + **{**self.DEFAULT_KWARGS, **kwargs}, ) return response + @logger def generate_text(self, prompt: str, *, llm_model: str | None = None, **kwargs): """Generate text using the OpenAI API.""" messages = [ @@ -91,6 +95,6 @@ class OpenAI(BaseProvider): response = self.client.chat.completions.create( messages=messages, model=llm_model or self.DEFAULT_MODEL, - **{**self.DEFAULT_KWARGS, **kwargs} + **{**self.DEFAULT_KWARGS, **kwargs}, ) return response.choices[0].message.content diff --git a/simplemind/providers/xai.py b/simplemind/providers/xai.py index 28ea14d..936bca2 100644 --- a/simplemind/providers/xai.py +++ b/simplemind/providers/xai.py @@ -1,8 +1,11 @@ from functools import cached_property +from typing import Type, TypeVar import instructor import openai as oa +from pydantic import BaseModel +from ..logging import logger from ..settings import settings from ._base import BaseProvider @@ -12,6 +15,8 @@ BASE_URL = "https://api.x.ai/v1" DEFAULT_MAX_TOKENS = 1000 DEFAULT_KWARGS = {"max_tokens": DEFAULT_MAX_TOKENS} +T = TypeVar("T", bound=BaseModel) + class XAI(BaseProvider): NAME = PROVIDER_NAME @@ -36,6 +41,7 @@ class XAI(BaseProvider): """A client patched with Instructor.""" return instructor.from_openai(self.client) + @logger def send_conversation(self, conversation: "Conversation", **kwargs): """Send a conversation to the OpenAI API.""" from ..models import Message @@ -62,9 +68,13 @@ class XAI(BaseProvider): llm_provider=PROVIDER_NAME, ) - def structured_response(self, prompt: str, response_model, *, llm_model: str): + @logger + def structured_response( + self, prompt: str, response_model: Type[T], *, llm_model: str + ) -> T: raise NotImplementedError("XAI does not support structured responses") + @logger def generate_text(self, prompt: str, *, llm_model: str, **kwargs): messages = [ {"role": "user", "content": prompt}, From 3a7383425f473f88fba571020cecde8371f4c8b2 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 11:09:54 +0530 Subject: [PATCH 07/26] sorted imports --- tests/conftest.py | 4 ++-- tests/test_generate_data.py | 8 ++++---- tests/test_generate_text.py | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 663d5b5..849d89d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ -import pytest - import os import sys +import pytest + # Add the project root to the Python path. sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) diff --git a/tests/test_generate_data.py b/tests/test_generate_data.py index bea35c8..3d8a6e3 100644 --- a/tests/test_generate_data.py +++ b/tests/test_generate_data.py @@ -1,8 +1,8 @@ -import pytest - -from simplemind.providers import Anthropic, Gemini, OpenAI, Groq, Ollama from pydantic import BaseModel +import pytest +from simplemind.providers import Anthropic, Gemini, Groq, Ollama, OpenAI + class ResponseModel(BaseModel): result: int @@ -25,4 +25,4 @@ def test_generate_data(provider_cls): data = provider.structured_response(prompt=prompt, response_model=ResponseModel) assert isinstance(data, ResponseModel) - assert type(data.result) == int + assert isinstance(data.result, int) diff --git a/tests/test_generate_text.py b/tests/test_generate_text.py index 55a9a13..80f062d 100644 --- a/tests/test_generate_text.py +++ b/tests/test_generate_text.py @@ -1,6 +1,5 @@ import pytest - -from simplemind.providers import Anthropic, Gemini, OpenAI, Groq, Ollama +from simplemind.providers import Anthropic, Gemini, Groq, Ollama, OpenAI @pytest.mark.parametrize( From f5b922ade80192995039eb8d747dcdf9626912e8 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 12:25:44 +0530 Subject: [PATCH 08/26] added proper type hinting --- simplemind/providers/_base.py | 5 ++++- simplemind/providers/anthropic.py | 7 +++++-- simplemind/providers/gemini.py | 10 +++++++--- simplemind/providers/groq.py | 10 +++++++--- simplemind/providers/ollama.py | 5 ++++- simplemind/providers/openai.py | 7 +++++-- simplemind/providers/xai.py | 18 ++++++++++++------ 7 files changed, 44 insertions(+), 18 deletions(-) diff --git a/simplemind/providers/_base.py b/simplemind/providers/_base.py index 42ffb9b..4485246 100644 --- a/simplemind/providers/_base.py +++ b/simplemind/providers/_base.py @@ -1,10 +1,13 @@ from abc import ABC, abstractmethod from functools import cached_property -from typing import Any, Type, TypeVar +from typing import TYPE_CHECKING, Any, Type, TypeVar from instructor import Instructor from pydantic import BaseModel +if TYPE_CHECKING: + from ..models import Conversation, Message + T = TypeVar("T", bound=BaseModel) diff --git a/simplemind/providers/anthropic.py b/simplemind/providers/anthropic.py index 9978a17..b51e40f 100644 --- a/simplemind/providers/anthropic.py +++ b/simplemind/providers/anthropic.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Type, TypeVar +from typing import TYPE_CHECKING, Type, TypeVar import anthropic import instructor @@ -9,6 +9,9 @@ from ..logging import logger from ..settings import settings from ._base import BaseProvider +if TYPE_CHECKING: + from ..models import Conversation, Message + T = TypeVar("T", bound=BaseModel) @@ -39,7 +42,7 @@ class Anthropic(BaseProvider): return instructor.from_anthropic(self.client) @logger - def send_conversation(self, conversation: "Conversation", **kwargs): + def send_conversation(self, conversation: "Conversation", **kwargs) -> "Message": """Send a conversation to the Anthropic API.""" from ..models import Message diff --git a/simplemind/providers/gemini.py b/simplemind/providers/gemini.py index 4a20262..cd91471 100644 --- a/simplemind/providers/gemini.py +++ b/simplemind/providers/gemini.py @@ -2,7 +2,7 @@ # IT is not currently working as desired. from functools import cached_property -from typing import Type, TypeVar +from typing import TYPE_CHECKING, Type, TypeVar import google.generativeai as genai import instructor @@ -12,12 +12,16 @@ from ..logging import logger from ..settings import settings from ._base import BaseProvider -PROVIDER_NAME = "gemini" -DEFAULT_MODEL = "models/gemini-1.5-flash-latest" +if TYPE_CHECKING: + from ..models import Conversation, Message T = TypeVar("T", bound=BaseModel) +PROVIDER_NAME = "gemini" +DEFAULT_MODEL = "models/gemini-1.5-flash-latest" + + class Gemini(BaseProvider): NAME = PROVIDER_NAME DEFAULT_MODEL = DEFAULT_MODEL diff --git a/simplemind/providers/groq.py b/simplemind/providers/groq.py index c3a82f8..5114e90 100644 --- a/simplemind/providers/groq.py +++ b/simplemind/providers/groq.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Type, TypeVar +from typing import TYPE_CHECKING, Type, TypeVar import groq import instructor @@ -9,13 +9,17 @@ from ..logging import logger from ..settings import settings from ._base import BaseProvider +if TYPE_CHECKING: + from ..models import Conversation, Message + +T = TypeVar("T", bound=BaseModel) + + PROVIDER_NAME = "groq" DEFAULT_MODEL = "llama3-8b-8192" DEFAULT_MAX_TOKENS = 1_000 DEFAULT_KWARGS = {"max_tokens": DEFAULT_MAX_TOKENS} -T = TypeVar("T", bound=BaseModel) - class Groq(BaseProvider): NAME = PROVIDER_NAME diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index e9c3220..c422eb4 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Type, TypeVar +from typing import TYPE_CHECKING, Type, TypeVar import instructor import ollama as ol @@ -10,6 +10,9 @@ from ..logging import logger from ..settings import settings from ._base import BaseProvider +if TYPE_CHECKING: + from ..models import Conversation, Message + T = TypeVar("T", bound=BaseModel) diff --git a/simplemind/providers/openai.py b/simplemind/providers/openai.py index f99e9b7..2a05080 100644 --- a/simplemind/providers/openai.py +++ b/simplemind/providers/openai.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Type, TypeVar +from typing import TYPE_CHECKING, Type, TypeVar import instructor import openai as oa @@ -9,6 +9,9 @@ from ..logging import logger from ..settings import settings from ._base import BaseProvider +if TYPE_CHECKING: + from ..models import Conversation, Message + T = TypeVar("T", bound=BaseModel) PROVIDER_NAME = "openai" @@ -38,7 +41,7 @@ class OpenAI(BaseProvider): return instructor.from_openai(self.client) @logger - def send_conversation(self, conversation: "Conversation", **kwargs): + def send_conversation(self, conversation: "Conversation", **kwargs) -> "Message": """Send a conversation to the OpenAI API.""" from ..models import Message diff --git a/simplemind/providers/xai.py b/simplemind/providers/xai.py index 936bca2..afc1faf 100644 --- a/simplemind/providers/xai.py +++ b/simplemind/providers/xai.py @@ -1,22 +1,28 @@ from functools import cached_property -from typing import Type, TypeVar +from typing import TYPE_CHECKING, Type, TypeVar import instructor import openai as oa from pydantic import BaseModel +from simplemind.models import Message + from ..logging import logger from ..settings import settings from ._base import BaseProvider +if TYPE_CHECKING: + from ..models import Conversation, Message + +T = TypeVar("T", bound=BaseModel) + + PROVIDER_NAME = "xai" DEFAULT_MODEL = "grok-beta" BASE_URL = "https://api.x.ai/v1" DEFAULT_MAX_TOKENS = 1000 DEFAULT_KWARGS = {"max_tokens": DEFAULT_MAX_TOKENS} -T = TypeVar("T", bound=BaseModel) - class XAI(BaseProvider): NAME = PROVIDER_NAME @@ -42,7 +48,7 @@ class XAI(BaseProvider): return instructor.from_openai(self.client) @logger - def send_conversation(self, conversation: "Conversation", **kwargs): + def send_conversation(self, conversation: "Conversation", **kwargs) -> "Message": """Send a conversation to the OpenAI API.""" from ..models import Message @@ -75,7 +81,7 @@ class XAI(BaseProvider): raise NotImplementedError("XAI does not support structured responses") @logger - def generate_text(self, prompt: str, *, llm_model: str, **kwargs): + def generate_text(self, prompt: str, *, llm_model: str, **kwargs) -> str: messages = [ {"role": "user", "content": prompt}, ] @@ -86,4 +92,4 @@ class XAI(BaseProvider): **{**self.DEFAULT_KWARGS, **kwargs}, ) - return response.choices[0].message.content + return str(response.choices[0].message.content) From 4b3e1bc6dd2ec3c9874e47db43307953d99b2d94 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 12:55:24 +0530 Subject: [PATCH 09/26] added methods to toggle logging --- simplemind/settings.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/simplemind/settings.py b/simplemind/settings.py index d561228..a0dad2f 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -51,5 +51,19 @@ class Settings(BaseSettings): key = getattr(self, f"{provider.upper()}_API_KEY", None) return key.get_secret_value() if key else None + def enable_logging(self) -> None: + """Enable logging for the application.""" + # adding imports here to avoid forced dependencies + import logfire + from logging import basicConfig + + self.logging.enabled = True + logfire.configure() + basicConfig(handlers=[logfire.LogfireLoggingHandler()]) + + def disable_logging(self) -> None: + """Disable logging for the application.""" + self.logging.enabled = False + settings = Settings() From 56b1e65d70b355c67ea0ba1a8e1ca542c2e25588 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 13:06:06 +0530 Subject: [PATCH 10/26] moved logging functions to LoggingConfig from Settings --- simplemind/settings.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/simplemind/settings.py b/simplemind/settings.py index a0dad2f..e5bb822 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -1,4 +1,4 @@ -from typing import Literal, Optional, Union +from typing import Literal, Optional, Union, Unpack from pydantic import Field, SecretStr, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -10,10 +10,28 @@ class LoggingConfig(BaseSettings): """The class that holds all the logging settings for the application.""" enabled: bool = Field(False, description="Enable logging") - level: logging_level = Field("INFO", description="The logging level") model_config = SettingsConfigDict(extra="forbid") + def enable_logging(self, **kwargs) -> None: + """Enable logging for the application.""" + # adding imports here to avoid forced dependencies + try: + import logfire + except ImportError as e: + raise ImportError( + "To enable logging, please install logfire: `pip install logfire`" + ) from e + from logging import basicConfig + + self.enabled = True + logfire.configure(**kwargs) + basicConfig(handlers=[logfire.LogfireLoggingHandler()]) + + def disable_logging(self) -> None: + """Disable logging for the application.""" + self.enabled = False + class Settings(BaseSettings): """The class that holds all the API keys for the application.""" @@ -51,19 +69,5 @@ class Settings(BaseSettings): key = getattr(self, f"{provider.upper()}_API_KEY", None) return key.get_secret_value() if key else None - def enable_logging(self) -> None: - """Enable logging for the application.""" - # adding imports here to avoid forced dependencies - import logfire - from logging import basicConfig - - self.logging.enabled = True - logfire.configure() - basicConfig(handlers=[logfire.LogfireLoggingHandler()]) - - def disable_logging(self) -> None: - """Disable logging for the application.""" - self.logging.enabled = False - settings = Settings() From fe06331662b8f85f2c692963604c2e4c6f05c14f Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 14:24:34 +0530 Subject: [PATCH 11/26] fixed forced imports + ensured return type in structure_response --- simplemind/providers/anthropic.py | 10 ++++++++-- simplemind/providers/groq.py | 13 +++++++++---- simplemind/providers/ollama.py | 9 +++++++-- simplemind/providers/openai.py | 9 +++++++-- simplemind/providers/xai.py | 7 ++++++- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/simplemind/providers/anthropic.py b/simplemind/providers/anthropic.py index b51e40f..4798933 100644 --- a/simplemind/providers/anthropic.py +++ b/simplemind/providers/anthropic.py @@ -1,7 +1,6 @@ from functools import cached_property from typing import TYPE_CHECKING, Type, TypeVar -import anthropic import instructor from pydantic import BaseModel @@ -34,6 +33,13 @@ class Anthropic(BaseProvider): """The raw Anthropic client.""" if not self.api_key: raise ValueError("Anthropic API key is required") + try: + import anthropic + except ImportError as exc: + raise ImportError( + "Please install the `anthropic` package: `pip install anthropic`" + ) from exc + return anthropic.Anthropic(api_key=self.api_key) @cached_property @@ -86,7 +92,7 @@ class Anthropic(BaseProvider): response_model=response_model, **{**self.DEFAULT_KWARGS, **kwargs}, ) - return response + return response_model.model_validate(response) @logger def generate_text(self, prompt: str, *, llm_model: str, **kwargs): diff --git a/simplemind/providers/groq.py b/simplemind/providers/groq.py index 5114e90..5b2801f 100644 --- a/simplemind/providers/groq.py +++ b/simplemind/providers/groq.py @@ -1,7 +1,6 @@ from functools import cached_property from typing import TYPE_CHECKING, Type, TypeVar -import groq import instructor from pydantic import BaseModel @@ -34,6 +33,12 @@ class Groq(BaseProvider): """The raw Groq client.""" if not self.api_key: raise ValueError("Groq API key is required") + try: + import groq + except ImportError as exc: + raise ImportError( + "Please install the `groq` package: `pip install groq`" + ) from exc return groq.Groq(api_key=self.api_key) @cached_property @@ -85,7 +90,7 @@ class Groq(BaseProvider): model=kwargs.pop("llm_model", self.DEFAULT_MODEL), **{**self.DEFAULT_KWARGS, **kwargs}, ) - return response + return response_model.model_validate(response) @logger def generate_text( @@ -94,7 +99,7 @@ class Groq(BaseProvider): *, llm_model: str, **kwargs, - ): + ) -> str: messages = [ {"role": "user", "content": prompt}, ] @@ -105,4 +110,4 @@ class Groq(BaseProvider): **{**self.DEFAULT_KWARGS, **kwargs}, ) - return response.choices[0].message.content + return str(response.choices[0].message.content) diff --git a/simplemind/providers/ollama.py b/simplemind/providers/ollama.py index c422eb4..3e00c25 100644 --- a/simplemind/providers/ollama.py +++ b/simplemind/providers/ollama.py @@ -2,7 +2,6 @@ from functools import cached_property from typing import TYPE_CHECKING, Type, TypeVar import instructor -import ollama as ol from openai import OpenAI from pydantic import BaseModel @@ -36,6 +35,12 @@ class Ollama(BaseProvider): """The raw Ollama client.""" if not self.host_url: raise ValueError("No ollama host url provided") + try: + import ollama as ol + except ImportError as exc: + raise ImportError( + "Please install the `ollama` package: `pip install ollama`" + ) from exc return ol.Client(timeout=self.TIMEOUT, host=self.host_url) @cached_property @@ -93,7 +98,7 @@ class Ollama(BaseProvider): response_model=response_model, **{**self.DEFAULT_KWARGS, **kwargs}, ) - return response + return response_model.model_validate(response) @logger def generate_text( diff --git a/simplemind/providers/openai.py b/simplemind/providers/openai.py index 2a05080..fb197e5 100644 --- a/simplemind/providers/openai.py +++ b/simplemind/providers/openai.py @@ -2,7 +2,6 @@ from functools import cached_property from typing import TYPE_CHECKING, Type, TypeVar import instructor -import openai as oa from pydantic import BaseModel from ..logging import logger @@ -33,6 +32,12 @@ class OpenAI(BaseProvider): """The raw OpenAI client.""" if not self.api_key: raise ValueError("OpenAI API key is required") + try: + import openai as oa + except ImportError as exc: + raise ImportError( + "Please install the `openai` package: `pip install openai`" + ) from exc return oa.OpenAI(api_key=self.api_key) @cached_property @@ -87,7 +92,7 @@ class OpenAI(BaseProvider): response_model=response_model, **{**self.DEFAULT_KWARGS, **kwargs}, ) - return response + return response_model.model_validate(response) @logger def generate_text(self, prompt: str, *, llm_model: str | None = None, **kwargs): diff --git a/simplemind/providers/xai.py b/simplemind/providers/xai.py index afc1faf..60e2654 100644 --- a/simplemind/providers/xai.py +++ b/simplemind/providers/xai.py @@ -2,7 +2,6 @@ from functools import cached_property from typing import TYPE_CHECKING, Type, TypeVar import instructor -import openai as oa from pydantic import BaseModel from simplemind.models import Message @@ -37,6 +36,12 @@ class XAI(BaseProvider): """The raw OpenAI client.""" if not self.api_key: raise ValueError("XAI API key is required") + try: + import openai as oa + except ImportError as exc: + raise ImportError( + "Please install the `openai` package: `pip install openai`" + ) from exc return oa.OpenAI( api_key=self.api_key, base_url=BASE_URL, From 0fb54d1987ea17d262a2c3f6793a56c4ee431d62 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 14:31:01 +0530 Subject: [PATCH 12/26] circular import problem solve --- simplemind/providers/xai.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/simplemind/providers/xai.py b/simplemind/providers/xai.py index 60e2654..c19a0ae 100644 --- a/simplemind/providers/xai.py +++ b/simplemind/providers/xai.py @@ -4,8 +4,6 @@ from typing import TYPE_CHECKING, Type, TypeVar import instructor from pydantic import BaseModel -from simplemind.models import Message - from ..logging import logger from ..settings import settings from ._base import BaseProvider From 1455b5ba13fb2d312c46d007ca609032bbe7cd36 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 14:31:19 +0530 Subject: [PATCH 13/26] remove unused import --- simplemind/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplemind/settings.py b/simplemind/settings.py index e5bb822..9f41377 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -1,4 +1,4 @@ -from typing import Literal, Optional, Union, Unpack +from typing import Literal, Optional, Union from pydantic import Field, SecretStr, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict From a2597709d21a488b01cc696390929cbef82de1f3 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 14:55:22 +0530 Subject: [PATCH 14/26] gemini works as expected --- simplemind/providers/gemini.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/simplemind/providers/gemini.py b/simplemind/providers/gemini.py index cd91471..abd39db 100644 --- a/simplemind/providers/gemini.py +++ b/simplemind/providers/gemini.py @@ -4,7 +4,6 @@ from functools import cached_property from typing import TYPE_CHECKING, Type, TypeVar -import google.generativeai as genai import instructor from pydantic import BaseModel @@ -30,12 +29,21 @@ class Gemini(BaseProvider): self.api_key = api_key or settings.get_api_key(PROVIDER_NAME) self.model_name = DEFAULT_MODEL + def set_model(self, model_name: str): + self.model_name = model_name + @cached_property - def client(self, model_name: str = DEFAULT_MODEL): + def client(self): """The raw Gemini client.""" if not self.api_key: raise ValueError("Gemini API key is required") - self.model_name = model_name + try: + import google.generativeai as genai + except ImportError as exc: + raise ImportError( + "Please install the `google-generativeai` package: `pip install google-generativeai`" + ) from exc + genai.configure(api_key=self.api_key) return genai.GenerativeModel(model_name=self.model_name) @cached_property @@ -73,8 +81,7 @@ class Gemini(BaseProvider): @logger def structured_response(self, prompt: str, response_model: Type[T], **kwargs) -> T: """Send a structured response to the Gemini API.""" - llm_model = kwargs.pop("llm_model", self.model_name) - + kwargs.pop("llm_model") try: response = self.structured_client.chat.completions.create( messages=[{"role": "user", "content": prompt}], @@ -86,13 +93,12 @@ class Gemini(BaseProvider): raise RuntimeError( f"Failed to send structured response to Gemini API: {e}" ) from e - return response + return response_model.model_validate(response) @logger def generate_text(self, prompt: str, **kwargs) -> str: """Generate text using the Gemini API.""" - llm_model = kwargs.pop("llm_model", self.model_name) - + kwargs.pop("llm_model") try: response = self.client.generate_content(prompt, **kwargs) except Exception as e: From d62f297b68f1dcf6edc1b06f3a3defc90b89a756 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 15:16:20 +0530 Subject: [PATCH 15/26] removed unused variable --- simplemind/models.py | 2 +- simplemind/settings.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/simplemind/models.py b/simplemind/models.py index 95df38c..54eade1 100644 --- a/simplemind/models.py +++ b/simplemind/models.py @@ -1,6 +1,6 @@ -from types import TracebackType import uuid from datetime import datetime +from types import TracebackType from typing import Any, Dict, List, Literal, Optional from pydantic import BaseModel, Field diff --git a/simplemind/settings.py b/simplemind/settings.py index 9f41377..8d690af 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -1,10 +1,8 @@ -from typing import Literal, Optional, Union +from typing import Optional, Union from pydantic import Field, SecretStr, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict -logging_level = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] - class LoggingConfig(BaseSettings): """The class that holds all the logging settings for the application.""" From ad1800840d769b91c0591d5cd29c7c1b5d858368 Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Fri, 1 Nov 2024 15:27:15 +0530 Subject: [PATCH 16/26] small changes --- simplemind/logging.py | 3 +-- simplemind/settings.py | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/simplemind/logging.py b/simplemind/logging.py index 6dcf779..369c4c1 100644 --- a/simplemind/logging.py +++ b/simplemind/logging.py @@ -8,10 +8,9 @@ from .settings import settings def logger(func: Callable[..., Any]) -> Callable[..., Any]: """A @logger decorator that logs the function parameters, function returns, and exceptions raised if logging is enabled.""" - is_logging_enabled = settings.logging.enabled def wrapper(*args, **kwargs) -> Any: - if not is_logging_enabled: + if not settings.logging.enabled: return func(*args, **kwargs) logfire.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") t1 = time.perf_counter() diff --git a/simplemind/settings.py b/simplemind/settings.py index 8d690af..a5548e7 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -16,16 +16,23 @@ class LoggingConfig(BaseSettings): # adding imports here to avoid forced dependencies try: import logfire + from logging import basicConfig except ImportError as e: raise ImportError( "To enable logging, please install logfire: `pip install logfire`" ) from e - from logging import basicConfig self.enabled = True logfire.configure(**kwargs) basicConfig(handlers=[logfire.LogfireLoggingHandler()]) + try: + logfire.configure(**kwargs) + basicConfig(handlers=[logfire.LogfireLoggingHandler()]) + except Exception as e: + self.enabled = False # Reset flag on failure + raise RuntimeError("Failed to configure logging") from e + def disable_logging(self) -> None: """Disable logging for the application.""" self.enabled = False From 3dd2e1b24825252741d31d5ec3aa31a095968a22 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:28:53 -0400 Subject: [PATCH 17/26] Refactor Gemini provider to handle missing llm_model key --- simplemind/providers/gemini.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/simplemind/providers/gemini.py b/simplemind/providers/gemini.py index abd39db..aa08db8 100644 --- a/simplemind/providers/gemini.py +++ b/simplemind/providers/gemini.py @@ -81,7 +81,9 @@ class Gemini(BaseProvider): @logger def structured_response(self, prompt: str, response_model: Type[T], **kwargs) -> T: """Send a structured response to the Gemini API.""" - kwargs.pop("llm_model") + # Only try to pop if the key exists + kwargs.pop("llm_model", None) # Add default value of None + try: response = self.structured_client.chat.completions.create( messages=[{"role": "user", "content": prompt}], From cd0be3ad8999993680516b4f19c533532fb09a66 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:36:05 -0400 Subject: [PATCH 18/26] Refactor LoggingConfig methods for enabling and disabling logging --- simplemind/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simplemind/settings.py b/simplemind/settings.py index a5548e7..232298d 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -11,7 +11,7 @@ class LoggingConfig(BaseSettings): model_config = SettingsConfigDict(extra="forbid") - def enable_logging(self, **kwargs) -> None: + def enable_logfire(self, **kwargs) -> None: """Enable logging for the application.""" # adding imports here to avoid forced dependencies try: @@ -33,7 +33,7 @@ class LoggingConfig(BaseSettings): self.enabled = False # Reset flag on failure raise RuntimeError("Failed to configure logging") from e - def disable_logging(self) -> None: + def disable_logfire(self) -> None: """Disable logging for the application.""" self.enabled = False From 173162e79876c59394c5bbef9b42d45df3f1b943 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:39:14 -0400 Subject: [PATCH 19/26] Refactor LoggingConfig methods for enabling and disabling logging --- simplemind/__init__.py | 6 ++++++ simplemind/logging.py | 6 ++++-- simplemind/settings.py | 8 ++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/simplemind/__init__.py b/simplemind/__init__.py index cf725c8..b9e4d1a 100644 --- a/simplemind/__init__.py +++ b/simplemind/__init__.py @@ -113,6 +113,11 @@ def generate_text( return provider.generate_text(prompt=prompt, llm_model=llm_model, **kwargs) +def enable_logfire() -> None: + """Enable logfire logging.""" + settings.logging.enable_logfire() + + # Syntax sugar. Plugin = BasePlugin @@ -125,4 +130,5 @@ __all__ = [ "BasePlugin", "Session", "Plugin", + "enable_logfire", ] diff --git a/simplemind/logging.py b/simplemind/logging.py index 369c4c1..c18d718 100644 --- a/simplemind/logging.py +++ b/simplemind/logging.py @@ -7,10 +7,12 @@ from .settings import settings def logger(func: Callable[..., Any]) -> Callable[..., Any]: - """A @logger decorator that logs the function parameters, function returns, and exceptions raised if logging is enabled.""" + """A decorator that logs the function parameters, function returns, + and exceptions raised if logging is enabled, using logfire. + """ def wrapper(*args, **kwargs) -> Any: - if not settings.logging.enabled: + if not settings.logging.is_enabled: return func(*args, **kwargs) logfire.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") t1 = time.perf_counter() diff --git a/simplemind/settings.py b/simplemind/settings.py index 232298d..a8179b2 100644 --- a/simplemind/settings.py +++ b/simplemind/settings.py @@ -7,7 +7,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict class LoggingConfig(BaseSettings): """The class that holds all the logging settings for the application.""" - enabled: bool = Field(False, description="Enable logging") + is_enabled: bool = Field(False, description="Enable logging") model_config = SettingsConfigDict(extra="forbid") @@ -22,7 +22,7 @@ class LoggingConfig(BaseSettings): "To enable logging, please install logfire: `pip install logfire`" ) from e - self.enabled = True + self.is_enabled = True logfire.configure(**kwargs) basicConfig(handlers=[logfire.LogfireLoggingHandler()]) @@ -30,12 +30,12 @@ class LoggingConfig(BaseSettings): logfire.configure(**kwargs) basicConfig(handlers=[logfire.LogfireLoggingHandler()]) except Exception as e: - self.enabled = False # Reset flag on failure + self.is_enabled = False # Reset flag on failure raise RuntimeError("Failed to configure logging") from e def disable_logfire(self) -> None: """Disable logging for the application.""" - self.enabled = False + self.is_enabled = False class Settings(BaseSettings): From 28a7b2f1401c104cb78dba870f50c13b7d8a2ae7 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:42:08 -0400 Subject: [PATCH 20/26] Refactor logging configuration to enable/disable logging --- simplemind/logging.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/simplemind/logging.py b/simplemind/logging.py index c18d718..06defc3 100644 --- a/simplemind/logging.py +++ b/simplemind/logging.py @@ -14,13 +14,17 @@ def logger(func: Callable[..., Any]) -> Callable[..., Any]: def wrapper(*args, **kwargs) -> Any: if not settings.logging.is_enabled: return func(*args, **kwargs) + logfire.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") t1 = time.perf_counter() + try: result = func(*args, **kwargs) t2 = time.perf_counter() logfire.info(f"{func.__name__} returned: {result} in {t2-t1} seconds") + return result + except Exception as e: t2 = time.perf_counter() logfire.error(f"Error in {func.__name__}: {e} in {t2-t1} seconds") From 873f5ba5f80ce926fbf22ab741ab3a6f0afab2b4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:44:18 -0400 Subject: [PATCH 21/26] Refactor logging configuration to enable/disable logging --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0e88da..48cc0d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Release History =============== +## 0.1.7 (2024-11-01) + +- Add `logger` decorator. +- Add `sm.enable_logfire()` function. +- General improvements. + ## 0.1.6 (2024-10-31) - Add `sm.Plugin` syntax sugar. From c648a922b4bfa6f75861092c9960eedb242ef24c Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:44:37 -0400 Subject: [PATCH 22/26] Bump version to v0.1.7 in conf.py and pyproject.toml --- docs/conf.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 30c9fd6..a123700 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ import simplemind project = "simplemind" copyright = "2024 Kenneth Reitz" author = "Kenneth Reitz" -release = "v0.1.6" +release = "v0.1.7" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyproject.toml b/pyproject.toml index 6da1744..f78847e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "simplemind" -version = "0.1.6" +version = "0.1.7" 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.10" From 34f463839c5137af6b475a296cda3f31a4ee2072 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:46:44 -0400 Subject: [PATCH 23/26] logfire --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7728df2..8f803e1 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,12 @@ The universe is never done. Simple, yet effective. +### Logging + +Simplemind uses [logfire](https://logfire.ai) for logging. To enable logging, call `sm.enable_logfire()`. + +### More Examples + Please see the [examples](examples) directory for executable examples. ------------------- @@ -217,4 +223,3 @@ Simplemind is licensed under the Apache 2.0 License. ## Acknowledgements Simplemind is inspired by the philosophy of "code for humans" and aims to make working with AI models accessible to all. Special thanks to the open-source community for their contributions and inspiration. - From cbec2c5f6dd1cb00e9893442999d1e83fb5d0f9c Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:48:39 -0400 Subject: [PATCH 24/26] special thanks --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 8f803e1..31202fe 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,33 @@ class Poem(BaseModel): title='Eternal Embrace' content='In the quiet hours of the night,\nWhen stars whisper secrets bright,\nTwo hearts beat in a gentle rhyme,\nDancing through the sands of time.\n\nWith every glance, a spark ignites,\nA flame that warms the coldest nights,\nIn laughter shared and whispers sweet,\nLove paints the world, a masterpiece.\n\nThrough stormy skies and sunlit days,\nIn myriad forms, it finds its ways,\nA tender touch, a knowing sigh,\nIn love’s embrace, we learn to fly.\n\nAs seasons change and moments fade,\nIn the tapestry of dreams we’ve laid,\nLove’s threads endure, forever bind,\nA timeless bond, two souls aligned.\n\nSo here’s to love, both bright and true,\nA gift we give, anew, anew,\nIn every heartbeat, every prayer,\nA story written in the air.' ``` +#### A more complex example + +```python +class InstructionStep(BaseModel): + step_number: int + instruction: str + +class RecipeIngredient(BaseModel): + name: str + quantity: str + unit: str + +class Recipe(BaseModel): + name: str + ingredients: list[RecipeIngredient] + instructions: list[InstructionStep] + +recipe = sm.generate_data( + "Write a recipe for chocolate chip cookies", + llm_model="gpt-4o-mini", + llm_provider="openai", + response_model=Recipe, +) +``` + +Special thanks to [@jxnl](https://github.com/jxnl) for building [Instructor](https://github.com/jxnl/instructor), which makes this possible! + ### Conversational AI SimpleMind also allows for easy conversational flows: From 76fa7521eb07f8c63cc42e2dbdb47a96bb08db0c Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:49:19 -0400 Subject: [PATCH 25/26] Refactor quantity field in RecipeIngredient model to use float instead of string --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31202fe..45f3889 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ class InstructionStep(BaseModel): class RecipeIngredient(BaseModel): name: str - quantity: str + quantity: float unit: str class Recipe(BaseModel): From 241a7ab4027a445cf11b37362fba0b4373305d1f Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Nov 2024 08:50:39 -0400 Subject: [PATCH 26/26] Refactor pyproject.toml to add logfire as a dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f78847e..b288937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.1.7" 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.10" -dependencies = ["pydantic", "pydantic-settings", "instructor", "openai", "anthropic", "ollama", "groq", "google-generativeai"] +dependencies = ["pydantic", "pydantic-settings", "instructor", "openai", "anthropic", "ollama", "groq", "google-generativeai", "logfire"] [build-system] requires = ["hatchling"]