Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frank/ollama module #1099

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Ollama", "src\Testcontainers.Ollama\Testcontainers.Ollama.csproj", "{FDC88529-64F5-4F0A-95BE-8FBF653201C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Ollama.Tests", "tests\Testcontainers.Ollama.Tests\Testcontainers.Ollama.Tests.csproj", "{0997BFAD-919D-482F-83E6-5DF9C4A1D313}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -556,6 +560,14 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU
{FDC88529-64F5-4F0A-95BE-8FBF653201C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDC88529-64F5-4F0A-95BE-8FBF653201C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDC88529-64F5-4F0A-95BE-8FBF653201C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDC88529-64F5-4F0A-95BE-8FBF653201C6}.Release|Any CPU.Build.0 = Release|Any CPU
{0997BFAD-919D-482F-83E6-5DF9C4A1D313}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0997BFAD-919D-482F-83E6-5DF9C4A1D313}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0997BFAD-919D-482F-83E6-5DF9C4A1D313}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0997BFAD-919D-482F-83E6-5DF9C4A1D313}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -647,5 +659,7 @@ Global
{1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{FDC88529-64F5-4F0A-95BE-8FBF653201C6} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{0997BFAD-919D-482F-83E6-5DF9C4A1D313} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ await moduleNameContainer.StartAsync();
| MySQL | `mysql:8.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MySql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MySql) |
| NATS | `nats:2.9` | [NuGet](https://www.nuget.org/packages/Testcontainers.Nats) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Nats) |
| Neo4j | `neo4j:5.4` | [NuGet](https://www.nuget.org/packages/Testcontainers.Neo4j) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Neo4j) |
| Ollama | `ollama/ollama:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.Ollama) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Ollama) |
| Oracle | `gvenzl/oracle-xe:21.3.0-slim-faststart` | [NuGet](https://www.nuget.org/packages/Testcontainers.Oracle) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Oracle) |
| Papercut | `jijiechen/papercut:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.Papercut) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Papercut) |
| PostgreSQL | `postgres:15.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.PostgreSql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.PostgreSql) |
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.Ollama/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
121 changes: 121 additions & 0 deletions src/Testcontainers.Ollama/OllamaBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
namespace Testcontainers.Ollama
{
/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class OllamaBuilder : ContainerBuilder<OllamaBuilder, OllamaContainer, OllamaConfiguration>
{
/// <summary>
/// Gets the default port of the Ollama API.
/// </summary>
public const int DefaultPort = 11434;

/// <summary>
/// Default image name and version tag.
/// </summary>
public const string OllamaImage = "ollama/ollama:0.1.22";

/// <summary>
/// Default volume path.
/// </summary>
public const string DefaultVolumePath = "/root/.ollama";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Volume mounting should be optional rather than the default, at least for now. We can align along with Java and Go implementation.


/// <summary>
/// Default volume name.
/// </summary>
public const string DefaultVolumeName = "ollama-volume";

/// <summary>
/// The default model name for the OllamaBuilder.
/// </summary>
public const string DefaultModelName = OllamaModels.Llama2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Models are just like images and should be provided as a string rather than having an specific type. It will also reduce the amount of PRs just to keep it up-to-date


/// <summary>
/// Initializes a new instance of the <see cref="OllamaBuilder" /> class.
/// </summary>
public OllamaBuilder()
: this(new OllamaConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="OllamaBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private OllamaBuilder(OllamaConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override OllamaConfiguration DockerResourceConfiguration { get; }

/// <summary>
/// Sets the Testcontainers.Ollama config.
/// </summary>
/// <param name="config">The Testcontainers.Ollama config.</param>
/// <returns>A configured instance of <see cref="OllamaBuilder" />.</returns>
public OllamaBuilder OllamaConfig(OllamaConfiguration config)
{
return Merge(DockerResourceConfiguration, config);
}

/// <inheritdoc />
public override OllamaContainer Build()
{
Validate();
return new OllamaContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
}

/// <inheritdoc />
protected override void Validate()
{
Guard.Argument(DockerResourceConfiguration.ModelName, nameof(DockerResourceConfiguration.ModelName)).NotNull().NotEmpty();
base.Validate();
}

/// <inheritdoc />
protected override OllamaBuilder Init()
{
return base.Init()
.WithImage(new DockerImage(OllamaImage))
.WithPortBinding(DefaultPort, true)
.WithVolumeMount(DefaultVolumeName, DefaultVolumePath)
;
}

/// <inheritdoc />
protected override OllamaBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new OllamaConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override OllamaBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new OllamaConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override OllamaBuilder Merge(OllamaConfiguration oldValue, OllamaConfiguration newValue)
{
return new OllamaBuilder(new OllamaConfiguration(oldValue, newValue));
}

/// <summary>
/// Sets the name of the model to run.
/// </summary>
/// <param name="name">The name of the model to run.</param>
/// <returns>A configured instance of <see cref="OllamaBuilder" />.</returns>
/// <exception cref="ArgumentNullException">The name of the model to run is <see langword="null" />.</exception>
/// <exception cref="ArgumentException">The name of the model to run is empty.</exception>
/// <remarks>
/// The name of the model to run is required.
/// </remarks>
public OllamaBuilder WithModelName(string name)
{
return Merge(DockerResourceConfiguration, new OllamaConfiguration(DockerResourceConfiguration, new OllamaConfiguration(modelName: name)));
}
}
}
82 changes: 82 additions & 0 deletions src/Testcontainers.Ollama/OllamaConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace Testcontainers.Ollama
{
/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class OllamaConfiguration : ContainerConfiguration
{
/// <summary>
/// The OllamaConfiguration class represents the configuration for an Ollama container.
/// </summary>
public OllamaConfiguration(string modelName = null, string volumePath = null, string volumeName = null, int? port = null)
{
ModelName = modelName ?? string.Empty;
VolumePath = volumePath ?? OllamaBuilder.DefaultVolumePath;
VolumeName = volumeName ?? OllamaBuilder.DefaultVolumeName;
Port = port ?? OllamaBuilder.DefaultPort;
}

/// <summary>
/// Initializes a new instance of the <see cref="OllamaConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public OllamaConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="OllamaConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public OllamaConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="OllamaConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public OllamaConfiguration(OllamaConfiguration resourceConfiguration)
: this(new OllamaConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="OllamaConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public OllamaConfiguration(OllamaConfiguration oldValue, OllamaConfiguration newValue)
: base(oldValue, newValue)
{
ModelName = BuildConfiguration.Combine(oldValue.ModelName, newValue.ModelName);
VolumePath = BuildConfiguration.Combine(oldValue.VolumePath, newValue.VolumePath);
VolumeName = BuildConfiguration.Combine(oldValue.VolumeName, newValue.VolumeName);
Port = BuildConfiguration.Combine(oldValue.Port, newValue.Port);
}

/// <summary>
/// Represents the configuration for the Ollama container.
/// </summary>
public string ModelName { get; set; }

/// <summary>
/// The OllamaConfiguration class represents the configuration for an Ollama container.
/// </summary>
public string VolumePath { get; set; }

/// <summary>
/// Gets or sets the name of the volume associated with the Ollama container.
/// </summary>
public string VolumeName { get; set; }

/// <summary>
/// The <see cref="Port"/> class represents the configuration for an Ollama container port.
/// </summary>
public int Port { get; set; }
}
}
59 changes: 59 additions & 0 deletions src/Testcontainers.Ollama/OllamaContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Threading;

namespace Testcontainers.Ollama
{
/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class OllamaContainer : DockerContainer
{
/// <summary>
/// Initializes a new instance of the <see cref="OllamaContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
/// <param name="logger">The logger.</param>
public OllamaContainer(OllamaConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
Configuration = configuration;
}

public OllamaConfiguration Configuration { get; private set; }

public Task Run(CancellationToken ct = default)
{
return Run(Configuration.ModelName, ct);
}

/// <summary>
/// Starts the Ollama container.
/// </summary>
public Task Run(string modelName, CancellationToken ct = default)
{
ModelName = modelName;
if (State!= TestcontainersStates.Created && State != TestcontainersStates.Running) {
ThrowIfResourceNotFound();
}

return ExecAsync(new List<string>() {
"ollama", "run", ModelName,
}, ct);
}

/// <summary>
/// Gets the base URL of the Ollama API.
/// </summary>
/// <returns>The base URL of the Ollama API.</returns>
/// <example>http://localhost:5000/api</example>
public string GetBaseUrl() => $"http://{Hostname}:{GetMappedPublicPort(OllamaBuilder.DefaultPort)}/api";

/// <summary>
/// Gets the name of the Docker image to use.
/// </summary>
public string ImageName { get; }

/// <summary>
/// Gets the name of the model to run.
/// </summary>
public string ModelName { get; private set; }
}
}
76 changes: 76 additions & 0 deletions src/Testcontainers.Ollama/OllamaModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace Testcontainers.Ollama
{
/// <summary>
/// A selection of OLLAMA models from the readme.
/// </summary>
/// <remarks>
/// See: https://github.com/ollama/ollama?tab=readme-ov-file#model-library
/// </remarks>
public static class OllamaModels
{
/// <summary>
/// Llama 2: 7B parameters, Size: 3.8GB, Command: ollama run llama2
/// </summary>
public const string Llama2 = "llama2";

/// <summary>
/// Mistral: 7B parameters, Size: 4.1GB, Command: ollama run mistral
/// </summary>
public const string Mistral = "mistral";

/// <summary>
/// Dolphin Phi: 2.7B parameters, Size: 1.6GB, Command: ollama run dolphin-phi
/// </summary>
public const string DolphinPhi = "dolphin-phi";

/// <summary>
/// Phi-2: 2.7B parameters, Size: 1.7GB, Command: ollama run phi
/// </summary>
public const string Phi2 = "phi";

/// <summary>
/// Neural Chat: 7B parameters, Size: 4.1GB, Command: ollama run neural-chat
/// </summary>
public const string NeuralChat = "neural-chat";

/// <summary>
/// Starling: 7B parameters, Size: 4.1GB, Command: ollama run starling-lm
/// </summary>
public const string Starling = "starling-lm";

/// <summary>
/// Code Llama: 7B parameters, Size: 3.8GB, Command: ollama run codellama
/// </summary>
public const string CodeLlama = "codellama";

/// <summary>
/// Llama 2 Uncensored: 7B parameters, Size: 3.8GB, Command: ollama run llama2-uncensored
/// </summary>
public const string Llama2Uncensored = "llama2-uncensored";

/// <summary>
/// Llama 2 13B: 13B parameters, Size: 7.3GB, Command: ollama run llama2:13b
/// </summary>
public const string Llama213B = "llama2:13b";

/// <summary>
/// Llama 2 70B: 70B parameters, Size: 39GB, Command: ollama run llama2:70b
/// </summary>
public const string Llama270B = "llama2:70b";

/// <summary>
/// Orca Mini: 3B parameters, Size: 1.9GB, Command: ollama run orca-mini
/// </summary>
public const string OrcaMini = "orca-mini";

/// <summary>
/// Vicuna: 7B parameters, Size: 3.8GB, Command: ollama run vicuna
/// </summary>
public const string Vicuna = "vicuna";

/// <summary>
/// LLaVA: 7B parameters, Size: 4.5GB, Command: ollama run llava
/// </summary>
public const string LLaVA = "llava";
}
}