Gaudiy Tech Blog

Gaudiyの技術、開発組織、カルチャーについてお伝えするブログです

Kubernetes初学者が担当したGKE移行プロセスの全貌

はじめまして。Gaudiyでエンジニアをしているあんどう(@Andoobomber)です。

クラウドネイティブ全盛の世の波に乗り、この度 Gaudiy では Cloud Run から Google Kubernetes Engine (GKE) への移行を行いました。

この記事では、その移行プロセスの全体像を共有し、得られた教訓と今後の展望を探ってみたいと思います。

1. Before After: 移行の概観

1-1. Before

Before

以前までの構成は、

  • マイクロサービスの実行環境として Cloud Run を使用
  • 監視ツールにはGCPの Cloud Monitoring, Cloud Logging, Cloud Trace を使用
  • Pub/Sub→マイクロサービス間に API Gateway を経由
  • アプリケーションのデプロイは Github Actions が行う

といった感じでした。

1-2. After

現在の構成は、

  • マイクロサービスの実行環境としてGKEを採用。1つの Cluster 上で全てのサービスを動かしています。
  • 監視ツールにはDatadogを採用し、OpenTelemetry Collector経由でテレメトリーデータを流しています。
  • Pub/Sub→マイクロサービス間にESPv2を経由。
  • アプリケーションのデプロイはArgo CDが行い、Github ActionsArtifact Registryに push した Image を Argo CD Image Updater が Watch し、Cluster にデプロイしています。

といった感じに変わりました。

2. なぜGKE環境に移行したのか

移行に至った経緯は様々あるのですが、主に↓の3つが理由です。

  • クラウドネイティブな技術選択をしたい
    • CNCF Projectsをはじめとする、コンテナ技術の最新トレンドを採択したい
    • Kubernetes Controller で自由に機能拡張できる
  • Ops 起因で重要な目的があった
    • これが大きな理由だったのですが、機密情報に当たるので申し訳ないですが詳細は伏せさせていただきます
  • 機械学習関連でマシンリソースを選びたかった
    • Gaudiyではレコメンドエンジンなど、AIにも注力しており、通常サーバーとは異なるマシンリソースが必要になった

3. 移行のプロセス

普通なら移行計画を立ててKubernetes(k8s)に移行していくところですが、僕はk8s未経験であり前提知識も全くなかったので、k8sを学ぶことから始めていきました。

3-1. Kubernetesを学ぶ (1週間: 2023/10/01~)

Kubernetes完全ガイド 第2版を読んでサンプルアプリを作ってました。これが無かったら移行完了までのスケジュールは大きく変わっていたと思うくらい良書でした。

この時に、DeploymentやServiceなどのk8sオブジェクトを理解するとともに、ライフサイクルやエコシステムなども学んでいきました。

3-2. Dev on GKE環境作成 (2-3週間)

FanlinkのアーキテクチャはFE → Load Balancer → BFF → BE(micro services)という構成になっており、今回は Load Balancer 以降を GKE に全く同じ環境を作成して、FEの向き先を変えて移行することにしました。

GKE環境を作る際に様々な技術を使用しましたが、1つずつ書いていくと書ききれないので、以下にやったことを抜粋して記載します。詳細は割愛しますが、後に別記事としていくつか公開する予定です。

  • Cluster の作成
  • 各マイクロサービスのKubernetesオブジェクト定義
    • サービス: 27個、cronjob: 8個ほどありました。
  • External Secrets の使用
    • Secret Managerを参照するため使用。
  • OpenTelemetry Collector の使用
    • テレメトリーデータをOpenTelemetry Collectorに集約させて、まとめて Datadog にexportしています。
  • Gateway API の使用
    • つい先日GAとなったKubernetes Gateway API を使って、Load Balancerを管理しています。
  • ESPv2 の使用
    • REST から gRPC へのトランスコーディングとして、Envoy ベースの ESPv2 を使用しています。
  • Argo CD の導入
    • k8s リソース管理及びCDとして、Argo CD を採択しました。
  • etc…

3-3. Staging on GKE環境作成 (2日)

基本的には環境変数の変更で、Devで作成したyamlをStagingも同じように作っていくだけなのでそこまで難しくはありませんでした。

この時、開発チームへGKE環境に触ってもらう・受け入れテストのお願いをアナウンスしました。

3-4. Private Clusterへの移行 (1-2週間)

今まではデフォルトの設定で Cluster を作成していましたが、Private Cluster にすることでコントロールプレーンとノードに対して外部アドレスを割り当てなくする(≒よりセキュアにする)ことができると知り、Stating/Prod 環境は Private Cluster で建てることにしました。

  • GCPリソースのTerraform
    • GKE Cluster
    • Network / Subnetwork
    • Firewall
    • etc…
  • Tailscale Operator 導入
    • Private Cluster の操作を Tailscale Network 経由で行うようにしました。
  • Private Clusterでの環境再作成
    • コントロールプレーンのパブリックエンドポイントアクセスを無効にして、よりセキュアな Clusterへと変更しました。

3-5. Prod on GKE環境作成 (2日)

こちらもStaging同様にyamlを複製し、Prod用の環境変数を割り当てる程度なのでそこまで難しくはありませんでした。

ただし、cron処理やoutbox patternが存在したので現行の環境に影響を与えないように気をつけながら作業を行っていきました。

3-6. 負荷試験・受け入れテスト (1週間)

Staging環境を作成した段階で、開発チームへのアナウンスや負荷試験をProd環境作成と並行して行っていきました。

最後の大詰めとして以下のような作業をコツコツと進めていきました。

  • バグの解消
    • 環境変数や外部サービスとの通信部分で特にバグが起きました…
  • Pod/Nodeリソース・スケール値の設定
    • Grafana k6で負荷試験をして、想定負荷に必要なリソースを計算しました。幸い、瞬間的に同時アクセスが大量に走るような複雑なサービスではないので、ある程度の負荷を余裕もって耐えれる位の設定値にしました。
    • ただ、それでも単一Podだと落ちた時が怖かったので複数Podをminimumとし、負荷試験に耐えられる位をmaxとして設定しました。

3-7. リリース(2023/12/04)

2023/12/04の深夜にCloud Run環境からGKE環境にSwitchしました。移行後から1週間くらいは異常が起きないかモニタリングを続けましたが、特段大きな問題はなくアプリケーションが稼働していたのでホッとしました。

4. 移行後の感想

4-1. 良かった点

  • おおよそ2ヶ月でk8s環境へ移行できた。
    • 経験者の知見もいただきながらですが、初学者ながらわりと順調に進めることができたと思います。
  • 最新のクラウドネイティブ技術への追従ができる。
    • CNCF Projectsに登録されているプロジェクトを見て活かせそうなツールがないかみるのが最近楽しいです。
  • リソースの調整
    • 機械学習系のマシンが使えるの嬉しい。
  • レスポンスタイム向上
    • 全体約200msほど
  • Datadogに移行したこと
    • 高機能でかなり使いやすいです。今後のSRE業務に拍車がかかりそうです。

4-2. 課題点

  • k8sの学習コストが高い
    • k8sへの完全移行は達成したものの、k8s APIやらeBPFなど学ぶことがいっぱい。まだまだこれからだなと感じています。
  • 運用コストも高い
    • 学習コストに近いですが、定期的なk8sのアップデートやCSP(クラウドサービスプロバイダ)の知識がないとアプリケーションを落としかねないので、運用は簡単とは言えなさそうです。
    • また、適切なリソース設定をしないと費用面でも無駄なコストがかかってしまいます。

5. 今後の展望

現在は最小構成のk8s環境で動いているので、様々なクラウドネイティブ技術を積極的に試していきたいと考えています。

  • Ciliumの導入
  • eBPFの調査
  • Flux (GitOps)
  • Kubernetes Custom Controller 作成
  • Config Connector

6. 結論

Kubernetes移行はかなり大変でしたが、Gaudiyのビジョンに向けたソリューションと技術的な野心を反映した、意義深い変革の一歩だったと思います。

クラウドネイティブ技術の進化に伴い、より良いサービスとソリューションを提供するために、今後もアップデートを続けていきたいと考えています。

この領域に知見のある方や、Gaudiyでのプロダクト開発に興味のある方がいたらぜひお話ししましょう!

site.gaudiy.com

site.gaudiy.com

Authlete を活用して OAuth 認可サーバの構築期間を短縮した

こんにちは、Gaudiyでソフトウェアエンジニアを担当しているsato(@yusukesatoo06)です。

弊社が提供するファンコミュニティプラットフォーム「Gaudiy Fanlink」において、外部サービスにAPI提供をする必要があったことから、外部連携について色々と調べて実装しました。

そこで今回は、調査からサーバ構築までのプロセスと、そこで得た学びや気づきを共有できればと思います。

1. OAuthとは

1-1. OAuthの概要

OAuthとは、Webやモバイルアプリケーションなどのクライアントアプリが、ユーザーに代わって他のウェブアプリがホストしているリソースにアクセスできるよう設計されたプロトコルです。(みなさんご存知かもしれないですが...)
参考) OAuth 2.0 とは何か、どのように役立つのか? - Auth0

OAuthを使用することで、ユーザーはクライアントアプリに対して、自身のID/パスワードを直接提供せず、他サービスとの連携を可能にします。そのため、複数のサービス間で連携を行うために非常に重要な技術となっています。

※OAuthが解決する課題については、LayerXさんの記事が参考になります。

1-2. OAuthのフロー

OAuthには認可フローがいくつか存在し、大きく以下の4つで構成されます。

  • 認可コード
  • インプリシット
  • リソースオーナー・パスワード・クレデンシャルズ
  • クライアント・クレデンシャルズ

それぞれのフローには、異なる用途やセキュリティ要件があるため、適切なフローを選択する必要があります。ここに関しては、以下の記事が詳しいです。

kb.authlete.com

僕たちは、上記記事でも推奨されている認可コード + PKCE(※)パターンを採用しました。

※ 認可コード横取り攻撃に対する対策
参考) Proof Key for Code Exchange (RFC 7636) - Authlete

2. OAuthが必要な背景

2-1. 外部サービス連携

今回、OAuthが必要になった背景として、外部サービスからGaudiyのAPIをコールしたいという要求がありました。また実装に関しても、1, 2ヶ月程度で行う必要があり、かなりタイトなスケジュール前提でのAPI連携となりました。

(※ ただしビジネス的な要件でAPI連携が後ろ倒しになったため、まだ実運用には至っていません)

