速水拓真 営業×AI 実践シリーズ
Qiita

商談の文字起こしを9項目の議事録に組み替える — Pythonで数字と条件をペアで抽出する

なぜ商談メモは当日中に処理しないといけないのか

30分の商談で交わされる会話は、日本語の話速(1分あたり300〜400字)で単純計算すると9,000〜12,000字になります。A4で5〜7枚ぶんの情報量です。これを話を聞きながら手で拾うのは不可能で、現場では「重要そうなところ」だけをメモすることになります。

この「重要そう」の判定を商談中にやるのが間違いです。話の流れに追われている人間に、後に効く情報の見極めはできません。しかも手書きメモは「自分が言ったこと」中心に残り、相手の発言が流れて消えます。次の商談とフォローメールで本当に要るのは相手の発言——「予算は来期から」「今使っているのはB社」「最終判断は社長」——です。残っていないから、次回の商談で聞き直しが発生し、相手は「前もお話ししましたよね」と冷めます。

解決の方向は1つです。商談中は録音と時刻のメモだけにして、議事録の生成は商談後に自動で回す。本記事では、生の文字起こしから9項目の議事録を組み立てる処理をPythonで実装します。文字起こしはCLOVA NoteやNottaといったサービスに丸投げしてよく、AI部分(誤変換の修正・9項目への構造化)だけをコードで固めるイメージです。

全体設計:3つの部品

処理の流れは次の3部品に分かれます。

  1. 録音運用 — 商談を録音し、ファイル名は YYYYMMDD_相手会社名_相手担当者名 の規則で付ける
  2. 文字起こし — 音声ファイルを文字起こしサービスに回す(生のままでよい。体感9割方合っていれば足りる)
  3. 構造化 — 生テキストを9項目の議事録に組み替える。ここをPythonで実装する

商談中に人間が書くのは「時刻+キーワード5文字以内」の見出しメモだけです。たとえば 14:05 現状14:20 予算14:41 決裁 のように区切りが来たときに3〜4回書くだけで足ります。時刻を付ける理由は、文字起こしがタイムスタンプ付きで出てくるため、この1行が商談中の索引になるからです。9,000字の文字起こしを最初から読み直す必要はありません。

実装1: 生の文字起こしから発言単位に分割する

文字起こしサービスの多くは 話者1: 話者2: 形式の行ラベルで出力します。まずこれをパースして、発言単位の辞書に変換します。

import re
from dataclasses import dataclass, field


@dataclass
class Utterance:
    speaker: str          # "話者1" / "話者2" など
    time: str             # タイムスタンプ(無ければ空文字)
    text: str             # 発言本文


def parse_transcript(raw: str) -> list[Utterance]:
    """生の文字起こしを発言単位に分割する。

    想定する入力形式(CLOVA Note / Notta 等の一般的な出力):
        [00:14:05] 話者1: では現状の運用についてお聞かせください。
        話者2: だいたい月30万くらいで回しています。
    """
    lines = [ln.strip() for ln in raw.splitlines() if ln.strip()]
    utterances = []
    ts_pattern = re.compile(r"^\[?(\d{1,2}:\d{2}(?::\d{2})?)\]?\s*")
    speaker_pattern = re.compile(r"^(話者\d+|私|相手|[A-Z]\s*様?)\s*[::]\s*")

    current = None
    for ln in lines:
        m_ts = ts_pattern.match(ln)
        ts = m_ts.group(1) if m_ts else ""
        body = ln[m_ts.end():] if m_ts else ln

        m_sp = speaker_pattern.match(body)
        if m_sp:
            # 新しい発言の開始
            if current:
                utterances.append(current)
            current = Utterance(
                speaker=m_sp.group(1),
                time=ts,
                text=body[m_sp.end():].strip(),
            )
        else:
            # 前の発言の続き(文字起こしの折り返し行)
            if current:
                current.text += body
    if current:
        utterances.append(current)
    return utterances

ポイントは2つです。1つ目は折り返し行の扱い。文字起こしサービスは1発言を複数行に折り返すことがあるので、「話者ラベルで始まらない行は前行の続き」として連結します。2つ目はタイムスタンプの分離。[00:14:05] のような先頭時刻は見出しメモ(14:05 現状)との照合に使うので、発言本文から切り離して保持します。

実装2: 数字と条件をセットで抽出する

議事録で最も価値が高いのは「相手が口にした数字と条件」です。「月30万」は拾えても「保守が込みか別枠か」を聞き逃すと、次回の見積もりで致命的なズレが生じます。数字は条件とセットで初めて意味を持ちます。

