Привет, Хабр! Продуктовая аналитика хорошо работает с событиями и метриками, но ломается на живых коммуникациях. Звонки зачастую остаются неохваченными анализом, хотя именно там слышно как клиент злится или сомневается, но эти сигналы доходят до менеджера продукта хаотично, а не в системном виде.
В этом гайде разберём, как превратить записи звонков в продуктовые инсайты без ручного прослушивания — с помощью Python, звонков от МТС Exolve, интерфейса на Streamlit и нейронкой MWS GPT от МТС.
Архитектура решения
Инструмент построен как простой и прозрачный пайплайн обработки данных. Он не требует отдельного бэкенда, очередей или фронтенда — вся логика укладывается в несколько Python-модулей и управляется из интерфейса Streamlit.
С точки зрения потока данных система состоит из последовательных шагов, каждый из которых решает одну конкретную задачу и не знает о деталях остальных.
Сбор данных. Забираем историю звонков и расшифровки по API с разделением ролей «клиент» и «оператор»
Анализ. Передаём текст диалога в нейронку и получаем структурированный JSON: краткую суть обращения, настроение клиента, риск ухода и продуктовые запросы
Визуализация. Показываем результат в дашборде и сохраняем выбранные инсайты в таблицу
Компоненты
МТС Exolve API — источник данных со звонками и транскрипциями
MWS GPT — анализ диалога и генерация структурированных данных с помощью модели mws-gpt-alpha
Streamlit — интерфейс, фильтры, метрики и кэширование. Позволяет быстро собрать рабочий интерфейс прямо на Python без отдельного фронтенда и API-слоя
Google Sheets — простой способ сохранить инсайты из интерфейса и передать их команде
Шаг 1. Забираем диалоги из МТС Exolve
Анализ запускается вручную из интерфейса. Это сделано это для контроля нагрузки и экономии затрат на нейронку.
По кнопке запрашиваем список последних звонков через метод GetList и получаем 10 последних звонков. Лимит можно легко увеличить, например до 100.
Полученный список отображается в интерфейсе. Вы выбираете конкретный звонок и вручную запускаете анализ — только в этот момент начинается дальнейший пайплайн.
Для выбранного звонка по его call_uid запрашивается транскрипция. Она приходит в виде набора фрагментов, привязанных к аудиоканалам. Эти каналы соответствуют ролям участников разговора — клиенту и оператору — и передаются в поле channel_tag.
Эту структуру важно сохранить. Если склеить текст без указания ролей, модель теряет контекст разговора и хуже интерпретирует эмоции, претензии и запросы клиента. Поэтому на этом этапе маппятся каналы на роли Client и Operator и собирается последовательный диалог.
На выходе получается структурирова��ный текст разговора, готовый к анализу нейронкой.
# exolve_provider.py
import requests
from config import Config
class ExolveProvider:
def __init__(self):
self.headers = {"Authorization": f"Bearer {Config.EXOLVE_API_KEY}"}
def get_last_calls(self, limit=10):
"""Запрашивает список последних звонков."""
url = "https://api.exolve.ru/statistics/call-history/v2/GetList"
try:
resp = requests.post(url, headers=self.headers, json={"limit": limit}, timeout=10)
return resp.json().get('calls', [])
except Exception as e:
return []
def get_transcript(self, call_uid: str) -> str | None:
"""Скачивает и форматирует расшифровку."""
url = f"{Config.EXOLVE_BASE_URL}/GetTranscribation"
try:
resp = requests.post(url, headers=self.headers, json={"uid": int(call_uid)}, timeout=10)
if resp.status_code != 200: return None
data = resp.json()
chunks = data.get('transcribation', [{}])[0].get('chunks', [])
# Склеиваем диалог: [Role]: Text
lines = []
for chunk in chunks:
role = "Operator" if chunk.get('channel_tag') == 2 else "Client"
text = chunk.get('text', '')
lines.append(f"[{role}]: {text}")
return "\n".join(lines)
except Exception:
return None
Шаг 2. Анализируем как продакт менеджер
На этом этапе получаем продуктовые инсайты, с которыми можно работать дальше. Для этого используем LLM в роли Senior Product Manager. Передаём модели полный диалог вместе с промптом, который явно задаёт роль и ожидаемый формат ответа.
Ключевой момент — строгая структура результата. Просим модель вернуть JSON с заранее определёнными полями:
краткая суть обращения
настроение клиента
риск ухода
продуктовые запросы
цитаты из речи клиента
Такой формат позволяет показывать в интерфейсе, фильтровать, сохранять и передавать в бэклог без дополнительной обработки.
# prompts.py
def get_product_analysis_prompt(transcript: str) -> str:
return f"""
Ты — Senior Product Manager. Твоя цель — найти точки роста продукта на основе звонка в техподдержку.
**Текст звонка:**
{transcript}
**Задача:**
Проанализируй диалог и верни JSON со следующей структурой:
{{
"summary": "Краткая суть проблемы (1 предложение)",
"customer_mood": "Настроение клиента (Angry/Neutral/Happy/Frustrated)",
"key_fear": "Глубинный страх клиента (потеря денег/времени/контроля)",
"feature_request": "Какую функцию явно или неявно хочет клиент? (или null)",
"churn_risk": "Риск ухода (Low/Medium/High)",
"original_phrases": ["Цитата 1", "Цитата 2"],
"tags": ["Tag1", "Tag2"]
}}
**Важно:**
1. Цитаты должны быть точными выдержками из текста клиента.
2. Отвечай только валидным JSON.
"""
Чтобы ответы были стабильными и воспроизводимыми:
Снижаем степень случайности через температуру до 0.1
Ограничиваем количество на ответ до 1500 для контроля расходов на нейронку
Ограничиваем выборку слов наиболее вероятными, задав top_p 0.1
Чтобы UI не падал с ошибками, используем try/except и возвращаем fallback-результат при неуспехе.
На выходе получаем структурированный результат анализа диалога, готовый к визуализации и сохранению.
# ai_engine.py
def analyze_transcript(self, transcript: str, prompt_func) -> dict:
# Заглушка на случай сбоя
fallback_result = {
"summary": "Ошибка анализа (LLM Error)",
"customer_mood": "Unknown",
"churn_risk": "Unknown",
"tags": ["Error"]
}
payload = {
"model": "mws-gpt-alpha",
"messages": [...],
"temperature": 0.1,
"top_p": 0.1, # Nucleus Sampling для максимальной строгости
"max_tokens": 1500
}
try:
resp = requests.post(Config.MTS_AI_URL, headers=self.headers, json=payload, timeout=30)
resp.raise_for_status()
content = resp.json()['choices'][0]['message']['content']
# Очистка от markdown-оберток (AI любит добавлять ```json)
clean_content = content.replace("```json", "").replace("```", "").strip()
return json.loads(clean_content)
except Exception as e:
logger.error(f"AI Error: {e}")
return fallback_result
Шаг 3. Собираем дашборд
Streamlit позволяет делать интерфейс прямо на Python без отдельного фронтенда. В нашем случае он показывает список звонков, запускает обработку и отображает результат.
Чтобы интерфейс работал быстро и не дёргал API лишний раз, кэшируем через @st.cache_data. Тяжёлые операции — например, загрузка списка звонков — выполняются один раз и переиспользуются при следующих действиях пользователя.
# app.py
import streamlit as st
import pandas as pd
from exolve_provider import ExolveProvider
from ai_engine import AIAnalyst
from prompts import get_product_analysis_prompt
# Инициализация сервисов (кэшируем, чтобы не пересоздавать объекты)
@st.cache_resource
def get_services():
return ExolveProvider(), AIAnalyst()
exolve, ai = get_services()
@st.cache_data(ttl=300) # Кэш данных живет 5 минут
def load_calls(limit):
return exolve.get_last_calls(limit)
# Основной интерфейс
st.title("📞 Анализ звонков: Поиск инсайтов")
# Сайдбар с настройками
with st.sidebar:
limit = st.slider("Загружать звонков", 5, 50, 10)
if st.button("🔄 Сброс кэша"):
st.cache_data.clear()
# Получаем список звонков
calls = load_calls(limit)
df = pd.DataFrame(calls)
# Выводим таблицу
st.dataframe(df[['date', 'calling_number', 'duration', 'status']], use_container_width=True)
# Выбираем звонок для анализа
selected_uid = st.selectbox("Выберите звонок:", df['uid'].unique())
if st.button("🚀 Запустить анализ"):
with st.spinner("Магия AI..."):
text = exolve.get_transcript(selected_uid)
analysis = ai.analyze_transcript(text, get_product_analysis_prompt)
# Визуализация метрик (KPI)
c1, c2, c3 = st.columns(3)
c1.metric("Настроение", analysis.get("customer_mood"))
# Подсветка риска
risk = analysis.get("churn_risk")
c2.metric("Риск ухода", risk, delta_color="inverse" if risk == "High" else "normal")
# Карточки с инсайтами (используем HTML/CSS внутри Markdown для красоты)
st.markdown(f"""
<div class="insight-box">
<b>Суть:</b> {analysis.get("summary")}<br>
<b>Глубинный страх:</b> {analysis.get("key_fear")}
</div>
""", unsafe_allow_html=True)
Полный код app.py занимает менее 100 строк, но дает полноценный UI с табами, выпадающими списками и красивыми метриками.