API連携を行う手段はいくつかあります。

  1. APIキーでの連携
  2. 独自方式での連携
  3. OAuthでの連携 など

今回は工期の関係や、後述する少し特殊な環境下での連携だったため、最初は1か2を選択しようと考えていました。

しかし最終的には、3のOAuth形式を採用することとなりました。その主な理由は以下です。

  • クライアントサイドの実装も独自となり、エコシステムを利用できない
  • 今後、別のサービスと連携する際の拡張性が乏しい

2-2. 他の連携方式との比較

1. APIキーでの連携

まず最初に検討にあがったのがAPIキーでの連携方式でした。

こちらはIP制限等と併用することで、一定のセキュリティを担保することができるかつ工数のかからない方式となります。

ただし今回、Fanlink APIをコールするクライアントが、サーバサイドだけではなくクライアントアプリも含まれる前提でした。そのため、早々に本手段は選択できないことが決定しました。

2. 独自方式での連携

前述した通り、今回の連携環境が少し特殊な関係で、独自方式の連携を検討していました。

具体的に説明すると、Fanlinkと連携サービスが同一のIdPとOIDC連携をしており、同じIdPのアカウントで作られたサービス間を連携する環境でした。

そのため、IdPで発行されたID Tokenを流用することでサービス間の連携ができないかを検討していました。

一般的な方式ではないですが、工期を考えるとOAuthをゼロから作ることが難しいため、本方式を検討しました。またセキュリティの監査会社に相談した結果、セキュリティホールとなる部分もありませんでした。ただし前述の通り、クライアントサイドも独自実装となるため、本手段も選択しませんでした。

そのため、僕たちは腹を括って短期間でOAuthの仕組みを構築することにしました。

3. OAuthの提供

3-1. 提供方式

短期間でOAuthサーバを提供する必要があるため、まずはどういった方式で提供できるかを検討しました。

その中で提供方式は大きく3つ出てきました。

  • フルマネージド
    • サービスプロバイダー側で提供されるOAuthサービスを利用する方式
    • 例) Auth0
  • ハイブリッド
    • サービスプロバイダー側で提供されるAPI群を利用し、自社で認可サーバを構築し提供する方式
    • 例) Authlete
  • フルスクラッチ
    • ライブラリ等を用いて、自社で認可サーバを0から構築し提供する
    • 例) fosite, hydra など

それぞれ以下のPros/Consがありました。 (Gaudiyの開発環境に依存する理由も含まれます)

サービス Pros Cons
フルマネージド 導入までの期間が圧倒的に短い。フルマネージドでの提供のため運用工数がかからない。Auth0等であればグローバルでの実績があり、セキュリティ面の担保もされている Firebase Authをすでに導入しており、IdP機能が既存サービスと競合してしまう。
ハイブリッド OAuth/OIDCに特化したサービスで、既存のIdPと競合しない。フルスクラッチに比べ運用工数がかからない。コアな部分はAPIとして提供されており、セキュリティ監査等をクリアした機能を利用できる。 フルマネージドに比べ、一定の開発/運用工数がかかってしまう。
フルスクラッチ フルマネージド / ハイブリッドと比較し、ランニングコストがサーバ費用のみ。 開発/運用工数がかなりかかる。セキュリティ監査など周辺工数/コストもかなりかかる。実装に問題があった際に、会社として大きなリスクを抱える。

3-2. 今回の選定方式

今回は前述した3つの方式のうちハイブリッド型を選択し、サービスとしてはAuthleteを採用しました。

すでに導入しているFirebase Authと競合しない点、フルスクラッチに比べると開発工期を大きく圧縮できる点が理由となります。

また開発者向けのドキュメントが充実していることや、Goで利用するライブラリも用意されていた点も決め手となりました。

4. OAuthサーバの構築

4-1. Authleteについて

では実際に、OAuthサーバを構築した際の内容に移ろうと思います。

本来であればここで苦労話を記載したいのですが、Authleteを活用したこともあり、1 ~ 2ヶ月の工期に対して意外とすんなり構築できました笑

今回採用したAuthleteは、RFCに準拠するエンドポイントをAPIとして提供していて、利用者はクライアントからのリクエストをバイパスするのみでOAuthサーバを提供できるサービスとなっています。 (詳しくはサービス概要をご覧ください)

https://www.authlete.com/ja/developers/overview/

そのため、まずは今回必要なエンドポイント群を整理するところから始めました。

4-2. 必要なエンドポイント

今回のOAuthサーバの提供にあたり、外部向けには大きく以下のエンドポイントを用意しました。

  • 認可エンドポイント
  • トークンエンドポイント
  • リソースサーバエンドポイント

それぞれ簡単に説明していきます。

認可エンドポイント

認可エンドポイントは、連携するサービスの認証/認可処理を開始するためのエンドポイントです。一般的には連携サービスの認証画面でログイン処理を行い、OAuthの処理を開始した後に、指定したリダイレクトURLが認可コードを付与された状態で戻ってきます。

仕様についてはRFC6749 3.1に仕様が記述されており、わりと柔軟に実装することができます。

例えばパスについては、フラグメントを含めなければどのような値でも利用することができます。

詳しい仕様はAuthleteさんの記事が分かりやすいです。

トークンエンドポイント

トークンエンドポイントでは、認可エンドポイントで取得した認可コードを用いてリクエストに利用するトークンを発行します。

またリフレッシュトークンを利用してリクエストすることで、トークンのリフレッシュも行うことが可能です。

仕様についてはRFC6749 3.2に記述されています。

リソースサーバエンドポイント

リソースサーバエンドポイントは、実際に認可されたリソースにアクセスするためのエンドポイントで、提供機能に応じて用意するエンドポイントとなります。

トークンエンドポイントで発行したトークンを用いてアクセスを行い、スコープに準拠したリソースであればOAuth連携したサービスから情報取得することができます。

上記以外にも、内部で利用するためのエンドポイントをいくつか用意しておりますが、今回は割愛させていただきます。

4-3. システム構成

今回、OAuthサーバを構築した際のシステム構成は以下です。

OAuthはHTTP/1.1をベースにしたプロトコルである一方で、GaudiyのバックエンドエコシステムがgRPCベースの構成となっているため、以下の形で実現しました。

構成図の通りではありますが、ポイントは以下です。

  • API Gatewayを構築し、OAuthに関する外部通信は基本API Gatewayでハンドリングすることで、プロトコル準拠の通信を実現
  • 後続サービスとの通信に関してはgRPCベースで通信を行い、Gaudiyのエコシステムを流用する
  • Authleteとの通信は基本的にパススルー方式とし、クライアントからのリクエストをほぼ加工せず送信することで、レスポンスも可能な範囲でそのままクライアント側に返却する

また外部公開するURL仕様やバージョニングも悩む部分が多かったです。

大手サービスが提供しているエンドポイントを調査すると以下のような仕様でした。

  1. Google

     https://accounts.google.com/o/oauth2/vX/auth
    
  2. Facebook

     https://www.facebook.com/vXX.X/dialog/oauth
    
  3. GitHub

     https://github.com/login/oauth/authorize
    
  4. Twitter

     https://api.twitter.com/oauth/authorize
    

上記のうちGoogleの形式を参考に、僕たちは以下のような設計にしました。

https://<domain>/oauth/vX/<method or resource>
例) https://<domain>/oauth/v1/authorize

API仕様が変わるケースは少ないと思いますが、外部連携を行う前提に立つとクライアント側との調整/修正が必要なため、後方互換性を担保しつつ切り替え可能にしました。

5. 開発を通じて

5-1. 開発を通じた学び/気づき

今回、OAuthサーバというRFCに厳密に準拠すべきサーバを開発して、色々と学び/気付きがありました。

まずRFCの内容が意外と解釈の余地があり、何かしらの指針がないと解釈に差が生まれる恐れがあるなと感じました。そのためAuthleteという、開発するための指針があったのは非常に助かりました。その一方で、RFCに準拠した自前の認可サーバを作る選択肢を取っていたら、途方に暮れる工数がかかっていただろうなと思います。

またこのようなプロトコルを策定した過去の技術者達のすごさを改めて感じました。Webサービスを開発しているとRFCを読む機会は少ないですが、一度RFCに準拠するサービスを作ることは非常に学びが多いなと感じました。

個人/チームとしては、GitHubのやり取りの中でRFCについて触れるなど、開発者としての成長を感じた場面もありました笑

5-2. フルスクラッチとの比較

フルスクラッチで開発した場合を想定し比較すると、半分以下の工数で実装できたのではないかと思います。工数を圧縮できた大きなポイントは以下です。

  • 基本的にAuthleteの設定後、Authlete提供のAPIをコールするだけでよいため実装方針が明確
  • RFCに準拠するために必要となる知識が圧倒的に少ない(とはいえ開発者としては理解しておくべきですが)
  • 自分たちの開発範囲が少ないため、セキュリティ監査などの対象/工数も削減できる
  • シンプルなものであればすぐに構築出来るため、動くものを確認しながら試行錯誤ができる

5-3. 今後の対応

これまでAuthleteを活用したOAuthサーバの構築に触れてきましたが、冒頭でも説明した通り、残念ながら実運用にはまだ至っていません。

本来であればOAuthスコープの設計をAPI毎に行う必要があり、スコープ設計もかなり重たい内容となる想定です。ただし今回は実運用を行うエンドポイントがなくなってしまったため、スコープ設計は後ろ倒しとなりました。

OAuthサーバの実運用を行った際は、再度スコープ周りの知見を共有できればと思います。

6. まとめ

今回はOAuthサーバの実装について、その採用背景から構築方法まで紹介させていただきました。調査した内容や実際に感じたこと含めてまとめてみたので、これからOAuthサーバの実装を検討している人の参考になれば嬉しいです。

また今後の実運用に向けて、OAuthのスコープ設計周りに知見がある人がいたらぜひお話ししてみたいです。

Gaudiyに興味を持っていただいた方は、ぜひ以下のサイトも覗いてみてください!

site.gaudiy.com

不確実性や心理的安全性に向き合い自己組織化するチームを作る実践プラクティス

こんにちは。Gaudiyでソフトウェアエンジニア兼スクラムマスターをしている Namiki ( @ruwatana ) です。

チームが向き合う不確実性が大きいと手戻りが増えて価値提供のリードタイムが遅くなる
チーム内の心理的安全性の低さや認知負荷の高さによってエンゲージメントが低下して従業員がオンボード・定着しにくい
...

