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

TECH PLAY

キャディ株式会社

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

237

はじめに こんにちは、2月にSenior Research Engineerとしてキャディに入社した 福原 です。現在、キャディでリサーチ組織を本格的に立ち上げています。 「リサーチ組織」と言っても、単に研究を行なって論文を書くことだけが我々の目的ではありません。キャディが掲げる「モノづくり産業のポテンシャルを解放する」というミッションを実現するために、最先端の技術を研究し、それをプロダクトの価値として実データ上で検証するところまでやり切る — それがこの組織の存在意義です。 この記事では、キャディが取り組んできた、あるいはこれから挑もうとしている技術課題が、Computer Vision(CV)やAIの分野で盛んに議論されている難問といかに深く重なっているかを、最新の研究動向とともに紹介します。 結論を先に述べると、キャディのプロダクトが行なっている「製造業の図面・3DCAD・仕様書といったマルチモーダルなデータを正確に認識・関連付けを行い、横断的に検索・推論をして、ユーザーの意思決定に重要な示唆を出す」というプロセスに含まれる技術的な課題は、Computer VisionとAIの研究コミュニティが直面している重要な課題と強く一致しています。しかも製造業は、曖昧さや「それっぽい答え」が許されない精度と再現性が特に重要な世界です。トップ会議の最先端研究で取り組まれている課題を、現実の厳しい制約のもとで解く — それがCADDiのリサーチ組織が取り組んでいる課題です。 はじめに キャディのリサーチ組織が取り組む研究課題 VLMの言語偏重:最先端モデルすら躓く空間・幾何推論の限界 テクスチャなき純粋幾何:形状だけでなく意味を捉える3D認識 きれいなペアデータは存在しない:不完全なマルチモーダル空間のアラインメント それっぽい3Dはいらない:幾何制約とトポロジーを保証する製造可能な3D生成 その他の研究開発課題 アカデミアとの連携 まとめ 仲間を募集しています! キャディのリサーチ組織が取り組む研究課題(詳細版) VLMの言語偏重 正確な3D幾何形状の認識 マルチモーダルなデータの処理 3D生成 その他の研究開発課題 キャディのリサーチ組織が取り組む研究課題 ここでは、具体的な研究課題を1つずつ取り上げ、それぞれがCVやAIの研究コミュニティでどのように取り組まれているかを、最新の研究動向とともに紹介します。 お知らせ 本章は、CVやAIの分野を専門とされていない方にもその面白さを知っていただけるよう、あえて噛み砕いた表現で解説しています。専門知識をお持ちの方や、より技術的に正確な内容を知りたい方は、ぜひ こちらの詳細版 をご覧ください。 VLMの言語偏重:最先端モデルすら躓く空間・幾何推論の限界 現在のVision-Language Model(VLM)は、言語空間における情報処理に著しく偏っています。その結果、製造業で最も重要な「幾何」や「空間」の認識において、致命的な弱点を抱えています。 純粋な視覚能力を測る「 BabyVision 」ベンチマークの報告によれば、最も性能の良いとされるGemini 3 Proですらスコアは49.7%に留まり、人間の平均的な大人のスコア(94.1%)から大きく乖離し、「人間の3歳児のスキルとほぼ同等」という衝撃的な結果が示されています。また、背景ノイズを取り除いた空間推論問題「 MathSpatial 」でも、人間が95%超で解ける問題に対して多くのモデルが60%に届きません。 製造業のプロダクトにおいて、「この穴は図面のどの位置にあるか」「裏側にどんな形状が隠れているか」といった空間的・幾何的な対応関係を正確に認識できなければ、品質管理や設計支援は成立しません。実際に、建築図面を対象とした最新のベンチマーク「 AECV-Bench 」(2026年1月公開)でも、テキスト中心のQAは高精度な一方で、空間認識などを要するタスクでは0.40〜0.55%程度の精度に留まることが報告されています。 キャディでも 以前のTech Blog で紹介した、製造業に関連したタスクに関する自社ベンチマークを用いた検証で、空間推論に関するタスクの性能の伸び悩みに直面しています。CVPR 2026で提案された、3D再構成タスクと空間推論を統合する「 G2VLM 」のようなアプローチが1つの希望ですが、実用レベルへの昇華はまさにこれからの課題です。 テクスチャなき純粋幾何:形状だけでなく意味を捉える3D認識 製造業で扱う3DCADモデルは、テクスチャ情報を持たない純粋な幾何形状の表現です。B-Rep、Mesh、Point Cloudといった形式で記述される3D形状を正確に認識することは、自然画像のようにテクスチャで補える情報が少ないため、2D画像認識などと比較すると依然として難しいタスクです。 現在、CADデータをLLMに入力するためにPythonコードやトークンに変換する手法(「 BrepCoder 」など)が登場し、幅広いタスクを処理できるようになりつつあります。しかし、この方法ではxyz座標を厳密に指定した推論や、特定の領域に関する高度な質疑応答は依然として困難です。 我々が解かなければならないのは、単なる形状の復元ではありません。「この穴はボルト用である」「この面は別の部品との嵌合面である」といった、形状の「意味(セマンティクス)」の理解です。 CVPR 2025のBest Paperに選出された「 VGGT 」を意味の同時推論にまで拡張させた「 UNITE 」(RGB画像から3D再構成とセマンティクスを同時に予測)や、NeRFの重みをLLMに直接入力して空間的な位置関係を把握するアプローチ( Amaduzzi et al., 2025 )など、3D CVの最前線がまさにこの領域に挑んでいますが、製造業の複雑な3DCADデータでどこまで通用するかは、我々が実証していく領域です。 きれいなペアデータは存在しない:不完全なマルチモーダル空間のアラインメント 実際の製造現場では、きれいなペアデータが存在しないケースも少なくありません。 2Dの図面、3DのCADモデル、そしてテキストで書かれた仕様書や不具合報告書。これら表現形式すら異なるマルチモーダルなデータが、不完全な状態で散らばっています。 私たちが実データ上で実現しなければならないのは、「仕様書のテキスト記述から、CAD上の特定の面や公差の情報をリンクさせる」といったモダリティを跨いだ情報の紐付けや、「3DのMeshデータ、画像、テキストを同じ潜在空間に埋め込む」といった統一的な処理です。 こうした課題に対し、現在の研究コミュニティでは、完全なペアがないことを前提に統一埋め込みへ整合させる「 CrossOver 」や、点群・画像・テキストを統合してCADベンチマークで最高水準の性能を達成した「 cadrille 」などのアプローチが注目されています。しかし、これらを現場で機能させるには、製造業特有のドメイン知識をマルチモーダルなモデルにどう組み込むかという非自明な課題に取り組む必要があります。 それっぽい3Dはいらない:幾何制約とトポロジーを保証する製造可能な3D生成 設計フェーズのアシストにおいて3DCADモデルの生成には大きな需要があります。しかし、現状の生成モデルが陥りがちなのが「見た目はそれっぽいが、製造業の現場では役に立たないデータ」を出力してしまう問題です。 製造業における3Dデータは、以下の条件を満たさなければ価値がありません。 ウォータータイトであること(穴が空いていたり、現実にはあり得ない位相になっていないこと) 面同士の拘束条件が適切であること 後から人間がパラメトリックに微修正できること ただの「それっぽい3D形状」ではなく「製造・編集が可能な3D形状」を生成する必要があります。この難題に対し、B-Repを距離関数として符号化し有効なトポロジーを保証する「 BR-DF 」や、VLMのガイドで複雑なCADプログラムを段階的に生成する「 CADEvolve 」といった最新研究が登場しています。「物理的に実現不可能で編集不可能な形状」からの脱却は、学術界と産業界が協力して解くべき重要なテーマです。 その他の研究開発課題 私たちが視野に入れている技術領域は、CVやNLPの枠に留まりません。認識したデータを基に、現場で価値のある意思決定を支援するため、中長期的には以下のような製造業特有の複雑なデータ構造や物理的制約に向き合う課題にも取り組んでいく計画です。 複雑に絡み合うグラフと因果関係 :製造業のデータは、部品構成(BOM)、製造工程、サプライチェーンなど、本質的に巨大なグラフ構造を持っており、GNNを活用した情報処理が有効です。また、歩留まり改善や不良品の根本原因分析(RCA)においては、「単なる相関」ではなく厳密な「因果推論」が出来ると強力です。「 CusGNN 」のような汎用アーキテクチャや、因果ベイジアンネットワークとナレッジグラフの融合が、現場の泥臭いデータでどこまで機能するのか。ここもまだ開拓の余地だらけです。 重すぎる物理シミュレーションの高速化 :製品設計に不可欠なCAE(構造・熱解析)やCFD(流体解析)といった物理シミュレーションは、計算コストが膨大です。これを代理モデルで数桁レベルで高速化する「 Neural Operator 」や、物理ソルバーを使わずに事前学習をする「 GeoPT 」などの技術は、設計プロセスを根本から覆すポテンシャルを持っています。 テナントを跨げない「機密データ」の壁 :キャディは世界4カ国に拠点を持ちますが、顧客の設計図やサプライヤーの製造データは機密情報であり、一箇所の中央サーバーに集約してモデルを学習させることはできません。データをそれぞれの場所に留めたまま強力なグローバルモデルを学習させる「連合学習」の活用も、モデルの性能の向上のために見逃せない技術です。 アカデミアとの連携 冒頭で論文の執筆は手段であって目的では無いということを書きましたが、だからといってキャディのリサーチ活動を、社内に閉じたものにするつもりはありません。ワークショップの主催やチャレンジの開催を通じて、アカデミアの力を最大限に借りて課題の解決を進めて行きます。 Computer Visionの分野では、産業界とアカデミアがコンペやワークショップの開催などを通して実世界の重要な課題を提示し、コミュニティ全体でそれを解く文化が根付いています。CVPR 2025の Perception for Industrial Robotics Automation (PIRA) ワークショップ ではAlphabet・NVIDIA・Google・Metaなどの企業が中心となって$60,000規模のBin Pickingチャレンジを開催しました。CVPR 2026でもNVIDIAの研究者がオーガナイザーを務める 4D Digital Twins (4DDT) ワークショップ やMicrosoft Researchが長年主導してた Computer Vision in the Wildワークショップ など、企業が研究コミュニティをリードする事例も増えています。 これまで本記事で取り上げてきた課題に対しても、CVPR 2026では多くのワークショップやチャレンジが開催されます。空間知能に関しては 3D-LLM/VLA や Multimodal Spatial Intelligence (MUSI) 、前述のComputer Vision in the Wild内の SITE-Bench challengeが、マルチモーダル認識では Any-to-Any Multimodal Learning (A2A-MML) や Computer Vision for the Built World (CV4AEC) が、3D生成では Generative 3D Reconstruction(GenRec3D) や 3D Geometry Generation for Scientific Computing (3D4S) がそれぞれ開催予定であり、これらの課題がコミュニティ全体で取り組まれていることがわかります。 私自身もICCV 2025で基盤モデルを実産業に移転するために必要なデータ構築・ドメイン適応・評価設計を議論する Foundation Data for Industrial Tech Transfer (FOUND)ワークショップ を主催しました。CVPR 2026においても複数のワークショップのオーガナイズを行なっており( VGIワークショップ 、 BigMACワークショップ )、キャディにおいてもコンペやワークショップの主催などを通じて、製造業AIという研究領域そのものの発展にも貢献していきたいと考えています。 まとめ キャディは日本・米国・ベトナム・タイの4カ国に拠点を持ち、800人を超える規模まで成長してきました。キャディのAI組織は こちらのブログ にも記載があるように紆余曲折を経てきましたが、再結集を果たした今、飛躍の時を迎えています。 ここまで述べてきたように、我々が扱う課題はComputer VisionとAIの最前線と深く交差しています。VLMの空間推論、正確な3D幾何形状の認識、不完全なマルチモーダルデータの統合、制約条件を考慮した3D生成 — いずれもトップ会議で活発に議論されながら、同時に製造業の現場で日々直面している課題です。そして我々には、最先端の研究成果をすぐに試せる実データとプロダクト、価値を届けるべきユーザーがいます。研究と価値検証の距離がこれほど近い環境は、なかなかありません。 ここに書いた課題のどれか1つでもピンときた方、キャディで一緒にモノづくり産業のポテンシャルを解放しましょう! 仲間を募集しています! キャディ株式会社では、本記事で紹介した研究課題をはじめ、AI要素技術の研究開発に全力で取り組み、ミッション「モノづくり産業のポテンシャルを解放する」の実現を目指しています。 この記事を読んで「自分ならこう解決する」「この技術、面白そう」と感じたリサーチャーやエンジニアの方、ぜひご応募お待ちしております! 詳細は以下の採用ページからご覧いただけます! https://speakerdeck.com/caddi_eng/enziniaxiang-kehui-she-shao-jie-zi-liao https://recruit.caddi.tech/29d6e245ed2d80f5bc93ffaa8d144860 弊社主催のイベントも開催していますので、ご興味あればぜひご参加下さい! https://connpass.com/event/385864/ キャディのリサーチ組織が取り組む研究課題(詳細版) VLMの言語偏重 2023年のGPT-4Vの登場以降、視覚と言語を統合するVLM(Vision-Language Model)/ MLLM(Multimodal LLM)は急速な進化を遂げ、その性能は向上し続けています。例えば、大学レベルの多分野マルチモーダル理解を評価する MMMU-Pro ベンチマークではGemini 3 Deep Thinkが81.5%に達し、人間の専門家の上位スコア(85.4%)に迫っています。文書理解の DocVQA では Qwen3-VL-32B が96.9%と人間のスコア(94.36%)を超え、数学的な推論能力を評価する MathVista でもOpenAI o1の時点で既に73.9%と人間の平均(60.3%)を大きく上回っています。 このような性能向上を受けて様々な産業でVLMやMLLMの社会実装が進んでいます。しかし、これらのVLM/MLLMの「賢さ」は言語空間における情報の処理に偏っており、空間や幾何に関するタスクでは最先端のモデルでも依然として低い性能にとどまっていることが明らかになっています。 BabyVision は、言語知識に依存しない純粋な視覚能力を測るために全388問・22サブクラス・4カテゴリのベンチマークを構築しました。結果、最も性能の良いGemini 3 Proですら49.7%と、平均的な大人のスコア(94.1%)から大きく乖離しており、人間の3歳児のスキルとほぼ同等という結果が報告されています。また、 MathSpatial は、背景やテクスチャなどによるノイズを取り除いた空間的な推論問題からなるベンチマークを作成し、人間が95%超で解ける問題に対して多くのMLLMが60%にも届かないことを示しました。他にも、 Chen et al., 2025 は、最先端のVLMであっても「under / behind」のような単純な二物体の空間関係ですら認識が難しいことが指摘されており、その原因がTransformerの注意機構の割り当ての問題にあることを示唆しています。 このように最先端のVLMであっても言語偏重によって幾何や空間に関する性能が限定的であることが報告されていますが、製造業においては幾何や空間的な認識はプロダクトの価値や性能に直結する中心的な能力です。部品の形状、寸法、公差、対応関係 — これらを正確に認識できなければ、VLMによる設計や品質管理の支援も成立しません。キャディでは 以前のTech Blog で紹介したように、製造業に関連した様々なタスクを用いたベンチマークを作成し、VLMも含めて網羅的な評価をしていますが、やはりこの言語偏重が原因と思われる空間・幾何タスクの性能の伸び悩みに直面しています。2026年1月に公開された建築図面を対象とした AECV-Bench でも、OCRやテキスト中心のQAは高い精度を示す一方で、空間認識などを要するタスクでは0.40〜0.55%の程度の精度に留まると報告されており、製造業・建築業といった類似ドメインに共通する課題であることが伺えます。 このギャップを埋めるための研究も進んでいます。CVPR 2026に採択された G2VLM は、VLMの空間知能の弱さの根本原因を「2D画像から3Dを再構成する幾何学習の欠如」と捉え、3D再構成タスクと空間推論を統合する「Geometry-grounded VLM」を提案しています。画像から幾何構造を明示的に学習させることで空間推論の精度を向上させるアプローチであり、我々が直面している課題に対する1つの解決の方向性を示しています。 正確な3D幾何形状の認識 製造業で扱う3DCADモデルは、テクスチャ情報を持たない純粋な幾何形状の表現です。B-Rep、Mesh、Point Cloudといった形式で記述される3D形状を正確に認識することは、自然画像のようにテクスチャで補える情報が少ないため、2D画像認識などと比較すると依然として難しいタスクです。 この問題に対する現在の主流なアプローチの1つは、CADのデータをPythonコードやトークンに変換してからモデルに入力する方法です。 BrepCoder は、3DCADのB-Rep表現(製造業で扱われる標準的な表現)とPythonコード表現(LLMが理解しやすい表現)を共通の空間に埋め込むエンコーダーを学習する手法を提案しました。これにより、標準的な表現であるB-Repのデータをエンコーダーを通すだけでLLMに渡すことが可能になり、形状の復元からCADに関する質疑応答まで、幅広いタスクを1つのモデルで統合的に処理することに成功しました。しかし、この方法ではxyz座標を使用したQAや領域を指定したQAは困難です。 製造業のタスクでは、3D形状の幾何を正確に認識するだけでなく、「この穴はボルト用」「この面は嵌合面」といった形状の意味(セマンティクス)も同時に理解することが求められます。3D Computer Visionの研究はまさにこの方向で進んでいます。 UNITE はCVPR 2025のBest Paperに選出された VGGT を拡張し、RGB画像のみから3D再構成とセマンティクスを統一モデルで同時に予測するアプローチを提案しており、形状と意味の同時認識という目標に直結する研究です。 3D形状に対する言語ベースのQAでは、NeRF(Neural Radiance Field)の重みを直接マルチモーダルLLMに入力するアプローチも注目されています。NeRFの重みは形状と外観を連続的に符号化しているため、点群への離散化では失われてしまう情報も保持したまま3D形状を扱えます。 LLaNA がこのアプローチを切り拓き、後続の Amaduzzi et al., 2025 の研究では、NeRFの重みから空間的に局所化されたトークン列を生成するメタエンコーダを導入することで、グローバルな単一トークンでは捉えられなかった部品間の位置関係や詳細な3D QAを実現しています。 ここで紹介した3D形状とセマンティクスの統合認識は、キャディにとって、図面やCADの幾何情報を正確に読み取り、類似部品の検索や加工方法の推定、見積り判断に繋げるために重要な要素技術です。 マルチモーダルなデータの処理 製造業で扱うデータは本質的にマルチモーダルです。図面(2D)、CAD(3D)、仕様書・不具合報告書・発注履歴(テキスト) — これらを横断的に統合して処理する必要があります。「3DCADから類似した図面を検索する」「図面から対応する3DCADを検索する」「仕様書の記述をCADの面・穴・公差とリンクする」といったモダリティを跨いだユースケースも頻出します。さらに、3D形状の表現1つをとってもMesh、Point Cloud、B-Repなど多様な表現形式を扱う必要があります。 このようなマルチモーダルなデータを統一的に扱うための研究(特に埋め込み空間に関する研究)も盛んに行われています。 ULIP-2 は3D点群・画像・言語の3つ組をトライモーダル事前学習で揃える枠組みを提案し、LLMによる言語記述の自動生成でデータのスケーリングを可能にしています。 CrossOver は、RGB画像、点群、CADモデル、フロアプラン、テキストなどの複数モダリティを、完全なペアがないことを前提に統一埋め込みへ整合させています。全モダリティのデータが揃っていない状況でも動作する点は、応用する上で強力な特徴です。 3DCADの検索や再構成タスクに対してもマルチモーダルなアプローチが検討されています。 cadrille はPoint Cloud、画像、テキストの3つのモダリティを統一的に扱い、強化学習によるファインチューニングで10個のCADベンチマークにおいて最高水準の性能を達成しており、CADデータにおけるマルチモーダル統合の有効性を実証しています。 GenCAD-3D はPoint Cloud、Meshなど異なる表現で表された3Dデータを対照学習で共通の空間に埋め込むことで、アラインメントのとれた強力な潜在空間の学習に成功し、検索と生成の両方で高い性能を達成しています。 3D生成 3DCADモデルの生成については、設計フェーズのアシストやモデル学習のための追加データ生成など製造業においても多くの需要があります。しかし、現状の生成モデルが出力する3D形状はウォータータイト(水密)になっていなかったり、面同士の拘束条件が適切に考慮されていなかったりします。結果として実際には実現不可能な形状が生成されたり、人間が微修正を加えることが困難だったりします。「見た目がそれっぽい3Dデータ」を作れるようになっても、製造業での応用上は形状の実現可能性や編集可能性が満たされていなければ価値が出ません。 MiCADangelo は3Dスキャンデータからパラメトリックで編集可能な3DCADモデルへの復元を、人間の設計プロセスに近い手順で行い、一部の制約の考慮が可能です。 CADKnitter は複数部品の組み合わせを前提に、幾何制約とテキストによる条件付けを満たす「補完パーツ」の生成をするモデルを310k超のデータセットで学習しました。また、ウォータータイトな形状が生成されることを保証するために、 BR-DF はB-RepをSDF/UDFの距離関数として符号化することで、確実に有効なトポロジーを持つB-Repへの変換を実現する表現を提案しました。直近では CADEvolve がVLMガイドの進化的編集パイプラインで単純な形状から産業レベルの複雑なCADプログラムを段階的に生成する方法を提案し、Image2CADの複数ベンチマークで最高水準の性能を達成しています。 その他の研究開発課題 上記に加えて、キャディでは以下のような領域にも研究開発課題があります。 GNN/因果推論 :製造業のデータは、部品構成・工程・サプライヤーなど、本質的にグラフとして扱うべき関係データが多くあり、GNNを活用する研究も盛んに行われています。例えば、 CusGNN はCADアセンブリモデリング向けの推薦システムのためにデータ固有のGNNアーキテクチャを自動設計する枠組みを提案しています。品質管理や根本原因分析(RCA)には因果推論も重要な技術で、 Schwarz et al., 2024 は半導体LED製造の再加工判断に因果推論を適用し2〜3%の歩留まり改善が出来ることを実データで実証していたり、 Wehner et al., 2024 は因果ベイジアンネットワークとナレッジグラフを組み合わせることで製造ラインのRCAを自動化する手法を提案しています。 代理モデルによるシミュレーションの高速化 :CAE(Computer Aided Engineering:構造・熱・振動などの数値シミュレーション)やCFD(Computational Fluid Dynamics:流体シミュレーション)は製造業における製品設計に不可欠ですが、その計算コストは非常に高く、代理モデルによる高速化には大きな需要があります。 Neural Operator はその先駆的な取り組みとして関数空間間の写像を学習し従来シミュレータに対して4〜5桁のスピードアップを達成しており、物理法則を損失関数に組み込む PIIN も製造業への活用が進んでいます。また、直近では GeoPT が、静的な3D幾何データに生成されたダイナミクスを付与する学習手法を提案し、物理ソルバーを一切使わない事前学習で必要なラベル付き物理データを20〜60%削減しつつ、自動車空力から衝突解析まで産業スケールの複数ベンチマークで一貫した改善を報告しています。 連合学習 :キャディは世界4カ国に拠点を持ちますが、国やテナントを跨いでデータやモデルをそのまま共有はできません。連合学習の技術を用いることで、データをそれぞれの場所に留めたまま強力なモデルを学習できます。 Islam et al., 2023 によって連合学習の製造業への応用に関するサーベイでは予知保全・品質管理・生産最適化など5つの領域で特に連合学習が有望であることが報告されています。
こんにちは。Data Platform部に専任QAとしてジョインし、現在QAチームの立ち上げに奮闘しているokanです。 皆さんはQAチームの立ち上げと聞いて、どのような状況を想像しますか? 「テストが全くない無法地帯に秩序をもたらす」「バグだらけのプロダクトを立て直す」…。 私も最初はそんな火消しのようなミッションを想像し、「これまでの経験を活かして、バリバリテストを回すぞ!」と意気込んでいました。 しかし、いざData Platformチームに入ってみると、そこには嬉しい想定外が待っていたのです。 今回は、すでにエンジニアの品質意識が高い組織において、立ち上げ直後のQAがどのように立ち回り、価値を出そうとしているのかという、現在進行形のリアルな試行錯誤をお届けします。 【本記事の主張(お伝えしたいこと)】 優秀なエンジニアが揃う組織において、QAはバグを見つけるテスターやプロセスを守らせる警察になっていけません。属人的な品質担保から脱却し、チームが最速かつ安全にスケールするための仕組みを作るアーキテクト(ギルドマスター)になるべきだ、ということです。 目次 あれ?すでに品質、めっちゃ高くない? 警察ではなくギルドマスターへ。インテリ武闘派QAの誕生 チームの課題を状態異常として可視化する(TCG風まとめ) データ品質はポーション。立ち上げた3つのKR 理想と現実。泥臭い試行錯誤の現在地 おわりに あれ?すでに品質、めっちゃ高くない? チームに入って真っ先に驚いたのは、エンジニア陣が日常的に作成しているドキュメントと、その品質に対する解像度の高さでした。 例えば、インフラのバージョンアップの際、エンジニアから以下のような構成のドキュメントが当たり前のように提出されます。 【提出される検証ドキュメントの構成例】 破壊的変更(Breaking Change)の特定 既存設定(JMXやキャッシュ等)との互換性分析 各レイヤーでのテスト計画(Unit / E2E / パフォーマンス) 万が一問題が起きた場合のフォールバック(Cloud Tasksを用いた再実行スクリプト等の添付) また、QAへ依頼が来る際も、単に「ここを作ったからテストして」ではありません。 変更内容とセットで、想定する影響範囲とリスク、なぜ問題ないと言えるのかの技術的根拠、さらには万が一問題が起きた場合のフォールバック(復旧手順や影響の極小化)までが表形式で言語化されています。それをベースに、stg環境で何をテストすべきかがエンジニア側からすでに提案されている状態だったのです。さらに、エラー発生時のリトライやリカバリ手順も、誰でも実行可能なレベルでスクリプト化・手順化されていました。 これは本当に素晴らしいことですよね。しかし、同時に立ち上げを任された私は強い焦りを感じました。 ここまでエンジニア自身でリスク評価とテスト設計を高いレベルで完結できる組織で、私が単にテスト設計・実行する人になっても、全くバリューを出せないぞ…? エンジニア個々人の高い意識によって品質が担保されている状態は、見方を変えれば属人性が高い状態でもあります。QC(品質管理)の観点から見ると、人の注意力に依存するプロセスは、組織がスケールした際にヒューマンエラーによるばらつきを生みます。 私は自らの役割を従来のいわゆるQAから、専門知識を活かして 人ではなく仕組みで解決するアーキテクト へと再定義する必要がありました。 警察ではなくギルドマスターへ。インテリ武闘派QAの誕生 そこで私は、自らの立ち位置を インテリ武闘派QA と名付けました。 インテリQA(マーケター視点) 市場や顧客の声を分析し、「そのデータは誰が、何の意思決定に使うのか」というデータリネージの最下流(価値)から逆算して、 無駄な開発を防ぐ視点 武闘派QA(技術・伴走視点) シフトレフトを実践し、コードレベルの技術的な会話を通じてチーム全体での品質向上を主導する 品質の伝道師 QAがいないのが当たり前だった組織に、いきなり正論を振りかざして武器を取り上げる警察になっても、現場は引いてしまいます。私が目指したのは、開発者に最強のバフ(品質保証)をかける ギルドマスター としての振る舞いでした。 「この設計で罠にかからない?」と地図(Design Doc)を持ち込んでくれれば 一緒に罠を探し(=アーキテクチャレビューでのエッジケースや非機能要件の抽出) 、「リリースが怖い」と言われれば 自動テストという結界を張る(=CIパイプラインへのE2Eテスト自動実行の組み込み) 。 そんな関係性を築くための第一歩として、お互いのことを知り、課題を共に解決していくことを願い、まずは各チームとざっくばらんに話す会を開催しました。 チームの課題を状態異常として可視化する(TCG風まとめ) 実はこのヒアリングを通して、もう一つ見えてきた組織の課題がありました。それはチーム間の横の繋がりです。 各チームが高度な専門性を持っているからこそ、開発の終盤になって他チームとの認識が噛み合っていないことが発覚するなど、コミュニケーションのすれ違いによる手戻りが発生しており、非常にもったいないと感じる場面があったのです。 お互いの強みを最大限に活かすには、まず部全体が他チームの特性や現在の課題を楽しく知るきっかけが必要だと考えました。 そこで、ヒアリングした内容をただの議事録(テキスト)で共有するのではなく、自身の愛犬をモデルに、QA TCG(トレーディングカードゲーム)風にまとめて各チームに展開したのです。 単なるお遊びではなく、チームの特性を以下の5つのパラメータに分類・言語化するフレームワークとして活用しています。チームの強みと課題を切り分けることで、QAがどこに介入すべきかが明確になります 属性 / クラス: チームの役割(例:Platform, AI Researcherなど) HP / MP: チームのリソース状況や、技術的な尖り具合 特殊能力 (Skill): そのチームならではの強みや特徴 状態異常 (Debuff): 今抱えている課題や悩み(ボトルネック) 召喚コスト (QA Request): QAにしてほしいこと(=QAが支払う工数) 【TCG化例】 この遊び心を取り入れた取り組みにより、単なるテスト実行者ではなく、 チームごとの特性を理解して一緒に状態異常を治してくれる仲間 として認識してもらう良いスタートダッシュが切れたはず…?きっと。 データ品質はポーション。立ち上げた3つのKR チームごとの課題が見えてきた今、ようやく私の持っているソフトウェア品質の専門知識(JSTQB、JCSQE、IT検証技術者など)を解決策として落とし込むフェーズに入っています。 私はデータ品質をRPGの ポーション に例えてチームに浸透させようとしています。ボス戦の最中に飲んだポーションが期限切れだったり、毒だったりすれば全滅してしまいます。 そして、その品質を改善するためには、まずは現状を正しく測定・定義するしかありません。属人化を排除し、誰もが安心して開発できる仕組みを作るため、QAチームのロードマップとして以下の3つのKR(Key Result)を策定し、実行に移し始めました。 KR1: データ品質の共通言語を定義し、設計プロセスへ導入する 「品質とは何か」「どう記録するか」というルール作りです。なんとなくの勘で行われているテスト設計に対し、同値分割や直交表などの専門性を注入しつつ、 スキーマ変更時の影響範囲特定やデータ鮮度(Freshness)の許容値などを盛り込んだTestDocのテンプレート化 を進めています。また、起票ルールやバグ管理のフォーマット統一を行うことで、ステークホルダー間の認識のズレをなくし、属人化の解消を目指しています。   KR2: 部全体が異常にすぐ気づける基準を確立する 「本当にこれで出して大丈夫か?」という疑心暗鬼を払拭するため、定量的な基準を作ります。具体的には、 データのサイレントな不整合を検知した際のリリース判定Gatekeeper基準 や、 Bugfix/Hotfixの優先度を判断するトリアージラインの策定 です。ゆくゆくはSLO/SLAなどの稼働水準策定や、異常検知後のインシデント対応の整備まで踏み込みたいです(自称インシデント大臣として腕の見せ所です!)。 KR3: 変更時の事故を防ぐ開発ガードレールの運用を開始する 不具合の流出を物理的・システム的に防ぐため、 通常開発・リリース承認・Bugfix・Hotfixそれぞれのワークフローを明文化 し、安全な手順を整備しています。また、変更によるデグレを防ぐためのリグレッションテスト項目の作成やテストデータの準備など、実際に手を動かす泥臭いプロセスにも着手しはじめています。 理想と現実。泥臭い試行錯誤の現在地 と、それっぽいことを書いていますが、現実は泥臭い試行錯誤の連続です。Data Platform特有の技術コンテキスト(ETL、分散処理、BigQueryの複雑なクエリなど)のキャッチアップに必死で、最初はエンジニアの書いた高度な影響範囲分析のレビューに全く歯が立たないこともありました。 また、仕組み化を急ぐあまり、ガチガチの承認フローを提案してしまい「それだとリリースの速度が死ぬ」とエンジニアからフィードバックをもらったこともあります(猛省)。 開発のアジリティ(速度)を落としてしまわないよう 、「この変更はリスクが低いからQAレビューをスキップする」といったリスクベースでのエンジニアとの責任分界点を日々探り合っています。 おわりに 品質意識がすでに高い組織における、QAチーム立ち上げのミッション。 それはゼロからテストの文化を作ることではなく、 すでに存在する優れた文化をスケールさせるための、揺るぎないQAプロセスの礎(いしずえ)を築くこと です。 もし皆さんの組織が「エンジニアの品質意識は高いけれど、属人化やリリースの重さに悩んでいる」のであれば、まずはQAが警察ではなくギルドマスターとして現場に入り込み、チームの状態異常を可視化することから始めてみてください。きっと、エンジニアたちは最高の協力者になってくれるはずです。 まだまだ手探りの状態ですが、これからもインテリ武闘派QAとして、Data Platform部という最強のパーティの価値最大化に貢献していきたいと思っています。 ※すでに品質が高い組織を仕組みでさらにブーストさせるという挑戦にワクワクした方、ぜひ一度ざっくばらんにお話ししましょう!
こんにちは、キャディで Quote というアプリケーションを開発している plant こと石田 ( @plant_ja ) です。 Claude Code はあくまでツールであって、使い方によって大きくパフォーマンスが左右されるように感じています。 今回はコンテキストという観点から Claude Code の動作原理を紐解きながら、我々が期待するコードを安定して出力してもらうためのコンテキストマネジメントの理論と実践について思いを馳せてみようと思います。 想定読者 Claude Code を日々使っているが、AI があまり賢くないように感じる人 Claude Code を使い始めたばかりの人 日々の開発で Plan Mode を活用しているが「なんで Plan Mode だとうまくいくんだろう」って思っている人 チームメンバーにコンテキストマネジメントの重要性を説明したい人 想定読者 この記事のゴールと意図 理論 コンテキストって何? Claude Code でのコーディングにおけるコンテキストの重要性 参考 実践 TIPS 一覧 Plan Mode を使おう Plan ファイルに進捗を記載しよう ステータスラインに現在のコンテキスト利用量を表示しよう 結果が気に入らなかったら、rewind で巻き戻して再度指示しよう /context でトークンを何に使っているかを見てみよう CLAUDE.md をスリムにしよう ファイルの指定方法を知ろう /insights コマンドで Claude Code に改善点を教えてもらおう 終わりに リンク集 この記事のゴールと意図 ゴールは、読んでくれているあなたの Claude Code の使い方に少しでも変容を起こすことです。出来るだけ設定が不要で、シュッと実務で使えるということを重要視した構成になっています。 この記事は本のように長いわけではないですが、理論→実践 という章立てになっています。 Claude Code ないし、AIエージェントの進化は凄まじいスピードなので、新機能が出てきた時に自身で良し悪しや使い所をちゃんと評価できるようになるためには、理論を押さえておくことが重要だと考えています。 理論パートはちょっと退屈に感じるかもしれませんが、Claude Code の気持ちを理解するパートとして読んでもらえると嬉しいです。 ▶ 補足: なぜ今更コンテキストマネジメントの記事? 2つの理由があります。 1つ目は、コンテキストマネジメントが他のあらゆるプラクティスの土台になっているからです。Skills や Subagents、MCP、Rules、Hooks など、Claude Code を取り巻く環境には様々なツールがあり、それぞれの使い方や Tips についての記事が多く出ています。しかし、それらがうまくワークするかどうかは、モデルの性能を引き出せているかどうかにも一定依存しています。こうしたツールは LLM の特性上、確率的な挙動になることが多く、この確率をいかにして高く維持するかが Claude Code に安定した挙動をさせるための重要な要素になります。 2つ目は、コンテキストマネジメントについての記事を最近あまり見かけなくなったからです。半年ほど前はコンテキストをどうマネジメントするかについての記事がちょくちょく出ていましたが、最近はそれを理解していることを前提とした記事が多くなっているように感じます。ここ最近 Claude Code を使い始めた人たちにとっては、その前提部分に触れる機会が少ないのではないでしょうか。そのギャップを埋める役割になればと思い、この記事を書きました。 理論 コンテキストって何? よく「コンテキストエンジニアリング」って聞きますよね。まずはコンテキストについての理解を深めていきましょう。 Claude Code は下記のような情報を全てコンテキストとして保持しています。 要素 説明 会話履歴 ユーザーとのやり取りすべて 利用できるツール群 利用できるスキルやサブエージェント、MCP の概要 ツール呼び出し結果 ファイル読み込み・コマンド実行の出力 システムプロンプト Claude Code の役割を明記しているプロンプト こういうやつ LLM には「コンテキストウィンドウ」と呼ばれる処理できるトークン数の上限があります。 この記事を書いている段階での最新モデルである Sonnet 4.6, Opus 4.6 のコンテキストウィンドウは 200,000 トークンです。(尚、β版として 1,000,000 トークンバージョンも提供されています) LLM の根幹として Self-Attention という機構があります。これは、入力として受け取ったトークン群(= コンテキスト)の中のトークン同士の関連性を計算するもので、この仕組みがあるからこそ Claude Code (ないし LLM) は、多少曖昧な指示でも文脈を踏まえてレスポンスを返してくれています。 ここで重要なのは、LLM にはモダンなプログラミング言語のようなガベージコレクションが存在しないことです。つまり、話題が切り替わった時に「不要になった」と判断してコンテキストを空けることができないのです。 前述の Self-Attention の仕組みは、トークン量を n とした時に計算量が O(n^2) で増加するため、コンテキストが大きくなるとレスポンスも遅くなります。しかし、それ以上に重要なのは品質面への影響です。トークンが増えるほど各トークンへの注意(attention)が分散し、本当に重要な情報を正しく参照する精度が下がっていきます。 このような性質から、コンテキストウィンドウ内のトークン量が増えるにつれ、モデルのパフォーマンスは劣化していく傾向があります。また、推論が必要なタスクについては、その傾向がより顕著になるとされています。さらに、関連しそうだけど間違っている情報に影響を受けやすくなるという報告もあります。 上記は Context Rot というタイトルが付けられたレポート (2025/07) の結果に基づいたものです。Opus 4 や Sonnet 4 での実験結果になっていますが、Opus 4.6 や Sonnet 4.6 も Transformer アーキテクチャなので、多少劣化しにくいとはいえ同じ性質を持つと考えられます。 LLM は毎回同じ結果を返すわけではないので「パフォーマンスが劣化する」というのは、「人間が与えたタスクを成功する確率が下がっていく」と解釈することができます。成功する時もあるけど、成功確率としては下がっていくということです。 尚、一般的な用語ではないのですが、一部の界隈で目安としてコンテキストウィンドウの ~40% までを Smart zone、それ以降を Dumb zone と呼んでいて、わかりやすくて僕は好きです。以降の説明でも使います。 ▶ 補足: Smart zone / Dumb zone の出典について Smart zone / Dumb zone という表現は、 Ralph Wiggum Loop の産みの親である Geoffrey Huntley が ai that works というビデオポッドキャスト に出演した際に、Dex という開発者が話していたものです。 AIエージェントを使った開発は確率と常に隣り合わせです。 AIエージェントの犯した過ちを「AI の性能がもっと良くなれば解決する」と考えるのではなく、「こういう状況下でも過ちを犯すのか」と自分の感覚値にフィードバックした上で、どのようなアプローチでその確率値を変えていけるかということを考えることが大事だと思っています。 Claude Code でのコーディングにおけるコンテキストの重要性 この性質を基に Claude Code でのコーディングを考えてみましょう。 Claude Code の “コーディング能力” を ドキュメントや調査結果から得られた「求められているコードの書き方」に沿ってコードを書く能力 と定義づけたとして、次のことが言えそうです。 一回のセッションが長くなるほど、コーディング能力が落ちていく可能性がある かつ、一回のセッションの中に複数種別の指示があると、それがより顕著になる 参照するドキュメントを増やしても、必ずしもコーディング能力が上がるとは限らない むしろ、タスクに関係ないドキュメントの読み込みは、コンテキストウィンドウを圧迫するのでコーディング能力を下げることに繋がる また、コーディング能力だけでなく、決められたルールに沿って開発を進める能力も同様に劣化していく傾向があります。 このような劣化は Opus のような推論能力の高いモデルだと相対的に起きにくいので、「Sonnet は変なコードを書くけど、Opus はちゃんとしたコードを書く!」という感覚につながるのだと思います。 では、この性質を基に理想的な Claude Code の使い方を考えてみるとどうなるでしょうか? 1つのセッションでは1つのタスクに集中させることで、モデルの性能が発揮しやすい ~40% (Smart zone) のコンテキストでタスクを終わらせる もしくは1つのタスクを小さくする タスク遂行に最小限必要な情報 ”だけ” を与える 指示を明確にすることで、できるだけ推論しないで作業できるようにしてパフォーマンスの劣化を受けにくくする 「Plan Mode を使う」というのが1つのプラクティスになっているのは、特に意識せずに使っても上記のポイントを押さえやすくなるからだと考えています。 Plan Mode 無しでの開発プロセスだと、色々な調査を行った上で実装に入ったり、途中で Claude に軌道修正させたりすることになるので、Smart zone 内で実装を完了させることが難しくなります。(トークン量はざっくりとしたイメージです) しかし、Plan Mode を使うと、実装プランを立てるセッションと実装するセッションを分けることができます。新しい綺麗なコンテキストウィンドウで、具体的な実装計画を基に実装することでモデルの一番性能の良い部分を実装フェーズに充てることができるようになります。 独立したコンテキストウィンドウを持つサブエージェントを活用することも、タスクの細分化ないし Smart zone の活用と考えることができますね。 例えば、ちょっと前のバージョンから、Claude Code が調査を行う時に自動的に下記のように Explore サブエージェントが調査を行うようになりました。 ⏺ Explore(XXX の調査) ⎿ Done (24 tool uses · 48.2k tokens · 1m 29s) これは、コードベースの調査というトークンを消費するタスクを別のコンテキストウィンドウに押し込むことによって、「調査の結果」だけをメインのセッションで得ることができて、性能劣化を回避しつつ後続のタスクを実施できるんだな、と理解することができるわけです。 参考 少し前の記事ですが、もう少し掘り下げて知りたい人は下記の記事がおすすめです。 Effective context engineering for AI agents \ Anthropic 実践 さて、やっと実践パートです。ここからは Claude Code などのツールの変化によって変わりやすい部分ではありますが、2026/03 時点でのプラクティスとして記載しておきます。 ここでは「すぐに実務で使える」ことを優先するために、設定などが不要な TIPS を紹介します。使い慣れている人にとっては当たり前のことしか書いていないかもしれないですが、ご了承ください。 TIPS 一覧 Plan Mode を使おう Plan ファイルに進捗を記載しよう ステータスラインに現在のコンテキスト利用量を表示しよう 結果が気に入らなかったら、rewind で巻き戻して再度指示しよう /context で何にトークンを使っているかを見てみよう CLAUDE.md をスリムにしよう ファイルの指定方法を知ろう /insights コマンドで Claude Code に改善点を教えてもらおう Plan Mode を使おう Claude Code を立ち上げて、 Shift+Tab を2回押すと Plan Mode になります。 /plan でも動きます。あとは適当に指示をすると、Claude Code が自律的に影響範囲を調査したり、ユーザーに質問したりした上で実装計画を作成してくれます。 また、 clear context (N% used) and auto-accept edits を実行すると、まっさらな綺麗なコンテキストで “必要最小限の” 実装計画に基づいて作業を進めてくれます。コンテキストの重要性を理解していると、これがどれほど有効かが実感できるかと思います。また、コンテキストをクリアしない 2. の auto-accept edits が、性能劣化したモデルに実装を任せることになりかねないことにも気づけるのではないでしょうか。 Plan ファイルの出力先は設定で変えられたりするので、.gitignore の設定に追加してレポジトリ内に置いておいてアクセスしやすくするのもおすすめです。 Plan ファイルに進捗を記載しよう Plan Mode を使っても実装量が減るわけではないので、まっさらな綺麗なコンテキストで実装を開始したとしてもコンテキストウィンドウの30~40%を超えてしまうことがあると思います。そんな時は、Plan Mode で生成した Plan ファイルに進捗を記載することで、コンテキストをクリアしつつ別コンテキストから作業を引き継げるようにしてみましょう。 下記のように指示すれば、Plan ファイルを更新してくれます。 ❯ 実装のキリが良いタイミングで Plan ファイルの実装進捗を更新してください。Plan ファイルは `Bash(ls -t -1 ~/.claude/plans/*.md | head -n 1)` で取得できます。 (Claude Code 2.1.50 だと、Plan Mode で実装を開始しても該当の Plan ファイルの存在は認知していないような挙動だったため、ここでは path を明示的に指定しています) Plan ファイルの更新が完了したら、別の Claude Code セッションを立ち上げて下記のように指示すると Plan ファイルを読み込んで続きから作業してくれます。 > Plan ファイルを読み込んで、作業計画と作業進捗を把握してください。`Bash(ls -t -1 ~/.claude/plans/*.md | head -n 1)` を実行して、一番直近の Plan ファイルを対象とすること。 こういった指示を毎回コピペするのが面倒になってきたら、Skills とかにちょろっと登録しておくと /resume-plan とか任意のコマンドとして呼び出せるようになります。それも Claude Code に 下記の指示を毎回打つのがめんどくさいので、Skills にして。(以下、コマンド) と指示すれば ok です。 ▶ 補足: コンテキストウィンドウが埋まってきたら自動的に走る auto-compact だとダメなの? compact はセッションの内容を要約して、新しいセッションに引き継ぐ機能です。 評価は人によって違うと思うのですが、自分は下記の理由から compact, auto-compact をあまり好んでいません。 auto-compact については、そもそも 95% になってから実行されるので実行タイミングとして遅い (めちゃくちゃ性能劣化した状態の LLM に作業進捗を要約させるの、怖くないですか?) compact というコンセプトはそもそもセッションの記憶(= コンテキスト)に引き継ぐ仕組みなので、複数のセッションからの write ができなかったりツールとの相性が悪い セッションではなくファイルに永続化して、複数のセッションからアクセスしやすくしたい 作業の計画・進捗はエージェントや我々にとって重要な資産です。これを揮発しやすい “コンテキスト” という概念として管理するのは勿体無いと感じているため、外部ファイルに何かしらの形として書き出すのが 2026-03 時点での自分の好みです。compact 先として外部ファイルを指定できるようになれば問題ないのかもしれませんが。 尚、この記事とは少し趣旨が違うため詳細は省きますが、自分は日々の開発では Plan Mode を使わずに自前で定義したフォーマットで計画・進捗を管理しています。 ステータスラインに現在のコンテキスト利用量を表示しよう ~/.claude/settings.json に下記を追加することで、Claude Code を立ち上げている最中に常にコンテキスト利用量が見えるようになります。(jq がインストールされている必要があります) "statusLine": { "type": "command", "command": "jq -r '(.model.display_name // \"Claude\") + \" | Context: \" + (.context_window.used_percentage // 0 | tostring) + \"% used\"'" } これで、ぱっと見でコンテキストウィンドウの何割を使っているかが分かるようになります。 /statusline コマンドからも色々設定が行えるので、興味がある人は試してみてください。 結果が気に入らなかったら、rewind で巻き戻して再度指示しよう 指示をした時に、思う通りに Claude Code が動いてくれないことがあると思います。 そんな時は rewind という機能でやり取りを無かったことにして、再度指示を出しましょう。Esc を2回押下 (もしくは /rewind ) すると、過去のやり取りが表示され、選択すると巻き戻しの選択肢が表示されます。 Restore conversation を実行すると、コンテキストウィンドウ内のトークンが巻き戻されます。修正指示を積み重ねると、モデルの性能が落ちてしまうので、クリーンな状態からやり直せるのは想像以上に便利です。 Restore code を実行すると、コードの修正も巻き戻せます。 また、 Summarize from here をすると、要約した情報だけを持ち帰ることもできる ( 参考 ) ので、色々活用してみてください。 /context でトークンを何に使っているかを見てみよう コンテキストウィンドウの使用量が見えるようになると、何にトークンを使っているのかが気になるようになってきます。そんな時は /context を実行すると、下図のように利用割合が分かります。MCP ツールなどは予想以上にトークンを使っていたりするので、そういったことに気づくことができます。 CLAUDE.md をスリムにしよう CLAUDE.md は、あくまでインデックスとして扱いましょう。ありとあらゆるルールや実装方法を詰め込むと、コンテキストウィンドウの逼迫による性能劣化や、CLAUDE.md の内容が無視されるといった挙動につながることがあります。 輪郭と地図があれば、Claude Code はタスクの内容に応じて自律的にコードベースを探索することができます。(このような段階的な探索、情報収集は Progressive disclosure と呼ばれています) 参考: Writing a good CLAUDE.md | HumanLayer Blog 効果的なCLAUDE.mdの書き方 ファイルの指定方法を知ろう Progressive disclosure 的な話の派生系としては、@を使ったファイル指定と、そうでないファイル指定などもうまく使い分けることをおすすめします。 観点 @ あり @ なし ファイル読み込みタイミング メッセージ送信時(即時) Claude が判断して Read ツール実行 確実性 必ずコンテキストに含まれる Claude が読まないこともある コンテキスト消費 常にトークンを消費 必要な場合のみ ユーザーの制御 明示的 Claude に委ねる @ でのファイル指定は、指定したファイルが即座にコンテキストに展開されます。しかし、私はあまり使いません。Claude Code が自律的に見にいくことで、必要なタイミングで必要な情報を取りに行かせることができますし、メインセッションのコンテキストを消費せず、サブエージェントの中で参照してもらえば良いケースもあるからです。 /insights コマンドで Claude Code に改善点を教えてもらおう /insights コマンドを打つと、過去のセッション情報に基づいて小綺麗なレポートが生成されます。面白いのでぜひ試してみてください。 終わりに この記事では、コンテキストエンジニアリングについての理論と実践を紹介しました。今回はあくまで「コンテキストエンジニアリング」についての記事でしたが、これ以外にも Claude Code の活用方法は様々です。もし、これを読んで興味が湧いたら、Skills/Subagents/Rules/Hooks などのキーワードで色々と調べてみてください。 手前味噌ですが 図で考える AI コーディングの最適化 - CADDi Tech Blog にも概念チックなことを色々書いているので、もし興味があればそちらも読んでみていただけると嬉しいです。 また、キャディでは一緒に働く仲間を大募集中なので、興味があれば下記もぜひご覧ください。 speakerdeck.com リンク集 cchistory - Claude Code Version History Context Rot: How Increasing Input Tokens Impacts LLM Performance | Chroma Research Effective context engineering for AI agents \ Anthropic Writing a good CLAUDE.md | HumanLayer Blog 効果的なCLAUDE.mdの書き方 図で考える AI コーディングの最適化 - CADDi Tech Blog
こんにちは!
 キャディ株式会社のCADDi Quote開発チームでQAエンジニアをしている naco です✌️ 2026年1月15日にオンラインイベント「 【AI時代の開発戦略】開発スピードと品質の両立に向けて ー 3社エンジニアの事例から学ぶ 」が開催されました。 そこで登壇の機会をいただきました。やったー!
 本記事では、その際にお話しした「QAがGeminiで分身して、受け入れ基準(AC)レビューを自動化し、開発スピードに追いつくための取り組み」をブログ向けに整理してまとめまーす。 当日のスライドはこちら👇 🍥はじめに:この記事でいう「ACレビュー」の定義を明確にします ここでのACレビューは、受け入れ基準(Acceptance Criteria)を書き出して合意するレビュー活動を指しています。適用範囲は「ストーリー着手前に、ACを明文化する場面」です。(スライドP.4) この段階で期待値が揃っていると、手戻りが減りテストも作りやすくなります。一方で、ここが曖昧なままだと、後工程にズレが持ち越されやすくなります。 🍥背景:開発は速くなっているのに、リリースが速くならない わたしの所属するチームでは、開発生産性向上のためにAI活用やSREによる運用改善などに取り組んでいました。つまり「作る速度」も「守る仕組み」もつよつよにしにいってる状態です。 QAもその中で、開発スピード向上の一環としてACレビューを実施していました。取り組みの狙いはシンプルで、着手前に期待値を揃え、後工程の手戻りを減らすことです。 でも実際には、ACレビュー自体は価値がある一方で、想像以上に工数が大きくなりました。増えていたのは、チェックそのものというより、レビューに必要な情報を集めて理解する時間でした。(スライドP.10) 結果として、ACレビューの工数増大により作業が詰まると、実装着手が遅れたり、ACレビューが間に合わず認識ズレが後ろに持ち越されたりして、遅れ方がもりもり増えてしまいます。 それにより、QAが本来投資したい品質基盤やプロセス改善の時間が取れず、「やることは増えてるのに改善が進まない」状態になっていました。 🍥課題の絞り込み:投資対効果が出やすいところから着手する QA業務の効率化を狙うにあたり、リターンが大きくコストが低いソリューションに限定して、短期で成果を出すことにフォーカスしました。(スライドP.11) ここを攻略できると、時間が生まれ、他の改善にも手が出せると考えました。 そこで今回選んだのが、「AIによるACレビュー自動化」です。ポイントは「AIが全部やる」ではなく、「QAの分身🥷」として働いてもらうことでした。人の判断は残しつつ、下準備をAIに寄せる狙いです。 🍥Key Success Factor:AIを分身として仲間にするための成立条件🥷 AIをQAの分身として活用するには、次の3点が必要だと整理しました。 QAの考えを型にする(判断の一貫性を担保する) 正しいデータと文脈を渡す(QA - AI間の前提ズレによる手戻りを減らす) 人とAIの役割分担を明確にする(過信・軽視の両方を防ぐ) 逆にここが曖昧だと、AIの指摘が揺れたり、誤った前提で筋のよいことを言ってしまったりして、運用として成立しにくくなります。 🍥ACレビュー自動化:具体的にやったこと ここでは、KSFを実装に落とすためにやったことを3つに分けて紹介します。 ①レビュー観点の型化(QAの判断を、AIに渡せる形へ) まず、ACレビュー観点を「型」にしました。イメージとしては、QAの頭の中にある判断基準を、AIが参照できる形に落とす取り組みです。 具体的には、普段の判断をMarkdownのプロンプト観点図にし、観点図はPlantUMLファイルとして管理しました。PlantUMLにした理由は次の通りです。 AIが構造として読み取りやすい 人も図としてすぐプレビューできる テキストなので差分管理でき、観点を資産として育てやすい(AIにも人にも同じソースを見せられる) マインドマップがMermaidより見やすいと感じた 狙いは、判断の一貫性と属人化の低減です。 ②文脈・データの接続(前提ズレを減らす) AIは前提が1つズレるだけで、それっぽいが外しているレビューになりやすいです。そうなると、人間 - AI間のレビューの手戻りが増えます。 そこで、AIが参照すべき情報にあたりに行ける状態を整えました。少なくとも、仕様・設計・実装にアクセスできないとレビューが成立しにくいからです。入力の品質を上げ、参照できる情報を増やすほど、出力の品質が安定します。 ③人が最終チェックする運用(過信も軽視も避ける) AIは下書き・提案を行い、最終判断は人が行う運用にしました。 役割分担を曖昧にすると、次の2つが起きやすいと考えています。 「AIがOKと言ったからOK」という過信 「AIの指摘だから軽く扱う」という軽視 どちらも品質の観点では危険です。 そこで、AIが一次レビュー → 人が妥当性を見て採否判断 → その結果を改善に回すというサイクルで回しています。 🍥Before / After:QAの業務範囲を絞る スライドでは、Before/Afterとして業務範囲(青枠)の変化を示しています。変化の本質は、QAが特に時間を使っていた 「情報収集」と「理解のための下準備」 をAIに寄せられた点です。(スライドP.16) これにより、ACレビューの「追いつかなさ」や「ばらつき」は解消方向に進みました。QAの役割も「情報を探す人」から「判断する人」へ寄せやすくなりました。 🍥これからの取り組み:短期ROIの維持と、中長期リターンへの再投資 ACレビューはROIが高いので、低コストで継続していく予定です。
