Telegram-бот для анализа бизнеса: Python + Gemini + ZvenoAI за один вечер
ChatGPT без плагинов не видит конкретные сайты и не знает мелких российских компаний. Выдаёт текст, который звучит правдоподобно, но ни к чему не привязан.
В этой статье мы соберём Telegram-бота, который реально работает: ходит на сайт, ищет отзывы, находит конкурентов. Всё это через единый API ZvenoAI — никаких дополнительных сервисов. Весь код — меньше 200 строк.
Что получится
Бот принимает ссылку или название компании и возвращает структурированный анализ:
🏢 Что за компания и какого масштаба
📦 Продукты и цены
📊 Бизнес в цифрах (трафик, сроки, объём)
🎯 Целевая аудитория и позиционирование
⚔️ Конкуренты с кратким сравнением
⭐ Реальные отзывы — что хвалят, что ругают
💪 Сильные стороны и риски
В конце статьи — полный реальный ответ бота по цветочному магазину rus-buket.ru.
Стек
-
Python 3 + aiogram 3 — асинхронный Telegram-бот
-
ZvenoAI API (api.zveno.ai/v1) — OpenAI-совместимый шлюз ко всем моделям
-
Gemini 2.5 Flash (google/gemini-3-flash-preview) — основная модель, умеет tool use, 1M токенов контекста
-
Perplexity Sonar Pro (perplexity/sonar-pro) — поиск в интернете с актуальными данными
-
agent-browser — просмотр веб-страниц из кода
-
Один API-ключ ZvenoAI — и Gemini, и Perplexity идут через него. Не нужно регистрироваться у каждого вендора отдельно.
Как устроен агент
Код будет понятнее, если сначала разобрать схему. Это агентный цикл с инструментами:
Запрос пользователя ↓ Gemini Flash ←──── системный промпт + история ↓ finish_reason? ┌────┴────┐ tool_calls stop ↓ ↓ Выполнить Финальный инструмент ответ ↓ Добавить результат в messages → снова GeminiGemini сам решает, какой инструмент вызвать и в каком порядке. Для анализа бизнеса он обычно делает три вызова за один запрос: открывает сайт, ищет отзывы, ищет конкурентов. После трёх вызовов у него достаточно данных для финального отчёта.
Шаг 0. Создаём бота и получаем ключи
BotFather. Открываем Telegram, ищем @BotFather, отправляем /newbot. Указываем имя и username (должен заканчиваться на bot). Получаем токен вида 8710689253:AAH2….
ZvenoAI. Идём на zveno.ai, логинимся, в разделе API Keys создаём новый ключ. Названия типа ai_business_bot помогут не запутаться, когда ключей станет несколько.
Шаг 1. Структура проекта
ai_business_bot/ bot.py ← точка входа, aiogram handlers agent.py ← LLM-агент: tool use loop с Gemini tools.py ← реализация инструментов: browse + search config.py ← настройки из env .env ← токены (не в git) requirements.txtСоздаём venv и ставим зависимости:
python3 -m venv .venv .venv/bin/pip install "aiogram>=3,<4" "openai>=1.0" python-dotenvТри пакета, никаких лишних зависимостей. OpenAI SDK используем не для OpenAI — он совместим с любым endpoint, в том числе с ZvenoAI.
Шаг 2. config.py
Все токены из .env, никаких хардкодов:
import os from dotenv import load_dotenvload_dotenv() TELEGRAM_TOKEN = os.environ["TELEGRAM_TOKEN"] ZVENO_API_KEY = os.environ["ZVENO_API_KEY"] ZVENO_BASE_URL = "https://api.zveno.ai/v1" MAIN_MODEL = "google/gemini-3-flash-preview" SEARCH_MODEL = "perplexity/sonar-pro".env:
TELEGRAM_TOKEN=ваш_токен_от_botfather ZVENO_API_KEY=ваш_ключ_от_zveno_aiОбратите внимание: две разные модели через один API. MAIN_MODEL — Gemini, который управляет агентом и пишет итоговый отчёт. SEARCH_MODEL — Perplexity, который умеет искать в интернете. Переключаемся между ними меняя одну строку.
Шаг 3. tools.py — два инструмента агента
генту доступны два инструмента: просмотр страницы и поиск в интернете.
browse_url — открываем сайт
Используем agent-browser — CLI-инструмент для управления браузером из скриптов. Устанавливать не нужно, npx скачает сам при первом вызове.
Есть нюанс: agent-browser — stateful-демон с одним глобальным состоянием браузера. Если вызвать close, весь браузер закрывается. Поэтому открываем новую вкладку, читаем, закрываем только её через tab close:
import subprocess import asyncio import logging from openai import AsyncOpenAI from config import ZVENO_API_KEY, ZVENO_BASE_URL, SEARCH_MODEL logger = logging.getLogger(__name__) _client = AsyncOpenAI(api_key=ZVENO_API_KEY, base_url=ZVENO_BASE_URL) def _run_agent_browser(url: str) -> str: def run(*args): return subprocess.run( ["npx", "--yes", "agent-browser", *args], capture_output=True, text=True, timeout=30, ) try: run("tab", "new", check=True) run("open", url, check=True) run("wait", "--load", "networkidle", check=True) result = run("get", "text", "body", check=True) text = result.stdout.strip() except subprocess.CalledProcessError as e: text = f"Ошибка загрузки: {e.stderr[:300]}" except subprocess.TimeoutExpired: text = "Страница не загрузилась за 30 секунд." finally: subprocess.run( ["npx", "--yes", "agent-browser", "tab", "close"], capture_output=True, timeout=10, ) # Обрезаем, чтобы не переполнить контекст модели if len(text) > 8000: text = text[:8000] + "\n... [текст обрезан]" return text async def browse_url(url: str) -> str: logger.info("browse_url: %s", url) return await asyncio.to_thread(_run_agent_browser, url)Весь бот асинхронный, но subprocess.run блокирует поток. asyncio.to_thread переносит этот вызов в пул потоков — бот не зависает.
search_web — поиск через Perplexity
Perplexity Sonar Pro не поддерживает tool use — используем его как обычный completion:
async def search_web(query: str) -> str: logger.info("search_web: %s", query) response = await _client.chat.completions.create( model=SEARCH_MODEL, messages=[ { "role": "system", "content": ( "Ты помощник для поиска информации о компаниях. " "Отвечай по-русски. Приводи источники в конце ответа." ), }, {"role": "user", "content": query}, ], ) return response.choices[0].message.content or "Нет результатов."Perplexity возвращает живые данные из интернета с источниками. Gemini потом использует эти данные при написании отчёта.
Шаг 4. agent.py — tool use loop
Gemini получает запрос, решает какие инструменты вызвать, мы выполняем вызовы и отдаём результаты обратно. Цикл продолжается до финального ответа.Сначала определяем инструменты в формате OpenAI function calling:
TOOLS = [ { "type": "function", "function": { "name": "browse_url", "description": "Открывает веб-страницу и возвращает текст.", "parameters": { "type": "object", "properties": { "url": {"type": "string", "description": "URL страницы"} }, "required": ["url"], }, }, }, { "type": "function", "function": { "name": "search_web", "description": "Ищет информацию о компании в интернете.", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "Поисковый запрос"} }, "required": ["query"], }, }, }, ]Системный промпт задаёт алгоритм работы: сначала смотрим сайт, потом ищем отзывы и конкурентов, потом делаем анализ:
SYSTEM_PROMPT = """\ Ты — профессиональный AI-аналитик бизнеса. Твоя задача — провести глубокий анализ. Алгоритм работы: 1. Если есть URL — browse_url сайта компании. 2. search_web: "отзывы [название] отзовик irecommend" 3. search_web: "конкуренты [название] рынок [сфера] Россия" 4. Собери анализ в структурированный отчёт. Формат ответа (строго на русском): 🏢 Компания — название, сфера, год, масштаб, география 📦 Продукты и цены — ассортимент, ценовой диапазон, уникальные позиции 📊 Бизнес в цифрах — трафик, количество заказов, сроки, рейтинги 🎯 Аудитория и позиционирование — кто покупает, УТП ⚔️ Конкуренты — 3-5 конкурентов с кратким сравнением ⭐ Отзывы — плюсы (2-3 из реальных), минусы (2-3 из реальных), рейтинг 💪 Сильные стороны — 3-4 реальных преимущества ⚠️ Риски — 3-4 конкретные проблемы Только конкретные данные, никаких общих фраз. Если данных нет — так и скажи. """Сам loop — до 5 итераций на случай сложных запросов:
async def run(user_message: str) -> str: messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_message}, ] for _ in range(5): response = await _client.chat.completions.create( model=MAIN_MODEL, messages=messages, tools=TOOLS, tool_choice="auto", ) choice = response.choices[0] message = choice.message if choice.finish_reason == "tool_calls": messages.append(message) for tc in message.tool_calls: fn_name = tc.function.name args = json.loads(tc.function.arguments) logger.info("Tool call: %s(%s)", fn_name, args) result = await _TOOL_FN[fn_name](**args) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": result, }) continue # отдаём результаты обратно модели return message.content or "Агент не вернул ответ." return "Достигнут лимит итераций."Когда finish_reason == «tool_calls» — Gemini хочет вызвать инструмент. Выполняем, добавляем результат в messages, идём на следующую итерацию. Когда finish_reason == «stop» — это финальный текстовый ответ.
Шаг 5. bot.py — aiogram handlers
import asyncio import logging from aiogram import Bot, Dispatcher, F from aiogram.filters import CommandStart, Command from aiogram.types import Message from aiogram.enums import ChatAction import agent from config import TELEGRAM_TOKEN bot = Bot(token=TELEGRAM_TOKEN) dp = Dispatcher() @dp.message(CommandStart()) async def cmd_start(message: Message) -> None: await message.answer( "👋 Я AI-аналитик бизнеса. Отправь ссылку на сайт или название компании.\n\n" "Примеры:\n" "• `/analyze https://rus-buket.ru`\n" "• `расскажи о компании Яндекс`", parse_mode="Markdown", ) @dp.message(Command("analyze")) async def cmd_analyze(message: Message) -> None: parts = (message.text or "").split(maxsplit=1) if len(parts) < 2: await message.answer("Укажи URL: `/analyze https://example.com`", parse_mode="Markdown") return await _process(message, parts[1].strip()) @dp.message(F.text) async def handle_text(message: Message) -> None: await _process(message, message.text or "") async def _process(message: Message, query: str) -> None: # Показываем typing пока агент думает stop = asyncio.Event() async def keep_typing(): while not stop.is_set(): await bot.send_chat_action(message.chat.id, ChatAction.TYPING) await asyncio.sleep(4) typing = asyncio.create_task(keep_typing()) try: answer = await agent.run(query) except Exception as e: answer = f"Ошибка: {e}" finally: stop.set() typing.cancel() if len(answer) > 4096: answer = answer[:4090] + "\n…" await message.answer(answer, parse_mode="Markdown") async def main(): await dp.start_polling(bot) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) asyncio.run(main())Typing action в Telegram гаснет через 5 секунд — поэтому обновляем его каждые 4 секунды в фоновой coroutine. Пока агент работает, пользователь видит «печатает…».
Шаг 6. Запуск
.venv/bin/python bot.pyВ логах увидите что-то такое:
INFO Starting bot... INFO aiogram.dispatcher: Run polling for bot @ai_business_analysis_bot INFO agent: Tool call: browse_url({'url': 'https://rus-buket.ru'}) INFO agent: Tool call: search_web({'query': 'Русский букет отзывы отзовик irecommend'}) INFO agent: Tool call: search_web({'query': 'конкуренты rus-buket.ru доставка цветов Россия 2024'})Три инструмента за одну итерацию — агент сам решил что нужно сделать и в каком порядке.Приветствие на /start:
Что получилось
Отправляем боту /analyze https://rus-buket.ru и через 32 секунды получаем:
Вот полный ответ бота:
🏢 Компания«Русский Букет» — международная сеть службы доставки цветов и подарков.
Сфера: E-commerce, флористика, логистика подарковГод основания: 2012 (активное развитие с этого периода)
География: Работает в более чем 100 городах России, доставка в 100+ стран мира
Масштаб: Одна из крупнейших федеральных сетей. Головной офис — Новосибирск. Сеть из 2000+ партнёрских салонов
📦 Продукты и цены
Ассортимент: монобукеты (розы, пионы, тюльпаны), авторские композиции, цветы в коробках, комнатные растения. Дополнительно: игрушки, сладости, воздушные шары.Бюджет: 1 500–2 500 ₽ (небольшие букеты, кенийские розы)
Средний чек: 4 500–7 000 ₽ (сборные авторские букеты)Премиум: от 15 000 ₽ (101 роза, гигантские корзины)Уникальные позиции: фотоотчёт в момент вручения (бесплатно), анонимная доставка
📊 Бизнес в цифрах
Трафик сайта: ~350–450 тыс. визитов в месяцСроки доставки: от 2 часов в крупных городах
В пиковые даты (8 марта, 14 февраля): сотни заказов в час
Рейтинг приложения: ТОП-3 в App Store / Google Play по версии РоскачестваПрограмма лояльности: кэшбэк до 10%
🎯 Аудитория и позиционирование
Основная аудитория — мужчины 25–45 лет (70% заказов), экспаты, люди, живущие далеко от родственников. Позиционирование: «Доставка радости через расстояния». Акцент на надёжности, фотоотчёте и SMS-уведомлениях.
УТП: гарантия свежести (замена букета в течение 24 часов) и международный охват через единое окно.
⚔️ Конкуренты
Flowwow — маркетплейс местных флористов, больше уникальности, но меньше стандартизации
Флорист.ру — федеральная сеть, схожая модель, ориентирована на массовый сегмент
Семицветик / Lush Flowers — региональные игроки, сильны в конкретных городах
Wildberries / Ozon — растут в нише, агрессивные цены, но нет экспресс-доставки цветов
Местные флористы — ниже цены и выше уникальность букетов, но нет масштаба и гарантий
⭐ Отзывы покупателей
Плюсы:
Скорость: «Заказал из США для мамы в Самаре, привезли через 2 часа»Сервис: бесплатная открытка и качественное фото получателя с букетомСоответствие: цветы в реальности часто совпадают с фото на сайте
Минусы:Праздничный коллапс: в 8 марта бывают задержки до 5–10 часов, замены цветов без согласования
Стойкость: жалобы на «уставшие» цветы в регионах с низким контролем партнёров
Средний рейтинг: 4.7–4.9 из 5 на крупных агрегаторах
💪 Сильные стороны
IT-платформа: удобное приложение с историей заказов и напоминаниями о датах
Глобальность: оплата картой РФ — доставка в Европу или СШАСтандартизация: жёсткие чек-листы для партнёров-флористов
SEO: топ выдачи по ключевым городам России
⚠️ Риски
Зависимость от партнёров: в малых городах качество зависит от конкретного местного магазина
Логистика: рост цен на топливо и курьеров давит на маржуИмпортозависимость: высокая доля эквадорских и кенийских цветов — цены волатильны из-за курса валют
ChatGPT без плагинов про rus-buket.ru практически ничего бы не сказал — компания слишком маленькая для его обучающей выборки. Здесь агент реально пошёл и посмотрел.
Сколько стоит один запрос
Один запрос к боту включает:
1 вызов browse_url — только CPU и трафик (agent-browser запускается локально, это бесплатно)
2 вызова search_web — 2 запроса к Perplexity Sonar Pro
2 вызова Gemini Flash — первый решает что делать и вызывает инструменты, второй пишет итоговый отчёт
Вот реальные данные из ZvenoAI для одного анализа rus-buket.ru:
Разбивка:
-
Gemini 3 Flash (первый вызов, решение о инструментах): 739 токенов → 0.07 ₽
-
Sonar Pro (поиск отзывов): ~1 000 токенов → 1.93 ₽
-
Sonar Pro (поиск конкурентов): ~1 100 токенов → 1.95 ₽
-
Gemini 3 Flash (финальный синтез отчёта): 2 800 + 1 300 токенов → 0.63 ₽
Итого: ~4.6 ₽ за полный анализ. Почти всё съедает Perplexity — два поисковых запроса по ~2 ₽ каждый. Gemini Flash работает практически бесплатно.На 500 ₽ баланса ZvenoAI — около 100 полных анализов.
Подводные камни
1. agent-browser — один глобальный демон. Нельзя вызвать close — закроется весь браузер вместе с другими вкладками. Используйте tab new + tab close.
2. Gemini возвращает `finish_reason: «tool_calls»`, а не `»stop»`. Нужен цикл, а не одиночный вызов API. Один запрос = один tool call, потом нужно снова звать модель с результатом.
3. Perplexity Sonar Pro не поддерживает tool use. Передавать ему tools — ошибка. Используйте как обычный completion.
4. Длинные страницы переполняют контекст. Обрезайте до ~8 000 символов. Для большинства сайтов достаточно — главная страница содержит нужное описание продуктов и сервиса.
5. Telegram typing action истекает через 5 секунд. Нужно обновлять каждые 4 секунды пока агент работает. Иначе пользователь видит тишину и не понимает, идёт ли что-то.6. SimilarWeb и похожие сервисы блокируют headless-браузеры через CloudFront. Данные о трафике лучше получать через search_web — Perplexity находит эти цифры из открытых источников.
Итог
Весь код — меньше 200 строк на четырёх файлах. Один API-ключ ZvenoAI закрывает и Gemini (tool use + анализ), и Perplexity (поиск). Никаких дополнительных сервисов.Агент не заменяет полноценное исследование — цифры по трафику стоит перепроверять, данные о партнёрах бот не знает. Но за 30 секунд и 5 ₽ он даёт куда больше, чем ChatGPT без плагинов по той же компании.