Автор: Александр Казанцев, руководитель отдела документации и контента
В последних версиях OpenWebUI появились Skils, и я решил сразу же их «пристроить» в дело. Одной из задач их применения виделась валидация ссылок, которые чат-бот техподдержки отдает в своем ответе: модель должна отвечать на вопросы по документации, строить корректные ссылки на статьи и не выдумывать несуществующие эндпоинты и URL.
|
AI-платформа Готовые серверы с LLM и инструментами для ИИ и машинного обучения. |
Казалось бы, простая задача — всего лишь нужно написать в системном промте: «Проверяй ссылки перед отправкой». Но модель не умеет делать HTTP-запросы. Она — текстогенератор, а не браузер. Поэтому встал вопрос:
«Как гарантировать, что сгенерированная ссылка действительно работает, а не ведёт на 404?»

В текущей экосистеме OpenWebUI мы имеем три уровня абстракции:
-
System Prompt — инструкция для LLM
-
Skills — пост-процессоры ответа
-
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:
-
Он находит URL в сгенерированном ответе (выполняет его парсинг);
-
Вызывает MCP Tools url_fetch_mcp передавая его последовательно найденные URL;
-
Применяет бизнес-правила: удалять битые ссылки, форматировать рабочие;
-
Возвращает «чистый» ответ без мета комментариев.
Чего не делает наш 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
-
LLM получает вопрос + системный промт;
-
Находит в базе документ: controlpanel@network_interface@ru.md;
-
Применяет алгоритм:
«убрать @ru.md» → [«controlpanel»,»network_interface»] > «https://hostkey.ru/documentation/controlpanel/network_interface/» ;
-
Генерирует черновик:
«Откройте панель Invapi → ‘Сеть’ → ‘Интерфейсы’.
Подробнее: [Настройка сетевого интерфейса]
(https://hostkey.ru/documentation/controlpanel/network_interface/)»
Шаг 2: Skill (url-validator-with-mcp)
-
Skill получает черновик;
-
Парсит текст, находит 1 ссылку;
-
Вызывает MCP Tool:
url_fetch_mcp(url=»https://…/network_interface/»)
-
Получает результат:
{ «valid»: true, «status_code»: 200, «response_time_ms»: 342 }
-
Проверяет формат: Markdown — Да, HTTPS — Да, текст ≠ вопрос — Да ;
-
Возвращает ответ без изменений (всё валидно).
Шаг 3: А если ссылка не валидна?
-
MCP Tool вернул:
{ «valid»: false, «error»: «HTTP 404» }
-
Skill удаляет конструкцию «[текст](ссылка)» из ответа;
-
Выдает ответ без ссылки:
«Откройте панель 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/