Создание плагина tool parser для vLLM: интеграция Apriel-1.6-15B-Thinker | AiManual
AiManual Logo Ai / Manual.
18 Янв 2026 Гайд

Как создать плагин tool parser для vLLM под конкретную модель (на примере Apriel-1.6-15B-Thinker)

Подробное руководство по созданию кастомного парсера инструментов для vLLM на примере модели Apriel-1.6-15B-Thinker. Шаги, код, нюансы.

Почему vLLM не понимает вашу модель

Вы скачали Apriel-1.6-15B-Thinker, запустили через vLLM, а tool calling не работает. Модель выводит какой-то текст, но vLLM ждет строгий JSON. Знакомая ситуация? vLLM из коробки поддерживает несколько форматов, но кастомные модели часто имеют свои причуды.

vLLM не волнует, как ваша модель обучена. Он ждет, что вывод будет в определенном формате. Если это не так — вам нужен кастомный parser.

В этой статье разберем, как заставить vLLM понимать вывод Apriel-1.6-15B-Thinker для tool calling. Мы создадим плагин с нуля, и я покажу все подводные камни.

Зачем это нужно?

Tool calling — это когда модель вызывает внешние инструменты, например, поиск в интернете или вычисления. vLLM использует парсеры для преобразования вывода модели в структурированный формат. Если ваш модель не соответствует ожиданиям vLLM, вы получаете ошибки или молчание.

Ссылаясь на статью "Держите свой JSON", помните: заставить модель выдавать JSON — полдела. Нужно еще правильно его распарсить.

Как vLLM обрабатывает tool calling

vLLM имеет систему плагинов для парсеров. BaseOutputParser — абстрактный класс, который вы должны наследовать. Основные методы: parse и format. Parse берет сырой вывод модели и возвращает структурированные данные. Format, наоборот, форматирует промпт для модели.

Для Apriel-1.6-15B-Thinker, модель обучена на определенном шаблоне. Нужно понять этот шаблон и написать под него парсер.

1 Анализ вывода модели

Сначала получите сырой вывод модели. Запустите vLLM с простым запросом, который должен вызвать инструмент. Посмотрите, что выводит модель. Например, Apriel может использовать формат как Hermes.

# Пример запроса
from vllm import LLM, SamplingParams

llm = LLM(model="path/to/apriel-1.6-15b-thinker")
sampling_params = SamplingParams(temperature=0)
output = llm.generate("Call tool X with params Y")
print(output[0].outputs[0].text)

Вы увидите что-то вроде:

Я нужно вызвать инструмент.

{"name": "search", "arguments": {"query": "что-то"}}

Или может быть без тегов. Важно зафиксировать точный формат.

2 Создание класса парсера

Создайте новый файл, например, apriel_tool_parser.py. Наследуйтесь от BaseOutputParser из vLLM.

from vllm.engine.output_parser import BaseOutputParser
from typing import Dict, Any, Optional
import json
import re

class AprielToolParser(BaseOutputParser):
    def __init__(self):
        super().__init__()
        # Паттерны для извлечения JSON
        self.pattern = re.compile(r'\s*(.*?)\s*', re.DOTALL)

    def parse(self, output: str) -> Optional[Dict[str, Any]]:
        # Ищем JSON между тегами 
        match = self.pattern.search(output)
        if not match:
            return None
        json_str = match.group(1)
        try:
            return json.loads(json_str)
        except json.JSONDecodeError:
            # Если JSON сломан, попробуем починить
            # ... логика исправления
            return None

    def format(self, tool_call: Dict[str, Any]) -> str:
        # Форматируем tool call в промпт для модели
        # Apriel ожидает теги 
        json_str = json.dumps(tool_call, ensure_ascii=False)
        return f"\n{json_str}\n"

Это базовый пример. В реальности, возможно, нужно обрабатывать несколько tool calls или другие форматы.

💡
Apriel-1.6-15B-Thinker может использовать формат, похожий на Hermes. Взгляните на hermes_tool_parser.py в исходниках vLLM для вдохновения.

3 Интеграция с vLLM

Теперь нужно зарегистрировать парсер в vLLM. Есть несколько способов. Самый простой — указать при создании LLMEngine.

