diff --git a/README.md b/README.md
index 4c4ee6c6..e43562cd 100644
--- a/README.md
+++ b/README.md
@@ -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:
diff --git a/Src/AiCommitMessage/AiCommitMessage.csproj b/Src/AiCommitMessage/AiCommitMessage.csproj
index 2da99183..7557acc8 100644
--- a/Src/AiCommitMessage/AiCommitMessage.csproj
+++ b/Src/AiCommitMessage/AiCommitMessage.csproj
@@ -28,6 +28,7 @@
+
diff --git a/Src/AiCommitMessage/Options/SetSettingsOptions.cs b/Src/AiCommitMessage/Options/SetSettingsOptions.cs
index 0934bf52..1a757ed6 100644
--- a/Src/AiCommitMessage/Options/SetSettingsOptions.cs
+++ b/Src/AiCommitMessage/Options/SetSettingsOptions.cs
@@ -5,28 +5,28 @@ namespace AiCommitMessage.Options;
///
/// Class SetSettingsOptions.
///
-[Verb("set-settings", HelpText = "Set the OpenAI settings.")]
+[Verb("set-settings", HelpText = "Set the AI model settings.")]
public class SetSettingsOptions
{
///
/// Gets or sets the URL.
///
/// The URL.
- [Option('u', "url", Required = false, HelpText = "The OpenAI url.")]
+ [Option('u', "url", Required = false, HelpText = "The model url.")]
public string Url { get; set; }
///
/// Gets or sets the key.
///
/// The key.
- [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; }
///
/// Gets or sets the model.
///
/// The model.
- [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; }
///
@@ -40,6 +40,6 @@ public class SetSettingsOptions
/// Gets or sets a value indicating whether [save encrypted].
///
/// true if [save encrypted]; otherwise, false.
- [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; }
}
diff --git a/Src/AiCommitMessage/Services/GenerateCommitMessageService.cs b/Src/AiCommitMessage/Services/GenerateCommitMessageService.cs
index 98fe2c3b..f77da9a9 100644
--- a/Src/AiCommitMessage/Services/GenerateCommitMessageService.cs
+++ b/Src/AiCommitMessage/Services/GenerateCommitMessageService.cs
@@ -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;
@@ -37,10 +39,10 @@ private static bool IsMergeConflictResolution(string message) =>
/// Generates a commit message based on the provided options and the OpenAI API.
///
/// An instance of containing the branch name, original message, and git diff.
- /// A string containing the generated commit message from the OpenAI API.
+ /// A string containing the generated commit message from the API.
///
/// 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.
///
/// Thrown if both the branch and diff are empty, as meaningful commit generation is not possible.
@@ -74,23 +76,74 @@ public string GenerateCommitMessage(GenerateCommitMessageOptions options)
+ "Git Diff: "
+ (string.IsNullOrEmpty(diff) ? "" : 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..];
@@ -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;
}
///
diff --git a/Src/AiCommitMessage/Services/SettingsService.cs b/Src/AiCommitMessage/Services/SettingsService.cs
index 3cb84888..25bc4f12 100644
--- a/Src/AiCommitMessage/Services/SettingsService.cs
+++ b/Src/AiCommitMessage/Services/SettingsService.cs
@@ -1,4 +1,6 @@
using AiCommitMessage.Options;
+using AiCommitMessage.Utility;
+using Spectre.Console;
namespace AiCommitMessage.Services;
@@ -17,21 +19,51 @@ public static class SettingsService
///
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}[/]");
}
}
diff --git a/Src/AiCommitMessage/Utility/EnvironmentLoader.cs b/Src/AiCommitMessage/Utility/EnvironmentLoader.cs
index 2a3674e3..6dbe19e8 100644
--- a/Src/AiCommitMessage/Utility/EnvironmentLoader.cs
+++ b/Src/AiCommitMessage/Utility/EnvironmentLoader.cs
@@ -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.
///
- public static string LoadOpenAiModel() => GetEnvironmentVariable("OPENAI_MODEL", "gpt-4o-mini");
+ public static string LoadModelName() => GetEnvironmentVariable("AI_MODEL", "gpt-4o-mini");
///
/// Loads the OpenAI API URL from the environment variables.
@@ -33,10 +33,10 @@ public static string LoadOpenAiApiUrl() =>
GetEnvironmentVariable("OPENAI_API_URL", "https://api.openai.com/v1");
///
- /// Loads the OpenAI API key.
+ /// Loads the OpenAI API key from the environment variables.
///
- /// System.String.
- /// Please set the OPENAI_API_KEY environment variable.
+ /// A string representing the OpenAI API key.
+ /// Thrown if the API key is not set in the environment variables.
public static string LoadOpenAiApiKey()
{
var encryptStr = GetEnvironmentVariable("OPENAI_KEY_ENCRYPTED", "false");
@@ -55,7 +55,21 @@ public static string LoadOpenAiApiKey()
}
///
- /// Loads the optional emoji.
+ /// Loads the Llama API key from the environment variables.
+ ///
+ /// A string representing the Llama API key.
+ public static string LoadLlamaApiKey() =>
+ GetEnvironmentVariable("LLAMA_API_KEY", string.Empty);
+
+ ///
+ /// Loads the Llama API URL from the environment variables.
+ ///
+ /// A string representing the Llama API URL.
+ public static string LoadLlamaApiUrl() =>
+ GetEnvironmentVariable("LLAMA_API_URL", string.Empty);
+
+ ///
+ /// Loads the optional emoji setting from the environment variables.
///
/// true if should include emoji in the commit message, false otherwise.
public static bool LoadOptionalEmoji() =>
@@ -64,8 +78,8 @@ public static bool LoadOptionalEmoji() =>
///
/// Decrypts the specified encrypted text.
///
- /// The encrypted text.
- /// System.String.
+ /// The encrypted text to decrypt.
+ /// A string representing the decrypted text.
private static string Decrypt(string encryptedText)
{
// Placeholder for decryption logic
@@ -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);
+ }
+ }
}
diff --git a/Tests/AiCommitMessage.Tests/Services/GenerateCommitMessageServiceTests.cs b/Tests/AiCommitMessage.Tests/Services/GenerateCommitMessageServiceTests.cs
index 3f97f98f..be8a3580 100644
--- a/Tests/AiCommitMessage.Tests/Services/GenerateCommitMessageServiceTests.cs
+++ b/Tests/AiCommitMessage.Tests/Services/GenerateCommitMessageServiceTests.cs
@@ -1,5 +1,7 @@
+using System.Text.RegularExpressions;
using AiCommitMessage.Options;
using AiCommitMessage.Services;
+using AiCommitMessage.Utility;
using FluentAssertions;
namespace AiCommitMessage.Tests.Services;
@@ -118,4 +120,43 @@ public void GenerateCommitMessage_Should_ReturnMessage_When_MergeConflictResolut
// var debugFileContent = File.ReadAllText("debug.json");
// debugFileContent.Should().Be(JsonSerializer.Serialize(chatCompletionResult));
//}
+
+ [Fact]
+ public void GenerateCommitMessage_WithLlamaModel_Should_MatchExpectedPattern()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("AI_MODEL", "llama-3-1-405B-Instruct");
+ var options = new GenerateCommitMessageOptions
+ {
+ Branch = "feature/llama",
+ Diff = "Add llama-specific functionality",
+ Message = "Initial llama commit"
+ };
+
+ // Act
+ var result = _service.GenerateCommitMessage(options);
+
+ // Assert
+ result.Should().MatchRegex("(?i)(?=.*add)(?=.*llama)");
+ }
+ [Fact]
+ public void GenerateCommitMessage_WithGPTModel_Should_MatchExpectedPattern()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("AI_MODEL", "gpt-4o-mini", EnvironmentVariableTarget.User);
+
+ var service = new GenerateCommitMessageService();
+ var options = new GenerateCommitMessageOptions
+ {
+ Branch = "feature/gpt",
+ Diff = "Add GPT-specific improvements",
+ Message = "Initial GPT commit"
+ };
+
+ // Act
+ var result = service.GenerateCommitMessage(options);
+
+ // Assert
+ result.Should().MatchRegex("(?i)(?=.*add)(?=.*gpt)");
+ }
}