Skip to content

Commit 384758f

Browse files
committed
Ollama text chat & infra page
Introduces support for the Ollama backend by updating configuration, service factory, and constants. Adds OllamaService implementing OpenAI-compatible API calls, updates the example project to include a ChatExampleOllama, and provides utility setup for Ollama API key. Also updates InferPage to allow selection and configuration of Ollama as a backend.
1 parent 563ca6b commit 384758f

9 files changed

Lines changed: 156 additions & 0 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Examples.Utils;
2+
using MaIN.Core.Hub;
3+
4+
namespace Examples.Chat;
5+
6+
public class ChatExampleOllama : IExample
7+
{
8+
public async Task Start()
9+
{
10+
OllamaExample.Setup(); //We need to provide Ollama API key
11+
Console.WriteLine("(Ollama) ChatExample is running!");
12+
13+
await AIHub.Chat()
14+
.WithModel("qwen3-next:80b")
15+
.WithMessage("Write a short poem about the color green.")
16+
.CompleteAsync(interactive: true);
17+
}
18+
}

Examples/Examples/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ static void RegisterExamples(IServiceCollection services)
7979
services.AddTransient<ChatExampleGroqCloud>();
8080
services.AddTransient<ChatExampleAnthropic>();
8181
services.AddTransient<ChatExampleXai>();
82+
services.AddTransient<ChatExampleOllama>();
8283
}
8384

8485
async Task RunSelectedExample(IServiceProvider serviceProvider)
@@ -174,6 +175,7 @@ public class ExampleRegistry(IServiceProvider serviceProvider)
174175
("\u25a0 GroqCloud Chat", serviceProvider.GetRequiredService<ChatExampleGroqCloud>()),
175176
("\u25a0 Anthropic Chat", serviceProvider.GetRequiredService<ChatExampleAnthropic>()),
176177
("\u25a0 xAI Chat", serviceProvider.GetRequiredService<ChatExampleXai>()),
178+
("\u25a0 Ollama Chat", serviceProvider.GetRequiredService<ChatExampleOllama>()),
177179
("\u25a0 McpClient example", serviceProvider.GetRequiredService<McpExample>()),
178180
("\u25a0 McpAgent example", serviceProvider.GetRequiredService<McpAgentsExample>()),
179181
("\u25a0 Chat with TTS example", serviceProvider.GetRequiredService<ChatWithTextToSpeechExample>()),
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using MaIN.Core;
2+
using MaIN.Domain.Configuration;
3+
4+
namespace Examples.Utils;
5+
6+
public class OllamaExample
7+
{
8+
public static void Setup()
9+
{
10+
MaINBootstrapper.Initialize(configureSettings: (options) =>
11+
{
12+
options.BackendType = BackendType.Ollama;
13+
options.OllamaKey = "<YOUR_OLLAMA_KEY>";
14+
});
15+
}
16+
}

src/MaIN.Domain/Configuration/MaINSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class MaINSettings
1313
public string? DeepSeekKey { get; set; }
1414
public string? AnthropicKey { get; set; }
1515
public string? GroqCloudKey { get; set; }
16+
public string? OllamaKey { get; set; }
1617
public string? XaiKey { get; set; }
1718
public MongoDbSettings? MongoDbSettings { get; set; }
1819
public FileSystemSettings? FileSystemSettings { get; set; }
@@ -30,4 +31,5 @@ public enum BackendType
3031
GroqCloud = 4,
3132
Anthropic = 5,
3233
Xai = 6,
34+
Ollama = 7,
3335
}

src/MaIN.InferPage/Program.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@
7676
apiKeyVariable = "ANTHROPIC_API_KEY";
7777
apiName = "Anthropic";
7878
break;
79+
80+
case "xai":
81+
Utils.Xai = true;
82+
apiKeyVariable = "XAI_API_KEY";
83+
apiName = "Xai";
84+
break;
85+
86+
case "ollama":
87+
Utils.Ollama = true;
88+
apiKeyVariable = "OLLAMA_API_KEY";
89+
apiName = "Ollama";
90+
break;
7991
}
8092

8193
var key = Environment.GetEnvironmentVariable(apiKeyVariable);
@@ -128,6 +140,20 @@
128140
settings.BackendType = BackendType.Anthropic;
129141
});
130142
}
143+
else if (Utils.Xai)
144+
{
145+
builder.Services.AddMaIN(builder.Configuration, settings =>
146+
{
147+
settings.BackendType = BackendType.Xai;
148+
});
149+
}
150+
else if (Utils.Ollama)
151+
{
152+
builder.Services.AddMaIN(builder.Configuration, settings =>
153+
{
154+
settings.BackendType = BackendType.Ollama;
155+
});
156+
}
131157
else
132158
{
133159
if (Utils.Path == null && !KnownModels.IsModelSupported(Utils.Model!))

src/MaIN.InferPage/Utils.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public static class Utils
1212
public static bool DeepSeek { get; set; }
1313
public static bool GroqCloud { get; set; }
1414
public static bool Anthropic { get; set; }
15+
public static bool Xai { get; set; }
16+
public static bool Ollama { get; set; }
1517
public static string? Path { get; set; }
1618
public static bool Reason { get; set; }
1719
}

