Исходный код, разобранный в данной статье, опубликован в этом репозитории
Для решения некоторых задач бизнес-требованием является запуск LLM модели локально на своём железе. Это связано с SJW цензурой, например, стандартный датасет для обучения Llama не позволяет вести консультации, носящие медицинский характер: рекомендовать лекарства, обсуждать носящую интимный характер медицинскую тайну с ИИ-терапевтом (см побочки антидепрессантов)

Так же, если модель не умеет вызывать инструменты, она не нужна: бессмысленно вести медицинскую консультацию, которая не закончится продажей фармокологического продукта. Ниже преведены протестированные модели, которые сочетают консистентный русский язык и возможность интеграции в сторонние сервисы
Обзор моделей
-
NVidia Nemotron Mini, https://ollama.com/library/nemotron-mini
Цитата: This instruct model is optimized for roleplay, RAG QA, and function calling in English. It supports a context length of 4,096 tokens. This model is ready for commercial use.
Плюсы
1. Работает на видеокарте ноутбука с 4Гб памяти
Позволяет пригласить стажера и, не засветив ключи openai, понять, а сможет ли он вообще в программирование
2. Умеет говорить на русском языке
Важный пункт, см фото про deepseek
3. Вызывает инструменты
Условно работает, но виновата Ollama: нужно отправить промпт-костыль. Однако, игнорирует required параметры инструментов: передает только те данные, в которых модель на 100% уверена
4. Не многословен: датасет заточен для генерации минимальных ответов: Никаких «наверное» или «вы хотели бы уточнить?»
Огромный плюс, если у вас есть опыт работы с моделью Gemini: она постоянно спамит текстом вида:
Would you like me to
,Okay, here’s a
,Okay, this is a
,This is likely
Минусы
1. Рекурсивно вызывает инструменты, неправильно реагируя на tool output.
Решается сбросом переписки после вызова инструментов и заметками для модели в system prompt
2. Игнорирует required параметры инструментов
При вызове метода добавления товара в корзину гарантировать получение имени товара может только анализ последнего сообщения пользователя руками
3. Из-за немногословности крайне не замотивированно ведет переписку: развести модель на нужное действие можно только задавая наводящие вопросы
Решается выбором другой модели тем агентам роя, которые должны быть многословны. Эта модель именно и позицинирует себя для сбора точной информации через чат для коммерции
4. Вызывает несуществующие инструменты
Если текстовый промпт содержит просьбу оплатить заказ, модель вызовет
checkout_tool
даже, если был подключен толькоadd_to_cart_tool
-
Saiga/YandexGPT, https://huggingface.co/IlyaGusev/saiga_yandexgpt_8b
Pretrain-версия младшей модели — YandexGPT 5 Lite Pretrain — опубликована в свободном доступе и будет полезна разработчикам, которые дообучают базовые версии моделей под свои задачи. Дообученная нами на её основе instruct-версия в ближайшее время станет доступна через API.
Плюсы
1. Яндекс заопенсорсил топологию своей модели Алисы по политическому решению
Это самая годная топология на рынке, не только для русского языка, а вообще: значительно лучше
Mistral NeMo
,Llama 3.1
,Nemotron
,Deepseek
и других2. При вызове инструментов ориентируется на required параметры
Важное отличие от Nemotron mini
3. Не вызывает инструменты рекурсивно
Критическое отличие от Nemotron mini: рекурсивный вызов инструмента создаст deadlock чата. Эта модель таким прелестным качеством не обладает
4. Не запустится на ноутбуке, но системный блок с видеокартой 3060 тянет эту модель
Если не работает на Saiga/YandexGPT, лучше только откат до OpenAI
Минусы
1. Так как опенсорс топологии был политическим, Яндекс утаил датасет.
Благо, есть Saiga: докинув 10% русского текста в датасет для дообучения LLama тупо повезло и модель сама выучила русский
2. Лексер LMStudio не может парсить случаи, когда модель запрашивает вызов двух инструментов одновременно
При просьбе добавить два товара одновременно в чат к пользователю вместо структурированного
tool_calls: IToolCall[]
улетает[TOOL_REQUEST]\n{"name": "add_to_cart_tool", "arguments": {"title": "Аспирин"}}\n[END_TOOL_REQUEST
как строкаcontent
. Лечится через фильтр сообщений от нейронки на предмет отсутствия JSON вообще, ограничением вызовов инструментов не более одного за сообщение через system prompt и мануальный фильтр выхлопа из completerНе стабильным решением проблемы является отлов JSON в сообщении от модели с последующей просьбой исправить ошибки в формате. Но, иногда приводит к рекурсии: модель долго рассуждает и всё равно дает невалидный формат вызова инструмента. Поэтому, стабильным способом выхода из положения при детекте JSON в выходе из модели — очистка переписки и отправка пользователю заглушки:
Я не расслышал, не могли бы вы повторить
Настройка среды
Для использования Nemotron потребуется скачать ollama с официального сайта, далее в терминале вбить команду ollama pull nemotron-mini:4b

Для использования YandexGPT потребуется скачать LMStudio, установить модель через поисковик как на скриншоте

После, нужно включить эмулятор OpenAI API через раздел в боковом меню. В вышеупомянутом разделе нужно выставить следующие настройки

Пример кода
Для простоты, в этой статье я укажу консольный пример чата. При необходимости, подключить то же API к верстке, не составит труда: на всякий случай, оставлю шаблон
import readline from "readline"; import { randomString, Subject } from "functools-kit"; const clientId = randomString(); const incomingSubject = new Subject(); const outgoingSubject = new Subject(); const ws = new WebSocket(`http://127.0.0.1:1337/?clientId=${clientId}`); ws.onmessage = (e) => { incomingSubject.next(JSON.parse(e.data)); }; ws.onopen = () => { outgoingSubject.subscribe((data) => { ws.send(JSON.stringify({ data })); }); }; ws.onclose = () => { console.log("Connection closed"); process.exit(-1); }; ws.onerror = () => { console.log("Connection error"); process.exit(-1); }; const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const askQuestion = () => { rl.question("pharma-bot => ", async (input) => { if (input === "exit") { rl.close(); return; } console.time("Timing"); await outgoingSubject.waitForListener(); await outgoingSubject.next(input); const { agentName, data } = await incomingSubject.toPromise(); console.timeEnd("Timing"); console.log(`[${agentName}]: ${data}`); askQuestion(); }); }; askQuestion(); rl.on("close", () => { process.exit(0); });
Код сервера следующий. Чтобы переподключить агента к другому провайдеру LLM, меняем completion: CompletionName.NemotronMiniCompletion
на completion: CompletionName.SaigaYandexGPTCompletion,
import { Adapter, addAgent, addCompletion, addSwarm, addTool, commitFlush, commitToolOutput, emit, execute, getAgentName, session, } from "agent-swarm-kit"; import type { ServerWebSocket } from "bun"; import { singleshot, str } from "functools-kit"; import { Ollama } from "ollama"; import OpenAI from "openai"; const getOllama = singleshot( () => new Ollama({ host: "http://127.0.0.1:11434" }) ); const getOpenAI = singleshot( () => new OpenAI({ baseURL: "http://127.0.0.1:12345/v1", apiKey: "noop" }) ); enum CompletionName { NemotronMiniCompletion = "nemotron_mini_completion", SaigaYandexGPTCompletion = "saiga_yandex_gpt_completion", } enum AgentName { TestAgent = "test_agent", } enum ToolName { AddToCartTool = `add_to_cart_tool`, } enum SwarmName { TestSwarm = "test_swarm", } addCompletion({ completionName: CompletionName.NemotronMiniCompletion, getCompletion: Adapter.fromOllama(getOllama(), "nemotron-mini:4b"), }); addCompletion({ completionName: CompletionName.SaigaYandexGPTCompletion, getCompletion: Adapter.fromOpenAI(getOpenAI(), "saiga_yandexgpt_8b_gguf"), }); addAgent({ agentName: AgentName.TestAgent, completion: CompletionName.SaigaYandexGPTCompletion, prompt: str.newline( "Вы являетесь агентом по продаже фармацевтических товаров.", "Предоставьте мне консультацию по фармацевтическому продукту" ), system: [ `Чтобы добавить фармацевтический продукт в корзину, вызовите следующий инструмент: ${ToolName.AddToCartTool}`, ], tools: [ToolName.AddToCartTool], }); addTool({ toolName: ToolName.AddToCartTool, validate: async ({ params }) => true, call: async ({ toolId, clientId, agentName, params }) => { console.log(ToolName.AddToCartTool, params); await commitToolOutput( toolId, `Фармацевтический продукт ${params.title} успешно добавлен.`, clientId, agentName ); await emit( `Продукт ${params.title} добавлен в корзину. Вы желаете оформить заказ?`, clientId, agentName ); }, type: "function", function: { name: ToolName.AddToCartTool, description: "Добавить фармацевтический продукт в корзину. Обязательно передай параметр title.", parameters: { type: "object", properties: { title: { type: "string", description: `Название фармацевтического продукта, который нужно добавить в корзину`, }, }, required: [], }, }, }); addSwarm({ swarmName: SwarmName.TestSwarm, agentList: [AgentName.TestAgent], defaultAgent: AgentName.TestAgent, }); type WebSocketData = { clientId: string; session: ReturnType<typeof session>; }; Bun.serve({ fetch(req, server) { const clientId = new URL(req.url).searchParams.get("clientId")!; console.log(`Connected clientId=${clientId}`); server.upgrade<WebSocketData>(req, { data: { clientId, session: session(clientId, SwarmName.TestSwarm), }, }); }, websocket: { async message(ws: ServerWebSocket<WebSocketData>, message: string) { const { data } = JSON.parse(message); const answer = await ws.data.session.complete(data); ws.send( JSON.stringify({ data: answer, agentName: await getAgentName(ws.data.clientId), }) ); }, }, hostname: "0.0.0.0", port: 1337, });
Заключение
Исходя из вышеизложенных тезисов, можно сделать следующие выводы
-
Для каждой новой локализации чата потребуется делать новый рой агентов
Языковые модели начинают путаться, если system prompt содержит информацию на чужом для пользователя языке: в русскоязычном тексте появляются сегментарные англоязычные включения
-
По OpenSource моделям есть продвижение, но сейчас закрытые модели лидируют
Адаптер для переключения с OpenAI на локальную модель критически важен для отлова edge cases: так как бесплатные модели часто глючат, на них проще исправлять ошибки. Прод среду же всё ещё стабильнее запускать на закрытой модели
-
Если проект нацелен на русскоязычную аудиторию — берите Saiga/YandexGPT
Мной так же была произведена попытка установить в LMStudio Vikhr-YandexGPT-5-Lite-8B, но инструменты не вызывались. Без инструментов, языковая модель не имеет смысл, так как не работают интеграции в сторонние сервисы
ссылка на оригинал статьи https://habr.com/ru/articles/892468/
Добавить комментарий