Integrating Ollama with C# and .NET lets you run open-source language and vision models locally — no cloud API keys, no per-call costs, no data leaving your server. This article covers the native Ollama HTTP API, structured output with JSON Schema, vision model image analysis, and a typed .NET adapter library that generates request schemas automatically from C# classes.
Two production projects already use this approach: the real estate aggregator uses Ollama to score apartment photos by renovation quality; the language learning bot uses it for vocabulary generation. Both run inference locally with no third-party API dependency.
Why Use Ollama Instead of a Cloud API
Cloud APIs (ChatGPT, DeepSeek, Gemini) are the obvious first choice — until they aren't. Common reasons to run locally:
| Situation | Why cloud doesn't work |
|---|---|
| Processing personal data | Sending user data to external providers may violate GDPR or other regulations |
| High availability requirements | Your uptime can't depend on a third-party API's SLA |
| Export-restricted countries | Some AI providers don't operate in certain regions |
| Cost-sensitive workloads | Per-call API pricing becomes expensive at volume |
| Offline or air-gapped environments | No internet access by definition |
For MVPs and hypothesis testing, pre-trained open-source models via Ollama are the fastest path: swap a model by changing one config string, no retraining required. If the MVP proves viable, you can replace the model with a fine-tuned version later — the integration code stays the same.
Ollama provides a universal HTTP API over different models. When you start Ollama, it runs a local service on port 11434 by default. Any HTTP client can call it — no SDK required.
The Native Ollama HTTP API
Text Generation
Send a POST to /api/generate:
{
"model": "gemma3:12b",
"prompt": "Translate the following text to French: 'The apartment has two rooms and a large balcony.'",
"stream": false
}
The response:
{
"model": "gemma3:12b",
"response": "L'appartement a deux pièces et un grand balcon."
}
To include an image, add the images field with a base64-encoded string. Only vision-capable models use it — for text-only models the field is ignored:
{
"model": "qwen2.5vl:3b",
"prompt": "Describe what you see in this photo.",
"stream": false,
"images": ["<base64EncodedImageBytes>"]
}
The response:
{
"model": "qwen2.5vl:3b",
"response": "The photo shows a bright living room with white walls, laminate flooring, and large windows."
}
Structured Output With JSON Schema
The format field enables structured output — the model returns a JSON object matching your schema instead of free text. This is critical for any use case where you need to parse the response programmatically:
{
"model": "qwen2.5vl:3b",
"prompt": "Analyze this apartment photo and return your assessment.",
"stream": false,
"images": ["<base64EncodedImageBytes>"],
"format": {
"type": ["object"],
"properties": {
"RenovationRating": {
"type": ["number"]
},
"Tags": {
"type": ["array"],
"items": { "type": ["string"] }
},
"Description": {
"type": ["string"]
}
}
}
}
The response matches the schema:
{
"model": "qwen2.5vl:3b",
"data": {
"RenovationRating": 0.82,
"Tags": ["clean", "bright", "new_windows", "modern_kitchen"],
"Description": "Well-maintained apartment with recent renovation, good natural light, and updated fixtures."
}
}
The Problem With the Native API in Practice
Writing the format JSON Schema by hand for every response type is tedious and error-prone. A missing "type" wrapper, a misspelled property name, or a wrong nesting level silently produces a response that doesn't match your C# class — and you find out at deserialization time, not at the call site.
The other pain point is boilerplate: every call needs HTTP client setup, JSON serialization, base64 encoding, error handling, and response parsing. None of that is interesting code.
Laraue.Ollama.NET: A Typed .NET Adapter
The Laraue.Ollama.NET NuGet package wraps the native API with a typed interface. It generates the JSON Schema automatically from your C# class using reflection — so schema changes are picked up automatically without touching any request code.
Note: this package was previously published as
Laraue.Core.Ollama. It was renamed toLaraue.Ollama.NET; update yourPackageReference/dotnet add packagecommand and namespace usings if you're upgrading from the old name.
| NuGet | |
| Downloads | |
| GitHub | Laraue.Ollama.NET |
The Interface
IOllamaPredictor (namespace Laraue.Ollama.NET) exposes three overloads:
public interface IOllamaPredictor
{
// Vision model: structured output + image
Task<TModel> PredictAsync<TModel>(
string modelName,
string prompt,
string base64EncodedImage,
Dictionary<string, object>? additionalParameters = null,
CancellationToken ct = default)
where TModel : class;
// Text model: structured output, no image
Task<TModel> PredictAsync<TModel>(
string modelName,
string prompt,
Dictionary<string, object>? additionalParameters = null,
CancellationToken ct = default)
where TModel : class;
// Raw string response — no schema, no deserialization
Task<string> PredictAsync(
string modelName,
string prompt,
Dictionary<string, object>? additionalParameters = null,
CancellationToken ct = default);
}
Use the generic overloads when you need structured output parsed into a C# type. Use the raw overload when you want the model's text response directly — useful for freeform generation or when you handle parsing yourself. additionalParameters is a new addition: pass through options like temperature or top_p straight to the underlying Ollama request without dropping to the native HTTP API.
Installation
dotnet add package Laraue.Ollama.NET
Setup
Register the predictor in your DI container:
services.AddHttpClient<IOllamaPredictor, OllamaPredictor>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://localhost:11434/");
// Replace with your Ollama host if running on a separate machine
});
Define Your Response Contract
Any C# class or record works. Properties map to the generated JSON Schema:
public record PredictionResult
{
public required double RenovationRating { get; set; } // maps to "number"
public required string[] Tags { get; set; } // maps to "array" of "string"
public required string Description { get; set; } // maps to "string"
}
The adapter reflects over PredictionResult at call time, builds the format JSON Schema, sends the request, and deserializes the response back into PredictionResult. Adding a new property to the record immediately affects the next request — no manual schema editing.
Text Analysis
var result = await ollamaPredictor.PredictAsync<PredictionResult>(
model: "gemma3:12b",
prompt: "Classify the following text and return structured output.",
ct: ct);
Console.WriteLine(result.RenovationRating); // 0.74
Console.WriteLine(string.Join(", ", result.Tags)); // "clean, bright, good_location"
Image Analysis
var imageBytes = File.ReadAllBytes("apartment.jpg");
var base64Image = Convert.ToBase64String(imageBytes);
var result = await ollamaPredictor.PredictAsync<PredictionResult>(
model: "qwen2.5vl:3b",
prompt: "Rate the renovation quality visible in this apartment photo.",
base64EncodedImage: base64Image,
ct: ct);
Model Selection Notes
- For text tasks:
gemma3:12bandqwen2.5:7bare good starting points. Larger parameter counts produce better reasoning; smaller ones run faster. - For vision tasks:
qwen2.5vl:3bhandles image analysis well at modest hardware requirements. Check the model's page on ollama.com/library to confirm vision support before downloading. - First call latency: Ollama downloads the model on first use if it's not already cached locally. Subsequent calls within the default 5-minute idle window load the model from memory and are significantly faster.
- Model switching: Change the
modelparameter string — nothing else in your code changes. This is the main reason to use Ollama over fine-tuned model hosting: zero-cost model comparison.
Real-World Usage
The real estate aggregator uses IOllamaPredictor with qwen2.5vl to score apartment photos for renovation quality. Every listing photo gets a RenovationRating between 0 and 1, plus arrays of Advantages and Problems tags that are stored for prompt debugging. The per-listing average across all photos feeds into the final ideality ranking. How the ranking formula works.