Skip to content

Commit 687f830

Browse files
Dynamic models, o-models, nebius support (#32)
1 parent 271d1a2 commit 687f830

28 files changed

+1742
-2504
lines changed

.github/workflows/run-unit-tests.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ jobs:
1919
echo OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }} >> .env
2020
echo LAMOOM_API_URI=${{ secrets.LAMOOM_API_URI }} >> .env
2121
echo LAMOOM_API_TOKEN=${{ secrets.LAMOOM_API_TOKEN }} >> .env
22-
echo FLOW_PROMPT_API_URI=${{ secrets.FLOW_PROMPT_API_URI }} >> .env
23-
echo FLOW_PROMPT_API_TOKEN=${{ secrets.FLOW_PROMPT_API_TOKEN }} >> .env
22+
cat .env
2423
2524
- name: Install dependencies
2625
run: |
@@ -41,3 +40,9 @@ jobs:
4140
- name: Run tests with pytest
4241
run: |
4342
poetry run make test
43+
44+
- name: Publish package
45+
env:
46+
PYPI_API_KEY: ${{ secrets.PYPI_API_KEY }}
47+
run: |
48+
poetry run make publish-release

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ publish-test-prerelease:
6262

6363

6464
publish-release:
65+
poetry config pypi-token.pypi "$(PYPI_API_KEY)"
6566
poetry version patch
6667
poetry build
6768
poetry publish

README.md

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,8 @@ os.setenv('OPENAI_API_KEY', 'your_key_here')
3535

