Skip to content

Commit

Permalink
Merge pull request #188 from stephentoub/updatemeai
Browse files Browse the repository at this point in the history
Update Microsoft.Extensions.AI to 9.3.0-preview.1.25114.11
  • Loading branch information
awaescher authored Feb 17, 2025
2 parents 9bf2377 + 1f4fdc7 commit 39d42d6
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 208 deletions.
50 changes: 20 additions & 30 deletions src/MicrosoftAi/AbstractionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ internal static class AbstractionMapper
/// </summary>
/// <param name="stream">The response stream with completion data.</param>
/// <param name="usedModel">The used model. This has to be a separate argument because there might be fallbacks from the calling method.</param>
/// <returns>A <see cref="ChatCompletion"/> object containing the mapped data.</returns>
public static ChatCompletion? ToChatCompletion(ChatDoneResponseStream? stream, string? usedModel)
/// <returns>A <see cref="ChatResponse"/> object containing the mapped data.</returns>
public static ChatResponse? ToChatResponse(ChatDoneResponseStream? stream, string? usedModel)
{
if (stream is null)
return null;

var chatMessage = ToChatMessage(stream.Message);

return new ChatCompletion(chatMessage)
return new ChatResponse(chatMessage)
{
FinishReason = ToFinishReason(stream.DoneReason),
AdditionalProperties = ParseOllamaChatResponseProps(stream),
Choices = [chatMessage],
CompletionId = stream.CreatedAtString,
CreatedAt = stream.CreatedAt,
ModelId = usedModel ?? stream.Model,
RawRepresentation = stream,
ResponseId = stream.CreatedAtString,
Usage = ParseOllamaChatResponseUsage(stream)
};
}
Expand Down Expand Up @@ -80,7 +80,7 @@ public static ChatRequest ToOllamaSharpChatRequest(IList<ChatMessage> chatMessag
var hasAdditionalProperties = options?.AdditionalProperties?.Any() ?? false;
if (!hasAdditionalProperties)
return request;

TryAddOllamaOption<bool?>(options, OllamaOption.F16kv, v => request.Options.F16kv = (bool?)v);
TryAddOllamaOption<float?>(options, OllamaOption.FrequencyPenalty, v => request.Options.FrequencyPenalty = Convert.ToSingle(v));
TryAddOllamaOption<bool?>(options, OllamaOption.LogitsAll, v => request.Options.LogitsAll = (bool?)v);
Expand Down Expand Up @@ -153,35 +153,25 @@ private static void TryAddOllamaOption<T>(ChatOptions? microsoftChatOptions, Oll
private static Tool? ToOllamaSharpTool(AITool tool)
{
if (tool is AIFunction f)
return ToOllamaSharpTool(f.Metadata);
return ToOllamaSharpTool(f);

return null;
}

/// <summary>
/// Converts <see cref="AIFunctionMetadata"/> to a <see cref="Tool"/>.
/// Converts an <see cref="AIFunction"/> to a <see cref="Tool"/>.
/// </summary>
/// <param name="functionMetadata">The function metadata to convert.</param>
/// <param name="function">The function to convert.</param>
/// <returns>A <see cref="Tool"/> object containing the converted data.</returns>
private static Tool ToOllamaSharpTool(AIFunctionMetadata functionMetadata)
private static Tool ToOllamaSharpTool(AIFunction function)
{
return new Tool
{
Function = new Function
{
Description = functionMetadata.Description,
Name = functionMetadata.Name,
Parameters = new Parameters
{
Properties = functionMetadata.Parameters.ToDictionary(p => p.Name, p => new Property
{
Description = p.Description,
Enum = GetPossibleValues(p.Schema as JsonObject),
Type = ToFunctionTypeString(p.Schema as JsonObject)
}),
Required = functionMetadata.Parameters.Where(p => p.IsRequired).Select(p => p.Name),
Type = Application.Object
}
Description = function.Description,
Name = function.Name,
Parameters = JsonSerializer.Deserialize<Parameters>(function.JsonSchema),
},
Type = Application.Function
};
Expand Down Expand Up @@ -217,7 +207,7 @@ private static IEnumerable<Message> ToOllamaSharpMessages(IList<ChatMessage> cha
{
foreach (var cm in chatMessages)
{
var images = cm.Contents.OfType<ImageContent>().Select(ToOllamaImage).Where(s => !string.IsNullOrEmpty(s)).ToArray();
var images = cm.Contents.OfType<DataContent>().Where(dc => dc.MediaType is null || dc.MediaTypeStartsWith("image/")).Select(ToOllamaImage).Where(s => !string.IsNullOrEmpty(s)).ToArray();
var toolCalls = cm.Contents.OfType<FunctionCallContent>().Select(ToOllamaSharpToolCall).ToArray();

// Only generates a message if there is text/content, images or tool calls
Expand Down Expand Up @@ -255,12 +245,12 @@ private static IEnumerable<Message> ToOllamaSharpMessages(IList<ChatMessage> cha
/// </summary>
/// <param name="content">The data content to convert.</param>
/// <returns>A string containing the base64 image data.</returns>
private static string ToOllamaImage(ImageContent? content)
private static string ToOllamaImage(DataContent? content)
{
if (content is null)
return string.Empty;

if (content.ContainsData && content.Data.HasValue)
if (content.Data.HasValue)
return Convert.ToBase64String(content.Data.Value.ToArray());

throw new NotSupportedException("Images have to be provided as content (byte-Array or base64-string) for Ollama to be used. Other image sources like links are not supported.");
Expand Down Expand Up @@ -321,20 +311,20 @@ private static Microsoft.Extensions.AI.ChatRole ToAbstractionRole(ChatRole? role
}

/// <summary>
/// Converts a <see cref="ChatResponseStream"/> to a <see cref="StreamingChatCompletionUpdate"/>.
/// Converts a <see cref="ChatResponseStream"/> to a <see cref="ChatResponseUpdate"/>.
/// </summary>
/// <param name="response">The response stream to convert.</param>
/// <returns>A <see cref="StreamingChatCompletionUpdate"/> object containing the latest chat completion chunk.</returns>
public static StreamingChatCompletionUpdate ToStreamingChatCompletionUpdate(ChatResponseStream? response)
/// <returns>A <see cref="ChatResponseUpdate"/> object containing the latest chat completion chunk.</returns>
public static ChatResponseUpdate ToStreamingChatCompletionUpdate(ChatResponseStream? response)
{
return new StreamingChatCompletionUpdate
return new ChatResponseUpdate
{
// no need to set "Contents" as we set the text
CompletionId = response?.CreatedAtString,
ChoiceIndex = 0, // should be left at 0 as Ollama does not support this
CreatedAt = response?.CreatedAt,
FinishReason = response?.Done == true ? ChatFinishReason.Stop : null,
RawRepresentation = response,
ResponseId = response?.CreatedAtString,
// TODO: Check if "Message" can ever actually be null. If not, remove the null-coalescing operator
Text = response?.Message?.Content ?? string.Empty,
Role = ToAbstractionRole(response?.Message?.Role),
Expand Down
2 changes: 1 addition & 1 deletion src/MicrosoftAi/IAsyncEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public static partial class IAsyncEnumerableExtensions
/// <param name="stream">The <see cref="IAsyncEnumerable{StreamingChatCompletionUpdate}"/> to stream.</param>
/// <param name="itemCallback">An optional callback to additionally process every single item from the IAsyncEnumerable.</param>
/// <returns>A single <see cref="StreamingChatCompletionUpdate"/> built up from every single IAsyncEnumerable item.</returns>
public static Task<StreamingChatCompletionUpdate?> StreamToEndAsync(this IAsyncEnumerable<StreamingChatCompletionUpdate?> stream, Action<StreamingChatCompletionUpdate?>? itemCallback = null)
public static Task<ChatResponseUpdate?> StreamToEndAsync(this IAsyncEnumerable<ChatResponseUpdate?> stream, Action<ChatResponseUpdate?>? itemCallback = null)
=> stream.StreamToEndAsync(new MicrosoftAi.StreamingChatCompletionUpdateAppender(), itemCallback);
}
10 changes: 5 additions & 5 deletions src/MicrosoftAi/StreamingChatCompletionUpdateAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ namespace OllamaSharp.MicrosoftAi;

/// <summary>
/// Appender to stream <see cref="IAsyncEnumerable{StreamingChatCompletionUpdate}" />
/// to build up one consolidated <see cref="StreamingChatCompletionUpdate"/> object
/// to build up one consolidated <see cref="ChatResponseUpdate"/> object
/// </summary>
internal class StreamingChatCompletionUpdateAppender : IAppender<StreamingChatCompletionUpdate?, StreamingChatCompletionUpdate?>
internal class StreamingChatCompletionUpdateAppender : IAppender<ChatResponseUpdate?, ChatResponseUpdate?>
{
private readonly StreamingChatCompletionUpdateBuilder _messageBuilder = new();
private readonly ChatResponseUpdateBuilder _messageBuilder = new();

/// <summary>
/// Appends a given <see cref="StreamingChatCompletionUpdate"/> item to build a single return object
/// </summary>
/// <param name="item">The item to append</param>
public void Append(StreamingChatCompletionUpdate? item) => _messageBuilder.Append(item);
public void Append(ChatResponseUpdate? item) => _messageBuilder.Append(item);

/// <summary>
/// Builds up one final, single <see cref="StreamingChatCompletionUpdate"/> object from the previously streamed items
/// </summary>
/// <returns>The completed, consolidated <see cref="StreamingChatCompletionUpdate"/> object</returns>
public StreamingChatCompletionUpdate? Complete() => _messageBuilder.Complete();
public ChatResponseUpdate? Complete() => _messageBuilder.Complete();
}
12 changes: 6 additions & 6 deletions src/MicrosoftAi/StreamingChatCompletionUpdateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
namespace OllamaSharp.MicrosoftAi;

/// <summary>
/// A builder that can append <see cref="StreamingChatCompletionUpdate"/> to one single completion update
/// A builder that can append <see cref="ChatResponseUpdate"/> to one single completion update
/// </summary>
internal class StreamingChatCompletionUpdateBuilder
internal class ChatResponseUpdateBuilder
{
private readonly StringBuilder _contentBuilder = new();
private StreamingChatCompletionUpdate? _first;
private ChatResponseUpdate? _first;

/// <summary>
/// Appends a completion update to build one single completion update item
/// </summary>
/// <param name="update">The completion update to append to the final completion update</param>
public void Append(StreamingChatCompletionUpdate? update)
public void Append(ChatResponseUpdate? update)
{
if (update is null)
return;
Expand All @@ -27,9 +27,9 @@ public void Append(StreamingChatCompletionUpdate? update)
_first.AdditionalProperties = update.AdditionalProperties;
_first.AuthorName = update.AuthorName;
_first.ChoiceIndex = update.ChoiceIndex;
_first.CompletionId = update.CompletionId;
_first.CreatedAt = update.CreatedAt;
_first.FinishReason = update.FinishReason;
_first.ResponseId = update.ResponseId;
_first.Role = update.Role;

//_first.Contents and .Text will be set in Complete() with values collected from each update
Expand All @@ -45,7 +45,7 @@ public void Append(StreamingChatCompletionUpdate? update)
/// updates that were appended before
/// </summary>
/// <returns>The final consolidated <see cref="StreamingChatCompletionUpdate"/> object</returns>
public StreamingChatCompletionUpdate? Complete()
public ChatResponseUpdate? Complete()
{
if (_first is null)
return null;
Expand Down
33 changes: 18 additions & 15 deletions src/OllamaApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ private async Task PostAsync<TRequest>(string endpoint, TRequest ollamaRequest,
private async Task<TResponse> PostAsync<TRequest, TResponse>(string endpoint, TRequest ollamaRequest, CancellationToken cancellationToken) where TRequest : OllamaRequest
{
using var requestMessage = CreateRequestMessage(HttpMethod.Post, endpoint, ollamaRequest);

using var response = await SendToOllamaAsync(requestMessage, ollamaRequest, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);

using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
Expand All @@ -289,8 +289,8 @@ private async Task<TResponse> PostAsync<TRequest, TResponse>(string endpoint, TR
await foreach (var result in ProcessStreamedResponseAsync<TResponse>(response, cancellationToken).ConfigureAwait(false))
yield return result;
}


private HttpRequestMessage CreateRequestMessage(HttpMethod method, string endpoint) => new(method, endpoint);

private HttpRequestMessage CreateRequestMessage<TRequest>(HttpMethod method, string endpoint, TRequest ollamaRequest) where TRequest : OllamaRequest
Expand All @@ -299,8 +299,8 @@ private HttpRequestMessage CreateRequestMessage<TRequest>(HttpMethod method, str
requestMessage.Content = GetJsonContent(ollamaRequest);
return requestMessage;
}
private StringContent GetJsonContent<TRequest>(TRequest ollamaRequest) where TRequest : OllamaRequest =>

private StringContent GetJsonContent<TRequest>(TRequest ollamaRequest) where TRequest : OllamaRequest =>
new(JsonSerializer.Serialize(ollamaRequest, OutgoingJsonSerializerOptions), Encoding.UTF8, MimeTypes.Json);

private async IAsyncEnumerable<TLine?> ProcessStreamedResponseAsync<TLine>(HttpResponseMessage response, [EnumeratorCancellation] CancellationToken cancellationToken)
Expand Down Expand Up @@ -397,22 +397,19 @@ private async Task EnsureSuccessStatusCodeAsync(HttpResponseMessage response)

#region IChatClient and IEmbeddingGenerator implementation

/// <inheritdoc/>
ChatClientMetadata IChatClient.Metadata => new(Application.Ollama, Uri, SelectedModel);

/// <inheritdoc/>
EmbeddingGeneratorMetadata IEmbeddingGenerator<string, Embedding<float>>.Metadata => new(Application.Ollama, Uri, SelectedModel);
private ChatClientMetadata _chatClientMetadata;
private EmbeddingGeneratorMetadata _embeddingGeneratorMetadata;

/// <inheritdoc/>
async Task<ChatCompletion> IChatClient.CompleteAsync(IList<ChatMessage> chatMessages, ChatOptions? options, CancellationToken cancellationToken)
async Task<ChatResponse> IChatClient.GetResponseAsync(IList<ChatMessage> chatMessages, ChatOptions? options, CancellationToken cancellationToken)
{
var request = AbstractionMapper.ToOllamaSharpChatRequest(chatMessages, options, stream: false, OutgoingJsonSerializerOptions);
var response = await ChatAsync(request, cancellationToken).StreamToEndAsync().ConfigureAwait(false);
return AbstractionMapper.ToChatCompletion(response, response?.Model ?? request.Model ?? SelectedModel) ?? new ChatCompletion([]);
return AbstractionMapper.ToChatResponse(response, response?.Model ?? request.Model ?? SelectedModel) ?? new ChatResponse([]);
}

/// <inheritdoc/>
async IAsyncEnumerable<StreamingChatCompletionUpdate> IChatClient.CompleteStreamingAsync(IList<ChatMessage> chatMessages, ChatOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken)
async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync(IList<ChatMessage> chatMessages, ChatOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var request = AbstractionMapper.ToOllamaSharpChatRequest(chatMessages, options, stream: true, OutgoingJsonSerializerOptions);
await foreach (var response in ChatAsync(request, cancellationToken).ConfigureAwait(false))
Expand All @@ -429,11 +426,17 @@ async Task<GeneratedEmbeddings<Embedding<float>>> IEmbeddingGenerator<string, Em

/// <inheritdoc/>
object? IChatClient.GetService(Type serviceKey, object? key) =>
key is null && serviceKey?.IsInstanceOfType(this) is true ? this : null;
key is not null ? null :
serviceKey == typeof(ChatClientMetadata) ? (_chatClientMetadata = new(Application.Ollama, Uri, SelectedModel)) :
serviceKey?.IsInstanceOfType(this) is true ? this :
null;

/// <inheritdoc />
object? IEmbeddingGenerator<string, Embedding<float>>.GetService(Type serviceKey, object? key) =>
key is null && serviceKey?.IsInstanceOfType(this) is true ? this : null;
key is not null ? null :
serviceKey == typeof(EmbeddingGeneratorMetadata) ? (_embeddingGeneratorMetadata = new(Application.Ollama, Uri, SelectedModel)) :
serviceKey?.IsInstanceOfType(this) is true ? this :
null;

/// <summary>
/// Releases the resources used by the <see cref="OllamaApiClient"/> instance.
Expand Down
2 changes: 1 addition & 1 deletion src/OllamaSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="9.1.0-preview.1.25064.3" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="9.3.0-preview.1.25114.11" />
</ItemGroup>
</Project>
Loading

0 comments on commit 39d42d6

Please sign in to comment.