
Ранее мы рассказывали о 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.
Если вам интересна теория на которой построена библиотека, то вся математика разобрана в отдельной статье: от вектора задачи и прогноза качества до судьи и обучения на отзывах.