などなど、昨今のチーム開発はこうした課題で溢れかえっていることかと思います。

結局のところ、我々は具体的にどんなプラクティスを行うことで、こうした課題を解決できていくのでしょうか?

本稿では、筆者と筆者が4ヶ月ほど前に配属することになったチームがこうした問題に対して執ったアプローチおよびその効果をより具体的に示すことができればと考えています。

プロダクトチーム開発を行う皆様に何かしらの参考になれば幸いです。

1. チーム構成と特性

Gaudiyでは、複数のIP(知的財産)に関するコミュニティプラットフォーム「Fanlink」を提供しています。

service.gaudiy.com

現在、Gaudiyにはプロダクト開発チームは複数存在しますが、筆者が所属するチームは「特定のIP向けの連携機能やエンハンス開発に注力したチーム」となっています。

そのため、下記のような特性があります。

  • IPプロバイダーとの協業をより求められ、チーム開発の中でIPの影響力が大きい
  • 連携機能はリッチで新規の機能開発が求められやすい
  • マイルストーンやスコープが変化しやすい

また、メンバー構成は以下の通りです。

  • 👥 プロダクトマネージャー 1名 (+ UXデザイナー1名も兼任)
  • 👥 UI/UXデザイナー 3名
    • お試し入社者を含む ※
  • 👥 フルサイクルエンジニア 4名 ← 筆者はここに所属しています
    • お試し入社者を含む
  • (その他、BizDev・コミュニティマネージャーとも連携)

※ お試し入社の制度に関しては採用情報をご覧ください
site.gaudiy.com

ピザ2枚ルール🍕にギリギリ収まるような人数構成です(これ以上増えても減ってもバランスが崩れそうなちょうど良い状態です)。

チームの中にデザイナーも所属しており、デザイン(ディスカバリー)と開発(デリバリー)のどちらの進捗も統合的に管理するデュアルトラックアジャイルのような開発手法をとっています。

弊チームだけでなくGaudiyのプロダクト開発チームは基本的にこの構成をとっているのが特徴です。

デュアルトラックな開発チームのプロセス例

2. 特性が生み出しうるリスクや課題

さて、こうした特性が以下のような問題を生み出すと考えられます。

2-1. プレッシャーが高くストレスフルな環境に陥りやすい

IPに密着したチーム特性ということで、特定IP向けのプロダクトが生み出すアウトカムが会社というよりも特にチーム(メンバー)に直結する構造となりえます。

完全内製なプロダクトなどと比較すると、先方と握った納期・スケジュールに間に合わせなければならず、何か問題が発生した場合に社内だけでなくIPにも影響が出てしまいかねないですし、先方の要望に叶った要件を着実にデリバリーしなければならないといった外部からのプレッシャーを比較的受けやすく、一概にはいえませんがストレスフルな環境に陥りやすい構造と考えられます。

2-2. 技術的な複雑性や負債が高くなりやすい

Fanlinkは、マルチテナント(IP)向けのプロダクトですが、IPからはIP特有の要望を受けることも少なくありません(要望をいただくことは率直にありがたいことです)。

その中で、機能追加したものが特定IP専用の機能にならないように極力、汎用的に要件を落とし込むという正解があるようでない難易度の高い判断が求められることもあります。

専用の機能が増えてしまうと、固有のロジックや他チームからしたら知りもしない機能が増え、複雑性や技術的負債が増加してしまいます。もしかすると、他チームが勝手に消しにかかってしまうかもしれません。

2-3. さまざまな不確実性が高い

また、さまざまな不確実性が高いということもいえます。

Gaudiyの推奨図書にもなっている広木大地さんの「エンジニアリング組織論への招待 不確実性に向き合う思考と組織のリファクタリング」によれば、不確実性は大きく下記の3つがあるとされています。例とともに分類します。

  • 目的不確実性
    • プロダクトにおけるマーケットに何が求められているのかの正解が見えない
    • 他の競合サービスなどの取り巻く環境の変化に適応しなければいけない
  • 方法不確実性
    • スケジュールに対して要件が決まりきらずに開発を始めなければならない
    • ある大きな機能を開発中に方針・優先度が変わってペンディング・ピボットする
    • チームにプラクティスが存在しない新しい技術を用いた実現方法を求められる
  • 通信不確実性
    • PdMやデザイナーが考えた要件がデリバリーする際に適切に伝わらず手戻りを起こす
    • お試し入社者が目の前のタスクをこなす上での情報やコンテキストの不足
    • リモート主体の開発で、メンバーに聞きにくい・意見しにくい

3. それぞれの課題に対するアプローチ

ここまでは、特に目新しい情報はあまりなかったと思いますが、もしかすると「うんうん」と頷いていただけた方もいらっしゃるのかなと思います。

ここからは、より具体的なアプローチをご紹介できればと思います。

3-1. チームの成長実感や自己肯定感の醸成

もしかしたら、目的不確実性によって価値提供をしてもマーケットのアウトカムを得られないということもあるかもしれません。

そんなときもプロセスに注目し、人やチームの良かった動きやことに対してねぎらいを与えるきっかけを作ることはとても重要であると考えます。

弊チームでは、1週間1スプリントとしてスクラム開発を行っており、レトロスペクティブ(振り返り)やスプリントレビューを定期的に実施して改善を続けてきましたが、その中のHowに工夫を加えていく動きが加速しているので一部をご紹介します。

3-1-1. レトロスペクティブにSpeed boatを導入

まず、レトロスペクティブでは従来 Win-Learn-Try のようなフォーマットを用いていましたが、割と個人の内省に使われることが多く、チームや他人に対してのフィードバックが欠如してきていました。これではみんなでやる理由があまりありません。また、ずっと同じ手法を使っていると飽きも来てしまうというのもあったかもしれません。

こうした問題に気づき、チームのメンバーが立候補して、レトロスペクティブの改善のオーナーを担ってくださいました。これによって、新フォーマットの Speed boat (国内ではSail boatという方がなじみが多いかもしれません)を行うことになりました。
※ちなみに現在は、また別のフォーマットを試すなど改善がどんどん進んでいます。

Speed boatでは、そのスプリントでのチームの動きを事象別に振り返ります。

テンションが上がったアイスブレイク的な内容(太陽)、チームが進むのに助けになった要因(追い風)、逆に止めた要因(錨)、わかった潜在的なリスク(岩)を書いて内容をシェアし、最後に次に取るべきアクションをボート上に乗せて投票でネクストアクションを決めるというシンプルな内容です。

チームのSpeed boatの様子 ※抽象化してます

これによって、他者の動きを褒めたり助かったなどの声が圧倒的に増えました。また、個人よりもチームに向いた振り返りができるようになり、みんなが納得感のある振り返りの場になったと思います。

自分の成果を自分であげるよりも、他者からフィードバックをもらえる方が自己肯定感を醸成できると思いますし、何より褒められたら嬉しいですよね?

Gaudiyは評価という仕組みがない会社なため、正も負もフィードバックを他者からもらえるシチュエーションが欠如しているという特性もあり、そういった部分にもアプローチできているのかなと考えています。

3-1-2. スプリントレビューにてPdMからの今週のスポットライト選出

スプリントレビューはプロダクトマネージャーが主導していますが、ビジネス周りのメンバーも含めてディスカバリー・デリバリーのスプリント内での成果を共有する場というのが基本的な立ち位置です。

そこに、毎週チームの中で良い動きをしたメンバーにスポットライトを当て、選出の理由とともに発表いただくアジェンダを追加するという動きがありました。

こちらもレトロスペクティブの改善と同様、チームメンバーのエンゲージメントを向上する要因となっていると思います。PdMからの視点をもらえるという意味でも、たとえ選出されなかったとしても選出理由を聞けるだけで学びが大きいなと感じます。

今週のスポットライトの例 ※

※ UNIFORMな人とは弊社のクレド(行動指針)の模範となっている人のことです
note.gaudiy.com

我々は、いつもPdMの方々にスポットライトをあてていただく側なので、この場を借りて弊チームのPdMの皆様にも素晴らしいアクションをとってくださったことを改めて讃えたいと思います。ありがとうございます!

3-2. チームとしての共有知やコンテキストレベルの底上げ

特定のメンバーだけが知っていること、複数人で会話する時に相互のコンテキストの違いによって前提が合わなくてコストがかかるといったことが起きないように、短いスパンでコンテキストの共有を行っていくことが必要と考えています。特にエンジニアは4名在籍しており、ここのコンテキストレベルのズレが大きく進捗にも影響しかねないです。

下記にそれらを実現するアプローチを示します。

3-2-1. SlackのハドルミーティングやGatherに常駐する

弊チームでは地方に住むメンバーもいたりするなどリモートが主体の開発を行っています。そこで、朝から晩まで、基本的にはSlackのハドルミーティングに常駐していつでも口頭での相談や同期ができることを実現しています。

話す時以外は大体ミュート状態にしている運用で、話しかける時に「〇〇さん今いけます?」的な形で質問や相談、ペア作業などがすぐにできるような形にしています。

これには、心理的安全性を高める効果があると考えています。

最近では、試験的に Gather というサービスを用いてバーチャルオフィスを構築し、チームのミーティングなどを全てこのサービス上で行ってみるような取り組みを全社的に行なっています。

隣り合うだけでプライベートの会話空間が作り出せるのでペアプロがしやすかったり、他の人が話している場合は吹き出しマークが出て視覚的にも様子がわかるなど、会話のし始めや既に会話しているところへのジョインのハードルを下げるような狙いもありそうです。

Slackハドルミーティングとの大きな違いとして、後から会話にこっそり入室しても何もフィードバックがない(気付きにくい)ので、リアルタイム空間のような他チームのミーティングの盗み聞きみたいなこともカジュアルにできるのが斬新だなと思います(笑)

Gatherでのチームミーティングの様子
(モザイクやアバターも相まって怪しさMAXですが健全な議論をしています)

3-2-2. QAをチーム全員参加で行う

Gaudiyでは、トランクベースの開発プロセスを取っており、ほぼ毎日リリースを行なっています。そのため、前日の夕方に各チームにてデイリーQAを行って、その日にマージされた改修の品質をE2Eでチェックしています。時間としてはその日のマージした分量にもよりますが、早い場合は5分で終わる場合もあれば、30分以上かかる場合もあります。

