GVATECHブログ
🧗

複雑なビジネスドメインとデータを扱うAIシステム開発PJにおける心得色々

に公開

はじめに

昨今は生成AIの追い風もあり、多くの現場で複雑な業務課題を解決するAIシステムが開発されていることと思います。

(筆者も現在進行形で、複雑なビジネスドメインを扱うLLMワークフロー開発と熱い格闘を繰り広げている真っ最中です。)

やはり、AIを取り扱うプロジェクトには不確実性による難しさがあります。
更に、既存のビジネスやシステムに対してAIを適用するとなると、これまで長年に渡って培われたビジネスドメインやデータに対して深く取り組む必要があり、この複雑性も難しさに拍車をかけます。

本稿は、これまで私が現職や以前のキャリアの中で、様々な現場業務に対してAIを適用するプロジェクトに携わったことによる経験と知見を踏まえて、複雑なビジネスドメインやデータを扱うAIシステム開発プロジェクトにおける心得を記述するものです。

本稿における前提

1. 検証における心得

先ずは、プロジェクト初期の検証段階における心得についてです。

初めに成果を得やすい課題をピンポイントに突破する

AI開発においては、実現可能性が分からないままプロジェクトが始まるケースが多くあります。そのため、関係者の関心が高い内に実現可能性の片鱗を見せられることが、プロジェクトを軌道に乗せるためには大切です。

プロジェクトの初期においては、初めから全てのビジネスロジックや制約条件を踏まえようとせず、課題の全体感や要点を捉えた上で、ごく短期間に解決可能な課題を見定めてピンポイントにこれを解くことが重要と考えます。先ずは部分的であっても目に見える成果を上げることで、プロジェクトの士気に火を付けます。

具体的には、以下のようなステップをたどるイメージです。

STEP タスク 説明
1 課題の整理/細分化 ビジネス的な課題について大まかな整理を行い、異なる制約や解法が必要とされる課題ごとに細分化。
2 技術的な解法の見当づけ 課題の解法となる技術の見当づけ。今回最も利用するライブラリやモデル種類など。
3 最初に解決する課題の特定 STEP1で整理した課題の中から、最も効果が見えそうな課題を特定。課題が特定できたら関連するデータをなるべくシンプルな形で定義して解く。

プロジェクトの初期は、プロジェクト企画者も具体的なアウトプットイメージまで描けていないことが多いです。「具体的に何が必要か説明できないが、見ればわかる」ということを意味するIKIWISI[1]という言葉が示す通り、小さく解決策を実現してみることによって、これを叩き台にして、関係者の方々から様々な要求や制約事項について、多くの情報を引き出すことができるようになります。

実際の業務データを読み尽くす

検証を始めるに際して、AIを適用する既存システムの業務データについて、各項目が何を意味しているか、どのようなデータパターンが許容されるかなど、読み込みを行うことになります。

この際、テスト用のデータではなく、実際の業務データを読み解くことが非常に重要です。実際の業務データを読んでいると想定していなかったデータに遭遇します。実際にこれらのデータについてヒアリングすることで、「それは昔使われてた商品コードで、これらについては特殊な対応が必要なんだよね」という様に有用な情報を聞き出すことができます。

また、複数のユーザーや現場が利用するマルチテナント環境においては、可能な限り協力者を募り、幅広く実際の業務データを確認することが、最終的な成功につながる秘訣であると思います。多くの業務データを確認する中で、ユーザーによって設定内容が全く異なっていたり、特定の項目の利用方法が多様であったり、現場によって動画像に映る照度やカメラ角度が全く違っていたりなど、様々なユースケースが存在することが見えてきます。

検証実装中も勇気を持ってリファクタリングを続ける

複雑なドメインを扱うAI案件においては、多様かつ多数のデータを様々な形で関係させながら処理を行うフローを作っていくことになります。少なくとも、筆者が経験したプロジェクトにおいては、いずれも多くのテーブルデータ、JSON、非構造化データを受け取るものばかりでした。

これに加えて、AI開発においては検証を進める中で、初めて明らかとなる制約条件や追加要件が継続的に発生します。場合によっては、扱うデータ種類自体が増えていくこともあります。

このようにAI開発プロジェクトにおいては、前提が変動する中で検証実装を進めていくことが多く、非常に認知負荷の高いコード内容になりやすいです。

プロジェクトには納期があります。加えて、複雑なシステム開発では、関係者間の認識齟齬を避けるためにも、素早く小まめにアウトプットをしなければという焦燥感も襲ってきます。この中でリファクタリングを行う判断を下すのは容易ではありません。

しかし、これまでの経験を振り返ると、これらのプレッシャーがありながらも、認知負荷の高いコードを抱え続けるぐらいであれば、勇気を持って随時リファクタリングした方が、結果的に良いのではないかと考えています。