そのうえで今後は、中長期的なリターンが大きい「プロセス改善」に取り組みます。単発の改善ではなく、定義して、運用して、改善が回る状態を目指します。 また、QAチームのAI活用はACレビューに留まらず、テストケース作成の半自動化や、不具合分析にも広がりつつあります。こちらも、より使いやすい形に進化させていきたいです。 🍥まとめ 開発スピードが上がっても、リリースが速くならないことがあります。 課題の一つが、上流工程であるACのレビューが追いつかないことでした。 対策として、QAがAIを使って分身し、ACレビューの下準備を自動化して開発スピードに追いつくアプローチを取りました。 KSFは、観点の型化/文脈データの接続/人とAIの役割分担の明確化の3点です。 まだ試行錯誤中ですが、現状は「AIで置き換える」より「AIに任せるところを決めて、運用として回す」のが使いやすいと感じています。 おしまい!
こんにちは、キャディで機械学習エンジニアをしている由川です。東京の大手町に最近オープンしたサウナ施設に行き、すごい洒落てんな〜と思いつつ十分にリラックスもできました。休息も大切です。 さて本題に戻ると、私は以下を目的としてLLM *1 に関する評価ベンチマークづくりに取り組んでいます。 製造業の課題を解決するためにはどのようなLLMを選べばよいか判断しやすくする キャディが持つ製造業の様々なデータ(図面画像、3Dモデル、仕様書など)に対してFine-tuningなどの手段を適用することで、製造業に関する様々な課題を解ける汎用的なLLMを開発する 以前、取り組みの全体像を 製造業特化LLMを開発するための評価ベンチマーク にて紹介しました。 本記事では、空間把握と呼んでいるベンチマークタスクを通して取り組みの具体例をお伝えしたいと思います。 ドメイン特化LLMを評価するベンチマークを作ろうと考えている方の参考になれば幸いです。 空間把握とは 2D図面から3DCADへの形状復元 評価方法・評価指標 評価方法 評価指標 まとめ 最後に 空間把握とは 空間把握とは以下の表にあるタスクのことです。 熟練した設計者や技術者のように、製造業について十分に理解している人間が図面を理解するプロセスの一つとして定義しています。このプロセスをLLMでも実現できるか評価することを意識してタスク設計を行っています。 タスクに関する詳細に興味があれば、 製造業特化LLMを開発するための評価ベンチマーク#ベンチマークタスクの定義 を参照いただければ思います。 人間が図面を理解するプロセス プロセスの概要 LLMが解くタスクの例 空間把握 図面の中に書かれている立体がどのような形か把握する 例:「この図面に書かれているのはL字型の板金」 2D図面から3DCADへの形状復元 以降、LLMが解くタスクである2D図面から3DCADへの形状復元について、LLMを用いた具体的な形状復元方法と、その評価方法を紹介します。 2D図面から3DCADへの形状復元 タスクの全体像は以下の画像のとおりです。 2D図面から3DCADへの形状復元の概要。図面はお客様の図面ではなく、サンプルです。 以下を意図してこのタスクを設定しました。 LLMには、図面とCADという製造業で取り扱うデータに対する空間把握能力があるかどうか明らかにしたい お客様への価値提供につながるユースケースの創出ができるか明らかにしたい このタスクでは、LLMを使ってどのように2D図面から3次元点群を作るかが重要です。 まずはすぐに思い浮かんだ、2D図面からxyz座標(≒3次元点群)で構成される点をLLMに直接出力させることを検討しました。 しかし、LLMのトークン数の上限により、十分な数の点を生成できませんでした。 点の数が少ない(≒点の密度が低い)と3DCADから作った正解点群との誤差が必要以上に大きくなり適切な評価ができないため、この方針を断念することにしました。 私は今回の取り組みまで、3Dデータを扱った経験がありませんでした。当然、LLMを使って3Dデータを生成する方法も全く知らなかったため、論文調査を通して世の中ではどのようにやっているのか調べることにしました。 調査した結果、メッシュ *2 に必要な頂点と面をLLMで生成し、頂点と面からメッシュを生成するというアプローチ *3 があるとわかりました。これを参考に以下画像のロジックを適用しました。その結果、正解点群と適切に比較・評価できるだけの、十分な数の点を生成できるようになりました。 余談:LLM(厳密にはVLM)は、物体の空間的な位置関係(前後、上下左右など)を把握するのが苦手という報告 *4 があります。 3DCADへの形状復元には空間把握能力が不可欠ですが、現時点ではLLMにはハードルの高いタスクだと言えます。そこで、空間把握能力を補い3DCADを生成する例として、以下のようなアプローチが提案されています。 LLMによる中間生成物を経由して3DCADを生成。 上記のように頂点と辺からメッシュを作る方法や、 CadQuery という3DCADを作るためのPythonライブラリのコードから作る方法 *5 があります。 なお、CadQueryから3DCADを作る方法は不採用にしました。 理由:本来の目的であるLLMの空間把握能力の評価ができなくなってしまうと考えたからです。この方法では、LLMが立体を正しく理解できているかではなく、CadQuery特有のコードをどれだけ知っているかという、ライブラリの知識を評価することになってしまいます。 3DCAD生成APIを外部ツールとして呼び出せるAIエージェントを構築する *6 。 評価方法・評価指標 LLMに空間把握能力があるかどうか知るためには、適切な評価方法と評価指標が必要です。ここでは、2D図面から3DCADへの形状復元において、どのような評価方法と評価指標を適用しているか紹介します。 評価方法 以下の通り評価を行っています。位置合わせはICP(Iterative Closest Point)という点群の位置合わせで代表的な方法や、正解点群と予測点群で原点の位置を揃えるといった方法を組み合わせています。 予測点群に対して正解点群と位置を合わせる理由は、後述する評価指標を形状の違いのみに起因する値とするためです。 位置を合わせずに評価すると、正解点群との形状の違いによる誤差と位置が違うことによる誤差の区別できなくなってしまいます。 評価指標 以下表に書いた指標を採用しています。これらの指標は形状復元に関する論文で採用されることがあります。 評価指標 概要 メリット デメリット Wasserstein距離 ・正解点群(CADから作った点群)と予測点群(LLMが作った点群)との距離 ・値が小さいほど復元精度が高い ・(F-scoreと比較すると)しきい値に依存せずに形状の違いを把握できる 0〜1の範囲で表現できないため、距離の大小がどんな意味を持つのかわかりにくい F-score 以下のPrecisionとRecallの調和平均。 値が大きいほど復元精度が高い ・Precision:正解点群と距離 以内にある予測点群の数 / 予測点群の数 ・Recall:正解点群と距離 以内にある予測点群の数 / 正解点群の数 ・しきい値 以内の点だけ正解と評価するので、指定した許容誤差で復元したいといった活用ができる ・0〜1の間で表現できるので、指標の良し悪しがわかりやすい しきい値 を決めるのが難しい 形状復元のタスクでは、Chamfer距離という指標が用いられることも多いですが、不採用としています。 理由は、Chamfer距離では正解点と予測点群の距離の平均をもとに計算しているため、点の分布のばらつきが大きい場合に距離が必要以上に大きくなるためです。そのため、Chamfer距離と比べて分布のばらつきを吸収できる *7 Wasserstein距離を採用しています。 まとめ 本記事では、空間把握というタスクを例に製造業特化LLMの開発に向けたベンチマーク作成のプロセスを紹介しました。 社内で広く使われるベンチマークを目的として、論文や画像処理タスクなどにより確立された標準的な手法を用いてLLMの能力を評価しています。 空間把握という取り組みを通して、思ったよりも形状復元ができるLLMもあれば、全く復元できないLLMもあるという事実を、評価指標という定量的な結果とLLMが作った点群と3DCADから作った点群を見比べるという定性的な結果で実感することができました。 製造業のLLMベンチマーク作りでは、3DCADや図面画像といった希少性、ドメイン性の高いデータを取り扱います。このようなデータに対して、どのような評価データや評価方法を採用すればよいか絶対の正解はありません。その中で迷いながら、何を評価する/しないのか決める難しさはあります。 しかし、希少性とドメイン性の高いデータに対するLLMの進化と限界を知ることができるので、とても面白い取り組みだと思っています。 最後に ご紹介した取り組みに限らず、我々が機械学習やLLMを使って解決したい製造業の課題は本当にたくさんあります。キャディは人が増えてきているからやり尽くしているんじゃないの?と言われることがあり、入社前は自分もそう思っていました。しかし、入社してみると、やりたいと思っているけどできていないことはたくさんあると実感しました。 本記事をきっかけにキャディとはそもそも何に取り組んでいる会社なのか、機械学習やLLMを使って何を実現したいのか、などご興味あればぜひお気軽にご連絡ください。 speakerdeck.com recruit.caddi.tech open.talentio.com *1 : 文章だけでなく画像も入力しているので、マルチモーダルLLM(MLLM)やVLM(Vision-Language Model)と表記するのが正しいですが、本記事ではわかりやすさを優先してLLMと表記します。 *2 : 3Dモデルを三角形や四角形の集まりで表現したもの。OBJやSTLというファイルフォーマットで表現することが一般的。 *3 : Wang, Zhengyi, et al. "Llama-mesh: Unifying 3d mesh generation with language models." arXiv preprint arXiv:2411.09595 (2024). *4 : Chen, Shiqi, et al. "Why Is Spatial Reasoning Hard for VLMs? An Attention Mechanism Perspective on Focus Areas." Proceedings of the 42nd International Conference on Machine Learning, PMLR 267:9910-9932, 2025. *5 : Rukhovich, Danila, et al. "Cad-recode: Reverse engineering cad code from point clouds." Proceedings of the IEEE/CVF International Conference on Computer Vision. 2025. *6 : Mallis, Dimitrios, et al. "CAD-assistant: tool-augmented vllms as generic cad task solvers." Proceedings of the IEEE/CVF International Conference on Computer Vision. 2025. *7 : Nguyen, Trung, et al. "Point-set distances for learning representations of 3d point clouds." Proceedings of the IEEE/CVF International Conference on Computer Vision. 2021.
Control Plane 部 認証認可グループ(※1)のエンジニアリングマネージャーをしている先山( @ksakiayma134 )です。 現在キャディは、CADDi Drawer と CADDi Quote といった複数のアプリケーションをお客様へ提供する「コンパウンド戦略(マルチプロダクト化)」を推し進めています。 こうした複数アプリケーションを展開するアプリケーションアーキテクチャでは、共通利用する機能をプラットフォームレイヤーとして切り出すことが一般的です。私たちも認証・認可機能をプラットフォーム化し、各アプリ開発者へ提供しています。 本記事では、現在のキャディの認証認可を支えているシステム群についてご紹介します。 Control Plane 部について 一般的な説明 まず、「Control Plane」という言葉について簡単に触れておきます。 一般的に SaaS アーキテクチャにおける Control Plane とは、 AWS のホワイトペーパー 等でも定義されている通り、「テナント(顧客)の管理」と「アプリケーションの機能」を分離した管理層のことを指します。 具体的には、テナントのライフサイクル管理(作成・削除)、認証・認可、課金、メトリクス集計など、すべてのテナントに横断的に適用される運用機能を担うシステム群です。これらをアプリケーション層(Application Plane)から切り出すことで、各アプリチームは純粋な機能開発に集中できるようになります。 認証認可グループの位置づけ 認証認可グループは、現在この Control Plane 部に所属しています。 これは「逆コンウェイの法則」を意識した組織設計です。つまり、システムの「ありたい姿(認証認可が独立したプラットフォームである状態)」を先に定義し、それに合わせて人員配置や組織構造を決定しました。 もともと認証認可グループは、CADDi Drawer の認証認可のみを責務としていました。しかし、コンパウンド戦略を遂行するにあたり、CADDi Drawer と密結合だったシステムを切り離し、独立して運用する必要があります。 すでに切り離しが完了したものもあれば、まだ密結合で疎になっていないものもあり、現在進行系で鋭意分離を進めているフェーズです。 Control Plane を支えるシステムたち 認証プラットフォーム CADDi Drawer や CADDi Quote 等、全アプリの認証を支える共通基盤です。 以前は、すべてのアプリケーションが個別に nextjs-auth0 SDK を組み込み、それぞれが OIDC Client として機能する構成をとっていました。 各アプリがそれぞれOIDC Clientを実装するイメージ この方法でもスケーラビリティは担保できますが、組織が拡大するにつれて以下のような課題が目立ってきました。 変更容易性の低下: 認証認可グループ側で基盤的な修正を入れたい場合、各アプリ側の設定変更やデプロイまで面倒を見なければならない。 コミュニケーションコストの増大: 新規アプリの立ち上げ時、開発者が OIDC/OAuth2 のパラメータ(Callback URL や Scope 等)を深く理解していないと設定が完了せず、都度認証認可グループのサポートが必要になる。 ライブラリ依存のリスク: 例えば nextjs-auth0 v4 のような破壊的な変更が入った際、各アプリチーム側でのバージョンアップ対応負荷が高く、統制が取りづらい。 そこで私たちは、各アプリが個別に OIDC フローをハンドリングするアーキテクチャをやめ、認証フローそのものを中央でコントロールする仕組み(Gateway/Proxy パターン)に切り替えました。 アーキテクチャのイメージとしては、OSS の oauth2-proxy に近いです。 認証 Proxy で実装したイメージ また、この刷新に合わせて、システム内部では Auth0 のトークンではなく、CADDi 独自の Internal Token を利用するよう変更しました。これは、JWT の Claim(情報)を自分たちで柔軟にコントロールするためです。 外部 IDaaS である Auth0 に内部ロジックやデータを依存させると見通しが悪くなるため、Auth0 は「認証」に特化させ、複雑な「認可」情報は Internal Token へ寄せる判断をしました。 M2M Token Issuer ユーザーの操作を介さないシステム間通信(System-to-System Call)や、バッチ処理などのワークロードに向けた仕組みです。 先の認証プラットフォーム刷新により、システム内部の API 認証は Internal Token に統一されました。そのため、ユーザー操作を伴わないワークロードであっても、同様に Internal Token を取得できる手段が必要になります。 そこで、OAuth2 の Client Credentials Flow を用いた、社内専用のトークン発行システム「M2M (Machine to Machine) Token Issuer」を開発しました。 現在はシンプルに client_id / client_secret を各ワークロードに配布して運用していますが、将来的にはこのような手動での鍵配布(アウトオブバンド運用)が不要な方式への移行を展望しています。 例えば OAuth 界隈でも OAuth SPIFFE Client Authentication がドラフトとして存在しているように、ワークロードの Identity をどう証明・管理するかという課題にも、認証認可グループとして積極的に向き合っていく予定です。 Tenant Platform Control Plane の中核である「テナント管理」も、認証認可グループの重要な責務です。 現在、私たちはテナント情報(どの顧客が何のプランを利用しているか、どの機能オプションが有効か等)を Tenant Platform というシステムにて保管しています。 以前これらの情報は CADDi Drawer のデータベース内に格納されていましたが、独立したサービスとして分離し、CADDi Quote 等の他システムからも統一されたインターフェースで参照できる状態にしています。 こうしてデータを中央に集約することで、各アプリ開発チームが個別に似たような管理データを持つ必要がなくなります。 また重要な点として、ここには「マルチテナント SaaS を制御するための管理データ(Metadata)」のみを保存しており、お客様の業務データ(図面データ等)は保存していません。このように責務を明確に分離することで、お客様の保存するデータ領域(Data Plane / Application Plane)との境界線を明確に引くアーキテクチャを実現しています。 顧客のテナント管理UIシステム お客様側の管理者(Administrator)のみが実行可能な操作を提供するフロントエンドアプリケーションです。 ユーザーの招待、権限(ロール)変更、アカウントブロックなどを行える画面等をホスティングしています。 このような管理画面は各アプリチームが独自に開発するのではなく、認証認可グループが提供する UI を利用することとしています。 まだ公開できないシステムたち ここで紹介したもの以外にも、鋭意開発中のシステムや、まだ公開できないプロジェクトが複数動いています。 正直に申し上げますと、システムの分離はまだ完全ではありません。今でもレガシーな構成で残っているシステムは存在します。 切り離しを確実かつ安全に進めていけるような体制やプロジェクトを、着実に組成していく予定です。 また R&D の一環として、Google Zanzibar や昨今の認可技術の潮流(※2)を参考に、社内で利用可能な「汎用的な認可制御システム」の検証なども行っています。 We're hiring! Control Plane 部では、現在バックエンドエンジニア、および認証認可エンジニアを絶賛募集中です! 現時点で OIDC や OAuth の深い知識がなくても、入社後にキャッチアップしていただければ問題ありません。 特定のプロトコルに詳しいことよりも、「スケーラブルなプラットフォームをどう設計・構築するか」という視点や、バックエンドエンジニアとしての汎用的な設計力を重視しています。 また、認証認可エンジニアの方にとっては、「自分たちで OIDC をスクラッチ実装できないから退屈」と思われるかもしれません。しかし、逆に言えば、認証のコアを Auth0 に任せることで、より高度な認可制御やプラットフォーム全体のアーキテクチャ設計にレバレッジを効かせられる環境でもあります。 技術スタックとしては、バックエンドでは Go を積極的に採用しており、サービスメッシュ層での認証制御なども行っています。 SaaS を支える共通基盤(Control Plane)という、攻めがいのある領域に興味がある方、ぜひ一度お話ししましょう。 カジュアル面談はこちらから! open.talentio.com ※1: 社内では IAM (Identity and Access Management) Group という名称で活動しています。 ※2: AWS の Cedar や SpiceDB などが台頭してきている印象を持っています。
こんにちは、Data&Analysis部の安本です。最近私用のPCをミニPCに乗り換えました。省スペースは正義。 さて、私の所属するAI for ApplicationチームではLLMによる図面読解を用いたプロダクト開発に取り組んでいます。その中で 専門領域における評価の曖昧性 という最近よく聞く課題に直面しました。この記事では、その課題と改善方法として「資格取得」という古典的だが非効率そうであまり選ばないhowがどう効いたかをご紹介します。この記事が専門領域の評価に苦しむ方の参考になれば嬉しいです。 なおこの記事で取った方法は開発者自身の知識を変えるものなので、新しい評価方法を期待する方にはnot for meと感じるかと思いますのでご承知おきください。 課題:AI出力の評価プロセスの曖昧性 対策:受動的なインプットから、能動的な構造化へ 成果:解像度の向上と属人性の改善 図面の見え方が変わった 評価プロセスの改善 今後の挑戦 最後に 課題:AI出力の評価プロセスの曖昧性 まず背景として「図面読解」というタスクについてご説明します。図面とは、製品を作るための情報を絵と文字で描いた「製作指示書」です。図面読解は読んで字の如く、図面を読み解くタスクです。 このタスクの面白い点は、読み手によって受け取る情報が異なることです。図面にはあえて全ての製作方法を文章で書き込まず、絵と断片的な情報のみが記載されています。これは、設計・製作に関わるあらゆる立場の人が、自分が必要な情報を一目で確認できるようにするためです。 実際に、下図のような図面を読む際の例を考えてみましょう。 図面から必要な情報を読み取っていくのですが、その情報が読み手の視点によって以下のように異なります。 製作者の視点: 穴のサイズや公差、板厚を読み取り、自社の加工プロセスを検討します。その上で、何を材料とし、どのような手順で加工するかという「工程」を組み立てます。 品質管理者の視点: それぞれの指示内容を、自社の機材でどのように評価・検品するかという「検査手法」を考えます。 このように、図面読解とは 「図面内の断片的な情報を漏れなく集めて行間を埋め、読み手にとって必要なコンテキストを形成すること」 だと言えます。 また、実際の図面は非常に複雑です。大量のラベル情報が網の目のようにつながる「グラフ構造」を持っており、たった一つの情報を見落とすだけで結果が全く異なってしまいます。この極めてシビアな精度が求められる点も、図面読解タスクの大きな特徴です。 LLMを使った図面読解検証も人の目と同様のプロセスでアプローチしています。すなわち、図面から指示に関連する要素を取り出し(画像認識)、その断片的な情報の行間を埋めて理解し(コンテキスト形成・推論)、指示に応じた回答を返す(出力生成)というプロセスです。 ここで評価が問題になります。最終回答の良し悪しは顧客からFBを得たり社内の専門人材に頼ることで得られますが、エンジニアリングとして何が律速になっていてどう改善するか決めるには、開発者が各プロセスの出力を読み解かないとできません。つまり開発者が図面を読めないとプロンプトの評価もできないのです。 足りない知識を調べつつ評価基準を作ったものの、エンドユーザーのユースケースに沿った体験をどれほど忠実に評価できているか自信を持ちづらいという課題に陥っていました。 対策:受動的なインプットから、能動的な構造化へ 対策としてまず試したのは、専門書をいくつか読むことです。これは解像度を上げる効果はありましたが、特に未知の情報では目が滑ってしまって「点」の情報しか得られず、自身の検証において体系的な評価・改善につながる効果はあまり感じられませんでした。 そこで資格の取得に取り組みました。数ある学習法の中で、あえて資格試験という形を取った理由は以下の2つです。 思考のトレーニングと構造化 書籍での学習は、不明な点があると読み飛ばしてしまいがちです。その点、問題演習は細部まで読み込まないと解けないため、知識を漏れなくインプットできます。また、過去問で反復演習を繰り返すことで、単なる暗記ではない「構造的な知識」として定着させることを狙いました。 強制力による高強度な学習 出願締切直前に申し込むことで、自分に「締め切り効果」という適度な負荷をかけました。これにより、短期間で集中してインプットに取り組むことができました。 受験したのは「機械設計技術者試験(3級)」で、設計者が何を考えて図面を書くのか、その背景知識を問う試験です。3級を選んだのは実務経験が不要だったからです。 この試験を通じて、図面の指示や書き方はもちろん、それを実現する加工方法や部品の役割(力の伝達計算など)を体系的に学べました。その結果、「製品」と「図面」、そして「生産方法」を一つの繋がりとして認識できるようになったのが大きな収穫です。また、私は化学系出身なのですが、試験範囲に専門分野が含まれていたため、全くのゼロからではなく、自分のバックグラウンドを活かしつつスムーズに学習を進めることができました。 成果:解像度の向上と属人性の改善 図面の見え方が変わった 対策を通じて最も変わったのは図面の見え方です。元々は画像としてみていたのですが、ソフトウェアでいうソースコードのような捉え方をするようになりました。例えば図面上の穴の見え方が以下のように変わりました。 これまで:穴という形状として見る 受験後:穴の形と寸法・位置関係・関連指示から「多分ボルトを通すための穴で、こういう風に加工してほしいんだろう」とその形状の目的と加工方法をイメージする 目的がわかるとそれを満たすための情報が芋づる式にわかります。例えばボルト用の穴ならそれほど厳しい公差はいらないはず、といった形で取捨選択ができます。実際の図面では様々なコンテキストが入り乱れているので、この取捨選択ができるようになり認知不可が格段に軽減しました。 また専門用語を理解できるようになったことも良かった点です。検索キーワードがシャープになり、求める情報を見つけやすくなりました。特にLLMに質問する際は専門用語を使うことでより質問に特化した答えを返してくれるようになりました。プロンプトの改善に関しては以下の記事でまとめてますのでよければご覧ください。 caddi.tech 評価プロセスの改善 その結果として狙い通り、評価プロセスが改善しました。 まず評価指標の設計が改善しました。ユーザー体験を想像できるようになったことで指標や分類の基準が明確になりました。 またプロンプトも改善しました。例えば下図の図面の中心の穴を軸をはめ込む穴としてLLMがうまく認識できていない場合、はめあい公差という特有の指示をまず探すように指示することで見落としが減るなど、内部のプロセスに踏み込んだ調整が可能になってきました。 さらに、評価データ作成を分担できるようになった点も嬉しい変化でした。自分が図面読解するためのノウハウをチームにインストールすることで、他の作業者との認識ズレが減ったため分担しやすくなり、チームとしてデータ作成を進めることができるようになりました。これまで細かな例外ケースをすり合わせながら進める必要があったところから考えると、属人性がかなり軽減したと感じています。 今後の挑戦 実はまだまだ道半ばです。一般知識に関してもまだまだ未知の領域も多いですし、業界別の知識など、これから深ぼるところがたくさんあります。 また、この図面読解タスクにおいてはコンテキストエンジニアリングが特に重要と考えています。 その理由の一つは、我々が扱うデータはそれぞれの顧客が独自で管理するクローズドなデータであることです。各社の独自のノウハウが盛り込まれていて、一般知識だけでは理解できないこともたくさん記載されています。したがって個社独自のコンテキストを元に図面記載を解釈する必要があります。 またこの記事で書いたことは単独の図面の読解ですが、実際の図面は様々なドキュメントや3Dデータなどのさまざまなファイルを介して、非常に複雑なコンテキストが形成されています。これらのファイル間のコンテキストもうまく扱う必要があります。 さらに、読み手のコンテキストも重要です。前述した通り、図面から読み取る情報は読み手によって異なります。LLMで図面読解をサポートするには、多様なユースケースに対して必要な情報を必要なだけ届ける工夫が必要です。 これらのコンテキストをうまく伝えることでLLMの図面読解性能が向上する傾向も見えてきています。さまざまな探求の余地がある面白い分野だと思っています。 最後に もしあなたが今評価で行き詰まりを感じているなら、ドメイン知識を深ぼることで活路が見えるかもしれません。一次情報に触れて理解できるとやはり楽しいので、個人的には資格を取ってよかったと感じています。AIを使った新しい業務プロセスを顧客に使ってもらうためにも、これを機に顧客と同じ視点で議論できると良いなと思っています。 また、キャディでは上記のような課題に一緒に取り組んでいただける仲間を募集しています。 曖昧な評価指標に頼るPoCから脱却したい。 業界の深いコンテキストに飛び込み、顧客と一緒にAIで新しい常識を実装したい。 そんな思いを持つ方、ぜひ一度お話ししましょう。以下からご応募お待ちしています。 open.talentio.com
こんにちは。キャディ株式会社Analysis Platform Groupでバックエンドエンジニアをしている森谷( @yudmo_ )です。 2025年11月にジョインし、現在は機械学習推論のためのインフラやバックエンドの構築や運用を担当しています。 2026年1月7日から3日間にわたり開催された Regional Scrum Gathering Tokyo 2026 (以下、RSGT)に、今年も実行委員として参加してきました。スタッフとしての活動がメインでしたが、印象に残ったセッションや、RSGTならではの体験、そしてスタッフワークを通じて感じたことを振り返りたいと思います。 印象に残ったセッション:「要はバランス」を見極める - ADR実践で目指す技術的卓越への道 スタッフ業務の合間に参加した中で、特に心に残ったのがこのセッションです。 公開された発表資料は こちら です。 このセッションでは、生成AIには不向きとされるADR(Architectural Decision Record)の作成実践を通じた学びが共有されました。弊社のプロダクト開発においても、ADRを作成しレビューするプロセスがあります。私が個人的に関心を持っていたのは、"将来の自分たちが意思決定を振り返る際にいかに有用な材料を残せるか"という点です。 実は、実行委員としてのセッション採択会議の段階から”これは聞いてみたいな”と推していたセッションでもありました。内容も期待通りで、ADRに含めるべき具体的な項目や、陥りがちなアンチパターンの紹介など、業務に活かせそうな知見を得ることができました。 RSGTでは、キーノートを含めて採択されたセッションは後日YouTubeで公開されるので、聞けなかったものを見返したり、チーム内で同時視聴会をしたりしながら、セッションから得られる学びを組織で共有できればと思っています。 参加者との交流とOST RSGTの醍醐味といえば、Day3に行われるOST(Open Space Technology)をはじめとした、セッション枠以外でも活発に行われる参加者同士の対話です。 廊下での立ち話や休憩時間の交流では、現在所属しているチームで”スプリントレビューを開催できていないこと”への課題感を話してみました。ステークホルダーからフィードバックをもらい、開発方針を適宜調整するのが理想であることは理解しつつも、関係者を集める難しさやある程度の開発内容がすでに決まっている状況があり、今はスクラムガイド通りに進められていません。そんな中で、”今の開発がうまくいっている手応えがあるなら、無理に形式に当てはめて開催しなくても良いのではないか”と話をしました。まずは、現状に即してできることをやるというところで、対話を通じて課題を共有しながら一歩進むことができたかなと感じています。 また、Day3のOSTでも、SREやPlatform Engineeringに携わるほかの参加者の皆さんと、その領域でどのようにスクラムを適用しているかについての議論に参加しました。15並列でセッションが行われる中、スプリントゴールの設定が難しいところであったり、緊急の対応などでスプリントが中断されやすいといった悩みが次々と上がり、同じような課題を抱えているんだなと共感するところもありました。現実の難しさを受け入れつつも、少しずつ改善していくためのアイデアを出し合えたのも、非常に有意義な議論の時間となりました。 私は今回で10回目のRSGTの参加となりましたが、国内外のスクラム実践者が集まって、互いに考えを共有する”場”として、原理原則の話だけでなく、実際の現状と実態を元に意見交換ができることに対して、改めて参加するメリットを感じました。 スクラムを体現するスタッフワーク 最後に、実行委員としてのふりかえりです。RSGTの運営は、実行委員を含めすべてボランティアで構成されています。今回は、実行委員に加えて、2回目以上のボランティア参加となるスタッフのみで運営していて、チームとしての練度の高さと自律的な動きが非常に印象的でした。 今年は会場変更という大きな変化があり、当日になって初めて気づく課題も多くありました。そのような状況下でも、指示されるのを待つのではなく、それぞれのスタッフがカンファレンスを成功させるために何が必要かを判断して動く様子は、理想的なスクラムチームを体現しているようだったなと感じています。できることはやってみる、できないことは来年改善しようという割り切りも含めて、チーム全体でできることに集中し、RSGTという体験を無事に提供できたのではないかと感じています。 おわりに 今回も実行委員として参加したことで、単にセッションを聴講すること以上に、RSGTという”場”そのものを皆さんと作り上げる体験ができました。 最後になりますが、お話をいただいたスピーカー、支えてくださったスポンサー、スタッフチーム、そして会場を盛り上げてくれた参加者の皆さん、本当にありがとうございました。 現在、私の所属するキャディのチームも非常にコミュニケーションが活発で良い状態にあります。今回のイベントを通じて得られた知見や、スタッフワークを通じて肌で感じた”チームが自律的に動ける理由”を自分なりに言語化し、組織の目標達成に向けて日々の業務に還元していきたいと思います。参加レポートとしてだけでなく、得られたエネルギーをもとに、より良いプロダクトづくりに繋げていきたいです。
Analysis Platform 部の松﨑です。 これは キャディ株式会社のアドベントカレンダー 25 日目の記事です。 CADDi では、機械学習ワークロードにおける複雑な後処理(Pythonスクリプト等)の実行基盤として、ワークフローエンジンの Kestra を採用しました。 様々なエンジンと比較検討した結果、我々のユースケースにとってベストな選択だったと思います。 しかし、実際に開発を始めてみると「開発元である Kestra 社の思想」と「我々がやりたい運用」の間に、少しだけ溝があることに気づきました。今回は、我々がその溝をどうやって埋め、Kestra と付き合っていっているかをご紹介します。 方向性の違いを最も感じたのは、Flow(ワークフロー定義)や File の登録に関するインターフェースです。Kestra は非常に優れた UI を持っており、UI 上で YAML を意識せずに Flow を定義することができます。この利点は生かさない手はなく、API 設計もこの UI 操作に最適化されている印象を受けます。 一方で、我々開発チームには「可能な限りすべてをコードベースで管理したい」という思いがあります。これを具体的に書き表すと GitHub 上でコードを管理し、変更履歴を残したい Pull Request ベースでレビューを行いたい マージされたら CI/CD で自動デプロイしたい ということになります。このアプローチをとろうとした時、Kestra 標準の API インターフェースでは一工夫が必要でした。 Kestra では一つの処理単位を Flow と呼び、YAML で記述します。 我々としては、この YAML ファイルを Git リポジトリで管理し、CI から API 経由で登録したいと考えました。しかし、Kestra の API は UI での操作を意識してか「1つの Flow を個別に登録する」ことが推奨されているような設計になっています。一括登録も用意されてはいますが、「複数の Flow 定義が 1 つのファイルにマージされている状態」を受け取るものでした。 リポジトリ上では可読性を高めるために「1 Flow = 1 ファイル」で管理したいのですが、API は「まとまった 1 つのファイル」であることを求められてしまっている状態です。地味なようですが、開発体験上悩ましい課題でした。そこで我々は、Kestra の仕様に合わせつつ、自分たちの開発体験も損なわないためのデプロイスクリプトを自作することにしました。 アプローチは以下の通りです。 YAML ファイルは 1 Flow = 1 ファイル で作成し、それぞれレビューを行う。 コミット時に Lint をかけ、構文エラーを防ぐ。 デプロイ時にスクリプトで全 YAML を「Kestra が好む 1 つのファイル形式」に結合する。 結合したデータを Kestra の Bulk API で登録する。削除オプションもつけ、不要な Flow の削除も可能にする。 これにより、開発者は Kestra の内部仕様や API を意識することなく、普段通りの Git フローで開発を進めることができるようになりました。実際に CI/CD で動かしている Python スクリプトのロジックは、概ね以下のような形になっています。 (詳細は隠蔽していますが、処理の流れのイメージです) def main (): # 1. コマンドライン引数の取得 # (対象ディレクトリ, Kestra URL, 対象Namespace, 削除フラグ, ファイル同期フラグなど) config = parse_arguments() # 2. フローの同期を実行 sync_flows(config) def sync_flows (config): """ フロー定義の同期処理 """ # 指定ディレクトリから対象となる名前空間(ディレクトリ)のリストを取得 target_namespaces = get_namespace_list(config.flows_dir, config.target_namespace) logger.info(f "{len(target_namespaces)} 個の名前空間についてフローを同期します" ) for namespace in target_namespaces: # A. その名前空間内のYAMLファイルを全て収集 yaml_files = collect_yaml_files(namespace) # B. 複数のYAMLを1つのリクエスト用にマージ (--- 区切りなど) merged_flow_content = merge_yamls(yaml_files) # C. Kestra API へ送信 (PUT/POST) # delete_flow=True の場合、ここに含まれない既存フローは削除される api_client.push_flows( namespace=namespace, content=merged_flow_content, delete_others=config.delete_flow ) if __name__ == "__main__" : main() このスクリプトを CI に組み込むことで、開発者は GitHub に Push するだけで Kestra 環境が同期される開発環境を実現できました。 昨今のソフトウェア開発において OSS の活用は不可欠ですが、ツールの思想と自分たちのやりたいことが 100% 一致することは稀です。もちろん、本家に Pull Request を送って改善できればベストですが、それが反映されるのを待っているとビジネスが止まってしまうこともあります。 そんな時は、「使いにくい」と諦めるのではなく、今回のように「自分たちの理想との差分をエンジニアリングで埋める」というアプローチも、一つの解だと思っています。 Kestra はクセもありますが、我々には必要不可欠な技術です。この記事が、Kestra や他のツール導入で悩んでいる方のヒントになれば幸いです。
こんにちは、Analysis Platformチームの上野です。 キャディ株式会社のアドベントカレンダー 24日目の記事です。 この記事ではAnalysis Platformチームで実施した、機械学習モデルの複雑な後処理の実行基盤の技術選定について説明します。同様の技術選定をする際の参考になると幸いです。 既存のアーキテクチャとその課題 キャディでは一部の機械学習モデルの推論処理を以下のような非同期のアーキテクチャで行なっています。一部の推論ワーカーでは他のワーカーの結果と組み合わせて後処理をする必要があり、ワーカーごとの推論結果を格納したBigQueryのテーブルを一度経由する形になっています。 推論ワーカーが処理した結果をGoogle Cloud Pub/Sub(以降Pub/Subと呼ぶ)にパブリッシュする。 Pub/SubからBigQueryに結果を格納する。 BigQueryに格納されたデータを後処理したものを最終的な結果として別のテーブルに格納する。 詳細は省きますが、この仕組みには以下のような課題があったため、後処理を行うシステムを別の仕組みで置き換えることになりました。 最終的なデータ品質の可視化の仕組みが不十分で、他のシステムからの利用にハードルがある。 複雑な後処理ロジックがSQLで記述されており、新規開発・メンテナンスが複雑で開発や運用工数が高くなる。 技術選定 上記のような課題を解決するために、大まかに以下の要件を満たす設計を行うことにしました。 要件1: データ品質を監視、可視化できること。 ここでは特に解析結果がエラー等でどの程度欠損しているかをデータ品質という言葉で表現しています。この記事では以降でも同様の意味でデータ品質という言葉を使用します。 要件2: 機械学習エンジニアが後処理を容易に開発、保守できること。 現在の仕組みを置き換えるためだけではなく、将来的により複雑な後処理や解析フローを実装したくなった際に、それをサポートできるとより良いだろう、という考えもありました。 要件3: 複数の解析ワーカーの出力が到着するのを待機して後処理を開始できること。 二つ目の要件である複雑な後処理や解析フローの実装にはワークフローエンジンを採用するのが適切であると考え、以下のワークフローエンジンから選定することにしました。 Temporal Kestra Google Cloud Workflows(以降Cloud Workflowsと呼ぶ) Argo Workflows ただ、Cloud WorkflowsとArgo Workflowsはそれぞれ以下の点から早い段階で検討対象から外しました。 Cloud Workflows: 複雑な処理では基本的に外部APIを呼び出す形になるので、後処理APIを別で用意する必要があり、追加の工数がかかる。 Argo Workflows: すでに他チームで運用実績があるが、運用に専門チームが必要であり運用工数が高そう。 TemporalとKestraがそれぞれ要件を満たせるかどうかを実際に少し触ってみて比較してみました。結果の概要を以下に示します。機能的にはどちらも問題なかったのですが、TemporalはKestraと比較して少し学習コストが高いかなという印象を受けました。 要件1 要件2 要件3 Temporal ⭕️ 🔺 ⭕️ Kestra ⭕️ ⭕️ ⭕️ Temporal Temporalは三つの要件のうち二つは対応可能でしたが、一つはKestraと比べて劣るという結果になりました。 まず、「データ品質を監視、可視化できること」についてですが、これはログなどを適切に仕込むことで問題なく要件を満たすことができると考えられます。また、実行中のWorkflowに対してメッセージを送ることのできる Signal機能 を使用すれば以下のように複数の解析ワーカーの出力の到着を待って処理を開始できるので、こちらの要件も満たせそうです。 まずは結果を受け取る側のワークフローを定義します。以下のようにSignal機能を用いて結果を受け取るための関数と、実際にワークフローの実行中に結果を待機する処理を書くことで結果の待機ができます。 @ workflow.defn class AnalysisWorkflow : ... # Signal機能を使って結果を受け取る @ workflow.signal def analysis_data_available (self, request: AnalysisRequest) -> None : ... self._received_analysis_data[request.analysis_type] = request # ワークフローとして実行される処理 @ workflow.run async def run (self, workflow_input: AnalysisWorkflowInput) -> dict [ str , Any]: ... # 必要なデータが揃うまで待機 expected_types = set (AnalysisType.__members__.values()) await workflow.wait_condition( lambda : set (self._received_analysis_data.keys()) == expected_types, timeout=timedelta(minutes= 10 ), # 10分のタイムアウト ) ... Signalを送信する側では Signal-With-Start の機能を用いて以下のようにSignalを送信できます。ワークフローが起動している場合はSignalを送信し、起動していない場合は起動してSignalを送信できます。 await self.client.start_workflow( AnalysisWorkflow.run, AnalysisWorkflowInput( job_id=request.job_id, tenant_id=request.tenant_id ), id =workflow_id, task_queue=activity.info().task_queue, start_signal= "analysis_data_available" , start_signal_args=[request], ) 一方で、実装の際にはTemporal特有の概念を学ぶ必要があり、後述のKestraと比較すると学習コストが高いように感じられました。学習コストが高いと実装のサポートやレビューの工数が高くなる可能性があり、保守運用や機能開発の工数を圧迫する懸念がありました。 Kestra Kestraは三つの要件全てに対応可能でした。 データ品質の監視・可視化はTemporalと同様に可能です。また、ワークフローの定義はYAMLで記載でき、GitHub Actions等に慣れていれば学習コストはそこまで高くなさそうです。さらに、複数ワーカーの出力の到着の待機も KV Store を用いて以下のように擬似的に実装可能です。 まずはPub/Subから結果を受け取ってKV Storeに格納するワークフローを用意して、以下のようなステップでKV Storeに書き込みを行います。 ... - id : set_kv type : io.kestra.plugin.core.kv.Set namespace: demo description: 解析結果をKVストアに保存 key: "{{trigger.body.job_id}}.{{trigger.body.analysis_type}}" value: "{{trigger.body}}" overwrite: true kvType: JSON ttl: PT24H # 24時間の有効期限 ... 次に、結果を利用するワークフローを別で用意し、そこでKV Storeから結果を読み取る形にします。 errorOnMissing: true とすることで、KV Storeに対象のキーが存在しない場合はリトライされ、結果を一定時間待機するような挙動を実現できます。 ... - id : get_analysis_results type : io.kestra.plugin.core.flow.ForEach values: "{{ inputs.required_analysis_types }}" tasks: - id : get_kv type : io.kestra.plugin.core.kv.Get key: "{{ trigger.body.job_id }}.{{ taskrun.value }}" errorOnMissing: true retry: type : exponential interval: PT10S maxInterval: PT10M maxDuration: PT12H ... 結論 最終的にKestraを採用することになりました。Temporalと比較すると学習コストが低く、開発や運用コストを抑えられそうな点を重視しました。結果を受け取ってKV Storeに格納する部分は実装が必要になりますが、その仕組みを我々のチームで実装することで後処理ロジックの実装の負担を増やすことなく要件を満たせそうです。 設計 余談ですが、Kestraを用いて後処理を置き換えた後のアーキテクチャは以下のようになりました。これにより、結果を取りまとめるために用いていた中間テーブルが不要になったり、Pythonで後処理ロジックを記載できるようになるなど、データやロジックの取り扱いがシンプルになります。 ちなみにKestraには Cloud版 が存在しますが、開発開始時点ではAlpha版だったため、社内に存在するGKEクラスタにOSS版をセルフホストすることにしました。 さいごに この記事ではキャディにおける機械学習モデルの後処理の現状と課題及びそれを解決するための技術選定について説明しました。この記事が同様の技術選定を行う際の参考になれば幸いです。
この記事は、 CADDi Tech/Product Advent Calendar 2025 の23日目の記事です。 DataManagementチームの福田です。弊社ではCTOと共に機密データの取り扱いを決定し、その方針をBigQuery Policy TagsとDataContractで自動的に反映する体制を構築しています。今回は約15,000カラム以上のデータで実践した経営層を巻き込んだデータガバナンスの仕組みを解説します。 はじめに 企業向けSaaSでは、顧客データに対して通常よりも厳格なアクセス制御が求められます。 例えば、機密性の高いデータを一般の社員には閲覧させず、特定のユースケースにおいてのみ限定されたグループのみがアクセスできるようにする必要があります。 このような要件に対して、BigQueryの Policy Tags と Data Masking Policy を活用することで、クエリ時に透過的なマスキングを実現できます。さらにDataContractで機密性を宣言的に管理することである程度の自動化と監査可能性を両立した基盤を構築しました。 BigQueryのPolicy Tags機能 Policy Tagsとは BigQueryのPolicy Tagsは、カラムレベルでアクセス制御とデータマスキングを実現するGA機能です。Taxonomyを作成し、その中にPolicy Tagを定義します。Policy TagにData Masking Policyを紐付け、テーブルのカラムに適用することで、IAM権限に応じて自動的にデータをマスキングできます。 透過的マスキングの仕組み Policy Tagsの最大の特徴は、 同じテーブルに同じクエリを実行しても、ユーザーの権限によって返されるデータが自動的に変わる ことです。マスキングビューを別途作成する必要はありません 具体例:顧客連絡先テーブルへのクエリ この例では、 email と phone カラムにPolicy Tagが適用されており、 customer_name は非機密カラムとして扱われています。 -- 特権グループのユーザーも一般ユーザーも、同じSQLを実行 SELECT customer_name, email, phone FROM `my-project.customer_data.customer_contacts` WHERE customer_id = 12345 特権グループのユーザー( roles/datacatalog.categoryFineGrainedReader 権限あり): customer_name email phone 田中太郎 tanaka-example@caddi.com 03-1234-5678 一般ユーザー(権限なし): customer_name email phone 田中太郎 ************************ ************ customer_name は非機密カラムのため両グループとも表示されますが、 email と phone は機密カラムとしてマスキングされます。このように、 カラム単位 で細かくアクセス制御を設定できます。 テーブルスキーマへのPolicy Tags適用 Policy Tagsをテーブルのカラムに適用するには、BigQueryのスキーマ定義にPolicy Tag IDを追加します。 Terraform での設定例: resource "google_bigquery_table" "customer_contacts" { dataset_id = "customer_data" table_id = "customer_contacts" schema = jsonencode ( [ { name = "customer_id" type = "STRING" mode = "REQUIRED" } , { name = "customer_name" type = "STRING" # Policy Tagなし(非機密カラム) } , { name = "email" type = "STRING" policyTags = { names = [ google_data_catalog_policy_tag.confidential.name ] } } , { name = "phone" type = "STRING" policyTags = { names = [ google_data_catalog_policy_tag.confidential.name ] } } ] ) } このように、機密性レベルに応じて 一部のカラムにのみPolicy Tagsを適用 することで、柔軟なアクセス制御が可能になります。 CADDiにおけるデータガバナンス体制 弊社では、機密データの取り扱いに関する意思決定をCTOと共に行っています。ここで決定した、どのデータを誰がどのような条件でアクセスできるかについて会社組織全体と合意形成を行っています。 この体制により、技術的な実装だけでなく、ビジネス要件とセキュリティ要件を両立させた意思決定が可能になっています。特に顧客データを預かるSaaS企業として、データの取り扱いについて経営層を巻き込んだガバナンスは不可欠です。 データガバナンスのプロセス 機密性分類の決定から技術的な適用まで、以下のプロセスで運用しています: データガバナンス委員会で合意 : CTO・データオーナーが参加し、機密性分類について議論・決定 Spreadsheetで管理 : 全カラムの機密性レベルを管理 DataContractでコード化 : CIでSpreadsheetからDataContract YAMLを自動生成しGit管理 様々な用途に自動反映 : Policy Tags、データカタログなど複数の用途に展開 大規模運用における課題とDataContractの必要性 大規模なデータ基盤で手動運用すると、全ての機密カラムに対してTerraformで1つずつ設定する必要があり、設定ミスのリスクが高くなります。これはマスタとなるスプレッドシートを用意し、スクリプトなどでTerraform向けJSONを生成するという選択肢も考えられます。しかし、我々のチームでは DataContractを中間層として挟む アプローチを選択しました。 DataContractとは DataContractは、データの「契約書」として、スキーマだけでなく、データ品質、機密性、所有権などのメタデータをコードで管理する仕組みです。データオーナーが「このカラムはどのように扱うべきか」を宣言的に定義します。 DataContractの基本構造: dataContractSpecification : 1.2.0 id : company.customers models : customers : description : 顧客マスタ fields : customer_id : type : string description : 顧客ID quality : - type : not_null - type : unique email : type : string description : メールアドレス confidentials : classification : 'pii' # 個人情報として分類 Data Contract CLIとは cli.datacontract.com DataContractの作成・管理・活用を支援するオープンソースのCLIツールです。YAML形式のDataContract定義ファイルを中心に、データガバナンスを自動化する包括的なエコシステムを提供します。主な機能として以下が挙げられます。 インポーター : BigQuery、Snowflake、PostgreSQLなどからスキーマを自動取得してDataContract生成 Export機能 : dbtスキーマ、OpenAPI、Avro、SQLなどへの変換 カタログ生成 : DataContractからHTMLデータカタログを自動生成 バリデーション : 実データとDataContractの整合性チェック カスタム拡張 : Importer クラスを継承して独自のデータソースに対応可能 今回我々はカスタムインポーター機能を使ってBigQueryに用意したテーブルからDataContractを自動生成し、標準のExport機能では対応していないTerraform向けJSON抽出は独自実装しています。 なぜData Contract CLIを採用したか dbt sourceファイルには機密性情報を含めていません。両者は目的が異なるためです。両者を分離することで関心の分離を実現し、それぞれの目的に特化した管理が可能になります。 dbt source : dbtモデル構築のためのメタデータ(データ型、テーブル構造) DataContract : データガバナンスのためのメタデータ(機密性分類、品質ルール) DataContractの作成と管理 前述のデータガバナンス委員会で決定された機密性分類は、まずSpreadsheetで管理されBigQueryの外部テーブルとしても参照できるようになっています。 DataContract YAMLファイルの作成・更新は、Data Contract CLIのカスタムインポーター機能を使って完全に自動化しています。 Data Contract CLIの Importer クラスを継承し、BigQueryに用意した外部テーブル(スプレッドシート)から機密性分類を取得してDataContract YAMLを自動生成します。 CIによりカスタムインポーターを実行し、DataContractファイルに差分があれば自動的にPRを作成します。 簡略化したコード例 : from datacontract.imports.importer import Importer class DatalakeColumnImporter (Importer): def import_source (self, data_contract_specification, source, import_args): """BigQuery外部テーブルからDataContractを自動生成""" query = """ SELECT dataset_name, table_name, column_name, data_type, confidential FROM `my-project.governance.spreadsheet_of_column_confidential_level` """ # DataContract YAMLを自動生成 for row in bq_client.query(query): field_spec = { "data_type" : row.data_type, "confidentials" : { "confidential" : row.confidential } } DataContractから機密カラムを抽出 前述の通り、Data Contract CLIの標準Export機能では、 「DataContractから特定の機密性分類を持つカラム一覧をTerraform向けにJSON形式で抽出する」ことができません 。Policy Tags適用のためには、以下の情報が必要です。 プロジェクトID、データセット名、テーブル名 機密カラム名とそのデータ型 Terraform for_each で処理できるJSON構造 そのため、独自のPythonスクリプトを実装しました。 抽出スクリプト例 def extract_confidential_columns (datacontract_path: Path) -> list [ dict [ str , str ]]: """ DataContract YAMLから confidential='yes' のカラムを抽出 """ with open (datacontract_path, encoding= "utf-8" ) as f: contract_data = yaml.safe_load(f) # パスから dataset_name と table_name を取得 # 例: datacontract/contracts/customer_data/customer_contacts/datacontract.yml dataset_name = datacontract_path.parts[- 3 ] table_name = datacontract_path.parts[- 2 ] confidential_columns = [] models = contract_data.get( "models" , {}) for model_name, model_info in models.items(): for column_name, field_info in model_info[ "fields" ].items(): confidentials = field_info.get( "confidentials" , {}) if confidentials.get( "confidential" ) == "yes" : confidential_columns.append({ "dataset_name" : dataset_name, "table_name" : table_name, "column_name" : column_name, }) return confidential_columns 生成されるJSON : confidential_columns.json [ { " project_id ": " my-project ", " dataset_name ": " customer_data ", " table_name ": " customer_contacts ", " columns ": [ { " name ": " customer_name ", " type ": " STRING " } , { " name ": " email ", " type ": " STRING " } , { " name ": " phone ", " type ": " STRING " } , { " name ": " address ", " type ": " STRING " } ] } ] 実行: cd terraform/modules/data_masking/scripts uv run python -m src.fetch_confidential_from_contract --env prod この生成したJSONをTerraformで読み込み、Policy TagsとData Masking Policyを作成してテーブルの機密カラムに自動適用することで、透過的なマスキングを実現します。 導入時の課題 この仕組みと運用ににおいて、最大の面倒くさいポイントは初期構築時に利用したい全てのカラムをスプレッドシートで精査し、機密性分類する必要があることです。 弊社の場合、全てのテーブルを合わせて15,000カラム以上を精査しました。しかしこれは安全に運用するための必要コストと割り切ってやりきりました。 一方でBigQueryや他社DWHにはAIで自動的に機密カラムを検出する機能が備わっています。ここで検出できる機密は当たり前ですがemailや住所などの個人情報です。我々はdescription や tag などのようなデータを保護したい機密データとして定義しているので適用は難しいです。このため我々は最終的には人間がレビューして正しい機密性分類をすることが重要と考えています。 まとめ BigQueryのPolicy TagsとDataContractを組み合わせることで機密データに対する厳格なアクセス制御を実現しました。 本アプローチは、機密度の高いデータ基盤において、知らない間に機密データが分析可能になるようなリスクを回避しつつ、適切なメンバーには分析可能な基盤を提供するために設計されました。 一方で、運用負荷とのトレードオフとして、連携している全てのデータに対してカラムレベルで機密性を判断・定義し続ける必要があります。 しかし、顧客からデータを預かる立場として安全に運用することはデータガバナンスの責務であり、この運用負荷は受け入れるべきコストと考えています。同様の課題がある皆様の参考になれば幸いです。 CADDiでは一緒に働くメンバーを絶賛募集中です! カジュアル面談などお気軽にご連絡ください。 カジュアル面談申し込み_エンジニア・UI/UXデザイナー / キャディ株式会社 Data Engineer / キャディ株式会社
本記事は CADDi Tech/Product Advent Calendar 2025 22日目の記事です。 こんにちは、Data & Analysis部で機械学習エンジニアをしている由川です。 私は、製造業特化LLMを開発するための評価ベンチマークづくりに取り組んでいます。本記事では、この取り組みにおいて得られた知見や苦労していることを紹介したいと思います。 ドメイン特化LLMに関する評価ベンチマークを作ろうとしている方の参考になれば幸いです。 なぜ製造業特化の評価ベンチマークを作るのか ベンチマークタスクの定義 ベンチマークタスクのデータセット作成 評価対象となる図面の選定 評価対象の図面をアノテーション ベンチマークタスクの評価 評価方法 評価指標 ベンチマークタスクの評価システム まとめ なぜ製造業特化の評価ベンチマークを作るのか 以下のとおり活用するためです。 ベンチマークでの精度を比較することで、製造業の課題を解決するためにはどのLLMを選べばよいか選定できるようにするため キャディが持つ製造業の様々なデータ(図面画像、3Dモデル、仕様書など)に対してFine-tuningなどの手段を適用することで、製造業に関する様々な課題を解ける汎用的なLLMを開発するため (特にやりたいのはこちら) 上記の活用をするために、まずはOpenAIの提供するGPT系やGoogleの提供するGemini系といった世の中にある汎用LLMが、製造業のどのような問題が得意/苦手か明らかにする必要があります。 これを明らかにするためには、製造業に関する評価ベンチマークが必要です。 しかし、製造業の図面、仕様書、3Dモデルについて理解できているか評価するベンチマークは世界的に見ても数が少ないです。 また、既存のベンチマークでは測れない、製造業特有の複雑さを評価するためには、実際の業務で流通しているデータが不可欠です。 ここで、キャディには町工場から大手企業まで様々なお客様に契約いただくことで蓄積されたデータがあります。 この膨大なデータを活用し、実践的な評価ベンチマークを作成することにしました。 なお、お客様のデータはキャディ社内で機械学習に活用することに同意いただいたデータのみ利用しています。 ベンチマークタスクの定義 LLMが製造業の課題を解けるか確認できるベンチマークタスクとは何でしょうか? この問いの答えとして、我々は熟練した設計者や技術者のように製造業について十分に理解している人間が図面を理解するプロセスを言語化しました。 そのうえで、このプロセスをLLMで再現するタスクを定義しました *1 。 人間が図面を見て理解するプロセスと、LLMが解くタスクとの対応関係は以下表のとおりです。 人間が図面を理解するプロセス プロセスの概要 LLMが解くタスクの例 空間把握 図面の中に書かれている立体がどのような形か把握する 例:「この図面に書かれているのはL字型の板金」 2D図面から3DCADへの再構築タスク 要素認識 図面内の個々の要素(文字、記号、寸法など)が何か認識する 例:「ここに直径10mmの円がある」 ・物体検出 ・寸法値の推定 構造把握 要素間の関係性や位置関係を把握する 例:「この寸法線は、この形状の深さを表している」 ・分類問題 ・数値の推定 機能理解 要素がどのような機能を果たすのか、どのような意図で設計されたのか理解する 例:「この公差 *2 はこの部品をはめるために必要」 画像キャプションタスク 製造・品質理解 図面を通して作られた設計がどのように製造され、検査されるべきかを理解する 例:「この形状は旋削加工 *3 をした方が良い」 「この寸法は測定器Aで検査する必要がある」 Q&Aタスク 表の対応関係により、LLMは図面に書かれている要素は何か、という単なる画像認識にとどまらず、高次の情報(例:要素間の関連性、要素にはどんな機能があり、その機能に込められた意図は何か)も理解しているか評価するようにしました。 「高次の情報」の理解を評価する具体例として、画像キャプションタスクの採用理由を説明します。 製造業の現場では、図面から記号や寸法といった要素を見つけることだけでなく、要素を構成する製品の設計意図を説明できることが求められます。 そこで、私たちはこの説明をLLMで再現するにはどうすればよいか考え、画像から適切な説明文(キャプション)を生成させるタスクを行えばよいだろうと判断し採用しました。 表の対応関係はすんなりとできたものではなく、熟練者が無意識に行っている脳内処理を評価可能なタスクとして定義することが難しかったです。 タスクを検討する際、社内で製造業にドメイン知識のある方たちに図面を読むときに何を考えているかをヒアリングしましたが、直感的に見ている傾向がありました。そのため、熟練者自身も言語化に苦戦していました。 また、人によって見解が異なっていたため、絶対的な正解はない中でタスクを定義しました。 以上を踏まえて、本節を通してお伝えしたいことは、ベンチマークタスクは人間が実際に行うプロセスを再現できるか、そして評価したいことは何か決めたうえで設計しましょう、ということです。 ベンチマークタスクのデータセット作成 ベンチマークタスクの定義ができたら、そのタスクに関する評価データセットを作る必要があります。評価データづくりは以下のステップで行っています。 評価対象となる図面の選定 評価対象の図面をアノテーション 評価対象となる図面の選定 量・割合・質という3つの観点で以下を意識して選定しました。 観点 量 精度にブレが出ない程度の量があること 割合 図面内の製品の形状や書き方のフォーマットがある程度典型的でありつつ、多様性もあること。 質 図面の書かれ方や画像の解像度などが、実際にお客様に利用されている状態に近い図面を選ぶこと データ選定にも難しさはあります。我々が苦労したのは、データクレンジングです。 例として、2D図面から3DCADへの再構築タスクの場合、製造業のドメイン知識があるプロダクトマネージャーが以下を1件ずつ目視したうえでデータを選定しました。 正解となる2D図面と3DCADの対応関係が正しいか 2D図面、3DCADが完成済みか(未完成のものが含まれることがあるため) 評価対象の図面をアノテーション キャディには、図面に関するアノテーションを行う組織があります(参考: MLの裏側を支えるアノテーション組織運営の実践禄 )。 基本的には、アノテーション組織の方に依頼してアノテーションいただくことで評価データセットを作りました *4 。 アノテーターに依頼してデータがたまるのを待つだけという単純な話にはならず、以下の点が難しいです。 何のデータが、どのくらいあるといいか見通しがつきにくい点 画像キャプションタスクのように 文章を正解として取り扱うタスクにおいて、絶対の正解が存在しない点。 例えば以下が難しさです。 何が書かれていれば正解とするかの定義 正解かどうかの判断基準(例:ある部分はあっているが別の部分が違うケース、記述は足りてないが間違っていないケースを正解とするかどうか) ベンチマークタスクの評価 評価データセットの作成ができたら、LLMの精度の優劣を評価できるようにするために、評価方法と評価指標を決める必要があります。 評価方法 以下を意識して、論文調査や事前検証したうえで方法を決めています。 LLM間で精度の優劣がつくタスクか 優劣がつくタスクである必要性は、 なぜ製造業特化のLLMベンチマークを作るのか に書いた活用ができるようにするためです。 アプリケーション開発の際に実際に行うであろう評価方法か アプリケーション開発で適用する入出力になっているか 入力の例:画像をリサイズしてからLLMに入力させる。 理由:元の画像サイズのままLLMに入力すると、あまりに画像サイズが大きい場合は推論速度が遅くなるうえに、計算資源の利用費も高くなるため。 出力の例:アプリケーションではLLMの推論結果をWeb APIを使って取得したいから、json出力ができる構造化出力を適用する。 評価指標 分類問題のようなよくあるタスクではPrecision、Recallなど代表的な評価指標を採用します。 そうではないベンチマークタスクの場合、評価結果をどのように活用するかユースケースを考え、LLMがそのユースケースを満たすか確認できる指標を定義しています。 多様なユースケースに対して、LLMが得意/苦手なことがわかるようにするために、基本的には複数の評価指標を設けています。 定義した評価指標によっては、算出された結果が妥当なのかを定性的に検証することもあります。 例えば、文章生成タスクにおいて文章が正しいかを評価する方法としてLLM as a Judgeを使う状況を考えます。 このとき、LLM as a Judgeにより出力される、正しい・正しくないという結果そのものが妥当なのか検証する必要があります。 いくつか方法は考えられますが、妥当性を確実に保証するには、LLMに判断結果だけでなく判断理由も出力させ、理由も踏まえて妥当なのかを人が評価せざるを得ないでしょう。 このように、いざとなったら人が評価結果の妥当性を保証する場合があるのが、評価ベンチマークづくりで大変なことの一つだと思っています。 ベンチマークタスクの評価システム 定義した評価方法と評価指標に基づく結果を参照できるようにするために、以下のような評価システムを社内向けに作っています。 ベンチマークの評価システム。ベンチマークタスクごとのLLMの精度がわかるようにしています。 このシステムで確認できるベンチマークタスクの評価結果を通して、 なぜ製造業特化のLLMベンチマークを作るのか に書いた活用ができることを目指しています。 まとめ 本記事では、道半ばではありますが、製造業特化LLMを開発するための評価ベンチマークに関する取り組みを紹介しました。 記事を通してお伝えしたいことは以下のとおりです。 ベンチマークタスクは、人間が実際に行うプロセスを再現できるか、そして評価したいことは何か決めたうえで設計すると良い 評価方法は、アプリケーション開発でも行われるであろう方法を採用すること 評価指標は、ベンチマークの結果を通して明らかにしたいユースケースを設定し、複数設けておくこと いざとなったら、以下を人が行う必要がある 評価データに不備や不要な情報がないかのデータクレンジング 評価結果そのものが妥当かどうかの確認 今回ご紹介した評価ベンチマークづくりに限らず、機械学習やLLMに関する技術によって解決したい製造業の課題はたくさんあります。 そもそも何に取り組んでいる会社なのか、機械学習やLLMを使って何を実現したいのかなど、ご興味あればぜひお気軽にご連絡ください。 speakerdeck.com recruit.caddi.tech open.talentio.com *1 : 図面をタスクの対象にしているのは、製造業において情報のやり取りの中心にあるのは図面だからです。仕様書のような文書データ、3Dデータに関するタスクも構築予定です。 *2 : 図面に書かれている寸法値からどれだけズレて良いかを表す許容誤差 *3 : 材料を回転させた後に工具を当てることで、目的の形状を作る加工方法 *4 : ベンチマークタスクの評価に関する検証をより素早く行うため、アノテーション組織の工数確保が難しいといった事情で、製造業のドメイン知識を持つプロダクトマネージャーが少量データをアノテーションすることもあります。
こんにちは、キャディのData & Analysis部の今野です。この記事は CADDi Tech/Product Advent Calendar 2025 21日目の記事です。今回は先日開催した当部の合宿についてご紹介します。 はじめに 自己紹介&チーミング 戦略説明 ハッカソン 最後に はじめに キャディは「製造業AIデータプラットフォームCADDi」を提供しています。 私が所属するData & Analysis部(以下、D&A)は、Tech組織の中で以下のような位置付けになっています。 Tech 組織図 D&Aは、図面や文書などの収集されたデータをML/AIを利用して解析を行うことがメインの業務になっています。今年の前半の時点では、モデルによって解析を行うAnalysis、解析基盤を作るAnalysis Platform、それらの技術のアプリケーションへの応用を探索するAI for Applicationというチームで構成されていました。その後、ワークフローベースのAIエージェントによるソリューションを高速に提供していくための基盤を開発するAgent Platformというチームが新設されました。 今回の合宿は、組織の変更や新しく入社するメンバーも多いD&A(私も今年の4月に入社しました)の中でコラボレーションのハードルを下げること、プロダクト及び部署の戦略を深めること、ハッカソンを通して全員がAgent基盤に触れることで一丸となって価値提供できる状態を作ること、を目的として開催されました。 自己紹介&チーミング まずはお互いのことをより深く知るべく、チームごとに自己紹介とチーミングを行いました。 自己紹介には以下のフォーマットを使用しました。 自己紹介のフォーマット 普段同じ部署で働いていても知らない内容が多いので、フォーマットに沿って自己を開示するだけでも改めてお互いを知る良い機会になったと感じました。 部長の自己紹介 チーミングのコンテンツとして、 ito というボードゲームを採用しました。itoの基本的なルールは、1~99までの数字のどれかが記載されたカードが1枚ずつ自分だけが見られる状態で配られ、事前に決められたテーマに沿った内容で配られた数字の大きさを表現し、お互いの数字の大きさを予測するというものです(例:テーマが「動物の大きさ」で、配られたカードが「80」の時、「熊」として周囲に伝える。周囲も同じように自分の数字に合わせた動物を選び、イメージを擦り合わせながらそれぞれの数字の大小を予測する)。 itoの様子 あまり話したことがないメンバーでも、ゲームの進行の中で自然にそれぞれの価値観を提示することになるので、非常にチーミングに向いているゲームだなと思いました。一方で、「飲食店の人気」というテーマの中で私の好きなあるチェーンが周囲の認識と明らかな乖離があったことがわかりモヤモヤが残っているので、これから良さを主張していきたいと思います。 ちなみに、D&Aにはボードゲームを愛するメンバーが複数いて、宴会後の有志のメインコンテンツもボードゲームでした。 戦略説明 次のコンテンツは戦略説明でした。 最初にVP of Product Strategyによるプロダクト戦略の説明があり、その後Data Platform本部、D&A、D&A内の各チームのレベルで戦略の紹介がありました。 改めて語られるD&Aのミッション 説明を聞いた後は、自己紹介&チーミングを経た各チームで内容を咀嚼して議論し、最終的に各チームから集まった質問がスピーカーにぶつけられるという流れになりました。キャディでは抽象的から具体的、全社からチームレベルまで様々な粒度で戦略がアップデートされ続けるので、業務の手を止めて一つ一つの理解度を高めてキャッチアップできる良い機会となりました。特に、プロダクト戦略は通常は全社に向けて発信される内容なので、合宿の中で行われたことでD&Aの具体的な取り組みと紐付けながら深掘りできる貴重な機会となったと思います。 ハッカソン この合宿のメインのコンテンツとなるハッカソンは、1日目の終盤から2日目を終日費やして実施しました。テーマはAgent基盤を利用して身近な課題を解決するソリューションを実装することでした。3~4人で編成されたチームでテーマ決め・MVPの実装・プレゼンを行い、最終的にアイデアのユニークさ・MVPとしての完成度・技術的な面白さの3つの観点で投票が行われ、1位のチームには豪華な景品が贈られました。 チームごとに実装中 個人的には、AIエージェントの質を高める要素の中では、何をAIの判断に委ね、何を決定論的に行い、どこに人間が介入するかのプロセス設計が大きなウェイトを占めていると思っています。普段の業務の中では製造業という非常に高度なドメイン知識が求められるのですが、今回は身近な課題解決というテーマに絞られていたのでドメイン理解は十分有している前提でプロセス設計に注力でき、ソリューションを作ることへの解像度を上げることができました。 最終的に、チームごとで以下のようなテーマとなりました。一つ一つを説明すると長くなるので詳細は割愛します。 1班:インシデントコマンダー エージェント 2班:高機能GoogleCalendarスケジュール調整エージェント 3班:ワークフロー最適化エージェント 4班:会議の用心棒 5班:ブラウザ・カメラ・マイク・扉 〜ローカルLLMのその先、WEB LLM と感情認識のシナジーで超えていく新世界〜 6班:(業務に関わる領域のため社外秘) 合宿の最後にチームの成果物への投票が行われました。最初の投票では同率1位となり、その後の決選投票の結果、私の所属する3班のワークフロー最適化エージェントが1位となりました。折角なので簡単に内容をご紹介したいと思います。これは①既存の任意のワークフローを呼び出して結果を評価し、②出力を元にAIが原因を特定し、③ワークフローの内容の修正を人間に提案、④修正内容が承認されればワークフローを再実行、承認されなければフィードバックを元に修正内容を再度提案するというプロセスを繰り返すものです。評価した際の精度が一定値以上またはループが一定回数以上になれば終了となります。 ワークフロー最適化エージェントのダイアグラム うまくいった点としては、人間のフィードバックを自然な流れで取り込む形にできたことと、任意のワークフローに適用できて汎用性がある形になっていたことかなと思います。今回は時間が限られていたため最適化するワークフローへの修正可能な部分をステップの中のプロンプトに絞りましたが、その他のパラメータやワークフロー内部のプランニング(実行するステップの選択)自体も変更できるように拡張できれば更に面白い内容になりそうだなと思いました。 実用の可能性があるワークフローを最適化の対象とし、今回のエージェントをデモデータに対して適用する形でワークフローの精度が実際に向上することを定量的に示すことができたことが投票してもらえた要因だったように思います。 これ以外にも、各チームで大変興味深い内容となっていました。非常に限られた時間の中でほとんどのチームがコンセプトだけでなく実際に動くものを提示することができていて、総評でも年々ハッカソンの成果物が洗練されていると言及されていました。Agent基盤が着実に整備されていた上で、Claude Codeなどのコーディングツールも利用できたことが要因として大きかったと思います。 最後に 様々なコンテンツを通して業務上の関わりを超えたメンバー同士の交流があり、当初の目的に適った実のある合宿になったように思います。 D&Aでは、MLエンジニア、MLOpsエンジニアをはじめとして様々な職種で採用を行っています! キャディにはお客様の非常に重要な資産である図面をはじめとする特有のデータがあり、世界最大の産業である製造業のポテンシャルの解放に向けて解かなければならない課題がたくさん存在しています。 汎用のAIは怒涛の勢いで進化を遂げていますが、ドメインの特性によって生じる期待とのギャップは依然として存在します。私の所属するAI for applicationチームは、これまで培ってきた解析技術・基盤の活用、ユーザー体験の検討、評価設計といった全てを組み合わせてアプリケーションに新たな価値を生み出すことを目指しています。 少しでも興味を持っていただけましたら、↓の採用ページをご覧ください。ご応募もカジュアル面談も、お待ちしております! エンジニア採用 カジュアル面談申し込み_エンジニア・UI/UXデザイナー / キャディ株式会社
こんにちは、キャディで Quote というアプリケーションを開発している plant こと石田 ( @plant_ja ) です。 この記事は キャディ株式会社のアドベントカレンダー の20日目の記事です。 adventar.org 今回は AI コーディングを図で表現しつつ、我々が期待する成果物を出力してもらうための様々なアプローチに思いを馳せてみようと思います。 ゴール設定 コーディングエージェントへの期待と現実 AI が書くコードを「確率密度関数」として考えてみる アプローチ1: 解空間の確率密度を上げる 解空間の定義 解空間のインプットコストとどう向き合うか コンテキストの増大 アプローチ2: 解空間外の出力を抑制する linter による抑制 タスクの分解によるコード出力バリエーションの絞り込み 2つのアプローチの比較 終わりに ゴール設定 まず、解きたい問題があります。例えば「新規ユーザー登録用の API を作成する」などです。説明を容易にするために、ここではもう少し細分化して「ユーザーエンティティを DB と firebase に保存する」というタスクを進めていると仮定しましょう。 細分化したとはいえ、実際の問題はもっと複雑です。着手する人が置かれた状況(= コンテキスト)によって上記の課題は様々な制約を持つこととなります。 言語的・形式的なコーディング規約に準拠していること Lint エラーがなく、フォーマッターが適用されている 命名規則などがチームの標準に従っている プロジェクト固有のアーキテクチャ・設計思想と整合していること モジュール間の依存関係の方向が正しいこと 既存の共通処理を正しく再利用している 本番運用に耐えうる非機能要件を満たしていること 適切なロギングや例外処理が実装されている パフォーマンスやセキュリティ上の懸念がない 実際のプロダクト開発では、明示的もしくは暗黙的にさらにさらに細粒度かつ多数の制約を持つこととなります。逆に、これらの制約条件を全て満たすようなコードは、main branch にマージできる受入可能なコードであると言えます。 「制約条件を全て満たす main branch にマージできるコード」は長いので、この記事では「解空間」と呼ぶことにしましょう。 この記事では、上記のような解空間に該当するコードを、コーディングエージェントに1回の指示で出力してもらうことをゴールと設定します。 コーディングエージェントへの期待と現実 さて、ではコーディングエージェントに「ユーザーエンティティを DB と firebase に保存して」と指示すれば、作業が全て完了して私たちは退勤できるのでしょうか? よほど成熟したコンテキストエンジニアリングが実践されていない限りは No でしょう。以下のような出力が考えられます。 機能要件は満たしているが、アーキテクチャルールに違反している 機能要件は満たしておりアーキテクチャルールも遵守しているが、ログなどの非機能要件が不足している そもそも機能要件を満たしていない 5回ほど、それぞれ異なるセッションで「ユーザーエンティティを DB と firebase に保存して」という指示を試してみたとしたら、次のように色々なコードが出力されるでしょう。 (図内の詳細なコード内容は重要ではなく、前述の制約条件を全て満たす出力の難しさが伝われば十分です) このような実験を10回、100回、1000回と繰り返すことを想像してみましょう。 解空間に相当するコードが生成されることもあれば、全ての円の外に位置するような、全ての制約を守れていないようなコードが生成されることもあるでしょう。このように、コーディングエージェントが生成する可能性がある全てのコード群を「探索空間」と呼ぶことにしましょう。 解空間の外に出力されるコードは全て、再度修正指示をしたり、手直しが必要ということになります。 LLM は確率的な回答を返すので、コーディングエージェントが生成するコードも同様に確率的な結果になります。では、少しこの確率について考えてみましょう。 AI が書くコードを「確率密度関数」として考えてみる コーディングエージェントを使った AI コーディングは、指示をインプットとして受け取って、コードをアウトプットとして出力する確率的な関数と考えることができます。 では、例として釣鐘型のグラフを考えてみましょう。尚、ここでの目的は確率自体の厳密性ではなく、AI コーディングを最適化するためのアイデアを得ることです。 先程は二次元だった探索空間が、ここでは一次元で表現されていることに注意 グラフの横幅が探索空間で、高さが確率密度を表しています。 探索空間: コーディングエージェントが生成する可能性がある全てのコード群 確率密度: 同じ指示を与えた時における、それぞれのバリエーションのコードの相対的な出力されやすさ このグラフに、先ほどの解空間をマッピングしてみましょう。 グラフ上での点の高さは確率密度(コードの相対的な出力されやすさ)を表すため、次のようなことが言えそうです。 全て制約を満たすコードが出力される確率 = 赤くハイライトされている面積 / グラフ全体の面積 今回は、一回の指示でこの解空間に該当するコードが生成されることをゴールと設定していますが、上記のグラフでは、全ての制約を満たす受入可能なコードが出力される確率はおおよそ 10% 程度しかありません。これでは、おおよそ10回に1回しか受入可能なコードを1発で生成してくれず、他の9回は修正指示が必要になります。うーん、これでは人間によるフィードバックが多く必要そうなので、作業の並列化などは難しそうです。逆に、これが8~9割ほどあれば、人間の関与はほとんど不要になりそうです。 グラフの例から、AI コーディングの最適化を 「グラフ全体に対しての赤い面積の割合を増やしていくゲーム」 だと考えてみましょう。赤い面積の割合が増えれば増えるほど、AI コーディングの精度や効率が上がって、修正指示に追われることがなくなるというです。 ここからは、どのようなアプローチによって赤い面積の割合を増やしていけそうかを考えていきます。 解空間の確率密度を上げる(赤い面積を増やす) 解空間外の出力を抑制する(赤くない部分の面積を減らす) アプローチ1: 解空間の確率密度を上げる まず最初に、解空間の確率密度を上げるというアプローチが考えられます。 これは、コーディングエージェントが出力するコードの精度を上げることを意味しており、グラフでいうところの赤い面積を増やすような考え方になります。 このアプローチは、指示の出し方を工夫するプロンプトエンジニアリングに始まり、エージェントにコンテキストを注入するコンテキストエンジニアリングなど幅広い手法で実現することができます。あまりにも多数の手法があるため、ここでは詳細には触れませんが、web で検索するなり Deep Research にかけるなりするとその時々のベストプラクティスが得られるでしょう。 手法は色々あれど、重要なのは 「解空間の定義(= 出力すべきコードが満たすべき制約条件)を明確にして、必要最低限の情報を必要な時にコーディングエージェントに伝える」 ということです。解空間の定義が曖昧だと、そもそもコーディングエージェントに正解を伝えることができませんし、情報が多すぎたりしても矛盾が発生したりコンテキストの逼迫による性能劣化が生じてしまいます。 解空間の定義 解空間の定義の重要性は AI コーディングに始まった話ではありません。コードを書くのが人間だろうが AI だろうが、品質基準は変わりません。「どういったコードを書くことを求められているのか」を細部まで説明できる、または必要になった時に参照できる状態ではないと、チームでの品質基準を満たしたコードを書くことは難しいでしょう。 それをそのまま指示に落とし込むことで、解空間の確率密度を上げることができます。 BAD:「ユーザーエンティティを DB と firebase に保存して」 GOOD:「User 型の引数を受け取ってユーザーエンティティを保存するメソッドを作成して。永続化は repository を介して DB と firebase に保存して。 firebase は外部サービスなので、UserRepository とは別に FirebaseRepository を作って。firebase でのユーザー作成はトランザクション境界の外になるので、共通モジュールとして提供されている logger を使ってリクエストが成功したログを残して。」 これの延長線にあるのが「仕様駆動開発」という手法だと私は理解しています。「何をどう作るか」を最初に定義することは、解空間の定義を明確にすることと考えることができます。 解空間のインプットコストとどう向き合うか 一方で、上記の例を見れば分かる通り、解空間が明示的になればなるほど、それをコーディングエージェントにインプットするコストが上がっていきます。毎回毎回こんな長文で指示をするのは億劫です。 そこで、よく知られているようにドキュメントやカスタムプロンプトの整備などを進めることによってコストを抑えることができます。ドキュメントをコーディングエージェントに渡したり、プロンプトを再利用可能にすることで文章を書くコストを下げることができます。 また、名付けを適切に行うことで文字数あたりの情報量を増やすという手段もあります。 先のプロンプトの例だと「Repository」という概念を DB への永続化にも firebase へのリクエストにも使ってしまっていますが、これだと「外部サービスとの通信の場合は〜」という if 文が指示もしくはドキュメントの中に必要になってしまいます。そこで、Ports & Adapters パターンに倣って、外部サービスとの接続には Repository ではなく Adapter という概念を用いるようにすると、if 文が1つ減ります。ドキュメントが増えれば増えるほど、この1つの違いは大きな差になります。また、ここでの名付けは、一般的であればあるほどモデルが学習しているため良いです。これを仮に Adapter ではなく独自に Gaiser( Gai -bu ser -vice)と名付けてしまうと、Gaiser の責務や概念を1つ1つインプットする必要性が生じてしまいます。 *1 コンテキストの増大 インプットするコストとは別に、コーディングエージェントが作業を進めるにつれてコンテキストが増大していくため、指示への注意が弱まってしまうという問題もあります。この問題への対応策として、コーディングエージェントが自律的に情報を収集・判断できるようにインデックスとなるドキュメントを用意しておいたり、必要なタイミングで情報を取得しにいくという段階的開示 (Progressive Disclosure) という手法が Claude Code では skill *2 という概念として実装されていたりします。 上記のような様々な取り組みによって、解空間の確率密度を上げることができます。次に、もう1つのアプローチについて考えてみましょう。 アプローチ2: 解空間外の出力を抑制する 「解空間の確率密度を上げる」というアプローチが、グラフでいうところの赤い面積を増やすという営みだったのに対して、こちらのアプローチは「赤くない部分の面積を減らす」という対照的な営みになります。 コーディングエージェントが作業完了した時に、機械的もしくは自己的なフィードバックを促すことで、基準に満たないコードだった場合に自律的に修正をしてもらおうというアプローチになります。確率的に生成されたコードを、linter などの静的解析や単体テストなどの決定的な処理によって検証することで、解空間外のコードを機械的に抑制することが可能です。 linter による抑制 例えば、linter で「ドメイン層からはインフラ層を参照してはならない」というルールを記述しておけば、コーディングエージェントがアーキテクチャ違反のコードを書いた瞬間にエラーとして弾くことができます。また、AST ベースでのルールを定義すれば 要素に alt text がついていることを強制したり 、 switch 文の中の case ラベルをアルファベット順に強制 したりと色々なことができます。 タスクの分解によるコード出力バリエーションの絞り込み また、タスクの分解というのも1つの手でしょう。1回の指示で出力するコードの量が少なければ少ないほど出力されるコードのバリエーションは限られるため、1回の指示で解空間に位置するコードを出力することが容易になるでしょう。 2つのアプローチの比較 では、上記の2つのアプローチをどのように使い分けるべきなのでしょうか?pros/cons を整理してみましょう。 アプローチ1: 解空間の確率密度を上げる 概要 指示やコンテキストを工夫し、最初から正解が出やすいようにする pros 直接的に出力精度が上がる 低コストで検証できる cons 確率的な出力を行う LLM の性質上、出力結果に一定のブレが生じることは許容する必要がある コンテキストの増大による性能劣化に弱い アプローチ2: 解空間外の出力を抑制する 概要 Linterやテストで出力を検証し、不正解を弾いて修正させる pros 決定的な処理によるフィードバックなので、確率に左右されない ただし、コーディングエージェントがコードを出力した後に lint や単体テストを実行させる仕組み作りが必要 個別のコーディングエージェントに依存しない cons 指示してから結果が得られるまでの時間がかかる コード出力 → 決定的な処理によるフィードバック → エージェントが自律的に判断して修正 複雑なルールについては実装が必要 それぞれで強みが異なるため、片方だけを使うというよりかは両方を組み合わせて活用していくのが良さそうですね。 終わりに ということで、今回は図を用いて AI コーディングの最適化について考えてみました。最適化が進んで、コーディングエージェントに1回の指示で受入可能なコードを出力してもらえる確率が上がると、複数のセッションを使った並行開発だったり Devin のようなフィードバックサイクルが長いエージェントをより効率的に利用することができるようになります。 コーディングエージェントが出てきてしばらくの時間が経ちました。人によって AI コーディングとの向き合い方は様々だと思いますが、ぜひ皆さんも年末というこのタイミングで改めて AI コーディングの可能性を探究してみてください。この記事が少しでも気づきや発見に繋がると幸いです。 また、キャディでは一緒に働く仲間を大募集中です。 どうやら最近 company deck が update されたようなので、興味があれば是非見てみてください。 speakerdeck.com CADDi Careers - 採用総合サイト careers.caddi.com CADDi Careers - エンジニア向け採用情報 careers.caddi.com *1 : 尚、Gaiser というアーティストの方がいらっしゃるようです *2 : https://platform.claude.com/docs/ja/agents-and-tools/agent-skills/best-practices#
こんにちは、D&A部の安本です。 この記事では私が日々AIと格闘する中で得たTIPSを紹介します。 なお、この記事は CADDi Tech/Product Advent Calendar 2025 19日目の記事です。他の記事についてもぜひご覧いただけると嬉しいです。 はじめに 課題 1.正解が定まらない内容が多いこと 2.望んだ回答が得られているかわかりづらいこと 解決策 1.回答してほしい対象を明確にしコンテキストを伝えること 2.専門用語を適切な文脈で使うこと 応用事例 図面解析の難しさ 我々の取り組み紹介 まとめ 最後に はじめに 私はAI for Applicationチームという、機械学習機能を業務アプリケーションに提供するためのチームに所属しています。そのために顧客の業務理解が必要な場面がたくさんあり、背景知識を得るために汎用LLM(以下AI)を活用しています。この記事では最近調査することが多い機械加工技術を対象に、つまづいた点とTIPS、活用事例をまとめていきます。 課題 私がAIに質問して機械加工技術について情報を得る上で、つまづいた点は大きく2つありました。まずそれらをご紹介していきます。 1.正解が定まらない内容が多いこと 例えば「ネジ穴の長さは最低どの程度必要か」が知りたい場合、リファレンスによって下図のように内容が異なります。 これらは記載の前提が異なるだけで、どれかが正解・間違いというわけではありません。しかし、AIから情報を得る際には自分が知りたかった内容かを確認する必要があります。 2.望んだ回答が得られているかわかりづらいこと AIから望んだ回答が得られないケースとして次の2パターンが考えられます。ひとつはハルシネーション(AIの嘘)です。よく知られている通り、AIは質問された内容に対して誤った回答をすることがあります。もうひとつは、課題1で記載したように、質問したい内容が伝わっていない場合があります。個人的には、AIから新しい知識を得る上では後者の影響が大きいと感じています。 ここで問題となるのは、望んだ回答が得られているかが判断が難しい点です。この原因のひとつは、自分にとって未知の知識のため正しさの判断が難しいこと、もうひとつはAIの回答に質問に対して直接的な回答でないことを多く含むことです。これはAIにうまく質問内容が伝わっていないために起こります。この場合、大抵はAIの中でも何を答えれば良いか具体的にはわかっていないため、AIは考えうる選択肢を網羅的に回答しようとします。これらの影響で「たくさん回答が出てきたけどよくわからなかった」という結果につながります。 解決策 このセクションでは上記課題の改善に役立ったTIPSをまとめます。 1.回答してほしい対象を明確にしコンテキストを伝えること よく言われることですが、対象を明確にし、コンテキストを伝えることはとても有効です。これらを伝えることで、自分の知りたい内容を指示できるので自分が欲しかった回答がピンポイントで得られるようになります。例として先ほどの「ネジ穴の溝長さは最低どの程度必要か」の場合について下図にまとめました。 このように書き換えることで、本当に欲しかった情報のみが回答されるようになります。 ※1:材質の一種。 ※2:タップ穴(=ねじ切り穴)サイズの呼称。 ※3:ねじ切り穴の別名。機械加工ではタップ穴と呼ばれることが多い。 ※4:穴の縁を筒状に立ち上げ、ネジの長さを確保する加工。 2.専門用語を適切な文脈で使うこと 専門用語を使うことで、効率的に質問の前提を伝えることができます。似た手法として「あなたは〇〇の専門家です」といった形で、特定の文脈をAIに与える手法があります。専門用語を使うことでさらに限定的な文脈情報を与えることができるため、より効果的になります。自分の体験としても、一般的な言葉を組み合わせて説明するよりも専門用語を使ってシンプルに指示した方が効果的な回答が返ってくると感じています。 この方法は、質問の具体性を上げることとよく似ています。例えばスマートフォンがネットにつながりにくい場合を考えてみましょう。ここでAIに質問する際に「スマートフォン」→「iPhone 12」と自分の使っている機種を伝えることで「結構古い機種を使っているんだな。iOSのサポートが切れていないか確認しよう」と考えられるようになります。専門用語を使うことでもこれと同様に、効率的に背景と課題を伝えることができます。 注意点もあります。ひとつは正しい文脈で用いる必要があることです。誤った文脈で専門用語を使うとかえって回答精度が下がる可能性があります。もうひとつは、AIの知識にない用語を使うとその解釈を誤ることです。特に社内用語にはご注意ください。普段当たり前に使っている言葉が一般的な言葉ではない場合があります。 応用事例 ここまでAI利用のコツを紹介してきましたが、これらは私たちが開発している図面解析の難しさに直結しています。その内容と関連する取り組みについてご紹介します。 図面解析の難しさ 図面とは製品の特徴や製作方法を説明するための画像情報です。このように書くと図面解析 = 画像解析というイメージになるかと思いますが、実際には以下のような課題が組み合わさった複合タスクです。 1枚の画像に込められた情報量が多く必要な情報のみを取り出す必要がある → 画像から何を読み取りたいか効果的に指定するプロンプトエンジニアリング 複数ファイル(画像と画像、画像と文書など)を組み合わせて指示する場合がある → 関連する情報を正しく引き当てる(RAGなど) 図面に書かれた文字・絵とAIの専門知識を組み合わせた読解が必要 → 画像情報と文字情報をセマンティック(意味的)に解釈・解析する 我々はこれらの情報群からユーザーが知りたい情報を適切に取り出すことにチャレンジしています。 我々の取り組み紹介 キャディは、以下のような武器を持っているため、この課題を解決できる数少ない企業の一つです。 製造業の調達プロセスに実際に入り込んだ経験知・ドメイン知識 製造業特化のLLMベンチマークデータセットを元にLLMの知識量を測定できる 様々な製造業データの解析技術・RAGを使った集計技術 筆者は上記のアセットを活用し、ドメインエキスパートの知識を借りつつ課題を整理して技術検証しています。ひとつひとつキャッチアップしながら進めるためとても泥臭いですが、着実に前に進められていると感じています。 なお、RAG技術については弊社竹本が別記事でまとめていますのでぜひご覧ください。 https://caddi.tech/2025/12/10/090000 またLLMベンチマークデータセット作成についても近日中に別記事でご紹介する予定です。 まとめ ご紹介した方法を使うことで自分が知りたい内容をAIに効率的に伝えることができ、コンテキストが揃わず望んだ回答が得られないことはグッと減ると思います。一方、AIは正しく質問を理解していても間違った回答を返すことがあるので、注意が必要です。 AIは、使い方には注意が必要ですが、適切に使えばとても便利なものです。私も顧客にAIプロダクトを提供するにあたり、製造業とAIプロダクトの両方を踏まえて議論する場面が頻繁にあり、両者を効率的に理解するためAIをたくさん活用しています。実はこのブログの画像もAIで生成した画像を少し手直ししたものです。 この知見がAIから知識を得たい方のお役に立てると嬉しいです。 最後に キャディでは様々なポジションで新しい仲間を募集しており、私が所属するAI for applicationチームでも大募集中です。新しい領域に飛び込むことが好きで、技術的な課題解決をしたい方には特にオススメです。ご興味あればぜひカジュアル面談にお越しください! https://open.talentio.com/r/1/c/caddi-jp-recruit/pages/78398
この記事は dbt Advent Calendar 2025 の16日目の記事です。 Data Management チームの森岡です。要らなくなったものをすぐに捨てられるデータ基盤を意識して日々開発しています。 この記事では、CADDi における SQL レビューを効率化するための CI の実践知について紹介します。 はじめに 生成 AI の台頭により、データエンジニアリングは大きく変わりつつあります。 CADDi では、AI エージェント「Devin」を Slack ワークフローに組み込むことで、Biz 職のメンバーでも自律的に dbt model の作成・修正が行える環境を構築しています。 この取り組みによって開発速度は大きく向上しましたが、その一方で 週30〜50件のPR という大量のレビュー負荷が発生しました。 SQL の作成コストがほぼゼロに近づく中で、ボトルネックは「実装」から「レビュー」へと移っています。 そのため現在の重要な課題は、いかに安全かつ高速にレビューを完了させるかです。 SQL レビューの辛さ SQL レビューにおいて、レビュアーの時間を奪う主な要因は次のような確認作業です。 実行可否の確認 (脳内コンパイルの限界): dbt model は Jinja を含むため、パッと見だけでは実行可能な SQL か判断できません。正確に確認するにはローカルでコンパイルを通す必要があり、地味に時間がかかります。 コーディングルールの遵守確認: 一貫性と保守性を保つため、さまざまな命名規則やコーディングルールが存在します。これらを毎回手動で確認するのは負担が大きく、レビュアーごとにチェック粒度が異なる「属人化」も起きがちです。 SQL ロジックの確認: SQLの構文が正しくても、作成者が意図した「ビジネスロジックとして正しいデータが出力されているか」の検証には時間がかかります。 他の dbt model への影響調査: 一つのモデル変更が下流にどう波及するか、毎回リネージグラフを辿って確認するのは骨の折れる作業です。特にモデル数が多いと、安全確認だけでかなりの時間を要します。 これらの課題を解決するため、CADDi では dbt の CI を整備しています。 開発フローの全体像 ※ エンジニアは、必ずしも Devin を経由する必要はなく、各自が好きな方法でPRを作成しています。以下は、簡単のため Devin を使った場合の開発フローを示したシーケンス図です。 sequenceDiagram actor 依頼者 participant Devin as Slackワークフロー(Devin) participant GitHub participant CI as GitHub Actions participant BQ_tmp as BigQuery(一時データセット) actor レビュアー participant BQ_prod as BigQuery(stg データセット) 依頼者->>Devin: Slackワークフローで<br/>dbt model作成/修正依頼 Devin->>GitHub: PR作成 GitHub->>CI: CI自動実行 CI->>CI: 変更されたmodelを検出 CI->>BQ_tmp: dbt build実行<br/>一時データセット作成<br/>(tmp_PR_XXX) CI->>GitHub: 「コンパイル済みSQL」と「一時データセットのリンク」をコメント CI->>依頼者: ビルド完了通知 依頼者->>BQ_tmp: 一時データセットでデータ確認 opt 修正が必要な場合 依頼者->>Devin: 確認・追加指示 Devin->>GitHub: PR更新 GitHub->>CI: CI再実行 end レビュアー->>GitHub: PRレビュー レビュアー->>GitHub: Approve & Merge GitHub->>BQ_prod: stg データセットにデプロイ Note over BQ_tmp: 7日後 BQ_tmp->>BQ_tmp: 一時データセット自動削除 CADDi の dbt CI の構成 ここからは、先ほどのシーケンス図に沿って、各 CI コンポーネントの役割を説明します。 ステップ1: PR作成(Devin による自動生成) 依頼者がSlackワークフローを起動すると、Devin AIが対話的にdbt modelを作成・修正し、GitHub PRを自動作成します。これにより、Biz職でも dbt model の変更が可能になります。 Slack ワークフローの起動画面 ステップ2: PR データセットのビルド 対応する課題: ① 実行可否の確認、③ SQLロジックの確認 GitHub でPRが作成されると、CI (GitHub Actions) が自動的に起動し、変更分のみを一時データセット ( tmp_PR_{PR番号} ) にビルドするフローを実行します。 ポイント PRに含まれる dbt model を単一のデータセット配下にまとめることには拘りました。これにより視認性が向上し、依頼者・レビュアーともに BigQuery コンソールから容易にデータを確認できます。 以下にその具体的な実装ステップを解説します。 2-1. 変更されたモデルの検出 まず、GitHub Actions では、 tj-actions/changed-files などを使ってPRで変更された dbt model を検出します。検出した モデル名は、後続の job に渡すために環境変数として保存します。 jobs : extract_dbt_models : runs-on : ubuntu-latest outputs : MODEL_NAMES : ${{ steps.extract-model-names.outputs.MODEL_NAMES }} steps : # ソースコードをチェックアウト - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # PRで変更されたファイルを検出 - name : Get changed files id : changed-files uses : tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47 with : files : | dbt/models/**/*.sql # ファイルパスからモデル名を抽出 # - dbt/models/hoge/model_name.sql → model_name - name : Extract model names from changed files id : extract-model-names run : | CHANGED_FILES="${{ steps.changed-files.outputs.all_changed_files }} " MODEL_NAMES="" for file in $CHANGED_FILES; do model_name=$(basename " $file" .sql) MODEL_NAMES="$MODEL_NAMES $model_name" done MODEL_NAMES=$(echo $MODEL_NAMES | xargs) # 先頭・末尾の空白削除 echo "Extracted model names: ${MODEL_NAMES}" echo "MODEL_NAMES=${MODEL_NAMES}" >> $GITHUB_OUTPUT 2-2. dbt build の実行 次に、抽出したモデル名を --select オプションに渡し、変更分のみをビルドします。 この際、--vars オプションを通じて「PR番号」と「PR対象モデル(変更したモデル)」も dbt に渡します。 # 変更されたモデルのみをビルド # - SELECT で指定したモデルは 一時データセットにリダイレクトして本番環境に影響を与えない - name: Build changed models id: dbt-build if: ${{ env.MODEL_NAMES != '' && env.PR_NUMBER != '' }} run: | echo "Building models: ${{ env.MODEL_NAMES }}" uv run dbt build \ --target prod \ --cache-selected-only \ --select ${{ env.MODEL_NAMES }} \ --vars '{"pr_number": "${{ env.PR_NUMBER }}","pr_target_models": "${{ env.MODEL_NAMES }}"}' working-directory: dbt/my_dbt_project ここで、vars引数と、dbt マクロのオーバーライドを組み合わせて、以下のような処理をしています。 一時データセットへのリダイレクト( generate_schema_name のオーバーライド) generate_schema_name マクロでは、以下のような処理をしています。 PR対象モデル: PR専用の一時データセット(tmp_PR_123 など)に作成 それ以外のモデル: 本番データセットをそのまま参照 {% macro is_pr_mode() %} {{ return (var( " pr_number " , none) is not none) }} {% endmacro %} {% macro generate_schema_name(custom_schema_name, node) -%} {# 変数を取得 #} {%- set node_name = node.name -%} {%- set default_schema = target.schema -%} {%- set pr_number = var( " pr_number " , "" ) -%} {%- set pr_target_models = var( " pr_target_models " , "" ).split() -%} {%- if is_pr_mode() and node_name in pr_target_models -%} {# PRモード かつ 現在のモデルがPR対象に含まれている場合はPR固有のスキーマを使用 #} {{ " tmp_PR_ " ~ pr_number }} {%- elif custom_schema_name is not none -%} {# config で schema が設定されている場合はそれを使用 #} {{ custom_schema_name | trim }} {%- else -%} {# デフォルト処理: target.schema をそのまま使用 #} {{ default_schema }} {%- endif -%} {%- endmacro %} エイリアス名の衝突防止( generate_alias_name のオーバーライド) dbt では、alias を使うことで、モデル名とは異なるテーブル名を指定できます。つまり、以下のようなテーブルを作成することができます。 dataset_1.hoge_table dataset_2.hoge_table しかし、PRデータセットでは、同じエイリアス名を持つテーブルが複数存在すると衝突してしまいます。 そこで、PRデータセットでは、エイリアス名を使用せず、必ずデフォルトのモデル名を使用します。 {% macro generate_alias_name(custom_alias_name=none, node=none) -%} {# 変数を取得 #} {%- set node_name = node.name -%} {%- set pr_target_models = var( " pr_target_models " , "" ).split() -%} {%- if is_pr_mode() and node_name in pr_target_models -%} {# PRモード かつ 現在のモデルがPR対象に含まれている場合はデフォルトエイリアスを使用 #} {{ node_name }} {%- elif custom_alias_name -%} {# config() で alias が設定されている場合はそれを使用 #} {{ custom_alias_name | trim }} {%- else -%} {# デフォルト処理: node.name をそのまま使用 #} {{ node_name }} {%- endif -%} {%- endmacro %} Incrementalモデルは常に差分更新( is_incremental のオーバーライド) Incremental モデルを初回実行する場合、通常は全件ビルドが行われるため、コストが高額になりがちです。 また、PRデータセットの主な目的は「SQLロジックの動作検証」であり、必ずしも全期間のデータを処理する必要はありません。 そこで、PRデータセット下では is_incremental() が常に true を返すようにマクロをオーバーライドし、強制的に差分更新モードとして動作させます。 {% macro is_incremental() %} {# PRデータセットでは常にtrueを返し、常に差分処理を行う #} {% if is_pr_mode() %} {{ return ( true ) }} {% else %} {{ return (dbt.is_incremental()) }} {% endif %} {% endmacro %} このオーバーライドにより、PRデータセットでは初回実行であっても is_incremental() が true となり、モデル内で定義された期間フィルタ(例:直近7日間)が自動的に適用されます。これにより、スキャン量を必要最小限に抑え、コスト削減ができます。 select * from {{ ref( " hoge_models " ) }} where true {% if (is_incremental()) %} -- PRデータセットでは、初回実行でもこのフィルタが適用される and jst_date >= date_sub( current_date (), interval 7 day) {% endif %} ポイント: defer 機能の検討 ここまで読むと、dbt に詳しい方であれば「dbt の defer 機能を使えば、よりシンプルに実現できるのでは?」と感じるかもしれません。 実際に defer の採用も検討しましたが、本構成では「PR で作成されるテーブルは単一のデータセットに集約する」という設計方針を採っています。その場合、defer を使ったとしても generate_schema_name などの macro オーバーライドが不可避でした。 defer を用いることで、変更のあったモデルの特定を dbt に委ねられるというメリットはありますが、その一方で、manifest ファイルの管理や、参照先が暗黙的に切り替わることによる挙動の分かりにくさといった運用コストも発生します。 これらを総合的に考慮した結果、本ケースでは defer の導入による効果は限定的と判断し、明示的に制御できる macro ベースのアプローチを採用しています。 ステップ3: コンパイル済みSQLと PRデータセットのリンクをコメント ビルド成功後、dbt がコンパイルした SQL を PR に自動コメントします。 # PR で作成された dbt の compiled された SQL を pull request のコメントに追加 # partial parsing を使うために vars を同じにする - name : Get compiled dbt model paths id : compiled-paths if : ${{ env.MODEL_NAMES != '' && env.PR_NUMBER != '' }} run : | dbt_model_paths_json=$(uv run dbt ls \ --quiet \ --target prod \ --no-populate-cache \ --resource-type model \ --select ${{ env.MODEL_NAMES }} \ --vars '{"pr_number": "${{ env.PR_NUMBER }}","pr_target_models": "${{ env.MODEL_NAMES }}"}' \ --output json \ --output-keys original_file_path \ ) dbt_compiled_paths=$(echo "$dbt_model_paths_json" | jq -r -s 'map("dbt/my_dbt_project/target/compiled/my_dbt_project/" + .original_file_path) | unique | join(" ")' ) echo "dbt_compiled_paths:: ${dbt_compiled_paths}" # 出力は1行になるため、シンプルな echo で設定できる echo "COMPILED_PATHS=${dbt_compiled_paths}" >> $GITHUB_OUTPUT working-directory : dbt/my_dbt_project - name : Comment compiled SQL on PR uses : actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 if : steps.compiled-paths.outputs.COMPILED_PATHS != '' env : COMPILED_PATHS : ${{ steps.compiled-paths.outputs.COMPILED_PATHS }} PR_NUMBER : ${{ env.PR_NUMBER }} with : github-token : ${{ secrets.GITHUB_TOKEN }} script : | ##### 長いのでコア部分のみ抜粋 ##### const fs = require('fs'); const path = require('path'); const compiledPaths = process.env.COMPILED_PATHS.split(' '); let bodySQLs = `## Compiled SQLs for changed models:\n\n`; for (const fullPath of compiledPaths) { const fileName = path.basename(fullPath); let detailBlock; try { const sql = fs.readFileSync(fullPath, 'utf8' ); detailBlock = `<details><summary>✅ $ { fileName } </summary>\n\n\`\`\`sql\n$ { sql.trim() } \n\`\`\`\n\n</details>\n`; } catch (error) { detailBlock = `<details><summary>🚫 $ { fileName } </summary>\n\n\`\`\`\nError : $ { error.message } \n\`\`\`\n\n</details>\n`; console.error(`Error reading file $ { fullPath } : $ { error.message } `); } bodySQLs += detailBlock; totalLength += detailBlock.length; } ##### 長いのでコア部分のみ抜粋 ##### 最終的に、以下のようなコメントが作成されます。これにより、BigQueryでそのまま実行できるコンパイル済みSQLをワンクリックで取得できます。 GitHub コメントの例 ステップ4: AIレビュー 対応する課題: ② コーディングルールの遵守確認 CI実行と並行して、 devin_coding_style_review ラベルが付いた PR に対して Devin AI が自動的にコーディングスタイルをレビューします。 レビュー観点の明文化 レビューの属人化を防ぐため、以下のドキュメントを整備し、Devin に読み込ませています。違反があれば、PR コメントで指摘されるので、それをもとに Devin が修正をします。 review_check_list.md : レビュー観点のチェックリスト coding_style_guide.md : コーディングスタイルガイド ステップ5: データ確認とフィードバック 対応する課題: ③ SQLロジックの確認 ビルド完了時に、PR 作成者の Slack スレッドに自動通知します。 依頼者は、Slack通知を受け取った後、BigQueryの一時データセット ( tmp_PR_{PR番号} ) で実際のデータを確認します。もし意図したデータと異なる場合は、Devinに追加指示を出してPRを更新できます。この修正ループにより、レビュアーに渡る前にデータの品質を高められます。 Slack ワークフローのコメント例 ステップ6: レビュー&マージ レビュアーは、以下を確認してPRをレビューします: コンパイル済みSQL : PRコメントで実際のSQLを確認 実データ : BigQueryで一時データセットのデータを確認 Devinレビュー結果 : AIレビューのコメントを確認 これらの情報が揃っているため、レビュー時間を大幅に短縮できます。承認後、PRをmainブランチにマージします。 ステップ7: STG デプロイ - Post-Merge 対応する課題: ④ 他のdbt modelへの影響調査 PR がマージされると、自動的にSTG環境で dbt build が実行されます。この際、対象モデルに + を付与することで、下流モデルも含めた依存関係の検証を行います。 もし build が失敗した場合は Slack に通知されます。これにより、本番デプロイ前に問題を確実に検出できます。 なぜ CI(Pre-Merge)でやらないのか? 本来であればマージ前の CI で全量チェックを行うのが理想ですが、以下の理由から Post-Merge での実施としています。 時間とコスト: 下流モデルまですべてビルドすると、CIの実行時間とコストが膨大になるため。 低頻度 : 完璧ではありませんが、Devinが作成するPRは依存関係も含めた修正PRをしてくれる可能性が高かった。 リカバリの猶予: 翌日の Prod ビルド(本番更新)までに修正が間に合えば実害はない。 まとめ 本記事では、CADDi における SQL レビューを効率化するための dbt CI の実践知 を紹介しました。 この仕組みにより、レビュアーは以下の形でレビューを行えます。 コンパイル済み SQL は PR コメントを見るだけ ビジネスロジックの初期検証は依頼者側で実施 コーディングルールは AI レビューで担保 下流影響は Post-Merge で自動検出 結果として、SQL レビューの負荷を大幅に軽減できました。 生成 AI が作成する PR のレビューに悩んでいるデータエンジニアの方々の参考になれば幸いです。 今年は、 CADDi Tech/Product Advent Calendar 2025 でも、マルチテナントSaaSで個別のBigQueryリソースを作成する際にハマった問題とその解決策について書きました。こちらも読んでいただけると嬉しいです。 TerraformのState肥大化を解消!Terramate で実現する マルチテナント SaaS のデータ基盤
Control Plane部の 小森 ( @littleforest12 )です。 こちらは キャディ株式会社のアドベントカレンダー の16日目の記事です。 最近、社内でこれまでの中では比較的大規模な開発プロジェクトのリードアーキテクトを拝命しまして、奔走しています。 プロジェクトはようやく立ち上がってきたところですが、アーキテクトとしての考えをメンバー向けに発信しようと書きはじめたことを、せっかくなのでTech Blogにしたためたいと思います。 背景 私たちは「 製造業AIデータプラットフォーム CADDi 」としてプロダクトを展開しており、プラットフォームの一部として CADDi Drawer や CADDi Quote といったアプリケーションを提供しています。 もちろん、これらは開発から運用まですべてが内製です。 通常は、各アプリケーションやプラットフォーム横断で、おおむね4〜8名程度のチームに分かれて新機能の開発や改修といった開発プロジェクトを日々進めています。 今回は複数チームの担当領域にまたがる開発が必要で、チーム横断でのアーキテクチャ設計や課題解決が必要となりました。 そこで、初期段階から関わっていた私がリードアーキテクトを担当することになりました。 私自身はいつもアーキテクトをやっているわけではありません。 ソフトウェアエンジニアとして、状況に応じた役割を担っていますが、今回は久しぶりに(そしてキャディにジョインして初めて!)アーキテクトの帽子をかぶることになりました。 これまでも、開発現場でアーキテクトやそれに準ずる立ち位置で仕事をしたことは何度かあります。 そのたびに悩んでいたのが「 自分が考えたアーキテクチャを、どうやってみんなに受け入れてもらうか? 」ということです。 何度かこの立場を経験することで、自分なりのアプローチが見えてきました。 本記事の前半ではこの悩みの背景を説明し、後半では私がとっているアプローチを紹介します。 先に核心を知りたい方は「 腹をくくり、最後まで伴走しきってこそ、アーキテクト 」から読みはじめてください。 目次 背景 アーキテクトはすべてを見通せない 要求/要件レベル(What)の不確実性 技術レベル(How)の不確実性 不確実性にどうアプローチするか? 腹をくくり、最後まで伴走しきってこそ、アーキテクト アーキテクトはスーパーマンではない 1. アーキテクチャに至った考えの道筋を共有する 2. 各チーム/メンバーに細部の検討を委譲する 3. トップダウンで押しつけない・こだわらずに提案を許容する 「共創」で不確実性を乗り越える 最後に アーキテクトはすべてを見通せない アーキテクチャとは、「要求事項をシステムとして実現する際の、その方式の屋台骨」とも言えるわけですが、最初に考えたアーキテクチャがすべての状況を想定できるわけではありません。 私たちが未来を見通せない以上、どんなアーキテクチャも、初期段階では本質的に不確実です。 この不確実性には、2種類の要因があります。 それは、「 要求/要件レベルの不確実性 」と「 技術レベルの不確実性 」です。 アーキテクチャ策定をすべきプロジェクトの初期段階では、この両方の不確実さを受け入れねばなりません。 要求/要件レベル(What)の不確実性 多くの皆さんがご存じの通り、ITシステムやプロダクトに対するユーザーの要求をきちんと言語化するのは困難です。 「ユーザーは何がしたいのか、そのためにシステムは何を実現すべきなのか」というWhatがはっきりわからない、というのが「要求/要件レベルの不確実性」です。 ウォーターフォール開発の考え方では、要求や要件を明確にしてからアーキテクチャを考えるべきですが、多くのユーザーにサービスを提供するプロダクト開発では、アーキテクチャ策定段階ですべての要求/要件を明確にするのは不可能です。 また、プロダクトは常に進化していくものです。 うっすらと見えている近い将来の構想に備えた「拡張性」という非機能要件も求められますが、うっすらとしか見えていないため「拡張性」という要件の具体的な言語化が困難で、当然それを踏まえたアーキテクチャ設計も困難になります *1 。 技術レベル(How)の不確実性 一方、要求/要件が確実であっても、その実現手段(How)がはっきりしないというのが「技術レベルの不確実性」です。 たとえば、典型的な要件で類似の開発事例も多いのなら、技術的な不確実性はほとんど無いでしょう。 古い例ですが「Web-App-RDBの三層構造で、SpringFrameworkを使えばOK!」程度の決めでも、なんとかなります。 こういった方針を決めておき、「残りは開発チームの皆さんでよろしく!」と、次の現場へ颯爽と去って行くアーキテクトはカッコいいかもしれません。 しかし、そんな理想通りにコトが進む開発現場を、私は見たことがありません。 なにがうまくいかないのでしょうか? 初期段階で、すべてを見通せるわけがないからです。 特に、多くの顧客に対して提供されて運用中のサービスに対する広範囲の修正や機能追加は、技術的に大きな不確実性を伴います。 残念ながら、既存システムの設計や振る舞いが、すべてきちんとドキュメント化されていることは稀でしょう。 そのため、アーキテクチャ策定段階で、それ以降の設計レベルの整合性を確認しきるのが困難です。 また設計が進んでから、隠れていた要件が発覚することも多いでしょう。 見落としていた要件に対応するため、アーキテクチャの見直しが必要になるケースもあるかもしれません。 アーキテクチャや基本設計レベルでは整合性が取れているように見えても、より詳細な設計や実装を進めていったときにはじめて明らかになる不整合はどうしても発生します。 たとえば、次のような状況を経験した人は多いのではないでしょうか。 機能Aの振る舞いを変更した結果、本来予定していなかった機能Bの振る舞いも同じように変更しないと、プロダクトとしての整合性がとれなくなる 新しい責務をコンポーネントAに追加したが、よくよく考えを進めると、その責務を持つべきなのはコンポーネントBの方だった チームの責任範囲に閉じた不整合ならば、チーム内の設計変更で済むので、大きな影響はありません。 しかし、機能配置や責務の見誤りといったレベルの問題は、チーム境界を跨がる問題に発展します。 本来ならば、アーキテクチャのレベルで軌道修正するのが筋だったりするケースも多いでしょう。 さらに、ウォーターフォール開発で工程毎に担当会社が違うようなケースはもっと大変です。 手戻りのコミュニケーションコストの方が大きいので、釈然としない思いをしつつ、現場レベルでの辻褄合わせをせざるを得ないケースも出てきます。 (そして「なんでこれを見落としたんだ!」と、人知れずアーキテクトは恨みを買います!) 不確実性にどうアプローチするか? この不確実性に対処するための一般的なアプローチはアジャイル開発です。 開発サイクルを高頻度にまわし、ユーザーからのフィードバックを受けることで不確実性から受ける影響を最小化するというアプローチを取ります。 小さな粒度のリリースを繰り返すため、アーキテクチャも漸進的に改善でき、技術的な不確実性も下げられるでしょう。 特に新規開発では、このアプローチはかなり有効です。 一方、アジャイル開発的アプローチが取りにくい状況もあります。 その1つが、既に多くのユーザーに使われているプロダクトに対して、既存機能に対する横断的な機能追加や変更を行う場合です。 現在私が取り組んでいるプロジェクトが、まさにその状況です。 私たちの展開するプロダクトのお客様は企業であり、場合によってはお客様側の業務にがっつり組み込まれて利用されているケースもあります。 そのため、利用者が増えれば増えるほど、すでに運用中の機能全体に影響するような変更を、そう簡単にリリースできなくなります。 本来ならば、ベータ版などを提供して反応を見ながら改善するといった手法をとりたいところです。 単発の新機能であればそれがしやすいですが、横断的な機能ではそうもいきません *2 。 下手をすると、お客様の企業活動に影響を与えるレベルの障害を発生させかねないためです。 腹をくくり、最後まで伴走しきってこそ、アーキテクト ここからが本題です。 このようにさまざまな不確実性がある状況で、プロジェクトを前に進めるために、アーキテクトはどのように振る舞うべきなのでしょうか。 最初に考えた案通りに最後まで走りきれることはむしろ少ないにも関わらず、初期段階では、多くの選択肢のうち、正解が明らかな状況は希です。 アーキテクトは無数の選択肢から、整合性をもって目的を達成できる組合せを複数生み出し、そこからさらにより良いと信じられる選択肢を選び取って最後は「 それに賭ける! 」と腹をくくらなければなりません。 まさに、目をつぶって針の穴に糸を通すような困難なことです。 私はアーキテクトを担当する以上、「 設計・実装・テスト・リリースまで開発プロジェクトに伴走し、初期に考案したアーキテクチャが実効性を持って形となるまで責任を持つべき 」だと考えています。 その過程では、チーム間での仕様を調整したり、全体に関わる課題の抽出や解決といった活動を継続的に行わなければなりません。 アーキテクトはスーパーマンではない とはいっても、アーキテクトもただの人間です。スーパーマンではありません。 プロジェクト規模が大きくなると細部すべてに目を配れませんし、責任感が勝ってすべての問題を一人で解決しようとすると、自分自身がボトルネックとなってプロジェクトを止めてしまいます。 「自分があと10人いれば・・・」なんて思ったことがある人も多いでしょう。私もよく思います。 では、どうすればよいのでしょうか。 私がこれまでの経験から得たポイントは、次の3つです。 考えの道筋を共有すること 細部の検討は各チームやメンバーに委譲すること 自分の考えにこだわりすぎず、変更を許容すること それぞれ説明しましょう。 1. アーキテクチャに至った考えの道筋を共有する 自分一人ではすべてを解決できないので、できるだけ同じ考えを共有し、同じ考えに基づいて解決できる人を増やす作戦です。 いわば、アーキテクチャ思考の水平スケーリングです。 今回は、初期アーキテクチャを考えるにあたり、プロジェクトに関わる各チームの主要人物に協力してもらい、アーキテクチャ検討チームとして一緒に考えました。 既存機能については、当然各チームのメンバーの方が詳しいですし、もとより私も今のシステムを細部まで把握できているわけではありません。 それぞれから設計案を出してもらいつつ、議論を重ねて私がそれを取りまとめるやりかたにしました。 アーキテクチャ検討チームとしてのアウトプットは、以下のようなものになりました。 全体の概念モデル 要求に対する理解を図示し、要件へと落とし込むための入り口となるもの。 ER図として表現。 ざっくりアーキテクチャ 各チームが担当する既存コンポーネントに対する責務分担 おおまかなレベルのAPIや想定シーケンスの提示 検討過程で抽出した技術課題の一覧・要件の一覧 アーキテクチャ策定過程で解決したものもあるし、各チームに解決を委ねたものもある 課題によっては「○○チームと××チームの間で解決してください」と委ねるケースもある 要件はPdMと話して「やらない」と決めたことやその理由も明らかにする 検討過程では、PdM(プロダクトマネージャー)が提示した要求事項を、PdMとの議論を重ねて具体的に深掘りしたり、不明確だった要件の具体化といった作業も行いました。 コミュニケーション量はかなり多くなりましたが、議論を経ることでアーキテクチャ検討チーム内の考え方は自然と揃ってきます。 この時点で、同じくらいの解像度でアーキテクチャを理解している人が複数できるので、一人でアーキテクチャを考えてドキュメントだけを通して伝えるよりも、後々の効率が格段に良くなります。 「アーキテクチャ設計」に対するアウトプットイメージは人によって違うかもしれませんが、ここまでのアウトプットをドキュメント化すれば、「アーキテクチャ設計完了」と言えるかもしれません。 しかしこのままでは、これ以降明らかになるであろう、さまざまな不確実性には対処できません。 ここまでは下準備。「アーキテクチャ案」と呼べるのがせいぜいで、これから先が本番です。 2. 各チーム/メンバーに細部の検討を委譲する ここまでのアウトプットを元にして、「1Day camp」と称した開発メンバー全員が集まる場を設定し、理解度のさらなる平準化を図りました。 午前中は概念モデルやアーキテクチャの解説、午後は実際の既存画面を全員で確認しながらPdMも交えて細部の要件の確認や議論を行いました。 特に午後のパートはアーキテクチャを踏まえた上で、「システムとしてユーザーにどのような振る舞いを見せるか?」「その振る舞いは今想定しているアーキテクチャで実現できるのか?」といった実効性のある議論ができました。 半日ですべての課題を解決はできませんが、「何が課題なのか?」「課題を解く前提とすべきアーキテクチャはどんなものか?」という目線が揃えられれば、大きな前進です。 それ以降の検討は、関係するチームに委譲することができます。 実際、この「1Day camp」を経ることで、各チームに細部の検討を委譲できるようになり、並列度があがって一気にスピードが増しました。 もちろん、アーキテクトとしての私は、可能な限り各チームの議論に参加したり、検討結果をチェックしたりして、当初の思想から大きなズレが生じないようなコントロールをしていきます。 3. トップダウンで押しつけない・こだわらずに提案を許容する そして大事なのが、初期のアーキテクチャにこだわりすぎないことです。 委譲した先で冒頭でも説明した整合性や要件の見落としなどが発覚すると、初期のアーキテクチャ案の見直しを迫られることもあるでしょう。 仮に「アーキテクチャ」という成果物「だけ」を受け取ったエンジニアからすると、その考え方に十分同意できずに「こんなアーキテクチャはイケてない!」と思ってしまったり、「自分だったらこうする!」と、別の方向性を思いついたりする人もいるはずです。 アーキテクトにとって、せっかく考えたアーキテクチャ通りに開発が進まないのは、あまり気分が良くないものかもしれません。 せっかく時間をかけて決めたことが蒸し返されるのは、非効率なコミュニケーションに見えることもあります。 それに対してアーキテクトが「いやいや、とにかく既に決めた事だから、この方針で作ってくれ!」という意志決定を押し通すと、どうなるでしょうか。 短期的にはコミュニケーションコストをかけずに済むので、良いかもしれません。 しかし、長期的には各エンジニアがそのプロジェクトでこの先発生する課題を自力で解決できず、どこかでアーキテクトにそのしわ寄せが来ます。 もし、その時点でアーキテクトがプロジェクトから離れていたら、当初のアーキテクチャは結局崩壊するでしょう。 つまり、不確実性の高いアーキテクチャを強制するのは結局失敗するのです。 現代のコンピュータシステムは、最終的にすべての振る舞いをコードで表現しなければ、期待通りに動作しません。 アーキテクチャや基本設計の段階で細部に至るまですべての振る舞いを規定できない以上、最終的には細部の設計を考えたりコードを書いたりする人の裁量に委ねなくてはならない部分がでてきます。 アーキテクチャなどの方針に納得できていない状況で、アーキテクチャに起因する想定の課題に直面したとき、エンジニアは自分の考えでそれを解決するのが困難になります。少なくとも、圧倒的にペースは落ちるでしょう。 開発が進んだ状況でこうなってしまうと、リカバリは困難です。 ですので、自分の考えとは違う設計案がメンバーから提示されたとき、明らかな誤りがないのであれば、私は一旦はそれを受け入れ、その人に引き続きその案を主体的に検討することをお願いするようにしています。 結果、その人が提示した案が採用され、より良い設計とできれば互いにとってHappyですし、最終的に提示された案がボツとなって当初の案に戻ったとしても、無駄ではありません。 その人は自分自身の案を考え、関係者と議論した結果、元の案に落ち着いたので、納得度が違います。 「与えられた方針の通りにつくる」のではなく、「自分で考えて至った結論に基づいてつくる」ので、以降の工程で発生する予期せぬ課題に対しても、より自律的に解決のための行動を取ることができるでしょう。 このようなやり方は、目先は無駄なコミュニケーションを取っているように見えます。 一見遠回りに見えますが、このほうが結果的にプロジェクト全体への良い影響(レバレッジ)が大きくなるのです。 このため、「アーキテクトが初期の設計にこだわらず、メンバーに細部の検討を委譲しつつも一緒に考える」ことで、プロジェクトメンバーとの目線・コンテキストを揃えることができ、アーキテクトがボトルネックとなることを防げます。 「共創」で不確実性を乗り越える アーキテクトが以上のような行動を取ることで、「アーキテクトが考えたアーキテクチャ」ではなく、「 みんなで考えたアーキテクチャ 」となることを私は期待しています。 アーキテクトが最初に提示するのは、その叩き台にすぎません。 さて今回、このアプローチがうまくいくのかどうか。 これは私にとっても挑戦であり、結果がわかるのは、しばらく先です。 最後に キャディでは、一緒に働く仲間を募集中です。 本記事で紹介したようなアプローチが取れるのも、それを受け入れて自律的に考え、行動に移せる頼もしい多くの仲間がいるからこそです。 そんなキャディでのソフトウェア開発に興味を持った方は、ぜひ採用サイトをご覧ください。 カジュアル面談 もお待ちしています! CADDi Careers - 採用総合サイト careers.caddi.com CADDi Careers - エンジニア向け採用情報 careers.caddi.com *1 : YAGNI原則に則って切り捨てたい気持ちも湧きますが、いろんな事情があるのです。 *2 : 当社の開発環境はdev-stg-prodとステージが分かれてはいますが、小さな粒度のリリースを前提としているため、「顧客に試験提供して、うまくいかなかったら、やり直し!」ということができるような複数面の環境を用意できているわけではありません。また、本プロジェクトで提供する機能を顧客に使ってもらうには、顧客側も相応の準備が必要であるため、アジャイル的手法が取りにくいという背景もあります。
この記事は CADDi Tech/Product Advent Calendar 2025 14日目の記事です。 Executive Summary 生成 AI アプリで評価プロセス改善 PoC をした 評価制度をアセット化し、生成 AI ツールを組み合わせることによって、評価プロセスを支援した 「メンバーの思考の整理」「メンバーからマネージャーへのコミュニケーションの改善」というポジティブな効果が得られた (画像は実際のシステム、入力されているテキストは架空の人物・チーム・業務) はじめに キャディのエンジニアリングマネージャーの橋本です。 突然ですが、この記事を読んでくださっている皆さんにひとつ聞きたいことがあります。あなたの会社にある評価プロセス、たとえば目標設定、月次振り返り、期末の自己評価、評価フィードバックなどは、あなたの仕事にとってポジティブに働いているでしょうか? 評価プロセスと業務はつながっていないこともある この質問に対する回答は、人によってグラデーションが出やすいものだと思います。「もちろん Yes さ、目標を設定することで集中できるし、フィードバックによって最高の成長機会を得られているよ!」という人もいれば、「うーん、どちらかといえば No かな。評価って時間を取られるけど、あんまり業務に対してポジティブに働いている実感はないんだよね」という人もいると思います。 私自身、エンジニアリングマネージャーになる前は典型的な後者のタイプで、正直に白状すると「評価って面倒だな」と思いながら、会社の評価プロセスに従って毎期の評価を受けていました。 そんな中、ここ1~2年は生成 AI の登場によって、エンジニアリングにおける設計やコーディング、サービス運用における働き方は大きく変化してきました。 今や生成 AI は設計の頼もしい相棒ですし、コーディングに関しては業務のあり方そのものが変わりつつあります。サービスの運用においても、生成 AI のポテンシャルはすでに広く知られているところであります。 生成 AI がエンジニアリングを変える、マネジメントは? エンジニアリングマネージャーになった私にとっては、生成 AI のマネジメント領域への適用については非常に興味深いものでした。特に評価の領域については、前述のとおり、エンジニアリングマネージャーに就任する前からぼんやりとした課題感を持っており、取り組んでみるのにちょうどよい題材だと考えました。 この記事では、エンジニアリングマネージャーというエンジニアでありかつマネージャーである私が、マネジメントの課題を生成 AI とエンジニアリングの力で解消しようとした、そんな試みを紹介します。 課題領域 どうして評価プロセスがしばしばエンジニアにとって自分の仕事を支援してくれるものと感じられないのでしょうか? 評価プロセスはエンジニアリングより手触り感が低い 要因はいくつかあるとは思いますが、ひとつの大きな仮説として、エンジニアリングで取り扱う題材にくらべて評価プロセスの “手触り感” が薄い、ということを仮説として立てました。 すなわち、エンジニアリングの業務は実行される行為を思い浮かべることが容易であるのに対し、評価プロセスにおける記述は実際の行動を想像することが難しいという特徴があり、エンジニアリングの業務と結びつけられていないのではないか、という仮説です。 実際、上の画像にキャディの評価基準からの抜粋と、実際の業務でありがちなタスクを並べてみましたが、実際の業務タスクと比べ、評価基準が抽象的で手触り感がないことが確認できると思います。 では、なぜ評価プロセスはこんなにも手触り感がないのでしょうか?私はその原因を、会社の外と中のそれぞれに分けて考えてみました。 評価制度の手触り感を一般知識と会社特有の観点に分解 まず、会社の外側にある理由として、「評価制度」に関する一般知識が説明から省かれがちであるこということが挙げられます。 たとえば、キャディ Tech では、「5軸に沿ったコンピテンシー評価」を導入しています。評価制度のプロであれば、この説明文からこの評価制度がどういう設計なのか、すなわち コンピテンシー評価とは何か 他の評価手法、例えば成果評価とは何が違うのか どのような課題を解決する評価制度なのか メンバー、マネージャーがそれぞれ注意するべきことがなにか ということが想像できることでしょう。(まるでソフトウェアエンジニアがマイクロサービスアーキテクチャについて生き生きと語るときのように!) しかし、実際にはエンジニアは評価制度のプロではないので、これらの知識を必ずしも有しているわけではありません。 また、会社の内側にある理由として、評価制度はその会社固有の価値観や信念を取り扱うものであるから、というものがあります。評価制度は、その会社にどのような人が集まるか、どのような行動が促進されるかに強い影響力を持ちます。そのため、より良い評価制度を設計すればするほど、評価制度にはその会社の個性が出やすいという特徴があります。別の言い方をすれば、同じ「ソフトウェアエンジニア」という職種であっても、ある会社と別の会社では評価体系が全然違うということすらありえるのです。 結果的に、評価制度はしばしばハイコンテキストなものになり、過去の経験による知識による解釈を難しくします。その会社に新しく入社したメンバーにとって解釈することが難しいのはもちろん、ずっとその会社にいたメンバーにとっても、事業の特性やフェーズが変わることによって評価制度はしばしば影響を受けるため、正しく評価制度を理解しつづけることは難しいという特徴があります。 解決領域 既存のアプローチ メンバーとマネージャーの間の対話によって知識差分を埋めてきた 課題領域に対する従来のアプローチでは、業務を深く理解しているマネージャーが、メンバー一人ひとりに合わせたコーチングを通じて会社全体に理解と納得を作り上げていくというアプローチが取られてきました。 この方法は、うまくいくケースがある一方で、以下のような課題があります 効率性の課題:マネージャーがメンバーのことをよく知る必要があり、メンバー・マネジメント双方のリソースを使う 実効性の課題:マネージャーに高い対人スキルと業務理解を要求するため、実効性にばらつきがある 従来のマネジメントは効率性・実効性に課題があることも ちょうどこの課題は冒頭に紹介した、 評価って時間を取られる → 効率性が低い あんまり業務に対してポジティブに働いている実感はない → 実効性が低い という実感とも繋がってきます。 生成 AI を統合したアプローチ - 概要 マネジメントの代わりに生成 AI と社内資料の整備を組み合わせる 既存のソリューションの課題に対し、私は生成 AI を活用することで「一人ひとりの状態に合わせたコーチング」を実現し、評価制度に関する一般知識、およびメンバーごとの理解度の差分を埋められるのではないかと考えました。 ただし、生成 AI では会社固有の知識を取り入れることができないため、合わせて生成 AI がアクセス可能なアセットを作ることを実施しました。 「生成 AI がアクセス可能なアセット」とは 生成 AI がアクセス可能なアセットとはなんでしょうか?これはただの社内ドキュメントとは違うのでしょうか? 生成 AI の活用を考える際に、最も重要な制約のひとつがコンテキストウィンドウです。LLM(Large Language Model; 生成 AI の基盤となる機構)には、特定の長さの文章までしか文脈を正しく捉えられないという課題があります [1]。コンテキストウィンドウとはあるモデルが捉えられる経験上の文脈の長さを示しており、例えば ChatGPT 4o では約13万トークンまでは妥当に取り扱えるとされています [2]。コンテキストウィンドウの制約は、LLM への入力を無制限に大きくできるわけではないということを示唆しています。 コンテキストウィンドウの制約を回避する方法は今までに多く研究されており、その代表的なもののひとつが AI エージェントです [3]。AI エージェントはデータ取得と推論を交互に繰り返すことによって、必要な情報を必要な分だけ取得し、必要な分だけ覚えておくことを実現し、アセットを効果的に利用することができます。 必要な情報だけを取得することで限られたコンテキストウィンドウを活用 AI エージェントの選定 では AI エージェントにとって優しいアセットはどのようなものでしょうか? 実際に PoC (proof of concept; 不確実性が高い部分を切り出してクイックに検証をすること)アプリケーションを作る際には、まず利用する AI エージェントを選定し、その AI エージェントにとって優しいアセットを作ることにしました。 今回は AI エージェントとして、すでに開発業務で使っていた Cline を使用することにしました。Cline はテキストエディタである VSCode の拡張機能で、通常コーディングの AI 支援として使われます。 今回 Cline を AI エージェントとして採用したのは、今回の取り組みがうまくいくかどうか不確実性が高く、クイックにユーザーを巻き込んだ検証を行うため すでに活発に開発・検証されたコンテキストエンジニアリング技術を利用する ユーザーとの対話インターフェースをすでに提供してくれているアプリケーションを利用する ユーザーもエンジニアなので開発業務で使っていた Cline を活用することに障壁がない という観点で利点が大きいと考えたためです。 評価制度アセットの構築 Cline は標準でディレクトリ内部のテキストファイルを解析し、検索してくれます [4]。既存の社内のアセットは Confluence 上に記載されたドキュメント Google Docs 上に記載されたドキュメント Google Spreadsheet 上に記載されたドキュメント があったため、これらをすべてマークダウンに変換し、Git レポジトリとして管理をすることにしました。Confluence、および Google Spreadsheet 上のドキュメントは Google Docs に貼り付けることができ、Google Docs はそのままマークダウンに変換できるため、マークダウンへの変換は容易に行うことができました また、元ファイルに対してコピー操作を行うため、新たに作成されたアセットについては、 インポート日 インポート作業者 元資料の URL を YAML フロントマターを利用して付与しました。この操作によって、アプリケーションの利用者がアセットが古くなっていた場合に自動的に最新の情報を確認し、必要に応じて更新できるようにしました。 分散した社内資料を Git repo に集約し管理する 具体的には以下のような YAML フロントマターがすべての資料の冒頭に書き込まれています。 --- title: "評価軸定義" import_date: "2025-06-24" source_url: "https://docs.google.com/spreadsheets/d/..." imported_by: "システム管理者" version: "1.0" --- # 評価軸定義 ... また、より AI エージェントが関連するドキュメントを探索できるように、アセットの追加時には関連資料を末尾に追加するようにしました。 AI エージェントが資料を連鎖的に辿れるようにアセットを作る 例えば評価制度の FAQ アセットについては、以下のように評価制度の概要や、評価軸の定義に対するドキュメントへのリンクを記載しました。 --- title: "評価制度FAQ(よくある質問と回答)" import_date: "2025-06-24" source_url: "https://docs.google.com/spreadsheets/d/..." imported_by: "システム管理者" version: "1.0" --- # 評価制度FAQ(よくある質問と回答) ... ## 関連資料 - [HELIX制度概要](helix-system-overview-2025.md) - [Career Track別の期待活躍イメージ](career-track-expectations-2025.md) - [評価軸定義](evaluation-axis-definitions-2025.md) - [Track別・Grade別期待活躍レベル一覧](track-grade-expectations-matrix-2025.md) - [Helix目標設定ガイド](helix-goal-setting-guide-2025.md) アセットの構築上の工夫 上記の YAML フロントマターの設定や、メタデータの更新を人力で行うと必ず抜け漏れが発生します。 特に、今回はアセットを作成したり、管理したりするために、エンジニアリングマネージャーである私だけでなく、Tech HR(Human Resource; 人事のこと)にも協力してもらいました。 そこで今回アセットを構築する際には、コミット前に AI エージェントにレビューを行わせ、自動で付け加えられる場合には AI エージェント自ら追記し、そうでないメタデータについては修正を要求するような工夫を行いました。 すなわち、AI エージェントを利用者だけが使うのではなく、アセット開発者も活用できるようにすることで、アセットの品質を保つようにしました。 AI エージェントに対する指示 AI エージェントをさらに有効に働かせるために、初期プロンプトを自動で与える Cline rules [5] も活用しました。 Cline では初期プロンプトをカスタマイズでき、またその組み合わせも変えることができます。 今回はアセットを作る人と使う人、それぞれで注目するべきポイントが違うため、Cline rules を複数作成し、Cline rules の切り替え機能を活用することで、アセットを作る人も使う人も使いやすい環境を整えました。 使う人のペルソナに応じたプロンプトを事前に用意 特にメンバー向けの Cline rules には以下のような指示を記載したことによって、AI エージェントに期待していることを明示し、また AI エージェントが回答できる限度を超えている場合には、適切にエスカレーションされるように配慮しました。 ### 評価アシスタントとしての心構え - 中立性の維持: 特定の評価結果に偏らない客観的なアドバイス - 継続的な学習: 評価制度の変更や更新に対応 - 実用性の重視: 理論だけでなく実践的なアドバイスを提供 ## サポートが必要な場合 - 評価制度の詳細: 人事担当者に確認を依頼 - 複雑な目標設定: 上司やメンターとの相談を推奨 結果 実際の PoC アプリケーションの挙動 以下は私が架空のエンジニアを想定し、自己評価の支援を依頼したときの実際のシステムの挙動です。 ユーザーが「自己評価の支援をして」と入力する AI エージェントがエントリーポイントとなるドキュメントを読み込む AI エージェントが関連するドキュメントを辿ってさらに読み込む AI エージェントがユーザーを対話的にコーチングしながら実務に連動した評価プロセスを提供する どのような支援が必要かをヒアリング 評価対象期間を特定する 役職・職位を確認する ユーザーの現状に合わせて対話を続ける。この後に複数の自由記述を含むステップが入る 最終的に自己評価として以下のような markdown が出力される 評価期間: 2025年4月~2025年9月 グレード: xxx 所属: Tech本部 xxx Team --- ## エグゼクティブサマリー FY25Q1-Q2期間においては、検索領域における機能拡充として、xxx の開発をリードしました。要件定義から設計、実装、デリバリーまでの全フェーズを通じて、複数チーム(xxx Team、xxx Team、xxx Team)を横断的に率い、技術的な不確実性を段階的に解消しながらプロジェクトを成功に導きました。 ... --- ## 評価軸別の実績と自己評価 ### 1. Expertise(技術力・専門性) #### 実績 本プロジェクトでは、以下の技術的な課題に対して高度な専門性を発揮しました: 技術的課題の特定と解決 - 既存の検索インデックス(Elasticsearch)のアーキテクチャを分析し、複数データソースを統合した新しいインデックス設計を主導 - ... ... #### 自己評価(ノッチ: x2) MG4 の評価基準である「大きなチームや他チームも関係するプロダクトに対して、全体的な構想を描き、設計することができる」 、... の要件を満たしていると考えます。 一方で、 ... については今後の課題と認識しています。 --- ### 2. Delivery(価値創造・提供) ... --- ## 総合評価と今後の成長課題 ### 強みとして発揮できた点 1. 技術的専門性とリーダーシップの両立: 検索技術の専門性を活かしながら、複数チームをリードする役割を果たせました ... ### さらに伸ばせる可能性がある点 1. 定量的な評価指標の活用: プロセス改善において、より定量的な指標(DORA metrics等)を活用した継続的改善サイクルの確立 ... ### 次期(FY25Q3-Q4)に向けた改善アクション 1. 本部戦略への貢献: 本部全体の技術戦略策定プロセスに積極的に参加し、検索領域以外の知見も獲得 ... --- ## 証跡・参考資料 - プロジェクト計画書: [Confluence](https://example.com) ... メンバーからの定性的なフィードバック 今回、実験的に半期の評価サイクルにあわせて、実際の評価プロセスで PoC アプリケーションを利用可能にしユーザーのフィードバックを収集しました。 対象はエンジニアリング組織全体で、利用は任意とする形式で運用しました。最終的に、全体の約3割のメンバーが実際に利用しました。 利用したメンバーから、ポジティブなフィードバックとして以下のようなものがありました 自己評価を書くときの初動ハードルが下がった 自分では気づいていなかった観点(協働や影響範囲など)を指摘してくれるのがありがたい 全体的に、文章の整形よりも思考の整理についてポジティブなフィードバックが多かったことが特徴的でした。AI が自分の行動を、評価の考え方に沿って構造化してくれるため、「何を書けばいいか」が明確になり、記述の負荷が軽減されたという声が多くありました。 一方で、以下のようなフィードバックも多く見られました。 AIが出してくれた文章をたたき台として修正する形がちょうどいい 実際、生成結果をそのまま使う人はほとんどおらず、多くの利用者が「AI の出力を土台にして、自分の言葉にリライトする」スタイルをとっていました。これは、生成 AI が解決できる課題として、文書化そのものよりも、知識へのアクセスを補助する部分が大きいという当初の仮説を支持する結果だと考えられます。 マネージャーからのフィードバック また、メンバーが PoC アプリケーションを利用したマネージャーからもフィードバックを得ることができました。 全体的にポジティブな内容が多く、 各評価軸に沿った構造的な書き方をされていて読みやすかった 行動と成果のつながりが明確でわかりやすかった トーンや文体が統一されており、比較しやすかった というフィードバックが得られました。これまでは文章表現の違いによって、評価プロセスのアウトプットの解釈が割れることがありましたが、一定のフォーマットでアウトプットが整理されることで、よりコンテンツの議論に集中できるようになったものだと考えられます。 今後の課題 PoC アプリケーションの実験的な導入によって見えてきた課題もありました。 利用者は全体の約3割にとどまり、まだ十分に浸透していない 評価制度そのものの記述や記載が曖昧な場合、AI エージェントの出力もぶれる つまり、AI が整理してくれるのは「構造化」と「言語化」の部分であって、評価の根幹にある制度設計やマネジメントの解像度については、アセットの継続的な改善を要するということがわかりました。 また、浸透課題については、体験の改善が必要だと考えられます。現状ではエディタと拡張機能のインストール、さらには Git レポジトリからのアセットダウンロードをすべてメンバー自身が実行する必要があるため、利用開始のハードルが高いという課題が見られました。PoC によって利用をすることによって得られるベネフィットが明らかになったため、踏み込んで社内サーバにホスティングし、環境構築を不要にしていき、より多くのメンバーが活用できる環境を作っていこうと考えています。 まとめ 今回の記事では、私たちが取り組んだ評価 x 生成 AI PoC、すなわち評価制度を AI が利用可能なアセットとして整備し、それらを適切に連携させることが、評価プロセスの効率化と質的向上に効果的であったことをご紹介しました。 実は、今回ご紹介した、AI の力で「ハイコンテキストな情報を構造化し、活用する」アプローチは、キャディの製造業領域におけるビジネスやプロダクトにおいても核となる考え方です。 AI と情報資産の連携は、まだまだこれからどんどん発展していく領域です。もし今回のブログでキャディってどんな会社なんだろう?と興味を持っていただいた方は、ぜひカジュアル面談で一緒にお話ししましょう! https://open.talentio.com/r/1/c/caddi-jp-recruit/pages/78398 参考文献 [1] Hahn, M. (2020). Theoretical Limitations of Self-Attention in neural sequence models. Transactions of the Association for Computational Linguistics, 8, 156–171. [2] OpenAI Platform . Available at: https://platform.openai.com/docs/models/gpt-4o (Accessed: 09 December 2025). [3] Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., & Cao, Y. (2022). ReAct: Synergizing Reasoning and Acting in Language Models. arXiv preprint arXiv:2210.03629 [4] Cline-tool guide. Available at: https://docs.cline.bot/exploring-clines-tools/cline-tools-guide (Accessed: 09 December 2025). [5] Cline Rules. Available at: https://docs.cline.bot/features/cline-rules (Accessed: 09 December 2025).
とうとう、秋の花粉症も発症してしまい、目のムズムズと格闘している藤田です。 Data & Analysis部で、CADDi Drawer等のプロダクトに提供するAIモデルを開発しています。 さて今回は、私がベトナムの開発メンバーとAIモデル開発をした話を紹介しようと思います。 海外のメンバーと開発を進めることは、言語の壁をはじめいろんな壁があります。 どんな壁がありどうやって乗り越えたのかをご紹介したいと思います。 海外メンバーとの開発をやっている方や、海外メンバーとの協業に興味がある方の参考になればなーと思っています。 なお、この記事は、[ https://adventar.org/calendars/12197:title ] 13日目の記事です。 海外の開発メンバーとAIモデル開発を実施した背景 きっかけ なぜ私が、ベトナムAI開発チームとのプロジェクト担当になったのか? 3つの壁 データの壁 受託文化の壁 ソフトウェアの継続的改善 要件が不明確 言語の壁 成果 今後 まとめ ベトナムでの写真 海外の開発メンバーとAIモデル開発を実施した背景 きっかけ こちら にある通り、キャディは2024年7月に、部品調達プラットフォームCADDi Manufacturing と 図面データ活用クラウドCADDi Drawerの2つのサービスの事業を統合し、「製造業AIデータプラットフォームCADDi 」へと転進することを決めました。この事業統合前まで、ベトナムのAI開発チームは、ベトナム現地において、Manufacturing事業のソリューション開発を主にやっていました。しかし事業統合後、ベトナムのAI開発チームは徐々にCADDi Drawerの開発に携わるようになりました。CADDi Drawerは基本的に日本で開発されているので、その頃から私を含め、Data & Analysis部のメンバーがベトナムチームとプロジェクトをやるようになっていきました。 なぜ私が、ベトナムAI開発チームとのプロジェクト担当になったのか? 一言で言うと、「やりたいです!!」と手を挙げたからです。 実は、私は入社当初から海外との仕事に興味があり、機会があれば手をあげたいと思っていました(なんと 入社エントリー にも書いています)。そんな矢先、「ベトナムと開発する話があるんだけど」と言われたので、思いっきり手を挙げました。 その結果、2025年1月頃から様々なプロジェクトをやらせてもらっています。 現在2025年12月なのではや1年程になりました! 3つの壁 海外チームとのプロジェクトの進め方と、日本チームとのプロジェクトの進め方とでは、色々と異なる点があります。 その違いから、私は色々な壁に当たりました。ここでは特に大変だった壁を3つ紹介します。 データの壁 まず1つ目は、データの壁です。我々が扱っている顧客データは、図面等の最重要機密データです。これらのデータは、法律で海外に送ることが禁止されています。AIモデル開発にとって、データはかなり重要なのですが、それが使えないとなるとできることが限られてしまいます。 そこで、データの制約がある状況でも、どんなタスクであればベトナムチームが取り組めるかを検討しました。 # データ制約があってもできるタスク 1 Open dataを使ったモデル開発 2 Data augmentationやdata creationなどデータを作るようなタスク 3 機密情報ではないデータなど、海外利用が許容されているデータを使う 4 データを加工して復元できない状態でデータを送りモデル開発をする 上記4つのカテゴリーそれぞれで具体的に何ができそうかを検討した結果、現時点では、以下の2つを主に取り組んでいます。 「#1 Open dataを使ったモデル開発」 「#2 Data AugmentationやData Creationなどデータを作るようなタスク」 これらの2つのタスクを選択した理由は、事業的にやりたいタスクの中に、このタスク#1と#2に該当するものがあったためです。 逆に、タスク #3, 4については、現時点では事業的なニーズが小さく、取り組んではいません。 タスク #1, 2の具体例は、 Open dataを使った3D系のモデル開発 LLM評価データセット作成 です。上記2つの例について、無事プロジェクトを完了することができています。 このように、顧客データが使えない中でも、モデル開発やデータセット作成など取り組めるタスクを考え、その結果、成果を残せたのは良かったと思います。 受託文化の壁 ベトナムのソフトウェア業界は、海外からのオフショア開発拠点として成長してきたこともあり、受託開発をやってきたエンジニア多いです。まさにキャディの開発チームでも、受託開発をやってきた方は多い一方で、プロダクト開発を経験したエンジニアは少ないです。 キャディでは、CADDi Drawer等のプロダクト開発をやっているので、ベトナムのエンジニアが、キャディの開発プロセスに戸惑うこと多々あります。 この節では、受託開発とプロダクト開発の違いが原因で、特に苦労したことを紹介したいと思います。 ソフトウェアの継続的改善 ベトナムチームとのプロジェクトを開始した当初、ソフトウェアの継続的改善への意識の違いがありました。 以下は、一般的に言われている、受託開発とプロダクト開発との、ソフトウェアの継続的改善への意識の違いです。基本的に、プロダクト開発の方が、ソフトウェアの継続的改善への意識が高いと思います。 比較項目 受託開発 プロダクト開発 最終ゴール 「納品」すること 仕様書通りのものを作り、検収を終えることがゴール。 「事業成長」すること ユーザーに使われ、利益を生み続けることがゴール。 改善の動機 クライアントの指示・予算 「お金をもらえるならやる」が基本。指示なき改善はコスト増(赤字)になるため避ける傾向。 ユーザーの満足・離脱防止 改善しないと競合に負ける、ユーザーが離れるという危機感が原動力。 技術的負債 先送り・回避したい リファクタリングは機能が増えるわけではないため、顧客に予算承認をもらいにくい。「動いているなら触らない」が安全。 積極的に解消したい コードが汚いと将来の開発スピードが落ちるため、自分たちのために定期的に掃除(改善)する。 リリース後 保守フェーズ(守り) 障害が起きないよう監視することが主務。大きな変更は別プロジェクト扱い。 運用・成長フェーズ(攻め) リリースしてからが本番。A/Bテストやデータ分析を行い、日々改善を繰り返す。 上記の通り、受託開発とプロダクト開発のビジネス構造の違いから、ソフトウェアの継続的改善へのモチベーションは大きく異なります。 プロダクト開発における、ソフトウェアの継続的改善の意識を持ってもらうために、対策を色々考えたのですが、結果的に以下を実施しました。 継続的改善を担保するような、開発プロセスが日本チームに既に存在するので、そのプロセスに従ってもらう。 この対策は言うは易しなのですが、実際にやってみると、日本側のレビューがかなり大変ではありました。 ただ、一回このプロセスを実施してもらえば今後のプロジェクトにも活きるため、必死にレビューを実施し無事全てのプロセスを完了することができました。 (良かった!) 今後は、ベトナムチームの開発リーダーを中心にこの開発プロセスを浸透させ、さらには改善させていくことを考えています。 要件が不明確 ベトナムチームとのプロジェクトを開始した当初、プロジェクトの要件が不明瞭でやりづらいと、ベトナムメンバーからコメントがありました。 これも受託開発とプロダクト開発の違いで、プロジェクトの要件定義の仕方が、受託開発とプロダクト開発とで異なります。 以下は、一般的に言われている、受託開発とプロダクト開発との、プロジェクト要件定義の仕方の違いです。 比較項目 受託開発 プロダクト開発 最大の目的 クライアントの要求を満たすこと (契約の履行・納品) ユーザーの課題解決・事業成長 (マーケットでの成功・PMF) 要件の決定権者 発注者(クライアント) プロダクトマネージャー (PdM) / 事業責任者 要件の源泉 クライアントの要望、RFP、業務フロー ユーザーヒアリング、データ分析、市場調査 定義のゴール 「仕様の確定」 見積もりとスケジュールの遵守のため、曖昧さを排除する。 「仮説の構築」 MVP(実用最小限の製品)を定義し、検証準備をする。 詳細度・粒度 高・網羅的 着手前にドキュメント(要件定義書)で細部まで固める。 中・可変的 コア機能以外はあえて決めすぎず、開発しながら調整する。 変更への対応 抑制的 (Change Control) コスト・納期への影響が大きいため、変更管理が必要。 推奨的 (Pivot/Iterate) ユーザーの反応を見て要件を変えることは「改善」とみなす。 受託開発では、最初になるべく明確に細かいところまで要件を決め切ります。それによって、プロジェクトの不確実性を落としコスト・納期の管理をしやすくします。 一方で、プロダクト開発でのプロジェクトは、仮説検証が主な目的です。例えば、「ユーザヒアリングの結果、Aという機能を作ればプロダクトの価値をあげられる」のような仮説をたて、それを検証するために機能開発をしユーザの反応を見ます。当然、仮説が最初から明確になっていることは少ないですし、途中で仮説を軌道修正することもあります。その都度、要件は変わりうります。 このように、受託開発とプロダクト開発では、要件定義の仕方が大きく異なります。このような違いもあり、メンバーのほとんどが最初、要求項目が曖昧で戸惑ったりしました。 この課題に対して、まずは、違いを明確に説明し、認識を統一しました。そして、最終的にはプロジェクトを継続して経験してもらうことで、習熟度を高めていくように働きかけました。 メンバーによっては、要件が決まらないことにストレスを感じる方もいましたが、その都度、「プロダクト開発では、プロジェクト初期から要件を明確に決めることはできない。」、「エンジニアも事業内容を理解した上で、積極的に要件を決めにいってほしい」と伝えました。 その甲斐もあってか、最近では「要求項目が曖昧で不確実性が高い状況を楽しめている」というコメントをメンバーからもらっています。 引き続きプロジェクトを重ねてもらい、プロダクト開発の要件の決め方に慣れていってもらいたいです。 言語の壁 言わずもがなですが、ベトナムチームとのコミュニケーションは、ベトナム語か英語になります。 ベトナム語で「こんにちは」は、「Xin chào」です。当然、英語を選択しました。 ただ、私自身、海外チームと仕事もしたことないし、海外の移住経験もありません。なので、最初は特に英語でのコミュニケーションに苦労しました。 ちょうど英語で悩んでいる時に、社内で英語サポートプログラムが始まったこともあり、それを利用させてもらいました。一日2時間英語勉強という、スパルタなプログラムでしたが、日々、ベトナムチームとのやりとりで英語力UPを感じていたので、それがモチベーションになり頑張ることができました。 プログラムが終わる頃には、英語を話す度胸がつき、綺麗な英語でなくとも勢いで話すことができるようになりました! 成果 そんなこんなでなんとかベトナム開発チームとプロジェクトをやってきていますが、特に成果を残せたなと思うのは、3D CAD関連の機能をアルファ版としてリリースできたことです。3D CAD関連プロジェクトは元々2年くらい前に、日本チームで精度検証を実施していたのですが、図面優先の方針もあり2年間ペンディングしてました。それをベトナム開発チームが引き継いで進めました。 モデルの精度改善をし、そのモデルをAIプラットフォームにのせ、APIとして使えるようにし、顧客にAIモデルの解析結果を届けることができました。 2年ペンディングしていたプロジェクトを、ベトナムメンバーと一緒にリリースまでやり切ったのは、個人的にも嬉しい成果でした。 今後 ベトナムチームと仕事を始めてから1年経ちますが、まだまだ組織としての改善点や、やり残した課題がたくさんあります。 日本の開発チームと比べ、アクセスできるデータが依然として少ない ソフトウェアの継続的改善をチームに浸透 それらを短期的に解決することは難しいが、定期的に目標を立てながら徐々に解決していき、ベトナムチームがより成果を出せるようにしていけたらと思っています。 まとめ 今回は、ベトナムの開発メンバーと共にAIモデル開発に挑戦した1年間の取り組みについて紹介しました。 振り返ると、以下の「3つの壁」といかに向き合うかがポイントでした。 データの壁:機密データの制約がある中で、Open Data活用やデータ作成タスクなど「今できること」にフォーカスして成果を出した。 受託文化の壁:「仕様通りの納品」から「仮説検証と継続的改善」へ。粘り強くプロセスを適用し、対話を重ねることで、プロダクト開発のマインドセットを徐々に浸透させた。 言語の壁:英語学習プログラムの活用と、「伝えようとする度胸」でコミュニケーションのハードルを乗り越えた。 決して平坦な道のりではありませんでしたが、2年間ペンディングしていた「3D CAD関連機能」をベトナムチームと共にリリースまで持っていけたことは、ベトナムチームや私にとって大きな自信となりました。 受託開発文化からプロダクト開発文化への転換など、組織として解決すべき課題はまだ残っています。しかし、国境を超えて一つのプロダクトを作り上げ、事業価値を生み出すプロセスには、困難以上の面白さとやりがいがあります。 今後もベトナムチームと共に、キャディのAI開発をさらに加速させていきたいと思います! ベトナムでの写真 定期的にベトナムに行き、開発メンバーと交流しています。その時の写真を共有します。 写真1:昼食後のカフェ(ベトナムでは昼食後にカフェに行く文化があるらしいです) 昼食後のカフェ 写真2:芋虫の入ったオムレツ(興味本位で食べたら、ちゃんと美味しくなかった 笑) 芋虫のオムレツ
はじめに CADDi Tech/Product Advent Calendar 2025 12日目の記事です。 こんにちは、DataFabric部の松本です。 私たちのチームでは、Clean Architectureを採用したTypeScriptプロジェクトで開発を進めています。 取り組んでいるプロジェクトでは、依存関係を管理するために、Microsoftが開発するDIライブラリ tsyringe を採用することにしました。Clean Architectureの依存関係逆転の原則を実現するには、DIコンテナが必須であるからです。tsyringeは軽量で使いやすく、枯れておりDIコンテナに必要な機能が一通りそろっていることが魅力的でした。 この記事では、tsyringeを使った 適切な実装方法 を、具体的なコード例とともに解説します。よくある間違いと注意点も紹介するので、Clean Architectureに必須のDIコンテナを迷わずに実装できるようになることを目的としています。 Clean Architecture における依存性注入の必要性 レイヤードアーキテクチャと依存関係逆転の原則 Clean Architectureでは、システムを複数の層に分割します。中心には Domain層 (エンティティ、Repositoryインタフェース)があります。その外側に Use Case層 (アプリケーションロジック)、 Infrastructure層 (Repository実装、DBアクセス)、 Presentation層 (Controller、Handler)が配置されます。 重要なのは、 依存関係の方向 です。Clean Architectureでは「依存関係逆転の原則(DIP)」に従い、 外側の層が内側の層に依存する ように設計します。 Presentation → Use Case → Domain ← Infrastructure 具体的には、Use Caseは IRepository インタフェース(Domain層)に依存し、 PrismaRepository などの具象実装(Infrastructure層)が IRepository を実装します。Use Case層は具体的な実装を知る必要がありません。 この設計により、データベースをPostgreSQLからMySQLに変更しても 1 、Use Caseのコードは一切変更不要になりますし、各層のテストコードを記述する際に容易にMockに差し替えることができるようになります。 Repository パターンとDI コンテナの必要性 しかし、この設計を実現するには「インスタンス生成をどこで行うか」が課題です。Use Caseはインタフェースに依存したいが、実際に動かすには Prisma などのO/Rマッパを使用する具象クラスが必要です。DIコンテナは具象クラスの生成と注入を自動化してくれるため、インスタンス生成そのものにも依存しない設計が可能になります。 テスト時には任意のモックに差し替えることができる点、ライフサイクル管理(シングルトン、スコープなど)もライブラリに任せられる点も良いでしょう。 tsyringe とは 概要と特徴 tsyringe は、Microsoft社が開発するTypeScript向けの軽量なDIコンテナです。 tsyringeは、 @injectable() や @inject() といったデコレータベースのシンプルなAPI 2 を提供しています。reflect-metadataを利用することでTypeScriptの型情報を実行時に活用でき、singleton、transient、scopedといったライフサイクル管理もサポートしています。他のDIライブラリと比較して軽量で学習コストが低いのも特徴です。 別の選択肢としてinversify.jsがありますが、筆者らのプロジェクトでは、 tsyringeが提供してくれている機能で十分であるという見立てがあったのでtsyringeを採用しました。DIライブラリでメジャーなものは提供してくれる機能に大差はないため、軽量で学習コストの低いtsyringeが適していると判断しました。 インストール npm install tsyringe reflect-metadata 動作確認環境: Node.js: 22.x TypeScript: 5.7.x tsyringe: 4.10.0 reflect-metadata: 0.2.2 tsyringeはデコレータ( @injectable() 、 @inject() など)を使って依存性注入を実現します。これらのデコレータはTypeScriptの型情報を実行時に利用するため、 reflect-metadata が必要です 3 。 // tsconfig.json に以下を追加 { "compilerOptions" : { "experimentalDecorators" : true , "emitDecoratorMetadata" : true } } 推奨実装パターン ここからは、架空のBlogエンティティを例に、Clean Architectureでの推奨実装パターンを解説します。 Clean Architectureでは、UseCaseはインタフェース( IBlogRepository )に依存すべきで、具象クラス( PrismaBlogRepository )に依存してはいけません。 TypeScriptの interface はコンパイル後に消えてしまうため、DIコンテナが実行時に「どの実装を注入すべきか」を判断できません。この問題を解決するために、 Symbolトークン をinterfaceの識別子として使います。 // Domain 層で interface と Symbol トークンをセットで定義 export interface IBlogRepository { save ( blog : Blog ): Promise < void > ; } export const IBlogRepositoryToken = Symbol ( "IBlogRepository" ); Symbolは実行時にも存在し、一意性が保証されるため、interfaceと実装を安全に紐付けられます。 実装例:Blog エンティティでの実践 それでは、Blogエンティティを例に、具体的な実装を見ていきましょう。 Domain 層:Repository インタフェースと Symbol トークン まず、Domain層でRepositoryのインタフェースとSymbolトークンを定義します。 // domain/repository/blog-repository.interface.ts import { Blog, BlogId } from "../entity/blog.js" ; export interface IBlogRepository { save ( blog : Blog ): Promise < void > ; findById ( id : BlogId ): Promise < Blog | null > ; findAll (): Promise < Blog []> ; } // Symbol token for DI export const IBlogRepositoryToken = Symbol ( "IBlogRepository" ); Infrastructure 層:Repository 実装 次に、Infrastructure層でRepositoryの具象実装を作ります。 // infrastructure/repository/in-memory-blog-repository.ts import { singleton } from "tsyringe" ; import { Blog, BlogId } from "../../domain/entity/blog.js" ; import { IBlogRepository } from "../../domain/repository/blog-repository.interface.js" ; @singleton () export class InMemoryBlogRepository implements IBlogRepository { private blogs : Map < BlogId , Blog > = new Map (); async save ( blog : Blog ): Promise < void > { this .blogs. set (blog. id , blog); } async findById ( id : BlogId ): Promise < Blog | null > { return this .blogs. get (id) ?? null ; } async findAll (): Promise < Blog []> { return Array . from ( this .blogs. values ()); } } @singleton() デコレータを付け、 IBlogRepository をimplementsします。実装の詳細(今回はInMemory)はDomain層に影響しない構造になっているのが分かると思います。 Use Case 層:依存性の注入 Use Caseでは、 @inject() デコレータでSymbolトークンを指定します。また、UseCase自体もinterfaceとTokenを定義することで、Handler層からの依存を抽象化できます。 // use-case/create-blog.use-case.ts import { inject, singleton } from "tsyringe" ; import { Blog, BlogId } from "../domain/entity/blog.js" ; import { IBlogRepository, IBlogRepositoryToken, } from "../domain/repository/blog-repository.interface.js" ; export interface CreateBlogCommand { id : BlogId ; title : string ; content : string ; authorId : string ; } // UseCase interface and token export interface ICreateBlogUseCase { execute ( command : CreateBlogCommand ): Promise < Blog > ; } export const ICreateBlogUseCaseToken = Symbol ( "ICreateBlogUseCase" ); @singleton () export class CreateBlogUseCase implements ICreateBlogUseCase { constructor ( @inject (IBlogRepositoryToken) private blogRepository : IBlogRepository ) {} async execute ( command : CreateBlogCommand ): Promise < Blog > { const blog = Blog.create( { id : command. id , title : command. title , content : command.content, authorId : command.authorId, } ); await this .blogRepository.save(blog); return blog; } } @inject(IBlogRepositoryToken) でSymbolトークンを指定し、型は IBlogRepository (インタフェース)とします。 @singleton() でライフサイクルをシングルトンに設定しています。 UseCaseにもinterfaceとTokenを定義することで、Handler層もUseCaseの具象実装に依存せず、interfaceに依存するようになります。 DI コンテナの設定 DIコンテナで、Symbolトークンと具象クラスを紐付けます。 // dependency-injection.ts import "reflect-metadata" ; import { container } from "tsyringe" ; import { IBlogRepositoryToken } from "./domain/repository/blog-repository.interface.js" ; import { InMemoryBlogRepository } from "./infrastructure/repository/in-memory-blog-repository.js" ; import { CreateBlogUseCase, ICreateBlogUseCaseToken, } from "./use-case/create-blog.use-case.js" ; // 実際はここにRepositoryやUseCaseの登録がずらっと並ぶ // Register repository with Symbol token container.registerSingleton(IBlogRepositoryToken, InMemoryBlogRepository); // Register UseCases with Symbol tokens container.registerSingleton(ICreateBlogUseCaseToken, CreateBlogUseCase); // Export container for use in other modules export { container } ; import "reflect-metadata"; は、アプリケーションのエントリーポイント(またはDIコンテナの設定ファイル)で 最初に インポートする必要があります。複数ファイルでimportしても問題ありませんが、エントリーポイントで一度importすれば十分です。これにより、デコレータが型情報を実行時に利用できるようになります。 container.registerSingleton() でSymbolトークンと実装を紐付けます。RepositoryだけでなくUseCaseも明示的に登録することで、Handler層がUseCaseのinterfaceに依存できるようになります。 Handler での利用 Handler(Presentation層)でUseCaseを解決して実行します。 // example.ts import "./dependency-injection.js" ; import { container } from "./dependency-injection.js" ; import { ICreateBlogUseCase, ICreateBlogUseCaseToken, } from "./use-case/create-blog.use-case.js" ; // Resolve use case from container using Symbol token const createBlogUseCase = container.resolve< ICreateBlogUseCase >( ICreateBlogUseCaseToken ); // Execute const createdBlog = await createBlogUseCase.execute( { id : "blog-001" , title : "Clean Architecture with tsyringe" , content : "..." , authorId : "author-001" , } ); container.resolve() はHandler層でのみ使い、UseCase内部では使いません。これは後述するServiceLocatorアンチパターンを避けるためです。Tokenを使うことで、Handler層もUseCaseの具象実装に依存せず、interfaceに依存します。 これで、 インタフェースに依存しながら、実行時に具象クラスを注入できる ようになりました。 Webフレームワーク(Hono)での利用例 実際のWebアプリケーションでは、HandlerでUseCaseを解決して実行します。以下は Hono を使った例です。実際はもっと細やかなエラーハンドリングなどが必要になりますが、基本的な流れは同じです。 // presentation/handler/blog/get.ts import { createFactory } from "hono/factory" ; import { container } from "../../dependency-injection.js" ; import { IGetBlogUseCase, IGetBlogUseCaseToken, } from "../../use-case/get-blog.use-case.js" ; const factory = createFactory(); export const getBlogHandler = factory.createHandlers( async ( c ) => { const { id } = await c.req.json(); // container.resolve() でUseCaseをSymbol tokenで取得 const getBlogUseCase = container.resolve< IGetBlogUseCase >( IGetBlogUseCaseToken ); const blog = await getBlogUseCase.execute( { id } ); if (!blog) { return c.json( { error : "Blog not found" } , 404 ); } return c.json( { id : blog. id , title : blog. title , content : blog.content, } , 200 ); } ); ポイント は、Handler層で container.resolve() を呼び出してUseCaseをSymbol tokenで取得することです。これにより、Handler層もUseCaseの具象実装に依存せず、interfaceに依存します。ここまでの設定により、tsyringeが自動的にUseCaseとRepositoryの依存関係を解決し、指定した具象クラスを注入してくれます。この依存関係解決の階層は何階層でも可能です。 テストでのモック差し替え DIの大きなメリットの1つは、 テスト時にモックへの差し替えが容易 なことです。 コンストラクタインジェクションなので、 containerを使わずにコンストラクタに直接モックを渡す方法が最もシンプル です。 Mock クラスを作成してテストする 以下はvitestでのテスト例です。 import "reflect-metadata" ; import { describe , it , expect , vi } from "vitest" ; import { CreateBlogUseCase } from "./create-blog.use-case.js" ; import { IBlogRepository } from "../domain/repository/blog-repository.interface.js" ; import { Blog, BlogId } from "../domain/entity/blog.js" ; // Mock class for IBlogRepository class MockBlogRepository implements IBlogRepository { public savedBlogs : Blog [] = [] ; async save ( blog : Blog ): Promise < void > { this .savedBlogs. push (blog); } async findById ( id : BlogId ): Promise < Blog | null > { return this .savedBlogs. find (( b ) => b. id === id) ?? null ; } async findAll (): Promise < Blog []> { return this .savedBlogs; } } describe ( "CreateBlogUseCase" , () => { it ( "should create and save a blog" , async () => { // Arrange: Create mock repository const mockRepository = new MockBlogRepository(); // Create UseCase with mock (without DI container) const useCase = new CreateBlogUseCase(mockRepository); // Act: Execute use case const result = await useCase.execute( { id : "test-001" , title : "Test Blog" , content : "Test Content" , authorId : "author-001" , } ); // Assert: Verify the result expect (result. id ).toBe( "test-001" ); expect (result. title ).toBe( "Test Blog" ); // Assert: Verify the blog was saved expect (mockRepository.savedBlogs).toHaveLength( 1 ); expect (mockRepository.savedBlogs[ 0 ].title).toBe( "Test Blog" ); } ); } ); Mockクラスを作成して IBlogRepository をimplementsし、UseCaseのコンストラクタに直接mockを渡します。containerを使わないため、シンプルで高速です。また、Mockの内部状態を直接検証できます。 なお、テストでcontainerを使わない場合でも import "reflect-metadata"; は必要です。UseCaseクラスに @singleton() や @inject() デコレータが付いているため、クラス定義をインポートする時点でデコレータが評価され、reflect-metadataが必要になります。 vi.fn() を使ったスパイ より簡易に検証する場合は、 vi.fn() を使ってスパイを作成できます。 it ( "should use vi.fn() for spying" , async () => { // Arrange: Create mock with spy functions const mockRepository: IBlogRepository = { save : vi.fn(), findById : vi.fn(), findAll : vi.fn(), } ; const useCase = new CreateBlogUseCase(mockRepository); // Act await useCase.execute( { id : "test-002" , title : "Another Test" , content : "Another Content" , authorId : "author-002" , } ); // Assert: Verify save was called expect (mockRepository.save).toHaveBeenCalledTimes( 1 ); expect (mockRepository.save).toHaveBeenCalledWith( expect .objectContaining( { id : "test-002" , title : "Another Test" , } ) ); } ); vi.fn() でスパイ関数を作成すると、呼び出し回数や引数を詳細に検証できます。Mockクラスよりも軽量ですが、内部状態の検証はできません。 container を使う方法(オプション) containerを使ってモックの登録もできますが、テストでは通常不要です。 import { container } from "tsyringe" ; beforeEach (() => { container.clearInstances(); } ); it ( "should work with container" , async () => { const mockRepository = new MockBlogRepository(); container. register (IBlogRepositoryToken, { useValue : mockRepository, } ); const useCase = container.resolve(CreateBlogUseCase); // ... テスト実行 } ); この方法は、Handler層のテストなど、実際のcontainerの動作を検証したい場合にのみ使います。UseCase単体のテストでは、 Mockをコンストラクタに直接渡す方法を基本使用 するようにします。 応用:ライフサイクル管理(@singleton と @injectable) tsyringe のライフサイクル管理 tsyringeは3つのライフサイクルをサポートしています。 デコレータ インスタンスの生存期間 用途 @singleton() アプリケーション全体で1つ ステートレスなクラス(UseCase、Repository) @injectable() resolve() のたびに新規作成 ステートフルなクラス @scoped() リクエストスコープごとに1つ ステートフルなクラス 推奨:UseCase と Repository は @singleton() 筆者らのプロジェクトでは、 すべてのUseCaseとRepositoryを @singleton() にしています 。 この判断には理由があります。まず、UseCaseとRepositoryは ステートレス に設計すべきというCleanArchitectureの原則があります。インスタンスに状態を持たなければ、複数のリクエストで同じインスタンスを共有しても問題ありません。 また、PrismaClientについては Prisma 公式ドキュメント でsingletonパターンが推奨されています。複数のPrismaClientインスタンスを作成すると、それぞれが独自のコネクションプールを持ちます。そのため、データベースの接続上限に達してしまうリスクがあります("FATAL: sorry, too many clients already" エラーが発生します)。 また、singletonパターンは不要なインスタンス生成を避けられるため、パフォーマンスとメモリ効率の面でもメリットがあります。 実装例: // PrismaClient: singleton で登録 container. register (PrismaClientToken, { useFactory : () => { if (!prismaClientInstance) { prismaClientInstance = new PrismaClient(...).$extends(extension); } return prismaClientInstance; } , } ); // Repository: @singleton() デコレータ @singleton () export class InMemoryBlogRepository implements IBlogRepository { private blogs : Map < BlogId , Blog > = new Map (); // 注意: この Map はアプリケーション全体で共有される // 本番環境では DB を使うため、この例では問題ない } // UseCase: @singleton() デコレータ @singleton () export class CreateBlogUseCase { constructor ( @inject (IBlogRepositoryToken) private blogRepository : IBlogRepository ) {} // インスタンス変数は依存関係の参照のみ(状態を持たない) } 応用:useFactory による Lazy initialization これまで useClass を使ってきましたが、tsyringeには useFactory という登録方法もあります。実際のプロジェクトでの使い分けを実例とともに解説します。 useClass と useFactory の違い 両方とも初回の resolve() 時に初期化されますが、useFactoryは 初期化ロジックをカスタマイズできる 点が特徴です。 実装例:PrismaClient の Lazy initialization 実際のプロジェクトでは、PrismaClientの初期化時にログを出力するために useFactory を使いました。 // dependency-injection.ts type ExtendedPrismaClient = ReturnType < PrismaClient [ "$extends" ]>; let prismaClientInstance: ExtendedPrismaClient | null = null ; // Register PrismaClient container. register (PrismaClientToken, { useFactory : () => { if (!prismaClientInstance) { ApplicationLogger. info ( "Initializing PrismaClient with configuration" , { attributes : { database_config : PRISMA_DATABASE_CONFIG, environment : process .env.NODE_ENV, } , } ); prismaClientInstance = new PrismaClient( { datasources : { db : { url : DATABASE_URL } } , } ).$extends(prismaExtension()); } return prismaClientInstance; } , } ); // Register Repository container. register (IBlogRepositoryToken, { useClass : PrismaBlogRepository, } ); useFactory自体はsingletonにならないため、 prismaClientInstance 変数でsingletonを実現しています。ほとんどの場合は useClass で十分ですが、初期化時にカスタムロジックが必要な場合は useFactory を使うことを検討しましょう。 よくある間違いと注意点 抽象クラスはトークンとして使えない abstractクラスはinterfaceと違ってコンパイル後も残るため、Symbolトークンなしで注入できると期待するかもしれません。しかし、tsyringeでは現時点(2025年12月)では動作しません。そのため、抽象クラスをトークンとして使う場合もSymbolトークンを定義して利用する必要があります。 Support for abstract classes as injection tokens #108 Abstract class as injection token #172 ServiceLocator アンチパターンを避ける 実装パターンでも述べましたが、UseCase内部で container.resolve() を呼ぶのは避けましょう。依存関係がコンストラクタに表出しないためテストが困難になり、DIのメリットが大きく損なわれます。 // NG: UseCase 内で container.resolve() を使う class CreateBlogUseCase { async execute ( command : CreateBlogCommand ) { const repository = container.resolve(IBlogRepositoryToken); // NG // ... } } // OK: コンストラクタで依存関係を明示 class CreateBlogUseCase { constructor ( @inject (IBlogRepositoryToken) private repository : IBlogRepository ) {} async execute ( command : CreateBlogCommand ) { // this.repository を使う } } コードレビューではDIの基本に立ち返り、 依存はコンストラクタで注入しDIライブラリで解決させる ことを徹底しましょう。 さいごに いかがでしたでしょうか。実際のアプリケーションに近い構造で示したので、実際に利用するときのイメージが湧いたのではないかと思います。 tsyringeを使ったClean Architectureの実装は、Symbolトークンパターンと正しいDIの使い方を理解すれば、シンプルで保守性の高いコードを書けます。この記事が、皆さんのプロジェクトでtsyringeを導入する際の参考になれば幸いです。 最後に、私たちCADDiでは一緒に働く仲間を募集しています。興味がある方はぜひ以下のリンクからご応募ください! https://recruit.caddi.tech/ 参考資料 tsyringe 公式リポジトリ Clean Architecture(ロバート・C・マーチン) 依存関係逆転の原則(DIP) ServiceLocator アンチパターン tsyringe Issue #108: Support for abstract classes tsyringe Issue #172: Abstract class as injection token 筆者はこのようなDB変更を経験したことはありませんが、将来起こりうるかもしれない変更に備える意味でClean Architectureの採用は有効です。 ↩ tsyringeはDIの形態としてコンストラクタインジェクションのみをサポートしています。プロパティインジェクションやメソッドインジェクションはサポートしていません。が、筆者の知る限りコンストラクタインジェクションでほとんどのユースケースをカバーできるため、問題になることは稀です。 ↩ 実は、 reflect-metadata のインポート忘れにより1時間ハマったりしました。 ↩