Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion SimplerLLM/language/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import SimplerLLM.language.llm_providers.anthropic_llm as anthropic_llm
import SimplerLLM.language.llm_providers.ollama_llm as ollama_llm
import SimplerLLM.language.llm_providers.lwh_llm as lwh_llm
import SimplerLLM.language.llm_providers.azureopenai_llm as azureopenai_llm

from SimplerLLM.prompts.messages_template import MessagesTemplate
from enum import Enum
import os
Expand All @@ -14,6 +16,7 @@ class LLMProvider(Enum):
ANTHROPIC = 3
OLLAMA = 4
LWH = 5
AZUREOPENAI = 6


class LLM:
Expand Down Expand Up @@ -53,6 +56,9 @@ def create(
return OllamaLLM(provider, model_name, temperature, top_p)
if provider == LLMProvider.LWH:
return LwhLLM(provider, model_name, temperature, top_p, api_key, user_id)
if provider == LLMProvider.AZUREOPENAI:
return AzureOpenAILLM(provider, model_name, temperature, top_p, api_key)

else:
return None

Expand All @@ -69,6 +75,100 @@ def prepare_params(self, model_name, temperature, top_p):
"top_p": top_p if top_p else self.top_p,
}

class AzureOpenAILLM(LLM):
def __init__(self, provider, model_name, temperature, top_p, api_key):
super().__init__(provider, model_name, temperature, top_p, api_key)
self.api_key = api_key or os.getenv("AZUREOPENAI_API_KEY", "")


def append_messages(self, system_prompt : str, messages: list):
model_messages = [{"role": "system", "content": system_prompt}]
if messages:
model_messages.extend(messages)
return model_messages



def generate_response(
self,
model_name: str =None,
prompt: str = None,
messages: list = None,
system_prompt: str="You are a helpful AI Assistant",
temperature: float=0.7,
max_tokens: int=300,
top_p: float=1.0,
full_response: bool=False,
):
params = self.prepare_params(model_name, temperature, top_p)

# Validate inputs
if prompt and messages:
raise ValueError("Only one of 'prompt' or 'messages' should be provided.")
if not prompt and not messages:
raise ValueError("Either 'prompt' or 'messages' must be provided.")

# Prepare messages based on input type
if prompt:
model_messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
]

if messages:
model_messages = self.append_messages(system_prompt, messages)



params.update(
{
"api_key": self.api_key,
"messages": model_messages,
"max_tokens": max_tokens,
"full_response": full_response,
}
)
return azureopenai_llm.generate_response(**params)

async def generate_response_async(
self,
model_name: str =None,
prompt: str = None,
messages: list = None,
system_prompt: str="You are a helpful AI Assistant",
temperature: float=0.7,
max_tokens: int=300,
top_p: float=1.0,
full_response: bool=False,
):
params = self.prepare_params(model_name, temperature, top_p)

# Validate inputs
if prompt and messages:
raise ValueError("Only one of 'prompt' or 'messages' should be provided.")
if not prompt and not messages:
raise ValueError("Either 'prompt' or 'messages' must be provided.")

# Prepare messages based on input type
if prompt:
model_messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
]

if messages:
model_messages = self.append_messages(system_prompt, messages)


params.update(
{
"api_key": self.api_key,
"messages": model_messages,
"max_tokens": max_tokens,
"full_response": full_response,
}
)
return await azureopenai_llm.generate_response_async(**params)



Expand Down Expand Up @@ -563,4 +663,4 @@ async def generate_response_async(
"max_tokens" : max_tokens
}
)
return await lwh_llm.generate_response_async(**params)
return await lwh_llm.generate_response_async(**params)
211 changes: 211 additions & 0 deletions SimplerLLM/language/llm_providers/azureopenai_llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# add streaming
from openai import AsyncAzureOpenAI
from openai import AzureOpenAI
from dotenv import load_dotenv
import asyncio
import os
import time
from .llm_response_models import LLMFullResponse,LLMEmbeddingsResponse

# Load environment variables
load_dotenv()

MAX_RETRIES = int(os.getenv("MAX_RETRIES", 3))
RETRY_DELAY = int(os.getenv("RETRY_DELAY", 2))


def generate_response(
model_name,
messages=None,
temperature=0.7,
max_tokens=300,
top_p=1.0,
full_response=False,
api_key = None,
):
start_time = time.time() if full_response else None
openai_client = AzureOpenAI(api_key=api_key,
api_version="2024-07-01-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")
Comment on lines +28 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Azure OpenAI API version needs to be updated to a stable version

