Junaid.GoogleGemini.Net.Extensions.AI
6.2.0
dotnet add package Junaid.GoogleGemini.Net.Extensions.AI --version 6.2.0
NuGet\Install-Package Junaid.GoogleGemini.Net.Extensions.AI -Version 6.2.0
<PackageReference Include="Junaid.GoogleGemini.Net.Extensions.AI" Version="6.2.0" />
<PackageVersion Include="Junaid.GoogleGemini.Net.Extensions.AI" Version="6.2.0" />
<PackageReference Include="Junaid.GoogleGemini.Net.Extensions.AI" />
paket add Junaid.GoogleGemini.Net.Extensions.AI --version 6.2.0
#r "nuget: Junaid.GoogleGemini.Net.Extensions.AI, 6.2.0"
#:package Junaid.GoogleGemini.Net.Extensions.AI@6.2.0
#addin nuget:?package=Junaid.GoogleGemini.Net.Extensions.AI&version=6.2.0
#tool nuget:?package=Junaid.GoogleGemini.Net.Extensions.AI&version=6.2.0
Junaid.GoogleGemini.Net
A production-ready .NET client for the Google Gemini API — resilient, observable, DI-native, and built to feel right in ASP.NET Core.
🤖 Built with AI. The v5 → v6 modernization was implemented end-to-end by Claude (Anthropic). See AI-assisted development.
It covers the modern Gemini surface (structured output, system instructions, thinking, grounding, the Files API, context caching), but what makes it worth choosing is everything around the API call:
| This library | Typical thin wrappers | |
|---|---|---|
| Resilience built in | ✅ retries + backoff on the HttpClient pipeline | ❌ roll your own |
| Client-side rate limiting | ✅ token-bucket, configurable | ❌ none |
| OpenTelemetry-native | ✅ traces + token/latency metrics (GenAI semconv), zero extra deps | ❌ none |
IChatClient / IEmbeddingGenerator |
✅ via companion package | sometimes |
| Typed structured output | ✅ GenerateAsync<T>() |
rare |
| DI-first + Options pattern | ✅ | varies |
| Cost governance | ✅ daily USD budget + per-request estimate + cost metric | ❌ not found in any .NET Gemini client we surveyed[^cost-governance-survey] |
[^cost-governance-survey]: Checked as of August 2026: Google's own Google.GenAI, Mscc.GenerativeAI, Google_GenerativeAI (784K+ downloads, self-described "most complete" .NET Gemini SDK), GeminiDotnet, Gemini.NET, dotnet-gemini-sdk, Google_Generative_AI, and GemiNet — none offer budget caps, cost tracking, or spend-limit enforcement. The closest any come is passing through the server's own 429 rate-limit errors as a typed exception, which is not the same thing.
v6 is a modernization release with breaking changes (idiomatic PascalCase models,
IAsyncEnumerablestreaming, typed exceptions). See ROADMAP.md.
Installation
dotnet add package Junaid.GoogleGemini.Net
# optional: Microsoft.Extensions.AI adapters (IChatClient, IEmbeddingGenerator)
dotnet add package Junaid.GoogleGemini.Net.Extensions.AI
Authentication
Get an API key from Google AI Studio, then provide it via environment variable (GeminiApiKey) or configuration:
{
"Gemini": {
"ApiKey": "your-api-key-here",
"DefaultModel": "gemini-3.6-flash",
"TimeoutSeconds": 100,
"MaxRetries": 3,
"RateLimit": { "Enabled": true, "RequestsPerMinute": 60 }
}
}
Quick start
builder.Services.AddGemini(builder.Configuration.GetSection("Gemini"));
app.MapGet("/", async (IGeminiService gemini) =>
{
var response = await gemini.GenerateAsync("Say hello!");
return response.Text();
});
Core features
Generation, vision, chat
// Text
var response = await gemini.GenerateAsync("Write a haiku about C#");
// Vision (text + image)
var image = new FileObject(File.ReadAllBytes("photo.jpg"), "photo.jpg");
var vision = await gemini.GenerateWithImageAsync("What's in this image?", image);
// Chat
var messages = new[]
{
new MessageObject("user", "Hello, who are you?"),
new MessageObject("model", "I'm Gemini."),
new MessageObject("user", "Tell me a joke."),
};
var chat = await gemini.ChatAsync(messages);
Streaming (IAsyncEnumerable)
await foreach (var chunk in gemini.StreamAsync("Tell me a long story"))
{
Console.Write(chunk.Text());
}
// Or a simple callback overload:
await gemini.StreamAsync("Tell me a long story", text => Console.Write(text));
⭐ Typed structured output
Get a strongly-typed result back — the JSON schema is derived from your type automatically:
record Recipe(string Title, string[] Ingredients, int Minutes);
Recipe recipe = await gemini.GenerateAsync<Recipe>("A quick pasta recipe");
Console.WriteLine(recipe.Title);
Reading responses safely
var response = await gemini.GenerateAsync("...");
string text = response.Text(); // "" if there was no text (never a placeholder)
if (response.TryGetText(out var t)) { /* ... */ }
string guaranteed = response.GetTextOrThrow(); // throws GeminiContentException if blocked/empty
var reason = response.FinishReason; // e.g. "STOP", "SAFETY"
var usage = response.Usage; // PromptTokenCount, CandidatesTokenCount, ...
Request options (system instructions, thinking, JSON, grounding)
var options = new GeminiRequestOptions
{
Model = GeminiConstants.Models.Gemini36Flash, // Gemini 3 (the default is gemini-3.6-flash)
SystemInstruction = "You are a terse senior engineer.",
ThinkingLevel = GeminiConstants.ThinkingLevels.Low, // Gemini 3 reasoning depth (use ThinkingBudget on 2.5)
EnableGoogleSearch = true, // ground answers with Google Search
};
var grounded = await gemini.GenerateAsync("What shipped in .NET 9?", options);
foreach (var q in grounded.Candidates?[0].GroundingMetadata?.WebSearchQueries ?? [])
Console.WriteLine($"searched: {q}");
Convenience presets: GeminiRequestOptions.Creative(), .Factual(), .Code(), .Fast().
Note: Google deprecated
temperature/topP/topKongemini-3.6-flashandgemini-3.5-flash-lite(July 2026) — those models ignore the sampling params entirely, so.Factual()/.Code()won't have the effect you'd expect on the default model. UseSystemInstructionwith explicit rules instead.
Gemini 3 ready. Model names aren't allow-listed, so any current/future model works without a library update.
ThinkingLevelandMediaResolutionare supported, and the model's encryptedthoughtSignatureis captured and can be replayed for multi-turn function calling via theContent-basedChatAsync/StreamChatAsyncoverloads. Tip: Gemini 3 thinking models default to deep reasoning — set a lowerThinkingLevelfor latency-sensitive calls.
Image generation
var response = await gemini.GenerateImageAsync("A watercolor painting of a lighthouse at sunset.");
foreach (var image in response.Images())
await File.WriteAllBytesAsync($"lighthouse.{image.MimeType.Split('/')[1]}", image.Data);
Defaults to the efficient flash image model; pass Model = GeminiConstants.Models.Gemini3ProImage for
higher quality, or ImageAspectRatio/ImageSize (Gemini 3+ image models) for finer control. See
docs/articles/image-generation.md.
Embeddings
var embedding = await embeddings.EmbedContentAsync(
"gemini-embedding-001", "Your text",
new EmbeddingOptions { TaskType = GeminiConstants.EmbeddingTaskTypes.RetrievalDocument });
var batch = await embeddings.BatchEmbedContentAsync("gemini-embedding-001", texts);
Files API & context caching
// Upload a file, wait until it's processed, then reference it
var file = await files.UploadFileAsync(bytes, "video/mp4", "clip.mp4");
await files.WaitUntilActiveAsync(file.Name!);
// Cache a large reusable context, then reference it by name to save tokens
var cache = await caching.CreateAsync(new CachedContent
{
Model = "models/gemini-2.5-flash",
Contents = [ /* large shared context */ ],
Ttl = "3600s",
});
var answer = await gemini.GenerateAsync("Summarize.", new GeminiRequestOptions { CachedContent = cache.Name });
Token counting & model info
var tokens = await gemini.CountTokensAsync("Your text");
Console.WriteLine(tokens.TotalTokens);
var models = await modelInfo.ListModelsAsync();
Cost governance
builder.Services.AddGemini(options =>
{
options.Budget = new BudgetOptions
{
MaxCostPerDayUsd = 50.00m, // reject the next call once today's real spend hits $50
MaxCostPerRequestUsd = 2.00m, // optional: reject a single call whose estimated cost is too high
};
});
// Once today's (UTC) real recorded spend reaches $50, the next call throws before it's sent:
try
{
var response = await gemini.GenerateAsync(prompt);
}
catch (GeminiBudgetExceededException ex)
{
// ex.CurrentSpendUsd, ex.BudgetLimitUsd
}
catch (GeminiRequestCostExceededException ex)
{
// A single request's estimated cost exceeded MaxCostPerRequestUsd.
// ex.EstimatedCostUsd, ex.MaxCostPerRequestUsd
}
Microsoft.Extensions.AI integration
Use Gemini anywhere the .NET AI abstractions are consumed (Semantic Kernel, agent frameworks, middleware):
builder.Services.AddGemini(builder.Configuration.GetSection("Gemini"));
builder.Services.AddGeminiChatClient("gemini-2.5-flash"); // registers IChatClient
builder.Services.AddGeminiEmbeddingGenerator("gemini-embedding-001"); // registers IEmbeddingGenerator
// elsewhere:
public class MyService(IChatClient chat)
{
public Task<ChatResponse> Ask(string q) =>
chat.GetResponseAsync([new ChatMessage(ChatRole.User, q)]);
}
Observability (OpenTelemetry)
Traces and metrics are emitted via System.Diagnostics following the OTel GenAI conventions — no OpenTelemetry dependency is forced on you. Opt in:
builder.Services.AddOpenTelemetry()
.WithTracing(t => t.AddSource(GeminiTelemetry.SourceName))
.WithMetrics(m => m.AddMeter(GeminiTelemetry.SourceName));
You get per-call spans (gen_ai.system, gen_ai.request.model, token counts, finish reasons) and the gen_ai.client.operation.duration / gen_ai.client.token.usage metrics.
Resilience & rate limiting
Configured once, applied automatically:
builder.Services.AddGemini(options =>
{
options.MaxRetries = 3; // retried on 429/5xx/transient with exponential backoff
options.RetryBaseDelay = TimeSpan.FromSeconds(2);
options.RateLimit.Enabled = true; // client-side token bucket
options.RateLimit.RequestsPerMinute = 60;
});
Failures surface as typed exceptions: GeminiApiException (status + parsed error), GeminiRateLimitException, GeminiTimeoutException, GeminiSerializationException, GeminiContentException — all deriving from GeminiException.
Cost governance
Cap what a Gemini integration can spend, and observe what it actually spends, without rolling your own token-counting and pricing math. As far as we've been able to find, no other .NET Gemini client offers this — see the footnote on the feature-comparison table at the top. Covers both non-streaming and streaming calls — a budget guardrail that silently didn't apply to StreamAsync/StreamChatAsync would let a runaway streaming loop blow through the budget completely unchecked.
builder.Services.AddGemini(options =>
{
options.Budget = new BudgetOptions
{
MaxCostPerDayUsd = 50.00m, // the primary, always-exact mechanism
MaxCostPerRequestUsd = 2.00m, // optional: reject one outsized call before it's sent
};
});
Every response's real token usage (including cached-content and "thinking" tokens, priced correctly per Gemini's billing rules) is converted to a USD cost and recorded as the gemini.client.cost.usd OpenTelemetry metric. Once today's (UTC) cumulative actual spend reaches MaxCostPerDayUsd, the next call throws GeminiBudgetExceededException before it's sent — no network round-trip, no cost incurred by the rejected call itself. This is the primary, exact mechanism (built from real billed usage).
MaxCostPerRequestUsd is a secondary, best-effort estimate ceiling checked before a single call: it spends one extra CountTokensAsync round-trip to get an exact input-token count (skipped entirely when MaxCostPerRequestUsd is unset, so it costs nothing when you don't use it), bounds the output side only when you set MaxTokens, and throws GeminiRequestCostExceededException if the estimate exceeds the ceiling. It can't be exact the way the daily budget is — see Cost governance for exactly what it can and can't guarantee, the multi-instance caveat, pricing overrides, and full details.
Documentation & samples
- Guides + full API reference: the
docs/DocFX site (Getting started, structured output, streaming, resilience, observability, M.E.AI, files & caching, cost governance, and a v5→v6 migration guide). Published to GitHub Pages via the Docs workflow. - Runnable sample:
samples/Junaid.GoogleGemini.Net.AspNetCoreSample— a minimal ASP.NET Core API showing generation,GenerateAsync<T>, streaming,IChatClient, and OpenTelemetry.
Requirements
- .NET 8.0, .NET 9.0, or any netstandard2.0 runtime (.NET Framework 4.6.1+, Mono, Unity)
- A Google AI Studio API key
AI-assisted development
This library is heavily developed with AI, and we want to be transparent about that. The v5 → v6 modernization — architecture, code, tests, documentation, and this README — was carried out end-to-end by Claude (Anthropic's coding agent) under the maintainer's direction, rather than written by hand.
What that means for you:
- Shipped behind guardrails. Changes go through an automated test suite and CI on every commit; the public API surface is documented and versioned with semantic versioning.
- 6.0 is stable, validated live against the Gemini API, and used as the default install for existing users upgrading from 5.x. Still pin a version you've tested for your own use case, as with any dependency.
- Transparency over polish. We'd rather tell you how the code is produced than hide it. If you spot something off, please open an issue.
Contributing & support
Issues and PRs welcome on GitHub. Licensed under MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Junaid.GoogleGemini.Net (>= 6.2.0)
- Microsoft.Extensions.AI.Abstractions (>= 10.6.0)
-
net9.0
- Junaid.GoogleGemini.Net (>= 6.2.0)
- Microsoft.Extensions.AI.Abstractions (>= 10.6.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.2.0 | 102 | 8/9/2026 |
| 6.1.0 | 99 | 8/9/2026 |
| 6.0.0 | 129 | 6/10/2026 |
| 6.0.0-rc.1 | 66 | 6/10/2026 |
| 6.0.0-beta.4 | 65 | 6/10/2026 |
| 6.0.0-beta.3 | 74 | 6/10/2026 |
| 6.0.0-beta.2 | 65 | 6/10/2026 |
| 6.0.0-beta.1 | 72 | 6/8/2026 |
| 6.0.0-alpha.4 | 302 | 6/7/2026 |
6.2.0: version bump to track the core package (no functional changes here) — the core's
new cost-governance feature and CountTokensAsync model-overload fix don't affect this
adapter.
6.1.0: version bump to track the core package (no functional changes here) — neither the
core's API-key-format fix nor its new image-generation surface affects this adapter.
6.0.1/6.0.0: no functional changes since initial release (IChatClient/IEmbeddingGenerator
adapters over Junaid.GoogleGemini.Net).