Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

AIと一緒にOSSに貢献してみた話(Dataform編)

こんにちは。Data Intelligence Engineering Unit Master Dataグループの上野です。

朝晩の冷え込みがいっそう厳しくなり、冬の訪れを感じる季節になりました。
皆さまいかがお過ごしでしょうか。

今回は、私が取り組んだ「Dataform」へのOSSコントリビュートについてご紹介します。
日々のデータ基盤開発の中で見つけた改善点を、AIツールを活用しながらコミュニティへ還元した取り組みです。

ぜひ最後まで読んでいただければ幸いです。

目次

きっかけ

Master Dataグループでは、その名の通りSansan各プロダクトで利用するマスタデータを構築しています。
マスタデータはGoogle CloudのBigQuery上に構築しており、そのパイプラインの管理にDataformを利用しています。

日々の開発・運用の中で不便に感じていた点の1つが、Assertion機能に関する課題です。
Assertionは、データが期待する条件を満たしているかをテストする機能です。DataformではこのAssertionに失敗すると、それに依存する処理の実行が停止します。
本番データに不正な値が混入するのを防ぐ上では非常に有用な機能ですが、開発・検証環境では厳密なデータチェックを無効にしたいケースも多くあります。

しかし当時、DataformにはAssertionだけを無効化する機能が存在しませんでした。
実現しようとすると、変数フラグを用意し、コードの各所に条件分岐を追加する必要があります。労力・保守コストともに大きく、大規模なコードベースでは現実的ではありません。

そんな折、DataformのGitHubリポジトリを確認したところ、同じ課題意識を持つ方がちょうどIssueを起票しているのを見つけました。

以前の私であれば、「このIssueが解決されるまで待とう」と考えていたかもしれません。
しかし今回は、思い切って自分でOSSにコミットしてみることを決意しました。
その背景には、近年の AIエージェントの進化 があります。

普段からAIエージェントを活用して開発しており、生成されるコードの精度やコンテキストの理解力に驚かされることが増えました。
「AIをうまく使えば、複雑なOSSの構造理解や実装にも自分の力で挑戦できるのではないか」と感じたことが、大きな後押しになりました。

利用ツール

本ブログで紹介する開発にはCursorを利用しました。使用モデルはClaude 4 Sonnetです。

Sansanでは「AIファースト」を掲げ、AIを活用したコード生成を推進しています。
社内制度としてCursorやClaude Codeなどのツールを会社負担で利用でき*1、私もこの制度を活用しています。
私は業務では主にCursorを利用しているため、本ブログでの開発も同ツールを用いて行いました。
Claude 4 Sonnetについては、普段の業務を通して最も精度が高く安定していると感じているため選択しています。

本記事では、実際に使用したプロンプトを一部紹介していますが、いずれもまだ改善の余地がある内容です。
「これくらいの指示でも十分に実装が進められるのか」といった視点で、温かく読んでいただければ幸いです。

実装

実装①:AIによる初期実装の試み

まずは、DataformのGitHubリポジトリをフォークし、Contribute関連のドキュメントに一通り目を通しました。
準備が整ったところでさっそく実装に取りかかろうとしたのですが、いざ始めてみるとどこからコードを読み進めればいいのかがわかりません。

そこで、まずは上で挙げたIssueをCursorに渡し、思い切って実装をAIに丸投げしてみることにしました。
これは、おおよその修正箇所をつかむことを目的としつつ、「もしかしたら一発で完璧な実装を出してくれるのでは?」という、ほんの少しの期待も込めた試みです。

以下が、実際に入力したプロンプトです。