弊チームでは基本的には、Pull Requestにスクラムのアイテム(JIRAチケットで管理)を紐づけるような運用となっていて、マージすると自動的にQAレーンに移動するようにしており、チケットに書かれた受け入れ条件に沿ってQAを行うという流れで開発を行っています。

JIRAの自動化設定

一見、エンジニアや受け入れ条件を確認するPO/PdM向けの作業に見られがちですが、ここにはデザイナーのメンバーにも参加してもらっています。

これによって、ディスカバリーとデリバリーの認知の分断を防ぐ役割や、エンジニアが気付きにくいデザイン目線でのデザイン仕様との乖離を検出しフィードバックしてもらえるなどの効果が得られています。

また、エンジニアメンバーが有志で行なったチームのコンテキストとは関係のないバグ修正や改善などに関しても、レビューからQAをチームとして担当・チケット管理するようにしており、属人化や個人への責任を緩和できるような効果も期待しています。

ちなみにコードレビューにおいても、弊チームではすべての領域の改修に関してチームのエンジニア全員のApproveを原則もらう形としており、チームメンバーが行なった改修内容のコンテキストを必ず得られるように工夫しています。(日課として、溜まったレビューを捌いたり、連携するところから業務が開始します)

3-2-3. チームで判断しきれない問題はチーム外を積極的に頼る

チームのエンジニアメンバーにおいて、どういうアーキテクチャで価値提供するかを調査・検証しレビューするというプロセスがありますが、迷った挙げ句結論がなかなか出なかったり、そもそも判断するための確度が高くないといったことが往々にして起こります。

その場合は、積極的にチーム外を頼るようにしています。
チーム外とは、社内のEnabling Teamなどの他エンジニアはもちろん、外部のコンサルタントなども含んでいます。

例えば、チーム内で認可サーバを実装する必要が出た時は、社内に詳しい知見を持つ人がいなかったため、利用するSaaSの窓口から外部の有識者を紹介していただいて仕様のレビューを受けるなどを行なってきました。

もちろん、チーム内で考えうる方針案を複数用意しつつ比較し、最適と思う案をピックアップするところまでは行い、相手のレビュー負荷を下げてから依頼することを怠ってはいけません。

このような外からの知見を獲得するプロセスを経ることによって、チームの中にも知見やコンテキストが増えていくことを期待しています。

3-3. コンテキストフォーカスによる高速デリバリーの実現

コンテキストのレベルを底上げしたり、共有したりすることも重要ですが、フォーカスすることもとても重要です。

3-3-1. フロー効率とリソース効率を意識して使い分ける

例えば、「エンジニアが4人いるなら、半分ずつに割ってそれぞれで大きなコンテキストを抱えて並行稼働すれば、二つもアウトカムを同時に得られるのでは?」というのは誰しもが考えうるかなと思います。

果たして、本当にそうでしょうか・・・?

その2ラインのレビューやQAは誰がやるのでしょうか?お互いに1人しかレビュアーがいないと質の部分に問題が生じるリスクがあり、結局相互に行わないといけないとなると設計や要件、直面した問題の背景なども十分に理解する必要が出てきてしまいます。
それを知るための時間や不確実性(ここでは先ほど挙げた通信不確実性)が暗黙的には増えてしまうわけです。

また、2つの価値提供がともに2人月ずつかかる見積もりだった場合、どちらの機能も提供まで4人2ラインでの開発では単純計算で1ヶ月かかってしまいます。

こうした半分に分けて生産性をあげようという考え方はリソース効率と呼ばれます。

もちろん適切に使えば、この考え方も大きな成果を挙げられる手法であるといえます。例えば、だれもがやり方を熟知していて誰がやっても一定のスピードでできることであれば、ペア作業で行うよりも、それぞれが分散して並行に着手する方が効率が良いに決まっています。

しかし、実装方法も定まっていない・仕様も詳しく知らないような不確実なものでは、コンテキストレベルを合わせたり、異なる視点を入れながら検討して前に進む方が手戻りも少ない可能性があります。コンテキストスイッチも不要なので、頭がパンクせずにしっかりコトに向き合えるというメリットもあるでしょう。

こうした、一つの物事を優先的に着手から完成まで行う考え方をフロー効率といいます。

例えば先ほどの価値提供の例も、全員で取り掛かったら一つの機能は半月でリリースできるかもしれません。もう半月経つと次の機能を出せて1ヶ月後から見ると価値提供完了のスピード自体は変わらないですが、一つの機能を早く価値提供でき仮説検証できるというアドバンテージが生まれます。

リソース効率とフロー効率

デュアルトラックアジャイルを敷いている我々にはこの早く検証を回せる状態はとても相性が良いと考えています。

リソース効率とフロー効率の両者をケースバイケースで使い分けていくことが、より早いリードタイム(価値提供)を生み出せるといえるのではないでしょうか。

3-3-2. ペア/モブプログラミングをカルチャーとして根付かせる

不確実なものにはフロー効率という考え方が良さそうということが先ほどの結論ではありますが、弊チームでは積極的に2人でのペアプログラミングやさらに多い人数でのモブプログラミング(モブ作業)を行なって手戻りリスクを抑えたり、同時に把握が必要なコンテキストの量を減らしレベルを揃えやすいような取り組みをしています。

プログラミングに精通しない人からすると、ペアプロする(2人で1つのことをやる)わけだから単純に速度も1/2になるのでは?と考えるかもしれませんが、下記の理由で十分に有効であると考えています。

  • ソロだとコーディング時にわからないことが出て、調査に時間がかかるかもしれない
  • 各々が分散し目前のコンテキストに集中した結果、他のメンバーのコンテキストに対してのフォローが行き届かなかったり、あまり理解しないままコードレビューまで問題に気づかず、後から手戻りするコストが発生するかもしれない
  • 従来のペアプロのドライバ・ナビゲータという役割を超えた並行的なペアプロが実現可能になったため、何もしていない時間は実は少ない
  • お試し入社者を置いてけぼりにせず、ハマりやすい罠を共有することで、早期に立ち上がってもらえる

XP(エクストリームプログラミング)などで提唱されている従来のペアプロ手法では、ドライバとナビゲータに分かれて指示を出す人とコーディングする人がそれぞれ存在しました。

同じPC上で一つのエディタを使ってお互いに操作しなければならなかったので必ず待つ人が発生してしまうという物理的な問題もあったと思われます。

しかし現在は、Visual Studio CodeのLive ShareやJetBrains製IDEのCode With Meといった主要なエディタにて、複数のPCから同時並行作業ができるソリューションが登場したことにより、双方が手を止めずに同一のブランチを並行で修正できるようになりました。もちろん、リモートにも対応していて、ホストのローカルホストポートの共有やコマンドラインの実行なども可能になっています。

これを生かし、弊チームでもペアプロ中にある程度のやるべきことだけ最初に認識を合わせたら、あとは別れてコーディングをすることも多いです。例えば、テストケースの名前だけ先に書いてその実装を分担したりするといったイメージです。一人がミーティングになっても、コネクションを繋げておけば残りの一人が作業を続行することも可能です。

頻繁にチーム内でペアプロされている様子

Gaudiyでは、どんどん新しい仲間が増えており、ほとんどのチームがお試し入社のメンバーを迎えながら開発をしています。お試し入社者の中には、特定の技術スタックは強いけど特定のスタックは経験がない・少ないというメンバーもいらっしゃいます(何を隠そう、自分が完全にそれでした)。

そうしたメンバーと作業をする場合は特にペアプロは大きな威力を発揮すると考えています。技術スタックや既存フィーチャーの仕様・コンテキストが不足していると、その時点で心理的安全性がかなり低くなってしまいます(自分も入社時はソロタスクが多く、わからないことを聞くためにも中々人が捕まりにくいなどの問題があり不安なこともありました)。自走ができる状態に持っていくための最短手段としても素晴らしいソリューションだと思います。

一方で、常にサポートが入っている状態だとアウトプットのスピードと質は保証できるかもしれませんが、個人としてのインプットや納得感・達成感は得られにくいといった問題は大いに考えられます。

時には、「一人でトライしてみたい」や、「最近あまり技術的なチャレンジできていない」といった意思やモヤをできるだけ表明しやすくし、他のメンバーはそれを尊重できるような関係性の構築も重要だといえるでしょう。

3-4. USMからバックログアイテムを転用

主にデザイナーが行うディスカバリーの手法としてはリーンXPやMVP仮説検証などを用いており、新たな価値を提供するときはユーザーストーリーマッピングという主にエンドユーザーの行動をベースとした体験を時系列などにまとめたものを制作しています。

現在はオンラインホワイトボードツールのMiroでUSM(ユーザーストーリーマップ)を作っていますが、JIRA連携の機能を利用してディスカバリーフェーズで作成されたユーザーストーリーをそのままデリバリーのバックログアイテムとして転用しています。

Miroで作ったUSMとJIRAチケットを連携し可視化するイメージ

これによって、わざわざスクラムにおけるプロダクトオーナー(PO)がバックログアイテムを作るという作業をスキップできるようになりました。

さらに、USMがユーザの行動ベースかつ比較的粒度自体も小さく設計されているため、チケットの要件記述も非常にシンプル(時にはタイトルで自明なので詳細不要)になり、要件の手戻りもしにくくなりました。

USMの例としては「ログインしていると〇〇が表示される」、「〇〇を押したら〇〇に遷移する」といった非常に小さな粒度となっています。

チーム構成のところでも紹介しましたが、メンバー数も比較的多く、デザインと開発をチーム内で抱えており、IPと密着したチーム特性もあることで、特にPdMの負荷が高くなっており、ビジネスなどのより本質的な仕事にフォーカスできるようにすべく、この手法を取り入れたという背景がありました。

チームの声としては、「チケットがわかりやすくなった」などのポジティブな反応もあったため、やって良かったと思います。

この取り組みに関しては、絶賛PDCA中になりますので、それなりに形になったタイミングでまたその内容を詳しく共有できればと考えています。

4. まとめ

いかがでしたでしょうか。最後に抽象的にはなってしまいますが、まとめです。

  1. チームの取り巻く状況や環境、特性を理解する
  2. 生まれうる/生まれた課題をしっかりと捉える
  3. 課題に対して適切なアプローチと改善のサイクルを早期に回す

この特性や課題の設定は、設定をする人のスキルセット(いわゆるメタ認知と構造化する力)に依存してしまうとは思います。そのため、その設定自体がブレていると、ただ自分がやりたいことを改善しているだけになってしまう可能性があり、本質を解決できていないことがあります。