NUM_PATTERN = re.compile(
    r"(\d[\d,,..]*(?:万|千|億)(?:円)?|\d[\d,,..]*\s*(?:円|人|社|件|年|月|日|時間|%|%|倍))"
)
# 「来月」「翌月」のような日時条件も拾う
DATE_CONTEXT = {"来期", "今期", "来月", "翌月", "翌年", "月次", "年間", "月額"}
BUDGET_CONTEXT = {"込み", "別枠", "別", "上限", "以内", "以上", "未満"}


def extract_facts(utterances: list[Utterance]) -> list[str]:
    """数字を含む発言から、事実として残すべき行を組み立てる。

    否定語(「〜じゃなくて」「〜ではなく」)が直後に続く条件語は、
    そのまま拾うと意味が反転するため、先に取り除いてから判定する。
    """
    facts = []
    keywords = BUDGET_CONTEXT | DATE_CONTEXT
    for ut in utterances:
        if not NUM_PATTERN.search(ut.text):
            continue
        # 「込みじゃなくて別枠」→ 否定されている語を除去して「別枠」だけ残す
        text = ut.text
        for w in keywords:
            text = re.sub(re.escape(w) + r"(じゃなくて|ではなく|じゃない|でない)",
                          "", text)
        conditions = {w for w in keywords if w in text}
        # 「別枠」があれば部分一致の「別」は重複なので除外
        if "別枠" in conditions:
            conditions.discard("別")
        cond_text = ("(条件: " + "・".join(sorted(conditions)) + ")"
                     if conditions else "")
        quote = ut.text if len(ut.text) <= 60 else ut.text[:57] + "…"
        ts = f"({ut.time})" if ut.time else ""
        facts.append(f"・{quote}{ts}{cond_text}")
    return facts

この抽出器を、冒頭で述べた典型パターンに通すとこうなります。

RAW = """[00:14:20] 話者2: だいたい月30万くらいを、それに保守が別で年間60万くらいかな。
あ、保守は込みじゃなくて別枠ですね。
[00:14:33] 話者1: 承知しました。来月の第2週にデモのご予定いかがですか。
"""

for f in extract_facts(parse_transcript(RAW)):
    print(f)
# 出力:
# ・だいたい月30万くらいを、それに保守が別で年間60万くらいかな。あ、保守は込み
# じゃなくて別枠ですね。(00:14:20)(条件: 別枠・年間)

「月30万」という数字と「込みではなく別枠」という条件が、同じ行に1つの事実として残りました。手書きメモが拾いがちなのは数字だけで、次回の見積もりで効くのは条件のほうです。この2つが常にセットで残るのが、自動化の最大の利点です。

実装3: LLMに構造化させるプロンプトと9項目への組立

数字抽出はルールで固められますが、「決まったこと」「懸念」の分類は文脈判断が要るためLLMに回します。コード側の役割は、パース済みの発言リストを仕様どおりのプロンプトに流し込み、9項目の形式で受け取ることです。

MINUTES_PROMPT = """あなたは営業の議事録作成担当です。以下の商談記録から議事録を作ってください。

■ 商談情報
・商談日: {date}
・相手: {company} / {dept} / {title} / {name}
・商談の種類: {meeting_type}
・誤変換しやすい固有名詞: {nouns}

■ 商談中メモ(時刻+キーワード)
{memo}

■ 発言リスト(話者・時刻・本文)
{utterances}

■ 出力形式(この見出し順で)
1. 商談概要(3行以内)
2. 相手の発言で残すべき事実(数字を含む。可能なかぎり相手の言葉のまま)
3. 決まったこと・合意したこと
4. 相手の懸念・ネガティブな発言(言葉のまま残す)
5. 私が約束したこと(期限付き)
6. 相手が約束したこと
7. 私の次のアクション(期限付き、最大5個)
8. 次回の予定と議題
9. 確認メモ: 商談中メモの各見出しに対応する原文1文を「見出し→原文」で並べる

■ 注意
・誤字は文脈で直す。固有名詞リストを最優先に反映する。
・聞き取れない部分は(不明)と書く。推測で埋めない。
・相手の発言を要約しすぎない。数字・条件・名前はそのまま残す。
"""


