Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public class ChatOptions
/// implementation-specific options type may be returned by this callback, for the <see cref="IChatClient" />
/// implementation to use instead of creating a new instance. Such implementations may mutate the supplied options
/// instance further based on other settings supplied on this <see cref="ChatOptions" /> instance or from other inputs,
/// like the enumerable of <see cref="ChatMessage"/>s, therefore, its **strongly recommended** to not return shared instances
/// like the enumerable of <see cref="ChatMessage"/>s, therefore, its <b>strongly recommended</b> to not return shared instances
/// and instead make the callback return a new instance per each call.
/// This is typically used to set an implementation-specific setting that isn't otherwise exposed from the strongly-typed
/// properties on <see cref="ChatOptions" />.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Text.Json.Serialization;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;
Expand Down Expand Up @@ -31,6 +33,25 @@ public int? Dimensions
/// <summary>Gets or sets additional properties for the embedding generation request.</summary>
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }

/// <summary>
/// Gets or sets a callback responsible of creating the raw representation of the embedding generation options from an underlying implementation.
/// </summary>
/// <remarks>
/// The underlying <see cref="IEmbeddingGenerator" /> implementation may have its own representation of options.
/// When <see cref="IEmbeddingGenerator{TInput, TEmbedding}.GenerateAsync" />
/// is invoked with an <see cref="EmbeddingGenerationOptions" />, that implementation may convert the provided options into
/// its own representation in order to use it while performing the operation. For situations where a consumer knows
/// which concrete <see cref="IEmbeddingGenerator" /> is being used and how it represents options, a new instance of that
/// implementation-specific options type may be returned by this callback, for the <see cref="IEmbeddingGenerator" />
/// implementation to use instead of creating a new instance. Such implementations may mutate the supplied options
/// instance further based on other settings supplied on this <see cref="EmbeddingGenerationOptions" /> instance or from other inputs,
/// therefore, its <b>strongly recommended</b> to not return shared instances and instead make the callback return a new instance per each call.
/// This is typically used to set an implementation-specific setting that isn't otherwise exposed from the strongly-typed
/// properties on <see cref="EmbeddingGenerationOptions" />.
/// </remarks>
[JsonIgnore]
public Func<IEmbeddingGenerator, object?>? RawRepresentationFactory { get; set; }

/// <summary>Produces a clone of the current <see cref="EmbeddingGenerationOptions"/> instance.</summary>
/// <returns>A clone of the current <see cref="EmbeddingGenerationOptions"/> instance.</returns>
/// <remarks>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.AI;

Expand All @@ -24,6 +26,25 @@ public class SpeechToTextOptions
/// <summary>Gets or sets any additional properties associated with the options.</summary>
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }

/// <summary>
/// Gets or sets a callback responsible of creating the raw representation of the embedding generation options from an underlying implementation.
/// </summary>
/// <remarks>
/// The underlying <see cref="ISpeechToTextClient" /> implementation may have its own representation of options.
/// When <see cref="ISpeechToTextClient.GetTextAsync" /> or <see cref="ISpeechToTextClient.GetStreamingTextAsync"/>
/// is invoked with an <see cref="SpeechToTextOptions" />, that implementation may convert the provided options into
/// its own representation in order to use it while performing the operation. For situations where a consumer knows
/// which concrete <see cref="ISpeechToTextClient" /> is being used and how it represents options, a new instance of that
/// implementation-specific options type may be returned by this callback, for the <see cref="ISpeechToTextClient" />
/// implementation to use instead of creating a new instance. Such implementations may mutate the supplied options
/// instance further based on other settings supplied on this <see cref="SpeechToTextOptions" /> instance or from other inputs,
/// therefore, its <b>strongly recommended</b> to not return shared instances and instead make the callback return a new instance per each call.
/// This is typically used to set an implementation-specific setting that isn't otherwise exposed from the strongly-typed
/// properties on <see cref="SpeechToTextOptions" />.
/// </remarks>
[JsonIgnore]
public Func<ISpeechToTextClient, object?>? RawRepresentationFactory { get; set; }