The code currently uses the preview version "2024-07-01-preview" which is a future date and appears to be incorrect. According to Azure OpenAI's current versioning scheme, you should use a stable version like "2023-05-15" or check the Azure OpenAI documentation for the latest stable version.

  • In SimplerLLM/language/llm_providers/azureopenai_llm.py, update the api_version in all four instances where it's set to "2024-07-01-preview"
🔗 Analysis chain

Verify the correctness of the api_version.

The api_version is set to "2024-07-01-preview". Please confirm that this is the correct API version for Azure OpenAI. The latest available version might be different, which could affect compatibility.

You can run the following script to list available API versions:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: List available API versions for Azure OpenAI.

# Note: Replace '<resource-group>' and '<resource-name>' with your actual resource group and resource name.
az openai account deployment list --resource-group <resource-group> --resource-name <resource-name> --query '[].apiVersion'

Length of output: 181


Script:

#!/bin/bash
# Let's check the Azure OpenAI documentation or configuration files in the codebase
rg -i "api[_-]version.*azure.*openai" -g "*.{py,json,yaml,yml,md,txt}" -B 2 -A 2

Length of output: 82


Script:

#!/bin/bash
# Search for any Azure OpenAI related configuration or constants
ast-grep --pattern 'api_version = "$_"'

Length of output: 41


Script:

#!/bin/bash
# Look for any Azure OpenAI related documentation or README files
fd -e md -e txt -e rst | xargs rg -i "azure.*openai" -B 2 -A 2

Length of output: 2224


Script:

#!/bin/bash
# Let's check the specific file content to understand the context better
cat SimplerLLM/language/llm_providers/azureopenai_llm.py

Length of output: 7566


Script:

#!/bin/bash
# Search for any version-related imports or configurations
rg -i "2024-07-01-preview" -g "*.{py,json,yaml,yml,md,txt}" -B 2 -A 2

Length of output: 2219

)

for attempt in range(MAX_RETRIES):
try:
completion = openai_client.chat.completions.create(
model=model_name,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p,
)
generated_text = completion.choices[0].message.content

if full_response:
end_time = time.time()
process_time = end_time - start_time
return LLMFullResponse(
generated_text=generated_text,
model=model_name,
process_time=process_time,
llm_provider_response=completion,
)
return generated_text

except Exception as e:
if attempt < MAX_RETRIES - 1:
time.sleep(RETRY_DELAY * (2**attempt))
else:
error_msg = f"Failed after {MAX_RETRIES} attempts"
if full_response:
end_time = time.time()
process_time = end_time - start_time
error_msg += f" and {process_time} seconds"
error_msg += f" due to: {e}"
print(error_msg)
return None
Comment on lines +54 to +65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve exception handling and use proper logging.

Currently, errors are being printed using print(error_msg), and the function returns None on failure. Consider using the logging module for error messages and raising exceptions to inform the caller of failures.

Apply the following changes:

  • Import the logging module and configure it as needed.
  • Replace print(error_msg) with logging.error(error_msg).
  • Instead of returning None, raise an exception with the error message.

Example diff:

+import logging

...

54          except Exception as e:
55              if attempt < MAX_RETRIES - 1:
56                  time.sleep(RETRY_DELAY * (2**attempt))
57              else:
58                  error_msg = f"Failed after {MAX_RETRIES} attempts"
59                  if full_response:
60                      end_time = time.time()
61                      process_time = end_time - start_time
62                      error_msg += f" and {process_time} seconds"
63                  error_msg += f" due to: {e}"
-64                  print(error_msg)
-65                  return None
+64                  logging.error(error_msg)
+65                  raise Exception(error_msg)

Committable suggestion skipped: line range outside the PR's diff.


async def generate_response_async(
model_name,
messages=None,
temperature=0.7,
max_tokens=300,
top_p=1.0,
full_response=False,
api_key = None,
):
start_time = time.time() if full_response else None
async_openai_client = AsyncAzureOpenAI(api_key=api_key,
api_version="2024-07-01-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")
)

for attempt in range(MAX_RETRIES):
try:
completion = await async_openai_client.chat.completions.create(
model=model_name,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p,
)
generated_text = completion.choices[0].message.content

if full_response:
end_time = time.time()
process_time = end_time - start_time
return LLMFullResponse(
generated_text=generated_text,
model=model_name,
process_time=process_time,
llm_provider_response=completion,
)
return generated_text

