Извлечение ответов из писем с Talon

Чистый новый текст из цепочки переписки библиотекой Talon

Зачем Talon?

В переписке накапливаются цитаты, которые заслоняют новый текст. В автоматической обработке почты нужен только новый фрагмент, а не вся история.

Talon выделяет чистый ответ за счёт сопоставления шаблонов и разбора структуры HTML/текста.

Сценарии

  • AI-агенты почты: новые реплики пользователя без полного треда
  • Автоматизация: разбор ответов на предмет действий
  • Анализ тредов: вклад каждого участника отдельно
  • Inbox: обрабатывать только новую информацию в ответах

Почему Talon

HTML из клиентов

Gmail, Outlook, Apple Mail, Thunderbird и др.

Высокая точность

93.8% на 64 реальных тестах

Несколько языков

Английский, японский, шведский, польский, голландский, немецкий

Скорость

~1.92 ms в среднем, ~488 писем/с


Как устроен Talon

Два подхода в зависимости от формата письма.

Текст (конвейер из 6 этапов)

  1. Классификация строк: метки 't' текст, 'm' цитата, 's' разделитель, 'e' пустая
  2. Шаблоны: regex по последовательностям меток
  3. Извлечение: удаление цитат, остаётся новый текст

Распознаётся:

  • префиксы >
  • заголовки ответов («On [date] [name] wrote:»)
  • пересылки («-----Original Message-----»)

HTML (8 этапов)

  1. Структурное удаление: известные блоки цитат (Gmail, blockquote, Outlook)
  2. Запасной путь: нестандартный HTML → строки → те же текстовые правила → вырезание в HTML

Режим

Удаление цитат (основной)

  • убирает процитированные ответы из треда
  • без отдельной инициализации
  • правила и шаблоны

Быстрый старт

1

Установка Talon

Через pip; для Python 3.11+ нужен обходной путь (см. следующий шаг):

$pip install talon
2

Обход для Python 3.11+

Замена зависимости cchardet:

1# Import workaround BEFORE importing talon
2import sys
3import chardet
4sys.modules['cchardet'] = chardet
5
6# Now safe to import talon
7import talon
8from talon import quotations

Без этого обхода на Python 3.11+ установка/импорт могут ломаться.

3

Извлечь ответ

Текст и HTML:

1from talon import quotations
2
3email = """Great work on the project!
4
5On Mon, Apr 11, 2011 at 6:54 PM, Bob wrote:
6> Can you review the document?
7> Need feedback by Friday.
8"""
9
10clean_reply = quotations.extract_from_plain(email)
11# Result: "Great work on the project!"

Производительность и точность

Тесты на 64 реальных письмах из разных клиентов и языков.

Сводка

МетрикаЗначение
Всего тестов64 письма
Успех60 (93.8%)
Провал4 (6.2%)
Среднее время1.92 ms
Пропускная способность488.6 писем/с
Мин / макс0.13 ms – 21.55 ms

Покрытие

  • 22 HTML: Gmail, Outlook, Apple Mail, Thunderbird, Mail.ru, Hotmail
  • 42 plain text: разные форматы ответов
  • 6+ языков: английский, японский, шведский, польский, голландский, немецкий
  • Мобильные клиенты: iPhone, Android, подписи «Sent from»

Время по сложности

Тип письмаСреднее времяСложность
Простой текстовый ответ0.2–0.5 msНизкая
HTML Gmail/Outlook2–4 msСредняя
Сложные треды4–22 msВысокая

Скорость и точность

БиблиотекаСреднее времяТочностьКогда
Talon1.92 ms93.8%Продакшен с HTML
quotequail0.96 ms~85%Умеренные требования
Свой regex0.1 ms~70%Только текст, критична скорость

Вывод: ~2 ms в среднем для бэкенда обычно пренебрежимо мало; даже ~21 ms быстрее типичного сетевого запроса.


Ограничения