あくまでも個人の意見ですが、認知負荷が高い作業を継続している状態かつ、外的なプレッシャーがかかる状況においては、精神的な負担が発生する傾向があると感じます。また、認知負荷が高い内容についてはメンバーとの共有が難しいため、段々とプロジェクト自体の属人化を招きます。このような事態を防ぐためにもコードベースをクリーンに保つことが重要と考えます。

少し具体的な設計の話をすると、多様かつ多数のデータを扱うプロジェクトにおいては、データのインターフェイスとしてのDTOと、AIモデルやワークフローから扱いやすいデータ構造に変換したドメインオブジェクトを区別して実装するのが良いと考えます。

DTOとドメインオブジェクト

私が特に心がけていることは、ドメインオブジェクトをなるべくシンプルにして可読性をキープすることです。私は大抵のケースで、DTOで受け取ったデータに対して前処理を経てドメインオブジェクトに変換する処理を書きますが、ドメインオブジェクト側に前処理が漏れ出てしまった場合や、ドメインオブジェクトの各クラスが大きくなり過ぎて難解になった場合には、リファクタリングを試みます。

2. 設計における心得

続いて、アプリケーションおよびインフラの設計段階における心得です。

既存システムのデータ構造をそのままAIシステムのIFに採用しない

上述の通り、検証工程においては既存システムのデータ読解を進めることになります。
初めは、ドメインロジックを多分に含んだデータの読解に苦労すると思います。しかし、理解が進むに連れて、難解に見えていたデータも複雑なドメインを上手く表現した合理的な形式に見えてくるものです。

また、検証における実装では、暫定的に既存システムから出力したデータを丸ごとロードして処理を進めるケースが多いと思います。数ヶ月に渡る検証期間中、ビジネスロジック部分は細かく修正されながら作り込まれていくことになると思いますが、該当のデータロード部分についてはそのまま利用され続ける可能性があります。加えて、AIの実証検証は想定していたよりも長引くケースがあるため、その間クライアント側の実装を進めようと、AIシステムのインターフェイス決定を急ぐこともあります。

このような条件が揃った時に、既存システムのデータ構造をAIシステムのAPIインターフェイスとしてそのまま採用してしまいたくなります。しかし、短絡的にインターフェイスを決定してしまった場合、以下のような不都合が生じます。

ケース1: 既存システムの仕様変更に弱い状態になっているケース

既存システムのデータ構造(JSON)に、 key1key2 という何らかの属性情報が格納されるキーが格納されるケースを想定します。それぞれ以下の値を取るとします。

  • key1: A or B
  • key2: C or D

key1key2 が取る値の組み合わせによって、AI側で実行されるプロセスを切り替える仕様を想定します。

key1とkey2を持つJSON

このデータ設計においては、既存システム側で仕様変更が行われた際に、未知の値が key1key2 に格納されてしまう可能性が考えられます。

期待しない値によるエラー

とりわけ、既存システムのデータ構造をそのままインターフェイスに採用してしまったことにより、既存システム側の担当者は「AIシステムには既存システムから出力されるデータ構造のままリクエストすれば大丈夫」という認識でいる可能性があり、況してこのような行き違いが起こりやすい状況になり得ます。

このような変更に弱い設計を避けるためにも、key1 key2 をそのまま受領せず、AI側で実行する process1~4 の属性値を受け取るキーを明示的に定めた方が事故が起こりづらいと想定します。

ケース2: 両システムの責務が不明確になっているケース

リクエストだけでなくレスポンスのインターフェイスについても注意が必要です。
例として以下のケースを考えます。

プロジェクトの最大の課題が「発注テーブルに計上する発注量を最適化したい」であるとします。ここで拙速に考えてしまうと「AIシステム側からのレスポンスは最適化された発注テーブルを返そう」という判断に至ります。

最適化された発注テーブルを返却するフロー図

しかし、発注テーブルには商品の発注量だけでなく、「発注先」や「納品期日」と言った様々な情報が付随しているかもしれません。また、特殊な条件に当てはまる場合、発注量自体にもビジネスロジックを加味した特別な計算を施して調整を行う必要があるかもしれません。

つまり、AIシステムから返却する発注テーブルを構成するためには、複雑なビジネスロジックを伴う「発注テーブル構成ロジック」が必要であるということです。この際、下図の通り、既存システム側でもAIシステム側でも、この「発注テーブル構成ロジック」を重複して持つことになってしまうかもしれません。

重複したロジックを持つフロー図

このような状態で、該当の「発注テーブル構成ロジック」に対して改修が入った場合、片方だけが更新され、片方の更新を忘れてしまうといったような、トラブルの温床になることが想定されます。

このケースにおいては、「商品コード」「発注日」「最適化演算によって出力された発注量」あたりの項目を、シンプルにレスポンスする構造が良いと思います。

非同期型でAIシステムを呼び出す構成にする