except Exception as e:
if attempt < MAX_RETRIES - 1:
await asyncio.sleep(RETRY_DELAY * (2**attempt))
else:
error_msg = f"Failed after {MAX_RETRIES} attempts"
if full_response:
end_time = time.time()
process_time = end_time - start_time
error_msg += f" and {process_time} seconds"
error_msg += f" due to: {e}"
print(error_msg)
return None

Comment on lines +67 to +116
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling in generate_response_async.

The asynchronous function generate_response_async uses print statements for error messages and returns None on failure. Align the error handling with the synchronous version by using the logging module and raising exceptions.

Apply similar changes as suggested for generate_response:

  • Use logging.error(error_msg) for error messages.
  • Raise an exception instead of returning None.

def generate_embeddings(
model_name,
user_input=None,
full_response = False,
api_key = None
):
Comment on lines +117 to +122
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for model_name in generate_embeddings.

The model_name parameter is critical for the API call but lacks validation. Ensure that model_name is provided and is a non-empty string.

Apply this diff to add validation:

117 def generate_embeddings(
118     model_name,
119     user_input=None,
120     full_response=False,
121     api_key=None
122 ):
+    if not model_name:
+        raise ValueError("model_name must be provided and cannot be empty.")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def generate_embeddings(
model_name,
user_input=None,
full_response = False,
api_key = None
):
def generate_embeddings(
model_name,
user_input=None,
full_response=False,
api_key=None
):
if not model_name:
raise ValueError("model_name must be provided and cannot be empty.")


if not user_input:
raise ValueError("user_input must be provided.")

start_time = time.time() if full_response else None

openai_client = AzureOpenAI(api_key=api_key,
api_version="2024-07-01-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")
)

for attempt in range(MAX_RETRIES):
try:

response = openai_client.embeddings.create(
model= model_name,
input=user_input
)
generate_embeddings = response.data

if full_response:
end_time = time.time()
process_time = end_time - start_time
return LLMEmbeddingsResponse(
generated_embedding=generate_embeddings,
model=model_name,
process_time=process_time,
llm_provider_response=response,
)
return generate_embeddings

except Exception as e:
if attempt < MAX_RETRIES - 1:
time.sleep(RETRY_DELAY * (2**attempt))
else:
error_msg = f"Failed after {MAX_RETRIES} attempts"
if full_response:
end_time = time.time()
process_time = end_time - start_time
error_msg += f" and {process_time} seconds"
error_msg += f" due to: {e}"
print(error_msg)
return None
Comment on lines +154 to +165
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve exception handling and use proper logging in generate_embeddings.

Similar to previous suggestions, replace print(error_msg) with logging.error(error_msg) and raise exceptions instead of returning None.

Apply the following changes:

+import logging

...

154         except Exception as e:
155             if attempt < MAX_RETRIES - 1:
156                 time.sleep(RETRY_DELAY * (2**attempt))
157             else:
158                 error_msg = f"Failed after {MAX_RETRIES} attempts"
159                 if full_response:
160                     end_time = time.time()
161                     process_time = end_time - start_time
162                     error_msg += f" and {process_time} seconds"
163                 error_msg += f" due to: {e}"
-164                 print(error_msg)
-165                 return None
+164                 logging.error(error_msg)
+165                 raise Exception(error_msg)

Committable suggestion skipped: line range outside the PR's diff.


async def generate_embeddings_async(
model_name,
user_input=None,
full_response = False,
api_key = None,
):
Comment on lines +167 to +172
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for model_name in generate_embeddings_async.

Similar to the synchronous version, ensure that model_name is provided and is a non-empty string.

Apply this diff:

167 async def generate_embeddings_async(
168     model_name,
169     user_input=None,
170     full_response=False,
171     api_key=None,
172 ):
+    if not model_name:
+        raise ValueError("model_name must be provided and cannot be empty.")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def generate_embeddings_async(
model_name,
user_input=None,
full_response = False,
api_key = None,
):
async def generate_embeddings_async(
model_name,
user_input=None,
full_response=False,
api_key=None,
):
if not model_name:
raise ValueError("model_name must be provided and cannot be empty.")

async_openai_client = AsyncAzureOpenAI(api_key=api_key,
api_version="2024-07-01-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")
)
if not user_input:
raise ValueError("user_input must be provided.")

start_time = time.time() if full_response else None
for attempt in range(MAX_RETRIES):
try:
result = await async_openai_client.embeddings.create(
model=model_name,
input=user_input,
)
generate_embeddings = result.data

