Назад к статьям
Статья

Интеграция Ollama с C# и .NET — локальные LLM, структурированный вывод и vision-модели

Интеграция 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.Ollama.NET: типизированный .NET-адаптер

Пакет Laraue.Ollama.NET оборачивает нативный API типизированным интерфейсом. JSON Schema генерируется автоматически из вашего C# класса через рефлексию — изменения схемы подхватываются автоматически без правки кода запроса.

Примечание: ранее пакет публиковался как Laraue.Core.Ollama. Он был переименован в Laraue.Ollama.NET — обновите команду dotnet add package/PackageReference и using-директивы при обновлении со старой версии.

NuGet последняя версия
Загрузки загрузки
GitHub Laraue.Ollama.NET

Интерфейс

IOllamaPredictor (пространство имён Laraue.Ollama.NET) предоставляет три перегрузки:

public interface IOllamaPredictor
{
    // Vision-модель: структурированный вывод + изображение
    Task<TModel> PredictAsync<TModel>(
        string modelName,
        string prompt,
        string base64EncodedImage,
        Dictionary<string, object>? additionalParameters = null,
        CancellationToken ct = default)
        where TModel : class;

    // Текстовая модель: структурированный вывод без изображения
    Task<TModel> PredictAsync<TModel>(
        string modelName,
        string prompt,
        Dictionary<string, object>? additionalParameters = null,
        CancellationToken ct = default)
        where TModel : class;

    // Сырая строка ответа — без схемы и десериализации
    Task<string> PredictAsync(
        string modelName,
        string prompt,
        Dictionary<string, object>? additionalParameters = null,
        CancellationToken ct = default);
}

Используйте generic-перегрузки, когда нужен структурированный вывод, разобранный в C# тип. Используйте сырую перегрузку, когда нужен текстовый ответ напрямую — для свободной генерации или когда парсинг реализован самостоятельно. additionalParameters — новое дополнение: передавайте параметры вроде temperature или top_p напрямую в запрос к Ollama, не обращаясь к нативному HTTP API.

Установка

dotnet add package Laraue.Ollama.NET

Настройка

Зарегистрируйте предиктор в 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: 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: "Оцени качество ремонта, видимое на фотографии квартиры.",
    base64EncodedImage: base64Image,
    ct: 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, которые хранятся для отладки промптов. Среднее по всем фото объявления входит в итоговый рейтинг идеальности. Как работает формула ранжирования.