OpenWebUI: System Prompt vs Skills vs MCP Tools — разбираем на живом примере валидации URL

от автора

Автор: Александр Казанцев, руководитель отдела документации и контента

В последних версиях OpenWebUI появились Skils, и я решил сразу же их «пристроить» в дело. Одной из задач их применения виделась валидация ссылок, которые чат-бот техподдержки отдает в своем ответе: модель должна отвечать на вопросы по документации, строить корректные ссылки на статьи и не выдумывать несуществующие эндпоинты и URL. 

AI-платформа

Готовые серверы с LLM и инструментами для ИИ и машинного обучения.

Узнать больше

Казалось бы, простая задача — всего лишь нужно написать в системном промте: «Проверяй ссылки перед отправкой». Но модель не умеет делать HTTP-запросы. Она — текстогенератор, а не браузер. Поэтому встал вопрос:

«Как гарантировать, что сгенерированная ссылка действительно работает, а не ведёт на 404?»

В текущей экосистеме OpenWebUI мы имеем три уровня абстракции:

  1. System Prompt — инструкция для LLM

  2. Skills — пост-процессоры ответа

  3. MCP Tools — исполняемый код для действий «в реальном мире»

В этой статье я подробно разберу, чем они отличаются, как взаимодействуют и как на их примере построить надёжный пайплайн валидации URL. Весь код — из продакшена, с обработкой ошибок и логированием.

Карта территории: три слоя архитектуры

Прежде чем погружаться в код, давайте визуализируем, как данные проходят через систему:

Каждый слой решает свою задачу. Давайте разберём их по порядку.

Уровень 1: System Prompt — «Должностная инструкция»

System Prompt — это текстовый контекст, который «настраивает» поведение языковой модели до генерации ответа. Это не код, не конфигурация — это естественный язык, который модель интерпретирует как правила поведения. В нашем случае это правила для поиска информации в базе знаний, ее интерпретация, форматирование.

Кратко стартовый блок и блок формирования ссылок выглядят вот так:

<role>ИИ-ассистент техподдержки HOSTKEY. Задача: помогать по серверам, панели Invapi и документации hostkey.ru.</role><rules>### 🌐 ЯЗЫК (приоритет №1)- Отвечай ТОЛЬКО на русском, независимо от языка вопроса.### 🔗 ССЫЛКИ — АЛГОРИТМ (строго)Файл в базе: `<раздел>@<тема>@ru.md`Преобразование:1. Убрать `@ru.md` → `faq@network_settings`2. Разбить по `@` → `["faq","network_settings"]`3. Собрать URL: `https://hostkey.ru/documentation/faq/network_settings/`</rules>

Что делает System Prompt в данном случае:

  • Задаёт роль, тон и тематику ответов;

  • Обучает модель правильно формировать ссылки по бизнес-алгоритму;

  • Запрещает нежелательные действия (код, выдуманные URL);

Чего он не делает:

  • Не проверяет, существует ли ссылка на самом деле;

  • Не делает сетевые запросы;

  • Не знает о существовании инструмента url_fetch_mcp — для модели это «магия».

System Prompt — это должностная инструкция сотрудника. Он знает, как надо писать отчёт, но не может выйти в поле и проверить данные.

Уровень 2: Skills — «Редактор с лупой»

Новый инструментарий Skills в OpenWebUI — это декларативные скрипты (в формате Markdown с метаданными YAML), которые выполняются после генерации ответа моделью. Они могут:

  • Парсить и анализировать текст ответа

  • Вызывать внешние инструменты (MCP Tools)

  • Модифицировать вывод перед отправкой пользователю

Если системный промт привязан к конкретной модели, то Skills могут вызываться и использоваться различными моделями совместно. Достаточно указать их при создании кастомной модели в Workspace. Мы реиспользуем этот Skills, например, в переводчиках, чтобы не было «битых» ссылок.

В нашем случае нам нужен валидатор ссылок, то есть Skill url-validator-with-mcp.

