Skip to content

Commit

Permalink
Feat/issue 154 llama support (#155)
Browse files Browse the repository at this point in the history
* #154 feat - add Llama support and update related configurations

* #154 adding llama support to model configuration and settings

* #154 feat - added tests for llama model commit message generation

* #154 feat - added support for a new model in services

* #154 feat - update README for llama model configuration instructions

* #154 style - fix coding style in test service file

* #154 style - Removed blank line in test file formatting
  • Loading branch information
RyanFloresTT authored Dec 20, 2024
1 parent b4988bc commit d71da19
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 45 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,38 @@ Use `git log -1` to review the last commit details and find the automatically ge

---

### Model Configuration and Settings

To configure and use models with `dotnet-aicommitmessage`, users need to set their settings once. This setup involves specifying the model, API key, and API URL. These settings will be stored as environment variables for future use.

#### Initial Setup

Run the following commands to configure the model and related settings:

```
dotnet-aicommitmessage set-settings -m gpt-4o-mini -k {api-key} -u {api-url}
dotnet-aicommitmessage set-settings -m llama-3-1-405b-instruct -k {api-key} -u {api-url}
```

Replace `{api-key}` with your API key and `{api-url}` with the URL of your API provider.

#### Switching Models

After the initial setup, you can easily switch between models without needing to provide the API key or URL again:

```
dotnet-aicommitmessage set-settings -m gpt-4o-mini
dotnet-aicommitmessage set-settings -m llama-3-1-405b-instruct
```

This allows for quick model changes while retaining your previously configured API details.

#### Supported Models

Currently supported models are `gpt-4o-mini` and `llama-3-1-405b-instruct`.

---

## Commit message pattern

The training model for the AI used is designed using as reference these guidelines:
Expand Down
1 change: 1 addition & 0 deletions Src/AiCommitMessage/AiCommitMessage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<None Remove="CommandLineParser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Inference" Version="1.0.0-beta.2" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="OpenAI" Version="2.1.0" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
Expand Down
10 changes: 5 additions & 5 deletions Src/AiCommitMessage/Options/SetSettingsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@ namespace AiCommitMessage.Options;
/// <summary>
/// Class SetSettingsOptions.
/// </summary>
[Verb("set-settings", HelpText = "Set the OpenAI settings.")]
[Verb("set-settings", HelpText = "Set the AI model settings.")]
public class SetSettingsOptions
{
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
[Option('u', "url", Required = false, HelpText = "The OpenAI url.")]
[Option('u', "url", Required = false, HelpText = "The model url.")]
public string Url { get; set; }

/// <summary>
/// Gets or sets the key.
/// </summary>
/// <value>The key.</value>
[Option('k', "key", Required = false, HelpText = "The OpenAI API key.")]
[Option('k', "key", Required = false, HelpText = "The model API key.")]
public string Key { get; set; }

/// <summary>
/// Gets or sets the model.
/// </summary>
/// <value>The model.</value>
[Option('m', "model", Required = false, HelpText = "The OpenAI model.")]
[Option('m', "model", Required = false, HelpText = "The model name (e.g., GPT-4o, Llama-3-1-405B-Instruct).")]
public string Model { get; set; }

/// <summary>
Expand All @@ -40,6 +40,6 @@ public class SetSettingsOptions
/// Gets or sets a value indicating whether [save encrypted].
/// </summary>
/// <value><c>true</c> if [save encrypted]; otherwise, <c>false</c>.</value>
[Option('e', "encrypted", Required = false, HelpText = "Persiste key encrypted or plain-text.")]
[Option('e', "encrypted", Required = false, HelpText = "Persist key encrypted or plain-text.")]
public bool SaveEncrypted { get; set; }
}
95 changes: 73 additions & 22 deletions Src/AiCommitMessage/Services/GenerateCommitMessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Text.RegularExpressions;
using AiCommitMessage.Options;
using AiCommitMessage.Utility;
using Azure;
using Azure.AI.Inference;
using OpenAI;
using OpenAI.Chat;

Expand Down Expand Up @@ -37,10 +39,10 @@ private static bool IsMergeConflictResolution(string message) =>
/// Generates a commit message based on the provided options and the OpenAI API.
/// </summary>
/// <param name="options">An instance of <see cref="GenerateCommitMessageOptions"/> containing the branch name, original message, and git diff.</param>
/// <returns>A string containing the generated commit message from the OpenAI API.</returns>
/// <returns>A string containing the generated commit message from the API.</returns>
/// <remarks>
/// This method retrieves API details (URL and key) from environment variables, constructs a message including the branch name,
/// original commit message, and git diff, and sends it to the OpenAI API for processing. It also handles debugging, saving
/// original commit message, and git diff, and sends it to the respective API for processing. It also handles debugging, saving
/// API responses to a JSON file if debugging is enabled. If the commit message is a merge conflict resolution, it is returned as-is.
/// </remarks>
/// <exception cref="InvalidOperationException">Thrown if both the branch and diff are empty, as meaningful commit generation is not possible.</exception>
Expand Down Expand Up @@ -74,23 +76,74 @@ public string GenerateCommitMessage(GenerateCommitMessageOptions options)
+ "Git Diff: "
+ (string.IsNullOrEmpty(diff) ? "<no changes>" : diff);

var model = EnvironmentLoader.LoadOpenAiModel();
var url = EnvironmentLoader.LoadOpenAiApiUrl();
var key = EnvironmentLoader.LoadOpenAiApiKey();
var model = EnvironmentLoader.LoadModelName();
return GenerateWithModel(model, formattedMessage, branch, message, options.Debug);
}