if full_response:
end_time = time.time()
process_time = end_time - start_time
return LLMEmbeddingsResponse(
generated_embedding=generate_embeddings,
model=model_name,
process_time=process_time,
llm_provider_response=result,
)
return generate_embeddings

except Exception as e:
if attempt < MAX_RETRIES - 1:
await asyncio.sleep(RETRY_DELAY * (2**attempt))
else:
error_msg = f"Failed after {MAX_RETRIES} attempts"
if full_response:
end_time = time.time()
process_time = end_time - start_time
error_msg += f" and {process_time} seconds"
error_msg += f" due to: {e}"
print(error_msg)
return None
Comment on lines +200 to +211
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling in generate_embeddings_async.

Align the asynchronous embeddings function with the error handling practices used elsewhere:

  • Use logging.error(error_msg) for error messages.
  • Raise exceptions instead of returning None.

Apply the following changes:

+import logging

...

200         except Exception as e:
201             if attempt < MAX_RETRIES - 1:
202                 await asyncio.sleep(RETRY_DELAY * (2**attempt))
203             else:
204                 error_msg = f"Failed after {MAX_RETRIES} attempts"
205                 if full_response:
206                     end_time = time.time()
207                     process_time = end_time - start_time
208                     error_msg += f" and {process_time} seconds"
209                 error_msg += f" due to: {e}"
-210                 print(error_msg)
-211                 return None
+210                 logging.error(error_msg)
+211                 raise Exception(error_msg)

Committable suggestion skipped: line range outside the PR's diff.

3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ VALUE_SERP_API_KEY="your_value_serp_api_key_here" #for Google search
SERPER_API_KEY="your_serper_api_key_here" #for Google search
STABILITY_API_KEY="your_stability_api_key_here" #for image generation

AZUREOPENAI_API_KEY="your_azureopenai_api_key_here"
AZURE_OPENAI_ENDPOINT='your_azureopenai_endpoint_url_here'

Comment on lines +50 to +52
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Standardize environment variable naming and add deployment name.

The Azure OpenAI configuration needs some adjustments:

  1. Standardize the naming convention: AZUREOPENAI_API_KEY should be AZURE_OPENAI_API_KEY to match the endpoint variable style
  2. Add the required AZURE_OPENAI_DEPLOYMENT_NAME environment variable which is typically needed for Azure OpenAI API calls
-AZUREOPENAI_API_KEY="your_azureopenai_api_key_here"
+AZURE_OPENAI_API_KEY="your_azure_openai_api_key_here"
 AZURE_OPENAI_ENDPOINT='your_azureopenai_endpoint_url_here'
+AZURE_OPENAI_DEPLOYMENT_NAME='your_azure_openai_deployment_name_here'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AZUREOPENAI_API_KEY="your_azureopenai_api_key_here"
AZURE_OPENAI_ENDPOINT='your_azureopenai_endpoint_url_here'
AZURE_OPENAI_API_KEY="your_azure_openai_api_key_here"
AZURE_OPENAI_ENDPOINT='your_azureopenai_endpoint_url_here'
AZURE_OPENAI_DEPLOYMENT_NAME='your_azure_openai_deployment_name_here'

🛠️ Refactor suggestion

Add Azure OpenAI usage example in code samples.

The environment variables are added but there's no example showing how to use Azure OpenAI in the "Creating an LLM Instance" section.

Add this example to the code samples section:

 # For Anthropic Claude 
 #llm_instance = LLM.create(LLMProvider.ANTHROPIC, model_name="claude-3-opus-20240229")
+
+# For Azure OpenAI
+#llm_instance = LLM.create(provider=LLMProvider.AZURE_OPENAI, model_name="your-deployment-model")
 
 response = llm_instance.generate_response(prompt="generate a 5 words sentence")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AZUREOPENAI_API_KEY="your_azureopenai_api_key_here"
AZURE_OPENAI_ENDPOINT='your_azureopenai_endpoint_url_here'
# For Anthropic Claude
#llm_instance = LLM.create(LLMProvider.ANTHROPIC, model_name="claude-3-opus-20240229")
# For Azure OpenAI
#llm_instance = LLM.create(provider=LLMProvider.AZURE_OPENAI, model_name="your-deployment-model")
response = llm_instance.generate_response(prompt="generate a 5 words sentence")

```

### Creating an LLM Instance
Expand Down