ブレていないかは、チーム内外のメンバーに雑談ベースでも良いので壁打ちしてもらい、ブラッシュアップするのが良いかなと感じています。

筆者も、チームへのジョイン時に既存メンバーと1on1を設定させていただき、チームが置かれている状況や課題などの解像度をより鮮明にするところからアプローチしました。

この課題設定の確度を上げることが、結果的に短期での解決や改善を生む要因になったのではないかと考察しているので、非常に重要なステップだと考えています。

また、こうした取り組みがどんどん回せているのは、筆者が主導・管理したからというわけでもなく、チーム全体で一人ひとりが問題に向き合い、自律的に改善することができている結果だと考えています。

一人が主導するよりも、このように自己組織化するチームになる方が、並行してたくさんの改善が回せると思うので、チームのカルチャー創りというのも非常に重要かなと思います。

--

以上になります、お読みいただきありがとうございます!

ぜひこの記事に対しても感じたことなどありましたら、率直なフィードバックをいただけますと幸いです 🙏 

site.gaudiy.com

11/16(木)の自社イベントでも開発組織についてお話しするので、ご興味ある方はぜひ!

gaudiy.connpass.com

ウォレットを ERC-4337 の Account Abstraction で実装して感じた課題と展望

こんにちは。ファンと共に時代を進める、Web3スタートアップ Gaudiy で、ブロックチェーン周りの開発をリードしているDoi(@taro_engineer)です。

「2023年は、Web3 のマスアダプションに向けて躍進する年だ」と昨年の後半くらいから言われていましたが、実際に、技術的にも法規制的にも進展があった一年だったと思います。

GaudiyもWeb3のマスアダプションに対して、長らく課題意識を持ってきましたが、今年は事業としても大きな転換を迎える年となりました。(このあたりの Gaudiy の事業背景や変遷は、ぜひ以下の記事をご覧いただければと思います。)

techblog.gaudiy.com

中でも、大きな進展として注目すべきポイントの一つが、ERC-4337 の Audit です。Gaudiy としても Account Abstraction、特に ERC-4337 には注視していたので、大きなニュースでした。

ERC-4337 は Draft ですが、OpenZeppelin による ERC-4337 Core チームのコントラクト Audit のニュースを見たときに、今後 ERC-4337 を開発していく流れを止めることはできないと確信しました。

これを受けて、実際にGaudiy のプロダクトに適用するにはどうするか?を考え、実装してみたため、今回は ERC-4337を実装する上で感じた課題や今後の展望について書いてみたいと思います。

1. Web3のマスアダプションに向けた課題

Web3 ひいてはブロックチェーンの世界に、一般ユーザーも参加してもらうには、まず大きな課題が2つあります。

  • ウォレット(EOA)という新しい概念のアカウントを管理しないといけない
  • ガス代という手数料の存在

それぞれの課題に対して単独での解決策はあり、それらを組み合わせることで、一般的なアプリに慣れているユーザーにもブロックチェーンの世界に足を踏み入れてもらうことはできました。

一方で、完全に自由な活動をユーザーが実現するのは難しく、その解決にはスマートコントラクトのアップデートが必要など、一部のユースケースにしか対応できないという難点もありました。

すべてのオンチェーン活動に対して自由に活動できて、ユーザーが余計な学習を必要としない状態にするには、まだ一歩足らない部分があったように感じます。

2. ERC-4337 とはなにか?

上記の問題を解決するために、Account Abstraction という技術でウォレットを作ることが考えられます。これは、ブロックチェーン上のスマートコントラクトをウォレットとし、EOA と同じ振る舞いができるように、署名とガス代の抽象化を実装するシステム全体のことを指します。

そして ERC-4337 は、Ethereum 系ネットワークのアプリケーションレイヤーで Account Abstraction を実装するための標準規格にあたり、Account Abstraction の最前線といっても過言ではない提案にあたります。

Ethereum 系ネットワークのコントラクト開発で標準規格化していくであろう提案ということは、つまり、どの開発者・プロバイダーでも周辺のエコシステムを利用すれば拡張できる。例えば、 Bundler・Paymaster を切り替えて利用できたりすることが、利点として考えられます。

また、トランザクションの署名に ECDSA 以外を利用するなど、署名の抽象化も実現できるため、今後の課題になるであろう暗号の強度や、署名検証の効率化などに対しても対策ができるのが特徴です。

これ以上の詳しい内容は、他の記事などを探せば出てくるので、ここでは深く言及しないようにします。

ERC-4337 を実装するにあたり、関連記事をひとつだけ紹介すると、個人的には Alchemy が出しているブログを読めば、ERC-4337 がだいたいわかると思っています。

www.alchemy.com

Account Abstraction を実装していく中で発生する課題を解決していき、最終的にERC-4337 の複雑さに行き着くまでのプロセスを紹介してくれているので、各登場人物が何を目的として存在するのか?がよくわかります。

このブログなどを参考に、実際の動きを確かめながら実装を進めました。

3. ERC-4337 実装のポイントと展望

ではここから、実際の ERC-4337 の実装について書いていきます。この章では、大きく分けて以下3つのポイントをご紹介したいと思います。

  1. アップグレードについて
  2. ウォレットのリカバリーについて
  3. 転売の防止について

まず、ERC-4337 を Production レベルで実装する際に、必ず考慮する必要がでてくる “アップグレード”“ウォレットのリカバリー” 。そして、Gaudiyの事業特性上考える必要があった “転売の防止” について言及していきます。

前提としてGaudiyのウォレット構成は、以下のようなイメージ(概念図)となります。

出典:https://speakerdeck.com/winor30/gaudiy-web3-tech?slide=15

簡単に説明すると、コントラクトの Storage に関わるデータは AccountStorage で定義して、ロジックの変更によって collision を引き起こす可能性を減らしています。各ロジックは Manager として定義し、最低限必要な機能を備えました。

SmartAccount というコントラクトが各ロジックを束ねており、これをシングルトンとしてネットワークにデプロイします。

各ユーザーごとのアカウントとして Proxy をデプロイし、ウォレットのアップグレードが必要な場合は、ユーザーからトランザクションを発行してもらい、事前にデプロイした新しいシングルトンコントラクトに向き先を変えてもらいます。

それでは各セクションについて、記載していきたいと思います。

3-1. アップグレードについて

1つめは、アップグレードです。スマートコントラクトの実装において、最も神経を使う部分の1つが Upgradeable なコントラクトの実装だと思っています。

今までの開発であれば、OpenZeppelin のコントラクトでも一般的に提供されている ERC-1967 を利用した Proxy Pattern が、一番メジャーで使用されている選択肢かなと思います。

スマートコントラクトウォレットに関しては、この通例とされる Upgrade とは少し違うアプローチを考えなければいけません。

3-1-1. モジュラーコントラクト

まず ERC-4337 は EIP ではまだ Draft であり、今後仕様が変わる可能性が大きいです。さらに、ユーザーのアカウントという性質上、セキュリティの脆弱性に対する対応や、今後ユーザーが新機能を追加したい/逆に削除したいと感じる可能性なども、NFTなどのコントラクトと比べると高いと考えられます。

そのため、コントラクトを拡張していく考え方をもつ従来の Proxy Pattern では、スケールするウォレットを提供することは難しいと感じました。

この点から、ウォレットの実装でよく見られるのは “モジュール” という概念を用いた実装です。

ざっくりしたイメージで言うと、ERC-2535 の Diamond Proxy の考え方をベースとし、各ロジックを組み替えたり、付け替えたりできるようにモジュール化する手法です。Account Abstraction のパイオニアと呼べる各プロジェクト、例えば Safe や Argent などには、この “モジュール” という要素が組み込まれています。

ERC-4337 の文脈でいくと、ERC-6900 の提案がより標準化に向けたモジューラブルなスマートコントラクトウォレットの実装につながると考えています。これもまだ Draft なので、今も Spec が変わりつつありますが、基本的な考え方はすでに長年 Account Abstraction に取り組んでいるプロジェクトでも利用されていたりします。

また、Alchemy のエンジニアが提案者なので、今後 Alchemy という大きなプラットフォーマーが ERC-6900 の標準化に向けて大きく推進させていったり、Account Abstraction の開発市場を取りに行くような動きをする可能性があると感じています。

3-1-2. どう実装したのか?

開発当初は ERC-6900 をベースにした実装を考えたのですが、実装当時はこの提案が出てからまだ半月も経っておらず、以下の懸念がありました。

  • 研究的な実装を進めたが、大幅に仕様が変更する可能性がある
  • 既に実装されているモジューラブルな実装方法で実装しても、 ERC-6900 の考え方と大幅に違ってくる可能性もある
  • 工数と期日が逼迫している

これらの懸念を考えると、変わらないコアな部分はどこかを見極めて、ポイントだけ押さえることが大事だと判断しました。

その結果、まずは ERC-1967 を応用した Upgrade の方式(結果的には OpenZeppelin v5.0.0 に含まれた ERC-7201 に近い状態になった)で、 Storage をロジックから切り離すように実装することを決めました。今はこのコアな部分のみ実装しつつ、将来的にモジューラブルなコントラクトに移行していきたいと考えています。

この実装自体は Soul Wallet から着想を得ており、開発当時は Soul Wallet チームが GitHub で水面下に開発していることに気づけなかったのですが、現在は ERC-6900 の考え方のサンプルの1つになりえるモジュールの構成になっています。

ethereum-magicians.org

チーム全体としても Empower Day を活用して知識を付けていったり、PoC をつくったりなど、より良い改善に向けた取り組みが現在進行中です。

3-2. ウォレットのリカバリーについて

2つめが、ウォレットのリカバリーです。Account Abstraction を実装する上で、今ではリカバリー手段を実装することはスタンダードになっています。

しかしながら、どの方法を選択すれば提供するユーザーにとってリカバリーが実行しやすいのか?学習コストをかけずにリカバリー手段を設定できるのか?など、実生活に反映させていくには考えねばならない観点が複雑に絡み合っていると思います。

まずは技術的に、ウォレットをリカバリーするための主な手段として以下の2つを考えました。

  • ソーシャルリカバリーを利用したオーナーの変更
  • マルチシグを利用したオーナーの冗長化
3-2-1. ソーシャルリカバリーを利用したオーナーの変更

まず、vitalik が提唱した Social Recovery は最良のソリューションのように聞こえますが、現実的にはどのアプローチをとっても一定のデメリットがあり難しいです。

