有価証券報告書(PDF)の情報をDWHに蓄積する
早いもので師走ですね。
今年は弊社の1期目ということもあり、気が付けば終わってたという感じなのですが
私からは今年最後となる(おそらく)Tech-Blogをお届けできればと思います。
モチベーション🔥
少し前に、経営戦略 ~ 経理などの幅広でプロダクトを考えている時期がありました。
現在は少し中身がつまりつつあるのですが、いかんせんこの分野で言えることは独自フォーマットでの管理や手集計のために手元用に最適化されたプロセスが多く、データ基盤として一元管理するにせよ、作り込みや整形・紐解きの負荷が大きすぎるなという印象でした。
そんな中ご存じの通り、昨今のLLMの進化は目を見張るものがあります。
先週にはOpenAIから o1-pro
が発表され、より強力にそして最適化されたモデルが来年以降も登場してくるのでしょう。
では、プロダクト(本番)として十分利用可なLLMモデルがどんどん登場してくる中、どのようなデータ処理プロセスのユースケースを開拓していくべきなのか
inputデータに対して推論を行い、付加情報を「生成」することでビジネスに貢献することが多いです。
データのパイプラインとしては、「加工」 をする部分のイメージです。
ですが先の通り、より洗練されたモデルが登場することによって、その担当箇所も広げられるのではないか。データを 「取得」 する部分においても、よりうまくやってくれるのではないか。
そう考えると期待も大きく、元々のフォーマットがバラバラな課題に対してもうまくアプローチできるのではないか、そんなところから今回は始めてみます。
PDFをLLMで読み解く📝
下記の本が最近面白かったです(宣伝でもアフィでもないです)
特に定石として書かれている下記のポイントが気になりました。
企業の一次情報を取得する
企業のKPIを把握する
時系列や他社の観点から比較を行う
決算書の裏側に隠れているビジネスモデルを見つけ出す etc
有価証券報告書は企業が出しているペーパーであり、一次情報としてもトップクラスで内容の濃いペーパーです。ただし、当たり前ですが情報量が多いが故にペーパーが非常に長いです。
例えば、トヨタ自動車の直近の有価証券報告書はPDF版で238ページあります😐
ここから財務諸表における数値を抜き出し、その数値に対しての考察を出力し蓄積することをやってみます。
なぜ今回のケースで蓄積が必要か🔍
これはユースケースの深堀だとも思いますが、定石にも合った通り、いくつか理由は考えられそうです。
(自社目線)
・自社の数値は生データで保持しているので、主には自社以外の蓄積目的
・過去の自社レポートや他社レポートを起点に次期のレポートの壁打ちを行う
(株主や投資家、他ビジネス目線)
・特定の企業を時系列で長く追うことによって、字面だけでは見えないことを把握したい
・特定の業界や業種におけるKPI(その中でも業界共通なもの、そうでないもの)を管理することでビジネスモデルの強さや目新しさを評価したい etc
LLMに放り込めば、いい感じで出てくる
というのは間違いなく今後も加速していくので
- どれだけより綺麗な形(構造が整形されている、どう基準で評価されている)でデータを保持するか
- どれだけデータ量をもっているか(オープンなデータでも手間を加えた綺麗な形で)
という部分の勝負になってくるのかなと感じています。
LLMのモデル📚
AnthropicのSonnet 3.5
を利用しました。
こちらも少し前に話題となっており、PDFサポートがβで登場しています。
画像周りの読み取り(OCR)要素も個人検証ではGPT-4o等よりも高精度な印象で、かなり正確に書き出しをしてもらえます。その上、PDFにおける文脈(表なども含む)も理解してもらえるとなると、こちら一択という形でした。
プロンプト🗒️
下記のように記載してみます。User文のみです。
"
添付の資料は企業の四半期報告書です。資料に含まれる財務諸表から項目を抜き出してください\
出力されたデータはデータベースに連携するため、カンマ区切りのCSVで出力してください。\
それ以外の文章の出力は不要です。csv形式のデータのみを出力してください\
出力するcsvデータのヘッダーは下記としてください\
|企業名|年度|カテゴリ|項目名|数値|補足|\
\
続けてヘッダーに含む情報の補足を記載します\
企業名:取得している企業の和名正式名称を記載すること、例:トヨタ自動車株式会社\
年度:取得できた年度を記載する、例:2022年12月期第3四半期\
カテゴリ:会社全体全体の収益を表す:全体 または、セグメントや内訳を示す場合:セグメント の2つのどちらかを選択すること\
項目名:企業にとって重要と思われる項目を記載すること、例:営業収益\
数値:項目に紐づく数値を記載すること、数値を区切るカンマは不要、単位は百万円として記載は不要、単位が千円などの場合は小数点を使って修正してください。数字のみ記載すること\
補足:その数値がなぜそのような結果になったか、起因する情報をまとめて100文字以内で記載し文章内にはカンマやクォーテーションを含めないこと、資料内の各状況に記載することが参考になる\
"
まずは、事前に出力してほしいフォーマットを妄想してから、プロンプトにおとした形です。
|企業名|年度|カテゴリ|項目名|数値|補足|
|xxx株式会社|2024年第2四半期|全体|総売上|1000|前期よりも○○%アップ、原因としては○○|
|xxx株式会社|2024年第2四半期|セグメント|○○事業売上|200|前期よりも○○%ダウン、原因としては○○|
今回は省略していますが、業界・業種
や 必須で取得すべき項目
など、実用に向けて検討すべきは多々あります。その他情報が多々あるので、より詳細に指定することが必要です。
また、項目名は企業や業界によって 振れ
があるので名寄などの検討も必要です。
これらは、LLMで実施する部分とシステム的に実施する部分もある気がするので、後段フェーズであるBigQuery上でのSQLやGeminiに呼び出しでもいいのではと思います(モデルの利用コストも安いので、うまく使い分けることも検討できます)
呼び出し例🖥️
サンプルコードはGithubに載せている(後述)ので、そちらをご覧ください。
結果としては、こんな感じになりました。
<元PDF>
株式会社ニトリホールディングス_2023年12月期第3四半期
<LLMの出力結果>
企業名,年度,カテゴリ,項目名,数値,補足
株式会社ニトリホールディングス,2023年12月期第3四半期,全体,売上高,663746,既存店改装や客数対策により増収 原材料価格上昇の影響あるも円安対策を継続
株式会社ニトリホールディングス,2023年12月期第3四半期,全体,営業利益,97865,物流の内製化や拠点再配置による発送配達費削減に努める
株式会社ニトリホールディングス,2023年12月期第3四半期,全体,経常利益,101268,為替差益や受取利息等の営業外収益が寄与
株式会社ニトリホールディングス,2023年12月期第3四半期,全体,親会社株主に帰属する四半期純利益,68535,特別損失として減損損失512百万円を計上
株式会社ニトリホールディングス,2023年12月期第3四半期,セグメント,ニトリ事業売上高,579571,既存店改装や商品施策が奏功し増収
株式会社ニトリホールディングス,2023年12月期第3四半期,セグメント,島忠事業売上高,91169,新規出店効果あるも既存店売上が減少
シンプルですが、情報は取れていそうな印象です。
実際はもう少し項目を取得できたほうがよさそうなので、Max Token数の調整やPDFを事前にサマライズするのも良さそうです。
システムフロー化↩️
Google Cloudをベースに処理をCloud Run functionsを用いてAPI化し、リクエストを送れるようにしてみました。経由としては下記です。
TROCCOでワークフローを起動
↓
TROCCOのジョブでCloud Run functions(API)をキック
↓
Cloud Storage(LLMの出力ファイル)
↓
TROCCOのジョブでデータ転送(取り込み)
↓
BigQuery(DWHに格納)
①なぜワークフローにTROCCOなのか
選択肢は多々あると思います。例えば
- データにおけるワークフローエンジン(Airflow, Prefect, Argo等)
- LLMにおけるワークフロー(Dify, Argo等)
といった形だと思います。用途や利用者によって選択すべきだと思いますが、今回のケースだと
- データをDWHに入れたり、外部ストレージにLog的にも格納したい(Difyはまだ厳しい)
- LLMの呼び出しはまだシンプルでOK、ただし将来的なことを考えると
- 呼び出しがより複雑化するならLangchain等での開発 => API化して呼び出し
- 加えてモデル・プロンプトの管理などが必要になる => LangSmithやWandBも含め、サービスを跨いだ管理も検討
将来を加味して検討が必要にはなります。でも、ミニマムで始めるにおいて
- データを取得して事前に加工する部分がメイン、まだ大規模なLLMの運用は不要
- OSSの自社ホスティングなどは辛い(誰が面倒みるのか問題)
- と言ってもマネージドにお金を使いたくない(ちょこっと実行するだけなので)
- 後で大幅に載せ替えたり、全く使わなくなる可能性があるツールは利用したくない
といった気持ちもあるのが正直なところです。TROCCOはその点、FREEプランで十分カバーできる範囲でもあり、もし載せ替えがあっても、以降で何かしらのデータ連携に使え、気軽に試せるのも重要なところです。
また、今回は経営戦略の分析をイメージしているので、定石にも書かれていた 競合との比較は重要な視点であり、財務の数値だけではなく、ビジネスモデル(指標)・非財務情報などの連携は将来的にあり得る話だと思います。
ちなみに12/10に行われたprimeNumber社の01イベントでも言及されていたポイントでした。
(会計領域だけではなく、人事や決済領域も含まれています)
②なぜStorageを経由するのか
これはシンプルに まだLLMを完全に信用していない からです。
形式が崩れたり、急に想定外の内容を入れ込みだしたりがあり得ます。Function側でデータロードでエラーハンドリングしちゃうと原因の切り分けなどが面倒なこともあります。
とりあえずはLLMにファイルとして出力はさせる、その上でDWHへのロードが落ちた場合に
- 最悪直接ロードファイルを手入力でまず直す
- Function側に修正をかけるのはプロンプトの部分のみ
- ロードまでに決まったETLが必要であれば、さらに処理を挟む
といったフローのほうがしっくり来るかな~と思っています。
③なぜDWHに蓄積するのか
今回のケースでは数値が含まれるので、後続での比較や計算処理ではSQLも利用しやすいです。
最終的なデータ利用はダッシュボードを主と考えていましたが、検索やRAGなどのケースも考えると、ベクトルDBなどもありえそうです。
ただ、BigQueryでもNestされた形でのベクトル管理もできたはずなので、とりあえずDWHにデータを乗せていく運用はLLMでも通用する と思っています。データ構造として 自然言語
と 数値
が共存する運用になることは間違いないので、PythonとSQLで処理分別は重要かと思います。そのあたりはSnowflakeやDatabricksが強そうですね。
コードと実行🧑💻
下記レポジトリにサンプルのコードを公開しました。
よければ、Cloud Run functionsへのデプロイも試してみてください。
TROCCOでは、下記のようなワークフローを組んでみました。
TROCCOには3社のURLを設定したので、12/6分で3社分の処理ループが実行されました。
Storageには下記のように格納されています。
最終的にはBQ側にこのような形で格納されるところまで確認しました。なかなか良い結果です。
⚠️実装中にあった注意点や補足⚠️
急にCSVで出力されなくなる
- LLMではあるあるの話だと思います
- 急にカンマ区切りではなく、パイプでの区切りになったりしました
- FunctionCallingをかませたり、Jsonモードの利用やガードレールの適用がやはり必須です
気が付けばコスト増
- 今回ClaudeのPDF読み取りを利用したわけですが、PDFの四半期報告書としては20-30ページあたりが中央値だと思います
- 1リクエストあたり、$0.15~0.25 という形で思ったよりコストかかるなという感じでした
- βではあるので、これからコスト感はより調整がかかると思いますが、PDFのトークン量は想定もしづらいので、プロジェクト毎やワークスペース毎のAPIコストリミットは必須ですね
Waitの対応はBQ側で実施
- ClaudeのPDF読み取りAPIは、
1分間につき1回のリクエストというRate limit
がついていました - デプロイしたFunctionのPythonコード内にSleepを入れる想定をしたのですが、リクエストするTROCCO側のジョブがタイムアウトで落ちます(おそらくはRails側のデフォルトタイムアウト)
- BQ側にSleepのProcedureを定義し、TROCCO側のデータチェックで呼び出すようにしました
さいごに
今回は今興味があることをベースに少し深堀の上、検証まで進めてみました。
少し前のAIやMLブームとは違って、on Ready なモデルが登場している中で、
どう組み込んで、どう制御して、どうFBするか
がより重要になるポイントだと考えています。私自身もちょっと触って精度に驚くだけのフェーズから、具体的に業務を効率化をするための取り組みフェーズにどんどんビジネスを昇華できればと思っています!
宣伝
弊社ではデータ基盤やLLMのご相談や構築も可能ですので、お気軽にお問合せください。
また、中途採用やインターンの問い合わせもお待ちしています!
Discussion