Skip to content

Commit a236657

Browse files
Added Anthropic models. Removed old style API class and tests (#10)
1 parent db0fd4f commit a236657

10 files changed

Lines changed: 678 additions & 448 deletions

File tree

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Python Debugger: Current File",
9+
"type": "debugpy",
10+
"request": "launch",
11+
"program": "${file}",
12+
"console": "integratedTerminal"
13+
}
14+
]
15+
}

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
### Modelsmith is a Python library that allows you to get structured responses in the form of Pydantic models and Python types from Google Vertex AI and OpenAI models.
77

88
Currently it allows you to use the following classes of model:
9-
- __OpenAIModel__ (most commonly used with `gpt-3.5-turbo`, `gpt-4` and `gpt-4o`)
10-
- __VertexAIChatModel__ (most commonly used with `chat-bison`)
11-
- __VertexAITextGenerationModel__ (most commonly used with `text-bison`)
12-
- __VertexAIGenerativeModel__ (most commonly used with `gemini-pro`)
9+
- __AnthropicModel__ (used with Anthropic's set of models such as `claude-3-haiku`, `claude-3-sonnet`, `claude-3-opus` and `claude-3_5-sonnet`)
10+
- __OpenAIModel__ (used with OpenAI's set of models such as `gpt-3.5-turbo`, `gpt-4` and `gpt-4o`)
11+
- __VertexAIChatModel__ (used with Google Vertex AI's chat models such as `chat-bison`)
12+
- __VertexAITextGenerationModel__ (used with Google Vertex AI's text generation models such as `text-bison`)
13+
- __VertexAIGenerativeModel__ (used with Google Vertex AI's generative models such as `gemini-pro`)
1314

1415
Modelsmith provides a unified interface over all of these. It has been designed to be extensible and can adapt to other models in the future.
1516

@@ -126,10 +127,10 @@ response = forge.generate("I have lived in Irvine, CA and Dallas TX")
126127
print(response) # [City(city='Irvine', state='CA'), City(city='Dallas', state='TX')]
127128
```
128129

129-
If we want to use an OpenAI model the same applies. Simply select the appropriate model class, specify which OpenAI model to use (in this case `gpt-4o`), and pass it to the `Forge` instance.
130+
If we want to use an Anthropic model the same applies. Simply select the appropriate model class, specify which Anthropic model to use (in this case `claude-3-haiku-20240307`), and pass it to the `Forge` instance.
130131

131132
```python
132-
from modelsmith import Forge, OpenAIModel # import the correct class
133+
from modelsmith import Forge, AnthropicModel # import the correct class
133134
from pydantic import BaseModel, Field
134135

135136

@@ -138,9 +139,9 @@ class City(BaseModel):
138139
state: str = Field(description="2-letter abbreviation of the state")
139140

140141

141-
# text-bison instead of gemini-pro
142+
# Anthropic's claude-3-haiku-20240307 instead of gemini-pro
142143
forge = Forge(
143-
model=OpenAIModel("gpt-4o"),
144+
model=AnthropicModel("claude-3-haiku-20240307"),
144145
response_model=list[City],
145146
)
146147

poetry.lock

Lines changed: 565 additions & 203 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "modelsmith"
3-
version = "0.5.0"
3+
version = "0.6.0"
44
description = "Get Pydantic models and Python types as LLM responses from Google Vertex AI and OpenAI models."
55
authors = ["Christo Olivier <mail@christoolivier.com>"]
66
maintainers = ["Christo Olivier <mail@christoolivier.com>"]
@@ -18,6 +18,7 @@ google-cloud-aiplatform = "^1.43.0"
1818
pydantic = "^2.6.4"
1919
jinja2 = "^3.1.3"
2020
openai = "^1.35.7"
21+
anthropic = "^0.30.1"
2122

2223
[tool.poetry.group.dev.dependencies]
2324
pytest = "^8.1.1"

src/modelsmith/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .exceptions import ModelNotDerivedError, PatternNotFound
22
from .forge import Forge
33
from .language_models import (
4+
AnthropicModel,
45
OpenAIModel,
56
VertexAIChatModel,
67
VertexAIGenerativeModel,
@@ -9,6 +10,7 @@
910
from .prompt import Prompt
1011

1112
__all__ = [
13+
"AnthropicModel",
1214
"Forge",
1315
"ModelNotDerivedError",
1416
"OpenAIModel",

src/modelsmith/forge.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33

44
from pydantic import ValidationError
55
from tenacity import RetryError, Retrying, stop_after_attempt
6-
from vertexai.generative_models import GenerativeModel
7-
from vertexai.language_models import (
8-
ChatModel,
9-
TextGenerationModel,
10-
)
116

127
from modelsmith.enums import ResponseModelType
138
from modelsmith.exceptions import (
@@ -16,11 +11,11 @@
1611
PatternNotFound,
1712
)
1813
from modelsmith.language_models import (
14+
AnthropicModel,
1915
OpenAIModel,
2016
VertexAIChatModel,
2117
VertexAIGenerativeModel,
2218
VertexAITextGenerationModel,
23-
_LanguageModelWrapper,
2419
)
2520
from modelsmith.prompt import Prompt
2621
from modelsmith.response_model import ResponseModel
@@ -40,9 +35,7 @@ class Forge(Generic[T]):
4035
def __init__(
4136
self,
4237
*,
43-
model: ChatModel
44-
| GenerativeModel
45-
| TextGenerationModel
38+
model: AnthropicModel
4639
| VertexAIChatModel
4740
| VertexAIGenerativeModel
4841
| VertexAITextGenerationModel
@@ -70,13 +63,7 @@ def __init__(
7063
return a response in the form of the response_model. If
7164
False, return None.
7265
"""
73-
# check if a Vertex AI model is being passed directly or if the new
74-
# Wrapper classes are being used.
75-
self.model = (
76-
_LanguageModelWrapper(model)
77-
if isinstance(model, (ChatModel, GenerativeModel, TextGenerationModel))
78-
else model
79-
)
66+
self.model = model
8067
self.response_model = ResponseModel(response_model)
8168
self.prompt = Prompt(prompt)
8269

src/modelsmith/language_models.py

Lines changed: 39 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import warnings
21
from abc import ABC, abstractmethod
3-
from typing import Any, Callable
2+
from typing import Any
43

4+
from anthropic import Anthropic
55
from openai import OpenAI
66
from vertexai.generative_models import GenerationResponse, GenerativeModel
77
from vertexai.language_models import (
@@ -10,11 +10,7 @@
1010
TextGenerationResponse,
1111
)
1212

13-
DEPRECATION_WARNING = (
14-
"Using VertexAI classes directly is deprecated and will be removed in "
15-
"a future release. Please import the classes from the `language_model` "
16-
"module instead."
17-
)
13+
DEFAULT_MAX_TOKENS = 1024
1814

1915

2016
class BaseLanguageModel(ABC):
@@ -30,6 +26,42 @@ def send(self, input: str, model_settings: dict[str, Any] | None = None) -> str:
3026
pass
3127

3228

29+
class AnthropicModel(BaseLanguageModel):
30+
"""
31+
Class that wraps the Anthropic API to handle sending inputs and receiving outputs.
32+
"""
33+
34+
def __init__(self, model_name: str, api_key: str | None = None) -> None:
35+
"""
36+
Create a new synchronous anthropic client instance.
37+
"""
38+
self.model_name = model_name
39+
self._client = Anthropic(api_key=api_key)
40+
41+
def send(self, input: str, model_settings: dict[str, Any] | None = None) -> str:
42+
"""
43+
Send the input to the LLM using the correct method from the underlying model.
44+
Return the text response from the LLM.
45+
46+
:param input: The input string to send to the LLM.
47+
:param model_settings: The dictionary containing the model's settings.
48+
:return: The response from the LLM.
49+
"""
50+
# If `max_tokens` not provided in model settings then set it to the default
51+
# of 1024
52+
model_settings = model_settings or {}
53+
if "max_tokens" not in model_settings:
54+
model_settings["max_tokens"] = DEFAULT_MAX_TOKENS
55+
56+
response = self._client.messages.create(
57+
model=self.model_name,
58+
messages=[{"role": "user", "content": input}],
59+
**(model_settings or {}),
60+
)
61+
62+
return response.content[0].text
63+
64+
3365
class OpenAIModel(BaseLanguageModel):
3466
"""
3567
Class that wraps the OpenAI API to handle sending inputs and receiving outputs.
@@ -153,55 +185,3 @@ def send(
153185
"""
154186
response = self.model.predict(input, **(model_settings or {}))
155187
return response.text
156-
157-
158-
class _LanguageModelWrapper:
159-
"""
160-
Class that wraps the LLM model to handle sending inputs and receiving outputs.
161-
"""
162-
163-
def __init__(
164-
self, model: ChatModel | GenerativeModel | TextGenerationModel
165-
) -> None:
166-
self.model = model
167-
self._llm_send_method = self._init_llm_send_method()
168-
169-
def _init_llm_send_method(self) -> Callable:
170-
"""
171-
Set the method to use to send the user input to the LLM.
172-
"""
173-
warnings.warn(
174-
DEPRECATION_WARNING,
175-
DeprecationWarning,
176-
stacklevel=2,
177-
)
178-
179-
if isinstance(self.model, TextGenerationModel):
180-
return self.model.predict
181-
182-
if isinstance(self.model, GenerativeModel):
183-
return self.model.generate_content
184-
185-
if isinstance(self.model, ChatModel):
186-
chat_session = self.model.start_chat()
187-
return chat_session.send_message
188-
189-
raise TypeError(
190-
"The model type must be ChatModel, TextGenerationModel or GenerativeModel"
191-
)
192-
193-
def send(self, input: str, model_settings: dict[str, Any] | None = None) -> str:
194-
"""
195-
Send the input to the LLM using the correct method from the underlying model.
196-
Return the response from the LLM.
197-
198-
:param input: The input string to send to the LLM.
199-
:param model_settings: The dictionary containing the model's settings.
200-
:return: The response from the LLM.
201-
"""
202-
# For a GenerativeModel the function signature differs from the other models
203-
if isinstance(self.model, GenerativeModel):
204-
response = self._llm_send_method(input, generation_config=model_settings)
205-
else:
206-
response = self._llm_send_method(input, **(model_settings or {}))
207-
return response.text

tests/settings.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
11
import pytest
22
from modelsmith.language_models import (
3+
AnthropicModel,
34
OpenAIModel,
45
VertexAIChatModel,
56
VertexAIGenerativeModel,
67
VertexAITextGenerationModel,
78
)
8-
from vertexai.generative_models import GenerativeModel
9-
from vertexai.language_models import ChatModel, TextGenerationModel
109

1110
MODEL_INSTANCE_PARAMS = [
1211
pytest.param(
13-
TextGenerationModel.from_pretrained("text-bison"), id="text_model_old_style"
12+
AnthropicModel("claude-3-haiku-20240307"), id="anthropic_claude_3_haiku_model"
1413
),
15-
pytest.param(VertexAITextGenerationModel("text-bison"), id="text_model"),
16-
pytest.param(ChatModel.from_pretrained("chat-bison"), id="chat_model_old_style"),
17-
pytest.param(VertexAIChatModel("chat-bison"), id="chat_model"),
18-
pytest.param(VertexAIGenerativeModel("gemini-1.0-pro"), id="gemini_1_0_pro"),
19-
pytest.param(VertexAIGenerativeModel("gemini-1.5-pro"), id="gemini_1_5_pro"),
20-
pytest.param(GenerativeModel("gemini-1.5-flash"), id="gemini_1_5_flash_old_style"),
21-
pytest.param(VertexAIGenerativeModel("gemini-1.5-flash"), id="gemini_1_5_flash"),
22-
pytest.param(OpenAIModel("gpt-3.5-turbo"), id="gpt-3.5-turbo"),
23-
pytest.param(OpenAIModel("gpt-4o"), id="gpt-4o"),
14+
pytest.param(
15+
AnthropicModel("claude-3-opus-20240229"), id="anthropic_claude_3_opus_model"
16+
),
17+
pytest.param(
18+
AnthropicModel("claude-3-5-sonnet-20240620"),
19+
id="anthropic_claude_3.5_sonnet_model",
20+
),
21+
pytest.param(
22+
VertexAITextGenerationModel("text-bison"), id="vertexai_text_bison_model"
23+
),
24+
pytest.param(VertexAIChatModel("chat-bison"), id="vertexai_chat_bison_model"),
25+
pytest.param(
26+
VertexAIGenerativeModel("gemini-1.0-pro"), id="vertexai_gemini_1_0_pro"
27+
),
28+
pytest.param(
29+
VertexAIGenerativeModel("gemini-1.5-pro"), id="vertexai_gemini_1_5_pro"
30+
),
31+
pytest.param(
32+
VertexAIGenerativeModel("gemini-1.5-flash"), id="vertexai_gemini_1_5_flash"
33+
),
34+
pytest.param(OpenAIModel("gpt-3.5-turbo"), id="openai_gpt-3.5-turbo"),
35+
pytest.param(OpenAIModel("gpt-4o"), id="openai_gpt-4o"),
2436
]
2537

2638
MODEL_SETTINGS_PARAMS = [

0 commit comments

Comments
 (0)