Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Examples/Examples/Chat/ChatExampleOllama.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Examples.Utils;
using MaIN.Core.Hub;

namespace Examples.Chat;

public class ChatExampleOllama : IExample
{
public async Task Start()
{
OllamaExample.Setup(); // We need to set Ollama backend type and optionally provide Ollama API key
Console.WriteLine("(Ollama) ChatExample is running!");

await AIHub.Chat()
.WithModel("gemma3:4b")
.WithMessage("Write a short poem about the color green.")
.CompleteAsync(interactive: true);
}
}
2 changes: 2 additions & 0 deletions Examples/Examples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ static void RegisterExamples(IServiceCollection services)
services.AddTransient<ChatExampleGroqCloud>();
services.AddTransient<ChatExampleAnthropic>();
services.AddTransient<ChatExampleXai>();
services.AddTransient<ChatExampleOllama>();
}

async Task RunSelectedExample(IServiceProvider serviceProvider)
Expand Down Expand Up @@ -174,6 +175,7 @@ public class ExampleRegistry(IServiceProvider serviceProvider)
("\u25a0 GroqCloud Chat", serviceProvider.GetRequiredService<ChatExampleGroqCloud>()),
("\u25a0 Anthropic Chat", serviceProvider.GetRequiredService<ChatExampleAnthropic>()),
("\u25a0 xAI Chat", serviceProvider.GetRequiredService<ChatExampleXai>()),
("\u25a0 Ollama Chat", serviceProvider.GetRequiredService<ChatExampleOllama>()),
("\u25a0 McpClient example", serviceProvider.GetRequiredService<McpExample>()),
("\u25a0 McpAgent example", serviceProvider.GetRequiredService<McpAgentsExample>()),
("\u25a0 Chat with TTS example", serviceProvider.GetRequiredService<ChatWithTextToSpeechExample>()),
Expand Down
16 changes: 16 additions & 0 deletions Examples/Examples/Utils/OllamaExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using MaIN.Core;
using MaIN.Domain.Configuration;

namespace Examples.Utils;

public class OllamaExample
{
public static void Setup()
{
MaINBootstrapper.Initialize(configureSettings: (options) =>
{
options.BackendType = BackendType.Ollama;
//options.OllamaKey = "<YOUR_OLLAMA_KEY>"; // set only if you want to use Ollama cloud
});
}
}
4 changes: 1 addition & 3 deletions MaIN.Core.IntegrationTests/MaIN.Core.IntegrationTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.22" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
Expand All @@ -19,7 +17,7 @@
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions Releases/0.8.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 0.8.1 release

- Add Ollama integration
2 changes: 1 addition & 1 deletion src/MaIN.Core/.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package>
<metadata>
<id>MaIN.NET</id>
<version>0.8.0</version>
<version>0.8.1</version>
<authors>Wisedev</authors>
<owners>Wisedev</owners>
<icon>favicon.png</icon>
Expand Down
13 changes: 2 additions & 11 deletions src/MaIN.Core/MaIN.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GTranslate" Version="2.3.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
<PackageReference Include="LLamaSharp" Version="0.25.0" />
<PackageReference Include="LLamaSharp.Backend.Cuda12" Version="0.25.0" />
<PackageReference Include="LLamaSharp.kernel-memory" Version="0.25.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
<PackageReference Include="Microsoft.KernelMemory" Version="0.98.250508.3" />
<PackageReference Include="Microsoft.KernelMemory.SemanticKernelPlugin" Version="0.98.250508.3" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.6" />
<PackageReference Include="Tesseract" Version="5.2.0" />
<PackageReference Include="Tesseract.Data.English" Version="4.0.0" />
</ItemGroup>

Expand All @@ -33,7 +24,7 @@
</ItemGroup>

<Target Name="link_deps" AfterTargets="AfterBuild" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="ln -sf /opt/homebrew/lib/libleptonica.dylib $(OutDir)x64/libleptonica-1.82.0.dylib"/>
<Exec Command="ln -sf /opt/homebrew/lib/libtesseract.dylib $(OutDir)x64/libtesseract50.dylib"/>
<Exec Command="ln -sf /opt/homebrew/lib/libleptonica.dylib $(OutDir)x64/libleptonica-1.82.0.dylib" />
<Exec Command="ln -sf /opt/homebrew/lib/libtesseract.dylib $(OutDir)x64/libtesseract50.dylib" />
</Target>
</Project>
2 changes: 2 additions & 0 deletions src/MaIN.Domain/Configuration/MaINSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class MaINSettings
public string? DeepSeekKey { get; set; }
public string? AnthropicKey { get; set; }
public string? GroqCloudKey { get; set; }
public string? OllamaKey { get; set; }
public string? XaiKey { get; set; }
public MongoDbSettings? MongoDbSettings { get; set; }
public FileSystemSettings? FileSystemSettings { get; set; }
Expand All @@ -30,4 +31,5 @@ public enum BackendType
GroqCloud = 4,
Anthropic = 5,
Xai = 6,
Ollama = 7,
}
4 changes: 0 additions & 4 deletions src/MaIN.InferPage/MaIN.InferPage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Costura.Fody" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Markdig" Version="0.44.0" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.13.2" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.13.2" />
</ItemGroup>