3636
# add Azure Keys
3737
os.setenv('AZURE_KEYS', '{"name_realm":{"url": "https://baseurl.azure.com/","key": "secret"}}')
38-
39-
# Other env Variables;
40-
# CLAUDE_API_KEY
41-
# GEMINI_API_KEY
42-
# OPENAI_ORG
43-
# NEBIUS_KEY
38+
# or creating flow_prompt obj
39+
Lamoom(azure_keys={"realm_name":{"url": "https://baseurl.azure.com/", "key": "your_secret"}})
4440
```
4541

4642
### Model Agnostic:
@@ -49,40 +45,25 @@ Mix models easily, and districute the load across models. The system will automa
4945
- Gemini
5046
- OpenAI (w/ Azure OpenAI models)
5147
- Nebius with (Llama, DeepSeek, Mistral, Mixtral, dolphin, Qwen and others)
52-
```
53-
from lamoom import LamoomModelProviders
54-
55-
def_behaviour = behaviour.AIModelsBehaviour(attempts=[
56-
AttemptToCall(provider='openai', model='gpt-4o', weight=100),
57-
AttemptToCall(provider='azure', realm='useast-1', deployment_id='gpt-4o' weight=100),
58-
AttemptToCall(provider='azure', realm='useast-2', deployment_id='gpt-4o' weight=100),
59-
AttemptToCall(provider=LamoomModelProviders.anthropic, model='claude-3-5-sonnet-20240620', weight=100
60-
),
61-
AttemptToCall(provider=LamoomModelProviders.gemini, model='gemini-1.5-pro', weight=100
62-
),
63-
AttemptToCall(provider=LamoomModelProviders.nebius, model='deepseek-ai/DeepSeek-R1', weight=100
64-
)
65-
])
66-
67-
response_llm = client.call(agent.id, context, def_behaviour)
68-
```
6948

70-
### Add Behavious:
71-
- use OPENAI_BEHAVIOR
72-
- or add your own Behaviour, you can set max count of attempts, if you have different AI Models, if the first attempt will fail because of retryable error, the second will be called, based on the weights.
73-
```
74-
from lamoom import OPENAI_GPT4_0125_PREVIEW_BEHAVIOUR
75-
behaviour = OPENAI_GPT4_0125_PREVIEW_BEHAVIOUR
76-
```
77-
or:
49+
Model string format is the following for Claude, Gemini, OpenAI, Nebius:
50+
`"{model_provider}/{model_name}"`
51+
For Azure models format is the following:
52+
`"azure/{realm}/{model_name}"`
53+
54+
```python
55+
response_llm = client.call(agent.id, context, model = "openai/gpt-4o")
56+
response_llm = client.call(agent.id, context, model = "azure/useast/gpt-4o")
7857
```
79-
from lamoom import behaviour
80-
behaviour = behaviour.AIModelsBehaviour(
81-
attempts=[
82-
AttemptToCall(provider='azure', realm='useast-1', deployment_id='gpt-4o' weight=100),
83-
AttemptToCall(provider='azure', realm='useast-2', deployment_id='gpt-4o' weight=100),
84-
]
85-
)
58+
59+
### Lamoom Keys
60+
Obtain an API token from Flow Prompt and add it:
61+
62+
```python
63+
# As an environment variable:
64+
os.setenv('LAMOOM_API_TOKEN', 'your_token_here')
65+
# Via code:
66+
Lamoom(api_token='your_api_token')
8667
```
8768

8869
## Usage Examples:
@@ -100,7 +81,7 @@ prompt.add("You're {name}. Say Hello and ask what's their name.", role="system")
10081
# Call AI model with Lamoom
10182
context = {"name": "John Doe"}
10283
# test_data - optional parameter used for generating tests
103-
response = client.call(prompt.id, context, behavior, test_data={
84+
response = client.call(prompt.id, context, "openai/gpt-4o", test_data={
10485
'ideal_answer': "Hello, I'm John Doe. What's your name?",
10586
'behavior_name': "gemini"
10687
}

lamoom/__init__.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@
44
from lamoom.ai_models import behaviour
55
from lamoom.prompt.prompt import Prompt
66
from lamoom.prompt.prompt import Prompt as PipePrompt
7-
from lamoom.ai_models.openai.behaviours import (
8-
OPENAI_GPT4_0125_PREVIEW_BEHAVIOUR,
9-
OPENAI_GPT4_1106_PREVIEW_BEHAVIOUR,
10-
OPENAI_GPT4_1106_VISION_PREVIEW_BEHAVIOUR,
11-
OPENAI_GPT4_BEHAVIOUR,
12-
OPENAI_GPT4_32K_BEHAVIOUR,
13-
OPENAI_GPT3_5_TURBO_0125_BEHAVIOUR,
14-
)
157
from lamoom.ai_models.attempt_to_call import AttemptToCall
168
from lamoom.ai_models.openai.openai_models import (
179
C_128K,
@@ -22,7 +14,6 @@
2214
)
2315
from lamoom.ai_models.openai.azure_models import AzureAIModel
2416
from lamoom.ai_models.claude.claude_model import ClaudeAIModel
25-
from lamoom.ai_models.gemini.gemini_model import GeminiAIModel
2617
from lamoom.responses import AIResponse
2718
from lamoom.ai_models.openai.responses import OpenAIResponse
2819
from lamoom.ai_models.behaviour import AIModelsBehaviour, PromptAttempts

lamoom/ai_models/ai_model.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,24 @@
1010
class AI_MODELS_PROVIDER(Enum):
1111
OPENAI = "openai"
1212
AZURE = "azure"
13-
CLAUDE = ("claude",)
13+
CLAUDE = "claude"
1414
GEMINI = "gemini"
15+
NEBIUS = "nebius"
1516

1617

1718
@dataclass(kw_only=True)
1819
class AIModel:
19-
max_tokens: int
2020
tiktoken_encoding: t.Optional[str] = "cl100k_base"
2121
provider: AI_MODELS_PROVIDER = None
2222
support_functions: bool = False
23-
_price_per_prompt_1k_tokens: Decimal = None
24-
_price_per_sample_1k_tokens: Decimal = None
2523

2624
@property
2725
def name(self) -> str:
2826
return "undefined_aimodel"
2927

30-
@property
31-
def price_per_prompt_1k_tokens(self) -> Decimal:
32-
return self._price_per_prompt_1k_tokens
33-
3428
def _decimal(self, value) -> Decimal:
3529
return Decimal(value).quantize(Decimal(".00001"))
3630

37-
def get_prompt_price(self, count_tokens: int) -> Decimal:
38-
return self._decimal(
39-
self.price_per_prompt_1k_tokens * Decimal(count_tokens) / 1000
40-
)
41-
42-
def get_sample_price(self, prompt_sample, count_tokens: int) -> Decimal:
43-
return self._decimal(
44-
self.price_per_sample_1k_tokens * Decimal(count_tokens) / 1000
45-
)
46-
47-
@property
48-
def price_per_sample_1k_tokens(self) -> Decimal:
49-
return self._price_per_sample_1k_tokens
50-
5131
def get_params(self) -> t.Dict[str, t.Any]:
5232
return {}
5333

lamoom/ai_models/behaviour.py

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,51 +12,28 @@
1212

1313
@dataclass
1414
class AIModelsBehaviour:
15-
# if you have mutiple AI Models, you can distribute the load across them.
16-
# If you wish to use as a fallback attempt a model which is not in the list, you can use fallback_attempt
17-
attempts: list[AttemptToCall]
18-
fallback_attempt: AttemptToCall = None
15+
attempt: AttemptToCall
16+
fallback_attempts: list[AttemptToCall] = None
1917

2018

2119
@dataclass
2220
class PromptAttempts:
2321
ai_models_behaviour: AIModelsBehaviour
24-
count_of_retries: t.Optional[int] = None
25-
count: int = 0
2622
current_attempt: AttemptToCall = None
2723

28-
def __post_init__(self):
29-
if self.count_of_retries is None:
30-
self.count_of_retries = len(self.ai_models_behaviour.attempts) + int(
31-
bool(self.ai_models_behaviour.fallback_attempt)
32-
)
33-
34-
def initialize_attempt(self, flag_increase_count: bool = True):
35-
if self.count > self.count_of_retries:
36-
raise BehaviourIsNotDefined(
37-
f"Count of retries {self.count_of_retries} exceeded {self.count}"
38-
)
39-
if (
40-
self.count == self.count_of_retries
41-
and self.ai_models_behaviour.fallback_attempt
42-
):
43-
self.current_attempt = self.ai_models_behaviour.fallback_attempt
24+
def initialize_attempt(self):
25+
if self.current_attempt is None:
26+
self.current_attempt = self.ai_models_behaviour.attempt
27+
self.fallback_index = 0 # Start fallback index at 0
4428
return self.current_attempt
45-
sum_weight = sum(
46-
[attempt.weight for attempt in self.ai_models_behaviour.attempts]
47-
)
48-
random_weight = random.randint(0, sum_weight)
49-
for attempt in self.ai_models_behaviour.attempts:
50-
random_weight -= attempt.weight
51-
if random_weight <= 0:
52-
if flag_increase_count:
53-
self.count += 1
54-
attempt.attempt_number = self.count
55-
self.current_attempt = copy(attempt)
29+
elif self.ai_models_behaviour.fallback_attempts:
30+
if self.fallback_index < len(self.ai_models_behaviour.fallback_attempts):
31+
self.current_attempt = self.ai_models_behaviour.fallback_attempts[self.fallback_index]
32+
self.fallback_index += 1
5633
return self.current_attempt
57-
raise BehaviourIsNotDefined(
58-
f"Count of retries {self.count_of_retries} exceeded {self.count}"
59-
)
34+
else:
35+
self.current_attempt = None # No more fallback attempts left
36+
return None
6037

6138
def __str__(self) -> str:
6239
return f"Current attempt {self.current_attempt} from {len(self.ai_models_behaviour.attempts)}"

lamoom/ai_models/claude/claude_model.py

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER, AIModel
22
import logging
33

4-
from lamoom.ai_models.constants import C_200K
4+
from lamoom.ai_models.constants import C_200K, C_4K
55
from lamoom.responses import AIResponse
66
from decimal import Decimal
77
from enum import Enum
@@ -27,36 +27,10 @@ class FamilyModel(Enum):
2727
opus = "Claude 3 Opus"
2828

2929

30-
DEFAULT_PRICING = {
31-
"price_per_prompt_1k_tokens": Decimal(0.003),
32-
"price_per_sample_1k_tokens": Decimal(0.015),
33-
}
34-
35-
CLAUDE_AI_PRICING = {
36-
FamilyModel.haiku.value: {
37-
C_200K: {
38-
"price_per_prompt_1k_tokens": Decimal(0.00025),
39-
"price_per_sample_1k_tokens": Decimal(0.00125),
40-
}
41-
},
42-
FamilyModel.sonnet.value: {
43-
C_200K: {
44-
"price_per_prompt_1k_tokens": Decimal(0.003),
45-
"price_per_sample_1k_tokens": Decimal(0.015),
46-
}
47-
},
48-
FamilyModel.opus.value: {
49-
C_200K: {
50-
"price_per_prompt_1k_tokens": Decimal(0.015),
51-
"price_per_sample_1k_tokens": Decimal(0.075),
52-
}
53-
},
54-
}
55-
56-
5730
@dataclass(kw_only=True)
5831
class ClaudeAIModel(AIModel):
5932
model: str
33+
max_tokens: int = C_4K
6034
api_key: str = None
6135
provider: AI_MODELS_PROVIDER = AI_MODELS_PROVIDER.CLAUDE
6236
family: str = None
@@ -92,13 +66,10 @@ def uny_all_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict
9266
result[-1]["content"] += message.get("content")
9367
return result
9468

95-
def call(
96-
self,
97-
messages: t.List[dict],
98-
max_tokens: int,
99-
client_secrets: dict = {},
100-
**kwargs,
101-
) -> AIResponse:
69+
70+
def call(self, messages: t.List[dict], max_tokens: int, client_secrets: dict = {}, **kwargs) -> AIResponse:
71+
max_tokens = min(max_tokens, self.max_tokens)
72+
10273
common_args = get_common_args(max_tokens)
10374
kwargs = {
10475
**common_args,
@@ -152,25 +123,10 @@ def call(
152123
logger.exception("[CLAUDEAI] failed to handle chat stream", exc_info=e)
153124
raise RetryableCustomError(f"Claude AI call failed!")
154125

126+
@property
155127
def name(self) -> str:
156128
return self.model
157129

158-
@property
159-
def price_per_prompt_1k_tokens(self) -> Decimal:
160-
keys = list(CLAUDE_AI_PRICING[self.family].keys())
161-
def_pricing = CLAUDE_AI_PRICING[self.family].get(keys[0])
162-
return CLAUDE_AI_PRICING[self.family].get(self.max_tokens, def_pricing)[
163-
"price_per_prompt_1k_tokens"
164-
]
165-
166-
@property
167-
def price_per_sample_1k_tokens(self) -> Decimal:
168-
keys = list(CLAUDE_AI_PRICING[self.family].keys())
169-
def_pricing = CLAUDE_AI_PRICING[self.family].get(keys[0])
170-
return CLAUDE_AI_PRICING[self.family].get(self.max_tokens, def_pricing)[
171-
"price_per_sample_1k_tokens"
172-
]
173-
174130
def get_params(self) -> t.Dict[str, t.Any]:
175131
return {
176132
"model": self.model,

lamoom/ai_models/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
C_128K = 128_000
77
C_200K = 200_000
8-
C_1M = 1_000_000
8+
C_1M = 1_000_000

lamoom/ai_models/gemini/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)