src/MaIN.Services/Constants/ServiceConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public static class HttpClients
1111
public const string GroqCloudClient = "GroqCloudClient";
1212
public const string AnthropicClient = "AnthropicClient";
1313
public const string XaiClient = "XaiClient";
14+
public const string OllamaClient = "OllamaClient";
1415
public const string ImageDownloadClient = "ImageDownloadClient";
1516
public const string ModelContextDownloadClient = "ModelContextDownloadClient";
1617
}
@@ -41,6 +42,10 @@ public static class ApiUrls
4142
public const string XaiImageGenerations = "https://api.x.ai/v1/images/generations";
4243
public const string XaiOpenAiChatCompletions = "https://api.x.ai/v1/chat/completions";
4344
public const string XaiModels = "https://api.x.ai/v1/models";
45+
46+
public const string OllamaImageGenerations = "https://ollama.com/v1/images/generations";
47+
public const string OllamaOpenAiChatCompletions = "https://ollama.com/v1/chat/completions";
48+
public const string OllamaModels = "https://ollama.com/v1/models";
4449
}
4550

4651
public static class Messages

src/MaIN.Services/Services/LLMService/Factory/LLMServiceFactory.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ public ILLMService CreateService(BackendType backendType)
4646
serviceProvider.GetRequiredService<IMemoryFactory>(),
4747
serviceProvider.GetRequiredService<IMemoryService>()),
4848

49+
BackendType.Ollama => new OllamaService(
50+
serviceProvider.GetRequiredService<MaINSettings>(),
51+
serviceProvider.GetRequiredService<INotificationService>(),
52+
serviceProvider.GetRequiredService<IHttpClientFactory>(),
53+
serviceProvider.GetRequiredService<IMemoryFactory>(),
54+
serviceProvider.GetRequiredService<IMemoryService>()),
55+
4956
BackendType.Anthropic => new AnthropicService(
5057
serviceProvider.GetRequiredService<MaINSettings>(),
5158
serviceProvider.GetRequiredService<INotificationService>(),
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System.Text;
2+
using MaIN.Domain.Configuration;
3+
using MaIN.Domain.Entities;
4+
using MaIN.Services.Constants;
5+
using MaIN.Services.Services.Abstract;
6+
using MaIN.Services.Services.LLMService.Memory;
7+
using MaIN.Services.Services.Models;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace MaIN.Services.Services.LLMService;
11+
12+
public sealed class OllamaService(
13+
MaINSettings settings,
14+
INotificationService notificationService,
15+
IHttpClientFactory httpClientFactory,
16+
IMemoryFactory memoryFactory,
17+
IMemoryService memoryService,
18+
ILogger<OpenAiService>? logger = null)
19+
: OpenAiCompatibleService(notificationService, httpClientFactory, memoryFactory, memoryService, logger)
20+
{
21+
private readonly MaINSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));
22+
23+
protected override string HttpClientName => ServiceConstants.HttpClients.OllamaClient;
24+
protected override string ChatCompletionsUrl => ServiceConstants.ApiUrls.OllamaOpenAiChatCompletions;
25+
protected override string ModelsUrl => ServiceConstants.ApiUrls.OllamaModels;
26+
27+
protected override string GetApiKey()
28+
{
29+
return _settings.OllamaKey ?? Environment.GetEnvironmentVariable("OLLAMA_API_KEY") ??
30+
throw new InvalidOperationException("Olama Key not configured");
31+
}
32+
33+
protected override void ValidateApiKey()
34+
{
35+
if (string.IsNullOrEmpty(_settings.OllamaKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OLLAMA_API_KEY")))
36+
{
37+
throw new InvalidOperationException("Ollama Key not configured");
38+
}
39+
}
40+
41+
public override async Task<ChatResult?> AskMemory(
42+
Chat chat,
43+
ChatMemoryOptions memoryOptions,
44+
ChatRequestOptions requestOptions,
45+
CancellationToken cancellationToken = default)
46+
{
47+
var lastMsg = chat.Messages.Last();
48+
var filePaths = await DocumentProcessor.ConvertToFilesContent(memoryOptions);
49+
var message = new Message()
50+
{
51+
Role = ServiceConstants.Roles.User,
52+
Content = ComposeMessage(lastMsg, filePaths),
53+
Type = MessageType.CloudLLM
54+
};
55+
56+
chat.Messages.Last().Content = message.Content;
57+
chat.Messages.Last().Files = [];
58+
var result = await Send(chat, new ChatRequestOptions(), cancellationToken);
59+
chat.Messages.Last().Content = lastMsg.Content;
60+
return result;
61+
}
62+
63+
private string ComposeMessage(Message lastMsg, string[] filePaths)
64+
{
65+
var stringBuilder = new StringBuilder();
66+
stringBuilder.AppendLine($"== FILES IN MEMORY");
67+
foreach (var path in filePaths)
68+
{
69+
var doc = DocumentProcessor.ProcessDocument(path);
70+
stringBuilder.Append(doc);
71+
stringBuilder.AppendLine();
72+
}
73+
stringBuilder.AppendLine($"== END OF FILES");
74+
stringBuilder.AppendLine();
75+
stringBuilder.Append(lastMsg.Content);
76+
return stringBuilder.ToString();
77+
}
78+
}

0 commit comments

Comments
 (0)