複雑なビジネスドメインを扱うシステムの処理時間は長くなりがちです。
そのため、基本的にはプロジェクトの初期段階からAIシステムは非同期型で呼び出す想定をしておいた方が良いのではないかと考えます。

AIシステムにおいて非同期型の構成を採る場合、以下のようなシーケンスになることが多いと思います。

フローの中に発注テーブル構成ロジックの重複あり

クライアント側への完了通知が実装できない場合には、結果取得リクエストをポーリングさせるのものありだと思います。

非同期型のデメリットは、実装工数が大きくなりがちという点です。
同期型での実装と比較して、AIシステム側およびクライアント側の実装工数も増大します。しかし、プロジェクトの初期段階から非同期型を採用する可能性を念頭に置いておくことで、工数に対する不確実性を低減することができます。

実際に、AIシステムの処理時間[2]について、筆者が経験したプロジェクトでは、以下のように想定より大幅に所要時間が増大したケースがあります。

  • 検証の初期段階では10秒程で完了していたものが、最終段階では要件もデータ種類も増えて30秒以上要するようになった
  • 初めは、1度のリクエストで1週間分のデータのみをリクエストする想定であったが、利用者の要望に応じて数ヶ月分のデータが送信されるようになり、当初より4~8倍ほど所要時間が増大した

更に、リリース後にエンハンスによって処理時間が追加になることも想定されます。このような事態に備える意味でも、初めから非同期型でAIシステムを呼び出す構成しておいた方が良いケースが多いのではないかと考えます。

3. リリースに向けた心得

最後に、リリースを間近に控えたプロジェクト最終段階における心得です。

リリース条件を関係者間で合意する

複雑性の高いプロジェクトにおいては、最後まで全体像を掴み切ったと確信を持つことは難しいものです。しかし、ある程度プロジェクトを俯瞰して見られるようになった段階で、対象とするユースケースと合否条件について、早めに関係者間で合意する機会を設けるのが良いと考えます。

No タスク 説明
1 ユースケース整理 今回作成するシステムの対象スコープとするユースケースを洗い出す。
2 合否条件の決定 それぞれのユースケースについて精度の測定方法や合否条件を決める。

あらゆる森羅万象のユースケースに対応するAIシステムを作ることは、ほとんど不可能に近いです。そのため、関係者間で開発物の対応スコープの認識と期待値を共有しつつ、AIでは解決出来ない部分について運用による対応要否などを検討しながら、スケジュール通りにリリースできるように調整を進めていくのが良いと考えます。

ユースケースをインテグレーションテストとして実装する

扱うドメインの複雑さによっては、AIシステムの内部で細かい条件分岐が生じたり、各種パラメータの取りうる値の組み合わせパターンが把握しきれないほど多数になるケースがあります。

このように、複雑な状態を内包するシステムにおいては、単体テストで各所の動作を網羅的に確認したつもりでも、通しでプログラムを実行した際にエラーが出てしまうことはあります。また、システムが複雑である程、細かい部分的な改修であっても、何かデグレーションさせてしまわないかと不安になるものです。

そのため、各種ユースケースをインテグレーションテストとして実装することで、最終的な動作を担保します。

テスト戦略としては、テストピラミッド(下図[3])の通り、基本的には単体テストで各種ロジックを細かく動作担保しつつ、要点を要領良く押さえたインテグレーションテストを実装します。

テストピラミッド

全てのインテグレーションテストを実行するためには時間を要する場合があります。そのため、同じリポジトリ内に実装するか悩むケースもありますが、筆者はリポジトリ内に実装する場合には、pytestのmarker[4]である @pytest.mark.integration をインテグレーションテストのテストメソッドを付与します。(筆者はPythonを用いて開発することが多いです。)

pytestのmarkerを活用することで、カスタムマーカーを付与したインテグレーションテストを除外して、単体テストのみを実行することが可能になるためです。

# 単体テストのみを実行する(結合テストを除く)
$ pytest -m "not integration"

おわりに

複雑なビジネスドメインとデータを扱うAIシステム開発プロジェクトにおける心得を、これまでの私の経験をベースに紹介いたしました。

このように記述してみると、いずれも基本的な当たり前の内容です。
しかし、AI開発プロジェクトでは不確実性や複雑性による難しさのために、つい初心を忘れてしまうこともあります。本稿に記述した心得がちょっとしたお守りにでもなれたら幸いです。

皆様が携わっているAIシステム開発プロジェクトが成功することを願っております!

脚注
  1. I'll know it when I see it. の略語 https://en.wiktionary.org/wiki/IKIWISI ↩︎

  2. 推論に要する時間のみでなく前処理や後処理も含めた所要時間 ↩︎

  3. Vladimir Khorikov著『単体テストの考え方/使い方(マイナビ出版)』 P.124を参考に作成 ↩︎

  4. https://docs.pytest.org/en/stable/example/markers.html ↩︎

GVATECHブログ
GVATECHブログ

Discussion