diff --git a/eng/packages/General.props b/eng/packages/General.props
index b2978f5b6be..a8de843a353 100644
--- a/eng/packages/General.props
+++ b/eng/packages/General.props
@@ -4,7 +4,6 @@
-
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIChatToolJson.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIChatToolJson.cs
deleted file mode 100644
index 7be8fee287a..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIChatToolJson.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections.Generic;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Microsoft.Extensions.AI;
-
-/// Used to create the JSON payload for an AzureAI chat tool description.
-internal sealed class AzureAIChatToolJson
-{
- [JsonPropertyName("type")]
- public string Type { get; set; } = "object";
-
- [JsonPropertyName("required")]
- public List Required { get; set; } = [];
-
- [JsonPropertyName("properties")]
- public Dictionary Properties { get; set; } = [];
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs
deleted file mode 100644
index 45081c0ab6c..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs
+++ /dev/null
@@ -1,552 +0,0 @@
-// 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.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization.Metadata;
-using System.Threading;
-using System.Threading.Tasks;
-using Azure.AI.Inference;
-using Microsoft.Shared.Diagnostics;
-
-#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
-#pragma warning disable SA1204 // Static elements should appear before instance elements
-
-namespace Microsoft.Extensions.AI;
-
-/// Represents an for an Azure AI Inference .
-internal sealed class AzureAIInferenceChatClient : IChatClient
-{
- /// Gets the JSON schema transform cache conforming to OpenAI restrictions per https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#supported-schemas.
- private static AIJsonSchemaTransformCache SchemaTransformCache { get; } = new(new()
- {
- RequireAllProperties = true,
- DisallowAdditionalProperties = true,
- ConvertBooleanSchemas = true,
- MoveDefaultKeywordToDescription = true,
- });
-
- /// Metadata about the client.
- private readonly ChatClientMetadata _metadata;
-
- /// The underlying .
- private readonly ChatCompletionsClient _chatCompletionsClient;
-
- /// Gets a ChatRole.Developer value.
- private static ChatRole ChatRoleDeveloper { get; } = new("developer");
-
- /// Initializes a new instance of the class for the specified .
- /// The underlying client.
- /// The ID of the model to use. If , it can be provided per request via .
- /// is .
- /// is empty or composed entirely of whitespace.
- public AzureAIInferenceChatClient(ChatCompletionsClient chatCompletionsClient, string? defaultModelId = null)
- {
- _ = Throw.IfNull(chatCompletionsClient);
-
- if (defaultModelId is not null)
- {
- _ = Throw.IfNullOrWhitespace(defaultModelId);
- }
-
- _chatCompletionsClient = chatCompletionsClient;
-
- // https://github.com/Azure/azure-sdk-for-net/issues/46278
- // The endpoint isn't currently exposed, so use reflection to get at it, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- var providerUrl = typeof(ChatCompletionsClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(chatCompletionsClient) as Uri;
-
- _metadata = new ChatClientMetadata("azure.ai.inference", providerUrl, defaultModelId);
- }
-
- ///
- object? IChatClient.GetService(Type serviceType, object? serviceKey)
- {
- _ = Throw.IfNull(serviceType);
-
- return
- serviceKey is not null ? null :
- serviceType == typeof(ChatCompletionsClient) ? _chatCompletionsClient :
- serviceType == typeof(ChatClientMetadata) ? _metadata :
- serviceType.IsInstanceOfType(this) ? this :
- null;
- }
-
- ///
- public async Task GetResponseAsync(
- IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
- {
- _ = Throw.IfNull(messages);
-
- // Make the call.
- ChatCompletions response = (await _chatCompletionsClient.CompleteAsync(
- ToAzureAIOptions(messages, options),
- cancellationToken: cancellationToken).ConfigureAwait(false)).Value;
-
- // Create the return message.
- ChatMessage message = new(ToChatRole(response.Role), response.Content)
- {
- CreatedAt = response.Created,
- MessageId = response.Id, // There is no per-message ID, but there's only one message per response, so use the response ID
- RawRepresentation = response,
- };
-
- if (response.ToolCalls is { Count: > 0 } toolCalls)
- {
- foreach (var toolCall in toolCalls)
- {
- if (toolCall is ChatCompletionsToolCall ftc && !string.IsNullOrWhiteSpace(ftc.Name))
- {
- FunctionCallContent callContent = ParseCallContentFromJsonString(ftc.Arguments, toolCall.Id, ftc.Name);
- callContent.RawRepresentation = toolCall;
-
- message.Contents.Add(callContent);
- }
- }
- }
-
- UsageDetails? usage = null;
- if (response.Usage is CompletionsUsage completionsUsage)
- {
- usage = new()
- {
- InputTokenCount = completionsUsage.PromptTokens,
- OutputTokenCount = completionsUsage.CompletionTokens,
- TotalTokenCount = completionsUsage.TotalTokens,
- };
- }
-
- // Wrap the content in a ChatResponse to return.
- return new ChatResponse(message)
- {
- CreatedAt = response.Created,
- ModelId = response.Model,
- FinishReason = ToFinishReason(response.FinishReason),
- RawRepresentation = response,
- ResponseId = response.Id,
- Usage = usage,
- };
- }
-
- ///
- public async IAsyncEnumerable GetStreamingResponseAsync(
- IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- _ = Throw.IfNull(messages);
-
- Dictionary? functionCallInfos = null;
- ChatRole? streamedRole = default;
- ChatFinishReason? finishReason = default;
- string? responseId = null;
- DateTimeOffset? createdAt = null;
- string? modelId = null;
- string lastCallId = string.Empty;
-
- // Process each update as it arrives
- var updates = await _chatCompletionsClient.CompleteStreamingAsync(ToAzureAIOptions(messages, options), cancellationToken).ConfigureAwait(false);
- await foreach (StreamingChatCompletionsUpdate chatCompletionUpdate in updates.ConfigureAwait(false))
- {
- // The role and finish reason may arrive during any update, but once they've arrived, the same value should be the same for all subsequent updates.
- streamedRole ??= chatCompletionUpdate.Role is global::Azure.AI.Inference.ChatRole role ? ToChatRole(role) : null;
- finishReason ??= chatCompletionUpdate.FinishReason is CompletionsFinishReason reason ? ToFinishReason(reason) : null;
- responseId ??= chatCompletionUpdate.Id; // While it's unclear from the name, this Id is documented to be the response ID, not the chunk ID
- createdAt ??= chatCompletionUpdate.Created;
- modelId ??= chatCompletionUpdate.Model;
-
- // Create the response content object.
- ChatResponseUpdate responseUpdate = new()
- {
- CreatedAt = chatCompletionUpdate.Created,
- FinishReason = finishReason,
- ModelId = modelId,
- RawRepresentation = chatCompletionUpdate,
- ResponseId = responseId,
- MessageId = responseId, // There is no per-message ID, but there's only one message per response, so use the response ID
- Role = streamedRole,
- };
-
- // Transfer over content update items.
- if (chatCompletionUpdate.ContentUpdate is string update)
- {
- responseUpdate.Contents.Add(new TextContent(update));
- }
-
- // Transfer over tool call updates.
- if (chatCompletionUpdate.ToolCallUpdate is { } toolCallUpdate)
- {
- if (toolCallUpdate.Id is not null)
- {
- lastCallId = toolCallUpdate.Id;
- }
-
- functionCallInfos ??= [];
- if (!functionCallInfos.TryGetValue(lastCallId, out FunctionCallInfo? existing))
- {
- functionCallInfos[lastCallId] = existing = new();
- }
-
- existing.Name ??= toolCallUpdate.Function.Name;
- if (toolCallUpdate.Function.Arguments is { } arguments)
- {
- _ = (existing.Arguments ??= new()).Append(arguments);
- }
- }
-
- if (chatCompletionUpdate.Usage is { } usage)
- {
- responseUpdate.Contents.Add(new UsageContent(new()
- {
- InputTokenCount = usage.PromptTokens,
- OutputTokenCount = usage.CompletionTokens,
- TotalTokenCount = usage.TotalTokens,
- }));
- }
-
- // Now yield the item.
- yield return responseUpdate;
- }
-
- // Now that we've received all updates, combine any for function calls into a single item to yield.
- if (functionCallInfos is not null)
- {
- var responseUpdate = new ChatResponseUpdate
- {
- CreatedAt = createdAt,
- FinishReason = finishReason,
- ModelId = modelId,
- ResponseId = responseId,
- MessageId = responseId, // There is no per-message ID, but there's only one message per response, so use the response ID
- Role = streamedRole,
- };
-
- foreach (var entry in functionCallInfos)
- {
- FunctionCallInfo fci = entry.Value;
- if (!string.IsNullOrWhiteSpace(fci.Name))
- {
- FunctionCallContent callContent = ParseCallContentFromJsonString(
- fci.Arguments?.ToString() ?? string.Empty,
- entry.Key,
- fci.Name!);
- responseUpdate.Contents.Add(callContent);
- }
- }
-
- yield return responseUpdate;
- }
- }
-
- ///
- void IDisposable.Dispose()
- {
- // Nothing to dispose. Implementation required for the IChatClient interface.
- }
-
- /// POCO representing function calling info. Used to concatenation information for a single function call from across multiple streaming updates.
- private sealed class FunctionCallInfo
- {
- public string? Name;
- public StringBuilder? Arguments;
- }
-
- /// Converts an AzureAI role to an Extensions role.
- private static ChatRole ToChatRole(global::Azure.AI.Inference.ChatRole role) =>
- role.Equals(global::Azure.AI.Inference.ChatRole.System) ? ChatRole.System :
- role.Equals(global::Azure.AI.Inference.ChatRole.User) ? ChatRole.User :
- role.Equals(global::Azure.AI.Inference.ChatRole.Assistant) ? ChatRole.Assistant :
- role.Equals(global::Azure.AI.Inference.ChatRole.Tool) ? ChatRole.Tool :
- role.Equals(global::Azure.AI.Inference.ChatRole.Developer) ? ChatRoleDeveloper :
- new ChatRole(role.ToString());
-
- /// Converts an AzureAI finish reason to an Extensions finish reason.
- private static ChatFinishReason? ToFinishReason(CompletionsFinishReason? finishReason) =>
- finishReason?.ToString() is not string s ? null :
- finishReason == CompletionsFinishReason.Stopped ? ChatFinishReason.Stop :
- finishReason == CompletionsFinishReason.TokenLimitReached ? ChatFinishReason.Length :
- finishReason == CompletionsFinishReason.ContentFiltered ? ChatFinishReason.ContentFilter :
- finishReason == CompletionsFinishReason.ToolCalls ? ChatFinishReason.ToolCalls :
- new(s);
-
- private ChatCompletionsOptions CreateAzureAIOptions(IEnumerable chatContents, ChatOptions? options) =>
- new(ToAzureAIInferenceChatMessages(chatContents, options))
- {
- Model = options?.ModelId ?? _metadata.DefaultModelId ??
- throw new InvalidOperationException("No model id was provided when either constructing the client or in the chat options.")
- };
-
- /// Converts an extensions options instance to an Azure.AI.Inference options instance.
- private ChatCompletionsOptions ToAzureAIOptions(IEnumerable chatContents, ChatOptions? options)
- {
- if (options is null)
- {
- return CreateAzureAIOptions(chatContents, options);
- }
-
- if (options.RawRepresentationFactory?.Invoke(this) is ChatCompletionsOptions result)
- {
- result.Messages = ToAzureAIInferenceChatMessages(chatContents, options).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 = 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)
- {
- result.StopSequences.Add(stopSequence);
- }
- }
-
- // This property is strongly typed on ChatOptions but not on ChatCompletionsOptions.
- if (options.TopK is int topK && !result.AdditionalProperties.ContainsKey("top_k"))
- {
- result.AdditionalProperties["top_k"] = new BinaryData(JsonSerializer.SerializeToUtf8Bytes(topK, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(int))));
- }
-
- if (options.AdditionalProperties is { } props)
- {
- foreach (var prop in props)
- {
- byte[] data = JsonSerializer.SerializeToUtf8Bytes(prop.Value, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
- result.AdditionalProperties[prop.Key] = new BinaryData(data);
- }
- }
-
- if (options.Tools is { Count: > 0 } tools)
- {
- foreach (AITool tool in tools)
- {
- if (tool is AIFunctionDeclaration af)
- {
- result.Tools.Add(ToAzureAIChatTool(af));
- }
- }
-
- if (result.ToolChoice is null && result.Tools.Count > 0)
- {
- switch (options.ToolMode)
- {
- case NoneChatToolMode:
- result.ToolChoice = ChatCompletionsToolChoice.None;
- break;
-
- case AutoChatToolMode:
- case null:
- result.ToolChoice = ChatCompletionsToolChoice.Auto;
- break;
-
- case RequiredChatToolMode required:
- result.ToolChoice = required.RequiredFunctionName is null ?
- ChatCompletionsToolChoice.Required :
- new ChatCompletionsToolChoice(new FunctionDefinition(required.RequiredFunctionName));
- break;
- }
- }
- }
-
- if (result.ResponseFormat is null)
- {
- if (options.ResponseFormat is ChatResponseFormatText)
- {
- result.ResponseFormat = ChatCompletionsResponseFormat.CreateTextFormat();
- }
- else if (options.ResponseFormat is ChatResponseFormatJson json)
- {
- if (SchemaTransformCache.GetOrCreateTransformedSchema(json) is { } schema)
- {
- var tool = JsonSerializer.Deserialize(schema, JsonContext.Default.AzureAIChatToolJson)!;
- result.ResponseFormat = ChatCompletionsResponseFormat.CreateJsonFormat(
- json.SchemaName ?? "json_schema",
- new Dictionary
- {
- ["type"] = _objectString,
- ["properties"] = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool.Properties, JsonContext.Default.DictionaryStringJsonElement)),
- ["required"] = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool.Required, JsonContext.Default.ListString)),
- ["additionalProperties"] = _falseString,
- },
- json.SchemaDescription);
- }
- else
- {
- result.ResponseFormat = ChatCompletionsResponseFormat.CreateJsonFormat();
- }
- }
- }
-
- return result;
- }
-
- /// Cached for "object".
- private static readonly BinaryData _objectString = BinaryData.FromString("\"object\"");
-
- /// Cached for "false".
- private static readonly BinaryData _falseString = BinaryData.FromString("false");
-
- /// Converts an Extensions function to an AzureAI chat tool.
- private static ChatCompletionsToolDefinition ToAzureAIChatTool(AIFunctionDeclaration aiFunction)
- {
- // Map to an intermediate model so that redundant properties are skipped.
- var tool = JsonSerializer.Deserialize(SchemaTransformCache.GetOrCreateTransformedSchema(aiFunction), JsonContext.Default.AzureAIChatToolJson)!;
- var functionParameters = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool, JsonContext.Default.AzureAIChatToolJson));
- return new(new FunctionDefinition(aiFunction.Name)
- {
- Description = aiFunction.Description,
- Parameters = functionParameters,
- });
- }
-
- /// Converts an Extensions chat message enumerable to an AzureAI chat message enumerable.
- private static IEnumerable ToAzureAIInferenceChatMessages(IEnumerable inputs, ChatOptions? options)
- {
- // Maps all of the M.E.AI types to the corresponding AzureAI types.
- // Unrecognized or non-processable content is ignored.
-
- if (options?.Instructions is { } instructions && !string.IsNullOrWhiteSpace(instructions))
- {
- yield return new ChatRequestSystemMessage(instructions);
- }
-
- foreach (ChatMessage input in inputs)
- {
- if (input.Role == ChatRole.System)
- {
- yield return new ChatRequestSystemMessage(input.Text ?? string.Empty);
- }
- else if (input.Role == ChatRoleDeveloper)
- {
- yield return new ChatRequestDeveloperMessage(input.Text ?? string.Empty);
- }
- else if (input.Role == ChatRole.Tool)
- {
- foreach (AIContent item in input.Contents)
- {
- if (item is FunctionResultContent resultContent)
- {
- string? result = resultContent.Result as string;
- if (result is null && resultContent.Result is not null)
- {
- try
- {
- result = JsonSerializer.Serialize(resultContent.Result, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
- }
- catch (NotSupportedException)
- {
- // If the type can't be serialized, skip it.
- }
- }
-
- yield return new ChatRequestToolMessage(result ?? string.Empty, resultContent.CallId);
- }
- }
- }
- else if (input.Role == ChatRole.User)
- {
- if (input.Contents.Count > 0)
- {
- if (input.Contents.All(c => c is TextContent))
- {
- if (string.Concat(input.Contents) is { Length: > 0 } text)
- {
- yield return new ChatRequestUserMessage(text);
- }
- }
- else if (GetContentParts(input.Contents) is { Count: > 0 } parts)
- {
- yield return new ChatRequestUserMessage(parts);
- }
- }
- }
- else if (input.Role == ChatRole.Assistant)
- {
- ChatRequestAssistantMessage message = new(string.Concat(input.Contents.Where(c => c is TextContent)));
-
- foreach (var content in input.Contents)
- {
- if (content is FunctionCallContent { CallId: not null } callRequest)
- {
- message.ToolCalls.Add(new ChatCompletionsToolCall(
- callRequest.CallId,
- new FunctionCall(
- callRequest.Name,
- JsonSerializer.Serialize(callRequest.Arguments, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(IDictionary))))));
- }
- }
-
- yield return message;
- }
- }
- }
-
- /// Converts a list of to a list of .
- private static List GetContentParts(IList contents)
- {
- Debug.Assert(contents is { Count: > 0 }, "Expected non-empty contents");
-
- List parts = [];
- foreach (var content in contents)
- {
- switch (content)
- {
- case TextContent textContent:
- parts.Add(new ChatMessageTextContentItem(textContent.Text));
- break;
-
- case UriContent uriContent when uriContent.HasTopLevelMediaType("image"):
- parts.Add(new ChatMessageImageContentItem(uriContent.Uri));
- break;
-
- case DataContent dataContent when dataContent.HasTopLevelMediaType("image"):
- parts.Add(new ChatMessageImageContentItem(BinaryData.FromBytes(dataContent.Data), dataContent.MediaType));
- break;
-
- case UriContent uriContent when uriContent.HasTopLevelMediaType("audio"):
- parts.Add(new ChatMessageAudioContentItem(uriContent.Uri));
- break;
-
- case DataContent dataContent when dataContent.HasTopLevelMediaType("audio"):
- AudioContentFormat format;
- if (dataContent.MediaType.Equals("audio/mpeg", StringComparison.OrdinalIgnoreCase))
- {
- format = AudioContentFormat.Mp3;
- }
- else if (dataContent.MediaType.Equals("audio/wav", StringComparison.OrdinalIgnoreCase))
- {
- format = AudioContentFormat.Wav;
- }
- else
- {
- break;
- }
-
- parts.Add(new ChatMessageAudioContentItem(BinaryData.FromBytes(dataContent.Data), format));
- break;
- }
- }
-
- return parts;
- }
-
- private static FunctionCallContent ParseCallContentFromJsonString(string json, string callId, string name) =>
- FunctionCallContent.CreateFromParsedArguments(json, callId, name,
- argumentParser: static json => JsonSerializer.Deserialize(json,
- (JsonTypeInfo>)AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(IDictionary)))!);
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceEmbeddingGenerator.cs
deleted file mode 100644
index 04383a85b86..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceEmbeddingGenerator.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-// 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.Buffers;
-using System.Buffers.Binary;
-using System.Buffers.Text;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using Azure.AI.Inference;
-using Microsoft.Shared.Diagnostics;
-
-#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
-
-namespace Microsoft.Extensions.AI;
-
-/// Represents an for an Azure.AI.Inference .
-internal sealed class AzureAIInferenceEmbeddingGenerator :
- IEmbeddingGenerator>
-{
- /// Metadata about the embedding generator.
- private readonly EmbeddingGeneratorMetadata _metadata;
-
- /// The underlying .
- private readonly EmbeddingsClient _embeddingsClient;
-
- /// The number of dimensions produced by the generator.
- private readonly int? _dimensions;
-
- /// Initializes a new instance of the class.
- /// The underlying client.
- ///
- /// The ID of the model to use. This can also be overridden per request via .
- /// Either this parameter or must provide a valid model ID.
- ///
- /// The number of dimensions to generate in each embedding.
- /// is .
- /// is empty or composed entirely of whitespace.
- /// is not positive.
- public AzureAIInferenceEmbeddingGenerator(
- EmbeddingsClient embeddingsClient, string? defaultModelId = null, int? defaultModelDimensions = null)
- {
- _ = Throw.IfNull(embeddingsClient);
-
- if (defaultModelId is not null)
- {
- _ = Throw.IfNullOrWhitespace(defaultModelId);
- }
-
- if (defaultModelDimensions is < 1)
- {
- Throw.ArgumentOutOfRangeException(nameof(defaultModelDimensions), "Value must be greater than 0.");
- }
-
- _embeddingsClient = embeddingsClient;
- _dimensions = defaultModelDimensions;
-
- // https://github.com/Azure/azure-sdk-for-net/issues/46278
- // The endpoint isn't currently exposed, so use reflection to get at it, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- var providerUrl = typeof(EmbeddingsClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(embeddingsClient) as Uri;
-
- _metadata = new EmbeddingGeneratorMetadata("azure.ai.inference", providerUrl, defaultModelId, defaultModelDimensions);
- }
-
- ///
- object? IEmbeddingGenerator.GetService(Type serviceType, object? serviceKey)
- {
- _ = Throw.IfNull(serviceType);
-
- return
- serviceKey is not null ? null :
- serviceType == typeof(EmbeddingsClient) ? _embeddingsClient :
- serviceType == typeof(EmbeddingGeneratorMetadata) ? _metadata :
- serviceType.IsInstanceOfType(this) ? this :
- null;
- }
-
- ///
- public async Task>> GenerateAsync(
- IEnumerable values, EmbeddingGenerationOptions? options = null, CancellationToken cancellationToken = default)
- {
- _ = Throw.IfNull(values);
-
- var azureAIOptions = ToAzureAIOptions(values, options);
-
- var embeddings = (await _embeddingsClient.EmbedAsync(azureAIOptions, cancellationToken).ConfigureAwait(false)).Value;
-
- GeneratedEmbeddings> result = new(embeddings.Data.Select(e =>
- new Embedding(ParseBase64Floats(e.Embedding))
- {
- CreatedAt = DateTimeOffset.UtcNow,
- ModelId = embeddings.Model ?? azureAIOptions.Model,
- }));
-
- if (embeddings.Usage is not null)
- {
- result.Usage = new()
- {
- InputTokenCount = embeddings.Usage.PromptTokens,
- TotalTokenCount = embeddings.Usage.TotalTokens
- };
- }
-
- return result;
- }
-
- ///
- void IDisposable.Dispose()
- {
- // Nothing to dispose. Implementation required for the IEmbeddingGenerator interface.
- }
-
- internal static float[] ParseBase64Floats(BinaryData binaryData)
- {
- ReadOnlySpan base64 = binaryData.ToMemory().Span;
-
- // Remove quotes around base64 string.
- if (base64.Length < 2 || base64[0] != (byte)'"' || base64[base64.Length - 1] != (byte)'"')
- {
- ThrowInvalidData();
- }
-
- base64 = base64.Slice(1, base64.Length - 2);
-
- // Decode base64 string to bytes.
- byte[] bytes = ArrayPool.Shared.Rent(Base64.GetMaxDecodedFromUtf8Length(base64.Length));
- OperationStatus status = Base64.DecodeFromUtf8(base64, bytes.AsSpan(), out int bytesConsumed, out int bytesWritten);
- if (status != OperationStatus.Done || bytesWritten % sizeof(float) != 0)
- {
- ThrowInvalidData();
- }
-
- // Interpret bytes as floats
- float[] vector = new float[bytesWritten / sizeof(float)];
- bytes.AsSpan(0, bytesWritten).CopyTo(MemoryMarshal.AsBytes(vector.AsSpan()));
- if (!BitConverter.IsLittleEndian)
- {
- Span ints = MemoryMarshal.Cast(vector.AsSpan());
-#if NET
- BinaryPrimitives.ReverseEndianness(ints, ints);
-#else
- for (int i = 0; i < ints.Length; i++)
- {
- ints[i] = BinaryPrimitives.ReverseEndianness(ints[i]);
- }
-#endif
- }
-
- ArrayPool.Shared.Return(bytes);
- return vector;
-
- static void ThrowInvalidData() =>
- throw new FormatException("The input is not a valid Base64 string of encoded floats.");
- }
-
- /// Converts an extensions options instance to an Azure.AI.Inference options instance.
- private EmbeddingsOptions ToAzureAIOptions(IEnumerable inputs, EmbeddingGenerationOptions? options)
- {
- if (options?.RawRepresentationFactory?.Invoke(this) is not EmbeddingsOptions result)
- {
- result = new EmbeddingsOptions(inputs);
- }
- else
- {
- foreach (var input in inputs)
- {
- result.Input.Add(input);
- }
- }
-
- result.Dimensions ??= options?.Dimensions ?? _dimensions;
- result.Model ??= options?.ModelId ?? _metadata.DefaultModelId;
- result.EncodingFormat = EmbeddingEncodingFormat.Base64;
-
- if (options?.AdditionalProperties is { } props)
- {
- foreach (var prop in props)
- {
- if (prop.Value is not null)
- {
- byte[] data = JsonSerializer.SerializeToUtf8Bytes(prop.Value, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
- result.AdditionalProperties[prop.Key] = new BinaryData(data);
- }
- }
- }
-
- return result;
- }
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceExtensions.cs
deleted file mode 100644
index 40d2932dd08..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceExtensions.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Azure.AI.Inference;
-
-namespace Microsoft.Extensions.AI;
-
-/// Provides extension methods for working with Azure AI Inference.
-public static class AzureAIInferenceExtensions
-{
- /// Gets an for use with this .
- /// The client.
- /// The ID of the model to use. If , it can be provided per request via .
- /// An that can be used to converse via the .
- public static IChatClient AsIChatClient(
- this ChatCompletionsClient chatCompletionsClient, string? modelId = null) =>
- new AzureAIInferenceChatClient(chatCompletionsClient, modelId);
-
- /// Gets an for use with this .
- /// The client.
- /// The ID of the model to use. If , it can be provided per request via .
- /// The number of dimensions generated in each embedding.
- /// An that can be used to generate embeddings via the .
- public static IEmbeddingGenerator> AsIEmbeddingGenerator(
- this EmbeddingsClient embeddingsClient, string? defaultModelId = null, int? defaultModelDimensions = null) =>
- new AzureAIInferenceEmbeddingGenerator(embeddingsClient, defaultModelId, defaultModelDimensions);
-
- /// Gets an for use with this .
- /// The client.
- /// The ID of the model to use. If , it can be provided per request via .
- /// The number of dimensions generated in each embedding.
- /// An that can be used to generate embeddings via the .
- public static IEmbeddingGenerator> AsIEmbeddingGenerator(
- this ImageEmbeddingsClient imageEmbeddingsClient, string? defaultModelId = null, int? defaultModelDimensions = null) =>
- new AzureAIInferenceImageEmbeddingGenerator(imageEmbeddingsClient, defaultModelId, defaultModelDimensions);
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceImageEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceImageEmbeddingGenerator.cs
deleted file mode 100644
index b04a7c73a39..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceImageEmbeddingGenerator.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-// 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.Linq;
-using System.Reflection;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using Azure.AI.Inference;
-using Microsoft.Shared.Diagnostics;
-
-#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
-
-namespace Microsoft.Extensions.AI;
-
-/// Represents an for an Azure.AI.Inference .
-internal sealed class AzureAIInferenceImageEmbeddingGenerator :
- IEmbeddingGenerator>
-{
- /// Metadata about the embedding generator.
- private readonly EmbeddingGeneratorMetadata _metadata;
-
- /// The underlying .
- private readonly ImageEmbeddingsClient _imageEmbeddingsClient;
-
- /// The number of dimensions produced by the generator.
- private readonly int? _dimensions;
-
- /// Initializes a new instance of the class.
- /// The underlying client.
- ///
- /// The ID of the model to use. This can also be overridden per request via .
- /// Either this parameter or must provide a valid model ID.
- ///
- /// The number of dimensions to generate in each embedding.
- /// is .
- /// is empty or composed entirely of whitespace.
- /// is not positive.
- public AzureAIInferenceImageEmbeddingGenerator(
- ImageEmbeddingsClient imageEmbeddingsClient, string? defaultModelId = null, int? defaultModelDimensions = null)
- {
- _ = Throw.IfNull(imageEmbeddingsClient);
-
- if (defaultModelId is not null)
- {
- _ = Throw.IfNullOrWhitespace(defaultModelId);
- }
-
- if (defaultModelDimensions is < 1)
- {
- Throw.ArgumentOutOfRangeException(nameof(defaultModelDimensions), "Value must be greater than 0.");
- }
-
- _imageEmbeddingsClient = imageEmbeddingsClient;
- _dimensions = defaultModelDimensions;
-
- // https://github.com/Azure/azure-sdk-for-net/issues/46278
- // The endpoint isn't currently exposed, so use reflection to get at it, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- var providerUrl = typeof(ImageEmbeddingsClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(imageEmbeddingsClient) as Uri;
-
- _metadata = new EmbeddingGeneratorMetadata("azure.ai.inference", providerUrl, defaultModelId, defaultModelDimensions);
- }
-
- ///
- object? IEmbeddingGenerator.GetService(Type serviceType, object? serviceKey)
- {
- _ = Throw.IfNull(serviceType);
-
- return
- serviceKey is not null ? null :
- serviceType == typeof(ImageEmbeddingsClient) ? _imageEmbeddingsClient :
- serviceType == typeof(EmbeddingGeneratorMetadata) ? _metadata :
- serviceType.IsInstanceOfType(this) ? this :
- null;
- }
-
- ///
- public async Task>> GenerateAsync(
- IEnumerable values, EmbeddingGenerationOptions? options = null, CancellationToken cancellationToken = default)
- {
- _ = Throw.IfNull(values);
-
- var azureAIOptions = ToAzureAIOptions(values, options);
-
- var embeddings = (await _imageEmbeddingsClient.EmbedAsync(azureAIOptions, cancellationToken).ConfigureAwait(false)).Value;
-
- GeneratedEmbeddings> result = new(embeddings.Data.Select(e =>
- new Embedding(AzureAIInferenceEmbeddingGenerator.ParseBase64Floats(e.Embedding))
- {
- CreatedAt = DateTimeOffset.UtcNow,
- ModelId = embeddings.Model ?? azureAIOptions.Model,
- }));
-
- if (embeddings.Usage is not null)
- {
- result.Usage = new()
- {
- InputTokenCount = embeddings.Usage.PromptTokens,
- TotalTokenCount = embeddings.Usage.TotalTokens
- };
- }
-
- return result;
- }
-
- ///
- void IDisposable.Dispose()
- {
- // Nothing to dispose. Implementation required for the IEmbeddingGenerator interface.
- }
-
- /// Converts an extensions options instance to an Azure.AI.Inference options instance.
- private ImageEmbeddingsOptions ToAzureAIOptions(IEnumerable inputs, EmbeddingGenerationOptions? options)
- {
- IEnumerable imageEmbeddingInputs = inputs.Select(dc => new ImageEmbeddingInput(dc.Uri));
- if (options?.RawRepresentationFactory?.Invoke(this) is not ImageEmbeddingsOptions result)
- {
- result = new ImageEmbeddingsOptions(imageEmbeddingInputs);
- }
- else
- {
- foreach (var input in imageEmbeddingInputs)
- {
- result.Input.Add(input);
- }
- }
-
- result.Dimensions ??= options?.Dimensions ?? _dimensions;
- result.Model ??= options?.ModelId ?? _metadata.DefaultModelId;
- result.EncodingFormat = EmbeddingEncodingFormat.Base64;
-
- if (options?.AdditionalProperties is { } props)
- {
- foreach (var prop in props)
- {
- if (prop.Value is not null)
- {
- byte[] data = JsonSerializer.SerializeToUtf8Bytes(prop.Value, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
- result.AdditionalProperties[prop.Key] = new BinaryData(data);
- }
- }
- }
-
- return result;
- }
-}
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/CHANGELOG.md b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/CHANGELOG.md
deleted file mode 100644
index a28e211d427..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/CHANGELOG.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# Microsoft.Extensions.AI.AzureAIInference Release History
-
-## NOT YET RELEASED
-
-- Fixed package references for net10.0 asset.
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-
-## 10.0.0-preview.1.25560.10
-
-- Updated .NET dependencies to 10.0.0 versions.
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.10.1-preview.1.25521.4
-
-- No changes.
-
-## 9.10.0-preview.1.25513.3
-
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.9.1-preview.1.25474.6
-
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.9.0-preview.1.25458.4
-
-- Updated tool mapping to recognize any `AIFunctionDeclaration`.
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-- Updated `AsIChatClient` for `OpenAIResponseClient` to support reasoning content with `GetStreamingResponseAsync`.
-
-## 9.8.0-preview.1.25412.6
-
-- Updated to depend on Azure.AI.Inference 1.0.0-beta.5.
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.7.0-preview.1.25356.2
-
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.6.0-preview.1.25310.2
-
-- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.5.0-preview.1.25265.7
-
-- Added `AsIEmbeddingGenerator` for Azure.AI.Inference `ImageEmbeddingsClient`.
-- Updated to accommodate the changes in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.4.4-preview.1.25259.16
-
-- Added an `AsIEmbeddingGenerator` extension method for `ImageEmbeddingsClient`.
-- Updated to accommodate the changes in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.4.3-preview.1.25230.7
-
-- Updated to accommodate the changes in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.4.0-preview.1.25207.5
-
-- Updated to Azure.AI.Inference 1.0.0-beta.4.
-- Renamed `AsChatClient`/`AsEmbeddingGenerator` extension methods to `AsIChatClient`/`AsIEmbeddingGenerator`.
-- Removed the public `AzureAIInferenceChatClient`/`AzureAIInferenceEmbeddingGenerator` types. These are only created now via the extension methods.
-- Updated to accommodate the changes in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.3.0-preview.1.25161.3
-
-- Updated to accommodate the changes in `Microsoft.Extensions.AI.Abstractions`.
-
-## 9.3.0-preview.1.25114.11
-
-- Updated to use Azure.AI.Inference 1.0.0-beta.3, adding support for structured output and audio input.
-
-## 9.1.0-preview.1.25064.3
-
-- Fixed handling of text-only user messages.
-
-## 9.0.1-preview.1.24570.5
-
- - Made the `ToolCallJsonSerializerOptions` property non-nullable.
-
-## 9.0.0-preview.9.24556.5
-
-- Fixed `AzureAIInferenceEmbeddingGenerator` to respect `EmbeddingGenerationOptions.Dimensions`.
-
-## 9.0.0-preview.9.24525.1
-
-- Lowered the required version of System.Text.Json to 8.0.5 when targeting net8.0 or older.
-- Updated to use Azure.AI.Inference 1.0.0-beta.2.
-- Added `AzureAIInferenceEmbeddingGenerator` and corresponding `AsEmbeddingGenerator` extension method.
-- Improved handling of assistant messages that include both text and function call content.
-
-## 9.0.0-preview.9.24507.7
-
-- Initial Preview
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/JsonContext.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/JsonContext.cs
deleted file mode 100644
index 89e0946d306..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/JsonContext.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Microsoft.Extensions.AI;
-
-/// Source-generated JSON type information.
-[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
- UseStringEnumConverter = true,
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- WriteIndented = true)]
-[JsonSerializable(typeof(AzureAIChatToolJson))]
-internal sealed partial class JsonContext : JsonSerializerContext;
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj
deleted file mode 100644
index ade32d3026c..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
- Microsoft.Extensions.AI
- Implementation of generative AI abstractions for Azure.AI.Inference.
- AI
- true
-
-
-
- preview
- false
- 86
- 0
-
-
-
- $(TargetFrameworks);netstandard2.0
- $(NoWarn);CA1063
- true
- true
-
-
-
- true
- true
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.json b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.json
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/README.md b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/README.md
deleted file mode 100644
index 18a8b2c8ea2..00000000000
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/README.md
+++ /dev/null
@@ -1,290 +0,0 @@
-# Microsoft.Extensions.AI.AzureAIInference
-
-Provides an implementation of the `IChatClient` interface for the `Azure.AI.Inference` package.
-
-## Install the package
-
-From the command-line:
-
-```console
-dotnet add package Microsoft.Extensions.AI.AzureAIInference
-```
-
-Or directly in the C# project file:
-
-```xml
-
-
-
-```
-
-## Usage Examples
-
-### Chat
-
-```csharp
-using Azure;
-using Microsoft.Extensions.AI;
-
-IChatClient client =
- new Azure.AI.Inference.ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!))
- .AsIChatClient("gpt-4o-mini");
-
-Console.WriteLine(await client.GetResponseAsync("What is AI?"));
-```
-
-> **Note:** When connecting with Azure Open AI, the URL passed into the `ChatCompletionsClient` needs to include `openai/deployments/{yourDeployment}`. For example:
-> ```csharp
-> new Azure.AI.Inference.ChatCompletionsClient(
-> new("https://{your-resource-name}.openai.azure.com/openai/deployments/{yourDeployment}"),
-> new AzureKeyCredential(Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY")!))
-> ```
-
-### Chat + Conversation History
-
-```csharp
-using Azure;
-using Microsoft.Extensions.AI;
-
-IChatClient client =
- new Azure.AI.Inference.ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!))
- .AsIChatClient("gpt-4o-mini");
-
-Console.WriteLine(await client.GetResponseAsync(
-[
- new ChatMessage(ChatRole.System, "You are a helpful AI assistant"),
- new ChatMessage(ChatRole.User, "What is AI?"),
-]));
-```
-
-### Chat streaming
-
-```csharp
-using Azure;
-using Microsoft.Extensions.AI;
-
-IChatClient client =
- new Azure.AI.Inference.ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!))
- .AsIChatClient("gpt-4o-mini");
-
-await foreach (var update in client.GetStreamingResponseAsync("What is AI?"))
-{
- Console.Write(update);
-}
-```
-
-### Tool calling
-
-```csharp
-using System.ComponentModel;
-using Azure;
-using Microsoft.Extensions.AI;
-
-IChatClient azureClient =
- new Azure.AI.Inference.ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!))
- .AsIChatClient("gpt-4o-mini");
-
-IChatClient client = new ChatClientBuilder(azureClient)
- .UseFunctionInvocation()
- .Build();
-
-ChatOptions chatOptions = new()
-{
- Tools = [AIFunctionFactory.Create(GetWeather)]
-};
-
-await foreach (var message in client.GetStreamingResponseAsync("Do I need an umbrella?", chatOptions))
-{
- Console.Write(message);
-}
-
-[Description("Gets the weather")]
-static string GetWeather() => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining";
-```
-
-### Caching
-
-```csharp
-using Azure;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.Options;
-
-IDistributedCache cache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()));
-
-IChatClient azureClient =
- new Azure.AI.Inference.ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!))
- .AsIChatClient("gpt-4o-mini");
-
-IChatClient client = new ChatClientBuilder(azureClient)
- .UseDistributedCache(cache)
- .Build();
-
-for (int i = 0; i < 3; i++)
-{
- await foreach (var message in client.GetStreamingResponseAsync("In less than 100 words, what is AI?"))
- {
- Console.Write(message);
- }
-
- Console.WriteLine();
- Console.WriteLine();
-}
-```
-
-### Telemetry
-
-```csharp
-using Azure;
-using Microsoft.Extensions.AI;
-using OpenTelemetry.Trace;
-
-// Configure OpenTelemetry exporter
-var sourceName = Guid.NewGuid().ToString();
-var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
- .AddSource(sourceName)
- .AddConsoleExporter()
- .Build();
-
-IChatClient azureClient =
- new Azure.AI.Inference.ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!))
- .AsIChatClient("gpt-4o-mini");
-
-IChatClient client = new ChatClientBuilder(azureClient)
- .UseOpenTelemetry(sourceName: sourceName, configure: c => c.EnableSensitiveData = true)
- .Build();
-
-Console.WriteLine(await client.GetResponseAsync("What is AI?"));
-```
-
-### Telemetry, Caching, and Tool Calling
-
-```csharp
-using System.ComponentModel;
-using Azure;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.Options;
-using OpenTelemetry.Trace;
-
-// Configure telemetry
-var sourceName = Guid.NewGuid().ToString();
-var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
- .AddSource(sourceName)
- .AddConsoleExporter()
- .Build();
-
-// Configure caching
-IDistributedCache cache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()));
-
-// Configure tool calling
-var chatOptions = new ChatOptions
-{
- Tools = [AIFunctionFactory.Create(GetPersonAge)]
-};
-
-IChatClient azureClient =
- new Azure.AI.Inference.ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!))
- .AsIChatClient("gpt-4o-mini");
-
-IChatClient client = new ChatClientBuilder(azureClient)
- .UseDistributedCache(cache)
- .UseFunctionInvocation()
- .UseOpenTelemetry(sourceName: sourceName, configure: c => c.EnableSensitiveData = true)
- .Build();
-
-for (int i = 0; i < 3; i++)
-{
- Console.WriteLine(await client.GetResponseAsync("How much older is Alice than Bob?", chatOptions));
-}
-
-[Description("Gets the age of a person specified by name.")]
-static int GetPersonAge(string personName) =>
- personName switch
- {
- "Alice" => 42,
- "Bob" => 35,
- _ => 26,
- };
-```
-
-### Dependency Injection
-
-```csharp
-using Azure;
-using Azure.AI.Inference;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
-
-// App Setup
-var builder = Host.CreateApplicationBuilder();
-builder.Services.AddSingleton(
- new ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(Environment.GetEnvironmentVariable("GH_TOKEN")!)));
-builder.Services.AddDistributedMemoryCache();
-builder.Services.AddLogging(b => b.AddConsole().SetMinimumLevel(LogLevel.Trace));
-
-builder.Services.AddChatClient(services => services.GetRequiredService().AsIChatClient("gpt-4o-mini"))
- .UseDistributedCache()
- .UseLogging();
-
-var app = builder.Build();
-
-// Elsewhere in the app
-var chatClient = app.Services.GetRequiredService();
-Console.WriteLine(await chatClient.GetResponseAsync("What is AI?"));
-```
-
-### Minimal Web API
-
-```csharp
-using Azure;
-using Azure.AI.Inference;
-using Microsoft.Extensions.AI;
-
-var builder = WebApplication.CreateBuilder(args);
-
-builder.Services.AddSingleton(new ChatCompletionsClient(
- new("https://models.inference.ai.azure.com"),
- new AzureKeyCredential(builder.Configuration["GH_TOKEN"]!)));
-
-builder.Services.AddChatClient(services =>
- services.GetRequiredService().AsIChatClient("gpt-4o-mini"));
-
-var app = builder.Build();
-
-app.MapPost("/chat", async (IChatClient client, string message) =>
-{
- var response = await client.GetResponseAsync(message);
- return response.Message;
-});
-
-app.Run();
-```
-
-## Documentation
-
-Refer to the [Microsoft.Extensions.AI libraries documentation](https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai) for more information and API usage examples.
-
-## Feedback & Contributing
-
-We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions).
diff --git a/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientIntegrationTests.cs
deleted file mode 100644
index a5f78eef135..00000000000
--- a/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientIntegrationTests.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.Extensions.AI;
-
-public class AzureAIInferenceChatClientIntegrationTests : ChatClientIntegrationTests
-{
- protected override IChatClient? CreateChatClient() =>
- IntegrationTestHelpers.GetChatCompletionsClient()
- ?.AsIChatClient(TestRunnerConfiguration.Instance["AzureAIInference:ChatModel"] ?? "gpt-4o-mini");
-}
diff --git a/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs
deleted file mode 100644
index 1431f5096f5..00000000000
--- a/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs
+++ /dev/null
@@ -1,1370 +0,0 @@
-// 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.ComponentModel;
-using System.Linq;
-using System.Net.Http;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Threading.Tasks;
-using Azure;
-using Azure.AI.Inference;
-using Azure.Core.Pipeline;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Caching.Memory;
-using Xunit;
-
-#pragma warning disable S103 // Lines should not be too long
-#pragma warning disable S3358 // Ternary operators should not be nested
-#pragma warning disable SA1204 // Static elements should appear before instance elements
-
-namespace Microsoft.Extensions.AI;
-
-public class AzureAIInferenceChatClientTests
-{
- [Fact]
- public void AsIChatClient_InvalidArgs_Throws()
- {
- Assert.Throws("chatCompletionsClient", () => ((ChatCompletionsClient)null!).AsIChatClient("model"));
-
- ChatCompletionsClient client = new(new("http://somewhere"), new AzureKeyCredential("key"));
- Assert.Throws("defaultModelId", () => client.AsIChatClient(" "));
- }
-
- [Fact]
- public async Task NullModel_Throws()
- {
- ChatCompletionsClient client = new(new("http://localhost/some/endpoint"), new AzureKeyCredential("key"));
- IChatClient chatClient = client.AsIChatClient(modelId: null);
-
- await Assert.ThrowsAsync(() => chatClient.GetResponseAsync("hello"));
- await Assert.ThrowsAsync(() => chatClient.GetStreamingResponseAsync("hello").GetAsyncEnumerator().MoveNextAsync().AsTask());
-
- await Assert.ThrowsAsync(() => chatClient.GetResponseAsync("hello", new ChatOptions { ModelId = null }));
- await Assert.ThrowsAsync(() => chatClient.GetStreamingResponseAsync("hello", new ChatOptions { ModelId = null }).GetAsyncEnumerator().MoveNextAsync().AsTask());
- }
-
- [Fact]
- public void AsIChatClient_ProducesExpectedMetadata()
- {
- Uri endpoint = new("http://localhost/some/endpoint");
- string model = "amazingModel";
-
- ChatCompletionsClient client = new(endpoint, new AzureKeyCredential("key"));
-
- IChatClient chatClient = client.AsIChatClient(model);
- var metadata = chatClient.GetService();
- Assert.Equal("azure.ai.inference", metadata?.ProviderName);
- Assert.Equal(endpoint, metadata?.ProviderUri);
- Assert.Equal(model, metadata?.DefaultModelId);
- }
-
- [Fact]
- public void GetService_SuccessfullyReturnsUnderlyingClient()
- {
- ChatCompletionsClient client = new(new("http://localhost"), new AzureKeyCredential("key"));
- IChatClient chatClient = client.AsIChatClient("model");
-
- Assert.Same(chatClient, chatClient.GetService());
- Assert.Same(client, chatClient.GetService());
-
- using IChatClient pipeline = chatClient
- .AsBuilder()
- .UseFunctionInvocation()
- .UseOpenTelemetry()
- .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions())))
- .Build();
-
- Assert.NotNull(pipeline.GetService());
- Assert.NotNull(pipeline.GetService());
- Assert.NotNull(pipeline.GetService());
- Assert.NotNull(pipeline.GetService());
- Assert.NotNull(pipeline.GetService