diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100755 index 0000000..d749e5c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,6 @@ +patreon: havendv +ko_fi: havendv +custom: [ + "https://www.paypal.me/havendv", + "https://www.buymeacoffee.com/havendv", + "https://www.upwork.com/freelancers/~017b1ad6f6af9cc189"] diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100755 index 0000000..95303ba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,41 @@ +name: Bug report +description: File a bug report +title: "Bug title" +labels: [bug] +body: + - type: textarea + validations: + required: true + attributes: + label: Describe the bug + description: Please enter a short, clear description of the bug. + + - type: textarea + validations: + required: true + attributes: + label: Steps to reproduce the bug + description: Please provide any required setup and steps to reproduce the behavior. + placeholder: | + 1. Go to '...' + 2. Click on '....' + + - type: textarea + attributes: + label: Expected behavior + description: Please provide a description of what you expected to happen + + - type: textarea + attributes: + label: Screenshots + description: If applicable, add screenshots here to help explain your problem + + - type: textarea + attributes: + label: NuGet package version + description: Specify the version you're using. + + - type: textarea + attributes: + label: Additional context + description: Enter any other applicable info here diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100755 index 0000000..fe68d5a --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,13 @@ +name: Build, test and publish +on: [ push ] + +jobs: + build-test-publish: + name: Build, test and publish + uses: HavenDV/workflows/.github/workflows/dotnet_build-test-publish.yml@main + with: + generate-build-number: false + conventional-commits-publish-conditions: false + additional-test-arguments: '--logger GitHubActions' + secrets: + nuget-key: ${{ secrets.NUGET_KEY }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a30d25..370da60 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +/.idea/ diff --git a/Cohere.sln b/Cohere.sln new file mode 100755 index 0000000..591d178 --- /dev/null +++ b/Cohere.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E793AF18-4371-4EBD-96FC-195EB1798855}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + src\Directory.Build.props = src\Directory.Build.props + .github\workflows\dotnet.yml = .github\workflows\dotnet.yml + LICENSE = LICENSE + docs\openapi.nswag = docs\openapi.nswag + docs\openapi.yaml = docs\openapi.yaml + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{61E7E11E-4558-434C-ACE8-06316A3097B3}" + ProjectSection(SolutionItems) = preProject + src\libs\Directory.Build.props = src\libs\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AAA11B78-2764-4520-A97E-46AA7089A588}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cohere", "src\libs\Cohere\Cohere.csproj", "{0028BC85-0064-4CE8-A21A-C1F5E922BD59}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cohere.IntegrationTests", "src\tests\Cohere.IntegrationTests\Cohere.IntegrationTests.csproj", "{592ADBC9-C951-4AF7-A163-B6C63B970B19}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0028BC85-0064-4CE8-A21A-C1F5E922BD59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0028BC85-0064-4CE8-A21A-C1F5E922BD59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0028BC85-0064-4CE8-A21A-C1F5E922BD59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0028BC85-0064-4CE8-A21A-C1F5E922BD59}.Release|Any CPU.Build.0 = Release|Any CPU + {592ADBC9-C951-4AF7-A163-B6C63B970B19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {592ADBC9-C951-4AF7-A163-B6C63B970B19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {592ADBC9-C951-4AF7-A163-B6C63B970B19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {592ADBC9-C951-4AF7-A163-B6C63B970B19}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0028BC85-0064-4CE8-A21A-C1F5E922BD59} = {61E7E11E-4558-434C-ACE8-06316A3097B3} + {592ADBC9-C951-4AF7-A163-B6C63B970B19} = {AAA11B78-2764-4520-A97E-46AA7089A588} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CED9A020-DBA5-4BE6-8096-75E528648EC1} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index b07aa3f..09b09be 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ # Cohere -Generated C# SDK based on official Cohere OpenAPI specification + +[![Nuget package](https://img.shields.io/nuget/vpre/Cohere)](https://www.nuget.org/packages/Cohere/) +[![dotnet](https://github.com/tryAGI/Cohere/actions/workflows/dotnet.yml/badge.svg?branch=main)](https://github.com/tryAGI/Cohere/actions/workflows/dotnet.yml) +[![License: MIT](https://img.shields.io/github/license/tryAGI/Cohere)](https://github.com/tryAGI/Cohere/blob/main/LICENSE.txt) +[![Discord](https://img.shields.io/discord/1115206893015662663?label=Discord&logo=discord&logoColor=white&color=d82679)](https://discord.gg/Ca2xhfBf3v) + +Generated C# SDK based on Cohere OpenAPI specification using NSwag. +Includes [tokenizer](https://github.com/tryAGI/Tiktoken) and some helper methods. + +### Usage +```csharp +using Cohere; + +using var client = new HttpClient(); +var api = new CohereApi(apiKey, client); +var response = await api.GenerateTextAsync( + RecommendedModelIds.Gpt2, + new GenerateTextRequest + { + Inputs = "Hello", + Parameters = new GenerateTextRequestParameters + { + Max_new_tokens = 250, + Return_full_text = false, + }, + Options = new GenerateTextRequestOptions + { + Use_cache = true, + Wait_for_model = false, + }, + }); +``` + +## Support + +Priority place for bugs: https://github.com/tryAGI/Cohere/issues +Priority place for ideas and general questions: https://github.com/tryAGI/Cohere/discussions +Discord: https://discord.gg/Ca2xhfBf3v \ No newline at end of file diff --git a/assets/nuget_icon.png b/assets/nuget_icon.png new file mode 100644 index 0000000..25574ba Binary files /dev/null and b/assets/nuget_icon.png differ diff --git a/docs/openapi.nswag b/docs/openapi.nswag new file mode 100755 index 0000000..24bde76 --- /dev/null +++ b/docs/openapi.nswag @@ -0,0 +1,100 @@ +{ + "runtime": "Default", + "defaultVariables": null, + "documentGenerator": { + "fromDocument": { + "json": "", + "url": "openapi.yaml", + "output": null, + "newLineBehavior": "Auto" + } + }, + "codeGenerators": { + "openApiToCSharpClient": { + "clientBaseClass": null, + "configurationClass": null, + "generateClientClasses": true, + "generateClientInterfaces": false, + "clientBaseInterface": null, + "injectHttpClient": true, + "disposeHttpClient": true, + "protectedMethods": [], + "generateExceptionClasses": true, + "exceptionClass": "ApiException", + "wrapDtoExceptions": true, + "useHttpClientCreationMethod": false, + "httpClientType": "System.Net.Http.HttpClient", + "useHttpRequestMessageCreationMethod": false, + "useBaseUrl": true, + "generateBaseUrlProperty": true, + "generateSyncMethods": false, + "generatePrepareRequestAndProcessResponseAsAsyncMethods": true, + "exposeJsonSerializerSettings": false, + "clientClassAccessModifier": "public", + "typeAccessModifier": "public", + "generateContractsOutput": false, + "contractsNamespace": null, + "contractsOutputFilePath": null, + "parameterDateTimeFormat": "s", + "parameterDateFormat": "yyyy-MM-dd", + "generateUpdateJsonSerializerSettingsMethod": true, + "useRequestAndResponseSerializationSettings": false, + "serializeTypeInformation": false, + "queryNullValue": "", + "className": "{controller}CohereApi", + "operationGenerationMode": "MultipleClientsFromOperationId", + "additionalNamespaceUsages": [], + "additionalContractNamespaceUsages": [], + "generateOptionalParameters": true, + "generateJsonMethods": false, + "enforceFlagEnums": false, + "parameterArrayType": "System.Collections.Generic.IEnumerable", + "parameterDictionaryType": "System.Collections.Generic.IDictionary", + "responseArrayType": "System.Collections.Generic.ICollection", + "responseDictionaryType": "System.Collections.Generic.IDictionary", + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "namespace": "Cohere", + "requiredPropertiesMustBeDefined": true, + "dateType": "System.DateTimeOffset", + "jsonConverters": null, + "anyType": "object", + "dateTimeType": "System.DateTimeOffset", + "timeType": "System.TimeSpan", + "timeSpanType": "System.TimeSpan", + "arrayType": "System.Collections.Generic.ICollection", + "arrayInstanceType": "System.Collections.ObjectModel.Collection", + "dictionaryType": "System.Collections.Generic.IDictionary", + "dictionaryInstanceType": "System.Collections.Generic.Dictionary", + "arrayBaseType": "System.Collections.ObjectModel.Collection", + "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "classStyle": "Poco", + "jsonLibrary": "SystemTextJson", + "generateDefaultValues": true, + "generateDataAnnotations": true, + "excludedTypeNames": [], + "excludedParameterNames": [], + "handleReferences": false, + "generateImmutableArrayProperties": false, + "generateImmutableDictionaryProperties": false, + "jsonSerializerSettingsTransformationMethod": null, + "inlineNamedArrays": false, + "inlineNamedDictionaries": false, + "inlineNamedTuples": true, + "inlineNamedAny": false, + "generateDtoTypes": true, + "generateOptionalPropertiesAsNullable": true, + "generateNullableReferenceTypes": true, + "templateDirectory": null, + "typeNameGeneratorType": null, + "propertyNameGeneratorType": null, + "enumNameGeneratorType": null, + "serviceHost": null, + "serviceSchemes": null, + "output": null, + "newLineBehavior": "Auto" + } + } +} \ No newline at end of file diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..d1244a5 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,171 @@ +openapi: 3.0.0 +info: + title: Cohere API + description: APIs for sampling from and fine-tuning language models + version: '1.0.0' +servers: + - url: https://api-inference.huggingface.co/ +tags: + - name: Cohere + description: The Cohere REST API +paths: + /models/{modelId}: + post: + operationId: generateText + parameters: + - in: path + name: modelId + schema: + type: string + required: true + description: Model ID of the user to get + tags: + - Cohere + summary: Creates a completion for the provided prompt and parameters. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateTextRequest' + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateTextResponse' + +components: + schemas: + ErrorResponse: + type: object + properties: + error: + anyOf: + - type: string + - type: array + items: + type: string + required: + - error + + GenerateTextRequest: + type: object + properties: + inputs: + description: | + The prompt that you want to complete. + nullable: true + type: string + default: '' + example: "This is a test." + parameters: + $ref: '#/components/schemas/GenerateTextRequestParameters' + options: + $ref: '#/components/schemas/GenerateTextRequestOptions' + required: + - inputs + + GenerateTextRequestParameters: + type: object + properties: + top_k: + type: integer + example: 1 + nullable: true + description: &completions_top_k_description | + Integer to define the top tokens considered within the sample operation to create new text. + top_p: + type: number + example: 1 + nullable: true + description: | + Float to define the tokens that are within the sample operation of text generation. + Add tokens in the sample for more probable to least probable until the sum of the probabilities is greater than top_p. + temperature: + type: number + minimum: 0 + maximum: 100 + default: 1 + example: 1 + nullable: true + description: | + The temperature of the sampling operation. + + 1 means regular sampling, + 0 means always take the highest score, + 100.0 is getting closer to uniform probability. + repetition_penalty: + type: number + minimum: 0 + maximum: 100 + example: 1 + nullable: true + description: | + The more a token is used within generation the more it is penalized to not be picked in successive generation passes. + max_new_tokens: + type: integer + minimum: 1 + maximum: 250 + example: 250 + nullable: true + description: | + The amount of new tokens to be generated, this does not include the input length it is a estimate of the size of generated text you want. + Each new tokens slows down the request, so look for balance between response times and length of text generated. + max_time: + type: number + minimum: 0 + maximum: 120 + example: 1 + nullable: true + description: | + The amount of time in seconds that the query should take maximum. Network can cause some overhead so it will be a soft limit. Use that in combination with max_new_tokens for best results. + return_full_text: + description: > + If set to False, the return results will not contain the original query making it easier for prompting. + default: true + num_return_sequences: + type: integer + default: 1 + example: 1 + nullable: true + description: | + The number of proposition you want to be returned. + do_sample: + description: > + Whether or not to use sampling, use greedy decoding otherwise. + default: true + + GenerateTextRequestOptions: + type: object + properties: + use_cache: + description: > + There is a cache layer on the inference API to speedup requests we have already seen. + Most models can use those results as is as models are deterministic (meaning the results will be the same anyway). + However if you use a non deterministic model, + you can set this parameter to prevent the caching mechanism from being used resulting in a real new query. + default: true + wait_for_model: + description: > + If the model is not ready, wait for it instead of receiving 503. + It limits the number of requests required to get your inference done. + It is advised to only set this flag to true after receiving a 503 error as it will limit hanging + in your application to known places. + default: false + + GenerateTextResponse: + type: array + items: + $ref: '#/components/schemas/GenerateTextResponseValue' + + GenerateTextResponseValue: + type: object + properties: + generated_text: + type: string + description: | + The resulting completion up to and excluding the stop sequences. + required: + - generated_text \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100755 index 0000000..86f2c1f --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,9 @@ + + + + preview + enable + enable + + + diff --git a/src/key.snk b/src/key.snk new file mode 100755 index 0000000..4205bc6 Binary files /dev/null and b/src/key.snk differ diff --git a/src/libs/Cohere/Cohere.csproj b/src/libs/Cohere/Cohere.csproj new file mode 100755 index 0000000..d55521f --- /dev/null +++ b/src/libs/Cohere/Cohere.csproj @@ -0,0 +1,43 @@ + + + + netstandard2.0;net4.6.2;net6.0;net7.0 + + + + + + + + Generated C# SDK based on Cohere OpenAPI specification. + api;client;sdk;dotnet;swagger;openapi;specification;huggingface;generated;nswag + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/src/libs/Cohere/Extensions/StringExtensions.cs b/src/libs/Cohere/Extensions/StringExtensions.cs new file mode 100644 index 0000000..44a5ef2 --- /dev/null +++ b/src/libs/Cohere/Extensions/StringExtensions.cs @@ -0,0 +1,47 @@ +namespace Cohere; + +/// +/// +/// +public static class StringExtensions +{ + /// + /// + /// + /// + /// + public static string AsHumanMessage(this string content) + { + return $"Human: {content}"; + } + + /// + /// + /// + /// + /// + public static string AsAssistantMessage(this string content) + { + return $"Assistant: {content}"; + } + + /// + /// + /// + /// + /// + public static string AsPrompt(this string content) + { + return $"\n\n${content.AsHumanMessage()}\n\nAssistant:"; + } + + /// + /// + /// + /// + /// + public static string AsPrompt(this string[] content) + { + return AsPrompt(string.Join("\n\n", content)); + } +} \ No newline at end of file diff --git a/src/libs/Cohere/Helpers/ApiHelpers.cs b/src/libs/Cohere/Helpers/ApiHelpers.cs new file mode 100644 index 0000000..59ea7e4 --- /dev/null +++ b/src/libs/Cohere/Helpers/ApiHelpers.cs @@ -0,0 +1,24 @@ +namespace Cohere; + +/// +/// +/// +public static class ApiHelpers +{ + /// + /// Calculates the maximum number of tokens possible to generate for a model.
+ /// According https://huggingface.co/docs/api-inference/detailed_parameters#text-generation-task
+ ///
+ /// + /// + /// + public static int CalculateContextLength(string modelId) + { + return modelId switch + { + RecommendedModelIds.Gpt2 => 250, + + _ => throw new NotImplementedException(), + }; + } +} \ No newline at end of file diff --git a/src/libs/Cohere/HuggingFaceApi.Authorization.cs b/src/libs/Cohere/HuggingFaceApi.Authorization.cs new file mode 100755 index 0000000..04b39bc --- /dev/null +++ b/src/libs/Cohere/HuggingFaceApi.Authorization.cs @@ -0,0 +1,36 @@ +using System.Net.Http.Headers; +using System.Text; + +// ReSharper disable UnusedParameter.Local +// ReSharper disable MemberCanBeMadeStatic.Local + +#pragma warning disable CA1822 + +namespace Cohere; + +public partial class CohereApi +{ + private string ApiKey { get; } = string.Empty; + + private Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, string url, CancellationToken cancellationToken) + { + if (!string.IsNullOrWhiteSpace(ApiKey)) + { + request.Headers.Authorization = new AuthenticationHeaderValue( + scheme: "Bearer", + parameter: ApiKey); + } + + return Task.CompletedTask; + } + + private Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, StringBuilder builder, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private Task ProcessResponseAsync(HttpClient client, HttpResponseMessage message, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/src/libs/Cohere/HuggingFaceApi.Constructors.cs b/src/libs/Cohere/HuggingFaceApi.Constructors.cs new file mode 100755 index 0000000..b49bb26 --- /dev/null +++ b/src/libs/Cohere/HuggingFaceApi.Constructors.cs @@ -0,0 +1,17 @@ +namespace Cohere; + +/// +/// Class providing methods for API access. +/// +public partial class CohereApi +{ + /// + /// Sets the selected apiKey as a default header for the HttpClient. + /// + /// + /// + public CohereApi(string apiKey, HttpClient httpClient) : this(httpClient) + { + ApiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey)); + } +} diff --git a/src/libs/Cohere/RecommendedModelIds.cs b/src/libs/Cohere/RecommendedModelIds.cs new file mode 100644 index 0000000..64ba85f --- /dev/null +++ b/src/libs/Cohere/RecommendedModelIds.cs @@ -0,0 +1,17 @@ +namespace Cohere; + +// ReSharper disable InconsistentNaming +#pragma warning disable CA1707 + +/// +/// Recommended models for Cohere. +/// +public static class RecommendedModelIds +{ + /// + /// https://huggingface.co/gpt2
+ /// Pretrained model on English language using a causal language modeling (CLM) objective. + /// It was introduced in this paper and first released at this page.
+ ///
+ public const string Gpt2 = "gpt2"; +} \ No newline at end of file diff --git a/src/libs/Directory.Build.props b/src/libs/Directory.Build.props new file mode 100755 index 0000000..d4b672c --- /dev/null +++ b/src/libs/Directory.Build.props @@ -0,0 +1,49 @@ + + + + + + true + $(MSBuildThisFileDirectory)../key.snk + + + + + <_Parameter1>false + + + + + 0.1.0 + true + true + tryAGI and contributors + MIT + nuget_icon.png + README.md + + + + + + + + + true + $(MSBuildThisFileDirectory)../../../LocalPackages + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + true + latest + All + + + diff --git a/src/tests/Cohere.IntegrationTests/Cohere.IntegrationTests.csproj b/src/tests/Cohere.IntegrationTests/Cohere.IntegrationTests.csproj new file mode 100755 index 0000000..b526853 --- /dev/null +++ b/src/tests/Cohere.IntegrationTests/Cohere.IntegrationTests.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tests/Cohere.IntegrationTests/Tests.cs b/src/tests/Cohere.IntegrationTests/Tests.cs new file mode 100755 index 0000000..21556e5 --- /dev/null +++ b/src/tests/Cohere.IntegrationTests/Tests.cs @@ -0,0 +1,69 @@ +using Cohere; + +namespace tryAGI.OpenAI.IntegrationTests; + +[TestClass] +public class GeneralTests +{ + [TestMethod] + public async Task Generate() + { + var apiKey = + Environment.GetEnvironmentVariable("HUGGINGFACE_API_KEY") ?? + throw new AssertInconclusiveException("HUGGINGFACE_API_KEY environment variable is not found."); + + using var client = new HttpClient(); + var api = new CohereApi(apiKey, client); + var response = await api.GenerateTextAsync( + RecommendedModelIds.Gpt2, + new GenerateTextRequest + { + Inputs = "Give random 5 words in response", + Parameters = new GenerateTextRequestParameters + { + Max_new_tokens = 250, + Return_full_text = false, + }, + Options = new GenerateTextRequestOptions + { + Use_cache = true, + Wait_for_model = false, + }, + }); + response.Should().NotBeEmpty(); + + foreach (var value in response) + { + Console.WriteLine(value.Generated_text); + } + } + + [TestMethod] + public async Task GenerateError() + { + var apiKey = + Environment.GetEnvironmentVariable("HUGGINGFACE_API_KEY") ?? + throw new AssertInconclusiveException("HUGGINGFACE_API_KEY environment variable is not found."); + + using var client = new HttpClient(); + var api = new CohereApi(apiKey, client); + Func act = async () => await api.GenerateTextAsync( + RecommendedModelIds.Gpt2, + new GenerateTextRequest + { + Inputs = "Hello", + Parameters = new GenerateTextRequestParameters + { + Max_new_tokens = 2501, + Return_full_text = false, + }, + Options = new GenerateTextRequestOptions + { + Use_cache = true, + Wait_for_model = false, + }, + }); + + await act.Should().ThrowAsync(); + } +}