Skip to content

Add the runtime libraries bits for MEAI #43985

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 4 additions & 2 deletions docs/ai/ai-extensions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Unified AI building blocks for .NET
description: Learn how to develop with unified AI building blocks for .NET using Microsoft.Extensions.AI and Microsoft.Extensions.AI.Abstractions libraries
ms.date: 11/04/2024
ms.date: 12/16/2024
ms.topic: quickstart
ms.custom: devx-track-dotnet, devx-track-dotnet-ai
author: alexwolfmsft
Expand All @@ -16,11 +16,13 @@ The .NET ecosystem provides abstractions for integrating AI services into .NET a
- How to work with AI abstractions in your apps and the benefits they offer.
- Essential AI middleware concepts.

For more information, see [Introduction to Microsoft.Extensions.AI](../core/extensions/artificial-intelligence.md).

## What is the Microsoft.Extensions.AI library?

`Microsoft.Extensions.AI` is a set of core .NET libraries created in collaboration with developers across the .NET ecosystem, including Semantic Kernel. These libraries provide a unified layer of C# abstractions for interacting with AI services, such as small and large language models (SLMs and LLMs), embeddings, and middleware.

:::image type="content" source="media/ai-extensions/meai-architecture-diagram.png" alt-text="An architectural diagram of the AI extensions libraries.":::
:::image type="content" source="media/ai-extensions/meai-architecture-diagram.png" lightbox="media/ai-extensions/meai-architecture-diagram.png" alt-text="An architectural diagram of the AI extensions libraries.":::

`Microsoft.Extensions.AI` provides abstractions that can be implemented by various services, all adhering to the same core concepts. This library is not intended to provide APIs tailored to any specific provider's services. The goal of `Microsoft.Extensions.AI` is to act as a unifying layer within the .NET ecosystem, enabling developers to choose their preferred frameworks and libraries while ensuring seamless integration and collaboration across the ecosystem.

Expand Down
282 changes: 282 additions & 0 deletions docs/core/extensions/artificial-intelligence.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions docs/core/extensions/http-ratelimiter.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Rate limiting an HTTP handler in .NET
description: Learn how to create a client-side HTTP handler that limits the number of requests, with the inbuilt rate limiter API from .NET.
author: IEvangelist
ms.author: dapine
ms.date: 03/13/2023
ms.date: 12/16/2024
---

# Rate limit an HTTP handler in .NET
Expand Down Expand Up @@ -161,12 +161,12 @@ You'll notice that the first logged entries are always the immediately returned

Note also that each URL's query string is unique: examine the `iteration` parameter to see that it's incremented by one for each request. This parameter helps to illustrate that the 429 responses aren't from the first requests, but rather from the requests that are made after the rate limit is reached. The 200 responses arrive later but these requests were made earlier—before the limit was reached.

To have a better understanding of the various rate-limiting algorithms, try rewriting this code to accept a different `RateLimiter` implementation. In addition to the `TokenBucketRateLimiter` you could try:
To have a better understanding of the various rate-limiting algorithms, try rewriting this code to accept a different <xref:System.Threading.RateLimiting.RateLimiter> implementation. In addition to the <xref:System.Threading.RateLimiting.TokenBucketRateLimiter> you could try:

- `ConcurrencyLimiter`
- `FixedWindowRateLimiter`
- `PartitionedRateLimiter`
- `SlidingWindowRateLimiter`
- <xref:System.Threading.RateLimiting.ConcurrencyLimiter>
- <xref:System.Threading.RateLimiting.FixedWindowRateLimiter>
- <xref:System.Threading.RateLimiting.PartitionedRateLimiter>
- <xref:System.Threading.RateLimiting.SlidingWindowRateLimiter>

## Summary

Expand Down
14 changes: 14 additions & 0 deletions docs/core/extensions/snippets/ai/AI.Shared/AI.Shared.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI" Version="9.0.1-preview.1.24570.5" />
<PackageReference Include="System.Threading.RateLimiting" Version="9.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.Extensions.AI;
using System.Runtime.CompilerServices;
using System.Threading.RateLimiting;

public sealed class RateLimitingChatClient(
IChatClient innerClient, RateLimiter rateLimiter)
: DelegatingChatClient(innerClient)
{
public override async Task<ChatCompletion> CompleteAsync(
IList<ChatMessage> chatMessages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
.ConfigureAwait(false);

if (!lease.IsAcquired)
{
throw new InvalidOperationException("Unable to acquire lease.");
}

return await base.CompleteAsync(chatMessages, options, cancellationToken)
.ConfigureAwait(false);
}

public override async IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
IList<ChatMessage> chatMessages,
ChatOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
.ConfigureAwait(false);

if (!lease.IsAcquired)
{
throw new InvalidOperationException("Unable to acquire lease.");
}

await foreach (var update in base.CompleteStreamingAsync(chatMessages, options, cancellationToken)
.ConfigureAwait(false))
{
yield return update;
}
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
rateLimiter.Dispose();
}

base.Dispose(disposing);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Example.Two;

// <two>
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.RateLimiting;