また、信頼できる家族や友人などの第三者の承認を得る、文字通りソーシャルな関係でリカバリーすることは、一般ユーザーをターゲットにするウォレットでは周りの人も Web3 に精通していない可能性が高く、実現が困難です。そのため、もう少しだけ抽象化した「本人が信頼できる Guardian を用意する・用意させる」という考え方でユーザーに届ける必要があると考えます。

各種方法はありますが、例えば OIDC を利用できる IdP を Guardian とするリカバリーが、ユーザーには一番馴染みがあって、学習コストをかけずに使用できるのではないかという仮説が考えられます。

ただ、これも良い方法に見えますが、以下のデメリットはあると考えました。

  • 公開鍵の変更があった場合、ID トークン検証用のシングルトンコントラクトのデプロイが毎回必要になる
  • IdP が悪意をもてばリカバリーを実行できてしまい、実質セミカストディアルな状態になる
  • この問題を解決しようとすると、よりアーキテクチャが複雑になる

結果的に、現在の知見や開発のためのコストや可逆性、費用対効果を考えると、他の方法で進めるのがよさそうでした。

次に、よりコスト感と見合った現実的なアプローチとして、すでに実装されているソーシャルリカバリーや関連するロジック、Argent の SecurityManager や Safe の OwnerManager などのロジックをベースに、ソーシャルリカバリーを実装することが考えられます。

この方法は現時点においては一番現実的だと思いますが、Guardian は ECDSA で検証ができる署名を作成できる必要があり、リカバリー用の秘密鍵をどこで生成し、保持してもらうか?が課題になります。

これは今までの「EOA をいかに一般ユーザーに対して安全に保管してもらうか?」と同じ課題になり、ブラウザウォレットをこれから作る我々としては悩ましい問題になりました。

3-2-2. マルチシグを利用したオーナーの冗長化

別手段として、マルチシグの仕組みを利用してオーナーを複数用意する方法も当然考えられます。

これはオーナーを増やして 1/n のマルチシグウォレットにすることで、オーナーのどれかを失っても、別のオーナーでウォレットを管理できるようになり、実質ソーシャルリカバリーに近い考え方となるということです。ロジックの複雑さはなく、実装もしやすいように思います。

ただし、複数のオーナーを設定できるようにすると、適切なオーナーを設定してもらいたいがそれを制限することは難しいと考えました。後述しますが、投機目的でウォレットの転売に利用されないか?も考慮すると、すべてのオーナーが「ユーザー自身が管理するものである」ことを検証できる必要があります。

また、ユーザーが学習コストをかけずに用意できて失いにくい方法で、かつウォレットを利用するときの体験も良い方法でオーナーを用意してもらう必要があるのは、変わらずクリアしなければいけない課題だと感じました。

3-2-3. どう実装したのか?

つまり、解決せねばならない主な課題は2点あります。

  • ユーザーに学習コストをかけずにウォレットのオーナー・リカバリーの鍵を生成してもらう
  • リカバリーをするときは適切なウォレットのオーナーにローテーションできる

この2点を現時点で達成するには、ウォレットのオーナーをオフチェーンの MPC で生成するのが適切な方法ではないかと考えました。

まず、ユーザーに今までの体験と変わりなくオーナーを生成してもらうには、ソーシャルログインでウォレットを作れるのが理想的だと考えます。これは弊社のプロダクトである Fanlink のアカウントを作れば、それがウォレットのオーナーになるようにするのがベストな状態だと考えました。

また、オンチェーン上でリカバリーをするとなると、新しいオーナーをユーザーが用意する必要があります。これもユーザーにとっては、私たちから何かしらの機能を提供しないと Metamask などの EOA を用意する必要が出てくるので、オーナーは変えずに同じ Fanlink アカウントでリカバリーができるようにする、要はオフチェーンでリカバリーできるのがベストな状態だと考えました。

これらを踏まえ、現実的に取れる選択肢を考えると、Web3Auth, Lit Protocol が当時は考えられましたが、Node の監査状況やサポート状況などを鑑みて、Web3Auth の tKey MPC を選択しました。

また、リカバリーの鍵を保持してもらう手段としては、リカバリーの鍵をプログラム上で生成して、そのデータを Cloud Storage に保存させることだと考えました。

この方法では仮に Google Drive を保存先と考えると、ユーザーは Google アカウントでアプリにアクセス権を許可すればリカバリーを取れる状態になり、体験としてはソーシャルログインのフローと変わりなくリカバリーを設定できます。

これをオンチェーンの Guardian ではなく、オフチェーンの MPC で分散した鍵をリカバリーとして設定するように考えました。このスキームでソーシャルリカバリーを実装すると、オンチェーンで Guardian を設定・保持してもらうときでも同様の流れで実現できるので、オンチェーンでのリカバリーも対応できます

参考にさせていただいた Argent や World App も同様のアプローチを取っているので、費用対効果が合っているソリューションだと思っています。

ウォレットのオーナーに関して、技術的にベストな選択をするならば、BLS や Schnorr などの集約署名を取り入れたオーナーの生成や WebAuthn を取り入れた 2Factor も導入するなど、実用性がありおもしろそうな領域はあります。

ですが、まずはユーザーに提供して本当に使いやすく Web3 の体験を楽しめるのかを確かめる必要があるため、ウォレットに関する最新技術の投入は段階的に取り組んでいく必要があると感じています。

上記に挙げた方法や理論は、数ある選択肢の一端であり、どれにも一定のデメリットがあります。ウォレットのリカバリーに関しては、これが正解だという方法はいまだなく、今後も改善が必要な分野だと思っています。

3-3. 転売の防止について

さいごに、転売の防止について触れます。Account Abstraction の利点の1つは秘密鍵を失ってもリカバリーができることですが、これは見る角度を変えると、第三者にウォレットを譲渡できるようになったということです。

3-3-1. 今までは “転売” が実質できなかった

今までの EOA では、第三者に譲渡したとしても秘密鍵を変更することはできないので、秘密鍵を知っている人、つまり譲渡した人がメモなどで秘密鍵を取っておいたら、仮に売買などを通してウォレットを譲渡したとしても確実に所有者が変わるとは言えませんでした。

ウォレットという自身の資産を管理するものを他人に受け渡すことは、実生活上では考えにくいかもしれませんが、Account Abstraction によって、このユースケースが技術的に可能になった点は、Gaudiyが考える「ファン国家」を実現するには考慮する必要がありました。

例えば、IP にまったく貢献していない人でも、ファン活動を頑張っているウォレットをお金で手に入れることができるようになるとしたら、適切なトークングラフを描けなくなってしまいます。また、投機目的のユーザーも入ってきて、ボラティリティが高くなり、本当のファンに負担がかかったり、ファンとしての熱量が下がってしまうかもしれません。

3-3-2. どう実装したのか?

これを解決するために「ウォレットのオーナーを変える処理は Gaudiy の署名も必要とする」ように実装することを考えました。具体的には、EIP-712 の署名を作成して、各メソッドで署名を検証するという、一般的なアプローチで考えました。

一方の課題として、適切なオーナーに変えようとしていることをどうやって判断するのか?は、変わらず議論しなければいけません。実装方法で他に良い方法があるとは思うのですが、今のところは IP 経済圏の公式なウォレットという状態を守るためのアプローチとなっており、その点では妥当性はあるかなと思っています。

もし、何か思いつく方・気づきがある方はぜひ議論させてほしいなと思っています。

4. まとめ

実際に取り組んでみて、今までのスマートコントラクトの開発とは違うアプローチを考えることや、現実世界で使えるレベルまで解像度をあげることは、限りある時間の中ではなかなか大変な部分もありました。この点は引き続き研鑽を積んでいきたいと思います。

また事業の特性上、完全にパブリックに開かれており、ユーザーに全主権があるという訳ではなく、どこかにルールを設ける必要があり、そのルールをどうコントラクトで定義するべきなのか?そもそも本当にルールが必要なのか?は研究とユーザー検証をしていく必要があると感じています。

べき論でいくと、ウォレットの UX を高めるためには、Starknet のようにチェーンがネイティブに Account Abstraction をサポートしていくことが必要だということも理解できますが、事業を進めることを考えると、可能な限り標準化に寄せながら、今の最善策を取っていくしかないと思っています。

ERC-4337 はまだまだ事例としては多くないですし、私たちも手探りで進めているので、もし何かシェアできるものがある方は、ぜひ教えてもらえたら大変嬉しいです。

site.gaudiy.com

ブロックチェーンエンジニアも募集してます。興味ある方いましたらぜひ。

herp.careers

site.gaudiy.com

また来週 11/7(火)に、Account Abstractionをデプロイする実践形式のオフラインイベントを開催するので、興味ある方いたらぜひご参加ください!

gaudiy.connpass.com

LangSmith で始める LLMOps

こんにちは。ファンと共に時代を進める、Web3スタートアップ Gaudiy の seya (@sekikazu01)と申します。

弊社では今 LLM をプロダクトに活用しているのですが、実際にユーザに提供するクオリティのものを作る・運用しようとすると様々な課題が立ちはだかってきました。

そんな数々の課題を解くために LangSmith というツールが活躍してくれた、また今後の活用・発展にもかなり期待ができるため、本記事ではそんな LangSmith について解説していきます。

LLM を使ったプロダクト開発において課題を感じている方々の参考になれば幸いです。

出てきた課題

まず LangSmith 自体の解説に入る前に、我々が直面した・ほぼ間違いなく今後するであろう課題たちをサラッとご紹介しようと思います。

大まかには次のような課題がありました。

  • プロンプトがアプリケーションコード内に書かれていたので、エンジニア以外がプロンプトチューニングをする時の受け渡しが手間
  • そもそもプロンプトチューニング自体がすごい手間
    • 様々なプロンプトテンプレートに対するインプットの組み合わせがある
    • 特に RAG で記憶を埋め込んだりするような文脈情報が多いプロンプトではデータを用意するのが大変
  • プロンプトの評価基準が言語化されていない
    • プロンプトはそれを持ってどんな体験をユーザに届けたいかの目的があるが、プロンプトの評価基準を最初からバシッと決めるのはおそらく不可能
    • なので逐次育てていくことになるが、そのログが取れていないため、後にいじったり他の環境(使用するモデルなど)で試した際にリグレッションが起きてないか確認する術がない