Expand Down
26 changes: 26 additions & 0 deletions src/MaIN.InferPage/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@
apiKeyVariable = "ANTHROPIC_API_KEY";
apiName = "Anthropic";
break;

case "xai":
Utils.Xai = true;
apiKeyVariable = "XAI_API_KEY";
apiName = "Xai";
break;

case "ollama":
Utils.Ollama = true;
apiKeyVariable = "OLLAMA_API_KEY";
apiName = "Ollama";
break;
}

var key = Environment.GetEnvironmentVariable(apiKeyVariable);
Expand Down Expand Up @@ -128,6 +140,20 @@
settings.BackendType = BackendType.Anthropic;
});
}
else if (Utils.Xai)
{
builder.Services.AddMaIN(builder.Configuration, settings =>
{
settings.BackendType = BackendType.Xai;
});
}
else if (Utils.Ollama)
{
builder.Services.AddMaIN(builder.Configuration, settings =>
{
settings.BackendType = BackendType.Ollama;
});
}
else
{
if (Utils.Path == null && !KnownModels.IsModelSupported(Utils.Model!))
Expand Down
2 changes: 2 additions & 0 deletions src/MaIN.InferPage/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public static class Utils
public static bool DeepSeek { get; set; }
public static bool GroqCloud { get; set; }
public static bool Anthropic { get; set; }
public static bool Xai { get; set; }
public static bool Ollama { get; set; }
public static string? Path { get; set; }
public static bool Reason { get; set; }
}
Expand Down
2 changes: 0 additions & 2 deletions src/MaIN.Infrastructure/MaIN.Infrastructure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
<PackageReference Include="MongoDB.Driver" Version="3.2.1" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.30.0" />
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions src/MaIN.Services/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ private static IServiceCollection AddHttpClients(this IServiceCollection service
services.AddHttpClient(ServiceConstants.HttpClients.GeminiClient);
services.AddHttpClient(ServiceConstants.HttpClients.DeepSeekClient);
services.AddHttpClient(ServiceConstants.HttpClients.GroqCloudClient);
services.AddHttpClient(ServiceConstants.HttpClients.OllamaClient);
services.AddHttpClient(ServiceConstants.HttpClients.OllamaLocalClient);
services.AddHttpClient(ServiceConstants.HttpClients.ImageDownloadClient);
services.AddHttpClient(ServiceConstants.HttpClients.ModelContextDownloadClient, client =>
{
Expand Down
8 changes: 8 additions & 0 deletions src/MaIN.Services/Constants/ServiceConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public static class HttpClients
public const string GroqCloudClient = "GroqCloudClient";
public const string AnthropicClient = "AnthropicClient";
public const string XaiClient = "XaiClient";
public const string OllamaClient = "OllamaClient";
public const string OllamaLocalClient = "OllamaLocalClient";
public const string ImageDownloadClient = "ImageDownloadClient";
public const string ModelContextDownloadClient = "ModelContextDownloadClient";
}
Expand Down Expand Up @@ -41,6 +43,12 @@ public static class ApiUrls
public const string XaiImageGenerations = "https://api.x.ai/v1/images/generations";
public const string XaiOpenAiChatCompletions = "https://api.x.ai/v1/chat/completions";
public const string XaiModels = "https://api.x.ai/v1/models";

public const string OllamaOpenAiChatCompletions = "https://ollama.com/v1/chat/completions";
public const string OllamaModels = "https://ollama.com/v1/models";

public const string OllamaLocalOpenAiChatCompletions = "http://localhost:11434/v1/chat/completions";
public const string OllamaLocalModels = "http://localhost:11434/v1/models";
}

public static class Messages
Expand Down
5 changes: 0 additions & 5 deletions src/MaIN.Services/MaIN.Services.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,12 @@
<ItemGroup>
<PackageReference Include="GTranslate" Version="2.3.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
<PackageReference Include="LLamaSharp" Version="0.25.0" />
<PackageReference Include="llamasharp.backend.cpu" Version="0.25.0" />
<PackageReference Include="LLamaSharp.Backend.Cuda12" Version="0.25.0" />
<PackageReference Include="LLamaSharp.kernel-memory" Version="0.25.0" />
<PackageReference Include="LLamaSharp.semantic-kernel" Version="0.25.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
<PackageReference Include="Microsoft.KernelMemory" Version="0.98.250508.3" />
<PackageReference Include="Microsoft.KernelMemory.SemanticKernelPlugin" Version="0.98.250508.3" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.68.0" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Google" Version="1.64.0-alpha" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.64.0-alpha" />
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.2.0-preview.1" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="NumSharp" Version="0.30.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using MaIN.Domain.Configuration;
using MaIN.Domain.Entities;
using MaIN.Services.Constants;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class ImageGenServiceFactory(IServiceProvider serviceProvider) : IImageGe
BackendType.Anthropic => null,
BackendType.Xai => new XaiImageGenService(serviceProvider.GetRequiredService<IHttpClientFactory>(),
serviceProvider.GetRequiredService<MaINSettings>()),
BackendType.Ollama => null,
BackendType.Self => new ImageGenService(serviceProvider.GetRequiredService<IHttpClientFactory>(),
serviceProvider.GetRequiredService<MaINSettings>()),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public ILLMService CreateService(BackendType backendType)
serviceProvider.GetRequiredService<IMemoryFactory>(),
serviceProvider.GetRequiredService<IMemoryService>()),

