From 952e5cf1bb0fed97717edcd26e22126dc8c24e5e Mon Sep 17 00:00:00 2001 From: "Frank R. Haugen" Date: Thu, 25 Jan 2024 21:15:20 +0100 Subject: [PATCH] Cleanup --- src/Testcontainers.Ollama/OllamaBuilder.cs | 158 +++++++++--------- .../OllamaConfiguration.cs | 142 ++++++++-------- src/Testcontainers.Ollama/OllamaContainer.cs | 77 +++++---- src/Testcontainers.Ollama/Usings.cs | 6 +- .../OllamaContainerTests.cs | 88 +++++----- 5 files changed, 253 insertions(+), 218 deletions(-) diff --git a/src/Testcontainers.Ollama/OllamaBuilder.cs b/src/Testcontainers.Ollama/OllamaBuilder.cs index 72013826c..18672fd19 100644 --- a/src/Testcontainers.Ollama/OllamaBuilder.cs +++ b/src/Testcontainers.Ollama/OllamaBuilder.cs @@ -1,88 +1,96 @@ -namespace Testcontainers.Ollama; - -/// -[PublicAPI] -public sealed class OllamaBuilder : ContainerBuilder +namespace Testcontainers.Ollama { - /// - /// Initializes a new instance of the class. - /// - public OllamaBuilder() - : this(new OllamaConfiguration()) + /// + [PublicAPI] + public sealed class OllamaBuilder : ContainerBuilder { - DockerResourceConfiguration = Init().DockerResourceConfiguration; - } + /// + /// Initializes a new instance of the class. + /// + public OllamaBuilder() + : this(new OllamaConfiguration()) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - private OllamaBuilder(OllamaConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - DockerResourceConfiguration = resourceConfiguration; - } + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private OllamaBuilder(OllamaConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } - /// - protected override OllamaConfiguration DockerResourceConfiguration { get; } + /// + protected override OllamaConfiguration DockerResourceConfiguration { get; } - /// - /// Sets the Testcontainers.Ollama config. - /// - /// The Testcontainers.Ollama config. - /// A configured instance of . - public OllamaBuilder OllamaConfig(OllamaConfiguration config) - { - return Merge(DockerResourceConfiguration, config); - } + /// + /// Sets the Testcontainers.Ollama config. + /// + /// The Testcontainers.Ollama config. + /// A configured instance of . + public OllamaBuilder OllamaConfig(OllamaConfiguration config) + { + return Merge(DockerResourceConfiguration, config); + } - /// - public override OllamaContainer Build() - { - Validate(); - return new OllamaContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); - } + /// + public override OllamaContainer Build() + { + Validate(); + return new OllamaContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + } - /// - protected override void Validate() - { - Guard.Argument(DockerResourceConfiguration.Port, nameof(DockerResourceConfiguration.Port)).ThrowIf(info => info.Value is < 1 or > 65535, info => new ArgumentOutOfRangeException(info.Name, info.Value, $"The port must be between 1 and 65535.")); - Guard.Argument(DockerResourceConfiguration.ModelName, nameof(DockerResourceConfiguration.ModelName)).NotNull().NotEmpty(); - Guard.Argument(DockerResourceConfiguration.ImageName, nameof(DockerResourceConfiguration.ImageName)).NotNull().NotEmpty(); - Guard.Argument(DockerResourceConfiguration.HostName, nameof(DockerResourceConfiguration.HostName)).NotNull().NotEmpty(); - Guard.Argument(DockerResourceConfiguration.Schema, nameof(DockerResourceConfiguration.Schema)).NotNull().NotEmpty(); - - base.Validate(); - } + /// + protected override void Validate() + { + Guard.Argument(DockerResourceConfiguration.ModelName, nameof(DockerResourceConfiguration.ModelName)).NotNull().NotEmpty(); + base.Validate(); + } - /// - protected override OllamaBuilder Init() - { - return base.Init() - .WithName("ollama-container") - .WithImage(new DockerImage(DockerResourceConfiguration.ImageName)) - .WithHostname(DockerResourceConfiguration.HostName) - .WithPortBinding(DockerResourceConfiguration.Port, DockerResourceConfiguration.Port) - .WithVolumeMount("ollama", "/root/.ollama") - .WithExposedPort(DockerResourceConfiguration.Port) - ; - } + /// + protected override OllamaBuilder Init() + { + return base.Init() + .WithImage(new DockerImage(OllamaConfiguration.ImageName)) + .WithPortBinding(OllamaConfiguration.DefaultPort, true) + .WithVolumeMount(OllamaConfiguration.DefaultVolumeName, OllamaConfiguration.DefaultVolumePath) + ; + } - /// - protected override OllamaBuilder Clone(IResourceConfiguration resourceConfiguration) - { - return Merge(DockerResourceConfiguration, new OllamaConfiguration(resourceConfiguration)); - } + /// + protected override OllamaBuilder Clone(IResourceConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new OllamaConfiguration(resourceConfiguration)); + } - /// - protected override OllamaBuilder Clone(IContainerConfiguration resourceConfiguration) - { - return Merge(DockerResourceConfiguration, new OllamaConfiguration(resourceConfiguration)); - } + /// + protected override OllamaBuilder Clone(IContainerConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new OllamaConfiguration(resourceConfiguration)); + } - /// - protected override OllamaBuilder Merge(OllamaConfiguration oldValue, OllamaConfiguration newValue) - { - return new OllamaBuilder(new OllamaConfiguration(oldValue, newValue)); + /// + protected override OllamaBuilder Merge(OllamaConfiguration oldValue, OllamaConfiguration newValue) + { + return new OllamaBuilder(new OllamaConfiguration(oldValue, newValue)); + } + + /// + /// Sets the name of the model to run. + /// + /// The name of the model to run. + /// A configured instance of . + /// The name of the model to run is . + /// The name of the model to run is empty. + /// + /// The name of the model to run is required. + /// + public OllamaBuilder WithModelName(string name) + { + return Merge(DockerResourceConfiguration, new OllamaConfiguration(DockerResourceConfiguration) {ModelName = name}); + } } } \ No newline at end of file diff --git a/src/Testcontainers.Ollama/OllamaConfiguration.cs b/src/Testcontainers.Ollama/OllamaConfiguration.cs index b3c3c866d..d3eb7e544 100644 --- a/src/Testcontainers.Ollama/OllamaConfiguration.cs +++ b/src/Testcontainers.Ollama/OllamaConfiguration.cs @@ -1,75 +1,85 @@ -namespace Testcontainers.Ollama; - -/// -[PublicAPI] -public sealed class OllamaConfiguration : ContainerConfiguration +namespace Testcontainers.Ollama { - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// - public OllamaConfiguration(string modelName = null, string schema = null, string hostName = null, int? port = null) + /// + [PublicAPI] + public sealed class OllamaConfiguration : ContainerConfiguration { - ModelName = modelName; - Schema = schema; - HostName = hostName; - Port = port ?? Port; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public OllamaConfiguration(string modelName = null, string schema = null, int? port = null) + { + ModelName = modelName; + } - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public OllamaConfiguration(IResourceConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public OllamaConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public OllamaConfiguration(IContainerConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public OllamaConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public OllamaConfiguration(OllamaConfiguration resourceConfiguration) - : this(new OllamaConfiguration(), resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public OllamaConfiguration(OllamaConfiguration resourceConfiguration) + : this(new OllamaConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } - /// - /// Initializes a new instance of the class. - /// - /// The old Docker resource configuration. - /// The new Docker resource configuration. - public OllamaConfiguration(OllamaConfiguration oldValue, OllamaConfiguration newValue) - : base(oldValue, newValue) - { - ModelName = BuildConfiguration.Combine(oldValue.ModelName, newValue.ModelName); - Schema = BuildConfiguration.Combine(oldValue.Schema, newValue.Schema); - HostName = BuildConfiguration.Combine(oldValue.HostName, newValue.HostName); - Port = BuildConfiguration.Combine(oldValue.Port, newValue.Port); - } + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public OllamaConfiguration(OllamaConfiguration oldValue, OllamaConfiguration newValue) + : base(oldValue, newValue) + { + ModelName = BuildConfiguration.Combine(oldValue.ModelName, newValue.ModelName); + } - public string ModelName { get; set; } = OllamaModels.Llama2; - public string Schema { get; set; } = "http"; - public string HostName { get; set; } = "localhost"; - public int Port { get; set; } = 11434; + /// + /// Name of the model to use. + /// + public string ModelName { get; set; } = OllamaModels.Llama2; - /// - /// Gets the name of the Docker image to use. - /// - public string ImageName { get; } = "ollama/ollama:latest"; + /// + /// Gets the default port of the Ollama API. + /// + public const int DefaultPort = 11434; + + /// + /// Default image name. + /// + public const string ImageName = "ollama/ollama:latest"; + + /// + /// Default volume path. + /// + public const string DefaultVolumePath = "/root/.ollama"; + + /// + /// Default volume name. + /// + public const string DefaultVolumeName = "ollama-volume"; + } } \ No newline at end of file diff --git a/src/Testcontainers.Ollama/OllamaContainer.cs b/src/Testcontainers.Ollama/OllamaContainer.cs index 6234cf574..826aceff1 100644 --- a/src/Testcontainers.Ollama/OllamaContainer.cs +++ b/src/Testcontainers.Ollama/OllamaContainer.cs @@ -1,37 +1,52 @@ -namespace Testcontainers.Ollama; - -/// -[PublicAPI] -public sealed class OllamaContainer : DockerContainer +namespace Testcontainers.Ollama { - /// - /// Initializes a new instance of the class. - /// - /// The container configuration. - /// The logger. - public OllamaContainer(OllamaConfiguration configuration, ILogger logger) - : base(configuration, logger) + /// + [PublicAPI] + public sealed class OllamaContainer : DockerContainer { - ModelName = configuration.ModelName; - Schema = configuration.Schema; - HostName = configuration.HostName; - Port = configuration.Port; - ImageName = configuration.ImageName; - } + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + /// The logger. + public OllamaContainer(OllamaConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + ModelName = configuration.ModelName; + ImageName = OllamaConfiguration.ImageName; + } + + /// + /// Starts the Ollama container. + /// + public async Task StartOllamaAsync() + { + if (State!= TestcontainersStates.Created && State != TestcontainersStates.Running) { + throw new InvalidOperationException("Cannot start a container that has not been created."); + } + Task.WaitAll(ExecAsync(new List() + { + "ollama", "run", ModelName, + })); + + await Task.CompletedTask; + } - public string GetBaseUrl() => $"{Schema}://{HostName}:{Port}/api"; - - public string Schema { get; } - public string HostName { get; } - public int Port { get; } + /// + /// Gets the base URL of the Ollama API. + /// + /// The base URL of the Ollama API. + /// http://localhost:5000/api + public string GetBaseUrl() => $"http://{Hostname}:{GetMappedPublicPort(OllamaConfiguration.DefaultPort)}/api"; - /// - /// Gets the name of the Docker image to use. - /// - public string ImageName { get; } + /// + /// Gets the name of the Docker image to use. + /// + public string ImageName { get; } - /// - /// Gets the name of the model to run. - /// - public string ModelName { get; } + /// + /// Gets the name of the model to run. + /// + public string ModelName { get; } + } } \ No newline at end of file diff --git a/src/Testcontainers.Ollama/Usings.cs b/src/Testcontainers.Ollama/Usings.cs index a0845cb29..13053db61 100644 --- a/src/Testcontainers.Ollama/Usings.cs +++ b/src/Testcontainers.Ollama/Usings.cs @@ -6,6 +6,6 @@ global using JetBrains.Annotations; global using Microsoft.Extensions.Logging; global using DotNet.Testcontainers.Images; -global using System.Linq; -global using System.Threading.Tasks; -global using System; \ No newline at end of file +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; \ No newline at end of file diff --git a/tests/Testcontainers.Ollama.Tests/OllamaContainerTests.cs b/tests/Testcontainers.Ollama.Tests/OllamaContainerTests.cs index c0c602310..55641c4ec 100644 --- a/tests/Testcontainers.Ollama.Tests/OllamaContainerTests.cs +++ b/tests/Testcontainers.Ollama.Tests/OllamaContainerTests.cs @@ -1,47 +1,49 @@ -namespace Testcontainers.Ollama.Tests; - -public class OllamaContainerTests : IAsyncLifetime +namespace Testcontainers.Ollama.Tests { - private readonly ITestOutputHelper _testOutputHelper; - private OllamaContainer _ollamaContainer; - - public OllamaContainerTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - public async Task InitializeAsync() - { - TestcontainersSettings.Logger = new TestOutputLogger(nameof(OllamaContainerTests), _testOutputHelper); - _ollamaContainer = new OllamaBuilder().Build(); - await _ollamaContainer.StartAsync(); - } - - public async Task DisposeAsync() - { - await _ollamaContainer.DisposeAsync().AsTask(); - } - - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task OllamaContainerReturnsSuccessful() + public class OllamaContainerTests : IAsyncLifetime { - var client = new OllamaApiClient(_ollamaContainer.GetBaseUrl(), _ollamaContainer.ModelName); - - var chatRequest = new ChatRequest() { - Model = _ollamaContainer.ModelName, - Stream = false, - Messages = new List() - { - new Message() { Content = "You are very helpful", Role = ChatRole.System }, - new Message() { Content = "Hello", Role = ChatRole.User }, - } - }; - - var response = await client.SendChat(chatRequest, stream => { }); - _testOutputHelper.WriteJson(response); - - Assert.True(response.Any()); + private readonly ITestOutputHelper _testOutputHelper; + private OllamaContainer _ollamaContainer; + + public OllamaContainerTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + public async Task InitializeAsync() + { + TestcontainersSettings.Logger = new TestOutputLogger(nameof(OllamaContainerTests), _testOutputHelper); + _ollamaContainer = new OllamaBuilder().Build(); + await _ollamaContainer.StartAsync(); + await _ollamaContainer.StartOllamaAsync(); + } + + public async Task DisposeAsync() + { + await _ollamaContainer.DisposeAsync().AsTask(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task OllamaContainerReturnsSuccessful() + { + var client = new OllamaApiClient(_ollamaContainer.GetBaseUrl(), _ollamaContainer.ModelName); + + var chatRequest = new ChatRequest() { + Model = _ollamaContainer.ModelName, + Stream = false, + Messages = new List() + { + new Message() { Content = "What is a name", Role = ChatRole.User }, + } + }; + + var response = await client.SendChat(chatRequest, stream => { }); + response = response.ToList(); + + _testOutputHelper.WriteJson(response); + + Assert.True(response.Any()); + } } - }