こちらはエムスリー Advent Calendar 2024 22日目の記事です。
こんにちは。2024/10 から AI・機械学習チームにジョインした鴨田です。 私が所属するAIチームには、機械学習やソフトウェア開発に深い知見を持つエンジニアが集まっています。日々のコードレビューや技術議論を通じて、「こんな考え方があったのか」「この実装方法は勉強になる」と新しい発見の連続です。今回のOSSコントリビュートは、チームメンバーがspotify/luigiの問題を特定し、コントリビュートの機会として共有してくれたことがきっかけでした。
本ブログでは私がエンジニア人生で初めてOSSコントリビュートした話を書いていきます。初めてのコントリビュート系の記事は既にたくさんの人に掲載されていますので*1、この記事ではAIチームの文化も絡めてお伝えしたいと思います。
OSSコントリビュートの動機
エムスリー株式会社が公開しているOSSの中にgokartという機械学習パイプラインツールがあります。これはSpotify社が開発したluigiのラッパーとして開発されました。
gokartによってどのような課題が解決できるかはAdvent Calendar 2 日目で池嶋さんが書かれた記事に詳しく記載されていますので、ぜひご覧ください。 www.m3tech.blog
ある日、AIチームでgokartを利用したプロダクトの実行時エラーを解析していたところ、「luigi.TupleParameter().parse()
はStringを受け取るとエラーにならずに、文字ごとに分解しTupleに変換してしまう」という、luigi起因の問題を確認しました。
luigiでは設定値の型チェックを実行時に行っており、静的解析ができない仕様となっています。
例えば次のようなケースで問題が発生します:
# config.cfg [TaskA] tuple_param = "('hoge')" # これはstr型です。本来は('hoge',)のような形式を想定。(カンマが大事) # pipeline.py class TaskA(luigi.Task): tuple_param:Tuple[str,...] = luigi.TupleParameter() def run(self): # tuple_paramは('h', 'o', 'g', 'e')として解釈される # 実行時までこの問題に気づけない
このような問題は、機械学習パイプラインの安定運用において重要な課題となり、特に長時間実行される処理の途中で意図しない処理が発生するとリソースの無駄遣いにつながってしまいます。
今回の場合は、luigi.TupleParameter().parse()
に文字列型が渡ってしまったときにエラーとするような処理を入れることにしました。
問題の原因とおおよその対処法がチャットにて議論された後に、AIチーム内でコントリビュートを促す投稿がされました。
エムスリーにジョインしたばかりの私はとにかく貢献したい気持ちが高まっていた時期でもあり、すぐに該当問題の修正に名乗り出ました。
コントリビュートの実施
冒頭にも記載しましたが、既にたくさんの人がコントリビュート系記事を記載されていますので、手順について詳しく記載しません。私の気づきや実装を中心に記載したいと思います。
Git forkってなんだ?
エムスリーや私の今までの開発は少人数チームが多く、業務では単一リモートブランチで開発を行ってきたので、fork文化に触れる機会がありませんでした
多くの記事では、まず最初にGitリポジトリをforkし、forkしたリポジトリに対して変更を加える手順が紹介されていました。
双方リポジトリを複製することに変わりはないですが、forkを利用するとオリジナルリポジトリとの繋がりを維持したまま、独立して開発可能とのことでした。*2
forkしてレポジトリが乱立すると開発状況が見にくくなるかわりに、開発中のものはPR・マージされない限り本家レポジトリに影響を及ぼさない、というOSSや大規模開発ならではのメリットがあるわけですね。
PRの作成
今回の場合、文字列型を渡した際に”文字ごとに分解しTupleに変換されてしまう”のは意図した挙動ではないので、文字列型を渡した際はエラーになるように修正しました。
except (ValueError, TypeError): - return tuple(literal_eval(x)) # if this causes an error, let that error be raised. + result = literal_eval(x) + # t_str = '("abcd")' + # Ensure that the result is not a string to avoid cases like ('a','b','c','d') + if isinstance(result, str): + raise ValueError("Parsed result cannot be a string") + return tuple(result) # if this causes an error, let that error be raised.
生成AIを活用したDescription(本文)作成
生成AIとの対話は次の流れで実施し、Descriptionを作成しました。
英語でPR作成することに一番ハードルを感じていましたが、生成AIを活用することによってストレスなく作業できました。
- 「PRのテンプレートとPRの内容を元に適宜質問しながら対話的に作成してほしい」 と依頼
- 生成AIからの質問に回答しながら、最終的な文章を作成。
- 具体的にどのような問題やバグを解決するものか?
- どのようにテストしたか?
- テストを作成していなかったので、テストを実装
- 生成AIにテスト実装内容を連携し適切なコミットメッセージを提案してもらう
- 最終的なDescriptionの作成を生成AIに依頼
生成したDescriptionがどれほど違和感のない言い回しだったかわかりませんが、無事マージしていただけたので最低限実装の内容や解決したい課題については伝わったのかなと思いました。
やってみての感想
意外とすんなり終わった
OSSコントリビュートはとてもハードルが高いイメージでしたが、いざ終わってみると「またやってみようかな」と思えるくらいあっさり終わりました。
GitHubコマンドが便利
AIチームの北川さんに教えていただき早速使ってみたのですが、とても便利だったので共有します。
従来リポジトリのforkはGitHubなどのホスティングサービス上で操作を行う必要がありました。
一方、GitHub CLI(Command Line Interface)のコマンドgh repo fork
は 、以下のような特徴があります。
- コマンドラインから直接GitHubリポジトリをフォークできる
- フォーク後に自動的にローカルクローンを作成するオプションがある。
- リモートの設定(upstream等)を自動的に行える。
個人的には” リモートの設定(upstream等)を自動的に行える”にとても助けられ、手順が簡略化され操作ミスを減らせたと感じています。
わいわいサポートしていただける環境に助けられた
実際の作業では、テストコードの実装場所や効果的なGitコマンドの使い方など、具体的で実践的なアドバイスをいただきました。特に印象的だったのは、自身の業務に直接は関係なくてもチームメンバーが積極的にサポートしてくれる姿勢です。「これってどうすればいいですか?」という初歩的な質問にも、いつも丁寧に回答いただき、OSSコントリビュートという新しいチャレンジを楽しく進めることができました。
AIチームのコミュニケーションについてはアドベントカレンダー7日目の記事に一部記載されていますのでよろしければそちらもご覧ください!
チームでは引き続きOSSコントリビュートのチャンスがたくさんあるので、今後も積極的にチャレンジしていきたいと思います!
luigi 3.6.0がリリースされました!
New Contributorsとして紹介されており、「ちゃんと紹介してくれている!」と嬉しくなる一方で、リポジトリの発展に関わる一員としての自覚がより一層強まりました。
まとめ
今回は私がエンジニア人生で初めてOSSコントリビュートした話とAIチームの文化についてご紹介しました。 みなさんのOSSコントリビュートデビューのきっかけにしていただけたり、AIチームの温かい文化を少しでも感じていただけたら嬉しいです。
We are hiring !!
エムスリーでは絶賛エンジニアを募集中です! OSSコントリビュートに限らずさまざまなことにチャレンジしてみたい方は是非ご応募ください!