private static string GenerateWithModel(string model, string formattedMessage, string branch, string message, bool debug)
{
string text;

if (model.Equals("llama-3-1-405B-Instruct", StringComparison.OrdinalIgnoreCase))
{
var endpoint = new Uri(EnvironmentLoader.LoadLlamaApiUrl());
var credential = new AzureKeyCredential(EnvironmentLoader.LoadLlamaApiKey());

var client = new ChatCompletionsClient(endpoint, credential, new AzureAIInferenceClientOptions());

var requestOptions = new ChatCompletionsOptions
{
Messages =
{
new ChatRequestSystemMessage(Constants.SystemMessage),
new ChatRequestUserMessage(formattedMessage),
},
Temperature = 1.0f,
NucleusSamplingFactor = 1.0f,
MaxTokens = 1000,
Model = "Meta-Llama-3.1-405B-Instruct"
};

var response = client.Complete(requestOptions);
text = response.Value.Content;
}
else if (model.Equals("gpt-4o-mini", StringComparison.OrdinalIgnoreCase))
{
var apiUrl = EnvironmentLoader.LoadOpenAiApiUrl();
var apiKey = EnvironmentLoader.LoadOpenAiApiKey();

var client = new ChatClient(
"gpt-4o-mini",
new ApiKeyCredential(apiKey),
new OpenAIClientOptions { Endpoint = new Uri(apiUrl) }
);

var chatCompletion = client.CompleteChat(
new SystemChatMessage(Constants.SystemMessage),
new UserChatMessage(formattedMessage)
);

text = chatCompletion.Value.Content[0].Text;
}
else
{
throw new NotSupportedException($"Model '{model}' is not supported.");
}

var client = new ChatClient(
model,
new ApiKeyCredential(key),
new OpenAIClientOptions { Endpoint = new Uri(url) }
);
text = ProcessGeneratedMessage(text, branch, message);

var chatCompletion = client.CompleteChat(
new SystemChatMessage(Constants.SystemMessage),
new UserChatMessage(formattedMessage)
);
if (!debug)
{
return text;
}

var text = chatCompletion.Value.Content[0].Text;
SaveDebugInfo(text);

return text;
}

private static string ProcessGeneratedMessage(string text, string branch, string message)
{
if (text.Length >= 7 && text[..7] == "type - ")
{
text = text[7..];
Expand Down Expand Up @@ -120,15 +173,13 @@ public string GenerateCommitMessage(GenerateCommitMessageOptions options)
text = $"{text} {gitVersionCommand}";
}

if (!options.Debug)
{
return text;
}
return text;
}

var json = JsonSerializer.Serialize(chatCompletion);
private static void SaveDebugInfo(string text)
{
var json = JsonSerializer.Serialize(new { DebugInfo = text });
File.WriteAllText("debug.json", json);

return text;
}

/// <summary>
Expand Down
54 changes: 43 additions & 11 deletions Src/AiCommitMessage/Services/SettingsService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using AiCommitMessage.Options;
using AiCommitMessage.Utility;
using Spectre.Console;

namespace AiCommitMessage.Services;