Шаг 4. Сохранение результатов
Если найденный инсайт кажется полезным, его можно сохранить одной кнопкой «Сохранить в Google Sheets». В этом случае результат анализа записывается в Google Sheets — как отдельная строка со структурой JSON от нейросети.
Таблица используется как простой бэклог: сюда попадают продуктовые проблемы, риски ухода и запросы на фичи, которые дальше можно разобрать командой.
expand_less
if st.button("💾 Сохранить в Google Sheets"):
if sheets.save_analysis(selected_uid, analysis):
st.success("Инсайт сохранен в бэклог!")
else:
st.error("Ошибка сохранения")Пример использования
Продакт-менеджер в нашем интерфейсе видит звонок длительностью 15 минут, что аномально долго для нашей поддержки.
Кликает «Анализировать»
Система показывает:
Настроение Frustrated (Раздражен)
Риск ухода High
Суть: «Клиент не смог выгрузить отчет в PDF, кнопка выдает 404»
Инсайт: «Клиент боится, что не успеет сдать отчетность налоговой из-за нашего бага»
Продакт нажимает «Сохранить в Google Sheets». Проблема улетает в общий реестр с тегом #critical.
Без этого инструмента звонок мог потеряться, а клиент мог бы молча уйти к конкурентам.
Заключение
Мы разобрали, как превратить записи звонков службы поддержки и отдела продаж в источник продуктовых инсайтов. Связка Python, МТС Exolve, Streamlit и MWS GPT даёт управляемый процесс: анализ запускается по требованию, нагрузка контролируется, а результат готов к дальнейшей работе.
Кстати, если вы не хотите писать код, у MWS GPT есть и No-code платформа, где похожие пайплайны можно собирать визуально. Но для инженера гибкость кода всегда дает преимущество.
Идеи для развития
Вместо ручного запуска пайплайн повесить на вебхуки об окончании звонка в МТС Exolve и анализировать диалоги автоматически
Собирать проблематику по всем звонкам и раз в сутки агрегировать результаты: формировать краткую выжимку по болям, рискам и запросам на фичи и отправлять её менеджерам продуктов, например, утром перед началом рабочего дня.
Отдельное направление — работа с промптами. При повышенной креативности модели можно просить нейронку предлагать 3–5 идей потенциальных фич на основе проблем и страхов клиентов, передавая ей текущий продуктовый контекст
Репозиторий с кодом.
