Skip to content

Commit

Permalink
Merge pull request #45 from CommunityToolkit/aaronpowell/issue31
Browse files Browse the repository at this point in the history
OllamaSharp client integration
  • Loading branch information
aaronpowell authored Oct 7, 2024
2 parents c69e49f + b016b57 commit 8c54eea
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 7 deletions.
14 changes: 14 additions & 0 deletions Aspire.CommunityToolkit.sln
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.CommunityToolkit.Hos
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.CommunityToolkit.Hosting.Ollama.Web", "examples\ollama\Aspire.CommunityToolkit.Hosting.Ollama.Web\Aspire.CommunityToolkit.Hosting.Ollama.Web.csproj", "{0A17021E-5F10-429B-88C8-E4073EADACB6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.CommunityToolkit.OllamaSharp", "src\Aspire.CommunityToolkit.OllamaSharp\Aspire.CommunityToolkit.OllamaSharp.csproj", "{22D81BC2-2659-483A-B7AC-8FCA086A99A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.CommunityToolkit.OllamaSharp.Tests", "tests\Aspire.CommunityToolkit.OllamaSharp.Tests\Aspire.CommunityToolkit.OllamaSharp.Tests.csproj", "{B170B19B-3FFA-4F71-AF12-A1E3955A0F8E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -143,6 +147,14 @@ Global
{0A17021E-5F10-429B-88C8-E4073EADACB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A17021E-5F10-429B-88C8-E4073EADACB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A17021E-5F10-429B-88C8-E4073EADACB6}.Release|Any CPU.Build.0 = Release|Any CPU
{22D81BC2-2659-483A-B7AC-8FCA086A99A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22D81BC2-2659-483A-B7AC-8FCA086A99A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22D81BC2-2659-483A-B7AC-8FCA086A99A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22D81BC2-2659-483A-B7AC-8FCA086A99A7}.Release|Any CPU.Build.0 = Release|Any CPU
{B170B19B-3FFA-4F71-AF12-A1E3955A0F8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B170B19B-3FFA-4F71-AF12-A1E3955A0F8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B170B19B-3FFA-4F71-AF12-A1E3955A0F8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B170B19B-3FFA-4F71-AF12-A1E3955A0F8E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -172,6 +184,8 @@ Global
{F4602DC8-3C17-4834-B640-9A3B27FE278A} = {14BD8AE7-C8DF-4C7C-8244-7F74C101569D}
{2F037600-2002-4A13-9359-98FB0D2416BE} = {14BD8AE7-C8DF-4C7C-8244-7F74C101569D}
{0A17021E-5F10-429B-88C8-E4073EADACB6} = {14BD8AE7-C8DF-4C7C-8244-7F74C101569D}
{22D81BC2-2659-483A-B7AC-8FCA086A99A7} = {414151D4-7009-4E78-A5C6-D99EBD1E67D1}
{B170B19B-3FFA-4F71-AF12-A1E3955A0F8E} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08B1D4B8-D2C5-4A64-BB8B-E1A2B29525F0}
Expand Down
33 changes: 33 additions & 0 deletions docs/integrations/ollamasharp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Aspire.CommunityTools.OllamaSharp

[![Aspire.CommunityToolkit.OllamaSharp](https://img.shields.io/nuget/v/Aspire.CommunityToolkit.OllamaSharp)](https://nuget.org/packages/Aspire.CommunityToolkit.OllamaSharp/) | [![Aspire.CommunityToolkit.OllamaSharp (latest)](<https://img.shields.io/nuget/vpre/Aspire.CommunityToolkit.OllamaSharp?label=nuget%20(preview)>)](https://nuget.org/packages/Aspire.CommunityToolkit.OllamaSharp/absoluteLatest)

## Overview

A .NET Aspire client integration that uses the [OllamaSharp](https://www.nuget.org/packages/OllamaSharp) client to interact with the Ollama container.

## Usage

Use the static `AddOllamaClientApi` method to add this client integration to the application builder of your client application. A callback can be provided to the `AddOllamaClientApi` method to configure the settings of the Ollama client.

```csharp
builder.AddOllamaClientApi("ollama");
```

Then you can inject the `IOllamaClientApi`` into your client application and use it to interact with the Ollama container.

```csharp
public class MyService(IOllamaClientApi ollamaClientApi)
{
public async Task DoSomething()
{
var chat = new Chat(ollamaClientApi);
while (true)
{
var message = Console.ReadLine();
await foreach (var answerToken in chat.Send(message))
Console.Write(answerToken);
}
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<ProjectReference Include="..\Aspire.CommunityToolkit.Hosting.Ollama.ServiceDefaults\Aspire.CommunityToolkit.Hosting.Ollama.ServiceDefaults.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.CommunityToolkit.OllamaSharp\Aspire.CommunityToolkit.OllamaSharp.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@page "/"
@using OllamaSharp
@using global::OllamaSharp
@rendermode InteractiveServer

<PageTitle>Home</PageTitle>
Expand All @@ -15,7 +15,7 @@
private string _response = "";

[Inject]
public required OllamaApiClient OllamaClient { get; set; }
public required IOllamaApiClient OllamaClient { get; set; }

private async Task GetPrompt()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Aspire.CommunityToolkit.Hosting.Ollama.Web;
using Aspire.CommunityToolkit.Hosting.Ollama.Web.Components;
using Aspire.CommunityToolkit.OllamaSharp;
using OllamaSharp;

var builder = WebApplication.CreateBuilder(args);
Expand All @@ -13,10 +14,7 @@

builder.Services.AddOutputCache();

builder.Services.AddScoped((_) => new OllamaApiClient(builder.Configuration["ConnectionStrings:ollama"]!)
{
SelectedModel = builder.Configuration["ollama:model"]!
});
builder.AddOllamaApiClient("ollama");

var app = builder.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace Aspire.Hosting;
/// </summary>
public static class OllamaResourceBuilderExtensions
{
private const string ConnectionStringEnvironmentName = "ConnectionStrings__";

/// <summary>
/// Adds the Ollama container to the application model.
/// </summary>
Expand All @@ -23,6 +25,7 @@ public static IResourceBuilder<OllamaResource> AddOllama(this IDistributedApplic
ArgumentNullException.ThrowIfNull(name, nameof(name));

builder.Services.TryAddLifecycleHook<OllamaResourceLifecycleHook>();

var resource = new OllamaResource(name);
return builder.AddResource(resource)
.WithAnnotation(new ContainerImageAnnotation { Image = OllamaContainerImageTags.Image, Tag = OllamaContainerImageTags.Tag, Registry = OllamaContainerImageTags.Registry })
Expand Down Expand Up @@ -81,7 +84,7 @@ public static IResourceBuilder<OllamaResource> AddModel(this IResourceBuilder<Ol
/// <summary>
/// Sets the default model to be configured on the Ollama server.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="builder">The <see cref="IResourceBuilder{T}"/>.</param>
/// <param name="modelName">The name of the model.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<OllamaResource> WithDefaultModel(this IResourceBuilder<OllamaResource> builder, string modelName)
Expand All @@ -93,6 +96,40 @@ public static IResourceBuilder<OllamaResource> WithDefaultModel(this IResourceBu
return builder;
}

/// <summary>
/// Adds a reference to an Ollama resource to the application model.
/// </summary>
/// <typeparam name="T">The type of the resource to add Ollama to.</typeparam>
/// <param name="builder">The <see cref="IResourceBuilder{T}"/>.</param>
/// <param name="ollama">The Ollama resource to add as a reference.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
/// <remarks>
/// This method adds the connection string and model references to the application model.
/// </remarks>
public static IResourceBuilder<T> WithReference<T>(this IResourceBuilder<T> builder, IResourceBuilder<OllamaResource> ollama) where T : IResourceWithEnvironment
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
ArgumentNullException.ThrowIfNull(ollama, nameof(ollama));

var resource = (IResourceWithConnectionString)ollama.Resource;
return builder.WithEnvironment(context =>
{
var connectionStringName = resource.ConnectionStringEnvironmentVariable ?? $"{ConnectionStringEnvironmentName}{resource.Name}";

context.EnvironmentVariables[connectionStringName] = new ConnectionStringReference(resource, optional: false);

for (int i = 0; i < ollama.Resource.Models.Count; i++)
{
var model = ollama.Resource.Models[i];
context.EnvironmentVariables[$"Aspire__OllamaSharp__{resource.Name}__Models__{i}"] = model;

if (model == ollama.Resource.DefaultModel)
{
context.EnvironmentVariables[$"Aspire__OllamaSharp__{resource.Name}__SelectedModel"] = model;
}
}
});
}

/// <summary>
/// Adds an administration web UI Ollama to the application model using Attu. This version the package defaults to the main tag of the Open WebUI container image
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup>
<PackageReadmeFile>ollamasharp.md</PackageReadmeFile>
<Description>A .NET Aspire client integration for the OllamaSharp library.</Description>
<AdditionalPackageTags>ollama ai ollamasharp</AdditionalPackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OllamaSharp" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Aspire.CommunityToolkit.OllamaSharp;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OllamaSharp;

namespace Microsoft.Extensions.Hosting;

public static class AspireOllamaSharpExtensions
{
private const string DefaultConfigSectionName = "Aspire:OllamaSharp";

/// <summary>
/// Adds <see cref="OllamaApiClient"/> services to the container.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.</param>
/// <exception cref="UriFormatException">Thrown when no Ollama endpoint is provided.</exception>
public static void AddOllamaApiClient(this IHostApplicationBuilder builder, string connectionName, Action<OllamaSharpSettings>? configureSettings = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(connectionName, nameof(connectionName));
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
AddOllamaClientInternal(builder, connectionName, configureSettings: configureSettings);
}

/// <summary>
/// Adds <see cref="OllamaApiClient"/> services to the container using the <paramref name="connectionName"/> as the service key.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.</param>
/// <exception cref="UriFormatException">Thrown when no Ollama endpoint is provided.</exception>
public static void AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, string connectionName, Action<OllamaSharpSettings>? configureSettings = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(connectionName, nameof(connectionName));
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
AddOllamaClientInternal(builder, connectionName, serviceKey: connectionName, configureSettings: configureSettings);
}

private static void AddOllamaClientInternal(IHostApplicationBuilder builder, string connectionName, string? serviceKey = null, Action<OllamaSharpSettings>? configureSettings = null)
{
OllamaSharpSettings settings = new();
builder.Configuration.GetSection($"{DefaultConfigSectionName}:{connectionName}").Bind(settings);

if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
{
settings.ConnectionString = connectionString;
}

configureSettings?.Invoke(settings);

if (string.IsNullOrWhiteSpace(settings.ConnectionString))
{
throw new UriFormatException("No endpoint for Ollama defined.");
}

OllamaApiClient client = new(new HttpClient { BaseAddress = new Uri(settings.ConnectionString) });

if (!string.IsNullOrWhiteSpace(settings.SelectedModel))
{
client.SelectedModel = settings.SelectedModel;
}

if (!string.IsNullOrEmpty(serviceKey))
{
builder.Services.AddKeyedSingleton<IOllamaApiClient>(serviceKey, client);
}
else
{
builder.Services.AddSingleton<IOllamaApiClient>(client);
}
}
}
22 changes: 22 additions & 0 deletions src/Aspire.CommunityToolkit.OllamaSharp/OllamaSharpSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Aspire.CommunityToolkit.OllamaSharp;

/// <summary>
/// Represents the settings for OllamaSharp.
/// </summary>
public sealed class OllamaSharpSettings
{
/// <summary>
/// Gets or sets the connection string.
/// </summary>
public string? ConnectionString { get; set; }

/// <summary>
/// Gets or sets the selected model.
/// </summary>
public string? SelectedModel { get; set; }

/// <summary>
/// Gets or sets the list of models to available.
/// </summary>
public IReadOnlyList<string> Models { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\..\src\Aspire.CommunityToolkit.OllamaSharp\Aspire.CommunityToolkit.OllamaSharp.csproj" />
</ItemGroup>

<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

</Project>
Loading

0 comments on commit 8c54eea

Please sign in to comment.