public static class RateLimitingChatClientExtensions
{
public static ChatClientBuilder UseRateLimiting(
this ChatClientBuilder builder, RateLimiter? rateLimiter = null) =>
builder.Use((innerClient, services) =>
new RateLimitingChatClient(
innerClient,
rateLimiter ?? services.GetRequiredService<RateLimiter>()));
}
// </two>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Example.One;

// <one>
using Microsoft.Extensions.AI;
using System.Threading.RateLimiting;

public static class RateLimitingChatClientExtensions
{
public static ChatClientBuilder UseRateLimiting(
this ChatClientBuilder builder, RateLimiter rateLimiter) =>
builder.Use(innerClient => new RateLimitingChatClient(innerClient, rateLimiter));
}
// </one>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Extensions.AI;
using System.Threading.RateLimiting;

public class RateLimitingEmbeddingGenerator(
IEmbeddingGenerator<string, Embedding<float>> innerGenerator, RateLimiter rateLimiter)
: DelegatingEmbeddingGenerator<string, Embedding<float>>(innerGenerator)
{
public override async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
IEnumerable<string> values,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
{
using var lease = await rateLimiter.AcquireAsync(permitCount: 1, cancellationToken)
.ConfigureAwait(false);

if (!lease.IsAcquired)
{
throw new InvalidOperationException("Unable to acquire lease.");
}

return await base.GenerateAsync(values, options, cancellationToken);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
rateLimiter.Dispose();
}

base.Dispose(disposing);
}
}
58 changes: 58 additions & 0 deletions docs/core/extensions/snippets/ai/AI.Shared/SampleChatClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Runtime.CompilerServices;
using Microsoft.Extensions.AI;

public sealed class SampleChatClient(Uri endpoint, string modelId) : IChatClient
{
public ChatClientMetadata Metadata { get; } = new(nameof(SampleChatClient), endpoint, modelId);

public async Task<ChatCompletion> CompleteAsync(
IList<ChatMessage> chatMessages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
// Simulate some operation.
await Task.Delay(300, cancellationToken);

// Return a sample chat completion response randomly.
string[] responses =
[
"This is the first sample response.",
"Here is another example of a response message.",
"This is yet another response message."
];

return new([new ChatMessage()
{
Role = ChatRole.Assistant,
Text = responses[Random.Shared.Next(responses.Length)],
}]);
}

public async IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
IList<ChatMessage> chatMessages,
ChatOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Simulate streaming by yielding messages one by one.
string[] words = ["This ", "is ", "the ", "response ", "for ", "the ", "request."];
foreach (string word in words)
{
// Simulate some operation.
await Task.Delay(100, cancellationToken);

// Yield the next message in the response.
yield return new StreamingChatCompletionUpdate
{
Role = ChatRole.Assistant,
Text = word,
};
}
}

public object? GetService(Type serviceType, object? serviceKey) => this;

public TService? GetService<TService>(object? key = null)
where TService : class => this as TService;

void IDisposable.Dispose() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.Extensions.AI;

public sealed class SampleEmbeddingGenerator(
Uri endpoint, string modelId)
: IEmbeddingGenerator<string, Embedding<float>>
{
public EmbeddingGeneratorMetadata Metadata { get; } =
new(nameof(SampleEmbeddingGenerator), endpoint, modelId);

public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
IEnumerable<string> values,
EmbeddingGenerationOptions? options = null,
CancellationToken cancellationToken = default)
{
// Simulate some async operation
await Task.Delay(100, cancellationToken);

// Create random embeddings
return
[
.. from value in values
select new Embedding<float>(
Enumerable.Range(0, 384)
.Select(_ => Random.Shared.NextSingle())
.ToArray())
];
}

public object? GetService(Type serviceType, object? serviceKey) => this;

public TService? GetService<TService>(object? key = null)
where TService : class => this as TService;

void IDisposable.Dispose() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\AI.Shared\AI.Shared.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;

var sampleChatClient = new SampleChatClient(
new Uri("http://coolsite.ai"), "target-ai-model");

IChatClient client = new ChatClientBuilder(sampleChatClient)
.UseDistributedCache(new MemoryDistributedCache(
Options.Create(new MemoryDistributedCacheOptions())))
.Build();

string[] prompts = ["What is AI?", "What is .NET?", "What is AI?"];

foreach (var prompt in prompts)
{
await foreach (var update in client.CompleteStreamingAsync(prompt))
{
Console.Write(update);
}

Console.WriteLine();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\AI.Shared\AI.Shared.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.Extensions.AI;

IChatClient client = new SampleChatClient(
new Uri("http://coolsite.ai"), "target-ai-model");

Console.WriteLine(await client.CompleteAsync(
[
new(ChatRole.System, "You are a helpful AI assistant"),
new(ChatRole.User, "What is AI?"),
]));
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\AI.Shared\AI.Shared.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.Extensions.AI;

IChatClient client = new SampleChatClient(
new Uri("http://coolsite.ai"), "target-ai-model");

await foreach (var update in client.CompleteStreamingAsync("What is AI?"))
{
Console.Write(update);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<ProjectReference Include="..\AI.Shared\AI.Shared.csproj" />
</ItemGroup>

</Project>
Loading
Loading