-
Notifications
You must be signed in to change notification settings - Fork 23
Feature/235 connector implementation inheritance upstage #463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gremh97
wants to merge
17
commits into
aliencube:main
Choose a base branch
from
gremh97:feature/235-connector-implementation-inheritance-upstage
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+326
−0
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
2f708a4
Implement UpstageConnector using OpenAI SDK compatibility
gremh97 114e6e1
Merge branch 'main' into feature/235-connector-implementation-inherit…
gremh97 095a6d1
Add UpstageConnector unit tests
gremh97 90aec64
#235 Replace magic strings with constants in UpstageConnectorTests
gremh97 8d7f18b
#235 Refactor test assertions from xUnit Assert.Throws to Shouldly pa…
gremh97 52f4d46
Merge upstream/main into feature/235-connector-implementation-inherit…
gremh97 ae18a9b
#235 Add inheritance relationship test for UpstageConnector
gremh97 23cd10c
#235 Add comprehensive test coverage for UpstageConnector
gremh97 61778c2
#235 Refactor test structure to separate Act and Assert sections
gremh97 f3c67a9
Merge branch 'main' into feature/235-connector-implementation-inherit…
gremh97 1c9a8b9
fix: Update UpstageConnector exception test to expect InvalidOperatio…
gremh97 ff2b2dc
refactor: Reorganize UpstageConnector test methods for better readabi…
gremh97 e0183f3
test: Enhance UpstageConnector CreateChatClientAsync validation tests
gremh97 6d3c438
test: Standardize UpstageConnector error message format with OpenAI p…
gremh97 982f381
Merge branch 'main' into feature/235-connector-implementation-inherit…
gremh97 3d598d2
test: Add comprehensive whitespace validation tests for CreateChatCli…
gremh97 5f5639e
refactor: improve parameter naming in UpstageConnector tests
gremh97 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using System.ClientModel; | ||
|
||
using Microsoft.Extensions.AI; | ||
|
||
using OpenAI; | ||
|
||
using OpenChat.PlaygroundApp.Abstractions; | ||
using OpenChat.PlaygroundApp.Configurations; | ||
|
||
namespace OpenChat.PlaygroundApp.Connectors; | ||
|
||
/// <summary> | ||
/// This represents the connector entity for Upstage. | ||
/// </summary> | ||
public class UpstageConnector(AppSettings settings) : LanguageModelConnector(settings.Upstage) | ||
{ | ||
/// <inheritdoc/> | ||
public override bool EnsureLanguageModelSettingsValid() | ||
{ | ||
var settings = this.Settings as UpstageSettings; | ||
if (settings is null) | ||
{ | ||
throw new InvalidOperationException("Missing configuration: Upstage."); | ||
} | ||
|
||
if (string.IsNullOrWhiteSpace(settings.BaseUrl?.Trim()) == true) | ||
{ | ||
throw new InvalidOperationException("Missing configuration: Upstage:BaseUrl."); | ||
} | ||
|
||
if (string.IsNullOrWhiteSpace(settings.ApiKey?.Trim()) == true) | ||
{ | ||
throw new InvalidOperationException("Missing configuration: Upstage:ApiKey."); | ||
} | ||
|
||
if (string.IsNullOrWhiteSpace(settings.Model?.Trim()) == true) | ||
{ | ||
throw new InvalidOperationException("Missing configuration: Upstage:Model."); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override async Task<IChatClient> GetChatClientAsync() | ||
{ | ||
var settings = this.Settings as UpstageSettings; | ||
|
||
var credential = new ApiKeyCredential(settings?.ApiKey ?? | ||
throw new InvalidOperationException("Missing configuration: Upstage:ApiKey.")); | ||
|
||
var options = new OpenAIClientOptions | ||
{ | ||
Endpoint = new Uri(settings.BaseUrl ?? | ||
throw new InvalidOperationException("Missing configuration: Upstage:BaseUrl.")) | ||
}; | ||
|
||
var client = new OpenAIClient(credential, options); | ||
var chatClient = client.GetChatClient(settings.Model) | ||
.AsIChatClient(); | ||
|
||
return await Task.FromResult(chatClient).ConfigureAwait(false); | ||
} | ||
} |
261 changes: 261 additions & 0 deletions
261
test/OpenChat.PlaygroundApp.Tests/Connectors/UpstageConnectorTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
using OpenChat.PlaygroundApp.Abstractions; | ||
using OpenChat.PlaygroundApp.Configurations; | ||
using OpenChat.PlaygroundApp.Connectors; | ||
|
||
namespace OpenChat.PlaygroundApp.Tests.Connectors; | ||
|
||
|
||
|
||
public class UpstageConnectorTests | ||
{ | ||
private const string BaseUrl = "https://api.upstage.ai/v1/solar"; | ||
private const string ApiKey = "test-api-key"; | ||
private const string Model = "solar-1-mini-chat"; | ||
|
||
private static AppSettings BuildAppSettings(string? baseUrl = BaseUrl, string? apiKey = ApiKey, string? model = Model) | ||
{ | ||
return new AppSettings | ||
{ | ||
ConnectorType = ConnectorType.Upstage, | ||
Upstage = new UpstageSettings | ||
{ | ||
BaseUrl = baseUrl, | ||
ApiKey = apiKey, | ||
Model = model | ||
} | ||
}; | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData(typeof(LanguageModelConnector), typeof(UpstageConnector), true)] | ||
[InlineData(typeof(UpstageConnector), typeof(LanguageModelConnector), false)] | ||
public void Given_BaseType_Then_It_Should_Be_AssignableFrom_DerivedType(Type baseType, Type derivedType, bool expected) | ||
{ | ||
// Act | ||
var result = baseType.IsAssignableFrom(derivedType); | ||
|
||
// Assert | ||
result.ShouldBe(expected); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Fact] | ||
public void Given_Settings_Is_Null_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw() | ||
{ | ||
// Arrange | ||
var appSettings = new AppSettings { ConnectorType = ConnectorType.Upstage, Upstage = null }; | ||
var connector = new UpstageConnector(appSettings); | ||
|
||
// Act | ||
Action action = () => connector.EnsureLanguageModelSettingsValid(); | ||
|
||
// Assert | ||
action.ShouldThrow<InvalidOperationException>() | ||
.Message.ShouldContain("Missing configuration: Upstage."); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData(null, typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
[InlineData("", typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
[InlineData(" ", typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
public void Given_Invalid_BaseUrl_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? baseUrl, Type expectedType, string expectedMessage) | ||
{ | ||
// Arrange | ||
var appSettings = BuildAppSettings(baseUrl: baseUrl); | ||
var connector = new UpstageConnector(appSettings); | ||
|
||
// Act | ||
Action action = () => connector.EnsureLanguageModelSettingsValid(); | ||
|
||
// Assert | ||
action.ShouldThrow(expectedType) | ||
.Message.ShouldContain(expectedMessage); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData(null, typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
[InlineData("", typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
[InlineData(" ", typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
public void Given_Invalid_ApiKey_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? apiKey, Type expectedType, string expectedMessage) | ||
{ | ||
// Arrange | ||
var appSettings = BuildAppSettings(apiKey: apiKey); | ||
var connector = new UpstageConnector(appSettings); | ||
|
||
// Act | ||
Action action = () => connector.EnsureLanguageModelSettingsValid(); | ||
|
||
// Assert | ||
action.ShouldThrow(expectedType) | ||
.Message.ShouldContain(expectedMessage); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData(null, typeof(InvalidOperationException), "Upstage:Model")] | ||
[InlineData("", typeof(InvalidOperationException), "Upstage:Model")] | ||
[InlineData(" ", typeof(InvalidOperationException), "Upstage:Model")] | ||
public void Given_Invalid_Model_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? model, Type expectedType, string expectedMessage) | ||
{ | ||
// Arrange | ||
var appSettings = BuildAppSettings(model: model); | ||
var connector = new UpstageConnector(appSettings); | ||
|
||
// Act | ||
Action action = () => connector.EnsureLanguageModelSettingsValid(); | ||
|
||
// Assert | ||
action.ShouldThrow(expectedType) | ||
.Message.ShouldContain(expectedMessage); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Fact] | ||
public void Given_Valid_Settings_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Return_True() | ||
{ | ||
// Arrange | ||
var appSettings = BuildAppSettings(); | ||
var connector = new UpstageConnector(appSettings); | ||
|
||
// Act | ||
var result = connector.EnsureLanguageModelSettingsValid(); | ||
|
||
// Assert | ||
result.ShouldBeTrue(); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Fact] | ||
public async Task Given_Valid_Settings_When_GetChatClient_Invoked_Then_It_Should_Return_ChatClient() | ||
{ | ||
// Arrange | ||
var settings = BuildAppSettings(); | ||
var connector = new UpstageConnector(settings); | ||
|
||
// Act | ||
var client = await connector.GetChatClientAsync(); | ||
|
||
// Assert | ||
client.ShouldNotBeNull(); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Fact] | ||
public void Given_Settings_Is_Null_When_GetChatClientAsync_Invoked_Then_It_Should_Throw() | ||
{ | ||
// Arrange | ||
var appSettings = new AppSettings { ConnectorType = ConnectorType.Upstage, Upstage = null }; | ||
var connector = new UpstageConnector(appSettings); | ||
|
||
// Act | ||
Func<Task> func = async () => await connector.GetChatClientAsync(); | ||
|
||
// Assert | ||
func.ShouldThrow<InvalidOperationException>() | ||
.Message.ShouldContain("Missing configuration: Upstage:ApiKey."); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData(null, typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
[InlineData("", typeof(ArgumentException), "key")] | ||
public void Given_Missing_ApiKey_When_GetChatClient_Invoked_Then_It_Should_Throw(string? apiKey, Type expected, string message) | ||
{ | ||
// Arrange | ||
var settings = BuildAppSettings(apiKey: apiKey); | ||
sikutisa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var connector = new UpstageConnector(settings); | ||
|
||
// Act | ||
Func<Task> func = async () => await connector.GetChatClientAsync(); | ||
|
||
// Assert | ||
func.ShouldThrow(expected) | ||
.Message.ShouldContain(message); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData(null, typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
[InlineData("", typeof(UriFormatException), "empty")] | ||
public void Given_Missing_BaseUrl_When_GetChatClient_Invoked_Then_It_Should_Throw(string? baseUrl, Type expected, string message) | ||
{ | ||
// Arrange | ||
var settings = BuildAppSettings(baseUrl: baseUrl); | ||
sikutisa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var connector = new UpstageConnector(settings); | ||
|
||
// Act | ||
Func<Task> func = async () => await connector.GetChatClientAsync(); | ||
|
||
// Assert | ||
func.ShouldThrow(expected) | ||
.Message.ShouldContain(message); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData(null, typeof(ArgumentNullException), "model")] | ||
[InlineData("", typeof(ArgumentException), "model")] | ||
public void Given_Missing_Model_When_GetChatClient_Invoked_Then_It_Should_Throw(string? model, Type expected, string message) | ||
{ | ||
// Arrange | ||
var settings = BuildAppSettings(model: model); | ||
sikutisa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var connector = new UpstageConnector(settings); | ||
|
||
// Act | ||
Func<Task> func = async () => await connector.GetChatClientAsync(); | ||
|
||
// Assert | ||
func.ShouldThrow(expected) | ||
.Message.ShouldContain(message); | ||
} | ||
sikutisa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[Trait("Category", "UnitTest")] | ||
[Fact] | ||
public async Task Given_Valid_Settings_When_CreateChatClientAsync_Invoked_Then_It_Should_Return_ChatClient() | ||
{ | ||
// Arrange | ||
var settings = BuildAppSettings(); | ||
|
||
// Act | ||
var client = await LanguageModelConnector.CreateChatClientAsync(settings); | ||
|
||
// Assert | ||
client.ShouldNotBeNull(); | ||
} | ||
|
||
[Trait("Category", "UnitTest")] | ||
[Theory] | ||
[InlineData("apiKey", null, typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
[InlineData("apiKey", "", typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
[InlineData("apiKey", " ", typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
[InlineData("apiKey", "\t\n\r", typeof(InvalidOperationException), "Upstage:ApiKey")] | ||
[InlineData("baseUrl", null, typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
[InlineData("baseUrl", "", typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
[InlineData("baseUrl", " ", typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
[InlineData("baseUrl", "\t\n\r", typeof(InvalidOperationException), "Upstage:BaseUrl")] | ||
[InlineData("model", null, typeof(InvalidOperationException), "Upstage:Model")] | ||
[InlineData("model", "", typeof(InvalidOperationException), "Upstage:Model")] | ||
[InlineData("model", " ", typeof(InvalidOperationException), "Upstage:Model")] | ||
[InlineData("model", "\t\n\r", typeof(InvalidOperationException), "Upstage:Model")] | ||
public void Given_Invalid_Settings_When_CreateChatClientAsync_Invoked_Then_It_Should_Throw(string parameterName, string? invalidValue, Type expectedType, string expectedMessage) | ||
{ | ||
// Arrange | ||
var settings = parameterName switch | ||
{ | ||
"apiKey" => BuildAppSettings(apiKey: invalidValue), | ||
"baseUrl" => BuildAppSettings(baseUrl: invalidValue), | ||
"model" => BuildAppSettings(model: invalidValue), | ||
_ => throw new ArgumentException($"Unknown parameter: {parameterName}") | ||
}; | ||
|
||
// Act | ||
Func<Task> func = async () => await LanguageModelConnector.CreateChatClientAsync(settings); | ||
|
||
// Assert | ||
func.ShouldThrow(expectedType) | ||
.Message.ShouldContain(expectedMessage); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.