Expand All @@ -17,21 +19,51 @@ public static class SettingsService
/// </remarks>
public static void SetSettings(SetSettingsOptions setSettingsOptions)
{
Environment.SetEnvironmentVariable(
"OPENAI_API_KEY",
setSettingsOptions.Key,
EnvironmentVariableTarget.User
);
if (!string.IsNullOrWhiteSpace(setSettingsOptions.Model))
{
Environment.SetEnvironmentVariable(
"AI_MODEL",
setSettingsOptions.Model,
EnvironmentVariableTarget.User
);
}

var model = EnvironmentLoader.LoadModelName();

if (model.Equals("gpt-4o-mini", StringComparison.OrdinalIgnoreCase))
{
EnvironmentLoader.SetEnvironmentVariableIfProvided(
"OPENAI_API_KEY",
setSettingsOptions.Key,
EnvironmentLoader.LoadOpenAiApiKey()
);

if (string.IsNullOrWhiteSpace(setSettingsOptions.Url))
EnvironmentLoader.SetEnvironmentVariableIfProvided(
"OPENAI_API_URL",
setSettingsOptions.Url,
EnvironmentLoader.LoadOpenAiApiUrl()
);
}
else if (model.Equals("llama-3-1-405B-Instruct", StringComparison.OrdinalIgnoreCase))
{
EnvironmentLoader.SetEnvironmentVariableIfProvided(
"LLAMA_API_KEY",
setSettingsOptions.Key,
EnvironmentLoader.LoadLlamaApiKey()
);

EnvironmentLoader.SetEnvironmentVariableIfProvided(
"LLAMA_API_URL",
setSettingsOptions.Url,
EnvironmentLoader.LoadLlamaApiUrl()
);
}
else
{
AnsiConsole.MarkupLine($"[red]{model} is not supported.[/]");
return;
}

Environment.SetEnvironmentVariable(
"OPEN_API_URL",
setSettingsOptions.Url,
EnvironmentVariableTarget.User
);
AnsiConsole.MarkupLine($"[green]Successfully switched to {model}[/]");
}
}
46 changes: 39 additions & 7 deletions Src/AiCommitMessage/Utility/EnvironmentLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static class EnvironmentLoader
/// This allows for flexibility in specifying which model to use without hardcoding it into the application.
/// It is particularly useful in scenarios where different models may be used in different environments, such as development, testing, or production.
/// </remarks>
public static string LoadOpenAiModel() => GetEnvironmentVariable("OPENAI_MODEL", "gpt-4o-mini");
public static string LoadModelName() => GetEnvironmentVariable("AI_MODEL", "gpt-4o-mini");

/// <summary>
/// Loads the OpenAI API URL from the environment variables.
Expand All @@ -33,10 +33,10 @@ public static string LoadOpenAiApiUrl() =>
GetEnvironmentVariable("OPENAI_API_URL", "https://api.openai.com/v1");

/// <summary>
/// Loads the OpenAI API key.
/// Loads the OpenAI API key from the environment variables.
/// </summary>
/// <returns>System.String.</returns>
/// <exception cref="InvalidOperationException">Please set the OPENAI_API_KEY environment variable.</exception>
/// <returns>A string representing the OpenAI API key.</returns>
/// <exception cref="InvalidOperationException">Thrown if the API key is not set in the environment variables.</exception>
public static string LoadOpenAiApiKey()
{
var encryptStr = GetEnvironmentVariable("OPENAI_KEY_ENCRYPTED", "false");
Expand All @@ -55,7 +55,21 @@ public static string LoadOpenAiApiKey()
}

/// <summary>
/// Loads the optional emoji.
/// Loads the Llama API key from the environment variables.
/// </summary>
/// <returns>A string representing the Llama API key.</returns>
public static string LoadLlamaApiKey() =>
GetEnvironmentVariable("LLAMA_API_KEY", string.Empty);

/// <summary>
/// Loads the Llama API URL from the environment variables.
/// </summary>
/// <returns>A string representing the Llama API URL.</returns>
public static string LoadLlamaApiUrl() =>
GetEnvironmentVariable("LLAMA_API_URL", string.Empty);

/// <summary>
/// Loads the optional emoji setting from the environment variables.
/// </summary>
/// <returns><c>true</c> if should include emoji in the commit message, <c>false</c> otherwise.</returns>
public static bool LoadOptionalEmoji() =>
Expand All @@ -64,8 +78,8 @@ public static bool LoadOptionalEmoji() =>
/// <summary>
/// Decrypts the specified encrypted text.
/// </summary>
/// <param name="encryptedText">The encrypted text.</param>
/// <returns>System.String.</returns>
/// <param name="encryptedText">The encrypted text to decrypt.</param>
/// <returns>A string representing the decrypted text.</returns>
private static string Decrypt(string encryptedText)
{
// Placeholder for decryption logic
Expand Down Expand Up @@ -96,4 +110,22 @@ private static string GetEnvironmentVariable(string name, string defaultValue)

return !string.IsNullOrWhiteSpace(value) ? value : defaultValue;
}

public static void SetEnvironmentVariableIfProvided(
string variableName,
string newValue,
string existingValue
)
{
if (!string.IsNullOrWhiteSpace(newValue))
{
Environment.SetEnvironmentVariable(variableName, newValue, EnvironmentVariableTarget.User);
Environment.SetEnvironmentVariable(variableName, newValue, EnvironmentVariableTarget.Process);
}
else if (!string.IsNullOrWhiteSpace(existingValue))
{
Environment.SetEnvironmentVariable(variableName, existingValue, EnvironmentVariableTarget.User);
Environment.SetEnvironmentVariable(variableName, existingValue, EnvironmentVariableTarget.Process);
}
}
}
Loading

0 comments on commit d71da19

Please sign in to comment.