これらの課題に対して LangSmith の次のような機能たちがハマりました(or まだしっかり運用に乗せられてないのでハマりそうな予感がしています)。

  • プロダクトで実際に送られたプロンプトのログが取れる & それをいじれる Playground がある
    • これによりエンジニアでない人も触れるようになるし、プロンプトに渡すデータもリアルなものがあるのでそれを元にいじれる
  • Evaluation 機能でプロンプトのテストが書ける & データセット機能でそれに対するインプットも作れる
  • Hub によってプロンプトの一元管理とバージョン管理ができる

それではこれらの機能について、より具体的に解説していこうと思います。

LangSmith の基本

まずはじめにサクッと LangSmith の超基礎的なところをお話しします。

LangSmith は、LLM アプリ開発の定番フレームワークとなった LangChain の開発元と同じところが開発している LLM アプリ開発支援サービスです。

※ LangSmith は2023/10月現在は private beta な状態なので waitlist に登録する必要があります。(ちなみに "private" とは呼んでますが、記事書いても大丈夫なことはお問合せして確認しました👍)

www.langchain.com

LangChain 本体の X でもよく新機能や Tips の紹介をしているので要チェックです。

LangChain (@LangChainAI) / X

インストールは LangChain を使っている場合、非常に簡単で、下記のように環境変数をセットするだけです。

export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
export LANGCHAIN_API_KEY=<your-api-key>
export LANGCHAIN_PROJECT=<your-project>  # if not specified, defaults to "default"

LangChain を使っていない場合でも langsmith のパッケージがあるので、それを活用して自分で LLM の実行結果などのログを取るようにすることができます。

github.com

Organization 機能について

仕事で使っていく上で気になるのは「複数人で同じ計測を見られるか」ですが、こちらについては8月ごろに Organization 機能が追加されました。ただ、現状は Max 5人までなのでそこはご注意です。

Oranization機能の画面
Oranization機能の画面

それでは次は具体的な LangSmith の機能について解説していきます。

プロンプトのログと Playground

LangSmith の最も基礎となる機能はプロンプトのログです。 実行された結果はこのように一覧に表示されます。

LangSmith のログ画面
LangSmith のログ画面

詳細にいくと、こんな感じで実際に送られたプロンプトとその結果が見られます。

LangSmith のプロンプトログ詳細画面
LangSmith のプロンプトログ詳細画面

そして右上の Playground を押すと、そのままプロンプトや様々なパラメータをいじってチューニングすることができます。

LangSmith のプロンプト Playground
LangSmith のプロンプト Playground

ちなみに OpenAI API 以外にも Anthropic や Vertex などを使うこともできます。

LLM Provider の選択
LLM Provider の選択

LangSmith 導入以前はエンジニア以外がプロンプトチューニングを始めるのが手間だったのですが、今は実際に送られたプロンプトを使ってスッと Playground でいじれるようになってそのハードルが大分下がりました。

まだ「インプットを切り替えた並列実行」「繰り返し実行」などチューニングしていく上で、もう一捻り欲しいなぁと考えている機能はあるのですが、これだけでも大分嬉しいです。ありがとう LangSmith。

小ネタ: タグにプロンプト名を入れて絞り込みやすくする

プロンプトのログはそのままだと様々なログを区別するものがないため、一覧の中からチューニングしたい対象のプロンプトを目grepで見つけることになるためいささか非効率です。

これを解決するために我々は実行時にプロンプト名をタグとして入れるようにしています。実際は ChatModel を扱うところをもう少し抽象化していたりするのですが、書き方としては以下のような感じです。

llm_chain = ChatModel("model-name" ,temperature=0.7, verbose=True).chain(prompt=prompt)
result = llm_chain.run(**kwargs_template, tags=["local", "test_message"])

こうしたデータを入れておくと LangSmith の UI にも次のようにタグで絞り込むためのチェックボックスが生まれます。(プロンプト以外のタグが将来的に欲しくなるかもしれないので “prompt:” みたいな prefix つけてもいいかも)

LangSmithでタグでのフィルターUI
LangSmithでタグでのフィルターUI

モニタリング

LangSmith では Monitor 機能が搭載されており、

  • 実行数
  • 成功率
  • レイテンシー
  • トークン消費量

などなどを確認することができます。

LangSmith のモニターUI
LangSmith のモニターUI

ただちょっと痒い所に手が届かない部分もあり、例として、以前気づいたらものすごい量のトークン消費をしていたことがあったのですが、その時知りたかったのが「どのプロンプトがどれくらい実行されている & トークンを消費しているのか」でした。一覧を見ていたらなんとなくアタリはつけられますが、現状 LangSmith ではこれをソートして見る方法がありません。

なるべくログを見る場所は集約したいので、この Monitor 画面でタグでフィルターできる機能ができるといいなぁとは思っていますが、現状は LangChain のコールバックでまた別のところ(我々の場合 BigQuery)に保存してクエリを書くようにしています。

詳しくは下記記事をご参照ください。

zenn.dev

Evaluation とテスト

次に Evaluation という機能について触れていきます。

Evaluation(評価)とは呼んでいますが、目的の感覚としては単体テストを書くのに近いです。 主にはリグレッションの確認を想定しています。

例えば…

  • プロンプトのトークン利用量が激しいのでプロンプト文を変えたり圧縮テクニックを使ったりする
  • 使う LLM のモデルを変える
  • 変数の値を変える

などなどがあり、これらの変数を変える度に都度手動で組み合わせを確認するのは中々に手間です。

そこで Evaluation 機能を活用します。 めちゃくちゃコードをかいつまむと下記のように評価基準を書いて、データセットという事前に用意したインプットを元に実行するだけです。

eval_config = RunEvalConfig(
  evaluators=[
    RunEvalConfig.Criteria(
      {"適切な文章量": "50文字以上200文字以内に収まっているか"
      " Respond Y if they are, N if they're entirely unique."}
      )
  ]
)

result  = run_on_dataset(
    client=client,
    dataset_name=dataset_name,
    llm_or_chain_factory=create_chain,
    evaluation=eval_config,
    verbose=True,
)

こういった評価基準をプロンプトチューニングする過程で育てていくと後のリグレッション確認や、追加でプロンプトを調整していく時に活躍すること間違いなしでしょう。

という訳で具体的に LangSmith で Evaluation を実行するまでの道のりを紹介します。

データセットを準備する

データセットは Evaluation の文脈ではプロンプトに対するインプットとして使われます。 データセットは UI から手動でもコードからでも作ることができるのと、エライのが LangSmith メインの機能であるログからも追加することができます。

UI から追加する

New Dataset を押して Dataset を作ります。Data type は今回は key-value にしておいてください。(LLM Chain から input の JSON を入れるのが目的なため)

データセットの作成
データセットの作成

そして add Example から JSON 形式で追加します、実行したい対象のプロンプトテンプレートに応じて整えてください。ちなみにデータセット内のこの examples は全て同じフォーマットの JSON でないと Evaluation 実行時にエラーになるので気をつけてください。

データセットのデータの追加
データセットのデータの追加

ログから追加する

Lang Smithの素晴らしいところがログから追加できることです! ログの詳細ページから Add to Dataset をクリックしてください。

ログ詳細からデータセットへの追加
ログ詳細からデータセットへの追加

押すとこんな感じでインプットの JSON をデータセットに追加することができます。 これでテストデータを作る手間が大分削減できそうですやったぜ…。

抽出されたインプットとアウトプットでデータセットに追加する
抽出されたインプットとアウトプットでデータセットに追加する

コードから追加する

コードで追加することもでき、他のデータソースから引っ張ってきたり、それなりに量があるデータセットを作る場合にはこのやり方が重宝するでしょう。

from langsmith import Client

example_inputs = [
  "a rap battle between Atticus Finch and Cicero",
  "a rap battle between Barbie and Oppenheimer",
  "a Pythonic rap battle between two swallows: one European and one African",
  "a rap battle between Aubrey Plaza and Stephen Colbert",
]

client = Client()
dataset_name = "Rap Battle Dataset"

dataset = client.create_dataset(
    dataset_name=dataset_name, description="Rap battle prompts.",
)
for input_prompt in example_inputs:
    client.create_example(
        inputs={"question": input_prompt},
        outputs=None,
        dataset_id=dataset.id,
    )

以下の環境変数が os.environ['LANGCHAIN_ENDPOINT'] で読めるような状態にしておいてください。

export LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
export LANGCHAIN_API_KEY=<your api key>

Evaluation を書く

それでは Evaluation を書いてみましょう!

冒頭に示した通りやり方としては Evaluation の基準を書いて

eval_config = RunEvalConfig(
  evaluators=[
    RunEvalConfig.Criteria(
      {"適切な文章量": "50文字以上200文字以内に収まっているか"
      " Respond Y if they are, N if they're entirely unique."}
      )
  ]
)

データセットに対して実行するだけです。

def create_chain():
    llm = ChatOpenAI(temperature=0)
    return LLMChain.from_string(llm, "Spit some bars about {input}.")
   
result  = run_on_dataset(
    client=client,
    dataset_name=dataset_name,
    llm_or_chain_factory=create_chain,
    evaluation=eval_config,
    verbose=True,
)

そうするとデータセット内に評価の結果が表示されます。

実行されたEvaluation結果画面
実行されたEvaluation結果画面

そして中身のスコアのところをクリックすると、なぜその評価になったのかを確認することもできます。

Evaluation詳細
Evaluation詳細

※ 余談ですがこの評価にもしっかり LLM が使われているのでお金がかかることを意識しておきましょう💰

評価プロンプトはどうなっているのか

評価にも LLM が使われているのですが、我々は基準を書いているだけです。実際に LangSmith がどんなプロンプトで評価基準を活用しているのかみてみましょう

You are assessing a submitted answer on a given task or input based on a set of criteria. Here is the data:
[BEGIN DATA]
***
[Input]: a rap battle between Aubrey Plaza and Stephen Colbert
***
[Submission]: 猫が可愛いにゃぁ
***
[Criteria]: 適切な文章量: 50文字以上200文字以内に収まっているか Respond Y if they are, N if they're entirely unique.
***
[END DATA]
Does the submission meet the Criteria? First, write out in a step by step manner your reasoning about each criterion to be sure that your conclusion is correct. Avoid simply stating the correct answers at the outset. Then print only the single character "Y" or "N" (without quotes or punctuation) on its own line corresponding to the correct answer of whether the submission meets all criteria. At the end, repeat just the letter again by itself on a new line.

