Статья написана от первого лица от нашего пользователя, который предпочёл скрыть своё имя. В данной статье мы заменили его имя на Алибек. Он занимается разработкой приложений. Стиль и стилистика сохранены. Мы лишь исправили некоторые грамматические ошибки и скрыли персональные данные. Выражаем огромную благодарность Алибеку за предоставленную статью ❤️
Короче, я не беру трубку с незнакомых номеров. Вообще. Да и плюс испытываю дискомфорт если честно во время живого разговора по телефону. Предпочитаю общение текстом в мессенджерах. И ненавижу голосовые в мессенджерах!
Проблема в том, что после неотвеченных звонков с незнакомых номеров я потом сижу и гадаю: а вдруг это был заказчик? И вдруг ему зашло моё портфолио и он реально готов заказать разработку? Перезвонить самому и общаться требует много сил (у меня), и мне было бы в кайф просто перевести диалог в любой мессенджер.
Да и даже настроившись, собравшись, поймав вайб для телефонного разговора, часто нарываюсь на очередные "выгодные предложения" и прочие скам звонки, где я просто бросаю трубку.
Поэтому я сделал себе личного телефонного секретаря. Её зовут Айсулу, она живёт в телеге, и ей можно просто скинуть номер. Дальше она сама перезванивает, выясняет кто это и чего хотели, и присылает мне итог: расшифровку и запись разговора голосовым. Не берут трубку три раза подряд, значит спам, она так и пишет. А если это рил что-то связанное с заказами на разработку, то просит написать их в Telegram или WhatsApp.
Звонит и разговаривает с номером голосовой ИИ агент AutoCall.kz, а сам бот у меня занимает несколько строк на питоне. Ниже рассказываю как собрал и кидаю весь код.
Как это работает
- Звонит незнакомый номер. Не беру, кидаю номер боту «Кто звонил?».
- Бот одним запросом в API AutoCall.kz ставит обзвон на этот номер с моим агентом.
- Айсулу звонит, представляется секретарём и выясняет: кто это и зачем. Ответы сама раскладывает по полям: кто, откуда, зачем, детали.
- Разговор закончился, AutoCall.kz стучится в бота вебхуком.
- Бот присылает мне итог, расшифровку и запись голосовым сообщением.
Никакой отдельной нейронки которая пишет промпты на лету или пересказывает разговор я делать не стал. Агента я один раз настроил в кабинете, итог разговора он записывает сам. Бот просто курьер между телегой и API.
Пример: Два звонка, два разных исхода
Первый. Номер звонил дважды. Айсулу набрала его три раза с перерывом в 15 минут, но никто не ответил. В телегу прилетел отчёт: не берёт трубку, попыток три, скорее всего спам. Ну и ладно.
Второй. Некий Сырым якобы по ошибке перевел мне деньги на Kaspi и переживал, что я их не верну. Айсулу записала сумму, дату, имя и объяснила: что всё передаст мне, а если перевод реально был, то я его верну обязательно. Через пару минут после звонка у меня в телеге было всё в текстовом виде.
Аудиозапись прилагается.
Кстати забавно: ИИ агент походу знает про схему с ошибочными переводами и поэтому сказала, что "Алибек сделает возврат прямо в приложении банка". Классика - просят вернуть деньги не тем же переводом назад, а на «другую карту» или «номер мамы». Просто делаю возврат отправителю в приложении банка и всё.
Шаг 1. Создаю секретаря
В кабинете AutoCall.kz есть раздел «ИИ-агенты». Промпт там руками писать не надо: описываешь своими словами нужное, а ИИ сам задаёт тебе вопросы и собирает сценарий. Выбрал женский голос и назвал Айсулу:
привет) хочу сделать себе секретаря. ты будешь Айсулу, мой ИИ-секретарь. меня зовут Алибек. когда звонит незнакомый номер, я трубку не беру т.к. не люблю говорить и кидаю номер боту в телегу, и ты перезваниваешь. Тебе нужно узнать кто звонил, откуда, зачем и вообще побольше деталей. всё это записываешь, я потом прочитаю это всё в телеге
Здравствуйте, Алибек! Отличная задача, с удовольствием буду вашим личным секретарём.
Я уже набросала первый вариант сценария и логику звонка.
Как я буду начинать разговор (в 3 шага):
1. Приветствие: «Алло, здравствуйте!» (пауза, жду ответа)
2. Вовлечение: «Не сильно отвлекаю? Удобно говорить?» (пауза)
3. Представление и суть: «Меня зовут Айсулу, я секретарь Алибека. Я персональный ассистент Алибека. Вы только что ему звонили, он сейчас не может ответить. Подскажите, пожалуйста, как вас зовут и по какому вы вопросу?»
Что я буду собирать в отчёт для вас:
| Номер | Статус | Длит. | Кто звонил | Компания / Откуда | Суть вопроса | Детали и пожелания |
|---|---|---|---|---|---|---|
| +7701XXXXXXX | Ответил | 65 | Ерлан | ТОО «СтройМаркет» | Счёт на оплату партии цемента | Просит перезвонить сегодня до 18:00 |
Устраивает ли такой состав отчёта и формулировки начала?
И подскажите пару нюансов:
1. Чем вы занимаетесь или какой у вас бизнес? (Чтобы я понимала контекст разговора, если начнут сыпать профессиональными терминами).
2. Что делать, если звонят с явным спамом или холодными продажами (кредиты, опросы, курсы)? Просто вежливо завершать звонок?
отчёт норм 👍 я фрилансер, делаю приложения и тг-ботов, иногда звонят клиенты. если это спам и продажи - вежливо попрощайся, но всё равно запиши кто бы это не был. и ещё важно: говори что Алибек приболел, у него пропал голос и сейчас не может говорить по телефону, поэтому перезваниваешь ты. только от моего имени ничего не обещай, а если просят перезвонить или не хотят говорить что хотели, то скажи лучше пусть напишут мне в Telegram или WhatsApp
Приняла все правки, Алибек!
Обновила сценарий:
Причина перезвона: Теперь я сразу объясняю, что ты приболел, у тебя пропал голос и по телефону говорить пока не можешь, поэтому перезваниваю я.
Спам и продажи: Если это реклама или продажи — вежливо прощаюсь, но обязательно фиксирую в отчёте, кто и зачем звонил.
Если скрытничают или просят перезвонить: Отвечаю, что от твоего имени ничего обещать не могу, и предлагаю написать тебе напрямую в Telegram или WhatsApp.
Кстати, минимально я уже готова — можете нажать «Протестировать» и поговорить со мной вживую. А мы пока продолжим меня дорабатывать.
Нужно ли отправлять человеку SMS с ссылкой на твой Telegram/WhatsApp, если он просит перезвонить или у него срочный вопрос? Или пока оставляем только разговор?
не, смс не надо. спасибо. главное постарайся всё разузнать пж
Договорились, SMS отправлять не будем!
Я буду внимательно и вежливо задавать уточняющие вопросы, чтобы собрать максимум подробностей по задаче, срокам и проекту, но ничего лишнего обещать не буду. Все данные сразу запишу в отчёт.
Мы всё настроили. Хочешь протестировать звонок прямо сейчас или добавим ещё какие-то детали?
Пару сообщений и секретарь готов. Вот что получилось:
Сценарий Айсулу целиком
## Кто ты Айсулу — персональный ИИ-секретарь Алибека (фрилансера по разработке приложений и Telegram-ботов). Звонишь людям, которые только что звонили Алибеку, чтобы узнать цель звонка и передать ему подробную информацию. Характер: вежливая, внимательная, собранная, деловая. ## Задача звонка Узнать, кто звонит, из какой компании, по какому вопросу (разработка приложений/ботов, сотрудничество, спам/продажи) и собрать максимум деталей. ## Ход разговора 1. Первая фраза РОВНО: «Алло?! Здравствуйте!» — и ВСЁ. Дальше молчишь и ждёшь отклика. 2. Отозвался — фраза для вовлечения: «Не сильно отвлекаю? Удобно говорить?» И снова ждёшь. 3. Ответил — представление и цель РОВНО: «Я персональный ассистент Алибека. Вы только что ему звонили, но он приболел — у него пропал голос, поэтому по телефону говорить пока не может и попросил меня перезвонить. Подскажите, пожалуйста, как вас зовут и по какому вы вопросу?» 4. Молчит после первых фраз — один раз переспроси «Алло, вы меня слышите?». Снова тишина — вежливо попрощайся и заверши звонок. 5. Выясни имя, компанию/сферу и суть обращения. Задавай уточняющие вопросы, чтобы узнать максимум подробностей о проекте или задаче. 6. Если это спам, реклама или сбыт/продажи — вежливо попрощайся и заверши звонок, но обязательно всё равно записать в отчёт, кто звонил и что предлагал. 7. Если собеседник отказался представляться, не хочет говорить цель звонка или просто просит, чтобы Алибек перезвонил — предложи написать Алибеку напрямую в Telegram или WhatsApp. 8. Узнай детали: срочность вопроса, подробности проекта или предложения. 9. Подтверди, что все данные записаны и будут переданы Алибеку. Вежливо попрощайся. ## Что собрать - caller_name: имя и фамилия звонящего. - company: компания, проект или «личное / физлицо». - purpose: суть обращения (заказ приложения, Telegram-бот, сотрудничество, спам/реклама). - details: подробности обращения. ## Границы - Ничего не обещать от имени Алибека (ни сроков, ни цен, ни согласия на проекты/встречи). - Не разглашать личную информацию Алибека, кроме того, что он приболел и у него пропал голос. - Если отказываются говорить или просят перезвонить — предлагать написать Алибеку в Telegram или WhatsApp.
Поля, которые прилетят боту: caller_name (кто), company (откуда),
purpose (зачем) и details (детали). Ключи запомните, бот выводит
именно их.
Шаг 2. API-ключ
В разделе «API» делаю отдельный ключ только под бота. Права по минимуму: обзвоны и звонки.
Шаг 3. Сам бот
Бота регаю у @BotFather, свой Telegram ID беру у @userinfobot: бот слушает только меня. Весь код в одном файле:
bot.py
import asyncio
import html
import os
import re
import shutil
from aiohttp import ClientSession, web
from aiogram import Bot, Dispatcher, F
from aiogram.types import BufferedInputFile, Message
API = "https://autocall.kz/api/v1"
TOKEN = os.environ["TELEGRAM_TOKEN"]
OWNER_ID = int(os.environ["OWNER_ID"])
AUTOCALL_KEY = os.environ["AUTOCALL_KEY"]
AGENT_ID = int(os.environ["AGENT_ID"])
PUBLIC_URL = os.environ["PUBLIC_URL"].rstrip("/")
HOOK_SECRET = os.environ["HOOK_SECRET"]
PORT = int(os.environ.get("PORT", 8080))
FIELDS = {
"caller_name": "Кто",
"company": "Откуда",
"purpose": "Зачем",
"details": "Детали",
}
MISSED = {
"no_answer": "не берёт трубку",
"busy": "всё время занято",
"error": "номер не существует",
"spam": "попросил больше не звонить",
}
bot = Bot(TOKEN)
dp = Dispatcher()
http: ClientSession
seen: set[int] = set()
def phone(text: str) -> str | None:
digits = re.sub(r"\D", "", text)
if len(digits) == 10:
digits = "7" + digits
if len(digits) != 11 or digits[0] not in "78":
return None
return "+7" + digits[1:]
async def to_voice(wav: bytes) -> bytes | None:
if not shutil.which("ffmpeg"):
return None
proc = await asyncio.create_subprocess_exec(
"ffmpeg", "-loglevel", "error", "-i", "pipe:0", "-c:a", "libopus", "-b:a", "24k", "-f", "ogg", "pipe:1",
stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
)
ogg, _ = await proc.communicate(wav)
return ogg if proc.returncode == 0 else None
@dp.message(F.from_user.id == OWNER_ID, F.text)
async def callback_request(message: Message):
number = phone(message.text)
if not number:
await message.answer("скинь номер, например +7 701 123 45 67")
return
payload = {
"name": f"Кто звонил {number}",
"type": "agent",
"agent_id": AGENT_ID,
"list_id": [{"number": number}],
"time_from": "11:00:00",
"time_to": "21:00:00",
"max_tries": 3,
"tries_interval": 15,
"record": True,
"callback_url": f"{PUBLIC_URL}/hook/{HOOK_SECRET}",
}
async with http.post(f"{API}/autocalls", json=payload) as r:
if r.status != 201:
error = (await r.json(content_type=None) or {}).get("message", r.status)
await message.answer(f"не получилось: {error}")
return
await message.answer(f"Айсулу перезвонит на {number} и всё узнает. если не возьмут, попробует ещё два раза")
async def fresh(call: dict) -> dict:
async with http.get(f"{API}/autocalls/{call['autocall_id']}") as r:
if r.status != 200:
return call
calls = (await r.json())["calls"]["data"]
return next((c for c in calls if c["id"] == call["id"]), call)
async def report(call: dict):
number = call["number"]
if call["status"] in MISSED:
text = f"{number} {MISSED[call['status']]}, попыток: {call['tries']}"
if call["status"] in ("no_answer", "busy"):
text += "\nскорее всего спам"
await bot.send_message(OWNER_ID, text)
return
# поля и расшифровка дописываются ещё секунд десять после звонка
await asyncio.sleep(15)
call = await fresh(call)
data = call.get("data") or {}
lines = [f"<b>{label}:</b> {html.escape(str(data[key]))}" for key, label in FIELDS.items() if data.get(key)]
await bot.send_message(OWNER_ID, f"поговорила с {number}\n\n" + "\n".join(lines), parse_mode="HTML")
async with http.get(f"{API}/calls/{call['id']}/transcript") as r:
text = (await r.json()).get("text") if r.status == 200 else None
if text:
quote = html.escape(text[:3500])
await bot.send_message(OWNER_ID, f"<blockquote expandable>{quote}</blockquote>", parse_mode="HTML")
async with http.get(f"{API}/calls/recordings/{call['id']}") as r:
if r.status != 200:
return
wav = await r.read()
voice = await to_voice(wav)
if voice:
await bot.send_voice(OWNER_ID, BufferedInputFile(voice, "call.ogg"))
return
await bot.send_document(OWNER_ID, BufferedInputFile(wav, f"{number}.wav"))
async def hook(request: web.Request) -> web.Response:
if request.match_info["secret"] != HOOK_SECRET:
raise web.HTTPNotFound()
call = (await request.json()).get("call")
if not call or call["status"] == "in_queue" or call["id"] in seen:
return web.Response()
seen.add(call["id"])
asyncio.create_task(report(call))
return web.Response()
async def main():
global http
http = ClientSession(headers={"Authorization": f"Bearer {AUTOCALL_KEY}", "Accept": "application/json"})
app = web.Application()
app.router.add_post("/hook/{secret}", hook)
runner = web.AppRunner(app)
await runner.setup()
await web.TCPSite(runner, port=PORT).start()
try:
await dp.start_polling(bot)
finally:
await http.close()
if __name__ == "__main__":
asyncio.run(main())
По коду пара моментов.
- Один запрос на звонок.
POST /autocallsсtype: agentсоздаёт обзвон из одного номера моим агентом.max_tries: 3иtries_interval: 15дают три попытки через 15 минут,record: trueпишет разговор, а вcallback_urlприлетит результат. - Промежуточные события скипаю. Между попытками звонок приходит со статусом
in_queue. Окончательный статус этоsuccess, если поговорили, илиno_answerиbusy, если нет. Реагирую только на окончательный. - Почему жду 15 секунд. Я заметил, что поля отчёта и расшифровка могут
дописываться ещё
несколько секунд после разговора у AutoCall.kz. Видимо работает суммарайзер и отдельная LLM.
Поэтому бот ждёт и перечитывает звонок через
GET /autocalls/{id}, чтобы прислать всё целиком, а не половину. - Вебхуку отвечаю сразу. Вебхук сразу
отдаёт
200. Дубли одного и того же звонка бот отбрасывает. - Голосовое вместо файла. Запись приходит в WAV. Если стоит ffmpeg, бот перегоняет её в OGG/Opus, и в телеге это обычное голосовое с волной и с кнопкой "Расшифровка". Если нет ffmpeg, то пришлёт файлом.
Шаг 4. Запуск
терминал
pip install aiogram aiohttp export TELEGRAM_TOKEN=123456:ABC export OWNER_ID=123456789 export AUTOCALL_KEY=ваш_ключ export AGENT_ID=id_агента export PUBLIC_URL=https://адрес-бота export HOOK_SECRET=любая-длинная-строка python bot.py
Боту нужен публичный https-адрес, на него AutoCall.kz шлёт результат звонка. Сервер для этого
не обязателен: бота можно гонять прямо с ноута и выставить наружу через Cloudflare Tunnel.
Команда cloudflared tunnel --url http://localhost:8080 выдаёт адрес, его и
пишите в PUBLIC_URL.
Код целиком, с README и примером настроек, лежит на GitHub. Форкайте и пилите под себя.
Сколько это стоит
Плачу только за разговор и посекундно: связь плюс работа ИИ.
| Тариф | Минута разговора с секретарём | Запись | Недозвон 1 раз | Недозвон 3 раза |
|---|---|---|---|---|
| BRONZE | 148.8 ₸ | 5 ₸ | 0 ₸ | 0.7 ₸ |
| SILVER | 102.6 ₸ | 3 ₸ | 0 ₸ | 0.5 ₸ |
| GOLD | 79.8 ₸ | 0.5 ₸ | 0 ₸ | 0.3 ₸ |
Обычный такой разговор укладывается в минуту. Актуальные цены и условия на странице тарифов.
Я закинул 1200 ₸ на баланс и за неделю потратилось около 190 ₸.
Что ещё можно допилить
- Создать другого агента для других целей (передать какую-то информацию или напоминать об оплате) и выбирать цели в самом боте с помощью кнопок.
- Жду когда появится функционал по входящим звонкам в AutoCall.kz и тогда я смогу переадресовывать звонки со своего телефона в AutoCall.kz и получать сразу итог в Telegram боте.
Надеюсь эта статья будет полезна всем, кто не любит говорить по телефону и не любит голосовые сообщения ;)
Команда AutoCall.kz готова предоставить бонусы на баланс в личном кабинете если вы предложите свою статью о вашем кейсе работы с нашим сервисом.