次のGitHub Issue(https://github.com/dataform-co/dataform/issues/1984)を読み、そこで議論されている課題や要望を正確に把握してください。  
そのうえで、Issueの内容を踏まえて必要な機能をDataformに実装するコードを作成してください。  

まさに“AIに丸投げ”といった内容ですが、このプロンプトを基にAIがコードを生成してくれました(詳細は後述します)。
もちろん、この時点ではそのコードが正しいかどうかはまだわかりません。

まずはcontributing.mdに記載されていた手順に従い、テストを実行してみます。
DataformではBazelというビルドツールを利用しています。
BazelはGoogleが社内で自社プロダクトのビルドに使っていたツール「Blaze」をベースとしたビルドツールです。キャッシュと並列処理によって大規模プロジェクトでも高速かつ再現性のあるビルドを実現できます。

bazel test //core/...

テストの結果は…失敗。
どうやら何らかの実装不備があったようです。

とはいえ、この段階で修正に関係しそうなコード箇所のあたりは見えてきました。
次は、AIが生成したコードの周辺を読み解きながら、問題の原因を探っていこうと思います。

実装②:生成コードを通じてコンパイル処理を理解する

AIが生成したコードの内容を理解するために、まず処理の全体像を把握することから始めました。
最初に、Dataformのコンパイル処理がどのように動作しているのかをAIに尋ねてみます。

DataformのCLIで`dataform compile`コマンドを実行した際に、
実際にコンパイル結果が生成されるまで、内部でどのような処理が行われているかを、図解を交えて詳しく説明してください。

AIの回答から、処理の流れはおおよそ次のように整理できました。

Dataformのコンパイル処理フロー

このフローのうち、最初の実装で修正が加えられていたのは赤で示した部分です。
その中でも特に重要そうだったActionsSessionについて、さらに掘り下げてみます。

「Actions」と「Session」まわりの実装を読み解き、実際のコードを根拠に動作や役割について説明してください。

AIの回答の一部を抜粋すると、次のような内容でした。

1. Session:全体の司令塔として機能
   - プロジェクト設定の管理
   - グローバルAPIの提供
   - Actionsの生成と管理
   - 依存関係の解決
   - エラー管理

2. Actions:具体的なデータ処理タスクを表現
   - ActionBuilderを基底クラスとする階層構造
   - 各Actionタイプが特定の処理(テーブル作成、操作実行など)を担当
   - Sessionを通じて他のActionsとの依存関係を管理

3. 統合メカニズム:
   - SQLXファイル → `sqlxAction()` → 適切なActionクラス生成
   - JavaScript API → `publish()`, `operate()` など → Actionクラス生成
   - `compile()` → 全Actionsを分類してCompiledGraphを生成

これらを基に、実際のコードを少し読み進めてみたところ、core処理の全体像が次第に見えてきました。
処理の流れを整理すると、概ね次のようになります。

  1. ファイルの解析(Compiler処理)
    compilersによって、拡張子ごとにソースファイルの字句解析が行われ、各ファイルに定義された設定値や依存関係などのメタ情報が抽出されます。

  2. Actionの生成
    抽出した設定値を基に、Actionが生成されます。
    Actionは「1つのテーブルを作成する」「1つのオペレーションを実行する」といった単一の処理単位をクラスとして表現します。

  3. Sessionによる管理と登録
    Sessionはコンパイル全体の状態を管理する役割を持ち、生成されたActionをactionsプロパティ(配列)に順次pushしていきます。
    これにより、すべてのActionがSession上で統一的に管理されます。

  4. 依存関係の解決とコンパイル
    Sessionのcompile()が実行されると、actions配列内の依存関係が解析・解決されます。
    最終的に、これらのAction情報を基にCompiledGraphが生成され、そこから実際のSQLやmanifestが出力される、という仕組みです。

ある程度処理の流れを理解したところで、最初にCursorが生成したコードを再度確認してみました。
修正が加えられていたのは、上で説明した「生成されたActionをactionsプロパティにpushしていく部分」です。
その変更内容は、「Assertionを無効化するフラグが有効な場合は、AssertionのActionをpushしない」というものでした。

一見すると正しそうな実装ですが、テストが失敗した理由は依存関係の解決に失敗したためです。
例えば、Assertionを持つテーブルを定義した場合、Table Actionはpushされる一方で、 そのテーブルが依存しているAssertion Actionはpushされません。
結果として、Table Actionの参照する依存先が存在せず、依存関係の解決に失敗してしまうというわけです。

問題の原因は判明しましたが、解決するには依存関係まわりの処理を見直す必要があります。
この領域のコードは構造が複雑で、考慮すべき条件も多そうです。
ここから先、少し雲行きが怪しくなってきました。

実装③:実装上の課題と正しいアプローチの発見

コードリーディングを続けていると、次のような記述を見つけました。

if (config.disabled) {
  this.disabled();
}

これはAssertion Actionクラスのコンストラクタ内にある処理で、ファイル側でdisabledが指定されている場合、そのActionを無効化状態として生成するというものです。

このdisabledプロパティを活用するのが、今回の実装で求められていた正しいアプローチでした。
オプション名から考えれば確かに自明ではあるのですが、最初にAIが生成した実装に引っ張られ、完全に見落としていたのです。

AIエージェントを使った開発では、こうした“思い込みのトラップ”に時折遭遇します。
だからこそ、AIを頼りにしすぎず、自分自身がコードの意味を理解した上で活用する姿勢が重要だと改めて感じました。

今回のケースでは、AIを使ってコード全体の構造を読み解いたことで、結果的にこの記述へとたどり着き、正しい修正方法を導き出すことができました。
遠回りではありましたが、AIを使った探索的な学習プロセスとしては非常に有意義だったと思います。

実装方針が明確になったため、Cursorによる初期実装を一度すべて削除し、disabledを活用して実装し直しました。
最終的に提出した修正版の実装は、次の通りです。

if (config.disabled || session.projectConfig.disableAssertions) {
  this.disabled();
}

実にシンプルな修正ですが、ここにたどり着くまでに半日ほどかかりました。
そして、この変更を適用した状態で再びテストを実行したところ、すべての単体テストが無事パス。

ようやく、レビューを依頼できる段階に到達しました。

実装④:レビューと統合テストによる完成

変更が完了したので、まずはPR(Pull Request)を作成します。
Descriptionなどの記述も必要になりますが、ここもAIに任せてみました。

Gitの差分から修正内容を要約させ、過去のPRのフォーマットに沿って文章を生成するように指示します。
出力された結果を基に、AI特有の不自然な表現を削り、人が書いたような自然な文章へと整えました。


レビューを依頼したところ、実装自体がシンプルだったため、指摘は主にテストに関する軽微な内容でした。
しかし、それらへの対応を進めている中で、次のようなコメントをいただきました。

These tests seem to be failing at the moment.
訳:これらのテストが現在失敗しているようです。

「これらのテスト」とはcli/index_test.tsを指しており、これはCLIのIntegration Test(統合テスト)です。
Dataformでは、コミッターによってこの統合テストがトリガーされ、テストがパスしない限りマージできない仕組みになっています。

私の確認不足で、手元では単体テストしか実行しておらず、統合テストを動かしていませんでした。
原因を確かめるためにローカル環境でテストを実行してみたところ、実装上の不具合によるエラーではなく、認証エラーが発生しました。

contributing.mdを再度確認すると、次の記述を見つけました。

If you need to run integration tests, that rely on encrypted secrets, please get in touch with the team.
訳:暗号化されたシークレットに依存する統合テストを実行する必要がある場合は、チームまでご連絡ください。

つまり、統合テストをローカルで実行するには、チームから暗号化されたシークレット情報を共有してもらう必要があるということです。
そこで、記載されていたメールアドレス宛に連絡しましたが、残念ながら返信は得られませんでした。
そこでGitHub上のコメントで直接問い合わせたところ、次のような丁寧な返答をいただきました。

Sadly we won't be able to share these credentials outside of our org.

I'm sorry for this inconvenience, here is the output of a failing test. If you still want to run these tests, I'd recommend overriding these credentials with your own (you may also need to override dataform-open-source with your own GCP project). If you manage to do it, feel free to update our guidance with instructions.
訳:
残念ながら、これらの認証情報は当社組織外で共有できません。

ご不便をおかけし申し訳ありません。以下に失敗したテストの出力を示します。これらのテストを実行したい場合は、ご自身の認証情報(必要に応じてdataform-open-sourceを自身のGCPプロジェクトに置き換える必要があります)を使用することをお勧めします。もし実行できた場合は、その手順をガイドラインに追記していただけると助かります。

つまり、自分のGCPプロジェクトで統合テストを実行し、その手順をドキュメントに追記してほしいという提案でした。
方針が明確になったので、まず既存の認証処理を調べてみることにしました。


BigQuery認証の仕組みを調査

AIに次の質問を投げ、CLI統合テストにおけるBigQuery認証の仕組みを確認しました。

CLIのインテグレーションテストにおいて、BigQueryへの接続がどのように実装・実行されているか。主に認証の観点から、詳しく説明してください。

回答を要約すると、Dataformでは次の手順で認証していることがわかりました。

  1. BigQueryの認証情報をGoogle Cloud Key Management Service(KMS)で暗号化したファイルをリポジトリに保持。
  2. テスト実行時、Bazelがgcloud_secretルールを実行。
  3. Google Cloud KMSで復号化し、bigquery.jsonを生成。
  4. 生成されたbigquery.jsonをテスト内で利用し、BigQuery認証を実施。

さらに、レポジトリにはupdate_test_credentialsというスクリプトがあり、 暗号化前のbigquery.jsonが次の形式で作成されていることも確認できました。

cat <<EOF > "${BIGQUERY_JSON_PATH}"
{
    "projectId": "dataform-open-source",
    "credentials": $(jq -Rsa < ${SECRET_JSON_PATH}),
    "location": "US"
}
EOF

独自の認証情報でテスト実行

上記を踏まえ、自分のGCPプロジェクトのサービスアカウント情報を使って、同等の認証ファイルを作成し、テストで利用できるように修正しました。
また、将来的に他の環境でも容易に切り替えられるよう、認証先を定数化して管理しやすくしています。

修正後にローカル環境で再度テストを実行したところ、想定どおり統合テストを動かすことができました。
これにより、レビューで指摘されていた統合テストの失敗原因を特定し、修正できました。

そして最後にもう一度テストを走らせると、すべてのテストが無事パス。
統合テスト環境の構築から修正まで、すべてが想定どおりに完了しました。

最後に、再現手順をcontributing.mdに追記し、作業を完了しました。


追加した手順に対する修正依頼もなく、PRはスムーズに承認されました。

Thank you for this contribution!

長い道のりでしたが、AIとともに進めた初めてのOSSコントリビュートが、ついに完了しました。

その後

レビュー対応も無事に完了し、Pull Requestはマージされました。
そして、追加した機能はv3.0.36として正式にリリースされました。

自分の開発した機能が、世界中のユーザーに利用される可能性があると思うと、少し不思議な気持ちになります。
Master Dataグループでもすぐにバージョンを更新し、チーム内で展開しました。
早速メンバーが活用してくれており、「やってよかった」と心から感じました。

チームメンバーからの声

また、チームではDataform REST APIも利用しており、そちらでもAssertionを無効化できるようにしようと考えました。
しかし、REST API側が今回追加した機能にまだ対応していませんでした。
REST API部分はOSSではないため、自分で修正できず、GoogleのIssue Trackerにリクエストを起票しました。

今後、REST APIにもこの機能が反映されることを期待しています。

最後に

今回は、Cursorを活用してDataform OSSにコントリビュートした経験について紹介しました。

OSSへのコミットは本当に良い経験でした。
技術力の向上や実績づくりといった面ももちろんありますが、それ以上に大きかったのは、「必要な機能がなければ自分で作る」という選択肢が持てるようになったことです。
これは、開発者としての視野を大きく広げてくれました。

そして、AIの進化が開発のあり方を大きく変えつつあることを強く感じました。
AIのおかげで、一人でもまるでペアプロやモブプロのように議論しながら開発を進められる時代になりました。
AIと共に試行錯誤し、理解を深めながら取り組んだ今回のOSSコミットは、とても新鮮で楽しいものでした。

次は、もう少し規模の大きい改善や新機能追加にも挑戦してみたいと思います。

最後までお読みいただき、ありがとうございました。

Sansan技術本部ではカジュアル面談を実施しています

Sansan技術本部では中途の方向けにカジュアル面談を実施しています。Sansan技術本部での働き方、仕事の魅力について、現役エンジニアの視点からお話しします。「実際に働く人の話を直接聞きたい」「どんな人が働いているのかを事前に知っておきたい」とお考えの方は、ぜひエントリーをご検討ください。

*1:2025年11月時点。

© Sansan, Inc.