BackendType.Ollama => new OllamaService(
serviceProvider.GetRequiredService<MaINSettings>(),
serviceProvider.GetRequiredService<INotificationService>(),
serviceProvider.GetRequiredService<IHttpClientFactory>(),
serviceProvider.GetRequiredService<IMemoryFactory>(),
serviceProvider.GetRequiredService<IMemoryService>()),

BackendType.Anthropic => new AnthropicService(
serviceProvider.GetRequiredService<MaINSettings>(),
serviceProvider.GetRequiredService<INotificationService>(),
Expand Down
77 changes: 77 additions & 0 deletions src/MaIN.Services/Services/LLMService/OllamaService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Text;
using MaIN.Domain.Configuration;
using MaIN.Domain.Entities;
using MaIN.Services.Constants;
using MaIN.Services.Services.Abstract;
using MaIN.Services.Services.LLMService.Memory;
using MaIN.Services.Services.Models;
using Microsoft.Extensions.Logging;

namespace MaIN.Services.Services.LLMService;

public sealed class OllamaService(
MaINSettings settings,
INotificationService notificationService,
IHttpClientFactory httpClientFactory,
IMemoryFactory memoryFactory,
IMemoryService memoryService,
ILogger<OpenAiService>? logger = null)
: OpenAiCompatibleService(notificationService, httpClientFactory, memoryFactory, memoryService, logger)
{
private readonly MaINSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));

private bool HasApiKey => !string.IsNullOrEmpty(_settings.OllamaKey) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OLLAMA_API_KEY"));

protected override string HttpClientName => HasApiKey ? ServiceConstants.HttpClients.OllamaClient : ServiceConstants.HttpClients.OllamaLocalClient;
protected override string ChatCompletionsUrl => HasApiKey ? ServiceConstants.ApiUrls.OllamaOpenAiChatCompletions : ServiceConstants.ApiUrls.OllamaLocalOpenAiChatCompletions;
protected override string ModelsUrl => HasApiKey ? ServiceConstants.ApiUrls.OllamaModels : ServiceConstants.ApiUrls.OllamaLocalModels;

protected override string GetApiKey()
{
return _settings.OllamaKey ?? Environment.GetEnvironmentVariable("OLLAMA_API_KEY") ?? string.Empty;
}

protected override void ValidateApiKey()
{
// No validation required - local Ollama doesn't need an API key
// Cloud Ollama will fail at runtime if the key is missing
}

public override async Task<ChatResult?> AskMemory(
Chat chat,
ChatMemoryOptions memoryOptions,
ChatRequestOptions requestOptions,
CancellationToken cancellationToken = default)
{
var lastMsg = chat.Messages.Last();
var filePaths = await DocumentProcessor.ConvertToFilesContent(memoryOptions);
var message = new Message()
{
Role = ServiceConstants.Roles.User,
Content = ComposeMessage(lastMsg, filePaths),
Type = MessageType.CloudLLM
};

chat.Messages.Last().Content = message.Content;
chat.Messages.Last().Files = [];
var result = await Send(chat, new ChatRequestOptions(), cancellationToken);
chat.Messages.Last().Content = lastMsg.Content;
return result;
}

private string ComposeMessage(Message lastMsg, string[] filePaths)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"== FILES IN MEMORY");
foreach (var path in filePaths)
{
var doc = DocumentProcessor.ProcessDocument(path);
stringBuilder.Append(doc);
stringBuilder.AppendLine();
}
stringBuilder.AppendLine($"== END OF FILES");
stringBuilder.AppendLine();
stringBuilder.Append(lastMsg.Content);
return stringBuilder.ToString();
}
}
Loading
Loading