Recursive AI

Call Ollama from C#

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

Ollama runs open models such as Llama on your own machine. OneLLM has a preset for its API, so a local model needs no key and has no per-token bill.

Terminal
dotnet add package RecursiveAI.OneLlm
ollama pull llama3.2

The second line needs Ollama running.

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

C#
using OneLlm;
 
string answer = await Llm.AskAsync(
    "ollama:llama3.2",
    "What is the capital of Australia?");
Console.WriteLine(answer);

Download this example (.zip): unzip, then dotnet run.

Prints the model’s answer, such as The capital of Australia is Canberra. ollama:llama3.2 isn’t a name from OneLLM’s model catalog: ollama: picks the Ollama preset, and the rest is the model name as Ollama knows it. Ollama wasn’t installed on the machine where we made this page, so every output below came from a small local stand-in server that speaks Ollama’s OpenAI-compatible API on the same port and answers with fixed text. Your model will word its answers differently.

The full program

This is the Program.cs in the download. It asks one question, makes the same kind of call with its usage and cost, and streams a haiku. If no server answers on port 11434, it says so instead of crashing.

C#
using OneLlm;
 
// Needs Ollama running on this machine (http://localhost:11434) with the model pulled:
//   ollama pull llama3.2
const string model = "ollama:llama3.2";
 
try
{
    // One question, one answer.
    string answer = await Llm.AskAsync(model, "What is the capital of Australia? One sentence.");
    Console.WriteLine(answer);
 
    // The same call with its usage and cost. Local servers are priced at zero.
    AskResult r = await Llm.GenerateAsync(model, "Say hello in five words.", []);
    Console.WriteLine($"{r.Text} [{r.Usage?.InputTokens} in, {r.Usage?.OutputTokens} out, {r.Cost}]");
 
    // Streaming: print the text as it arrives.
    await foreach (string piece in Llm.StreamTextAsync(model, "Write a haiku about a local LLM."))
        Console.Write(piece);
    Console.WriteLine();
}
catch (TransportException e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine("Is Ollama running? Install it from https://ollama.com, then run: ollama pull llama3.2");
}

With the stand-in server running, dotnet run printed:

Text
The capital of Australia is Canberra.
Hello from the local model. [14 in, 6 out, $0.000000]
Fans hum in the dark,
the model thinks on my desk,
no bill comes at dawn.

With nothing on port 11434, it prints:

Text
ollama: the request failed: Connection refused (localhost:11434)
Is Ollama running? Install it from https://ollama.com, then run: ollama pull llama3.2

ollama: picks OneLLM’s Ollama preset: base URL http://localhost:11434/v1, no API key, priced at zero because the server is on your machine. The part after the colon is the model name Ollama knows, sent as is. GenerateAsync returns the answer with its usage and cost; its third argument is a list of attachments (images, PDFs), and [] sends none. A failed connection is a TransportException; the docs list transport failures as retried, with two retries by default, before it is thrown. The token counts are whatever the server reports, so they differ by model.

Variations

A conversation. Llm.Chat keeps the history and sends it with each question:

C#
using OneLlm;
 
using ChatSession chat = Llm.Chat("ollama:llama3.2", "You answer in one sentence.");
Console.WriteLine(await chat.AskAsync("What is the capital of Australia?"));
Console.WriteLine(await chat.AskAsync("Say hello."));
Text
The capital of Australia is Canberra.
Hello from the local model.

The second request carried the system prompt, the first question, the first answer and the new question. This is what the stand-in server logged:

Text
{"model": "llama3.2", "messages": [{"role": "system", "content": "You answer in one sentence."}, {"role": "user", "content": "What is the capital of Australia?"}, {"role": "assistant", "content": "The capital of Australia is Canberra."}, {"role": "user", "content": "Say hello."}]}

Ollama on another machine or port. Register the preset with your own base URL. Here it was a second stand-in server on port 11500; use your server’s address, such as http://gpu-box:11434/v1:

C#
using OneLlm;
using OneLlm.Providers.OpenAI;
 
var runtime = new Runtime();
runtime.Providers.Set("ollama", OpenAICompatibleProvider.FromPreset(
    runtime.Catalog, "ollama", TimeProvider.System, baseUrl: new Uri("http://127.0.0.1:11500/v1")));
Llm.Default = runtime;
 
Console.WriteLine(await Llm.AskAsync("ollama:llama3.2", "What is the capital of Australia?"));
Text
The capital of Australia is Canberra.

