- Регистрация
- 01.10.2015
- Сообщения
- 245
- Благодарностей
- 989
- Баллы
- 93
Приветствую всех!
В одной из предыдущих статей по LLM я упоминал о возможности пробрасывать им вызов внешних функций:
Помимо function calling в интернете можно встретить названия tool use, function call. Всё это плюс-минус значит одно и то же – (дать) возможность модели выполнять функции (практически любые) или целые инструменты. А сейчас ещё и целые API и интеграции (с помощью MCP, Model Context Protocol)
С помощью этой фичи можно превращать LLM-ки из простого собеседника или генератора контента в AI-ассистента, а то и полноценного агента. Простыми словами, у модели появляются "руки"/инструменты.
Но, разумеется, какие у неё будут "руки"/инструменты, должны позаботиться вы сами.
Понятно, что для python уже миллион готовых решений как это делать и использовать.
Но если мы обычные пользователи ZennoPoster или ZennoDroid (относительно обычные, чутка понимать C#-сниппеты важно), и хотим нативно использовать function call внутри Zenno, без необходимости присобачивать скрипты на питоне к шаблону. Что нам в таком случае делать?
Об этом и поговорим в данной статье, и рассмотрим как заставить LLM вызывать наши же функции, написанные внутри Zenno (спойлер – внутри блока "Общий код").
Для вызова функций моделью нам понадобится в первую очередь сама модель.
Для этого будем использовать бюджетный и безопасный вариант – открытые локальные LLM-ки, и соответствующий софт для их запуска. В одной из предыдущих статей я подробно рассматривал LM Studio – её предлагаю использовать и в этот раз. Так как если что-то непонятно по софту для запуска модели, можно посмотреть в той предыдущей статье.
Возможностей для использования масса (так как пробрасываемые в модель функции могут быть абсолютно любыми), детали скорее зависят от вашей фантазии и ваших шаблонов, схем работы.
В качестве понятного начального примера предлагаю рассмотреть в некотором смысле "полувнешний" кейс – случай, когда мы решили сделать себе ИИ-помощника/ассистента по управлению шаблонами в ZennoPoster.
Иными словами, мы хотим для удобства использовать базовое предназначение LLM-ком обработку естественного языка. То есть, говорим ей своими словами что нужно сделать, она понимает что нужно, и запускает соответствующую функцию с нужными параметрами.
Например, мы хотим написать сообщение нейронке "Запусти шаблон Parser123 на 100 выполнений", и чтобы оно сразу так и заработало. А не заходить в зеннку и тыкать какие-то кнопки, какие-то настройки.
В предыдущем конкурсе Zennolab Master были 2 хорошие работу на тему управления шаблонами:
Автоматизация управления проектами
EasyControl 2.0 : Управляйте ZennoPoster с помощью Telegram
Что я хочу сказать – можно скомбинировать материалы этих работ и LLM c поддержкой function call – и получить возможность управлять своими шаблонами просто путём писания своими словами команд/задач ИИ-ассистенту через чатик в telegram.
На этот простой кейс и буду ориентироваться, демонстрируя далее код и пример шаблона, как использовать модель с tool use внутри Zenno.
Прежде чем переходить к тестовому шаблону и коду, надо сказать несколько слов о самих моделях.
Есть модели, которые нативно поддерживают tool use (он же function calling, в разных местах названия разные), т.е. они изначально обучались под использование в таком качестве.
Именно их лучше всего выбирать для наших задач.
Благо, сейчас в LM Studio есть прям такая опция и пиктограммка при поиске моделей.
Заодно сразу скажу, что для наших целей не нужны большие модели – подойдут в том числе такие крохи, как этот младший Qwen 3 со скриншота.
Для запуска такой модели и видеокарта не сказать что сильно нужна – будет вполне неплохо работать и на процессоре с оперативкой, пусть и помедленнее.
После того, как скачали подходящую модель, нам нужно будет запустить локальный сервер в LM Studio, чтобы обращаться к LLM-ке из ZennoPoster.
Для этого просто делаем действия как показано на скриншоте.
Рассмотрим тестовый проект, в котором используется обращение к модели из LM Studio с возможностью вызывать ею наши функции. Он прикреплён к статье внизу в виде файла.
Сразу скажу, что код всего проекта целиком в блоке "Общий код". Когда открываем прикреплённый шаблон, по этой причине сразу стоит идти туда, так как в сниппете просто дёргается оттуда код.
Код демонстрации небольшой, всего на 400 строк, но удобнее всего его писать и рассматривать (а также расширять) в стандартной объектной структуре C#. Адаптировать в свои шаблоны тоже сразу лучше в этом виде, плюс с помощью тех же нейронок (в качестве ваших ассистентов по кодингу) расширять тоже будет вам проще.
Код разбит на 3 смысловых блока.
Давайте начнём с функций, которые будем "пробрасывать" модели.
Для примера я подготовил 5 простых функций – 3 с прямым воздействием на какой-нибудь из добавленных шаблонов в интерфейс ZennoPoster, 2 просто тестовых со сложением и умножений чисел.
Перейдём к коду, который будет обращаться к модели в LM Studio.
Метод RunExample у нас по сути является диспетчером, в котором последовательно выполняются нужные шаги.
Внутри метода RunExample вызываются остальные методы, ответственные за действия на каждом шаге. В целом, в них нет чего-то специфического, чтобы рассматривать каждый из них.
Поэтому тут приведу код пары из них, наиболее интересных (остальное можете посмотреть самостоятельно, открыв шаблон в ProjectMaker).
В методе BuildInitialRequest происходит формирование запроса к модели. Тут же нам надо оформить доступные функции для вызова (а также доступные параметры для них) в понятном для модели виде.
Тут кстати сразу замечу, что в данном демонстрационном шаблоне я сделал подхват промпта из файла "prompt.txt" из папки с шаблоном. Разумеется, так делать необязательно, можно вынести промпт в любое место, хоть во входные настройки, хоть посылать его через бота в telegram, как упоминалось выше.
Если модель ответила нам, решив вызвать какую-либо функцию – в методе InvokeFunction мы перехватываем это "намерение" модели и, собственно, производим вызов функции по её выбору, сразу с нужными параметрами.
Параметры, как можно понять, модель выбирает так же, как и саму функцию.
Вот в общем-то и всё, что нужно, чтобы заставить модель использовать функции внутри Zenno.
Повторюсь на всякий случай, что функции могут быть абсолютно любыми (какие сами напишите, найдёте на форуме/в интернете, какие напишет вам другая нейронка).
Также замечу, что в демонстрационном шаблоне представлена упрощённая схема – модель просто вызывает функцию при необходимости, и всё. Разумеется, ничего не мешает расширить код и сделать другую логику под ваши задачи. Например, чтобы результат выполнения функции возвращался модели, и при необходимости она с ним что-то сразу делала – к примеру, вызывала вторую функцию, используя результат от первой как параметр для второй. Логику можно сооружать какую угодно, всё зависит от конкретных задач.
Наглядную демонстрацию работы шаблона посмотрим в видео.
Если вы плюс-минус интересуетесь новостями по LLM-кам, то наверняка были свидетелями некоторого хайпа по поводу MCP (Model Context Protocol) – открытого протокола предоставления контекста и инструментов моделям, представленого Anthropic в прошлом году.
Если говорить простыми словами – по сути это то же самое, что и обычный tool use, но более удобно для масштабных задач и при целях отвязки от своих локальных функций.
Вы можете не писать свои функции, а просто подключаться к MCP-серверам, которые предоставляют нужные возможности. MCP позволяет компаниям и сервисам самостоятельно создавать MCP-серверы, давая вашим моделям нужный функционал и контекст при подключении к ним. Как можно заметить, это что-то типа привычных нам API у различных сервисов. Только эти API не для программ, а для LLM-ок.
Ну и конечно вы можете поднимать свои MCP-сервера, отделяя функции и другие инструменты, контекст для своих моделей в полную отдельную сущность, например, и заодно на отдельную удалённую машину, как вариант.
Тут сразу пару дисклеймеров.
На этом всё, спасибо за внимание
В одной из предыдущих статей по LLM я упоминал о возможности пробрасывать им вызов внешних функций:
Однако, как что-то подобное сделать в рамках Zenno, тогда не затрагивалось – и похоже, сейчас пришло время.Способность запускать шаблон агенту-тестеру можно дать с помощью function calling (т.е. написать код нужных функций и прописать возможность нейронке вызывать их при необходимости). Для простого понимания – например, способность ChatGPT ходить гуглить что-то, обращаться к DALL-E за генерацией картинок – как раз реализована с помощью function calling.
Помимо function calling в интернете можно встретить названия tool use, function call. Всё это плюс-минус значит одно и то же – (дать) возможность модели выполнять функции (практически любые) или целые инструменты. А сейчас ещё и целые API и интеграции (с помощью MCP, Model Context Protocol)
С помощью этой фичи можно превращать LLM-ки из простого собеседника или генератора контента в AI-ассистента, а то и полноценного агента. Простыми словами, у модели появляются "руки"/инструменты.
Но, разумеется, какие у неё будут "руки"/инструменты, должны позаботиться вы сами.
Понятно, что для python уже миллион готовых решений как это делать и использовать.
Но если мы обычные пользователи ZennoPoster или ZennoDroid (относительно обычные, чутка понимать C#-сниппеты важно), и хотим нативно использовать function call внутри Zenno, без необходимости присобачивать скрипты на питоне к шаблону. Что нам в таком случае делать?
Об этом и поговорим в данной статье, и рассмотрим как заставить LLM вызывать наши же функции, написанные внутри Zenno (спойлер – внутри блока "Общий код").
Для вызова функций моделью нам понадобится в первую очередь сама модель.
Для этого будем использовать бюджетный и безопасный вариант – открытые локальные LLM-ки, и соответствующий софт для их запуска. В одной из предыдущих статей я подробно рассматривал LM Studio – её предлагаю использовать и в этот раз. Так как если что-то непонятно по софту для запуска модели, можно посмотреть в той предыдущей статье.
Кейсы использования
Возможностей для использования масса (так как пробрасываемые в модель функции могут быть абсолютно любыми), детали скорее зависят от вашей фантазии и ваших шаблонов, схем работы.
В качестве понятного начального примера предлагаю рассмотреть в некотором смысле "полувнешний" кейс – случай, когда мы решили сделать себе ИИ-помощника/ассистента по управлению шаблонами в ZennoPoster.
Иными словами, мы хотим для удобства использовать базовое предназначение LLM-ком обработку естественного языка. То есть, говорим ей своими словами что нужно сделать, она понимает что нужно, и запускает соответствующую функцию с нужными параметрами.
Например, мы хотим написать сообщение нейронке "Запусти шаблон Parser123 на 100 выполнений", и чтобы оно сразу так и заработало. А не заходить в зеннку и тыкать какие-то кнопки, какие-то настройки.
В предыдущем конкурсе Zennolab Master были 2 хорошие работу на тему управления шаблонами:
Автоматизация управления проектами
EasyControl 2.0 : Управляйте ZennoPoster с помощью Telegram
Что я хочу сказать – можно скомбинировать материалы этих работ и LLM c поддержкой function call – и получить возможность управлять своими шаблонами просто путём писания своими словами команд/задач ИИ-ассистенту через чатик в telegram.
На этот простой кейс и буду ориентироваться, демонстрируя далее код и пример шаблона, как использовать модель с tool use внутри Zenno.
Выбор моделей в LM Studio
Прежде чем переходить к тестовому шаблону и коду, надо сказать несколько слов о самих моделях.
Есть модели, которые нативно поддерживают tool use (он же function calling, в разных местах названия разные), т.е. они изначально обучались под использование в таком качестве.
Именно их лучше всего выбирать для наших задач.
Благо, сейчас в LM Studio есть прям такая опция и пиктограммка при поиске моделей.
Заодно сразу скажу, что для наших целей не нужны большие модели – подойдут в том числе такие крохи, как этот младший Qwen 3 со скриншота.
Для запуска такой модели и видеокарта не сказать что сильно нужна – будет вполне неплохо работать и на процессоре с оперативкой, пусть и помедленнее.
Запуск сервера в LM Studio
После того, как скачали подходящую модель, нам нужно будет запустить локальный сервер в LM Studio, чтобы обращаться к LLM-ке из ZennoPoster.
Для этого просто делаем действия как показано на скриншоте.
Шаблон с демонстрацией
Рассмотрим тестовый проект, в котором используется обращение к модели из LM Studio с возможностью вызывать ею наши функции. Он прикреплён к статье внизу в виде файла.
Сразу скажу, что код всего проекта целиком в блоке "Общий код". Когда открываем прикреплённый шаблон, по этой причине сразу стоит идти туда, так как в сниппете просто дёргается оттуда код.
Код демонстрации небольшой, всего на 400 строк, но удобнее всего его писать и рассматривать (а также расширять) в стандартной объектной структуре C#. Адаптировать в свои шаблоны тоже сразу лучше в этом виде, плюс с помощью тех же нейронок (в качестве ваших ассистентов по кодингу) расширять тоже будет вам проще.
Код разбит на 3 смысловых блока.
- Функции (методы), которые будут доступны нашей модели для вызова.
- Методы обращения к модели в LM Studio.
- Классы для сериализации в JSON запросов к модели (чтобы было красиво и понятно).
Давайте начнём с функций, которые будем "пробрасывать" модели.
Для примера я подготовил 5 простых функций – 3 с прямым воздействием на какой-нибудь из добавленных шаблонов в интерфейс ZennoPoster, 2 просто тестовых со сложением и умножений чисел.
C#:
/// <summary>
/// Добавляет выполнения указанному шаблону по его имени
/// </summary>
public static bool AddTriesToTemplate (string templateName, int countTries)
{
if (IsTaskExists(templateName))
{
ZennoPoster.AddTries(templateName, countTries);
return true;
}
return false;
}
/// <summary>
/// Устанавливает максимальное количество потоков указанному шаблону по его имени
/// </summary>
public static bool SetMaxThreadsToTemplate (string templateName, int countThreads)
{
if (IsTaskExists(templateName))
{
ZennoPoster.SetMaxThreads(templateName, countThreads);
return true;
}
return false;
}
/// <summary>
/// Останавливает в ZennoPoster указанный шаблон по его имени
/// </summary>
public static bool StopTemplate (string templateName)
{
if (IsTaskExists(templateName))
{
ZennoPoster.StopTask(templateName);
return true;
}
return false;
}
/// <summary>
/// Складывает два числа
/// </summary>
public static int Sum (int x, int y)
{
return x + y;
}
/// <summary>
/// Умножает два числа
/// </summary>
public static int Multiply (int x, int y)
{
return x * y;
}
Метод RunExample у нас по сути является диспетчером, в котором последовательно выполняются нужные шаги.
C#:
public static void RunExample (IZennoPosterProjectModel project)
{
string url = project.Variables["ApiUrl"].Value;
if (string.IsNullOrWhiteSpace(url)) throw new Exception("Не указан API URL во входных настройках!");
// Формирование запроса к модели
var initialRequest = BuildInitialRequest(project);
// Отправка запроса к модели
var response = SendChatRequest(url, initialRequest);
// Парсинг функции, есло модель решила вызвать какую-либо
var toolCall = ParseToolCall(response);
if (toolCall != null)
{
// Выполнение функции, которую решила вызвать модель
string result = InvokeFunction(toolCall);
project.SendInfoToLog($"Результат выполнения функции, которую вызвала модель: {result}", true);
}
else
{
project.SendInfoToLog("Модель не запросила вызов функции.", true);
}
}
Поэтому тут приведу код пары из них, наиболее интересных (остальное можете посмотреть самостоятельно, открыв шаблон в ProjectMaker).
В методе BuildInitialRequest происходит формирование запроса к модели. Тут же нам надо оформить доступные функции для вызова (а также доступные параметры для них) в понятном для модели виде.
C#:
static ChatRequest BuildInitialRequest (IZennoPosterProjectModel project)
{
// Определяем функции, которые будут доступны LLM
var sumFunc = new ToolDefinition
{
Function = new ToolFunction
{
Name = "Sum",
Description = "Складывает два целых числа x и y",
Parameters = new FunctionParametersSchema
{
Properties = new Dictionary<string, FunctionParameter>
{
{ "x", new FunctionParameter { Type = "integer", Description = "Первое число" } },
{ "y", new FunctionParameter { Type = "integer", Description = "Второе число" } }
},
Required = new List<string> { "x", "y" }
}
}
};
var multiplyFunc = new ToolDefinition
{
Function = new ToolFunction
{
Name = "Multiply",
Description = "Умножает два целых числа x и y",
Parameters = new FunctionParametersSchema
{
Properties = new Dictionary<string, FunctionParameter>
{
{ "x", new FunctionParameter { Type = "integer", Description = "Первое число" } },
{ "y", new FunctionParameter { Type = "integer", Description = "Второе число" } }
},
Required = new List<string> { "x", "y" }
}
}
};
var addTriesFunc = new ToolDefinition
{
Function = new ToolFunction
{
Name = "AddTriesToTemplate",
Description = "Добавляет выполнения указанному шаблону по его имени",
Parameters = new FunctionParametersSchema
{
Properties = new Dictionary<string, FunctionParameter>
{
{ "templateName", new FunctionParameter { Type = "string", Description = "Точное название шаблона" } },
{ "countTries", new FunctionParameter { Type = "integer", Description = "Количество повторов, которое надо добавить" } }
},
Required = new List<string> { "templateName", "countTries" }
}
}
};
var setMaxThreadsFunc = new ToolDefinition
{
Function = new ToolFunction
{
Name = "SetMaxThreadsToTemplate",
Description = "Устанавливает максимальное количество потоков указанному шаблону по его имени",
Parameters = new FunctionParametersSchema
{
Properties = new Dictionary<string, FunctionParameter>
{
{ "templateName", new FunctionParameter { Type = "string", Description = "Точное название шаблона" } },
{ "countThreads", new FunctionParameter { Type = "integer", Description = "Количество повторов, которое надо добавить" } }
},
Required = new List<string> { "templateName", "countThreads" }
}
}
};
var stopTaskFunc = new ToolDefinition
{
Function = new ToolFunction
{
Name = "StopTemplate",
Description = "Останавливает в ZennoPoster указанный шаблон по его имени",
Parameters = new FunctionParametersSchema
{
Properties = new Dictionary<string, FunctionParameter>
{
{ "templateName", new FunctionParameter { Type = "string", Description = "Точное название шаблона" } },
},
Required = new List<string> { "templateName" }
}
}
};
// Получаем промпт из файла
string prompt = GetPromptFromFile(project);
// Формируем сообщения к модели
var messages = new List<Message>
{
new Message { Role = "system", Content = "Ты ассистент-помощник по управлению шаблонами в ZennoPoster, который может при необходимости вызывать функции." },
new Message { Role = "user", Content = prompt }
};
// Получаем название модели из входных настроек
string model = project.Variables["Model"].Value;
if (string.IsNullOrWhiteSpace(model)) throw new Exception("Во входных настройках не указана модель!");
return new ChatRequest
{
Model = model,
Tools = new List<ToolDefinition> { sumFunc, multiplyFunc, addTriesFunc, setMaxThreadsFunc, stopTaskFunc },
Messages = messages
};
}
Если модель ответила нам, решив вызвать какую-либо функцию – в методе InvokeFunction мы перехватываем это "намерение" модели и, собственно, производим вызов функции по её выбору, сразу с нужными параметрами.
Параметры, как можно понять, модель выбирает так же, как и саму функцию.
C#:
static string InvokeFunction (FunctionCall call)
{
int x, y, countTries, countThreads;
string result, templateName;
switch (call.Name)
{
case "Sum":
x = Convert.ToInt32(call.Arguments["x"]);
y = Convert.ToInt32(call.Arguments["y"]);
result = Sum(x, y).ToString();
break;
case "Multiply":
x = Convert.ToInt32(call.Arguments["x"]);
y = Convert.ToInt32(call.Arguments["y"]);
result = Multiply(x, y).ToString();
break;
case "AddTriesToTemplate":
templateName = call.Arguments["templateName"];
countTries = Convert.ToInt32(call.Arguments["countTries"]);
result = AddTriesToTemplate(templateName, countTries).ToString();
break;
case "SetMaxThreadsToTemplate":
templateName = call.Arguments["templateName"];
countThreads = Convert.ToInt32(call.Arguments["countThreads"]);
result = SetMaxThreadsToTemplate(templateName, countThreads).ToString();
break;
case "StopTemplate":
templateName = call.Arguments["templateName"];
result = StopTemplate(templateName).ToString();
break;
default:
throw new Exception($"Неизвестная функция: {call.Name}");
}
return result;
}
Повторюсь на всякий случай, что функции могут быть абсолютно любыми (какие сами напишите, найдёте на форуме/в интернете, какие напишет вам другая нейронка).
Также замечу, что в демонстрационном шаблоне представлена упрощённая схема – модель просто вызывает функцию при необходимости, и всё. Разумеется, ничего не мешает расширить код и сделать другую логику под ваши задачи. Например, чтобы результат выполнения функции возвращался модели, и при необходимости она с ним что-то сразу делала – к примеру, вызывала вторую функцию, используя результат от первой как параметр для второй. Логику можно сооружать какую угодно, всё зависит от конкретных задач.
Наглядную демонстрацию работы шаблона посмотрим в видео.
MCP, или куда можно двигаться дальше
Если вы плюс-минус интересуетесь новостями по LLM-кам, то наверняка были свидетелями некоторого хайпа по поводу MCP (Model Context Protocol) – открытого протокола предоставления контекста и инструментов моделям, представленого Anthropic в прошлом году.
Если говорить простыми словами – по сути это то же самое, что и обычный tool use, но более удобно для масштабных задач и при целях отвязки от своих локальных функций.
Вы можете не писать свои функции, а просто подключаться к MCP-серверам, которые предоставляют нужные возможности. MCP позволяет компаниям и сервисам самостоятельно создавать MCP-серверы, давая вашим моделям нужный функционал и контекст при подключении к ним. Как можно заметить, это что-то типа привычных нам API у различных сервисов. Только эти API не для программ, а для LLM-ок.
Ну и конечно вы можете поднимать свои MCP-сервера, отделяя функции и другие инструменты, контекст для своих моделей в полную отдельную сущность, например, и заодно на отдельную удалённую машину, как вариант.
Тут сразу пару дисклеймеров.
- В сети уже очень много самых различных MCP-серверов с кучей интересного и полезного функционала. Но это не значит что все из них безопасны.
- Изучать MCP и переходить сразу к нему не имеет смысла, если вы используете или планируете использовать только локальный свой tool use и подгрузку своего контекста моделям – так как этом случае никаким принципиальных изменений не будет, просто смена одного кода на другой.
На этом всё, спасибо за внимание

Вложения
-
14,5 КБ Просмотры: 3
-
73 байт Просмотры: 4
Последнее редактирование модератором: