ADOLF MARKETING — Раздел 4: Open WebUI
Проект: ÐвтоматичеÑкое управление рекламными кампаниÑми
Модуль: Marketing / Open WebUI
ВерÑиÑ: 1.0
Дата: Январь 2026
4.1 Обзор
Ðазначение
Примечание (Март 2026): Ниже описана архитектура интеграции через chat-агента. Фактическая реализация использует standalone-страницу
/marketing. Структура страницы:
Вкладка Описание Дашборд Общая сводка Кампании Управление рекламными кампаниями Ключевые слова Управление ключевыми словами Safety Logic Логика безопасности ставок AI-рекомендации Рекомендации AI Документация агента ниже сохранена как спецификация backend API.
Раздел опиÑывает интеграцию Ð¼Ð¾Ð´ÑƒÐ»Ñ Marketing Ñ Open WebUI:
- Pipeline
@Adolf_Marketing— агент ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€ÐµÐºÐ»Ð°Ð¼Ð¾Ð¹ - Tools — Function Calling Ð´Ð»Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¹ Ñ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñми и Ñтавками
- Interactive UI — кнопки, карточки, графики в чате
Компоненты интеграции
| Компонент | Ðазначение |
|---|---|
| Pipeline | Обработка запроÑов, маршрутизациÑ, форматирование |
| Tools | Функции Ð´Ð»Ñ Function Calling |
| Valves | ÐаÑтройки Pipeline |
| Buttons | Кнопки быÑтрых дейÑтвий |
| Cards | Карточки кампаний и ключей |
| Charts | Графики ÑтатиÑтики |
Ðрхитектура интеграции
4.2 Pipeline: @Adolf_Marketing
4.2.1 КонфигурациÑ
python
# pipelines/adolf_marketing.py
"""
title: Adolf Marketing Pipeline
author: Adolf Team
version: 1.0.0
description: Управление рекламными кампаниÑми на маркетплейÑах
"""
from typing import List, Generator, Optional
from pydantic import BaseModel, Field
import requests
import json
class Pipeline:
"""Pipeline Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€ÐµÐºÐ»Ð°Ð¼Ð½Ñ‹Ð¼Ð¸ кампаниÑми."""
class Valves(BaseModel):
"""ÐаÑтройки Pipeline."""
MIDDLEWARE_URL: str = Field(
default="http://middleware:8000",
description="URL Middleware API"
)
MIDDLEWARE_API_KEY: str = Field(
default="",
description="API ключ Middleware"
)
DEFAULT_MODEL: str = Field(
default="gpt-5-mini",
description="Модель Ð´Ð»Ñ Ñ€ÑƒÑ‚Ð¸Ð½Ð½Ñ‹Ñ… запроÑов"
)
ANALYTICS_MODEL: str = Field(
default="claude-opus-4-5",
description="Модель Ð´Ð»Ñ Ð°Ð½Ð°Ð»Ð¸Ñ‚Ð¸ÐºÐ¸"
)
ENABLE_REALTIME_STATS: bool = Field(
default=True,
description="Загружать ÑтатиÑтику в реальном времени"
)
DEFAULT_STATS_PERIOD: int = Field(
default=7,
description="Период ÑтатиÑтики по умолчанию (дни)"
)
def __init__(self):
self.name = "Adolf Marketing"
self.valves = self.Valves()
async def on_startup(self):
"""Ð˜Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¸ запуÑке."""
print(f"[{self.name}] Pipeline started")
async def on_shutdown(self):
"""ОчиÑтка при оÑтановке."""
print(f"[{self.name}] Pipeline stopped")
def inlet(self, body: dict, __user__: dict) -> dict:
"""
Preprocessing — проверка доÑтупа и добавление контекÑта.
"""
user_role = __user__.get("role", "staff")
user_brand = __user__.get("valves", {}).get("brand_id", "all")
# Проверка доÑтупа (Staff не имеет доÑтупа)
if user_role == "staff":
body["messages"].append({
"role": "system",
"content": "У Ð²Ð°Ñ Ð½ÐµÑ‚ доÑтупа к модулю Marketing. ОбратитеÑÑŒ к руководителю."
})
return body
# Добавление ÑиÑтемного контекÑта
system_context = self._build_system_context(user_role, user_brand)
messages = body.get("messages", [])
if messages and messages[0].get("role") != "system":
messages.insert(0, {"role": "system", "content": system_context})
# Загрузка summary Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚ÐµÐºÑта (еÑли включено)
if self.valves.ENABLE_REALTIME_STATS:
summary = self._fetch_campaigns_summary(__user__)
if summary:
messages.insert(1, {
"role": "system",
"content": f"Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñводка по рекламе:\n{summary}"
})
body["messages"] = messages
return body
def pipe(
self,
body: dict,
__user__: dict,
__event_emitter__: callable = None
) -> Generator:
"""ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ° запроÑов."""
user_id = __user__.get("id")
user_role = __user__.get("role", "staff")
user_brand = __user__.get("valves", {}).get("brand_id", "all")
# Определение модели
model = self._select_model(body.get("messages", []))
# Ð—Ð°Ð¿Ñ€Ð¾Ñ Ðº Middleware
response = requests.post(
f"{self.valves.MIDDLEWARE_URL}/v1/chat/completions",
headers={
"Authorization": f"Bearer {self.valves.MIDDLEWARE_API_KEY}",
"X-User-ID": str(user_id),
"X-User-Role": user_role,
"X-User-Brand": user_brand,
"X-Module": "marketing"
},
json={
"model": model,
"messages": body.get("messages", []),
"stream": True,
"tools": self._get_tools_for_role(user_role)
},
stream=True
)
# Streaming response
for line in response.iter_lines():
if line:
yield line.decode("utf-8")
def outlet(self, body: dict, __user__: dict) -> dict:
"""
Postprocessing — добавление кнопок и форматирование.
"""
user_role = __user__.get("role", "staff")
# Добавление кнопок по умолчанию
if "buttons" not in body:
body["buttons"] = self._get_default_buttons(user_role)
return body
def _build_system_context(self, role: str, brand: str) -> str:
"""Формирование ÑиÑтемного контекÑта."""
context = """Ты — Adolf Marketing, AI-аÑÑиÑтент Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€ÐµÐºÐ»Ð°Ð¼Ð½Ñ‹Ð¼Ð¸ кампаниÑми на маркетплейÑах.
Твои возможноÑти:
- ПроÑмотр и управление рекламными кампаниÑми (WB, Ozon, YM)
- ПроÑмотр ÑтатиÑтики: CTR, CPC, CPO, ДРÐ
- Управление Ñтавками и бюджетами
- Ðнализ ÑффективноÑти и рекомендации
- ÐаÑтройка Ñтратегий биддинга
Правила:
- Отвечай на руÑÑком Ñзыке
- ИÑпользуй инÑтрументы (tools) Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…
- Показывай данные в Ñтруктурированном виде
- При изменении Ñтавок вÑегда показывай текущее и новое значение
- Предупреждай о риÑках при агреÑÑивных наÑтройках
"""
# Добавление ограничений по роли
if role == "manager":
context += f"""
ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ€Ð¾Ð»Ð¸ Manager:
- ДоÑтуп только к бренду: {brand}
- Изменение Ñтавок только в пределах уÑтановленных лимитов
- Ðет доÑтупа к изменению Max Bid, Daily Limit, Ñтратегий
"""
elif role == "senior":
context += """
Права Ð´Ð»Ñ Ñ€Ð¾Ð»Ð¸ Senior:
- ДоÑтуп ко вÑем брендам
- Изменение Max Bid, Daily Limit, Ñтратегий
- ПроÑмотр Ñводных отчётов
- AI-рекомендации
"""
elif role in ("director", "administrator"):
context += """
Полный доÑтуп:
- Ð’Ñе операции без ограничений
- ÐаÑтройки Safety Logic (только Admin)
- СиÑтемные наÑтройки (только Admin)
"""
return context
def _select_model(self, messages: List[dict]) -> str:
"""Выбор модели на оÑнове запроÑа."""
last_message = messages[-1].get("content", "").lower() if messages else ""
# ÐналитичеÑкие запроÑÑ‹ → Claude Opus 4.5
analytics_keywords = [
"анализ", "рекомендац", "оптимиз", "прогноз",
"почему", "что делать", "инÑайт", "trend"
]
if any(kw in last_message for kw in analytics_keywords):
return self.valves.ANALYTICS_MODEL
return self.valves.DEFAULT_MODEL
def _fetch_campaigns_summary(self, user: dict) -> Optional[str]:
"""Получение краткой Ñводки по кампаниÑм."""
try:
response = requests.get(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/summary",
headers={
"Authorization": f"Bearer {self.valves.MIDDLEWARE_API_KEY}",
"X-User-ID": str(user.get("id")),
"X-User-Role": user.get("role"),
"X-User-Brand": user.get("valves", {}).get("brand_id", "all")
},
params={"period_days": self.valves.DEFAULT_STATS_PERIOD},
timeout=5
)
if response.status_code != 200:
return None
data = response.json()
summary = f"""
Ðктивных кампаний: {data.get('active_campaigns', 0)}
РаÑход за {self.valves.DEFAULT_STATS_PERIOD} дней: {data.get('total_spent', 0):,.0f} ₽
Средний ДРР: {data.get('avg_drr', 0):.1f}%
Ключей приоÑтановлено: {data.get('paused_keywords', 0)}
Ðлертов: {data.get('pending_alerts', 0)}
"""
return summary
except Exception:
return None
def _get_tools_for_role(self, role: str) -> List[dict]:
"""Получение ÑпиÑка инÑтрументов по роли."""
# Базовые tools Ð´Ð»Ñ Manager+
base_tools = [
{
"type": "function",
"function": {
"name": "marketing_campaigns_list",
"description": "СпиÑок рекламных кампаний",
"parameters": {
"type": "object",
"properties": {
"marketplace": {
"type": "string",
"enum": ["wb", "ozon", "ym", "all"],
"description": "МаркетплейÑ"
},
"status": {
"type": "string",
"enum": ["active", "paused", "all"],
"description": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ð¸"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "marketing_campaign_stats",
"description": "СтатиÑтика кампании",
"parameters": {
"type": "object",
"properties": {
"campaign_id": {
"type": "string",
"description": "ID кампании"
},
"period_days": {
"type": "integer",
"description": "Период в днÑÑ…",
"default": 7
}
},
"required": ["campaign_id"]
}
}
},
{
"type": "function",
"function": {
"name": "marketing_keywords_list",
"description": "СпиÑок ключевых Ñлов кампании",
"parameters": {
"type": "object",
"properties": {
"campaign_id": {
"type": "string",
"description": "ID кампании"
},
"status": {
"type": "string",
"enum": ["active", "paused", "all"],
"description": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÐºÐ»ÑŽÑ‡ÐµÐ¹"
}
},
"required": ["campaign_id"]
}
}
},
{
"type": "function",
"function": {
"name": "marketing_update_bid",
"description": "Изменить Ñтавку ключевого Ñлова",
"parameters": {
"type": "object",
"properties": {
"keyword_id": {
"type": "string",
"description": "ID ключевого Ñлова"
},
"new_bid": {
"type": "number",
"description": "ÐÐ¾Ð²Ð°Ñ Ñтавка в рублÑÑ…"
}
},
"required": ["keyword_id", "new_bid"]
}
}
},
{
"type": "function",
"function": {
"name": "marketing_pause_keyword",
"description": "ПриоÑтановить ключевое Ñлово",
"parameters": {
"type": "object",
"properties": {
"keyword_id": {
"type": "string",
"description": "ID ключевого Ñлова"
}
},
"required": ["keyword_id"]
}
}
},
{
"type": "function",
"function": {
"name": "marketing_resume_keyword",
"description": "Возобновить ключевое Ñлово",
"parameters": {
"type": "object",
"properties": {
"keyword_id": {
"type": "string",
"description": "ID ключевого Ñлова"
}
},
"required": ["keyword_id"]
}
}
},
{
"type": "function",
"function": {
"name": "marketing_alerts_list",
"description": "СпиÑок алертов",
"parameters": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["unread", "all"],
"description": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð°Ð»ÐµÑ€Ñ‚Ð¾Ð²"
}
}
}
}
}
]
# РаÑширенные tools Ð´Ð»Ñ Senior+
senior_tools = [
{
"type": "function",
"function": {
"name": "marketing_update_strategy",
"description": "Изменить Ñтратегию биддинга",
"parameters": {
"type": "object",
"properties": {
"campaign_id": {
"type": "string",
"description": "ID кампании"
},
"strategy": {
"type": "string",
"enum": ["position_hold", "min_price", "aggressive", "roi_optimize"],
"description": "СтратегиÑ"
},
"target_position": {
"type": "integer",
"description": "Ð¦ÐµÐ»ÐµÐ²Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ (Ð´Ð»Ñ position_hold)"
},
"target_drr": {
"type": "number",
"description": "Целевой ДРР% (Ð´Ð»Ñ roi_optimize)"
}
},
"required": ["campaign_id", "strategy"]
}
}
},
{
"type": "function",
"function": {
"name": "marketing_update_limits",
"description": "Изменить лимиты кампании",
"parameters": {
"type": "object",
"properties": {
"campaign_id": {
"type": "string",
"description": "ID кампании"
},
"max_bid": {
"type": "number",
"description": "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ñтавка"
},
"daily_limit": {
"type": "number",
"description": "Дневной бюджет"
}
},
"required": ["campaign_id"]
}
}
},
{
"type": "function",
"function": {
"name": "marketing_summary_report",
"description": "Сводный отчёт по рекламе",
"parameters": {
"type": "object",
"properties": {
"period_days": {
"type": "integer",
"description": "Период в днÑÑ…",
"default": 7
},
"group_by": {
"type": "string",
"enum": ["marketplace", "brand", "campaign"],
"description": "Группировка"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "marketing_ai_recommendations",
"description": "AI-рекомендации по оптимизации",
"parameters": {
"type": "object",
"properties": {
"campaign_id": {
"type": "string",
"description": "ID кампании (опционально)"
}
}
}
}
}
]
# Admin tools
admin_tools = [
{
"type": "function",
"function": {
"name": "marketing_safety_settings",
"description": "ÐаÑтройки Safety Logic",
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["get", "update"],
"description": "ДейÑтвие"
},
"settings": {
"type": "object",
"description": "Ðовые наÑтройки (Ð´Ð»Ñ update)"
}
},
"required": ["action"]
}
}
}
]
tools = base_tools.copy()
if role in ("senior", "director", "administrator"):
tools.extend(senior_tools)
if role == "administrator":
tools.extend(admin_tools)
return tools
def _get_default_buttons(self, role: str) -> List[dict]:
"""Кнопки по умолчанию Ð´Ð»Ñ Ñ€Ð¾Ð»Ð¸."""
buttons = [
{
"label": "📊 Мои кампании",
"action": "marketing_campaigns_list",
"params": {"status": "active"}
},
{
"label": "🔔 Ðлерты",
"action": "marketing_alerts_list",
"params": {"status": "unread"}
}
]
if role in ("senior", "director", "administrator"):
buttons.extend([
{
"label": "📈 Сводный отчёт",
"action": "marketing_summary_report",
"params": {"period_days": 7}
},
{
"label": "💡 AI-рекомендации",
"action": "marketing_ai_recommendations",
"params": {}
}
])
return buttons4.3 Marketing Tools
4.3.1 СпиÑок Tools
| Tool | ОпиÑание | Роли |
|---|---|---|
marketing_campaigns_list | СпиÑок кампаний | Manager+ |
marketing_campaign_stats | СтатиÑтика кампании | Manager+ |
marketing_keywords_list | Ключевые Ñлова кампании | Manager+ |
marketing_update_bid | Изменение Ñтавки | Manager+ |
marketing_pause_keyword | Пауза ключа | Manager+ |
marketing_resume_keyword | Возобновление ключа | Manager+ |
marketing_alerts_list | СпиÑок алертов | Manager+ |
marketing_pause_campaign | Пауза кампании | Manager+ |
marketing_resume_campaign | Возобновление кампании | Manager+ |
marketing_update_strategy | Изменение Ñтратегии | Senior+ |
marketing_update_limits | Изменение лимитов | Senior+ |
marketing_summary_report | Сводный отчёт | Senior+ |
marketing_ai_recommendations | AI-рекомендации | Senior+ |
marketing_safety_settings | ÐаÑтройки Safety Logic | Admin |
4.3.2 Tool: marketing_campaigns_list
python
# tools/marketing_campaigns.py
"""
title: Marketing Campaigns Tools
author: Adolf Team
version: 1.0.0
"""
from typing import Callable, Any, Optional, List
from pydantic import BaseModel, Field
import requests
class Valves(BaseModel):
MIDDLEWARE_URL: str = Field(default="http://middleware:8000")
MIDDLEWARE_API_KEY: str = Field(default="")
class Tools:
"""ИнÑтрументы Ð´Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Ñ Ñ€ÐµÐºÐ»Ð°Ð¼Ð½Ñ‹Ð¼Ð¸ кампаниÑми."""
def __init__(self):
self.valves = Valves()
def marketing_campaigns_list(
self,
marketplace: str = "all",
status: str = "active",
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Получение ÑпиÑка рекламных кампаний.
Args:
marketplace: ÐœÐ°Ñ€ÐºÐµÑ‚Ð¿Ð»ÐµÐ¹Ñ (wb, ozon, ym, all)
status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ð¹ (active, paused, all)
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Загрузка кампаний...", "done": False}
})
try:
params = {"status": status}
if marketplace != "all":
params["marketplace"] = marketplace
response = requests.get(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/campaigns",
headers=self._get_headers(__user__),
params=params
)
campaigns = response.json()
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": f"Ðайдено {len(campaigns)} кампаний", "done": True}
})
if not campaigns:
return "Ðет рекламных кампаний по заданным критериÑм."
return self._format_campaigns_list(campaigns)
except Exception as e:
return f"Ошибка при загрузке кампаний: {str(e)}"
def marketing_campaign_stats(
self,
campaign_id: str,
period_days: int = 7,
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Получение ÑтатиÑтики кампании.
Args:
campaign_id: ID кампании
period_days: Период в днÑÑ…
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": f"Загрузка ÑтатиÑтики за {period_days} дней...", "done": False}
})
try:
response = requests.get(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/campaigns/{campaign_id}/stats",
headers=self._get_headers(__user__),
params={"period_days": period_days}
)
if response.status_code == 404:
return f"ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ {campaign_id} не найдена."
data = response.json()
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "СтатиÑтика загружена", "done": True}
})
return self._format_campaign_stats(data)
except Exception as e:
return f"Ошибка: {str(e)}"
def marketing_pause_campaign(
self,
campaign_id: str,
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
ПриоÑтановить кампанию.
Args:
campaign_id: ID кампании
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "ПриоÑтановка кампании...", "done": False}
})
try:
response = requests.post(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/campaigns/{campaign_id}/pause",
headers=self._get_headers(__user__)
)
if response.status_code == 200:
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "✅ ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð¾Ñтановлена", "done": True}
})
return f"✅ ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ **{campaign_id}** приоÑтановлена."
else:
error = response.json().get("detail", "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°")
return f"⌠Ошибка: {error}"
except Exception as e:
return f"⌠Ошибка: {str(e)}"
def marketing_resume_campaign(
self,
campaign_id: str,
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Возобновить кампанию.
Args:
campaign_id: ID кампании
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Возобновление кампании...", "done": False}
})
try:
response = requests.post(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/campaigns/{campaign_id}/resume",
headers=self._get_headers(__user__)
)
if response.status_code == 200:
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "✅ ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð²Ð¾Ð·Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð°", "done": True}
})
return f"✅ ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ **{campaign_id}** возобновлена."
else:
error = response.json().get("detail", "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°")
return f"⌠Ошибка: {error}"
except Exception as e:
return f"⌠Ошибка: {str(e)}"
def _get_headers(self, user: dict) -> dict:
"""Формирование заголовков запроÑа."""
return {
"Authorization": f"Bearer {self.valves.MIDDLEWARE_API_KEY}",
"X-User-ID": str(user.get("id")),
"X-User-Role": user.get("role", "staff"),
"X-User-Brand": user.get("valves", {}).get("brand_id", "all")
}
def _format_campaigns_list(self, campaigns: List[dict]) -> str:
"""Форматирование ÑпиÑка кампаний."""
output = "**📊 Рекламные кампании:**\n\n"
# Группировка по маркетплейÑам
by_mp = {}
for c in campaigns:
mp = c.get("marketplace", "unknown")
if mp not in by_mp:
by_mp[mp] = []
by_mp[mp].append(c)
mp_icons = {"wb": "🟣", "ozon": "🔵", "ym": "🟡"}
status_icons = {"active": "🟢", "paused": "â¸ï¸", "error": "🔴"}
for mp, mp_campaigns in by_mp.items():
output += f"\n### {mp_icons.get(mp, '⚪')} {mp.upper()}\n\n"
for c in mp_campaigns:
status_icon = status_icons.get(c.get("status"), "â“")
output += f"---\n"
output += f"{status_icon} **{c.get('name', 'Без названиÑ')}**\n"
output += f"ID: `{c.get('id')}` | Тип: {c.get('campaign_type')}\n"
output += f"Бюджет: {c.get('daily_limit', 0):,.0f} ₽/день | "
output += f"Max Bid: {c.get('max_bid', 0):,.0f} ₽\n"
stats = c.get("today_stats", {})
output += f"СегоднÑ: {stats.get('spent', 0):,.0f} ₽ | "
output += f"CTR: {stats.get('ctr', 0):.2f}% | "
output += f"ДРР: {stats.get('drr', 0):.1f}%\n"
output += f"\n`[СтатиÑтика: marketing_campaign_stats(\"{c.get('id')}\")]`\n"
return output
def _format_campaign_stats(self, data: dict) -> str:
"""Форматирование ÑтатиÑтики кампании."""
campaign = data.get("campaign", {})
stats = data.get("stats", {})
keywords = data.get("top_keywords", [])
output = f"**📈 СтатиÑтика кампании: {campaign.get('name')}**\n\n"
output += f"МаркетплейÑ: {campaign.get('marketplace', '').upper()}\n"
output += f"СтатуÑ: {campaign.get('status')}\n"
output += f"СтратегиÑ: {campaign.get('strategy')}\n\n"
output += "### Метрики за период\n\n"
output += f"| Метрика | Значение |\n"
output += f"|---------|----------|\n"
output += f"| Показы | {stats.get('views', 0):,} |\n"
output += f"| Клики | {stats.get('clicks', 0):,} |\n"
output += f"| CTR | {stats.get('ctr', 0):.2f}% |\n"
output += f"| РаÑход | {stats.get('spent', 0):,.0f} ₽ |\n"
output += f"| CPC | {stats.get('cpc', 0):.2f} ₽ |\n"
output += f"| Заказы | {stats.get('orders', 0):,} |\n"
output += f"| Выручка | {stats.get('revenue', 0):,.0f} ₽ |\n"
output += f"| CPO | {stats.get('cpo', 0):.0f} ₽ |\n"
output += f"| ДРР| {stats.get('drr', 0):.1f}% |\n\n"
if keywords:
output += "### Топ-5 ключевых Ñлов\n\n"
output += "| Ключ | Ставка | CTR | ДРР|\n"
output += "|------|--------|-----|-----|\n"
for kw in keywords[:5]:
output += f"| {kw.get('keyword', '')[:20]} | "
output += f"{kw.get('current_bid', 0):.0f} ₽ | "
output += f"{kw.get('ctr', 0):.2f}% | "
output += f"{kw.get('drr', 0):.1f}% |\n"
return output4.3.3 Tool: marketing_keywords
python
# tools/marketing_keywords.py
"""
title: Marketing Keywords Tools
author: Adolf Team
version: 1.0.0
"""
from typing import Callable, Any, Optional, List
from pydantic import BaseModel, Field
import requests
class Valves(BaseModel):
MIDDLEWARE_URL: str = Field(default="http://middleware:8000")
MIDDLEWARE_API_KEY: str = Field(default="")
class Tools:
"""ИнÑтрументы Ð´Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ñ‹Ð¼Ð¸ Ñловами."""
def __init__(self):
self.valves = Valves()
def marketing_keywords_list(
self,
campaign_id: str,
status: str = "all",
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Получение ÑпиÑка ключевых Ñлов кампании.
Args:
campaign_id: ID кампании
status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÐºÐ»ÑŽÑ‡ÐµÐ¹ (active, paused, all)
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Загрузка ключевых Ñлов...", "done": False}
})
try:
response = requests.get(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/campaigns/{campaign_id}/keywords",
headers=self._get_headers(__user__),
params={"status": status}
)
keywords = response.json()
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": f"Ðайдено {len(keywords)} ключей", "done": True}
})
if not keywords:
return "Ðет ключевых Ñлов в кампании."
return self._format_keywords_list(keywords, campaign_id)
except Exception as e:
return f"Ошибка: {str(e)}"
def marketing_update_bid(
self,
keyword_id: str,
new_bid: float,
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Изменить Ñтавку ключевого Ñлова.
Args:
keyword_id: ID ключевого Ñлова
new_bid: ÐÐ¾Ð²Ð°Ñ Ñтавка в рублÑÑ…
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": f"Обновление Ñтавки на {new_bid} ₽...", "done": False}
})
try:
response = requests.put(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/keywords/{keyword_id}/bid",
headers=self._get_headers(__user__),
json={"new_bid": new_bid}
)
if response.status_code == 200:
data = response.json()
old_bid = data.get("old_bid", 0)
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "✅ Ставка обновлена", "done": True}
})
diff = new_bid - old_bid
diff_sign = "+" if diff > 0 else ""
return f"✅ Ставка изменена: **{old_bid:.0f} ₽** → **{new_bid:.0f} ₽** ({diff_sign}{diff:.0f} ₽)"
elif response.status_code == 400:
error = response.json()
if error.get("code") == "BID_EXCEEDS_MAX":
return f"⌠Ставка {new_bid} ₽ превышает макÑимум ({error.get('max_bid')} ₽)"
return f"⌠Ошибка: {error.get('detail')}"
elif response.status_code == 403:
return "⌠Ðет прав на изменение Ñтавки"
else:
return f"⌠Ошибка: {response.json().get('detail', 'ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°')}"
except Exception as e:
return f"⌠Ошибка: {str(e)}"
def marketing_pause_keyword(
self,
keyword_id: str,
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
ПриоÑтановить ключевое Ñлово.
Args:
keyword_id: ID ключевого Ñлова
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "ПриоÑтановка ключа...", "done": False}
})
try:
response = requests.post(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/keywords/{keyword_id}/pause",
headers=self._get_headers(__user__)
)
if response.status_code == 200:
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "✅ Ключ приоÑтановлен", "done": True}
})
return f"✅ Ключевое Ñлово **{keyword_id}** приоÑтановлено."
else:
return f"⌠Ошибка: {response.json().get('detail')}"
except Exception as e:
return f"⌠Ошибка: {str(e)}"
def marketing_resume_keyword(
self,
keyword_id: str,
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Возобновить ключевое Ñлово.
Args:
keyword_id: ID ключевого Ñлова
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Возобновление ключа...", "done": False}
})
try:
response = requests.post(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/keywords/{keyword_id}/resume",
headers=self._get_headers(__user__)
)
if response.status_code == 200:
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "✅ Ключ возобновлён", "done": True}
})
return f"✅ Ключевое Ñлово **{keyword_id}** возобновлено."
else:
return f"⌠Ошибка: {response.json().get('detail')}"
except Exception as e:
return f"⌠Ошибка: {str(e)}"
def _get_headers(self, user: dict) -> dict:
return {
"Authorization": f"Bearer {self.valves.MIDDLEWARE_API_KEY}",
"X-User-ID": str(user.get("id")),
"X-User-Role": user.get("role", "staff"),
"X-User-Brand": user.get("valves", {}).get("brand_id", "all")
}
def _format_keywords_list(self, keywords: List[dict], campaign_id: str) -> str:
"""Форматирование ÑпиÑка ключевых Ñлов."""
output = f"**🔑 Ключевые Ñлова кампании {campaign_id}:**\n\n"
status_icons = {"active": "🟢", "paused": "â¸ï¸", "rejected": "🔴"}
output += "| Ð¡Ñ‚Ð°Ñ‚ÑƒÑ | Ключ | Ставка | CTR | ДРР| ДейÑÑ‚Ð²Ð¸Ñ |\n"
output += "|--------|------|--------|-----|-----|----------|\n"
for kw in keywords:
status = kw.get("status", "unknown")
icon = status_icons.get(status, "â“")
keyword_text = kw.get("keyword", "")[:25]
output += f"| {icon} | {keyword_text} | "
output += f"{kw.get('current_bid', 0):.0f} ₽ | "
output += f"{kw.get('ctr', 0):.2f}% | "
output += f"{kw.get('drr', 0):.1f}% | "
if status == "active":
output += f"â¸ï¸ `pause` "
else:
output += f"â–¶ï¸ `resume` "
output += "|\n"
output += "\n_Ð”Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñтавки: `marketing_update_bid(\"keyword_id\", новаÑ_Ñтавка)`_"
return output4.3.4 Tool: marketing_analytics
python
# tools/marketing_analytics.py
"""
title: Marketing Analytics Tools
author: Adolf Team
version: 1.0.0
"""
from typing import Callable, Any, Optional
from pydantic import BaseModel, Field
import requests
class Valves(BaseModel):
MIDDLEWARE_URL: str = Field(default="http://middleware:8000")
MIDDLEWARE_API_KEY: str = Field(default="")
class Tools:
"""ÐналитичеÑкие инÑтрументы Marketing."""
def __init__(self):
self.valves = Valves()
def marketing_summary_report(
self,
period_days: int = 7,
group_by: str = "marketplace",
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
Сводный отчёт по рекламе.
Args:
period_days: Период в днÑÑ… (7, 14, 30)
group_by: Группировка (marketplace, brand, campaign)
"""
user_role = __user__.get("role", "staff")
if user_role not in ("senior", "director", "administrator"):
return "⌠Сводные отчёты доÑтупны только Ð´Ð»Ñ Senior, Director, Administrator."
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": f"Формирование отчёта за {period_days} дней...", "done": False}
})
try:
response = requests.get(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/reports/summary",
headers=self._get_headers(__user__),
params={"period_days": period_days, "group_by": group_by}
)
data = response.json()
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Отчёт Ñформирован", "done": True}
})
return self._format_summary_report(data, period_days, group_by)
except Exception as e:
return f"Ошибка: {str(e)}"
def marketing_ai_recommendations(
self,
campaign_id: Optional[str] = None,
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
AI-рекомендации по оптимизации рекламы.
Args:
campaign_id: ID кампании (опционально, еÑли не указан — общие рекомендации)
"""
user_role = __user__.get("role", "staff")
if user_role not in ("senior", "director", "administrator"):
return "⌠AI-рекомендации доÑтупны только Ð´Ð»Ñ Senior, Director, Administrator."
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Ðнализ данных...", "done": False}
})
try:
params = {}
if campaign_id:
params["campaign_id"] = campaign_id
response = requests.get(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/ai/recommendations",
headers=self._get_headers(__user__),
params=params
)
data = response.json()
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Ðнализ завершён", "done": True}
})
return self._format_ai_recommendations(data)
except Exception as e:
return f"Ошибка: {str(e)}"
def marketing_alerts_list(
self,
status: str = "unread",
__user__: dict = {},
__event_emitter__: Callable[[dict], Any] = None
) -> str:
"""
СпиÑок алертов Marketing.
Args:
status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ (unread, all)
"""
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": "Загрузка алертов...", "done": False}
})
try:
response = requests.get(
f"{self.valves.MIDDLEWARE_URL}/api/v1/marketing/alerts",
headers=self._get_headers(__user__),
params={"status": status}
)
alerts = response.json()
if __event_emitter__:
__event_emitter__({
"type": "status",
"data": {"description": f"Ðайдено {len(alerts)} алертов", "done": True}
})
if not alerts:
return "✅ Ðет активных алертов."
return self._format_alerts_list(alerts)
except Exception as e:
return f"Ошибка: {str(e)}"
def _get_headers(self, user: dict) -> dict:
return {
"Authorization": f"Bearer {self.valves.MIDDLEWARE_API_KEY}",
"X-User-ID": str(user.get("id")),
"X-User-Role": user.get("role", "staff"),
"X-User-Brand": user.get("valves", {}).get("brand_id", "all")
}
def _format_summary_report(self, data: dict, period: int, group_by: str) -> str:
"""Форматирование Ñводного отчёта."""
output = f"**📊 Сводный отчёт по рекламе ({period} дней)**\n\n"
totals = data.get("totals", {})
output += f"### Общие показатели\n\n"
output += f"| Метрика | Значение |\n"
output += f"|---------|----------|\n"
output += f"| Ðктивных кампаний | {totals.get('active_campaigns', 0)} |\n"
output += f"| Общий раÑход | {totals.get('total_spent', 0):,.0f} ₽ |\n"
output += f"| Средний ДРР| {totals.get('avg_drr', 0):.1f}% |\n"
output += f"| Общий CTR | {totals.get('avg_ctr', 0):.2f}% |\n"
output += f"| Заказы | {totals.get('total_orders', 0):,} |\n"
output += f"| Выручка | {totals.get('total_revenue', 0):,.0f} ₽ |\n\n"
# Данные по группам
groups = data.get("groups", [])
if groups:
output += f"### По {group_by}\n\n"
output += "| Группа | РаÑход | ДРР| CTR | Заказы |\n"
output += "|--------|--------|-----|-----|--------|\n"
for g in groups:
output += f"| {g.get('name', '-')} | "
output += f"{g.get('spent', 0):,.0f} ₽ | "
output += f"{g.get('drr', 0):.1f}% | "
output += f"{g.get('ctr', 0):.2f}% | "
output += f"{g.get('orders', 0):,} |\n"
return output
def _format_ai_recommendations(self, data: dict) -> str:
"""Форматирование AI-рекомендаций."""
output = "**💡 AI-рекомендации по оптимизации**\n\n"
recommendations = data.get("recommendations", [])
if not recommendations:
output += "_Ðет критичеÑких рекомендаций. Кампании работают Ñффективно._"
return output
priority_icons = {"high": "🔴", "medium": "🟡", "low": "🟢"}
for i, rec in enumerate(recommendations, 1):
priority = rec.get("priority", "low")
icon = priority_icons.get(priority, "⚪")
output += f"---\n"
output += f"{icon} **{i}. {rec.get('title')}**\n\n"
output += f"{rec.get('description')}\n\n"
output += f"**ДейÑтвие:** {rec.get('action')}\n"
output += f"**Ожидаемый Ñффект:** {rec.get('expected_effect')}\n"
if rec.get("risks"):
output += f"**РиÑки:** {rec.get('risks')}\n"
output += "\n"
return output
def _format_alerts_list(self, alerts: list) -> str:
"""Форматирование ÑпиÑка алертов."""
output = "**🔔 Ðлерты Marketing**\n\n"
severity_icons = {"critical": "🔴", "warning": "🟡", "info": "🔵"}
for alert in alerts:
severity = alert.get("severity", "info")
icon = severity_icons.get(severity, "⚪")
output += f"---\n"
output += f"{icon} **{alert.get('title')}**\n"
output += f"КампаниÑ: {alert.get('campaign_name')} | {alert.get('marketplace', '').upper()}\n"
output += f"{alert.get('message')}\n"
output += f"_ВремÑ: {alert.get('created_at')}_\n\n"
return output4.4 Интерактивные Ñлементы
4.4.1 Кнопки быÑтрых дейÑтвий
| Кнопка | ДейÑтвие | Роли |
|---|---|---|
| 📊 Мои кампании | marketing_campaigns_list | Manager+ |
| 🔔 Ðлерты | marketing_alerts_list | Manager+ |
| 📈 Сводный отчёт | marketing_summary_report | Senior+ |
| 💡 AI-рекомендации | marketing_ai_recommendations | Senior+ |
4.4.2 Карточка кампании
┌────────────────────────────────────────────â”
│ 🟢 ЛетнÑÑ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ 2026 │
├────────────────────────────────────────────┤
│ 🟣 WB | Ðукцион | ID: camp_12345 │
│ │
│ Бюджет: 5 000 ₽/день | Max Bid: 200 ₽ │
│ СтратегиÑ: Position Hold (Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ 5) │
│ │
│ СегоднÑ: │
│ • РаÑход: 3 240 ₽ (64%) │
│ • CTR: 3.8% | ДРР: 12.5% │
│ • Заказов: 8 │
│ │
│ [📊 СтатиÑтика] [🔑 Ключи] [â¸ï¸ Пауза] │
└────────────────────────────────────────────┘4.4.3 Карточка ключевого Ñлова
┌────────────────────────────────────────────â”
│ 🟢 летнее платье женÑкое │
├────────────────────────────────────────────┤
│ Ставка: 120 ₽ | ПозициÑ: ~5 │
│ │
│ СтатиÑтика (7 дней): │
│ • Показы: 12 450 | Клики: 485 │
│ • CTR: 3.9% | CPC: 115 ₽ │
│ • Заказы: 12 | ДРР: 8.2% │
│ │
│ [â¬†ï¸ +10₽] [â¬‡ï¸ -10₽] [âœï¸ Ставка] [â¸ï¸ Пауза] │
└────────────────────────────────────────────┘4.4.4 Ð ÐµÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ ÐºÐ½Ð¾Ð¿Ð¾Ðº
python
# tools/marketing_buttons.py
def generate_campaign_buttons(campaign: dict, user_role: str) -> list:
"""Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ ÐºÐ½Ð¾Ð¿Ð¾Ðº Ð´Ð»Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ¸ кампании."""
buttons = [
{
"label": "📊 СтатиÑтика",
"action": "marketing_campaign_stats",
"params": {"campaign_id": campaign["id"]}
},
{
"label": "🔑 Ключи",
"action": "marketing_keywords_list",
"params": {"campaign_id": campaign["id"]}
}
]
if campaign["status"] == "active":
buttons.append({
"label": "â¸ï¸ Пауза",
"action": "marketing_pause_campaign",
"params": {"campaign_id": campaign["id"]},
"confirm": "ПриоÑтановить кампанию?"
})
else:
buttons.append({
"label": "â–¶ï¸ Ð’Ð¾Ð·Ð¾Ð±Ð½Ð¾Ð²Ð¸Ñ‚ÑŒ",
"action": "marketing_resume_campaign",
"params": {"campaign_id": campaign["id"]}
})
if user_role in ("senior", "director", "administrator"):
buttons.append({
"label": "âš™ï¸ ÐаÑтройки",
"action": "marketing_campaign_settings",
"params": {"campaign_id": campaign["id"]}
})
return buttons
def generate_keyword_buttons(keyword: dict, user_role: str) -> list:
"""Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ ÐºÐ½Ð¾Ð¿Ð¾Ðº Ð´Ð»Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ¸ ключевого Ñлова."""
current_bid = keyword.get("current_bid", 0)
buttons = [
{
"label": "â¬†ï¸ +10₽",
"action": "marketing_update_bid",
"params": {"keyword_id": keyword["id"], "new_bid": current_bid + 10}
},
{
"label": "â¬‡ï¸ -10₽",
"action": "marketing_update_bid",
"params": {"keyword_id": keyword["id"], "new_bid": max(1, current_bid - 10)}
},
{
"label": "âœï¸ Ставка",
"action": "prompt",
"prompt": f"Введите новую Ñтавку Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° '{keyword['keyword']}' (текущаÑ: {current_bid} ₽):"
}
]
if keyword.get("status") == "active":
buttons.append({
"label": "â¸ï¸",
"action": "marketing_pause_keyword",
"params": {"keyword_id": keyword["id"]}
})
else:
buttons.append({
"label": "â–¶ï¸",
"action": "marketing_resume_keyword",
"params": {"keyword_id": keyword["id"]}
})
return buttons4.5 Примеры диалогов
4.5.1 ПроÑмотр кампаний (Manager)
👤 User: Покажи мои рекламные кампании
🤖 Adolf Marketing:
**📊 Рекламные кампании:**
### 🟣 WB
---
🟢 **ЛетнÑÑ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ 2026**
ID: `camp_wb_001` | Тип: Ðукцион
Бюджет: 5 000 ₽/день | Max Bid: 200 ₽
СегоднÑ: 3 240 ₽ | CTR: 3.8% | ДРР: 12.5%
`[СтатиÑтика: marketing_campaign_stats("camp_wb_001")]`
---
🟢 **Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ**
ID: `camp_wb_002` | Тип: ÐвтоматичеÑкаÑ
Бюджет: 3 000 ₽/день | Max Bid: 150 ₽
СегоднÑ: 1 850 ₽ | CTR: 2.9% | ДРР: 15.2%
`[СтатиÑтика: marketing_campaign_stats("camp_wb_002")]`
[📊 Мои кампании] [🔔 Ðлерты]4.5.2 Изменение Ñтавки (Manager)
👤 User: Увеличь Ñтавку на ключ "летнее платье" до 150 рублей
🤖 Adolf Marketing:
✅ Ставка изменена: **120 ₽** → **150 ₽** (+30 ₽)
Ключевое Ñлово: "летнее платье женÑкое"
КампаниÑ: ЛетнÑÑ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ 2026
âš ï¸ ÐžÐ±Ñ€Ð°Ñ‚Ð¸Ñ‚Ðµ внимание: Ð½Ð¾Ð²Ð°Ñ Ñтавка приближаетÑÑ Ðº Max Bid (200 ₽).
[â¬†ï¸ +10₽] [â¬‡ï¸ -10₽] [â¸ï¸ Пауза]4.5.3 AI-рекомендации (Senior)
👤 User: Дай рекомендации по оптимизации рекламы
🤖 Adolf Marketing:
**💡 AI-рекомендации по оптимизации**
---
🔴 **1. Ð’Ñ‹Ñокий ДРРв кампании "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ"**
ДРРÑоÑтавлÑет 15.2%, что выше целевого (10%). ОÑновной вклад вноÑÑÑ‚ 3 ключевых Ñлова Ñ Ð”Ð Ð > 20%.
**ДейÑтвие:** ПриоÑтановить ключи "футболка оверÑайз", "худи женÑкое", "джинÑÑ‹ мом" или Ñнизить Ñтавки на 30%.
**Ожидаемый Ñффект:** Снижение ДРРдо 10-11%
**РиÑки:** Временное Ñнижение показов на 15-20%
---
🟡 **2. Ðизкий CTR на позициÑÑ… 8-10**
5 ключей находÑÑ‚ÑÑ Ð½Ð° позициÑÑ… 8-10 Ñ CTR < 2%. Увеличение Ñтавок позволит занÑть позиции 4-6.
**ДейÑтвие:** Увеличить Ñтавки на 20 ₽ Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡ÐµÐ¹: "платье миди", "Ñарафан летний", "юбка плиÑÑе"
**Ожидаемый Ñффект:** РоÑÑ‚ CTR до 3.5-4%, роÑÑ‚ заказов на 25%
---
🟢 **3. Ðффективный ключ без роÑта бюджета**
Ключ "платье в горошек" показывает ДРР5.8% при CTR 4.2%. ЕÑть потенциал Ð´Ð»Ñ Ð¼Ð°ÑштабированиÑ.
**ДейÑтвие:** Увеличить Max Bid Ñ 150 ₽ до 200 ₽ и дневной бюджет кампании.
**Ожидаемый Ñффект:** РоÑÑ‚ заказов на 40% при Ñохранении ДРÐ
[📊 Сводный отчёт] [🔔 Ðлерты]4.6 Матрица доÑтупа Tools
| Tool | Manager | Senior | Director | Admin |
|---|---|---|---|---|
| marketing_campaigns_list | ✅ | ✅ | ✅ | ✅ |
| marketing_campaign_stats | ✅ | ✅ | ✅ | ✅ |
| marketing_keywords_list | ✅ | ✅ | ✅ | ✅ |
| marketing_update_bid | ✅* | ✅ | ✅ | ✅ |
| marketing_pause_keyword | ✅ | ✅ | ✅ | ✅ |
| marketing_resume_keyword | ✅ | ✅ | ✅ | ✅ |
| marketing_pause_campaign | ✅ | ✅ | ✅ | ✅ |
| marketing_resume_campaign | ✅ | ✅ | ✅ | ✅ |
| marketing_alerts_list | ✅ | ✅ | ✅ | ✅ |
| marketing_update_strategy | ⌠| ✅ | ✅ | ✅ |
| marketing_update_limits | ⌠| ✅ | ✅ | ✅ |
| marketing_summary_report | ⌠| ✅ | ✅ | ✅ |
| marketing_ai_recommendations | ⌠| ✅ | ✅ | ✅ |
| marketing_safety_settings | ⌠| ⌠| ⌠| ✅ |
* Manager может изменÑть Ñтавки только в пределах уÑтановленных лимитов (max_bid).
4.7 Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð² UI
4.7.1 Типы уведомлений
| Тип | Иконка | ОпиÑание |
|---|---|---|
| budget_warning | âš ï¸ | Бюджет израÑходован на 80% |
| campaign_paused | â¸ï¸ | ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð¾Ñтановлена |
| keyword_paused | 🔴 | Ключ приоÑтановлен (Safety Logic) |
| bid_capped | 📊 | Ставка ограничена Max Bid |
| anomaly | 🔔 | ÐÐ½Ð¾Ð¼Ð°Ð»Ð¸Ñ Ñ€Ð°Ñходов |
4.7.2 Формат push-уведомлениÑ
json
{
"title": "Marketing: Бюджет 80%",
"body": "ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ 'ЛетнÑÑ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ': израÑходовано 4 000 из 5 000 ₽",
"icon": "âš ï¸",
"action": {
"tool": "marketing_campaign_stats",
"params": {"campaign_id": "camp_wb_001"}
},
"timestamp": "2026-01-22T14:30:00Z"
}Документ подготовлен: Январь 2026
ВерÑиÑ: 1.0
СтатуÑ: Черновик