LM Studio or vLLM. They have presets too (lmstudio: on port 1234, vllm: on port 8000), and the call is the same:

C#
using OneLlm;
 
Console.WriteLine(await Llm.AskAsync("lmstudio:llama-3.2-3b-instruct", "What is the capital of Australia?"));
Text
The capital of Australia is Canberra.

Run the code without Ollama, in tests or CI, with the scripted provider registered under the preset’s name:

C#
using OneLlm;
using OneLlm.Testing;
 
var runtime = new Runtime();
runtime.Providers.Set("ollama", new ScriptedProvider("ollama").EnqueueText("Canberra."));
Llm.Default = runtime;
 
Console.WriteLine(await Llm.AskAsync("ollama:llama3.2", "What is the capital of Australia?"));
Text
Canberra.

The short answer

To call Ollama from C#, install Ollama, pull a model (ollama pull llama3.2), add the RecursiveAI.OneLlm package and call Llm.AskAsync("ollama:llama3.2", "your prompt"). The ollama: prefix selects OneLLM’s preset for Ollama’s OpenAI-compatible endpoint at http://localhost:11434/v1. It needs no key, and the call is priced at zero. The same library streams (Llm.StreamTextAsync), keeps a conversation (Llm.Chat) and reports token usage, and switching to a hosted model later means changing the model name.

OneLLM is free and open source under MIT or Apache 2.0, with no native dependencies. The rest of this page answers the questions people ask next.

Does Ollama have an OpenAI-compatible API?

Yes. Ollama’s documentation on OpenAI compatibility says it “supports a subset of the OpenAI API”, served by the local server at http://localhost:11434/v1/. Its /v1/chat/completions endpoint lists chat completions, streaming, JSON mode, vision and tools among its supported features. For a local server, it notes that OpenAI clients require an API key value, “but Ollama ignores it.”

OneLLM uses that endpoint. Its OpenAI and compatible servers notes list the presets: ollama at http://localhost:11434/v1 with no key variable, alongside vllm, lmstudio and hosted servers such as OpenRouter, Groq, DeepSeek, xAI, Mistral and Together. The same notes say “Presets accept any model name” and “Presets for servers on the local machine (local: true) are priced at zero.” Features beyond chat depend on the server: OneLLM’s README describes what the compatible provider supports as “what the server implements.”

How do you use Ollama’s API from C#?

There are two ways in.

  • Plain HTTP. POST JSON to http://localhost:11434/v1/chat/completions with HttpClient and parse the reply yourself. That works for one call, but you write the request and response types, streaming, retries and error handling.
  • A library. With OneLLM, the request is built, sent and parsed for you, and you get a .NET exception per failure kind. A refused connection is a TransportException, and the errors notes say transport failures are retried, two retries by default.

The request OneLLM sends is plain Chat Completions JSON. For the snippet at the top, the stand-in server logged exactly this:

Text
{"model": "llama3.2", "messages": [{"role": "user", "content": "What is the capital of Australia?"}]}

A streamed call adds "stream": true and "stream_options": {"include_usage": true}, so the token counts arrive with the stream.

Which Ollama model should you use from C#?

Pick the model by the task and by what your machine can run. Ollama’s model library lists what’s available, with sizes. Whatever you pull, use the same name after ollama:, for example ollama:llama3.2. Presets accept any model name, so OneLLM sends the name to Ollama as is, typos included.

To compare a local model with a hosted one, run the same code with two model names and compare the answers and the cost lines. Hosted calls need that provider’s key (OPENAI_API_KEY, GEMINI_API_KEY, ANTHROPIC_API_KEY). Of the hosted providers, OneLLM’s results say a live smoke test at release passed against OpenAI (8 of 8 cases) and Gemini (7 of 7). Anthropic, Azure OpenAI / Foundry, Amazon Bedrock and Vertex AI were not run live. For the compatible-server presets, the providers notes add that their recorded fixtures “pin the request each preset sends and how its answers decode, not the server’s behaviour.”

Can you build AI agents with C# and Ollama?

Yes, if the model can call tools. Ollama’s documentation lists tools among the features of its chat completions endpoint, and a OneLLM agent sends its tools with each chat request. Set model: ollama:llama3.2 in the agent’s definition file. The agent recipe shows the loop, with C# methods as tools. How well a model picks and fills in tools varies, so try your own task on the model before you rely on it.

All onellm recipes