Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
057e4fb
chore: Provider Unit Tests (#173)
MiNeves00 Dec 18, 2024
2cd3f94
[fix] bump prerelease version in pyproject.toml
actions-user Dec 18, 2024
cdeb84f
chore: rename action
diogoncalves Dec 18, 2024
f9feb73
feat: added action to run tests on PR
diogoncalves Dec 18, 2024
95c1723
chore: comments
diogoncalves Dec 18, 2024
8ea43c6
fix: fix azure config tests
diogoncalves Dec 18, 2024
9f42faa
chore: style format
diogoncalves Dec 18, 2024
91f232e
fix: tests workflow
diogoncalves Dec 18, 2024
ee7c373
Feature/prompt management (#200)
brunoalho99 Jan 23, 2025
549e388
[fix] bump prerelease version in pyproject.toml
actions-user Jan 23, 2025
c6cdad0
[bugfix] return empty prompt
brunoalho99 Jan 29, 2025
8d83a45
[bugfix] return empty prompt (#201)
brunoalho99 Jan 29, 2025
2a5b4fb
[fix] bump prerelease version in pyproject.toml
actions-user Jan 29, 2025
d330303
Update CONTRIBUTING.md
diogoncalves Jan 29, 2025
21e9352
Feat/ Use Openai Usage to calculate Cache and Reasoning Costs (#199)
MiNeves00 Jan 30, 2025
c23dfae
chore: update poetry.lock
diogoncalves Jan 30, 2025
2660006
chore: specify python versions
diogoncalves Jan 30, 2025
8fbe717
chore: moving langchain integration tests to sdk
diogoncalves Jan 30, 2025
29e39d8
chore: format
diogoncalves Jan 30, 2025
39443df
feat: added support for o3-mini and updated o1-mini prices. also upda…
MiNeves00 Feb 5, 2025
9610bcb
chore: removed duplicated code; removed duplicated integration tests
MiNeves00 Feb 10, 2025
7b9a866
chore: updated github actions to run integration tests
MiNeves00 Feb 10, 2025
c231e91
chore: fixing github actions
MiNeves00 Feb 10, 2025
0503810
chore: fixing github actions again
MiNeves00 Feb 10, 2025
30e4fa6
chore: fixing github actions again-x2
MiNeves00 Feb 10, 2025
91562b3
chore: fixing github actions again-x2
MiNeves00 Feb 10, 2025
545d990
chore: added cache of dependencies to integration-tests in githubaction
MiNeves00 Feb 10, 2025
ddc250f
chore: updated integration-tests action to inject github secrets into…
MiNeves00 Feb 10, 2025
83d7b55
Feat/bedrock support for Nova models through the ConverseAPI (#207)
MiNeves00 Feb 12, 2025
74e6b4f
[fix] bump prerelease version in pyproject.toml
actions-user Feb 12, 2025
7840ef4
[fix] bump prerelease version in pyproject.toml
actions-user Feb 12, 2025
65d0f22
[fix] bump prerelease version in pyproject.toml
actions-user Feb 12, 2025
162f1af
Update pyproject.toml
MiNeves00 Feb 12, 2025
10a604d
[fix] bump prerelease version in pyproject.toml
actions-user Feb 12, 2025
92223ea
chore: updated llmstudio sdk poetry.lock
MiNeves00 Feb 12, 2025
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
37 changes: 37 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Tests

on:
pull_request:
branches:
- main
types:
- opened
- synchronize
- reopened

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

# Set up Python environment
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.x"

# Install Poetry
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -

# Install lib and dev dependencies
- name: Install llmstudio-core
working-directory: ./libs/core
run: |
poetry install

- name: Run unit-tests
run: |
make unit-tests
2 changes: 1 addition & 1 deletion .github/workflows/upload-pypi-dev.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: PyPI prerelease and build/push Docker image.
name: PyPI prerelease any module.

on:
workflow_dispatch:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ env3
.env*
.env*.local
.venv*
*venv*
env*/
venv*/
ENV/
Expand All @@ -66,6 +67,7 @@ venv.bak/
config.yaml
bun.lockb


# Jupyter Notebook
.ipynb_checkpoints

Expand All @@ -76,4 +78,4 @@ bun.lockb
llmstudio/llm_engine/logs/execution_logs.jsonl
*.db
.prettierignore
db
db
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
format:
pre-commit run --all-files

unit-tests:
pytest libs/core/tests/unit_tests
137 changes: 118 additions & 19 deletions libs/core/llmstudio_core/providers/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,25 @@ async def agenerate_client(self, request: ChatRequest) -> Any:
return self.generate_client(request=request)

def generate_client(self, request: ChatRequest) -> Any:
"""Generate an AzureOpenAI client"""
"""
Generates an AzureOpenAI client for processing a chat request.

This method prepares and configures the arguments required to create a client
request to AzureOpenAI's chat completions API. It determines model-specific
configurations (e.g., whether tools or functions are enabled) and combines
these with the base arguments for the API call.

Args:
request (ChatRequest): The chat request object containing the model,
parameters, and other necessary details.

Returns:
Any: The result of the chat completions API call.

Raises:
ProviderError: If there is an issue with the API connection or an error
returned from the API.
"""

self.is_llama = "llama" in request.model.lower()
self.is_openai = "gpt" in request.model.lower()
Expand All @@ -72,15 +90,13 @@ def generate_client(self, request: ChatRequest) -> Any:
try:
messages = self.prepare_messages(request)

# Prepare the optional tool-related arguments
tool_args = {}
if not self.is_llama and self.has_tools and self.is_openai:
tool_args = {
"tools": request.parameters.get("tools"),
"tool_choice": "auto" if request.parameters.get("tools") else None,
}

# Prepare the optional function-related arguments
function_args = {}
if not self.is_llama and self.has_functions and self.is_openai:
function_args = {
Expand All @@ -90,14 +106,12 @@ def generate_client(self, request: ChatRequest) -> Any:
else None,
}

# Prepare the base arguments
base_args = {
"model": request.model,
"messages": messages,
"stream": True,
}

# Combine all arguments
combined_args = {
**base_args,
**tool_args,
Expand All @@ -116,13 +130,13 @@ def prepare_messages(self, request: ChatRequest):
if self.is_llama and (self.has_tools or self.has_functions):
user_message = self.convert_to_openai_format(request.chat_input)
content = "<|begin_of_text|>"
content = self.add_system_message(
content = self.build_llama_system_message(
user_message,
content,
request.parameters.get("tools"),
request.parameters.get("functions"),
)
content = self.add_conversation(user_message, content)
content = self.build_llama_conversation(user_message, content)
return [{"role": "user", "content": content}]
else:
return (
Expand All @@ -139,6 +153,20 @@ async def aparse_response(
yield chunk

def parse_response(self, response: AsyncGenerator, **kwargs) -> Any:
"""
Processes a generator response and yields processed chunks.

If `is_llama` is True and tools or functions are enabled, it processes the response
using `handle_tool_response`. Otherwise, it processes each chunk and yields only those
containing "choices".

Args:
response (Generator): The response generator to process.
**kwargs: Additional arguments for tool handling.

Yields:
Any: Processed response chunks.
"""
if self.is_llama and (self.has_tools or self.has_functions):
for chunk in self.handle_tool_response(response, **kwargs):
if chunk:
Expand Down Expand Up @@ -388,9 +416,25 @@ def convert_to_openai_format(self, message: Union[str, list]) -> list:
return [{"role": "user", "content": message}]
return message

def add_system_message(
def build_llama_system_message(
self, openai_message: list, llama_message: str, tools: list, functions: list
) -> str:
"""
Builds a complete system message for Llama based on OpenAI's message, tools, and functions.

If a system message is present in the OpenAI message, it is included in the result.
Otherwise, a default system message is used. Additional tool and function instructions
are appended if provided.

Args:
openai_message (list): List of OpenAI messages.
llama_message (str): The message to prepend to the system message.
tools (list): List of tools to include in the system message.
functions (list): List of functions to include in the system message.

Returns:
str: The formatted system message combined with Llama message.
"""
system_message = ""
system_message_found = False
for message in openai_message:
Expand All @@ -407,15 +451,31 @@ def add_system_message(
"""

if tools:
system_message = system_message + self.add_tool_instructions(tools)
system_message = system_message + self.build_tool_instructions(tools)

if functions:
system_message = system_message + self.add_function_instructions(functions)
system_message = system_message + self.build_function_instructions(
functions
)

end_tag = "\n<|eot_id|>"
return llama_message + system_message + end_tag

def add_tool_instructions(self, tools: list) -> str:
def build_tool_instructions(self, tools: list) -> str:
"""
Builds a detailed instructional prompt for tools available to the assistant.

This function generates a message describing the available tools, focusing on tools
of type "function." It explains to the LLM how to use each tool and provides an example of the
correct response format for function calls.

Args:
tools (list): A list of tool dictionaries, where each dictionary contains tool
details such as type, function name, description, and parameters.

Returns:
str: A formatted string detailing the tool instructions and usage examples.
"""
tool_prompt = """
You have access to the following tools:
"""
Expand Down Expand Up @@ -449,7 +509,21 @@ def add_tool_instructions(self, tools: list) -> str:

return tool_prompt

def add_function_instructions(self, functions: list) -> str:
def build_function_instructions(self, functions: list) -> str:
"""
Builds a detailed instructional prompt for available functions.

This method creates a message describing the functions accessible to the assistant.
It includes the function name, description, and required parameters, along with
specific guidelines for calling functions.

Args:
functions (list): A list of function dictionaries, each containing details such as
name, description, and parameters.

Returns:
str: A formatted string with instructions on using the provided functions.
"""
function_prompt = """
You have access to the following functions:
"""
Expand Down Expand Up @@ -479,35 +553,60 @@ def add_function_instructions(self, functions: list) -> str:
"""
return function_prompt

def add_conversation(self, openai_message: list, llama_message: str) -> str:
def build_llama_conversation(self, openai_message: list, llama_message: str) -> str:
"""
Appends the OpenAI message to the Llama message while formatting OpenAI messages.

This function iterates through a list of OpenAI messages and formats them for inclusion
in a Llama message. It handles user messages that might include nested content (lists of
messages) by safely evaluating the content. System messages are skipped.

Args:
openai_message (list): A list of dictionaries representing the OpenAI messages. Each
dictionary should have "role" and "content" keys.
llama_message (str): The initial Llama message to which the conversation is appended.

Returns:
str: The Llama message with the conversation appended.
"""
conversation_parts = []
for message in openai_message:
if message["role"] == "system":
continue
elif message["role"] == "user" and isinstance(message["content"], str):
try:
# Attempt to safely evaluate the string to a Python object
content_as_list = ast.literal_eval(message["content"])
if isinstance(content_as_list, list):
# If the content is a list, process each nested message
for nested_message in content_as_list:
conversation_parts.append(
self.format_message(nested_message)
)
else:
# If the content is not a list, append it directly
conversation_parts.append(self.format_message(message))
except (ValueError, SyntaxError):
# If evaluation fails or content is not a list/dict string, append the message directly
conversation_parts.append(self.format_message(message))
else:
# For all other messages, use the existing formatting logic
conversation_parts.append(self.format_message(message))

return llama_message + "".join(conversation_parts)

def format_message(self, message: dict) -> str:
"""Format a single message for the conversation."""
"""
Formats a single message dictionary into a structured string for a conversation.

The formatting depends on the content of the message, such as tool calls,
function calls, or simple user/assistant messages. Each type of message
is formatted with specific headers and tags.

Args:
message (dict): A dictionary containing message details. Expected keys
include "role", "content", and optionally "tool_calls",
"tool_call_id", or "function_call".

Returns:
str: A formatted string representing the message. Returns an empty
string if the message cannot be formatted.
"""
if "tool_calls" in message:
for tool_call in message["tool_calls"]:
function_name = tool_call["function"]["name"]
Expand Down
Loading
Loading