---name: url-validator-with-mcpdescription: Validates URLs via url_fetch_mcp, removes invalid ones,              and ensures proper formatting for valid links.version: "2.0"tags: [validation, urls, links, mcp, formatting, self-check]requires_tools: ["url_fetch_mcp"]---# 🔗 URL Validator Skill (MCP-backed + Format Enforcement)## 🎯 PurposeAfter generating a response containing URLs:1. **Validate accessibility** using `url_fetch_mcp`2. **Remove entirely** invalid/unreachable links (text + URL)3. **Enforce proper formatting** for valid links4. Output ONLY the cleaned, validated response — no logs, no commentary

Обратите внимание на секцию между —. В ней вы описываете ваш Skills и задаете, какие Tools он должен использовать. Также не забудьте включить Skills в настройках группы для ваших пользователей.

Разберемся, как же работает наш Skill:

  1. Он находит URL в сгенерированном ответе (выполняет его парсинг);

  2. Вызывает MCP Tools url_fetch_mcp передавая его последовательно найденные URL;

  3. Применяет бизнес-правила: удалять битые ссылки, форматировать рабочие;

  4. Возвращает «чистый» ответ без мета комментариев.

Чего не делает наш Skill:

  • Не учит модель, как строить ссылки (это задача системного промта);

  • Не заменяет логику бизнес-правил;

  • Не работает автономно и требует подключённого MCP Tool, так как сам он не может пройти по ссылке и проверить ответ от сервера.

Skill — это редактор, который проверяет черновик. Он не пишет за автора, но исправляет ошибки, удаляет нерабочие ссылки и приводит текст к стандарту.

Уровень 3: MCP Tools — «Курьер с прибором»

MCP (Model Context Protocol) Tools — это внешний исполняемый код (обычно Python/Node.js), который выполняет конкретные действия: запросы к API, проверка URL, работа с файлами. В OpenWebUI есть три механизма использования: встроенные инструменты, Toos и внешние MCP Tools. 

В нашем случае у нас есть уже специальный сервер mcpo (MCP over HTTP) от тех же разработчиков, на котором расположен инструментарий работы с Invapi, поэтому добавить туда еще один было не проблемой. Но никто не запрещает тот же код реализовать через внутренние Tools OpenWebUI. 

По факту сам инструмент — это простой fastmcp-сервер и код на питоне, который и проверяет ссылки. mcpo же в данном случае — это «переводчик» между миром локальных CLI-инструментов (MCP) и миром веб-приложений (OpenWebUI). 

Плюсом mcpo будет интерактивная документация в swagger-формате, доступная в /docs.

Наш MCP инструмент носит имя check_url()

