mirror of
https://github.com/kennethreitz/simplemind.git
synced 2026-06-05 14:50:16 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75a42044e5 | |||
| cc66dbf8e5 | |||
| a174e60a1e | |||
| b03695f626 | |||
| 082bc24e91 | |||
| aca1b87180 | |||
| 1ff4c5660e | |||
| 241a7ab402 | |||
| 76fa7521eb | |||
| cbec2c5f6d | |||
| 34f463839c | |||
| c648a922b4 | |||
| 873f5ba5f8 | |||
| 28a7b2f140 | |||
| 173162e798 | |||
| 75c42278a2 | |||
| c25f1e1058 | |||
| 2a5966eb10 | |||
| f19263d309 | |||
| 25b742db1f | |||
| 8d83050a64 |
@@ -4,3 +4,4 @@ export GROQ_API_KEY=""
|
||||
export OLLAMA_HOST_URL=""
|
||||
export OPENAI_API_KEY=""
|
||||
export XAI_API_KEY=""
|
||||
export AMAZON_PROFILE_NAME=""
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
Release History
|
||||
===============
|
||||
|
||||
## 0.2.0 (2024-11-01)
|
||||
|
||||
- Add Amazon Bedrock provider.
|
||||
- Make all provider optional dependencies. Use `$ pip install 'simplemind[full]'` to install all providers.
|
||||
- General improvements.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -19,6 +19,7 @@ With Simplemind, tapping into AI is as easy as a friendly conversation.
|
||||
To specify a specific provider or model, you can use the `llm_provider` and `llm_model` parameters when calling: `generate_text`, `generate_data`, or `create_conversation`. The APIs remain identital between all supported providers/models.
|
||||
|
||||
- [**Anthropic's Claude**](https://www.anthropic.com/claude)
|
||||
- [**Amazon Bedrock**](https://aws.amazon.com/bedrock/)
|
||||
- [**Google's Gemini**](https://gemini.google/)
|
||||
- [**Groq's Groq**](https://groq.com/)
|
||||
- [**Ollama**](https://ollama.com)
|
||||
@@ -28,6 +29,7 @@ To specify a specific provider or model, you can use the `llm_provider` and `llm
|
||||
If you want to see Simplemind support, additional providers or models, please send a pull request!
|
||||
|
||||
## Why SimpleMind?
|
||||
|
||||
- **Intuitive**: Built with Pythonic simplicity and readability in mind.
|
||||
- **For Humans**: Emphasizes a human-friendly interface, just like `requests` for HTTP.
|
||||
- **Open Source**: Simplemind is open source, and contributions are always welcome!
|
||||
@@ -39,7 +41,7 @@ Also, why not? :)
|
||||
Simplemind takes care of the complex API calls so you can focus on what matters—building, experimenting, and creating.
|
||||
|
||||
```bash
|
||||
$ pip install simplemind
|
||||
$ pip install 'simplemind[full]'
|
||||
```
|
||||
|
||||
First, authenticate your API keys by setting them in the environment variables:
|
||||
@@ -56,7 +58,6 @@ Next, import Simplemind and start using it:
|
||||
import simplemind as sm
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some examples of how to use Simplemind:
|
||||
@@ -90,6 +91,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: float
|
||||
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:
|
||||
@@ -163,6 +191,7 @@ conversation.add_message(
|
||||
text="Please write a poem about the moon",
|
||||
)
|
||||
```
|
||||
|
||||
```pycon
|
||||
>>> conversation.send()
|
||||
In the vast expanse where stars do play,
|
||||
@@ -198,11 +227,18 @@ 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.
|
||||
|
||||
-------------------
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions of all kinds. Feel free to open issues for bug reports or feature requests, and submit pull requests to make SimpleMind even better.
|
||||
|
||||
To get started:
|
||||
@@ -213,8 +249,9 @@ To get started:
|
||||
4. Submit a pull request.
|
||||
|
||||
## License
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import simplemind
|
||||
project = "simplemind"
|
||||
copyright = "2024 Kenneth Reitz"
|
||||
author = "Kenneth Reitz"
|
||||
release = "v0.1.6"
|
||||
release = "v0.2.0"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
+13
-2
@@ -1,10 +1,21 @@
|
||||
[project]
|
||||
name = "simplemind"
|
||||
version = "0.1.6"
|
||||
version = "0.2.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.10"
|
||||
dependencies = ["pydantic", "pydantic-settings", "instructor", "openai", "anthropic", "ollama", "groq", "google-generativeai"]
|
||||
dependencies = ["pydantic", "pydantic-settings", "instructor", "logfire"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
full = [
|
||||
"openai",
|
||||
"anthropic",
|
||||
"ollama",
|
||||
"groq",
|
||||
"google-generativeai",
|
||||
"botocore",
|
||||
"boto3"
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -7,18 +7,24 @@ 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()
|
||||
|
||||
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")
|
||||
|
||||
@@ -7,5 +7,6 @@ from .groq import Groq
|
||||
from .ollama import Ollama
|
||||
from .openai import OpenAI
|
||||
from .xai import XAI
|
||||
from .amazon import Amazon
|
||||
|
||||
providers: List[Type[BaseProvider]] = [Anthropic, Gemini, Groq, OpenAI, Ollama, XAI]
|
||||
providers: List[Type[BaseProvider]] = [Anthropic, Gemini, Groq, OpenAI, Ollama, XAI, Amazon]
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
from typing import Type, TypeVar
|
||||
|
||||
import instructor
|
||||
import anthropic
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ._base import BaseProvider
|
||||
from ..settings import settings
|
||||
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
|
||||
PROVIDER_NAME = "amazon"
|
||||
DEFAULT_MODEL = "anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
DEFAULT_MAX_TOKENS = 5_000
|
||||
|
||||
|
||||
class Amazon(BaseProvider):
|
||||
NAME = PROVIDER_NAME
|
||||
DEFAULT_MODEL = DEFAULT_MODEL
|
||||
|
||||
def __init__(self, profile_name: str | None = None):
|
||||
self.profile_name = profile_name or settings.AMAZON_PROFILE_NAME
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
"""The AnthropicBedrock client."""
|
||||
if not self.profile_name:
|
||||
raise ValueError("Profile name is not provided")
|
||||
|
||||
return anthropic.AnthropicBedrock(aws_profile=self.profile_name)
|
||||
|
||||
@property
|
||||
def structured_client(self):
|
||||
"""A client patched with Instructor."""
|
||||
return instructor.from_anthropic(self.client)
|
||||
|
||||
def send_conversation(self, conversation: "Conversation", **kwargs):
|
||||
"""Send a conversation to the OpenAI API."""
|
||||
from ..models import Message
|
||||
|
||||
messages = [
|
||||
{"role": msg.role, "content": msg.text} for msg in conversation.messages
|
||||
]
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model=conversation.llm_model or DEFAULT_MODEL, messages=messages, **kwargs
|
||||
)
|
||||
|
||||
# Get the response content from the OpenAI response
|
||||
assistant_message = response.choices[0].message
|
||||
|
||||
# Create and return a properly formatted Message instance
|
||||
return Message(
|
||||
role="assistant",
|
||||
text=assistant_message.content or "",
|
||||
raw=response,
|
||||
llm_model=conversation.llm_model or DEFAULT_MODEL,
|
||||
llm_provider=PROVIDER_NAME,
|
||||
)
|
||||
|
||||
def structured_response(
|
||||
self, prompt, response_model: Type[T], *, llm_model: str | None = None, **kwargs
|
||||
) -> T:
|
||||
# Ensure messages are provided in kwargs
|
||||
messages = [
|
||||
{"role": "user", "content": prompt},
|
||||
]
|
||||
|
||||
response = self.structured_client.chat.completions.create(
|
||||
messages=messages,
|
||||
model=llm_model or self.DEFAULT_MODEL,
|
||||
response_model=response_model,
|
||||
max_tokens=DEFAULT_MAX_TOKENS,
|
||||
**kwargs,
|
||||
)
|
||||
return response
|
||||
|
||||
def generate_text(self, prompt, *, llm_model, **kwargs):
|
||||
messages = [
|
||||
{"role": "user", "content": prompt},
|
||||
]
|
||||
|
||||
response = self.client.messages.create(
|
||||
model=llm_model or self.DEFAULT_MODEL,
|
||||
messages=messages,
|
||||
max_tokens=DEFAULT_MAX_TOKENS,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return response.content[0].text
|
||||
@@ -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,17 +30,18 @@ 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):
|
||||
"""The class that holds all the API keys for the application."""
|
||||
|
||||
AMAZON_PROFILE_NAME: Optional[str] = Field("default", description="AWS Named Profile")
|
||||
ANTHROPIC_API_KEY: Optional[SecretStr] = Field(
|
||||
None, description="API key for Anthropic"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
import pytest
|
||||
from simplemind.providers import Anthropic, Gemini, Groq, Ollama, OpenAI
|
||||
|
||||
from simplemind.providers import Anthropic, Gemini, OpenAI, Groq, Ollama, Amazon
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ResponseModel(BaseModel):
|
||||
@@ -16,6 +18,7 @@ class ResponseModel(BaseModel):
|
||||
OpenAI,
|
||||
Groq,
|
||||
Ollama,
|
||||
Amazon
|
||||
],
|
||||
)
|
||||
def test_generate_data(provider_cls):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from simplemind.providers import Anthropic, Gemini, Groq, Ollama, OpenAI
|
||||
|
||||
from simplemind.providers import Anthropic, Gemini, OpenAI, Groq, Ollama, Amazon
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -10,6 +11,7 @@ from simplemind.providers import Anthropic, Gemini, Groq, Ollama, OpenAI
|
||||
OpenAI,
|
||||
Groq,
|
||||
Ollama,
|
||||
Amazon,
|
||||
],
|
||||
)
|
||||
def test_generate_text(provider_cls):
|
||||
|
||||
Reference in New Issue
Block a user