Skip to content

Microsoft.Extensions.AI IChatClient Implementation

Gunpal Jain edited this page Nov 15, 2026 · 3 revisions

Microsoft.Extensions.AI Integration (IChatClient)

The Google_GenerativeAI.Microsoft package implements the Microsoft.Extensions.AI (MEAI) abstractions on top of the SDK, so Gemini can be used anywhere an IChatClient, IEmbeddingGenerator or IImageGenerator is expected, including MEAI middleware (function invocation, logging, caching, telemetry) and dependency injection.

MEAI abstraction Implementation Wraps
IChatClient GenerativeAIChatClient GenerativeModel
IEmbeddingGenerator<string, Embedding<float>> GenerativeAIEmbeddingGenerator EmbeddingModel (see Embeddings)
IImageGenerator GenerativeAIImageGenerator (Gemini image models), GenerativeAIImagenGenerator (obsolete: Imagen is retired) see Image Generation

Installation

dotnet add package Google_GenerativeAI.Microsoft

The package references Microsoft.Extensions.AI.Abstractions 10.x on net8.0 and net9.0, and 11.x on net10.0, net11.0, netstandard2.0 and net462. Middleware such as UseFunctionInvocation() and the AddChatClient(...) DI helper come from the separate Microsoft.Extensions.AI package; add it if you use them, with the same major version.

Creating the client

using GenerativeAI;
using GenerativeAI.Microsoft;
using Microsoft.Extensions.AI;

// Gemini API with an API key (model defaults to GoogleAIModels.DefaultGeminiModel)
IChatClient client = new GenerativeAIChatClient(apiKey, GoogleAIModels.Gemini38Flash);

// Any platform adapter, e.g. Vertex AI
IChatClient vertexClient = new GenerativeAIChatClient(
    new VertextPlatformAdapter(projectId: "my-project", region: "us-central1"),
    VertexAIModels.Gemini.Gemini38Flash);

Constructors:

  • GenerativeAIChatClient(string apiKey, string modelName = GoogleAIModels.DefaultGeminiModel, bool autoCallFunction = true)
  • GenerativeAIChatClient(IPlatformAdapter adapter, string modelName = GoogleAIModels.DefaultGeminiModel, bool autoCallFunction = true)

The underlying GenerativeModel is available through the model property.

Basic usage

ChatResponse response = await client.GetResponseAsync("What is the capital of France?");
Console.WriteLine(response.Text);

Conversation history

IChatClient is stateless: pass the whole conversation each time and append the replies.

var history = new List<ChatMessage>
{
    new(ChatRole.System, "You are a helpful assistant."),
    new(ChatRole.User, "Give me three names for a cat.")
};

ChatResponse reply = await client.GetResponseAsync(history);
history.AddMessages(reply);

history.Add(new ChatMessage(ChatRole.User, "Which one is the shortest?"));
reply = await client.GetResponseAsync(history);
Console.WriteLine(reply.Text);

System messages, together with ChatOptions.Instructions, become the Gemini system instruction.

Streaming

await foreach (ChatResponseUpdate update in client.GetStreamingResponseAsync("Write a haiku about the sea."))
{
    Console.Write(update.Text);
}

Chat options

var options = new ChatOptions
{
    Temperature = 0.7f,
    MaxOutputTokens = 1024,
    TopP = 0.9f,
    TopK = 40,
    StopSequences = new List<string> { "END" },
    Seed = 42,
    ResponseFormat = ChatResponseFormat.Json // or ChatResponseFormat.ForJsonSchema(...)
};

ChatResponse response = await client.GetResponseAsync(history, options);

Temperature, TopP, TopK, MaxOutputTokens, StopSequences, Seed, PresencePenalty, FrequencyPenalty and ResponseFormat map to Gemini's GenerationConfig. A JSON schema in ResponseFormat is converted to the schema subset Gemini accepts. Structured output helpers such as client.GetResponseAsync<T>(...) from Microsoft.Extensions.AI work as well.

Gemini-specific options

Use ChatOptions.AdditionalProperties with the keys in AdditionalPropertiesKeys:

Key Value type Gemini setting
AdditionalPropertiesKeys.ThinkingBudget int? ThinkingConfig.ThinkingBudget
AdditionalPropertiesKeys.ThinkingConfigIncludeThoughts bool? ThinkingConfig.IncludeThoughts
AdditionalPropertiesKeys.ResponseModalities List<Modality> ResponseModalities (for example text + image)
AdditionalPropertiesKeys.ImageConfigAspectRatio string ImageConfig.AspectRatio
AdditionalPropertiesKeys.ImageConfigImageSize string ImageConfig.ImageSize
AdditionalPropertiesKeys.ImageOutputOptionsMimeType string ImageConfig.ImageOutputOptions.MimeType
AdditionalPropertiesKeys.ImageOutputOptionsCompressionQuality int ImageConfig.ImageOutputOptions.CompressionQuality
"ResponseLogprobs" / "Logprobs" bool? / int? log probabilities
var options = new ChatOptions
{
    AdditionalProperties = new AdditionalPropertiesDictionary
    {
        [AdditionalPropertiesKeys.ThinkingBudget] = 1024,
        [AdditionalPropertiesKeys.ThinkingConfigIncludeThoughts] = true
    }
};

