Интеграция Ollama с C# и .NET позволяет запускать open-source языковые и vision-модели локально — без облачных API-ключей, без оплаты за каждый вызов, без передачи данных на внешние серверы. В этой статье разбирается нативный HTTP API Ollama, структурированный вывод через JSON Schema, анализ изображений vision-моделями и типизированный .NET-адаптер, который генерирует схемы запросов из C# классов автоматически.
Два продакшн-проекта уже используют этот подход: агрегатор недвижимости применяет Ollama для оценки фотографий квартир по качеству ремонта; бот для изучения языков — для генерации словарного запаса. Оба работают локально без зависимости от сторонних API. Описание проекта агрегатора квартир.
Почему Ollama, а не облачный API
Облачные API (ChatGPT, DeepSeek, Gemini) — очевидный первый выбор, пока не сталкиваешься с ограничениями. Типичные причины запускать модели локально:
| Ситуация | Почему облако не подходит |
|---|---|
| Обработка персональных данных | Передача данных пользователей внешнему провайдеру может нарушать 152-ФЗ, GDPR или внутреннюю политику |
| Высокие требования к доступности | Uptime не может зависеть от SLA стороннего сервиса |
| Санкционные ограничения | Часть AI-провайдеров недоступна в ряде стран из-за экспортных ограничений |
| Высокая нагрузка | Поцентрично-платная модель становится дорогой при большом объёме вызовов |
| Офлайн или изолированная среда | Нет доступа в интернет по условиям инфраструктуры |
Для MVP и проверки гипотез предобученные open-source модели через Ollama — самый быстрый путь: смените модель изменением одной строки конфига, без переобучения. Если MVP докажет жизнеспособность, модель можно заменить на дообученную — интеграционный код не изменится.
Ollama предоставляет единый HTTP API для разных моделей. При запуске Ollama поднимает локальный сервис на порту 11434 по умолчанию. Любой HTTP-клиент может его вызвать — никакого SDK не требуется.
Нативный HTTP API Ollama
Генерация текста
Отправьте POST на /api/generate:
{
"model": "gemma3:12b",
"prompt": "Переведи следующий текст на французский язык: 'Квартира имеет две комнаты и большой балкон.'",
"stream": false
}
Ответ:
{
"model": "gemma3:12b",
"response": "L'appartement a deux pièces et un grand balcon."
}
Для передачи изображения добавьте поле images с base64-строкой. Только vision-модели его обрабатывают — для текстовых моделей поле игнорируется:
{
"model": "qwen2.5vl:3b",
"prompt": "Опиши, что изображено на фотографии.",
"stream": false,
"images": ["<base64EncodedImageBytes>"]
}
Ответ:
{
"model": "qwen2.5vl:3b",
"response": "На фотографии изображена светлая гостиная с белыми стенами, ламинатом и большими окнами."
}
Структурированный вывод через JSON Schema
Поле format включает структурированный вывод — модель возвращает JSON-объект по вашей схеме вместо произвольного текста. Это необходимо в любом сценарии, где ответ нужно парсить программно:
{
"model": "qwen2.5vl:3b",
"prompt": "Проанализируй фотографию квартиры и верни оценку.",
"stream": false,
"images": ["<base64EncodedImageBytes>"],
"format": {
"type": ["object"],
"properties": {
"RenovationRating": {
"type": ["number"]
},
"Tags": {
"type": ["array"],
"items": { "type": ["string"] }
},
"Description": {
"type": ["string"]
}
}
}
}
Ответ соответствует схеме:
{
"model": "qwen2.5vl:3b",
"data": {
"RenovationRating": 0.82,
"Tags": ["clean", "bright", "new_windows", "modern_kitchen"],
"Description": "Ухоженная квартира с недавним ремонтом, хорошим естественным светом и обновлёнными элементами."
}
}
Проблема нативного API на практике
Писать format JSON Schema вручную для каждого типа ответа утомительно и чревато ошибками. Пропущенная обёртка "type", опечатка в имени свойства или неправильный уровень вложенности молча приводят к ответу, не совпадающему с вашим C# классом — и вы узнаёте об этом при десериализации, а не в точке вызова.
Вторая проблема — бойлерплейт: каждый вызов требует настройки HTTP-клиента, сериализации JSON, base64-кодирования, обработки ошибок и парсинга ответа. Ни одна из этих задач не является интересным кодом.
Laraue.Core.Ollama: типизированный .NET-адаптер
Пакет Laraue.Core.Ollama оборачивает нативный API типизированным интерфейсом. JSON Schema генерируется автоматически из вашего C# класса через рефлексию — изменения схемы подхватываются автоматически без правки кода запроса.
| NuGet |
|
| Загрузки |
|
| GitHub | Laraue.Core.Ollama |
Интерфейс
IOllamaPredictor предоставляет три перегрузки:
public interface IOllamaPredictor
{
// Vision-модель: структурированный вывод + изображение
Task<TModel> PredictAsync<TModel>(
string modelName,
string prompt,
string base64EncodedImage,
CancellationToken ct = default)
where TModel : class;
// Текстовая модель: структурированный вывод без изображения
Task<TModel> PredictAsync<TModel>(
string modelName,
string prompt,
CancellationToken ct = default)
where TModel : class;
// Сырая строка ответа — без схемы и десериализации
Task<string> PredictAsync(
string modelName,
string prompt,
CancellationToken ct = default);
}
Используйте generic-перегрузки, когда нужен структурированный вывод, разобранный в C# тип. Используйте сырую перегрузку, когда нужен текстовый ответ напрямую — для свободной генерации или когда парсинг реализован самостоятельно.
Установка
dotnet add package Laraue.Core.Ollama
Настройка
Зарегистрируйте предиктор в DI-контейнере:
services.AddHttpClient<IOllamaPredictor, OllamaPredictor>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://localhost:11434/");
// Замените на адрес вашего Ollama, если запущен на отдельной машине
});
Определите контракт ответа
Подходит любой C# класс или record. Свойства маппятся в генерируемую JSON Schema:
public record PredictionResult
{
public required double RenovationRating { get; set; } // → "number"
public required string[] Tags { get; set; } // → "array" of "string"
public required string Description { get; set; } // → "string"
}
Адаптер рефлектирует PredictionResult в момент вызова, строит format JSON Schema, отправляет запрос и десериализует ответ обратно в PredictionResult. Добавление нового свойства в record сразу влияет на следующий запрос — правки схемы вручную не требуется.
Анализ текста
var result = await ollamaPredictor.PredictAsync<PredictionResult>(
model: "gemma3:12b",
prompt: "Классифицируй следующий текст и верни структурированный результат.",
ct);
Console.WriteLine(result.RenovationRating); // 0.74
Console.WriteLine(string.Join(", ", result.Tags)); // "clean, bright, good_location"
Анализ изображений
var imageBytes = File.ReadAllBytes("apartment.jpg");
var base64Image = Convert.ToBase64String(imageBytes);
var result = await ollamaPredictor.PredictAsync<PredictionResult>(
model: "qwen2.5vl:3b",
prompt: "Оцени качество ремонта, видимое на фотографии квартиры.",
base64Image,
ct);
Выбор модели
-
Для текстовых задач:
gemma3:12bиqwen2.5:7b— хорошие отправные точки. Больше параметров — лучше рассуждения, меньше — быстрее инференс. -
Для vision-задач:
qwen2.5vl:3bхорошо справляется с анализом изображений при умеренных требованиях к железу. Проверьте поддержку vision на странице модели на ollama.com/library до загрузки. - Задержка первого вызова: Ollama скачивает модель при первом использовании, если она не закэширована локально. Последующие вызовы в пределах стандартного 5-минутного окна простоя загружают модель из памяти и значительно быстрее.
-
Смена модели: Измените строку параметра
model— больше ничего в коде менять не нужно. Это главное преимущество Ollama перед хостингом дообученной модели: сравнение моделей без инфраструктурных затрат.
Применение в реальных проектах
Агрегатор недвижимости использует IOllamaPredictor с qwen2.5vl для оценки фотографий квартир по качеству ремонта. Каждое фото получает RenovationRating от 0 до 1, а также массивы Advantages и Problems, которые хранятся для отладки промптов. Среднее по всем фото объявления входит в итоговый рейтинг идеальности. Как работает формула ранжирования.
Часто задаваемые вопросы
На каком порту работает Ollama по умолчанию?
Ollama слушает порт 11434 по умолчанию. Базовый URL для всех API-вызовов — http://localhost:11434. Это можно изменить через переменную окружения OLLAMA_HOST, если Ollama запущен на отдельной машине или в контейнере.
Как работает структурированный вывод Ollama в C#?
Поле format в запросе Ollama принимает JSON Schema объект. Модель ограничивает свой вывод этой схемой перед возвратом. Адаптер Laraue.Core.Ollama генерирует эту схему автоматически из вашего C# класса через рефлексию — вы определяете форму ответа как C# record, адаптер делает остальное.
Можно ли использовать Ollama с vision-моделями в C#?
Да. Передайте base64-закодированные байты изображения в поле images запроса или используйте перегрузку PredictAsync<TModel>(modelName, prompt, base64EncodedImage, ct) в адаптере. Изображения обрабатывают только модели с поддержкой vision — подтвердите поддержку на странице модели на ollama.com/library до загрузки.
Как переключаться между моделями в Ollama?
Измените параметр model в API-вызове или вызове PredictAsync. Никаких других изменений в коде не требуется. Ollama автоматически управляет загрузкой и выгрузкой моделей. Это делает сравнение качества моделей для конкретной задачи простым без инфраструктурных изменений.
Подходит ли Ollama для продакшна?
Ollama хорошо работает в продакшне для нагрузок, нечувствительных к задержке и привязанных к GPU, где ограничения по приватности данных или стоимости исключают облачные API. Для latency-sensitive или высоконагруженных продакшн-систем тщательно оцените пропускную способность на целевом железе перед принятием решения. Архитектурный паттерн из проекта недвижимости — изолированный GpuWorkerHost, разбирающий очередь — практичный способ отвязать throughput инференса от остального приложения.