def build_prompt(record: dict) -> str:
    uts = record["utterances"]
    ut_text = "\n".join(f"{u.speaker}({u.time}): {u.text}" for u in uts)
    memo = "\n".join(record.get("memo", [])) or "なし"
    return MINUTES_PROMPT.format(
        date=record["date"], company=record["company"], dept=record["dept"],
        title=record["title"], name=record["name"], meeting_type=record["meeting_type"],
        nouns=", ".join(record["nouns"]), memo=memo, utterances=ut_text,
    )

誤変換しやすい固有名詞(会社名・製品名・人名)をあらかじめ列挙して渡しておくのが精度の肝です。生の文字起こしは人名・社名・製品名が崩れるのが普通ですが、プロンプトにリストを書いておけばLLMが文脈で直します。推測で埋めさせないための指定(「聞き取れない部分は(不明)と書く」)も重要です。AIはメモにないことをそれらしく書くので、この1行で抑えます。

運用パターン: 全体の流れと所要時間

時系列で並べるとこうなります。

商談終了(0分)→ ファイル名を付けて文字起こしに回す(30秒)→ 帰り道に処理完了(5〜10分)→ プロンプトで議事録生成(3〜5分)→ 確認メモで原文照合(2分)→ フォローメール送信

合計は20分前後です。思い出しながら議事録を書く従来方式の45〜60分と比べれば、1件あたり30分、月20商談で10時間の差になります。

運用上の注意が3つあります。

  1. AIに入れてよい情報の約束 — 文字起こしと議事録の生成は、会社が契約したAI(企業・有料プラン)に限ります。無料の個人アカウントに顧客名・金額・契約条件を貼るのはやめる。企業プランなら入力データをAIの学習に使わない設定にできます
  2. 数字の照合だけは手で守る — 9項目の中の「確認メモ」は、見出しメモと原文を照合する表です。AIが数字を書き換える事故はこの照合でほぼ防げます。数字を相手に送る前に原文へ当たる、この1手順だけは人間が実施します
  3. 録音の断り文句は主語をはっきり — 「念のため録音させて……」と弱く言えば警戒される。「本日のお話を議事録に正確に残すため録音をしてもよろしいですか」と議事録という目的を言えば、経験上10商談の9は通ります。Web会議ならZoom・Teams・Google Meetはいずれも録画時に参加者へ自動で許諾を求める仕組みを持つので、それをオンにします

録音を断られた場合の保険として「5分ボイスメモ」があります。商談が終わって相手と別れたら、歩きながらかエレベーターの中でスマホに5分話す。①相手が言った数字と条件 ②決まったこと ③相手の宿題 ④自分の宿題、の順です。声で5分なら800字前後になり、これを同じパイプラインに通せば9項目の議事録が出ます。録音ありに比べれば精度は落ちますが、思い出しながら60分かける議事録より、相手の発言が確実に残る分だけ上です。

よくある失敗パターンと対処

  • 文字起こしをきれいにしてからAIに渡す — 不要です。生のままでよいという前提でプロンプトを作るほうが、固有名詞リストの指定だけで済み、手間が減ります
  • 議事録を翌日に仕上げる — 議事録の価値は半減期で決まります。当日に届けば、相手の記憶が残っているうちに「決めたこと」を確定でき、翌日以降だと話が並び替わって確定までに日数が伸びます。自動化の最大の利益は正確さよりも速さです
  • 9項目すべてを毎回使う — 形式は使う本人が決めていい。自分の商談に不要な項目は削って運用します
  • 文字起こしサービスを複数併用する — 1つに決めて回します。出力形式が揃っていると、パーサ(実装1)の保守が不要になります

このパイプラインを回すと、商談後のあなたの仕事は「議事録を書く」から「次の一手を打つ」に変わります。9項目が並んだ時点で、フォローメールの引用素材(項目2)、次回商談の冒頭で確認する宿題(項目6)、断られフォローの原材料(項目4)が全部揃った状態です。

本記事の内容は、AI営業自動化シリーズ第3作『商談をAIに鍛えさせろ: 事前準備から断られフォローまで、商談の全工程をAIで強くする』の第3章から、実装面を抽出して再構成したものです。


📝 この記事の元になっている本は、営業の準備をAIに任せる手順を1冊にまとめたものです。
読者特典(プロンプト雛形とテンプレート集)は、著者のメール登録から無料で受け取れます。
https://takuma.yuenreach.com

営業×AI実践シリーズ 全3作

Kindle Unlimited 読み放題対象 — 追加料金なしで読めます

← 記事一覧へ

← 商談のあと、駅のホームでメモを開く話…「今、困っている会社」をAIに探させる — 検索… →