前置き
議事録というものには一般的に、議題、会議の内容、発言記録などが記載されているが、その書き方(記法)は人それぞれである。
発言記録には、主に「発言者」「役職」「発言内容」といった情報が含まれている。どんな人がどんな発言をしたのかを記録するためだ。
しかしながらその記法はバラバラで、
田中(議長)こんにちは
という形式もあれば、
議長(田中)こんにちは
であったり、
田中議長 こんにちは
であったりもする。
この文字列から、誰が、どんな人で、どんな発言をしたのか、
つまり「発言者名」「役職」「発言内容」を切り分けたいと思った時、どんな方法があるだろう?
AIに任せちゃおう
今回はAIに任せてみた方法をご紹介しよう。
使用したLLMはopenAIのgpt-4oモデル。pythonでAPIを呼び出し、発言者情報をGPTモデルに渡して判別させる。
対象の議事録
対象の議事録はとある市町村の会議録。これは先ほどの話で言う田中議長 こんにちは
の記法であり、「田中」と「議長」をプログラムで分離させることが難しいために、今回の方法を試した。
実装コード
from openai import OpenAI
api_key = os.environ.get('OPENAI_API_KEY')
client = OpenAI(api_key=api_key)
model = 'gpt-4o'
def separate_name_and_title(speaker_info):
# GPTへのプロンプトを作成
prompt = f"以下の発言者情報のうち、名前を表している文字列のみを返答してください。それ以外に余計なことは返答しないこと。\n\n発言者情報:{speaker_info}\n\n"
# GPTに対してリクエストを送信
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "あなたは日本語の助言者です。"},
{"role": "user", "content": prompt}
]
)
# GPTの応答から名前を取得
name = response.choices[0].message.content.strip()
return name
プロンプトに渡しているspeaker_infoには、発言者情報を表す文字列が入っている。
例えば、
○水沢議長 これで諸般の報告を終わります。
と言った発言記録の場合、「○水沢議長」にあたる位置の文字列を正規表現で抜き出して渡した。
APIの呼び出しの記述は一般的だと思う(多分)。
モデルからの返答をそのまま変数に入れて使うので、プロンプトには余計なことを言わないように(例えば、「名前に当たるのは水沢です」みたいな返答をしないように)念を押した。意外とこれで回答を制御できる。
その後はnameを基準にpost(役職)を分離した。
結果
以下は出力結果である。
見てわかる通り、同じ文字列でも出力結果が異なっている。
田村議長を「田村」としている場合もあれば、「田村議長」としている場合もある
つまり回答にゆれがみられた。
これでは使えないかな、と落ち込んだが、一応「回答ゆれ」のパターンはどのようなものがあるのか、全体のどの程度なのかを調べることにした。
結果を分析する
同じ発言者情報のうち、いくつの回答ゆれパターンがあるのかを調べた。
田村議長の場合で言うなら、「田村」「田村議長」のほかにパターンは存在するのか。それぞれのパターンは全体のうちいくつあるのか等の数値を出す。
すると驚くべきことに、今回のデータに限った話ではあるが、回答ゆれのパターンは二つ。
1
名前と役職を正しく分離し、名前のみを出力している
2
発言者情報(元の文字列)をそのまま出力している
役職名の方も同じ処理でカウントして比較しているが、数字は合っているし、同じように2パターン(正しく分離できているか、nullか)だった。そしてこれら2パターンの合計を取ると総数となる。
つまり本当にこの2パターンのようだ(今回に限った話)。
結果と分析を踏まえて
ここまでの情報を元に考えると、LLMの回答ゆれが2パターンなら、パターン2の際にfalseということにしてもう一度apiを呼び出し、パターン1がでるまで続けるというガチャガチャ方式が取れる。
元としたデータ数が少ない(2600例ほど)うえにほとんどが田村議長なので、多分さらにやっていくと別のパターンも出てくるだろうが、田中議長 こんにちは
記法の分離方法に散々悩まされてきた私としては、これはかなり嬉しい結果だった。
ガチャガチャを試してみたら、また書こう。