diff --git a/dotnet/.editorconfig b/dotnet/.editorconfig
index 4da1adc5de68..5a604ce00961 100644
--- a/dotnet/.editorconfig
+++ b/dotnet/.editorconfig
@@ -141,7 +141,7 @@ csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
# Code block
-csharp_prefer_braces = false:none
+csharp_prefer_braces = true:warning
# Using statements
csharp_using_directive_placement = outside_namespace:error
@@ -173,6 +173,11 @@ dotnet_diagnostic.CS1573.severity = none
# disable CS1570: XML comment has badly formed XML
dotnet_diagnostic.CS1570.severity = none
+dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code
+dotnet_diagnostic.IDE0161.severity = warning # Use file-scoped namespace
+
+csharp_style_var_elsewhere = true:suggestion # Prefer 'var' everywhere
+
# disable check for generated code
[*.generated.cs]
generated_code = true
\ No newline at end of file
diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln
index b29e5e21e950..3d0848fb3110 100644
--- a/dotnet/AutoGen.sln
+++ b/dotnet/AutoGen.sln
@@ -35,12 +35,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral.Tests", "te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel.Tests", "test\AutoGen.SemanticKernel.Tests\AutoGen.SemanticKernel.Tests.csproj", "{1DFABC4A-8458-4875-8DCB-59F3802DAC65}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.OpenAI.Tests", "test\AutoGen.OpenAI.Tests\AutoGen.OpenAI.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}"
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Tests", "test\AutoGen.OpenAI.Tests\AutoGen.OpenAI.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Autogen.Ollama", "src\Autogen.Ollama\Autogen.Ollama.csproj", "{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Autogen.Ollama.Tests", "test\Autogen.Ollama.Tests\Autogen.Ollama.Tests.csproj", "{C24FDE63-952D-4F8E-A807-AF31D43AD675}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Ollama", "src\AutoGen.Ollama\AutoGen.Ollama.csproj", "{9F9E6DED-3D92-4970-909A-70FC11F1A665}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Ollama.Tests", "test\AutoGen.Ollama.Tests\AutoGen.Ollama.Tests.csproj", "{03E31CAA-3728-48D3-B936-9F11CF6C18FE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -96,14 +97,6 @@ Global
{15441693-3659-4868-B6C1-B106F52FF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.Build.0 = Release|Any CPU
- {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Release|Any CPU.Build.0 = Release|Any CPU
- {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Release|Any CPU.Build.0 = Release|Any CPU
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -116,6 +109,14 @@ Global
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9F9E6DED-3D92-4970-909A-70FC11F1A665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9F9E6DED-3D92-4970-909A-70FC11F1A665}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9F9E6DED-3D92-4970-909A-70FC11F1A665}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9F9E6DED-3D92-4970-909A-70FC11F1A665}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03E31CAA-3728-48D3-B936-9F11CF6C18FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03E31CAA-3728-48D3-B936-9F11CF6C18FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03E31CAA-3728-48D3-B936-9F11CF6C18FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03E31CAA-3728-48D3-B936-9F11CF6C18FE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -133,11 +134,11 @@ Global
{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{6585D1A4-3D97-4D76-A688-1933B61AEB19} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{15441693-3659-4868-B6C1-B106F52FF3BA} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
- {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
- {C24FDE63-952D-4F8E-A807-AF31D43AD675} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{1DFABC4A-8458-4875-8DCB-59F3802DAC65} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{D36A85F9-C172-487D-8192-6BFE5D05B4A7} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
+ {9F9E6DED-3D92-4970-909A-70FC11F1A665} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
+ {03E31CAA-3728-48D3-B936-9F11CF6C18FE} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}
diff --git a/dotnet/src/AutoGen.Core/Message/ImageMessage.cs b/dotnet/src/AutoGen.Core/Message/ImageMessage.cs
index 1239785c411a..d2e2d0803003 100644
--- a/dotnet/src/AutoGen.Core/Message/ImageMessage.cs
+++ b/dotnet/src/AutoGen.Core/Message/ImageMessage.cs
@@ -49,7 +49,9 @@ public ImageMessage(Role role, BinaryData data, string? from = null)
public string BuildDataUri()
{
if (this.Data is null)
+ {
throw new NullReferenceException($"{nameof(Data)}");
+ }
return $"data:{this.Data.MediaType};base64,{Convert.ToBase64String(this.Data.ToArray())}";
}
diff --git a/dotnet/src/AutoGen.Mistral/DTOs/ChatMessage.cs b/dotnet/src/AutoGen.Mistral/DTOs/ChatMessage.cs
index b0a05d85730b..b0fa1757c12e 100644
--- a/dotnet/src/AutoGen.Mistral/DTOs/ChatMessage.cs
+++ b/dotnet/src/AutoGen.Mistral/DTOs/ChatMessage.cs
@@ -13,7 +13,7 @@ public class ChatMessage
///
/// role.
/// content.
- public ChatMessage(RoleEnum? role = default(RoleEnum?), string? content = null)
+ public ChatMessage(RoleEnum? role = default, string? content = null)
{
this.Role = role;
this.Content = content;
diff --git a/dotnet/src/Autogen.Ollama/Agent/OllamaAgent.cs b/dotnet/src/AutoGen.Ollama/Agent/OllamaAgent.cs
similarity index 67%
rename from dotnet/src/Autogen.Ollama/Agent/OllamaAgent.cs
rename to dotnet/src/AutoGen.Ollama/Agent/OllamaAgent.cs
index 6f87e20e2332..9ef68388d605 100644
--- a/dotnet/src/Autogen.Ollama/Agent/OllamaAgent.cs
+++ b/dotnet/src/AutoGen.Ollama/Agent/OllamaAgent.cs
@@ -13,7 +13,7 @@
using System.Threading.Tasks;
using AutoGen.Core;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
///
/// An agent that can interact with ollama models.
@@ -21,7 +21,6 @@ namespace Autogen.Ollama;
public class OllamaAgent : IStreamingAgent
{
private readonly HttpClient _httpClient;
- public string Name { get; }
private readonly string _modelName;
private readonly string _systemMessage;
private readonly OllamaReplyOptions? _replyOptions;
@@ -36,13 +35,14 @@ public OllamaAgent(HttpClient httpClient, string name, string modelName,
_systemMessage = systemMessage;
_replyOptions = replyOptions;
}
+
public async Task GenerateReplyAsync(
IEnumerable messages, GenerateReplyOptions? options = null, CancellationToken cancellation = default)
{
ChatRequest request = await BuildChatRequest(messages, options);
request.Stream = false;
- using (HttpResponseMessage? response = await _httpClient
- .SendAsync(BuildRequestMessage(request), HttpCompletionOption.ResponseContentRead, cancellation))
+ var httpRequest = BuildRequest(request);
+ using (HttpResponseMessage? response = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseContentRead, cancellation))
{
response.EnsureSuccessStatusCode();
Stream? streamResponse = await response.Content.ReadAsStreamAsync();
@@ -52,6 +52,7 @@ public async Task GenerateReplyAsync(
return output;
}
}
+
public async IAsyncEnumerable GenerateStreamingReplyAsync(
IEnumerable messages,
GenerateReplyOptions? options = null,
@@ -59,7 +60,7 @@ public async IAsyncEnumerable GenerateStreamingReplyAsync(
{
ChatRequest request = await BuildChatRequest(messages, options);
request.Stream = true;
- HttpRequestMessage message = BuildRequestMessage(request);
+ HttpRequestMessage message = BuildRequest(request);
using (HttpResponseMessage? response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
response.EnsureSuccessStatusCode();
@@ -69,22 +70,28 @@ public async IAsyncEnumerable GenerateStreamingReplyAsync(
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested)
{
string? line = await reader.ReadLineAsync();
- if (string.IsNullOrWhiteSpace(line)) continue;
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
ChatResponseUpdate? update = JsonSerializer.Deserialize(line);
- if (update != null)
+ if (update is { Done: false })
{
yield return new MessageEnvelope(update, from: Name);
}
+ else
+ {
+ var finalUpdate = JsonSerializer.Deserialize(line) ?? throw new Exception("Failed to deserialize response");
- if (update is { Done: false }) continue;
-
- ChatResponse? chatMessage = JsonSerializer.Deserialize(line);
- if (chatMessage == null) continue;
- yield return new MessageEnvelope(chatMessage, from: Name);
+ yield return new MessageEnvelope(finalUpdate, from: Name);
+ }
}
}
}
+
+ public string Name { get; }
+
private async Task BuildChatRequest(IEnumerable messages, GenerateReplyOptions? options)
{
var request = new ChatRequest
@@ -152,49 +159,22 @@ private void BuildChatRequestOptions(OllamaReplyOptions replyOptions, ChatReques
}
private async Task> BuildChatHistory(IEnumerable messages)
{
- if (!messages.Any(m => m.IsSystemMessage()))
+ var history = messages.Select(m => m switch
{
- var systemMessage = new TextMessage(Role.System, _systemMessage, from: Name);
- messages = new[] { systemMessage }.Concat(messages);
- }
+ IMessage chatMessage => chatMessage.Content,
+ _ => throw new ArgumentException("Invalid message type")
+ });
- var collection = new List();
- foreach (IMessage? message in messages)
+ // if there's no system message in the history, add one to the beginning
+ if (!history.Any(m => m.Role == "system"))
{
- Message item;
- switch (message)
- {
- case TextMessage tm:
- item = new Message { Role = tm.Role.ToString(), Value = tm.Content };
- break;
- case ImageMessage im:
- string base64Image = await ImageUrlToBase64(im.Url!);
- item = new Message { Role = im.Role.ToString(), Images = [base64Image] };
- break;
- case MultiModalMessage mm:
- var textsGroupedByRole = mm.Content.OfType().GroupBy(tm => tm.Role)
- .ToDictionary(g => g.Key, g => string.Join(Environment.NewLine, g.Select(tm => tm.Content)));
-
- string content = string.Join($"{Environment.NewLine}", textsGroupedByRole
- .Select(g => $"{g.Key}{Environment.NewLine}:{g.Value}"));
-
- IEnumerable> imagesConversionTasks = mm.Content
- .OfType()
- .Select(async im => await ImageUrlToBase64(im.Url!));
-
- string[]? imagesBase64 = await Task.WhenAll(imagesConversionTasks);
- item = new Message { Role = mm.Role.ToString(), Value = content, Images = imagesBase64 };
- break;
- default:
- throw new NotSupportedException();
- }
-
- collection.Add(item);
+ history = new[] { new Message() { Role = "system", Value = _systemMessage } }.Concat(history);
}
- return collection;
+ return history.ToList();
}
- private static HttpRequestMessage BuildRequestMessage(ChatRequest request)
+
+ private static HttpRequestMessage BuildRequest(ChatRequest request)
{
string serialized = JsonSerializer.Serialize(request);
return new HttpRequestMessage(HttpMethod.Post, OllamaConsts.ChatCompletionEndpoint)
@@ -202,15 +182,4 @@ private static HttpRequestMessage BuildRequestMessage(ChatRequest request)
Content = new StringContent(serialized, Encoding.UTF8, OllamaConsts.JsonMediaType)
};
}
- private async Task ImageUrlToBase64(string imageUrl)
- {
- if (string.IsNullOrWhiteSpace(imageUrl))
- {
- throw new ArgumentException("required parameter", nameof(imageUrl));
- }
- byte[] imageBytes = await _httpClient.GetByteArrayAsync(imageUrl);
- return imageBytes != null
- ? Convert.ToBase64String(imageBytes)
- : throw new InvalidOperationException("no image byte array");
- }
}
diff --git a/dotnet/src/Autogen.Ollama/Autogen.Ollama.csproj b/dotnet/src/AutoGen.Ollama/AutoGen.Ollama.csproj
similarity index 86%
rename from dotnet/src/Autogen.Ollama/Autogen.Ollama.csproj
rename to dotnet/src/AutoGen.Ollama/AutoGen.Ollama.csproj
index 9a01f95ca8ea..20924a476b76 100644
--- a/dotnet/src/Autogen.Ollama/Autogen.Ollama.csproj
+++ b/dotnet/src/AutoGen.Ollama/AutoGen.Ollama.csproj
@@ -2,6 +2,7 @@
netstandard2.0
+ AutoGen.Ollama
True
diff --git a/dotnet/src/Autogen.Ollama/DTOs/ChatRequest.cs b/dotnet/src/AutoGen.Ollama/DTOs/ChatRequest.cs
similarity index 94%
rename from dotnet/src/Autogen.Ollama/DTOs/ChatRequest.cs
rename to dotnet/src/AutoGen.Ollama/DTOs/ChatRequest.cs
index a48fb42cfbfb..3b0cf04a1a0d 100644
--- a/dotnet/src/Autogen.Ollama/DTOs/ChatRequest.cs
+++ b/dotnet/src/AutoGen.Ollama/DTOs/ChatRequest.cs
@@ -1,11 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ChatRequest.cs
-using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public class ChatRequest
{
@@ -19,7 +18,7 @@ public class ChatRequest
/// the messages of the chat, this can be used to keep a chat memory
///
[JsonPropertyName("messages")]
- public IList Messages { get; set; } = Array.Empty();
+ public IList Messages { get; set; } = [];
///
/// the format to return a response in. Currently, the only accepted value is json
diff --git a/dotnet/src/Autogen.Ollama/DTOs/ChatResponse.cs b/dotnet/src/AutoGen.Ollama/DTOs/ChatResponse.cs
similarity index 97%
rename from dotnet/src/Autogen.Ollama/DTOs/ChatResponse.cs
rename to dotnet/src/AutoGen.Ollama/DTOs/ChatResponse.cs
index 2de150f7235a..7d8142de785c 100644
--- a/dotnet/src/Autogen.Ollama/DTOs/ChatResponse.cs
+++ b/dotnet/src/AutoGen.Ollama/DTOs/ChatResponse.cs
@@ -3,7 +3,7 @@
using System.Text.Json.Serialization;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public class ChatResponse : ChatResponseUpdate
{
diff --git a/dotnet/src/AutoGen.Ollama/DTOs/ChatResponseUpdate.cs b/dotnet/src/AutoGen.Ollama/DTOs/ChatResponseUpdate.cs
new file mode 100644
index 000000000000..8b4dac194f46
--- /dev/null
+++ b/dotnet/src/AutoGen.Ollama/DTOs/ChatResponseUpdate.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// ChatResponseUpdate.cs
+
+using System.Text.Json.Serialization;
+
+namespace AutoGen.Ollama;
+
+public class ChatResponseUpdate
+{
+ [JsonPropertyName("model")]
+ public string Model { get; set; } = string.Empty;
+
+ [JsonPropertyName("created_at")]
+ public string CreatedAt { get; set; } = string.Empty;
+
+ [JsonPropertyName("message")]
+ public Message? Message { get; set; }
+
+ [JsonPropertyName("done")]
+ public bool Done { get; set; }
+}
diff --git a/dotnet/src/Autogen.Ollama/DTOs/ChatResponseUpdate.cs b/dotnet/src/AutoGen.Ollama/DTOs/Message.cs
similarity index 66%
rename from dotnet/src/Autogen.Ollama/DTOs/ChatResponseUpdate.cs
rename to dotnet/src/AutoGen.Ollama/DTOs/Message.cs
index 181dacfc34b5..2e0d891cc61e 100644
--- a/dotnet/src/Autogen.Ollama/DTOs/ChatResponseUpdate.cs
+++ b/dotnet/src/AutoGen.Ollama/DTOs/Message.cs
@@ -4,25 +4,20 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
-public class ChatResponseUpdate
+public class Message
{
- [JsonPropertyName("model")]
- public string Model { get; set; } = string.Empty;
-
- [JsonPropertyName("created_at")]
- public string CreatedAt { get; set; } = string.Empty;
-
- [JsonPropertyName("message")]
- public Message? Message { get; set; }
+ public Message()
+ {
+ }
- [JsonPropertyName("done")]
- public bool Done { get; set; }
-}
+ public Message(string role, string value)
+ {
+ Role = role;
+ Value = value;
+ }
-public class Message
-{
///
/// the role of the message, either system, user or assistant
///
diff --git a/dotnet/src/Autogen.Ollama/DTOs/ModelReplyOptions.cs b/dotnet/src/AutoGen.Ollama/DTOs/ModelReplyOptions.cs
similarity index 99%
rename from dotnet/src/Autogen.Ollama/DTOs/ModelReplyOptions.cs
rename to dotnet/src/AutoGen.Ollama/DTOs/ModelReplyOptions.cs
index d7854b77b20e..9d54a1bb83b4 100644
--- a/dotnet/src/Autogen.Ollama/DTOs/ModelReplyOptions.cs
+++ b/dotnet/src/AutoGen.Ollama/DTOs/ModelReplyOptions.cs
@@ -3,7 +3,7 @@
using System.Text.Json.Serialization;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
//https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values
public class ModelReplyOptions
diff --git a/dotnet/src/Autogen.Ollama/DTOs/OllamaReplyOptions.cs b/dotnet/src/AutoGen.Ollama/DTOs/OllamaReplyOptions.cs
similarity index 99%
rename from dotnet/src/Autogen.Ollama/DTOs/OllamaReplyOptions.cs
rename to dotnet/src/AutoGen.Ollama/DTOs/OllamaReplyOptions.cs
index 97bf57cb10cb..c7c77d1db256 100644
--- a/dotnet/src/Autogen.Ollama/DTOs/OllamaReplyOptions.cs
+++ b/dotnet/src/AutoGen.Ollama/DTOs/OllamaReplyOptions.cs
@@ -3,12 +3,12 @@
using AutoGen.Core;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public enum FormatType
{
None,
- Json
+ Json,
}
public class OllamaReplyOptions : GenerateReplyOptions
diff --git a/dotnet/src/Autogen.Ollama/Embeddings/ITextEmbeddingService.cs b/dotnet/src/AutoGen.Ollama/Embeddings/ITextEmbeddingService.cs
similarity index 92%
rename from dotnet/src/Autogen.Ollama/Embeddings/ITextEmbeddingService.cs
rename to dotnet/src/AutoGen.Ollama/Embeddings/ITextEmbeddingService.cs
index f1ea1b8406c6..5ce0dc8cc40a 100644
--- a/dotnet/src/Autogen.Ollama/Embeddings/ITextEmbeddingService.cs
+++ b/dotnet/src/AutoGen.Ollama/Embeddings/ITextEmbeddingService.cs
@@ -4,7 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public interface ITextEmbeddingService
{
diff --git a/dotnet/src/Autogen.Ollama/Embeddings/OllamaTextEmbeddingService.cs b/dotnet/src/AutoGen.Ollama/Embeddings/OllamaTextEmbeddingService.cs
similarity index 98%
rename from dotnet/src/Autogen.Ollama/Embeddings/OllamaTextEmbeddingService.cs
rename to dotnet/src/AutoGen.Ollama/Embeddings/OllamaTextEmbeddingService.cs
index db913377a5f5..2e431e7bcb81 100644
--- a/dotnet/src/Autogen.Ollama/Embeddings/OllamaTextEmbeddingService.cs
+++ b/dotnet/src/AutoGen.Ollama/Embeddings/OllamaTextEmbeddingService.cs
@@ -9,7 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public class OllamaTextEmbeddingService : ITextEmbeddingService
{
diff --git a/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsRequest.cs b/dotnet/src/AutoGen.Ollama/Embeddings/TextEmbeddingsRequest.cs
similarity index 97%
rename from dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsRequest.cs
rename to dotnet/src/AutoGen.Ollama/Embeddings/TextEmbeddingsRequest.cs
index 1577dc536433..7f2531c522ad 100644
--- a/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsRequest.cs
+++ b/dotnet/src/AutoGen.Ollama/Embeddings/TextEmbeddingsRequest.cs
@@ -3,7 +3,7 @@
using System.Text.Json.Serialization;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public class TextEmbeddingsRequest
{
diff --git a/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsResponse.cs b/dotnet/src/AutoGen.Ollama/Embeddings/TextEmbeddingsResponse.cs
similarity index 90%
rename from dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsResponse.cs
rename to dotnet/src/AutoGen.Ollama/Embeddings/TextEmbeddingsResponse.cs
index eb46359fdb2d..580059c033b5 100644
--- a/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsResponse.cs
+++ b/dotnet/src/AutoGen.Ollama/Embeddings/TextEmbeddingsResponse.cs
@@ -3,7 +3,7 @@
using System.Text.Json.Serialization;
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public class TextEmbeddingsResponse
{
diff --git a/dotnet/src/AutoGen.Ollama/Extension/OllamaAgentExtension.cs b/dotnet/src/AutoGen.Ollama/Extension/OllamaAgentExtension.cs
new file mode 100644
index 000000000000..4c0df513ef84
--- /dev/null
+++ b/dotnet/src/AutoGen.Ollama/Extension/OllamaAgentExtension.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// OllamaAgentExtension.cs
+
+using AutoGen.Core;
+
+namespace AutoGen.Ollama.Extension;
+
+public static class OllamaAgentExtension
+{
+ ///
+ /// Register an to the
+ ///
+ /// the connector to use. If null, a new instance of will be created.
+ public static MiddlewareStreamingAgent RegisterMessageConnector(
+ this OllamaAgent agent, OllamaMessageConnector? connector = null)
+ {
+ if (connector == null)
+ {
+ connector = new OllamaMessageConnector();
+ }
+
+ return agent.RegisterStreamingMiddleware(connector);
+ }
+
+ ///
+ /// Register an to the where T is
+ ///
+ /// the connector to use. If null, a new instance of will be created.
+ public static MiddlewareStreamingAgent RegisterMessageConnector(
+ this MiddlewareStreamingAgent agent, OllamaMessageConnector? connector = null)
+ {
+ if (connector == null)
+ {
+ connector = new OllamaMessageConnector();
+ }
+
+ return agent.RegisterStreamingMiddleware(connector);
+ }
+}
diff --git a/dotnet/src/AutoGen.Ollama/Middlewares/OllamaMessageConnector.cs b/dotnet/src/AutoGen.Ollama/Middlewares/OllamaMessageConnector.cs
new file mode 100644
index 000000000000..e4e1c4ba47bb
--- /dev/null
+++ b/dotnet/src/AutoGen.Ollama/Middlewares/OllamaMessageConnector.cs
@@ -0,0 +1,183 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// OllamaMessageConnector.cs
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using AutoGen.Core;
+
+namespace AutoGen.Ollama;
+
+public class OllamaMessageConnector : IStreamingMiddleware
+{
+ public string Name => nameof(OllamaMessageConnector);
+
+ public async Task InvokeAsync(MiddlewareContext context, IAgent agent,
+ CancellationToken cancellationToken = default)
+ {
+ var messages = ProcessMessage(context.Messages, agent);
+ IMessage reply = await agent.GenerateReplyAsync(messages, context.Options, cancellationToken);
+
+ return reply switch
+ {
+ IMessage messageEnvelope when messageEnvelope.Content.Message?.Value is string content => new TextMessage(Role.Assistant, content, messageEnvelope.From),
+ IMessage messageEnvelope when messageEnvelope.Content.Message?.Value is null => throw new InvalidOperationException("Message content is null"),
+ _ => reply
+ };
+ }
+
+ public async IAsyncEnumerable InvokeAsync(MiddlewareContext context, IStreamingAgent agent,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var messages = ProcessMessage(context.Messages, agent);
+ var chunks = new List();
+ await foreach (var update in agent.GenerateStreamingReplyAsync(messages, context.Options, cancellationToken))
+ {
+ if (update is IStreamingMessage chatResponseUpdate)
+ {
+ var response = chatResponseUpdate.Content switch
+ {
+ _ when chatResponseUpdate.Content.Message?.Value is string content => new TextMessageUpdate(Role.Assistant, content, chatResponseUpdate.From),
+ _ => null,
+ };
+
+ if (response != null)
+ {
+ chunks.Add(chatResponseUpdate.Content);
+ yield return response;
+ }
+ }
+ else
+ {
+ yield return update;
+ }
+ }
+
+ if (chunks.Count == 0)
+ {
+ yield break;
+ }
+
+ // if the chunks are not empty, aggregate them into a single message
+ var messageContent = string.Join(string.Empty, chunks.Select(c => c.Message?.Value));
+ var message = new Message
+ {
+ Role = "assistant",
+ Value = messageContent,
+ };
+
+ yield return MessageEnvelope.Create(message, agent.Name);
+ }
+
+ private IEnumerable ProcessMessage(IEnumerable messages, IAgent agent)
+ {
+ return messages.SelectMany(m =>
+ {
+ if (m is IMessage messageEnvelope)
+ {
+ return [m];
+ }
+ else
+ {
+ return m switch
+ {
+ TextMessage textMessage => ProcessTextMessage(textMessage, agent),
+ ImageMessage imageMessage => ProcessImageMessage(imageMessage, agent),
+ MultiModalMessage multiModalMessage => ProcessMultiModalMessage(multiModalMessage, agent),
+ _ => [m],
+ };
+ }
+ });
+ }
+
+ private IEnumerable ProcessMultiModalMessage(MultiModalMessage multiModalMessage, IAgent agent)
+ {
+ var messages = new List();
+ foreach (var message in multiModalMessage.Content)
+ {
+ messages.AddRange(message switch
+ {
+ TextMessage textMessage => ProcessTextMessage(textMessage, agent),
+ ImageMessage imageMessage => ProcessImageMessage(imageMessage, agent),
+ _ => throw new InvalidOperationException("Invalid message type"),
+ });
+ }
+
+ return messages;
+ }
+
+ private IEnumerable ProcessImageMessage(ImageMessage imageMessage, IAgent agent)
+ {
+ byte[]? data = imageMessage.Data?.ToArray();
+ if (data is null)
+ {
+ if (imageMessage.Url is null)
+ {
+ throw new InvalidOperationException("Invalid ImageMessage, the data or url must be provided");
+ }
+
+ var uri = new Uri(imageMessage.Url);
+ // download the image from the URL
+ using var client = new HttpClient();
+ var response = client.GetAsync(uri).Result;
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new HttpRequestException($"Failed to download the image from {uri}");
+ }
+
+ data = response.Content.ReadAsByteArrayAsync().Result;
+ }
+
+ var base64Image = Convert.ToBase64String(data);
+ var message = imageMessage.From switch
+ {
+ null when imageMessage.Role == Role.User => new Message { Role = "user", Images = [base64Image] },
+ null => throw new InvalidOperationException("Invalid Role, the role must be user"),
+ _ when imageMessage.From != agent.Name => new Message { Role = "user", Images = [base64Image] },
+ _ => throw new InvalidOperationException("The from field must be null or the agent name"),
+ };
+
+ return [MessageEnvelope.Create(message, agent.Name)];
+ }
+
+ private IEnumerable ProcessTextMessage(TextMessage textMessage, IAgent agent)
+ {
+ if (textMessage.Role == Role.System)
+ {
+ var message = new Message
+ {
+ Role = "system",
+ Value = textMessage.Content
+ };
+
+ return [MessageEnvelope.Create(message, agent.Name)];
+ }
+ else if (textMessage.From == agent.Name)
+ {
+ var message = new Message
+ {
+ Role = "assistant",
+ Value = textMessage.Content
+ };
+
+ return [MessageEnvelope.Create(message, agent.Name)];
+ }
+ else
+ {
+ var message = textMessage.From switch
+ {
+ null when textMessage.Role == Role.User => new Message { Role = "user", Value = textMessage.Content },
+ null when textMessage.Role == Role.Assistant => new Message { Role = "assistant", Value = textMessage.Content },
+ null => throw new InvalidOperationException("Invalid Role"),
+ _ when textMessage.From != agent.Name => new Message { Role = "user", Value = textMessage.Content },
+ _ => throw new InvalidOperationException("The from field must be null or the agent name"),
+ };
+
+ return [MessageEnvelope.Create(message, agent.Name)];
+ }
+ }
+}
diff --git a/dotnet/src/Autogen.Ollama/OllamaConsts.cs b/dotnet/src/AutoGen.Ollama/OllamaConsts.cs
similarity index 93%
rename from dotnet/src/Autogen.Ollama/OllamaConsts.cs
rename to dotnet/src/AutoGen.Ollama/OllamaConsts.cs
index 49e91ebc318a..f305446a9aa4 100644
--- a/dotnet/src/Autogen.Ollama/OllamaConsts.cs
+++ b/dotnet/src/AutoGen.Ollama/OllamaConsts.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// OllamaConsts.cs
-namespace Autogen.Ollama;
+namespace AutoGen.Ollama;
public class OllamaConsts
{
diff --git a/dotnet/src/Autogen.Ollama/Middlewares/OllamaMessageConnector.cs b/dotnet/src/Autogen.Ollama/Middlewares/OllamaMessageConnector.cs
deleted file mode 100644
index 6defedbe02a4..000000000000
--- a/dotnet/src/Autogen.Ollama/Middlewares/OllamaMessageConnector.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// OllamaMessageConnector.cs
-
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-using AutoGen.Core;
-
-namespace Autogen.Ollama;
-
-public class OllamaMessageConnector : IMiddleware, IStreamingMiddleware
-{
- public string Name => nameof(OllamaMessageConnector);
-
- public async Task InvokeAsync(MiddlewareContext context, IAgent agent,
- CancellationToken cancellationToken = default)
- {
- IEnumerable messages = context.Messages;
- IMessage reply = await agent.GenerateReplyAsync(messages, context.Options, cancellationToken);
- switch (reply)
- {
- case IMessage messageEnvelope:
- Message? message = messageEnvelope.Content.Message;
- return new TextMessage(Role.Assistant, message != null ? message.Value : "EMPTY_CONTENT", messageEnvelope.From);
- default:
- throw new NotSupportedException();
- }
- }
-
- public async IAsyncEnumerable InvokeAsync(MiddlewareContext context, IStreamingAgent agent,
- [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- await foreach (IStreamingMessage? update in agent.GenerateStreamingReplyAsync(context.Messages, context.Options, cancellationToken))
- {
- switch (update)
- {
- case IMessage complete:
- {
- string? textContent = complete.Content.Message?.Value;
- yield return new TextMessage(Role.Assistant, textContent!, complete.From);
- break;
- }
- case IMessage updatedMessage:
- {
- string? textContent = updatedMessage.Content.Message?.Value;
- yield return new TextMessageUpdate(Role.Assistant, textContent, updatedMessage.From);
- break;
- }
- default:
- throw new InvalidOperationException("Message type not supported.");
- }
- }
- }
-}
diff --git a/dotnet/test/AutoGen.Ollama.Tests/AutoGen.Ollama.Tests.csproj b/dotnet/test/AutoGen.Ollama.Tests/AutoGen.Ollama.Tests.csproj
new file mode 100644
index 000000000000..27f80716f1c0
--- /dev/null
+++ b/dotnet/test/AutoGen.Ollama.Tests/AutoGen.Ollama.Tests.csproj
@@ -0,0 +1,33 @@
+
+
+
+ $(TestTargetFramework)
+ enable
+ false
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/dotnet/test/AutoGen.Ollama.Tests/OllamaAgentTests.cs b/dotnet/test/AutoGen.Ollama.Tests/OllamaAgentTests.cs
new file mode 100644
index 000000000000..c1fb466f0b09
--- /dev/null
+++ b/dotnet/test/AutoGen.Ollama.Tests/OllamaAgentTests.cs
@@ -0,0 +1,224 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// OllamaAgentTests.cs
+
+using System.Text.Json;
+using AutoGen.Core;
+using AutoGen.Ollama.Extension;
+using AutoGen.Tests;
+using FluentAssertions;
+
+namespace AutoGen.Ollama.Tests;
+
+public class OllamaAgentTests
+{
+ [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
+ public async Task GenerateReplyAsync_ReturnsValidMessage_WhenCalled()
+ {
+ string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
+ ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
+ string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
+ ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
+ OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
+
+ var message = new Message("user", "hey how are you");
+ var messages = new IMessage[] { MessageEnvelope.Create(message, from: modelName) };
+ IMessage result = await ollamaAgent.GenerateReplyAsync(messages);
+
+ result.Should().NotBeNull();
+ result.Should().BeOfType>();
+ result.From.Should().Be(ollamaAgent.Name);
+ }
+
+ [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
+ public async Task GenerateReplyAsync_ReturnsValidJsonMessageContent_WhenCalled()
+ {
+ string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
+ ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
+ string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
+ ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
+ OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
+
+ var message = new Message("user", "What color is the sky at different times of the day? Respond using JSON");
+ var messages = new IMessage[] { MessageEnvelope.Create(message, from: modelName) };
+ IMessage result = await ollamaAgent.GenerateReplyAsync(messages, new OllamaReplyOptions
+ {
+ Format = FormatType.Json
+ });
+
+ result.Should().NotBeNull();
+ result.Should().BeOfType>();
+ result.From.Should().Be(ollamaAgent.Name);
+
+ string jsonContent = ((MessageEnvelope)result).Content.Message!.Value;
+ bool isValidJson = IsValidJsonMessage(jsonContent);
+ isValidJson.Should().BeTrue();
+ }
+
+ [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
+ public async Task GenerateStreamingReplyAsync_ReturnsValidMessages_WhenCalled()
+ {
+ string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
+ ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
+ string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
+ ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
+ OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
+
+ var msg = new Message("user", "hey how are you");
+ var messages = new IMessage[] { MessageEnvelope.Create(msg, from: modelName) };
+ IStreamingMessage? finalReply = default;
+ await foreach (IStreamingMessage message in ollamaAgent.GenerateStreamingReplyAsync(messages))
+ {
+ message.Should().NotBeNull();
+ message.From.Should().Be(ollamaAgent.Name);
+ var streamingMessage = (IMessage)message;
+ if (streamingMessage.Content.Done)
+ {
+ finalReply = message;
+ break;
+ }
+ else
+ {
+ streamingMessage.Content.Message.Should().NotBeNull();
+ streamingMessage.Content.Done.Should().BeFalse();
+ }
+ }
+
+ finalReply.Should().BeOfType>();
+ var update = ((MessageEnvelope)finalReply!).Content;
+ update.Done.Should().BeTrue();
+ update.TotalDuration.Should().BeGreaterThan(0);
+ }
+
+ [ApiKeyFact("OLLAMA_HOST")]
+ public async Task ItReturnValidMessageUsingLLavaAsync()
+ {
+ var host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
+ ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
+ var modelName = "llava:latest";
+ var ollamaAgent = BuildOllamaAgent(host, modelName);
+ var imagePath = Path.Combine("images", "image.png");
+ var base64Image = Convert.ToBase64String(File.ReadAllBytes(imagePath));
+ var message = new Message()
+ {
+ Role = "user",
+ Value = "What's the color of the background in this image",
+ Images = [base64Image],
+ };
+
+ var messages = new IMessage[] { MessageEnvelope.Create(message, from: modelName) };
+ var reply = await ollamaAgent.GenerateReplyAsync(messages);
+
+ reply.Should().BeOfType>();
+ var chatResponse = ((MessageEnvelope)reply).Content;
+ chatResponse.Message.Should().NotBeNull();
+ }
+
+ [ApiKeyFact("OLLAMA_HOST")]
+ public async Task ItCanProcessMultiModalMessageUsingLLavaAsync()
+ {
+ var host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
+ ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
+ var modelName = "llava:latest";
+ var ollamaAgent = BuildOllamaAgent(host, modelName)
+ .RegisterMessageConnector();
+ var image = Path.Combine("images", "image.png");
+ var binaryData = BinaryData.FromBytes(File.ReadAllBytes(image), "image/png");
+ var imageMessage = new ImageMessage(Role.User, binaryData);
+ var textMessage = new TextMessage(Role.User, "What's in this image?");
+ var multiModalMessage = new MultiModalMessage(Role.User, [textMessage, imageMessage]);
+
+ var reply = await ollamaAgent.SendAsync(multiModalMessage);
+ reply.Should().BeOfType();
+ reply.GetRole().Should().Be(Role.Assistant);
+ reply.GetContent().Should().NotBeNullOrEmpty();
+ reply.From.Should().Be(ollamaAgent.Name);
+ }
+
+ [ApiKeyFact("OLLAMA_HOST")]
+ public async Task ItCanProcessImageMessageUsingLLavaAsync()
+ {
+ var host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
+ ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
+ var modelName = "llava:latest";
+ var ollamaAgent = BuildOllamaAgent(host, modelName)
+ .RegisterMessageConnector();
+ var image = Path.Combine("images", "image.png");
+ var binaryData = BinaryData.FromBytes(File.ReadAllBytes(image), "image/png");
+ var imageMessage = new ImageMessage(Role.User, binaryData);
+
+ var reply = await ollamaAgent.SendAsync(imageMessage);
+ reply.Should().BeOfType();
+ reply.GetRole().Should().Be(Role.Assistant);
+ reply.GetContent().Should().NotBeNullOrEmpty();
+ reply.From.Should().Be(ollamaAgent.Name);
+ }
+
+ [ApiKeyFact("OLLAMA_HOST")]
+ public async Task ItReturnValidStreamingMessageUsingLLavaAsync()
+ {
+ var host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
+ ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
+ var modelName = "llava:latest";
+ var ollamaAgent = BuildOllamaAgent(host, modelName);
+ var squareImagePath = Path.Combine("images", "square.png");
+ var base64Image = Convert.ToBase64String(File.ReadAllBytes(squareImagePath));
+ var imageMessage = new Message()
+ {
+ Role = "user",
+ Value = "What's in this image?",
+ Images = [base64Image],
+ };
+
+ var messages = new IMessage[] { MessageEnvelope.Create(imageMessage, from: modelName) };
+
+ IStreamingMessage? finalReply = default;
+ await foreach (IStreamingMessage message in ollamaAgent.GenerateStreamingReplyAsync(messages))
+ {
+ message.Should().NotBeNull();
+ message.From.Should().Be(ollamaAgent.Name);
+ var streamingMessage = (IMessage)message;
+ if (streamingMessage.Content.Done)
+ {
+ finalReply = message;
+ break;
+ }
+ else
+ {
+ streamingMessage.Content.Message.Should().NotBeNull();
+ streamingMessage.Content.Done.Should().BeFalse();
+ }
+ }
+
+ finalReply.Should().BeOfType>();
+ var update = ((MessageEnvelope)finalReply!).Content;
+ update.Done.Should().BeTrue();
+ update.TotalDuration.Should().BeGreaterThan(0);
+ }
+
+ private static bool IsValidJsonMessage(string input)
+ {
+ try
+ {
+ JsonDocument.Parse(input);
+ return true;
+ }
+ catch (JsonException)
+ {
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An unexpected exception occurred: " + ex.Message);
+ return false;
+ }
+ }
+
+ private static OllamaAgent BuildOllamaAgent(string host, string modelName)
+ {
+ var httpClient = new HttpClient
+ {
+ BaseAddress = new Uri(host)
+ };
+ return new OllamaAgent(httpClient, "TestAgent", modelName);
+ }
+}
diff --git a/dotnet/test/AutoGen.Ollama.Tests/OllamaMessageTests.cs b/dotnet/test/AutoGen.Ollama.Tests/OllamaMessageTests.cs
new file mode 100644
index 000000000000..3f37db70275f
--- /dev/null
+++ b/dotnet/test/AutoGen.Ollama.Tests/OllamaMessageTests.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// OllamaMessageTests.cs
+
+using AutoGen.Core;
+using AutoGen.Ollama;
+using AutoGen.Tests;
+using FluentAssertions;
+using Xunit;
+using Message = AutoGen.Ollama.Message;
+
+namespace Autogen.Ollama.Tests;
+
+public class OllamaMessageTests
+{
+ [Fact]
+ public async Task ItProcessUserTextMessageAsync()
+ {
+ var messageConnector = new OllamaMessageConnector();
+ var agent = new EchoAgent("assistant")
+ .RegisterMiddleware(async (msgs, _, innerAgent, ct) =>
+ {
+ msgs.Count().Should().Be(1);
+ var innerMessage = msgs.First();
+ innerMessage.Should().BeOfType>();
+ var message = (IMessage)innerMessage;
+ message.Content.Value.Should().Be("Hello");
+ message.Content.Images.Should().BeNullOrEmpty();
+ message.Content.Role.Should().Be("user");
+ return await innerAgent.GenerateReplyAsync(msgs);
+ })
+ .RegisterMiddleware(messageConnector);
+
+ // when from is null and role is user
+ await agent.SendAsync("Hello");
+
+ // when from is user and role is user
+ var userMessage = new TextMessage(Role.User, "Hello", from: "user");
+ await agent.SendAsync(userMessage);
+
+ // when from is user but role is assistant
+ userMessage = new TextMessage(Role.Assistant, "Hello", from: "user");
+ await agent.SendAsync(userMessage);
+ }
+
+ [Fact]
+ public async Task ItProcessAssistantTextMessageAsync()
+ {
+ var messageConnector = new OllamaMessageConnector();
+ var agent = new EchoAgent("assistant")
+ .RegisterMiddleware(async (msgs, _, innerAgent, ct) =>
+ {
+ msgs.Count().Should().Be(1);
+ var innerMessage = msgs.First();
+ innerMessage.Should().BeOfType>();
+ var message = (IMessage)innerMessage;
+ message.Content.Value.Should().Be("Hello");
+ message.Content.Images.Should().BeNullOrEmpty();
+ message.Content.Role.Should().Be("assistant");
+ return await innerAgent.GenerateReplyAsync(msgs);
+ })
+ .RegisterMiddleware(messageConnector);
+
+ // when from is null and role is assistant
+ var assistantMessage = new TextMessage(Role.Assistant, "Hello");
+ await agent.SendAsync(assistantMessage);
+
+ // when from is assistant and role is assistant
+ assistantMessage = new TextMessage(Role.Assistant, "Hello", from: "assistant");
+ await agent.SendAsync(assistantMessage);
+
+ // when from is assistant but role is user
+ assistantMessage = new TextMessage(Role.User, "Hello", from: "assistant");
+ await agent.SendAsync(assistantMessage);
+ }
+
+ [Fact]
+ public async Task ItProcessSystemTextMessageAsync()
+ {
+ var messageConnector = new OllamaMessageConnector();
+ var agent = new EchoAgent("assistant")
+ .RegisterMiddleware(async (msgs, _, innerAgent, ct) =>
+ {
+ msgs.Count().Should().Be(1);
+ var innerMessage = msgs.First();
+ innerMessage.Should().BeOfType>();
+ var message = (IMessage)innerMessage;
+ message.Content.Value.Should().Be("Hello");
+ message.Content.Images.Should().BeNullOrEmpty();
+ message.Content.Role.Should().Be("system");
+ return await innerAgent.GenerateReplyAsync(msgs);
+ })
+ .RegisterMiddleware(messageConnector);
+
+ // when role is system
+ var systemMessage = new TextMessage(Role.System, "Hello");
+ await agent.SendAsync(systemMessage);
+ }
+
+ [Fact]
+ public async Task ItProcessImageMessageAsync()
+ {
+ var messageConnector = new OllamaMessageConnector();
+ var agent = new EchoAgent("assistant")
+ .RegisterMiddleware(async (msgs, _, innerAgent, ct) =>
+ {
+ msgs.Count().Should().Be(1);
+ var innerMessage = msgs.First();
+ innerMessage.Should().BeOfType>();
+ var message = (IMessage)innerMessage;
+ message.Content.Images!.Count.Should().Be(1);
+ message.Content.Role.Should().Be("user");
+ return await innerAgent.GenerateReplyAsync(msgs);
+ })
+ .RegisterMiddleware(messageConnector);
+
+ var square = Path.Combine("images", "square.png");
+ BinaryData imageBinaryData = BinaryData.FromBytes(File.ReadAllBytes(square), "image/png");
+ var imageMessage = new ImageMessage(Role.User, imageBinaryData);
+ await agent.SendAsync(imageMessage);
+ }
+
+ [Fact]
+ public async Task ItProcessMultiModalMessageAsync()
+ {
+ var messageConnector = new OllamaMessageConnector();
+ var agent = new EchoAgent("assistant")
+ .RegisterMiddleware(async (msgs, _, innerAgent, ct) =>
+ {
+ msgs.Count().Should().Be(2);
+ var textMessage = msgs.First();
+ textMessage.Should().BeOfType>();
+ var message = (IMessage)textMessage;
+ message.Content.Role.Should().Be("user");
+
+ var imageMessage = msgs.Last();
+ imageMessage.Should().BeOfType>();
+ message = (IMessage)imageMessage;
+ message.Content.Role.Should().Be("user");
+ message.Content.Images!.Count.Should().Be(1);
+
+ return await innerAgent.GenerateReplyAsync(msgs);
+ })
+ .RegisterMiddleware(messageConnector);
+
+ var square = Path.Combine("images", "square.png");
+ BinaryData imageBinaryData = BinaryData.FromBytes(File.ReadAllBytes(square), "image/png");
+ var imageMessage = new ImageMessage(Role.User, imageBinaryData);
+ var textMessage = new TextMessage(Role.User, "Hello");
+ var multiModalMessage = new MultiModalMessage(Role.User, [textMessage, imageMessage]);
+
+ await agent.SendAsync(multiModalMessage);
+ }
+}
diff --git a/dotnet/test/Autogen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs b/dotnet/test/AutoGen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs
similarity index 97%
rename from dotnet/test/Autogen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs
rename to dotnet/test/AutoGen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs
index 7f2d94e147db..06522bdd8238 100644
--- a/dotnet/test/Autogen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs
+++ b/dotnet/test/AutoGen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs
@@ -4,7 +4,7 @@
using AutoGen.Tests;
using FluentAssertions;
-namespace Autogen.Ollama.Tests;
+namespace AutoGen.Ollama.Tests;
public class OllamaTextEmbeddingServiceTests
{
diff --git a/dotnet/test/AutoGen.Ollama.Tests/images/image.png b/dotnet/test/AutoGen.Ollama.Tests/images/image.png
new file mode 100644
index 000000000000..ca276f81f5b0
--- /dev/null
+++ b/dotnet/test/AutoGen.Ollama.Tests/images/image.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:300b7c9d6ba0c23a3e52fbd2e268141ddcca0434a9fb9dcf7e58e7e903d36dcf
+size 2126185
diff --git a/dotnet/test/AutoGen.Ollama.Tests/images/square.png b/dotnet/test/AutoGen.Ollama.Tests/images/square.png
new file mode 100644
index 000000000000..afb4f4cd4df8
--- /dev/null
+++ b/dotnet/test/AutoGen.Ollama.Tests/images/square.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8323d0b8eceb752e14c29543b2e28bb2fc648ed9719095c31b7708867a4dc918
+size 491
diff --git a/dotnet/test/Autogen.Ollama.Tests/Autogen.Ollama.Tests.csproj b/dotnet/test/Autogen.Ollama.Tests/Autogen.Ollama.Tests.csproj
deleted file mode 100644
index a10ce496ae7e..000000000000
--- a/dotnet/test/Autogen.Ollama.Tests/Autogen.Ollama.Tests.csproj
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
- false
- true
- True
-
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
-
diff --git a/dotnet/test/Autogen.Ollama.Tests/OllamaAgentTests.cs b/dotnet/test/Autogen.Ollama.Tests/OllamaAgentTests.cs
deleted file mode 100644
index b22432fdad69..000000000000
--- a/dotnet/test/Autogen.Ollama.Tests/OllamaAgentTests.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// OllamaAgentTests.cs
-
-using System.Text.Json;
-using AutoGen.Core;
-using AutoGen.Tests;
-using FluentAssertions;
-
-namespace Autogen.Ollama.Tests;
-
-public class OllamaAgentTests
-{
-
- [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
- public async Task GenerateReplyAsync_ReturnsValidMessage_WhenCalled()
- {
- string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
- ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
- string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
- ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
- OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
-
- var messages = new IMessage[] { new TextMessage(Role.User, "Hello, how are you") };
- IMessage result = await ollamaAgent.GenerateReplyAsync(messages);
-
- result.Should().NotBeNull();
- result.Should().BeOfType>();
- result.From.Should().Be(ollamaAgent.Name);
- }
-
- [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
- public async Task GenerateReplyAsync_ReturnsValidJsonMessageContent_WhenCalled()
- {
- string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
- ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
- string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
- ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
- OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
-
- var messages = new IMessage[] { new TextMessage(Role.User, "Hello, how are you") };
- IMessage result = await ollamaAgent.GenerateReplyAsync(messages, new OllamaReplyOptions
- {
- Format = FormatType.Json
- });
-
- result.Should().NotBeNull();
- result.Should().BeOfType>();
- result.From.Should().Be(ollamaAgent.Name);
-
- string jsonContent = ((MessageEnvelope)result).Content.Message!.Value;
- bool isValidJson = IsValidJsonMessage(jsonContent);
- isValidJson.Should().BeTrue();
- }
-
- [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")]
- public async Task GenerateStreamingReplyAsync_ReturnsValidMessages_WhenCalled()
- {
- string host = Environment.GetEnvironmentVariable("OLLAMA_HOST")
- ?? throw new InvalidOperationException("OLLAMA_HOST is not set.");
- string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME")
- ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
- OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName);
-
- var messages = new IMessage[] { new TextMessage(Role.User, "Hello how are you") };
- IStreamingMessage? finalReply = default;
- await foreach (IStreamingMessage message in ollamaAgent.GenerateStreamingReplyAsync(messages))
- {
- message.Should().NotBeNull();
- message.From.Should().Be(ollamaAgent.Name);
- finalReply = message;
- }
-
- finalReply.Should().BeOfType>();
- }
-
- private static bool IsValidJsonMessage(string input)
- {
- try
- {
- JsonDocument.Parse(input);
- return true;
- }
- catch (JsonException)
- {
- return false;
- }
- catch (Exception ex)
- {
- Console.WriteLine("An unexpected exception occurred: " + ex.Message);
- return false;
- }
- }
-
- private static OllamaAgent BuildOllamaAgent(string host, string modelName)
- {
- var httpClient = new HttpClient
- {
- BaseAddress = new Uri(host)
- };
- return new OllamaAgent(httpClient, "TestAgent", modelName);
- }
-}