請求書自動化サービスで使われている技術ご紹介(第3回)
久保熱錬様のPDF形式発注書からのデータ読み取り処理のアプリを開発していて気が付いたいくつかのトピックをご紹介します。
今更感はありますが、ChatGPTのAPIを使って処理を行う場合、ChatGPTに対してどのような事をして欲しいかという命令を書く必要があります。これをプロンプトと呼びます。
以前にも書きましたが、ChatGPTへの命令は以下のようなコードとなっていて、この中のfunction_callの個所で指定されるfunctionがCgatGPTとの間での、アプリケーションデータのやり取りに使用されます。
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
max_tokens=2048,
temperature=0,
function_call={"name": function_info["name"]},
functions=[function_info]
)
そしてこのfunction_infoはJSON形式のデータで、その中にChatGPTへの依頼の命令が書かれています。
久保熱錬様のプロジェクトが始まって1月ほどたった時、実際にお客様からの発注書を使い、処理がどの程度うまく動くかを見てみることにしました。
そうすると直ぐに目についた問題が2つありました。1つは発注元会社名が「久保熱錬」になる事、もう一つは発注書内の「形状」という項目には値が入っているのに、処理結果はこの欄が空白になってしまう事です。
この後者の問題については、理由は割と簡単でした。GoogleOCRでの読み取りの結果、すでにこの項目の内容が読み取れていませんでした。ChatGPTにはGoogleOCRの読み取り結果を入力データとして渡すので、結果的にこの欄が空白になってしまいます。
(GoogleOCRが読み取れなかったのはカタカナでした。別の欄のカタカナの文字列は読み取れていたので、字の大きさが問題なのかなと考えています)
そして前者の問題ですが、これはプロンプトに、"#important 発注元会社名は必ず 「XXXXXXXX」(お客様会社名称)というキーワードを含む"を加えることで解決しました。
ただ、まがりなりにも今までプログラムを書いてきた人間からすると、この解決方法はかなり違和感があります。なぜなら「プログラミング」が持つ論理性がないからです。
それでもこうすることで(しか?)解決できるので、簡単と言えば簡単なのですが、「これは奥が深い」と感じました。決められた命令セットや数式で論理構造を記述するわけでなく、日本語の自然言語での命令をだすわけですから、いくらでも変えられるし、その度に返される答えが変わるわけです。
実は最初このプロンプトを追加する事を考えず、後処理に例外処理として発注元会社名に「久保熱錬」の文字列が入っていた場合、マスターに持っている発注元の会社名を使って上書きすることを考えそのためのコード(約30行程度)を書いていました。
でもプロンプトを変えることで問題が解決することで、例外処理も不要となり余計なバグをいれる可能性も減るので、やはり最も重要なのは「いかにして自分の欲しい回答を引き出すプロンプトをつくるのか」というのが、ChatGPTを使用するうえでの最重要課題だと体で覚えた瞬間でした。
ちなみにこの方法だとプロンプト内に、発注先の会社名が入ることとなり、発注先毎にプロンプトを変えないといけなくなりますがこの点は最初から発注元会社数が50程度で、発注書自体が会社毎にそのフォーム、用語が異なることから元々やり取りに使用されるJSON形式の構造体も発注会社毎に用意しようと考えていたのであまり問題だとは考えていません。
ただ、今回の事でChatGPTを使うプログラミングでは、プログラムロジックだけでなくChatGPTに出す自然言語での命令について、少しでも欲しい結果を得るためにどういう風に改善すべきかを考える必要があるという事を理解しました。