Skip to content

Commit aba6fd6

Browse files
authored
Make IChatClient/IEmbeddingGenerator.GetService non-generic (#5608)
1 parent 8b320e2 commit aba6fd6

File tree

19 files changed

+173
-59
lines changed

19 files changed

+173
-59
lines changed

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatClientExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,22 @@ namespace Microsoft.Extensions.AI;
1111
/// <summary>Provides a collection of static methods for extending <see cref="IChatClient"/> instances.</summary>
1212
public static class ChatClientExtensions
1313
{
14+
/// <summary>Asks the <see cref="IChatClient"/> for an object of type <typeparamref name="TService"/>.</summary>
15+
/// <typeparam name="TService">The type of the object to be retrieved.</typeparam>
16+
/// <param name="client">The client.</param>
17+
/// <param name="serviceKey">An optional key that may be used to help identify the target service.</param>
18+
/// <returns>The found object, otherwise <see langword="null"/>.</returns>
19+
/// <remarks>
20+
/// The purpose of this method is to allow for the retrieval of strongly-typed services that may be provided by the <see cref="IChatClient"/>,
21+
/// including itself or any services it might be wrapping.
22+
/// </remarks>
23+
public static TService? GetService<TService>(this IChatClient client, object? serviceKey = null)
24+
{
25+
_ = Throw.IfNull(client);
26+
27+
return (TService?)client.GetService(typeof(TService), serviceKey);
28+
}
29+
1430
/// <summary>Sends a user chat text message to the model and returns the response messages.</summary>
1531
/// <param name="client">The chat client.</param>
1632
/// <param name="chatMessage">The text content for the chat message to send.</param>

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/DelegatingChatClient.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@ public virtual IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreaming
6363
}
6464

6565
/// <inheritdoc />
66-
public virtual TService? GetService<TService>(object? key = null)
67-
where TService : class
66+
public virtual object? GetService(Type serviceType, object? serviceKey = null)
6867
{
69-
#pragma warning disable S3060 // "is" should not be used with "this"
70-
// If the key is non-null, we don't know what it means so pass through to the inner service
71-
return key is null && this is TService service ? service : InnerClient.GetService<TService>(key);
72-
#pragma warning restore S3060
68+
_ = Throw.IfNull(serviceType);
69+
70+
// If the key is non-null, we don't know what it means so pass through to the inner service.
71+
return
72+
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
73+
InnerClient.GetService(serviceType, serviceKey);
7374
}
7475
}

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/IChatClient.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,13 @@ IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
5656
/// <summary>Gets metadata that describes the <see cref="IChatClient"/>.</summary>
5757
ChatClientMetadata Metadata { get; }
5858

59-
/// <summary>Asks the <see cref="IChatClient"/> for an object of type <typeparamref name="TService"/>.</summary>
60-
/// <typeparam name="TService">The type of the object to be retrieved.</typeparam>
61-
/// <param name="key">An optional key that may be used to help identify the target service.</param>
59+
/// <summary>Asks the <see cref="IChatClient"/> for an object of the specified type <paramref name="serviceType"/>.</summary>
60+
/// <param name="serviceType">The type of object being requested.</param>
61+
/// <param name="serviceKey">An optional key that may be used to help identify the target service.</param>
6262
/// <returns>The found object, otherwise <see langword="null"/>.</returns>
6363
/// <remarks>
6464
/// The purpose of this method is to allow for the retrieval of strongly-typed services that may be provided by the <see cref="IChatClient"/>,
6565
/// including itself or any services it might be wrapping.
6666
/// </remarks>
67-
TService? GetService<TService>(object? key = null)
68-
where TService : class;
67+
object? GetService(Type serviceType, object? serviceKey = null);
6968
}

