Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Collections.Generic;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -118,6 +119,26 @@ public string? ChatThreadId
[JsonIgnore]
public IList<AITool>? Tools { get; set; }

/// <summary>
/// Gets or sets a callback responsible of creating the raw representation of the chat options from an underlying implementation.
/// </summary>
/// <remarks>
/// The underlying <see cref="IChatClient" /> implementation may have its own representation of options.
/// When <see cref="IChatClient.GetResponseAsync" /> or <see cref="IChatClient.GetStreamingResponseAsync" />
/// is invoked with a <see cref="ChatOptions" />, 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="IChatClient" /> is being used and how it represents options, an instance of that
/// 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
/// 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" />.
/// </remarks>
[JsonIgnore]
public Func<IChatClient, object?>? RawRepresentationFactory { get; set; }

/// <summary>Gets or sets any additional properties associated with the options.</summary>
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }

Expand All @@ -144,6 +165,7 @@ public virtual ChatOptions Clone()
ModelId = ModelId,
AllowMultipleToolCalls = AllowMultipleToolCalls,
ToolMode = ToolMode,
RawRepresentationFactory = RawRepresentationFactory,
AdditionalProperties = AdditionalProperties?.Clone(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,66 +273,68 @@ private static ChatRole ToChatRole(global::Azure.AI.Inference.ChatRole role) =>
finishReason == CompletionsFinishReason.ToolCalls ? ChatFinishReason.ToolCalls :
new(s);

private ChatCompletionsOptions CreateAzureAIOptions(IEnumerable<ChatMessage> chatContents, ChatOptions? options) =>
new(ToAzureAIInferenceChatMessages(chatContents))
{
Model = options?.ModelId ?? _metadata.DefaultModelId ??
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>
private ChatCompletionsOptions ToAzureAIOptions(IEnumerable<ChatMessage> chatContents, ChatOptions? options)
{
ChatCompletionsOptions result = new(ToAzureAIInferenceChatMessages(chatContents))
if (options is null)
{
Model = options?.ModelId ?? _metadata.DefaultModelId ?? throw new InvalidOperationException("No model id was provided when either constructing the client or in the chat options.")
};
return CreateAzureAIOptions(chatContents, options);
}

if (options is not null)
if (options.RawRepresentationFactory?.Invoke(this) is ChatCompletionsOptions result)
{
result.Messages = ToAzureAIInferenceChatMessages(chatContents).ToList();
result.Model ??= options.ModelId ?? _metadata.DefaultModelId ??
throw new InvalidOperationException("No model id was provided when either constructing the client or in the chat options.");
}
else
{
result.FrequencyPenalty = options.FrequencyPenalty;
result.MaxTokens = options.MaxOutputTokens;
result.NucleusSamplingFactor = options.TopP;
result.PresencePenalty = options.PresencePenalty;
result.Temperature = options.Temperature;
result.Seed = options.Seed;

if (options.StopSequences is { Count: > 0 } stopSequences)
result = CreateAzureAIOptions(chatContents, options);
}

result.FrequencyPenalty ??= options.FrequencyPenalty;
result.MaxTokens ??= options.MaxOutputTokens;
result.NucleusSamplingFactor ??= options.TopP;
result.PresencePenalty ??= options.PresencePenalty;
result.Temperature ??= options.Temperature;
result.Seed ??= options.Seed;

if (options.StopSequences is { Count: > 0 } stopSequences)
{
foreach (string stopSequence in stopSequences)
{
foreach (string stopSequence in stopSequences)
{
result.StopSequences.Add(stopSequence);
}
result.StopSequences.Add(stopSequence);
}
}

// These properties are strongly typed on ChatOptions but not on ChatCompletionsOptions.
if (options.TopK is int topK)
if (options.AdditionalProperties is { } props)
{
foreach (var prop in props)
{
result.AdditionalProperties["top_k"] = new BinaryData(JsonSerializer.SerializeToUtf8Bytes(topK, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(int))));
byte[] data = JsonSerializer.SerializeToUtf8Bytes(prop.Value, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
result.AdditionalProperties[prop.Key] = new BinaryData(data);
}
}

if (options.AdditionalProperties is { } props)
if (options.Tools is { Count: > 0 } tools)
{
foreach (AITool tool in tools)
{
foreach (var prop in props)
if (tool is AIFunction af)
{
switch (prop.Key)
{
// Propagate everything else to the ChatCompletionsOptions' AdditionalProperties.
default:
if (prop.Value is not null)
{
byte[] data = JsonSerializer.SerializeToUtf8Bytes(prop.Value, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
result.AdditionalProperties[prop.Key] = new BinaryData(data);
}

break;
}
result.Tools.Add(ToAzureAIChatTool(af));
}
}

if (options.Tools is { Count: > 0 } tools)
if (result.ToolChoice is null && result.Tools.Count > 0)
{
foreach (AITool tool in tools)
{
if (tool is AIFunction af)
{
result.Tools.Add(ToAzureAIChatTool(af));
}
}

switch (options.ToolMode)
{
case NoneChatToolMode:
Expand All @@ -351,7 +353,10 @@ private ChatCompletionsOptions ToAzureAIOptions(IEnumerable<ChatMessage> chatCon
break;
}
}
}

if (result.ResponseFormat is null)
{
if (options.ResponseFormat is ChatResponseFormatText)
{
result.ResponseFormat = ChatCompletionsResponseFormat.CreateTextFormat();
Expand Down
Loading
Loading