Автор: Александр Казанцев, руководитель отдела документации и контента
В последних версиях 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-mcp
description: 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)
## 🎯 Purpose
After 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 links
4. 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 annotations
import logging, re, sys, time
from dataclasses import asdict, dataclass
from typing import Optional
from urllib.parse import urlparse
import requests
logging.basicConfig(
stream=sys.stderr,
level=logging.INFO,
format='%(asctime)s [URL_FETCH] %(levelname)s: %(message)s'
)
logger = logging.getLogger(__name__)
DEFAULT_TIMEOUT = 5.0
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."
@dataclass
class 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 и инструментами для ИИ и машинного обучения.



