4 из 64 тестов не прошли. Ниже разбор.

Кейс 1: сложный тред со смешанным содержимым

Вход:

Thank you, Sonya Johnson.
I have sent an invite for 10:30am Monday PDT (today). I
hope you can join.
Regards,
Christopher Edwards
On Mon, Jun 3, 2024 at 12:53 AM Cody Hart <omerritt@example.com> wrote:
> Hi Christopher Edwards,
>
> 10.30 AM pacific is good for me.
>
> Thanks & Regards,
>
> Cody Hart

Ожидалось: только первые 5 строк (до Christopher Edwards)

Фактически: весь текст, включая цитату с «On Mon, Jun 3…» и строки >

Время: 2.55 ms

Причина: подпись перед цитатой сбивает эвристики


Кейс 2: встроенные ответы внутри цитаты

Вход:

On Tue, Apr 29, 2014 at 4:22 PM, Example Dev <sugar@example.com> wrote:
> okay. Well, here's some stuff I can write.
>
> And if I write a 2 second line you and maybe reply under this?
>
> Or if you didn't really feel like it, you could reply under this line.
I will reply under this one
>
> okay?
>
and under this.
>
> -- Tim

Ожидалось: только I will reply under this one и and under this.

Фактически: весь фрагмент, включая заголовок «On Tue, Apr 29…» и цитаты

Время: 0.48 ms

Причина: встроенные ответы между строками > не распознаны как новый текст


Кейс 3: пересылка Gmail (HTML)

Вход:

1<html><head></head><body><div dir="ltr">test<div><br /></div><div>blah</div>
2<div><br /><div class="gmail_quote">---------- Forwarded message ----------<br />
3From: <b class="gmail_sendername">Foo Bar</b>
4<span dir="ltr">&lt;<a href="mailto:foo@bar.example">foo@bar.example</a>&gt;</span><br />
5Date: Thu, Mar 24, 2016 at 5:17 PM<br />
6Subject: The Subject<br />
7To: John Doe &lt;<a href="mailto:john@doe.example">john@doe.example</a>&gt;<br />
8<br /><br /><div dir="ltr">Some text<div><br /></div><div><br /></div></div>
9</div><br /></div></div></body></html>

Ожидалось: только testblah (до маркера пересылки)

Фактически: остаётся «---------- Forwarded message ----------» и тело пересылки

Время: 3.41 ms

Причина: HTML-заголовки пересылки Gmail не вырезаются как цитата


Кейс 4: пересылка Thunderbird (HTML)

Вход:

1<html><body bgcolor="#FFFFFF" text="#000000">
2<p><br /></p>
3<div class="moz-forward-container"><br /><br />
4-------- Forwarded Message --------
5<table class="moz-email-headers-table">
6 <tbody>
7 <tr><th>Subject:</th><td>Re: Example subject</td></tr>
8 <tr><th>Date:</th><td>Tue, 3 May 2016 14:54:27 +0200 (CEST)</td></tr>
9 <tr><th>From:</th><td>John Doe &lt;johndoe@example.com&gt;</td></tr>
10 </tbody>
11</table>
12<br /><br />
13<div>Dear John,</div>
14<div><br /></div>
15<div>This is a test.</div>
16</div></body></html>

Ожидалось: пусто (только пересылка, без нового текста)

Фактически: остаётся «-------- Forwarded Message --------» и пересылаемое содержимое

Время: 4.34 ms

Причина: класс Thunderbird moz-forward-container не распознан


Итог: 3 из 4 провалов — пересылки. Обычные ответы — точность 98%+.

Пример 1: простой ответ Gmail

Вход:

Awesome! I haven't had another problem with it.
On Aug 22, 2011, at 7:37 PM, defunkt<reply@reply.github.com> wrote:
> Loader seems to be working well.

Результат Talon: Awesome! I haven't had another problem with it.

Время: 0.2 ms

