diff --git a/src/MicrosoftAi/AbstractionMapper.cs b/src/MicrosoftAi/AbstractionMapper.cs index 8e1a0d4..6e125e7 100644 --- a/src/MicrosoftAi/AbstractionMapper.cs +++ b/src/MicrosoftAi/AbstractionMapper.cs @@ -70,37 +70,38 @@ public static ChatRequest ToOllamaSharpChatRequest(IList chatMessag if (options?.AdditionalProperties?.Any() ?? false) { - TryAddOllamaOption(options, OllamaOption.F16kv, v => request.Options.F16kv = v); - TryAddOllamaOption(options, OllamaOption.FrequencyPenalty, v => request.Options.FrequencyPenalty = v); - TryAddOllamaOption(options, OllamaOption.LogitsAll, v => request.Options.LogitsAll = v); - TryAddOllamaOption(options, OllamaOption.LowVram, v => request.Options.LowVram = v); - TryAddOllamaOption(options, OllamaOption.MainGpu, v => request.Options.MainGpu = v); - TryAddOllamaOption(options, OllamaOption.MinP, v => request.Options.MinP = v); - TryAddOllamaOption(options, OllamaOption.MiroStat, v => request.Options.MiroStat = v); - TryAddOllamaOption(options, OllamaOption.MiroStatEta, v => request.Options.MiroStatEta = v); - TryAddOllamaOption(options, OllamaOption.MiroStatTau, v => request.Options.MiroStatTau = v); - TryAddOllamaOption(options, OllamaOption.Numa, v => request.Options.Numa = v); - TryAddOllamaOption(options, OllamaOption.NumBatch, v => request.Options.NumBatch = v); - TryAddOllamaOption(options, OllamaOption.NumCtx, v => request.Options.NumCtx = v); - TryAddOllamaOption(options, OllamaOption.NumGpu, v => request.Options.NumGpu = v); - TryAddOllamaOption(options, OllamaOption.NumGqa, v => request.Options.NumGqa = v); - TryAddOllamaOption(options, OllamaOption.NumKeep, v => request.Options.NumKeep = v); - TryAddOllamaOption(options, OllamaOption.NumPredict, v => request.Options.NumPredict = v); - TryAddOllamaOption(options, OllamaOption.NumThread, v => request.Options.NumThread = v); - TryAddOllamaOption(options, OllamaOption.PenalizeNewline, v => request.Options.PenalizeNewline = v); - TryAddOllamaOption(options, OllamaOption.PresencePenalty, v => request.Options.PresencePenalty = v); - TryAddOllamaOption(options, OllamaOption.RepeatLastN, v => request.Options.RepeatLastN = v); - TryAddOllamaOption(options, OllamaOption.RepeatPenalty, v => request.Options.RepeatPenalty = v); - TryAddOllamaOption(options, OllamaOption.Seed, v => request.Options.Seed = v); - TryAddOllamaOption(options, OllamaOption.Stop, v => request.Options.Stop = v); - TryAddOllamaOption(options, OllamaOption.Temperature, v => request.Options.Temperature = v); - TryAddOllamaOption(options, OllamaOption.TfsZ, v => request.Options.TfsZ = v); - TryAddOllamaOption(options, OllamaOption.TopK, v => request.Options.TopK = v); - TryAddOllamaOption(options, OllamaOption.TopP, v => request.Options.TopP = v); - TryAddOllamaOption(options, OllamaOption.TypicalP, v => request.Options.TypicalP = v); - TryAddOllamaOption(options, OllamaOption.UseMlock, v => request.Options.UseMlock = v); - TryAddOllamaOption(options, OllamaOption.UseMmap, v => request.Options.UseMmap = v); - TryAddOllamaOption(options, OllamaOption.VocabOnly, v => request.Options.VocabOnly = v); + TryAddOllamaOption(options, OllamaOption.F16kv, v => request.Options.F16kv = (bool?)v); + TryAddOllamaOption(options, OllamaOption.FrequencyPenalty, v => request.Options.FrequencyPenalty = (float?)v); + TryAddOllamaOption(options, OllamaOption.LogitsAll, v => request.Options.LogitsAll = (bool?)v); + TryAddOllamaOption(options, OllamaOption.LowVram, v => request.Options.LowVram = (bool?)v); + TryAddOllamaOption(options, OllamaOption.MainGpu, v => request.Options.MainGpu = (int?)v); + TryAddOllamaOption(options, OllamaOption.MinP, v => request.Options.MinP = (float?)v); + TryAddOllamaOption(options, OllamaOption.MiroStat, v => request.Options.MiroStat = (int?)v); + TryAddOllamaOption(options, OllamaOption.MiroStatEta, v => request.Options.MiroStatEta = (float?)v); + TryAddOllamaOption(options, OllamaOption.MiroStatTau, v => request.Options.MiroStatTau = (float?)v); + TryAddOllamaOption(options, OllamaOption.Numa, v => request.Options.Numa = (bool?)v); + TryAddOllamaOption(options, OllamaOption.NumBatch, v => request.Options.NumBatch = (int?)v); + TryAddOllamaOption(options, OllamaOption.NumCtx, v => request.Options.NumCtx = (int?)v); + TryAddOllamaOption(options, OllamaOption.NumGpu, v => request.Options.NumGpu = (int?)v); + TryAddOllamaOption(options, OllamaOption.NumGqa, v => request.Options.NumGqa = (int?)v); + TryAddOllamaOption(options, OllamaOption.NumKeep, v => request.Options.NumKeep = (int?)v); + TryAddOllamaOption(options, OllamaOption.NumPredict, v => request.Options.NumPredict = (int?)v); + TryAddOllamaOption(options, OllamaOption.NumThread, v => request.Options.NumThread = (int?)v); + TryAddOllamaOption(options, OllamaOption.PenalizeNewline, v => request.Options.PenalizeNewline = (bool?)v); + TryAddOllamaOption(options, OllamaOption.PresencePenalty, v => request.Options.PresencePenalty = (float?)v); + TryAddOllamaOption(options, OllamaOption.RepeatLastN, v => request.Options.RepeatLastN = (int?)v); + TryAddOllamaOption(options, OllamaOption.RepeatPenalty, v => request.Options.RepeatPenalty = (float?)v); + TryAddOllamaOption(options, OllamaOption.Seed, v => request.Options.Seed = (int?)v); + TryAddOllamaOption(options, OllamaOption.Stop, + v => request.Options.Stop = (v as IEnumerable)?.ToArray()); + TryAddOllamaOption(options, OllamaOption.Temperature, v => request.Options.Temperature = (float?)v); + TryAddOllamaOption(options, OllamaOption.TfsZ, v => request.Options.TfsZ = (float?)v); + TryAddOllamaOption(options, OllamaOption.TopK, v => request.Options.TopK = (int?)v); + TryAddOllamaOption(options, OllamaOption.TopP, v => request.Options.TopP = (float?)v); + TryAddOllamaOption(options, OllamaOption.TypicalP, v => request.Options.TypicalP = (float?)v); + TryAddOllamaOption(options, OllamaOption.UseMlock, v => request.Options.UseMlock = (bool?)v); + TryAddOllamaOption(options, OllamaOption.UseMmap, v => request.Options.UseMmap = (bool?)v); + TryAddOllamaOption(options, OllamaOption.VocabOnly, v => request.Options.VocabOnly = (bool?)v); } return request; @@ -113,10 +114,10 @@ public static ChatRequest ToOllamaSharpChatRequest(IList chatMessag /// The chat options from the Microsoft abstraction /// The Ollama setting to add /// The setter to set the Ollama option if available in the chat options - private static void TryAddOllamaOption(ChatOptions microsoftChatOptions, OllamaOption option, Action optionSetter) + private static void TryAddOllamaOption(ChatOptions microsoftChatOptions, OllamaOption option, Action optionSetter) { if ((microsoftChatOptions?.AdditionalProperties?.TryGetValue(option.Name, out var value) ?? false) && value is not null) - optionSetter((T)value); + optionSetter(value); } /// @@ -200,13 +201,17 @@ private static IEnumerable ToOllamaSharpMessages(IList cha var images = cm.Contents.OfType().Select(ToOllamaImage).Where(s => !string.IsNullOrEmpty(s)).ToArray(); var toolCalls = cm.Contents.OfType().Select(ToOllamaSharpToolCall).ToArray(); - yield return new Message + // Only generates a message if there is text/content, images or tool calls + if (cm.Text is not null || images.Length > 0 || toolCalls.Length > 0) { - Content = cm.Text, - Images = images.Length > 0 ? images : null, - Role = ToOllamaSharpRole(cm.Role), - ToolCalls = toolCalls.Length > 0 ? toolCalls : null, - }; + yield return new Message + { + Content = cm.Text, + Images = images.Length > 0 ? images : null, + Role = ToOllamaSharpRole(cm.Role), + ToolCalls = toolCalls.Length > 0 ? toolCalls : null, + }; + } // If the message contains a function result, add it as a separate tool message foreach (var frc in cm.Contents.OfType()) diff --git a/test/AbstractionMapperTests.cs b/test/AbstractionMapperTests.cs index e838637..df876cd 100644 --- a/test/AbstractionMapperTests.cs +++ b/test/AbstractionMapperTests.cs @@ -249,6 +249,53 @@ public void Maps_Messages_With_Tools() tool.Type.Should().Be("function"); } + [TestCaseSource(nameof(StopSequencesTestData))] + public void Maps_Messages_With_IEnumerable_StopSequences(object? enumerable) + { + var chatMessages = new List + { + new() + { + AdditionalProperties = [], + AuthorName = "a1", + RawRepresentation = null, + Role = Microsoft.Extensions.AI.ChatRole.User, + Text = "What's the weather in Honululu?" + } + }; + + var options = new ChatOptions() + { + AdditionalProperties = new AdditionalPropertiesDictionary() { ["stop"] = enumerable } + }; + + var chatRequest = AbstractionMapper.ToOllamaSharpChatRequest(chatMessages, options, stream: true, JsonSerializerOptions.Default); + + var stopSequences = chatRequest.Options.Stop; + var typedEnumerable = (IEnumerable?)enumerable; + + if (typedEnumerable == null) + { + stopSequences.Should().BeNull(); + return; + } + stopSequences.Should().HaveCount(typedEnumerable?.Count() ?? 0); + } + + public static IEnumerable StopSequencesTestData + { + get + { + yield return new TestCaseData((object?)(JsonSerializer.Deserialize("[\"stop1\", \"stop2\"]")).EnumerateArray().Select(e => e.GetString())); + yield return new TestCaseData((object?)(IEnumerable?)null); + yield return new TestCaseData((object?)new List { "stop1", "stop2", "stop3", "stop4" }); + yield return new TestCaseData((object?)new string[] { "stop1", "stop2", "stop3" }); + yield return new TestCaseData((object?)new HashSet { "stop1", "stop2", }); + yield return new TestCaseData((object?)new Stack(new[] { "stop1" })); + yield return new TestCaseData((object?)new Queue(new[] { "stop1" })); + } + } + [Test] public void Maps_Messages_With_ToolResponse() { @@ -316,6 +363,47 @@ public void Maps_Messages_With_MultipleToolResponse() user.Role.Should().Be(OllamaSharp.Models.Chat.ChatRole.User); } + [Test] + public void Maps_Messages_WithoutContent_MultipleToolResponse() + { + var aiChatMessages = new List + { + new() + { + AdditionalProperties = [], + AuthorName = "a1", + RawRepresentation = null, + Role = Microsoft.Extensions.AI.ChatRole.User, + Contents = [ + new FunctionResultContent( + callId: "123", + name: "Function1", + result: new { Temperature = 40 }), + + new FunctionResultContent( + callId: "456", + name: "Function2", + result: new { Summary = "This is a tool result test" } + ), + ] + } + }; + + var chatRequest = AbstractionMapper.ToOllamaSharpChatRequest(aiChatMessages, new(), stream: true, JsonSerializerOptions.Default); + var chatMessages = chatRequest.Messages?.ToList(); + + chatMessages.Should().HaveCount(2); + + var tool1 = chatMessages[0]; + var tool2 = chatMessages[1]; + tool1.Content.Should().Contain("\"Temperature\":40"); + tool1.Content.Should().Contain("\"CallId\":\"123\""); + tool1.Role.Should().Be(OllamaSharp.Models.Chat.ChatRole.Tool); + tool2.Content.Should().Contain("\"Summary\":\"This is a tool result test\""); + tool2.Content.Should().Contain("\"CallId\":\"456\""); + tool2.Role.Should().Be(OllamaSharp.Models.Chat.ChatRole.Tool); + } + [Test] public void Maps_Options() {