src/Libraries/Microsoft.Extensions.AI.Abstractions/Embeddings/DelegatingEmbeddingGenerator.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ public virtual Task<GeneratedEmbeddings<TEmbedding>> GenerateAsync(IEnumerable<T
5959
InnerGenerator.GenerateAsync(values, options, cancellationToken);
6060

6161
/// <inheritdoc />
62-
public virtual TService? GetService<TService>(object? key = null)
63-
where TService : class
62+
public virtual object? GetService(Type serviceType, object? serviceKey = null)
6463
{
65-
#pragma warning disable S3060 // "is" should not be used with "this"
66-
// If the key is non-null, we don't know what it means so pass through to the inner service
67-
return key is null && this is TService service ? service : InnerGenerator.GetService<TService>(key);
68-
#pragma warning restore S3060
64+
_ = Throw.IfNull(serviceType);
65+
66+
// If the key is non-null, we don't know what it means so pass through to the inner service.
67+
return
68+
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
69+
InnerGenerator.GetService(serviceType, serviceKey);
6970
}
7071
}

src/Libraries/Microsoft.Extensions.AI.Abstractions/Embeddings/EmbeddingGeneratorExtensions.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,43 @@ namespace Microsoft.Extensions.AI;
1515
/// <summary>Provides a collection of static methods for extending <see cref="IEmbeddingGenerator{TInput,TEmbedding}"/> instances.</summary>
1616
public static class EmbeddingGeneratorExtensions
1717
{
18+
/// <summary>Asks the <see cref="IEmbeddingGenerator{TInput,TEmbedding}"/> for an object of type <typeparamref name="TService"/>.</summary>
19+
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
20+
/// <typeparam name="TEmbedding">The numeric type of the embedding data.</typeparam>
21+
/// <typeparam name="TService">The type of the object to be retrieved.</typeparam>
22+
/// <param name="generator">The generator.</param>
23+
/// <param name="serviceKey">An optional key that may be used to help identify the target service.</param>
24+
/// <returns>The found object, otherwise <see langword="null"/>.</returns>
25+
/// <remarks>
26+
/// The purpose of this method is to allow for the retrieval of strongly-typed services that may be provided by the
27+
/// <see cref="IEmbeddingGenerator{TInput,TEmbedding}"/>, including itself or any services it might be wrapping.
28+
/// </remarks>
29+
public static TService? GetService<TInput, TEmbedding, TService>(this IEmbeddingGenerator<TInput, TEmbedding> generator, object? serviceKey = null)
30+
where TEmbedding : Embedding
31+
{
32+
_ = Throw.IfNull(generator);
33+
34+
return (TService?)generator.GetService(typeof(TService), serviceKey);
35+
}
36+
37+
// The following overload exists purely to work around the lack of partial generic type inference.
38+
// Given an IEmbeddingGenerator<TInput, TEmbedding> generator, to call GetService with TService, you still need
39+
// to re-specify both TInput and TEmbedding, e.g. generator.GetService<string, Embedding<float>, TService>.
40+
// The case of string/Embedding<float> is by far the most common case today, so this overload exists as an
41+
// accelerator to allow it to be written simply as generator.GetService<TService>.
42+
43+
/// <summary>Asks the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> for an object of type <typeparamref name="TService"/>.</summary>
44+
/// <typeparam name="TService">The type of the object to be retrieved.</typeparam>
45+
/// <param name="generator">The generator.</param>
46+
/// <param name="serviceKey">An optional key that may be used to help identify the target service.</param>
47+
/// <returns>The found object, otherwise <see langword="null"/>.</returns>
48+
/// <remarks>
49+
/// The purpose of this method is to allow for the retrieval of strongly-typed services that may be provided by the
50+
/// <see cref="IEmbeddingGenerator{TInput,TEmbedding}"/>, including itself or any services it might be wrapping.
51+
/// </remarks>
52+
public static TService? GetService<TService>(this IEmbeddingGenerator<string, Embedding<float>> generator, object? serviceKey = null) =>
53+
GetService<string, Embedding<float>, TService>(generator, serviceKey);
54+
1855
/// <summary>Generates an embedding vector from the specified <paramref name="value"/>.</summary>
1956
/// <typeparam name="TInput">The type from which embeddings will be generated.</typeparam>
2057
/// <typeparam name="TEmbedding">The numeric type of the embedding data.</typeparam>

src/Libraries/Microsoft.Extensions.AI.Abstractions/Embeddings/IEmbeddingGenerator.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,13 @@ Task<GeneratedEmbeddings<TEmbedding>> GenerateAsync(
4040
/// <summary>Gets metadata that describes the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/>.</summary>
4141
EmbeddingGeneratorMetadata Metadata { get; }
4242

43-
/// <summary>Asks the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> for an object of type <typeparamref name="TService"/>.</summary>
44-
/// <typeparam name="TService">The type of the object to be retrieved.</typeparam>
45-
/// <param name="key">An optional key that may be used to help identify the target service.</param>
43+
/// <summary>Asks the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> for an object of the specified type <paramref name="serviceType"/>.</summary>
44+
/// <param name="serviceType">The type of object being requested.</param>
45+
/// <param name="serviceKey">An optional key that may be used to help identify the target service.</param>
4646
/// <returns>The found object, otherwise <see langword="null"/>.</returns>
4747
/// <remarks>
48-
/// The purpose of this method is to allow for the retrieval of strongly-typed services that may be provided by the <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/>,
49-
/// including itself or any services it might be wrapping.
48+
/// The purpose of this method is to allow for the retrieval of strongly-typed services that may be provided by the
49+
/// <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/>, including itself or any services it might be wrapping.
5050
/// </remarks>
51-
TService? GetService<TService>(object? key = null)
52-
where TService : class;
51+
object? GetService(Type serviceType, object? serviceKey = null);
5352
}

src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,16 @@ public AzureAIInferenceChatClient(ChatCompletionsClient chatCompletionsClient, s
5757
public ChatClientMetadata Metadata { get; }
5858

5959
/// <inheritdoc />
60-
public TService? GetService<TService>(object? key = null)
61-
where TService : class =>
62-
typeof(TService) == typeof(ChatCompletionsClient) ? (TService?)(object?)_chatCompletionsClient :
63-
this as TService;
60+
public object? GetService(Type serviceType, object? serviceKey = null)
61+
{
62+
_ = Throw.IfNull(serviceType);
63+
64+
return
65+
serviceKey is not null ? null :
66+
serviceType == typeof(ChatCompletionsClient) ? _chatCompletionsClient :
67+
serviceType.IsInstanceOfType(this) ? this :
68+
null;
69+
}
6470

6571
/// <inheritdoc />
6672
public async Task<ChatCompletion> CompleteAsync(

src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceEmbeddingGenerator.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,16 @@ public AzureAIInferenceEmbeddingGenerator(
7070
public EmbeddingGeneratorMetadata Metadata { get; }
7171

7272
/// <inheritdoc />
73-
public TService? GetService<TService>(object? key = null)
74-
where TService : class =>
75-
typeof(TService) == typeof(EmbeddingsClient) ? (TService)(object)_embeddingsClient :
76-
this as TService;
73+
public object? GetService(Type serviceType, object? serviceKey = null)
74+
{
75+
_ = Throw.IfNull(serviceType);
76+
77+
return
78+
serviceKey is not null ? null :
79+
serviceType == typeof(EmbeddingsClient) ? _embeddingsClient :
80+
serviceType.IsInstanceOfType(this) ? this :
81+
null;
82+
}
7783

7884
/// <inheritdoc />
7985
public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,14 @@ public async IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAs
166166
}
167167

168168
/// <inheritdoc />
169-
public TService? GetService<TService>(object? key = null)
170-
where TService : class
171-
=> key is null ? this as TService : null;
169+
public object? GetService(Type serviceType, object? serviceKey = null)
170+
{
171+
_ = Throw.IfNull(serviceType);
172+
173+
return
174+
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
175+
null;
176+
}
172177

173178
/// <inheritdoc />
174179
public void Dispose()

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,14 @@ public OllamaEmbeddingGenerator(Uri endpoint, string? modelId = null, HttpClient
5757
public EmbeddingGeneratorMetadata Metadata { get; }
5858

5959
/// <inheritdoc />
60-
public TService? GetService<TService>(object? key = null)
61-
where TService : class
62-
=> key is null ? this as TService : null;
60+
public object? GetService(Type serviceType, object? serviceKey = null)
61+
{
62+
_ = Throw.IfNull(serviceType);
63+
64+
return
65+
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
66+
null;
67+
}
6368

6469
/// <inheritdoc />
6570
public void Dispose()

0 commit comments

Comments
 (0)