Thought signatures returned by Gemini 3.x are kept in the AdditionalProperties of the returned contents (key AdditionalPropertiesKeys.ThoughtSignature) and sent back when you pass the messages in the next request, which multi-turn function calling with thinking models needs.

Multimodal input

Add DataContent (bytes plus media type) to a message; it is sent as inline data. DataContent larger than 20 MB is uploaded through the Files API automatically instead. To reference a file you already uploaded (see Files Client), use the SDK's RemoteFileContent(RemoteFile remoteFile, string? mimeType = null) from GenerativeAI.Microsoft.Extensions. Other content types such as UriContent are not converted and are skipped.

var message = new ChatMessage(ChatRole.User, new List<AIContent>
{
    new TextContent("Describe this picture."),
    new DataContent(await File.ReadAllBytesAsync("photo.jpg"), "image/jpeg")
});

ChatResponse response = await client.GetResponseAsync(message);

Function calling

Built-in function calling (default)

With autoCallFunction: true (the default) the client runs the AIFunctions in ChatOptions.Tools itself and sends the results back to the model:

var options = new ChatOptions
{
    Tools = new List<AITool> { AIFunctionFactory.Create(GetWeather) }
};

ChatResponse response = await client.GetResponseAsync("What's the weather in Paris?", options);
Console.WriteLine(response.Text);

[Description("Gets the current weather for a city")]
static string GetWeather(string city) => $"It's sunny in {city}.";

Hosted tools

HostedWebSearchTool in ChatOptions.Tools turns on Gemini's Google Search tool, and HostedCodeInterpreterTool turns on code execution.

Using MEAI's FunctionInvokingChatClient

To use MEAI's function-invocation middleware instead, turn the built-in loop off with autoCallFunction: false:

IChatClient client = new GenerativeAIChatClient(apiKey, GoogleAIModels.Gemini38Flash, autoCallFunction: false)
    .AsBuilder()
    .UseFunctionInvocation()
    .Build();

You can also change it later with the AutoCallFunction property.

Tools from the SDK's tool generators

The source-generated and reflection-based tools from Google_GenerativeAI.Tools (see Function Calling) convert to MEAI tools:

// Method-based: [FunctionTool(MeaiFunctionTool = true)]
options.Tools = new Tools([GetCurrentDateTime]).AsMeaiTools();

// Interface-based: [GenerateJsonSchema(MeaiFunctionTool = true)]
options.Tools = new WeatherService().AsMeaiTools();

// Reflection-based
options.Tools = new List<AITool> { new QuickTool(GetCurrentDateTime).AsMeaiTool() };

v4.0.0 fixes

  • Parallel function calls: when the model asks for several functions in one turn, the model's function-call turn is added to the history once, followed by all function results in one turn. Earlier versions repeated the function-call turn for every call.
  • Streaming function-call detection: GetStreamingResponseAsync collects parts across the whole stream, so function calls are found even when the last chunk only carries the finish reason (common with newer models).
  • user role for function results: results are sent with the user role because Gemini 3.x rejects the function role.

Dependency injection

using GenerativeAI.Microsoft;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;

string apiKey = Environment.GetEnvironmentVariable("GOOGLE_API_KEY")!;

// Plain registration (Google_GenerativeAI.Microsoft only)
services.AddSingleton<IChatClient>(_ => new GenerativeAIChatClient(apiKey));

// Or with the MEAI builder and middleware (needs the Microsoft.Extensions.AI package)
services.AddChatClient(_ => new GenerativeAIChatClient(apiKey, GoogleAIModels.Gemini38Flash, autoCallFunction: false))
        .UseFunctionInvocation();

Inject IChatClient into your services:

public class MyChatService(IChatClient chatClient)
{
    public async Task<string> AskAsync(string userMessage)
    {
        ChatResponse response = await chatClient.GetResponseAsync(userMessage);
        return response.Text;
    }
}

For the SDK's own DI integration (IGenerativeAiService), see Web Application Integration.

Important considerations

  1. API keys: keep keys out of source code; use environment variables, user secrets or a secret store.
  2. Errors: handle exceptions from API calls (for example GenerativeAIException and HTTP errors).
  3. Rate limits: respect the API's rate limits and add retries or throttling where needed.
  4. Context window: long conversations can exceed the model's context window; trim or summarize older messages.
  5. Model choice: pick the model for your task and budget; see Supported Models.

References

Clone this wiki locally