キャディ株式会社のブログ - TECH PLAY

TECH PLAY

キャディ株式会社

キャディ株式会社 の技術ブログ

237

CADDi Tech/Product Advent Calendar 2025 10日目の記事です。 こんにちは、Data&Analysis部の竹本です。 本記事ではRAGシステムを構築する上で、ユーザー意図の把握が難しい曖昧なクエリにどのように対応すべきかという課題に着目し、関連する論文や技術記事を紹介します。 クエリの「情報不足」と「曖昧性」という壁 知識ギャップによる情報不足 ユーザークエリの曖昧性 Query Transformation Query Rewriting Multi-Query Diversify then Verify RAG-Fusion Diversify-verify-adapt Verified-Diversification with Consolidation 対話的な解決 動的にユーザーに問い合わせる まとめ クエリの「情報不足」と「曖昧性」という壁 キャディではRAGを用いて、ユーザーが膨大なドキュメントから適切に情報を得られる機能の検証を行っています。この取り組みの背景については、4日目の記事で詳しく紹介していますので是非ご覧ください。 caddi.tech ユーザーが膨大なドキュメントから必要な情報を適切に取得できるようにすることがRAGシステムの狙いですが、必要としているドキュメントに辿り着けないという課題が検証の中で発生しました。そこでユーザーが入力したクエリ(以下、ユーザークエリ)、中間処理の結果、および最終的な回答を分析したところ、ユーザークエリに含まれる情報の不足や曖昧さが一因となり、ユーザーが求める情報が適切にヒットしていないケースが確認されました。 情報不足や曖昧性は大きく分けて以下の2つのパターンがあると考えます。 知識ギャップによる情報不足 ユーザー自身が適切な聞き方がわからず、所望の情報が回答されないケースです。ユーザーが質問をする際、そのトピックに関する専門用語や正しい名称を知らない(周辺情報も不足している)状況は多々あります。その結果、ユーザーの意図が適切にクエリに反映されず、ユーザークエリと欲しい情報を見つけるための検索用語との間にミスマッチが発生し、所望のドキュメントがヒットしないケースです。 具体例としては、「製品Aの動きがガタつく不具合は過去に報告されていますか?」というユーザークエリに対して、ドキュメントには「ガタつく不具合」とは記載されておらず、「クリアランス(隙間)過大」と記載されていることがあります。 ユーザークエリの曖昧性 クエリが短すぎる、または抽象的すぎるため、広範なドキュメントがヒットしてしまい、回答にノイズが含まれるもしくは欲しい情報がノイズに埋もれて回答されないケースです。 ユーザークエリの具体例としては、「環境試験とは?」のようなケースです。この場合ユーザーはどの環境試験(温湿度試験、冷熱衝撃試験など)について知りたいのか、具体的に知りたい対象の製品があるのか、抽象的なクエリからは判断できません。 以降ではユーザクエリの情報不足や曖昧性に対処する手法を紹介します。 Query Transformation アプローチの1つ目はQuery Transformationやクエリ拡張と呼ばれている手法です。Query Transformationはその中でも複数の種類があります。 Query Rewriting Rewriterを用いてユーザークエリを書き換える手法です。Rewriterは主に2種類あります。 Vanilla LLM Rewriter メリット:追加学習が不要で、検証・導入が容易 デメリット:書き換えによる効果が保証されない。意図しない変更やノイズ混入のリスクがある Fine-tuned Rewriter メリット:書き換えタスク専用に調整できること、また軽量モデルを採用することで計算コストやレイテンシーを抑えられる デメリット:学習とそのための準備が必要 以下の論文ではQuery Rewritingを導入したRewrite-Retrieve-Readのフレームワークを提案しており、曖昧もしくは長文なユーザークエリに対してfine-tuningしたt5-largeを使い、検索意図を明確化にすることによる改善を報告しています。 Query Rewritingのアーキテクチャ 「Query Rewriting for Retrieval-Augmented Large Language Models」より引用 arxiv.org Multi-Query 曖昧なユーザークエリから明確化したクエリを複数生成し(マルチクエリ)、それぞれに対応する回答と根拠文書を提供する手法です。ベクトル検索の弱点を補い、検索の取りこぼしを減らすメリットもあります。 これもQuery RewritingのRewriterと同様の選択肢があります。また、マルチクエリで得られた検索結果を全て回答に使用するのか、それとも回答前に各検索結果に対する評価を挟むのかという選択肢があります。後者の手法は後述します。 Diversify then Verify アプローチの2つ目は曖昧なユーザークエリに対して以下の2段階を踏む、Diversify then Verify(DtV)という手法です。 Diversify:ユーザークエリが持ちうる複数の意図を網羅するために、複数のバリエーションでマルチクエリを生成 Verify:検索されたドキュメントや生成された回答候補を評価し、確実性が最も高いものを選抜、あるいは矛盾を排除 RAG-Fusion RAG-Fusionは、ユーザクエリからマルチクエリを生成しベクトル検索でドキュメントを取得した上で、マルチクエリで取得したドキュメントを「複数の異なるクエリ検索で共通して上位に現れるドキュメントは信頼性が高い」という仮定に基づき、Reciprocal Rank FusionでRe-rankingする手法(Multi-Query + Re-ranking)です。 以下のアーキテクチャ図を見ると理解しやすいと思います。 RAG-Fusionのアーキテクチャ 「Forget RAG, the Future is RAG-Fusion」より引用 記事では、マルチクエリの使用によって本来のユーザー意図が薄れる可能性を指摘しています。その対策として、プロンプトエンジニアリングによって元のユーザークエリに重点を置くように指示することを推奨しています。 またRAG-Fusionの課題として、回答が冗長になりすぎるリスクや、LLMのコンテキストウィンドウを圧迫するリスクがある点も課題として挙げられています。 medium.com github.com Diversify-verify-adapt Diversify-verify-adapt(DIVA)はマルチクエリによる検索結果をLLMによって評価し、その結果から検索結果を元にした回答とClosed-book LLMによる回答を使い分ける手法です。 具体的には以下の3つのモジュールで構成されています。 Retrieval Diversifier: LLMを用いて曖昧さのタイプ(主語、目的語、述語、時間、場所)を特定 特定した結果に基づいて別のLLMが曖昧箇所を明確化したマルチクエリ(Pseudo-interpretations)を生成 各クエリで検索して、得られたチャンク群に対してノイズスコアを計算し、関連性の低いチャンクを削除(Pruning)して最終的なチャンクセットを作成 Retrieval Quality Verifier: 選定されたチャンクセットを使って、各クエリに対し十分な回答が得られているか(Yes or No)を評価 各評価結果を元に最終的に「全てのクエリでYes(=Useful)」、「一部のクエリでYes(=PartialUseful)」、「全てのクエリでNo(Useless)」の3段階で評価 Adaptive Generator: Retrieval Quality Verifierで「Useful」もしくは「PartialUseful」の場合は、検索結果をプロンプトに含めてLLMによる回答を実施 Retrieval Quality Verifierで「Useless」の場合は、検索結果を完全に無視してClosed-book LLMによる回答を実施 DIVAのアーキテクチャ 「Diversify-verify-adapt: Efficient and Robust Retrieval-Augmented Ambiguous Question Answering」より引用 GPT-4を使用した場合、曖昧性の検出とマルチクエリの生成を同時に行うとLLMへの負荷が高まり、性能が大幅に低下したと報告されています。論文ではステップを分離していますが、より高性能な後継モデルを使う場合は、同時処理が可能かどうか再検証してみても良いかもしれません。 一方、DIVAの課題としては、曖昧でないユーザークエリに対する過剰な処理が挙げられます。これを防ぐには、曖昧かどうかを事前に分類する手法と組み合わせて、ケースに応じて処理を使い分ける必要があると言及しています。 arxiv.org Verified-Diversification with Consolidation アプローチの3つ目はDtVが抱えていた課題を改善したVerified-Diversification with Consolidation(VERDICT)という手法です。 DtVの具体的な課題とそれに対するVERDICTの解決策は以下になります。 「根拠のないマルチクエリ生成」を排除 DtV:コーパスに存在しない無関係な解釈まで生成してしまい、無駄な検索が発生する VERDICT:ユーザークエリを緩和したクエリに書き換え、関連する広範囲な検索を一度だけ実行し、得られた文書を起点(Grounding)としてマルチクエリを生成する 「回答に使えない文書」を早期除外 DtV:検索器が関連性が高いと判断した文書を全てLLMに渡すため、コンテキストウィンドウの圧迫とノイズによるハルシネーション発生リスクがある VERDICT:解釈の生成と同時に「その文書で回答可能か」を判断することで回答生成に使えないノイズを最初から除外する 特に1の課題については、単なる計算コストの増大だけでなく、前段のミスが後段に連鎖するカスケーディングエラーを招きやすいという構造的な問題がありました。従来のDtVが「まず広げて(Diversify)、後で検証(Verify)」という分離されたパイプラインだったのに対し、VERDICTはこの2つを統合し、「検証しながら広げる」アプローチを採用することで、計算コストとこのカスケーディングエラーの両面で問題を解決しています。 DtVとVERDICTの比較 「Agentic Verification for Ambiguous Query Disambiguation」より引用 VERDICTの具体的なワークフローは、以下の4ステップで構成されています。 Rewrite user query as relaxed query:LLMを使用し、曖昧で短いユーザークエリをより広範囲の情報を網羅できる緩和されたクエリに書き換える Universe Retrieval:書き換えたクエリを用いて、広範囲に検索 Verified Diversification:検索された各チャンクに対し、LLMに「このチャンクを使って、ユーザー意図を明確にしたクエリとその回答を作る」というタスクを指示し、生成に失敗したりチャンク内に根拠が見当たらない場合はフィルタリング Consolidation:生成された「複数のクエリ(解釈)と回答のペア」に対してノイズ除去とクラスタリングを実施 各ペアをベクトル空間に射影(クエリだけでなく回答の一貫性も評価するためにペアで埋め込むのがポイント) HDBSCAN(Hierarchical Density-Based Spatial Clustering of Applications with Noise)を用いてクラスタリング クラスタに属さない外れ値は、誤った解釈やハルシネーションである可能性が高いため除外 形成されたクラスタの中心(Medoid)となるペアを取得 VERDICTのアーキテクチャ 「Agentic Verification for Ambiguous Query Disambiguation」より引用 DtVとVERDICTをend-to-endのパイプラインで比較した図が以下になります。 VERDICTは検索が1回で済む一方で、得られたチャンク数分のLLM呼び出しが発生します。LLM呼び出しの並列処理が難しい環境だとその分レイテンシーが悪化する点と、外部LLMのAPIを使用する場合はレートリミットや課金体系に注意が必要です。 DtVとVERDICTのend-to-endのアーキテクチャ比較 「Agentic Verification for Ambiguous Query Disambiguation」より引用 arxiv.org www.snowflake.com github.com 対話的な解決 最後となるアプローチの4つ目は、ユーザーへの「問いかけ」によって意図を明確化する手法です。これまでの3つのアプローチは、あくまでLLMの事前知識やコーパスに基づき、正解と思われるクエリを推論していました。しかし状況によってはユーザーに直接問い合わせる方が、より正確な情報を迅速に得られる場合があります。 動的にユーザーに問い合わせる LangChainの記事(2023年のlangchainがv0.0.318だった頃)では、以下2つを行い、ユーザーとの対話を通して曖昧性を解決するアプローチを紹介しています。 検索結果に十分なコンテキストが含まれていないとLLM agentが判定した場合はユーザーへ逆質問する 元の質問、検索結果、会話履歴などを元にLLM agentが質問を生成 動的にユーザーへ問いかけ 「Improve LLM responses in RAG use cases by interacting with the user」より引用 現在では LangGraphの割り込み(Interrupt) を使ったHuman-in-the-loopが推奨されています。 aws.amazon.com 上記の記事では触れていませんが、実際のプロダクトに組み込む際は、「どのタイミングで、どんな内容を、どれくらいの量、どのように作成し、どのようにユーザーへ提示するのか」といったUI/UXの設計が重要になります。この設計が不十分だと、せっかく提案されたクエリを使ったのに十分な回答が得られなかったり、意図したクエリ候補が提案されなかったりと、結果としてユーザーのがっかり体験に繋がります。 まとめ 本記事ではRAGにおける「曖昧なクエリ」が持つ課題と、代表的な解決アプローチを紹介しました。 Query Transformation:比較的導入しやすいが、精度に限界がある場合も DtV (RAG-Fusion / DIVA):検索精度は向上するが、計算コストの増大やノイズ混入のリスクがある VERDICT:DtVの課題を構造的に解決する一方で、LLM呼び出し回数(コスト・レイテンシー)とのバランス検討が必要 対話的な解決:確実性は高いが、ユーザー目線での高度なUX設計が求められる どの手法を選択するかは、求められる回答品質、許容できるコストとレイテンシーのトレードオフによって決まります。 いずれの手法をとるにせよ、実運用において曖昧なクエリへの対応は重要な課題です。まずはシンプルな手法からスモールステップで検証を重ね、プロダクトに最適な形を模索していくのが望ましいでしょう。 最後に、キャディでは現在エンジニアを絶賛採用中です。本記事を読んで興味を持ってくれた方はぜひご連絡ください。ここには面白い課題が沢山あります。 recruit.caddi.tech
この記事は CADDi Tech/Product Advent Calendar 2025 の9日目の記事です。 Data Management チームの森岡です。要らなくなったものをすぐに捨てられるデータ基盤を意識して日々開発しています。 この記事では、プロダクトの成長に伴って直面した Terraform State の肥大化問題を Terramate を活用して解決した実践的な事例を紹介します。 はじめに キャディでは、製造業AIデータプラットフォームを開発しています。 我々の顧客には大手エンタープライズ企業も多く含まれるため、セキュリティとデータガバナンスは最優先事項です。 その一方で、キャディには、カスタマーサクセスや、エンタープライズソリューションチームが存在し、顧客への価値提供に取り組んでいます。 これらのチームでは、顧客への提供価値を最大化するために、BigQuery 上のデータを活用し、利用状況分析、プロダクト上の顧客データを抽出・加工してプロダクトへ反映、さらにはデータを活用した高度なソリューション提案などを行っています。 このような背景から、「堅牢な分離」と「活用」を両立するため、テナントごとに BigQuery のデータセットを作成し、そのデータセットには特定の許可された社員のみがアクセスできるようにしています。 直面した問題 当初の Terraform 構成は、簡略化すると以下のようになっていました。1 つの環境につき 1 つの tfstate が存在し、その中で全テナントのリソースを一元管理していました。 ├── README.md ├── environments │ ├── prod │ │ ├── main.tf # ここで全てのリソースを呼び出し │ │ └── tenant_data.json # tenant の一覧 │ ├── stg │ └── dev └── modules ├── iam # 共通リソース定義 | └── main.tf ├── bigquery └── tenant_resource # tenant ごとのリソース定義 ├── tenant_iam.tf └── tenant_datasets.tf しかし、テナント数の増加に伴い、以下のような問題が顕在化しました。 パフォーマンスの悪化 管理リソースの増大に伴い、 terraform plan/apply の実行時間が著しく増加。 これにより、CIの待ち時間による開発生産性の低下。 デプロイ安定性の低下 大量のリソースを一括で更新・参照するため、Google Cloudの API Rate Limit が発生。 特に BigQuery の getTable API 等において秒間リクエスト数制限を超過し、「コードは正しいのにデプロイが失敗する」という事象が多発。 運用アジリティの欠如 State が単一であるためロックの競合が頻発し、複数人による並行開発が難しい。 1テナントの修正であっても全リソースへの参照が発生。 これらの課題を解決するためには、モノリシックな tfstate を分割し、テナントごとに独立した tfstate を管理する構成(Multi-State)への移行が必要でした。 しかし、単にディレクトリを分割するだけでは、テナントの数だけ .tf ファイルの複製管理が必要となり煩雑です。そこで、Terramate の採用を検討しました。 Terramate Terramate とは Terramate は、Terraform(および OpenTofu, Terragrunt)のための オーケストレーター兼コードジェネレーター です。主に以下の特徴を持っています。 Stacks(スタック)の概念 : Terramate を理解する上で最も重要な概念が 「Stack」 です。 一言で言えば、Stack とは 「Terraform の State を持つ最小のデプロイ単位」 のことを指します。リソースをこの Stack という論理グループ単位で管理することで、tfstate 分離し、独立した操作を可能にします。 コード生成(Code Generation) : 共通の HCL 設定を親ディレクトリで定義し、各スタック配下に Terraform コードとして生成・配布できます。 強力なオーケストレーション : Git の差分検知機能を持っており、変更があったスタックのみに対して plan や apply を実行できます。 公式ページに quick start ガイド がありますので、基本的な使い方はそちらをご参照ください。 Terragrunt との比較 terraform で tfstate 分割と DRY を実現するツールとしては Terragrunt が有名です。今回の選定にあたり、両者を以下のように比較しました。 特徴 Terramate Terragrunt アプローチ Orchestrator コード生成で Terraform コードを出力。Stack 単位で管理・実行する。 Wrapper 実行時に動的に設定を生成・注入する 管理/可読性 〇: テンプレート(generate_hcl)とグローバル変数で管理。テナント追加時は、スクリプトで stack.tm.hcl の作成が必要。tf ファイルが生成されるので可読性がよい。 〇: include による継承機能。テナント追加時は、同様にterragrunt.hcl の作成が必要。柔軟だが可読性は悪い。 実行制御 〇: git 差分検知 (terramate run --changed) やstackのタグ管理が強力 △: run-all で依存関係順に実行。--terragrunt-include-dir で特定 dir に絞った実行は可能 API Rate Limit 対策 〇: 変更がないStackに対するAPIコールはゼロ。後述する自作のretryも強力。 △ : 依存解決やPlan時に多くのAPIコール(Refresh)が発生しやすいが、--terragrunt-include-dir で回避はできる。 学習コスト ◎: 生成ルール(generate_hcl)以外は標準の Terraform の知識で完結する 〇: 独自の HCL 記法や継承ルールの学習が必要だが、そこまで複雑ではない Terragrunt は素晴らしいツールであり、複雑な依存関係を持つインフラ(例:VPCを作ってからEKSを作り、その上にアプリを載せるなど)には最適です。 しかし、我々のケースは「依存関係は薄くフラットな構造であるが、とにかく数が膨大にある」 という特徴があります。 この場合、動的な解決を行う Terragrunt よりも、静的なコード生成と Git ベースの差分実行を行う Terramate の方が、パフォーマンス・運用コストの両面で有利であると判断しました。 Terramate 導入後の構成 Terramate 導入後のディレクトリ構成は以下のようになりました。すべてを解説すると長くなるため、主要なポイントに絞って説明します。 ├── terramate.tm.hcl # グローバル設定(全環境共通) ├── scripts │ └── sync-tenants.sh # テナント Stack 自動生成スクリプト ├── _imports # 共有テンプレート │ ├── backend.tm.hcl # Backend & Provider 生成テンプレート(統合版) │ └── tenant_resources.tm.hcl # テナントリソース生成テンプレート ├── environments │ ├── prod │ │ ├── env_config.tm.hcl # 環境固有設定 │ │ ├── data │ │ │ └── tenant_data.json # 既存ファイル(prod 環境のテナントデータ) │ │ ├── _platform # 共有リソースの Stack │ │ │ ├── imports.tm.hcl # _platformに適用するテンプレートの定義 │ │ │ ├── stack.tm.hcl │ │ │ ├── main.tf # 既存ファイル(生成しない) │ │ │ ├── local.tf # 既存ファイル(生成しない) │ │ │ └── _gen_backend.tf # 生成ファイル │ │ └── _tenants # テナント Stack 群 │ │ ├── imports.tm.hcl # _tenantsに適用するテンプレートの定義 │ │ ├── tenant_aaaaa # テナント個別のStack │ │ │ ├── stack.tm.hcl │ │ │ ├── _gen_backend.tf │ │ │ └── _gen_main.tf │ │ ├── tenant_{TENANT_ID} │ │ │ ├── stack.tm.hcl │ │ │ ├── _gen_backend.tf │ │ │ └── _gen_main.tf │ │ └── ... │ ├── stg │ └── ... └── modules ├── iam ├── bigquery └── tenant_resource # 単一テナント用にリファクタリング ├── datasets.tf └── iam_tenant.tf 主要な構成要素の解説 1. Stack の構成 この構成では、大きく2種類の Stack があります。 _platform Stack : IAM ロールやプロジェクト共通の BigQuery データセットなど、全テナント共通のリソースを管理します。 _tenants/{tenant_id} Stack : 各テナント専用のリソース(データセット、IAM バインディングなど)を管理します。テナントごとに独立した tfstate を持ちます。 2. テナント Stack の自動生成 テナント数が増減するたびに手動でディレクトリを作成するのは非効率です。そこで、 tenant_data.json を元に Stack を自動生成するスクリプト sync-tenants.sh を作成しました。 このスクリプトは、 tenant_data.json を読み込み、存在しないテナント Stack ディレクトリを terramate create と terramate generate コマンドで生成します。 tenant_data.json の例: [ { " tenant_id ": " aaaaa ", " tenant_name ": " tenant A " } , { " tenant_id ": " bbbbb ", " tenant_name ": " tenant B " } ] sync-tenants.sh で実行される terramate create コマンドの例: # terramate create でディレクトリと stack.tm.hcl を作成 # --id にテナントID、--name にテナント名を設定 terramate create "$stack_dir" \ --id "${tenant_id}" \ --name "${tenant_name}" \ --description "Resources for tenant: ${tenant_name}" \ --after "../../_platform" \ --tags "tenant,${ENV},tenant-${tenant_id}" 生成される stack.tm.hcl の例: stack { id = "aaaaa" name = "tenant A" description = "Resources for tenant: tenant A" tags = [ "prod" , "tenant" , "tenant-aaaaa" ] after = [ "../../_platform" ] } これにより、新規テナントの追加は tenant_data.json への追加と非常にシンプルなスクリプトの実行だけで完結します。 3. コード生成の仕組み コード生成は Terramate のコア機能のひとつです。 _imports/ 配下のテンプレートファイルを、各 Stack の imports.tm.hcl で読み込むことで、必要な Terraform コードを自動生成します。 例えば、 environments/prod/_tenants/imports.tm.hcl は以下のようになっています。 environments/prod/_tenants/imports.tm.hcl import { source = "../../../_imports/backend.tm.hcl" } import { source = "../../../_imports/tenant_resources.tm.hcl" } これにより、このディレクトリ配下の全 Stack に backend.tm.hcl と tenant_resources.tm.hcl の 2 つのテンプレートを適用させています テンプレートファイルである _imports/backend.tm.hcl では以下のようにバックエンド設定を定義しています。 _imports/backend.tm.hcl # Backend 設定生成テンプレート generate_hcl "_gen_backend.tf" { content { terraform { # Terraform バージョン required_version = global.terraform.version # このあたりの変数は terramate.tm.hcl や env_config.tm.hcl で定義 backend "gcs" { # 環境固有のバケット bucket = global.terraform.backend.gcs.bucket # Stack ID ベースのパスで State を分離 prefix = "stacks/$ { terramate.stack.id } " } required_providers { google = { source = global.terraform.providers.google.source version = global.terraform.providers.google.version } } } provider "google" { project = global.project.id } } } このテンプレートにより、各 Stack に 以下のような _gen_backend.tf が生成され、Stack ごとに異なる GCS パスで tfstate が保存されます。 environments/prod/_tenants/tenant_aaaaa/_gen_backend.tf // TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT terraform { required_version = "x.x.x" backend "gcs" { bucket = "hoge" prefix = "stacks/aaaaa" } required_providers { google = { source = "hashicorp/google" version = "x.x.x" } } } provider "google" { project = "hoge" } 同様に、テンプレートファイル _imports/tenant_resources.tm.hcl では、テナントリソースの Terraform コードを生成します。 _imports/tenant_resources.tm.hcl # テナント Stack 専用の設定 # 各テナント Stack から個別に import される generate_hcl "_gen_main.tf" { content { # テナントローカル変数の生成 locals { tenant_id = terramate.stack.id # stack.tm.hcl で設定された id が入る tenant_name = terramate.stack.name } # Platform Stack の outputs を参照 data "terraform_remote_state" "platform" { backend = "gcs" config = { bucket = global.terraform.backend.gcs.bucket prefix = "stacks/$ { global.platform.stack_id } " # Platform Stack の ID を使用 } } # テナントリソースモジュールの呼び出し module "tenant_resource" { source = "path/to/modules/tenant_resource" # プロジェクト情報 project_id = global.project.id # テナント情報 (locals から取得) tenant_id = local.tenant_id tenant_name = local.tenant_name # Platform Stack からの出力を参照 data_access_type_tag_values = data.terraform_remote_state.platform.outputs.data_access_type_tag_values } } } environments/prod/_tenants/tenant_aaaaa/_gen_main.tf // TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT locals { tenant_id = "aaaaa" tenant_name = "tenant A" } data "terraform_remote_state" "platform" { backend = "gcs" config = { bucket = "hoge" prefix = "stacks/platform-prod" } } module "tenant_resource" { source = "path/to/modules/tenant_resource" project_id = "hoge" tenant_id = local.tenant_id tenant_name = local.tenant_name data_access_type_tag_values = data.terraform_remote_state.platform.outputs.data_access_type_tag_values } このように、Terramate のコード生成機能を活用することで、テナントごとに独立した Stack を簡単に管理できるようになっています 4. 運用フロー Terramate を使った terraform コマンドの実行例は以下の通りです。 # 全 Stack での実行 (10個の Stack を並列実行) terramate run --parallel 10 -- terraform plan # 変更された Stack のみ plan (Git ベース) terramate run --changed --parallel 10 -- terraform plan # タグを使った制御(":" で AND "," で OR) terramate run --tags prod:tenant_aaaaa -- terraform plan # あるいは、生成された terraform コードを直接操作することも可能 cd environments/prod/_tenants/tenant_aaaaa terraform plan Github Actions との連携も非常に簡単で、 公式ページ をなぞればすぐに構築できます。 キャディでは、日次で対象 tenant の変化を検知して、① tenant_data.json の更新 ②sync-tenants.sh の実行、③ terramate run --changed で terraform plan および apply を実行するワークフローを構築しています。 その他の工夫 API Rate Limit 対策 Terramate により Stack ごとに独立した tfstate を持つことで、少数のテナントへの変更における API Rate Limit の問題は大幅に軽減されました。 特に --changed フラグによる差分実行では、変更がない Stack は API コールが発生しないため、日常的な運用では問題が起きなくなりました。 しかし、全テナントに対して大規模な変更を加える場合(例:共通モジュールのバージョンアップや、セキュリティポリシーの一斉適用など)、短時間に大量の API リクエストが発生し、依然として API Rate Limit に抵触するリスクがあります。 そこで、Terramate の Script 機能 を活用し、一時的な API エラーに対して自動的にリトライする仕組みを導入しました。 Terramate Script は、各 Stack で実行するコマンドを HCL で定義できる機能で、通常の terraform コマンドの代わりに独自のスクリプトを実行できます。 以下は、 terraform apply を最大3回リトライする Script の例です。 terramate.tm.hcl に記載 script "retriable_apply" { description = "Run terraform apply with automatic retries for transient errors" job { commands = [ [ "bash" , "-c" , <<-BASH for i in {1..3}; do if terraform apply -auto-approve -no-color; then exit 0 fi if [ $i -lt 3 ]; then echo "Attempt $i failed, retrying in 10 seconds..." >&2 sleep 10 fi done echo "Terraform apply failed after 3 attempts" >&2 exit 1 BASH ] ] } } 使い方も非常に簡単で、 terramate run --parallel 10 -- terraform apply コマンドの代わりに terramate script run --parallel 10 retriable_apply を実行するだけです。 導入効果 Terramate 導入とtfstate分割による効果は劇的でした。 CICD時間の短縮 : 以前までは、単一 State 構成での terraform plan/apply が 60 分以上かかることも珍しくありませんでしたが、Terramate 導入後は、数テナントの変更であれば数分以内に完了するようになりました。 安定性の向上 : retry により、API Rate Limit による問題も解消されました。 運用も基本は、CI/CD パイプラインで自動化されており、terramateを意識することなく進められています。 また、たまに手動介入するときも、特定テナントの Stack に移動して通常の Terraform コマンドを実行するだけで済むため、追加の学習コストもほとんど発生していません。 おわりに 私が Terramate で最も気に入っている点は、Terramate の責務と Terraform の責務が明確に分離されており、非常に疎結合であることです。 ざっくりいえば、Terramate の責務は以下の2点のみです。 コード生成: DRY を実現するための tf ファイル生成 オーケストレーション: terramate run による実行対象 Stack の選定とコマンド発行 実際の tfstate 操作や API 通信といったコア処理は、標準の Terraform に完全に委ねられています。なので、問題発生時における切り分けも容易であり、Terraform の豊富なドキュメントやコミュニティリソースを活用できる点が非常に助かっています。 terramate は比較的新しいツールということもあり、実践的な資料がまだまだ少ないです。この記事が同様の課題に直面している方々の参考になれば幸いです。
この記事は CADDi Tech/Product Advent Calendar 2025 の8日目の記事です。 こんにちは。Control Plane部で認証周りの開発をしている宇都宮と申します。 キャディでは、メール送信基盤として SendGrid を利用しています。少し前に、SendGrid の生成するイベントデータを分析基盤に連携する仕組みを構築しました。その際に遭遇した、署名検証処理の実装において直面した課題と、それを解決するためのアプローチを紹介します。 Event Webhook 連携の流れ SendGrid には、イベントを Webhook 連携 する機能があります。この機能をベースに、以下のような仕組みを構築しました。 sequenceDiagram autonumber participant SG as SendGrid participant CW as Cloudflare Workers participant AP as 分析基盤 Note over SG: イベント発生 rect rgb(240, 248, 255) Note over SG: 署名生成 (ECDSA)<br/>Data = Timestamp + Payload end SG->>CW: HTTP POST (Webhook)<br/>Headers: Signature, Timestamp Note over CW: リクエスト受信 rect rgb(255, 250, 240) Note over CW: 署名検証処理<br/>1. PubKey取得<br/>2. Data結合 (Timestamp + Payload)<br/>3. Verify(PubKey, Signature, Data) end alt 検証成功 CW->>AP: イベントデータを送信 CW-->>SG: 204 No Content else 検証失敗 Note over CW: 不正なリクエストとして破棄 CW-->>SG: 400 Bad Request end SendGrid が Webhook でイベントデータを送信する。この際、リクエストヘッダーには「タイムスタンプ」と「署名(タイムスタンプとペイロードを結合したものに対する署名)」が付与される。 Cloudflare Workers でリクエストを受け付け、ヘッダーの署名を検証する。 署名の検証に成功したら、ペイロードをパースして分析基盤に連携する。 Webhook エンドポイントはインターネットに公開されるため、不正なリクエストが送られてくる可能性があります。そこで、SendGridが提供するデジタル署名の仕組みを使って、正規のリクエストであることを検証しています。 Cloudflare Workersの特徴と制約 連携の中核を担うのは Cloudflare Workers です。高速に起動するサーバレス環境で、CDNのエッジ上で動作するという特徴もあります。パフォーマンスとスケーラビリティに優れ、コスト面でも優秀です。 ただし、一つ注意すべき制約があります。それは、Cloudflare Workersで動作するのは独自のJavaScriptランタイムで、Node.jsではないという点です。 nodejs_compat というフラグを有効化することで互換モードにすることはできますが、サポートされていないAPIや言語機能があります。 SendGrid は Webhookの署名検証を行うライブラリ を提供していますが、このライブラリが間接的に依存している js-sha256 の v0.9.0 は eval を使っていました。Cloudflare Workers のセキュリティモデルでは eval の実行が禁止 されているため、SendGridの公式ライブラリを使うことはできませんでした。 一応、npm 等の overrides 機能を使うことで eval に依存しないバージョンに置き換えることは可能です。 " overrides ": { " js-sha256 ": " 0.11.1 " } しかし、ライブラリの互換性の懸念からこの方法は避けました。 代替案: Web Crypto API SendGrid の署名は ドキュメント で説明されている通り、ECDSA(Elliptic Curve Digital Signature Algorithm, 楕円曲線デジタル署名アルゴリズム)を使っています。これは広く利用されているデジタル署名アルゴリズムなので、一般的な暗号ライブラリでも対応できるはずです。 そこで、Cloudflare Workersで利用可能なライブラリを調べたところ、 Web Crypto API が利用できることがわかりました。 実際のコードを見ていただいたほうが早いでしょう。Web Crypto API を用いた検証ロジックは以下のようになります。 interface VerifySendGridSignatureArgs { publicKey : string ; payload : string ; signature : string ; timestamp : string ; } export async function verifySendGridSignature ( { publicKey , payload , signature , timestamp , } : VerifySendGridSignatureArgs ): Promise < boolean > { try { // 公開鍵の読み込み const publicKeyBytes = base64ToBytes(publicKey); const cryptoKey = await crypto . subtle .importKey( 'spki' , publicKeyBytes, { name : 'ECDSA' , namedCurve : 'P-256' } , false , // 署名検証のみに使うので extractable は false でよい [ 'verify' ] ); // 検証するデータをバイト列に変換 const encoder = new TextEncoder (); const data = encoder. encode (timestamp + payload); // 署名をバイト列に変換 const signatureDer = base64ToBytes(signature); const signatureRaw = derSignatureToRaw(signatureDer); // この関数の実装は後述 // 署名を検証 return await crypto . subtle . verify ( { name : 'ECDSA' , hash : { name : 'SHA-256' } } , cryptoKey, signatureRaw, data ); } catch (error) { console .error( 'Signature verification failed with error:' , error); return false ; } } function base64ToBytes ( base64 : string ) { const binary = atob (base64); const bytes = new Uint8Array (binary. length ); for ( let i = 0 ; i < binary. length; i++) { bytes[i] = binary. charCodeAt (i); } return bytes; } ぱっと見はこれで良さそうですが、実は厄介な点があります。SendGridの署名はDER形式になっていますが、Web Crypto APIの verify メソッドにはRAW形式の署名を渡す必要があります。そこで、DER形式の署名をRAW形式に変換する必要があります。この処理は以下のように実装できますが、コードは難解で、後で保守する際に困りそうです。 /** * !!!注意!!! * この関数はセキュリティ専門家による実装ではありません。 * 本番環境では利用しないでください。 * * DER 形式 (ASN.1) の署名を Raw 形式 (R|S) に変換する関数 * ECDSA P-256 の場合、R と S はそれぞれ 32 バイトである必要がある。 */ function derSignatureToRaw ( derSignature : Uint8Array ): Uint8Array { // DER 構造: 0x30 | 全長 | 0x02 | R長 | R | 0x02 | S長 | S let offset = 0 ; if (derSignature[offset++] !== 0x30 ) { throw new Error ( 'Invalid DER signature: missing sequence tag' ); } // sequence length (skip) offset++; const extractInteger = (): Uint8Array => { if (derSignature[offset++] !== 0x02 ) { throw new Error ( 'Invalid DER signature: missing integer tag' ); } const length = derSignature[offset++]; const slice = derSignature. slice (offset, offset + length ); offset += length ; // DER 整数は符号付きなので、最上位ビットが1の場合、先頭に 0x00 が付くことがある。 // Raw 形式 (32バイト固定) にするために調整する。 // 1. 余分な 0x00 を取り除く (33バイトの場合など) let raw = slice; while (raw. length > 32 && raw[ 0 ] === 0 ) { raw = raw. slice ( 1 ); } // 2. 32バイト未満なら先頭を 0 で埋める if (raw. length < 32 ) { const padded = new Uint8Array ( 32 ); padded. set (raw, 32 - raw. length ); return padded; } // 3. 32バイトちょうどならそのまま return raw; } ; const r = extractInteger(); const s = extractInteger(); // R と S を結合して返す const rawSignature = new Uint8Array ( 64 ); rawSignature. set (r, 0 ); rawSignature. set (s, 32 ); return rawSignature; } @noble/curves の採用 Web Crypto API を使うコードも動作はしますが、セキュリティや保守性の点で不安が残ります。そこで、Cloudflare Workers でも動作する、より高レベルなライブラリを探したところ、 @noble/curves を見つけました。 先ほどの derSignatureToRaw 関数は、 @noble/curves の v1 なら以下の1行で実装できます。 const signatureRaw = p256.Signature.fromBytes(signatureDer, 'der' ); 2025年8月にリリースされた v2 では署名の形式の変換も不要で、DER形式の署名をそのまま verify に渡すことができます。 import { p256 } from '@noble/curves/nist.js' ; // v2.0.1 ... export async function verifySendGridSignature ( { publicKey , payload , signature , timestamp , } : VerifySendGridSignatureArgs ): Promise < boolean > { try { // 公開鍵の読み込み const publicKeySpki = base64ToBytes(publicKey); const publicKeyRaw = await p256SpkiToRaw(publicKeySpki); // この関数の実装は後述 // 検証するデータをバイト列に変換 const encoder = new TextEncoder (); const data = encoder. encode (timestamp + payload); // 署名をバイト列に変換 const signatureDer = base64ToBytes(signature); // 署名を検証 return p256. verify (signatureDer, data, publicKeyRaw, { format : 'der' , // DER形式だと明示すればRAWへの変換は不要 lowS : false , // SendGrid の署名は Low-s 強制されていない } ); } catch (error) { console .error( 'Signature verification failed with error:' , error); return false ; } } v2 の変更点 は他にもあり、メッセージをハッシュ化せずに渡せるようになったりと、より少ない手数で実装できるよう改善されています。一方で、lowSがデフォルトでtrue(主にブロックチェーン関係で利用する設定)になっており、この点は注意が必要です。 残念ながら、 @noble/curves は公開鍵のフォーマット変換には対応していません。そのため、ここだけWeb Crypto APIを使いました。 /** * P-256フォーマットのSPKI鍵をRAW形式の公開鍵に変換する */ async function p256SpkiToRaw ( p256SpkiKey : Uint8Array ): Promise < Uint8Array > { const cryptoKey = await crypto . subtle .importKey( 'spki' , p256SpkiKey, { name : 'ECDSA' , namedCurve : 'P-256' } , true , // export するので extractable は true にする必要がある [] , // export するので usage は空でよい ); const rawBuffer = await crypto . subtle .exportKey( 'raw' , // 'raw' の場合、 ArrayBuffer が返る cryptoKey, ); if (!(rawBuffer instanceof ArrayBuffer )) { throw new Error ( 'Expected rawBuffer to be an ArrayBuffer' ); } return new Uint8Array (rawBuffer); } なお、この関数は、実は Web Crypto API を使わずとも実装可能です。 /** * !!!注意!!! * この関数はセキュリティ専門家による実装ではありません。 * エラーハンドリングを意図的に省略しています。 * 本番環境では利用しないでください。 */ function p256SpkiToRaw ( p256SpkiKey : Uint8Array ): Uint8Array { /** * P-256 の SPKI 形式 の構造: * [ASN.1 Header] + [0x04 (非圧縮マーカー)] + [X (32バイト)] + [Y (32バイト)] * * 非圧縮マーカー + Raw鍵 (X+Y) を取得する */ return p256SpkiKey. slice (- 65 ); } このコードは先ほどの derSignatureToRaw 関数に比べればシンプルではありますが、やはり暗号アルゴリズムが専門ではない開発者が保守するには不安があります。そこで、あえて Web Crypto API で変換処理を行う形にしました。 おわりに 本記事では、Cloudflare Workers環境でも動作する、Web Crypto APIと @noble/curves を利用した署名検証処理の実装例を紹介しました。 ここまで読んで、「Node.jsベースのサーバレスを使えばこんなに頑張る必要ないのでは?」と思う方もいるかもしれません。今回はワークロードの特性やデプロイの容易さなどの観点から Cloudflare Workers を使いましたが、AWS Lambda や Cloud Run functions といった、完全な Node.js ランタイムを持つサーバレス環境を選択するのも有力な解決策でしょう。
キャディでAIエバンジェリストとしてBizdevをしている川村です! さて、 「AIで業務改善しなきゃ!」 という機運で、世界はあふれています。 壁打ちや議事録作成などは恐ろしいほど自由自在で、自分でやるよりよっぽどきれいなスライド資料まで作ってくれる時代になりました。 そうすると当然、もっと難しい業務も行けるのでは?という期待から、 「新しい自動車部品の設計FMEA *1 を作成して!」 みたいな呪文が、今日も元気にMicrosoftやGoogleのサーバーに送信されます。 しかし、AIはビタどまり。 業界慣習、企業の技術、個人の思想が混然一体となった神Excelは一向に出力されません。 「だからカーパシー *2 は、「Agent元年じゃなくて Decadeだ」って言ったのか・・」 と、設計者は肩を落とし、汎用AIではまず解けないタスクであることを受け入れます。 「そもそも業務が定まっていない」問題 さて、そんな顧客から依頼があり、圧倒的な生産性向上に向けたAIプロジェクトに取り組むことになったときにまず考えることは何か? ユーザーFBをどこまでAIの学習に組み込むのか どこまでの自律性をAIに認めるのか タスク完了をどのように定義するか こういったことを検討している時間は、全体の0.2%くらいです。 業界スタンダードの知識を頭に入れた上で、 「そもそも今この業務は、誰がいつどうやって実施してるんですか?」 と、極限まで細かく業務を捉えることが初めに来ます。 そしてその中で、 ベテランAさんのFMEA作成手法 別製品でやってるBさん独自の手法 一応あるらしい会社公式のようなもの 承認者Cさんはここを気にするという極めて重要なルール のような、 マルチフレームワークが存在する 業務であることを知ります。 業務解像度というレンズ 「業務解像度」という言葉で指しているのは、 その業務をどの粒度・どの観点で分解できているか です。 実際にFMEAを書いている設計者にヒアリングして、 「図面が確定したらFMEAを作って、DRで承認をとっています」 と返ってきたとしても、これだけではAIワークフローを設計するには解像度が粗すぎます。 更に次のような質問を重ねていきます。 FMEAを書くとき、最初に開くのは何の画面か 過去のFMEAは、どこから、どういう条件で探しているのか 故障モードは、頭の中から書いているのか、テンプレートがあるのか 誰のレビューが通れば「このFMEAはOK」と判断されるのか さらにさらに一段解像度を上げると、こんな会話になります。 「(私)エンジンマウントブラケットのFMEAを書くとき、まず最初に見るのは何ですか?」 「3D CADですね。あと、似ている形の過去FMEAを見ます」 「(私)“似ている”はどうやって判断しています?」 「形状と、取り付け位置と、荷重条件ですね。まあ、だいたい頭に入ってます」 「(私)それは検索条件に落とせるものなんですか?」 「うーん、「3年前のプロジェクトのやつが近いかなぁ」とかそんな感じです。」 自分がその業務をやれと言われたら、せめて 見習いとして最低限動きを辿れるレベルに、脳内で業務モデルをくみ上げ ます。 超絶地道ですが、ここが後の設計にめちゃくちゃ効いてきます。 プロジェクト成果を重ねる中で、「少なくともあと数年は、丁稚奉公しないと役立つものはできないな。」と確信してきました。 *3 解像度が低いまま設計すると何が起きるか 解像度が低いままAIワークフローを設計すると、だいたい次のようなことが起きます。 「それっぽい」アウトプットは出せる LLMに過去FMEAを読み込ませれば、「FMEAっぽい表」は出ます。 web記事もあるので、概念としてそれが何かは知ってたりします。 詳しくない自分から見れば、これでよいかも?と思えてしまいます。 ユーザーが見れば、一発で使えないと分かる 一発でわかります。Excelファイルを開く人差し指の感覚でバレます。 機密データの為、AIの学習データはweb上にほぼ存在しません。 前提となる口頭コミュニケーションや企業文化の中で形成される業務の為、単なる入出力のセットでとらえきれるものでもありません。 これは生のLLMの推論能力の問題ではなく、何をやってほしいのか 「AIに渡す前の業務モデルが粗すぎる」 ことが原因です。 AIにやらせたいことを、 元の非構造データをどう構造データとして表現するか タスクのどこをAIで柔軟に、どこを冪等性の高い手法でいくか 人との界面の体験、業務分担をどう設計するか というレベルで分解する必要があります。 「この業務、ポイントがだいたい分かってきたかも。」 と、勘所が体得され、自身でAI出力の良し悪しを最低限は評価して、FBループを高速に回していくことが欠かせません。 そうやって業務解像度をミリミリ上げていくと、AIワークフローの設計はだんだん “勝手に決まっていく” 感覚があります。 Bizdevとして見る「AIワークフロー設計」 キャディでBizdevというロールをやっていると、 製造業の業務ドメイン側から見える「現場の複雑さ」 AIエンジニアリング側から見える「AIの性質と限界」 の両方に同時に触れることになります。 そうした設計経験から 「AIワークフローのコツ」 をまとめると、 圧倒的な業務理解で、AIが扱える複雑性までタスク分解して再構成する です。 兎にも角にも、顧客業務を最前線で見に行くことをしてみる。 それがAgent Decade の入り口として、一番地味で、一番効くところだと感じています。 *1 : 「Failure Mode and Effects Analysis(故障モード影響解析)」の略で、切り戻しができない製造業において、設計起因のリスクを事前に網羅的に検討したもので、大抵Excel。セル結合され画像が貼られコメントがつき、ここまで使われれば電卓も本望。 *2 : 世界的なAIの技術者。「評価方法に致命的欠点があるからAIエージェントの進化はけっこうかかるぞ」みたいなことを最近言ってた。 *3 : 大変で裏切られたい気持ちもあります。
この記事は CADDi Tech/Product Advent Calendar 2025 の 5 日目の記事です。 こんにちは、キャディで CADDi Quote の開発をしている majimaccho です。 今回は、2025 年 11 月に開催されたアーキテクチャカンファレンス 2025 のキーノートセッションに参加した際のある一つの講演に電撃を受けたようなビビビッと来た話を共有したいと思います。 文字にすると当たり前だな〜と自分でも思ってしまいますが、自分の中で本当に腹落ちしたことをシェアしたいと思います。 TL;DR アーキテクチャカンファレンス 2025 に参加して、このイベントのキーノートを聞いて自分の考え方が大きく変わりました。 今回の気づきを一言で表すと、以下の通りです。 技術的な意思決定において、関係者全員がトレードオフを理解した上で意思決定できるように支援することがアーキテクトの役割である 技術的な意思決定において、自分がいいと思っているアイデアを伝えるだけでは十分ではない ただ意見を伝えるだけでは、意見を持っている人の 1 人に過ぎない アーキテクチャの意思決定はトレードオフであるということは理解していたつもりでしたが、そのことに対する向き合い方が間違っていたと気付かされました。 この記事について この記事は、2025 年 11 月に開催されたアーキテクチャカンファレンス 2025 のキーノートセッションの内容に基づいていますが、セッション内容の紹介や要約を目的としたものではありません。 あくまで、私個人の気づきや学びを共有することを目的としています。 想定読者 ミドルくらいのエンジニア、技術リードやアーキテクト、システム設計に関心のあるエンジニアを想定しています。 イベントとキーノート 今回の記事は アーキテクチャカンファレンス 2025 のキーノートセッションに基づいています。 Gregor Hohpe 氏の「アーキテクト思考 ― チームでより良い技術的意思決定を導くリーダーシップ」というタイトルの講演でした。 architecture-con.findy-tools.io 反省: イベントに参加する前の自分の考え方 アーキテクチャ上の意思決定はトレードオフであるということはわかっているつもりでいました。 しかし、アーキテクチャ上の意思決定を行う際に、潜在的に自分の持っているバイアスに気付かず、自分の意見を押し通そうとしてしまっていることがありました。 つまり、結論ありきで、その結論を正当化するための理由付けをしていました。 また、自分がいいアイディアを考えて、それを通すことのみが技術的なリーダーの役割(アーキテクトはその最上級)だと思っていました。 自分の意見がなぜいいのか、他者の意見がなぜ悪いのかというコミュニケーションをしてしまっていたことがありました。 時には「〜の方が直感的」「〜の方が自然」「〜の方が好み」なんていう言い方をしてしまっている時もあります。自分はアーキテクトと呼ばれるポジションではないですが、技術的な意思決定を行う立場ではある。その上で、これらは悪い振る舞いだと考えるようになりました。 目から鱗が落ちた話 和訳: アーキテクトは部屋の中で最も賢い人ではない。彼らは他の全員をより賢くする人だ。 このスライドを見たときに、ハッとしました。そして、この講演を通じて、自分の考え方が大きく変わりました。反省の項に書いたように、自分の意見が正しいということを主張してきたのではないかと考えさせられました。またそれは、最も賢い人であろうとしてしまっていたのだと気付きました。 ここからは、この講演で特に印象に残ったポイントをスライドと合わせて紹介します。 ※ あくまで、私自身が印象に残ったポイントであり、講演内容の要約や紹介を目的としたものではありません。 魔法の砂時計 このスライドでは、「バズワードになっているような内容 (マイクロサービス | CQRS | DDD | クリーンアーキテクチャ)が必要だから、人と時間、お金を確保して実行に移すけれども、なぜその方法なのか、なぜ他の方法ではないのか意味のあるロジックが不足している状態でことがよくある。この状態では、多くのリソースをかけて、何かを大きなことを成し遂げたとしても、得たかった成果は得られない可能性が高い」という話がありました。砂時計の形は How と Why の間のロジックが極めて薄いことを表現しています。 このロジックの部分が非常に重要で、意思決定を行う際に当然重要なはずだけれどもなぜか欠落してしまうものだという話でした。 そんなことがあってはいけないのは誰でもわかっているはずなのに、なぜか起きてしまう、ということに心当たりがありました。特にジュニアの時ほど、ベストプラクティス 1 つしか知らない状態でそれを盲目的に信じてしまったり、それを正当化するための理由付けを後から考えてしまったりしてしまうことがあると思います。 選択肢と次元を発見する よくある意思決定の例として、モノリス vs マイクロサービスの話がありました。 スケーラビリティの話をする際に、モノリスとマイクロサービスを比較することがよくあります。 この軸のみで比較した場合、マイクロサービスに軍配が上がります。 しかし、この時に、もう 1 つの次元を加えることを提案していました。それは、デザイン時なのかランタイムなのか、という次元です。 軸を加えることで、4 章限のマトリクスが生まれます。 (次のスライドの写真を撮りそびれましたが、左上は複製、右下はモジュラーモノリスだということが示されます。) 実際(発表者)が同様の議論を Google で行った時に複製が選ばれたと言われていました。Hohpe 氏がこの Google で議論をしたのは、モジュラーモノリスやマイクロサービスという言葉が生まれる前です。今の時代の私たちは最初から、モノリス・モジュラーモノリス・マイクロサービスという言葉がある状態で議論を始めることはできるでしょう。 しかし、それらがどう異なるのか、どの軸で比較すればよいのかを考えることは依然として重要です。 自分の関心のある軸だけで議論を始めてしまうと、他の重要な観点が抜け落ちてしまう可能性があります。テーブルに十分な選択肢と判断軸を揃えられるかというのが、アーキテクトの手腕が問われる部分だと思います。 異なる観点を考慮する この「軸を発見する」ということは、異なる視点を持つ関係者同士でのコミュニケーションにおいても重要です。アーキテクチャは短期・長期での開発速度、運用負荷、テスト容易性、セキュリティ、ビジネス戦略あらゆる側面(次元)があります。そういった関係者が認識を揃えるということはそれぞれの次元が考慮されていることを確認することでもあります。 データを揃える 「データがなければ、あなたは意見を持っている人の 1 人に過ぎない」 異なる立場の人たちが集まって意思決定を行う際に、各々が自分の意見を持ち寄るだけでは、単なる意見のぶつかり合いになってしまいます。できるかぎり具体的で測定可能なデータを揃えることが重要です。 しかし、データを揃えることにコストがかかりすぎる場合もあります。その場合は、各々の意見がどのような前提に基づいているのかを明確にすることも有効です。 前提条件を明確にすることは、無意識のバイアスに気付くことにもつながります。 アーキテクトブーメラン How の話をする時に Why に立ち返ってから選択肢をプリンシプルに照らし合わせて評価することが重要であるとこのスライドとその前で説明がありました。名前をつけてもらえた+イラスト化ことで、この考え方を忘れにくくなりそうです。 アーキテクトエレベーター アーキテクトは会社組織の最も上のレイヤー(経営層、CTO)にも、最も下のレイヤー(開発者)にも説明責任がありますが、各層の語彙は完全に異なっているので言葉を使い分けなければなりません。 完全に語彙が異なるとはっきり言われていました。つまり、アーキテクトは各層の語彙を理解し、適切に翻訳して伝える能力が求められるということです。開発者の語彙で経営層に説明しても伝わりませんし、その逆も同様です。 開発者からアーキテクトと呼ばれるようになるまでに新たな領域の学習が求められるということだと理解しました。 おわりに アーキテクトというロールを特別に担っているわけではない私ですが、技術的な意思決定を行う立場として非常に学びの多い講演でした。 企業によってはそのロールが明確に定義されていない場合もあるかもしれませんが、技術的な意思決定を行う立場にあるエンジニアであれば、意識すべき内容だと思います。 AI が普及していく中で、技術的な意思決定を行う立場にあるエンジニアの役割はますます重要になっていくと思います。 意思決定に必要な選択肢と次元を揃え、関係者全員がトレードオフを理解した上で意思決定ができるように学ぶべきことは多いと感じたイベントでした。
はじめに これは CADDi Tech/Product Advent Calendar 2025 5日目の記事です。 こんにちは、Data&Analysis部の宇佐見です。最近30%キーボードを買って新体験のタイピングを楽しんでいます。 さて、今回はキャディにおけるRAGを利用したプロダクト開発の技術選定と開発プロセスについて紹介いたします。 キャディにおけるRAGの位置づけ キャディでは、製造業AIデータプラットフォームを開発しており、その中の1機能としてドキュメント機能というものがあります。 これは、不具合情報や設計変更情報などの製造業に特化したドキュメントを管理・検索する機能です。自動的にドキュメント内容を読み取ることで、ユーザーはキーワードを用いてドキュメント検索することができます。 その中で我々はRAG技術を活用して、ユーザーがドキュメントから適切に情報を得られる機能の検証を行っています。 キャディにおけるRAG技術選定の変遷 初期の技術選定(何を重視したか) 初期の技術選定では、以下のポイントを重視しました。 顧客に届けられるまでの速度 我々はスタートアップであり、迅速な市場投入が重要です。また、キャディとしても初めてのRAGを活用したプロダクトであったため、早期にユーザーフィードバックを得ることが求められました。 実験のしやすさ 技術選定の段階では、様々なコンポーネントを試す必要がありました。実験のしやすさは、開発スピードに直結するため重要な要素でした。 技術選定で直面した課題 フェーズ変更に伴う課題の変化 最初のPoCフェーズでは、実験のしやすさが重要であり、構成としては以下のようなものでした。 UI: Streamlit LLM Framework: LangChain, litellm 検索エンジン: Elasticsearch 基本的にLangChainでingestionされたchunking, embeddingの取得を行い、Elasticsearchを利用して検索してからLLMを呼び出すという構成をとっていて、それをStreamlit上で見せる形でした。これにより、顧客にプロトタイプを早期に届けてフィードバックを得ることができました。 しかし、本番開発フェーズに移行する際、以下のような課題が浮上しました。 LangChainの制約 LangChainは非常に便利なフレームワークですが、簡単に利用できるようにされている反面、ロジックは隠されていて、動作を思うとおりコントロールするにはコードを深くまで読む必要があります。それにより開発効率が低下していると感じていました。また、当時はv1.0リリース前で破壊的変更が多かったこともあります。 プロダクトとの連携 PoCで使用していたコンポーネントは、プロダクトに組み込むには適していない部分がありました。我々はPythonを使ってPoCを開発していましたが、プロダクトはTypeScriptで構築されており、言語の違いによる統合の難しさがありました。 検討した代替案 LangChainの代替 LangChainの代替として、我々は独自実装を行うことを決定しました。以下の理由からです。 自由度の向上 独自実装により、各コンポーネントの動作を細かく制御できるようになり、プロダクトの要件に柔軟に対応できるようになりました。 開発効率の向上 LangChainのコードを深く理解する必要がなくなり、開発効率が向上しました。 チームのスキルセットの活用 チームメンバーはPythonに精通しており、独自実装により、既存のスキルセットを最大限に活用できました。 もちろんLangChainを利用するメリットも多く存在します。Memory機能やRDB連携など、便利な機能が多くあります。しかし我々が顧客に提供すべき価値を満たすレベルにおいては、独自実装で十分であると判断しました。 また、顧客から得られたフィードバックを素直に反映し、プロダクトの改善に注力できるという点でも独自実装は有効でした。 プロダクトとの連携方法の検討 プロダクトとの連携方法としては、以下のような案が挙げられるでしょう。 マイクロサービス化 RAGコンポーネントを独立したマイクロサービスとして構築し、プロダクトからAPI経由で呼び出す方法。 プロダクトへの直接組み込み RAGコンポーネントをTypeScriptで再実装し、プロダクトに直接組み込む方法。 最終的に我々はプロダクトへの直接組み込みを選択しました。理由としては、マイクロサービス化は運用コストが増加することが挙げられます。RAGを用いた機能は未だβ版で、開発メンバーも3人しかおらず、全員がMLEであるためプラットフォーム運用には長けていません。プラットフォームチームも余剰リソースがないため、新しいサービスを運用するのは現実的ではないと判断しました。 現状のRAGプロダクトの開発プロセス この経緯を踏まえた現在の開発プロセスを紹介します。 要件定義フェーズ 顧客からのフィードバックを受け、課題を分解し、要件を明確化 PoCフェーズ Pythonでのプロトタイプ開発 必要があれば顧客への早期デモとフィードバック収集を行います。これはカスタマーサクセスだけでなく、PdMやエンジニアも同席して行います。 ※課題が明確であれば、PoCフェーズをスキップして要件定義フェーズから本番開発フェーズに直接移行することもあります。 本番開発フェーズ TypeScriptでのプロダクト組み込み applicationチームと密に連携し、RAGコンポーネントをプロダクトに組み込みます。 実際どうなのか? RAG開発チームのメンバーは皆MLEであり、TypeScriptでの開発経験はほぼなかったことから、本番開発フェーズは苦労もあります。また、DDDの考え方を取り入れたプロダクトコードの構成に戸惑うこともありました。 しかし、RAG開発チームがプロダクトに組み込む部分はうまく分離されていて、触るべきところが明確なのでそこまで迷わない状態になっているので思っていたよりはスムーズに進んでいます。(applicationチームに感謝です) また、現在はAIによるコーディング支援が実用レベルに達しているという点も大きいです。TypeScript文法やライブラリの使い方をAIに教えてもらったり、ベースコードを生成してもらったりすることで、開発効率が大幅に向上しています。 現状では、課題設定から本番開発完了まで1~2週間程度でproduction環境にまで我々が実装した機能を届けられていて、速度という面ではこの選択が正解だったと感じています。 今後の展望 技術選定の振り返りと改善点 今後も技術選定のプロセスを継続的に改善し、より迅速かつ効果的な開発を目指していきたいと思っています。 今のところβ版であり、ユーザー数がそこまで多くないためにスケールをそこまで意識していない点は特に改善の余地がありそうです。将来的にはスケーラビリティも考慮した構成を検討していく必要があるでしょう。 まとめ キャディにおけるRAG技術の選定と開発プロセスについて紹介しました。 実はRAG機能の開発が始まったのは今年の5月であり、まだ日が浅いものです。しかし、迅速な技術選定と開発プロセスの確立により、顧客に価値を提供できるプロダクトを短期間で構築できる体制を整えることができました。 ただ、まだまだ改善の余地があり、開発を加速させる必要があります。そのためにももっと多くのエンジニアの力が必要です。 もし興味があれば、ぜひ一度カジュアル面談にお越しいただき、我々の取り組みについて直接お話させてください。 もちろん、他にキャディが何やってるのか気になるという人もウェルカムです。 MLE/MLOps エンジニア募集 カジュアル面談はこちらから
こんにちわ、Core Infrastructure チームの前多です。膝が痛い。 こちらは キャディ株式会社のアドベントカレンダー の3日目の記事です。 先日、弊社の同僚からCADDiのアーキテクチャと開発組織に変遷に関する発表が行われました。 14:55〜E会場 キャディ株式会社/CADDiの発表資料 「事業状況で変化する最適解。進化し続ける開発組織とアーキテクチャ」を公開しました🙌 よろしければお手元でもご覧ください! https://t.co/DrStp16fon #アーキテクチャcon_findy — CADDi.tech (@CaddiTech) 2025年11月21日 私たちのプロダクトのインフラは Terraform で構成しています。 プロダクトがロンチされてから3年以上経っていて、その発展に従ってTerraformの構成も大きく変化してきました。 この記事ではプロダクトのTerraformがどのように変化してきたかを紹介していきます。 というのは建前で、どこかでTerraformネタで発表しようと思って溜めていてたネタだったんですが、機会がなかったのでここで記事にしました。 CADDi Drawer 初期(2021-2022) 図面管理SaaSとしての基本的な機能を作ってロンチした頃。 この頃は、SaaSの機能は少なくTerraformの構成も次のようにシンプルなものでした。 terraform ├ environments │ ├ dev │ │ ├ main.tf │ │ └ variables.tf │ ├ stg │ └ prod └ modules   ├ cloudsql   │ └ main.tf   ├ iam   ├ gke   ├ gcs   ├ network   ├ pubsub   └ secret environments は terraform のapply対象、state管理の対象となるルートモジュールで、ここでGoogle Cloudのプロジェクトや環境ごとのパラメータを持っています。 moduels配下は、ルートモジュールから参照されるもので、Google Cloud に作成する実際のリソースを管理しています。 当時はモジュールは、Google Cloudの機能相当で分割していたようです。 networkモジュールでVPCやサブネットを、gkeモジュールでGKEクラスタやノードプールを、のようにインフラの共通リソースの定義から始まって、 それを踏襲して Cloud Pub/Subが欲しくなったので pubsubモジュールを、のようにモジュールをクラウドの機能単位で作っていました。 (後から振り返りますが、これはモジュールの作りとしてはあまり良くはないことがわかってきます) また当時から、このリポジトリには Terraformと Terraform Providerの更新を自動化する仕組みや、PullRequestのステータスに合わせて Plan/Applyを自動化する仕組みを導入していました。 これがあったからこそ、後続のリファクタリングがうまくいったと言っても過言ではありません。 では次にどうなったのかを見てみましょう。 CADDi Drawer 成長期(2023-2024) 機能強化やCADDi Quoteなどのプロダクトの追加といった様々な追加開発を行なっていた頃です。 開発に関わるメンバーも増え、チーム体制を取ったりと組織面でも大きな変化があった時期です。 この頃の Terraform の構成はおおよそ次のようになっていました。 terraform ├ environments │ ├ dev │ │ ├ main.tf │ │ └ variables.tf │ ├ stg │ └ prod └ modules   ├ cloudsql   │ ├ main.tf   │ └ service_a.tf   ├ bigquery   ├ iam   │ ├ main.tf   │ └ service_a.tf   ├ gke   ├ network   ├ pubsub   ├ gcs   │ ├ main.tf   │ └ service_a.tf   ├ secret   │ ├ main.tf   │ └ service_a.tf   └ some_saas ディレクトリ構成的にはあまり変わっていません。 Google Cloud で利用するAPIの追加に従ってモジュールが増える他、 この頃には Google Cloud以外の外部SaaSも使い始めてその管理用のモジュール(some_saasとしておきます)も追加されました。 そして、Google Cloud の機能単位で作成された pubsub,secret,iamなどのモジュールは 複数の開発チームが相乗りして、それぞれ必要とするリソースを追加していました。 この状態のまま、Terraform のコードが増えていったため、次のような困りごとが出てくるようになりました。 1つの修正で複数モジュールを修正する必要がある Google Cloudのリソース同士は依存性を持つことがあります。最も良くあるのが、リソースに対してサービスアカウントのIAM Roleを付与するパターンです。 この場合、リソース単位でまとめたモジュールだとモジュール間の依存性が生まれます。 GCS バケットとサービスアカウントを作成してIAM Roleを割り当てるには現状のモジュール構成だと以下のようになります。 iamモジュール内でサービスアカウントを設定して、memberをoutputで返す。 resource "google_service_account" "some_sa" { account_id = "some_sa" } output "some_sa_member" { value = resource.google_service_account.some_sa.member } gcsモジュールでGCSバケットを作成し、variableでSAメンバー名を受け取り、IAMロールを付与する # gcs module resource "google_storage_bucket" "some_bucket" { name = "some_bucket" project = var.project_id location = "ASIA-NORTHEAST1" force_destroy = false } resource "google_storage_bucket_iam_member" "iam_member_example" { bucket = google_storage_bucket.some_bucket.name role = "roles/storage.user" member = var.some_sa_member } ルートモジュールでmodule間のoutputとvariableを渡します。 module "iam" { source = "../../modules/iam" } module "gcs" { source = "../../modules/iam" # iam moduleの outputのSA memberを渡す some_sa_mamber = module.iam.some_sa_member # リソースが追加されるたびに variableが増えていく hoge_sa_member = module.iam..... } とある開発チームが、GCSバケットと権限を設定するためには、二つのモジュールを修正し、モジュール間のパラメータの受け渡し(outputとvariable)を追加する必要があります。 こういったことがGCSやPub/Subなど様々なリソースで起きるので、何かしらの変更が起きるたびに複数のモジュールにまたがる修正が必要でした。 複数のリリース対象が混じっている Google Cloudと 他のSaaS のTerraform 構成が一つのstateに混在した結果、SaaSのみをアップデートしたくてもGoogle Cloud側のリソースの修正も混じっていてリリースタイミングの調整が必要になることがありました。 このようなことから、今後更なる機能追加の障害になると考え、モジュール構成の見直しとstateの分割を検討しました。 モジュール構成の見直し 一般的なプログラミングにおける良いモジュールとは、モジュール間の依存が少なく、モジュールの中には関連が強いものが集まる、つまり疎結合・高凝集であることです。 なんからの修正に対して単一のモジュールのみの修正で済んだり、他のモジュールに影響を与えずにモジュールの追加・削除ができることが望ましい姿であると言えます。 複数の開発チームが同時に開発している状況では、チームが開発している各サービスでリソースをまとめるのが適切だろうと判断しました。 次のようなモジュール構成にすることにしました。 terraform ├ environments │ ├ dev │ │ ├ infra │ │ │ ├ main.tf │ │ │ ├ infra.tf │ │ │ ├ app_a.tf │ │ │ └ app_b.tf │ │ └ some_saas │ │   └ main.tf │ ├ stg │ │ ├ infra │ │ └ some_saas │ └ prod │   ├ infra │   └ some_saas └ modules   ├ cloudsql   ├ network   ├ gke   ├ service_a   │ ├ iam.tf   │ ├ gcs.tf   │ ├ pubsub.tf   │ └ secret.tf   ├ service_b   └ service_c modulesについては、開発チームが作成しているサービス単位でモジュールを作成しそこにそのサービスで使うリソースをまとめます。 ただし、VPCや GKEなどの共通基盤として利用するリソースはそのままです。 ルートモジュールについては、Google Cloud とその他のSaaSについてはこの時点でstateを分けることにしたので、階層を下げました。 Google Cloudのルートモジュールについては単一のtfファイルでモジュールの呼び出しをしていたものを、アプリケーション単位でファイル分割します。 これは将来的にstateを分割することも考慮しています。 こうすることで、前述の GCSバケットとIAMの設定については同一モジュール内で定義が済むことになります。 resource "google_service_account" "some_sa" { account_id = "some_sa" } resource "google_storage_bucket" "some_bucket" { name = "some_bucket" project = var.project_id location = "ASIA-NORTHEAST1" force_destroy = false } resource "google_storage_bucket_iam_member" "iam_member_example" { bucket = google_storage_bucket.some_bucket.name role = "roles/storage.user" member = google_service_account.some_sa.member } outputもvariableも不要になり、すっきりします。 ですが、モジュールの構成を変えるというは単にソースコードを直せば良いというわけではありません。 どうやってこれを達成したかを次に解説します。 同一state内のリソースの移動 Terraformのリソース定義は、名称を変更するだけでも stateとの差分が発生するので リソースの削除と新規作成という結果になります。 これは、Terraformの仕様上しょうがない部分で、stateを tarraform state mv のようなコマンドで直接修正するという方法があります。 developer.hashicorp.com ただコマンドによるstate の変更は、stateを直接更新してしまうので、試行錯誤しながら作業を進めていくのは難しいです。 Terraform 1.1 から moved block, import block, removed block という機能が提供されました。 developer.hashicorp.com これは、stateの変更をしたい内容をルートモジュールに記載しておくことで、変更の結果を加味してplan/apply を行なってくれる機能です。 これを使えば、変更の結果を試行錯誤しつつ作業を進められます。 例えば、前述のgcs, iam モジュールの内容を service_a というモジュールに移動する場合、移動先のモジュールのソースコードを書いて、 次のような moved block を書きます。 moved { from = module.gcs.google_storage_bucket.some_bucket to = module.service_a.google_storage_bucket.some_bucket } moved { from = module.gcs.google_storage_bucket_iam_member.iam_member_example to = module.service_a.google_storage_bucket_iam_member.iam_member_example } moved { from = module.iam.google_service_account.some_sa to = module.service_a.google_service_account.some_sa } movedブロックがある状態で planをすると、モジュールを変更した状態で比較が行われるので基本的に差分は無しになります。 名称のミスなどがあって差分が出た場合でも、安心して修正ができます。 余談ですが、この作業はモジュール内のリソースの一覧を出力するなどしてある程度は機械化できるのですが、結構大変でした。 当時はcopilotが登場したくらいの頃だったので、今ならAIツールでもっと賢くできるかもしれません。 以下の画像が一気にモジュール構成を変えた時のPRのサマリです。この量でもplan結果はほぼ差分なしでした。 補足 import, removed block 基本的には moved ブロックだけで事足りるのですが、作業を進めていく上でいくつか個別対処したことがあります。 1つめは、複数のモジュールで同一のリソースが定義されているというものでした。 IAM ロールを割り振る iam_member リソースは、色々なモジュールで定義されていて、モジュールの変更を見直していたら全く同じ内容が出てきて一つにマージする必要がありました。 単純にまとめてしまうと、片方のiam_memberリソースが削除扱いになるので場合によってはiam_memberが消えてしまう可能性もあります。 この場合、removed blockによってTerraformのstateでだけそのリソースを無かったことにします。 removed { from = modue.iam.google_storage_bucket_iam_member.some_member lifecycle { destroy = false } } destroy = false でGoogle Cloudからはリソースを削除しないとを明示することに注意します。 2つめは、Terraformで管理されていないリソースがある環境でだけあったというもので、これは import block でterraform stateに取り込みます。 import { to = module.service_c.google_service_account.some_sa id = "projects/$ { var.project_id } /serviceAccounts/some_sa@$ { var.project_id } .iam.gserviceaccount.com" } import は import したいリソースごとに id を指定します。idに何を書くかはリソースによって異なるので、ドキュメントを読んで正しいIDを指定することに注意します。 state分割でのリソースの移動 state を分割する場合、 前述の moved ブロックは使用できません。 state mv コマンドでモジュール単位で別のstate に移動していきます。 state の移動元と移動先それぞれで、removedブロック,importブロックを使えばひょっとすると代替できるかもしれません。 しかし import ブロックは上で述べた通りID指定が必須なので移動したいリソースのIDを列挙するのは困難なのでお勧めしません。 stateをまたいだリソースの移動は次の手順で行います。 移動元、移動先それぞれのルートモジュールでstateをローカルにダウンロードして、ローカルのstateを参照する stateまたぎでmoduleを移動する 両方のルートモジュールで plan を実行し、差分がなければローカルstateをリモートにpushする 次のスクリプトで1,2を自動化します。 #!/bin/bash # 移動元ルートモジュールのパス SRC = $1 # 移動先ルートモジュールのパス TARGET = $2 # スペース区切りでルートモジュール内の移動するmodule 名のリスト。 MODUELS = $3 base_dir = $( pwd ) echo " SRC ローカルにstateをダウンロード " cd $SRC terraform init terraform state pull > ${base_dir} / ${TARGET} /src.tfstate echo " TARGET ローカルにstateをダウンロード " cd $base_dir cd $TARGET terraform init terraform state pull > target.tfstate # localのstateを使うように一時ファイルで上書き cat << EOF > override.tf terraform { backend "local" { path = "target.tfstate" } } EOF # ローカルstateを使うようにinit をやり直す terraform init -reconfigure # モジュールリストごとにstateをmove for module in $( tr ' ' ' \n ' <<< ${MODUELS}) do echo " move module. ${module} " # state-out で移動先のstate ファイルを指定する terraform state mv -state = src.tfstate -state-out = target.tfstate module. ${module} module. ${module} done この状態で plan を実施して、差分がないようなら 次のスクリプトで更新後のstateを反映します。 #!/bin/bash SRC = $1 TARGET = $2 base_dir = $( pwd ) echo " TARGET: リモートのstateを使うように設定し直して、state をpushする " cd ${TARGET} # push target state rm override.tf terraform init -reconfigure terraform state push target.tfstate echo " SRC: state をpushする " cd ${base_dir} mv $TARGET /src.tfstate $SRC /src.tfstate cd $SRC terraform state push src.tfstate planのチェックもスクリプトで自動化してしまえば全作業が自動化できそうです。 現在そして将来 これまでの作業で、ある程度モジュールの独立性が確保されたため、Terraformのコード修正は比較的楽になりました。 その結果、Terraform コードが増えたので今は次のような問題を抱えています。 plan/apply にかかる時間が増えている, 3-5分かかっている リソースが増えすぎていて、モジュールやリソースのオーナーがわかりづらくなっている これらの問題についてどのように解決するかは現在進行形ですが、次のように考えています。 stateをアプリケーション単位で分割し、plan/applyを並列化する ルートモジュールごとに default labelを付与して、生成されるリソースのオーナーがわかるようにする stateを分割すると、state間の情報共有をどうするかやapplyの順序といった問題が出てきます。 多分完璧なやり方はないだろうと思っているので、ある程度の妥協をしつつ進めていくのかなと思っています。 何か良いアイデアをお持ちの方はぜひ教えてください。 まとめ モジュールは関連の強いリソースでまとめましょう。そして関連は技術的な軸ではなく開発組織の軸で考えましょう そこが思い浮かばないなら、無理にモジュールにしなくても良いです モジュールの構成をしくじっても、どうにかなります。気合と根性で解決したことも今ならAIで楽になるはず moved blockが使えなければ詰んでいたので、Terraformのアップデートは運用に組み込みましょう これを見ているあなたもぜひ、我が家のTerraformの歴史を公開してみてください
キャディTechチームは、先日開催された「アーキテクチャカンファレンス 2025」にGoldスポンサーとして協賛し、ブース出展・セッションへの登壇の両方で参加させていただきました。 この記事では当日のブースの様子と、登壇したキャディCTO室長山田の資料をお届けします! 会場の熱気とキャディブースの様子 会場の雰囲気 会場全体は、最新の技術トレンドや大規模システムの課題解決に対する熱気に包まれていました。 昨年よりもかなりパワーアップした大きな会場で装飾も素晴らしく、会場に足を踏み入れただけで気持ちが盛り上がりました。 入り口の暖簾かわいい! キャディブースのご紹介 キャディのブースではオリジナルの「最適解おみくじ」を楽しんでもらったり、「あなたのチームは今どんなフェーズ?」というパネルを設置して来訪者のみなさまにシールを貼っていただくなどインタラクティブな取り組みにチャレンジ。 ブースに設置したモニターでは、出来立てほやほやのキャディTechドキュメンタリー動画を放映しました。 動画はこちらからご覧いただけますのでぜひどうぞ! youtu.be 最適解おみくじ ブースに来ていただいた皆さまの投票結果 ノベルティには様々なイベントで大好評の「キャディ特製ビッグカツ」をご用意しました! 噂のキャディ特製ビッグカツ CEO加藤もお気に入りのキャディ法被で参戦 登壇内容の紹介:事業状況で変化する最適解。進化し続ける組織とアーキテクチャ イベント2日目にはキャディCTO室長の山田のセッションがあり、多くの方に参加いただきました。 山田の登壇は社内でも期待が大きく、登壇が決まってから多くのメンバーが楽しみにしていたものです。 多くのメンバーの期待を背に、山田も普段以上に気合いの入った登壇となりました。 直前まで資料の調整をする山田 セッションはおかげさまでほぼ満員となり、登壇後はブースにお立ち寄りいただく数も増え、大盛況のうちに終了となりました。 セッションの様子 ブース大盛況 当日の山田の登壇資料をSpeaker Deckで公開しています。会場で参加くださった方も、見逃してしまった方もぜひご覧ください。 https://speakerdeck.com/caddi_eng/shi-ye-zhuang-kuang-debian-hua-suruzui-shi-jie-jin-hua-sisok-kerukai-fa-zu-zhi-toakitekutiya speakerdeck.com まとめ 今年のアーキテクチャカンファレンスは、大規模システムの課題解決や複雑なドメインのデータ活用、組織的な信頼性構築などをテーマにしたセッションが多く見られ、キャディでも日々直面する課題に向けた示唆のある内容が多かったように感じています。 会期中は、ブース運営の応援をしつつキャディのエンジニア達が現地参加してセッションを聞くなど、学びの多い充実した時間となりました。 主催のFindyさんがこちらに当日のセッション資料をまとめてくれていましたので、興味のある方はぜひ覗いてみてください。 conference.findy-code.io 最後に キャディでは全方位で採用を強化しています。 採用サイトをリニューアルしましたので、こちらも併せてぜひご覧ください! careers.caddi.com https://open.talentio.com/r/1/c/caddi-jp-recruit/pages/115650 open.talentio.com https://open.talentio.com/r/1/c/caddi-jp-recruit/pages/78398 open.talentio.com
こんにちは、Data&Analysis部(D&A)です。 D&Aでは週1回、機械学習の勉強会を開催しており、本記事は、勉強会の内容を生成AIを活用して記事にまとめたものです。 ※勉強会内容公開の経緯は こちら ※過去の勉強会は「社内勉強会」タグからもご覧いただけます。 はじめに:我々が直面していた課題 現在、我々はドキュメントを解析するプロジェクトを推進しています。その中で以下のような壁に直面しました。 フォーマットの多様性 PDF、Word、PPT、スキャン画像など、形式がバラバラなドキュメントの前処理が大変 構造情報の損失 テキスト抽出時にレイアウト、表、図が崩れて意味が失われてしまう 既存ツールの限界 商用ツールは高価かつクラウド必須の制約があったり、OSSでは品質・機能不安があったり それらを解決するため、Doclingが効果がありそうだと分かり、その性能検証を進めることにしました。 DoclingはIBMによって開発されたOSSのドキュメント変換ツールキットです。次の章でDoclingについて説明していきます。 ちなみに、この記事は以下の論文とテクニカルレポートを参考にした内容になります。 [2408.09869] Docling Technical Report [2501.17887] Docling: An Efficient Open-Source Toolkit for AI-driven Document Conversion (AAAI,2025) Doclingの主要なコンセプトと特徴 Doclingは以下の4つのキーワードでその強みを説明できます。 高性能AIモデル搭載 DocLayNetベースのレイアウト分析モデルを内蔵し、ページ内のタイトル、本文、表、図などを検出します。RT-DETRベースの高速オブジェクト検出器で、文書レイアウトに特化したDocLayNetで学習済み。 TableFormer(表構造認識)を内蔵し、表の画像からセルの結合や階層ヘッダーを含む複雑な構造を正確に読み取ります。Vision Transformerベースで、罫線がない表や複雑なレイアウトの表も言語に依存せず高速に解析可能。 OCR(文字認識)はEasyOCR(デフォルト)とTesseractをサポートしています。 完全ローカル実行 クラウドにデータを送ることなく、手元のマシンやオンプレミス環境で完結するため、機密性の高い文書も安心して扱えます。 豊富な対応フォーマット PDF、画像、Word、PowerPoint、Excel、HTMLなど、ビジネスで使われる主要なドキュメント形式を幅広くサポートしています。 開発者に優しい MITライセンスで商用利用も可能であり、Pythonライブラリとして提供されるため、LangChainやLlamaIndexなどの主要なフレームワークと簡単に連携可能です。 DoclingDocument データモデル DoclingDocumentは、Doclingの中核となるデータモデルであり、様々なフォーマットの情報を1つの型で表現することで、後段の処理での扱いやすさを追求しています。 多様な要素 テキスト、表、リスト、画像、キャプションなどを個別の要素として認識します。 階層構造 セクションやヘッダー/フッターといった階層構造を保持します。 豊富なメタデータ 各要素がページのどこにあるか(座標情報)や、どのページ由来か(出所情報)といったメタデータを持ちます。 柔軟な操作 ドキュメントの構築、検査、そしてRAGに最適な「チャンク」への分割も容易です。 内蔵AIモデルの詳細 Doclingに内蔵されているAIモデルは以下の通りです。 レイアウト分析モデル (DocLayNetベース) ページ内のどこが「タイトル」「本文」「表」「図」なのかを検出する。 RT-DETRベースの高速オブジェクト検出器。文書レイアウトに特化したデータセットDocLayNetで学習済み。 TableFormer (表構造認識) 表の画像から、セルの結合や階層ヘッダーを含む複雑な構造を正確に読み取る。 Vision Transformerベース。罫線がない表や複雑なレイアウトの表も、言語に依存せず高速に解析可能。 OCR (文字認識) スキャンされた画像からテキストを抽出する。 EasyOCR(デフォルト)とTesseractをサポート。 これらのレイアウト分析と表構造認識モデルは、事前学習済みの重み(Hugging Faceでホスト)と、推論コード用のPythonパッケージ(doclingibm-models)が提供されています。 パフォーマンス比較 テクニカルレポート内で行われていた、幾つかのOSSとの比較実験を紹介します。 比較対象は、unstructured.io (Unstructured.io Team 2024)、Marker (Paruchuri 2024)、MinerU (Wang et al. 2024)です。 実験内容 データセット: 多様なスタイル、機能、コンテンツ、長さをカバーする89個のPDFファイル(4008ページ、56246個のテキスト項目、1842個の表、4676枚の画像を含む)からなるテストセットを使用。それぞれのデータで項目の抽出にかかった時間を比較。 システム構成 AWS EC2 VM (g6.xlarge, Nvidia L4 GPU搭載)とMacBook Pro M3 Max (ARM) で比較。 まずDoclingで実行環境の違いによる抽出速度の差を確認してみましょう。 ほとんどのタスクにおいてはGPU搭載の環境での速度が最速でした。 ただし、pdfのパースにおいてはあまりGPUの恩恵は受けられないようです。 続いて、各OSSとの比較です。 結論として、GPU環境ではMinerUが最速ですが、Doclingはどのようなマシンでも満遍なく速いという特徴が見られました。unstructuredに関してはあまりGPUの恩恵を受けられないようです。 苦手な点・今後の課題 縦書き文字の表示が崩れる可能性があります。 追加実験 実際に手元でdoclingを利用してドキュメントを変換してみます。 uvでdoclingをインストールすれば、下記のようにdoclingを実行できます。 uv run docling target_data まず、doclingの論文を変換してみましょう。web上のデータも対象にできます。 uv run docling https://arxiv.org/pdf/2501.17887 変換結果の一部分は以下の通りです。 テキストはmarkdown構造で出力され、画像はbase64変換されており、vscodeのプレビュー機能で綺麗に表示できます。 表構造も表としてmarkdown内で表現されています。 別の例として、日本語の文章も変換してみましょう。 例として、こちらの 令和6年度 年次経済財政報告 を変換してみます。 uv run docling https://www5.cao.go.jp/keizai3/2024/0802wp-keizai/setsumei00.pdf こちらは元々pdf形式のスライドなのですが、以下の通り画像も出力できていて、表構造や文書の構造も守って変換できているように見えます。 しかし、以下のような縦書きのラベルに関してはうまく出力できていません。一番右の「製品・サービスの品質低下を招く」というラベルは、「製品・サ」で途中までしか出力されていません。また、その他のラベルは全く出力されていません。 まとめ Doclingは、非構造化データのAI活用におけるドキュメント変換の課題を解決する、高性能で柔軟なオープンソースツールキットです。 特にRAGシステム構築において、その構造理解能力とローカル実行の安全性は大きなメリットとなることがわかりました。 しかし縦書きの文字はうまく出力できないという課題もあり、日本語の文書に適用するには工夫が必要です。
はじめに 「Excellence」とは、スキルではありません。 それは「明日を今日よりも良いものにできる」という信念の表れであり、自らの選択です。 こういった考えは、一見楽観主義のようにも映ってしまいますが、そのような受け身なものではありません。そこには強い意志を伴う決断が必要不可欠です。なぜなら、私たちは生まれつき楽な方へと流されやすい生き物だからです。 私たち人間は習慣の生き物であり、安定や現状維持を好みます。 私が採用の意思決定において「カルチャーフィット」という言葉を使わないのは、これが理由です。この言葉を使うこと自体に異議を唱えるつもりはありません。スタートアップという環境において、共通の価値観を持ち、同じ目標を達成したいと願うことは極めて重要です。しかし、この言葉には、変化への無言の抵抗や現状維持を望むニュアンスが潜んでいます。 だからこそ私は 「カルチャーインパクト 」という言葉を好んで使います。「この人はチームにフィットするか」と問うのではなく、「この人はチームをどう変えてくれるか」と考えるのです。これは些細な違いに思えるかもしれませんが、Excellenceとは日々の進化なくしてはあり得ません。故に私たちは、あらゆる変化を受け入れ、飽くなき進化を求め続けます。 そもそもスタートアップとは、現状を受け入れるのではなく、より良い世界に向けたビジョンを実現するために挑戦する存在です。 キャディのミッションは「モノづくり産業のポテンシャルを解放する」です。 なぜなら、私たちは製造業にはまだ解き放たれていない、計り知れない可能性があると信じているからです。このミッションに突き動かされ、私たちはわずか数名の組織から、8年足らずで4カ国にまたがる700名超の企業へと成長しました。しかし成長に伴い、私たちはリスクを避け、プロセスを重視し、意思決定において保守的になる傾向が強まっていることも事実です。これは当然のことです。私たちのソフトウェアに事業の運営を託してくださる、大切なお客様が存在するのですから。 しかし、忘れてはならないのは、私たちはミッション達成には程遠い場所にいるということです。決して自己満足に陥ってはなりません。その瞬間に、私たちが変えようと志したはずの「現状」そのものになってしまうからです。 Excellenceの追求 エンタープライズソフトウェアにおけるExcellence エンタープライズソフトウェアは、しばしば「ひどいものだ」と揶揄されがちです。2018年、YCombinatorは「Request for Startups」の中で、「大企業で使われるソフトウェアは依然としてひどいままだが、非常に儲かる」と指摘しました。これは事実です。その理由の一つは、エンタープライズソフトウェアの開発がとにかく難しいことにあります。企業は大規模で複雑なだけでなく、多くのステークホルダーが関わっているからです。 コンシューマー向けソフトウェアでは、ユーザー満足度と購入行動が比較的ダイレクトに結びついています。しかし、エンタープライズアプリケーションは、もっと複雑で広範な利害関係者の要望に応えなければなりません。予算を握る経営層、システムを維持する管理者、カスタマイズを行う外部のSIer、そして最終的に実際に利用するエンドユーザー。つまり、多くの場合「使う人」が「買う人」ではないのです。 たとえば経費精算システムを購入するのは経理部門であって、毎日それを使う従業員ではありません。法規制を満たす必要もあり、その結果ユーザビリティが犠牲になることもあります。購買チームは巨大な機能比較表を用いて判断し、ベンダーは「十分な数のチェックボックスを埋める」ことに注力します。なぜなら、それがこのゲームのルールだからです。 確かにこれらの制約は紛れもない現実であり、私たちは、その現実から目をそむけるつもりはありません。 キャディのプロダクトビジョンに、 "Kickstarting Transformation" という言葉が含まれています。真の変革には、短期的な現場の問題に対処し、それを長期的な成果に結びつける必要があると認識しているからです。私たちは、大きな変革がトップダウンの指示だけでは達成できないことを知っています。経営層の支援と、組織全体での有機的な繋がりの、両方が必要なのです。そのため、私たちのソフトウェアは機能的であり、すべてのチェックボックスを埋め、コスト削減、リードタイム短縮、あるいは組織文化の変革といった、経営層にとって重要な成果を提供しなければなりません。 しかし、ここがまさに落とし穴なのです。プロダクトが最低限許容できる成果を出せるようになると、そこで満足し、それが合理的でさえあるように思えてきます。どうせユーザーは使うことを義務付けられているのに、なぜパフォーマンスを最適化する必要があるのか? 機能の幅広さが購買担当者の心を掴むのに、なぜ優れたUXに投資する必要があるのか? ソフトウェアエンジニアという業界人として、私たちは高性能なアプリケーションを構築する方法を知っています。それなりのLighthouseスコアを達成することは、別に難しいことではありません。クラウドプロバイダーは堅牢なインフラを提供してくれますし、オープンソースコミュニティは優れたフレームワークで私たちをサポートしてくれます。コンポーネントライブラリやデザインツールは成熟し、Web上のユーザーインタラクションモデルも確立されています。 それにもかかわらず、私たちは「時間がない」「ユーザー要件ではない」「他に実装すべき機能がある」といった言い訳を見つけては、手を抜いてしまいます。 しかし実際には、これは現実主義という名目で、組織やエンジニアリングの規律の欠如を覆い隠しているに過ぎないのです。 高名なドン・ノーマンは、著書『エモーショナル・デザイン』の中で、デザインには3つのタイプがあると述べています。 本能的(visceral)、行動的(behavioral)、内省的(reflective) です。本能的とは深く感情に根ざした直感的な反応、行動的とはプロダクトの効果とユーザビリティ、そして内省的とはそのプロダクトとの知的な関係性を指します。私たちはエンタープライズの世界において、あたかも内省的な側面にだけ集中すればよいかのように、本能的・行動的な反応を軽視しがちです。しかし、マネージャーや経営者も人間です。彼らの仕事は不確実性の中で意思決定を行うことであり、論理と同じくらい直感や経験に頼っています。 私たちが真に "Kickstarting Transformation" を実現したいのであれば、プロダクト開発のプロフェッショナルは、単に顧客に喜ばれるだけのツールを提供するだけでは不十分です。顧客組織の「真の変革」は、上からの命令一つで生まれるものでは決してありません。だからこそ私たちの使命は、顧客に自信を与え、行動を変え、最後には組織を変革することなのです。そしてそのようなプロダクトを、明確な意志をもって世に送り出すことなのです。 Excellenceを追求し、それを実現する意思こそが文化を形づくります。もし「あと一歩」で意味のある改善ができるのなら、議論をやめて実行しましょう。 自分の仕事を誇りに思いたいなら、影響を生み出したいなら、私たちは立ち上がり、Excellenceを追求し続けなければならないのです。 チームビルディングにおけるExcellence 私たちがよく目にする典型的な採用活動はこんなものです。 LinkedInで一斉に送られる、どこか形式的で熱意を感じないメッセージ。そのうちの数名が反応してくれることを期待するやり方です。多くの組織において、この方法でも十分に機能することはあります。量を打てば、ある程度の成果は必ず出るからです。 しかし、私たちはエンジニアの採用をそのように捉えてはいません。 なぜなら採用とは、単に人数を満たすための行為ではなく、会社のミッションを実現できる「より良い、より有能な組織」を築くための営みだからです。 もちろん、送信したメッセージ数や返信率といった指標は、Talent Acquisitionにとって重要な先行指標です。ですが、それらはあくまで数字にすぎません。 採用チームにおけるExcellenceとは、組織にとって本当に最善を尽くそうとするその純粋な姿勢です。キャディにおいて、エンジニアのリクルーターはVP of Engineeringの直属です。 彼らは開発組織のAll Hands MTGにも出席し、組織・プロダクト・技術への深い理解を持ち続けます。エンジニア組織づくりの全体プロセスに積極的に関与する採用チームを育てるには、強いコミットメントと規律が必要です。しかし、それこそがまさに私たちの目指す「Excellence」なのです。 私たちはキーワードを使ってレジュメをスクリーニングしますが、それだけでは十分ではありません。リクルーターには、さまざまな技術の関係性を理解するだけでなく、業界ごとの特性を理解することを求めています。受託開発の会社と自社プロダクトをもっている会社とでは開発スタイルが大きく異なりますし、航空機の電子機器とモバイルアプリではプロセスや信頼性の要件も全く違うはずです。こうした違いは、候補者を評価する際に極めて重要です。 リクルーターに技術の専門家であることを期待しているわけではありません。しかし、強い好奇心を持ち、エンジニアリングマネージャー(以下EM)と歩調を合わせ、優れたチームをつくりたいという情熱を持つことを期待しています。それこそが採用におけるExcellenceであり、優れたソフトウェアを生み出す土台となるのです。 マネジメントにおけるExcellence Excellenceを追求することは従業員一人ひとりの責務ですが、その水準を組織として維持し、徹底させるのはマネージャーの役割です。Excellenceは偶然生まれるものではありません。意図的な行動、緩むことのない当事者意識、そして困難な道を選ぶことを厭わない勇気が必要です。人はあらゆる場面で、現状維持という心地よさに甘えたくなるものです。顧客から不満の声が上がらない時、現状維持は合理的な判断であるかのように見えてしまうのです。だからこそ、私たちはマネージャーに高い基準を設定し、その上でRaise the barし続けること、つまりこのExcellenceの守護者としての役割を果たすことを期待するのです。 組織一丸となって、私たちは妥協という誘惑に断固として抗い、Excellenceを追求します。なぜなら私たちの未来は、そこにかかっているからです。 Guardians of excellence 妥協は破滅への道 誰しも経験があるのではないでしょうか。締め切りと山のような要件を前にしたとき、スコープが削れるたびにほっと胸をなでおろす。キャリアを重ねるにつれて、私たちは自分の時間を確保するために顧客や同僚と交渉する方法を学んでいきます。経験を積むことで、何が問題になりうるかを予測し、スケジュールのバッファを確保したり、ステークホルダーを説得して要件を取り下げさせたりと、リスクを軽減する方法を身につけます。それは悪いことではありません。ビジネスとはそういうものです。 しかし、時が経つにつれ、自分自身とプロダクトの間に距離を感じるようになることがあります。業界での経験が長くなるほど、現実から乖離していくリスクが高まるのです。私たちは子供たちに感情をコントロールし、敬意を払い、要求がましくならないように教えます。そして大人である私たちは、時としてその自制心を自分自身にも適用し、自分自身や他者にExcellenceを追い求め続けることを忘れてしまいます。たとえ心の中に情熱の炎があっても、組織やシステムに逆らうことの精神的な負担を避けるために、その炎を消してしまうのです。このことは、自分の心の健康を守る助けにはなります。しかし、 「自分を守ること」と、「(情熱の炎を燃やさずに)現状をただ受け入れること」の間には決定的な違いがあります。 Excellenceとは、制御不能な感情の爆発ではありません。それは、 より良いものを求め続ける、巧みにコントロールされた炎 なのです。 現代のエンタープライズソフトウェアの現実は、まさにこの「距離を置く」プロセスが生み出した産物です。純粋に合理的な観点から見れば、最小限の仕事で同じ結果を出すことは理にかなっています。これは短期的には確かに機能します。しかし、常により少ない労力で済ませようとする組織は、不確実な時代に自らを前進させる内的な強さを欠いています。もし私たちが外部からの評価だけで自分の価値を判断するならば、今日の期待を超える夢を見ることは決してできないでしょう。マネージャーとして、私たちは自分自身だけでなく、チームにもExcellenceを要求すべきです。 ほんの少しの努力で何かが格段に良くなるのであれば、私たちが率先してそれを推進すべきなのです。 私たちは、製造業における新しいビジネスの形を開発しています。私たちが提供する価値は計り知れず、外部からの評価を得るには数ヶ月、あるいは数年かかることもあります。だからこそ、マネージャーは、たとえまだ誰からも称賛されていなくても、 未来への道を切り拓くための強い内的な羅針盤 を持たなければならないのです。 エンジニアリングマネジメントの精神 エンジニアリングマネジメントの真髄、それはExcellenceを追い求め、妥協なく要求し、そして必ず形にすることにあります。EMとして、プロダクトを新たな高みへ、チームを新たなステージへ、そして自らを新たな次元へと引き上げていくのです。それは単に「機械を動かし続ける」ということではありません。ビジネスの次のステージのために、 「機械そのものを継続的に再発明していく」 ということです。そして、自らの行動において確固たる原則を持ち、ミッションに忠実であり続けることでもあるのです。 エンジニアリングマネジメントは単なる「ピープルマネジメント」にとどまりません。チームを前に会社を代表し、高次元の戦略を解釈し、チームが理解し実行できる形に翻訳することを意味します。その役割は、チーム設計や複雑なアーキテクチャ設計から、ストレッチゴールの設定、そして全員を高みへと挑戦させることまで多岐にわたります。 そして何より重要なのは、不確実性の中で適切なカードを切り、困難な決断を下すことです。EMはまるで未知の海を航海する船長のような存在です。未来という霧に視界を遮られながらも、ミッションに基づいた内なる羅針盤、顧客への深い理解、そしてWhatとHowを自在に行き来できる技術的な力量を頼りに、早く、果断に行動する必要があります。 アスリートのコーチがそれぞれ独自のスタイルを持つように、すべてのEMも一人ひとり異なります。 基礎や目標は共通していても、それぞれが独自の方法でこの見通しの悪い海を航海します。一つの方法論を押しつけることはしません。重要なのは、すべてのマネージャーが必要な力を備え、確固たる信念をもち、最高の成果を出すことにコミットすることです。そして、その上で、ミッション達成のために自分自身のスタイルを築けるようにすることです。 一つの学問としてのマネジメント 著名な文献 偉大なEMの個人的なスタイルは皆、先人たちの基礎的な業績の上に築かれています。偉大なコーチがそのスポーツの歴史的な戦術を研究するように、すべてのマネージャーは経営科学の巨人たちから学びます。ビジネススクールに通ったことのある人なら誰でも、ピーター・ドラッカー、アンディ・グローブ、ジム・コリンズといった名前を学んだことがあるでしょう。ドラッカーはマネジメントを一つの学問として確立し、MBO(目標管理)のような概念を導入し、活動よりも効果性を重視しました。グローブはその基盤の上に、規律ある実行に焦点を当て、OKRのフレームワークを創り出しました。ジム・コリンズは広範な歴史研究を通じて、偉大で永続的な企業の特徴を特定しました。 日本では、パナソニックの創業者である松下幸之助氏は「プロダクトを作る前に人を作る」ことを強調し、京セラとKDDIの創業者である稲盛和夫氏は、道徳哲学と従業員の幸福に基づいた経営を導入しました。トヨタ生産方式は、「最も効率的な方法を追求する中で、あらゆる無駄を徹底的に排除するという思想」に基づいており、品質管理と継続的改善に関するW・エドワーズ・デミングの業績に触発されたと言われています。 デミングとドラッカーは、世界が産業経済から知識経済へと移行していることを認識していました。産業経済において、労働者は歯車の一部と見なされ、効率が最重要視されていました。世界が知識労働中心に変わるにつれて、ドラッカーは効果性と個人の判断がより重要になると主張しました。その数十年後、グローブは急速に変化する環境の中でIntelを率い、世界で最も収益性の高い企業の一つに育て上げました。彼はデミングとドラッカーの考えを発展させ、テクノロジー企業におけるスピードの重要性を強調し、マネージャーがもたらす価値を、『自身のチームと、関係する他チームが生み出すアウトプットの総和である』と明確に定義づけたのです。 過去数十年で、製造業のバリューチェーンもその方向にシフトしてきました。金属加工プロセスは、数値制御やロボット技術の進歩により、大部分が自動化されています。AppleやNVIDIAのような企業は、資本集約的でプロセス重視の半導体生産をTSMCに委託し、代わりに設計、ソフトウェア、エコシステムに注力しています。近年では、自動車メーカーが、かつて私たちがSDN(Software Defined Networking)について語ったのと同じように、SDV(Software Defined Vehicle)について語るようになりました。要するに、OEMはオペレーションの効率化を外部委託し、ますます知識労働集約型になっているのです。 しかし、産業がいかに進化しようとも、組織というものは、その根底にある特定の世界観、つまり「ビジネスとはこういうものだ」という強い思い込みの上に成り立っています。 そして、そうした企業が持つ前提は、日々のマネジメントや事業運営のやり方そのものに色濃く反映されるのです。例えば、シリコンバレー流の経営を、日本の家電メーカーのような現場力が求められるOEM企業に持ち込んでも、うまく機能するはずがありません。 クラウドソフトは短いサイクルで改善を繰り返すことが前提ですが、ハードウェア製品は一度世に出れば長く使われ、間違いがあれば大規模なリコールという大きな痛みを伴うからです。 モノづくりの世界では、ソフトウェアのように時間を巻き戻す「ロールバック」は決してできないのです。 すべての組織の経営哲学は、その世界観の上に築かれています。エンジニアリングマネジメントの具体的な側面に踏み込む前に、私たちの哲学の根底にあるいくつかの前提を共有したいと思います。 戦略的な世界観 「この世界で成功するために、何が必要か?」 私たちが世界をどのようにみているのか、それは周囲の環境がどう動いているかについての「仮説」です。それは、知識に基づいた推測、個人的な見解、意見が混ざり合ってできています。この考え方は、おそらく時間とともに進化していくでしょうが、現時点では、私たちの組織を構築し、運営する上での基盤となっています。 スピードを求めるのが、資本主義であり、人のエゴである 私たちはベンチャーキャピタルの台頭によって生まれたスタートアップであり、誰も試みたことのないことに挑戦しています。それはハイリスク・ハイリターンを意味します。時間軸は、資本のサイクル、テクノロジーのサイクル、そして単純に私たち自身のキャリアの長さによって、自ずと制約されます。変化の早いこの世界で100年単位の長期的な計画を立てることは現実的ではありません。私たちは自分たちの仕事がもたらすインパクトを、自分たちが生きている間にこの目で見たいと願っているからです。 労働力の多様化は必然であり不可避 日本の状況が特に顕著ですが、多くの先進国で急速な高齢化が進んでおり、昨年だけで100万人近くの人口が減少しました。ソフトウェアエンジニアリングのスキルは世界中で通用しますが、文化的な規範はそうではありません。説明責任、スケジューリング、フィードバックのニュアンスは大きく異なることがあります。15カ国以上から集まったエンジニアと共に、私たちはチーム全体の共通理解を築くため、意図的に(エリン・メイヤーの『異文化理解力』のような)フレームワークを活用したオンボーディングセッションを始めています。 世界はますます分断され、規制されていく 地政学的な緊張、データ主権、関税、そして様々な国の規制が、私たちのビジネスのやり方をますます形成しています。プライバシー規制が広まったのは、グローバルに相互接続されたインターネットの性質のおかげとも言えます。各国は時として国益のためにトラフィックを迂回させたり、ファイアウォールを設置したりします。物理的なモノの流れは常に分断され、規制された産業でしたが、今や私たちはグローバルなインターネットにおいても同様の状況を目の当たりにしています。 組織が拡大するにつれ、優秀な人材の濃度は薄まっていく 組織が成長するにつれて、人材の分布は自然に広がります。永続的な価値を創造するには、大規模な労働力が必要です。わずか千人の従業員で数兆ドル規模の企業を作ることはできません。若手社員の採用は、長期的な成長のために不可欠です。しかし、だからこそ、社員のキャリア育成やスキルアップが欠かせません。ただ採用だけに頼ることはできないのです。組織全体で高いレベルを維持するには、継続的に人材を育成し、全体の能力を押し上げていく必要があります。 私たちが為すべきこと 顧客は、最も価値ある知的財産やビジネス上重要なデータを私たちに託しています。だからこそExcellenceは我々にとって「望ましい選択肢」ではなく、「果たすべき絶対的な責務」です。私たちは国境や言語を越えて活動しています。グローバルな組織である以上、体系的な実行力と統一された基準が必要です。このような複雑性の中で、Excellenceは自然に生まれるものではありません。積極的に育まなければならないものなのです。 マネジメント実践 概観 私たちは、それぞれが持つ世界観に基づいて行動しています。 そして、マネジメントとは、その世界観(環境)の中で、物事をより良くしていくための手法です。ドラッカーは 「マネジメントとは物事を正しく行うことであり、リーダーシップとは正しいことを行うことである」 という有名な言葉を残しました。 「物事を正しく行う」 とは、効果的なソフトウェア開発ライフサイクルを確立し、開発の速度と品質を高め、開発状況をモニタリングし、標準を策定・適用することを意味します。それはガバナンスとコンプライアンスにも関わります。私たちは顧客データを扱っており、顧客の期待に応えるためにデータセキュリティ基準を遵守することは、マネージャーの責務です。 「正しいことを行う」 とは、私たちが本当に最も重要な課題に向き合えているかを常に確認し、その過程で成長を妨げる固定観念に疑問を投げかけることを意味します。それはチームのビジョンを描き、言語化し、すべてのメンバーが同じ目標に向かって進められるようにすることでもあります。 「エンジニアリングマネジメント」には、これら両方の視点を同時に行き来しながら実践することが求められます。マネージャーによって考え方は様々かもしれませんが、これらはマネジメントやリーダーシップという抽象的な概念を具体的なビジネス成果へと繋げるために、マネージャーが注力すべき重要な領域なのです。 領域カテゴリ テクノロジーマネジメント マネージャーであろうとIC(Individual Contributor)であろうと、コードを書く時間が減るにつれて、技術的な判断はより重要になります。システムを設計し、プロダクトの方向性に長期的な影響を及ぼす可能性のある技術判断を下す必要があります。財務マネージャーが金融資産と負債の貸借対照表に責任を持つのと同様に、EMは技術的資産と技術的負債の両方を管理する責任があります。これは、設計をレビューし、規律あるソフトウェア開発ライフサイクルを運用し、品質基準を遵守することを意味します。戦略的な視点では、アーキテクチャや技術の方向性について長期的な判断を下すことが求められます。実行面では、メンバーの日々の業務を技術面から保証する責任があります。 デリバリーマネジメント デリバリーとは、単にフレームワークに従うことではなく、チームやプロダクトに合った進め方や仕組みを確立することです。それは不確実性を減らし、チーム間の依存関係を解消し、リスクを減らすことで、持続可能な形でスループットを最大化します。 品質はスピードと引き換えにするものではありません。なぜなら、この二つは切り離せないものだからです。 私たちが「睡眠か食事か」を問わないのと同じように(どちらも健康に不可欠だからです)、品質とスピードはどちらもデリバリーに不可欠です。もし両者の優先度を議論しているのであれば、それは要求定義が不十分であるということであることを意味します。また、マネージャーはISO25010のようなフレームワークや自身の経験を活かして、チームに「何を届けるのか」「どのように測定するのか」という共通認識を形成すべきです。 プロダクトマネジメント プロダクトマネジメントは専門職の肩書きでもありますが、すべてのEMが理解すべき領域でもあります。なぜなら、この役割こそが「Successとは何か」を定義するからです。プロダクトマネジメントは、ビジョンを戦略に結びつけ、ビジネス目標を技術的な施策へと落とし込んでいきます。 マネージャーは、顧客が自社プロダクトを選択する理由を的確に理解した上で、チーム全体が進むべき方向性を見失わないよう示す役割を担います。さらに、エンジニア一人ひとりがプロダクトに最大のインパクトをもたらす意思決定を行えるよう支援することが求められます。 現場レベルでは、単に開発したものをリリースするだけでなく、それが本当にビジネス成果に貢献していることを常に意識することが求められます。 ピープルマネジメント ピープルマネジメントは、単に1on1ミーティングを行うことではありません。それは、最高の仕事ができる環境を創り出し、個人の成長を最大化することです。 コーチング、ティーチング、メンタリングは重要ですが、もっと本質的な役割は、個人の動機を組織の方向性と整合させ、時間をかけて能力を育成していくことです。 スポーツにおいて、コーチは選手より優れたプレーをするわけではありませんが、チーム全体を成功に導く方法を知っています。 人材育成も同じです。マネージャーに求められるのは「コードをより早く、より上手く書くこと」ではなく、より高次元で物事を捉え、チーム全体を成功に導くことです。だからこそ、高い技術力を持つシニアICが、優れたマネージャーになるケースが少なくありません。彼らは「自分で手を動かすこと」から「他のメンバーが成果を出せるように支援すること」へと自身の視点を切り替えられるからです。 情報マネジメント 情報マネジメントは、単に進捗報告を送ることではありません。言語や文化が入り混じる多様な組織において、スムーズな情報伝達を実現するには意図的な設計が必要です。マネージャーは、チームが不要な情報に惑わされることなく、必要とされる情報とそのコンテキストを受け取れるようにしなければなりません。また、重要な知見やアラートをメンバー層に適切に伝え、彼らが正しく判断し行動できるようにすることも重要です。 これは一貫したコミュニケーションとドキュメンテーションのシステムを構築することを意味します。日々の業務においては、適切なタイミングで、適切な人に、適切な情報が届くようにすることです。 ステークホルダーマネジメント 企業は内部構造が複雑であるだけでなく、多様な規制や外部からの要求にも対応しなければなりません。そのような組織に対して価値を創造するには、大規模なチーム、時には複数のチームで協力して課題を乗り越える必要があります。EMは、組織が内部的にも外部的にもどのように機能するかを理解し、エンジニアだけでなく、ビジネス部門などすべての関係者と目標や期待値をすり合わせていくことが求められます。この役割は、組織内で誰が影響力を持っているかを把握しながら、長期的な信頼関係を構築し、関係者に適時適切に情報を共有することで不意の事態を防ぎ、約束した成果を確実に実現することを意味します。 オーケストレーション これらの各マネジメント機能は互いに独立して存在するものではありません。技術は独立して存在するものではなく、相互に作用しながらチームが優れたプロダクトを構築できるように機能しています。プロダクトの方向性は技術的判断に影響を及ぼし、ステークホルダーマネジメントにはデリバリーのスケジュール調整が不可欠です。情報マネジメントが不十分であれば、ピープルマネジメントに課題や誤解を生じさせることもあります。マネジメントとは、各領域を個別に最適化することではなく、それらを一貫性を持って統合し、目の前の課題解決に結び付ける営みです。 どの領域にも、「戦略」(方向性を決めて、将来に投資すること)と、「実行」(その決断を現実にしていくこと)という二つの側面があります。当然ながら、その比重は各マネージャーの役割と組織のニーズによって異なります。現場のマネジャーは設計レビューやソフトウェア品質の担保といった「実行」に重きを置きますが、VPクラスは技術アーキテクチャや組織構造など、長期的な「戦略」により多くの時間を割く傾向があります。 こうした役割による違いはあれど、向き合うべき本質的な領域が変わるわけではありません。 プロダクトと組織の成長フェーズに応じて、戦略と実行という両極の間をいかに自在に往復できるか。そこに、マネージャーとしての真の手腕が問われるのです。 「このバグをどう解決するか?」から「2年後に達成すべき目標は何か?」といった問いの間を弾力的に行き来する能力こそが、マネージャーの的確な意思決定を可能にするのです。 未来を築く 人類は何千年もの時間をかけて、社会は少しずつ安定を積み重ねてきました。農業は食料供給を安定させ、人々を日々の狩猟から解放し、専門職や交易を生み出しました。現代社会も、社会保障制度を通じて、その安定性を更に拡張しました。こうした安定が人類の進歩を推し進める一方で、同時に隠れたリスクも孕んでいます。私たちに革新や挑戦を可能にするシステムは、その一方で安住や惰性をも生み出す可能性があるのです。何もしなければ、安定は停滞へと変わります。だからこそスタートアップもリーダーも、この安定に甘んじることなく、抗おうとします。それは、飽くなき好奇心と常に高みを目指すという強い意志によって支えられる行為なのです。 歴史学者ユヴァル・ノア・ハラリは『サピエンス全史』の中で、16世紀頃に社会が変革を迎えたと述べています。それ以前の社会は未来に希望を持てず、そのためお金の貸し借り(信用)という概念が存在しませんでした。しかし近代に入り未来への展望が期待されるようになると、状況は一変します。 当然のことですが、 「未来が明るい」と信じられないなら、誰が新規事業を始めようとする者にお金を貸すでしょうか? 信用の欠如は貨幣供給の不足、そして経済成長の停滞へと直結します。科学革命の時代、未知のものを発見できるという考え方が広まるにつれ、この「人類は進歩する」という信念が経済的な進歩を後押ししました。「より良い未来がきっと利益を生み出す」と多くの人が考えるようになり、信用で取引をすることが活発になったのです。この変革こそが、現代の金融システムを生み出し、今日、私たちが享受するイノベーションと進歩のスピードを実現させたのです。 現代の製造業は、地政学的リスク、関税、労働力減少といった逆風の真っ只中で岐路に立っています。 製造業は長らく商業の発展を牽引し、世界中の大企業を支えるソフトウェアの基盤を形成してきた、先駆者的な存在です。しかし何十年もの間、製造業は労働集約的であり、効率重視の伝統的な「コスト削減・効率第一」という旧来の考え方に縛られ、リスク回避に注力せざるを得ませんでした。その結果、守りの姿勢が生まれ、進歩と停滞の狭間で揺れ動いてきたのです。 それでも、私たちはより良い未来に賭けるのです。 製造業の企業は何十年もかけて蓄積してきた知識、経験、そして膨大なデータを抱えています。これらは、過去の世代が想像すらできなかった財産です。これらを最新のコンピュータビジョンや機械学習技術と組み合わせることで、ディスク上の「単なるデータ」だったものが、「事業を大きく前進させる力」へと変わります。 私たちは、組織が持つ集合知を活用することで、現場の一人ひとりの従業員がより良く、より速く意思決定できるようになる未来を信じています。 かつて未来を信じた社会が爆発的な経済成長を遂げたように、自社の未来の可能性を信じる製造業の方々が、これまで長年磨き上げてきたプロセスや品質向上への取り組みから飛躍的なリターンを得られるようにしたいのです。 Excellenceの追求とは、現状維持に安住せず、「明日を今日よりも良くできる」という信念を具体的な行動で示し続けることだといえます。 EMやリーダーの役割は、まさにこの実行部分にあります。それは、今日のためだけに最適化するのではなく、明日の基盤を築くようなチーム、システム、文化を創り上げることです。このような世界を実現するためには、「現状維持で十分」という誘惑に断固として抗い、常にこの「Excellence」を追求し続けなければなりません。
Intro Excellence is not a skill. It is a choice, and an expression of belief that tomorrow can be better than today. This kind of optimism is not passive – it requires deliberate decisions, because it often goes against our natural inclination toward comfort. We humans are creatures of habit, and we are drawn to stability and the status quo. It is the reason I do not use the term “cultural fit” when making hiring decisions. I have nothing against its use. After all, having common values and wanting to achieve the same goals is critical in a startup environment, but the term hints at an unspoken resistance to change and a desire for the status quo. I prefer to say “cultural impact”. Instead of asking “will this person fit into the team”, I prefer to think “how will this person change the team”. It may seem like a minor distinction, but excellence requires constant evolution, and we embrace change and adapt relentlessly. By definition, a startup is about rejecting the status quo in favor of a better world vision. At CADDi our mission is to “unleash the potential of manufacturing” because we believe that the manufacturing industry has enormous untapped potential. Driven by this mission, we have grown from just a handful of employees to hundreds of employees across four countries in under eight years. As we have grown, we have become more risk-averse, more process-heavy, more conservative in our decision-making. This is understandable; we have real customers who depend on our software to run their business. However, we must not forget that we are nowhere close to accomplishing our mission and cannot let ourselves slide into complacency. At that point we become the status quo that we set out to change. The pursuit of excellence Excellence in enterprise software Enterprise software is often ridiculed for being terrible. In 2018, YCombinator put it plainly in its “request for startups”, stating that “software used by large companies is still awful and still very lucrative.” It is true, and part of the reason is that enterprise software is just hard. Not only are enterprises large and complex, but they also involve many stakeholders. While consumer software often has a more direct relationship between user satisfaction and purchase, enterprise applications must serve a wider, more complex web of stakeholders. They must satisfy executives with budgetary power, system administrators who maintain it, third party integrators who customize it, and finally the end users who use the apps. In other words, the end user is often not the buyer. An expense reporting system, for example, is purchased by the accounting team, not by the employees who have to use it. It must meet various regulatory requirements that may come at the cost of usability. Purchasing teams often use massive feature comparison tables to make decisions, and vendors optimize for “checking enough boxes,” because that is how the game works. These constraints are real, and we are not naive about them. CADDi’s product vision includes the phrase “Kickstarting Transformation,” because we recognize that true change requires addressing short-term, on-the-ground problems and connecting them to long-term strategic outcomes. We acknowledge that major changes cannot be achieved through top-down directives alone. They require both executive support and organic adoption throughout the organization. For that reason, our software must be functional, check all the boxes, and deliver outcomes that matter to executives, such as cost reduction, lead-time reduction, or cultural transformation. But this is exactly where complacency begins to set in. Once a product delivers the minimum acceptable outcome, it becomes easy, perhaps even rational, to stop there. Why optimize performance, when your users are required to use it anyway? Why invest in better UX when feature breadth wins over the buyers? As an industry, we software engineers know how to build performant applications. Achieving a decent Lighthouse score is not rocket science. Cloud providers provide bullet-proof infrastructure, and the open source community empowers us with great frameworks. Component libraries and design tooling have matured and user interaction models on the web are well-established. Yet, we still find excuses to cut corners – “we don’t have time”, “it’s not a user requirement”, “we have other features to build.” In reality, this is often just a lack of organizational and engineering discipline masquerading as pragmatism. The venerable Don Norman describes three types of design in his book “Emotional Design”: visceral, behavioral, and reflective – visceral being the instinctive gut reaction that is deeply emotional, behavioral being the effectiveness and usability of the product, and reflective being the conscious intellectual relationship with the product. So often we dismiss the power of the visceral and behavioral response in the enterprise, as if we can just focus on the reflective. But managers and executives are also human. Their job is to make decisions in the face of uncertainty, and rely on their instincts and experience as much as logic. If we truly want to “Kickstart Transformation”, we product development professionals need to do more than simply delivering tools that win customers. We must intentionally build products that inspire confidence and change behavior, because transformation inside a customer organization does not happen by directive alone. The pursuit of excellence, and the wherewithal to make it happen is what ultimately shapes culture. If we are just one step away from making something meaningfully better, let’s stop debating and just get it done. If we want to be proud of our work, if we want to make an impact, then we must step up to the plate and strive for excellence. Excellence in building teams We are all familiar with what typical recruiting looks like: generic, seemingly halfhearted messages on LinkedIn, in hopes that a few will respond. In many organizations, this approach actually works well enough. With enough volume you’re bound to produce some results. But that is not how we believe technical recruiting should be done, because recruiting is not just about fulfilling a hiring quota. It’s about building a better and more capable organization that can deliver on the company’s mission. Of course, metrics such as messages sent and replies received are important as leading indicators for any talent acquisition team, but in the end they are just numbers. What makes a recruiting team excellent is a genuine desire to do what’s best for the organization. At CADDi, our technical recruiters report to the VP of Engineering. They attend engineering all-hands meetings, and maintain a deep understanding of the organization, product, and technology. It takes commitment and discipline to build a talent acquisition team that is actively involved in the holistic process of building engineering teams, but that is the excellence we strive for. Yes, we use keywords to help screen resumes, but we expect our recruiters to understand not only how different technologies relate to each other, but also how different industries operate. A development agency will have a very different development style from a product company. Processes and reliability requirements are likely very different between aircraft avionics and a mobile app. These distinctions matter when evaluating a candidate. We do not expect recruiters to be technical experts, but we expect them to be curious, aligned with engineering managers, and have the desire to build great teams. That is what excellence in recruiting looks like, and it is the foundation for excellence in software. Excellence through management It is the responsibility of each and every employee to strive for excellence, but it is the managers who ensure we uphold it. Excellence is not something that happens by accident. It requires deliberate actions, continuous vigilance, and the courage to make difficult decisions. Complacency tempts us at every turn; when customers are not complaining, the status quo almost seems rational. That is why we expect managers to set high standards, raise the bar, and serve as guardians of excellence. Together, we actively resist the allure of mediocrity and pursue excellence, because our future depends on it. Guardians of excellence The slippery slope of mediocrity We have all done it before. Faced with a deadline and a mountain of requirements, we breathe a sigh of relief every time we can scope something out. As we advance in our careers, we learn how to negotiate with customers and peers to protect our time. With experience we gain the ability to anticipate what could go wrong, and learn to mitigate risks, whether it be padding timelines, or talking stakeholders out of a requirement. Nothing wrong with that–it’s just the way business goes. But over time, this can create distance between ourselves and the product. The more time we spend in industry, the more detached we risk becoming. We teach our children to control their emotions, to be respectful, and avoid being demanding. As adults, we sometimes apply that same restraint to ourselves, and forget how to demand excellence from ourselves and from others. Even if there’s a fire inside of us, we smother it because fighting the system can be an emotional drain. This coping mechanism helps us protect our sanity, but there is a critical difference between protecting ourselves, and accepting the status quo. Excellence is not an uncontrolled outburst – it is a well-controlled fire, continuously demanding better. The reality of modern enterprise software is the product of this very distancing process. From a purely rational perspective, doing the least possible work to deliver the same results is logical. This certainly works in the short-term. But an organization that constantly strives to do less is an organization that lacks the internal strength to drive itself forward in times of uncertainty. If we can only evaluate our value based on external validation, we would never be able to dream beyond today’s expectations. As managers, we should be demanding excellence not only from ourselves, but also from our teams. If it’s just a bit more effort is going to make something vastly better, we should be the first to push for it. We are developing new ways of doing business in manufacturing. The value we deliver is immense, and external validation can take months or even years. That is why managers need to have a strong internal compass to blaze a path forward, even when no one is applauding yet. The ethos of engineering management Engineering management is the art of pursuing, demanding, and delivering excellence. As an EM, you push the product to be better, push the team to be better, and push yourself to be better. It is not just about “keeping the machine running.” It is also about continuously reinventing the machine for the next stage of the business. It is about being principled in your actions and staying true to the mission. It is much more than just people management. It means representing the company in front of the team, interpreting and translating high-level strategy into something digestible and actionable by the team. It spans everything from designing team structures and architecting complex software, to setting stretch goals and pushing everybody to reach for the stars. Finally, it is also about playing the right cards at the right time, and making hard decisions in the face of uncertainty. You are the captain of a ship navigating uncharted waters, with visibility obscured by the fog of the future. You need to act early and decisively, often without perfect information, guided by an internal compass built on the mission, a deep understanding of the customer, and the technical acumen to move fluidly between the “what” and the “how”. Every engineering manager is different, just as every athlete’s coach has their own style. The fundamentals and goals are the same, but each navigates the murky waters in their own unique style. We do not insist on one prescribed methodology. What matters is that each manager is equipped, principled, and committed to excellence, and empowered to build their own style in service of the mission. Management as a discipline Notable literature Every great engineering manager’s personal style is built on the foundational work of those who came before them. Just as a great coach studies historical plays of the sport, every manager learns from the giants of management science. Anybody who has been to business school has likely studied Peter Drucker, Andy Grove, or Jim Collins just to name a few. Drucker established management as a discipline, introducing ideas such as MBO and emphasizing effectiveness over activity. Grove built on that foundation with a focus on disciplined execution and created the OKR framework. Jim Collins identified traits of great long-lasting companies through extensive historical research. In Japan, the founder of Panasonic Konosuke Matsushita emphasized building people, not products, while Kazuo Inamori, founder of Kyocera and KDDI, introduced management based on moral philosophy, and employee happiness. Toyota’s TPS (Toyota Production System) is “based on the philosophy of achieving the complete elimination of waste in pursuit of the most efficient methods”, and is said to have been inspired by W. Edwards Deming’s work on quality management and continuous improvement. Deming and Drucker recognized that the world was transitioning from an industrial economy to a knowledge economy. In the industrial era, laborers were seen more as cogs in a wheel and efficiency was paramount. As the world changed to be knowledge-work heavy, Drucker argued that effectiveness and individual judgement mattered more. Decades later Grove led Intel in a fast changing environment to become one of the most profitable companies in the world. He built on Deming and Drucker’s ideas with an emphasis on speed for technology companies, and defined a manager’s contribution as the output of their team and neighboring teams. Over the last few decades, the manufacturing value chain has shifted in that direction as well. Metal fabrication processes are largely automated through advancements in numerical control and robotics. Companies such as Apple and NVIDIA have outsourced capital intensive and process heavy semiconductor production to TSMC, and instead focused on design, software, and ecosystems. In recent years, automakers now talk about SDV (software defined vehicle) in much the same way we used to talk about SDN (software defined networking). In short, OEMs are outsourcing operational efficiency, and becoming increasingly knowledge-work heavy. But even as industries evolve, organizations are still built on strong assumptions of the world. These assumptions are expressed in how companies manage and operate. A Silicon Valley software company’s management methodology will not deliver the operational excellence demanded in a home appliance OEM. Software in the cloud is built through very rapid iterations. Hardware has long lifetimes and correcting mistakes require expensive recalls. There is no “rollback” in manufacturing. Every organization’s management philosophy is built upon its view of the world. Before diving into the operational aspects of engineering management, we want to share some of the assumptions that underpin our own philosophy. Strategic World View "What does it take to thrive in this world?" Our view of the world is an assumption about how our environment behaves. It is a combination of educated guesses, personal views, and opinions. These will likely evolve over time, but today, they form the foundation upon which we build and run our organization. Capitalism and our egos demand speed We are a startup, enabled by the rise of venture capital, trying to do something nobody else has attempted. That means high risk and high reward. Our timeframes are naturally constrained by capital cycles, technology cycles, and simply by the length of our own careers. Planning over a century-long horizon is not realistic; the world moves too fast, and we want to see the impact of our work in our own lifetimes. Workforce diversification is necessary and inevitable Much of the developed world is aging quickly with Japan at the forefront, losing almost a million people in the last year. Software engineering skills are largely transferable across the world, but cultural norms are not. Nuances in accountability, scheduling, and feedback can vary significantly. With engineers from over 15 different countries, we have deliberately started onboarding sessions that utilize frameworks (e.g. “The Culture Map” by Erin Meyers), to build shared understanding across the team. The world is becoming increasingly fragmented and regulated Geopolitical tensions, data sovereignty, tariffs, and various national regulations are increasingly shaping the way we do business. We can thank the globally interconnected nature of the internet for the proliferation of privacy regulations. Countries sometimes reroute or firewall traffic in their nation’s interest. Flow of physical goods has always been a fragmented and regulated industry, but now we are seeing something similar with the global internet. Talent density declines at scale unless we fight it As organizations grow, talent distributions naturally broaden. Creating long lasting value requires a large workforce – you cannot create trillion dollar companies with just a thousand employees. Hiring junior employees is not optional, it is critical for long-term growth. But that makes career development and upskilling essential. We cannot rely on hiring alone. Sustaining excellence at scale requires continuously developing talent and pushing the talent distribution moving upward. What we must do Our customers entrust us with their most valuable IP and business critical data. Excellence is not a preference. It is our moral obligation. We operate across borders and languages. We are a global organization requiring systematic execution and unified standards. With such complexity, excellence does not emerge naturally. It must be actively cultivated. Management in practice Overview Our view of the world gives us the foundational assumptions upon which we operate, and management is the discipline through which we cultivate excellence in this environment. Drucker famously wrote that “management is doing things right; leadership is doing the right things.” Doing things right is about ensuring an effective software development lifecycle, about improving velocity and quality, monitoring execution, developing standards, and enforcing them. It is about governance and compliance–we work with customer data, and it is the manager’s responsibility to enforce data security standards, so that we live up to our customer’s expectations. Doing the right things is about making sure we are solving the most important problems, and in the process, challenging assumptions that could be holding us back. It is about creating and articulating a vision for the team, and ensuring that teams are aligned around the same goals. Engineering management requires operating across both of these perspectives simultaneously. Every manager has their own take, but in practice, this work spans several core domains that reflect the primary areas of influence through which EMs translate the abstract ideas of management and leadership into business impact. Domains Technology Management Regardless of whether you are a manager or an individual contributor, your technical judgment becomes more critical as you code less. You are architecting systems and making strategic technical decisions that can affect product direction for years to come. Just as finance managers are responsible for the balance sheet of financial assets and liabilities, engineering managers are responsible for managing both technical assets and technical debt. This means reviewing designs, operating a disciplined SDLC, and ensuring we live up to our quality standards. Strategically, it involves making long-term bets about architecture and technology direction. From an execution perspective, it means ensuring that today’s work is technically sound. Delivery Management Delivery is not about following frameworks, but it is about establishing rhythms and structures that fit the team and product. It is about reducing uncertainty, and resolving dependencies between teams, mitigating risks, and maximizing throughput sustainably. Quality is never traded off for speed, because they are inseparable. Just as we never ask ourselves whether we should sleep or eat, because both are paramount for health, quality and speed are both essential to deliver. If we are debating between the two, it means our requirements are ill-defined, and managers should be able to leverage experience and frameworks such as ISO25010 to align the team around shared understandings of what we are delivering and how to measure it. Product Management Product management can be a title, but it is also a domain that every engineering manager needs to be familiar with. It is this function that defines what success means. This function connects vision to strategy, and business objectives to technical initiatives. Managers must understand the customer journeys that make our products stand out, steer teams to stay aligned with the high level direction, and empower engineers to make the right decisions to drive product impact. On the ground, it is about ensuring day-to-day work contributes to real outcomes, not just outputs. People Management People Management is not simply holding one-on-one meetings. It is about creating conditions where individuals can thrive and do their best work. Coaching, teaching, and mentoring are important methods, but the deeper responsibility is aligning motivation and building capabilities over time. Coaches do not play sports better than their teams, but they know how to take the team to success. Developing people and teams is the same–we do not expect managers to code better and faster, but we expect them to excel at a higher level of abstraction. For this reason, senior ICs often make strong managers, as they carry technical credibility while shifting their focus from making to enabling others. Information Management Information Management is not sending out status reports. In a diverse organization that crosses languages and cultures, it takes deliberate effort to ensure information flows well. Managers need to make sure their teams receive the right context without being overwhelmed by unrelated noise, and that critical insights and alerts alike flow upward such that leaders can interpret and act on them appropriately. Strategically this means building consistent systems of communication and documentation. In the day-to-day, it means making sure the right people get the right information at the right time. Stakeholder Management Not only are enterprises complex internally, but they are also further shaped by diverse regulations and external demands. Delivering to such organizations requires large teams, and often teams of teams, to tackle these challenges. Engineering managers must understand how organizations function, both internally and externally, and ensure that expectations are set, aligned, and communicated clearly with all stakeholders, technical and otherwise. This means building long-term trust and mapping influence across the organization, and keeping relevant stakeholders informed to avoid surprises, and delivering on commitments. Orchestration These domains are not independent, because technology does not exist in a vacuum. They function together to enable teams to build great products. Product direction affects technical decisions, and stakeholder management requires coordinating delivery timelines. Lack of good information management can cause people management problems and misunderstandings. Management is not about optimizing each domain individually, but rather orchestrating them in a coherent manner to solve the problems at hand. In each of these domains, there is a spectrum from the strategic side of deciding on direction and making bets, to the execution side of actually turning those decisions into reality. Naturally, the relative proportion depends on each manager’s role and the needs of the organization. A frontline manager will be heavier on execution, reviewing design documents and ensuring that engineers are producing quality software, whereas a VP may be more focused more on long term strategy such as technical architecture and organizational structure. Regardless of these differences, the underlying domains do not change very much. The real skill lies in the ability to dynamically move up and down along the spectrum as the product and organization evolves. The elasticity to go back and forth between questions such as “how do we resolve this bug?” to “what goals do we need to hit in two years?”, is what allows great managers to operate across the organization, and drive good decisions. Building the future Over the millennia, societies have built stability layer by layer. Agriculture increased the caloric yield of the land, freeing some from the daily need to hunt and gave birth to specializations and the trades. Modern economies extended that stability through social safety nets. This stability fuels progress, but it also carries a hidden risk. The very systems that allow us to innovate and take risks can just as easily foster complacency. Left unchecked, stability becomes inertia. For startups and leaders alike, resisting is not an accident – it is a conscious act of will, powered by curiosity and a commitment to excellence. As historian Yuval Noah Harari writes in Sapiens, our society went through a transformation around the 16th century, shifting from one having low trust in the future and thus lacking appetite for credit, to one with high trust in the future outlook. After all, if you do not believe the future is bright, why would you lend money to somebody looking to start a business? Basic economics tells us that lack of credit translates to lower money supply and sluggish growth. Around the Scientific Revolution, perhaps as the elite grew accustomed to the idea of discovering the unknown, this belief in human progress gave way to economic progress, convincing many to extend credit in the hopes that a better future will bring about returns. This transformation has given us the modern financial system, and the speed of innovation and progress that we all enjoy today. Manufacturing today is at an inflection point, facing headwinds of geopolitics, tariffs, and a declining working force. It has been a trailblazing industry that has led to the rise of commerce as we know it today, and formed the foundation for software that runs all of the biggest companies in the world. And yet, for decades, manufacturing has been labor-intensive, anchored in a traditional cost-up efficiency-first paradigm, requiring a strong focus on risk mitigation. This fosters a defensive mindset and walks a fine line between progress and stagnation. But we bet on a better future. Manufacturing companies have decades of knowledge, accumulated experience, and operational data that previous generations could only dream of. When combined with modern computer vision and machine learning technologies, they can transform what used to be bits and bytes on a disk, into leverage. We believe in a future where organizations can take their collective wisdom, and empower individual workers to make smarter and faster decisions. Just as societies that believed in the future unlocked economic growth, we want manufacturers who bet on their future capabilities to unlock exponential returns on their years of process refinement and quality initiatives. Pursuing excellence is the practical expression of optimism. The role of an engineering manager or leader is to translate this belief into action. It is to build teams, systems, and cultures that do not optimize for today, but create the foundations for tomorrow. To achieve such a world, we must actively resist the allure of mediocrity, and pursue excellence.
こんにちは、Data&Analysis部(D&A)です。 D&Aでは週1回、機械学習の勉強会を開催しており、本記事は、勉強会の内容を生成AIを活用して記事にまとめたものです。 ※勉強会内容公開の経緯は こちら ※過去の勉強会は「社内勉強会」タグからもご覧いただけます。 はじめに 12-Factor Agentsとは 12-Factor Agentsを書いた背景 12-Factor Agentsの概略 Agentは様々な経路から入力を受けつけ、出力は構造化されている プロンプトやコンテキスト、制御フローをコードとして管理する 状態管理をし、人の介入やエラー修正を可能にする 小さなエージェントを複数構築し単一の結果を得られるようにする まとめ はじめに 今回の勉強会では、信頼性・保守性の高いLLMアプリケーション構築の原則として提唱された12-Factor Agentsを紹介します。動機としてはLLMを用いたアプリケーションが高品質であるための体系的な理解が必要だと考えたためです。 本記事では、以下の資料を参考にしています。 humanlayer/12-factor-agents Twelve-Factor App 12-Factor Agentsとは 12-Factor Agentsとは、大規模言語モデル(LLM)を活用したアプリケーション(以下ここではAIエージェントと呼称していきます)を信頼性が高く、スケーラブルで、保守しやすいものとして構築するために提唱された12の原則です。なぜ12かというとWebアプリケーション開発のベストプラクティスであるThe 12-Factor Appをなぞらえたものだと思います。 12-Factor Agentsを書いた背景 従来のソフトウェア開発では、必要な処理とその順序から最終成果物の定義を人が実装し(例: イベントトリガーやワークフローのためのDAGの記述等)、事前に定義された手続きやフローに基づいて処理が行われてきました。AIエージェントが将来もたらすものとは、LLMが指示に基づいてDAGを自動で記述するように処理の順序を適切に構築するという機能です。 理想的なAIエージェント(資料1より引用) 現在のエージェントは「LLMが次のステップを決定 → ツールを実行 → 実行結果をコンテキストに追加 → 再びLLMが次のステップを決定」というループ構造で実装されることが多く、DAGが不要になるという理想とは異なっています。むしろ、このループ(制御フロー)をどう管理するかが重要です。 ループとしてのエージェント(資料1より引用) この実行順序のループを高い品質に保つための12の原則が12-Factor Agentsです。 12-Factor Agentsの概略 ここから12-Factor Agentsについて概略したものを紹介していきます。概ね4つのテーマに集約してみました。 構造化された結果を返すものとして定義する(#1, #4) プロンプトをコードとして管理する(#2, #3, #8) 状態管理をし人の手を借りたりエラーを修正できるようにする(#5, #6, #7, #9) 小さなエージェントを複数構築し単一の結果を得られるようにする(#10, #11, #12) Agentは様々な経路から入力を受けつけ、出力は構造化されている Agentは入力をスケジュールされたタスク、システムイベントや人のテキスト入力など様々なところから自然言語で受けつけるようにします。また出力結果はコンピュータが実行できるものでなければなりません。例えばプロンプトを受け取りAPIのURIを返すAgentのようなものです。原則1「Natural Language to Tool Calls」や原則4「Tools are just structured outputs」に書かれています。結果が構造化されていることでAgentがそれを実行したり他のAgentに渡せるようになります。Agentの起動条件については原則11「Trigger from anywhere, meet users where they are」に書かれています。 プロンプトやコンテキスト、制御フローをコードとして管理する 制御フローは当然ですが、プロンプトやコンテキストもフレームワークに任せずに内製し、バージョン管理を徹底できるようにします。それによってAgentをテスト可能にし改善可能なものにします。原則2「Own your prompts」ではプロンプトを内製することについて、原則3「Own your context window」ではコンテキストウィンドウを内製することについて、原則8「Own your control flow」では制御フローを内製することについて書かれています。このテーマは12 Factor Appの原則1「コードベース」の一部(コードはバージョン管理システムで常に変更を追跡している)と関わりがあると考えます。 状態管理をし、人の介入やエラー修正を可能にする LLMは非構造化データを入力できます。そこでAgentを停止して人が追加の指示を入力することができます。また起こったエラーを入力し、それを解決するようにAgentを動かすこともできます。このようなAgentの状態や構造化された出力などを管理し柔軟なAgentの構築やリカバリーを容易にします。原則5「Unify execution state and business state」では状態管理について、原則6「Launch/Pause/Resume with simple APIs」はAPIの実行や一時停止させることについて、原則7「Contact humans with tool calls」は人の介在について、原則9「Compact Errors into Context Window」はAgentのエラー対応について書かれています。 小さなエージェントを複数構築し単一の結果を得られるようにする 複数のタスクを実行するのではなく単一のタスクを実行するAgentを作るように心がけます。またAgentはステートレスであることを心がけ、スケーリングできるような構成になるようにします。原則10「Small, Focused Agents」ではAgentのタスク設計について、原則12「Make your agent a stateless reducer」ではAgentのスケーラビリティについて書かれています。なお複数のステートレスなプロセスにするという考え方は12 Factor Appの原則4「プロセス」の発想に近しいと思います。 まとめ このように、12-Factor Agentsは非構造データの入出力が可能なAgentを従来のソフトウェアのように開発するための原則です。GitHubに公開されているのでぜひご覧ください。
こんにちは、 Drawer Growth グループの大木です。 最近(というかずっと)AIが熱いですね、エージェントモデルが出てきてコーディングの常識がまた一つ変わろうとしているように感じます もちろんキャディでもAIツールは多数導入しており、この変化に追従するために組織としてAI活用に積極的に取り組んでいます 今回はその取り組みの一環として、会社内で Vibe Coding Hackathon を開催しましたのでご紹介します! きっかけ 今年2025年の2月頃から、有志を募ってAIツールを積極的に検証していました 初期ではツールごとに推進者を立て、実際に試験的に使ってみて費用感や効果・ベスプラなどを調査して全社に展開していくような動きをしていました ある程度全社的にもツールの導入が終わり使用フェーズに入ったところで、 もっと利用を促進させる AI活用のナレッジを組織に貯める ために、今回のハッカソンを企画しました 1日ガッツリの短期集中型で企画し参加者を募ったところ、最終的に約50名ほどのメンバーが集まりました エンジニアはもちろん、PdMやデザイナーなどからも参加がありました! ルール ルールは「 基本的にコーディングはAIにだけやらせる」 、だけです 特にテーマも限定せずに、作りたいものを1日で作ってくれ!というスタンスで開催しました 使ってもらうツールはメインとして以下二つにしました Cline Devin 理由は、開催タイミングで全員がライセンス利用可能なClineとDevinにフォーカスして利用を促したかったからです 投稿日時点ではClaude Codeが主流になっていますが、こちらの利用に向けた検証も進んでいます ハッカソン開始 当日の案内を軽く済ませたら早速みなさんに作業に取り掛かってもらいました 開始直後からすでに参加者のエディターではAIが爆速でコーディングしているのが見えます アイデアを出したり、AIの仕事を眺める人たち まだ開始から1時間程度にも関わらず完成させている猛者も出てきています Clineの メモリーバンク や rules の機能を試したりといい感じにAIを使い倒していますね 一次審査 また今回参加者が多いため、一次審査を設けました ハッカソンの中間地点でコンセプトや機能の詳細について企画書を書いて提出してもらいます ここでの結果をもとに、予選通過者を決めて決勝投票に進みます AI推進活動リーダーのimaiが採点用のAIツールを作ってくれましたので、審査もAIにやってもらいます 以下が実際の採点ツールです 1位 ~ 3位には景品もあります!! プロンプトインジェクションに釘を指すimai 企画書も多くの人がClineなどのAIツールを活用して提出していました ラストスパート 運営が採点中にも参加者のみなさんはガンガン開発を進めてくれていていますが、徐々にAIの辛い部分も見えてきていそうですね 採点が終わり予選通過車が確定して、ハッカソンも大詰めに入ってきました 発表会 最後の入賞者たちの発表です! AI審査の予選を通過した人たちにはプレゼンをしてもらい、その後参加者全員で投票をしました 今回優勝に輝いたのは、インシデント対応を助けてくれるCLI / TUIツールでした (普通に実用できるレベルのもので凄かったです) その他の参加者たちにも、たくさんのツールやアプリなどを作ってもらい、Vibe Codingに1日どっぷり浸かってもらえたようでした 惜しくも決勝に届かなかった作品たちの供養 閉幕 やってみた感想 今回一日中AIにガッツリ触れてもらうことでたくさんの学びが集まりました 設計やルールが不十分だとAIは暴走するため、ガードレールの整備が不可欠 AIを活かすには人間の読解力・判断力がボトルネックになりやすい 試作やUI生成は爆速だが、運用を見据えるなら人の補完が必須 一通り成果物を作るところまで自分で触ってもらったことで、どこら辺が限界なのか、何が得意なのかなどを生の情報で学べるのは Vibe Coding ならではの成果です 特にマネージャー陣やデザイナー、PdMなど普段コーディングをあまりしない人たちにも積極的に参加してもらえたことで、組織全体に対してよりVibe Codingの解像度を高めることができたんじゃないかと思います これから 今回のハッカソンを通じて、より一層全員がAIを活用して仕事を進められる体制に一歩近づいたと思います しかしフィードバックから課題も明らかになりました 特にAIを活用していくにはきちんとしたガードレールが必要であったり、AIフレンドリーな環境にしていくことが不可欠です 次のフェーズとして、AIを活用した時にレバレッジの効く環境を用意することが必要だと感じており、今後はこれらの活動により一層注力していく予定です We are hiring!! こんな感じでキャディではAIもめちゃくちゃ活用しています! 今回紹介しきれていないツール群もたくさん社内では使われており、今後新しく出てきたものも積極的に採用していくつもりです AI活用して生産性を爆上げしたい方はぜひ、一緒に働きましょう! https://recruit.caddi.tech/
AI × ソフトウェア開発の最前線——キャディ における Devin 活用のリアル AIがソフトウェア開発の在り方を大きく変えつつある今、キャディではその変化をチャンスと捉え、エンジニアの生産性と創造性を引き出す取り組みを進めています。中でも注目しているのが、話題のAIソフトウェアエンジニア「Devin」です。 Devinの活用に関する知見を深め、社内でのベストプラクティスを探るべく、先日LTを開催しました。さまざまなチームのエンジニアが登壇し、Devinを日々の業務でどう活用しているか、そこで得られた気づきや成果を共有しました。 この記事では、そんな社内LTの一部をご紹介します。キャディの取り組みやエンジニアリング文化の一端に触れていただけたら嬉しいです。そしてもし興味をもっていただけたなら、ぜひ私たちと一緒に新しいソフトウェア開発のあり方を切り拓いていきましょう! 🎯 図面解析チーム:Devinの現実的な活用とシニアエンジニアのスキルアップ 最初の発表は、 図面解析チーム から。Devinを初めて使う人にとって貴重な知見となる、チームでの実体験を共有してくれました。 Devin Search や Devin Wiki といった最新機能よりも、Devinを使う上でのマインドを共有することに焦点を当てていました。 図面解析チームのDevin活用術: 答えが分かっているタスク: リファクタリングや単体テストなど、ある程度自分たちで方針が見えているタスクを任せている。 いくつかのサブタスクに分割し、既存のPRなど具体例を参考に修正するよう指示。 未知のエラー調査: エラーの原因や修正方針が不明確なタスクについて、Devinに初期調査を依頼。 提案されたアプローチを参考に適宜、人手で修正。 基本スタンス:タスクの60〜80%をDevinに実装させ、残りを人間が引き継ぐ気持ちで扱うと良いと話していました。このスタンスは 公式ドキュメントの言及 と使ってみた感触に基づいているとのことです。 Devinはジュニアエンジニア 曖昧で広範囲なタスクよりも、スコープが小さく明確なタスクが得意 実例を示すと適切な修正になりやすい 公式ドキュメント より また、Devinを使ってみて良かった点ともう少し良くなってほしい点を共有していました。 良かった点: 後回しにしがちなタスク (=難易度は高くないし、やっておきたいが、優先度の都合上着手できてないタスク) を消化できる 迅速なプロトタイピング: 新しいアイデアの初期コードを素早く作ることができます 。 シニアエンジニアとしてのスキル向上: 指示を明確にしないと思わぬ変更をしてしまうことがあるDevinだが、ポジティブに捉えれば、シニアエンジニアとして必要になるタスク分解能力や開発スコープの設定力を鍛える機会になる。 良くなってほしい点: 中途半端な待ち時間: Devinの作業時間(10分程度)は、何もせずに待つには長く、他の複雑な作業を自分が取り組むには短い 30分放置していると、セッションが切れるうえにその間にもACU(Agent Compute Unit:Devinの計算リソース単位)を消費するので、Devinに付きっきりにならないといけないと感じる 既存コードの保護: 「既存コードは変更しないで」と明示的に指示しないと、正しいコード行まで修正・削除してしまうことがある Playbook ) に変更しない旨を書けば対応可能だが、まだ活用しきれていない 今後の改善点: 今後導入していきたい取り組みとして以下を挙げていました。 プロンプトのテンプレート化: プロンプトの属人化を防ぐうえに、ACUの無駄遣いを避けるため、プロンプトの標準化を検討中 PRのdescriptionを書くつもりで指示を出すと、再利用性も高まり便利だったという他チームの知見も共有 Knowledge の活用: 設計書やADR(Architecture Decision Records)をKnowledgeに登録 プロトタイプ作成や設計思想に沿った改修をしやすくすることを期待 🐘 Data Management チーム:AIでチームのボトルネックを解決!Damboの成果発表! 続いて登壇したのは、 Data Managementチーム 。 「Dambo」 という、社内で開発しているAIワークフローを紹介してくれました。 前四半期は5人のチームで 110件もの依頼対応 に対応しましたが、これが大きなボトルネックとなり、依頼対応のリードタイム遅延やチームの他の業務にも影響を与えていました。 そこで開発したのが Dambo です。これはDevinを活用してテーブル変更依頼を自動化するAIワークフローです。 ワークフロー: ユーザーが対象テーブルや変更内容(例:特定のロジックを持つカラムの追加)をフォームから依頼します 。 するとDevinが作業を引き継ぎ、変更計画を立て、プルリクエストを作成します 。 この一連の流れは、必要な情報をDevinに伝えるSlackワークフローを通じて開始されます 。 驚きの処理能力: Damboはわずか1ヶ月で 28件のタスクを完了 。 これには小規模なもの(1時間程度)から大規模なもの(1日以上)まで含まれます 。 例えば、既存の売上テーブルからデータをクレンジング、整理、フィルタリングし、ピボットして新しいユーザーフレンドリーなテーブルを作成する、といった複雑なタスクもこなしました 。 学びと改善: Damboは当初のボトルネックを見事に解消しましたが、その高い効率性ゆえに、今度はレビュー待ちのPRが大量に発生するという新たなボトルネックが生まれました。 この課題に対し、チームでは現在以下のような改善に取り組んでいます。 Devinが作成するPRが、レビューしやすい構成・内容になるようチューニングを行う PR作成時点で差分のあるテーブルを自動でBigQuery上にビルドし、データの中身をすぐに確認できる仕組みを整備する こうした取り組みにより、レビュープロセス負荷の軽減と開発サイクルのさらなる高速化を目指しています。 その他の活用: Data Managementチームのエンジニアは、 Devin Search がいかに提案の質を高めるかについても言及 。 他チームのリポジトリを横断して調査ができるため、他チームへデータの仕様などを質問する際には「これは、何ですか?」という曖昧な質問から、「これは、この理解であっていますか?」という、より具体的で質の高い質問へとシフトでき、コミュニケーションコストの大幅な削減につながったそうです 。 🧭 Quoteチーム:Devin × JIRA連携で開発を「フロントローディング」! 次に、 Quoteチーム のエンジニアが、Devinを「フロントローディング」に活用する革新的な試みについて発表しました。「フロントローディング」とは、開発ライフサイクルの早い段階で潜在的な問題を発見し解決することで、実装開始後に想定外の変更箇所に気づいたり 、受け入れ条件が不十分なままスプリントプランニングに突入してしまったり 、といったよくある手戻りを防ぐことを目指す取り組みです 。 DevinとJIRAの連携: チームはDevin × JIRA連携機能を活用しています 。 JIRAチケットに "Devin" ラベルを付与すると、Devinが以下の情報を返してくれます : タスクの機能要件や不明瞭な点 関連する既存コードの現状 全体的なアプローチ、変更が必要なファイル、設計上の決定事項など、提案される解決策 主な活用方法: チケットの完成度チェック: DevinがJIRAチケットに対する 「自信度 (Confidence Score)」 を表示します 。 スコアが低い場合は、開発に着手する前に受け入れ条件やアプローチをより明確化する必要がある、というシグナルになります 。 コード変更の抜け漏れ早期発見: Devinが提示する実装計画を確認することで、追加のコード変更が必要な箇所や、ロジックの考慮漏れに早期に気づくことができます 。 これにより、リスクを未然に減らすことが可能になります 。 学び: 優れた解釈能力: Devin内部のインデックス作成能力は非常に高く、多少ラフに書かれた内容でもある程度解釈し、業務フローや各種図も理解してくれるそうです 。 インプットの質が重要: とはいえ、質の高いJIRAチケットを作成しなければ、Devinからの回答も浅いものになってしまうため、チケットをしっかり書くコストとのバランスが重要になります 。 AI時代においては、受け入れ条件や実装アプローチを言語化する重要性がますます高まっており、愚直に取り組むか、そのコストを下げる工夫が大切だと語りました 。 総括: QuoteチームにとってDevinは、単にコードを書くだけでなく、開発プロセスそのものを改善するパートナーとなりつつあります 。 🛠️ APチーム:Devinと共に開発をフルスロットルで推進! 最後に登壇したのは、 Analysis Platform (AP) チームのエンジニア。Devinがいかに深く彼らの開発サイクルに組み込まれているかを力強く語ってくれました。 APチームは、今四半期の間に、チームリポジトリで Devinが作成したプルリクエストを22件もマージ するという成果を上げています! これらは単なる軽微な修正だけではなく、以下のようなタスクもDevinが担当しました: IaC (Infrastructure as Code) におけるIP制限設定 機械学習推論サーバーの初期コミット Terraformディレクトリのリファクタリング 活用の秘訣: 集中できるインターフェース選択: スピーカーが Devin とやりとりする際はSlack上ではなく、主にapp.devin.aiを利用することで、スレッドが流れずに把握できます。またAIがリアルタイムでコードを編集する様子を見るのも楽しんでいるそうです 。 スプリントのスタートダッシュ: 新しいタスクやスプリントに着手する際の「初動の重さ」をDevinが解消 。スプリントプランニング後にDevinにタスクを依頼することで、すぐに開発が進み始めます 。 レビューとオンボーディングの効率化: Devinが作成したPRへのコメントにはDevinが自動で応答するため、非同期レビューがスムーズになりました。 また、Devin SearchやDevin Wikiは、複雑なai-labモノレポに新メンバーがキャッチアップする上で非常に役立っており 、ファイル検索や既存実装の理解にかかる時間を大幅に削減しています。 APグループが実感する主なメリット: 時間短縮: ブランチ作成、ファイル検索、PRレビューといった細かな作業時間を大幅に削減 並行開発の実現: 「とりあえずDevinに任せる」ことで、多くの開発タスクを並行して進められるように 明確なタスクにおける高精度: 実装方針が明確な場合や、参考にできる既存実装がある場合の精度は高いとのこと 今後の展望: APチームは、Devin単独で検証可能なテスト設計や 、Jira/Confluenceとの連携強化により、さらなるワークフローの効率化を目指しています 。 ✨ 私たちのDevin活用ジャーニー:LT全体を通じた学び 今回のLT全体を通して、いくつかの共通するテーマが見えてきました。 各チームの発表後には多くの質疑応答が生まれ、実際の業務で直面するであろう課題やその解決策について、具体的な議論が深まった ことは、社内LTならではの大きな収穫でした。 迅速な開発スタート: Devinは開発タスクの初動を早めるのに貢献しています 。 オンボーディングと知識共有の進化: Devin SearchやWikiは、大規模なコードベースの理解や既存システムの把握に強力なツールとなっています 。 定型業務の自動化: 「Dambo」の事例のように、明確に定義された反復作業はDevinに任せることで、エンジニアはより複雑な課題に集中できます。 プロアクティブな問題解決: JIRA連携によるフロントローディングは、問題が大きな手戻りとなる前にDevinがその発見を助ける可能性を示しています 。 人間とAIの協調が鍵: 最も成功している活用法は、エンジニアがDevinを導き、タスクを分解し、その成果をレビューするという形です。これは「置き換え」ではなく「能力拡張」と言えるでしょう。 継続的な学習: Devinに効果的に「指示(プロンプト)」を出し、最大限の能力を引き出す方法は、私たち全員がまだ学んでいる最中です。Devinを、常に学習し続ける非常に有能なジュニアエンジニアのように捉え 、共に成長していく姿勢が大切です。 Devinの活用は、より良いソフトウェアを生み出し、ダイナミックで革新的なエンジニアリング文化を築くための挑戦の一つにすぎません。3か月後には、私たちがメインで活用しているAIエージェントはDevinではないかもしれません。しかし、Devinを通じて得られた知見や経験は大いに活かされると思っています。 💡 未来の開発を、私たちと一緒に形にしませんか? もし少しでも興味を持っていただけたなら、ぜひ気軽にお話ししましょう。 採用ページ( https://recruit.caddi.tech/ )では、私たちの挑戦や価値観、そしてあなたがどのように活躍できるかをご紹介しています。ワクワクするような未来を、一緒に切り拓いていきましょう!
こんにちは。キャディでプロダクトマネージャー(以下PdM)をしている北林です。 昨年の6月にキャディに入社し、現在はリリース前の新機能のPdMをしています。 今日はこの新機能のDiscovery *1 での、デザイナーやエンジニアとのコラボレーション事例について共有しようと思います。 こんな方に向けて書いています DiscoveryでのPdM、デザイナー、エンジニアのコラボレーション事例を知りたい方 グローバルなチームでのコラボレーション事例を知りたい方 チーム紹介 まずは私たちのチームを紹介します。 まだ正式リリース前の機能ということもあり、PdM、デザイナー、エンジニアがそれぞれ1名のスモールなチームです。私は日本生まれ日本育ち、ワンさんは台湾出身、Matthiはドイツ出身と、グローバルなチームでもあり、普段のミーティングは日本語と英語のMixで進むことが多いです。 チーム紹介 今回のDiscoveryの進め方 これまでDiscoveryはPdMが中心になって進めることが多かったのですが、今回は PdM・デザイナー・エンジニアの3人でコラボレーション して進めました。 デザインシンキングのフレーム にそって今回の進め方を整理 table { border-collapse: collapse; } th { border: solid 1px #666666; color: #000000; background-color: #e6e6e6; } td { border: solid 1px #666666; color: #000000; background-color: #ffffff; } デザインシンキングのステップ 今回のDiscoveryの進め方 共感(Empathize)  ユーザーの立場に立って、感情・行動・ニーズを深く理解する チーム全員でインタビューに参加 問題定義(Define)  集めた情報をもとに、本質的な課題を明確にする チーム全員でMiroボードでユーザーの業務フローや課題を整理しディスカッション アイデア発想(Ideate)  課題に対する解決策をたくさん出す 課題に対しチーム全員でIdeation sessionを開催して解決策を検討 プロトタイプ(Prototype)  アイデアを簡単な形にしてみる エンジニアがHi-fi(高忠実度)プロトタイプ *2 を作成 テスト(Test)  ユーザーに試してもらい、フィードバックを得る Hi-fiプロトタイプをユーザーに実際の業務を想定したシナリオで使ってもらい、観察する このような進め方をとった背景には、以下のような理由があります。 プロダクトがまだ立ち上げ段階にあった→ 仕様や実装方針が固まりきっておらず、チームで仮説を立てながら柔軟に方向性を探るアプローチが有効だった。 PdM・デザイナー・エンジニアがそれぞれ1名ずつという非常にスモールなチーム構成だった→ 意思決定のスピードを保ちながら、密なコラボレーションがしやすい状況だった。 デザイナー・エンジニアが今回の機能領域の開発経験があった→ 私が1人で考えるより、Discoveryから3人で議論を重ねることで、よりよい意思決定につながると考えた。 なお、こういったコラボレーション型のDiscoveryはすべてのケースに適しているわけではなく、例えば以下のような状況ではPdMが単独で進める、もしくはDiscovery自体を軽量化する方が適している場合もあると思います。 リードタイムが極端に短い場合→ スピードを優先した判断と意思決定が求められるため、意思決定プロセスをよりシンプルにする必要がある。 大規模なプロダクトやチームの場合→ 関係者が多すぎると合意形成に時間がかかり、コラボレーションの効果が薄れてしまう。 探索の余地が少ないリニューアル・改善案件の場合→ 既知の課題に対する明確な解決策があるケースは、時間をかけたDiscoveryは不要なこともある。 要件が外部により規定される場合(法令対応など)→探索よりも正確な実装や影響範囲の把握が重要となる。 チームで具体的に取り組んだこと 全員でユーザーインタビューに参加 お客様を訪問し、 実際の業務を想定したシナリオに沿って Hi-fiプロトタイプを使っていただき、その様子を観察しました。 現地にて実際に動くプロトタイプを使っていただくことで、ユーザビリティ上の課題が発見できたり、検討できていなかったサービス外でのタッチポイントでの課題を発見することができました。 また、お客様から「これなら〇〇の業務にも使えそう」とコメントをいただくなど、想定していなかったユースケースに気づくことができました。 お客様を訪問した様子 インタビュー結果を視覚化 Miro上に業務フローを書き出し、どこで顧客の課題が発生しているかをマッピングしました。 製造業の業務フローは非常に複雑ですが、ダイアグラムにまとめることで、キャッチアップがしやすく、またチーム内での認識を揃えることができました。 (また、グローバルチームで全員母国語が異なるため、できるだけ文章ではなくダイアグラムに整理するようにしていました) ユーザーの業務フローをダイアグラムに整理 チームでのIdeation Workshop 課題を洗い出した後は、チームで重要課題を決め、その課題について解決策のアイデアをたくさん出すというIdeation workshopを行いました。 Ideationは発散のフェーズであるため、「否定しない」「質より量」「自由奔放」「人のアイデアに便乗する」という雰囲気作りも重要です。 Ideationには色々な方を巻き込むのが望ましいので、CPOの白井と、PdMの庭瀬も巻き込みました。 色々な視点からたくさんの新しいアイデアが出て、その中から実際にロードマップに追加されるものもうまれました。 Ideation workshopで使ったボード PRD *3 もチームで共創 PRDは私はこれまで1人で書くことが多かったのですが、今回はチームで共同で書きました。 具体的には、ユーザーペインはPdM、ユーザーストーリーはデザイナー、機能要求はIdeationの結果をふまえてPdMがまとめ、技術のリスク点などはエンジニアが書く・・というようなかたちです。 PRDをチームで書いているので、レビューの時間が短縮できるという副次的な効果もありました。 チームでDiscoveryを進めてみて、正直どうだった? 今回チームでDiscoveryを進めてみて、正直どうだったかを3人でディスカッションしてみました。 得られたこと 全員でアイデアを出すことでソリューションの 選択肢が広がり、より良い判断ができた  デザイナー・エンジニアも顧客理解が進み、 ユーザー視点での設計・実装が可能になった  Hi-fiプロトタイプでの検証 などPdMだけではできないアプローチが実現した 技術的観点での実装時のリスクや落とし穴 を早期に認識できた。(今までは、PRDを読んだ段階で ”おっと!これは難しいぞ” となることもあったので) 「なぜこの機能を作るのか?」という共通認識がチームに生まれた 難しかったこと・乗り越え方 PdMが単独で進めるより 時間はかかる。 顧客インタビューの日は移動も含めると丸一日デザインや開発が進まない。 → 全員がすべてのインタビューに参加するのではなく、検証したいことや実際に自分の目で確認して欲しい顧客属性など、対象のインタビューを選ぶ。 Hi-fiプロトタイプを作る 工数が大きい → Hi-fiプロトタイプでしか検証できないことにフォーカスし、不要な部分は作らない。それでもやっぱり時間はかかったので、工数の見積もりはシビアにおこなう。 グローバルチームならではの 言語の壁 →できるだけテキストではなく、Diagramや実際のプロトタイプで説明することを心がける。 職種の役割に囚われない...といっても 役割分担が難しい →お互いのcanやwillの相互理解が重要。チームビルディングが大切。 (これは私が勝手に思っていたことですが)「PdMが全部考えないといけない」という固定観念があった →PdMの仕事は結果を出すことであるため、チームに自分よりもっと得意な人がいたら余計なプライドは捨てて任せるべきだし、良いアイデアはチームで出せば良い 最後に:まずは小さく試してみよう 前述の通り、Discoveryをチームでおこなうのは一定の時間もかかりますし、エンジニアやデザイナーの時間の使い方も変わるため、組織的な調整も必要です。 いきなりフルで巻き込むのではなく、 ユーザーインタビューに1件だけ参加してみる 1時間だけIdeation sessionを開いてみる など、小さなことから始めてみるのはいかがでしょうか。 *1 : プロダクト開発における、何を作るべきかを発見するプロセスのこと *2 : Hi-fiプロトタイプ:実際の製品に近いプロトタイプ。実際に動くものを指すことが多い。 対比する言葉に、Lo-fiプロトタイプ(Figmaや手書きで作成した、レイアウトや機能の流れを表現した簡単なプロトタイプ)がある。 *3 : PRD(Product Requirements Document):プロダクトの背景・目的・要件を整理し、チームの共通認識を作るためのドキュメント
こんにちは、Data&Analysis部(D&A)です。 D&Aでは週1回、機械学習の勉強会を開催しており、本記事は、勉強会の内容を生成AIを活用して記事にまとめたものです。 ※勉強会内容公開の経緯は こちら ※過去の勉強会は「社内勉強会」タグからもご覧いただけます。 はじめに 従来の異常検知モデルの異常度に関する課題 MAD-Benchの構築 MLLMベースの手法 実験内容 実験結果 ベンチマークとモデルタイプ分析 (RQ1) バイナリ・マルチレベル性能相関 (RQ2) 異常領域面積効果 (RQ3) 深刻度別の検出性能 (RQ4) 正常クラス拡張 (RQ5) ロバストネス分析 (RQ6) まとめ はじめに 本記事では、以下の資料を参考にしています。 Are Anomaly Scores Telling the Whole Story? A Benchmark for Multilevel Anomaly Detection 従来の異常検知モデルの異常度に関する課題 異常検知の分野において、従来の異常検出モデルが出力する異常スコアマップは、入力画像に対してピクセルレベルまたは領域レベルで異常の度合いを示します。しかし、これは実際の深刻度を反映していない可能性があるのではないか? という問題提起が本論文内でなされています。例えば、小さい変化であっても、医療分野においては見逃してはいけない初見である可能性があり、異常スコアが低いからといってその深刻度も低いとは限りません。 したがって、実際の応用においては、異常を単に「正常か異常か」として検出するだけでなく、その深刻度をレベル分けして評価する必要があると本論文内で提起されています。本論文では「レベル順に異常スコアが割り当てられるような関数を発見することがゴール」と定義されています。 MAD-Benchの構築 上記の問いと必要性に応えるため、深刻度に応じてレベル分けされた新しい異常検出データセット「MAD(Multilevel Anomaly Detection)-Bench」が構築されました。これは、従来のベンチマークでは評価が難しかった異常スコアと深刻度の整合性を評価するための基盤となります。 MAD-Benchは元々存在するデータセットを以下の条件で分割するものになります。 学習データをL0, L1, ..., Lnの集合に分割する Lはlevelを意味し、L0が正常、L1, L2, ..., Lnの順に深刻度が上昇 L0のみで学習し、テストはL0からLnまで全てのデータを対象とする 具体的には、以下のようなデータセットが作成されました。 Multi-Dogs-MAD 犬(L0)からレベルが上がるにつれて異なる犬種、猫、鳥、花へと変化します。 MVTec-MAD 異常検出に特化した既存データセット(MVTec、VisA)を基に、欠陥の深刻さごとにレベルを設定しています。 DRD-MAD (Diabetic Retinopathy Detection) 糖尿病性網膜症検出データセットを基に、網膜画像の所見の深刻度に合わせてレベルを設定しています。 VisA-MAD 異常検知に特化したデータセットであるVisAより作成。欠陥の深刻さごとにレベルを設定しています。 Covid19-MAD 胸部X線画像を基に、所見の深刻度に合わせてレベルを設定しています。 SkinLesion-MAD 健康な皮膚画像と異常画像を含み、所見の深刻度に合わせてレベルを設定しています。 Binary Anomaly DetectionとMultilevel Anomary Detection(論文より引用) 論文内ではこれらのデータセットを用いて、既存の異常検知モデル、およびMLLMベースの手法の異常検知における異常度と深刻度の関連性を調査しています。 MLLMベースの手法 3枚の正常画像とプロンプトを与えて、テスト画像を推論させます。プロンプトには以下のような情報を含みます。 コンテキスト:画像の説明 タスク説明:目的や潜在的な異常の説明など 深刻度レベル説明:こういった特徴を持った画像は深刻というような説明 フォーマットガイドライン:0-100のスコアと理由を返して欲しいというような要望 MLLMベースの手法(論文より引用) 実験内容 MAD-Benchを用いて、以下の観点で異常検出モデルを評価しています。 RQ(Research Question)1:ベンチマークとモデルタイプ分析 異なる種類の異常検出モデルが、様々なアプリケーションにわたる深刻度レベルと整合した異常スコアをどの程度正確に割り当てられるか? RQ2:バイナリ・マルチレベル性能相関 バイナリ異常検出(正常or異常という観点で異常を判定するタスク)でうまく機能するモデルは、マルチレベル異常検出でもうまく機能するか? RQ3:異常領域面積効果 異常領域の面積は、検出モデルによって生成される異常スコアにどのように影響するか? RQ4:深刻度別の検出性能 異常の異なる深刻度レベル間で、バイナリ検出性能はどのように変化するか? RQ5:正常クラス拡張 軽微な異常が許容され、正常クラスの一部として含まれた場合、モデルの性能はどうなるか? RQ6:ロバストネス分析 データ破損の下で、異常スコアを深刻度と整合させる上で、検出モデルはどの程度ロバストか? 評価指標には以下の3つが採用されています。 AUROC (AUC):バイナリ異常検出能力(正常か異常か)を評価する。 C-index:異常スコアと深刻度レベルの整合性を評価する。1に近いほど整合性が高い。 ケンドールの順位相関係数 (Ken):C-indexと同様に整合性を評価する。同じ深刻度レベル内のサンプルが完全に同じスコアを持つ場合に最大値1となる、より厳格な指標。 実験結果 ベンチマークとモデルタイプ分析 (RQ1) VisA-MAD以外のデータセットではMLLMベースのモデルが良い性能を示すことが示されました。MultiDogs-MAD、 MVTec-MAD、 SkinLesion-MADで、より高いCおよびKenの値を持っていて、マルチレベルでの異常スコアと深刻度の整合性が高いことが示されています。つまり、深刻度が高いレベルでの異常スコアが高くなるような傾向が見られたということです。ただし、専門知識が必要なDRD-MADではMLLMベースでも性能が低くなりました。これはMLLMであっても、特定の分野においては事前知識の注入が重要であることを示していると言えます。 バイナリ・マルチレベル性能相関 (RQ2) AUCとC-index、AUCとKenのSpearman相関係数はそれぞれ0.973、0.916と強い正の相関を持つことから、バイナリ検出性能の高いモデルは、概して異常スコアと深刻度レベルの整合性も高い傾向があるといけます。MLLMはよりその傾向があり整合性を持たせやすいようですが、バイナリ検出性能は従来のモデルに劣る場合もあります。 異常領域面積効果 (RQ3) 異常領域の面積が異常スコアに影響を及ぼすという仮説があります。つまり、深刻であるかどうかに関わらず、異常らしき部分の面積が大きいほど異常スコアが高くなってしまうという仮説です。この実験ではMAD-Benchで使用する経済的影響といった深刻度ベースと、面積ベースでの深刻度レベル(異常領域の面積に基づいて定義)で性能が比較されました。すると、従来のモデルは異常領域の面積に強いバイアスを持ち仮説通りの挙動を示します。したがって、小さいが深刻な異常を過小評価する可能性があります。ただし、MLLMベースだとこの傾向が小さく、バイアスが抑えられているようでした。 深刻度別の検出性能 (RQ4) 深刻度が高いものほど異常度とみなしたいため、期待する挙動としては、深刻度が高いほど異常スコアが高くあって欲しいものです。実験の結果から一般的に深刻度が高いものほど検出性能が上がる傾向を示しますが、一部のモデル(OCR-GAN, PNI)では隣接するレベルで逆転が見られる場合がありました。 正常クラス拡張 (RQ5) この実験では高レベルの異常を正常データに含むほど検出性能は下がる傾向にありました。異常らしい特徴を正常として学習してしまうので当然の挙動とも言えます。しかし、MLLMを使った場合、性能を維持できるデータセットもあります。深刻度についてのコンテキストが事前に注入されていることが効いているのかもしれません。唯一DRD-MADは傾向に反し、軽微な異常を正常に含めると性能が上がりました。これは軽微な異常と正常なデータが似通っていて、軽微な異常に低い異常スコアをつけられるためと考えられます。 ロバストネス分析 (RQ6) こちらも当然ですが、明るさ調整やノイズといったデータ破損は全てのデータセットで性能に影響を与える。特にMVTec-MADとSkinLesion-MADは影響が大きかったようです。これらのデータセットに含まれる異常の特徴とノイズが似ているからと考えられています。 MultiDogs-MADに関しては影響を受けにくいという結果になりました。これは細かい特徴よりも、より抽象度の高い特徴に依存するためと考えられます。 まとめ MAD-Benchのような実用的な展開を見据えたデータセット作成が、現実世界を踏まえたモデルの性能評価につながるというのは面白い取り組みでした。発表者は以前外観検査に取り組んでいたこともあり、異常スコアと深刻度が一致しないことによる見逃しや過検知と言った課題は認識していましたが、MLLMを使ったアプローチでその課題を解決できそうであるというのは非常に興味深かったです。しかし、発表内では以下のような懸念も参加者から挙げられました。 既存の有名なデータセット(MVTecなど)がベンチマークとして使用されている場合、MLLMなどがこれらのデータセットで学習済みである可能性(データリーク)が懸念されます。 MAD-Benchにおける深刻度レベルの具体的なアノテーション基準(例えば「経済的影響に応じて」といった記述の詳細)が不明瞭な場合があります。基準が明確でないと、結果の解釈や再現性に影響が出る可能性がある。 この手法を実業務に取り込む場合は、データセットは公開されていないもので評価することや、アノテーション基準を明確にしておくことが重要ですね。
こんにちは、Data&Analysis部(D&A)です。 D&Aでは週1回、機械学習の勉強会を開催しており、本記事は、勉強会の内容を生成AIを活用して記事にまとめたものです。 ※勉強会内容公開の経緯は こちら ※過去の勉強会は「社内勉強会」タグからもご覧いただけます。 はじめに LLMシステムアーキテクチャの概要 テナントごとの体験を提供するLLMシステム RAG ファインチューニング RAGとファインチューニングの併用 サイロとプール サイロ プール LLMシステムを構築する際の考慮事項 テナント分離 コスト計算 ノイジーネイバー まとめ はじめに 今回は、マルチテナントSasSにおけるLLMシステムアーキテクチャの方針と考慮事項について調査しました。調査の経緯は以下のとおりです。 キャディでは様々なデータに対してLLMを活用した技術検証が進んでおり、近い将来CADDi Drawerを始めとしたマルチテナントSaaSにLLMソリューションを何個も提供する可能性があるため。 その将来の実現のために、本記事での紹介内容をあらかじめ考慮したシステムを考えておく必要があると感じたため。 本記事では、以下の資料を参考にしています。 マルチテナントSaaSアーキテクチャの構築 16章 生成AIとマルチテナント re:Invent 2024: AWSのマルチテナントSaaSにおけるLLM活用アーキテクチャ LLMシステムアーキテクチャの概要 LLMシステムをマルチテナントSaaSで展開する場合、シンプルな構成として下図のようなものが考えられます。 この場合、同じプロンプトを送信した場合同じレスポンスしか得られません。そのため、個社(以降、テナント)ごとの体験(例:社内資料に関する質問への回答など)を提供することで顧客体験の改善をさせたい需要が出てくる場合、その需要に対応できません。 そのため、テナントごとにカスタマイズされたLLMシステムの構築が重要となります。 テナントごとの体験を提供するLLMシステム テナントごとの体験を提供するためのアプローチとして、下図のようなRAG(Retrieval-Augmented Generation)とファインチューニングがあげられます。 RAG RAGを利用する場合は、リクエストの度に外部のデータソースから関連情報を検索し、その情報をプロンプトに含めてLLMに送信することで、テナント固有の情報を持ったレスポンスを生成することができます。 手順としては以下のようなステップを踏みます。 テナント識別: JWT(JSON Web Tokens)などを用いてリクエストがどのテナントからのものかを識別します。 データ検索: JWTから抽出できるテナントIDを基に、該当するテナントのデータに対してベクトル検索を実行します。 コンテキストの追加: 検索結果として得られたテナント固有の情報は、プロンプトに追加されLLMに送信されます。 ただし、以下のような課題もあります。 コスト: RAGではリクエストごとにプロンプトとコンテキストを結合してLLMにを送信する必要があるため、トークン数のコストが増加する可能性があります。 精度: プロンプトとコンテキスト全体のトークン数がLLMの許容上限を超えることで、チャンク化する際の文章の切れ目が悪くなり回答の精度が劣化する可能性があります。 アクセス制御:別の顧客の情報が流出してしまうのを防ぐために、適切なテナントのデータにアクセスするように制御しなければなりません。 ファインチューニング テナント固有のデータを用いて既存のLLMモデルを再学習させることで、そのテナント特有の知識をモデルに埋め込むことができます。 ファインチューニングを利用すると、RAGを使わずともテナント固有の情報を提供できるのてRAGのデメリットを低減できるというメリットがあります。 しかし、個社ごとにLLMモデルを作成、デプロイ、運用する必要があるため、管理が煩雑になってしまうというデメリットがあります。 RAGとファインチューニングの併用 RAGとファインチューニングは決して排他的なものではなく、プロダクト要求や顧客のTier(例:料金プラン)に応じて併用するという考え方もあります。資料では、以下の通り、Tierに応じてRAGとファインチューニングを併用する方針が紹介されていたので、共有します。 Basic Tier(例:料金プランが低めの顧客): RAGを活用した共通のLLMモデルを提供 Premium Tier(例:料金プランが高めの顧客): ファインチューニングを施した個別のLLMモデルを提供 余談:Tierに応じて共通のモデルを使うか個別のモデルを使うか決めるというこの方針は、LLMのみならず機械学習モデルにおいても適用できる考え方であるとともに、次節のサイロとプールのメリットをうまく享受し、デメリットもうまく制御できるのでとても参考になりました。 サイロとプール 全テナントで共通のモデル、または個別のモデルを提供するための基本的な概念として、下図で表されるサイロとプールというアーキテクチャパターンがあります。 サイロ 各マイクロサービスを一つのテナントが占有するパターンです。 メリット: データとテナントを明確に分離できるので、情報漏洩のリスクが低いです。 顧客のアプリケーションの利用状況が把握しやすいため、クラウドサービスを通してアプリケーションを提供している場合、利用コストの管理が容易です。 他テナントによるノイジーネイバー(後述)の影響を受けません。 デメリット: テナント数が増加すると、個別のシステム変更や運用が必要となり、管理コストが増加します。 プール 各マイクロサービスを全テナントで共有するパターンです。 メリット: 特に多数のテナントが存在する場合、管理コストを抑えられます。 デメリット: テナントごとにデータが分離していることの保証が難しく、テナントごとのコスト把握が困難です。 一部のテナントによる過剰なリソース利用(ノイジーネイバー)が発生し、他テナントのサービス品質に影響を与える可能性があります。 これらのパターンは排他的なものではなく、プロダクト要求や技術要件などに従いマイクロサービスごと、その中のコンピューティングリソースとストレージごとに織り交ぜるといった使い分けが現実的です。 LLMシステムを構築する際の考慮事項 個社向け、または共通のLLMを提供する際には、以下の点が重要な考慮事項となります。 テナント分離 プール(共通LLM)で提供する場合、各テナントが適切なデータのみを参照していることを保証する必要があります。 例えばRAGの場合、テナントごとにトークンを取得し、そのトークンに基づいて検索インデックスへのアクセス権限を制御するなどの仕組みを実装する必要があります(参考資料:RAG における検索システムの権限分離と評価)。 コスト計算 テナントごとのLLM利用コスト(主に入出力トークン数)を把握することは、ビジネスとエンジニアリングの両面で重要です。 ビジネス視点: トークン利用料がSaaS利用料を上回っていないかを確認し、収益性を評価するために必要です。 エンジニアリング視点: どのテナントが多くの負荷をかけているかを把握するために重要です。 プール構成の場合、テナントごとのコスト把握は困難であり、利用トークン数を記録する以下のような専用のシステムを構築する必要があるかもしれません。 一方、サイロ構成の場合、クラウドサービスのコンソールやダッシュボードからテナントIDを指定するといったことにより、比較的容易にコストを把握できます。 ノイジーネイバー マルチテナントSaaSにおけるノイジーネイバーとは、少数のテナントがシステム全体のリソースを過剰に利用することで、他多数のテナントが正常にサービスを利用できなくなる現象です。 LLMにおいて、共通モデルを利用するテナントで発生しやすい傾向があります。 対策としてはスロットリング(例:テナントごとやTierごとにトークン利用量の上限を設定する)が有効になります。 まとめ マルチテナントSaaSにおけるLLMシステムアーキテクチャの設計においては、共通のLLMモデルと個別のLLMモデルの使い分けが重要です。使い分けの基準の一つとしてSaaSの料金プランといったTierに応じた使い分けがあります。 共通のLLMモデル: 料金プランが低めのテナントに対して、RAGを利用することで個別の体験を提供できます。 考慮事項は、各テナントに対して適切なデータを参照しているか保証するためのアクセスコントロール、コスト把握のための専用システムの構築、ノイジーネイバー対策です。 個別のLLMモデル: 料金プランが高めのテナントに対して、ファインチューニングしたLLMを利用することで、個別の体験を提供できます。 考慮事項は、顧客ごとにモデルを作成、デプロイ、運用する必要があるため、管理が煩雑になるのを防ぐ手立てを用意しておくことです。
こんにちは、Drawer Growthグループ ソフトウェアエンジニアの内田( id:usadamasa , @usadamasa )です。弊社ではApache Icebergの活用 *1 とともに、一部のアプリケーションにJavaを導入しています。今回は、システムアーキテクチャから一段レイヤを下げてアプリケーションレベルのお話しをしたいと思います。 アプリケーションアーキテクチャの設計と運用課題 アプリケーション開発において、私たちエンジニアは通常、パッケージ構成やレイヤの依存関係、ロギングなどの観点からアーキテクチャを設計します。 しかし、実装との不整合やチーム内での共通認識が不十分なまま進むと、品質課題として潜在化し、やがて本番障害や開発者の疲弊といった形で問題に発展します。また、DevinやClineなどのAIエージェントに適切に実装してもらうにはプロンプトやドキュメントで設計を伝える必要がありますが、相応の準備が求められます。 このような課題を解決する手段として、設計と実装の整合性をテスト可能にするJavaライブラリ「 ArchUnit *2 」があります。JUnitのフレームワーク上で動作し、設計ルールを宣言的なコードで定義してテストとして実行できるため、普段の自動テストと同様の迅速なフィードバックが得られます。 ArchUnitの導入の経緯と効果 私たちのアプリケーションは開発開始から約半年の若いコードベースで、初期はアーキテクチャらしい構造もないプロトタイプからスタートしました。開発が進む中で、パッケージとレイヤ構成を設計し、リファクタリングを重ねて整理してきました。 次の図はそのレイヤ構成です。「オブジェクト設計スタイルガイド *3 」を参考に、Application層、Service層、Domain層、InfraStructure層の構成を採用しました。 レイヤ構成図 実装の大部分はこの構成に従っていますが、初期のコードやレビューをすり抜けた箇所には、設計に適合しない部分が残っていました。開発が一段落し整理を試みたものの、実際にどの程度のコードが不適合かは把握できていない状況でした。 そこで、冒頭のArchUnitを導入し、不適合な実装の網羅的な検出と、今後のルール遵守を継続的にチェックできる仕組みを構築しました。 ArchUnitでは、パッケージやクラス間のアクセス、継承、循環参照など様々なルール検査が可能です *4 。今回はその中でも「Layer Checks *5 」を用い、定義したレイヤ構造への準拠を確認しました。以降で、その具体的なルールと実装の一例を紹介します。 初期バージョン 「DAO層にアクセスできるのはRepositoryImpl層のみである」という設計をコード化しましょう。 DAO( persist.dao パッケージ)層はRDBなどのMWと直接通信をする層です。Service層( service パッケージ)はRepository層( domain.repository パッケージ)をインタフェイスとし、RepositoryImpl( persist.impl パッケージ)がDAO層に直接アクセスします。つまり、DAO層に直接アクセスできるのはRepositoryImpl層のみという設計です。この設計をArchUnitで記述すると、次のようになります。 *6 @AnalyzeClasses (packages = "com.example.sample" ) public class LayeredArchitectureTest { @ArchTest static ArchRule test_DaoMustBeAccessedOnlyFromRepositoryImpl = // DAO <- only repository impl layeredArchitecture().consideringAllDependencies() // <persist> DAO .layer( "DAO" ).definedBy( "com.example.sample.persist.dao.." ) // <persist> Repository Impl .layer( "RepositoryImpl" ).definedBy( "com.example.sample.persist.impl.." ) // Assert .whereLayer( "DAO" ).mayOnlyBeAccessedByLayers( "RepositoryImpl" ); } @AnalyzeClasses のアノテーションと、フィールド変数としてテストケースが記述されていることに驚くかも知れませんが、これはキャッシュ化のためで、本筋からは外れるので、詳細は省きます。 *7 Layer Checksは Architectures.layeredArchitecture に続いてメソッドチェインでルールを記述していきます。検査対象のレイヤ名と対応するパッケージ名のセット( LayerDefinition )は複数記述できます。 LayerDefinition に続いて、どのレイヤにどのようなルールが期待されるのかを whereLayer("DAO").mayOnlyBeAccessedByLayers() のような形で続けます。これもメソッドチェインで複数記述できます。 さて、このテストケースはJUnit5のテストスイートとして実行できます。実行は高速で数秒で完了します。 JUnitによる実行結果 さて、検査に違反する場合を見て見ましょう。サンプルとしてわざとDAO層のクラスをService層である ExampleQueryService から直接呼び出す実装を書いてみます。 package com.example.sample.service; // ..snip.. public class ExampleQueryService { // FIXME ! This dependency must be removed!! ExampleDao exampleDao; // ..snip.. } この状態でJUnit5を実行すると、期待通りテストが失敗しました。少々見慣れないメッセージが出力されますが、読み解くことは容易です。 java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture considering all dependencies, consisting of layer ' DAO ' ( ' com.example.sample.persist.dao.. ' ) layer ' RepositoryImpl ' ( ' com.example.sample.persist.impl.. ' ) where layer ' DAO ' may only be accessed by layers [ ' RepositoryImpl ' ]' was violated ( 1 times): Field <com.example.sample.service.ExampleQueryService.exampleDao> has type <com.example.sample.persist.dao.exampleDao> in (ExampleQueryService.java: 0 ) (StackTrace省略) AssertionError としてArchitectureの違反(Violation)が報告され、5行目の Field ~~ が具体的な違反コードへの指摘となります。 さてサンプルコードがやや長くなりましたが、このような形で設計意図をルールとして記述し、JUnitとして実行できることがおわかりいただけたかと思います。 チームへの共有とルールの改善 さて、このようなテストコードを書き、実際に修正に入る前に導入についてチームに共有したところ、このようなフィードバックをもらいました。 チームからのフィードバックコメント チームメンバの言うとおり、たしかに私はこの設計について把握しておらず、とても有益な情報でした。ではこの設計をArchUnitに反映させましょう。先ほどのテストケースに追加・編集します。 @AnalyzeClasses (packages = "com.example.sample" ) public class LayeredArchitectureTest { // [ADDED] QueryService allow to access DAO directly static DescribedPredicate<JavaClass> QUERY_SERVICE_PREDICATE = resideInAPackage( "com.example.sample.service.." ) .and(nameEndingWith( "QueryService" )); @ArchTest static ArchRule test_DaoMustBeAccessedOnlyFromRepositoryAndQueryService = // DAO <- only repository impl layeredArchitecture().consideringAllDependencies() // <persist> DAO .layer( "DAO" ).definedBy( "com.example.sample.persist.dao.." ) // <persist> Repository Impl .layer( "RepositoryImpl" ).definedBy( "com.example.sample.persist.impl.." ) // [ADDED] <service> Query Service .layer( "QueryService" ).definedBy(QUERY_SERVICE_PREDICATE) // Assert .whereLayer( "DAO" ).mayOnlyBeAccessedByLayers( "RepositoryImpl" , "QueryService" // [ADDED] ); } DescribedPredicate が新しく登場しました。これは、独自のルールを記述するためのAPIであり、and条件、or条件や様々な評価方法を組み合わせることができます *8 。ここではService層のなかでも、クラス名が QueryService で終わるもののみDAO層にアクセスできるというルールを追加しています *9 。これにより先ほど違反として検出されたコードも、適合していると判定されるようになりました。あとは残った違反コードを修正すれば終わりです *10 。 このような形で当初の課題であったアプリケーションアーキテクチャの継続的かつ自動的なチェックが可能になりました。 ArchUnitの所感 ここからはArchUnitの意義や所感についてやや雑多に述べようと思います。 よいところ 自然言語で記述した設計ルールや意図をコードとして落とし込めること PRのレビューで初めて知った、が防げる 解釈のブレも抑えられる フィードバックが高速かつJUnitに統合されていること 追加の手順が不要で実行漏れが防げる CIパイプラインにそのまま載せられる 画像のようにGitHub ActionsのJUnit Test Reportで通知も可能 GitHub ActionsでのJUnit Test Report 気になるところ/所感 一方で、ArchUnitにはいくつか注意すべき点もあります。特に宣言的でDSL(ドメイン特化言語)色が強いため、習得・定着には一定のコストがかかると感じました。 中でも DescribedPredicate を用いてルールをカスタムする場合には、ルールそのものの動作検証が必要になるため、実装には慎重さが求められます。 このため、ArchUnitで記述するルールは、設計意図に基づいたものに限定し、必要以上に複雑な実装は避けるべきでしょう。 一般的なコーディング規約や静的解析ルールについては、SpotBugs *11 やSonarQube *12 のようなプリセットの揃ったLinterと併用するのが望ましいと感じました。 また、ArchUnitでは循環的複雑度などの構造的な指標を定量的に評価し、ルール化できます *13 。 しかし、あくまで単一のコードベースに対するLinterであり、全社的な品質管理やチーム横断的な指標の可視化には向きません。 クオリティマネジメント職やマネージャー層向けには、別途横断的に可視化できるツールの導入を検討すべきでしょう。 ArchUnitに過度な期待を抱かず、その役割を正しく見極めたうえで、目的に応じて他のツールと適切に使い分けることが重要です。 結びに替えて: 生成AIとソフトウェアアーキテクチャ ここから先はArchUnitとは直接関係のない与太話なので話半分で読んでいただけると幸いです。 ここ数ヶ月で、ソフトウェアエンジニアの開発環境に生成AIが急速に浸透しつつあります。 生成AIと協働するコーディング活動を Vibe Coding *14 と呼ぶようになってまだ新しいですが、 AIのコード生産量に人間が追いつけなくなるのはそう遠い未来ではないでしょう。 人間によるコードレビューが困難になったとしても、機能品質は従来の自動テストやE2Eテストでの担保や、そもそもソフトウェアエンジニアを介さずプロダクトマネージャーなどの検証で十分になるかもしれません。では非機能品質は、内部品質は、ソフトウェアアーキテクチャはどのように担保出来るでしょうか? そもそもこの先ソフトウェアアーキテクチャにその存在意義はあるでしょうか? 話が大きくなりますが、生成AIによるソフトウェア開発に即したアーキテクチャとその評価指標、つまり 生成AIを前提としたアーキテクチャ適応度関数 *15 の発明が必要なのだと思います。 生成AI自身もソフトウェアであり、実行にはサーバのリソースや時間、つまり電力を必要とします *16 。 それらは有限かつ希少な資源です。人類が無限のエネルギーを手に入れるにはまだだいぶ掛かるでしょう。 となると、AIの消費エネルギー *17 を節約するアーキテクチャ *18 が求められ、それを測定・評価する指標を元に開発するようになるのではないでしょうか。もしかしたらそれは、人間がいままで考え出してきたソフトウェア設計・プラクティスとそんなに変わらないのかも知れません *19 。 加えて、AIへのフィードバックには網羅的、高速かつ自動的な検査が必要になります。2025年現在、一般的な開発組織ではアーキテクチャ適応度関数が十分に浸透しているとは言いがたい状況かもしれません。当然その自動化も発展の余地があります。本記事はアプリケーションアーキテクチャの検証が主題でしたが、生成AIによるソフトウェア開発がより高いレイヤに組み込まれるにつれて、それに合わせたより抽象度の高い適応度関数を用いていく必要が出てくるかもしれません。 そのようなソフトウェア設計活動は依然としてエキサイティングかつ創造的であり、ソフトウェアエンジニアという職はまだまだ面白いものであり続けるのではないかと期待しています。 さて、いい加減個人ブログに書けと言われかねないためそろそろお決まりの言葉で締めさせてください。キャディ株式会社では製造業AIデータプラットフォームとして成長していくためにエンジニアの採用を加速しております。生成AIが発展していってもソフトウェアエンジニアとしてまだまだやっていきたいという方は、ぜひご連絡ください! recruit.caddi.tech ArchUnitに関する詳細な解説・先行事例 ArchUnitでアーキテクチャの単体テストを行う ArchUnitでアーキテクチャをテストする - mtx2s’s blog ArchUnit をマルチモジュール構成に対して適用する | omuomugin ArchUnit使い方メモ #Java - Qiita *1 : キャディでの Apache Iceberg 活用事例 , Apache IcebergとCDCによるデータレイクハウス拡張 , 氷山を穿つ - Apache Icebergに大量データを投入するTopic *2 : Unit test your Java architecture - ArchUnit *3 : オブジェクト設計スタイルガイド - O'Reilly Japan *4 : ArchUnit User Guide 4. What to Check *5 : ArchUnit User Guide 4.6 Layer Checks *6 : pomやgradleの記述などはリファレンスを参考にしてください。 *7 : ArchUnit User Guide 3.3. Using JUnit 4 or JUnit 5 *8 : ArchUnit User Guide 7.3. Creating Custom Rules *9 : 特別なルールはコメントや背景を付記すると良いでしょう。テストもコードです。 *10 : 実際はArchUnitの導入を先に行い、既存の違反コードは Freezing Arch Rules でマーク、徐々に違反コードを削減していきました。 *11 : SpotBugs *12 : Better Code & Better Software | Ultimate Security and Quality | Sonar *13 : ArchUnit User Guide 8.7. Software Architecture Metrics *14 : Vibe coding - Wikipedia ここでは広義のVibe codingを意図しています。 *15 : ソフトウェアアーキテクチャメトリクス - O'Reilly Japan *16 : 【提言】生成AIの普及が与える日本の電力需要への影響 *17 : DevinならACU *18 : awslabs/mcp: AWS MCP Servers — specialized MCP servers that bring AWS best practices directly to your development workflow *19 : AI-friendly code design | Technology Radar | Thoughtworks
はじめに はじめまして、Drawerグループ所属の もりや です。 キャディは入社して約2年になりますが、ブログ記事を書くのは初めてです。よろしくお願いします。 私は入社時から 製造業データ活用クラウドCADDi Drawer の開発に携わっており、最初のRBACベースの認可を私が中心に実装しました。 その関係から、今はIDチームで認証認可周りの開発を担当してます。 今回は、CADDi DrawerでSSOをサポートしたことについて、主にAuth0の観点で書きます。 おことわり この記事は、Auth0をある程度使ったことがある方向けに書いています。 タイトルに「1年かけて」とありますが、開発着手からリリースまでの期間を指しています。 途中で他の機能開発をしていた期間も含まれており、丸々1年を全て開発に費やしたわけではない点にご留意ください。 CADDi Drawer とは 認証観点で簡単に書くと、CADDi Drawerはマルチテナント構成のSaaSで、認証にはAuth0という認証・認可のプラットフォームを使用しています。 アプリケーション側は、Next.jsと nextjs-auth0 というライブラリを使ってAuth0を利用しています。 1つのAuth0テナントに、複数のCADDi Drawerのテナントが存在する構成になっています。 CADDi Drawerは 多くの大企業でも使用いただいています。 傾向として、ユーザー数が多いテナントほどSSOを求める声は多くあり、今回はその要望に応えるためにSSOを実装しました。 補足として、以前に 独自の認証認可基盤を開発 していたこともありましたが、今のところはAuth0を使い続けています。 本記事の構成 大まかに以下のような構成になっています。 検討: Auth0でSSOを提供するために検討したこと 実装: Auth0を使ってSSOを実装したこと 今後: 作りたい機能や課題など 時系列順には書いていないので、その点もご留意ください。 また、文字数が多くなりそうだったためかなり端折っています。 もっと細かい部分も聞いてみたいと思われた方は、ぜひ カジュアル面談 で話しましょう。 1. 検討 Auth0 での SSO の提供方法(Auth0 Organizations) CADDi Drawerで求められるSSOは、Googleログインのように全ユーザーに提供するものではなく、契約している各企業のIdPを、特定のテナントのみに紐付けてSSOを提供します。 そのため、単にアプリケーション全体で提供するのではなく、テナント単位に設定する必要があります。 調査した結果、こうした場合はAuth0 Organizationsという機能を使うと良いという結論に至りました。 Auth0 Organizations とは? Auth0 Organizationsという機能を利用することで、複数の組織を管理し、組織ごとの設定(SSOなど)が簡単に実現できます。 データ構造としては、Organization(= CADDi Drawerのテナント)という情報が新たに登場し、そこにユーザー情報を紐づけるという形になります。 補足として、これまでは app_metadata を使って所属するテナントを管理していました。 Auth0 Organizationsは、このような独自の管理を使わずに、Auth0だけで組織を管理できます。 Auth0 Organizations を使った場合のトークンの変更 Auth0から発行されるIDトークン、アクセストークンに org_id というクレームが追加されます。 また、設定を変更することで org_name というクレームも付与されます。 これらの情報を使うことで、アプリケーション側でもどのOrganization(テナント)に所属しているユーザーなのかを判断できます。 ログイン体験の決定 Auth0 Organizationsを使うと、Auth0 Applicationの単位でログインの体験を決定できます。 ここはユーザー体験に重要なポイントになるので、PdMやCSチームと相談しながら決定しました。 Type of Users ログイン可能なユーザーの種類です。以下の3つがあります。 Individuals : Auth0 Organizationsを使わない Business Users : Auth0 Organizationsが必須 Both : どちらでも可 Auth0 Organizationsを使う場合は Business Users または Both を選択する必要があります。 CADDi Drawerでは、全てのユーザーは何らかのOrganizationに所属する事を必須にしたいので Business Users を選択しました。 Login Flow ログインフローの選択肢です。以下の3つがあります。 Prompt for Credentials : 最初にユーザーはメールアドレスを入力し、メールドメインに基づいてログイン方法 (Auth0 Connection) を決定しログインする方式 Prompt for Organization : 最初にユーザーがOrganizationを入力し、該当するOrganizationにログインする方式 No Prompt : 呼び出し側のアプリケーションで判断する方式 Prompt for Credentials は一般的によく見られる形式で、既存の体験とも大きく変わらないので一見良い方法に見えました。 しかし、この体験を実現するAuth0のHome Realm Discovery (HRD) がCADDi Drawerとしては採用しづらいものでした。 HRDは、ユーザーが入力したメールアドレスではなく、メールアドレスの「ドメイン」に基づいて、ログイン方法(Auth0 Connection)を判断します。 例えば1つの企業で複数のテナントを契約している場合だと、SSOの提供に支障が発生する可能性があったため、HRDを使う判断はしませんでした。 Prompt for Organization は、ユーザーが最初にOrganizationを入力する必要があるため、ユーザー体験としてはあまり良くありません。 また、特にユーザー数が多いテナントの場合、組織名を周知するのも大変というオペレーションの課題もあります。 よって、こちらも採用しませんでした。 このあたりは色々と検討したのですが、最終的には以下のような結論になりました。 パスワードでログインするユーザーにはこれまで通りの体験を提供する SSOを利用するユーザーには、SSO専用のURLを使ってログインしてもらう Auth0の設定としては Prompt for Credentials を選択しつつ、アプリケーション側で専用URLでない場合はパスワードでのログインを行えるように制御しました。 nextjs-auth0を使ったコードのイメージは以下のとおりです(※実際に稼働しているコードとは異なります) // 参考: https://github.com/auth0/nextjs-auth0/issues/701#issuecomment-1255350171 import { handleAuth, handleLogin } from "@auth0/nextjs-auth0" ; const login = async ( req , res ) => { // 1. クエリパラメーターから Auth0 Organization の情報を取得 const organization: string | undefined = req.query. organization ; // 2. Auth0 Organization が指定されていない場合は、パスワードでのログインを強制 const connection = organization === undefined ? undefined : "Username-Password-Authentication" ; await handleLogin(req, res, { authorizationParams : { organization , connection , } , } ); } ; export default handleAuth( { login } ); このようにすることで、既存の体験の維持と、SSOの提供を両立させることができました。 ユーザーデータの作り方 SSOを提供した場合、単純に作ると同一のメールアドレスで以下の2種類のユーザーが存在可能になります。 パスワードでのログインを行うユーザー(以下「パスワードユーザー」) SSOでログインするユーザー(以下「SSOユーザー」) これは、Auth0のConnectionが異なる場合は、一意性制約が効かないためです。 (Auth0のConnection単位で見れば一意になります) メールアドレスとしては同じユーザーに見えますが、ユーザーIDは異なります。 CADDi Drawerでは、様々なデータをユーザーIDベースで管理しているので、システムとしては別のユーザーに見えます。 これは、個人の設定情報が共有できない、利用状況を把握しづらくなる、など色々と問題になりそうです。 メールアドレスをIDとして管理できるようにすれば解決できそうですが、大きな改修が必要になるため、今回は見送りました。 これも色々検討した結果、以下のような構成に落ち着きました。 全てのユーザーは、パスワードでのログイン(Username-Password-Authentication)をベースに作成する User Account Linking を使って、上記のユーザーにログイン方法だけ追加するような形式にする パスワードでのログインは、Auth0の機能的にOFFにできないので、Auth0 Actionsで制御する こうすることで、SSOを使う場合でもメールアドレスに対して必ず1つのAuth0ユーザーとなりました。 初回のログイン処理時にUser Account Linkingをしたり、ログイン方法をチェックするなど、多少複雑にはなります。 しかし、メールアドレスに対してユーザーが一意になるので、システムとしてはシンプルに扱えるようになりました。 2. 実装 Auth0 Organizations への移行(とドメイン移行) SSOのサポートのために、まずはAuth0 Organizationsへの移行しました。 大きな変更になるので、既存のログインとAuth0 Organizationsを使った新しいログインの両方のどちらも使える並行期間を設け、慎重に行いました。 また、同時にCADDi Drawerのドメイン移行の計画もあったため、SREチームと連携しながらユーザーに影響を与えないように進めました。 既存ユーザーのデータ移行 既存のデータベースにあるテナント情報から、Auth0にOrganizationを作り、所属するユーザーのデータを紐づけていきます。 Organizationに紐づける情報を追加するだけで、既存のユーザーデータには変更が発生しないので、割と気楽にできる作業でした。 移行は、以下のAPIを組み合わせて、全て自動で行うことができます。 全ユーザー情報の取得: https://auth0.com/docs/manage-users/user-migration/bulk-user-exports Organizationの作成: https://auth0.com/docs/api/management/v2/organizations/post-organizations Organizationとユーザーの紐づけ: https://auth0.com/docs/api/management/v2/organizations/post-members 注意点としてAuth0のRate Limitがそれなりに厳しいです(参考: Enterprise のRate Limit)。 本番で稼働しているので、安全に移行できるように、1リクエストごとにスリープ処理を入れ、Rate Limitに引っかからないようにしました。 Next.js で複数の Auth0 Application の対応 まずNext.js側では nextjs-auth0 の設定を2つ用意し、Auth0クライアントのインスタンスを切り替えることで、ログインの並行稼働を実現しました。 具体的には、HTTPの Host ヘッダーに応じてAuth0のドメインとクライアントIDを切り替えることで、新旧どちらのAuth0 Organizationsにも対応できるようにしました。 実際のやり方としては、まずAuth0のApplicationを2つ用意します。 既存のAuth0 Organizationsが「無効」なApplication 新規のAuth0 Organizationsが「有効」なApplication nextjs-auth0 は initAuth0 という関数を使ってAuth0のインスタンスを初期化でき、それぞれのApplicationに対応するAuth0のインスタンスを用意します。 より具体的な実装で言うと、以下のようなイメージでAuth0のインスタンスを切り替えて渡していました(※実際に稼働しているコードとは異なります) import { initAuth0 } from '@auth0/nextjs-auth0' ; export const getAuth0 = ( req ) => { const host = req. headers [ 'Host' ]; if (host === NewDomain) { return initAuth0( /* 新ドメイン用の Auth0 設定 */ ); } return initAuth0( /* 旧ドメイン用の Auth0 設定 */ ); } ; 停止を伴う移行 以下の設定については並行稼働できないので、サービス停止を行って一斉に移行しました。 Auth0のカスタムドメイン Auth0が発行したAccess Token (JWT) の検証につかうJWKsのURL 特に難しい点はなく、移行手順の準備や、Staging環境でのリハーサルを行っていたため、大きな問題なく移行を完了できました。 補足として、先に書いた通りドメイン移行と同時に行っていたので、このタイミングで旧ドメイン→新ドメインへのリダイレクト対応なども行っています。 ここまでで、ユーザーに気づかれることなく、ユーザーデータの構造変更を完了させました。 SSO 機能の実装 ここが本題ですが、実際のところSSO機能そのものはAuth0に任せるので、CADDi Drawer固有の実装が少しあった程度でした。 主に、Auth0 Actionsでの開発になります。 なお、CADDi Drawerのユーザー管理機能などの開発については省略します。 パスワードでのログインが禁止されているかのチェック 「ユーザーデータの作り方」で説明した通り、全てのユーザーはパスワードでのログイン方法を持ちます。 しかし、組織ポリシーでパスワードでのログインを禁止したいケースも発生することが想定されています。 Auth0のユーザーの app_metadata に、パスワードでのログインが禁止されているかどうかのフラグをもたせます。 Auth0 Actionsのコードのイメージは以下のとおりです(※実際に稼働しているコードとは異なります) exports .onExecutePostLogin = async ( event , api ) => { const isDisablePasswordLoginUser = event.user.app_metadata.disablePasswordLogin === true ; const isLoginWithPassword = event. connection .strategy === "auth0" ; if (isDisablePasswordLoginUser && isLoginWithPassword) { // ※注意: api.session.deny だと Auth0 セッションが残り続け、自発的に Cookie を消さない限りログインできなくなる api.session.revoke( "disallow_password_login" ); return ; } } ; SSO の初回ログイン時の User Account Linking 処理 「ユーザーデータの作り方」で説明した通り、全てのユーザーはパスワードでのログイン方法を持ちます。 よって、SSOで初回ログインした後に、User Account Linkingを行い、パスワードユーザーと統合する処理が必要になります。 前提として、ユーザーを招待する時に app_metadata.linkUserTo に統合先のユーザーIDを設定しておきます。 そして、以下のようなAuth0 Actionsを実装し、User Account Linkingを行います(※実際に稼働しているコードとは異なります) exports .onExecutePostLogin = async ( event , api ) => { // User Account Linking が必要か判定 const loginUserId = event.user.user_id; const linkUserTo = event.user.app_metadata?.linkUserTo; if (linkUserTo == null || linkUserTo === loginUserId) { return ; // Skip } const primaryUserId = linkUserTo; const secondaryUserId = loginUserId; const secondaryUserProvider = event.user.identities[ 0 ].provider; // Auth0 Organization が一致するかのチェック const managementApiClient = "(省略)" ; const {data : primaryUserOrganizations } = await managementApiClient.users.getUserOrganizations( { id : primaryUserId } ); const primaryUserOrganizationIds = primaryUserOrganizations. map (( org ) => org. id ); const secondaryUserOrganizationId = event. organization . id ; if (!primaryUserOrganizationIds. includes (secondaryUserOrganizationId)) { api.access.deny( "(Auth0 Organization の不一致エラー)" ); return ; } // User Account Linking の実行 await managementApiClient.users. link ( { id : primaryUserId } , { user_id : secondaryUserId, provider : secondaryUserProvider } , ); // ※下記で解説 api.access.deny( `request_re-login: ${ JSON . stringify ( { organization : event. organization . name , connection : event. connection . name } ) } ` , ); } ポイントは、User Account Linking成功後に api.access.deny でカスタマイズしたエラーを返す部分です。 まず前提として、このログインフローは「SSOユーザー」でログインした状態ですが、終了時点では「パスワードユーザー」に統合され、「SSOユーザー」は存在しなくなります。 なので「パスワードユーザー」に切り替える必要があります。 実は api.authentication.setPrimaryUser() というメソッドも用意されていますが、今回のCADDi Drawerの設定では使えませんでした。 以下を読む限り、Auth0 Organizationsで "Prompt for Credentials" を使用している場合は api.authentication.setPrimaryUser() を利用できないようです。 Error: Organizations is Not Supported Together with Primary User Modifications in Rules - Auth0 Community 苦肉の策として、カスタマイズしたエラーに organization と connection の情報を持たせ、Next.js側でハンドリングして、再度ログインする、という方法を取っています。 補足すると、再度ログインするといってもSSOなので、通常は何度かリダイレクトを挟むだけでユーザーの操作は不要です。そこまで大きな体験の悪化はありません。 データの不整合チェック 今回採用した設定だと、Auth0 Organizationsに所属していないユーザーはログインができなくなります。 万一そういったデータが発生した場合に備え、不整合を検出するスクリプトを作って自動でチェックをできるようにしました。 このスクリプトは、GitHub Actionsで毎日実行するように設定して、何かあれば通知が来るようにしました。 日次なのでリアルタイム性は若干低いものの、これによってデータに問題はないことを毎日確認でき、安心できました。 ちなみに、この記事を書いている時点では、テストデータなどを除いて不整合は発生していません。 3. 今後 ログイン体験の向上 現状では、テナントごとにSSO専用のURLを発行し、それを使用した場合のみSSOでログインできます。 これはログアウト後に再度SSOでログインするすることができないので、ユーザー体験が悪いなどの問題があります。 また、既存のユーザーにSSO専用URLが浸透しづらいというオペレーションの課題もあります。 専用URLなしでもSSOでログインできるように、ログインフローを見直しています。 ユーザー自身でのSSOの設定 現在は、キャディ側でSSOを設定していますが、ユーザー自身でSSOの設定を行えるようにしたいと考えています。 Self-Service Single Sign-On を使えないか検討しています。 おわりに 調査や下準備など含めると長い開発でしたが、ひとまず無事にSSOを提供できてよかったです。 まだまだ課題はありますが、少しずつ改善していきたいと思います。 We are hiring! キャディでは認証認可領域のエンジニアも絶賛募集中です! Senior Software Engineer, Backend - 認証認可 - / キャディ株式会社 recruit.caddi.tech
こんにちは、Data&Analysis部(D&A)です。 D&Aでは週1回、機械学習の勉強会を開催しており、本記事は、勉強会の内容を生成AIを活用して記事にまとめたものです。 ※勉強会内容公開の経緯は こちら ※過去の勉強会は「社内勉強会」タグからもご覧いただけます。 概要 GraphRAGの概要 RAGの概要と課題 GraphRAGの基本的な考え方 ナレッジグラフとは アルゴリズムの概要 標準的なGraphRAG MicrosoftによるGraphRAG GraphRAGの評価 ナレッジグラフの評価 GraphRAGの評価 ナレッジグラフ / GraphRAGの課題 ナレッジグラフの課題 GraphRAGの課題 参考文献 概要 今回の勉強会では、ナレッジグラフ(後述)とRAG(Retrieval-Augmented Generation)を組み合わせた技術であるGraphRAGについて調査しました。 調査の動機は、社内でRAGを用いたソリューションの検討が進められており、さらなるソリューションの創出に向けた一案としてGraphRAGが挙げられていたためです。 本記事では、GraphRAGのユースケース、アルゴリズムの概要、評価方法、そして課題について紹介します。 GraphRAGの概要 RAGの概要と課題 従来のRAGは、質問と意味的に類似する内容(コンテキスト)をベクトル検索によって抽出し、質問とコンテキストをプロンプトとしてLLM(Large Language Model)に入力することで回答を生成します。 しかし、ドメイン知識が関与する質問、特に物事間の関係性が重要な質問に対しては、関連性の低い内容を出力してしまうことがあります。 原因は回答を生成する過程で行う文章のチャンク化の際、ドメイン知識を表す文章が含まれていない可能性があるためとされています。 GraphRAGの基本的な考え方 GraphRAGは、RAGの課題を解決するために、ナレッジグラフを利用します。 質問のドメイン知識と合致する回答をナレッジグラフから出力し、それをコンテキストとすることで、質問により関連した内容を見つけ出せることが期待できます。 実務においては、強みと弱みを補い合うためにGraphRAGを全文検索やRAGと併用することが効果的です。 GraphRAGの基本的な流れは、自然言語である質問をグラフデータベース用のクエリ言語に変換し、データベースに問い合わせることでコンテキストを取得し、質問とコンテキストをLLMに入力して回答を得るというものです。 ナレッジグラフとは ナレッジグラフとは、エンティティ(ノード)同士をリレーション(エッジ)で繋いだグラフの集合のことです。 情報の関係性をグラフという繋がりで表現するため、RAGよりもドメイン知識を加味した回答を出しやすくなります。 例えば、「日本の首都は東京」という関係性は、"日本"と"東京"というエンティティが"首都"というリレーションで繋がったグラフとして表現されます。エンティティにはプロパティ(属性)を持たせることも可能です。 アルゴリズムの概要 GraphRAGのアルゴリズムは、大きく分けて下図の標準的なGraphRAGとMicrosoftによるGraphRAGの2種類があります。 標準的なGraphRAG インデクシング時: テキストなどの非構造化データからLLMを用いてエンティティとリレーションを抽出し、グラフ構造に変換してグラフデータベースに格納します。 検索時: 質問をLLMによってグラフデータベース用のクエリ言語(一般的にはCypher、他にGremlinやSPARQLなど)に変換し、グラフデータベースに問い合わせを行います。 グラフデータベースとしては、 Neo4j や Amazon Neptune などが例として挙げられます。 LangChain などのフレームワークを利用して実装することも可能です。 MicrosoftによるGraphRAG MicrosoftによるGraphRAGは、標準的なGraphRAGとは異なり、検索時にクエリ言語への変換を行いません。 インデクシング時: LLMを用いてテキストからエンティティとリレーションを抽出し、それらに紐づく文章の要約対応するベクトルをデータベースに格納します。 さらに、コミュニティ検出アルゴリズムを用いて内容が類似するエンティティをグループ化(このグループのことをコミュニティと呼ぶ)し、コミュニティ内のエンティティに紐づく文章を要約したコミュニティレポートと呼ばれるものを作成します。 検索時: 広い範囲の話題について概要を知ることをを目的としたグローバル検索(例:LLMについて教えて下さい)と、特定の話題に関して詳しく知ることをを目的としたローカル検索(例:OpenAIが提供しているLLM別に、その特徴と得意・苦手なタスク、活用事例を教えて下さい)の2つの方法質問に答えます。 グローバル検索では、各コミュニティレポートと質問に対する回答と重要度を出力し、重要度の高い回答をLLMのコンテキストにして回答を生成します。 ローカル検索では、エンティティの文章と質問文とのベクトル検索で意味が近いエンティティを抽出し、類似したエンティティに関連する情報をLLMが要約して回答します。 GraphRAGの評価 ナレッジグラフの評価 評価方法の例として以下のようなものがあります [6] 。 GNN(Graph Neural Network)を用いてナレッジグラフをベクトル化し、下流タスク(分類、ベクトル検索など)の精度で評価する。 正解データから重要な単語やエンティティを抽出し、その単語に関する内容がナレッジグラフにどれだけ含まれているかを評価する。 あるエンティティのサブカテゴリになるものがどれだけ含まれているか(=エンティティの多様性)を評価する。 例:人というエンティティに対して、友人、親子などの関連性のあるエンティティがナレッジグラフにどれだけ存在するか。 GraphRAGの評価 GraphRAGの評価方法としては、主に以下の2つがあります。 人手で作成した質問・回答と、GraphRAGが出力する内容との関連性を人手評価する。 LLMの評価手法を利用する(例: ragas 、 LLM as a judge など)。 MicrosoftのGraphRAGでは、LLMによって回答を評価する手法が用いられています。 ナレッジグラフ / GraphRAGの課題 ナレッジグラフの課題 同一内容のエンティティの統合: 表記揺れやデータソースの重複などにより、同一内容のエンティティが複数存在してしまうため、それらを統合する必要があります。 取り組みの例として、何らかのルールに基づいて統合する手法や、エンティティに関する情報を特徴量とし、エンティティの分類タスクを解き、同じ分類結果ならばエンティティを統合するという試みもあります。 情報の正確性の担保: 信頼できる情報源からデータを取得すること以外に、ナレッジグラフの情報の正確性をどのように担保するかが課題となります。 グラフの更新の難しさ: 既存のデータソースに含まれない情報(例:バズワード)、知識の変化(例:製品名が変わった)を既存のグラフとの整合性や既存のグラフ同士の関係性を保ったうえで更新させるのが難しいという課題があります。 GraphRAGの課題 トークン料の増大: エンティティ・リレーション抽出など、様々な処理でLLMを使用するため、トークン料が膨大になる可能性があります。 対策として、 Triplex などのエンティティ・リレーション抽出を効率化するツールも存在します。 その他の課題: 上記のナレッジグラフ自体の課題も、GraphRAGの品質に影響を与えるため、GraphRAGの課題と言えます。特に、同一内容のエンティティの統合については、 MicrosoftのGraphRAGに該当のフラグを設定する機能が計画されています が、本記事執筆時点(2025年4月)では未実装です。 参考文献 [1] GraphRAGをわかりやすく解説 [2] 話題のGraphRAG、その可能性と課題を理解する [3] 話題のGraphRAGとは - 内部構造の解析と実用性の考察 [4] From Local to Global: A Graph RAG Approach to Query-Focused Summarization [5] Welcome to GraphRAG [6] Structural Quality Metrics to Evaluate Knowledge Graphs