From a493758900a283dba2ebf2b54729f6a9df6e2a4f Mon Sep 17 00:00:00 2001 From: OkJa Date: Sun, 12 Oct 2025 12:30:15 +0900 Subject: [PATCH 01/12] feat: Add Foundry Local connector implementation --- .../Abstractions/LanguageModelConnector.cs | 1 + .../Connectors/FoundryLocalConnector.cs | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs diff --git a/src/OpenChat.PlaygroundApp/Abstractions/LanguageModelConnector.cs b/src/OpenChat.PlaygroundApp/Abstractions/LanguageModelConnector.cs index f2857bac..ca4006fe 100644 --- a/src/OpenChat.PlaygroundApp/Abstractions/LanguageModelConnector.cs +++ b/src/OpenChat.PlaygroundApp/Abstractions/LanguageModelConnector.cs @@ -38,6 +38,7 @@ public static async Task CreateChatClientAsync(AppSettings settings { ConnectorType.AzureAIFoundry => new AzureAIFoundryConnector(settings), ConnectorType.GitHubModels => new GitHubModelsConnector(settings), + ConnectorType.FoundryLocal => new FoundryLocalConnector(settings), ConnectorType.HuggingFace => new HuggingFaceConnector(settings), ConnectorType.LG => new LGConnector(settings), ConnectorType.OpenAI => new OpenAIConnector(settings), diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs new file mode 100644 index 00000000..9e9297fe --- /dev/null +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -0,0 +1,60 @@ +using System.ClientModel; + +using Microsoft.AI.Foundry.Local; +using Microsoft.Extensions.AI; + +using OpenAI; + +using OpenChat.PlaygroundApp.Configurations; +using OpenChat.PlaygroundApp.Abstractions; + +namespace OpenChat.PlaygroundApp.Connectors; + +/// +/// This represents the connector entity for Foundry Local. +/// +/// instance. +public class FoundryLocalConnector(AppSettings settings) : LanguageModelConnector(settings.FoundryLocal) +{ + private readonly AppSettings _appSettings = settings ?? throw new ArgumentNullException(nameof(settings)); + + /// + public override bool EnsureLanguageModelSettingsValid() + { + if (this.Settings is not FoundryLocalSettings settings) + { + throw new InvalidOperationException("Missing configuration: FoundryLocal."); + } + + if (string.IsNullOrWhiteSpace(settings.Alias?.Trim())) + { + throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); + } + + return true; + } + + /// + public override async Task GetChatClientAsync() + { + var settings = this.Settings as FoundryLocalSettings; + var alias = settings!.Alias!; + + var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias); + var model = await manager.GetModelInfoAsync(aliasOrModelId: alias); + + var credential = new ApiKeyCredential(manager.ApiKey); + var options = new OpenAIClientOptions() + { + Endpoint = manager.Endpoint, + }; + + var client = new OpenAIClient(credential, options); + var chatClient = client.GetChatClient(model?.ModelId) + .AsIChatClient(); + + Console.WriteLine($"The {this._appSettings.ConnectorType} connector created with model: {settings.Alias}"); + + return chatClient; + } +} \ No newline at end of file From d9732ca05f3b36e2fc628c0f868fe090af5831a1 Mon Sep 17 00:00:00 2001 From: OkJa Date: Sun, 12 Oct 2025 12:30:45 +0900 Subject: [PATCH 02/12] feat: Add unit and integration tests for FoundryLocalConnector --- .../Connectors/FoundryLocalConnectorTests.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs new file mode 100644 index 00000000..64a9c1a5 --- /dev/null +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -0,0 +1,99 @@ +using OpenChat.PlaygroundApp.Abstractions; +using OpenChat.PlaygroundApp.Configurations; +using OpenChat.PlaygroundApp.Connectors; + +namespace OpenChat.PlaygroundApp.Tests.Connectors; + +public class FoundryLocalConnectorTests +{ + private const string Alais = "test-alias"; + + private static AppSettings BuildAppSettings(string? alias = Alais) + { + return new AppSettings + { + ConnectorType = ConnectorType.FoundryLocal, + FoundryLocal = new FoundryLocalSettings + { + Alias = alias + } + }; + } + + [Trait("Category", "UnitTest")] + [Theory] + [InlineData(typeof(LanguageModelConnector), typeof(FoundryLocalConnector), true)] + [InlineData(typeof(FoundryLocalConnector), 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 settings = new AppSettings { ConnectorType = ConnectorType.FoundryLocal, FoundryLocal = null }; + var connector = new FoundryLocalConnector(settings); + + // Act + var ex = Assert.Throws(() => connector.EnsureLanguageModelSettingsValid()); + + // Assert + ex.Message.ShouldContain("FoundryLocal"); + } + + [Trait("Category", "UnitTest")] + [Theory] + [InlineData(null, typeof(InvalidOperationException), "FoundryLocal:Alias")] + [InlineData("", typeof(InvalidOperationException), "FoundryLocal:Alias")] + [InlineData(" ", typeof(InvalidOperationException), "FoundryLocal:Alias")] + public void Given_Invalid_Alias_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? alias, Type expectedType, string expectedMessage) + { + // Arrange + var settings = BuildAppSettings(alias: alias); + var connector = new FoundryLocalConnector(settings); + + // Act + var ex = Assert.Throws(expectedType, () => connector.EnsureLanguageModelSettingsValid()); + + // Assert + ex.Message.ShouldContain(expectedMessage); + } + + [Trait("Category", "UnitTest")] + [Fact] + public void Given_Valid_Settings_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Return_True() + { + // Arrange + var settings = BuildAppSettings(); + var connector = new FoundryLocalConnector(settings); + + // Act + var result = connector.EnsureLanguageModelSettingsValid(); + + // Assert + result.ShouldBeTrue(); + } + + [Trait("Category", "IntegrationTest")] + [Trait("Category", "LLMRequired")] + [Fact] + public async Task Given_Valid_Settings_When_GetChatClient_Invoked_Then_It_Should_Return_ChatClient() + { + // Arrange + var settings = BuildAppSettings(); + var connector = new FoundryLocalConnector(settings); + + // Act + var client = await connector.GetChatClientAsync(); + + // Assert + client.ShouldNotBeNull(); + } +} From 6c33f5cc81fffcbd2333df428ff707a334cd14b5 Mon Sep 17 00:00:00 2001 From: OkJa Date: Sun, 12 Oct 2025 12:32:35 +0900 Subject: [PATCH 03/12] feat: Add Foundry Local documentation and update README --- docs/README.md | 1 + docs/foundry-local.md | 77 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 docs/foundry-local.md diff --git a/docs/README.md b/docs/README.md index fa29a638..ca40857a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,7 @@ - [Azure AI Foundry](azure-ai-foundry.md) - [GitHub Models](github-models.md) +- [Foundry Local](foundry-local.md) - [Hugging Face](hugging-face.md) - [LG](lg.md) - [OpenAI](openai.md) diff --git a/docs/foundry-local.md b/docs/foundry-local.md new file mode 100644 index 00000000..53a933f6 --- /dev/null +++ b/docs/foundry-local.md @@ -0,0 +1,77 @@ +# OpenChat Playground with Foundry Local + +This page describes how to run OpenChat Playground (OCP) with Foundry Local models integration. + +## Get the repository root + +1. Get the repository root. + + ```bash + # bash/zsh + REPOSITORY_ROOT=$(git rev-parse --show-toplevel) + ``` + + ```powershell + # PowerShell + $REPOSITORY_ROOT = git rev-parse --show-toplevel + ``` + +## Run on local machine + +1. Make sure the Foundry Local server is up and running. + + ```bash + foundry service start + ``` + +1. Download the Foundry Local model. The default model OCP uses is `phi-4-mini`. + + ```bash + foundry model download phi-4-mini + ``` + + Alternatively, if you want to run with a different model, say `qwen2.5-7b`, other than the default one, download it first by running the following command. + + ```bash + foundry model download qwen2.5-7b + ``` + + Make sure to follow the model MUST be selected from the CLI output of `foundry model list`. + +1. Make sure you are at the repository root. + + ```bash + cd $REPOSITORY_ROOT + ``` + +1. Run the app. + + ```bash + # bash/zsh + dotnet run --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp -- \ + --connector-type FoundryLocal + ``` + + ```powershell + # PowerShell + dotnet run --project $REPOSITORY_ROOT\src\OpenChat.PlaygroundApp -- ` + --connector-type FoundryLocal + ``` + + Alternatively, if you want to run with a different model, say `qwen2.5-7b`, make sure you've already downloaded the model by running the `foundry model download qwen2.5-7b` command. + + ```bash + # bash/zsh + dotnet run --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp -- \ + --connector-type FoundryLocal \ + --alias qwen2.5-7b + ``` + + ```powershell + # PowerShell + dotnet run --project $REPOSITORY_ROOT\src\OpenChat.PlaygroundApp -- ` + --connector-type FoundryLocal ` + --alias qwen2.5-7b + ``` + +1. Open your web browser, navigate to `http://localhost:5280`, and enter prompts. \ No newline at end of file From 7b61de6d81d3f2322a9f82fa2d13c88cc84b33ce Mon Sep 17 00:00:00 2001 From: OkJa Date: Sun, 12 Oct 2025 15:10:46 +0900 Subject: [PATCH 04/12] fix: Reorder using directives in FoundryLocalConnector for consistency --- src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index 9e9297fe..7c349267 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -5,8 +5,8 @@ using OpenAI; -using OpenChat.PlaygroundApp.Configurations; using OpenChat.PlaygroundApp.Abstractions; +using OpenChat.PlaygroundApp.Configurations; namespace OpenChat.PlaygroundApp.Connectors; From 5bafb4d65b178a098878ff3312f813fab4b6b761 Mon Sep 17 00:00:00 2001 From: OkJa Date: Tue, 14 Oct 2025 19:38:46 +0900 Subject: [PATCH 05/12] fix: Add ConfigureAwait(false) to asynchronous calls in FoundryLocalConnector --- .../Connectors/FoundryLocalConnector.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index 7c349267..7629c765 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -40,8 +40,10 @@ public override async Task GetChatClientAsync() var settings = this.Settings as FoundryLocalSettings; var alias = settings!.Alias!; - var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias); - var model = await manager.GetModelInfoAsync(aliasOrModelId: alias); + var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias) + .ConfigureAwait(false); + var model = await manager.GetModelInfoAsync(aliasOrModelId: alias) + .ConfigureAwait(false); var credential = new ApiKeyCredential(manager.ApiKey); var options = new OpenAIClientOptions() From d398f2ca6faf8723187636710d627c3156c6540e Mon Sep 17 00:00:00 2001 From: OkJa Date: Tue, 14 Oct 2025 21:48:39 +0900 Subject: [PATCH 06/12] refactor: Simplify asynchronous call handling in FoundryLocalConnector and improve exception assertions in tests --- .../Connectors/FoundryLocalConnector.cs | 6 ++---- .../Connectors/FoundryLocalConnectorTests.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index 7629c765..6e9be476 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -40,10 +40,8 @@ public override async Task GetChatClientAsync() var settings = this.Settings as FoundryLocalSettings; var alias = settings!.Alias!; - var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias) - .ConfigureAwait(false); - var model = await manager.GetModelInfoAsync(aliasOrModelId: alias) - .ConfigureAwait(false); + var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias).ConfigureAwait(false); + var model = await manager.GetModelInfoAsync(aliasOrModelId: alias).ConfigureAwait(false); var credential = new ApiKeyCredential(manager.ApiKey); var options = new OpenAIClientOptions() diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs index 64a9c1a5..4d9af398 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -42,10 +42,11 @@ public void Given_Settings_Is_Null_When_EnsureLanguageModelSettingsValid_Invoked var connector = new FoundryLocalConnector(settings); // Act - var ex = Assert.Throws(() => connector.EnsureLanguageModelSettingsValid()); + Action action = () => connector.EnsureLanguageModelSettingsValid(); // Assert - ex.Message.ShouldContain("FoundryLocal"); + action.ShouldThrow() + .Message.ShouldContain("FoundryLocal"); } [Trait("Category", "UnitTest")] @@ -60,10 +61,11 @@ public void Given_Invalid_Alias_When_EnsureLanguageModelSettingsValid_Invoked_Th var connector = new FoundryLocalConnector(settings); // Act - var ex = Assert.Throws(expectedType, () => connector.EnsureLanguageModelSettingsValid()); + Action action = () => connector.EnsureLanguageModelSettingsValid(); // Assert - ex.Message.ShouldContain(expectedMessage); + action.ShouldThrow(expectedType) + .Message.ShouldContain(expectedMessage); } [Trait("Category", "UnitTest")] From 4f350295583f703810977d8913365ad66102fa4c Mon Sep 17 00:00:00 2001 From: OkJa Date: Wed, 15 Oct 2025 18:42:25 +0900 Subject: [PATCH 07/12] test: Enable FoundryLocalConnector test case in LanguageModelConnectorTests --- .../Abstractions/LanguageModelConnectorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs index 8efc9cf3..0701ee93 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs @@ -60,7 +60,7 @@ public async Task Given_Unsupported_ConnectorType_When_CreateChatClient_Invoked_ [InlineData(typeof(GitHubModelsConnector))] // [InlineData(typeof(GoogleVertexAIConnector))] // [InlineData(typeof(DockerModelRunnerConnector))] - // [InlineData(typeof(FoundryLocalConnector))] + [InlineData(typeof(FoundryLocalConnector))] [InlineData(typeof(HuggingFaceConnector))] // [InlineData(typeof(OllamaConnector))] // [InlineData(typeof(AnthropicConnector))] From d5c083785a3c1343547be0276627033618701fbc Mon Sep 17 00:00:00 2001 From: OkJa Date: Thu, 16 Oct 2025 19:48:53 +0900 Subject: [PATCH 08/12] enhance unit tests --- .../Connectors/FoundryLocalConnector.cs | 2 +- .../Connectors/FoundryLocalConnectorTests.cs | 120 +++++++++++++++++- 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index 6e9be476..986e56d1 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -26,7 +26,7 @@ public override bool EnsureLanguageModelSettingsValid() throw new InvalidOperationException("Missing configuration: FoundryLocal."); } - if (string.IsNullOrWhiteSpace(settings.Alias?.Trim())) + if (string.IsNullOrWhiteSpace(settings.Alias!.Trim())) { throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); } diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs index 4d9af398..bc0ae662 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.AI; + using OpenChat.PlaygroundApp.Abstractions; using OpenChat.PlaygroundApp.Configurations; using OpenChat.PlaygroundApp.Connectors; @@ -33,12 +35,28 @@ public void Given_BaseType_Then_It_Should_Be_AssignableFrom_DerivedType(Type bas result.ShouldBe(expected); } + [Trait("Category", "UnitTest")] + [Fact] + public void Given_Null_Settings_When_Instantiated_Then_It_Should_Throw() + { + // Act + Action action = () => new FoundryLocalConnector(null!); + + // Assert + action.ShouldThrow() + .Message.ShouldContain("settings"); + } + [Trait("Category", "UnitTest")] [Fact] public void Given_Settings_Is_Null_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw() { // Arrange - var settings = new AppSettings { ConnectorType = ConnectorType.FoundryLocal, FoundryLocal = null }; + var settings = new AppSettings + { + ConnectorType = ConnectorType.FoundryLocal, + FoundryLocal = null + }; var connector = new FoundryLocalConnector(settings); // Act @@ -49,11 +67,46 @@ public void Given_Settings_Is_Null_When_EnsureLanguageModelSettingsValid_Invoked .Message.ShouldContain("FoundryLocal"); } + [Trait("Category", "UnitTest")] + [Fact] + public void Given_Settings_When_Instantiated_Then_It_Should_Return() + { + // Arrange + var settings = BuildAppSettings(); + + // Act + var result = new FoundryLocalConnector(settings); + + // Assert + result.ShouldNotBeNull(); + } + + [Trait("Category", "UnitTest")] + [Fact] + public void Given_Null_Settings_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw() + { + // Arrange + var settings = new AppSettings + { + ConnectorType = ConnectorType.FoundryLocal, + FoundryLocal = null + }; + var connector = new FoundryLocalConnector(settings); + + // Act + Action action = () => connector.EnsureLanguageModelSettingsValid(); + + // Assert + action.ShouldThrow() + .Message.ShouldContain("FoundryLocal"); + } + [Trait("Category", "UnitTest")] [Theory] - [InlineData(null, typeof(InvalidOperationException), "FoundryLocal:Alias")] + [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] [InlineData("", typeof(InvalidOperationException), "FoundryLocal:Alias")] [InlineData(" ", typeof(InvalidOperationException), "FoundryLocal:Alias")] + [InlineData("\t\n\r", typeof(InvalidOperationException), "FoundryLocal:Alias")] public void Given_Invalid_Alias_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? alias, Type expectedType, string expectedMessage) { // Arrange @@ -83,6 +136,27 @@ public void Given_Valid_Settings_When_EnsureLanguageModelSettingsValid_Invoked_T result.ShouldBeTrue(); } + [Trait("Category", "IntegrationTest")] + [Trait("Category", "LLMRequired")] + [Theory] + [InlineData(null, typeof(InvalidOperationException), "Model not found in catalog.")] + [InlineData("", typeof(InvalidOperationException), "Model not found in catalog.")] + [InlineData(" ", typeof(InvalidOperationException), "Model not found in catalog.")] + [InlineData("not-a-model", typeof(InvalidOperationException), "Model not-a-model not found in catalog.")] + public void Given_Invalid_Alias_When_GetChatClient_Invoked_Then_It_Should_Throw(string? alias, Type expected, string message) + { + // Arrange + var settings = BuildAppSettings(alias: alias); + var connector = new FoundryLocalConnector(settings); + + // Act + Func func = async () => await connector.GetChatClientAsync(); + + // Assert + func.ShouldThrow(expected) + .Message.ShouldContain(message); + } + [Trait("Category", "IntegrationTest")] [Trait("Category", "LLMRequired")] [Fact] @@ -97,5 +171,47 @@ public async Task Given_Valid_Settings_When_GetChatClient_Invoked_Then_It_Should // Assert client.ShouldNotBeNull(); + client.ShouldBeAssignableTo(); + } + + [Trait("Category", "UnitTest")] + [Theory] + [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] + [InlineData("", typeof(InvalidOperationException), "Missing configuration: FoundryLocal")] + [InlineData(" ", typeof(InvalidOperationException), "Missing configuration: FoundryLocal")] + public void Given_Invalid_Settings_When_CreateChatClientAsync_Invoked_Then_It_Should_Throw(string? alias, Type expected, string expectedMessage) + { + // Arrange + var settings = new AppSettings + { + ConnectorType = ConnectorType.FoundryLocal, + FoundryLocal = new FoundryLocalSettings + { + Alias = alias + } + }; + + // Act + Func func = async () => await LanguageModelConnector.CreateChatClientAsync(settings); + + // Assert + func.ShouldThrow(expected) + .Message.ShouldContain(expectedMessage); + } + + [Trait("Category", "IntegrationTest")] + [Trait("Category", "LLMRequired")] + [Fact] + public async Task Given_Valid_Settings_When_CreateChatClientAsync_Invoked_Then_It_Should_Return_IChatClient() + { + // Arrange + var settings = BuildAppSettings(); + + // Act + var result = await LanguageModelConnector.CreateChatClientAsync(settings); + + // Assert + result.ShouldNotBeNull(); + result.ShouldBeAssignableTo(); } } From c72c5759a80438b9ee609d1f83e9f08eb4388bc3 Mon Sep 17 00:00:00 2001 From: OkJa Date: Thu, 16 Oct 2025 19:56:25 +0900 Subject: [PATCH 09/12] test: Remove FoundryLocal connector type from unsupported connectors test --- .../Abstractions/LanguageModelConnectorTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs index 7f9faa50..2ce915dd 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Abstractions/LanguageModelConnectorTests.cs @@ -77,7 +77,6 @@ public void Given_Null_Settings_When_CreateChatClient_Invoked_Then_It_Should_Thr [InlineData(ConnectorType.AmazonBedrock)] [InlineData(ConnectorType.GoogleVertexAI)] [InlineData(ConnectorType.DockerModelRunner)] - [InlineData(ConnectorType.FoundryLocal)] [InlineData(ConnectorType.Ollama)] [InlineData(ConnectorType.Anthropic)] [InlineData(ConnectorType.Naver)] From 31bf1bf178536a11463a234bde772aca3cf226a4 Mon Sep 17 00:00:00 2001 From: OkJa Date: Thu, 16 Oct 2025 19:56:31 +0900 Subject: [PATCH 10/12] fix: Update alias in FoundryLocalConnectorTests to match expected value --- .../Connectors/FoundryLocalConnectorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs index bc0ae662..9bf62179 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -8,7 +8,7 @@ namespace OpenChat.PlaygroundApp.Tests.Connectors; public class FoundryLocalConnectorTests { - private const string Alais = "test-alias"; + private const string Alais = "phi-4-mini"; private static AppSettings BuildAppSettings(string? alias = Alais) { From 63f112607b4bb47b6ce87c4354a47db1b43dd3b7 Mon Sep 17 00:00:00 2001 From: OkJa Date: Thu, 16 Oct 2025 20:00:09 +0900 Subject: [PATCH 11/12] fix: Correct alias variable name in FoundryLocalConnectorTests --- .../Connectors/FoundryLocalConnectorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs index 9bf62179..3e78f38f 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -8,9 +8,9 @@ namespace OpenChat.PlaygroundApp.Tests.Connectors; public class FoundryLocalConnectorTests { - private const string Alais = "phi-4-mini"; + private const string Alias = "phi-4-mini"; - private static AppSettings BuildAppSettings(string? alias = Alais) + private static AppSettings BuildAppSettings(string? alias = Alias) { return new AppSettings { From 8c259ceef328655e7bc13eadc53e727bfe0012b2 Mon Sep 17 00:00:00 2001 From: OkJa Date: Thu, 16 Oct 2025 20:05:06 +0900 Subject: [PATCH 12/12] fix: Mark Foundry Local as supported in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8278f7c..980764e1 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Open Chat Playground (OCP) is a web UI that is able to connect virtually any LLM - [x] [GitHub Models](https://docs.github.com/github-models/about-github-models) - [ ] [Google Vertex AI](https://cloud.google.com/vertex-ai/docs) - [ ] [Docker Model Runner](https://docs.docker.com/ai/model-runner) -- [ ] [Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/what-is-foundry-local) +- [x] [Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/what-is-foundry-local) - [x] [Hugging Face](https://huggingface.co/docs) - [ ] [Ollama](https://github.com/ollama/ollama/tree/main/docs) - [ ] [Anthropic](https://docs.anthropic.com)