Criteria 毎に改行して結果を出してというシンプルな内容ですね。

ちなみにですが、元のプロンプトは英語で与えられた文字列が日本語で大丈夫なのかと疑問に思うかもしれませんが、結論としては問題ありませんでした。おそらくですが LLM が日本語の文字列を英語として解釈してから reasoning しているように見えます。

もしかしたら日本語特有のニュアンスが失われてしまうことはあるかもしれませんが、Evaluation に影響が出るほどではないのかなと予想しています。

LangSmith のプリセット評価基準

上記では自分で評価基準を書いてましたが、LangChain もいくつかプリセットの評価基準を用意してくれているので把握しておくと楽になることがあるかもしれません。

docs.smith.langchain.com

ドキュメントには全部書いてないですが、コードから抜いてくると下記のような評価基準がありました。 自分でカスタムで作る前に同じ観点での基準があるか探してみると良さそうです。

class EvaluatorType(str, Enum):
    """The types of the evaluators."""

    QA = "qa"
    """Question answering evaluator, which grades answers to questions
    directly using an LLM."""
    COT_QA = "cot_qa"
    """Chain of thought question answering evaluator, which grades
    answers to questions using
    chain of thought 'reasoning'."""
    CONTEXT_QA = "context_qa"
    """Question answering evaluator that incorporates 'context' in the response."""
    PAIRWISE_STRING = "pairwise_string"
    """The pairwise string evaluator, which predicts the preferred prediction from
    between two models."""
    LABELED_PAIRWISE_STRING = "labeled_pairwise_string"
    """The labeled pairwise string evaluator, which predicts the preferred prediction
    from between two models based on a ground truth reference label."""
    AGENT_TRAJECTORY = "trajectory"
    """The agent trajectory evaluator, which grades the agent's intermediate steps."""
    CRITERIA = "criteria"
    """The criteria evaluator, which evaluates a model based on a
    custom set of criteria without any reference labels."""
    LABELED_CRITERIA = "labeled_criteria"
    """The labeled criteria evaluator, which evaluates a model based on a
    custom set of criteria, with a reference label."""
    STRING_DISTANCE = "string_distance"
    """Compare predictions to a reference answer using string edit distances."""
    PAIRWISE_STRING_DISTANCE = "pairwise_string_distance"
    """Compare predictions based on string edit distances."""
    EMBEDDING_DISTANCE = "embedding_distance"
    """Compare a prediction to a reference label using embedding distance."""
    PAIRWISE_EMBEDDING_DISTANCE = "pairwise_embedding_distance"
    """Compare two predictions using embedding distance."""
    JSON_VALIDITY = "json_validity"
    """Check if a prediction is valid JSON."""
    JSON_EQUALITY = "json_equality"
    """Check if a prediction is equal to a reference JSON."""

run_on_dataset の返り値

こんな感じの結果サマリっぽいものが返ってくるので、それを元に CI で判定とかもできるかもしれません。

{
   "project_name":"0f5fcfffcd824b5c9ae533e9b9b27d86-LLMChain",
   "results":{
      "83a72ad6-0929-4456-bcea-a3966ce01318":{
         "output":{
            "input":"a rap battle between Aubrey Plaza and Stephen Colbert",
            "text":"猫が可愛いにゃぁ"
         },
         "feedback":[
            "Feedback(id=UUID(""bd7ccd82-af71-4f39-a5ac-f5ef4ca53ac4"")",
            created_at=datetime.datetime(2023, 9, 6, 59, 36, 813970),
            modified_at=datetime.datetime(2023, 9, 6, 59, 36, 813970),
            "run_id=UUID(""62cd7d98-10e3-4a40-95d6-1bd14215b27f"")",
            "key=""適切な文章量",
            score=0.0,
            value=0.0,
            "comment=""The criteria is asking if the submission is between 50 and 200 characters long. The submission \"猫が可愛いにゃぁ\" is only 9 characters long. Therefore, it does not meet the criteria.\n\nN",
            "correction=None",
            "feedback_source=FeedbackSourceBase(type=""model",
            "metadata="{
               "__run":{
                  "run_id":"780943ca-496a-4ba1-8bc0-2de00bdb2d1e"
               }
            }"))"
         ]
      }
   }
}

LangSmith の Cookbook の中にも pytest を使ったサンプルコードがあるのでこちらもご参考ください。

github.com

小ネタ: RAG を使った評価

先ほど注意点としてサラッと書きましたが、この Evaluation は LLM を使って行われているため時間もかかるしお金もかかります💰

そこで最近ちょっと話題だったのが Embedding を使った評価です。この手法では Embedding で事前に用意した想定回答との類似度を見ることによって LLM の遅さと API 代の高さをカバーしています。

gpt-index.readthedocs.io

評価手法も色々策定されていってるので知見を育てていきたいですね。

Hub によるプロンプトの管理

こちらはまだ運用で試せていないのですが、結構個人的には期待している機能です。

LangSmith の中に Hub という機能があり、ざっくり言うとプロンプトの共有サイトです。色んな人が投稿しているプロンプトを見て学ぶことができます。

Hubの画面
Hubの画面

が、これはプライベートなプロンプト管理ツールとしても優秀な予感がしています…!というのも以下の機能を備えているからです。

プロンプトは private にもできる

上記は公開されたプロンプトたちですが、Organization 以外の人が見られない private な状態にすることができます。

Privateに作ったプロンプト
Privateに作ったプロンプト

hub.pull でプログラマブルにアップロードしたプロンプトを引っ張ってこれる

下記のコードだけで Hub で作成したプロンプトを読み込むことができます。

from langchain import hub
obj = hub.pull("gaudiy/some-prompt")

ちなみに LANGCHAIN_API_KEY がない場合ちゃんと読み込めないことを確認したので、プライベートにしたプロンプトもちゃんと認証されています。

これを実行すると、上記の obj の中には次のようなデータが返ってきます。一通りのプロンプトのテンプレートやインプットの定義が取得できるので、これをコードにもバッチリ反映できます。

input_variables = ['post_content', 'feedback']
output_parser = None
partial_variables = {}

messages = [
    SystemMessagePromptTemplate(
        prompt=PromptTemplate(
            input_variables=[],
            output_parser=None,
            partial_variables={},
            template='あなたはプロのプロです。',
            template_format='f-string',
            validate_template=True
        ),
        additional_kwargs={}
    ),
    
    HumanMessagePromptTemplate(
        prompt=PromptTemplate(
            input_variables=['key', 'value'],
            output_parser=None,
            partial_variables={},
            template=(
                'こちらはホゲホゲ\n'
                '{key}\n\n'
                'ホゲータ\n'
                '{value}\n\n'
                '上記のフィードバックを元に投稿の内容を修正してください。'
            ),
            template_format='f-string',
            validate_template=True
        ),
        additional_kwargs={}
    )
]

コミットログと共にバージョン管理をすることができる

コミットのタイトルや変更理由を書く場所がないのがちょっと惜しいポイントではありますが、バージョン管理をすることができます。

コミットする一連のUI
コミットする一連のUI

これらの機能と共にこの Hub をプロンプトの Single source of Truth として扱えないかなぁと画策しています。具体的には下図のように Hub で変更があったら CI で検知してコードに反映し、都度 Hub にリクエストを送らずとも実行できるようにならんかなと。

HubとCIの連携図
HubとCIの連携図

ですが、今の所以下の機能が足りておらず、ゴリ押しできなくはないのですが今後に期待かなぁという印象です。

  • 階層やタグをつけることができない
    • プロンプトが大量に増えてくるとドメインに応じて分割して管理したくなってくる
  • Oranization のもの全部とってくるみたいなことができない
  • commit した時に変更検知できない
    • Webhook とかできると嬉しいかも

データの取り扱いについて

最後に、技術的な話ではないのですが、プロンプトのデータを LangChain のサーバに送ることもあり、内容によってはそもそも送って良いのかだったり利用規約の改変の必要があるかが議題に上がったので、そこで法務の方に相談して調べていただいた内容を記します。

結論から申し上げると

LangSmithでの個人データの処理については、規約・プライバシーポリシーを踏まえると、「個人データの取り扱い」ではないと整理できるので、いわゆるクラウドでの情報の取り扱いと同じく、同意は不要と考えて問題なさそうです(弁護士確認済み)

とのことでした。

LangSmith の利用規約とセキュリティーポリシーのリンクは下記の通りです。

https://smith.langchain.com/terms-of-service.pdf https://smith.langchain.com/data-security-policy.pdf

利用規約の 4.1 に「LangChain agrees that it will not use Customer Data to develop or improve its products and services」と「個人データを取り扱わない旨」が定められており、また、4.2の「(i) ensure the security and integrity of Customer Data (ii) protect against threats or hazards to the security or integrity of Customer Data; and (iii) prevent unauthorized access to Customer Data」と定められているため「適切にアクセス制御」も行われていると考えられる、とのことでした!

もちろんプロンプトの用途によっては、法律的にはOKでも世間的・倫理的にはアウトみたいなことはあり得るので都度判断が必要ですが、ひとまず規約に明示的にこの LangSmith のための同意を得る対応は必要なさそうです。

おわりに

以上、LangSmith の機能の解説と我々のチームで発生している課題の解決への希望について触れていきました。

実は似たような、いわゆる LLMOps と呼ばれるツールは今まさに群雄割拠で色んなツールが出てきているのですが、セットアップの簡単さからもわかる通り、 LangSmith の優位性は「LangChain という LLM アプリ開発のデファクトになりつつあるツールとの連携がすごい」ことにあるのかなと考えています。 トラッキング始めるまでの準備も環境変数を仕込むだけですし、ログも LangChain の Chain の種類に応じていい感じに整えて取得してくれます。

ある意味ではロックインのリスクとも取れるかもしれないのですが、LangChain をプロダクト開発に活用されている方はトライしてみるのもいいのではないでしょうか?

最後に、Gaudiy では現在 LLM をかなり踏み込んで活用して新しいエンタメ体験を鋭意開発中です!もしご興味持っていただければ一緒に働きましょう💪

herp.careers

herp.careers

https://herp.careers/v1/gaudiy/FAqLGhW5HFr5herp.careers

まずはカジュアル面談で「いや LLM 開発実際はね〜…」みたいなところから雑談するのでも大丈夫です、お待ちしております。

https://recruit.gaudiy.com/9b513345d630418799bdbb219acc7063recruit.gaudiy.com

それでは!!!👋