From a17f1642894be71005465980d8bc713cfda782e8 Mon Sep 17 00:00:00 2001 From: OkJa Date: Sat, 25 Oct 2025 20:45:58 +0900 Subject: [PATCH 01/12] feat: Enhance Foundry Local configuration and argument options for docker --- .../Abstractions/ArgumentOptions.cs | 15 +++++- .../Configurations/FoundryLocalSettings.cs | 16 ++++++ .../Connectors/FoundryLocalConnector.cs | 54 +++++++++++++++---- .../Constants/ArgumentOptionConstants.cs | 15 ++++++ .../Options/FoundryLocalArgumentOptions.cs | 36 +++++++++++++ src/OpenChat.PlaygroundApp/appsettings.json | 4 +- 6 files changed, 128 insertions(+), 12 deletions(-) diff --git a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs index 73d58f07..28417e38 100644 --- a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs @@ -33,6 +33,9 @@ private static readonly (ConnectorType ConnectorType, string Argument, bool IsSw (ConnectorType.DockerModelRunner, ArgumentOptionConstants.DockerModelRunner.Model, false), // Foundry Local (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Alias, false), + (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Endpoint, false), + (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.ModelId, false), + (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager, false), // Hugging Face (ConnectorType.HuggingFace, ArgumentOptionConstants.HuggingFace.BaseUrl, false), (ConnectorType.HuggingFace, ArgumentOptionConstants.HuggingFace.Model, false), @@ -213,8 +216,13 @@ public static AppSettings Parse(IConfiguration config, string[] args) case FoundryLocalArgumentOptions foundryLocal: settings.FoundryLocal ??= new FoundryLocalSettings(); settings.FoundryLocal.Alias = foundryLocal.Alias ?? settings.FoundryLocal.Alias; + settings.FoundryLocal.Endpoint = foundryLocal.Endpoint ?? settings.FoundryLocal.Endpoint; + settings.FoundryLocal.ModelId = foundryLocal.ModelId ?? settings.FoundryLocal.ModelId; + settings.FoundryLocal.DisableFoundryLocalManager = foundryLocal.DisableFoundryLocalManager; - settings.Model = foundryLocal.Alias ?? settings.FoundryLocal.Alias; + settings.Model = foundryLocal.DisableFoundryLocalManager + ? foundryLocal.ModelId ?? settings.FoundryLocal.ModelId + : foundryLocal.Alias ?? settings.FoundryLocal.Alias; break; case HuggingFaceArgumentOptions huggingFace: @@ -419,12 +427,15 @@ private static void DisplayHelpForDockerModelRunner() private static void DisplayHelpForFoundryLocal() { + // --model The OpenAI model name. Default to 'gpt-4.1-mini' var foregroundColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(" ** Foundry Local: **"); Console.ForegroundColor = foregroundColor; - Console.WriteLine(" TBD"); + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Alias} The alias. Default to 'phi-4-mini'"); + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Endpoint} The endpoint URL. Default to 'http://127.0.0.1:55438/v1'"); + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.ModelId} The model ID. Default to 'Phi-4-mini-instruct-generic-cpu:4'"); Console.WriteLine(); } diff --git a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs index 5555b800..9f1b817f 100644 --- a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs +++ b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs @@ -20,4 +20,20 @@ public class FoundryLocalSettings : LanguageModelSettings /// Gets or sets the alias of FoundryLocal. /// public string? Alias { get; set; } + + /// + /// Gets or sets the Endpoint of FoundryLocal. + /// + public string? Endpoint { get; set; } + + /// + /// Gets or sets the model ID of FoundryLocal. + /// + public string? ModelId { get; set; } + + /// + /// Gets or sets a value indicating whether to disable the automatic FoundryLocal manager and use a manually configured endpoint. + /// + public bool DisableFoundryLocalManager { get; set; } + } \ No newline at end of file diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index c3c36e4b..ebfe58aa 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -18,6 +18,8 @@ public class FoundryLocalConnector(AppSettings settings) : LanguageModelConnecto { private readonly AppSettings _appSettings = settings ?? throw new ArgumentNullException(nameof(settings)); + private const string ApiKey = "OPENAI_API_KEY"; + /// public override bool EnsureLanguageModelSettingsValid() { @@ -26,9 +28,24 @@ public override bool EnsureLanguageModelSettingsValid() throw new InvalidOperationException("Missing configuration: FoundryLocal."); } - if (string.IsNullOrWhiteSpace(settings.Alias!.Trim())) + if (settings.DisableFoundryLocalManager == true) { - throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); + if (string.IsNullOrWhiteSpace(settings.Endpoint!.Trim()) == true) + { + throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint is required when DisableFoundryLocalManager is enabled."); + } + + if (string.IsNullOrWhiteSpace(settings.ModelId!.Trim()) == true) + { + throw new InvalidOperationException("Missing configuration: FoundryLocal:ModelId is required when DisableFoundryLocalManager is enabled."); + } + } + else + { + if (string.IsNullOrWhiteSpace(settings.Alias!.Trim())) + { + throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias is required when DisableFoundryLocalManager is disabled."); + } } return true; @@ -38,22 +55,41 @@ public override bool EnsureLanguageModelSettingsValid() public override async Task GetChatClientAsync() { var settings = this.Settings as FoundryLocalSettings; - var alias = settings!.Alias!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); - var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias).ConfigureAwait(false); - var model = await manager.GetModelInfoAsync(aliasOrModelId: alias).ConfigureAwait(false); + Uri endpoint; + string modelId; + + if (settings!.DisableFoundryLocalManager == true) + { + var settingsEndpoint = settings.Endpoint!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint."); + if (Uri.IsWellFormedUriString(settingsEndpoint, UriKind.Absolute) == false) + { + throw new UriFormatException($"Invalid URI: The Foundry Local endpoint '{settingsEndpoint}' is not a valid URI."); + } + endpoint = new Uri(settingsEndpoint); + modelId = settings.ModelId!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:ModelId."); + } + else + { + var alias = settings!.Alias!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); + var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias).ConfigureAwait(false); + var model = await manager.GetModelInfoAsync(aliasOrModelId: alias).ConfigureAwait(false); + + endpoint = manager.Endpoint; + modelId = model?.ModelId ?? alias; + } - var credential = new ApiKeyCredential(manager.ApiKey); + var credential = new ApiKeyCredential(ApiKey); var options = new OpenAIClientOptions() { - Endpoint = manager.Endpoint, + Endpoint = endpoint, }; var client = new OpenAIClient(credential, options); - var chatClient = client.GetChatClient(model?.ModelId) + var chatClient = client.GetChatClient(modelId) .AsIChatClient(); - Console.WriteLine($"The {this._appSettings.ConnectorType} connector created with model: {alias}"); + Console.WriteLine($"The {this._appSettings.ConnectorType} connector created with model: {modelId}"); return chatClient; } diff --git a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs index c3f88624..02221a7c 100644 --- a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs +++ b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs @@ -134,6 +134,21 @@ public static class FoundryLocal /// Defines the constant for '--alias'. /// public const string Alias = "--alias"; + + /// + /// Defines the constant for '--endpoint'. + /// + public const string Endpoint = "--endpoint"; + + /// + /// Defines the constant for '--model-id'. + /// + public const string ModelId = "--model-id"; + + /// + /// Defines the constant for '--disable-foundrylocal-manager'. + /// + public const string DisableFoundryLocalManager = "--disable-foundrylocal-manager"; } /// diff --git a/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs index d19b209a..a8048f3e 100644 --- a/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs @@ -14,6 +14,21 @@ public class FoundryLocalArgumentOptions : ArgumentOptions /// public string? Alias { get; set; } + /// + /// Gets or sets the Endpoint of FoundryLocal. + /// + public string? Endpoint { get; set; } + + /// + /// Gets or sets the model ID of FoundryLocal. + /// + public string? ModelId { get; set; } + + /// + /// Gets or sets a value indicating whether to disable the automatic FoundryLocal manager and use a manually configured endpoint. + /// + public bool DisableFoundryLocalManager { get; set; } + /// protected override void ParseOptions(IConfiguration config, string[] args) { @@ -23,6 +38,9 @@ protected override void ParseOptions(IConfiguration config, string[] args) var foundryLocal = settings.FoundryLocal; this.Alias ??= foundryLocal?.Alias; + this.Endpoint ??= foundryLocal?.Endpoint; + this.ModelId ??= foundryLocal?.ModelId; + this.DisableFoundryLocalManager = foundryLocal?.DisableFoundryLocalManager ?? false; for (var i = 0; i < args.Length; i++) { @@ -35,6 +53,24 @@ protected override void ParseOptions(IConfiguration config, string[] args) } break; + case ArgumentOptionConstants.FoundryLocal.Endpoint: + if (i + 1 < args.Length) + { + this.Endpoint = args[++i]; + } + break; + + case ArgumentOptionConstants.FoundryLocal.ModelId: + if (i + 1 < args.Length) + { + this.ModelId = args[++i]; + } + break; + + case ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager: + this.DisableFoundryLocalManager = true; + break; + default: break; } diff --git a/src/OpenChat.PlaygroundApp/appsettings.json b/src/OpenChat.PlaygroundApp/appsettings.json index 7c909edb..9ca87bb3 100644 --- a/src/OpenChat.PlaygroundApp/appsettings.json +++ b/src/OpenChat.PlaygroundApp/appsettings.json @@ -41,7 +41,9 @@ }, "FoundryLocal": { - "Alias": "phi-4-mini" + "Alias": "phi-4-mini", + "Endpoint": "http://127.0.0.1:55438/v1", + "ModelId": "Phi-4-mini-instruct-generic-cpu:4" }, "HuggingFace": { From bb4ed17d1624034be3cc3c3f2af784943e79f614 Mon Sep 17 00:00:00 2001 From: OkJa Date: Sat, 25 Oct 2025 20:47:05 +0900 Subject: [PATCH 02/12] docs: Update Foundry Local usage instructions and add support for local container --- README.md | 2 +- docs/foundry-local.md | 122 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c3dac65f..3f0b657d 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Open Chat Playground (OCP) is a web UI that is able to connect virtually any LLM - [Use Azure AI Foundry](./docs/azure-ai-foundry.md#run-in-local-container) - [Use GitHub Models](./docs/github-models.md#run-in-local-container) - [Use Docker Model Runner](./docs/docker-model-runner.md#run-in-local-container) -- ~~Use Foundry Local~~ 👉 NOT SUPPORTED +- [Use Foundry Local](./docs/foundry-local.md#run-in-local-container) - [Use Hugging Face](./docs/hugging-face.md#run-in-local-container) - [Use Ollama](./docs/ollama.md#run-on-local-container) - [Use LG](./docs/lg.md#run-in-local-container) diff --git a/docs/foundry-local.md b/docs/foundry-local.md index 53a933f6..fa7ccd2b 100644 --- a/docs/foundry-local.md +++ b/docs/foundry-local.md @@ -74,4 +74,124 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode --alias qwen2.5-7b ``` -1. Open your web browser, navigate to `http://localhost:5280`, and enter prompts. \ No newline at end of file +1. Open your web browser, navigate to `http://localhost:5280`, and enter prompts. + +## Run in local container + +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-instruct-generic-cpu:4`. + + ```bash + foundry model download Phi-4-mini-instruct-generic-cpu:4 + ``` + + Alternatively, if you want to run with a different model, say `qwen2.5-7b-instruct-generic-cpu:3`, other than the default one, download it first by running the following command. + + ```bash + foundry model download qwen2.5-7b-instruct-generic-cpu:3 + ``` + + Make sure to follow the model MUST be selected from the CLI output of `foundry model list`. + +1. Load the Foundry Local model. The default model OCP uses is `Phi-4-mini-instruct-generic-cpu:4`. + + ```bash + foundry model load Phi-4-mini-instruct-generic-cpu:4 + ``` + + Alternatively, if you want to run with a different model, say `qwen2.5-7b-instruct-generic-cpu:3`, other than the default one, download it first by running the following command. + + ```bash + foundry model load qwen2.5-7b-instruct-generic-cpu:3 + ``` + +1. Make sure you are at the repository root. + + ```bash + cd $REPOSITORY_ROOT + ``` + +1. Build a container. + + ```bash + docker build -f Dockerfile -t openchat-playground:latest . + ``` + + 1. Run the app. The default model OCP uses is `Phi-4-mini-instruct-generic-cpu:4`. + + ```bash + # bash/zsh - from locally built container + docker run -i --rm -p 8080:8080 openchat-playground:latest \ + --connector-type FoundryLocal \ + --endpoint http://host.docker.internal:55438/v1 \ + --disable-foundrylocal-manager + ``` + + ```powershell + # PowerShell - from locally built container + docker run -i --rm -p 8080:8080 openchat-playground:latest ` + --connector-type FoundryLocal ` + --endpoint http://host.docker.internal:55438/v1 ` + --disable-foundrylocal-manager + ``` + + ```bash + # bash/zsh - from GitHub Container Registry + docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest \ + --connector-type FoundryLocal \ + --endpoint http://host.docker.internal:55438/v1 \ + --disable-foundrylocal-manager + ``` + + ```powershell + # PowerShell - from GitHub Container Registry + docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest ` + --connector-type FoundryLocal ` + --endpoint http://host.docker.internal:55438/v1 ` + --disable-foundrylocal-manager + ``` + + Alternatively, if you want to run with a different model, say `qwen2.5-7b-instruct-generic-cpu:3`, make sure you've already downloaded the model by running the `foundry model load qwen2.5-7b-instruct-generic-cpu:3` command. + + ```bash + # bash/zsh - from locally built container + docker run -i --rm -p 8080:8080 openchat-playground:latest \ + --connector-type FoundryLocal \ + --endpoint http://host.docker.internal:55438/v1 \ + --model-id qwen2.5-7b-instruct-generic-cpu:3 \ + --disable-foundrylocal-manager + ``` + + ```powershell + # PowerShell - from locally built container + docker run -i --rm -p 8080:8080 openchat-playground:latest ` + --connector-type FoundryLocal ` + --endpoint http://host.docker.internal:55438/v1 ` + --model-id qwen2.5-7b-instruct-generic-cpu:3 ` + --disable-foundrylocal-manager + ``` + + ```bash + # bash/zsh - from GitHub Container Registry + docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest \ + --connector-type FoundryLocal \ + --endpoint http://host.docker.internal:55438/v1 \ + --model-id qwen2.5-7b-instruct-generic-cpu:3 \ + --disable-foundrylocal-manager + ``` + + ```powershell + # PowerShell - from GitHub Container Registry + docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest ` + --connector-type FoundryLocal ` + --endpoint http://host.docker.internal:55438/v1 ` + --model-id qwen2.5-7b-instruct-generic-cpu:3 ` + --disable-foundrylocal-manager + ``` + +1. Open your web browser, navigate to `http://localhost:8080`, and enter prompts. From 618802b9eced83ea2cdc1fa352d8abaa455ba775 Mon Sep 17 00:00:00 2001 From: OkJa Date: Sun, 26 Oct 2025 09:11:17 +0900 Subject: [PATCH 03/12] test, foundry local connectort --- .../Connectors/FoundryLocalConnectorTests.cs | 124 ++++++++++++++++-- 1 file changed, 116 insertions(+), 8 deletions(-) diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs index 8a47a1b9..4c83018c 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -1,3 +1,5 @@ +using System.Threading.Tasks; + using Microsoft.Extensions.AI; using OpenChat.PlaygroundApp.Abstractions; @@ -9,15 +11,22 @@ namespace OpenChat.PlaygroundApp.Tests.Connectors; public class FoundryLocalConnectorTests { private const string Alias = "phi-4-mini"; + private const string Endpoint = "http://127.0.0.1:55438/v1"; + private const string ModelId = "Phi-4-mini-instruct-generic-cpu:4"; + private const bool DisableFoundryLocalManager = false; - private static AppSettings BuildAppSettings(string? alias = Alias) + private static AppSettings BuildAppSettings( + string? alias = Alias, string? endpoint = Endpoint, string? modelId = ModelId, bool disableFoundryLocalManager = DisableFoundryLocalManager) { return new AppSettings { ConnectorType = ConnectorType.FoundryLocal, FoundryLocal = new FoundryLocalSettings { - Alias = alias + Alias = alias, + Endpoint = endpoint, + ModelId = modelId, + DisableFoundryLocalManager = disableFoundryLocalManager } }; } @@ -102,6 +111,46 @@ public void Given_Invalid_Alias_When_EnsureLanguageModelSettingsValid_Invoked_Th .Message.ShouldContain(expectedMessage); } + [Trait("Category", "UnitTest")] + [Theory] + [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] + [InlineData("", typeof(InvalidOperationException), "FoundryLocal:Endpoint")] + [InlineData(" ", typeof(InvalidOperationException), "FoundryLocal:Endpoint")] + [InlineData("\t\n\r", typeof(InvalidOperationException), "FoundryLocal:Endpoint")] + public void Given_Invalid_Endpoint_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? endpoint, Type expectedType, string expectedMessage) + { + // Arrange + var settings = BuildAppSettings(endpoint: endpoint, disableFoundryLocalManager: true); + var connector = new FoundryLocalConnector(settings); + + // Act + Action action = () => connector.EnsureLanguageModelSettingsValid(); + + // Assert + action.ShouldThrow(expectedType) + .Message.ShouldContain(expectedMessage); + } + + [Trait("Category", "UnitTest")] + [Theory] + [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] + [InlineData("", typeof(InvalidOperationException), "FoundryLocal:ModelId")] + [InlineData(" ", typeof(InvalidOperationException), "FoundryLocal:ModelId")] + [InlineData("\t\n\r", typeof(InvalidOperationException), "FoundryLocal:ModelId")] + public void Given_Invalid_ModelId_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? modelId, Type expectedType, string expectedMessage) + { + // Arrange + var settings = BuildAppSettings(modelId: modelId, disableFoundryLocalManager: true); + var connector = new FoundryLocalConnector(settings); + + // 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() @@ -177,6 +226,51 @@ public void Given_Invalid_Alias_When_GetChatClient_Invoked_Then_It_Should_Throw( .Message.ShouldContain(message); } + [Trait("Category", "IntegrationTest")] + [Trait("Category", "LLMRequired")] + [Trait("Category", "IgnoreGitHubActions")] + [Theory] + [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] + [InlineData("", typeof(UriFormatException), "Invalid URI:")] + [InlineData(" ", typeof(UriFormatException), "Invalid URI:")] + [InlineData("\t\r\n", typeof(UriFormatException), "Invalid URI:")] + [InlineData("invalid-uri-format", typeof(UriFormatException), "Invalid URI:")] + public void Given_Invalid_Endpoint_When_GetChatClient_Invoked_Then_It_Should_Throw(string? endpoint, Type expected, string message) + { + // Arrange + var settings = BuildAppSettings(endpoint: endpoint, disableFoundryLocalManager: true); + 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")] + [Trait("Category", "IgnoreGitHubActions")] + [Theory] + [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] + [InlineData("", typeof(ArgumentException), "Value cannot be an empty string.")] + [InlineData(" ", typeof(ArgumentException), "Value cannot be an empty string.")] + [InlineData("\t\r\n", typeof(ArgumentException), "Value cannot be an empty string.")] + public async Task Given_Invalid_ModelId_When_GetChatClient_Invoked_Then_It_Should_Throw(string? modelId, Type expected, string message) + { + // Arrange + var settings = BuildAppSettings(modelId: modelId, disableFoundryLocalManager: true); + 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")] [Trait("Category", "IgnoreGitHubActions")] @@ -197,11 +291,22 @@ public async Task Given_Valid_Settings_When_GetChatClient_Invoked_Then_It_Should [Trait("Category", "UnitTest")] [Theory] - [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] - [InlineData("", typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - [InlineData(" ", typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - [InlineData("\t\r\n", typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - public void Given_Invalid_Alias_When_CreateChatClientAsync_Invoked_Then_It_Should_Throw(string? alias, Type expected, string expectedMessage) + [InlineData(null, null, null, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(null, Endpoint, ModelId, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData("", Endpoint, ModelId, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] + [InlineData(" ", Endpoint, ModelId, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] + [InlineData("\t\r\n", Endpoint, ModelId, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] + [InlineData(null, null, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(Alias, null, ModelId, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(Alias, "", ModelId, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] + [InlineData(Alias, " ", ModelId, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] + [InlineData(Alias, "\t\r\n", ModelId, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] + [InlineData(Alias, Endpoint, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(Alias, Endpoint, "", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:ModelId")] + [InlineData(Alias, Endpoint, " ", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:ModelId")] + [InlineData(Alias, Endpoint, "\t\r\n", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:ModelId")] + public void Given_Invalid_Alias_When_CreateChatClientAsync_Invoked_Then_It_Should_Throw( + string? alias, string? endpoint, string? modelId, bool disableFoundryLocalManager, Type expected, string expectedMessage) { // Arrange var settings = new AppSettings @@ -209,7 +314,10 @@ public void Given_Invalid_Alias_When_CreateChatClientAsync_Invoked_Then_It_Shoul ConnectorType = ConnectorType.FoundryLocal, FoundryLocal = new FoundryLocalSettings { - Alias = alias + Alias = alias, + Endpoint = endpoint, + ModelId = modelId, + DisableFoundryLocalManager = disableFoundryLocalManager } }; From dd9bd98dbddd64766e766c4f82425719bd25fade Mon Sep 17 00:00:00 2001 From: OkJa Date: Mon, 27 Oct 2025 18:11:37 +0900 Subject: [PATCH 04/12] feat: Remove ModelId from FoundryLocal settings and update related configurations --- .../Abstractions/ArgumentOptions.cs | 7 +----- .../Configurations/FoundryLocalSettings.cs | 5 ----- .../Connectors/FoundryLocalConnector.cs | 22 +++++-------------- .../Constants/ArgumentOptionConstants.cs | 5 ----- .../Options/FoundryLocalArgumentOptions.cs | 13 ----------- src/OpenChat.PlaygroundApp/appsettings.json | 3 +-- 6 files changed, 8 insertions(+), 47 deletions(-) diff --git a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs index 28417e38..53d6ef05 100644 --- a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs @@ -34,7 +34,6 @@ private static readonly (ConnectorType ConnectorType, string Argument, bool IsSw // Foundry Local (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Alias, false), (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Endpoint, false), - (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.ModelId, false), (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager, false), // Hugging Face (ConnectorType.HuggingFace, ArgumentOptionConstants.HuggingFace.BaseUrl, false), @@ -217,12 +216,9 @@ public static AppSettings Parse(IConfiguration config, string[] args) settings.FoundryLocal ??= new FoundryLocalSettings(); settings.FoundryLocal.Alias = foundryLocal.Alias ?? settings.FoundryLocal.Alias; settings.FoundryLocal.Endpoint = foundryLocal.Endpoint ?? settings.FoundryLocal.Endpoint; - settings.FoundryLocal.ModelId = foundryLocal.ModelId ?? settings.FoundryLocal.ModelId; settings.FoundryLocal.DisableFoundryLocalManager = foundryLocal.DisableFoundryLocalManager; - settings.Model = foundryLocal.DisableFoundryLocalManager - ? foundryLocal.ModelId ?? settings.FoundryLocal.ModelId - : foundryLocal.Alias ?? settings.FoundryLocal.Alias; + settings.Model = foundryLocal.Alias ?? settings.FoundryLocal.Alias; break; case HuggingFaceArgumentOptions huggingFace: @@ -435,7 +431,6 @@ private static void DisplayHelpForFoundryLocal() Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Alias} The alias. Default to 'phi-4-mini'"); Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Endpoint} The endpoint URL. Default to 'http://127.0.0.1:55438/v1'"); - Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.ModelId} The model ID. Default to 'Phi-4-mini-instruct-generic-cpu:4'"); Console.WriteLine(); } diff --git a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs index 9f1b817f..9dfa65a4 100644 --- a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs +++ b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs @@ -26,11 +26,6 @@ public class FoundryLocalSettings : LanguageModelSettings /// public string? Endpoint { get; set; } - /// - /// Gets or sets the model ID of FoundryLocal. - /// - public string? ModelId { get; set; } - /// /// Gets or sets a value indicating whether to disable the automatic FoundryLocal manager and use a manually configured endpoint. /// diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index ebfe58aa..dfddecfe 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -28,24 +28,14 @@ public override bool EnsureLanguageModelSettingsValid() throw new InvalidOperationException("Missing configuration: FoundryLocal."); } - if (settings.DisableFoundryLocalManager == true) + if (string.IsNullOrWhiteSpace(settings.Alias!.Trim())) { - if (string.IsNullOrWhiteSpace(settings.Endpoint!.Trim()) == true) - { - throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint is required when DisableFoundryLocalManager is enabled."); - } - - if (string.IsNullOrWhiteSpace(settings.ModelId!.Trim()) == true) - { - throw new InvalidOperationException("Missing configuration: FoundryLocal:ModelId is required when DisableFoundryLocalManager is enabled."); - } + throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); } - else + + if (settings.DisableFoundryLocalManager == true && string.IsNullOrWhiteSpace(settings.Endpoint!.Trim()) == true) { - if (string.IsNullOrWhiteSpace(settings.Alias!.Trim())) - { - throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias is required when DisableFoundryLocalManager is disabled."); - } + throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint is required when DisableFoundryLocalManager is enabled."); } return true; @@ -61,13 +51,13 @@ public override async Task GetChatClientAsync() if (settings!.DisableFoundryLocalManager == true) { + modelId = settings.Alias!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); var settingsEndpoint = settings.Endpoint!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint."); if (Uri.IsWellFormedUriString(settingsEndpoint, UriKind.Absolute) == false) { throw new UriFormatException($"Invalid URI: The Foundry Local endpoint '{settingsEndpoint}' is not a valid URI."); } endpoint = new Uri(settingsEndpoint); - modelId = settings.ModelId!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:ModelId."); } else { diff --git a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs index 02221a7c..041d8c66 100644 --- a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs +++ b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs @@ -140,11 +140,6 @@ public static class FoundryLocal /// public const string Endpoint = "--endpoint"; - /// - /// Defines the constant for '--model-id'. - /// - public const string ModelId = "--model-id"; - /// /// Defines the constant for '--disable-foundrylocal-manager'. /// diff --git a/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs index a8048f3e..5bb52621 100644 --- a/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs @@ -19,11 +19,6 @@ public class FoundryLocalArgumentOptions : ArgumentOptions /// public string? Endpoint { get; set; } - /// - /// Gets or sets the model ID of FoundryLocal. - /// - public string? ModelId { get; set; } - /// /// Gets or sets a value indicating whether to disable the automatic FoundryLocal manager and use a manually configured endpoint. /// @@ -39,7 +34,6 @@ protected override void ParseOptions(IConfiguration config, string[] args) this.Alias ??= foundryLocal?.Alias; this.Endpoint ??= foundryLocal?.Endpoint; - this.ModelId ??= foundryLocal?.ModelId; this.DisableFoundryLocalManager = foundryLocal?.DisableFoundryLocalManager ?? false; for (var i = 0; i < args.Length; i++) @@ -60,13 +54,6 @@ protected override void ParseOptions(IConfiguration config, string[] args) } break; - case ArgumentOptionConstants.FoundryLocal.ModelId: - if (i + 1 < args.Length) - { - this.ModelId = args[++i]; - } - break; - case ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager: this.DisableFoundryLocalManager = true; break; diff --git a/src/OpenChat.PlaygroundApp/appsettings.json b/src/OpenChat.PlaygroundApp/appsettings.json index 9ca87bb3..b2f016cf 100644 --- a/src/OpenChat.PlaygroundApp/appsettings.json +++ b/src/OpenChat.PlaygroundApp/appsettings.json @@ -42,8 +42,7 @@ "FoundryLocal": { "Alias": "phi-4-mini", - "Endpoint": "http://127.0.0.1:55438/v1", - "ModelId": "Phi-4-mini-instruct-generic-cpu:4" + "Endpoint": "http://127.0.0.1:55438/v1" }, "HuggingFace": { From 6dae7109ab872e1cdb17206ea56292ace186b8a2 Mon Sep 17 00:00:00 2001 From: OkJa Date: Mon, 27 Oct 2025 18:16:29 +0900 Subject: [PATCH 05/12] feat: Remove ModelId from FoundryLocalConnector tests and update related methods --- .../Connectors/FoundryLocalConnectorTests.cs | 73 +++---------------- 1 file changed, 12 insertions(+), 61 deletions(-) diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs index 4c83018c..ac77379b 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -12,11 +12,10 @@ public class FoundryLocalConnectorTests { private const string Alias = "phi-4-mini"; private const string Endpoint = "http://127.0.0.1:55438/v1"; - private const string ModelId = "Phi-4-mini-instruct-generic-cpu:4"; private const bool DisableFoundryLocalManager = false; private static AppSettings BuildAppSettings( - string? alias = Alias, string? endpoint = Endpoint, string? modelId = ModelId, bool disableFoundryLocalManager = DisableFoundryLocalManager) + string? alias = Alias, string? endpoint = Endpoint, bool disableFoundryLocalManager = DisableFoundryLocalManager) { return new AppSettings { @@ -25,7 +24,6 @@ private static AppSettings BuildAppSettings( { Alias = alias, Endpoint = endpoint, - ModelId = modelId, DisableFoundryLocalManager = disableFoundryLocalManager } }; @@ -131,26 +129,6 @@ public void Given_Invalid_Endpoint_When_EnsureLanguageModelSettingsValid_Invoked .Message.ShouldContain(expectedMessage); } - [Trait("Category", "UnitTest")] - [Theory] - [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] - [InlineData("", typeof(InvalidOperationException), "FoundryLocal:ModelId")] - [InlineData(" ", typeof(InvalidOperationException), "FoundryLocal:ModelId")] - [InlineData("\t\n\r", typeof(InvalidOperationException), "FoundryLocal:ModelId")] - public void Given_Invalid_ModelId_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? modelId, Type expectedType, string expectedMessage) - { - // Arrange - var settings = BuildAppSettings(modelId: modelId, disableFoundryLocalManager: true); - var connector = new FoundryLocalConnector(settings); - - // 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() @@ -249,28 +227,6 @@ public void Given_Invalid_Endpoint_When_GetChatClient_Invoked_Then_It_Should_Thr .Message.ShouldContain(message); } - [Trait("Category", "IntegrationTest")] - [Trait("Category", "LLMRequired")] - [Trait("Category", "IgnoreGitHubActions")] - [Theory] - [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] - [InlineData("", typeof(ArgumentException), "Value cannot be an empty string.")] - [InlineData(" ", typeof(ArgumentException), "Value cannot be an empty string.")] - [InlineData("\t\r\n", typeof(ArgumentException), "Value cannot be an empty string.")] - public async Task Given_Invalid_ModelId_When_GetChatClient_Invoked_Then_It_Should_Throw(string? modelId, Type expected, string message) - { - // Arrange - var settings = BuildAppSettings(modelId: modelId, disableFoundryLocalManager: true); - 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")] [Trait("Category", "IgnoreGitHubActions")] @@ -291,22 +247,18 @@ public async Task Given_Valid_Settings_When_GetChatClient_Invoked_Then_It_Should [Trait("Category", "UnitTest")] [Theory] - [InlineData(null, null, null, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData(null, Endpoint, ModelId, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData("", Endpoint, ModelId, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - [InlineData(" ", Endpoint, ModelId, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - [InlineData("\t\r\n", Endpoint, ModelId, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - [InlineData(null, null, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData(Alias, null, ModelId, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData(Alias, "", ModelId, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] - [InlineData(Alias, " ", ModelId, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] - [InlineData(Alias, "\t\r\n", ModelId, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] - [InlineData(Alias, Endpoint, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData(Alias, Endpoint, "", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:ModelId")] - [InlineData(Alias, Endpoint, " ", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:ModelId")] - [InlineData(Alias, Endpoint, "\t\r\n", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:ModelId")] + [InlineData(null, null, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(null, Endpoint, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData("", Endpoint, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] + [InlineData(" ", Endpoint, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] + [InlineData("\t\r\n", Endpoint, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] + [InlineData(null, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(Alias, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(Alias, "", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] + [InlineData(Alias, " ", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] + [InlineData(Alias, "\t\r\n", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] public void Given_Invalid_Alias_When_CreateChatClientAsync_Invoked_Then_It_Should_Throw( - string? alias, string? endpoint, string? modelId, bool disableFoundryLocalManager, Type expected, string expectedMessage) + string? alias, string? endpoint, bool disableFoundryLocalManager, Type expected, string expectedMessage) { // Arrange var settings = new AppSettings @@ -316,7 +268,6 @@ public void Given_Invalid_Alias_When_CreateChatClientAsync_Invoked_Then_It_Shoul { Alias = alias, Endpoint = endpoint, - ModelId = modelId, DisableFoundryLocalManager = disableFoundryLocalManager } }; From 424914d9c51cd5b31682c8a0a0f67f4462341843 Mon Sep 17 00:00:00 2001 From: OkJa Date: Mon, 27 Oct 2025 19:49:17 +0900 Subject: [PATCH 06/12] docs: Update Foundry Local documentation with container --- docs/foundry-local.md | 73 +++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/docs/foundry-local.md b/docs/foundry-local.md index fa7ccd2b..b847f8b6 100644 --- a/docs/foundry-local.md +++ b/docs/foundry-local.md @@ -78,36 +78,48 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode ## Run in local container +1. Set the Foundry Local service port. The default port OCP uses is `55438`. + + ```bash + foundry service set --port 55438 + ``` + + Alternatively, if you want to run with a different port, say `63997`, other than the default one, set it first by running the following command. + + ```bash + foundry service set --port 63997 + ``` + 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-instruct-generic-cpu:4`. +1. Download the Foundry Local model. The default model OCP uses is `phi-4-mini`. ```bash - foundry model download Phi-4-mini-instruct-generic-cpu:4 + foundry model download phi-4-mini ``` - Alternatively, if you want to run with a different model, say `qwen2.5-7b-instruct-generic-cpu:3`, other than the default one, download it first by running the following command. + 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-instruct-generic-cpu:3 + foundry model download qwen2.5-7b ``` Make sure to follow the model MUST be selected from the CLI output of `foundry model list`. -1. Load the Foundry Local model. The default model OCP uses is `Phi-4-mini-instruct-generic-cpu:4`. +1. Load the Foundry Local model. The default model OCP uses is `phi-4-mini`. ```bash - foundry model load Phi-4-mini-instruct-generic-cpu:4 + foundry model load phi-4-mini ``` - Alternatively, if you want to run with a different model, say `qwen2.5-7b-instruct-generic-cpu:3`, other than the default one, download it first by running the following command. + 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 load qwen2.5-7b-instruct-generic-cpu:3 + foundry model load qwen2.5-7b ``` 1. Make sure you are at the repository root. @@ -122,12 +134,13 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode docker build -f Dockerfile -t openchat-playground:latest . ``` - 1. Run the app. The default model OCP uses is `Phi-4-mini-instruct-generic-cpu:4`. + 1. Run the app. The `{{Model ID}}` refers to the `Model ID` shown in the output of the `foundry service list` command. ```bash # bash/zsh - from locally built container docker run -i --rm -p 8080:8080 openchat-playground:latest \ --connector-type FoundryLocal \ + --alias {{Model ID}} \ --endpoint http://host.docker.internal:55438/v1 \ --disable-foundrylocal-manager ``` @@ -136,6 +149,7 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode # PowerShell - from locally built container docker run -i --rm -p 8080:8080 openchat-playground:latest ` --connector-type FoundryLocal ` + --alias {{Model ID}} ` --endpoint http://host.docker.internal:55438/v1 ` --disable-foundrylocal-manager ``` @@ -144,6 +158,7 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode # bash/zsh - from GitHub Container Registry docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest \ --connector-type FoundryLocal \ + --alias {{Model ID}} \ --endpoint http://host.docker.internal:55438/v1 \ --disable-foundrylocal-manager ``` @@ -152,46 +167,8 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode # PowerShell - from GitHub Container Registry docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest ` --connector-type FoundryLocal ` + --alias {{Model ID}} ` --endpoint http://host.docker.internal:55438/v1 ` --disable-foundrylocal-manager - ``` - - Alternatively, if you want to run with a different model, say `qwen2.5-7b-instruct-generic-cpu:3`, make sure you've already downloaded the model by running the `foundry model load qwen2.5-7b-instruct-generic-cpu:3` command. - - ```bash - # bash/zsh - from locally built container - docker run -i --rm -p 8080:8080 openchat-playground:latest \ - --connector-type FoundryLocal \ - --endpoint http://host.docker.internal:55438/v1 \ - --model-id qwen2.5-7b-instruct-generic-cpu:3 \ - --disable-foundrylocal-manager - ``` - - ```powershell - # PowerShell - from locally built container - docker run -i --rm -p 8080:8080 openchat-playground:latest ` - --connector-type FoundryLocal ` - --endpoint http://host.docker.internal:55438/v1 ` - --model-id qwen2.5-7b-instruct-generic-cpu:3 ` - --disable-foundrylocal-manager - ``` - - ```bash - # bash/zsh - from GitHub Container Registry - docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest \ - --connector-type FoundryLocal \ - --endpoint http://host.docker.internal:55438/v1 \ - --model-id qwen2.5-7b-instruct-generic-cpu:3 \ - --disable-foundrylocal-manager - ``` - - ```powershell - # PowerShell - from GitHub Container Registry - docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest ` - --connector-type FoundryLocal ` - --endpoint http://host.docker.internal:55438/v1 ` - --model-id qwen2.5-7b-instruct-generic-cpu:3 ` - --disable-foundrylocal-manager - ``` 1. Open your web browser, navigate to `http://localhost:8080`, and enter prompts. From d3bbc05851bf88acadaa5e1cb909577531c8e3f2 Mon Sep 17 00:00:00 2001 From: OkJa Date: Mon, 27 Oct 2025 19:58:05 +0900 Subject: [PATCH 07/12] feat: code clarity --- src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs | 1 - .../Configurations/FoundryLocalSettings.cs | 1 - src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs | 3 ++- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs index 53d6ef05..b78169b3 100644 --- a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs @@ -423,7 +423,6 @@ private static void DisplayHelpForDockerModelRunner() private static void DisplayHelpForFoundryLocal() { - // --model The OpenAI model name. Default to 'gpt-4.1-mini' var foregroundColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(" ** Foundry Local: **"); diff --git a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs index 9dfa65a4..acfecb02 100644 --- a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs +++ b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs @@ -30,5 +30,4 @@ public class FoundryLocalSettings : LanguageModelSettings /// Gets or sets a value indicating whether to disable the automatic FoundryLocal manager and use a manually configured endpoint. /// public bool DisableFoundryLocalManager { get; set; } - } \ No newline at end of file diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index dfddecfe..1ec5f241 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -51,13 +51,14 @@ public override async Task GetChatClientAsync() if (settings!.DisableFoundryLocalManager == true) { - modelId = settings.Alias!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); var settingsEndpoint = settings.Endpoint!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint."); if (Uri.IsWellFormedUriString(settingsEndpoint, UriKind.Absolute) == false) { throw new UriFormatException($"Invalid URI: The Foundry Local endpoint '{settingsEndpoint}' is not a valid URI."); } + endpoint = new Uri(settingsEndpoint); + modelId = settings.Alias!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); } else { From bdb91f63a6c04e2528412dcf59861ea506aaa73c Mon Sep 17 00:00:00 2001 From: OkJa Date: Mon, 27 Oct 2025 20:04:41 +0900 Subject: [PATCH 08/12] missing ``` --- docs/foundry-local.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/foundry-local.md b/docs/foundry-local.md index b847f8b6..58de8210 100644 --- a/docs/foundry-local.md +++ b/docs/foundry-local.md @@ -170,5 +170,6 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode --alias {{Model ID}} ` --endpoint http://host.docker.internal:55438/v1 ` --disable-foundrylocal-manager + ``` 1. Open your web browser, navigate to `http://localhost:8080`, and enter prompts. From 64486673732d15643bd9fb8dfb6393c1e48d425e Mon Sep 17 00:00:00 2001 From: OkJa Date: Mon, 27 Oct 2025 20:06:38 +0900 Subject: [PATCH 09/12] spacing --- docs/foundry-local.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/foundry-local.md b/docs/foundry-local.md index 58de8210..59f01ce0 100644 --- a/docs/foundry-local.md +++ b/docs/foundry-local.md @@ -134,7 +134,7 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode docker build -f Dockerfile -t openchat-playground:latest . ``` - 1. Run the app. The `{{Model ID}}` refers to the `Model ID` shown in the output of the `foundry service list` command. +1. Run the app. The `{{Model ID}}` refers to the `Model ID` shown in the output of the `foundry service list` command. ```bash # bash/zsh - from locally built container From 2947935a037b88d7c4b3828121f05f4fcd4ef82d Mon Sep 17 00:00:00 2001 From: OkJa Date: Mon, 27 Oct 2025 20:25:51 +0900 Subject: [PATCH 10/12] fix: ensure modelId is always assigned from model in FoundryLocalConnector --- 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 1ec5f241..0ba9de48 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -67,7 +67,7 @@ public override async Task GetChatClientAsync() var model = await manager.GetModelInfoAsync(aliasOrModelId: alias).ConfigureAwait(false); endpoint = manager.Endpoint; - modelId = model?.ModelId ?? alias; + modelId = model!.ModelId; } var credential = new ApiKeyCredential(ApiKey); From ecb71d7d51993a2f68d2f56cd553fa57f27804c7 Mon Sep 17 00:00:00 2001 From: Justin Yoo Date: Wed, 29 Oct 2025 20:29:28 +0300 Subject: [PATCH 11/12] Update FoundryLocal connector --- .../Abstractions/ArgumentOptions.cs | 15 +-- .../Configurations/FoundryLocalSettings.cs | 10 +- .../Connectors/FoundryLocalConnector.cs | 78 ++++++++++------ .../Constants/ArgumentOptionConstants.cs | 18 +++- .../Options/FoundryLocalArgumentOptions.cs | 22 +++-- src/OpenChat.PlaygroundApp/appsettings.json | 5 +- .../Abstractions/ArgumentOptionsTests.cs | 4 +- .../Connectors/FoundryLocalConnectorTests.cs | 91 ++++++++++--------- .../FoundryLocalArgumentOptionsTests.cs | 26 +++--- 9 files changed, 153 insertions(+), 116 deletions(-) diff --git a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs index b78169b3..d161e90d 100644 --- a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs @@ -32,9 +32,10 @@ private static readonly (ConnectorType ConnectorType, string Argument, bool IsSw (ConnectorType.DockerModelRunner, ArgumentOptionConstants.DockerModelRunner.BaseUrl, false), (ConnectorType.DockerModelRunner, ArgumentOptionConstants.DockerModelRunner.Model, false), // Foundry Local + (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.BaseUrl, false), (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Alias, false), - (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Endpoint, false), - (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager, false), + (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager, true), + (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManagerInShort, true), // Hugging Face (ConnectorType.HuggingFace, ArgumentOptionConstants.HuggingFace.BaseUrl, false), (ConnectorType.HuggingFace, ArgumentOptionConstants.HuggingFace.Model, false), @@ -214,11 +215,11 @@ public static AppSettings Parse(IConfiguration config, string[] args) case FoundryLocalArgumentOptions foundryLocal: settings.FoundryLocal ??= new FoundryLocalSettings(); - settings.FoundryLocal.Alias = foundryLocal.Alias ?? settings.FoundryLocal.Alias; - settings.FoundryLocal.Endpoint = foundryLocal.Endpoint ?? settings.FoundryLocal.Endpoint; + settings.FoundryLocal.BaseUrl = foundryLocal.BaseUrl ?? settings.FoundryLocal.BaseUrl; + settings.FoundryLocal.AliasOrModel = foundryLocal.AliasOrModel ?? settings.FoundryLocal.AliasOrModel; settings.FoundryLocal.DisableFoundryLocalManager = foundryLocal.DisableFoundryLocalManager; - settings.Model = foundryLocal.Alias ?? settings.FoundryLocal.Alias; + settings.Model = foundryLocal.AliasOrModel ?? settings.FoundryLocal.AliasOrModel; break; case HuggingFaceArgumentOptions huggingFace: @@ -428,8 +429,10 @@ private static void DisplayHelpForFoundryLocal() Console.WriteLine(" ** Foundry Local: **"); Console.ForegroundColor = foregroundColor; + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.BaseUrl} The endpoint URL. Default to 'http://127.0.0.1:55434/'"); Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Alias} The alias. Default to 'phi-4-mini'"); - Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Endpoint} The endpoint URL. Default to 'http://127.0.0.1:55438/v1'"); + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager}|{ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManagerInShort} Disable the built-in Foundry local manager."); + Console.WriteLine($" When this flag is set, you must specify '--base-url'."); Console.WriteLine(); } diff --git a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs index acfecb02..2ad0023e 100644 --- a/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs +++ b/src/OpenChat.PlaygroundApp/Configurations/FoundryLocalSettings.cs @@ -17,17 +17,17 @@ public partial class AppSettings public class FoundryLocalSettings : LanguageModelSettings { /// - /// Gets or sets the alias of FoundryLocal. + /// Gets or sets the Base URL of Foundry Local. If `DisableFoundryLocalManager` is set, this value must be provided. /// - public string? Alias { get; set; } + public string? BaseUrl { get; set; } /// - /// Gets or sets the Endpoint of FoundryLocal. + /// Gets or sets either alias or model ID of Foundry Local. /// - public string? Endpoint { get; set; } + public string? AliasOrModel { get; set; } /// - /// Gets or sets a value indicating whether to disable the automatic FoundryLocal manager and use a manually configured endpoint. + /// Gets or sets a value indicating whether to disable the automatic Foundry Local manager and use a manually configured endpoint. /// public bool DisableFoundryLocalManager { get; set; } } \ No newline at end of file diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index 0ba9de48..fbb3f076 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -1,4 +1,5 @@ using System.ClientModel; +using System.Text.RegularExpressions; using Microsoft.AI.Foundry.Local; using Microsoft.Extensions.AI; @@ -16,10 +17,12 @@ namespace OpenChat.PlaygroundApp.Connectors; /// instance. public class FoundryLocalConnector(AppSettings settings) : LanguageModelConnector(settings.FoundryLocal) { - private readonly AppSettings _appSettings = settings ?? throw new ArgumentNullException(nameof(settings)); - private const string ApiKey = "OPENAI_API_KEY"; + private static readonly Regex modelIdSuffix = new(@"\:[0-9]+$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private readonly AppSettings _appSettings = settings ?? throw new ArgumentNullException(nameof(settings)); + /// public override bool EnsureLanguageModelSettingsValid() { @@ -28,14 +31,21 @@ public override bool EnsureLanguageModelSettingsValid() throw new InvalidOperationException("Missing configuration: FoundryLocal."); } - if (string.IsNullOrWhiteSpace(settings.Alias!.Trim())) + if (settings.DisableFoundryLocalManager == true && + string.IsNullOrWhiteSpace(settings.BaseUrl!.Trim()) == true) + { + throw new InvalidOperationException("Missing configuration: FoundryLocal:BaseUrl is required when DisableFoundryLocalManager is enabled."); + } + + if (string.IsNullOrWhiteSpace(settings.AliasOrModel!.Trim()) == true) { - throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); + throw new InvalidOperationException("Missing configuration: FoundryLocal:AliasOrModel."); } - if (settings.DisableFoundryLocalManager == true && string.IsNullOrWhiteSpace(settings.Endpoint!.Trim()) == true) + if (settings.DisableFoundryLocalManager == true && + modelIdSuffix.IsMatch(settings.AliasOrModel!.Trim()!) == false) { - throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint is required when DisableFoundryLocalManager is enabled."); + throw new InvalidOperationException("When DisableFoundryLocalManager is enabled, FoundryLocal:AliasOrModel must be the exact model name with version suffix."); } return true; @@ -46,29 +56,9 @@ public override async Task GetChatClientAsync() { var settings = this.Settings as FoundryLocalSettings; - Uri endpoint; - string modelId; - - if (settings!.DisableFoundryLocalManager == true) - { - var settingsEndpoint = settings.Endpoint!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Endpoint."); - if (Uri.IsWellFormedUriString(settingsEndpoint, UriKind.Absolute) == false) - { - throw new UriFormatException($"Invalid URI: The Foundry Local endpoint '{settingsEndpoint}' is not a valid URI."); - } - - endpoint = new Uri(settingsEndpoint); - modelId = settings.Alias!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); - } - else - { - var alias = settings!.Alias!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:Alias."); - var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias).ConfigureAwait(false); - var model = await manager.GetModelInfoAsync(aliasOrModelId: alias).ConfigureAwait(false); - - endpoint = manager.Endpoint; - modelId = model!.ModelId; - } + (Uri? endpoint, string? modelId) = settings!.DisableFoundryLocalManager == true + ? ParseFromModelId(settings) + : await ParseFromManagerAsync(settings).ConfigureAwait(false); var credential = new ApiKeyCredential(ApiKey); var options = new OpenAIClientOptions() @@ -84,4 +74,34 @@ public override async Task GetChatClientAsync() return chatClient; } + + private static (Uri? endpoint, string? modelId) ParseFromModelId(FoundryLocalSettings settings) + { + var baseUrl = settings.BaseUrl!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:BaseUrl."); + if (Uri.IsWellFormedUriString(baseUrl, UriKind.Absolute) == false) + { + throw new UriFormatException($"Invalid URI: The Foundry Local base URL '{baseUrl}' is not a valid URI."); + } + + var endpoint = new Uri($"{baseUrl.TrimEnd('/')}/v1"); + var modelId = settings.AliasOrModel!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:AliasOrModel."); + if (modelIdSuffix.IsMatch(modelId) == false) + { + throw new InvalidOperationException("When DisableFoundryLocalManager is enabled, FoundryLocal:AliasOrModel must be the exact model name with version suffix."); + } + + return (endpoint, modelId); + } + + private static async Task<(Uri? endpoint, string? modelId)> ParseFromManagerAsync(FoundryLocalSettings settings) + { + var alias = settings!.AliasOrModel!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:AliasOrModel."); + var manager = await FoundryLocalManager.StartModelAsync(aliasOrModelId: alias).ConfigureAwait(false); + var model = await manager.GetModelInfoAsync(aliasOrModelId: alias).ConfigureAwait(false); + + var endpoint = manager.Endpoint; + var modelId = model!.ModelId; + + return (endpoint, modelId); + } } \ No newline at end of file diff --git a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs index 041d8c66..97baa207 100644 --- a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs +++ b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs @@ -130,20 +130,30 @@ public static class DockerModelRunner /// public static class FoundryLocal { + /// + /// Defines the constant for '--base-url'. + /// + public const string BaseUrl = "--base-url"; + /// /// Defines the constant for '--alias'. /// public const string Alias = "--alias"; /// - /// Defines the constant for '--endpoint'. + /// Defines the constant for '--model'. /// - public const string Endpoint = "--endpoint"; + public const string Model = "--model"; + + /// + /// Defines the constant for '--disable-foundry-local-manager'. + /// + public const string DisableFoundryLocalManager = "--disable-foundry-local-manager"; /// - /// Defines the constant for '--disable-foundrylocal-manager'. + /// Defines the constant for '--disable-flm'. /// - public const string DisableFoundryLocalManager = "--disable-foundrylocal-manager"; + public const string DisableFoundryLocalManagerInShort = "--disable-flm"; } /// diff --git a/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs index 5bb52621..03b9563b 100644 --- a/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Options/FoundryLocalArgumentOptions.cs @@ -10,14 +10,14 @@ namespace OpenChat.PlaygroundApp.Options; public class FoundryLocalArgumentOptions : ArgumentOptions { /// - /// Gets or sets the alias of Foundry Local. + /// Gets or sets the base URL of Foundry Local. If `DisableFoundryLocalManager` is set, this value must be provided. /// - public string? Alias { get; set; } + public string? BaseUrl { get; set; } /// - /// Gets or sets the Endpoint of FoundryLocal. + /// Gets or sets either alias or model ID of Foundry Local. /// - public string? Endpoint { get; set; } + public string? AliasOrModel { get; set; } /// /// Gets or sets a value indicating whether to disable the automatic FoundryLocal manager and use a manually configured endpoint. @@ -32,29 +32,31 @@ protected override void ParseOptions(IConfiguration config, string[] args) var foundryLocal = settings.FoundryLocal; - this.Alias ??= foundryLocal?.Alias; - this.Endpoint ??= foundryLocal?.Endpoint; + this.BaseUrl ??= foundryLocal?.BaseUrl; + this.AliasOrModel ??= foundryLocal?.AliasOrModel; this.DisableFoundryLocalManager = foundryLocal?.DisableFoundryLocalManager ?? false; for (var i = 0; i < args.Length; i++) { switch (args[i]) { - case ArgumentOptionConstants.FoundryLocal.Alias: + case ArgumentOptionConstants.FoundryLocal.BaseUrl: if (i + 1 < args.Length) { - this.Alias = args[++i]; + this.BaseUrl = args[++i]; } break; - case ArgumentOptionConstants.FoundryLocal.Endpoint: + case ArgumentOptionConstants.FoundryLocal.Alias: + case ArgumentOptionConstants.FoundryLocal.Model: if (i + 1 < args.Length) { - this.Endpoint = args[++i]; + this.AliasOrModel = args[++i]; } break; case ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager: + case ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManagerInShort: this.DisableFoundryLocalManager = true; break; diff --git a/src/OpenChat.PlaygroundApp/appsettings.json b/src/OpenChat.PlaygroundApp/appsettings.json index b2f016cf..c492b80f 100644 --- a/src/OpenChat.PlaygroundApp/appsettings.json +++ b/src/OpenChat.PlaygroundApp/appsettings.json @@ -41,8 +41,9 @@ }, "FoundryLocal": { - "Alias": "phi-4-mini", - "Endpoint": "http://127.0.0.1:55438/v1" + "Endpoint": "http://localhost:55434/", + "AliasOrModel": "phi-4-mini", + "DisableFoundryLocalManager": false }, "HuggingFace": { diff --git a/test/OpenChat.PlaygroundApp.Tests/Abstractions/ArgumentOptionsTests.cs b/test/OpenChat.PlaygroundApp.Tests/Abstractions/ArgumentOptionsTests.cs index e7227bff..c5ee2860 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Abstractions/ArgumentOptionsTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Abstractions/ArgumentOptionsTests.cs @@ -395,7 +395,7 @@ public void Given_Help_When_Parse_Invoked_Then_It_Should_Return_Help(string argu [InlineData(AppSettingConstants.ConnectorType, "GitHubModels", "GitHubModels:Model", "test-github-model", "test-github-model")] [InlineData(AppSettingConstants.ConnectorType, "GoogleVertexAI", "GoogleVertexAI:Model", "test-vertex-model", "test-vertex-model")] [InlineData(AppSettingConstants.ConnectorType, "DockerModelRunner", "DockerModelRunner:Model", "test-dmr-model", "test-dmr-model")] - [InlineData(AppSettingConstants.ConnectorType, "FoundryLocal", "FoundryLocal:Alias", "test-alias", "test-alias")] + [InlineData(AppSettingConstants.ConnectorType, "FoundryLocal", "FoundryLocal:AliasOrModel", "test-alias", "test-alias")] [InlineData(AppSettingConstants.ConnectorType, "HuggingFace", "HuggingFace:Model", "test-hf-model", "test-hf-model")] [InlineData(AppSettingConstants.ConnectorType, "Ollama", "Ollama:Model", "test-ollama-model", "test-ollama-model")] [InlineData(AppSettingConstants.ConnectorType, "Anthropic", "Anthropic:Model", "test-anthropic-model", "test-anthropic-model")] @@ -480,7 +480,7 @@ public void Given_ConnectorType_With_ModelConfig_And_Arguments_When_Parse_Invoke [InlineData(AppSettingConstants.ConnectorType, "GitHubModels", "GitHubModels:Model", "config-model", "config-model")] [InlineData(AppSettingConstants.ConnectorType, "GoogleVertexAI", "GoogleVertexAI:Model", "config-model", "config-model")] [InlineData(AppSettingConstants.ConnectorType, "DockerModelRunner", "DockerModelRunner:Model", "config-model", "config-model")] - [InlineData(AppSettingConstants.ConnectorType, "FoundryLocal", "FoundryLocal:Alias", "config-alias", "config-alias")] + [InlineData(AppSettingConstants.ConnectorType, "FoundryLocal", "FoundryLocal:AliasOrModel", "config-alias", "config-alias")] [InlineData(AppSettingConstants.ConnectorType, "HuggingFace", "HuggingFace:Model", "config-model", "config-model")] [InlineData(AppSettingConstants.ConnectorType, "Ollama", "Ollama:Model", "config-model", "config-model")] [InlineData(AppSettingConstants.ConnectorType, "Anthropic", "Anthropic:Model", "config-model", "config-model")] diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs index ac77379b..2356285d 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/FoundryLocalConnectorTests.cs @@ -10,20 +10,20 @@ namespace OpenChat.PlaygroundApp.Tests.Connectors; public class FoundryLocalConnectorTests { - private const string Alias = "phi-4-mini"; - private const string Endpoint = "http://127.0.0.1:55438/v1"; + private const string BaseUrl = "http://localhost:55434/"; + private const string AliasOrModel = "phi-4-mini"; private const bool DisableFoundryLocalManager = false; private static AppSettings BuildAppSettings( - string? alias = Alias, string? endpoint = Endpoint, bool disableFoundryLocalManager = DisableFoundryLocalManager) + string? baseUrl = BaseUrl, string? aliasOrModel = AliasOrModel, bool disableFoundryLocalManager = DisableFoundryLocalManager) { return new AppSettings { ConnectorType = ConnectorType.FoundryLocal, FoundryLocal = new FoundryLocalSettings { - Alias = alias, - Endpoint = endpoint, + BaseUrl = baseUrl, + AliasOrModel = aliasOrModel, DisableFoundryLocalManager = disableFoundryLocalManager } }; @@ -92,13 +92,13 @@ public void Given_Null_FoundryLocalSettings_When_EnsureLanguageModelSettingsVali [Trait("Category", "UnitTest")] [Theory] [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) + [InlineData("", typeof(InvalidOperationException), "FoundryLocal:BaseUrl")] + [InlineData(" ", typeof(InvalidOperationException), "FoundryLocal:BaseUrl")] + [InlineData("\t\n\r", typeof(InvalidOperationException), "FoundryLocal:BaseUrl")] + public void Given_Invalid_BaseUrl_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? baseUrl, Type expectedType, string expectedMessage) { // Arrange - var settings = BuildAppSettings(alias: alias); + var settings = BuildAppSettings(baseUrl: baseUrl, disableFoundryLocalManager: true); var connector = new FoundryLocalConnector(settings); // Act @@ -111,14 +111,15 @@ public void Given_Invalid_Alias_When_EnsureLanguageModelSettingsValid_Invoked_Th [Trait("Category", "UnitTest")] [Theory] - [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] - [InlineData("", typeof(InvalidOperationException), "FoundryLocal:Endpoint")] - [InlineData(" ", typeof(InvalidOperationException), "FoundryLocal:Endpoint")] - [InlineData("\t\n\r", typeof(InvalidOperationException), "FoundryLocal:Endpoint")] - public void Given_Invalid_Endpoint_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? endpoint, Type expectedType, string expectedMessage) + [InlineData(null, false, typeof(NullReferenceException), "Object reference not set to an instance of an object")] + [InlineData("", false, typeof(InvalidOperationException), "FoundryLocal:AliasOrModel")] + [InlineData(" ", false, typeof(InvalidOperationException), "FoundryLocal:AliasOrModel")] + [InlineData("\t\n\r", false, typeof(InvalidOperationException), "FoundryLocal:AliasOrModel")] + [InlineData("invalid-model", true, typeof(InvalidOperationException), "FoundryLocal:AliasOrModel")] + public void Given_Invalid_Alias_With_DisableFoundryLocalManager_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? alias, bool disableFoundryLocalManager, Type expectedType, string expectedMessage) { // Arrange - var settings = BuildAppSettings(endpoint: endpoint, disableFoundryLocalManager: true); + var settings = BuildAppSettings(aliasOrModel: alias, disableFoundryLocalManager: disableFoundryLocalManager); var connector = new FoundryLocalConnector(settings); // Act @@ -164,13 +165,19 @@ public void Given_Null_FoundryLocalSettings_When_GetChatClient_Invoked_Then_It_S .Message.ShouldContain("Object reference not set to an instance of an object."); } - [Trait("Category", "UnitTest")] + [Trait("Category", "IntegrationTest")] + [Trait("Category", "LLMRequired")] + [Trait("Category", "IgnoreGitHubActions")] [Theory] [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] - public void Given_Null_Alias_When_GetChatClient_Invoked_Then_It_Should_Throw(string? alias, Type expected, string message) + [InlineData("", typeof(UriFormatException), "Invalid URI:")] + [InlineData(" ", typeof(UriFormatException), "Invalid URI:")] + [InlineData("\t\r\n", typeof(UriFormatException), "Invalid URI:")] + [InlineData("invalid-uri-format", typeof(UriFormatException), "Invalid URI:")] + public void Given_Invalid_BaseUrl_When_GetChatClient_Invoked_Then_It_Should_Throw(string? baseUrl, Type expected, string message) { // Arrange - var settings = BuildAppSettings(alias: alias); + var settings = BuildAppSettings(baseUrl: baseUrl, disableFoundryLocalManager: true); var connector = new FoundryLocalConnector(settings); // Act @@ -181,19 +188,13 @@ public void Given_Null_Alias_When_GetChatClient_Invoked_Then_It_Should_Throw(str .Message.ShouldContain(message); } - [Trait("Category", "IntegrationTest")] - [Trait("Category", "LLMRequired")] - [Trait("Category", "IgnoreGitHubActions")] + [Trait("Category", "UnitTest")] [Theory] [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] - [InlineData("", typeof(InvalidOperationException), "not found in catalog")] - [InlineData(" ", typeof(InvalidOperationException), "not found in catalog")] - [InlineData("\t\r\n", typeof(InvalidOperationException), "not found in catalog")] - [InlineData("not-a-model", typeof(InvalidOperationException), "not found in catalog")] - public void Given_Invalid_Alias_When_GetChatClient_Invoked_Then_It_Should_Throw(string? alias, Type expected, string message) + public void Given_Null_Alias_When_GetChatClient_Invoked_Then_It_Should_Throw(string? alias, Type expected, string message) { // Arrange - var settings = BuildAppSettings(alias: alias); + var settings = BuildAppSettings(aliasOrModel: alias); var connector = new FoundryLocalConnector(settings); // Act @@ -209,14 +210,14 @@ public void Given_Invalid_Alias_When_GetChatClient_Invoked_Then_It_Should_Throw( [Trait("Category", "IgnoreGitHubActions")] [Theory] [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")] - [InlineData("", typeof(UriFormatException), "Invalid URI:")] - [InlineData(" ", typeof(UriFormatException), "Invalid URI:")] - [InlineData("\t\r\n", typeof(UriFormatException), "Invalid URI:")] - [InlineData("invalid-uri-format", typeof(UriFormatException), "Invalid URI:")] - public void Given_Invalid_Endpoint_When_GetChatClient_Invoked_Then_It_Should_Throw(string? endpoint, Type expected, string message) + [InlineData("", typeof(InvalidOperationException), "not found in catalog")] + [InlineData(" ", typeof(InvalidOperationException), "not found in catalog")] + [InlineData("\t\r\n", typeof(InvalidOperationException), "not found in catalog")] + [InlineData("not-a-model", typeof(InvalidOperationException), "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(endpoint: endpoint, disableFoundryLocalManager: true); + var settings = BuildAppSettings(aliasOrModel: alias, disableFoundryLocalManager: false); var connector = new FoundryLocalConnector(settings); // Act @@ -248,17 +249,17 @@ public async Task Given_Valid_Settings_When_GetChatClient_Invoked_Then_It_Should [Trait("Category", "UnitTest")] [Theory] [InlineData(null, null, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData(null, Endpoint, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData("", Endpoint, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - [InlineData(" ", Endpoint, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] - [InlineData("\t\r\n", Endpoint, false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Alias")] + [InlineData(BaseUrl, null, false, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData(BaseUrl, "", false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:AliasOrModel")] + [InlineData(BaseUrl, " ", false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:AliasOrModel")] + [InlineData(BaseUrl, "\t\r\n", false, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:AliasOrModel")] [InlineData(null, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData(Alias, null, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] - [InlineData(Alias, "", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] - [InlineData(Alias, " ", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] - [InlineData(Alias, "\t\r\n", true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:Endpoint")] + [InlineData(null, AliasOrModel, true, typeof(NullReferenceException), "Object reference not set to an instance of an object.")] + [InlineData("", AliasOrModel, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:BaseUrl")] + [InlineData(" ", AliasOrModel, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:BaseUrl")] + [InlineData("\t\r\n", AliasOrModel, true, typeof(InvalidOperationException), "Missing configuration: FoundryLocal:BaseUrl")] public void Given_Invalid_Alias_When_CreateChatClientAsync_Invoked_Then_It_Should_Throw( - string? alias, string? endpoint, bool disableFoundryLocalManager, Type expected, string expectedMessage) + string? baseUrl, string? alias, bool disableFoundryLocalManager, Type expected, string expectedMessage) { // Arrange var settings = new AppSettings @@ -266,8 +267,8 @@ public void Given_Invalid_Alias_When_CreateChatClientAsync_Invoked_Then_It_Shoul ConnectorType = ConnectorType.FoundryLocal, FoundryLocal = new FoundryLocalSettings { - Alias = alias, - Endpoint = endpoint, + BaseUrl = baseUrl, + AliasOrModel = alias, DisableFoundryLocalManager = disableFoundryLocalManager } }; diff --git a/test/OpenChat.PlaygroundApp.Tests/Options/FoundryLocalArgumentOptionsTests.cs b/test/OpenChat.PlaygroundApp.Tests/Options/FoundryLocalArgumentOptionsTests.cs index 4dced43c..4ef46f2c 100644 --- a/test/OpenChat.PlaygroundApp.Tests/Options/FoundryLocalArgumentOptionsTests.cs +++ b/test/OpenChat.PlaygroundApp.Tests/Options/FoundryLocalArgumentOptionsTests.cs @@ -9,11 +9,11 @@ namespace OpenChat.PlaygroundApp.Tests.Options; public class FoundryLocalArgumentOptionsTests { - private const string Alias = "test-foundry-local-alias"; - private const string AliasConfigKey = "FoundryLocal:Alias"; + private const string AliasOrModel = "test-foundry-local-alias"; + private const string AliasConfigKey = "FoundryLocal:AliasOrModel"; private static IConfiguration BuildConfigWithFoundryLocal( - string? configAlias = Alias, + string? configAlias = AliasOrModel, string? envAlias = null) { // Base configuration values (lowest priority) @@ -73,7 +73,7 @@ public void Given_Nothing_When_Parse_Invoked_Then_It_Should_Set_Config() // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(Alias); + settings.FoundryLocal.AliasOrModel.ShouldBe(AliasOrModel); } [Trait("Category", "UnitTest")] @@ -93,7 +93,7 @@ public void Given_CLI_Alias_When_Parse_Invoked_Then_It_Should_Use_CLI_Alias(stri // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(cliAlias); + settings.FoundryLocal.AliasOrModel.ShouldBe(cliAlias); } [Trait("Category", "UnitTest")] @@ -110,7 +110,7 @@ public void Given_CLI_ArgumentWithoutValue_When_Parse_Invoked_Then_It_Should_Use // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(Alias); + settings.FoundryLocal.AliasOrModel.ShouldBe(AliasOrModel); } [Trait("Category", "UnitTest")] @@ -126,7 +126,7 @@ public void Given_Unrelated_CLI_Arguments_When_Parse_Invoked_Then_It_Should_Use_ // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(Alias); + settings.FoundryLocal.AliasOrModel.ShouldBe(AliasOrModel); } [Trait("Category", "UnitTest")] @@ -146,7 +146,7 @@ public void Given_FoundryLocal_With_AliasName_StartingWith_Dashes_When_Parse_Inv // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(alias); + settings.FoundryLocal.AliasOrModel.ShouldBe(alias); } [Trait("Category", "UnitTest")] @@ -163,7 +163,7 @@ public void Given_ConfigValues_And_No_CLI_When_Parse_Invoked_Then_It_Should_Use_ // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(configAlias); + settings.FoundryLocal.AliasOrModel.ShouldBe(configAlias); } [Trait("Category", "UnitTest")] @@ -184,7 +184,7 @@ public void Given_ConfigValues_And_CLI_When_Parse_Invoked_Then_It_Should_Use_CLI // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(cliAlias); + settings.FoundryLocal.AliasOrModel.ShouldBe(cliAlias); } [Trait("Category", "UnitTest")] @@ -203,7 +203,7 @@ public void Given_EnvironmentVariables_And_No_Config_When_Parse_Invoked_Then_It_ // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(envAlias); + settings.FoundryLocal.AliasOrModel.ShouldBe(envAlias); } [Trait("Category", "UnitTest")] @@ -220,7 +220,7 @@ public void Given_ConfigValues_And_EnvironmentVariables_When_Parse_Invoked_Then_ // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(envAlias); + settings.FoundryLocal.AliasOrModel.ShouldBe(envAlias); } [Trait("Category", "UnitTest")] @@ -241,7 +241,7 @@ public void Given_ConfigValues_And_EnvironmentVariables_And_CLI_When_Parse_Invok // Assert settings.FoundryLocal.ShouldNotBeNull(); - settings.FoundryLocal.Alias.ShouldBe(cliAlias); + settings.FoundryLocal.AliasOrModel.ShouldBe(cliAlias); } From c4ad9650c2818d63416e98fef6ef548a3c6ab6f2 Mon Sep 17 00:00:00 2001 From: Justin Yoo Date: Wed, 29 Oct 2025 21:58:29 +0300 Subject: [PATCH 12/12] Update FoundryLocalConnector --- README.md | 2 +- docs/foundry-local.md | 43 +++++++++++-------- .../Abstractions/ArgumentOptions.cs | 22 +++++----- .../Connectors/FoundryLocalConnector.cs | 6 +-- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 3f0b657d..d3936414 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Open Chat Playground (OCP) is a web UI that is able to connect virtually any LLM - [x] [Hugging Face](https://huggingface.co/docs) - [x] [Ollama](https://github.com/ollama/ollama/tree/main/docs) - [ ] [Anthropic](https://docs.anthropic.com) -- [ ] [Naver](https://api.ncloud-docs.com/docs/ai-naver-clovastudio-summary) +- [ ] ~~[Naver](https://api.ncloud-docs.com/docs/ai-naver-clovastudio-summary)~~ - [x] [LG](https://github.com/LG-AI-EXAONE) - [x] [OpenAI](https://openai.com/api) - [x] [Upstage](https://console.upstage.ai/docs/getting-started) diff --git a/docs/foundry-local.md b/docs/foundry-local.md index 59f01ce0..0bb9df4c 100644 --- a/docs/foundry-local.md +++ b/docs/foundry-local.md @@ -1,6 +1,6 @@ # OpenChat Playground with Foundry Local -This page describes how to run OpenChat Playground (OCP) with Foundry Local models integration. +This page describes how to run OpenChat Playground (OCP) with [Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/what-is-foundry-local) integration. ## Get the repository root @@ -18,7 +18,7 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode ## Run on local machine -1. Make sure the Foundry Local server is up and running. +1. Make sure the Foundry Local server is up and running with the following command. ```bash foundry service start @@ -78,22 +78,27 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode ## Run in local container -1. Set the Foundry Local service port. The default port OCP uses is `55438`. +1. Make sure the Foundry Local server is up and running. ```bash - foundry service set --port 55438 + foundry service start ``` - Alternatively, if you want to run with a different port, say `63997`, other than the default one, set it first by running the following command. +1. Get the Foundry Local service port. ```bash - foundry service set --port 63997 + # bash/zsh + FL_PORT_NUMBER=$(foundry service set --show true | sed -n '/^{/,/^}/p' | jq -r ".serviceSettings.port") ``` -1. Make sure the Foundry Local server is up and running. - - ```bash - foundry service start + ```powershell + # PowerShell + $FL_PORT_NUMBER = (foundry service set --show true | ` + ForEach-Object { ` + if ($_ -match '^{') { $capture = $true } ` + if ($capture) { $_ } ` + if ($_ -match '^}') { $capture = $false } ` + } | Out-String | ConvertFrom-Json).serviceSettings.port ``` 1. Download the Foundry Local model. The default model OCP uses is `phi-4-mini`. @@ -136,12 +141,14 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode 1. Run the app. The `{{Model ID}}` refers to the `Model ID` shown in the output of the `foundry service list` command. + > **NOTE**: Make sure it MUST be the model ID, instead of alias. + ```bash # bash/zsh - from locally built container docker run -i --rm -p 8080:8080 openchat-playground:latest \ --connector-type FoundryLocal \ - --alias {{Model ID}} \ - --endpoint http://host.docker.internal:55438/v1 \ + --base-url http://host.docker.internal:$FL_PORT_NUMBER/ \ + --model "Phi-4-mini-instruct-generic-gpu:4" \ --disable-foundrylocal-manager ``` @@ -149,8 +156,8 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode # PowerShell - from locally built container docker run -i --rm -p 8080:8080 openchat-playground:latest ` --connector-type FoundryLocal ` - --alias {{Model ID}} ` - --endpoint http://host.docker.internal:55438/v1 ` + --base-url http://host.docker.internal:$FL_PORT_NUMBER/ ` + --model {{Model ID}} ` --disable-foundrylocal-manager ``` @@ -158,8 +165,8 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode # bash/zsh - from GitHub Container Registry docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest \ --connector-type FoundryLocal \ - --alias {{Model ID}} \ - --endpoint http://host.docker.internal:55438/v1 \ + --base-url http://host.docker.internal:$FL_PORT_NUMBER/ \ + --model {{Model ID}} \ --disable-foundrylocal-manager ``` @@ -167,8 +174,8 @@ This page describes how to run OpenChat Playground (OCP) with Foundry Local mode # PowerShell - from GitHub Container Registry docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest ` --connector-type FoundryLocal ` - --alias {{Model ID}} ` - --endpoint http://host.docker.internal:55438/v1 ` + --base-url http://host.docker.internal:$FL_PORT_NUMBER/ ` + --model {{Model ID}} ` --disable-foundrylocal-manager ``` diff --git a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs index d161e90d..bcbb251a 100644 --- a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs +++ b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs @@ -34,6 +34,7 @@ private static readonly (ConnectorType ConnectorType, string Argument, bool IsSw // Foundry Local (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.BaseUrl, false), (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Alias, false), + (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.Model, false), (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager, true), (ConnectorType.FoundryLocal, ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManagerInShort, true), // Hugging Face @@ -366,10 +367,10 @@ private static void DisplayHelpForAmazonBedrock() Console.WriteLine(" ** Amazon Bedrock: **"); Console.ForegroundColor = foregroundColor; - Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.AccessKeyId} The AWSCredentials Access Key ID."); - Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.SecretAccessKey} The AWSCredentials Secret Access Key."); - Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.Region} The AWS region."); - Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.ModelId} The model ID. Default to 'anthropic.claude-sonnet-4-20250514-v1:0'"); + Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.AccessKeyId} The AWSCredentials Access Key ID."); + Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.SecretAccessKey} The AWSCredentials Secret Access Key."); + Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.Region} The AWS region."); + Console.WriteLine($" {ArgumentOptionConstants.AmazonBedrock.ModelId} The model ID. Default to 'anthropic.claude-sonnet-4-20250514-v1:0'"); Console.WriteLine(); } @@ -429,10 +430,10 @@ private static void DisplayHelpForFoundryLocal() Console.WriteLine(" ** Foundry Local: **"); Console.ForegroundColor = foregroundColor; - Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.BaseUrl} The endpoint URL. Default to 'http://127.0.0.1:55434/'"); - Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Alias} The alias. Default to 'phi-4-mini'"); - Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager}|{ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManagerInShort} Disable the built-in Foundry local manager."); - Console.WriteLine($" When this flag is set, you must specify '--base-url'."); + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.BaseUrl} The endpoint URL. Default to 'http://localhost:/'"); + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.Alias}|{ArgumentOptionConstants.FoundryLocal.Model} The alias or model ID. Default to 'phi-4-mini'"); + Console.WriteLine($" {ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManager}|{ArgumentOptionConstants.FoundryLocal.DisableFoundryLocalManagerInShort} Disable the built-in Foundry local manager."); + Console.WriteLine($" When this flag is set, you must specify '--base-url'."); Console.WriteLine(); } @@ -479,7 +480,8 @@ private static void DisplayHelpForLG() Console.WriteLine(" ** LG: **"); Console.ForegroundColor = foregroundColor; - Console.WriteLine(" TBD"); + Console.WriteLine($" {ArgumentOptionConstants.LG.BaseUrl} The baseURL. Default to 'http://localhost:11434'"); + Console.WriteLine($" {ArgumentOptionConstants.LG.Model} The model name. Default to 'hf.co/LGAI-EXAONE/EXAONE-4.0-1.2B-GGUF'"); Console.WriteLine(); } @@ -501,7 +503,7 @@ private static void DisplayHelpForOpenAI() Console.WriteLine(" ** OpenAI: **"); Console.ForegroundColor = foregroundColor; - Console.WriteLine($" {ArgumentOptionConstants.OpenAI.ApiKey} The OpenAI API key. (Env: OPENAI_API_KEY)"); + Console.WriteLine($" {ArgumentOptionConstants.OpenAI.ApiKey} The OpenAI API key."); Console.WriteLine($" {ArgumentOptionConstants.OpenAI.Model} The OpenAI model name. Default to 'gpt-4.1-mini'"); Console.WriteLine(); } diff --git a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs index fbb3f076..923448a4 100644 --- a/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs +++ b/src/OpenChat.PlaygroundApp/Connectors/FoundryLocalConnector.cs @@ -19,7 +19,7 @@ public class FoundryLocalConnector(AppSettings settings) : LanguageModelConnecto { private const string ApiKey = "OPENAI_API_KEY"; - private static readonly Regex modelIdSuffix = new(@"\:[0-9]+$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex modelIdSuffix = new(":[0-9]+$", RegexOptions.Compiled | RegexOptions.IgnoreCase); private readonly AppSettings _appSettings = settings ?? throw new ArgumentNullException(nameof(settings)); @@ -45,7 +45,7 @@ public override bool EnsureLanguageModelSettingsValid() if (settings.DisableFoundryLocalManager == true && modelIdSuffix.IsMatch(settings.AliasOrModel!.Trim()!) == false) { - throw new InvalidOperationException("When DisableFoundryLocalManager is enabled, FoundryLocal:AliasOrModel must be the exact model name with version suffix."); + throw new InvalidOperationException("When DisableFoundryLocalManager is true, FoundryLocal:AliasOrModel must be the exact model name with version suffix."); } return true; @@ -87,7 +87,7 @@ private static (Uri? endpoint, string? modelId) ParseFromModelId(FoundryLocalSet var modelId = settings.AliasOrModel!.Trim() ?? throw new InvalidOperationException("Missing configuration: FoundryLocal:AliasOrModel."); if (modelIdSuffix.IsMatch(modelId) == false) { - throw new InvalidOperationException("When DisableFoundryLocalManager is enabled, FoundryLocal:AliasOrModel must be the exact model name with version suffix."); + throw new InvalidOperationException("When DisableFoundryLocalManager is true, FoundryLocal:AliasOrModel must be the exact model name with version suffix."); } return (endpoint, modelId);