diff --git a/simplemind/__init__.py b/simplemind/__init__.py index 7dbc0d4..e69de29 100644 --- a/simplemind/__init__.py +++ b/simplemind/__init__.py @@ -1 +0,0 @@ -from .integrations import * diff --git a/simplemind/vector_store/__init__.py b/simplemind/concepts/__init__.py similarity index 100% rename from simplemind/vector_store/__init__.py rename to simplemind/concepts/__init__.py diff --git a/simplemind/concepts.py b/simplemind/concepts/context.py similarity index 100% rename from simplemind/concepts.py rename to simplemind/concepts/context.py diff --git a/simplemind/config.py b/simplemind/config.py deleted file mode 100644 index 1a45fea..0000000 --- a/simplemind/config.py +++ /dev/null @@ -1,13 +0,0 @@ -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - openai_api_key: str = "" - anthropic_api_key: str = "" - default_model: str = "gpt-4" - - class Config: - env_file = ".env" - - -settings = Settings() diff --git a/simplemind/conversation.py b/simplemind/conversation.py index cb07fbf..4bae53e 100644 --- a/simplemind/conversation.py +++ b/simplemind/conversation.py @@ -1,12 +1,24 @@ class Conversation: - def __init__(self, ai_client): - self.messages = [] - self.ai_client = ai_client - - def say(self, message): - self.messages.append({'role': 'user', 'content': message}) + """A class to manage conversation state with an AI model.""" - def get_reply(self): - reply = self.ai_client.message(messages=self.messages) - self.messages.append({'role': 'system', 'content': reply.text}) - return reply \ No newline at end of file + def __init__(self, client): + self.client = client + self.messages = [] + + def add_message(self, message, role="user"): + """Add a message to the conversation history.""" + self.messages.append({"role": role, "content": message}) + return self + + def send(self, message=None, **kwargs): + """Send the conversation history (and optionally a new message) to the AI.""" + if message: + self.add_message(message) + + response = self.client.message(message_history=self.messages, **kwargs) + + # Add the AI's response to the conversation history + if isinstance(response.text, str): + self.add_message(response.text, role="assistant") + + return response diff --git a/simplemind/core.py b/simplemind/core.py index 8b11bb1..e69de29 100644 --- a/simplemind/core.py +++ b/simplemind/core.py @@ -1,44 +0,0 @@ -from typing import Dict, Any, Optional -from .models import AIResponse -from .concepts import Context -from .integrations.base import BaseClientProvider - - -class SimpleMind: - """Main class for SimpleMind functionality.""" - - def __init__( - self, api_key: str, provider: str = "openai", context: Optional[Context] = None - ): - """Initialize SimpleMind with the specified provider.""" - self.api_key = api_key - self.provider = provider - self.context = context or Context() - self._client = self._get_provider() - - def _get_provider(self) -> BaseClientProvider: - """Get the appropriate provider client.""" - from .integrations.openai import OpenAI - from .integrations.anthropic import Anthropic - - providers = {"openai": OpenAI, "anthropic": Anthropic} - - if self.provider not in providers: - raise ValueError( - f"Provider '{self.provider}' not supported. Available providers: {list(providers.keys())}" - ) - - return providers[self.provider](api_key=self.api_key) - - def generate(self, prompt: str, **kwargs) -> AIResponse: - """Generate a response using the configured provider.""" - return self._client.message(prompt, **kwargs) - - def create_conversation(self, initial_message: str) -> str: - """Create a new conversation and return its ID.""" - conversation = self._client.create_conversation(initial_message) - return conversation.id - - def send_message(self, conversation_id: str, message: str) -> AIResponse: - """Send a message in an existing conversation.""" - return self._client.send_message(conversation_id, message) diff --git a/simplemind/core/__init__.py b/simplemind/core/__init__.py new file mode 100644 index 0000000..23aebb8 --- /dev/null +++ b/simplemind/core/__init__.py @@ -0,0 +1,44 @@ +from typing import Dict, Any, Optional +from .models import AIResponse +from ..concepts.context import Context +from ..integrations.base import BaseClientProvider + + +class SimpleMind: + """Main class for SimpleMind functionality.""" + + def __init__( + self, api_key: str, provider: str = "openai", context: Optional[Context] = None + ): + """Initialize SimpleMind with the specified provider.""" + self.api_key = api_key + self.provider = provider + self.context = context or Context() + self._client = self._get_provider() + + def _get_provider(self) -> BaseClientProvider: + """Get the appropriate provider client.""" + from .integrations.openai import OpenAI + from .integrations.anthropic import Anthropic + + providers = {"openai": OpenAI, "anthropic": Anthropic} + + if self.provider not in providers: + raise ValueError( + f"Provider '{self.provider}' not supported. Available providers: {list(providers.keys())}" + ) + + return providers[self.provider](api_key=self.api_key) + + def generate(self, prompt: str, **kwargs) -> AIResponse: + """Generate a response using the configured provider.""" + return self._client.message(prompt, **kwargs) + + def create_conversation(self, initial_message: str) -> str: + """Create a new conversation and return its ID.""" + conversation = self._client.create_conversation(initial_message) + return conversation.id + + def send_message(self, conversation_id: str, message: str) -> AIResponse: + """Send a message in an existing conversation.""" + return self._client.send_message(conversation_id, message) diff --git a/simplemind/client.py b/simplemind/core/client.py similarity index 90% rename from simplemind/client.py rename to simplemind/core/client.py index 60e3211..a51b4b7 100644 --- a/simplemind/client.py +++ b/simplemind/core/client.py @@ -1,6 +1,6 @@ from typing import Optional -from simplemind.models import Conversation, AIResponse -from simplemind.concepts import Context +from simplemind.core.models import Conversation, AIResponse +from simplemind.concepts.context import Context from simplemind.integrations.openai import OpenAI from simplemind.integrations.anthropic import Anthropic import logging @@ -24,7 +24,7 @@ class Client: if provider not in self.providers: raise ValueError(f"Provider '{provider}' not supported.") return self.providers[provider].create_conversation( - initial_message="Hello!", context=self.context.dict() + initial_message="Hello!", context=self.context.model_dump() ) def _handle_api_error(self, error: Exception, operation: str): diff --git a/simplemind/core/config.py b/simplemind/core/config.py new file mode 100644 index 0000000..a7d1813 --- /dev/null +++ b/simplemind/core/config.py @@ -0,0 +1,17 @@ +from pydantic_settings import BaseSettings +from typing import Optional + + +class Settings(BaseSettings): + openai_api_key: Optional[str] = None + anthropic_api_key: Optional[str] = None + ollama_host_url: Optional[str] = None + default_model: str = "gpt-4" + log_level: str = "INFO" + + class Config: + env_file = ".env" + case_sensitive = False + + +settings = Settings() diff --git a/simplemind/core/errors.py b/simplemind/core/errors.py new file mode 100644 index 0000000..3aaf310 --- /dev/null +++ b/simplemind/core/errors.py @@ -0,0 +1,22 @@ +class SimpleMindError(Exception): + """Base exception for SimpleMind errors.""" + + pass + + +class ProviderError(SimpleMindError): + """Raised when there's an error with the AI provider.""" + + pass + + +class ConfigurationError(SimpleMindError): + """Raised when there's a configuration error.""" + + pass + + +class AuthenticationError(SimpleMindError): + """Raised when authentication fails.""" + + pass diff --git a/simplemind/logger.py b/simplemind/core/logger.py similarity index 100% rename from simplemind/logger.py rename to simplemind/core/logger.py diff --git a/simplemind/models.py b/simplemind/core/models.py similarity index 100% rename from simplemind/models.py rename to simplemind/core/models.py diff --git a/simplemind/integrations/anthropic.py b/simplemind/integrations/anthropic.py index d07525a..15ac541 100644 --- a/simplemind/integrations/anthropic.py +++ b/simplemind/integrations/anthropic.py @@ -5,8 +5,8 @@ import instructor from anthropic import Anthropic as BaseAnthropic from .base import BaseClientProvider -from ..models import AIResponse, Conversation -from ..logger import logger +from ..core.models import AIResponse, Conversation +from ..core.logger import logger DEFAULT_MODEL = "claude-3-5-sonnet-20241022" diff --git a/simplemind/integrations/base.py b/simplemind/integrations/base.py index 40e8742..938585c 100644 --- a/simplemind/integrations/base.py +++ b/simplemind/integrations/base.py @@ -2,7 +2,7 @@ from pydantic import BaseModel from typing import Any, Dict, List, Optional -from ..models import AIResponse, Conversation, Message +from ..core.models import AIResponse, Conversation, Message import uuid diff --git a/simplemind/integrations/ollama.py b/simplemind/integrations/ollama.py index 7581c61..dcaf537 100644 --- a/simplemind/integrations/ollama.py +++ b/simplemind/integrations/ollama.py @@ -3,7 +3,7 @@ import os from ollama import Client as BaseOllama from .base import BaseClientProvider -from ..models import AIResponse +from ..core.models import AIResponse from ..conversation import Conversation TIMEOUT = 60 @@ -19,7 +19,7 @@ class Ollama(BaseClientProvider): """Initialize Ollama client, with Instructor enabled.""" if not os.environ.get('OLLAMA_HOST_URL'): raise ValueError("Please set the OLLAMA_HOST_URL environment variable") - + if not os.environ.get('OLLAMA_MODEL'): raise ValueError("Please set the OLLAMA_MODEL environment variable") else: diff --git a/simplemind/integrations/openai.py b/simplemind/integrations/openai.py index cd808da..7d43a06 100644 --- a/simplemind/integrations/openai.py +++ b/simplemind/integrations/openai.py @@ -1,32 +1,29 @@ -import os -from typing import Optional, List -import instructor +from typing import List, Optional from openai import OpenAI as BaseOpenAI - +from ..core.errors import AuthenticationError, ProviderError +from simplemind.core.config import settings +from ..core.logger import logger from .base import BaseClientProvider -from ..models import AIResponse, Conversation -from ..logger import logger -from simplemind.config import settings - - -DEFAULT_MODEL = "gpt-4o" class OpenAI(BaseClientProvider): - def __init__(self, model: str = DEFAULT_MODEL, api_key: Optional[str] = None): + def __init__(self, model: str = "gpt-4", api_key: Optional[str] = None): super().__init__(model=model, api_key=api_key) self.login() - def login(self): + def login(self) -> None: if not self._api_key: self._api_key = settings.openai_api_key if not self._api_key: - raise ValueError("OpenAI API key not provided.") - self.client = BaseOpenAI(api_key=self._api_key) - self.instructor_client = instructor.from_openai(self.client) - if not self.test_connection(): - raise ConnectionError("Failed to connect to OpenAI API.") - logger.info("Logged in to OpenAI successfully.") + raise AuthenticationError("OpenAI API key not provided") + + try: + self.client = BaseOpenAI(api_key=self._api_key) + if not self.test_connection(): + raise ProviderError("Failed to connect to OpenAI API") + logger.info("Successfully connected to OpenAI") + except Exception as e: + raise ProviderError(f"OpenAI initialization failed: {str(e)}") @property def available_models(self) -> List[str]: @@ -36,39 +33,32 @@ class OpenAI(BaseClientProvider): logger.error(f"Error fetching models: {e}") return [] - def test_connection(self) -> bool: + def test_connection(self): + """Test the connection to OpenAI API.""" try: - models = self.available_models - if models: - logger.info(f"Available models: {models}") - return True - else: - logger.warning("No available models found.") - return False + # A simple test call to verify API key works + self.client.models.list() + return True except Exception as e: - logger.error(f"Error testing connection: {e}") return False - def generate_response(self, conversation: Conversation) -> AIResponse: - messages = conversation.get_messages() - params = { - "model": self.model, - "messages": [ - { - "role": msg.role, - "content": [{"type": "text", "text": msg.content}], # New format - } - for msg in messages - ], - "temperature": getattr( - self, "temperature", 0.7 - ), # Use 0.7 as default if not set - } + def _handle_api_error(self, e: Exception) -> None: + """Handle API errors.""" + logger.error(f"OpenAI API error: {e}") + raise ProviderError(f"OpenAI API error: {e}") + def generate_response(self, conversation) -> str: + """Generate a response using the OpenAI API.""" try: - completion = self.client.chat.completions.create(**params) - return completion + messages = [ + {"role": msg.role, "content": msg.content} + for msg in conversation.messages + ] + + response = self.client.chat.completions.create( + model=self.model, messages=messages + ) + + return response.choices[0].message.content except Exception as e: - # Enhanced error handling (optional) - logger.error(f"OpenAI API Error: {e}") - raise RuntimeError(f"Failed to generate response: {e}") + self._handle_api_error(e) diff --git a/simplemind/models/base.py b/simplemind/models/base.py new file mode 100644 index 0000000..9981ed1 --- /dev/null +++ b/simplemind/models/base.py @@ -0,0 +1,26 @@ +from datetime import datetime +from typing import Any, Dict, List, Optional +from pydantic import BaseModel + +class Message(BaseModel): + role: str + content: str + created_at: datetime = datetime.now() + +class Conversation(BaseModel): + id: str + messages: List[Message] = [] + context: Dict[str, Any] = {} + created_at: datetime = datetime.now() + updated_at: datetime = datetime.now() + + def add_message(self, role: str, content: str) -> Message: + message = Message(role=role, content=content) + self.messages.append(message) + self.updated_at = datetime.now() + return message + +class AIResponse(BaseModel): + text: str + response: Any + metadata: Dict[str, Any] = {} diff --git a/simplemind/plugins/base.py b/simplemind/plugins/base.py index 5aa48e5..f346f70 100644 --- a/simplemind/plugins/base.py +++ b/simplemind/plugins/base.py @@ -1,12 +1,13 @@ from abc import ABC, abstractmethod -from typing import Any, Dict +from typing import Any, Dict, Optional class BasePlugin(ABC): """Base class for all SimpleMind plugins.""" def __init__(self): - self.is_enabled = True + self.enabled: bool = True + self.name: str = self.__class__.__name__ @abstractmethod def process(self, context: Dict[str, Any]) -> Dict[str, Any]: @@ -20,10 +21,14 @@ class BasePlugin(ABC): """ pass - def enable(self): + def enable(self) -> None: """Enable the plugin.""" - self.is_enabled = True + self.enabled = True - def disable(self): + def disable(self) -> None: """Disable the plugin.""" - self.is_enabled = False + self.enabled = False + + @property + def is_enabled(self) -> bool: + return self.enabled diff --git a/simplemind/utils/__init__.py b/simplemind/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simplemind/vector_store/faiss_store.py b/simplemind/utils/faiss_store.py similarity index 100% rename from simplemind/vector_store/faiss_store.py rename to simplemind/utils/faiss_store.py diff --git a/t.py b/t.py index d5e0ca5..0f4aa90 100644 --- a/t.py +++ b/t.py @@ -2,11 +2,11 @@ import os from pprint import pprint from pydantic import BaseModel import simplemind -from simplemind.concepts import Context +from simplemind.concepts.context import Context from simplemind.plugins.kv import KVPlugin from simplemind.plugins.basic_memory import BasicMemoryPlugin from simplemind.chains.reverse_text import ReverseTextChain -from simplemind.client import Client +from simplemind.core.client import Client class CustomContext(Context): @@ -27,12 +27,15 @@ aiclient = Client( print(aiclient.available_models) # Example usage -conversation = aiclient.create_conversation(provider="openai") +conversation = aiclient.create_conversation(provider="anthropic", context=ctx) response = aiclient.send_message( - conversation, "Who is Kenneth Reitz?", provider="openai" + conversation, "Who is Kenneth Reitz?", provider="anthropic" ) -print(response) +# response = aiclient.send_message( +# conversation, "Who is Kenneth Reitz?", provider="openai" +# ) +# print(response) -reverse_chain = ReverseTextChain() -result = reverse_chain.run("Hello, World!") -print(result) # Output: !dlroW ,olleH +# reverse_chain = ReverseTextChain() +# result = reverse_chain.run("Hello, World!") +# print(result) # Output: !dlroW ,olleH diff --git a/t2.py b/t2.py index 0bed67f..e352d81 100644 --- a/t2.py +++ b/t2.py @@ -47,22 +47,3 @@ message = "who is kenneth reitz?" print(f"> {message}") pprint(openai.message(message, response_model=BioData)) - -# claude = simplemind.integrations.Anthropic() - -# # print(claude.test_connection()) -# # print(claude.available_models) - -# claude.login() - -vector_store = FAISSStore(dimension=768) # Example dimension for embeddings - -# Add embeddings -embeddings = np.random.random((10, 768)).astype('float32') -ids = [f"doc_{i}" for i in range(10)] -vector_store.add_embeddings(embeddings, ids) - -# Search -query_embedding = np.random.random((1, 768)).astype('float32') -results = vector_store.search(query_embedding, top_k=3) -print(results) diff --git a/t3.py b/t3.py index 1e6d754..28f23bf 100644 --- a/t3.py +++ b/t3.py @@ -1,17 +1,16 @@ - import simplemind aiclient = simplemind.Ollama() -print('Messaging client') +print("Messaging client") message_response = aiclient.message(message="Once upon a time in a land far away...") print(message_response) -print('Generating Text') +print("Generating Text") generated_text = aiclient.generate_text(prompt="Once upon a time in a land far away...") print(generated_text) -print('Initiating Conversation') +print("Initiating Conversation") conversation = aiclient.start_conversation() # Add a message to the conversation @@ -27,4 +26,3 @@ conversation.say("What number did I ask you to remember?") # Get the AI's response reply = conversation.get_reply() print(reply) - diff --git a/tests/test_openai.py b/tests/test_openai.py index 8b08252..7a41224 100644 --- a/tests/test_openai.py +++ b/tests/test_openai.py @@ -1,25 +1,21 @@ import unittest from unittest.mock import patch, MagicMock from simplemind.integrations.openai import OpenAI +from simplemind.core.errors import AuthenticationError, ProviderError class TestOpenAIProvider(unittest.TestCase): + def setUp(self): + self.api_key = "test_key" + @patch("simplemind.integrations.openai.BaseOpenAI") - def setUp(self, mock_openai): - self.mock_openai = mock_openai.return_value - self.mock_openai.models.list.return_value = [MagicMock(id="gpt-4")] - self.provider = OpenAI(api_key="test_api_key", model="gpt-4") + def test_initialization(self, mock_openai): + provider = OpenAI(api_key=self.api_key) + self.assertIsNotNone(provider.client) - def test_available_models(self): - models = self.provider.available_models - self.assertIn("gpt-4", models) - - def test_test_connection_success(self): - self.assertTrue(self.provider.test_connection()) - - def test_generate_response_not_implemented(self): - with self.assertRaises(NotImplementedError): - self.provider.generate_response(None) + def test_missing_api_key(self): + with self.assertRaises(AuthenticationError): + OpenAI(api_key=None) if __name__ == "__main__":