Skip to content
Merged
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
6 changes: 5 additions & 1 deletion src/OllamaSharp/MicrosoftAi/AbstractionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ internal static class AbstractionMapper
if (stream is null)
return null;

var responseId = stream.CreatedAtString ?? Guid.NewGuid().ToString("N");
var chatMessage = ToChatMessage(stream.Message);
chatMessage.CreatedAt = stream.CreatedAt;
chatMessage.MessageId = responseId;

return new ChatResponse(chatMessage)
{
Expand All @@ -41,7 +43,7 @@ internal static class AbstractionMapper
CreatedAt = stream.CreatedAt,
ModelId = usedModel ?? stream.Model,
RawRepresentation = stream,
ResponseId = stream.CreatedAtString ?? Guid.NewGuid().ToString("N"),
ResponseId = responseId,
Usage = ParseOllamaChatResponseUsage(stream)
};
}
Expand Down Expand Up @@ -348,6 +350,7 @@ public static ChatResponseUpdate ToChatResponseUpdate(ChatResponseStream? respon
AdditionalProperties = ParseOllamaChatResponseProps(done),
CreatedAt = done.CreatedAt,
FinishReason = done.DoneReason is null ? null : new ChatFinishReason(done.DoneReason),
MessageId = responseId,
RawRepresentation = response,
ResponseId = responseId,
ModelId = done.Model
Expand All @@ -359,6 +362,7 @@ public static ChatResponseUpdate ToChatResponseUpdate(ChatResponseStream? respon
// no need to set "Contents" as we set the text
CreatedAt = response?.CreatedAt,
FinishReason = response?.Done == true ? ChatFinishReason.Stop : null,
MessageId = responseId,
RawRepresentation = response,
ResponseId = responseId,
ModelId = response?.Model
Expand Down
65 changes: 64 additions & 1 deletion test/AbstractionMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,7 @@ public void Maps_Known_Properties()
response.CreatedAt.ShouldBe(new DateTimeOffset(2023, 08, 04, 08, 52, 19, 385, 406, TimeSpan.FromHours(-7)));
response.FinishReason.ShouldBe(ChatFinishReason.Stop);
response.Messages[0].AuthorName.ShouldBeNull();
response.Messages[0].MessageId.ShouldBe(ollamaCreatedStamp);
response.Messages[0].RawRepresentation.ShouldBe(stream.Message);
response.Messages[0].Role.ShouldBe(Microsoft.Extensions.AI.ChatRole.Assistant);
response.Messages[0].Text.ShouldBe("Hi.");
Expand Down Expand Up @@ -974,6 +975,7 @@ public void Maps_Known_Properties()
((TextContent)streamingChatCompletion.Contents[0]).Text.ShouldBe("Hi.");
streamingChatCompletion.CreatedAt.ShouldBe(new DateTimeOffset(2023, 08, 04, 08, 52, 19, 385, 406, TimeSpan.FromHours(-7)));
streamingChatCompletion.FinishReason.ShouldBe(ChatFinishReason.Stop);
streamingChatCompletion.MessageId.ShouldBe("12345");
streamingChatCompletion.RawRepresentation.ShouldBe(stream);
streamingChatCompletion.ResponseId.ShouldBe("12345");
streamingChatCompletion.Role.ShouldBe(Microsoft.Extensions.AI.ChatRole.Assistant);
Expand Down Expand Up @@ -1004,6 +1006,7 @@ public void Maps_Thinking_Tokens()
((TextReasoningContent)streamingChatCompletion.Contents[0]).Text.ShouldBe("Beer.");
streamingChatCompletion.CreatedAt.ShouldBe(new DateTimeOffset(2023, 08, 04, 08, 52, 19, 385, 406, TimeSpan.FromHours(-7)));
streamingChatCompletion.FinishReason.ShouldBe(ChatFinishReason.Stop);
streamingChatCompletion.MessageId.ShouldBe("12345");
streamingChatCompletion.RawRepresentation.ShouldBe(stream);
streamingChatCompletion.ResponseId.ShouldBe("12345");
streamingChatCompletion.Role.ShouldBe(Microsoft.Extensions.AI.ChatRole.Assistant);
Expand Down Expand Up @@ -1068,6 +1071,58 @@ public void Maps_ToolCalls()
chatMessage.Text.ShouldBe("It seems the sun will be out all day.");
}

/// <summary>
/// Verifies that MEAI keeps updates from separate provider calls in separate messages.
/// </summary>
[Test]
public void Separates_Updates_With_Different_MessageIds()
{
var updates = new[]
{
AbstractionMapper.ToChatResponseUpdate(new ChatResponseStream
{
Message = new Message { Role = OllamaSharp.Models.Chat.ChatRole.Assistant, Content = "First" }
}, "response-1"),
AbstractionMapper.ToChatResponseUpdate(new ChatResponseStream
{
Message = new Message { Role = OllamaSharp.Models.Chat.ChatRole.Assistant, Content = "Second" }
}, "response-2")
};

var response = updates.ToChatResponse();

response.Messages.Count.ShouldBe(2);
response.Messages[0].MessageId.ShouldBe("response-1");
response.Messages[0].Text.ShouldBe("First");
response.Messages[1].MessageId.ShouldBe("response-2");
response.Messages[1].Text.ShouldBe("Second");
}

/// <summary>
/// Verifies that MEAI combines chunks that belong to the same logical message.
/// </summary>
[Test]
public void Combines_Updates_With_The_Same_MessageId()
{
var updates = new[]
{
AbstractionMapper.ToChatResponseUpdate(new ChatResponseStream
{
Message = new Message { Role = OllamaSharp.Models.Chat.ChatRole.Assistant, Content = "Hel" }
}, "response-1"),
AbstractionMapper.ToChatResponseUpdate(new ChatResponseStream
{
Message = new Message { Role = OllamaSharp.Models.Chat.ChatRole.Assistant, Content = "lo" }
}, "response-1")
};

var response = updates.ToChatResponse();

response.Messages.Count.ShouldBe(1);
response.Messages[0].MessageId.ShouldBe("response-1");
response.Messages[0].Text.ShouldBe("Hello");
}

/// <summary>
/// Verifies that performance metrics from ChatDoneResponseStream (LoadDuration, TotalDuration, etc.)
/// are correctly mapped to the AdditionalProperties dictionary.
Expand Down Expand Up @@ -1107,6 +1162,7 @@ public void Maps_Performance_Metrics_To_AdditionalProperties()
// Assert - AdditionalProperties contains performance metrics
const double NANOSECONDS_PER_MILLISECOND = 1_000_000;

streamingChatCompletion.MessageId.ShouldBe(responseId);
streamingChatCompletion.AdditionalProperties.ShouldNotBeNull();
streamingChatCompletion.AdditionalProperties.ShouldContainKey(Application.LoadDuration);
streamingChatCompletion.AdditionalProperties[Application.LoadDuration].ShouldBe(TimeSpan.FromMilliseconds(123_456_789 / NANOSECONDS_PER_MILLISECOND));
Expand All @@ -1127,6 +1183,13 @@ public void Maps_Performance_Metrics_To_AdditionalProperties()
usageContent.Details.InputTokenCount.ShouldBe(42);
usageContent.Details.OutputTokenCount.ShouldBe(15);
usageContent.Details.TotalTokenCount.ShouldBe(42 + 15);

var combinedResponse = new[] { streamingChatCompletion }.ToChatResponse();
combinedResponse.Messages.Single().AdditionalProperties.ShouldNotBeNull();
combinedResponse.Messages.Single().AdditionalProperties.ShouldContainKey(Application.TotalDuration);
combinedResponse.Usage.ShouldNotBeNull();
combinedResponse.Usage.InputTokenCount.ShouldBe(42);
combinedResponse.Usage.OutputTokenCount.ShouldBe(15);
}
}
}
Expand Down Expand Up @@ -1273,4 +1336,4 @@ public void Maps_Response()
}

#pragma warning restore CS8602 // Dereference of a possibly null reference.
#pragma warning restore CS8604 // Possible null reference argument.
#pragma warning restore CS8604 // Possible null reference argument.
Loading