Skip to content

Commit d35b376

Browse files
committed
Support local Ollama
1 parent 3ed5cb0 commit d35b376

6 files changed

Lines changed: 30 additions & 19 deletions

File tree

Examples/Examples/Chat/ChatExampleOllama.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ public class ChatExampleOllama : IExample
77
{
88
public async Task Start()
99
{
10-
OllamaExample.Setup(); //We need to provide Ollama API key
10+
OllamaExample.Setup(); // We need to set Ollama backend type and optionally provide Ollama API key
1111
Console.WriteLine("(Ollama) ChatExample is running!");
1212

1313
await AIHub.Chat()
14-
.WithModel("qwen3-next:80b")
14+
.WithModel("gemma3:4b")
1515
.WithMessage("Write a short poem about the color green.")
1616
.CompleteAsync(interactive: true);
1717
}

Examples/Examples/Utils/OllamaExample.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public static void Setup()
1010
MaINBootstrapper.Initialize(configureSettings: (options) =>
1111
{
1212
options.BackendType = BackendType.Ollama;
13-
options.OllamaKey = "<YOUR_OLLAMA_KEY>";
13+
//options.OllamaKey = "<YOUR_OLLAMA_KEY>"; // set only if you want to use Ollama cloud
1414
});
1515
}
1616
}

src/MaIN.Services/Bootstrapper.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ private static IServiceCollection AddHttpClients(this IServiceCollection service
112112
services.AddHttpClient(ServiceConstants.HttpClients.GeminiClient);
113113
services.AddHttpClient(ServiceConstants.HttpClients.DeepSeekClient);
114114
services.AddHttpClient(ServiceConstants.HttpClients.GroqCloudClient);
115+
services.AddHttpClient(ServiceConstants.HttpClients.OllamaClient);
116+
services.AddHttpClient(ServiceConstants.HttpClients.OllamaLocalClient);
115117
services.AddHttpClient(ServiceConstants.HttpClients.ImageDownloadClient);
116118
services.AddHttpClient(ServiceConstants.HttpClients.ModelContextDownloadClient, client =>
117119
{

src/MaIN.Services/Constants/ServiceConstants.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static class HttpClients
1212
public const string AnthropicClient = "AnthropicClient";
1313
public const string XaiClient = "XaiClient";
1414
public const string OllamaClient = "OllamaClient";
15+
public const string OllamaLocalClient = "OllamaLocalClient";
1516
public const string ImageDownloadClient = "ImageDownloadClient";
1617
public const string ModelContextDownloadClient = "ModelContextDownloadClient";
1718
}
@@ -45,6 +46,9 @@ public static class ApiUrls
4546

4647
public const string OllamaOpenAiChatCompletions = "https://ollama.com/v1/chat/completions";
4748
public const string OllamaModels = "https://ollama.com/v1/models";
49+
50+
public const string OllamaLocalOpenAiChatCompletions = "http://localhost:11434/v1/chat/completions";
51+
public const string OllamaLocalModels = "http://localhost:11434/v1/models";
4852
}
4953

5054
public static class Messages

src/MaIN.Services/Services/LLMService/OllamaService.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,21 @@ public sealed class OllamaService(
2020
{
2121
private readonly MaINSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));
2222

23-
protected override string HttpClientName => ServiceConstants.HttpClients.OllamaClient;
24-
protected override string ChatCompletionsUrl => ServiceConstants.ApiUrls.OllamaOpenAiChatCompletions;
25-
protected override string ModelsUrl => ServiceConstants.ApiUrls.OllamaModels;
23+
private bool HasApiKey => !string.IsNullOrEmpty(_settings.OllamaKey) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OLLAMA_API_KEY"));
24+
25+
protected override string HttpClientName => HasApiKey ? ServiceConstants.HttpClients.OllamaClient : ServiceConstants.HttpClients.OllamaLocalClient;
26+
protected override string ChatCompletionsUrl => HasApiKey ? ServiceConstants.ApiUrls.OllamaOpenAiChatCompletions : ServiceConstants.ApiUrls.OllamaLocalOpenAiChatCompletions;
27+
protected override string ModelsUrl => HasApiKey ? ServiceConstants.ApiUrls.OllamaModels : ServiceConstants.ApiUrls.OllamaLocalModels;
2628

2729
protected override string GetApiKey()
2830
{
29-
return _settings.OllamaKey ?? Environment.GetEnvironmentVariable("OLLAMA_API_KEY") ??
30-
throw new InvalidOperationException("Olama Key not configured");
31+
return _settings.OllamaKey ?? Environment.GetEnvironmentVariable("OLLAMA_API_KEY") ?? string.Empty;
3132
}
3233

3334
protected override void ValidateApiKey()
3435
{
35-
if (string.IsNullOrEmpty(_settings.OllamaKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OLLAMA_API_KEY")))
36-
{
37-
throw new InvalidOperationException("Ollama Key not configured");
38-
}
36+
// No validation required - local Ollama doesn't need an API key
37+
// Cloud Ollama will fail at runtime if the key is missing
3938
}
4039

4140
public override async Task<ChatResult?> AskMemory(

src/MaIN.Services/Services/LLMService/OpenAiCompatibleService.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ await _notificationService.DispatchNotification(
292292
CancellationToken cancellationToken)
293293
{
294294
var client = _httpClientFactory.CreateClient(HttpClientName);
295-
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
295+
SetAuthorizationIfNeeded(client, apiKey);
296296

297297
var requestBody = BuildRequestBody(chat, conversation, true);
298298

@@ -411,7 +411,7 @@ await _notificationService.DispatchNotification(
411411
CancellationToken cancellationToken)
412412
{
413413
var client = _httpClientFactory.CreateClient(HttpClientName);
414-
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
414+
SetAuthorizationIfNeeded(client, apiKey);
415415

416416
var requestBody = BuildRequestBody(chat, conversation, false);
417417

@@ -522,9 +522,7 @@ public virtual async Task<string[]> GetCurrentModels()
522522
ValidateApiKey();
523523

524524
var client = _httpClientFactory.CreateClient(HttpClientName);
525-
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
526-
"Bearer",
527-
GetApiKey());
525+
SetAuthorizationIfNeeded(client, GetApiKey());
528526

529527
using var response = await client.GetAsync(ModelsUrl);
530528
response.EnsureSuccessStatusCode();
@@ -575,6 +573,14 @@ private static bool HasFiles(Message message)
575573
return message.Files != null && message.Files.Count > 0;
576574
}
577575

576+
private static void SetAuthorizationIfNeeded(HttpClient client, string apiKey)
577+
{
578+
if (!string.IsNullOrEmpty(apiKey))
579+
{
580+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
581+
}
582+
}
583+
578584
private async Task ProcessStreamingChatAsync(
579585
Chat chat,
580586
List<ChatMessage> conversation,
@@ -585,7 +591,7 @@ private async Task ProcessStreamingChatAsync(
585591
CancellationToken cancellationToken)
586592
{
587593
var client = _httpClientFactory.CreateClient(HttpClientName);
588-
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
594+
SetAuthorizationIfNeeded(client, apiKey);
589595

590596
var requestBody = BuildRequestBody(chat, conversation, true);
591597

@@ -670,7 +676,7 @@ private async Task ProcessNonStreamingChatAsync(
670676
CancellationToken cancellationToken)
671677
{
672678
var client = _httpClientFactory.CreateClient(HttpClientName);
673-
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
679+
SetAuthorizationIfNeeded(client, apiKey);
674680

675681
var requestBody = BuildRequestBody(chat, conversation, false);
676682

0 commit comments

Comments
 (0)