Skip to content

Commit ef606f2

Browse files
authored
Add AdditionalPropertiesDictionary.TryGetValue<T> (#5528)
* Add AdditionalPropertiesDictionary.TryGetValue<T> * Update comments
1 parent 87308c7 commit ef606f2

File tree

16 files changed

+119
-118
lines changed

16 files changed

+119
-118
lines changed

eng/MSBuild/Shared.props

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
<Project>
2-
<ItemGroup Condition="'$(InjectSharedCollectionExtensions)' == 'true'">
3-
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\CollectionExtensions\*.cs" LinkBase="Shared\CollectionExtensions" />
4-
</ItemGroup>
5-
62
<ItemGroup Condition="'$(InjectSharedDiagnosticIds)' == 'true'">
73
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\DiagnosticIds\*.cs" LinkBase="Shared\DiagnosticIds" />
84
</ItemGroup>

src/Libraries/Microsoft.Extensions.AI.Abstractions/AdditionalPropertiesDictionary.cs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Globalization;
79
using System.Linq;
810

911
namespace Microsoft.Extensions.AI;
@@ -45,7 +47,7 @@ public AdditionalPropertiesDictionary(IEnumerable<KeyValuePair<string, object?>>
4547
/// A shallow clone of the properties dictionary. The instance will not be the same as the current instance,
4648
/// but it will contain all of the same key-value pairs.
4749
/// </returns>
48-
public AdditionalPropertiesDictionary Clone() => new AdditionalPropertiesDictionary(_dictionary);
50+
public AdditionalPropertiesDictionary Clone() => new(_dictionary);
4951

5052
/// <inheritdoc />
5153
public object? this[string key]
@@ -94,6 +96,9 @@ public object? this[string key]
9496
/// <inheritdoc />
9597
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() => _dictionary.GetEnumerator();
9698

99+
/// <inheritdoc />
100+
IEnumerator IEnumerable.GetEnumerator() => _dictionary.GetEnumerator();
101+
97102
/// <inheritdoc />
98103
public bool Remove(string key) => _dictionary.Remove(key);
99104

@@ -103,6 +108,52 @@ public object? this[string key]
103108
/// <inheritdoc />
104109
public bool TryGetValue(string key, out object? value) => _dictionary.TryGetValue(key, out value);
105110

106-
/// <inheritdoc />
107-
IEnumerator IEnumerable.GetEnumerator() => _dictionary.GetEnumerator();
111+
/// <summary>Attempts to extract a typed value from the dictionary.</summary>
112+
/// <typeparam name="T">Specifies the type of the value to be retrieved.</typeparam>
113+
/// <param name="key">The key to locate.</param>
114+
/// <param name="value">
115+
/// The value retrieved from the dictionary, if found and successfully converted to the requested type;
116+
/// otherwise, the default value of <typeparamref name="T"/>.
117+
/// </param>
118+
/// <returns>
119+
/// <see langword="true"/> if a non-<see langword="null"/> value was found for <paramref name="key"/>
120+
/// in the dictionary and converted to the requested type; otherwise, <see langword="false"/>.
121+
/// </returns>
122+
/// <remarks>
123+
/// If a non-<see langword="null"/> is found for the key in the dictionary, but the value is not of the requested type but is
124+
/// an <see cref="IConvertible"/> object, the method will attempt to convert the object to the requested type.
125+
/// </remarks>
126+
public bool TryGetValue<T>(string key, [NotNullWhen(true)] out T? value)
127+
{
128+
if (TryGetValue(key, out object? obj))
129+
{
130+
switch (obj)
131+
{
132+
case T t:
133+
// The object is already of the requested type. Return it.
134+
value = t;
135+
return true;
136+
137+
case IConvertible:
138+
// The object is convertible; try to convert it to the requested type. Unfortunately, there's no
139+
// convenient way to do this that avoids exceptions and that doesn't involve a ton of boilerplate,
140+
// so we only try when the source object is at least an IConvertible, which is what ChangeType uses.
141+
try
142+
{
143+
value = (T)Convert.ChangeType(obj, typeof(T), CultureInfo.InvariantCulture);
144+
return true;
145+
}
146+
catch (Exception e) when (e is ArgumentException or FormatException or InvalidCastException or OverflowException)
147+
{
148+
// Ignore known failure modes.
149+
}
150+
151+
break;
152+
}
153+
}
154+
155+
// Unable to find the value or convert it to the requested type.
156+
value = default;
157+
return false;
158+
}
108159
}

src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
</PropertyGroup>
2020

2121
<PropertyGroup>
22-
<InjectSharedCollectionExtensions>true</InjectSharedCollectionExtensions>
2322
<InjectSharedEmptyCollections>true</InjectSharedEmptyCollections>
2423
<InjectStringHashOnLegacy>true</InjectStringHashOnLegacy>
2524
<InjectStringSyntaxAttributeOnLegacy>true</InjectStringSyntaxAttributeOnLegacy>

src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
<PropertyGroup>
2222
<InjectCompilerFeatureRequiredOnLegacy>true</InjectCompilerFeatureRequiredOnLegacy>
23-
<InjectSharedCollectionExtensions>true</InjectSharedCollectionExtensions>
2423
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
2524
<InjectSharedEmptyCollections>true</InjectSharedEmptyCollections>
2625
<InjectStringHashOnLegacy>true</InjectStringHashOnLegacy>

src/Libraries/Microsoft.Extensions.AI.Ollama/Microsoft.Extensions.AI.Ollama.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
<PropertyGroup>
2222
<InjectCompilerFeatureRequiredOnLegacy>true</InjectCompilerFeatureRequiredOnLegacy>
2323
<InjectRequiredMemberOnLegacy>true</InjectRequiredMemberOnLegacy>
24-
<InjectSharedCollectionExtensions>true</InjectSharedCollectionExtensions>
2524
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
2625
<InjectSharedEmptyCollections>true</InjectSharedEmptyCollections>
2726
<InjectStringHashOnLegacy>true</InjectStringHashOnLegacy>

src/Libraries/Microsoft.Extensions.AI.Ollama/OllamaChatClient.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System.Text.Json;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14-
using Microsoft.Shared.Collections;
1514
using Microsoft.Shared.Diagnostics;
1615

1716
#pragma warning disable EA0011 // Consider removing unnecessary conditional access operator (?)
@@ -298,7 +297,7 @@ private OllamaChatRequest ToOllamaChatRequest(IList<ChatMessage> chatMessages, C
298297

299298
void TransferMetadataValue<T>(string propertyName, Action<OllamaRequestOptions, T> setOption)
300299
{
301-
if (options.AdditionalProperties?.TryGetConvertedValue(propertyName, out T? t) is true)
300+
if (options.AdditionalProperties?.TryGetValue(propertyName, out T? t) is true)
302301
{
303302
request.Options ??= new();
304303
setOption(request.Options, t);

src/Libraries/Microsoft.Extensions.AI.Ollama/OllamaEmbeddingGenerator.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.Net.Http.Json;
99
using System.Threading;
1010
using System.Threading.Tasks;
11-
using Microsoft.Shared.Collections;
1211
using Microsoft.Shared.Diagnostics;
1312

1413
namespace Microsoft.Extensions.AI;
@@ -75,12 +74,12 @@ public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(IEnumerab
7574

7675
if (options?.AdditionalProperties is { } requestProps)
7776
{
78-
if (requestProps.TryGetConvertedValue("keep_alive", out long keepAlive))
77+
if (requestProps.TryGetValue("keep_alive", out long keepAlive))
7978
{
8079
request.KeepAlive = keepAlive;
8180
}
8281

83-
if (requestProps.TryGetConvertedValue("truncate", out bool truncate))
82+
if (requestProps.TryGetValue("truncate", out bool truncate))
8483
{
8584
request.Truncate = truncate;
8685
}

src/Libraries/Microsoft.Extensions.AI.OpenAI/Microsoft.Extensions.AI.OpenAI.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
</PropertyGroup>
2020

2121
<PropertyGroup>
22-
<InjectSharedCollectionExtensions>true</InjectSharedCollectionExtensions>
2322
<InjectSharedEmptyCollections>true</InjectSharedEmptyCollections>
2423
<InjectStringHashOnLegacy>true</InjectStringHashOnLegacy>
2524
</PropertyGroup>

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System.Text.Json.Serialization;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14-
using Microsoft.Shared.Collections;
1514
using Microsoft.Shared.Diagnostics;
1615
using OpenAI;
1716
using OpenAI.Chat;
@@ -410,37 +409,37 @@ private ChatCompletionOptions ToOpenAIOptions(ChatOptions? options)
410409

411410
if (options.AdditionalProperties is { Count: > 0 } additionalProperties)
412411
{
413-
if (additionalProperties.TryGetConvertedValue(nameof(result.EndUserId), out string? endUserId))
412+
if (additionalProperties.TryGetValue(nameof(result.EndUserId), out string? endUserId))
414413
{
415414
result.EndUserId = endUserId;
416415
}
417416

418-
if (additionalProperties.TryGetConvertedValue(nameof(result.IncludeLogProbabilities), out bool includeLogProbabilities))
417+
if (additionalProperties.TryGetValue(nameof(result.IncludeLogProbabilities), out bool includeLogProbabilities))
419418
{
420419
result.IncludeLogProbabilities = includeLogProbabilities;
421420
}
422421

423-
if (additionalProperties.TryGetConvertedValue(nameof(result.LogitBiases), out IDictionary<int, int>? logitBiases))
422+
if (additionalProperties.TryGetValue(nameof(result.LogitBiases), out IDictionary<int, int>? logitBiases))
424423
{
425424
foreach (KeyValuePair<int, int> kvp in logitBiases!)
426425
{
427426
result.LogitBiases[kvp.Key] = kvp.Value;
428427
}
429428
}
430429

431-
if (additionalProperties.TryGetConvertedValue(nameof(result.AllowParallelToolCalls), out bool allowParallelToolCalls))
430+
if (additionalProperties.TryGetValue(nameof(result.AllowParallelToolCalls), out bool allowParallelToolCalls))
432431
{
433432
result.AllowParallelToolCalls = allowParallelToolCalls;
434433
}
435434

436435
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
437-
if (additionalProperties.TryGetConvertedValue(nameof(result.Seed), out long seed))
436+
if (additionalProperties.TryGetValue(nameof(result.Seed), out long seed))
438437
{
439438
result.Seed = seed;
440439
}
441440
#pragma warning restore OPENAI001
442441

443-
if (additionalProperties.TryGetConvertedValue(nameof(result.TopLogProbabilityCount), out int topLogProbabilityCountInt))
442+
if (additionalProperties.TryGetValue(nameof(result.TopLogProbabilityCount), out int topLogProbabilityCountInt))
444443
{
445444
result.TopLogProbabilityCount = topLogProbabilityCountInt;
446445
}
@@ -488,7 +487,10 @@ private ChatCompletionOptions ToOpenAIOptions(ChatOptions? options)
488487
/// <summary>Converts an Extensions function to an OpenAI chat tool.</summary>
489488
private ChatTool ToOpenAIChatTool(AIFunction aiFunction)
490489
{
491-
_ = aiFunction.Metadata.AdditionalProperties.TryGetConvertedValue("Strict", out bool strict);
490+
bool? strict =
491+
aiFunction.Metadata.AdditionalProperties.TryGetValue("Strict", out object? strictObj) &&
492+
strictObj is bool strictValue ?
493+
strictValue : null;
492494

493495
BinaryData resultParameters = OpenAIChatToolJson.ZeroFunctionParametersSchema;
494496

@@ -643,7 +645,7 @@ private sealed class OpenAIChatToolJson
643645
new(toolCalls.Values) { ParticipantName = input.AuthorName } :
644646
new(input.Text) { ParticipantName = input.AuthorName };
645647

646-
if (input.AdditionalProperties?.TryGetConvertedValue(nameof(message.Refusal), out string? refusal) is true)
648+
if (input.AdditionalProperties?.TryGetValue(nameof(message.Refusal), out string? refusal) is true)
647649
{
648650
message.Refusal = refusal;
649651
}

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Reflection;
88
using System.Threading;
99
using System.Threading.Tasks;
10-
using Microsoft.Shared.Collections;
1110
using Microsoft.Shared.Diagnostics;
1211
using OpenAI;
1312
using OpenAI.Embeddings;
@@ -144,12 +143,12 @@ void IDisposable.Dispose()
144143
if (options?.AdditionalProperties is { Count: > 0 } additionalProperties)
145144
{
146145
// Allow per-instance dimensions to be overridden by a per-call property
147-
if (additionalProperties.TryGetConvertedValue(nameof(openAIOptions.Dimensions), out int? dimensions))
146+
if (additionalProperties.TryGetValue(nameof(openAIOptions.Dimensions), out int? dimensions))
148147
{
149148
openAIOptions.Dimensions = dimensions;
150149
}
151150

152-
if (additionalProperties.TryGetConvertedValue(nameof(openAIOptions.EndUserId), out string? endUserId))
151+
if (additionalProperties.TryGetValue(nameof(openAIOptions.EndUserId), out string? endUserId))
153152
{
154153
openAIOptions.EndUserId = endUserId;
155154
}

0 commit comments

Comments
 (0)