diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml
index ecfc628f..66bcfeeb 100644
--- a/.github/workflows/azure-dev.yml
+++ b/.github/workflows/azure-dev.yml
@@ -31,6 +31,7 @@ jobs:
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
GH_MODELS_TOKEN: ${{ secrets.GH_MODELS_TOKEN }}
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
@@ -133,6 +134,7 @@ jobs:
shell: bash
env:
GitHubModels__Token: ${{ env.GH_MODELS_TOKEN }}
+ Anthropic__ApiKey: ${{ env.ANTHROPIC_API_KEY }}
OpenAI__ApiKey: ${{ env.OPENAI_API_KEY }}
run: |
azd provision --no-prompt
diff --git a/README.md b/README.md
index f8278f7c..f086a9be 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ Open Chat Playground (OCP) is a web UI that is able to connect virtually any LLM
- [ ] [Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/what-is-foundry-local)
- [x] [Hugging Face](https://huggingface.co/docs)
- [ ] [Ollama](https://github.com/ollama/ollama/tree/main/docs)
-- [ ] [Anthropic](https://docs.anthropic.com)
+- [x] [Anthropic](https://docs.anthropic.com)
- [ ] [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)
@@ -63,6 +63,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-on-local-machine)
- [Use GitHub Models](./docs/github-models.md#run-on-local-machine)
- [Use Hugging Face](./docs/hugging-face.md#run-on-local-machine)
+- [Use Anthropic](./docs/anthropic.md#run-on-local-machine)
- [Use LG](./docs/lg.md#run-on-local-machine)
- [Use OpenAI](./docs/openai.md#run-on-local-machine)
- [Use Upstage](./docs/upstage.md#run-on-local-machine)
@@ -72,6 +73,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 Hugging Face](./docs/hugging-face.md#run-in-local-container)
+- [Use Anthropic](./docs/anthropic.md#run-in-local-container)
- [Use LG](./docs/lg.md#run-in-local-container)
- [Use OpenAI](./docs/openai.md#run-in-local-container)
- [Use Upstage](./docs/upstage.md#run-in-local-container)
@@ -81,6 +83,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-on-azure)
- [Use GitHub Models](./docs/github-models.md#run-on-azure)
- [Use Hugging Face](./docs/hugging-face.md#run-on-azure)
+- [Use Anthropic](./docs/anthropic.md#run-on-azure)
- [Use LG](./docs/lg.md#run-on-azure)
- [Use OpenAI](./docs/openai.md#run-on-azure)
- [Use Upstage](./docs/upstage.md#run-on-azure)
diff --git a/docs/README.md b/docs/README.md
index 7c7091f2..2d98ff71 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -3,6 +3,7 @@
- [Azure AI Foundry](azure-ai-foundry.md)
- [GitHub Models](github-models.md)
- [Hugging Face](hugging-face.md)
+- [Anthropic](anthropic.md)
- [LG](lg.md)
- [OpenAI](openai.md)
- [Upstage](upstage.md)
diff --git a/docs/anthropic.md b/docs/anthropic.md
new file mode 100644
index 00000000..6c1fc548
--- /dev/null
+++ b/docs/anthropic.md
@@ -0,0 +1,225 @@
+# OpenChat Playground with Anthropic
+
+This page describes how to run OpenChat Playground (OCP) with [Anthropic models](https://docs.claude.com/en/docs/about-claude/models) integration.
+
+## Get the repository root
+
+1. Get the repository root.
+
+ ```bash
+ # bash/zsh
+ REPOSITORY_ROOT=$(git rev-parse --show-toplevel)
+ ```
+
+ ```powershell
+ # PowerShell
+ $REPOSITORY_ROOT = git rev-parse --show-toplevel
+ ```
+
+## Run on local machine
+
+1. Make sure you are at the repository root.
+
+ ```bash
+ cd $REPOSITORY_ROOT
+ ```
+
+1. Add Anthropic API Key for Claude connection. Make sure you should replace `{{ANTHROPIC_API_KEY}}` with your Anthropic API key.
+
+ ```bash
+ # bash/zsh
+ dotnet user-secrets --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp \
+ set Anthropic:ApiKey "{{ANTHROPIC_API_KEY}}"
+ ```
+
+ ```bash
+ # PowerShell
+ dotnet user-secrets --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp `
+ set Anthropic:ApiKey "{{ANTHROPIC_API_KEY}}"
+ ```
+
+ > For more details about Anthropic API keys, refer to the doc, [Anthropic API Documentation](https://docs.anthropic.com/claude/reference/getting-started-with-the-api).
+
+1. Run the app. The default model OCP uses is [Claude Sonnet 4](https://www-cdn.anthropic.com/6be99a52cb68eb70eb9572b4cafad13df32ed995.pdf).
+
+ ```bash
+ # bash/zsh
+ dotnet run --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp -- \
+ --connector-type Anthropic
+ ```
+
+ ```powershell
+ # PowerShell
+ dotnet run --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp -- `
+ --connector-type Anthropic
+ ```
+
+ Alternatively, if you want to run with a different model, say [Claude Opus 4.1](http://www.anthropic.com/claude-opus-4-1-system-card), other than the default one, you can specify it as an argument:
+
+ ```bash
+ # bash/zsh
+ dotnet run --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp -- \
+ --connector-type Anthropic \
+ --model claude-opus-4-1
+ ```
+
+ ```powershell
+ # PowerShell
+ dotnet run --project $REPOSITORY_ROOT/src/OpenChat.PlaygroundApp -- `
+ --connector-type Anthropic `
+ --model claude-opus-4-1
+ ```
+
+1. Open your web browser, navigate to `http://localhost:5280`, and enter prompts.
+
+## Run in local container
+
+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. Get Anthropic API Key.
+
+ ```bash
+ # bash/zsh
+ API_KEY=$(dotnet user-secrets --project ./src/OpenChat.PlaygroundApp list --json | \
+ sed -n '/^\/\//d; p' | jq -r '."Anthropic:ApiKey"')
+ ```
+
+ ```bash
+ # PowerShell
+ $API_KEY = (dotnet user-secrets --project ./src/OpenChat.PlaygroundApp list --json | `
+ Select-String -NotMatch '^//(BEGIN|END)' | ConvertFrom-Json).'Anthropic:ApiKey'
+ ```
+
+1. Run the app. The default model OCP uses is [Claude Sonnet 4](https://www-cdn.anthropic.com/6be99a52cb68eb70eb9572b4cafad13df32ed995.pdf).
+
+ ```bash
+ # bash/zsh - from locally built container
+ docker run -i --rm -p 8080:8080 openchat-playground:latest --connector-type Anthropic \
+ --api-key $API_KEY
+ ```
+
+ ```powershell
+ # PowerShell - from locally built container
+ docker run -i --rm -p 8080:8080 openchat-playground:latest --connector-type Anthropic `
+ --api-key $API_KEY
+ ```
+
+ ```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 Anthropic \
+ --api-key $API_KEY
+ ```
+
+ ```powershell
+ # PowerShell - from GitHub Container Registry
+ docker run -i --rm -p 8080:8080 ghcr.io/aliencube/open-chat-playground/openchat-playground:latest `
+ --connector-type Anthropic `
+ --api-key $API_KEY
+ ```
+
+ Alternatively, if you want to run with a different model, say [Claude Opus 4.1](http://www.anthropic.com/claude-opus-4-1-system-card), other than the default one, you can specify it as an argument:
+
+ ```bash
+ # bash/zsh - from locally built container with custom model
+ docker run -i --rm -p 8080:8080 openchat-playground:latest --connector-type Anthropic \
+ --api-key $API_KEY \
+ --model claude-opus-4-1
+ ```
+
+ ```powershell
+ # PowerShell - from locally built container with custom model
+ docker run -i --rm -p 8080:8080 openchat-playground:latest --connector-type Anthropic `
+ --api-key $API_KEY `
+ --model claude-opus-4-1
+ ```
+
+1. Open your web browser, navigate to `http://localhost:8080`, and enter prompts.
+
+## Run on Azure
+
+1. Make sure you are at the repository root.
+
+ ```bash
+ cd $REPOSITORY_ROOT
+ ```
+
+1. Login to Azure.
+
+ ```bash
+ azd auth login
+ ```
+
+1. Check login status.
+
+ ```bash
+ azd auth login --check-status
+ ```
+
+1. Initialize `azd` template.
+
+ ```bash
+ azd init
+ ```
+
+ > **NOTE**: You will be asked to provide environment name for provisioning.
+
+1. Get Anthropic API Key.
+
+ ```bash
+ # bash/zsh
+ API_KEY=$(dotnet user-secrets --project ./src/OpenChat.PlaygroundApp list --json | \
+ sed -n '/^\/\//d; p' | jq -r '."Anthropic:ApiKey"')
+ ```
+
+ ```bash
+ # PowerShell
+ $API_KEY = (dotnet user-secrets --project ./src/OpenChat.PlaygroundApp list --json | `
+ Select-String -NotMatch '^//(BEGIN|END)' | ConvertFrom-Json).'Anthropic:ApiKey'
+ ```
+
+1. Set Anthropic API Key to azd environment variables.
+
+ ```bash
+ azd env set ANTHROPIC_API_KEY $API_KEY
+ ```
+
+ The default model OCP uses is [Claude Sonnet 4](https://www-cdn.anthropic.com/6be99a52cb68eb70eb9572b4cafad13df32ed995.pdf). If you want to run with a different model, say [Claude Opus 4.1](http://www.anthropic.com/claude-opus-4-1-system-card), other than the default one, add it to azd environment variables.
+
+ ```bash
+ azd env set ANTHROPIC_MODEL claude-opus-4-1
+ ```
+
+1. Set the connector type to `Anthropic`.
+
+ ```bash
+ azd env set CONNECTOR_TYPE Anthropic
+ ```
+
+1. Run the following commands in order to provision and deploy the app.
+
+ ```bash
+ azd up
+ ```
+
+ > **NOTE**: You will be asked to provide Azure subscription and location for deployment.
+
+ Once deployed, you will be able to see the deployed OCP app URL.
+
+1. Open your web browser, navigate to the OCP app URL, and enter prompts.
+
+1. Clean up all the resources.
+
+ ```bash
+ azd down --force --purge
+ ```
diff --git a/infra/main.bicep b/infra/main.bicep
index 16809ba8..cb3774f7 100644
--- a/infra/main.bicep
+++ b/infra/main.bicep
@@ -29,6 +29,9 @@ param huggingFaceModel string = ''
// Ollama
param ollamaModel string = ''
// Anthropic
+param anthropicModel string = ''
+@secure()
+param anthropicApiKey string = ''
// LG
param lgModel string = ''
// Naver
@@ -89,6 +92,8 @@ module resources 'resources.bicep' = {
githubModelsToken: githubModelsToken
huggingFaceModel: huggingFaceModel
ollamaModel: ollamaModel
+ anthropicModel: anthropicModel
+ anthropicApiKey: anthropicApiKey
lgModel: lgModel
openAIModel: openAIModel
openAIApiKey: openAIApiKey
diff --git a/infra/main.parameters.json b/infra/main.parameters.json
index b17eb69b..6ea910f0 100644
--- a/infra/main.parameters.json
+++ b/infra/main.parameters.json
@@ -29,6 +29,12 @@
"huggingFaceModel": {
"value": "${HUGGING_FACE_MODEL=hf.co/Qwen/Qwen3-0.6B-GGUF}"
},
+ "anthropicModel" : {
+ "value": "${ANTHROPIC_MODEL=claude-sonnet-4-0}"
+ },
+ "anthropicApiKey" : {
+ "value": "${ANTHROPIC_API_KEY}"
+ },
"lgModel": {
"value": "${LG_MODEL=hf.co/LGAI-EXAONE/EXAONE-4.0-1.2B-GGUF}"
},
diff --git a/infra/resources.bicep b/infra/resources.bicep
index a9896604..993a801f 100644
--- a/infra/resources.bicep
+++ b/infra/resources.bicep
@@ -24,6 +24,9 @@ param huggingFaceModel string = ''
// Ollama
param ollamaModel string = ''
// Anthropic
+param anthropicModel string = ''
+@secure()
+param anthropicApiKey string = ''
// LG
param lgModel string = ''
// Naver
@@ -231,6 +234,17 @@ var envOllama = connectorType == 'Ollama' ? concat(ollamaModel != '' ? [
}
] : []) : []
// Anthropic
+var envAnthropic = connectorType == 'Anthropic' ? concat(anthropicModel != '' ? [
+ {
+ name: 'Anthropic__Model'
+ value: anthropicModel
+ }
+] : [], anthropicApiKey != '' ? [
+ {
+ name: 'Anthropic__ApiKey'
+ secretRef: 'anthropic-api-key'
+ }
+] : []) : []
// LG
var envLG = connectorType == 'LG' ? concat(lgModel != '' ? [
{
@@ -288,6 +302,11 @@ module openchatPlaygroundApp 'br/public:avm/res/app/container-app:0.18.1' = {
name: 'github-models-token'
value: githubModelsToken
}
+ ] : [], anthropicApiKey != '' ? [
+ {
+ name: 'anthropic-api-key'
+ value: anthropicApiKey
+ }
] : [], openAIApiKey != '' ? [
{
name: 'openai-api-key'
@@ -326,6 +345,7 @@ module openchatPlaygroundApp 'br/public:avm/res/app/container-app:0.18.1' = {
envGitHubModels,
envHuggingFace,
envOllama,
+ envAnthropic,
envLG,
envOpenAI,
envUpstage,
diff --git a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs
index 73d58f07..24c09ad0 100644
--- a/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs
+++ b/src/OpenChat.PlaygroundApp/Abstractions/ArgumentOptions.cs
@@ -42,6 +42,7 @@ private static readonly (ConnectorType ConnectorType, string Argument, bool IsSw
// Anthropic
(ConnectorType.Anthropic, ArgumentOptionConstants.Anthropic.ApiKey, false),
(ConnectorType.Anthropic, ArgumentOptionConstants.Anthropic.Model, false),
+ (ConnectorType.Anthropic, ArgumentOptionConstants.Anthropic.MaxTokens, false),
// LG
(ConnectorType.LG, ArgumentOptionConstants.LG.BaseUrl, false),
(ConnectorType.LG, ArgumentOptionConstants.LG.Model, false),
@@ -237,6 +238,7 @@ public static AppSettings Parse(IConfiguration config, string[] args)
settings.Anthropic ??= new AnthropicSettings();
settings.Anthropic.ApiKey = anthropic.ApiKey ?? settings.Anthropic.ApiKey;
settings.Anthropic.Model = anthropic.Model ?? settings.Anthropic.Model;
+ settings.Anthropic.MaxTokens = anthropic.MaxTokens ?? settings.Anthropic.MaxTokens;
settings.Model = anthropic.Model ?? settings.Anthropic.Model;
break;
@@ -460,7 +462,8 @@ private static void DisplayHelpForAnthropic()
Console.ForegroundColor = foregroundColor;
Console.WriteLine($" {ArgumentOptionConstants.Anthropic.ApiKey} The Anthropic API key.");
- Console.WriteLine($" {ArgumentOptionConstants.Anthropic.Model} The Anthropic model name. Default to 'claude-sonnet-4-0'");
+ Console.WriteLine($" {ArgumentOptionConstants.Anthropic.Model} The Anthropic model name. Default to 'claude-sonnet-4-0'");
+ Console.WriteLine($" {ArgumentOptionConstants.Anthropic.MaxTokens} The maximum tokens (>= 1). Default to 1000");
Console.WriteLine();
}
diff --git a/src/OpenChat.PlaygroundApp/Configurations/AnthropicSettings.cs b/src/OpenChat.PlaygroundApp/Configurations/AnthropicSettings.cs
index 5e8ba75f..fa780396 100644
--- a/src/OpenChat.PlaygroundApp/Configurations/AnthropicSettings.cs
+++ b/src/OpenChat.PlaygroundApp/Configurations/AnthropicSettings.cs
@@ -12,17 +12,22 @@ public partial class AppSettings
}
///
-/// This represents the app settings entity for Anthropic Claude.
+/// This represents the app settings entity for Anthropic.
///
public class AnthropicSettings : LanguageModelSettings
{
///
- /// Gets or sets the API key for Anthropic Claude.
+ /// Gets or sets the API key for Anthropic.
///
public string? ApiKey { get; set; }
///
- /// Gets or sets the model name of Anthropic Claude.
+ /// Gets or sets the model name of Anthropic.
///
public string? Model { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of output tokens for Anthropic.
+ ///
+ public int? MaxTokens { get; set; }
}
\ No newline at end of file
diff --git a/src/OpenChat.PlaygroundApp/Connectors/AnthropicConnector.cs b/src/OpenChat.PlaygroundApp/Connectors/AnthropicConnector.cs
new file mode 100644
index 00000000..7166ae20
--- /dev/null
+++ b/src/OpenChat.PlaygroundApp/Connectors/AnthropicConnector.cs
@@ -0,0 +1,66 @@
+using Microsoft.Extensions.AI;
+
+using Anthropic.SDK;
+
+using OpenChat.PlaygroundApp.Abstractions;
+using OpenChat.PlaygroundApp.Configurations;
+
+namespace OpenChat.PlaygroundApp.Connectors;
+
+///
+/// This represents the connector entity for Anthropic.
+///
+public class AnthropicConnector(AppSettings settings) : LanguageModelConnector(settings.Anthropic)
+{
+ private readonly AppSettings _appSettings = settings ?? throw new ArgumentNullException(nameof(settings));
+
+ ///
+ public override bool EnsureLanguageModelSettingsValid()
+ {
+ if (this.Settings is not AnthropicSettings settings)
+ {
+ throw new InvalidOperationException("Missing configuration: Anthropic.");
+ }
+
+ if (string.IsNullOrWhiteSpace(settings.ApiKey!.Trim()) == true)
+ {
+ throw new InvalidOperationException("Missing configuration: Anthropic:ApiKey.");
+ }
+
+ if (string.IsNullOrWhiteSpace(settings.Model!.Trim()) == true)
+ {
+ throw new InvalidOperationException("Missing configuration: Anthropic:Model.");
+ }
+
+ return true;
+ }
+
+ ///
+ public override async Task GetChatClientAsync()
+ {
+ var settings = this.Settings as AnthropicSettings;
+ var apiKey = settings?.ApiKey;
+
+ if (string.IsNullOrWhiteSpace(apiKey) == true)
+ {
+ throw new InvalidOperationException("Missing configuration: Anthropic:ApiKey.");
+ }
+
+ var client = new AnthropicClient() { Auth = new APIAuthentication(apiKey) };
+
+ var chatClient = client.Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Use((messages, options, next, cancellationToken) =>
+ {
+ options!.ModelId = settings!.Model;
+ options.MaxOutputTokens ??= 1000;
+ return next(messages, options, cancellationToken);
+ })
+ .Build();
+
+ Console.WriteLine($"The {this._appSettings.ConnectorType} connector created with model: {settings!.Model}");
+
+ return await Task.FromResult(chatClient).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs
index c3f88624..a9170e03 100644
--- a/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs
+++ b/src/OpenChat.PlaygroundApp/Constants/ArgumentOptionConstants.cs
@@ -182,6 +182,11 @@ public static class Anthropic
/// Defines the constant for '--model'.
///
public const string Model = "--model";
+
+ ///
+ /// Defines the constant for '--max-tokens'.
+ ///
+ public const string MaxTokens = "--max-tokens";
}
///
diff --git a/src/OpenChat.PlaygroundApp/Options/AnthropicArgumentOptions.cs b/src/OpenChat.PlaygroundApp/Options/AnthropicArgumentOptions.cs
index d3a17e59..553b0407 100644
--- a/src/OpenChat.PlaygroundApp/Options/AnthropicArgumentOptions.cs
+++ b/src/OpenChat.PlaygroundApp/Options/AnthropicArgumentOptions.cs
@@ -5,20 +5,26 @@
namespace OpenChat.PlaygroundApp.Options;
///
-/// This represents the argument options entity for Anthropic Claude.
+/// This represents the argument options entity for Anthropic.
///
public class AnthropicArgumentOptions : ArgumentOptions
{
///
- /// Gets or sets the API key for Anthropic Claude.
+ /// Gets or sets the API key for Anthropic.
///
public string? ApiKey { get; set; }
///
- /// Gets or sets the model name of Anthropic Claude.
+ /// Gets or sets the model name of Anthropic.
///
public string? Model { get; set; }
+ ///
+ /// Gets or sets the maximum number of tokens for Anthropic.
+ /// Mirrors the 'max_tokens' option as 'MaxTokens'.
+ ///
+ public int? MaxTokens { get; set; }
+
///
protected override void ParseOptions(IConfiguration config, string[] args)
{
@@ -26,9 +32,10 @@ protected override void ParseOptions(IConfiguration config, string[] args)
config.Bind(settings);
var anthropic = settings.Anthropic;
-
+
this.ApiKey ??= anthropic?.ApiKey;
this.Model ??= anthropic?.Model;
+ this.MaxTokens ??= anthropic?.MaxTokens;
for (var i = 0; i < args.Length; i++)
{
@@ -48,6 +55,16 @@ protected override void ParseOptions(IConfiguration config, string[] args)
}
break;
+ case ArgumentOptionConstants.Anthropic.MaxTokens:
+ if (i + 1 < args.Length)
+ {
+ if (int.TryParse(args[++i], out var maxTokens))
+ {
+ this.MaxTokens = maxTokens;
+ }
+ }
+ break;
+
default:
break;
}
diff --git a/src/OpenChat.PlaygroundApp/appsettings.json b/src/OpenChat.PlaygroundApp/appsettings.json
index 1ee914c5..c971cf06 100644
--- a/src/OpenChat.PlaygroundApp/appsettings.json
+++ b/src/OpenChat.PlaygroundApp/appsettings.json
@@ -56,7 +56,8 @@
"Anthropic": {
"ApiKey": "{{ANTHROPIC_API_KEY}}",
- "Model": "claude-sonnet-4-0"
+ "Model": "claude-sonnet-4-0",
+ "MaxTokens": "1000"
},
"LG": {
diff --git a/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs b/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs
index 18d749d9..621cf2f8 100644
--- a/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs
+++ b/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs
@@ -1,4 +1,4 @@
-using Microsoft.Playwright;
+using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
using OpenChat.PlaygroundApp.Connectors;
diff --git a/test/OpenChat.PlaygroundApp.Tests/Connectors/AnthropicConnectorTests.cs b/test/OpenChat.PlaygroundApp.Tests/Connectors/AnthropicConnectorTests.cs
new file mode 100644
index 00000000..a8848c5f
--- /dev/null
+++ b/test/OpenChat.PlaygroundApp.Tests/Connectors/AnthropicConnectorTests.cs
@@ -0,0 +1,220 @@
+using Microsoft.Extensions.AI;
+
+using OpenChat.PlaygroundApp.Abstractions;
+using OpenChat.PlaygroundApp.Configurations;
+using OpenChat.PlaygroundApp.Connectors;
+
+namespace OpenChat.PlaygroundApp.Tests.Connectors;
+
+public class AnthropicConnectorTests
+{
+ private const string ApiKey = "test-api-key";
+ private const string Model = "test-model";
+
+ private static AppSettings BuildAppSettings(string? apiKey = ApiKey, string? model = Model)
+ {
+ return new AppSettings
+ {
+ ConnectorType = ConnectorType.Anthropic,
+ Anthropic = new AnthropicSettings
+ {
+ ApiKey = apiKey,
+ Model = model
+ }
+ };
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact(Skip = "Anthropic connector is not enabled yet.")]
+ public void Given_Null_Settings_When_Instantiated_Then_It_Should_Throw()
+ {
+ // Act
+ Action action = () => new AnthropicConnector(null!);
+
+ // Assert
+ action.ShouldThrow()
+ .Message.ShouldContain("settings");
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact(Skip = "Anthropic connector is not enabled yet.")]
+ public void Given_Settings_When_Instantiated_Then_It_Should_Return()
+ {
+ // Arrange
+ var settings = BuildAppSettings();
+
+ // Act
+ var result = new AnthropicConnector(settings);
+
+ // Assert
+ result.ShouldNotBeNull();
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory(Skip = "Anthropic connector is not enabled yet.")]
+ [InlineData(typeof(LanguageModelConnector), typeof(AnthropicConnector), true)]
+ [InlineData(typeof(AnthropicConnector), typeof(LanguageModelConnector), false)]
+ public void Given_BaseType_Then_It_Should_Be_AssignableFrom_DerivedType(Type baseType, Type derivedType, bool expected)
+ {
+ // Act
+ var result = baseType.IsAssignableFrom(derivedType);
+
+ // Assert
+ result.ShouldBe(expected);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact(Skip = "Anthropic connector is not enabled yet.")]
+ public void Given_Settings_Is_Null_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw()
+ {
+ // Arrange
+ var settings = new AppSettings { ConnectorType = ConnectorType.Anthropic, Anthropic = null };
+ var connector = new AnthropicConnector(settings);
+
+ // Act
+ Action action = () => connector.EnsureLanguageModelSettingsValid();
+
+ // Assert
+ action.ShouldThrow()
+ .Message.ShouldContain("Missing configuration: Anthropic.");
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory(Skip = "Anthropic connector is not enabled yet.")]
+ [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")]
+ [InlineData("", typeof(InvalidOperationException), "Anthropic:ApiKey")]
+ [InlineData(" ", typeof(InvalidOperationException), "Anthropic:ApiKey")]
+ [InlineData("\t\n\r", typeof(InvalidOperationException), "Anthropic:ApiKey")]
+ public void Given_Invalid_ApiKey_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? apiKey, Type expectedException, string expectedMessage)
+ {
+ // Arrange
+ var settings = BuildAppSettings(apiKey: apiKey);
+ var connector = new AnthropicConnector(settings);
+
+ // Act
+ Action action = () => connector.EnsureLanguageModelSettingsValid();
+
+ // Assert
+ action.ShouldThrow(expectedException)
+ .Message.ShouldContain(expectedMessage);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory(Skip = "Anthropic connector is not enabled yet.")]
+ [InlineData(null, typeof(NullReferenceException), "Object reference not set to an instance of an object")]
+ [InlineData("", typeof(InvalidOperationException), "Anthropic:Model")]
+ [InlineData(" ", typeof(InvalidOperationException), "Anthropic:Model")]
+ [InlineData("\t\n\r", typeof(InvalidOperationException), "Anthropic:Model")]
+ public void Given_Invalid_Model_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Throw(string? model, Type expectedException, string expectedMessage)
+ {
+ // Arrange
+ var settings = BuildAppSettings(apiKey: "valid-key", model: model);
+ var connector = new AnthropicConnector(settings);
+
+ // Act
+ Action action = () => connector.EnsureLanguageModelSettingsValid();
+
+ // Assert
+ action.ShouldThrow(expectedException)
+ .Message.ShouldContain(expectedMessage);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact(Skip = "Anthropic connector is not enabled yet.")]
+ public void Given_Valid_Settings_When_EnsureLanguageModelSettingsValid_Invoked_Then_It_Should_Return_True()
+ {
+ // Arrange
+ var settings = BuildAppSettings();
+ var connector = new AnthropicConnector(settings);
+
+ // Act
+ var result = connector.EnsureLanguageModelSettingsValid();
+
+ // Assert
+ result.ShouldBeTrue();
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact(Skip = "Anthropic connector is not enabled yet.")]
+ public async Task Given_Valid_Settings_When_GetChatClientAsync_Invoked_Then_It_Should_Return_ChatClient()
+ {
+ // Arrange
+ var settings = BuildAppSettings();
+ var connector = new AnthropicConnector(settings);
+
+ // Act
+ var client = await connector.GetChatClientAsync();
+
+ // Assert
+ client.ShouldNotBeNull();
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact(Skip = "Anthropic connector is not enabled yet.")]
+ public void Given_Settings_Is_Null_When_GetChatClientAsync_Invoked_Then_It_Should_Throw()
+ {
+ // Arrange
+ var settings = new AppSettings { ConnectorType = ConnectorType.Anthropic, Anthropic = null };
+ var connector = new AnthropicConnector(settings);
+
+ // Act
+ Func func = async () => await connector.GetChatClientAsync();
+
+ // Assert
+ func.ShouldThrow()
+ .Message.ShouldContain("Missing configuration: Anthropic:ApiKey.");
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory(Skip = "Anthropic connector is not enabled yet.")]
+ [InlineData(null, typeof(InvalidOperationException), "Anthropic:ApiKey")]
+ [InlineData("", typeof(InvalidOperationException), "Anthropic:ApiKey")]
+ public void Given_Missing_ApiKey_When_GetChatClientAsync_Invoked_Then_It_Should_Throw(string? apiKey, Type expectedException, string expectedMessage)
+ {
+ // Arrange
+ var settings = BuildAppSettings(apiKey: apiKey);
+ var connector = new AnthropicConnector(settings);
+
+ // Act
+ Func func = async () => await connector.GetChatClientAsync();
+
+ // Assert
+ func.ShouldThrow(expectedException)
+ .Message.ShouldContain(expectedMessage);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact(Skip = "Anthropic connector is not enabled in the factory yet.")]
+ public async Task Given_Valid_Settings_When_CreateChatClientAsync_Invoked_Then_It_Should_Return_ChatClient()
+ {
+ // Arrange
+ var settings = BuildAppSettings();
+
+ // Act
+ var result = await LanguageModelConnector.CreateChatClientAsync(settings);
+
+ // Assert
+ result.ShouldNotBeNull();
+ result.ShouldBeAssignableTo();
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory(Skip = "Anthropic connector is not enabled in the factory yet.")]
+ [InlineData(null, "claude-sonnet-4-0", typeof(NullReferenceException))]
+ [InlineData("", "claude-sonnet-4-0", typeof(InvalidOperationException))]
+ [InlineData(" ", "claude-sonnet-4-0", typeof(InvalidOperationException))]
+ [InlineData("test-api-key", null, typeof(NullReferenceException))]
+ [InlineData("test-api-key", "", typeof(InvalidOperationException))]
+ [InlineData("test-api-key", " ", typeof(InvalidOperationException))]
+ public void Given_Invalid_Settings_When_CreateChatClientAsync_Invoked_Then_It_Should_Throw(string? apiKey, string? model, Type expectedType)
+ {
+ // Arrange
+ var settings = BuildAppSettings(apiKey: apiKey, model: model);
+
+ // Act
+ Func func = async () => await LanguageModelConnector.CreateChatClientAsync(settings);
+
+ // Assert
+ func.ShouldThrow(expectedType);
+ }
+}
\ No newline at end of file
diff --git a/test/OpenChat.PlaygroundApp.Tests/Options/AnthropicArgumentOptionsTests.cs b/test/OpenChat.PlaygroundApp.Tests/Options/AnthropicArgumentOptionsTests.cs
index a0d8183f..918b6a6b 100644
--- a/test/OpenChat.PlaygroundApp.Tests/Options/AnthropicArgumentOptionsTests.cs
+++ b/test/OpenChat.PlaygroundApp.Tests/Options/AnthropicArgumentOptionsTests.cs
@@ -11,14 +11,18 @@ public class AnthropicArgumentOptionsTests
{
private const string ApiKey = "test-api-key";
private const string Model = "test-model";
+ private const string MaxTokens = "1000";
private const string ApiKeyConfigKey = "Anthropic:ApiKey";
private const string ModelConfigKey = "Anthropic:Model";
+ private const string MaxTokensConfigKey = "Anthropic:MaxTokens";
private static IConfiguration BuildConfigWithAnthropic(
string? configApiKey = ApiKey,
string? configModel = Model,
+ string? configMaxTokens = MaxTokens,
string? envApiKey = null,
- string? envModel = null)
+ string? envModel = null,
+ string? envMaxTokens = null)
{
// Base configuration values (lowest priority)
var configDict = new Dictionary
@@ -34,7 +38,13 @@ private static IConfiguration BuildConfigWithAnthropic(
{
configDict[ModelConfigKey] = configModel;
}
- if (string.IsNullOrWhiteSpace(envApiKey) == true && string.IsNullOrWhiteSpace(envModel) == true)
+ if (string.IsNullOrWhiteSpace(configMaxTokens) == false)
+ {
+ configDict[MaxTokensConfigKey] = configMaxTokens;
+ }
+ if (string.IsNullOrWhiteSpace(envApiKey) == true &&
+ string.IsNullOrWhiteSpace(envModel) == true &&
+ string.IsNullOrWhiteSpace(envMaxTokens) == true)
{
return new ConfigurationBuilder()
.AddInMemoryCollection(configDict!)
@@ -51,12 +61,18 @@ private static IConfiguration BuildConfigWithAnthropic(
{
envDict[ModelConfigKey] = envModel;
}
+ if (string.IsNullOrWhiteSpace(envMaxTokens) == false)
+ {
+ envDict[MaxTokensConfigKey] = envMaxTokens;
+ }
return new ConfigurationBuilder()
.AddInMemoryCollection(configDict!) // Base configuration (lowest priority)
.AddInMemoryCollection(envDict!) // Environment variables (medium priority)
.Build();
}
+
+ private static int? IntValueOf(string? value) => string.IsNullOrWhiteSpace(value) ? null : int.Parse(value);
[Trait("Category", "UnitTest")]
[Theory]
@@ -87,6 +103,7 @@ public void Given_Nothing_When_Parse_Invoked_Then_It_Should_Set_Config()
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(ApiKey);
settings.Anthropic.Model.ShouldBe(Model);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(MaxTokens));
}
[Trait("Category", "UnitTest")]
@@ -108,6 +125,7 @@ public void Given_CLI_ApiKey_When_Parse_Invoked_Then_It_Should_Use_CLI_ApiKey(st
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(cliApiKey);
settings.Anthropic.Model.ShouldBe(Model);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(MaxTokens));
}
[Trait("Category", "UnitTest")]
@@ -129,19 +147,43 @@ public void Given_CLI_Model_When_Parse_Invoked_Then_It_Should_Use_CLI_Model(stri
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(ApiKey);
settings.Anthropic.Model.ShouldBe(cliModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(MaxTokens));
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory]
+ [InlineData("2000")]
+ public void Given_CLI_MaxTokens_When_Parse_Invoked_Then_It_Should_Use_CLI_MaxTokens(string cliMaxTokens)
+ {
+ // Arrange
+ var config = BuildConfigWithAnthropic();
+ var args = new[]
+ {
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
+ };
+
+ // Act
+ var settings = ArgumentOptions.Parse(config, args);
+
+ // Assert
+ settings.Anthropic.ShouldNotBeNull();
+ settings.Anthropic.ApiKey.ShouldBe(ApiKey);
+ settings.Anthropic.Model.ShouldBe(Model);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(cliMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("cli-api-key", "cli-model")]
- public void Given_All_CLI_Arguments_When_Parse_Invoked_Then_It_Should_Use_CLI(string cliApiKey, string cliModel)
+ [InlineData("cli-api-key", "cli-model", "2000")]
+ public void Given_All_CLI_Arguments_When_Parse_Invoked_Then_It_Should_Use_CLI(string cliApiKey, string cliModel, string cliMaxTokens)
{
// Arrange
var config = BuildConfigWithAnthropic();
var args = new[]
{
ArgumentOptionConstants.Anthropic.ApiKey, cliApiKey,
- ArgumentOptionConstants.Anthropic.Model, cliModel
+ ArgumentOptionConstants.Anthropic.Model, cliModel,
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
};
// Act
@@ -151,12 +193,14 @@ public void Given_All_CLI_Arguments_When_Parse_Invoked_Then_It_Should_Use_CLI(st
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(cliApiKey);
settings.Anthropic.Model.ShouldBe(cliModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(cliMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
[InlineData(ArgumentOptionConstants.Anthropic.ApiKey)]
[InlineData(ArgumentOptionConstants.Anthropic.Model)]
+ [InlineData(ArgumentOptionConstants.Anthropic.MaxTokens)]
public void Given_CLI_ArgumentWithoutValue_When_Parse_Invoked_Then_It_Should_Use_Config(string argument)
{
// Arrange
@@ -170,6 +214,7 @@ public void Given_CLI_ArgumentWithoutValue_When_Parse_Invoked_Then_It_Should_Use
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(ApiKey);
settings.Anthropic.Model.ShouldBe(Model);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(MaxTokens));
}
[Trait("Category", "UnitTest")]
@@ -187,6 +232,7 @@ public void Given_Unrelated_CLI_Arguments_When_Parse_Invoked_Then_It_Should_Use_
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(ApiKey);
settings.Anthropic.Model.ShouldBe(Model);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(MaxTokens));
}
[Trait("Category", "UnitTest")]
@@ -208,6 +254,29 @@ public void Given_Anthropic_With_ModelName_StartingWith_Dashes_When_Parse_Invoke
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(ApiKey);
settings.Anthropic.Model.ShouldBe(cliModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(MaxTokens));
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory]
+ [InlineData("-1")]
+ public void Given_Anthropic_With_MaxTokens_StartingWith_Dashes_When_Parse_Invoked_Then_It_Should_Treat_As_Value(string cliMaxTokens)
+ {
+ // Arrange
+ var config = BuildConfigWithAnthropic();
+ var args = new[]
+ {
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
+ };
+
+ // Act
+ var settings = ArgumentOptions.Parse(config, args);
+
+ // Assert
+ settings.Anthropic.ShouldNotBeNull();
+ settings.Anthropic.ApiKey.ShouldBe(ApiKey);
+ settings.Anthropic.Model.ShouldBe(Model);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(cliMaxTokens));
}
[Trait("Category", "UnitTest")]
@@ -229,15 +298,16 @@ public void Given_Anthropic_With_ApiKey_StartingWith_Dashes_When_Parse_Invoked_T
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(cliApiKey);
settings.Anthropic.Model.ShouldBe(Model);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(MaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("config-api-key", "config-model")]
- public void Given_ConfigValues_And_No_CLI_When_Parse_Invoked_Then_It_Should_Use_Config(string configApiKey, string configModel)
+ [InlineData("config-api-key", "config-model", "1000")]
+ public void Given_ConfigValues_And_No_CLI_When_Parse_Invoked_Then_It_Should_Use_Config(string configApiKey, string configModel, string configMaxTokens)
{
// Arrange
- var config = BuildConfigWithAnthropic(configApiKey, configModel);
+ var config = BuildConfigWithAnthropic(configApiKey, configModel, configMaxTokens);
var args = Array.Empty();
// Act
@@ -247,21 +317,23 @@ public void Given_ConfigValues_And_No_CLI_When_Parse_Invoked_Then_It_Should_Use_
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(configApiKey);
settings.Anthropic.Model.ShouldBe(configModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(configMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("config-api-key", "config-model", "cli-api-key", "cli-model")]
+ [InlineData("config-api-key", "config-model", "1000", "cli-api-key", "cli-model", "2000")]
public void Given_ConfigValues_And_CLI_When_Parse_Invoked_Then_It_Should_Use_CLI(
- string configApiKey, string configModel,
- string cliApiKey, string cliModel)
+ string configApiKey, string configModel, string configMaxTokens,
+ string cliApiKey, string cliModel, string cliMaxTokens)
{
// Arrange
- var config = BuildConfigWithAnthropic(configApiKey, configModel);
+ var config = BuildConfigWithAnthropic(configApiKey, configModel, configMaxTokens);
var args = new[]
{
ArgumentOptionConstants.Anthropic.ApiKey, cliApiKey,
- ArgumentOptionConstants.Anthropic.Model, cliModel
+ ArgumentOptionConstants.Anthropic.Model, cliModel,
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
};
// Act
@@ -271,17 +343,18 @@ public void Given_ConfigValues_And_CLI_When_Parse_Invoked_Then_It_Should_Use_CLI
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(cliApiKey);
settings.Anthropic.Model.ShouldBe(cliModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(cliMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("env-api-key", "env-model")]
- public void Given_EnvironmentVariables_And_No_Config_When_Parse_Invoked_Then_It_Should_Use_EnvironmentVariables(string envApiKey, string envModel)
+ [InlineData("env-api-key", "env-model", "1500")]
+ public void Given_EnvironmentVariables_And_No_Config_When_Parse_Invoked_Then_It_Should_Use_EnvironmentVariables(string envApiKey, string envModel, string envMaxTokens)
{
// Arrange
var config = BuildConfigWithAnthropic(
- configApiKey: null, configModel: null,
- envApiKey: envApiKey, envModel: envModel);
+ configApiKey: null, configModel: null, configMaxTokens: null,
+ envApiKey: envApiKey, envModel: envModel, envMaxTokens: envMaxTokens);
var args = Array.Empty();
// Act
@@ -291,17 +364,20 @@ public void Given_EnvironmentVariables_And_No_Config_When_Parse_Invoked_Then_It_
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(envApiKey);
settings.Anthropic.Model.ShouldBe(envModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(envMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("config-api-key", "config-model", "env-api-key", "env-model")]
+ [InlineData("config-api-key", "config-model", "1000", "env-api-key", "env-model", "1500")]
public void Given_ConfigValues_And_EnvironmentVariables_When_Parse_Invoked_Then_It_Should_Use_EnvironmentVariables(
- string configApiKey, string configModel,
- string envApiKey, string envModel)
+ string configApiKey, string configModel, string configMaxTokens,
+ string envApiKey, string envModel, string envMaxTokens)
{
// Arrange
- var config = BuildConfigWithAnthropic(configApiKey, configModel, envApiKey, envModel);
+ var config = BuildConfigWithAnthropic(
+ configApiKey, configModel, configMaxTokens,
+ envApiKey, envModel, envMaxTokens);
var args = Array.Empty();
// Act
@@ -311,22 +387,26 @@ public void Given_ConfigValues_And_EnvironmentVariables_When_Parse_Invoked_Then_
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(envApiKey);
settings.Anthropic.Model.ShouldBe(envModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(envMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("config-api-key", "config-model", "env-api-key", "env-model", "cli-api-key", "cli-model")]
+ [InlineData("config-api-key", "config-model", "1000", "env-api-key", "env-model", "1500", "cli-api-key", "cli-model", "2000")]
public void Given_ConfigValues_And_EnvironmentVariables_And_CLI_When_Parse_Invoked_Then_It_Should_Use_CLI(
- string configApiKey, string configModel,
- string envApiKey, string envModel,
- string cliApiKey, string cliModel)
+ string configApiKey, string configModel, string configMaxTokens,
+ string envApiKey, string envModel, string envMaxTokens,
+ string cliApiKey, string cliModel, string cliMaxTokens)
{
// Arrange
- var config = BuildConfigWithAnthropic(configApiKey, configModel, envApiKey, envModel);
+ var config = BuildConfigWithAnthropic(
+ configApiKey, configModel, configMaxTokens,
+ envApiKey, envModel, envMaxTokens);
var args = new[]
{
ArgumentOptionConstants.Anthropic.ApiKey, cliApiKey,
- ArgumentOptionConstants.Anthropic.Model, cliModel
+ ArgumentOptionConstants.Anthropic.Model, cliModel,
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
};
// Act
@@ -336,17 +416,20 @@ public void Given_ConfigValues_And_EnvironmentVariables_And_CLI_When_Parse_Invok
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(cliApiKey);
settings.Anthropic.Model.ShouldBe(cliModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(cliMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("config-api-key", "config-model", null, "env-model")]
+ [InlineData("config-api-key", "config-model", "1000", null, "env-model", null)]
public void Given_Partial_EnvironmentVariables_When_Parse_Invoked_Then_It_Should_Mix_Config_And_Environment(
- string configApiKey, string configModel,
- string? envApiKey, string envModel)
+ string configApiKey, string configModel, string configMaxTokens,
+ string? envApiKey, string envModel, string? envMaxTokens)
{
// Arrange
- var config = BuildConfigWithAnthropic(configApiKey, configModel, envApiKey, envModel);
+ var config = BuildConfigWithAnthropic(
+ configApiKey, configModel, configMaxTokens,
+ envApiKey, envModel, envMaxTokens);
var args = Array.Empty();
// Act
@@ -356,22 +439,26 @@ public void Given_Partial_EnvironmentVariables_When_Parse_Invoked_Then_It_Should
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(configApiKey);
settings.Anthropic.Model.ShouldBe(envModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(configMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("config-api-key", "config-model", "env-api-key", null, null, "cli-model")]
+ [InlineData("config-api-key", "config-model", "1000", "env-api-key", null, null, null, "cli-model", null)]
public void Given_Mixed_Priority_Sources_When_Parse_Invoked_Then_It_Should_Respect_Priority_Order(
- string configApiKey, string configModel,
- string envApiKey, string? envModel,
- string? cliApiKey, string cliModel)
+ string configApiKey, string configModel, string configMaxTokens,
+ string envApiKey, string? envModel, string? envMaxTokens,
+ string? cliApiKey, string cliModel, string? cliMaxTokens)
{
// Arrange
- var config = BuildConfigWithAnthropic(configApiKey, configModel, envApiKey, envModel);
+ var config = BuildConfigWithAnthropic(
+ configApiKey, configModel, configMaxTokens,
+ envApiKey, envModel, envMaxTokens);
var args = new[]
{
ArgumentOptionConstants.Anthropic.ApiKey, cliApiKey,
- ArgumentOptionConstants.Anthropic.Model, cliModel
+ ArgumentOptionConstants.Anthropic.Model, cliModel,
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
};
// Act
@@ -381,19 +468,21 @@ public void Given_Mixed_Priority_Sources_When_Parse_Invoked_Then_It_Should_Respe
settings.Anthropic.ShouldNotBeNull();
settings.Anthropic.ApiKey.ShouldBe(envApiKey);
settings.Anthropic.Model.ShouldBe(cliModel);
+ settings.Anthropic.MaxTokens.ShouldBe(IntValueOf(configMaxTokens));
}
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("cli-api-key", "cli-model")]
- public void Given_Anthropic_With_KnownArguments_When_Parse_Invoked_Then_Help_Should_Be_False(string cliApiKey, string cliModel)
+ [InlineData("cli-api-key", "cli-model", "2000")]
+ public void Given_Anthropic_With_KnownArguments_When_Parse_Invoked_Then_Help_Should_Be_False(string cliApiKey, string cliModel, string cliMaxTokens)
{
// Arrange
- var config = BuildConfigWithAnthropic(ApiKey, Model);
+ var config = BuildConfigWithAnthropic(ApiKey, Model, MaxTokens);
var args = new[]
{
ArgumentOptionConstants.Anthropic.ApiKey, cliApiKey,
- ArgumentOptionConstants.Anthropic.Model, cliModel
+ ArgumentOptionConstants.Anthropic.Model, cliModel,
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
};
// Act
@@ -407,6 +496,7 @@ public void Given_Anthropic_With_KnownArguments_When_Parse_Invoked_Then_Help_Sho
[Theory]
[InlineData(ArgumentOptionConstants.Anthropic.ApiKey)]
[InlineData(ArgumentOptionConstants.Anthropic.Model)]
+ [InlineData(ArgumentOptionConstants.Anthropic.MaxTokens)]
public void Given_Anthropic_With_KnownArgument_WithoutValue_When_Parse_Invoked_Then_Help_Should_Be_False(string argument)
{
// Arrange
@@ -423,7 +513,7 @@ public void Given_Anthropic_With_KnownArgument_WithoutValue_When_Parse_Invoked_T
[Trait("Category", "UnitTest")]
[Theory]
[InlineData("cli-api-key", "--unknown-flag")]
- public void Given_Anthropic_With_Known_And_Unknown_Argument_When_Parse_Invoked_Then_Help_Should_Be_True(
+ public void Given_Anthropic_With_Known_ApiKey_And_Unknown_Argument_When_Parse_Invoked_Then_Help_Should_Be_True(
string cliApiKey, string argument)
{
// Arrange
@@ -443,15 +533,58 @@ public void Given_Anthropic_With_Known_And_Unknown_Argument_When_Parse_Invoked_T
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("cli-api-key", "cli-model")]
- public void Given_CLI_Only_When_Parse_Invoked_Then_Help_Should_Be_False(string cliApiKey, string cliModel)
+ [InlineData("cli-model", "--unknown-flag")]
+ public void Given_Anthropic_With_Known_Model_And_Unknown_Argument_When_Parse_Invoked_Then_Help_Should_Be_True(
+ string cliModel, string argument)
+ {
+ // Arrange
+ var config = BuildConfigWithAnthropic();
+ var args = new[]
+ {
+ ArgumentOptionConstants.Anthropic.Model, cliModel,
+ argument
+ };
+
+ // Act
+ var settings = ArgumentOptions.Parse(config, args);
+
+ // Assert
+ settings.Help.ShouldBeTrue();
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory]
+ [InlineData("2000", "--unknown-flag")]
+ public void Given_Anthropic_With_Known_MaxTokens_And_Unknown_Argument_When_Parse_Invoked_Then_Help_Should_Be_True(
+ string cliMaxTokens, string argument)
+ {
+ // Arrange
+ var config = BuildConfigWithAnthropic();
+ var args = new[]
+ {
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens,
+ argument
+ };
+
+ // Act
+ var settings = ArgumentOptions.Parse(config, args);
+
+ // Assert
+ settings.Help.ShouldBeTrue();
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Theory]
+ [InlineData("cli-api-key", "cli-model", "2000")]
+ public void Given_CLI_Only_When_Parse_Invoked_Then_Help_Should_Be_False(string cliApiKey, string cliModel, string cliMaxTokens)
{
// Arrange
var config = BuildConfigWithAnthropic();
var args = new[]
{
ArgumentOptionConstants.Anthropic.ApiKey, cliApiKey,
- ArgumentOptionConstants.Anthropic.Model, cliModel
+ ArgumentOptionConstants.Anthropic.Model, cliModel,
+ ArgumentOptionConstants.Anthropic.MaxTokens, cliMaxTokens
};
// Act
@@ -463,13 +596,13 @@ public void Given_CLI_Only_When_Parse_Invoked_Then_Help_Should_Be_False(string c
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData("env-api-key", "env-model")]
- public void Given_EnvironmentVariables_Only_When_Parse_Invoked_Then_Help_Should_Be_False(string envApiKey, string envModel)
+ [InlineData("env-api-key", "env-model", "1500")]
+ public void Given_EnvironmentVariables_Only_When_Parse_Invoked_Then_Help_Should_Be_False(string envApiKey, string envModel, string envMaxTokens)
{
// Arrange
var config = BuildConfigWithAnthropic(
- configApiKey: null, configModel: null,
- envApiKey: envApiKey, envModel: envModel);
+ configApiKey: null, configModel: null, configMaxTokens: null,
+ envApiKey: envApiKey, envModel: envModel, envMaxTokens: envMaxTokens);
var args = Array.Empty();
// Act
@@ -481,8 +614,8 @@ public void Given_EnvironmentVariables_Only_When_Parse_Invoked_Then_Help_Should_
[Trait("Category", "UnitTest")]
[Theory]
- [InlineData(null, null, ConnectorType.Unknown, false)]
- public void Given_AnthropicArgumentOptions_When_Creating_Instance_Then_Should_Have_Correct_Properties(string? expectedApiKey, string? expectedModel, ConnectorType expectedConnectorType, bool expectedHelp)
+ [InlineData(null, null, null, ConnectorType.Unknown, false)]
+ public void Given_AnthropicArgumentOptions_When_Creating_Instance_Then_Should_Have_Correct_Properties(string? expectedApiKey, string? expectedModel, string? expectedMaxTokens, ConnectorType expectedConnectorType, bool expectedHelp)
{
// Act
var options = new AnthropicArgumentOptions();
@@ -491,6 +624,7 @@ public void Given_AnthropicArgumentOptions_When_Creating_Instance_Then_Should_Ha
options.ShouldNotBeNull();
options.ApiKey.ShouldBe(expectedApiKey);
options.Model.ShouldBe(expectedModel);
+ options.MaxTokens.ShouldBe(IntValueOf(expectedMaxTokens));
options.ConnectorType.ShouldBe(expectedConnectorType);
options.Help.ShouldBe(expectedHelp);
}