FAIRouter вышел на NuGet. Выбираем языковую модель под задачу в несколько строк на C#


Ранее мы рассказывали о FAIRouter, нашей библиотеке для маршрутизации запросов. Коротко напомню, что она делает. Есть несколько языковых моделей, у них разные цены, скорость и сильные стороны. FAIRouter сам выбирает модель под задачу, проверяет готовый ответ и учится на отзывах. Общая схема работы показана на рисунке 1.

Версия на C# теперь опубликована на NuGet, поэтому собирать ее из исходников больше не нужно. В этой статье покажу, как поставить пакет и что с ним можно делать. Все примеры ниже были собраны на пакете версии 0.1.0.
Установка
Понадобится .NET 10. Создадим консольный проект и подключим пакет:
dotnet new console -f net10.0 -n RouterDemo
cd RouterDemo
dotnet add package FAI.RouterТакже нужен ключ поставщика моделей. В примерах будем использовать OpenRouter, ключ положим в переменную среды:
$env:OPENROUTER_API_KEY = "ваш ключ"Первый запрос
Заменим содержимое Program.cs следующим кодом:
using FAI.Router;
FaiRouter router = await FaiRouter.FromOpenRouterAsync(
apiKey: Environment.GetEnvironmentVariable("OPENROUTER_API_KEY")!,
modelIds:
[
"google/gemini-2.5-flash",
"openai/gpt-4.1-mini",
"anthropic/claude-haiku-4.5"
],
databasePath: "fai-router.db");
RouterAnswer answer = await router.AskAsync(
"Напиши обзор методов кластеризации на 1500 знаков");
Console.WriteLine($"{answer.Winner.Name}: {answer.Score}");
Console.WriteLine(answer.Text);
Console.WriteLine(answer.Critic);Здесь modelIds задает список кандидатов, а в файле fai-router.db хранятся веса и журнал ходов. Если файла нет, он будет создан. В ответе нас интересуют четыре поля:
Winnerпоказывает, какая модель была выбрана;Textсодержит ответ модели;Scoreсодержит оценку судьи от 0 до 1;Criticпоказывает, чем ответ отличается от заказа, по пунктам.
Судья проверяет ответ автоматически. Например, если в задании было четыре раздела и таблица, а в ответе один раздел без таблицы, это будет видно в Critic.
Список моделей можно вообще не задавать. Тогда выбор идет среди 54 популярных моделей из комплекта пакета:
FaiRouter router = await FaiRouter.FromOpenRouterAsync(
apiKey: Environment.GetEnvironmentVariable("OPENROUTER_API_KEY")!,
databasePath: "fai-router.db");Обучение на отзывах
Самое интересное начинается, когда маршрутизатор получает отзывы. Отзыв задается числом от 0 до 1, где 1 означает отличный ответ, а 0.2 слабый:
router.Feedback(answer.RoundId!.Value, score: 1.0);
router.Train(epochs: 10);
router.Save();После этого веса сохраняются в базе, и на похожих задачах маршрутизатор учитывает полученный опыт. Отзывы можно накапливать и обучать маршрутизатор реже, например один раз в день. Стоит учитывать, что в журнале хранятся тексты запросов.
Что важнее, цена или качество
В библиотеке есть три готовых профиля: Quality, Balance и Price. По умолчанию используется Balance. Профиль можно менять на каждом запросе:
RouterAnswer cheap = await router.AskAsync(
prompt, weights: RouteWeights.Price);
RouterAnswer strong = await router.AskAsync(
prompt, weights: RouteWeights.Quality);Часто удобнее другое правило: найти тех, кто справится с задачей, и среди них выбрать самого дешевого и быстрого. Для этого есть планка достаточности, т.е. минимальная вероятность того, что ответ понравится человеку:
RouterAnswer enough = await router.AskAsync(prompt, bar: 0.7);
Console.WriteLine(enough.Reached);Поле Reached показывает, дотянул ли кто-то до планки. Планка начинает работать, когда накопится хотя бы три отзыва человека.
Диалог вместо одного запроса
В AskAsync можно передать диалог целиком. Задание распознается по последней реплике пользователя, а модель видит всю историю:
using AI.LLM.Core.Models.Common.Messages;
LLMMessage[] dialog =
[
new(LLMMessage.UserRole, "Что такое кластеризация?"),
new(LLMMessage.AssistantRole, "Это разбиение объектов на группы."),
new(LLMMessage.UserRole, "Теперь объясни это школьнику")
];
RouterAnswer replied = await router.AskAsync(dialog);Другие поставщики
Пакет работает не только с OpenRouter. Всего есть три способа подключения:
Метод | Куда подключается |
FromOpenRouterAsync | OpenRouter |
FromFractalRouterAsync | FractalRouter |
FromOpenAiCompatibleAsync | любой сервер с протоколом OpenAI chat completions |
Последний вариант подойдет и для своей модели. Цену такой модели нужно указать самому:
FaiRouter local = await FaiRouter.FromOpenAiCompatibleAsync(
baseUrl: "http://localhost:8000/v1",
apiKey: "local",
modelIds: ["my-model"],
prices: new Dictionary<string, Price>
{
["my-model"] = new Price(0, 0)
},
judgeModel: "my-model");Судья работает через того же поставщика, по умолчанию на модели openai/gpt-4o-mini. Если на вашем сервере такой модели нет, укажите другую в параметре judgeModel, как в примере выше.
Не только модели
Выбирать можно не только модели, но и инструменты с агентами. Для этого в конструктор передаются свои кандидаты и функция, которая их запускает. Рассмотрим пример с двумя вариантами поиска:
using AI.LLM.Core.Models.Common.Messages;
using AI.LLM.Services.LLM;
using FAI.Router;
using FAI.Router.LLM;
using FAI.Router.RoutedElements;
BaseRoutedElement deepSearch = new()
{
Name = "deep-search",
DPMTInp = 5,
DPMTOutp = 20,
TPS = 30
};
BaseRoutedElement fastSearch = new()
{
Name = "fast-search",
DPMTInp = 1,
DPMTOutp = 2,
TPS = 200
};
LLMBase judge = new OpenAiCompatibleLlm(
Providers.OpenRouter,
"ваш ключ",
"openai/gpt-4o-mini");
FaiRouter tools = new(
[deepSearch, fastSearch],
(candidate, messages, token) =>
RunSearchAsync(candidate.Name!, messages, token),
"tools.db",
llm: judge);Здесь DPMTInp и DPMTOutp задают цену за миллион токенов, а TPS скорость в токенах в секунду. Для инструмента эти числа условные, важно только их соотношение. Функцию RunSearchAsync вы пишете сами, она запускает нужный поиск и возвращает текст. Такие кандидаты стартуют со случайными весами, поэтому первое время им нужны ваши отзывы.
Итоги
Таким образом, чтобы попробовать FAIRouter, достаточно одной команды dotnet add package FAI.Router и нескольких строк кода. Пакет распространяется под лицензией Apache-2.0, исходный код и подробная документация опубликованы на GitHub, а замечания и вопросы можно оставить в разделе issues.
Если вам интересна теория на которой построена библиотека, то вся математика разобрана в отдельной статье: от вектора задачи и прогноза качества до судьи и обучения на отзывах.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.