from __future__ import annotationsimport logging, re, sys, timefrom dataclasses import asdict, dataclassfrom typing import Optionalfrom urllib.parse import urlparseimport requestslogging.basicConfig(    stream=sys.stderr,    level=logging.INFO,    format='%(asctime)s [URL_FETCH] %(levelname)s: %(message)s')logger = logging.getLogger(__name__)DEFAULT_TIMEOUT = 5.0USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."@dataclassclass URLCheckResult:    valid: bool    url: str    normalized_url: Optional[str] = None    status_code: Optional[int] = None    response_time_ms: Optional[float] = None    error: Optional[str] = None    content_type: Optional[str] = None    final_url: Optional[str] = None    ssl_valid: Optional[bool] = None        def to_dict(self) -> dict:        return {k: v for k, v in asdict(self).items() if v is not None}def check_url(    url: str,    timeout: float = DEFAULT_TIMEOUT,    follow_redirects: bool = True,    check_ssl: bool = True) -> dict:    """MCP Tool: Проверка доступности URL"""    logger.info(f"MCP call: check_url(url='{url}')")        # 1. Валидация формата    is_valid, err = _validate_url_format(url)    if not is_valid:        return URLCheckResult(valid=False, url=url, error=err).to_dict()        # 2. HTTP-запрос с обработкой ошибок    try:        response = requests.head(url, timeout=timeout,                                 allow_redirects=follow_redirects,                                verify=check_ssl)        # ... анализ ответа ...        return URLCheckResult(valid=True, status_code=response.status_code,                              response_time_ms=...).to_dict()                                 except requests.exceptions.Timeout:        return URLCheckResult(valid=False, error="Таймаут").to_dict()    except requests.exceptions.SSLError:        return URLCheckResult(valid=False, error="SSL-ошибка").to_dict()    # ... другие исключения ...

Что наш валидатор делает:

  • Реально «стучится» по ссылке (сетевой запрос);

  • Возвращает структурированный результат: status_code, response_time_ms, error;

  • Обрабатывает исключения: таймауты, SSL-ошибки, редиректы;

Также его можно использовать не только в нашем конкретном Skill, но и в других, а также напрямую в моделях.

Но так как это просто код на Python, то он не знает контекст диалога (кто пользователь, о чём вопрос), не принимает решений: «удалять ссылку или нет», так как это логика Skill, и не форматирует финальный ответ, а отдает только данные в json формате.

После этого остается только прописать наш MCP Tools в OpenWebUI.

MCP Tool — это курьер с прибором учета. Он не решает, куда ехать и что писать в отчёте. Он только измеряет: «Доставлено/не доставлено, время в пути, причина сбоя».

Полный цикл: от вопроса до ответа

Давайте проследим, как работает вся цепочка на реальном сценарии.

Вход: пользователь спрашивает

«Как настроить сетевой интерфейс в панели Invapi?»

Шаг 1: Model + System Prompt

  1. LLM получает вопрос + системный промт;

  2. Находит в базе документ: controlpanel@network_interface@ru.md;

  3. Применяет алгоритм: 

    «убрать @ru.md» → [«controlpanel»,»network_interface»] > «https://hostkey.ru/documentation/controlpanel/network_interface/» ;

  4. Генерирует черновик:

«Откройте панель Invapi → ‘Сеть’ → ‘Интерфейсы’.

    Подробнее: [Настройка сетевого интерфейса]

    (https://hostkey.ru/documentation/controlpanel/network_interface/)»

Шаг 2: Skill (url-validator-with-mcp)

  1. Skill получает черновик;

  2. Парсит текст, находит 1 ссылку;

  3. Вызывает MCP Tool: 

 url_fetch_mcp(url=»https://…/network_interface/»)

  1. Получает результат:

   { «valid»: true, «status_code»: 200, «response_time_ms»: 342 }

  1. Проверяет формат: Markdown — Да, HTTPS — Да, текст ≠ вопрос — Да ;

  2. Возвращает ответ без изменений (всё валидно).

Шаг 3: А если ссылка не валидна?

  1. MCP Tool вернул:

 { «valid»: false, «error»: «HTTP 404» }

  1. Skill удаляет конструкцию «[текст](ссылка)» из ответа;

  2. Выдает ответ без ссылки:

   «Откройте панель Invapi → ‘Сеть’ → ‘Интерфейсы’.

    Точной инструкции не нашёл. Для помощи: 

    [Техподдержка](https://hostkey.ru/customer-care/).»

Сравнение подходов

Так что-же лучше? System Prompt, Skill или MCP Tools. Свели все основные критерии в такую таблиц, чтобы вы могли выбрать наиболее подходящий вам инструмент.

Критерий

System Prompt

Skill

MCP Tool

Когда выполняется

До генерации ответа

После генерации

По вызову из Skill

Язык реализации

Естественный язык

Markdown + логика

Python/JS код

Доступ к сети

Нет

Только через Tools

Да

Контекст диалога

Полный

Ответ модели

Только параметры

Основная задача

Поведение, правила

Пост-обработка, координация

Конкретное действие

Гибкость

Высокая (текст)

Средняя (шаблон)

Низкая (код)

Сложность поддержки

Низкая

Средняя

Высокая

Заключение

Разработка надёжного ИИ-ассистента — это не только про «умную модель». Это про архитектуру, где составляющие ее компоненты делают своё дело:

  • System Prompt —  отвечает за поведение ассистента, правила, бизнес-логику, «Знай, как надо»;

  • Skill — на нем пост-обработка и координация, «Проверь, что сделано»;

  • MCP Tool — выполняет конкретные действия «в мире», «Сделай и доложи».

Когда каждый компонент знает свою зону ответственности, система становится предсказуемой, тестируемой и легко расширяемой. А пользователь получает то, что ему нужно: в нашем случае это точный ответ с рабочими ссылками.

Полезные ссылки

AI-платформа

Готовые серверы с LLM и инструментами для ИИ и машинного обучения.

Узнать больше

ссылка на оригинал статьи https://habr.com/ru/articles/1029730/