/// <summary>Produces a clone of the current <see cref="SpeechToTextOptions"/> instance.</summary>
/// <returns>A clone of the current <see cref="SpeechToTextOptions"/> instance.</returns>
public virtual SpeechToTextOptions Clone()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private ChatCompletionsOptions CreateAzureAIOptions(IEnumerable<ChatMessage> cha
throw new InvalidOperationException("No model id was provided when either constructing the client or in the chat options.")
};

/// <summary>Converts an extensions options instance to an AzureAI options instance.</summary>
/// <summary>Converts an extensions options instance to an Azure.AI.Inference options instance.</summary>
private ChatCompletionsOptions ToAzureAIOptions(IEnumerable<ChatMessage> chatContents, ChatOptions? options)
{
if (options is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
{
_ = Throw.IfNull(values);

var azureAIOptions = ToAzureAIOptions(values, options, EmbeddingEncodingFormat.Base64);
var azureAIOptions = ToAzureAIOptions(values, options);

var embeddings = (await _embeddingsClient.EmbedAsync(azureAIOptions, cancellationToken).ConfigureAwait(false)).Value;

Expand Down Expand Up @@ -164,16 +164,35 @@ static void ThrowInvalidData() =>
}

/// <summary>Converts an extensions options instance to an Azure.AI.Inference options instance.</summary>
private EmbeddingsOptions ToAzureAIOptions(IEnumerable<string> inputs, EmbeddingGenerationOptions? options, EmbeddingEncodingFormat format)
private EmbeddingsOptions ToAzureAIOptions(IEnumerable<string> inputs, EmbeddingGenerationOptions? options)
{
EmbeddingsOptions result = new(inputs)
if (options is null)
{
Dimensions = options?.Dimensions ?? _dimensions,
Model = options?.ModelId ?? _metadata.DefaultModelId,
EncodingFormat = format,
};
return new EmbeddingsOptions(inputs)
{
Dimensions = _dimensions,
Model = _metadata.DefaultModelId,
EncodingFormat = EmbeddingEncodingFormat.Base64,
};
}

if (options.RawRepresentationFactory?.Invoke(this) is EmbeddingsOptions result)
{
// Input doesn't have a setter, so use reflection to set it.
typeof(EmbeddingsOptions)
.GetField("<Input>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(result, inputs.ToList());
}
else
{
result = new EmbeddingsOptions(inputs);
}

result.Dimensions ??= options.Dimensions ?? _dimensions;
result.Model ??= options.ModelId ?? _metadata.DefaultModelId;
result.EncodingFormat ??= EmbeddingEncodingFormat.Base64;

if (options?.AdditionalProperties is { } props)
if (options.AdditionalProperties is { } props)
{
foreach (var prop in props)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
{
_ = Throw.IfNull(values);

var azureAIOptions = ToAzureAIOptions(values, options, EmbeddingEncodingFormat.Base64);
var azureAIOptions = ToAzureAIOptions(values, options);

var embeddings = (await _imageEmbeddingsClient.EmbedAsync(azureAIOptions, cancellationToken).ConfigureAwait(false)).Value;

Expand Down Expand Up @@ -117,16 +117,37 @@ void IDisposable.Dispose()
}

/// <summary>Converts an extensions options instance to an Azure.AI.Inference options instance.</summary>
private ImageEmbeddingsOptions ToAzureAIOptions(IEnumerable<DataContent> inputs, EmbeddingGenerationOptions? options, EmbeddingEncodingFormat format)
private ImageEmbeddingsOptions ToAzureAIOptions(IEnumerable<DataContent> inputs, EmbeddingGenerationOptions? options)
{
ImageEmbeddingsOptions result = new(inputs.Select(dc => new ImageEmbeddingInput(dc.Uri)))
var imageEmbeddingInputs = inputs.Select(dc => new ImageEmbeddingInput(dc.Uri));

if (options is null)
{
return new ImageEmbeddingsOptions(imageEmbeddingInputs)
{
Dimensions = _dimensions,
Model = _metadata.DefaultModelId,
EncodingFormat = EmbeddingEncodingFormat.Base64,
};
}

if (options.RawRepresentationFactory?.Invoke(this) is ImageEmbeddingsOptions result)
{
// Input doesn't have a setter, so use reflection to set it.
typeof(ImageEmbeddingsOptions)
.GetField("<Input>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(result, imageEmbeddingInputs.ToList());
}
else
{
Dimensions = options?.Dimensions ?? _dimensions,
Model = options?.ModelId ?? _metadata.DefaultModelId,
EncodingFormat = format,
};
result = new ImageEmbeddingsOptions(imageEmbeddingInputs);
}

result.Dimensions ??= options.Dimensions ?? _dimensions;
result.Model ??= options.ModelId ?? _metadata.DefaultModelId;
result.EncodingFormat ??= EmbeddingEncodingFormat.Base64;

if (options?.AdditionalProperties is { } props)
if (options.AdditionalProperties is { } props)
{
foreach (var prop in props)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,14 @@ private static EmbeddingGeneratorMetadata CreateMetadata(string providerName, st
new(providerName, Uri.TryCreate(providerUrl, UriKind.Absolute, out Uri? providerUri) ? providerUri : null, defaultModelId, defaultModelDimensions);

/// <summary>Converts an extensions options instance to an OpenAI options instance.</summary>
private OpenAI.Embeddings.EmbeddingGenerationOptions? ToOpenAIOptions(EmbeddingGenerationOptions? options)
private OpenAI.Embeddings.EmbeddingGenerationOptions ToOpenAIOptions(EmbeddingGenerationOptions? options)
{
OpenAI.Embeddings.EmbeddingGenerationOptions openAIOptions = new()
if (options?.RawRepresentationFactory?.Invoke(this) is not OpenAI.Embeddings.EmbeddingGenerationOptions result)
{
Dimensions = options?.Dimensions ?? _dimensions,
};

if (options?.AdditionalProperties is { Count: > 0 } additionalProperties)
{
if (additionalProperties.TryGetValue(nameof(openAIOptions.EndUserId), out string? endUserId))
{
openAIOptions.EndUserId = endUserId;
}
result = new OpenAI.Embeddings.EmbeddingGenerationOptions();
}

return openAIOptions;
result.Dimensions ??= options?.Dimensions ?? _dimensions;
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#pragma warning disable S1067 // Expressions should not be too complex
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
#pragma warning disable S3604 // Member initializer values should not be redundant
#pragma warning disable SA1204 // Static elements should appear before instance elements

namespace Microsoft.Extensions.AI;

Expand Down Expand Up @@ -315,88 +316,59 @@ private static ChatRole ToChatRole(MessageRole? role) =>
null;

/// <summary>Converts a <see cref="ChatOptions"/> to a <see cref="ResponseCreationOptions"/>.</summary>
private static ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? options)
private ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? options)
{
ResponseCreationOptions result = new();
if (options is null)
{
return new ResponseCreationOptions();
}

if (options is not null)
if (options.RawRepresentationFactory?.Invoke(this) is not ResponseCreationOptions result)
{
// Handle strongly-typed properties.
result.MaxOutputTokenCount = options.MaxOutputTokens;
result.PreviousResponseId = options.ConversationId;
result.TopP = options.TopP;
result.Temperature = options.Temperature;
result.ParallelToolCallsEnabled = options.AllowMultipleToolCalls;

// Handle loosely-typed properties from AdditionalProperties.
if (options.AdditionalProperties is { Count: > 0 } additionalProperties)
{
if (additionalProperties.TryGetValue(nameof(result.EndUserId), out string? endUserId))
{
result.EndUserId = endUserId;
}
result = new ResponseCreationOptions();
}

if (additionalProperties.TryGetValue(nameof(result.Instructions), out string? instructions))
{
result.Instructions = instructions;
}
// Handle strongly-typed properties.
result.MaxOutputTokenCount ??= options.MaxOutputTokens;
result.PreviousResponseId ??= options.ConversationId;
result.TopP ??= options.TopP;
result.Temperature ??= options.Temperature;
result.ParallelToolCallsEnabled ??= options.AllowMultipleToolCalls;

if (additionalProperties.TryGetValue(nameof(result.Metadata), out IDictionary<string, string>? metadata))
// Populate tools if there are any.
if (options.Tools is { Count: > 0 } tools)
{
foreach (AITool tool in tools)
{
switch (tool)
{
foreach (KeyValuePair<string, string> kvp in metadata)
{
result.Metadata[kvp.Key] = kvp.Value;
}
}
case AIFunction af:
var oaitool = JsonSerializer.Deserialize(SchemaTransformCache.GetOrCreateTransformedSchema(af), ResponseClientJsonContext.Default.ResponseToolJson)!;
var functionParameters = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(oaitool, ResponseClientJsonContext.Default.ResponseToolJson));
result.Tools.Add(ResponseTool.CreateFunctionTool(af.Name, af.Description, functionParameters));
break;

if (additionalProperties.TryGetValue(nameof(result.ReasoningOptions), out ResponseReasoningOptions? reasoningOptions))
{
result.ReasoningOptions = reasoningOptions;
}
case HostedWebSearchTool:
WebSearchToolLocation? location = null;
if (tool.AdditionalProperties.TryGetValue(nameof(WebSearchToolLocation), out object? objLocation))
{
location = objLocation as WebSearchToolLocation;
}

if (additionalProperties.TryGetValue(nameof(result.StoredOutputEnabled), out bool storeOutputEnabled))
{
result.StoredOutputEnabled = storeOutputEnabled;
}
WebSearchToolContextSize? size = null;
if (tool.AdditionalProperties.TryGetValue(nameof(WebSearchToolContextSize), out object? objSize) &&
objSize is WebSearchToolContextSize)
{
size = (WebSearchToolContextSize)objSize;
}

if (additionalProperties.TryGetValue(nameof(result.TruncationMode), out ResponseTruncationMode truncationMode))
{
result.TruncationMode = truncationMode;
result.Tools.Add(ResponseTool.CreateWebSearchTool(location, size));
break;
}
}

// Populate tools if there are any.
if (options.Tools is { Count: > 0 } tools)
if (result.ToolChoice is null && result.Tools.Count > 0)
{
foreach (AITool tool in tools)
{
switch (tool)
{
case AIFunction af:
var oaitool = JsonSerializer.Deserialize(SchemaTransformCache.GetOrCreateTransformedSchema(af), ResponseClientJsonContext.Default.ResponseToolJson)!;
var functionParameters = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(oaitool, ResponseClientJsonContext.Default.ResponseToolJson));
result.Tools.Add(ResponseTool.CreateFunctionTool(af.Name, af.Description, functionParameters));
break;

case HostedWebSearchTool:
WebSearchToolLocation? location = null;
if (tool.AdditionalProperties.TryGetValue(nameof(WebSearchToolLocation), out object? objLocation))
{
location = objLocation as WebSearchToolLocation;
}

WebSearchToolContextSize? size = null;
if (tool.AdditionalProperties.TryGetValue(nameof(WebSearchToolContextSize), out object? objSize) &&
objSize is WebSearchToolContextSize)
{
size = (WebSearchToolContextSize)objSize;
}

result.Tools.Add(ResponseTool.CreateWebSearchTool(location, size));
break;
}
}

switch (options.ToolMode)
{
case NoneChatToolMode:
Expand All @@ -415,8 +387,10 @@ private static ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptio
break;
}
}
}

// Handle response format.
if (result.TextOptions is null)
{
if (options.ResponseFormat is ChatResponseFormatText)
{
result.TextOptions = new()
Expand Down
Loading
Loading