from vllm.engine.arg_utils import EngineArgs
from vllm.engine.llm_engine import LLMEngine
from apriel_tool_parser import AprielToolParser

engine_args = EngineArgs(model="path/to/model", ...)
engine = LLMEngine.from_engine_args(engine_args)
engine.output_parser = AprielToolParser()

Или через конфигурацию, если вы используете высокоуровневый API.

Но vLLM также поддерживает плагины через entry points. Создайте setup.py или pyproject.toml для вашего плагина.

# setup.py
from setuptools import setup

setup(
    name="apriel-vllm-parser",
    entry_points={
        'vllm.output_parsers': [
            'apriel = apriel_tool_parser:AprielToolParser',
        ],
    },
)

Тогда вы можете указать парсер через аргумент командной строки или конфиг.

4 Тестирование и отладка

Запустите тестовые запросы. Убедитесь, что парсер корректно извлекает JSON и что модель понимает отформатированные промпты.

Используйте логирование для отладки.

import logging
logging.basicConfig(level=logging.DEBUG)

Если парсер не работает, проверьте:

  • Регулярные выражения: они должны захватывать весь JSON, но не лишнее.
  • Кодировку: модель может выводить Unicode-символы.
  • Экранирование: JSON может содержать escaped-символы, которые нужно обработать.

Нюансы, которые вас убьют

1. Модель может выдавать неполный JSON. Например, обрываться на середине. В таком случае, нужно либо достраивать JSON, либо игнорировать вывод.

2. Несколько tool calls в одном выводе. Парсер должен возвращать список вызовов, а не один.

3. Промпт engineering. Чтобы модель стабильно выдавала нужный формат, возможно, придется настроить промпт. Смотрите статью "Когда LLM врёт о документах" для идей по контролю вывода.

Вот улучшенная версия парсера с обработкой ошибок:

class RobustAprielToolParser(BaseOutputParser):
    def parse(self, output: str) -> Optional[List[Dict[str, Any]]]:
        # Ищем все tool calls
        pattern = re.compile(r'\s*(.*?)\s*', re.DOTALL)
        matches = pattern.findall(output)
        if not matches:
            return None
        tool_calls = []
        for json_str in matches:
            try:
                tool_calls.append(json.loads(json_str))
            except json.JSONDecodeError:
                # Попробуем исправить распространенные ошибки
                json_str = self.fix_json(json_str)
                try:
                    tool_calls.append(json.loads(json_str))
                except:
                    continue  # Пропускаем сломанный вызов
        return tool_calls if tool_calls else None

    def fix_json(self, json_str: str) -> str:
        # Удаляем лишние запятые в конце объектов или массивов
        json_str = re.sub(r',\s*}', '}', json_str)
        json_str = re.sub(r',\s*]', ']', json_str)
        # Другие исправления...
        return json_str

Частые ошибки

Ошибка Причина Решение
Парсер не находит tool call Модель выводит в другом формате Изучите вывод модели и адаптируйте регулярные выражения
JSONDecodeError Некорректный JSON Добавьте логику исправления JSON или валидацию
Модель игнорирует tool call Промпт не отформатирован правильно Настройте метод format для соответствия ожиданиям модели

А что если модель не Apriel?

Принцип тот же: анализ вывода, создание парсера, интеграция. Для других моделей, например, GLM 4.5 Air, смотрите статью "GLM 4.5 Air в режиме тупняка". Там могут быть свои особенности.

И помните: tool calling — это не магия. Это просто парсинг текста. Главное — понять, как ваша модель думает.

Если вы работаете с большими контекстами, обратите внимание на статью "Lost in the Middle", чтобы избежать потери информации.

Итог

Создание кастомного парсера для vLLM — это не ракетостроение. Это анализ вывода модели и написание кода, который этот вывод понимает. Для Apriel-1.6-15B-Thinker мы использовали подход на основе тегов . Для других моделей шаблон будет другим.

Не бойтесь копать в исходниках vLLM и смотреть, как реализованы другие парсеры. И всегда тестируйте с реальными запросами.

Удачи в интеграции! И если что-то не работает, помните: чаще всего проблема в регулярных выражениях.