---
title: "IChatClient for Claude and Gemini in C#"
url: https://recursiveai.org/onellm/cookbook/ichatclient-for-claude-and-gemini/
description: "Use IChatClient with Anthropic Claude and Gemini in C#. A complete .NET 10 example that runs offline with OneLLM, real output and a runnable download."
version: "1.0.1"
---

# IChatClient for Claude and Gemini in C#

**OneLLM** is a free, open-source LLM and agent library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneLlm.Extensions.AI) · [GitHub](https://github.com/recursive-ai-oss/onellm-csharp) · [Docs](https://recursiveai.org/onellm/docs/)

Use Microsoft.Extensions.AI’s IChatClient with Anthropic (Claude) and Gemini.

```shell
dotnet add package \
    RecursiveAI.OneLlm.Extensions.AI
```

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using Microsoft.Extensions.AI;
using OneLlm;
using OneLlm.Extensions.AI;
var runtime = new Runtime();
using var ai = runtime.AsChatClient(
    "claude-sonnet");
var reply = await ai.GetResponseAsync("Hi");
Console.WriteLine(reply.Text);
```

Calls Claude using `ANTHROPIC_API_KEY`.

**[Download this example (.zip)](https://recursiveai.org/onellm/cookbook/ichatclient-for-claude-and-gemini/ichatclient-for-claude-and-gemini.zip)**: unzip, then `dotnet run`.

## The full program

The download runs offline with canned replies, no API key needed.

`claude-sonnet` and `gemini-flash` are shipped catalog aliases: the pinned catalog maps them to `claude-sonnet-5-5` and `gemini-3.8-flash`, respectively ([model names](https://recursiveai.org/onellm/docs/getting-started/#model-names)).

The download registers both scripted providers before creating the client. The default catalog model is `claude-sonnet`; a per-request `ChatOptions.ModelId` selects `gemini-flash`. These are catalog aliases, not a claim that the two providers accept identical options or return identical answers.

```csharp
using Microsoft.Extensions.AI;
using OneLlm;
using OneLlm.Extensions.AI;
using OneLlm.Testing;

var runtime = new Runtime();
var claude = new ScriptedProvider("anthropic");
var gemini = new ScriptedProvider("gemini");
runtime.Providers.Set("anthropic", claude);
runtime.Providers.Set("gemini", gemini);
claude.EnqueueText("Hello from scripted Claude.");
gemini.EnqueueText("Hello from scripted Gemini.");

using IChatClient client = runtime.AsChatClient("claude-sonnet");
Console.WriteLine((await client.GetResponseAsync("Say hello.")).Text);
var options = new ChatOptions { ModelId = "gemini-flash" };
Console.WriteLine((await client.GetResponseAsync("Say hello.", options)).Text);
Console.WriteLine($"Anthropic requests: {claude.Requests.Count}");
Console.WriteLine($"Gemini requests: {gemini.Requests.Count}");
```

`dotnet run` prints:

```text
Hello from scripted Claude.
Hello from scripted Gemini.
Anthropic requests: 1
Gemini requests: 1
```

An unknown model raises `ModelNotFoundException`; the scripted provider also throws if the program makes an extra request with no reply queued.

### With a real model

The [pinned getting-started docs](https://recursiveai.org/onellm/docs/getting-started/#install-and-keys) name `ANTHROPIC_API_KEY` for Anthropic and `GEMINI_API_KEY` for Gemini. Set those environment variables to your own credentials outside source code. To use only Claude, set `ANTHROPIC_API_KEY` and omit the Gemini call.

In the full program, remove both `ScriptedProvider` declarations, both `runtime.Providers.Set(...)` registrations, and both `EnqueueText` calls. Also remove the two final `Requests.Count` print statements: those inspect the stand-ins. The runtime setup becomes:

```csharp
using OneLlm;
var runtime = new Runtime();
```

Keep the `AsChatClient`, `GetResponseAsync` and `ChatOptions.ModelId` calls unchanged. The built-in providers read their environment keys on the first request, as the docs describe. The snippet at the top uses this real-provider setup, not the canned replies.

This live-model path was not run here: no API key was used. The output shown above comes from the scripted providers, not from Anthropic or Gemini.

## Variations

**Stream text through the same interface.** Queue a streamed reply and replace the response call with:

```csharp
claude.Enqueue(ScriptStep.Text("Hel", "lo"));
await foreach (var update in client.GetStreamingResponseAsync("Say hello."))
    Console.Write(update.Text);
Console.WriteLine();
```

After the full program’s original calls have consumed their replies, this prints `Hello`.

## Related recipes

- [Require human approval for LLM tool calls in .NET](https://recursiveai.org/onellm/cookbook/approve-agent-tool-calls/)
- [Persist LLM chat history in C# and resume it](https://recursiveai.org/onellm/cookbook/save-and-resume-a-chat/)
- [Classify support tickets with an LLM in C#](https://recursiveai.org/onellm/cookbook/classify-support-tickets/)
- [Generate a Git commit message with an LLM in C#](https://recursiveai.org/onellm/cookbook/commit-message-from-git-diff/)

---

## The short answer

Install `RecursiveAI.OneLlm.Extensions.AI` and call `runtime.AsChatClient("claude-sonnet")`. The returned `IChatClient` uses OneLLM’s model catalog and provider registry. Use `ChatOptions.ModelId` to select another catalog model for one request, as the download does with `gemini-flash`.

The runnable example exercises the bridge path without contacting either service. Its replies explicitly say “scripted,” and its counters show one request going to each registered provider. That avoids confusing an interface demonstration with a live Anthropic or Gemini integration test.

## How do I use the Anthropic SDK in C#?

For the `IChatClient` interface shown here, install `RecursiveAI.OneLlm.Extensions.AI`, set `ANTHROPIC_API_KEY`, and call `new Runtime().AsChatClient("claude-sonnet")`; OneLLM supplies the Anthropic provider behind that interface.

With OneLLM’s bridge, application code can consume `IChatClient` while OneLLM handles the provider-facing request. The package references the core library and `Microsoft.Extensions.AI.Abstractions`. Its [bridge documentation](https://recursiveai.org/onellm/docs/extensions-ai/) describes both directions: exposing a OneLLM model as a client, and registering an existing client as a OneLLM provider.

For a live application, remove the scripted-provider registrations and configure the real provider credentials outside source code. The download never reads a key. OneLLM’s release [results](https://recursiveai.org/onellm/docs/results/) say conformance uses recorded fixtures and Anthropic was not run live; this page does not extend those claims. Follow the [provider setup documentation](https://recursiveai.org/onellm/docs/getting-started/) for the connection you intend to use.

A shared interface does not erase provider differences. The catalog adapts requests to the chosen model, and dropped options appear as warnings. Read `OneLlmProperties.Warnings` when you depend on an option’s effect. Do not assume every setting on `ChatOptions` is forwarded unchanged: the docs list fields that are not carried, including `ConversationId`.

### Use Gemini through the same interface

Select the Gemini model through the same runtime and client abstraction. The full program overrides `ModelId` for the second call, leaving the client’s default Claude model intact. Creating a separate `runtime.AsChatClient("gemini-flash")` is also appropriate when different services should have different defaults.

Keep conversation messages in your application when it owns the history. A single `GetResponseAsync("Say hello.")` call does not make every later call share a conversation. Pass the messages you want to send, and choose one history policy rather than assuming that switching model IDs also transfers server-side conversation state.

The bridge carries text, reasoning and tool calls through the corresponding content types. For streaming, consume `GetStreamingResponseAsync` to its end if you need the complete result and final usage. The docs say streaming ends with one update carrying the finish reason and one usage item, so aggregation counts usage once.

Tools need a separate execution decision. This client does not run functions merely because a response contains a function call. The bridge docs recommend function-invoking middleware or a OneLLM tool loop when execution is desired. Treat that as an explicit application design choice, not an incidental feature of changing the model name. For this recipe’s text-only request, neither provider is given tools.