Почему сработало: шаблон «On [date] [name] wrote:» и маркеры >


Пример 2: Outlook с линией-разделителем

Вход:

Outlook with a reply directly above line
________________________________________
From: CRM Comments [crm-comment@example.com]
Sent: Friday, 23 March 2012 5:08 p.m.
To: John S. Greene
Subject: [contact:106] John Greene
A new comment has been added to the Contact named 'John Greene':
I am replying to a comment.

Результат: Outlook with a reply directly above line

Время: 0.51 ms

Почему: линия подчёркиваний и заголовки From/Sent как разделитель


Пример 3: HTML Outlook

Вход:

1<html>
2 <body>
3 <div>Reply</div>
4 <span id="OLK_SRC_BODY_SECTION">
5 <div>
6 <span>From: </span>Bob &lt;<a href="mailto:bob@example.com">bob@example.com</a>&gt;<br />
7 <span>Date: </span>Tue, 01 Nov 2011 18:54:39 -0700<br />
8 <span>To: </span>Rob &lt;<a href="mailto:rob@example.com">rob@example.com</a>&gt;<br />
9 <span>Subject: </span>Test<br />
10 </div>
11 <div>Hi</div>
12 </span>
13 </body>
14</html>

Результат: Reply

Время: 4.02 ms

Почему: удалён блок Outlook OLK_SRC_BODY_SECTION

Talon тяжелее, но покрывает HTML и сложные кейсы.

  • Talon: ~1.92 ms (с HTML)
  • email-reply-parser: ~0.03 ms (в основном текст)

Для сервера задержка Talon обычно мала по сравнению с сетью.

Пересылки, особенно HTML, — слабое место:

  • Текстовые пересылки часто ок
  • HTML может оставлять заголовки пересылки
  • Обходной путь: извлечение из plain text или постобработка маркеров forward

Обработка ошибок

Оборачивайте разбор в try/except:

1from talon import quotations
2
3def safe_extract(email_body, is_html=False):
4 try:
5 if is_html:
6 return quotations.extract_from_html(email_body)
7 else:
8 return quotations.extract_from_plain(email_body)
9 except Exception as e:
10 # Fallback: вернуть исходное тело
11 print(f"Talon extraction failed: {e}")
12 return email_body

Тестирование

Проверяйте на своих реальных форматах:

1# Create a test suite with your actual email patterns (Gmail, Outlook, Apple Mail)
2test_emails = [
3 "path/to/gmail_reply.html",
4 "path/to/outlook_reply.txt",
5 "path/to/forward.html"
6]
7
8for email_file in test_emails:
9 with open(email_file) as f:
10 content = f.read()
11 result = quotations.extract_from(content)
12 print(f"{email_file}: {len(result)} chars extracted")

Прогоняйте реальные письма из тех клиентов, что у ваших пользователей: уникальные шаблоны бывают вне обучающей выборки Talon.


Вариант на JavaScript

Для TypeScript/JavaScript — TalonJS (порт Talon).

Сравнение

РешениеТочностьСкоростьКогда
Python Talon93.8%1.92 msМаксимальная точность
TalonJS90.6%1.88 msNode/TS без Python

TalonJS чуть быстрее в бенчмарке и удобен в JS-стеке.

Быстрый старт

1

Установка TalonJS

$npm install talonjs
2

Извлечение ответов

1import * as talon from 'talonjs';
2
3const email = `Great work on the project!
4
5On Mon, Apr 11, 2011 at 6:54 PM, Bob wrote:
6> Can you review the document?
7> Need feedback by Friday.
8`;
9
10const result = talon.quotations.extractFromPlain(email);
11const cleanReply = result.body.trim();
12// Output: "Great work on the project!"

TalonJS или Python Talon:

  • TalonJS — если стек TS/JS и достаточно ~90.6% точности
  • Python Talon — если нужна максимальная точность или уже Python
  • Разница ~3 п.п. для многих сценариев приемлема