TECH PLAY

KINTOテクノロジーズ

KINTOテクノロジーズ の技術ブログ

936

この記事は KINTOテクノロジーズ Advent Calendar 2025 の 6 日目の記事です🎅🎄 はじめに こんにちは。KINTO テクノロジーズ Cloud Security グループの渡邉です。 当社では AWS Organizations と Control Tower を利用したマルチアカウント運用を続けていますが、長年の積み重ねにより、一部の設計を見直す必要が出てきました。そこで、既存環境を前提にセキュリティガバナンス設計の再整理を進めることにしました。 本プロジェクトでは、いきなり全体を作り直すのではなく、影響の大きい領域から優先順位を付けて段階的に整理していく方針を取っています。そのうえで、安全性の判断を優先しつつ、新規 OU や AWS のマネージド機能の活用など、将来の運用改善につながる変更も一部取り入れています。 プロジェクトはまだ設計フェーズの段階であり、最終成果はこれからですが、本記事では現時点での意思決定の流れと背景を共有します。 ※ 本記事は AWS Organizations / AWS Control Tower / Security Hub CSPM / SCP / OU 設計に一定の理解がある読者を対象としています。 ※ プロジェクト前半である設計フェーズ時点の内容をベースにしており、今後の実装フェーズで変わる可能性があります。 設計項目の棚卸し まずは既存環境のセキュリティガバナンス要素を整理し、影響度と改善効果の観点から、以下の 5 項目を優先的に棚卸ししました。 最初の 3 項目は影響範囲が広く、クラウドセキュリティ以外のチームにも関係する領域です。後半の 2 項目は、それらに付随して検討が必要となる領域です。 OU 設計 └ OU 構造がそのままガードレール設計に影響するため 予防的ガードレール └ 望ましくない操作を事前にブロックする仕組みであり、既存アカウントに影響するため 発見的ガードレール └ 望ましくない設定を早期に検出する仕組みであり、予防的ガードレールと相互補完の関係にあるため 設定自動化ツール └ 新規アカウントの初期設定を自動化する社内ツール アカウント発行フロー └ 新規アカウント払い出しのプロセス 本記事では、それぞれの棚卸し過程で見えてきた課題と判断の背景を紹介します。 AWS セキュリティガバナンス構成の整理 この章では、当社の AWS セキュリティガバナンスがどのようなレイヤで構成されているかを整理します。 次の図は、 管理アカウントで適用される設定 ( AWS Organizations / AWS Control Tower / AWS CloudFormation ) それとは別に、設定自動化ツールによって管理アカウントおよびユーザアカウントに追加される設定 が、ユーザアカウント側で 3 段階の予防ガードレール ( 1 段目 : Control Tower 標準、2 段目 : 追加ガードレール、3 段目 : カスタムSCP ) 2 系統の検出 ( Security Hub CSPM 系統、Control Tower 系統) 監視・予防対象リソースの事前適正化 (設定自動化ツールによる初期設定) として作用する全体構造を示したものです。 図のとおり、当社環境では Control Tower の標準ガードレールに加えて、設定自動化ツールで補完した追加ガードレールやカスタム SCP により、3 段階の予防コントロールを構成しています。 また、Security Hub CSPM 系統と、Control Tower 系統という 2 系統の検出レイヤ、そして設定自動化ツールによるリソースの事前適正化も組み合わせることで、ガバナンス全体を構築しています。 これらは導入時期や目的が異なるため、OU や アカウント間で設定のばらつきが生じている部分もあります。 この図における主要な役割分担は、次の 5 つに整理できます。 Control Tower 標準ガードレール └ AWS が提供する予防・検出のベースライン 追加ガードレール (設定自動化ツールで有効化) └ 標準だけではカバーできない制約を追加するための予防・検出ガードレール カスタムSCP (設定自動化ツールから設定) └ 例外的な要件に対応するための当社独自の制限 Security Hub CSPM 系統 ( CSPM 側の検出レイヤ) └ AWS Config と連携し、設定ミスやベストプラクティス違反を継続的に検出するサービス Control Tower 系統 ( Control Tower 側の検出レイヤ) └ AWS Config と連携し、Control Tower のガードレールに紐づく検出コントロールを実現 こうした多層構造は AWS ガバナンスでは珍しくない構成ですが、棚卸しを進めるうえでは、どのレイヤがどのコントロールを提供し、各設定が Control Tower / Security Hub CSPM / 設定自動化ツールのどこに属しているのかを明確に整理する必要があり、一定の複雑さを伴いました。 今回は、まず Control Tower の追加ガードレール、特に影響の大きい予防コントロールを最新の状態へ追従させることを優先しました。レイヤ間の重複や役割分担の最適化については、今後の改善テーマとして段階的に進めていく方針としています。 OU 設計の棚卸し OU 設計は Control Tower の動作と密接に関係するため、設計変更時の影響範囲を見極めるのが特に難しい領域です。 特に影響が大きく、判断を難しくしたのは以下の 3 点です。 一部の動作がドキュメント化されておらず、事前に挙動を読み切れない場面がある点 └ 例えば、OU 名の変更後に必要となるランディングゾーンのリセット時に、何が行われるのかに関する詳細仕様が公開されていない点など OU ごとの差異に加え、過去の実装や運用の積み重ねが影響しており、OU 内のアカウントを別 OU に移動する際に、どの設定がどう再適用されるかを個別確認する必要がある点 本番アカウントや Control Tower の履歴を含む状態を検証環境で正確に再現できないため、検証だけでは安全性を保証しきれない点 └ 例えば、本番アカウントにおいて、Control Tower の運用に必要なリソースが、過去の運用の中で何らかの理由により変更・削除されているケースなど 上記を踏まえ、今後作成されるアカウントの収容先として、次の 3 パターンを比較しました。 ここでの 3 パターンは次のようなイメージです。 既存 OU (現状維持) : いま使っている OU 構造やガードレールを変えずに、新規アカウントを収容していく。 既存 OU (新ガードレール適用) : いま使っている OU 群に新しいガードレールを適用し、新規アカウントを収容していくことで、既存アカウントも含めて一気に状態を揃える。 新規 OU 群 : 既存 OU 群は現状維持として、新しいガードレールを適用した OU 群を新たに作成し、その OU に新規アカウントを収容していく。 No 収容先 変更の影響 長期の健全性 導入容易性 コメント 1 既存 OU (現状維持) ◎ 既存への影響なし × 負債が残る ◎ 現状維持で容易 一時的には安全だが長期負債が増える 2 既存 OU (新ガードレール適用) × 既存全体へ影響大 ◎ 健全性が高い × 検証負荷が大きい 理想的だが影響が重く段階移行が必要 3 新規 OU 群 ◎ 既存への影響なし △ OU 分散リスク ◎ 導入・検証が容易 OU 分散を後続フェーズで解消する必要あり 今回は、既存環境への影響を避けつつも新たな仕組みの導入が容易な案 3 を採用しました。 ただし、これは恒久的な解ではなく、既存 OU をいきなり組み替えずに新しいガードレール構成と運用モデルを検証するための最初の一歩という位置付けです。 新規 OU 群で運用実績と検証結果を蓄積したうえで、Sandbox OU など影響の小さい既存アカウントから段階的に移設し、最終的には OU 構成とガードレールを可能な限り統一していくことを中長期の方針としています。 予防的ガードレールの棚卸し 予防的ガードレールは、望ましくない操作や構成を未然に防ぐための仕組みです。当社環境では、AWS Control Tower の標準ガードレールに加え、その不足分を補完するため、設定自動化ツールで追加のガードレール有効化・カスタム SCP を適用しています。 段 名称 主体 役割 1 段目 標準ガードレール AWS Control Tower AWS 標準の予防コントロール 2 段目 追加ガードレール 設定自動化ツール ( CDK ) 標準ガードレールでカバーできない不足分の補完 3 段目 カスタムガードレール 設定自動化ツール ( CDK ) 当社固有の要件に合わせた独自の SCP 新規アカウントを新設 OU に収容する方針としたため、新規 OU に設定するガードレール追加に伴う既存環境への影響はありません。そのため、初期導入後に追加された Control Tower の新しい予防コントロールを有効化すべきかどうかを検討しました。 しかしながら、AWS が提供する「重大度」だけでは、以下に挙げる 3 つの例のように、なぜその評価になっているのか、実運用でどのような影響が出るのかまでは見えませんでした。 重大度が高だが、導入判断がつかないものの例 (1) [CT.EC2.PV.4] Require that Amazon EBS direct APIs are not called (2) [CT.S3.PV.2] Require all requests to Amazon S3 resources use authentication based on an Authorization header 重大度が中だが、導入しても良いと思われるものの例 (3) [CT.EC2.PV.11] Disallow public sharing of Amazon Machine Images ( AMIs ) そこで、Amazon Q Developer Pro ※ を用いて、各コントロールの SCP を、運用影響・推奨度・導入プロセスの観点で評価しました。上記 1~3 については、次のような気付きがありました。 EBS direct APIs を利用する AWS Backup パートナー製品などのバックアップに影響する可能性があるため注意が必要 ( CT.EC2.PV.4 ) Presigned URL が使えなくなる ( CT.S3.PV.2 ) 導入は妥当であるが、SCP ではなく DECLARATIVE_POLICY なので挙動が異なる点に注意が必要 ( CT.EC2.PV.11 ) こうした整理を通じて、有効化が推奨され、影響が小さいものは、新規 OU で積極的に有効化する方針としました。 ※ Amazon Q Developer Pro を利用した理由は、他の AI と比較すると応答速度は遅いものの、AWS の公式ドキュメントを都度参照したうえで回答していると推察でき、仕様変更の多い領域でも一定の安心感があったためです。 発見的ガードレールの棚卸し 当社環境では、セキュリティ基準の一元管理と自動更新性の観点から Security Hub CSPM をセキュリティ監査の主軸としています。 Control Tower の検出コントロールは、Control Tower が提供するガードレールや共通基盤の構成に関する観点を含むチェック群であり、当社では Security Hub CSPM を補完する仕組みとして位置付けています。 今回の見直しでは、運用開始後に追加された Control Tower の検出コントロールについて、「重大度:重大」または「ガイダンス:強く推奨」に該当するものを対象に、有効化の要否を確認しました。 その結果、例えば以下のようなコントロールは、Security Hub CSPM の基準にて既に監査可能であることを確認できたため、Control Tower 側では重複して有効化しない方針としました。 [CONFIG.KMS.DT.1] Checks if AWS Key Management Service ( AWS KMS ) keys are not scheduled for deletion in AWS KMS [CONFIG.KMS.DT.2] Checks if the AWS KMS key policy allows public access 一方、以下のように、サービス固有の設定を扱う一部のコントロールについては、当社での実際の利用状況や将来の利用計画に応じて、必要性を個別に検討する方針としました。 [CONFIG.EMR.DT.1] Checks if an account with Amazon EMR has block public access settings enabled 設定自動化ツールの棚卸し 当社では、新規アカウントの初期設定を自動化するため、設定自動化ツール ( CDK ベース) を開発・運用しています。長年にわたり、このツールが新規アカウントの標準化や初期設定の抜け漏れ防止に大きく寄与してきました。 今回の棚卸しでは、現在のアカウント規模やチーム構成、AWS のマネージド機能の拡充状況を踏まえ、この仕組みをどこまでシンプルにできるかを整理しています。 一般的には自動化範囲を広げる事例も多いですが、当社では維持しやすさを優先し、必要な部分に絞る方向性としました。 設定自動化ツールで実施している内容は以下の通りです。 追加の予防的ガードレール適用 カスタム SCP の追加 Security Hub CSPM の重複・不要なアラーム抑制 Security Hub CSPM 準拠を目的としたサービス設定 (例:デフォルト暗号化やパブリックアクセスブロックの有効化など) その他のセキュリティ設定の自動適用 現時点の方針としては、以下の 3 段構えで進める想定です。 AWS のマネージド機能に任せられる部分は、できる限り移行する マネージド機能では置き換えられない部分は、宣言的 IaC ( CloudFormation、CloudFormation StackSets、Terraform など) での管理を優先する IaC にも寄せられない例外的な処理は、最小限のコードとして残し、具体的な実装方式は後続フェーズで判断する 単にコードを減らすことが目的ではなく、組織として長期的に持ち続けるべき責務だけをコードに残すことを狙いとしています。 特に項目 4 については、 Security Hub CSPM の中央設定 に移行することで、一部の設定を AWS のマネージド機能に寄せられる可能性があります。既存 OU ではコントロールの状態に個別差分が残っており、適用には棚卸しが必要ですが、新規 OU から段階的に活用する予定です。 また、5 の中で実施している S3 ブロックパブリックアクセス有効化の設定についても、先日公開された S3 ポリシー への移行により、同様に AWS のマネージド機能に寄せられる可能性があります。こちらも今後、導入可否についての調査を進めていく予定です。 なお、設定自動化ツールには CDK による条件分岐など独自の強みがあり、これが見直しにおける判断ポイントとなる可能性があります。引き続き、上記方針が適切かどうかを慎重に検証しながら進めていく予定です。 アカウント発行フローの棚卸し 当社では、これまで新規アカウント発行時にルートユーザの MFA を有効化する運用を行っていましたが、物理作業が伴うため、発行の都度一定の稼働が発生していました。 2024年末に メンバーアカウントのルートアクセス一元管理 により、この運用自体を見直せる可能性があります。 今回の再設計と併せて、アカウント発行フローもできる限りシンプルかつ安全な形に整理することを検討しています。 まとめ 今回の棚卸しでは、長年の運用で積み上がった設定や仕組みを前提に、将来の拡張性と保守性の両面から、どこを変え、どこを維持すべきかを整理しました。 既存 Organizations を維持したまま理想的な構成へ作り替えることは簡単ではありません。OU 設計、ガードレール、初期設定の自動化ツールといった複数レイヤが相互に依存しているため、どれか一つを更新すると他のレイヤにも影響が波及するからです。 そのため今回は、すべてを全面刷新するのではなく、本番を止めずに少しずつ負債を減らしながら改善を進めるという方針で整理を進めました。特に Control Tower の挙動や一部のガードレールの影響範囲は、公開情報だけでは読み切れない部分もあるため、リスクを分割しながら段階的に検証する進め方が不可欠だと感じています。 今後は、AWS が提供するマネージド機能を積極的に活用しつつ、既存環境との整合を取りながら変更箇所を局所化し、段階的に改善を続けていく方針です。 本記事が、既存環境を前提にガバナンスを再設計する方々にとって、少しでも判断材料や視点のヒントになれば幸いです。
アバター
This article is for Day 6 of the KINTO Technologies Advent Calendar 2025 and Day 6 of the Tech Event & Conference Management Know-how Advent Calendar 2025 🎅🎄 Introduction Hello! I'm high-g ( @high_g_engineer ) from the Master Maintenance Tool Development Team, KINTO Backend Development Group, KINTO Development Division, also working with the Developer Relations Group, and based at Osaka Tech Lab. I work as a frontend engineer. Frontend Conference Kansai 2025 (hereafter referred to as FEC Kansai 2025) was held on Sunday, November 30, 2025. I participated as a founding member of this conference, serving as the leader of the Speaker Team, which handled CfP, various speaker-related matters, timetable creation, and session management on the day of the event. In this article, I'll share the story behind how FEC Kansai 2025 came to be and the significance of hosting a conference in a regional area, while reflecting on my own experiences with tech community activities. Results of FEC Kansai 2025 First, regarding FEC Kansai 2025, we had over 200 participants on the day. The keynote had standing room only, networking party tickets sold out, and sponsor booths were a huge success. While we had some challenges to address, as an inaugural event, I believe we achieved a successful launch without any major issues. KINTO Technologies was also a sponsor!! Frontend Conference and Me 2025 was a great year for Frontend Conferences, with events held not only in Kansai but also in Hokkaido and Tokyo. I also attended Frontend Conference Hokkaido 2025, where I learned from the presentations and enjoyed meeting people from the Hokkaido community at the networking party. Now, about Frontend Conference; it's not something that just started recently. Before the COVID-19 pandemic, it was held annually in Kansai as well, serving as a yearly festival where attendees could catch up on the latest frontend trends and gain practical knowledge about new frameworks and libraries. I can honestly say that Frontend Conference played a significant role in launching my career as a frontend engineer. However, after the 2019 event, the Kansai Frontend Conference went on a 6-year hiatus. As someone who loves frontend development, the absence of this annual festival left me feeling like something was missing. How FEC Kansai 2025 Got Started Last year, TSKaigi Kansai 2024, a Kansai regional version of TSKaigi, Japan's largest TypeScript conference, was held in Kyoto. That was my debut as a conference staff member. https://note.com/highgrenade/n/nde9f7e059e2e Riding that momentum, I organized the Kansai Frontend Year-End Party 2024 at our company. https://kinto-technologies.connpass.com/event/337002/ The event was a hit, and afterward, I went out for drinks with some TSKaigi Kansai 2024 staff members and Vue Fes Japan staff who live in Kansai. There, we discovered we all shared the same desire to bring back Frontend Conference, and FEC Kansai officially kicked off in 2025. https://fec-kansai.connpass.com/event/339864/ The Journey to FEC Kansai 2025 Of course, since this was a first-time event, we started from scratch with no rules, no budget, no staff, and no name recognition. Naturally, we had to handle everything ourselves: recruiting staff, securing sponsors, booking the venue, managing the call for proposals, building the website, creating merchandise, and placing orders. I optimistically assumed that securing staff and sponsors would be easy thanks to the name recognition of past Frontend Conferences, but in reality, it wasn’t nearly that simple. Since this was our first event, we focused on ensuring a smooth and successful launch. We avoided aiming too high, yet still made sure not to lose sight of delivering a satisfying experience for participants. Despite these circumstances, I'm incredibly grateful to all the staff who joined us and the companies who sponsored us. To help shape our vision for the conference, I personally attended various other conferences: February 1: BuriKaigi 2025 (Toyama) May 23-24: TSKaigi 2025 (Tokyo) *Participated as staff July 19: PHP Conference Kansai 2025 (Kobe) July 26: Kinoko Conference in Kansai (Kyoto) *Not a conference per se, but memorable enough to mention September 6: Frontend Conference Hokkaido (Sapporo) September 17: Developers Summit 2025 KANSAI (Osaka) The Significance of Hosting Conferences in Regional Areas What struck me when attending regional conferences like BuriKaigi and Frontend Conference Hokkaido was the extraordinary feeling of gathering in places I would rarely visit otherwise, surrounded by people who share the same purpose, and experiencing talks with a live energy you can only feel on-site, an excitement shared exclusively among those who came together on that day. It felt like giving up my day off was completely worth it, and to go even further, it was the kind of experience that made me genuinely grateful to be an engineer. It's similar to the feeling of attending a music festival and experiencing moments of pure joy that only exist in that time and place. (For anyone who has never been to a music festival, just imagine everyone sharing that same excitement while enjoying incredibly delicious yakiniku together.) Reflecting on it, I realized that back when I attended Frontend Conference before the pandemic, I wasn't just going to learn—I was going to share in that collective experience. I believe that if regional conferences can consistently create this kind of excitement, they can truly energize the local engineering community. I want to enjoy this myself, and I want others to experience this excitement too, so we can all have fun with technology while working together. If we can create something truly great through collective knowledge, strengthen the local pool of engineers, and generate a positive cycle in the hiring market, I don’t think there could be anything better. Closing Thoughts Going forward, I want to continue expanding my knowledge, engaging with more conferences and tech communities, and refining FEC Kansai. I hope to help attendees feel that engineering is fun! and frontend is amazing!! while contributing to the Kansai tech community in any way I can. I really do love frontend development!! Thank you for reading to the end.
アバター
✨ はじめに こんにちは、KINTOテクノロジーズグループコアシステム部のAngela Wangです。 最近、 Microsoft Power Automate に触れる機会があり、ローコードで簡単にシステムを構築できる点に魅力を感じました。そこで、一つのソリューション例を通してご紹介させていただきます。 日々の業務の中で、こんな悩みはありませんか? タスクの指示がチャット内で散乱している 進捗報告が属人的で、追跡が難しい 状況を一覧で把握できない これらの課題は、 Microsoft Teams と Power Platform(Power Automate/Power Apps/Power BI) を組み合わせることで、 誰でも簡単に自動化・可視化できるタスク管理システム として解決できます。 🧩 1. 全体構成の概要 下記がこのソリューションの基本コンセプトです。 「Teams を中心に、タスク作成から進捗追跡、レポート分析までをワンストップで実現する」 ![Architecture](/assets/blog/authors/angela.wang/architecture.png =600x688) 構成要素 コンポーネント 役割 説明 Microsoft Teams 操作の入口・通知ハブ タスクの作成、更新、通知をチャネル上で実行 Power Apps タスク管理アプリ 直感的な UI でタスクを登録・更新 Power Automate 自動化ワークフロー 通知、リマインド、レポート生成を自動化 Power BI 可視化・分析 タスク完了率・遅延傾向をリアルタイムで可視化 SharePoint List データ保存 タスクデータの保存 ⚙️ 2. 機能設計 ① タスクの作成と担当割り当て(Power Apps) Power Apps 上で、以下の情報を入力できるフォームを作成します。 タスク名 担当者(MSユーザー) ステータス 優先度 期限日 作成者 完了日 詳細内容 ![feature1](/assets/blog/authors/angela.wang/feature1.png =431x681) 作成後、SharePoint Listに記録します。 また、タスク一覧やタスク詳細を確認・編集できる画面も作成します。 ![feature2](/assets/blog/authors/angela.wang/feature2.png =431x681) ![feature3](/assets/blog/authors/angela.wang/feature3.png =431x681) ② 処理の自動化(Power Automate) 代表的な自動化シナリオは次の通りです。 シナリオ 処理内容 実装方法 コメント タスク作成時 担当者にTeamsに通知 「項目が作成された時」トリガーし、Teamsに 担当者へ送信 実現済 ステータス変更 ログ更新 SharePointへ更新イベント 例 期限超過 担当者と上長へリマインド 条件分岐 + Teams メッセージ 例 週次レポート タスク集計をTeamsに送信 スケジュールトリガー 例 ③ データの可視化(Power BI) Power BI では、以下のようなレポートを作成します: ✅ タスクステータス・進捗率 🏷 期限日分布 👤 担当者別数(※) ※:担当者情報を取得するためには Power BI Desktop で編集する必要がありますが、今回は実施しないことにしました。 💬 3. Teams 連携による「一体型」体験 Teams での操作を中心にすることで、以下の体験を実現できます。 Teams チャネル内で Power Apps アプリをタブ表示 Power Automate Bot による自動通知 Power BI ダッシュボードの直接閲覧 つまり、 ユーザーは Teams から一歩も出ずに、タスク管理を完結できます。 これこそが「Microsoft 365 の真の強み」です。 ※Power Apps アプリや Power BI ダッシュボードを Teams チャンネルにタブ追加するには権限が必要ですが、今回は実施しないことにしました。 🧱 4. データ構造(SharePoint List) 列名 データ型 説明 ID 自動番号 一意のタスクID Title テキスト タスク名 Description 複数行テキスト 詳細内容 Assignee ユーザー 担当者 Status 選択肢 ステータス:未開始 / 進行中 / 完了 / 遅延 Priority 選択肢 優先度:高 / 中 / 低 DueDate 日付 期限日 CreatedBy ユーザー 作成者 CompletedDate 日付 完了日 🚀 5. 実装ステップ SharePoint List(タスクリスト)の準備 SharePoint に 「TaskList」 を作成します。 ![list](/assets/blog/authors/angela.wang/list.png =760x440) Power Apps(タスク管理アプリ)の構築 「アプリ テンプレートで開始する」という便利な機能を使い、迅速にアプリを構築します。 ![powerapps1](/assets/blog/authors/angela.wang/powerapps1.png =760x238) ![powerapps2](/assets/blog/authors/angela.wang/powerapps2.png =736x318) Power Automate(タスク管理フロー)の構築 新規タスク作成時に担当者の Teams へ通知が送信されるように実装します。 ![powerplatform](/assets/blog/authors/angela.wang/powerplatform.png =760x600) Power BI(タスク管理ダッシュボード)の作成 ダッシュボードに「タスク進捗」や「期限日分布」のビジュアルを作成します。 ![powerbi](/assets/blog/authors/angela.wang/powerbi.png =760x328) Teamsの統合設定 Power Apps・Power BI のタブを追加します(権限が必要ですが、今回は実施しないことにしました)。 🏁 6. まとめ Power Platform を活用すれば、 「誰でも作れる・すぐ使える・チームに馴染む」 タスク管理ソリューションが実現できます。ぜひ業務の中でご検討ください。
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の5日目の記事です🎅🎄 はじめに KINTO開発部 FEチーム では、React/Next.jsを用いたフロントエンド開発を行っています。 開発タスクの管理にはJiraを使用しており、チケットベースでの開発フローを採用しています。 チーム規模が拡大する中で、「ブランチ名の命名規則」「コミットメッセージの形式」「PRテンプレートの選択」といった 開発フロー上の統一と認知コスト が課題と感じていました。 この記事では、Claude Code と Atlassian MCP を組み合わせることで、これらの「考えなくてもいいこと」を自動化し、開発者が本質的な問題解決に集中できる環境をどう構築したかを紹介します。 技術スタック Claude Code : AI駆動の開発アシスタント Atlassian MCP : Jira/ConfluenceとのAPI連携(チケット情報の自動取得) GitHub CLI (gh) : PR操作の自動化 CLAUDE.md : プロジェクト固有のルール定義 背景・課題:開発フローの「認知負荷」問題 開発者が本来集中すべきはコードの実装です。 しかし、実際の開発では以下のような 「本質的でない作業」 に認知リソースが奪われていました: 「このチケット番号なんだっけ?Jira開いて確認するか...」 「ブランチ名どうするんだっけ? feature/JIRAKEY-1234/ の後は何を書けばいい?」 「このブランチ、developから切るんだっけ?プロジェクトブランチから切るんだっけ?」 「コミットメッセージの絵文字、 :sparkles: と :wrench: どっちだっけ?」 「PRのタイトルは :m: 付けるんだっけ?付けないんだっけ?」 「PRテンプレートどっち使うんだっけ? for_dev.md ?デフォルト?」 これらは些細に見えますが、 1日に何度も発生する意思決定 です。積み重なると開発者の集中力を大きく削ぎます。 解決策:「考えない」開発フローの実現 「チケット番号を伝えるだけで、あとは全部自動化する」 これを実現するために、Claude Code と Atlassian MCP を組み合わせました。 開発者がやること 開発者: 「JIRAKEY-1234のブランチを作成して」 Claude Codeがやること(自動) ✅ Jira APIでチケット情報を取得 ✅ エピック判定でプロジェクト所属を確認 ✅ 適切なブランチ名を自動生成( feature/JIRAKEY-1234/update_claude_docs ) ✅ 適切なベースブランチを自動判定(develop or プロジェクトブランチ) ✅ ブランチ作成 開発者がやること 開発者: 「コミットして」 Claude Codeがやること(自動) ✅ 変更内容を解析 ✅ 適切な絵文字ショートコードを選択( :pencil: , :bug: , :sparkles: など) ✅ コミット実行 開発者がやること 開発者: 「PRを作成して」 Claude Codeがやること(自動) ✅ ブランチ名からチケット番号を抽出 ✅ Jira APIでチケット件名を取得 ✅ PRタイトルを自動生成( JIRAKEY-1234: Claude Codeの運用ルールの標準化 ) ✅ ベースブランチを自動判定(develop or プロジェクトブランチ) ✅ 適切なPRテンプレートを自動選択 ✅ PR作成実行 開発者がやることは3つの指示を出すだけ。ブランチ作成、コミット実行、PR作成実行は、すべてClaude Codeが自動で行います。 実装方法:CLAUDE.mdという「AIが読むルールブック」 すべての自動化は CLAUDE.md というドキュメントに記述されたルールで実現されています。 ### ブランチ命名規則 プロジェクトベースブランチ: `feature/プロジェクト名` - 例: `feature/simulation` - ベースブランチ: `develop` 機能ブランチ(プロジェクト配下): `feature/JIRAKEY-チケット番号/説明` - 例: `feature/JIRAKEY-1234/add_simulation_list` - ベースブランチ: `feature/プロジェクト名` 通常の機能ブランチ(プロジェクト外): `feature/JIRAKEY-チケット番号/説明` - 例: `feature/JIRAKEY-1234/fix_bug` - ベースブランチ: `develop` 親子チケットの場合: - 親ブランチ: `feature/JIRAKEY-親チケット番号/develop` - 子ブランチ: `feature/JIRAKEY-親チケット番号/JIRAKEY-子チケット番号/説明` エピックとプロジェクトベースブランチの関係 - エピック判定: Jiraチケットの `parent.fields.issuetype.name` が「エピック」の場合、そのチケットはプロジェクトに属する - 重要: エピック配下のタスクのブランチ作成時は、必ずユーザーにプロジェクトベースブランチ名を確認すること ### コミットメッセージフォーマット - 必須形式: `:emoji: JIRAKEY-チケット番号: サブジェクト` - 絵文字ショートコードは`.commit_template`を参照 - コミット例: :bug: JIRAKEY-1234: ログイン時のクラッシュを修正 :sparkles: JIRAKEY-2345: ユーザープロフィール画像アップロード機能を追加 :robot: JIRAKEY-3456: ログインコンポーネントのテストを追加 ### PR作成ルール - タイトル形式: - プロジェクトベースブランチ → develop: `:m: JIRAKEY-チケット番号: チケット件名` - 親ブランチ → develop: `:m: JIRAKEY-親チケット番号: チケット件名` - 通常の機能ブランチ → develop: `JIRAKEY-チケット番号: チケット件名` - その他の PR: `JIRAKEY-チケット番号: チケット件名` - テンプレート使用: - `:m:` 付きPR: `.github/for_dev_template.md` - `:m:` なしPR: `.github/pull_request_template.md` これだけ です。コード変更は一切ありません。 実際の動作フロー sequenceDiagram actor 開発者 participant Claude Code participant Jira API participant git participant gh Note over 開発者,gh: ブランチ作成フロー 開発者->>Claude Code: 「JIRAKEY-1234のブランチを作成して」 Claude Code->>Jira API: チケット情報取得 Jira API-->>Claude Code: 件名、エピック情報など Claude Code->>Claude Code: ブランチ名生成<br/>(feature/JIRAKEY-1234/update_claude_docs) Claude Code->>git: git checkout -b 実行 Claude Code-->>開発者: ブランチ作成完了 Note over 開発者,gh: コミットフロー 開発者->>Claude Code: コード変更後「コミットして」 Claude Code->>Claude Code: 変更内容を分析 Claude Code->>Claude Code: :pencil:形式でメッセージ自動生成 Claude Code->>git: git commit実行 Claude Code-->>開発者: コミット完了 Note over 開発者,gh: PR作成フロー 開発者->>Claude Code: 「PRを作成して」 Claude Code->>Claude Code: ベースブランチ判定(develop) Claude Code->>Claude Code: PRタイトル生成<br/>(JIRAKEY-1234: チケット件名) Claude Code->>Claude Code: テンプレート選択<br/>(.github/pull_request_template.md) Claude Code->>gh: gh pr create実行 gh-->>Claude Code: PR URL Claude Code-->>開発者: PR作成完了(URL付き) 導入効果:「考えない」ことで得られた価値 認知負荷の劇的な削減 ✅ ブランチ名を考える ✅ ベースブランチを確認する ✅ コミットメッセージの形式を思い出す ✅ PRタイトルをチケットからコピペする ✅ PRテンプレートを選択する → 「チケット番号を伝えるだけ」で完結 一貫性の担保 ブランチ名・コミットメッセージ・PRタイトルがプロジェクトルールに100%準拠 レビュワーが「これ命名規則違う」と指摘する手間が消滅 オンボーディング時間の短縮 新規メンバーが「ブランチ名のルール覚えなきゃ...」と悩む必要がない 「CLAUDE.mdを見て」ではなく「Claude Codeに聞いて」でOK 開発速度の向上 「Jira開いてチケット件名コピー」という往復が消滅 意思決定の回数が減ることで フロー状態を維持しやすい 今後の展望 サブエージェント活用によるコンテキストウィンドウ最適化 現在の実装では、Atlassian MCPを使用するとコンテキストウィンドウが圧迫される課題があります。この解決策として、 サブエージェント を活用した階層的なタスク分散を検討しています。 サブエージェントとは? Claude Code のサブエージェントは、特定のタスクに特化したAIアシスタントで、 独立したコンテキストウィンドウ を持ちます。 これにより ✅ メインエージェントのコンテキストを汚染しない ✅ 専門的なタスクを効率的に処理 ✅ 大量の情報取得と処理を分離できる 実装計画: 3つの専門サブエージェント 1. Jira情報取得サブエージェント ( jira-researcher ) **役割**: - Atlassian MCPでチケット情報を取得 - 必要な情報のみを抽出(チケット番号、件名、エピック、ステータス) 2. ブランチ戦略判定サブエージェント ( branch-strategist ) **役割**: - チケット情報からブランチ名を生成 - 親子チケット関係を判定 - 分岐パターンからベースブランチを決定 3. PR作成サブエージェント ( pr-creator ) **役割**: - PR作成の分岐判定を実行 - 適切なテンプレート選択 まとめ:AIアシスタントは「考えない開発」を実現する 「チケット番号を伝えるだけで、ブランチ作成・コミット・PR作成が完了する」 これを実現したのは、CLAUDE.mdという「AIが読めるドキュメント」とMCP連携だけです。コード変更はゼロ。既存システムへの影響もゼロ。 重要なのは、 開発者が「考えなくていいこと」を明確にし、AIに任せた 点です。 AIアシスタントを「コード補完ツール」から「開発フロー全体のパートナー」に進化させることで、開発者は 本質的な問題解決にだけ集中できる 世界が実現できます。
アバター
Introduction Hello, I'm Angela Wang from the Group Core Systems Division at KINTO Technologies. Recently, I had the opportunity to explore Microsoft Power Automate and was impressed by how easily systems can be built using low-code. I'd like to introduce one solution example. Do you ever face these challenges in your daily work? Task instructions scattered across chat messages Progress reporting is person-dependent and difficult to track Unable to get an overview of the situation at a glance These challenges can be solved by combining Microsoft Teams with Power Platform (Power Automate/Power Apps/Power BI) to create a task management system that anyone can easily automate and visualize . 1. Architecture Overview Here is the basic concept of this solution: Achieve end-to-end task management from creation to progress tracking and report analysis, all centered around Teams. ![Architecture](/assets/blog/authors/angela.wang/architecture.png =600x688) Components Component Role Description Microsoft Teams Entry point & notification hub Execute task creation, updates, and notifications within channels Power Apps Task management app Register and update tasks with an intuitive UI Power Automate Automation workflow Automate notifications, reminders, and report generation Power BI Visualization & analysis Visualize task completion rates and delay trends in real-time SharePoint List Data storage Store task data 2. Feature Design Task Creation and Assignment (Power Apps) Create a form in Power Apps to input the following information: Task name Assignee (MS user) Status Priority Due date Created by Completion date Details ![feature1](/assets/blog/authors/angela.wang/feature1.png =431x681) After creation, the data is recorded in SharePoint List. Screens for viewing and editing the task list and task details are also created. ![feature2](/assets/blog/authors/angela.wang/feature2.png =431x681) ![feature3](/assets/blog/authors/angela.wang/feature3.png =431x681) Process Automation (Power Automate) Here are typical automation scenarios: Scenario Processing Implementation Comments Task creation Notify assignee via Teams Trigger on "When an item is created" and send to assignee via Teams Implemented Status change Update log Update event to SharePoint Example Past due Remind assignee and supervisor Conditional branching + Teams message Example Weekly report Send task summary to Teams Scheduled trigger Example Data Visualization (Power BI) In Power BI, create reports such as: Task status and progress rate Due date distribution Task count by assignee (*) *: Retrieving assignee information requires editing in Power BI Desktop, but this was not implemented this time. 3. Integrated Experience Through Teams Integration By centering operations around Teams, the following experience can be achieved: Display Power Apps as a tab within Teams channels Automatic notifications via Power Automate Bot Direct viewing of Power BI dashboards In other words, Users can complete task management without ever leaving Teams. This is the true strength of Microsoft 365. *Adding Power Apps and Power BI dashboards as tabs to Teams channels requires permissions, but this was not implemented this time. 4. Data Structure (SharePoint List) Column Name Data Type Description ID Auto number Unique task ID Title Text Task name Description Multi-line text Details Assignee User Assignee Status Choice Status: Not Started / In Progress / Completed / Delayed Priority Choice Priority: High / Medium / Low DueDate Date Due date CreatedBy User Created by CompletedDate Date Completion date 5. Implementation Steps Prepare SharePoint List (Task List) Create "TaskList" in SharePoint. ![list](/assets/blog/authors/angela.wang/list.png =760x440) Build Power Apps (Task Management App) Use the convenient "Start with an app template" feature to quickly build the app. ![powerapps1](/assets/blog/authors/angela.wang/powerapps1.png =760x238) ![powerapps2](/assets/blog/authors/angela.wang/powerapps2.png =736x318) Build Power Automate (Task Management Flow) Implement notifications to be sent to the assignee's Teams when a new task is created. ![powerplatform](/assets/blog/authors/angela.wang/powerplatform.png =760x600) Create Power BI (Task Management Dashboard) Create visuals for "Task Progress" and "Due Date Distribution" on the dashboard. ![powerbi](/assets/blog/authors/angela.wang/powerbi.png =760x328) Teams Integration Settings Add Power Apps and Power BI tabs (requires permissions, but this was not implemented this time). 6. Conclusion By leveraging Power Platform, you can create a task management solution that anyone can build, is immediately usable, and seamlessly fits into your team . Please consider implementing this in your work.
アバター
This article is the Day 5 entry of the KINTO Technologies Advent Calendar 2025🎅🎄 Introduction The KINTO Development Division Frontend Team handles frontend development using React/Next.js. We use Jira for task management and have adopted a ticket-based development flow. As the team has grown, we felt that standardizing development flow conventions and reducing cognitive overhead — such as branch naming conventions, commit message formats, and PR template selection — had become a challenge. This article introduces how we combined Claude Code with Atlassian MCP to automate these things you don't have to think about, creating an environment where developers can focus on solving real problems. Technology Stack Claude Code : AI-driven development assistant Atlassian MCP : API integration with Jira/Confluence (automatic ticket information retrieval) GitHub CLI (gh) : PR operation automation CLAUDE.md : Project-specific rule definitions Background and Challenge: The Cognitive Load Problem in Development Flows What developers should really focus on is writing code. However, in actual development, cognitive resources were being consumed by non-essential tasks like these: "What was that ticket number again? Let me open Jira to check..." "What should the branch name be? What goes after feature/JIRAKEY-1234/ ?" "Should I branch from develop? Or from the project branch?" "Which emoji was it for the commit message, :sparkles: or :wrench: ?" "Do I add :m: to the PR title or not?" "Which PR template should I use? for_dev.md ? The default one?" These may seem trivial, but they are decisions that occur multiple times a day . When accumulated, they significantly drain developers' focus. Solution: Achieving a "No-Thinking" Development Flow "Just provide the ticket number, and everything else is automated." To achieve this, we combined Claude Code with Atlassian MCP. What the Developer Does Developer: "Create a branch for JIRAKEY-1234" What Claude Code Does (Automatically) ✅ Retrieves ticket information via Jira API ✅ Checks project affiliation through epic determination ✅ Automatically generates the appropriate branch name ( feature/JIRAKEY-1234/update_claude_docs ) ✅ Automatically determines the appropriate base branch (develop or project branch) ✅ Creates the branch What the Developer Does Developer: "Commit" What Claude Code Does (Automatically) ✅ Analyzes the changes ✅ Selects the appropriate emoji shortcode ( :pencil: , :bug: , :sparkles: , etc.) ✅ Executes the commit What the Developer Does Developer: "Create a PR" What Claude Code Does (Automatically) ✅ Extracts the ticket number from the branch name ✅ Retrieves the ticket title via Jira API ✅ Automatically generates the PR title ( JIRAKEY-1234: Standardizing Claude Code Operation Rules ) ✅ Automatically determines the base branch (develop or project branch) ✅ Automatically selects the appropriate PR template ✅ Executes PR creation The developer only needs to give three instructions. Branch creation, commit execution, and PR creation are all handled automatically by Claude Code. Implementation: CLAUDE.md — A "Rulebook for AI to Read" All automation is achieved through rules written in a document called CLAUDE.md . ### Branch Naming Conventions Project base branch: `feature/project-name` - Example: `feature/simulation` - Base branch: `develop` Feature branch (under project): `feature/JIRAKEY-ticket-number/description` - Example: `feature/JIRAKEY-1234/add_simulation_list` - Base branch: `feature/project-name` Regular feature branch (outside project): `feature/JIRAKEY-ticket-number/description` - Example: `feature/JIRAKEY-1234/fix_bug` - Base branch: `develop` For parent-child tickets: - Parent branch: `feature/JIRAKEY-parent-ticket-number/develop` - Child branch: `feature/JIRAKEY-parent-ticket-number/JIRAKEY-child-ticket-number/description` Relationship Between Epics and Project Base Branches - Epic determination: If `parent.fields.issuetype.name` of a Jira ticket is "Epic", that ticket belongs to a project - Important: When creating branches for tasks under an epic, always confirm the project base branch name with the user ### Commit Message Format - Required format: `:emoji: JIRAKEY-ticket-number: subject` - Refer to `.commit_template` for emoji shortcodes - Commit examples: :bug: JIRAKEY-1234: Fix crash during login :sparkles: JIRAKEY-2345: Add user profile image upload feature :robot: JIRAKEY-3456: Add tests for login component ### PR Creation Rules - Title format: - Project base branch → develop: `:m: JIRAKEY-ticket-number: ticket-title` - Parent branch → develop: `:m: JIRAKEY-parent-ticket-number: ticket-title` - Regular feature branch → develop: `JIRAKEY-ticket-number: ticket-title` - Other PRs: `JIRAKEY-ticket-number: ticket-title` - Template usage: - PRs with `:m:`: `.github/for_dev_template.md` - PRs without `:m:`: `.github/pull_request_template.md` That's it. No code changes whatsoever. Actual Operation Flow sequenceDiagram actor Developer participant Claude Code participant Jira API participant git participant gh Note over Developer,gh: Branch Creation Flow Developer->>Claude Code: "Create a branch for JIRAKEY-1234" Claude Code->>Jira API: Retrieve ticket information Jira API-->>Claude Code: Title, epic information, etc. Claude Code->>Claude Code: Generate branch name<br/>(feature/JIRAKEY-1234/update_claude_docs) Claude Code->>git: Execute git checkout -b Claude Code-->>Developer: Branch creation complete Note over Developer,gh: Commit Flow Developer->>Claude Code: After code changes, "Commit" Claude Code->>Claude Code: Analyze changes Claude Code->>Claude Code: Auto-generate message in :pencil: format Claude Code->>git: Execute git commit Claude Code-->>Developer: Commit complete Note over Developer,gh: PR Creation Flow Developer->>Claude Code: "Create a PR" Claude Code->>Claude Code: Determine base branch (develop) Claude Code->>Claude Code: Generate PR title<br/>(JIRAKEY-1234: ticket-title) Claude Code->>Claude Code: Select template<br/>(.github/pull_request_template.md) Claude Code->>gh: Execute gh pr create gh-->>Claude Code: PR URL Claude Code-->>Developer: PR creation complete (with URL) Impact: The Value Gained from "No Thinking" Dramatic Reduction in Cognitive Load ✅ Creating branch names ✅ Checking the base branch ✅ Remembering commit message formats ✅ Copying and pasting PR titles from tickets ✅ Selecting PR templates → Everything is completed by "just providing the ticket number" Ensuring Consistency Branch names, commit messages, and PR titles are 100% compliant with project rules The hassle of reviewers pointing out "this doesn't follow the naming convention" has disappeared Reduced Onboarding Time New team members don’t need to worry about memorizing the branch naming rules." Instead of "look at CLAUDE.md", it's just "ask Claude Code" Improved Development Speed The back-and-forth of "opening Jira and copying the ticket title" has disappeared Fewer decisions make it easier to maintain flow state Future Outlook Context Window Optimization Through Sub-agent Utilization In the current implementation, there is an issue where using Atlassian MCP consumes the context window. As a solution, we are considering leveraging sub-agents for hierarchical task distribution. What Are Sub-agents? Claude Code sub-agents are AI assistants specialized for specific tasks, with independent context windows . This enables: ✅ Not polluting the main agent's context ✅ Efficient processing of specialized tasks ✅ Separating bulk information retrieval and processing Implementation Plan: Three Specialized Sub-agents 1. Jira Information Retrieval Sub-agent ( jira-researcher ) **Role**: - Retrieve ticket information via Atlassian MCP - Extract only necessary information (ticket number, title, epic, status) 2. Branch Strategy Determination Sub-agent ( branch-strategist ) **Role**: - Generate branch names from ticket information - Determine parent-child ticket relationships - Decide base branch from branching patterns 3. PR Creation Sub-agent ( pr-creator ) **Role**: - Execute PR creation branching logic - Select appropriate templates Conclusion: AI Assistants Enable "No-Thinking Development" "Just provide the ticket number, and branch creation, commits, and PR creation are all completed." This was achieved with just CLAUDE.md — a "document that AI can read" — and MCP integration. Zero code changes. Zero impact on existing systems. The key point is that we clearly identified what developers don't have to think about and delegated it to AI . By evolving AI assistants from "code completion tools" to "partners for the entire development flow", we can realize a world where developers can focus solely on solving real problems .
アバター
Introduction Hello. I'm Yamada, and I work on developing and operating internal tools at the Platform Engineering Team, Platform Group of KINTO Technologies. Please also check out my previous articles on Spring AI and Text-to-SQL! https://blog.kinto-technologies.com/posts/2025-06-11-springAI/ https://blog.kinto-technologies.com/posts/2025-01-16-generativeAI_and_Text-to-SQL/ In this article, I'd like to share how I built an AI Agent that automatically analyzes and gathers AWS resource dependencies for products built on AWS, using GitHub Copilot. Background and Issues The Platform Engineering Team develops and operates two internal tools: Configuration Management Database (CMDB) and an incident management tool (Incident Manager). CMDB is a configuration management database system that centrally manages configuration information for internal products. It has various features including managing product owners and teams, vulnerability information management. One of these particular features is managing ARN information for AWS resources (ECS, RDS, ALB, CloudFront, etc.) associated with products. For Incident Manager, there was a requirement to visualize the topology information (system architecture diagram) of the product where an incident occurred and highlight the root cause to help us promptly identify it and recover the system after the incident. However, simply having AWS resource ARN information was insufficient to visualize topology information—we needed to understand the dependencies between resources (e.g., CloudFront -> ALB -> ECS -> RDS) . Previously, we needed to manually configure this dependency information, which led to the following issues: Manual updates to dependencies were required every time when new resources were added Complex system architectures causes the difficulty in understanding dependencies Human errors led to missing or incorrect dependency configurations Solution Approach To solve these issues, I decided to build an AI Agent that automatically analyzes and gathers AWS resource dependencies using the ARN information managed by CMDB . After interactions with GitHub Copilot to proceed with the implementation on a trial-and-error basis, I completed an AI Agent with the following capabilities: Automatically analyzes dependencies between AWS resources, based on ARN information retrieved from CMDB Calls multiple AWS APIs to infer connection relationships from security groups and network configurations Saves gathered node (AWS resource) and edge (dependency) information to the database Used for showing a topology diagram when an incident occurs in Incident Manager Technology Stack Development support tool: GitHub Copilot (Agent mode - Claude Sonnet 4.5) AI Agent Framework: LangGraph LLM: Amazon Bedrock (Claude Sonnet 4.5) Language: Python 3.12 Key Libraries: LangChain, LangChain-AWS boto3 (AWS SDK for Python) AI Agent Development Process 1. Initial Prompt First, I asked GitHub Copilot to design the AI Agent with the following prompt, which is partially abbreviated and edited. Using the AWS resource ARN information retrieved from CMDB's ARN management table, implement an AI Agent that automatically analyzes and gathers dependencies between resources. Technology Stack: - LangGraph, LangChain - Amazon Bedrock (Claude Sonnet 4.5) - AWS SDK (boto3) - Python 3.12 Functional Requirements: - API Endpoint: POST /service_configurations - Parameters: sid, environment (both required) - Search for ARNs in the ARN management table using sid and environment as conditions - Using the retrieved CloudFront, S3, WAF, ALB, TargetGroup, ECS, RDS, ElastiCache ARN information, shape Nodes and Edges information with LLM and Agent (LangGraph) - Save gathered information to DB How to Retrieve Dependencies (Few-shot Examples): ### CloudFront -> S3/ALB Edge Determination Method 1. Get domain name using CloudFront API aws cloudfront get-distribution --id {distribution-id} 2. Get Origin from behavior information to determine Edge (relationship) - If DomainName contains s3: CloudFront -> S3 - If DomainName contains ALB: CloudFront -> ALB ### TargetGroup -> ECS Edge Determination Method 1. Get target IP address using elbv2 API aws elbv2 describe-target-health --target-group-arn {arn} 2. Match with ECS task IP addresses to create Edge ### ECS -> RDS Edge Determination Method (determined by security permissions, not actual access) 1. Get ENI (Elastic Network Interface) ID from ECS task aws ecs describe-tasks --cluster {cluster} --tasks {task-arn} 2. Get ECS task security group ID from ENI aws ec2 describe-network-interfaces --network-interface-ids {eni-id} 3. Get RDS security group aws rds describe-db-instances --db-instance-identifier {instance-id} 4. Get inbound rules for RDS security group aws ec2 describe-security-groups --group-ids {sg-id} 5. Create Edge if ECS security group is included in RDS inbound rules AWS APIs to be used: - cloudfront:get_distribution - cloudfront:list_distributions - wafv2:list_web_acls - wafv2:get_web_acl - wafv2:get_web_acl_for_resource - elbv2:describe_target_groups - elbv2:describe_target_health - elbv2:describe_load_balancers - elbv2:describe_load_balancer_attributes - ecs:list_clusters - ecs:describe_tasks - ecs:describe_task_definition - ecs:list_tasks - ecs:describe_services - ec2:describe_network_interfaces - ec2:describe_security_groups - rds:describe_db_clusters - rds:describe_db_instances - elasticache:describe_replication_groups - elasticache:describe_cache_clusters Related DDL: {ARN management table DDL} {Node information table DDL} {Edge information table DDL} Important: - First understand the current repository structure - Organize and confirm the plan for the implementation before its start - Consider extensibility as AWS APIs and Nodes to retrieve may increase - Keep code simple and straightforward Following this prompt, GitHub Copilot first analyzed the entire repository, understood the existing code, and then proposed the following implementation plan, which is partially abbreviated and edited. (Read files...) I have understood the repository, so I will summarize the design approach. Understanding the Repository Structure The existing project has the following structure: ・FastAPI-based API server ・LLM functionality using **Bedrock (Claude)** ・MySQL database connection functionality (implemented in text_to_sql.py) ・Functionality to call AWS APIs with boto3 (implemented in aws_diagram.py) ・LangChain dependencies (listed in pyproject.toml) Implementation Approach 1. Architecture Design Retrieve ARN information from ARN management table (DB) ↓ Gather Edges information by calling AWS APIs with LangGraph Agent ↓ Save Nodes/Edges information to tables (DB) ↓ Return response 2. Module Structure (Newly created files) ・`service_config_router.py`: FastAPI endpoint ・`service_config_agent.py`: Agent implementation using LangGraph ・`aws_tools.py`: AWS API call tool collection ・`service_config_db.py`: DB operations (ARN retrieval, Node/Edge saving) 3. Implementation Phases ・Phase 1 (DB layer): Implement ARN retrieval and Node/Edge saving functions ・Phase 2 (AWS tools layer): Implement CloudFront, ELB, ECS, RDS, etc. API call functions in LangChain Tool format ・Phase 3 (Agent layer): Build LangGraph StateGraph, pass tools to LLM to infer Edge information ・Phase 4 (Router layer): FastAPI endpoint, request validation, and response formatting 4. Key Design Points ・Extensibility: Whitelist approach makes it easy to add new AWS APIs ・Simplicity: Clearer state management with StateGraph and calling each tool using one function per API ・Data flow: Clear flow from API, Agent, AWS APIs, to data save to DB 5. Implementation Code Structure (Pseudocode) (Code samples) Is it okay to proceed with implementation based on the above design approach? Once confirmed, I will start implementation in sequence. Since the design approach was not significantly misaligned with what was in my head at this point, I had GitHub Copilot proceed with the implementation. 2. Iterative Correction Requests Due to various factors including basic design I forgot to include in the prompt, detailed design I couldn't fully communicate, and prompt issues, the generated code didn't work perfectly on my first implementation attempt. I had to go back and forth with GitHub Copilot multiple times to repeatedly correct the code. Since I forgot to include the architecture layer separation in the prompt, a large amount of business logic was implemented in the Controller layer. There were calls to non-existent functions in the Bedrock model invocation process (it was using functions from a different version). Error handling was insufficient. Just while I thought it was working, part of Node and Edge information wasn't being retrieved. I had to improve the Agent's prompts... There were so many issues, but within a few hours, it started working as expected. 3. Final Completed Code GitHub Copilot handled over 90% of the coding. Let me share some key excerpts of what the final code looked like. Agent Implementation with LangGraph This is the core part of the AI Agent. It defines the processing flow using LangGraph. The definition is partially abbreviated and edited. def create_service_config_agent() -> StateGraph: """Create system configuration collection Agent""" workflow = StateGraph(AgentState) # Add nodes workflow.add_node("initialize_nodes", initialize_nodes) workflow.add_node("collect_edges", collect_edges_with_llm) # Set entry point workflow.set_entry_point("initialize_nodes") # Conditional branching: collect edges if nodes exist, otherwise end workflow.add_conditional_edges( "initialize_nodes", should_collect_edges, { "collect_edges": "collect_edges", "end": END } ) workflow.add_edge("collect_edges", END) return workflow.compile() def collect_edges_with_llm(state: AgentState) -> AgentState: """Collect edge information using LLM and tools""" llm = get_llm_for_agent() llm_with_tools = llm.bind_tools(AWS_TOOLS) prompt = f""" You are an expert in analyzing AWS resource dependencies. # Task From ARN information of the following AWS resources (Nodes), identify and infer the connection relationships (Edges) between resources. Understand the characteristics of each AWS service and common architecture patterns, and call appropriate AWS APIs to verify connections. # Available Nodes {nodes_summary} # Available Tools 1. **call_aws_api**: A tool that can call permitted AWS APIs - Can retrieve resource details, configurations, and related resources 2. **extract_resource_id_from_arn**: Extract resource ID and other information from ARN - Can retrieve parameters (ID, name, etc.) needed for API calls # Available AWS APIs (no others can be used) - cloudfront:get_distribution - wafv2:get_web_acl_for_resource - elbv2:describe_target_groups ... # Edge Detection Methods Infer and investigate connections between resources from the following perspectives: ## Common Connection Patterns 1. **Frontend layer**: CloudFront -> S3/ALB, WAF -> CloudFront/ALB, Route53 domain -> CloudFront 2. **Load balancer layer**: ALB -> Target Group -> ECS/EC2 3. **API layer**: API Gateway -> Lambda 4. **Application layer**: ECS -> RDS/ElastiCache (via security groups), Lambda -> RDS (via security groups) 5. **Data layer**: RDS, ElastiCache ## Connection Detection Approach - **Configuration-based**: When resource configuration contains ARNs or IDs of other resources (e.g., CloudFront Origins settings) - **Network-based**: When permitted by security group inbound rules (e.g., ECS -> RDS) - **Service characteristics**: Connections that can be logically inferred from each AWS service's role (e.g., TargetGroup -> ECS) ## Important Investigation Points - **ARN analysis**: First analyze ARN with extract_resource_id_from_arn to identify resource type and required parameters - **Incremental investigation**: Don't call all APIs at once; determine the next needed API based on results - **Security groups**: Verify ECS/RDS/ElastiCache connections through security group inbound rules - Check if ECS ENI -> Security Group ID -> is included in RDS/ElastiCache SG inbound rules - **Error handling**: Continue investigation even if unauthorized APIs or errors are returned # Few-shot Examples (must be referenced) ## Example 1: ECS -> RDS Investigation 1. Get ECS task ENI: call_aws_api("ecs", "describe_tasks", ...) 2. Get SG from ENI: call_aws_api("ec2", "describe_network_interfaces", ...) 3. Get RDS SG: call_aws_api("rds", "describe_db_instances", ...) 4. Verify SG match -> Create Edge ... # Important Notes - Check all resource combinations - Continue investigation even if API errors occur - Ignore RDS snapshot, parameter group, subnet group, etc. - Determine connectivity by security groups # Output Format When investigation is complete, return results in the following JSON format: ```json {{ "nodes": [ {{ "service_name": "cloudfront", "arn": "xxx", "resource": "" }} ], "edges": [ {{ "from_arn": "xxx", "to_arn": "xxx", "details": "xxx" }} ] }} ``` """ messages = [HumanMessage(content=prompt)] try: max_iterations = 30 # Maximum iteration count edges = [] for iteration in range(max_iterations): response = llm_with_tools.invoke(messages) messages.append(response) # Check if there are tool calls if hasattr(response, 'tool_calls') and response.tool_calls: # Execute tools tool_node = ToolNode(AWS_TOOLS) tool_results = tool_node.invoke({"messages": messages}) # Add tool results to messages messages.extend(tool_results["messages"]) else: # If no tool calls, process as final response try: # Create LLM for structured response structured_llm = llm.with_structured_output(GraphResult) # Add final result summary prompt final_prompt = """ Investigation complete. Return all discovered edges and new nodes (such as domain names) in JSON format. """ messages.append(HumanMessage(content=final_prompt)) # Get structured response result = structured_llm.invoke(messages) edges = [edge.model_dump() for edge in result.edges] new_nodes = [node.model_dump() for node in result.nodes] # Add new nodes returned by LLM (such as domain names) to existing node list if new_nodes: state["nodes"].extend(new_nodes) except Exception as e: # Fallback processing on error ... break state["edges"] = edges state["current_step"] = "edges_collected" state["messages"] = messages except Exception as e: ... state["edges"] = [] return state AWS API Call Tools These are the tools used by the Agent. A whitelist approach ensures the safety of executable AWS APIs. The tools are partially abbreviated and edited. # Whitelist of allowed AWS APIs ALLOWED_AWS_APIS: Set[str] = { # CloudFront "cloudfront:get_distribution", "cloudfront:list_distributions", # WAF "wafv2:list_web_acls", "wafv2:get_web_acl", "wafv2:get_web_acl_for_resource", ... } @tool def call_aws_api( service_name: str, method_name: str, parameters: Dict[str, Any], region: str = "ap-northeast-1" ) -> Dict[str, Any]: """General AWS API call tool This tool can only call permitted AWS APIs. Call the AWS APIs needed to retrieve Edge information. Args: service_name: AWS service name (lowercase) Permitted: 'cloudfront', 'wafv2', 'elbv2', 'ecs', 'ec2', 'rds', 'elasticache', 'apigateway', 'lambda' method_name: Method name to call (boto3 method name, snake_case) Examples: 'get_distribution', 'describe_target_health', 'describe_security_groups' parameters: Dictionary of parameters to pass to the method Examples: {"Id": "ABC123"} or {"GroupIds": ["sg-12345"]} region: AWS region (default: ap-northeast-1) Returns: Dictionary of API call results Returns {"error": "error message"} in case of error List of permitted APIs: - cloudfront:get_distribution - cloudfront:list_distributions - wafv2:list_web_acls - wafv2:get_web_acl - wafv2:get_web_acl_for_resource - ... Examples: # Get CloudFront Distribution information call_aws_api( service_name="cloudfront", method_name="get_distribution", parameters={"Id": "ABC123"} ) # Get target group health information call_aws_api( service_name="elbv2", method_name="describe_target_health", parameters={"TargetGroupArn": "arn:aws:elasticloadbalancing:..."} ) # Get security group information call_aws_api( service_name="ec2", method_name="describe_security_groups", parameters={"GroupIds": ["sg-12345"]} ) # Get ECS task information call_aws_api( service_name="ecs", method_name="describe_tasks", parameters={"cluster": "my-cluster", "tasks": ["arn:aws:ecs:..."]} ) """ try: # Step 1: Validate if API is in whitelist is_valid, error_message = validate_aws_api(service_name, method_name) if not is_valid: return { "error": error_message, "error_type": "unauthorized_api", "allowed_apis": get_allowed_apis_list() } # Step 2: Get client client = get_aws_client(service_name, region) # Step 3: Check if method exists if not hasattr(client, method_name): return {"error": error_msg, "available_methods": dir(client)} # Step 4: Get method and execute method = getattr(client, method_name) response = method(**parameters) return response except Exception as e: ... Here are the key points I focused on in this implementation: Whitelist approach: Prevents LLM from calling arbitrary AWS APIs, ensuring safety Dynamic API calls: Dynamically executes boto3 methods using Python's getattr() Detailed docstrings: Includes argument descriptions, usage examples, and permitted API list to allow LLM to understand how to use the tools Extensibility: Adding new APIs only requires adding them to ALLOWED_AWS_APIS Topology Rendering in Incident Manager I created a system topology diagram in Incident Manager using the Node and Edge information gathered by the AI Agent. Here is how it finally looked like: When a system failure occurs, the affected area turns red, helping us to intuitively understand the system architecture and failure location at a glance! Thoughts on Implementing with GitHub Copilot Positive Aspects Significant reduction in development time This implementation was completed in about a day. Without GitHub Copilot, I would have needed to start by learning LangGraph, which would have taken at least several weeks. High-quality implementation plan proposals While there's still room for improvement, following detailed information about what I wanted to achieve and the design in the initial prompt, GitHub Copilot generated the following high-quality implementation plan: Proposed a consistent design based on the understanding of the existing repository structure Proposed a design with both extensibility and simplicity Interactive quality improvement When I pointed out concerns during implementation, corrections were made immediately: Architecture improvements Library version issues Adding error handling And more Challenging Aspects Validation of generated code is essential Code generated by generative AI, not just by GitHub Copilot, requires the person who gave the instructions to take responsibility for reviewing it . In AI-assisted coding, review takes the most time. I reviewed the code from the following perspectives: Compliance with existing code conventions: Does it follow the repository's naming conventions and coding style? Requirements coverage: Are all specified functional requirements implemented without omissions? Architecture patterns: Is there appropriate layer separation? Are responsibilities clear? Implementation appropriateness: Are there more common or simpler implementation methods? Is it unnecessarily complex? Error handling: Is exception handling properly implemented? Are error messages appropriate? Operational verification: Does it work as intended when the app is actually started? Future Plans Adding more Nodes and Edges to gather Currently, as an initial trial, I limited the Nodes and Edges retrieval to specific AWS services. The prompt allows for easy extension as retrieved AWS resources were added, and then the whitelist used for the AWS API call tools includes permitted APIs to retrieve Edge information. Therefore, I plan to gradually increase the number of gathered resources going forward. Creating prompt templates Since there were oversights in the initial instructions this time, I created a prompt template, which is reusable and helpful to implement new features. By including the following content, I aim to generate higher-quality code from the initial instructions: Feature overview Technology stack (framework, libraries, language version) Functional requirements Architecture requirements (layer structure, error handling, etc.) Non-functional requirements (extensibility, performance, security) Confirmation items before implementation (understanding repository structure, checking consistency with existing code, etc.) Important notes (placed at the end of the prompt with clearly specific rules that must be followed) Summary This time, I implemented an AI Agent that gathers system topology information for about one day using GitHub Copilot. With the implemented AI Agent, AWS resource dependencies that previously required manual configuration are now automatically gathered, enabling topology visualization when an incident arises in Incident Manager. I plan to continue actively utilizing generative AI, including GitHub Copilot, to increase development productivity.
アバター
Introduction I'm tetsu from the Platform Group. In this article, I'll summarize the operations behind our company-wide joint study sessions at KTC. The study sessions are held monthly, with over 50 participants attending each time. I've compiled the tips and tricks for keeping them going, so this is a must-read for those who want to hold study sessions at their company or are thinking about starting ones! Overview of the Study Sessions Frequency: Once a month Format: Hybrid (in-person and Zoom) Scale: Approx. 50–100 people What the session is like: Speakers come from various divisions across the company Engineers, designers, directors, HR members, and more Each person presents for 10–15 minutes; for example, with 3 speakers, it takes an hour in total A meetup session is held after the study session where attendees can talk with the speakers Anyone who wants to present can do so, regardless of the presentation topic Examples of past presentation topics Sharing details about recently released projects Promoting tools developed internally Sessions focused on specific topics (on figma study sessions, creative generative AI, security, QA, etc.) Timetable 17:05–17:10 Opening 17:10–17:25 Presentation 1 17:25–17:40 Presentation 2 17:40–17:55 Presentation 3 17:55–18:00 Closing 18:00–19:00 Meetup session (optional) Background of the Study Sessions Before these study sessions began, each group within KTC was already holding their own study sessions and orientations. However, there was a situation where know-how was not easily shared between teams in different divisions or those with little work-related interaction. That was a concern, which some people felt. To this end, the joint study sessions were launched with an aim of creating a place where teams and divisions with little interaction can share their knowledge with each other and strengthen collaboration between them. Continuing Study Sessions Is Surprisingly Difficult While many companies share backgrounds similar to the above-mentioned one, many internal study sessions start up but eventually fizzle out. We've managed to continue for over 15 sessions so far, but it certainly hasn't been smooth sailing. In general, the barriers to continuity, which we've actually faced, include: Operations becoming dependent on specific individuals : When a heavy workload is shouldered on specific individuals, and if they transfer or leave their organizations, business operation cannot be run smoothly, which may result in the increase in operational costs. Difficulty in holding hybrid events : Meeting the demands for both online and on-site participants is harder than you expected Consistency of participants : At the launch of study sessions, things are lively, but gradually, only specific participants came to regularly engage in the sessions To overcome these barriers, we've put the following four tips into action. Tips and Tricks for Continuity Tip 1: Standardizing Operations and Implementing a Rotation System To prevent specific individuals attending study sessions from bearing an excessive burden of the operation for the sessions, we've implemented the following: Identify all necessary tasks for the operation and manage them as Jira tickets Assign the tickets to the operation team members on a monthly rotation The first point is to identify all necessary tasks and managing them as Jira tickets. We use Jira's feature to automatically create its tickets for running the joint study sessions. This allows all members to understand the tasks required for operations. The second point is to assign the ticket to the operation team members on a monthly rotation. We divide the study session tasks in the following three categories to rotate them: facilitation, venue preparation, and coordination. The task rotation helps all operations team members understand every task, which prevents dependence on specific individuals. When running study sessions, it's not uncommon for the operators to become exhausted, which causes the sessions to fall through. For joint study sessions in KTC, we've standardized the operation to reduce the workload. *JIRA tickets are issued and organized as shown below. Tip 2: Creating an Environment Where All Employees Can Easily Attend Continuing to hold study sessions, we may face issues of consistent participants or lower engagement of online attendees. We need to address these issues because study session can be more valuable when it offers opportunities not only to listen but also to discuss and share opinions among attendees about what they've learned. To prevent the issues, we've implemented the following measures: Schedule the study session on all employees' Outlook calendars Hold the session in a communication space shared on a company-wide basis Prepare a Slack channel for casual chat and opinion exchange that allows online participants to easily join in and the operations team to actively participate in the chat Avoid holding sessions during busy times like the beginning of the month The goal of the study session isn't just to attend but to increase business knowledge and technical skills through participation. That said, you can't get started without attending the session, so it is important to make a framework for casual participation. Slack for casual chat (See below) Tip 3: Responding to Survey Requests This may seem obvious, but we make sure to respond to requests from surveys regarding the study sessions. For example, we received the following feedback: "I'd like to know about case studies on how business specifications are decided" "The venue is really quiet when presentation is not provided, so I thought playing some background music might be nice" "I'd like some salty snacks at the meetup session" We receive various request. Some related to study session topics, some about improving the venue atmosphere, and so on. We read through each one, consider the background, and try to respond to it appropriately. " I'd like to know about case studies on how business specifications are determined " -> Project managers, product managers, and producers handle these business requirements, so it might be good to hear the case studies from these people with multiple perspectives! Additionally, this request probably came from an engineer, so it would be great to create a good point of connection between engineers and the people making decisions on business specifications. "The venue is really quiet when presentation is not provided, so I thought playing some background music might be nice" -> Indeed, we understand silence is uncomfortable moment... We could play the background music and have the facilitator fill the time between presentations so there's no awkward silence! "I'd like some salty snacks at the networking session" -> We may have a narrow preference of the snack flavors. Let's add some salty options! Tip 4: Continuous Improvement Cycle The tips above were established through retrospectives held among the operations team members after each study session. At KTC's joint study sessions, we use the KPT method for retrospectives as shown in the table below, continuing to keep what's good and improve problems to prevent us from the recurrence. (Improvements from this process get reflected in the operations JIRA tickets.) Category Keep (Good/Worked well/Want to continue) Problem (Issue/Challenge/Trouble) Try (Intention for improvements next time) Discussion on the day Overall This was the first time we held a session on a specific topic. We could do sessions in such style a few more times in the future Attendance might be low on Tuesdays at 5 P.M. Change the event time ... Operations before the event ... ... ... ... Preparation / clean-up on the event day ... ... ... ... Presentations ... ... ... ... Meetup session ... ... ... ... Survey ... ... ... ... Positive Effects of Continuing the Study Group for Over a Year Here are some positive effects we've noticed from continuing the study sessions. It has become a place for exchanging opinions and collaborating with people from different departments Particularly from people who work across the company, such as those in divisions engaging in shared internal tools or security-related projects, we especially receive comments, like "I'm happy to be able to exchange opinions" Requests for presentations from employees and collaborative study sessions with other departments have increased, so we no longer run out of topics We receive various requests, such as "I want to practice for an external presentation" or "I introduced a new development method to drive a project forward and want to talk about it" We, operation members, don't give a presentation, seeking speakers as volunteers to run the sessions in a manner of that we can respond to various requests, so we haven't run out of presentation topics Conclusion Finally, our study session participants are taking time out of their busy schedules to attend the sessions even though they could spend time in doing other things. To meet their expectations, we strive to provide high-quality study sessions. Of course, you can't run things perfectly from the start. The bottom line is to take that first step, and then, listen to participants' demands and continuously make improvements through retrospectives—that's what we believe matters. Thank you for reading to the end!
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の4日目の記事です🎅🎄 はじめに Platformグループのtetsuです。 本記事では、KTCで実施している全社横断の合同勉強会の運営に関する内容をまとめます。 この合同勉強会は毎月開催され、毎回50人以上の方が参加しています。 継続するための工夫やコツをまとめましたので、「社内で勉強会を開催したい方」「これから始めたい方」必見です! 勉強会の概要 開催頻度:月1回 形式:ハイブリッド(オフライン・Zoom) 規模:約50〜100名 特徴: 全社の様々な部署から登壇者が集まる エンジニア、デザイナー、ディレクター、人事メンバーなど 1人あたり10〜15分の発表 × 3人で計1時間 勉強会後に登壇者と話せる交流会を実施 テーマを問わず、登壇したい人が登壇できるようにする 過去の登壇テーマ例 直近でリリースのあったプロジェクトの内容共有 社内向けに作成したツールの宣伝 特定テーマ会(Figma勉強会、クリエイティブ関連の生成AI、セキュリティ、QA など) タイムテーブル 17:05~17:10 オープニング 17:10~17:25 発表1 17:25~17:40 発表2 17:40~17:55 発表3 17:55~18:00 クロージング 18:00~19:00 交流会(任意参加) 勉強会の実施背景 勉強会が開催される以前は元々はKTC内の各グループで勉強会やオリエンテーションが開催されていました。ただ、他部署や業務で関わりが少ないチーム間ではお互いのノウハウが共有されにくい状況であり、そこに課題を感じている人たちがいました。 そこで、「関わりが少ないチームや他部署間で良いノウハウを共有しあえる場所を作りたい」「部署やチーム間の連携を高められる場所を作りたい」という目的意識から、合同勉強会の発足に至りました。 勉強会を継続するのって意外と大変 ただ、上記のような実施背景は多くの企業で持ちながらも、社内勉強会が立ち上がっては消えていくことが多いと思います。 私たちも15回以上継続できていますが、決して順風満帆ではありませんでした。 一般的に、そして私たちも実際に直面した「継続を阻む壁」は以下のようなものです: 運営の属人化 : 特定の人に負担が集中し、その人が異動・退職すると立ち行かなくなり、運営工数が上がってしまう ハイブリッド開催の難しさ : オンライン・オフライン両方を満足させるのは想像以上に難しい 参加者の固定化 : 最初は盛り上がるが、徐々に参加者が固定化されていく これらの課題に対して、私たちは以下の4つの工夫を実践してきました。 継続するための工夫・コツ 工夫1: 運営タスクの標準化とローテーション制 運営の属人化を防ぐために、工夫している内容は以下の通りです。 やるべきタスクを洗い出し、チケットにして管理 チケットの担当者を毎月運営メンバー内でローテーションでアサイン 1点目の「やるべきタスクを洗い出し、チケットにして管理」については、JIRAの「自動化」機能を使い、合同勉強会を実施するためのチケットを自動で作成するようにしています。これにより誰でも運営に必要なタスクを把握することが可能です。 2点目の「チケットの担当者を毎月運営メンバー内でローテーションでアサイン」については、「司会」「会場準備」「調整」の3分類のタスクをローテーションするようにしています。ローテーションすることで運営メンバーが全部のタスクを経験することになるため、属人化を防ぐことができています。 勉強会の運営をするにあたり、運営が疲弊して頓挫してしまうケースも少なくないと思います。KTCの合同勉強会では、運営工数を減らせるように標準化しています。 ※JIRAのチケットは以下のように切っています。 工夫2: 全社員が参加しやすい環境づくり 参加者が固定化したり、オンラインで参加する人のエンゲージメントが低くなってしまうことがあると思います。勉強会は聞くだけでなく、聞いた内容をもとに意見を言い合えるとより効果があると考えているため、これらは課題になります。これらを防げるように以下のように工夫しています。 全社員のOutlook カレンダーに勉強会を登録 会社にある全社員が利用できる交流スペースで実施 オンラインの人でも参加しやすいようにワイガヤ(雑談・意見交換)用のSlackチャンネルを用意し、運営も積極的にワイガヤする 月初など忙しいタイミングの開催は避ける 勉強会に参加することがゴールではなく、勉強会への参加を通じて業務知識や技術力の向上に繋がることが大事ではあると思いますが、勉強会に参加しないと始まらないので、参加しやすくすることは大切です。 ⇓ワイガヤ用のSlack 工夫3: アンケート要望に応える 当たり前かもしれませんが、アンケートでいただいた要望には応えるようにします。 例えば、アンケートには次のようなものが届きました。 「業務仕様をどうやって決めているのかが知れるような事例を聞きたい」 「発表の無いときの会場がすごく静かなので、BGMとかあってもよいかなーと思いました」 「交流会で提供されるお菓子に塩辛いものが欲しい」 勉強会のテーマに関わる要望、会場の雰囲気をいい感じにしてほしい要望、など色々と要望をいただきますが、それぞれ目を通して、その背景を考えながら要望に応えるようにします。 「業務仕様をどうやって決めているのかが知れるような事例を聞きたい」 ⇒ プロジェクトマネージャやプロダクトマネージャ、プロデューサーがこういった業務要件を検討するので、これらの人から多角的に聞けるといいかもしれない!あとエンジニアの人から来てそうな要望だから、エンジニア <-> 業務仕様を決める人たちのいい接点が生まれる場にできるといいなあ。 「発表の無いときの会場がすごく静かなので、BGMとかあってもよいかなーと思いました」 ⇒ 確かに、無言の時間って気まずい・・。BGMも改善したほうがいいし、司会の人が間を繋いで気まずい時間が流れないようにしよう! 「交流会で提供されるお菓子に塩辛いものが欲しい」 ⇒ 味が偏っているかもしれない。塩辛いの追加してみよう! 工夫4: 継続的な改善サイクル 上記の工夫たちは勉強会を開催後に運営メンバー内で振り返りをした結果として生まれたものです。 KTCの合同勉強会の運営では以下の表のようにKPT 法を利用して振り返りを行い、「良いことを継続」「問題は再発しないように改善」を続けています(この改善を通じて、運営のJIRAチケットに反映されるものが増えています)。 カテゴリ Keep(良い/上手い/続けたい) Problem(問題だ/課題だ/困った) Try(次回こうしたい) 当日議論 全体的に 特定のテーマに沿って実施したのが初。今後も何回か実施しても良いと思う 火曜の17時だと人の集まり悪いかも 開催時間を変更する ・・・ 前日までの運営 ・・・ ・・・ ・・・ ・・・ 当日準備/片付け ・・・ ・・・ ・・・ ・・・ 発表の部 ・・・ ・・・ ・・・ ・・・ 交流会の部 ・・・ ・・・ ・・・ ・・・ アンケート ・・・ ・・・ ・・・ ・・・ 勉強会を1年以上続けてきた嬉しい効果 勉強会を継続してきたことで感じた嬉しい効果を紹介します。 他部署の人と意見交換ができたり、プロデュースできる場となってる 社内共通ツールやセキュリティ関連部署など、社内横断で仕事をする人たちから特に「意見交換ができて嬉しい」というコメントをもらえます 社員からの登壇や部署ごとのコラボ勉強会の要望が増えて勉強会ネタに困らなくなってきた 「外部登壇の練習をしたい」「プロジェクトを推進するのに新しい開発手法を入れてみたから話してみたい」など、いろいろな要望をいただきます 運営自身で登壇することはせず、登壇者を募って実施する形式にしており、いろいろな要望に応えられるようにしているので、ネタには困っていない状態です 最後に 最後に、勉強会に参加される方々は、他にできることがある中で貴重な時間を割いて参加してくれています。その期待に応えられるよう、私たちは質の高い勉強会の提供を心がけています。 もちろん、最初から完璧な運営ができるわけではありません。大切なのは、まず一歩を踏み出すこと。そして参加者の声に耳を傾け、振り返りを重ねながら、継続的に改善していくことだと考えています。 最後まで読んでいただき、ありがとうございました!
アバター
はじめに こんにちは。KINTOテクノロジーズ プラットフォームグループ Platform Engineeringチームで内製ツールの開発・運用をおこなっている山田です。 過去に書いたSpring AIとText-to-SQLの記事もぜひご覧ください! https://blog.kinto-technologies.com/posts/2025-06-11-springAI/ https://blog.kinto-technologies.com/posts/2025-01-16-generativeAI_and_Text-to-SQL/ 今回はGitHub Copilotを活用して、AWS上で構築しているプロダクトの、AWSリソースの依存関係を自動で分析・収集するAI Agentを構築したお話をしたいと思います。 背景と課題 Platform Engineeringチームでは、CMDB (Configuration Management Database) とIncident Manager (インシデント管理ツール) という2つの内製ツールを開発・運用しています。 CMDBは構成管理データベースというシステムで、社内プロダクトの構成情報を一元管理しています。CMDBにはプロダクトの担当者や担当チーム、脆弱性情報の管理などさまざまな機能があり、その一つにプロダクトに関連するAWSリソース (ECS、RDS、ALB、CloudFrontなど) のARN情報を管理する機能があります。 Incident Managerでは、インシデント発生時に迅速な原因特定と復旧をサポートするため、 インシデントが発生したプロダクトのトポロジー情報 (システム構成図) と原因箇所を可視化する機能 が求められていました。 しかし、トポロジー情報を可視化するためには、単にAWSリソースのARN情報を持っているだけでは不十分で、 リソース間の依存関係 (例: CloudFront → ALB → ECS → RDS) を把握する必要がありました。 従来、この依存関係情報は手動で設定する必要があり、以下のような課題がありました。 新しいリソースが追加されるたびに手動で依存関係を更新する必要がある 複雑なシステム構成では依存関係の把握が困難 人的ミスによる依存関係の設定漏れや誤り 解決アプローチ これらの課題を解決するために、 CMDBが管理しているARN情報を活用して、AWSリソースの依存関係を自動で分析・収集するAI Agentを構築する ことにしました。 GitHub Copilotと対話しながら実装を進めた結果、以下のような機能を持つAI Agentが完成しました。 CMDBから取得したARN情報を起点に、AWSリソース間の依存関係を自動で分析 複数のAWS APIを呼び出して、セキュリティグループやネットワーク設定から接続関係を推論 収集したノード (AWSリソース) とエッジ (依存関係) の情報をデータベースに保存 Incident Managerでインシデント発生時のトポロジー図表示に活用 技術スタック 開発支援ツール: GitHub Copilot (Agentモード - Claude Sonnet 4.5) AI Agent Framework: LangGraph LLM: Amazon Bedrock (Claude Sonnet 4.5) 言語: Python 3.12 主要ライブラリ: LangChain, LangChain-AWS boto3 (AWS SDK for Python) AI Agent構築の流れ 1. 最初のプロンプト まず初めに、以下のプロンプトでGitHub CopilotにAI Agentを構築するための設計をお願いしました。(一部、省略・編集しています) CMDBのARN管理テーブルから取得したAWSリソースのARN情報を使って、 リソース間の依存関係を自動で分析・収集するAI Agentを実装してください。 技術スタック: - LangGraph、LangChain - Amazon Bedrock (Claude Sonnet 4.5) - AWS SDK (boto3) - Python 3.12 機能要件: - API Endpoint: POST /service_configurations - パラメータ: sid, environment (どちらも必須) - ARN管理テーブルからsid、environmentを条件にARNを検索 - 取得したCloudFront、S3、WAF、ALB、TargetGroup、ECS、RDS、ElastiCacheのARN情報を使って、Nodes、Edges情報をLLMとAgent (LangGraph) で成形 - 収集した情報をDBに保存 依存関係の取得方法 (Few-shot Examples): ### CloudFront → S3/ALB のEdge判定方法 1. CloudFront APIでドメイン名を取得 aws cloudfront get-distribution --id {distribution-id} 2. ビヘイビア情報からOriginを取得してEdge(紐づき)を判定 - DomainNameにs3がある場合: CloudFront → S3 - DomainNameにALBがある場合: CloudFront → ALB ### TargetGroup → ECS のEdge判定方法 1. elbv2のAPIでターゲットのIPアドレスを取得 aws elbv2 describe-target-health --target-group-arn {arn} 2. ECS APIでタスクのIPアドレスと照合してEdgeを作成 ### ECS → RDS のEdge判定方法 (実際のアクセスではなく、セキュリティの許可で判定) 1. ECSタスクからENI (Elastic Network Interface) のIDを取得 aws ecs describe-tasks --cluster {cluster} --tasks {task-arn} 2. ENIからECSタスクのセキュリティグループIDを取得 aws ec2 describe-network-interfaces --network-interface-ids {eni-id} 3. RDSのセキュリティグループを取得 aws rds describe-db-instances --db-instance-identifier {instance-id} 4. RDSセキュリティグループのインバウンドルールを取得 aws ec2 describe-security-groups --group-ids {sg-id} 5. ECSのセキュリティグループがRDSのインバウンドルールに含まれていればEdge作成 使用予定のAWS API: - cloudfront:get_distribution - cloudfront:list_distributions - wafv2:list_web_acls - wafv2:get_web_acl - wafv2:get_web_acl_for_resource - elbv2:describe_target_groups - elbv2:describe_target_health - elbv2:describe_load_balancers - elbv2:describe_load_balancer_attributes - ecs:list_clusters - ecs:describe_tasks - ecs:describe_task_definition - ecs:list_tasks - ecs:describe_services - ec2:describe_network_interfaces - ec2:describe_security_groups - rds:describe_db_clusters - rds:describe_db_instances - elasticache:describe_replication_groups - elasticache:describe_cache_clusters 関連するDDL: {ARN管理テーブルのDDL} {Node情報テーブルのDDL} {Edge情報テーブルのDDL} 重要: - まずは現状のリポジトリ構成を理解する - 実装を始める前に整理して、実装方針を確認してから実装を行う - 呼び出すAWS APIや取得するNodeは増える可能性があるため、拡張性を考慮する - コードは簡単でシンプルな内容にする このプロンプトに対して、GitHub Copilotはまずリポジトリ全体を分析し、既存のコードを理解した上で以下の実装方針を提案してくれました。(一部、省略・編集しています) (Read files...) リポジトリの理解ができましたので、設計方針をまとめます。 リポジトリ構造の理解 既存のプロジェクトは以下の構成になっています: ・FastAPIベースのAPIサーバー ・**Bedrock (Claude)**を使用したLLM機能 ・MySQLデータベース接続機能(text_to_sql.pyで実装例あり) ・boto3でAWS APIを呼び出す機能(aws_diagram.pyで実装例あり) ・LangChainの依存関係あり(pyproject.tomlに記載) 実装方針 1. アーキテクチャ設計 ARN管理テーブルからARN情報取得 (DB) ↓ LangGraph Agent でAWS APIを呼び出し、Edges情報を収集 ↓ Nodes/Edges情報をテーブルに保存 (DB) ↓ レスポンス返却 2. モジュール構成 (新規作成ファイル) ・`service_config_router.py`: FastAPIエンドポイント ・`service_config_agent.py`: LangGraphによるAgent実装 ・`aws_tools.py`: AWS API呼び出しツール群 ・`service_config_db.py`: DB操作 (ARN取得、Node/Edge保存) 3. 実装フェーズ ・Phase 1 (DB層): ARN取得とNode/Edge保存関数の実装 ・Phase 2 (AWSツール層): CloudFront、ELB、ECS、RDS等のAPI呼び出し関数をLangChain Tool形式で実装 ・Phase 3 (Agent層): LangGraphのStateGraphを構築し、LLMにツールを渡してEdge情報を推論 ・Phase 4 (Router層): FastAPIエンドポイントとリクエスト検証・レスポンス整形 4. 設計の重要ポイント ・拡張性: ホワイトリスト方式で新しいAWS APIを追加しやすく ・シンプルさ: StateGraphで状態管理を明確に、各ツールは1関数=1API呼び出し ・データフロー: API → Agent → AWS APIs → DB保存という明確な流れ 5. 実装コード構造(擬似コード) (コードのサンプル) この設計方針で実装を進めてよろしいでしょうか? 確認いただけましたら、順次実装を開始します。 この設計時点で大きく方針がずれていなかったため、このままGitHub Copilotに実装してもらいました。 2. 繰り返し修正依頼 プロンプトに書き忘れた基本設計、すべて伝えきれていない詳細設計、プロンプトの問題など様々な要因はありますが、生成されたコードは一発で完璧に動くことはなく、GitHub Copilotと何度もやりとりをしてコードの修正を繰り返しました。 アーキテクチャのレイヤー分けをプロンプトに書き忘れたため、Controller層に大量にビジネスロジックを実装されてしまったり、 Bedrockのモデル呼び出し処理で存在しない関数呼び出しをしていたり (別バージョンの関数を利用していた)、 エラーハンドリングが足りなかったり、 動いたと思ったら取得できていないNodeとEdgeの情報があったり、 Agentのプロンプトの改善をしたり... たくさんの問題がありましたが、数時間で想定通りの動作をするようになりました。 3. 最終的に完成したコード 9割以上をGitHub Copilotにコーディングをしてもらって最終的にどんなコードになったのか、一部重要な部分を抜粋してご紹介しようと思います。 LangGraphによるAgent実装 AI Agentの核心部分です。LangGraphを使って処理フローを定義しています。(一部、省略・編集しています) def create_service_config_agent() -> StateGraph: """システム構成収集Agentを作成""" workflow = StateGraph(AgentState) # ノードを追加 workflow.add_node("initialize_nodes", initialize_nodes) workflow.add_node("collect_edges", collect_edges_with_llm) # エントリーポイントを設定 workflow.set_entry_point("initialize_nodes") # 条件分岐: ノードが存在すればEdge収集、なければ終了 workflow.add_conditional_edges( "initialize_nodes", should_collect_edges, { "collect_edges": "collect_edges", "end": END } ) workflow.add_edge("collect_edges", END) return workflow.compile() def collect_edges_with_llm(state: AgentState) -> AgentState: """LLMとツールを使用してエッジ情報を収集""" llm = get_llm_for_agent() llm_with_tools = llm.bind_tools(AWS_TOOLS) prompt = f""" あなたはAWSリソースの依存関係を分析するエキスパートです。 # タスク 以下のAWSリソース (Nodes) のARNから、リソース間の接続関係 (Edges) を推測・特定してください。 各AWSサービスの特性と一般的なアーキテクチャパターンを理解し、適切なAWS APIを呼び出して接続を確認してください。 # 利用可能なNodes {nodes_summary} # 利用可能なツール 1. **call_aws_api**: 許可されたAWS APIを呼び出せるツール - リソースの詳細情報、設定、関連リソースを取得できます 2. **extract_resource_id_from_arn**: ARNからリソースIDやその他の情報を抽出 - API呼び出しに必要なパラメータ (ID、名前など) を取得できます # 使用可能なAWS API (これ以外は使用できません) - cloudfront:get_distribution - wafv2:get_web_acl_for_resource - elbv2:describe_target_groups ... # Edge検出の方法 以下の観点から、リソース間の接続を推測・調査してください: ## 一般的な接続パターン 1. **フロントエンド層**: CloudFront → S3/ALB、WAF → CloudFront/ALB、Route53ドメイン → CloudFront 2. **ロードバランサー層**: ALB → ターゲットグループ → ECS/EC2 3. **API層**: API Gateway → Lambda 4. **アプリケーション層**: ECS → RDS/ElastiCache (セキュリティグループ経由) 、Lambda → RDS (セキュリティグループ経由) 5. **データ層**: RDS、ElastiCache ## 接続検出の考え方 - **設定ベース**: リソースの設定に他のリソースのARNやIDが含まれている場合 (例: CloudFrontのOrigins設定) - **ネットワークベース**: セキュリティグループのインバウンドルールで許可されている場合 (例: ECS → RDS) - **サービス特性**: 各AWSサービスの役割から論理的に推測できる接続 (例: TargetGroup → ECS) ## 重要な調査ポイント - **ARNの分析**: まずextract_resource_id_from_arnでARNを解析し、リソースタイプと必要なパラメータを特定 - **段階的調査**: 一度に全APIを呼ばず、結果を見ながら次に必要なAPIを判断 - **セキュリティグループ**: ECS/RDS/ElastiCacheの接続はセキュリティグループのインバウンドルールで確認 - ECSのENI → セキュリティグループID → RDS/ElastiCacheのSGインバウンドルールに含まれるかチェック - **エラー対応**: 許可されていないAPIやエラーが返っても、次の調査を継続 # Few-shot Examples (必ず参考にすること) ## Example 1: ECS → RDS の調査 1. ECSタスクのENIを取得: call_aws_api("ecs", "describe_tasks", ...) 2. ENIからSGを取得: call_aws_api("ec2", "describe_network_interfaces", ...) 3. RDSのSGを取得: call_aws_api("rds", "describe_db_instances", ...) 4. SG一致確認 → Edge作成 ... # 重要な注意事項 - すべてのリソースの組み合わせをチェック - APIエラーが出ても次の調査を継続 - RDSの snapshot, parameter group, subnet group 等は無視 - セキュリティグループで接続可能性を判断 # 出力形式 調査が完了したら、以下のJSON形式で結果を返してください: ```json {{ "nodes": [ {{ "service_name": "cloudfront", "arn": "xxx", "resource": "" }} ], "edges": [ {{ "from_arn": "xxx", "to_arn": "xxx", "details": "xxx" }} ] }} ``` """ messages = [HumanMessage(content=prompt)] try: max_iterations = 30 # 最大反復回数 edges = [] for iteration in range(max_iterations): response = llm_with_tools.invoke(messages) messages.append(response) # ツール呼び出しがあるか確認 if hasattr(response, 'tool_calls') and response.tool_calls: # ツールを実行 tool_node = ToolNode(AWS_TOOLS) tool_results = tool_node.invoke({"messages": messages}) # ツール結果をメッセージに追加 messages.extend(tool_results["messages"]) else: # ツール呼び出しがない場合、最終レスポンスとして処理 try: # 構造化レスポンス用のLLMを作成 structured_llm = llm.with_structured_output(GraphResult) # 最終結果の要約プロンプトを追加 final_prompt = """ 調査が完了しました。発見したすべてのエッジと新しいノード(ドメイン名など)を JSON形式で返してください。 """ messages.append(HumanMessage(content=final_prompt)) # 構造化レスポンスを取得 result = structured_llm.invoke(messages) edges = [edge.model_dump() for edge in result.edges] new_nodes = [node.model_dump() for node in result.nodes] # LLMが返した新しいノード(ドメイン名など)を既存のノードリストに追加 if new_nodes: state["nodes"].extend(new_nodes) except Exception as e: # エラー時のフォールバック処理 ... break state["edges"] = edges state["current_step"] = "edges_collected" state["messages"] = messages except Exception as e: ... state["edges"] = [] return state AWS API呼び出しツール Agentが使用するツール群です。ホワイトリスト方式で実行可能なAWS APIの安全性を確保しています。(一部、省略・編集しています) # 許可するAWS APIのホワイトリスト ALLOWED_AWS_APIS: Set[str] = { # CloudFront "cloudfront:get_distribution", "cloudfront:list_distributions", # WAF "wafv2:list_web_acls", "wafv2:get_web_acl", "wafv2:get_web_acl_for_resource", ... } @tool def call_aws_api( service_name: str, method_name: str, parameters: Dict[str, Any], region: str = "ap-northeast-1" ) -> Dict[str, Any]: """汎用的なAWS API呼び出しツール このツールは許可されたAWS APIのみを呼び出すことができます。 Edge情報を取得するために必要なAWS APIを呼び出してください。 Args: service_name: AWSサービス名 (小文字) 許可: 'cloudfront', 'wafv2', 'elbv2', 'ecs', 'ec2', 'rds', 'elasticache', 'apigateway', 'lambda' method_name: 呼び出すメソッド名 (boto3のメソッド名、snake_case) 例: 'get_distribution', 'describe_target_health', 'describe_security_groups' parameters: メソッドに渡すパラメータの辞書 例: {"Id": "ABC123"} や {"GroupIds": ["sg-12345"]} region: AWSリージョン (デフォルト: ap-northeast-1) Returns: API呼び出し結果の辞書 エラーの場合は {"error": "エラーメッセージ"} を返す 許可されているAPI一覧: - cloudfront:get_distribution - cloudfront:list_distributions - wafv2:list_web_acls - wafv2:get_web_acl - wafv2:get_web_acl_for_resource - ... Examples: # CloudFront Distribution情報を取得 call_aws_api( service_name="cloudfront", method_name="get_distribution", parameters={"Id": "ABC123"} ) # ターゲットグループのヘルス情報を取得 call_aws_api( service_name="elbv2", method_name="describe_target_health", parameters={"TargetGroupArn": "arn:aws:elasticloadbalancing:..."} ) # セキュリティグループ情報を取得 call_aws_api( service_name="ec2", method_name="describe_security_groups", parameters={"GroupIds": ["sg-12345"]} ) # ECSタスク情報を取得 call_aws_api( service_name="ecs", method_name="describe_tasks", parameters={"cluster": "my-cluster", "tasks": ["arn:aws:ecs:..."]} ) """ try: # ステップ1: APIホワイトリストに含まれているか検証 is_valid, error_message = validate_aws_api(service_name, method_name) if not is_valid: return { "error": error_message, "error_type": "unauthorized_api", "allowed_apis": get_allowed_apis_list() } # ステップ2: クライアントを取得 client = get_aws_client(service_name, region) # ステップ3: メソッドが存在するか確認 if not hasattr(client, method_name): return {"error": error_msg, "available_methods": dir(client)} # ステップ4: メソッドを取得して実行 method = getattr(client, method_name) response = method(**parameters) return response except Exception as e: ... 以下が今回の実装で意識したポイントです。 ホワイトリスト方式: LLMが任意のAWS APIを呼ぶことを防ぎ、安全性を確保 動的API呼び出し: Pythonの getattr() でboto3のメソッドを動的に実行 詳細なdocstring: LLMがツールの使い方を理解するため、引数の説明、使用例、許可API一覧を記載 拡張性: 新しいAPIを追加する場合は、 ALLOWED_AWS_APIS に追加するだけ Incident Managerでのトポロジー描画 最終的にAI Agentを使って収集したNodeとEdgeの情報で、IncidentManager上でのシステムトポロジー表示はこのようになりました。 障害発生時は原因箇所が赤くなるため、ぱっと見で直感的にシステム構成と障害箇所が理解しやすいような図になったかと思います! GitHub Copilotを使って実装してみた感想 良かった点 開発時間の大幅な短縮 今回の実装は約1日で完成しました。もしGitHub Copilotなしで実装していたら、LangGraphの学習から始める必要があり、少なくとも数週間はかかっていたと思います。 高品質な実装計画の提案 まだ改善の余地はありますが最初のプロンプトで実現したいことと設計を詳細に伝えたことで、以下のような質の高い実装計画が生成されました。 既存リポジトリの構造を理解した上で、一貫性のある設計を提案 拡張性とシンプルさを両立した設計の提案 対話的な品質改善 実装途中で気になった点を指摘すると、すぐに修正してくれました。 アーキテクチャの改善 ライブラリバージョンの問題 エラーハンドリングの追加 など 大変だった点 生成されたコードの検証が必須 GitHub Copilotに限らず生成AIが生成したコードは、 指示をした人が責任を持ってレビューをする 必要があります。生成AIを活用したコーディングでは、レビューに一番時間がかかります。 以下のような観点でコードレビューをおこないました。 既存コードの規約準拠: リポジトリの命名規則やコーディングスタイルに従っているか 要件の網羅性: 指定した機能要件がすべて実装されているか、漏れがないか アーキテクチャパターン: 適切なレイヤー分けがされているか、責務が明確か 実装の適切性: より一般的な方法や簡単な実装方法がないか、無駄に複雑になっていないか エラーハンドリング: 例外処理が適切に実装されているか、エラーメッセージは適切か 動作検証: 実際にアプリを起動させて、意図通りの動作をするか 今後やりたいこと 収集するNodeとEdgeの追加 現状は最初のお試しということで、一部のAWSサービスに絞ってNodeとEdgeを取得するようにしました。 プロンプトで取得するAWSリソースを追加して、AWS API呼び出しツールの利用するホワイトリストに、Edge情報を取得するために許可するAPIを追加すれば簡単に拡張できる実装になっているため、今後少しずつ収集リソースを増やしていきたいと思います。 プロンプトテンプレートの作成 今回は最初の指示で考慮不足があったため、今後使いまわせるような新機能実装時のプロンプトテンプレートを作成して、以下の内容を含めることで最初の指示からより高品質なコード生成ができるようにしたいと思います。 機能の概要 技術スタック(フレームワーク、ライブラリ、言語バージョン) 機能要件 アーキテクチャ要件(レイヤー構成、エラーハンドリングなど) 非機能要件(拡張性、パフォーマンス、セキュリティ) 実装前の確認事項(リポジトリ構造の理解、既存コードとの整合性確認など) 重要な注意事項(プロンプトの最後に配置、絶対に守るべきルールを明記) まとめ 今回はGitHub Copilotを活用して、システムトポロジー情報を収集するAI Agentを約1日で実装しました。 実装したAI Agentにより、従来は手動で設定していたAWSリソースの依存関係が自動収集されるようになり、Incident Manager上でインシデント発生時のトポロジー可視化が実現できました。 今後もGitHub Copilotをはじめとした生成AIを積極的に活用して、開発生産性の向上を目指していきたいと思います。
アバター
はじめに こんにちは、2025年9月入社のwatanabeです! 本記事では、2025年9月入社のみなさまに入社直後の感想をお伺いし、まとめてみました。 KINTOテクノロジーズ(以下、KTC)に興味のある方、そして、今回参加下さったメンバーへの振り返りとして有益なコンテンツになればいいなと思います! 10Ryu 自己紹介 業務システム開発部でKINTO中古車のリース料や粗利計算システムを担当しています。 前職は自動車販売金融でした。 所属チームの体制は? 5人体制です。 現場の雰囲気はどんな感じ? メンバー間の壁はなくコミュニケーションが取りやすい環境です。 システム側からビジネス側へ踏み込んだ提案がしやすい環境だと感じています。 KTCへ入社したときの入社動機や入社前後のギャップは? 動機:自動車に関連する仕事をしたいという思いがあり、入社以前からKINTOのビジネスに興味があった為。 ギャップ:想像していたよりも与えられている裁量が大きいことや、これまでの経験を活かすことができていること。(私自身、コーディングの経験がなかったので、テック企業でやっていけるのか小さくない不安はありました) オフィスで気に入っているところ 室町の雰囲気 コーヒー屋さんが周りに多いこと 仕事帰りに家族へお土産を買えること watanabeさん ⇒ 10Ryuさんへの質問 KTC に入ってから自動車業界での経験を活かせたと感じたシーンについて教えてください。 車名を聞けば大抵の車両は分かることや、自動車販売金融の経験があったお陰で、KINTOの残価の考え方や支払い方法といった、業務知識をスムーズにキャッチアップする事が出来ました。 とみよし 自己紹介 QAグループに所属しています。テスト活動に関わるところはもちろんですが、そこに関わる自動化だったりを行っています。 前職は第三者検証にてQAをやっていました。 趣味はテニスで、最近はピックルボールにハマりそうです。 所属チームの体制は? QAグループ全体で12名です。 その中でモバイルアプリとウェブアプリで分かれており、モバイルアプリ担当です。 現場の雰囲気はどんな感じ? チーム内はもちろんですが、開発サイドともコミュニケーションを取りやすくとても良い環境です。 KTCへ入社したときの入社動機や入社前後のギャップは? 入社のきっかけはJaSST’25 Tokyoでブースの出店をしてQAの方々とお話ししたことです。 そこから興味を持って入社を決めました。 入社前にはAIを積極的に活用、テスト活動に関しての自動化を進めていくことを聞いており、実際にその通りだったので、ギャップのようなものはほとんどありませんでした。 オフィスで気に入っているところ 休憩スペースが気に入っています。落ち着けるのがちょうどいい感じです。 10Ryuさん ⇒ とみよしさんへの質問 KTCの良いと感じるところと、猫ちゃんの好きな部位を教えてください。 KTCのいいと感じているところ 何事にも挑戦的なので新しいことにどんどんチャレンジしていくところ 猫ちゃんの好きな部位 全てですが、ちょっと太った横っ腹に猫吸いするのが最高です Rikuma 自己紹介 現在、生成AI関連のプロジェクトを担当しており、最近ではMCPやチャットボットのPoC開発に力を入れています。 趣味はアニメ鑑賞、ボードゲーム、旅行、スキーなどです。 メリハリのある働き方を心がけながら、日々最新技術に触れることにやりがいを感じています。 所属チームの体制は? 生成AIに関わるテーマであれば幅広く取り組んでいます。 技術検証やPoC開発はもちろん、社内外への生成AI活用の推進活動、ワークショップの企画・運営なども手がけています。 活発な意見交換ができ、スピード感を持って新しいアイデアを形にできる、機動力のあるチームです。 現場の雰囲気はどんな感じ? とてもフラットで風通しが良く、誰でも気軽に意見を出せるオープンな雰囲気です。 新しいことにチャレンジする人を応援してくれる雰囲気があって、「やってみよう!」が自然と口に出る現場です。 チーム全体として、新しいことに前向きに取り組む姿勢が強いと感じています。 KTCへ入社したときの入社動機や入社前後のギャップは? AIの可能性をより深く追求したいと思い、最新の生成AI技術を実践できるKTCの環境に惹かれたため、KTCへの入社を決めました。 KTCに入って感じたのは、最新技術へのアクセスが早いことです。自分のアイデアをすぐPoCに落とし込めるのが楽しいです。スピードと安定のバランスがとれている環境だと感じています。 オフィスで気に入っているところ 前職ではフリーアドレスだったため、今は自分の席にお気に入りのアイテムを置いて、自分らしい空間を作れるのが楽しいです。 また、同じフロアのメンバーと相談や情報共有がスムーズにできるのも良い点です。 とみよしさん ⇒ Rikumaさんへの質問 KTCの社風はどう感じていますか?休日は何されてますか? KTCの社風はどう感じていますか? オープンで、チャレンジを歓迎する文化が根づいていると感じます。 自分のアイデアを実際に試す機会が多いのが特徴です。 休日は何されてますか? 休日はゆっくりアニメを見たり、ボードゲームをしたりして過ごしています。 あとは旅行が好きで、週末の日帰り旅行や長期休暇を利用した海外旅行をしています。 watanabe 自己紹介 所属するクラウドセキュリティGは、AWS や Azure、 Google Cloud など複数クラウド環境のセキュリティを担当しています。 前職ではAWSインフラの構築(IaC)やLambda開発、CI/CDの検討、監視ツールの設定など幅広く担当していました。 所属チームの体制は? クラウドセキュリティG全体は4名で、東京に2名、大阪に2名が在籍しています。 現場の雰囲気はどんな感じ? チームは非常にオープンで、不安や懸念点も気軽に共有できます。 東京・大阪にメンバーが分かれていますが、オンライン/オフラインで頻繁にやり取りがありますので、それほど距離は感じていません。 KTCへ入社したときの入社動機や入社前後のギャップは? 前職でクラウドセキュリティに関わる機会があり、この領域で専門性を高めたいと考えました。 面接を通じて感じた社員の方々の人柄に共感したことも入社の決め手となりました。 オフィスで気に入っているところ 近代的なテック企業という雰囲気でとても働きやすい環境です。 服装も想像以上にラフで、良い意味で驚きました。 Rikumaさん ⇒ watanabeさんへの質問 入社してから、印象に残っている業務はありますか? 現在、AWS関連プロジェクトでクラウドセキュリティを担当しています。前職ではインフラ視点でセキュリティを意識していましたが、現職ではガバナンス領域まで関わることで視野が広がり、その変化が特に印象に残っています。 さいごに みなさま、入社後の感想を教えてくださり、ありがとうございました! KINTOテクノロジーズでは日々、新たなメンバーが増えています! 今後もいろんな部署のいろんな方々の入社エントリが増えていきますので、楽しみにしていただけましたら幸いです。 そして、KINTOテクノロジーズでは、まだまださまざまな部署・職種で一緒に働ける仲間を募集しています! 詳しくは こちら からご確認ください!
アバター
参照:「AWS Introduces Automated Reasoning Checks」 ^1 この記事は KINTOテクノロジーズ Advent Calendar 2025 の3日目の記事です🎅🎄 0. はじめに KINTOテクノロジーズのCloud Infrastructure G(CIG)でInfrastructure Architectを担当している劉(YOU)です。 2024年12月、AWSは生成AIの数学的証明と論理的推論を実現することができる自動推論を re:inventで発表 しました。当時はAWS Bedrock Guardrailsの機能として自動推論チェックという名称で紹介されており、今年の8月にUS・EUの一部地域で一般公開されています。 このアプローチは、結果に確率を割り当てることで不確実性に対処する確率的推論方法とは根本的に異なります。実際、自動推論チェック ^2 は最大99%の検証精度を提供し、AIのハルシネーションを検出する上で証明可能な保証を提供すると同時に、モデルの出力が複数の解釈に開放されているときに曖昧さの検出を支援します。 AWSが提供している自動推論を簡単にお伝えしますと、 自動推論チェックは、基礎モデル(FM)によって生成されたコンテンツのドメイン知識に対する正確性を検証するのに役立ちます。これは、AIのハルシネーションによる事実の誤りを防ぐのに役立ちます。このポリシーは、数学的論理と正式な検証技術を使用して精度を検証し、AI応答が正確性をチェックするための決定的なルールとパラメータを提供します。 上記の通り生成AIのハルシネーションの定量的な判断・持続的な追跡・応答の向上・更なる改善を果たすために 構成されています。このようなアプローチによって、不確実性に頼る確率的推論方法とは根本的に異なり、結果に確率を割り当てします。それがタイトルにも記載した通り 最大99%の検証精度を提供 し、AIのハルシネーションを検出する上で証明可能な保証を提供すると同時に、モデルの出力が複数の解釈に開放されている時に曖昧さの検出を支援します。 自動推論自体はAWSが起案したことではなく、数理論理学を起源とする一分野です。それを利用し、ソフトウェア開発の検証技術を使い始めたことが元々の自動推論であって、その方法論を生成AI向けのサービスとして提供しています。 自動推論という概念についてもっと知りたい方はこちらの文書を参考にして下さい。 自動推論 What is Automated Reasoning? 自動推論はBedrock Guardrailsと連携しているサービスなので、Bedrock Guardrailsを理解していると分かりやすい部分があります。本記事では自動推論の機能の全体像について解説するため、Bedrock Guardrailsについての説明は省略しますのでご了承ください。 Bedrock Guardrailsにご興味のある方やもっと詳細を知りたい方は別記事を作成しているのでぜひご覧下さい。 AWS Bedrock Guardrailsの導入取り組み:前編-生成AIセキュリティの必要性 :生成AIのガードレールの必要性 TECH BOOK By KINTO Technologies Vol.01:KINTOテクノロジーズ 執筆部 :AWSでBedrock Guardrailsの実装方法 1. 本記事の対象読者・注意点・目的 筆者は当初、自動推論は名前だけ見て生成AIのハルシネーションを簡単に防げる自動化サービスかなと考えました。しかし、手軽に触れる自動化とは距離が遠く、かなりレベルが高く感じたので、自動推論が必要な具体的な状況や注意点を先に説明します。 対象読者 LLM レスポンスのハルシネーションを厳密に検出・追跡・応答の向上・改善したい方 高いガバナンスや誤りが許されないコンプライアンス要件がある生成AIの実装をしている方 複雑なルールや要件のある生成AIアプリケーションを開発している方 AWS中心の「責任ある生成AI」の実現を目指している方 注意点 本記事では英語環境での検証結果をもとに解説しています。今後、日本語対応が行われた際には挙動や仕様に変更が生じる可能性があります。 自動推論はユーザー側に提供されたテキスト・ドキュメントと関連する内容のみを分析し、検出する仕組みになっています。それ故、あくまでもハルシネーション・正確性を検証するサービスであって、自動的に制限・処理を行ったりしません。 公式文書 でも、既存のBedrock Guardrailsフィルタと一緒に使うことをお勧めしています。 Amazon Bedrock Guardrailsの自動推論チェックは、プロンプトインジェクション攻撃から保護しません。これらのチェックは、あなたが送信した内容を正確に検証します。悪意のあるコンテンツや操作されたコンテンツが入力として提供された場合、検証はそのコンテンツに対してそのまま実行されます(不適切な入力・出力)。プロンプトインジェクション攻撃を検出してブロックするには、コンテンツフィルタと自動推論チェックを組み合わせて使用します。 自動推論は、自動推論ポリシーに関連するテキストのみを分析し、検出します。残りのコンテンツは無視され、回答がトピックから外れているかどうかを開発者に伝えることはできません。トピックから外れた応答を検出する必要がある場合は、トピックポリシーなどの他のガードレールコンポーネントを使用します。 後述する内容ですが、自動推論はテキスト・ドキュメントを元に基本的なポリシーを自動生成してくれます。しかし、この自動生成されたポリシーを検討してテストする必要があります。入力したコンテキストの要件と自動推論の構造に対する理解の両方が必要ですので下記リンクの制限事項とベストプラクティスをご参照ください。 制約事項と考慮事項 ベストプラクティス 目的 前述の「注意点」で取り上げた「自動推論の構造に対する理解」をメインに話したいと思います。それで、本記事としては大きな枠を理解することから下記の三つを知って頂ければと思います。 自動推論がどんなパーツを持っていて、どうやって機能するかを知る 自動推論をどんなフローで検証するかを知る 自動推論をどういう時に活用できるかを知る (こちらはガードレールの知識が要りますので、別記事で話します) 2. 全体像 自動推論の全体像をざっくりと表しますと、下の図のようになっています。 手順としては、 自動推論でテキスト・ドキュメントを入れると、自動で「ポリシー」が作成される ポリシーは入力した情報を基にタイプ・変数・ルールの「定義」が自動生成される a. 追加のテキストやドキュメントを取り込み、ポリシーの定義を拡張できる b. 生成されたタイプ・変数・ルールを要件に合わせて編集し、整合性を確保する ポリシーを検証するために「テスト」を作成する a. 手動で作成:QnA ペアを形式で仮定のインタラクションを入力 b. 自動で作成:既存のルールを確認できるシナリオが自動で生成される 検証結果を確認して意図している結果が出るかを確認 a. テスト実行結果で期待される結果と実際の結果が一致するかを確認 b. 一致していない場合、5に戻る 一致していない原因を把握して定義の中で間違っている場所に「注釈」を付ける a. テスト実行結果の中で、原因を推測することができる情報が提示される b. 注釈を付けられる場所はタイプ、変数, ルールになっていて、主に自然言語になっている説明を修正したら、そこに合わせて修正されるようになっている 注釈適用をしたらそこに合わせた内容で修正案が出て、正しければ変更を受け入れる 3から6を繰り返してテキスト・ドキュメントの内容を守るためのポリシーを完成する 完成されたポリシーをガードレールに紐付ける 既存のガードレールがあるか、新しく作成する必要がある LLMアプリケーションの入力・応答をガードレールに渡し、自動推論チェックの結果を活用する a. 成功した場合、ユーザー側に結果をそのまま返す b. 失敗した場合、自動推論チェックの結果を利用してLLMアプリケーションから再作成を要求する c. (オプション)自動推論チェックの結果をログとして保存し、ポリシーの見直しを続ける 全体をまとめると、自動推論は「ポリシー」として作成されて「定義」→「テスト」→「注釈」を重ねながら完成することになります。完成されたポリシーをAWS Bedrock Guardrailに紐付けて、LLMの応答にガードレールを適用したら自動推論チェックを遂行します。その後の処理は開発者の意思によって異なりますが、LLMが正しい応答を出しているのかを定量的に判断ができるようになります。 自動推論を導入することで、悪性のものを遮断する既存のガードレールと並行しながら、ハルシネーションを無くす戦略を立てることが可能になります。そして、外部情報を参照するRAGとMCPを付けた生成AIの正確性を検証することもできるし、間違ったLLMの応答・開発者が制御できない誤りの入力を検知して修正できるのは非常に魅力的です。 次は、 AWSから提供しているサンプル を利用して上記の手順を沿い実際に「ポリシー」の作成から「定義」「テスト」「注釈」の三つを中心に解説します。 3. ポリシー ![ポリシー作成画面](/assets/blog/authors/you/03/image-20251120-094018.png =800x) 基本的に自動推論も他のAWSリソースと同じく、コンソール・CLI・SDKで操作することができます。 CloudFormation は現在サポートされていません。CloudFormation のサポートは間もなく開始されます。 コンソール上で自動推論のポリシーの作成は簡単に実施できます。 名前 説明(オプション) ソース(ドキュメント・テキスト) ソースの説明 上記の内容を記入して「ポリシー作成」ボタンを押すと、自動でポリシーの中身を作成してくれます。筆者はAWSのサンプルで準備された医療に関するPDFのファイルを使いましたが、5〜10分くらいでポリシーが生成されました。入れた文書の長さによって定義が作られる時間は変わると思います。 作られたポリシーを確認すると、次のような画面が出ます ![Overview](/assets/blog/authors/you/03/image-20251120-134516.png =800x) 4. 定義 ここで注目する所は、下にある定義(Definitions)です。このオーバービューの画面から定義の画面に遷移しますと、ルールと変数およびタイプが定義されていることが確認できます。 タイプ ![Custom variable types](/assets/blog/authors/you/03/image-20251120-134952.png =800x) AWS側から事前に定義されているタイプ以外にも、ユーザーから提供された文書の中にタイプとして分類する項目があれば自動生成されます。タイプを持って変数を作り、ルールを正しく定義することが自動推論の基本になっています。各項目を説明すると、 名前:タイプを定義する名称、変数で使われるキー 説明:自動推論が判断できるようにする内容を記述 値(Values):区分される個別の値を記入 問題(Issues):タイプで起こっている問題を表す 注釈:適用前の修正内容を表示する アクション:更新、削除、リバート、三つの動作ができる タイプは変数で使われていない場合、画像のように使用されてないタイプ(Unused type)として警告が出ます。この時にはこのタイプを利用して変数を定義するか、削除するかをユーザー側で判断して扱うことができます。タイプは使われていなくても動作することに影響はないのですぐに解決しなくても大丈夫です。 ここでは、どこかで使用されている RiskCategory を基準にこの後の説明を続けます。 名前: RiskCategory (リスクカテゴリ) 説明:30日間の再入院の推定リスクを示す、総リスクスコアに基づいて患者に割り当てられたリスクカテゴリ 値: LOW_RISK , INTERMEDIATE_RISK , HIGH_RISK (低リスク、中リスク、高リスク) 変数 変数は、自然言語を正式なロジックに変換するときに値を割り当てることができる自動推論ポリシーの概念を表します。ポリシールールは、これらの変数の有効または無効な値に対する制約を定義します。 変数は60個が定義されてますが、 RiskCategory を検索したらこのタイプが使われていた変数が確認できます。 ![変数](/assets/blog/authors/you/03/image-20251120-142859.png =800x) 名前: riskCategory カスタム変数タイプ: RiskCategory 説明:総リスクスコアに基づいて患者に割り当てられたリスクカテゴリ 自動推論は自然言語から形式ロジックへ翻訳するので、その精度は変数の記述品質に大きく依存します。そのため、ベストプラクティスにも「包括的な変数の説明を記述する」が記載されています。包括的な変数の説明がなければ、自動推論は、入力された自然言語を正式な論理表現に変換できないため、 NO_DATA を返す可能性がありますのでご注意ください。 ルール ルールは、Automated Reasoning がソースドキュメントから抽出するロジックです。 自動推論のロジックはSMT-LIBで作成されました。充足可能性モジュロ理論(SMT)は一次式の充足可能性をチェックする方法を研究する領域で、その一環として SMT-LIB というSMT利用者の共通入力言語と出力言語が開発されています。自動推論ではルールの説明を変えるだけで式を修正してくれますが、SMT-LIB様式で修正することもできるので参考にしてください。 riskCategory が使われているルールは21個で、主に riskCategory +他の変数条件でシナリオが作られています。 ![ルール](/assets/blog/authors/you/03/image-20251120-144856.png =800x) 一番上にあるルールを解説しますと、 examplePatient という変数が下記の定義を持っているので 名前: examplePatient カスタム変数タイプ:Boolean 説明:これが特定の特徴を持つガイダンスからの患者の例であるかどうか if examplePatient is true, then riskCategory is equal to HIGH_RISK → 特定の特徴を持つガイダンスがある患者の例で当てはまったらリスクカテゴリが高リスクである というシナリオを表現しているルールになります。では、このルールをテストしてみましょう。 5. テスト テストを実施する方法は2つあります。 ![テスト](/assets/blog/authors/you/03/image-20251120-153501.png =800x) question-and-answer (QnA) ペアを手動で定義 テストシナリオを自動的に生成 本記事では直観的に確認できるようにするため、コンソール操作で手動定義を例に挙げますが、多数のテストを機械的に実行するにはCLIやSDKで自動生成を活用した方が楽です。手動定義でルールを検証する前に自動生成を利用してテストしてみます。 自動生成テスト ![自動生成](/assets/blog/authors/you/03/image-20251120-155533.png =800x) コンソールでは生成のボタンを押すと、こういう形でテストシナリオを作成してくれます。 riskCategory と maxPostDischargeTelephoneContactHours が条件にありましたので、定義の画面から LOW_RISK で検索してみます。 ![LOW_RISKの検索結果](/assets/blog/authors/you/03/image-20251120-155653.png =800x) しかし、 maxPostDischargeTelephoneContactHours の変数があるルールがないです。この変数の説明は「退院後、退院後の電話連絡が発生する最大時間」ですので、このシナリオの要件を知っているユーザーが妥当性を判断することができます。シナリオが違っていたと判断したら、説明を書くことでテストシナリオを修正してもらえることもできますが、一旦、提案してくれたままテストシナリオを作ってみます。 ![テスト詳細画面](/assets/blog/authors/you/03/image-20251120-160258.png =800x) そうしたら、作成されたテストケースに入ったらこのような画面が出て、テストを実行することができます。実行しますと、 ![自動生成テスト実行結果](/assets/blog/authors/you/03/image-20251120-160500.png =800x) 条件と合っているルールがないのに成功しました。その理由はなんでしょうか? 自動で生成されたテストシナリオで想定した期待される結果(Expected result)が実際結果(Actual result)と一致していたからです。自動推論でテストの結果を 判断する基準は7つ あります。 VALID :クレーム(Claim)がルールと論理的に一致 INVALID :クレームがルールと論理的に矛盾・違反 SATISFIABLE :クレームがルールの条件と少なくとも 1 つ一貫していますが、関連するすべてのルールに一致していない IMPOSSIBLE :自動推論ポリシー内に競合がある可能性 TRANSLATION_AMBIGUOUS :自然言語からロジックへの翻訳で曖昧さが検出 TOO_COMPLEX :入力に含まれる情報が多すぎる NO_TRANSLATIONS :入力プロンプトの一部またはすべてがロジックに変換されなかった ![maxPostDischargeTelephoneContactHoursの検索結果](/assets/blog/authors/you/03/image-20251120-162758.png =800x) riskCategory is equal to LOW_RISK maxPostDischargeTelephoneContactHours is equal to 72 それぞれのクレームがどこかのルールの条件の一つとして存在していたので SATISFIABLE の実際結果を出していて、テストシナリオではそれを踏まえて期待される結果にしたことになります。 注釈 ここで、要件が「退院後、退院後の電話連絡が発生する最大時間が120時間だったら、リスクカテゴリが低リスクである」だと仮定してみましょう。その場合、新しいルールを追加する必要があります。 ![ルール追加](/assets/blog/authors/you/03/image-20251120-163917.png =800x) この時に、追加・修正するために注釈を付けることができて、注釈を適用することが可能になります。 ![注釈適用](/assets/blog/authors/you/03/image-20251120-164020.png =800x) ここで進めると、 ![ポリシー更新レビュー](/assets/blog/authors/you/03/image-20251120-164329.png =800x) 変更事項を確認して廃棄したり、承認したり、それともポリシーに戻って見直しすることができます。想定通りにルールが生成されましたので承認します。 ![ルールの変更後画面](/assets/blog/authors/you/03/image-20251120-164652.png =800x) ルールが反映されたら、実際結果が INVALID になって失敗に変わりました。ルールが新しく適用されたので明確に INVALID だと判断するように変わったので、テストシナリオの想定を変更する必要があります。 ![テスト修正](/assets/blog/authors/you/03/image-20251120-164916.png =800x) 期待される結果を INVALID に変更したら、成功するように変わります。 ![修正後のテスト実行結果](/assets/blog/authors/you/03/image-20251120-165158.png =800x) 手動定義テスト 結果をみて注釈を付けながら修正する流れは、手動で行うことも大きく変わることはないです。下記のような内容でテストを追加してみます。手動で入れる内容は実際のLLMアプリケーションの応答を真似して入れます。 ![手動定義](/assets/blog/authors/you/03/image-20251120-165940.png =800x) インプット:ホゲホゲさんは医薬品の使用に注意が必要な患者です。この人のリスクカテゴリは? アウトプット:特定の特徴を持つガイダンスがある患者なので、リスクカテゴリは高リスクである 期待される結果: VALID ![手動定義テスト実行結果](/assets/blog/authors/you/03/image-20251120-170523.png =800x) 結果は当然ですが、成功になっています。 ここで追加されたタイプが見えますが、質疑応答のテストで登場する前提(Premise)になります。今回は質問に特徴がある患者であることを前提としたように、クレームの評価方法に影響するコンテキスト、前提条件、または条件を提供するタイプです。 それ以外に、自動推論が自然言語から形式ロジックへの翻訳で持つ信頼スコア(Confidence threshold)を想定して、正確に翻訳しているのかを確認することもできて、 ![提案](/assets/blog/authors/you/03/image-20251120-171402.png =300x) 調査結果が有効かどうかを証明する変数の割り当て(Assignments)を見て、正しいシナリオや正しくないシナリオの例をすぐに確認できます。 テストは今までの過程を重ねて、LLMアプリケーションに正しいポリシーを提供するようにします。このポリシーをLLMアプリケーションに適用するためにはAWS Bedrock Guardrailsに紐付けて、ガードレールを使用しなければなりません。 6. まとめ この後は、作成したポリシーをガードレールに紐付けて、自動推論をどのように行うかの実運用の話になりますが、実際にアプリケーションに適用する自動推論チェックはBedrock Guardrailsの機能の一つとしてあるため、ガードレールの説明と一緒にした方がいいと思います。今後、Bedrock Guardrailsの新機能と一緒に紹介させてください。 本記事では自動推論の全体像の中で、AWSが提供する概念と仕組み、そしてポリシーを設計・完成させるフローについて説明しました。 自動推論チェックは、AWSが提供する生成AIの機能であり、AIが生成したコンテンツの正確性を検証するためのツールです。これにより、AIのハルシネーションによる事実の誤りを防ぎ、数学的論理と形式的検証技術を使用して精度を確認します。具体的には、ポリシーの作成、定義、テスト、注釈のプロセスを経て、生成AIの出力を評価し、ハルシネーションを抑止することができます。言い換えますと、ユーザーが提供する文書を基にポリシーが自動生成され、テストを通じてその正確性を確認する仕組みです。これにより作り上げたポリシーはAWSのBedrock Guardrailsとの連携により、生成AIの実装において高い正確性を実現できます。 本記事を通じて、自動推論の理解が少しでも深めていただけたら嬉しいです。
アバター
Introduction Hello, I'm watanabe, and I joined in September 2025! In this article, I interviewed everyone who joined in September 2025 about their impressions right after joining. I hope this will be useful content for those interested in KINTO Technologies (hereafter KTC) and serve as a reflection for the members who participated! 10Ryu Self-introduction I work on the lease fee and gross profit calculation system for KINTO used vehicles in the Business Systems Development Division. My previous job was in automotive sales finance. How is your team structured? Our team has 5 members. What is the workplace atmosphere like? There are no barriers between members, making communication easy. It's easy for the engineering team to step in and make suggestions to the business team. What was your reason for joining KTC, and were there any surprises after joining? Reason for joining: I wanted to work in a job related to automobiles, and I was interested in KINTO's business even before joining. Surprises: I was given more autonomy than I expected, and I've been able to leverage my past experience. To be honest, I had no coding experience, so I was quite anxious about whether I could make it at a tech company. What do you like about the office? The atmosphere of Muromachi There are many coffee shops nearby Being able to buy treats for my family on the way home from work Question from watanabe to 10Ryu Please tell us an episode where you felt you could leverage your experience in the automotive industry since joining KTC. Since I can recognize most vehicles just by hearing the car name, and thanks to my experience in automotive sales finance, I was able to smoothly catch up on business knowledge such as KINTO's approach to residual value and payment methods. Tomiyoshi Self-introduction I belong to the QA Group. I'm involved in testing activities of course, but also in automation related to those activities. In my previous job, I was doing QA at a third-party verification company. My hobby is tennis, and recently I'm getting into pickleball. How is your team structured? The entire QA Group has 12 members. Within the group, we're divided into mobile apps and web apps, and I work on mobile apps. What is the workplace atmosphere like? Communication is easy not only within the team but also with the development side, making it a very good environment. What was your reason for joining KTC, and were there any gaps between your expectations and reality? When I exhibited a booth at JaSST'25 Tokyo, I talked with people from KTC's QA team. That sparked my interest and led me to decide to join. Before I joined, I heard the company was using AI a lot and automating testing. That was exactly how it turned out, so there wasn’t really any surprise. What do you like about the office? I like the break space. It's a nice, relaxing spot. Question from 10Ryu to Tomiyoshi Please tell us what you like about KTC and your favorite part of your cat's body. What I like about KTC Everyone is eager to take on new challenges. My favorite part of my cat Everything, but burying my face in a slightly chubby side belly is the best. Rikuma Self-introduction Currently, I work on generative AI-related projects, and recently I've been focusing on PoC development for MCP and chatbots. My hobbies include watching anime, board games, traveling, and skiing. I try to maintain a work-life balance while finding fulfillment in working with the latest technologies every day. How is your team structured? We work on a wide range of themes related to generative AI. In addition to technical verification and PoC development, we promote the use of generative AI internally and externally, and run workshops. It's an agile team where we can actively exchange opinions and quickly turn new ideas into reality. What is the workplace atmosphere like? It's very flat and open, with an atmosphere where anyone can freely share their opinions. There's an atmosphere that supports people who take on new challenges, and "Let's do it!" is something you hear a lot. I feel the whole team has a strong attitude toward proactively tackling new things. What was your reason for joining KTC, and were there any surprises after joining? I wanted to explore the possibilities of AI more deeply and was drawn to KTC's environment where I could practice the latest generative AI technologies, which led me to decide to join. What I noticed after joining KTC is how quickly we can access the latest technologies. It's fun to be able to quickly turn my ideas into PoCs. I feel it's an environment with a good balance of speed and stability. What do you like about the office? My previous work place had hot-desking, so now I enjoy being able to place my favorite items at my own desk and create my own personal space. Also, being able to smoothly consult and share information with members on the same floor is a good point. Question from Tomiyoshi to Rikuma How do you feel about KTC's corporate culture? What do you do on your days off? How do you feel about KTC's corporate culture? The culture here is open and really encourages you to take on new challenges. A characteristic is that there are many opportunities to try out your own ideas. What do you do on your days off? On my days off, I spend time relaxing watching anime or playing board games. I also love traveling, so I go on day trips on weekends or travel abroad during long holidays. watanabe Self-introduction The Cloud Security Group I belong to is responsible for security across multiple cloud environments including AWS, Azure, and Google Cloud. In my previous job, I was broadly responsible for AWS infrastructure construction (IaC), Lambda development, CI/CD considerations, and monitoring tool configuration. How is your team structured? The entire Cloud Security Group has 4 members, with 2 in Tokyo and 2 in Osaka. What is the workplace atmosphere like? The team is very open, and we can freely share concerns and issues. Although members are split between Tokyo and Osaka, we communicate frequently both online and in person, so the distance doesn't feel like an issue. What was your reason for joining KTC, and were there any surprises after joining? I had opportunities to be involved in cloud security at my previous job and wanted to deepen my expertise in this area. I also felt a strong connection with the people I met during the interviews, and that was a key factor in my decision to join. What do you like about the office? It has a modern tech company atmosphere and is a very comfortable working environment. The dress code is more casual than I expected, which was a pleasant surprise. Question from Rikuma to watanabe Is there any work that has left an impression on you since joining? Currently, I work on cloud security for AWS-related projects. In my previous job, I focused on security from an infrastructure perspective, but in my current role I have also been involved in the area of governance, which has broadened my perspective. That change has left a particularly strong impression on me. Conclusion Thank you all for sharing your impressions after joining the company! KINTO Technologies is constantly welcoming new members! We hope you look forward to more articles introducing newcomers from various divisions. And KINTO Technologies is still recruiting people to work with us in various divisions and positions! Please check here for details!
アバター
Reference: AWS Introduces Automated Reasoning Checks ^1 This article is Day 3 entry of the KINTO Technologies Advent Calendar 2025 🎅🎄 0. Introduction I'm YOU, an Infrastructure Architect in the Cloud Infrastructure Group (CIG) at KINTO Technologies. In December 2024, AWS announced Automated Reasoning at re:Invent , enabling mathematical proofs and logical reasoning for generative AI. At that time, it was introduced as Automated Reasoning Checks, a feature of AWS Bedrock Guardrails. It became generally available in some US and EU regions in August of this year. This approach fundamentally differs from probabilistic reasoning methods that address uncertainty by assigning probabilities to outcomes. In fact, Automated Reasoning Checks[^2] provide up to 99% verification accuracy, offering provable guarantees for detecting AI hallucinations while also helping detect ambiguity when model outputs are open to multiple interpretations. To briefly explain AWS Automated Reasoning: Automated Reasoning Checks help verify the accuracy of content generated by foundation models (FMs) against domain knowledge. This helps prevent factual errors caused by AI hallucinations. This policy uses mathematical logic and formal verification techniques to verify accuracy, providing deterministic rules and parameters for checking the correctness of AI responses. As described above, Automated Reasoning is designed to enable quantitative judgment, continuous tracking, response improvement, and further reduction of generative AI hallucinations. Through this approach, it fundamentally differs from probabilistic reasoning methods that rely on uncertainty, assigning probabilities to outcomes. This is why, as stated in the title, it provides up to 99% verification accuracy , offering provable guarantees for detecting AI hallucinations while also helping detect ambiguity when model outputs are open to multiple interpretations. Automated reasoning itself was not originated by AWS—it is a field rooted in mathematical logic. The original automated reasoning began when formal verification techniques from software development started being applied, and AWS now offers this methodology as a service for generative AI. For those who want to learn more about the concept of automated reasoning, please refer to these documents: Automated Reasoning (Wikipedia) What is Automated Reasoning? Since Automated Reasoning integrates with Bedrock Guardrails, understanding Bedrock Guardrails will make some aspects easier to grasp. This article explains the overall picture of Automated Reasoning functionality, so please note that explanations of Bedrock Guardrails are omitted. For those interested in Bedrock Guardrails or wanting more details, I've written separate articles: AWS Bedrock Guardrails Deployment: Part 1 - The Need for Generative AI Security : The necessity of guardrails for generative AI TECH BOOK By KINTO Technologies Vol.01 : How to implement Bedrock Guardrails on AWS 1. Target Readers, Considerations, and Objectives Initially, I thought Automated Reasoning, based on its name alone, would be an automated service that easily prevents generative AI hallucinations. However, it's quite far from being easily accessible automation—it felt quite advanced. So I'll first explain specific situations where Automated Reasoning is needed and important considerations. Target Readers Those who want to rigorously detect, track and mitigate LLM response hallucinations Those implementing generative AI with high governance or compliance requirements where errors are unacceptable Those developing generative AI applications with complex rules and requirements Those aiming to achieve AWS-centric Responsible AI Considerations This article explains findings based on testing in an English environment. When Japanese support is added in the future, behavior and specifications may change. Automated Reasoning analyzes and detects only content related to text and documents provided by users. Therefore, it is a service that verifies hallucinations and accuracy—it does not automatically restrict or process content. The official documentation also recommends using it together with existing Bedrock Guardrails filters. Amazon Bedrock Guardrails Automated Reasoning Checks do not protect against prompt injection attacks. These checks verify exactly what you submit. If malicious or manipulated content is provided as input, verification will be performed on that content as-is (inappropriate input/output). To detect and block prompt injection attacks, use content filters in combination with Automated Reasoning Checks. Automated Reasoning only analyzes and detects text related to the Automated Reasoning policy. The remaining content is ignored, and it cannot tell developers whether responses are off-topic. If you need to detect off-topic responses, use other guardrail components such as topic policies. As discussed later, Automated Reasoning automatically generates basic policies based on text and documents. However, you need to review and test these auto-generated policies. Understanding both the input context requirements and the Automated Reasoning structure is necessary, so please refer to the limitations and best practices in the links below: Constraints and Considerations Best Practices Objectives I want to focus mainly on understanding the Automated Reasoning structure mentioned in the Considerations section above. For this article, I hope you'll learn these three things to understand the big picture: Know what components Automated Reasoning has and how it works Know the flow for testing Automated Reasoning Know when to use Automated Reasoning (This requires knowledge of Guardrails, so I'll discuss it in a separate article) 2. Overview Roughly speaking, the overall picture of Automated Reasoning looks like the diagram below. The procedure is: When you input text/documents into Automated Reasoning, a Policy is automatically created The policy auto-generates Definitions of types, variables, and rules based on the input information a. Additional text or documents can be incorporated to expand policy definitions b. Generated types, variables, and rules can be edited to match requirements and ensure consistency Create Tests to verify the policy a. Manual creation: Enter hypothetical interactions in QnA pair format b. Automatic creation: Scenarios that can verify existing rules are auto-generated Check verification results to confirm expected outcomes a. Verify that expected results match actual results in test execution b. If they don't match, return to step 5 Identify the cause of mismatch and add Annotations to incorrect definitions a. Information suggesting the cause is presented in test execution results b. Annotations can be added to types, variables, and rules—mainly by modifying natural language descriptions, which triggers corresponding updates After applying annotations, suggested modifications appear; accept changes if correct Repeat steps 3-6 to complete a policy that protects the text/document content Attach the completed policy to a Guardrail Either use an existing Guardrail or create a new one Pass LLM application input/responses to the Guardrail and utilize Automated Reasoning Check results a. If successful, return results to the user as-is b. If failed, use Automated Reasoning Check results to request regeneration from the LLM application c. (Optional) Save Automated Reasoning Check results as logs and continue policy reviews In summary, Automated Reasoning is created as a Policy, completed through iterations of Definition → Test → Annotation. The completed policy is attached to AWS Bedrock Guardrails, and when the Guardrail is applied to LLM responses, Automated Reasoning Checks are performed. Subsequent processing varies by the developer's decisions, but you gain the ability to quantitatively judge whether the LLM is producing correct responses. By introducing Automated Reasoning, you can develop strategies to eliminate hallucinations while running parallel to existing Guardrails that block malicious content. Being able to verify the accuracy of generative AI with RAG and MCP that reference external information, and detect and correct erroneous LLM responses and uncontrollable incorrect inputs, is extremely attractive. Next, I'll use samples provided by AWS to explain the three main elements—Definition, Test, and Annotation—following the procedure from Policy creation. 3. Policy ![ポリシー作成画面](/assets/blog/authors/you/03/image-20251120-094018.png =800x) Basically, like other AWS resources, Automated Reasoning can be operated via console, CLI, or SDK. CloudFormation is not currently supported. CloudFormation support will be available soon. Creating an Automated Reasoning policy on the console is straightforward. Name Description (optional) Source (document/text) Source description After entering the above content and clicking the Create Policy button, the policy contents are automatically created. I used a medical PDF file prepared in the AWS sample, and the policy was generated in about 5-10 minutes. The time to create definitions likely varies depending on the length of the input document. When you check the created policy, the following screen appears: ![Overview](/assets/blog/authors/you/03/image-20251120-134516.png =800x) 4. Definitions The focus here is on the Definitions at the bottom. When you navigate from this overview screen to the definitions screen, you can confirm that rules, variables, and types are defined. Types ![Custom variable types](/assets/blog/authors/you/03/image-20251120-134952.png =800x) In addition to types pre-defined by AWS, if there are items in user-provided documents that can be classified as types, they are auto-generated. Creating variables with types and correctly defining rules is fundamental to Automated Reasoning. Explaining each item: Name: The name defining the type, a key used in variables Description: Content written so Automated Reasoning can make judgments Values: Individual values to be categorized Issues: Indicates problems occurring with the type Annotations: Displays modification content before application Actions: Three operations available—update, delete, revert When a type is not used in any variable, a warning appears as shown in the image indicating an unused type. At this point, users can decide whether to define a variable using this type or delete it. Types that aren't used don't affect operation, so they don't need to be resolved immediately. Here, I'll continue the explanation using RiskCategory , which is used somewhere. Name: RiskCategory Description: Risk category assigned to a patient based on total risk score, indicating estimated risk of 30-day readmission Values: LOW_RISK , INTERMEDIATE_RISK , HIGH_RISK Variables Variables represent concepts in an Automated Reasoning policy that can be assigned values when translating natural language to formal logic. Policy rules define constraints on valid or invalid values for these variables. 60 variables are defined, and searching for RiskCategory reveals the variables using this type. ![変数](/assets/blog/authors/you/03/image-20251120-142859.png =800x) Name: riskCategory Custom variable type: RiskCategory Description: Risk category assigned to a patient based on total risk score Since Automated Reasoning translates from natural language to formal logic, its accuracy heavily depends on the quality of variable descriptions. Therefore, the best practices include writing comprehensive variable descriptions. Without comprehensive variable descriptions, Automated Reasoning may return NO_DATA because it cannot convert the input natural language to formal logic expressions. Rules Rules are the logic that Automated Reasoning extracts from source documents. Automated Reasoning logic is created in SMT-LIB. Satisfiability Modulo Theories (SMT) is a field studying methods to check the satisfiability of first-order formulas, and as part of this, SMT-LIB —a common input and output language for SMT users—was developed. In Automated Reasoning, you can modify formulas just by changing rule descriptions, but you can also modify them in SMT-LIB format for reference. There are 21 rules using riskCategory , mainly creating scenarios with riskCategory plus other variable conditions. ![ルール](/assets/blog/authors/you/03/image-20251120-144856.png =800x) Explaining the rule at the top: since the variable examplePatient has the following definition: Name: examplePatient Custom variable type: Boolean Description: Whether this is an example of a patient from guidance with specific characteristics if examplePatient is true, then riskCategory is equal to HIGH_RISK → If it matches an example of a patient with specific characteristics from guidance, the risk category is high risk This is the rule that represents this scenario. Now let's test this rule. 5. Tests There are two ways to run tests. ![テスト](/assets/blog/authors/you/03/image-20251120-153501.png =800x) Manually define question-and-answer (QnA) pairs Automatically generate test scenarios This article uses console operations with manual definition as an example for intuitive understanding, but for running many tests mechanically, using automatic generation via CLI or SDK is easier. Before verifying rules with manual definition, let's try testing with automatic generation. Automatic Generation Tests ![自動生成](/assets/blog/authors/you/03/image-20251120-155533.png =800x) On the console, clicking the generate button creates test scenarios in this format. Since riskCategory and maxPostDischargeTelephoneContactHours were in the conditions, let's search for LOW_RISK from the definitions screen. ![LOW_RISKの検索結果](/assets/blog/authors/you/03/image-20251120-155653.png =800x) However, there's no rule with the maxPostDischargeTelephoneContactHours variable. Since this variable's description is "maximum hours after discharge when post-discharge telephone contact occurs," users who know this scenario's requirements can judge its validity. If you determine the scenario is incorrect, you can have the test scenario modified by writing a description, but for now, let's create a test scenario as suggested. ![テスト詳細画面](/assets/blog/authors/you/03/image-20251120-160258.png =800x) Then, entering the created test case shows this screen, where you can run the test. When executed: ![自動生成テスト実行結果](/assets/blog/authors/you/03/image-20251120-160500.png =800x) It succeeded even though there's no matching rule for the conditions. Why is that? Because the expected result anticipated by the auto-generated test scenario matched the actual result. There are 7 criteria for judging test results in Automated Reasoning. VALID : Claim logically matches rules INVALID : Claim logically contradicts or violates rules SATISFIABLE : Claim is consistent with at least one rule condition, but doesn't match all relevant rules IMPOSSIBLE : There may be conflicts within the Automated Reasoning policy TRANSLATION_AMBIGUOUS : Ambiguity detected in translation from natural language to logic TOO_COMPLEX : Input contains too much information NO_TRANSLATIONS : Part or all of the input prompt was not converted to logic ![maxPostDischargeTelephoneContactHoursの検索結果](/assets/blog/authors/you/03/image-20251120-162758.png =800x) riskCategory is equal to LOW_RISK maxPostDischargeTelephoneContactHours is equal to 72 Since each claim existed as one of the conditions in some rule, it produced a SATISFIABLE actual result, and the test scenario set this as the expected result accordingly. Annotations Now, let's assume the requirement is "if the maximum hours for post-discharge telephone contact is 120 hours, the risk category is low risk." In that case, we need to add a new rule. ![ルール追加](/assets/blog/authors/you/03/image-20251120-163917.png =800x) At this point, you can add annotations for additions/modifications and apply them. ![注釈適用](/assets/blog/authors/you/03/image-20251120-164020.png =800x) Proceeding here: ![ポリシー更新レビュー](/assets/blog/authors/you/03/image-20251120-164329.png =800x) You can review changes and discard, approve, or return to the policy for reconsideration. The rule was generated as expected, so it is approved. ![ルールの変更後画面](/assets/blog/authors/you/03/image-20251120-164652.png =800x) Once the rule is reflected, the actual result changes to INVALID and shows as failed. Since the new rule was applied, it now clearly judges as INVALID , so we need to change the test scenario expectation. ![テスト修正](/assets/blog/authors/you/03/image-20251120-164916.png =800x) Changing the expected result to INVALID makes it succeed. ![修正後のテスト実行結果](/assets/blog/authors/you/03/image-20251120-165158.png =800x) Manual Definition Tests The flow of reviewing results and making corrections with annotations isn't much different when done manually. Let's add a test with the following content. For manual input, enter content mimicking actual LLM application responses. ![手動定義](/assets/blog/authors/you/03/image-20251120-165940.png =800x) Input: Mr. Foo is a patient who requires caution with medication use. What is this person's risk category? Output: Since this is a patient with specific characteristics from guidance, the risk category is high risk Expected result: VALID ![手動定義テスト実行結果](/assets/blog/authors/you/03/image-20251120-170523.png =800x) The result is, of course, successful. You can see an added type here—it becomes a Premise that appears in QnA tests. Just as this question assumed the patient has characteristics, it's a type that provides context, preconditions, or conditions that affect how claims are evaluated. Additionally, you can check whether Automated Reasoning is translating accurately by anticipating the confidence threshold for translation from natural language to formal logic. ![提案](/assets/blog/authors/you/03/image-20251120-171402.png =300x) You can also view variable Assignments that prove whether findings are valid, allowing you to quickly check examples of correct and incorrect scenarios. Tests repeat this process to provide correct policies to LLM applications. To apply this policy to an LLM application, you must attach it to AWS Bedrock Guardrails and use the Guardrail. 6. Conclusion From here, the discussion moves to practical operations of how to perform Automated Reasoning by attaching the created policy to a Guardrail. However, since Automated Reasoning Checks applied to actual applications exist as one feature of Bedrock Guardrails, I think it's better to explain together with Guardrails. I'll introduce this along with new Bedrock Guardrails features in the future. This article explained the concepts and mechanisms provided by AWS within the overall picture of Automated Reasoning, as well as the flow for designing and completing policies. Automated Reasoning Checks are a generative AI feature provided by AWS—a tool for verifying the accuracy of AI-generated content. This prevents factual errors caused by AI hallucinations and uses mathematical logic and formal verification techniques to confirm accuracy. Specifically, through the process of policy creation, definition, testing, and annotation, it evaluates generative AI output and can prevent hallucinations. In other words, policies are auto-generated based on user-provided documents, and their accuracy is confirmed through testing. Policies built this way can achieve high accuracy in generative AI implementations through integration with AWS Bedrock Guardrails. I hope this article has, in some way, deepened your understanding of Automated Reasoning. [^2]: When released in 2024, it was introduced as Automated Reasoning Checks since integration with Guardrails was fundamental. However, in the August GA this year, Automated Reasoning emerged as an independent Bedrock feature. Therefore, this article uses Automated Reasoning Checks when functioning from Guardrails and Automated Reasoning when functioning independently.
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2025 の3日目の記事です🎅🎄 はじめに はじめまして。KINTOテクノロジーズでAndroidアプリ開発を担当しているJongSeokです。 Claude Codeを使ってAndroid開発をしながら、SubAgents機能は少し使っていました。 使っているうちに、もっと効率よく使えるんじゃないかと思い始めて、いろいろ調べてみました。 そしたら最近発表された Skills という機能も見つけました。 この機会に SubAgents と Skills、両方試してみたので、その内容をまとめてみました。 1. SubAgents? 一言で言うと 「別の作業スペースを持つ専門家」 です。 普通にClaude Codeと会話すると、すべてが一つのコンテキスト(会話の流れ)に入ります。 でもSubAgentsは別のコンテキストで作業して、結果だけ報告してくれます。 チームで例えると、リーダーが専門家に仕事を任せて結果報告を受けるイメージですね。 1.1 主な特徴 特徴 説明 独立したコンテキスト メインの会話を汚さない 専門化されたプロンプト 役割に特化した指示を設定できる ツール権限の制限 必要な機能だけ許可できる 1.2 作り方 SubAgentsを作る方法は2つあります。 1. コマンドで作成(推奨) Claude Codeでは /agents コマンドで簡単に作成できます。 List Make Projectに定義されているAgentsの確認や、新規作成ができます。 2. 直接ファイルを作成 .claude/agents/ フォルダに .md ファイルを追加して作ることもできます。 1.3 活用例:kotlin-method-namer Kotlinでメソッド名を考えるのは意外と悩むポイントです。 そこで、Android/Kotlinのスタイルに合ったメソッド名を提案してくれるSubAgentを作ってみました。 どんな時に使う? メソッドの機能を英語でどう表現するか迷う時 Kotlinの命名規則に合っているか確認したい時 より良いメソッド名の候補が欲しい時 --- name: kotlin-method-namer description: Expert for suggesting Android/Kotlin method names tools: Read, Glob, Grep model: sonnet color: cyan --- You are an expert Android Kotlin developer specializing in creating clear, idiomatic method names. ... ( Full version ) DEMO SubAgentsは直接指定して呼ぶ必要があります。 color: cyan を設定したので、Agentが実行されているのが分かります。 DEMOの結果 initializeVariable() という名前を提案してくれました。 このように、SubAgentsは特定の作業に特化したAgentsを作れます。 繰り返し発生する専門的な作業があれば、SubAgentsにしてみると効率が上がるかもしれません。 2. Skills? 一言で言うと 「自分だけの業務ガイドブック」 です。 SubAgentsが別の作業スペースを持つ専門家だとすれば、 Skillsはメインエージェントに専門知識を追加するイメージです。 Claudeが作業する時に参考にするガイドブックを事前に作っておくと、 関連する作業を依頼した時に自動で認識して使ってくれます。 2.1 主な特徴 特徴 説明 メインコンテキストに統合 SubAgentsと違い、別空間ではなくメインの会話で動作 自動認識 (Auto-invoked) 関連する作業を依頼すると、Claudeが自動で使用 Progressive Disclosure 必要な情報だけを段階的にロード Deterministic スクリプト実行で一貫性のある結果を保証 2.2 Progressive Disclosureとは? Skillsの核心となる設計原則です。 一度にすべての内容をロードせず、段階的に必要な情報だけを取得します。 段階 ロードされる内容 タイミング 1段階 name , description 起動時にシステムプロンプトへロード 2段階 SKILL.md 本文 関連する作業を依頼した時 3段階 追加ファイル (scripts, references など) 必要な時だけ 整理されたガイドブックのように、目次 → 該当チャプター → 付録の順で必要なものだけ読む方式です。 これにより、 複数のSkillsをインストールしてもコンテキストを無駄にせず使えます。 2.3 作り方 .claude/skills/ フォルダ内にスキルフォルダを作成し、 SKILL.md ファイルを追加します。 project/ └── .claude/ └── skills/ └── your-skill-name/ └── SKILL.md SKILL.md --- name: wildcard-import-fixer description: Converts wildcard imports (e.g., import java.util.*) to specific imports in Kotlin/Java files. allowed-tools: Bash, Read, Glob, Grep --- YAML Frontmatter 項目 ルール 必須 name 小文字 + ハイフン使用、64文字以下 ✅ description 200文字以下、Claudeがいつ使うか判断する重要な部分 ✅ version バージョン管理用 (例: 1.0.0) - SKILL.md 本文 500行以下推奨 長くなったら別ファイルに分離 (例: reference.md , scripts/ ) セキュリティ APIキー、パスワードなどの機密情報をハードコーディング禁止 信頼できるソースのスキルのみインストール 💡 SubAgentsとの違い SubAgents: .claude/agents/ファイル名.md (ファイル単位) Skills: .claude/skills/スキル名/SKILL.md (フォルダ単位) 2.4 活用例: wildcard-import-fixer 💡 Skillsではスクリプトを実行できます。 今回はAndroidプロジェクトなのでKotlinを使用しましたが、Python、JavaScript(npm)なども対応しています。 Kotlinでよくある wildcard import ( import java.util.* ) を個別のimportに変換するSkillを作ってみました。 どんな時に使う? wildcard importを整理したい時 コード品質を改善したい時 import文を明確にしたい時 フォルダ構成 .claude/skills/wildcard-import-fixer/ ├── SKILL.md ← 必須 ├── scripts/ │ └── fix-wildcards.kts ← 任意(自分で追加) ├── templates/ │ └── report_template.html ← 任意(自分で追加) ├── backups/ ← 自動生成(スクリプト実行時) └── reports/ ← 自動生成(スクリプト実行時) 💡 必須なのは SKILL.md だけです。 その他のファイルやフォルダは用途に合わせて自由に構成できます。 なぜスクリプトを使う? LLMにコードを毎回生成させると、結果が微妙に変わることがあります。 でもスクリプトを事前に用意しておけば、 毎回同じ結果 が保証されます。 これがSkillsの Deterministic な特徴です。 SKILL.md --- name: wildcard-import-fixer description: Converts wildcard imports (e.g., import java.util.*) to specific imports in Kotlin/Java files. allowed-tools: Bash, Read, Glob, Grep --- # Wildcard Import Fixer Automatically converts wildcard imports to specific imports in Kotlin and Java files by analyzing actual class usage in the code. ## Instructions ### Step 1: Analyze the Request When a user asks to fix wildcard imports: 1. Determine the scope (entire project, specific directory, or single file) 2. Decide if a dry-run preview is appropriate first 3. Check if backups should be created ### Step 2: Run the Fixer Script Execute the appropriate command based on the scope... ### Step 3: Review Results After running, check the console output and HTML report... ### Step 4: Handle Edge Cases If no usage is detected, suggest removing the unused import... ( Full version ) 💡 このようにStep形式で指示を書くと、Claudeが順番通りに作業を進めてくれます。 DEMO このデモでは、一度実行した後に /clear で会話をリセットして、もう一度同じ作業を依頼しています。 /clear してもSkillsの name と description は再ロードされる(Progressive Disclosure) スクリプト実行なので、 毎回同じ結果 が返ってくる(Deterministic) これがSkillsの強みです。 DEMOの結果 結果を一目で確認しやすくhtmlにもするようにしました。 3. SubAgents vs Skills SubAgents : 別室で作業する専門家。結果だけ報告してくれる。 Skills : 手元に置いておくマニュアル。必要な時に参照して作業する。 3.1 比較表 項目 SubAgents Skills コンテキスト 独立(別の作業スペース) メインに統合 呼び出し方 直接指定して呼ぶ 自動認識(Auto-invoked) ファイル構成 .claude/agents/ファイル名.md .claude/skills/スキル名/SKILL.md 単位 ファイル単位 フォルダ単位 スクリプト実行 ❌ ✅ 向いている作業 複雑なワークフロー、並列作業 繰り返しのルーティン作業 3.2 使い分け SubAgentsを使う場面 コードレビューなど、専門的な視点が必要な時 メインの会話を汚したくない時 複数のステップがある複雑な作業 Skillsを使う場面 同じ作業を繰り返す時 一貫した結果が欲しい時(Deterministic) 複数のプロジェクトで再利用したい時 3.3 組み合わせて使う SubAgentsとSkillsは競合するものではなく、 補完関係 にあります。 例: PRレビューの自動化 compose-reviewer (SubAgent) がComposeコードをレビュー kotlin-style-checker (Skill) でスタイルチェック test-generator (SubAgent) がテストコードを生成 SubAgentが専門的な判断を、Skillが一貫したルールチェックを担当します。 4. まとめ 今回SubAgentsとSkillsを試してみて、使い分けが少し見えてきました。 こんな時は 使うもの 専門的な視点でレビューしてほしい SubAgents メインの会話を汚したくない SubAgents 同じ作業を毎回同じ結果で実行したい Skills スクリプトで自動化したい Skills 両方必要な複雑なワークフロー 組み合わせ まだ使い始めたばかりですが、繰り返しの作業はSkillsに、専門的な判断が必要な作業はSubAgentsに任せると効率が上がりそうです。 興味がある方はぜひ試してみてください! References SubAgents SubAgents - Claude Docs Skills Agent Skills - Claude Docs Equipping agents for the real world with Agent Skills anthropics/skills - GitHub 比較 Skills explained - Claude Blog
アバター
This article is the Day 3 entry for KINTO Technologies Advent Calendar 2025 🎅🎄 Introduction Hello, I'm JongSeok, an Android app developer at KINTO Technologies. While developing Android apps with Claude Code, I had been using the SubAgents feature a bit. As I continued using it, I started thinking there might be ways to use it more efficiently, so I did some research. That's when I discovered a recently announced feature called Skills. I took this opportunity to try both SubAgents and Skills, and here's a summary of what I learned. 1. SubAgents? In short, they are specialists with their own workspace . When you normally chat with Claude Code, everything goes into a single context (conversation flow). But SubAgents work in a separate context and only report back the results. Think of it like a team leader delegating work to a specialist and receiving a report. 1.1 Key Features Feature Description Independent Context Doesn't clutter the main conversation Specialized Prompts Can set role-specific instructions Tool Permission Restrictions Can allow only necessary features 1.2 How to Create There are two ways to create SubAgents. 1. Create via Command (Recommended) In Claude Code, you can easily create them with the /agents command. List Make You can check Agents defined in the Project or create new ones. 2. Create Files Directly You can also add .md files to the .claude/agents/ folder. 1.3 Use Case: kotlin-method-namer Coming up with method names in Kotlin can be surprisingly tricky. So I created a SubAgent that suggests method names following Android/Kotlin style. When to Use? When you're unsure how to express a method's functionality in English When you want to verify if it follows Kotlin naming conventions When you want better method name candidates --- name: kotlin-method-namer description: Expert for suggesting Android/Kotlin method names tools: Read, Glob, Grep model: sonnet color: cyan --- You are an expert Android Kotlin developer specializing in creating clear, idiomatic method names. ... ( Full version ) DEMO SubAgents need to be called directly by name. Since I set color: cyan , you can see when the Agent is running. DEMO Result It suggested the name initializeVariable() . As you can see, SubAgents let you create Agents specialized for specific tasks. If you have recurring specialized work, turning them into SubAgents might improve your efficiency. 2. Skills? In short, they are your personal work guidebook . If SubAgents are specialists with their own workspace, Skills are like adding specialized knowledge to the main agent. If you prepare a guidebook in advance for Claude to reference during work, it will automatically recognize and use it when you request related tasks. 2.1 Key Features Feature Description Integrated into Main Context Unlike SubAgents, operates in the main conversation, not a separate space Auto-invoked Claude automatically uses it when you request related work Progressive Disclosure Loads only necessary information in stages Deterministic Script execution guarantees consistent results 2.2 What is Progressive Disclosure? This is the core design principle of Skills. Instead of loading everything at once, it retrieves only the necessary information in stages. Stage Content Loaded Timing Stage 1 name , description Loaded into system prompt at startup Stage 2 SKILL.md body When related work is requested Stage 3 Additional files (scripts, references, etc.) Only when needed Like a well-organized guidebook, it reads only what's needed: table of contents -> relevant chapter -> appendix. This means you can install multiple Skills without wasting context. 2.3 How to Create Create a skill folder inside the .claude/skills/ folder and add a SKILL.md file. project/ └── .claude/ └── skills/ └── your-skill-name/ └── SKILL.md SKILL.md --- name: wildcard-import-fixer description: Converts wildcard imports (e.g., import java.util.*) to specific imports in Kotlin/Java files. allowed-tools: Bash, Read, Glob, Grep --- YAML Frontmatter Item Rule Required name Lowercase + hyphens, 64 characters or less ✅ description 200 characters or less, critical for Claude to determine when to use it ✅ version For version management (e.g., 1.0.0) - SKILL.md Body Recommended to be under 500 lines Split into separate files if it gets long (e.g., reference.md , scripts/ ) Security Never hardcode sensitive information like API keys or passwords Only install skills from trusted sources 💡 Difference from SubAgents SubAgents: .claude/agents/filename.md (file-based) Skills: .claude/skills/skill-name/SKILL.md (folder-based) 2.4 Use Case: wildcard-import-fixer 💡 Skills can execute scripts. Since this is an Android project, I used Kotlin, but Python and JavaScript (npm) are also supported. I created a Skill that converts common Kotlin wildcard imports ( import java.util.* ) to individual imports. When to Use? When you want to clean up wildcard imports When you want to improve code quality When you want to make import statements explicit Folder Structure .claude/skills/wildcard-import-fixer/ ├── SKILL.md ← Required ├── scripts/ │ └── fix-wildcards.kts ← Optional (add yourself) ├── templates/ │ └── report_template.html ← Optional (add yourself) ├── backups/ ← Auto-generated (when script runs) └── reports/ ← Auto-generated (when script runs) 💡 Only SKILL.md is required. Other files and folders can be freely structured according to your needs. Why Use Scripts? If you have the LLM generate code each time, results can vary slightly. But if you prepare scripts in advance, the same results are guaranteed every time . This is the Deterministic characteristic of Skills. SKILL.md --- name: wildcard-import-fixer description: Converts wildcard imports (e.g., import java.util.*) to specific imports in Kotlin/Java files. allowed-tools: Bash, Read, Glob, Grep --- # Wildcard Import Fixer Automatically converts wildcard imports to specific imports in Kotlin and Java files by analyzing actual class usage in the code. ## Instructions ### Step 1: Analyze the Request When a user asks to fix wildcard imports: 1. Determine the scope (entire project, specific directory, or single file) 2. Decide if a dry-run preview is appropriate first 3. Check if backups should be created ### Step 2: Run the Fixer Script Execute the appropriate command based on the scope... ### Step 3: Review Results After running, check the console output and HTML report... ### Step 4: Handle Edge Cases If no usage is detected, suggest removing the unused import... ( Full version ) 💡 Writing instructions in this Step format helps Claude follow them in order. DEMO In this demo, after running once, I reset the conversation with /clear and requested the same task again. Even after /clear , the Skills' name and description are reloaded (Progressive Disclosure) Since it's script execution, the same results are returned every time (Deterministic) This is the strength of Skills. DEMO Result I also made it generate HTML for easy result viewing. 3. SubAgents vs Skills SubAgents : Specialists working in a separate room. They only report back results. Skills : Manuals you keep on hand. Referenced when needed for work. 3.1 Comparison Table Item SubAgents Skills Context Independent (separate workspace) Integrated into main Invocation Called directly by name Auto-invoked File Structure .claude/agents/filename.md .claude/skills/skill-name/SKILL.md Unit File-based Folder-based Script Execution ❌ ✅ Best For Complex workflows, parallel tasks Repetitive routine tasks 3.2 When to Use Each When to Use SubAgents When you need a specialized perspective, like code reviews When you don't want to clutter the main conversation For complex tasks with multiple steps When to Use Skills When you repeat the same task When you want consistent results (Deterministic) When you want to reuse across multiple projects 3.3 Using Them Together SubAgents and Skills complement each other rather than compete. Example: Automating PR Reviews compose-reviewer (SubAgent) reviews Compose code kotlin-style-checker (Skill) performs style checks test-generator (SubAgent) generates test code SubAgents handle specialized judgment while Skills handle consistent rule checking. 4. Summary After trying SubAgents and Skills, I've started to see when to use each. When You Need Use A specialized perspective for reviews SubAgents To keep the main conversation clean SubAgents To run the same task with identical results every time Skills To automate with scripts Skills Complex workflows requiring both Combination I've just started using them, but it seems like delegating repetitive tasks to Skills and tasks requiring specialized judgment to SubAgents improves efficiency. If you're interested, give them a try! References SubAgents SubAgents - Claude Docs Skills Agent Skills - Claude Docs Equipping agents for the real world with Agent Skills anthropics/skills - GitHub Comparison Skills explained - Claude Blog
アバター
Hello, I'm Moriya (emim) from the Engineering Office. In this article, I'll be reporting on the topic mentioned in the title as a cross-post for December 2nd of both the KINTO Technologies Advent Calendar 2025 and the Accessibility Advent Calendar 2025 . I work in the Engineering Office, a division dedicated to strengthening our organization’s development capabilities, where I focus on design. Alongside that, as a personal ongoing activity, I engage in advocacy to raise awareness about digital accessibility. I was amazed by a staff member who spontaneously did some wonderful accessibility work, and I asked him about what he did and what motivated him. Who Added Alt Text to the X (formerly Twitter) Post!? First, please take a look at the following post. https://x.com/KintoTech_Dev/status/1976163362016043509?s=20 Does it just look like an introduction to our Fukuoka office? Take a close look: the attached photo has been given wonderfully thoughtful alt text. ![KINTOテクノロジーズの10月9日のX投稿に4枚の写真が添付されていて、すべてに「ALT」ボタンが表示されている様子のキャプチャー](/assets/blog/authors/emim/2025-12-02-SNSA11y/2025-12-02-SNSA11y_post1.png =600x) ![ビルの写真に「KINTOテクノロジーズの福岡オフィスが入居する福岡大名ガーデンシティのビル外観」と代替テキストが付いているのをポップアップしたところ](/assets/blog/authors/emim/2025-12-02-SNSA11y/2025-12-02-SNSA11y_post2.png =500x) We have a chat channel called team-accessibility within the company where we regularly share information. However, the person who made this post wasn't someone who actively participated in those discussions, so I was absolutely astonished. Someone unexpectedly added proper alt text!!! I spoke with the following two people for this article: Takenaka Background: After 12 years of development and PM experience at a system integrator, worked as a Scrum Master for in-house development at a bank. Currently serves as manager of the Developer Relations Group. Is the one who made the X post Yukachi Background: First job was in the travel industry; moved to IT after being hit by the COVID-19 pandemic. At KINTO Technologies, originally handled accounting work, then over time transitioned to a dedicated role in the Developer Relations Group, handling event management and social media outreach. Is very interested in accessibility recently About the Alt Text in This Post The alt text added to these four images in the post. One screen reader user commented that it was high quality and wonderful, very readable (or rather, easy to listen to), and described it with an abstract expression as alt text that feels like a gentle breeze. So I asked Takenaka why he added alt text this time, and received the following response. —— (Takenaka) I had heard of digital accessibility, but didn't really understand what it meant. This time, the ALT button happened to stand out when I was posting on X, and I'd been exposed to accessibility information daily through the team-accessibility chat. I'd also heard it was gaining attention, so I gave it a go. I’d been putting things out there without knowing who’d see them, and it’s wild that they had a subliminal impact here! I also asked if they had any difficulty writing it. —— (Takenaka) I understood that I needed to write it, but I didn’t know how. So right before, I did a quick internet search and tried to keep it concise, making sure not to include anything outside the main text. I only found out how it would actually appear after checking the published post. I understood that this is a feature often overlooked unless you pay attention. For people familiar with accessibility, the button labeled ALT may be visible at a glance, but it’s important to keep in mind that some people won’t notice it at all. Insights Gained Through Alt Text Isn’t it wonderful to imagine a world where even those who aren’t very aware of accessibility add alternative text? I was both surprised and touched, so I posted about how I tracked down who had added it internally, and that post received quite a lot of likes. https://x.com/emim/status/1977921547869561149?s=20 When I shared the feedback again, Yukachi was really moved. She’s not an engineer, so she usually doesn’t show up in the accessibility chat. I asked both of them again about these responses. —— (Takenaka) I was surprised it became a topic, and I realized that even alt text alone could spark reactions. —— (Yukachi) I simply wasn't aware that you could add alt text, and I learned for the first time that adding it helps some people. Accessibility was something I’d never really thought about, but now I’m curious if there’s something I can do too. Hearing these words, I felt I'd received answers that clearly showed how even small things, when communicated as feedback, can influence people's behavior. She also added the following opinion. —— (Yukachi) I think it's important, but since I don't know what to do, it would be nice to have more learning opportunities that even beginners can understand. In recent years, accessibility-related study groups have certainly increased compared to the past. However, one of the industry’s challenges is that they tend to skip the basics and cater more to advanced participants. On the other hand, the Digital Agency released the Digital Society Promotion Standard Guidelines in October, which includes DS-672.1 Web Accessibility Guidebook for Public Relations, summarized for PR personnel (accessibility beginners). https://www.digital.go.jp/resources/standard_guidelines We’re planning to use these publicly available materials to organize study sessions for the Developer Relations Group. After checking with the Digital Agency, we received confirmation that we’re free to use them as much as we like. If we have the capacity, we’d also like to invite external participants to join these sessions. So please look forward to what we’ll be doing next year!
アバター
この記事は KINTOテクノロジーズ Advent Calendar 2025 の2日目の記事です🎅🎄 はじめに こんにちは! KINTO開発部 KINTOバックエンド開発G マスターメンテナンスツール開発チーム、Osaka Tech Lab 所属の high-g( @high_g_engineer )です。 API 連携が多い現代のフロントエンド開発において、こんな課題を感じたことはないでしょうか? API の型定義を手動で書いていて、仕様変更のたびに修正漏れが発生する 自動生成ファイルの置き場所がバラバラで、どこから何を import すればいいか分からない チームメンバーごとにディレクトリ構造の解釈が異なり、コードレビューで議論になる これらの課題を解決するキーワードが、 「型安全」「スキーマ駆動」「自動生成」「ディレクトリ設計」 です。 この記事では、OpenAPI を唯一の情報源として型安全なコードを自動生成し、それを Feature-Sliced Design のルールに則って管理するアプローチを紹介します。 具体的には、Orval で「どんなコードが生成されるのか」を見ながら、Feature-Sliced Design のディレクトリ構造に沿った効果的な設計パターンを解説します。 この記事で紹介すること OpenAPI を元に Orval から型とカスタムフックを出力する流れ Orval が実際に出力するコード例の詳細 Feature-Sliced Design の層構造とインポートルール Orval 生成コードを Feature-Sliced Design のディレクトリ構造で管理する設計パターン 想定読者 REST API と型定義の手動管理に疲れているフロントエンド開発者 TypeScript + React を使っている方 API 変更に強い設計に興味がある方 ディレクトリ構造のルール化に関心がある方 基礎となる知識 OpenAPI について OpenAPI は、HTTP API をプログラムで解釈可能な形式で定義するための標準です。API の仕様を YAML または JSON で記述することで、以下のようなメリットがあります。 API の入出力が明確に定義される ドキュメント生成が自動化される クライアント・サーバー間での齟齬を防ぐ 例:OpenAPI の記述の一部(簡略版) openapi: 3.1.0 paths: /posts: get: summary: 投稿一覧を取得 parameters: - name: page in: query schema: type: integer responses: "200": description: Success content: application/json: schema: $ref: "#/components/schemas/GetPostsResponse" post: summary: 投稿を作成 requestBody: content: application/json: schema: $ref: "#/components/schemas/CreatePostRequest" responses: "201": description: Created content: application/json: schema: $ref: "#/components/schemas/CreatePostResponse" /posts/{postId}: put: summary: 投稿を更新 parameters: - name: postId in: path required: true schema: type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/UpdatePostRequest" responses: "200": description: Success content: application/json: schema: $ref: "#/components/schemas/Post" delete: summary: 投稿を削除 parameters: - name: postId in: path required: true schema: type: string responses: "204": description: No Content components: schemas: Post: type: object required: [id, title, createdAt, updatedAt, status] properties: id: type: string title: type: string body: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time status: type: string enum: [draft, published, archived] GetPostsResponse: type: object properties: items: type: array items: $ref: "#/components/schemas/Post" total: type: integer page: type: integer CreatePostRequest: type: object required: [title] properties: title: type: string body: type: string CreatePostResponse: allOf: - $ref: "#/components/schemas/Post" - type: object properties: createdBy: type: string UpdatePostRequest: type: object properties: title: type: string body: type: string status: type: string enum: [draft, published, archived] この YAML は以下を定義しています。 /posts エンドポイント:一覧取得(GET)と作成(POST) /posts/{postId} エンドポイント:更新(PUT)と削除(DELETE) スキーマ駆動開発について 従来の手動管理の問題 これまで、私たちフロントエンド開発者は以下のような作業を手動で行っていました。 // 手動で型定義を書く type Post = { id: string; title: string; body?: string; createdAt: string; updatedAt: string; status: "draft" | "published" | "archived"; }; // API 呼び出しも手で書く const getPost = async (id: string): Promise<Post> => { const response = await fetch(`/api/posts/${id}`); return response.json(); }; この方法には以下の問題があります。 手動修正のコスト OpenAPI を確認 → 型定義を手で修正 → 利用箇所をすべて修正 修正漏れのリスク 型定義と実際の API 仕様がズレる 複数の箇所で同じ型を使っていると漏れが生じやすい ドキュメントとコードの非同期 OpenAPI ≠ 実装コード になることもある スキーマ駆動開発のアプローチ 上記で挙げた手動対応の問題を解消するために考えられたのが、「スキーマ(API 仕様)を最初に定義し、そこから実装を進める」開発手法です。 従来:実装 → ドキュメント(後付け) → 仕様との齟齬 スキーマ駆動:スキーマ定義 → 自動生成 → 実装 → 完了 (実装 = ドキュメント、常に同期) スキーマ駆動開発の特徴 実装 = ドキュメント :常に API 仕様とコードが同期 型安全性 :コンパイル時に API 不整合を検出 開発効率 :型定義の手作業が不要 チーム連携 :フロントエンドとバックエンド両方が同じ OpenAPI を参照 Orval について Orval は、OpenAPI の仕様書から TypeScript の型定義やカスタムフックなどのコードを コマンドひとつで自動生成 してくれるツールです。 Orval の主な特徴 特徴 説明 型定義の自動生成 API のリクエスト・レスポンスの型を自動で作成 カスタムフックの自動生成 TanStack Query などのフックも自動生成 複数ライブラリ対応 TanStack Query だけでなく、Axios などのライブラリにも対応 モック生成 テスト用のモックデータも生成可能 Orval を使うメリット 時間の節約 :型定義や API 呼び出しコードを手書きする時間がゼロに ミスの防止 :手書きによるタイプミスや仕様の読み間違いがなくなる 常に最新 :OpenAPI が更新されたら、再生成するだけで最新の状態に スキーマ駆動開発における Orval の役割 ここまでの内容をまとめると、スキーマ駆動開発における Orval の役割は以下のようになります。 OpenAPI(信頼できる唯一の情報源) ↓ Orval によるコードの自動生成で、 型定義 + TanStack Query フックを常に同期 ↓ 低コストで型安全な開発が可能 Feature-Sliced Design について 冒頭でも触れた通り、現在開発中のプロジェクトでは、フロントエンドのコード構成に Feature-Sliced Design というアーキテクチャパターンを採用しています。 Feature-Sliced Design は、コードベースを レイヤー 、 スライス 、 セグメント という3つの概念で整理するアーキテクチャです。 概念 説明 例 レイヤー (Layer) アプリケーションの責務による分割。上位から app → pages → features → entities → shared の5層。 app はルーティングやレイアウトなどアプリ全体の設定、 pages は URL に対応する画面を担当 app/ , pages/ , features/ スライス (Slice) 各レイヤー内でのビジネスドメインや機能ごとの分割単位 features/auth/ , entities/user/ セグメント (Segment) スライス内での技術的な役割による分割 ui/ , model/ , api/ src/ ├── features/ ← レイヤー │ ├── auth/ ← スライス │ │ ├── ui/ ← セグメント │ │ ├── model/ ← セグメント │ │ └── index.ts この構造により、「どこに何を置くか」が明確になり、チーム全体でコードの配置ルールを統一できます。 Feature-Sliced Design のディレクトリ構造 私たちのチームでは、以下のようなディレクトリ構造で運用しています。セグメント( api/ 、 model/ 、 ui/ など)の分け方はプロジェクトに合わせてカスタマイズしています。 workspaces/typescript/src/ ├── app/ ← ① アプリケーション層:ルーティング、グローバル設定 │ ├── layouts/ 全体的なページで利用するレイアウト │ ├── routes/ ルーティング定義 │ └── App.tsx ルートとなるtsxファイル │ ├── pages/ ← ② ページ層:各ページコンポーネント(URLに対応) │ ├── users/ │ └── login/ │ ├── features/ ← ③ 機能層:再利用可能なビジネスロジック │ ├── {slice}/ ドメインごとにスライスという単位で分割 (例:user, auth) │ │ ├── {component}/ ドメインに属するコンポーネント │ │ │ ├── model/ ロジック部分 │ │ │ ├── ui/ UI部分 │ │ │ └── index.ts 公開API(バレルファイル) │ │ ... │ ... │ ├── entities/ ← ④ エンティティ層:ビジネスドメインの定義 │ ├── user/ 各種ドメイン │ │ ├── @x クロスインポート記法 ※後述 │ │ ├── api/ shared/ の自動生成ファイルを import して利用(ファサード) │ │ │ ├── hooks.ts apiフック │ │ │ └── index.ts 公開API(バレルファイル) │ │ ├── model/ ドメインロジック │ │ ├── ui/ ドメイン内にとどまる最小レベルのUI │ │ └── index.ts 公開API(バレルファイル) │ ... │ └── shared/ ← ⑤ 共有層:プロジェクト非依存のユーティリティ ├── api/ │ └── generated/ Orval による自動生成ファイル(修正禁止) │ ├── types.ts │ ├── hooks.ts │ └── client.ts ├── config/ 設定定数 ├── errors/ 共通利用エラー関数 ├── lib/ ユーティリティ関数 └── ui/ 汎用UIコンポーネント Feature-Sliced Design の層間インポート制限ルール Feature-Sliced Design の最も重要なルール: レイヤーは自身より下位のレイヤーのみをインポート可能 です。 また、同一レイヤー間の相互インポートも原則不可です(例外は後述の @x 記法)。 app ← 最上位(抽象度が高い) ↓ import可能 pages ↓ features ↓ entities ※ shared はどのレイヤーからもインポート可能 つまり、以下のようなルールがプロジェクト内で設けられています。 ✅ pages/ は features/、entities/、shared/ を import 可能 ✅ features/ は entities/、shared/ を import 可能 ✅ entities/ は shared/ を import 可能 ❌ entities/ は features/ や pages/ を import してはいけない ❌ shared/ は他のどのレイヤーも import してはいけない entities 層の特別な役割:entities/@x(クロスインポート記法) しかし、entities 層ではビジネスドメイン同士が関連することが多く、例えば「Post が User を参照する」といったケースが発生します。 これを解決するために、entities 層内でのみ許可される特別なインポート方法が @x 記法です。 ディレクトリ構造の例 entities/ ├── user/ │ ├── @x/ │ │ └── post.ts # 外部スライス向けに公開する型・関数 │ ├── model/ │ │ └── types.ts # 内部で使用する型定義 │ ├── ui/ │ └── index.ts # 通常の公開API │ └── post/ ├── model/ │ └── usePost.ts # ここから user の型を参照したい └── index.ts 使用例 // entities/post/model/usePost.ts から entities/user を使用する場合 // ❌ 通常のインポート(Feature-Sliced Design違反:同一レイヤー間のインポート) import type { User } from "@/entities/user"; // ✅ @x を使ったクロスインポート(許可) import type { User } from "@/entities/user/@x/post"; // @x ディレクトリは「このスライスが外部に公開する、クロスインポート専用のAPI」を表します。 @x を使うことで、「意図的に外部公開している」ことが明示され、依存関係が追跡しやすくなります。 では、基礎となる知識が押さえられたところで、ここから本題に入っていきます。 Orval の使い方と出力コード セットアップ 現在開発中のプロジェクトでは pnpm をパッケージマネージャーとして使用しています。 また、OpenAPI は予め定義された前提で話を進めます。 # Orval のインストール pnpm add -D orval 次に、Orval の設定ファイルを作成します。 ※ hooks は、自動生成されたコードを Biome で format するために定義しています。 // orval.config.ts import { defineConfig } from "orval"; const API_DIR = "./src/shared/api"; const INPUT_DIR = "../../docs/api"; const GENERATED_DIR = `${API_DIR}/generated`; export default defineConfig({ postApi: { hooks: { afterAllFilesWrite: "pnpm format:write:generate", }, input: { target: `${INPUT_DIR}/openapi.yaml`, }, output: { clean: true, biome: true, client: "react-query", override: { mutator: { path: `${API_DIR}/customInstance.ts`, name: "useCustomInstance", }, query: { useSuspenseQuery: true, version: 5, }, }, schemas: `${GENERATED_DIR}/model`, target: `${GENERATED_DIR}/hooks/index.ts`, }, }, }); 次に、API リクエストを実行するカスタムインスタンスを作成します。これは Orval の設定で指定した mutator として使用されます。 // src/shared/api/customInstance.ts import { ApiHttpError, type ErrorDetail } from "../errors"; import { getAccessToken } from "../lib"; const BASE_URL = import.meta.env.VITE_API_BASE_URL || ""; // リクエスト設定の型定義 export type RequestConfig = { url: string; method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record<string, string>; params?: Record<string, unknown>; data?: unknown; signal?: AbortSignal; }; // Fetch API を使用したリクエスト関数 const fetchApi = async <T>(config: RequestConfig): Promise<T> => { const { url, method, headers = {}, params, data, signal } = config; // 認証トークンを取得 const token = getAccessToken(); // クエリパラメータの構築 const queryString = params ? `?${new URLSearchParams(params as Record<string, string>).toString()}` : ""; const fullUrl = `${BASE_URL}${url}${queryString}`; // ヘッダーの構築 const requestHeaders: Record<string, string> = { "Content-Type": "application/json", ...headers, }; if (token) { requestHeaders.Authorization = `Bearer ${token}`; } // リクエストオプションの構築 const options: RequestInit = { method, headers: requestHeaders, signal, }; if (data && ["POST", "PUT", "PATCH"].includes(method)) { options.body = JSON.stringify(data); } const response = await fetch(fullUrl, options); // エラーハンドリング if (!response.ok) { let errorMessage = `API error: ${response.status}`; let errorDetails: ErrorDetail[] = []; try { const errorData = await response.json(); errorDetails = errorData?.errors?.details ?? []; if (typeof errorData.message === "string") { errorMessage = errorData.message; } } catch { // JSONパースに失敗した場合はデフォルトメッセージを使用 } throw new ApiHttpError({ status: response.status, message: errorMessage, details: errorDetails, }); } // 204 No Content の場合 if (response.status === 204) { return null as T; } return response.json(); }; // Orval で使用するカスタムインスタンス関数 export const useCustomInstance = <T>(config: RequestConfig): Promise<T> => { const controller = new AbortController(); const promise = fetchApi<T>({ ...config, signal: controller.signal, }); // TanStack Query のキャンセル機能用 // @ts-expect-error cancel プロパティを動的に追加 promise.cancel = () => controller.abort(); return promise; }; export default useCustomInstance; この useCustomInstance は Orval が生成するフック内で HTTP リクエストを実行する際に使用されます。認証トークンの付与やエラーハンドリングなど、プロジェクト固有の設定をここに集約できます。 実際のプロジェクトでは、トークンリフレッシュ処理やリトライロジックなどを追加することが多いです。詳細は Orval 公式ドキュメント - Custom Client を参照してください。 あとは、コード生成を実行するだけです。 # コード生成実行 pnpm orval 現在開発中のプロジェクトでは、定期的に pnpm orval を実行し、API 仕様の変更をまとめて反映する運用をしています。 Orval が生成するコード実例 それでは、Orval が実際に何を生成するか、具体例で見ていきます。 生成物 1:型定義 OpenAPIの Post スキーマから、以下のような TypeScript 型が自動生成されます。 // src/shared/api/generated/types.ts // ↓ OpenAPIから自動生成される export type Post = { id: string; title: string; body?: string; createdAt: string; // ISO 8601形式 updatedAt: string; status: "draft" | "published" | "archived"; }; export type GetPostsResponse = { items: Post[]; total: number; page: number; }; export type CreatePostRequest = { title: string; body?: string; }; export type CreatePostResponse = Post & { createdBy: string; }; export type UpdatePostRequest = { title?: string; body?: string; status?: "draft" | "published" | "archived"; }; 重要なポイント OpenAPIのスキーマがそのまま型になる enum は TypeScript の Union Type に変換される 必須・オプション( ? )の区別も自動判定される 生成ファイルなので修正してはいけない(次の再実行で上書きされる) 生成物 2:TanStack Query カスタムフック Orval は TanStack Query のフックも自動生成します。以下は理解しやすいよう簡略化した例です(実際の生成コードはカスタムインスタンスや詳細な型定義を含みます)。 // src/shared/api/generated/hooks.ts // ↓ Orval が TanStack Query のフックを生成(簡略化した例) import { useSuspenseQuery, useMutation } from "@tanstack/react-query"; import type { UseSuspenseQueryOptions, UseMutationOptions, } from "@tanstack/react-query"; import type { Post, GetPostsResponse, CreatePostRequest, CreatePostResponse, UpdatePostRequest, } from "./model"; import { useCustomInstance } from "../customInstance"; type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1]; // GET リクエスト → useSuspenseQuery フック export const useGetPosts = < TData = Awaited<ReturnType<ReturnType<typeof useCustomInstance<GetPostsResponse>>>>, TError = Error, >( options?: { query?: Partial<UseSuspenseQueryOptions<GetPostsResponse, TError, TData>>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<GetPostsResponse>(); return useSuspenseQuery({ queryKey: ["posts"], queryFn: () => customInstance({ url: `/api/posts`, method: "GET" }), ...options?.query, }); }; // POST リクエスト → useMutation フック export const useCreatePost = <TError = Error, TContext = unknown>( options?: { mutation?: UseMutationOptions<CreatePostResponse, TError, CreatePostRequest, TContext>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<CreatePostResponse>(); return useMutation({ mutationFn: (data: CreatePostRequest) => customInstance({ url: `/api/posts`, method: "POST", data, }), ...options?.mutation, }); }; // PUT リクエスト export const useUpdatePost = <TError = Error, TContext = unknown>( postId: string, options?: { mutation?: UseMutationOptions<Post, TError, UpdatePostRequest, TContext>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<Post>(); return useMutation({ mutationFn: (data: UpdatePostRequest) => customInstance({ url: `/api/posts/${postId}`, method: "PUT", data, }), ...options?.mutation, }); }; // DELETE リクエスト export const useDeletePost = <TError = Error, TContext = unknown>( postId: string, options?: { mutation?: UseMutationOptions<void, TError, void, TContext>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<void>(); return useMutation({ mutationFn: () => customInstance({ url: `/api/posts/${postId}`, method: "DELETE", }), ...options?.mutation, }); }; このフックの便利さ TypeScript の型推論により、 data の型が自動的に GetPostsResponse に推論される エラーハンドリングも型安全(Error の型が決まっている) TanStack Query のキャッシング、再フェッチなどの機能もそのまま使える API URL の手入力が不要(URL の記述ミスを防げる) Orval 生成コードの活用ポイント 特徴 メリット OpenAPI の自動追跡 API 仕様変更 → 再実行 → 完全に同期 型とフックが連動 useGetPosts の戻り値の型も自動推論 TypeScript ジェネリクスを活用 エラーハンドリングも型安全 プラグイン拡張可能 カスタム生成ロジックを追加できる API のバージョン管理に強い 古いバージョンの API 仕様からの生成もサポート 生成コードは修正禁止 pnpm orval を実行すると型定義とカスタムフックが上書きされるため、 src/shared/api/generated/ 配下のファイルは 修正禁止 です。 // ❌ こうやって直接修正してはいけない // src/shared/api/generated/hooks.ts export const useGetPosts = () => { // ↓ このコードは Orval の再実行で上書きされる return useSuspenseQuery({ // ... }); }; カスタマイズは entities 層で行う カスタマイズが必要な場合は、entities 層でラップして独自のインターフェースを提供します。これにより、生成コードへの依存を一箇所に集約できます。 // src/entities/post/api/hooks.ts import { useGetPosts as useGetPostsGenerated } from "@/shared/api/generated"; /** * 利用側に使いやすいインターフェースを提供 * - Orval 生成コードの詳細を隠蔽 * - 戻り値を整理して返す */ export const usePosts = () => { const { data, isLoading, error } = useGetPostsGenerated(); return { posts: data?.items ?? [], isLoading, hasError: !!error, }; }; 詳細な実装パターンは次章で解説します。 実装パターンと構造設計 ここから、Orval が生成したコードを効果的に使うための 設計パターン を 3 つ紹介します。 パターン A:単純なラッピング シナリオ :投稿一覧を取得する API ステップ 1:Orval による生成コードを確認 前述の「生成物 2:TanStack Query カスタムフック」で示した useGetPosts がそのまま使用されます。 ステップ 2:entities 層でラッピング // src/entities/post/api/hooks.ts import { useGetPosts as useGetPostsGenerated } from "@/shared/api/generated"; /** * 投稿一覧を取得するカスタムフック * shared/api/generated への依存を entities層に隔離 */ export const usePosts = () => { const { data, isLoading, error } = useGetPostsGenerated(); return { posts: data?.items ?? [], isLoading, hasError: !!error, }; }; ステップ 3:公開 API // src/entities/post/api/index.ts export { usePosts } from "./hooks"; ステップ 4:features 層で使用 // src/features/PostManagement/ui/PostList.tsx import { usePosts } from "@/entities/post/api"; function PostList() { const { posts, isLoading } = usePosts(); if (isLoading) return <div>読み込み中...</div>; return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } このパターンのメリット Orval の生成コード変更が entities/post/api に限定される features/PostManagement はシンプルなインターフェースだけを知ればいい テストも entities/post/api をモック一箇所で OK Feature-Sliced Design の視点 entities/post/api が Orval(外部)と features(内部)の 境界を作る features は generated コードの詳細を知らない 修正範囲を entities に限定できる パターン B:複数 API の組み合わせ シナリオ :「投稿一覧 + 投稿の詳細」が必要な場合 複数の API 呼び出しを組み合わせる必要があります。これも entities 層で対応します。 ※ 以下の例では、投稿詳細を取得する useGetPostDetails フックが別途 Orval で生成されている想定です。 ステップ 1:entities 層で複数 API を組み合わせ // src/entities/post/api/hooks.ts import { useGetPosts as useGetPostsGenerated, useGetPostDetails as useGetPostDetailsGenerated, } from "@/shared/api/generated"; /** * 複数のAPI呼び出しを組み合わせる * 呼び出し側はこの複雑性を意識しない */ export const usePostWithDetails = (postId: string) => { const { data: posts, isLoading: postsLoading } = useGetPostsGenerated(); const { data: details, isLoading: detailsLoading } = useGetPostDetailsGenerated(postId); return { posts: posts?.items ?? [], details: details ?? null, isLoading: postsLoading || detailsLoading, // 便利な導出データも提供 hasDetails: !!details, }; }; ステップ 2:features 層から利用 利用側は複雑さを知らなくて OK です。 // src/features/PostManagement/ui/PostDetail.tsx import { usePostWithDetails } from "@/entities/post/api"; function PostDetail({ postId }: Props) { const { posts, details, isLoading, hasDetails } = usePostWithDetails(postId); // 複雑さは entities層に隠蔽! return <div>{hasDetails && <PostInfo details={details} />}</div>; } パターン C:エラーハンドリングの統一 シナリオ :エラーを共通のフォーマットで扱いたい場合 Orval 生成のエラー型を、独自のエラー型に変換します。 ステップ 1:entities 層でエラー型を定義・変換 // src/entities/post/api/hooks.ts export type ApiError = { message: string; code: "NETWORK_ERROR" | "NOT_FOUND" | "UNAUTHORIZED" | "SERVER_ERROR"; details?: unknown; }; export type UsePostsResult = { posts: Post[]; isLoading: boolean; error: ApiError | null; retry: () => void; }; export const usePosts = (): UsePostsResult => { const { data, isLoading, error, refetch } = useGetPostsGenerated(); // Orval生成のエラー型を独自のエラー型に変換 const mappedError: ApiError | null = error ? { message: error.message || "エラーが発生しました", code: mapErrorCode(error), details: error, } : null; return { posts: data?.items ?? [], isLoading, error: mappedError, retry: () => refetch(), }; }; // ヘルパー関数 // TanStack Query の error は Error 型として扱われる // カスタムインスタンス側でステータスコードを含めた Error を throw する想定 type ApiErrorWithStatus = Error & { status?: number }; function mapErrorCode(error: unknown): ApiError["code"] { if (!navigator.onLine) return "NETWORK_ERROR"; const apiError = error as ApiErrorWithStatus; if (apiError.status === 404) return "NOT_FOUND"; if (apiError.status === 401) return "UNAUTHORIZED"; return "SERVER_ERROR"; } ステップ 2:features 層で統一的にエラー処理 利用側ではエラーハンドリングが統一されます。 // src/features/PostManagement/ui/PostList.tsx import { usePosts } from "@/entities/post/api"; function PostList() { const { posts, isLoading, error, retry } = usePosts(); if (error) { return ( <div> <p>エラー: {error.message}</p> <button onClick={retry}>再試行</button> </div> ); } // ... 以下、通常の処理 } Orval + Feature-Sliced Design のアーキテクチャ図 ここまでのパターンを図にまとめました。依存の方向が統一されているため、変更の影響範囲が明確になります。 shared/api/generated/ ← Orvalの生成物(修正禁止) ├─ useGetPosts ├─ useCreatePost ├─ useGetPostDetails └─ types.ts ↓ [境界線] ↓ entities/post/api/ ← Orvalの生成物をラッピングする層(修正可能) ├─ usePosts(カスタマイズ版) ├─ usePostWithDetails(複数API組み合わせ) ├─ ApiError型 └─ index.ts(公開API) ↓ features/ ← 機能層 ├─ PostManagement/ │ ├─ ui/PostList.tsx │ ├─ ui/PostDetail.tsx │ ├─ lib/... │ └─ index.ts ... ↓ pages/ ← ページ層 └─ PostPage/ ↓ app/ ← アプリケーション層 ├─ routes/ └─ ... 導入してみた感想 ✅ メリット 型安全性が圧倒的に向上 :手入力で型を定義する開発には戻れません。 API 変更への耐性が高い :一箇所修正(entities 層)で全てが完了します。 ドキュメント = コード :OpenAPI とコードを常に同期できます。 チーム全体の効率が向上 :API 設計 → 実装 → テストの流れがスムーズです。 バグが減る :型不整合によるバグがほぼなくなります。 ⚠️ 注意点 チーム全体での学習コストがかかる :Feature-Sliced Design は習得に時間がかかるアーキテクチャであり、チーム全員の理解が必要です。 OpenAPI 確定までの待ち時間が発生 :API 仕様変更を伴う UI 実装では、OpenAPI の更新完了を待つ必要があります。対策として MSW などのモック API を活用することで、フロントエンド開発を並行して進められます。 Orval のバージョンアップ時の互換性確認の必要性 :Orval のメジャーバージョンアップ時には生成コードの形式が変わる可能性があるため、アップグレード前にリリースノートの確認が必要です。 まとめ Orval を利用したスキーマ駆動開発は、フロントエンド開発における 「API 変更への耐性」 と 「型安全性」 を大幅に向上させます。 現在開発中のプロジェクトでは、立ち上げ当初から Orval を導入しており、バックエンドとフロントエンドのエンジニア間のコミュニケーションコストが軽減され、無駄な実装コストもほとんどなくなりました。 また、Feature-Sliced Design の導入により、チーム全体で理解しコードに落とし込むまでに時間がかかったものの、明確なルールのおかげでコードの可読性と保守性が向上しました。 以下のような課題を感じている方は、ぜひ Orval × Feature-Sliced Design の組み合わせを試してみてください。 API の型やカスタムフックを手書きしていて、コストがかかっている スキーマ駆動開発は導入済みだが、ディレクトリ構造にルールがない 自動生成ファイルを様々な場所から import している状態 最後までお読みいただきありがとうございました。 参考文献 Orval 公式ドキュメント OpenAPI Specification v3.1.0 TanStack Query Feature-Sliced Design 公式ドキュメント
アバター
This article is the Day 2 entry of the KINTO Technologies Advent Calendar 2025 . Introduction Hello! I'm high-g ( @high_g_engineer ) from the Master Maintenance Tool Development Team in the KINTO Backend Development Group, KINTO Development Division at Osaka Tech Lab. In modern frontend development with heavy API integration, have you ever experienced challenges like these? Manually writing API type definitions often leads to missed updates when the spec changes Auto-generated files scattered everywhere often make it unclear where to import from Team members interpreting directory structures differently often lead to debates during code reviews The keywords to solve these challenges are type safety , schema-driven , auto-generation , and directory design . This article introduces an approach where OpenAPI serves as the single source of truth for auto-generating type-safe code, managed according to Feature-Sliced Design rules. Specifically, we'll walk through what code Orval generates and explain effective design patterns aligned with Feature-Sliced Design's directory structure. What This Article Covers The flow of outputting types and custom hooks from OpenAPI using Orval Detailed examples of the code Orval generates Feature-Sliced Design's layer structure and import rules Design patterns for managing Orval-generated code within Feature-Sliced Design's directory structure Target Audience Frontend developers tired of manually managing REST APIs and type definitions Developers using TypeScript + React Those interested in designs resilient to API changes Those interested in establishing directory structure rules Foundational Knowledge OpenAPI OpenAPI is a standard for defining HTTP APIs in a machine-readable format. By describing API specifications in YAML or JSON, you gain benefits like: Clearly defined API inputs and outputs Automated documentation generation Prevention of discrepancies between client and server Example: Partial OpenAPI Definition (Simplified) openapi: 3.1.0 paths: /posts: get: summary: Get list of posts parameters: - name: page in: query schema: type: integer responses: "200": description: Success content: application/json: schema: $ref: "#/components/schemas/GetPostsResponse" post: summary: Create a post requestBody: content: application/json: schema: $ref: "#/components/schemas/CreatePostRequest" responses: "201": description: Created content: application/json: schema: $ref: "#/components/schemas/CreatePostResponse" /posts/{postId}: put: summary: Update a post parameters: - name: postId in: path required: true schema: type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/UpdatePostRequest" responses: "200": description: Success content: application/json: schema: $ref: "#/components/schemas/Post" delete: summary: Delete a post parameters: - name: postId in: path required: true schema: type: string responses: "204": description: No Content components: schemas: Post: type: object required: [id, title, createdAt, updatedAt, status] properties: id: type: string title: type: string body: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time status: type: string enum: [draft, published, archived] GetPostsResponse: type: object properties: items: type: array items: $ref: "#/components/schemas/Post" total: type: integer page: type: integer CreatePostRequest: type: object required: [title] properties: title: type: string body: type: string CreatePostResponse: allOf: - $ref: "#/components/schemas/Post" - type: object properties: createdBy: type: string UpdatePostRequest: type: object properties: title: type: string body: type: string status: type: string enum: [draft, published, archived] This YAML defines the following: /posts endpoint: list retrieval (GET) and creation (POST) /posts/{postId} endpoint: update (PUT) and deletion (DELETE) About Schema-Driven Development Problems with Traditional Manual Management Previously, frontend developers performed tasks like these manually: // Manually writing type definitions type Post = { id: string; title: string; body?: string; createdAt: string; updatedAt: string; status: "draft" | "published" | "archived"; }; // Manually writing API calls const getPost = async (id: string): Promise<Post> => { const response = await fetch(`/api/posts/${id}`); return response.json(); }; This approach has the following problems: Cost of manual updates Check OpenAPI → manually update type definitions → update all usage sites Risk of missed updates Type definitions and actual API specs get out of sync Easy to miss updates when the same type is used in multiple places Documentation and code desynchronization OpenAPI ≠ implementation code can happen The Schema-Driven Development Approach To address these manual management problems, a development methodology emerged: define the schema (API specification) first, then proceed with implementation. Traditional: Implementation → Documentation (afterthought) → Discrepancies with spec Schema-driven: Schema definition → Auto-generation → Implementation → Done (Implementation = Documentation, always in sync) Characteristics of Schema-Driven Development Implementation = Documentation : API specs and code are always synchronized Type safety : API inconsistencies detected at compile time Development efficiency : No manual type definition work Team collaboration : Both frontend and backend reference the same OpenAPI Orval Orval is a tool that auto-generates TypeScript type definitions and custom hooks with a single command from OpenAPI specifications. Main Features of Orval Feature Description Auto-generated type definitions Automatically creates types for API requests and responses Auto-generated custom hooks Also auto-generates hooks for TanStack Query and others Multiple library support Supports not just TanStack Query but also Axios and other libraries Mock generation Can also generate mock data for testing Benefits of Using Orval Time savings : Zero time spent hand-writing type definitions or API call code Error prevention : Eliminates typos and spec misreadings from manual writing Always current : Just regenerate when OpenAPI is updated to stay current Orval's Role in Schema-Driven Development Summarizing the content so far, Orval's role in schema-driven development is as follows: OpenAPI (single source of truth) ↓ Auto-generation by Orval keeps type definitions + TanStack Query hooks always in sync ↓ Low-cost, type-safe development is possible Feature-Sliced Design As mentioned at the beginning, the ongoing project adopts Feature-Sliced Design as an architectural pattern for frontend code organization. Feature-Sliced Design is an architecture that organizes the codebase using three concepts: Layers , Slices , and Segments . Concept Description Examples Layer Division by application responsibility. From top: app → pages → features → entities → shared (5 layers). app handles routing and layouts for the entire app, pages handles screens corresponding to URLs app/ , pages/ , features/ Slice Division unit by business domain or feature within each layer features/auth/ , entities/user/ Segment Division by technical role within a slice ui/ , model/ , api/ src/ ├── features/ ← Layer │ ├── auth/ ← Slice │ │ ├── ui/ ← Segment │ │ ├── model/ ← Segment │ │ └── index.ts This structure clarifies where to put what, enabling the team to unify code placement rules. Feature-Sliced Design Directory Structure Our team operates with the following directory structure. The segment divisions ( api/ , model/ , ui/ , etc.) are customized to fit the project. workspaces/typescript/src/ ├── app/ ← ① Application layer: routing, global settings │ ├── layouts/ Layouts used across all pages │ ├── routes/ Routing definitions │ └── App.tsx Root tsx file │ ├── pages/ ← ② Pages layer: each page component (corresponds to URL) │ ├── users/ │ └── login/ │ ├── features/ ← ③ Features layer: reusable business logic │ ├── {slice}/ Divided by domain into units called slices (e.g., user, auth) │ │ ├── {component}/ Components belonging to the domain │ │ │ ├── model/ Logic portion │ │ │ ├── ui/ UI portion │ │ │ └── index.ts Public API (barrel file) │ │ ... │ ... │ ├── entities/ ← ④ Entities layer: business domain definitions │ ├── user/ Various domains │ │ ├── @x Cross-import notation *described later │ │ ├── api/ Imports and uses auto-generated files from shared/ (facade) │ │ │ ├── hooks.ts API hooks │ │ │ └── index.ts Public API (barrel file) │ │ ├── model/ Domain logic │ │ ├── ui/ Minimal UI staying within the domain │ │ └── index.ts Public API (barrel file) │ ... │ └── shared/ ← ⑤ Shared layer: project-independent utilities ├── api/ │ └── generated/ Auto-generated files by Orval (modification prohibited) │ ├── types.ts │ ├── hooks.ts │ └── client.ts ├── config/ Configuration constants ├── errors/ Commonly used error functions ├── lib/ Utility functions └── ui/ Generic UI components Feature-Sliced Design Layer Import Restriction Rules The most important rule of Feature-Sliced Design: A layer can only import from layers below itself. Additionally, mutual imports between the same layer are also prohibited in principle (exception described later with @x notation). app ← Top level (highest abstraction) ↓ import allowed pages ↓ features ↓ entities * shared can be imported from any layer This means the following rules are established within the project: ✅ pages/ can import from features/, entities/, shared/ ✅ features/ can import from entities/, shared/ ✅ entities/ can import from shared/ ❌ entities/ must not import from features/ or pages/ ❌ shared/ must not import from any other layer Special Role of the Entities Layer: entities/@x (Cross-Import Notation) However, in the entities layer, business domains often relate to each other. For example, cases like "Post references User" occur. To solve this, a special import method allowed only within the entities layer is the @x notation. Directory Structure Example entities/ ├── user/ │ ├── @x/ │ │ └── post.ts # Types/functions exposed for external slices │ ├── model/ │ │ └── types.ts # Type definitions used internally │ ├── ui/ │ └── index.ts # Normal public API │ └── post/ ├── model/ │ └── usePost.ts # Wants to reference user's types from here └── index.ts Usage Example // When using entities/user from entities/post/model/usePost.ts // ❌ Normal import (Feature-Sliced Design violation: import between same layer) import type { User } from "@/entities/user"; // ✅ Cross-import using @x (allowed) import type { User } from "@/entities/user/@x/post"; // The @x directory represents "cross-import-specific API that this slice exposes externally." By using @x , it becomes explicit that something is intentionally exposed externally, making dependency tracking easier. Now that we've covered the foundational knowledge, let's get into the main topic. How to Use Orval and Output Code Setup The ongoing project uses pnpm as the package manager. Also, we'll proceed assuming OpenAPI is already defined. # Install Orval pnpm add -D orval Next, create the Orval configuration file. Note: hooks are defined to format auto-generated code with Biome. // orval.config.ts import { defineConfig } from "orval"; const API_DIR = "./src/shared/api"; const INPUT_DIR = "../../docs/api"; const GENERATED_DIR = `${API_DIR}/generated`; export default defineConfig({ postApi: { hooks: { afterAllFilesWrite: "pnpm format:write:generate", }, input: { target: `${INPUT_DIR}/openapi.yaml`, }, output: { clean: true, biome: true, client: "react-query", override: { mutator: { path: `${API_DIR}/customInstance.ts`, name: "useCustomInstance", }, query: { useSuspenseQuery: true, version: 5, }, }, schemas: `${GENERATED_DIR}/model`, target: `${GENERATED_DIR}/hooks/index.ts`, }, }, }); Next, create a custom instance that executes API requests. This is used as the mutator specified in the Orval configuration. // src/shared/api/customInstance.ts import { ApiHttpError, type ErrorDetail } from "../errors"; import { getAccessToken } from "../lib"; const BASE_URL = import.meta.env.VITE_API_BASE_URL || ""; // Type definition for request configuration export type RequestConfig = { url: string; method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; headers?: Record<string, string>; params?: Record<string, unknown>; data?: unknown; signal?: AbortSignal; }; // Request function using Fetch API const fetchApi = async <T>(config: RequestConfig): Promise<T> => { const { url, method, headers = {}, params, data, signal } = config; // Get authentication token const token = getAccessToken(); // Build query parameters const queryString = params ? `?${new URLSearchParams(params as Record<string, string>).toString()}` : ""; const fullUrl = `${BASE_URL}${url}${queryString}`; // Build headers const requestHeaders: Record<string, string> = { "Content-Type": "application/json", ...headers, }; if (token) { requestHeaders.Authorization = `Bearer ${token}`; } // Build request options const options: RequestInit = { method, headers: requestHeaders, signal, }; if (data && ["POST", "PUT", "PATCH"].includes(method)) { options.body = JSON.stringify(data); } const response = await fetch(fullUrl, options); // Error handling if (!response.ok) { let errorMessage = `API error: ${response.status}`; let errorDetails: ErrorDetail[] = []; try { const errorData = await response.json(); errorDetails = errorData?.errors?.details ?? []; if (typeof errorData.message === "string") { errorMessage = errorData.message; } } catch { // Use default message if JSON parsing fails } throw new ApiHttpError({ status: response.status, message: errorMessage, details: errorDetails, }); } // For 204 No Content if (response.status === 204) { return null as T; } return response.json(); }; // Custom instance function used by Orval export const useCustomInstance = <T>(config: RequestConfig): Promise<T> => { const controller = new AbortController(); const promise = fetchApi<T>({ ...config, signal: controller.signal, }); // For TanStack Query's cancel functionality // @ts-expect-error dynamically adding cancel property promise.cancel = () => controller.abort(); return promise; }; export default useCustomInstance; This useCustomInstance is used when executing HTTP requests within the hooks that Orval generates. You can centralize project-specific settings here, such as attaching authentication tokens and error handling. In actual projects, token refresh processing and retry logic are often added. For details, see the Orval Official Documentation - Custom Client . All that’s left is to run the code generation. # Run code generation pnpm orval In the ongoing project, we periodically run pnpm orval to batch-apply API spec changes. Actual Examples of Orval-Generated Code Now let's look at specific examples of what Orval actually generates. Generated Output 1: Type Definitions From OpenAPI's Post schema, TypeScript types like the following are auto-generated. // src/shared/api/generated/types.ts // ↓ Auto-generated from OpenAPI export type Post = { id: string; title: string; body?: string; createdAt: string; // ISO 8601 format updatedAt: string; status: "draft" | "published" | "archived"; }; export type GetPostsResponse = { items: Post[]; total: number; page: number; }; export type CreatePostRequest = { title: string; body?: string; }; export type CreatePostResponse = Post & { createdBy: string; }; export type UpdatePostRequest = { title?: string; body?: string; status?: "draft" | "published" | "archived"; }; Important Points OpenAPI schemas become types directly enum is converted to TypeScript Union Types Required/optional ( ? ) distinction is automatically determined Since it's a generated file, do not modify it (will be overwritten on next run) Generated Output 2: TanStack Query Custom Hooks Orval also auto-generates TanStack Query hooks. The following is a simplified example for easier understanding (actual generated code includes custom instances and detailed type definitions). // src/shared/api/generated/hooks.ts // ↓ Orval generates TanStack Query hooks (simplified example) import { useSuspenseQuery, useMutation } from "@tanstack/react-query"; import type { UseSuspenseQueryOptions, UseMutationOptions, } from "@tanstack/react-query"; import type { Post, GetPostsResponse, CreatePostRequest, CreatePostResponse, UpdatePostRequest, } from "./model"; import { useCustomInstance } from "../customInstance"; type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1]; // GET request → useSuspenseQuery hook export const useGetPosts = < TData = Awaited<ReturnType<ReturnType<typeof useCustomInstance<GetPostsResponse>>>>, TError = Error, >( options?: { query?: Partial<UseSuspenseQueryOptions<GetPostsResponse, TError, TData>>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<GetPostsResponse>(); return useSuspenseQuery({ queryKey: ["posts"], queryFn: () => customInstance({ url: `/api/posts`, method: "GET" }), ...options?.query, }); }; // POST request → useMutation hook export const useCreatePost = <TError = Error, TContext = unknown>( options?: { mutation?: UseMutationOptions<CreatePostResponse, TError, CreatePostRequest, TContext>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<CreatePostResponse>(); return useMutation({ mutationFn: (data: CreatePostRequest) => customInstance({ url: `/api/posts`, method: "POST", data, }), ...options?.mutation, }); }; // PUT request export const useUpdatePost = <TError = Error, TContext = unknown>( postId: string, options?: { mutation?: UseMutationOptions<Post, TError, UpdatePostRequest, TContext>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<Post>(); return useMutation({ mutationFn: (data: UpdatePostRequest) => customInstance({ url: `/api/posts/${postId}`, method: "PUT", data, }), ...options?.mutation, }); }; // DELETE request export const useDeletePost = <TError = Error, TContext = unknown>( postId: string, options?: { mutation?: UseMutationOptions<void, TError, void, TContext>; request?: SecondParameter<ReturnType<typeof useCustomInstance>>; } ) => { const customInstance = useCustomInstance<void>(); return useMutation({ mutationFn: () => customInstance({ url: `/api/posts/${postId}`, method: "DELETE", }), ...options?.mutation, }); }; Convenience of These Hooks TypeScript type inference automatically infers data type as GetPostsResponse Error handling is also type-safe (Error type is determined) TanStack Query features like caching and refetching work as-is No manual API URL entry needed (prevents URL typos) Key Points for Using Orval-Generated Code Feature Benefit Automatic OpenAPI tracking API spec change → re-run → fully synchronized Types and hooks are linked Return type of useGetPosts is also auto-inferred Utilizes TypeScript generics Error handling is also type-safe Plugin extensible Can add custom generation logic Strong for API versioning Supports generation from older API spec versions Generated Code Must Not Be Modified Running pnpm orval overwrites type definitions and custom hooks, so files under src/shared/api/generated/ are modification prohibited . // ❌ Do not modify directly like this // src/shared/api/generated/hooks.ts export const useGetPosts = () => { // ↓ This code will be overwritten on Orval re-run return useSuspenseQuery({ // ... }); }; Customization Is Done in the Entities Layer When customization is needed, wrap in the entities layer to provide your own interface. This centralizes dependencies on generated code in one place. // src/entities/post/api/hooks.ts import { useGetPosts as useGetPostsGenerated } from "@/shared/api/generated"; /** * Provides a user-friendly interface * - Hides details of Orval-generated code * - Returns organized return values */ export const usePosts = () => { const { data, isLoading, error } = useGetPostsGenerated(); return { posts: data?.items ?? [], isLoading, hasError: !!error, }; }; Detailed implementation patterns are explained in the next chapter. Implementation Patterns and Structural Design From here, we'll introduce 3 design patterns for effectively using Orval-generated code. Pattern A: Simple Wrapping Scenario : API to get a list of posts Step 1: Check Orval-Generated Code The useGetPosts shown in "Generated Output 2: TanStack Query Custom Hooks" above is used as-is. Step 2: Wrap in Entities Layer // src/entities/post/api/hooks.ts import { useGetPosts as useGetPostsGenerated } from "@/shared/api/generated"; /** * Custom hook to get list of posts * Isolates dependency on shared/api/generated to entities layer */ export const usePosts = () => { const { data, isLoading, error } = useGetPostsGenerated(); return { posts: data?.items ?? [], isLoading, hasError: !!error, }; }; Step 3: Public API // src/entities/post/api/index.ts export { usePosts } from "./hooks"; Step 4: Use in Features Layer // src/features/PostManagement/ui/PostList.tsx import { usePosts } from "@/entities/post/api"; function PostList() { const { posts, isLoading } = usePosts(); if (isLoading) return <div>Loading...</div>; return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } Benefits of This Pattern Orval-generated code changes are limited to entities/post/api features/PostManagement only needs to know the simple interface Testing also works with mocking just entities/post/api From a Feature-Sliced Design Perspective entities/post/api creates a boundary between Orval (external) and features (internal) Features don't know the details of generated code Modification scope can be limited to entities Pattern B: Combining Multiple APIs Scenario : When both "list of posts + post details" are needed Multiple API calls need to be combined. This is also handled in the entities layer. Note: The following example assumes a useGetPostDetails hook is separately generated by Orval. Step 1: Combine Multiple APIs in Entities Layer // src/entities/post/api/hooks.ts import { useGetPosts as useGetPostsGenerated, useGetPostDetails as useGetPostDetailsGenerated, } from "@/shared/api/generated"; /** * Combines multiple API calls * Callers don't need to be aware of this complexity */ export const usePostWithDetails = (postId: string) => { const { data: posts, isLoading: postsLoading } = useGetPostsGenerated(); const { data: details, isLoading: detailsLoading } = useGetPostDetailsGenerated(postId); return { posts: posts?.items ?? [], details: details ?? null, isLoading: postsLoading || detailsLoading, // Also provide convenient derived data hasDetails: !!details, }; }; Step 2: Use from Features Layer Callers don't need to know the complexity. // src/features/PostManagement/ui/PostDetail.tsx import { usePostWithDetails } from "@/entities/post/api"; function PostDetail({ postId }: Props) { const { posts, details, isLoading, hasDetails } = usePostWithDetails(postId); // Hide Complexity in entities layer! return <div>{hasDetails && <PostInfo details={details} />}</div>; } Pattern C: Unified Error Handling Scenario : When you want to handle errors in a common format Convert Orval-generated error types to custom error types. Step 1: Define and Convert Error Types in Entities Layer // src/entities/post/api/hooks.ts export type ApiError = { message: string; code: "NETWORK_ERROR" | "NOT_FOUND" | "UNAUTHORIZED" | "SERVER_ERROR"; details?: unknown; }; export type UsePostsResult = { posts: Post[]; isLoading: boolean; error: ApiError | null; retry: () => void; }; export const usePosts = (): UsePostsResult => { const { data, isLoading, error, refetch } = useGetPostsGenerated(); // Convert Orval-generated error type to custom error type const mappedError: ApiError | null = error ? { message: error.message || "An error occurred", code: mapErrorCode(error), details: error, } : null; return { posts: data?.items ?? [], isLoading, error: mappedError, retry: () => refetch(), }; }; // Helper function // TanStack Query's error is treated as Error type // Assumes custom instance throws Error with status code type ApiErrorWithStatus = Error & { status?: number }; function mapErrorCode(error: unknown): ApiError["code"] { if (!navigator.onLine) return "NETWORK_ERROR"; const apiError = error as ApiErrorWithStatus; if (apiError.status === 404) return "NOT_FOUND"; if (apiError.status === 401) return "UNAUTHORIZED"; return "SERVER_ERROR"; } Step 2: Unified Error Processing in Features Layer Error handling becomes unified on the caller side. // src/features/PostManagement/ui/PostList.tsx import { usePosts } from "@/entities/post/api"; function PostList() { const { posts, isLoading, error, retry } = usePosts(); if (error) { return ( <div> <p>Error: {error.message}</p> <button onClick={retry}>Retry</button> </div> ); } // ... normal processing below } Architecture Diagram: Orval + Feature-Sliced Design Here's a diagram summarizing the patterns so far. Since dependency directions are unified, the scope of change impact becomes clear. shared/api/generated/ ← Orval output (modification prohibited) ├─ useGetPosts ├─ useCreatePost ├─ useGetPostDetails └─ types.ts ↓ [Boundary] ↓ entities/post/api/ ← Layer wrapping Orval output (modifiable) ├─ usePosts (customized version) ├─ usePostWithDetails (multiple API combination) ├─ ApiError type └─ index.ts (public API) ↓ features/ ← Features layer ├─ PostManagement/ │ ├─ ui/PostList.tsx │ ├─ ui/PostDetail.tsx │ ├─ lib/... │ └─ index.ts ... ↓ pages/ ← Pages layer └─ PostPage/ ↓ app/ ← Application layer ├─ routes/ └─ ... Impressions After Adoption ✅ Benefits Dramatically improved type safety : Cannot go back to development with manually typed definitions. High resilience to API changes : Modifications complete in one place (entities layer). Documentation = Code : OpenAPI and code can always stay synchronized. Improved team-wide efficiency : Smooth flow from API design → implementation → testing. Fewer bugs : Bugs from type mismatches have nearly disappeared. ⚠️ Important Notes Learning cost for the entire team : Feature-Sliced Design is an architecture that takes time to master, requiring understanding from all team members. Wait time until OpenAPI is finalized : For UI implementation involving API spec changes, you need to wait for OpenAPI updates to complete. As a countermeasure, using mock APIs like MSW allows frontend development to proceed in parallel. Need for compatibility checks during Orval version upgrades : During Orval major version upgrades, generated code format may change, so checking release notes before upgrading is necessary. Summary Schema-driven development using Orval significantly improves resilience to API changes and type safety in frontend development. In the ongoing project, Orval was introduced from the start, reducing communication costs between backend and frontend engineers and nearly eliminating wasteful implementation costs. Additionally, while adopting Feature-Sliced Design took time for the entire team to understand and implement in code, the clear rules improved code readability and maintainability. If you're experiencing challenges like the following, please try the Orval × Feature-Sliced Design combination: Manually writing API types and custom hooks, incurring costs Schema-driven development is already adopted, but there are no directory structure rules Auto-generated files are imported from various places Thank you for reading to the end. References Orval Official Documentation OpenAPI Specification v3.1.0 TanStack Query Feature-Sliced Design Official Documentation
アバター
こんにちは、Engineering Officeの守谷(emim)です。 この記事では、 KINTOテクノロジーズ Advent Calendar 2025 と アクセシビリティ Advent Calendar 2025 の12月2日のクロスポストとして表題の件をレポートしていきます。 普段わたしは社内で、組織の開発力を上げることをミッションにした部署(Engineering Office)でデザイン周りのことを考える傍ら、個人活動でライフワークとしているアクセシビリティの啓発活動を行っています。 そこで、想定外(?)に自発的に素敵なアクセシビリティ活動を行ってくれたスタッフがいたので、その内容と心意気について伺いました。 X(旧Twitter)ポストに誰が代替テキストを!? まずは、以下のポストをぜひご覧ください。 https://x.com/KintoTech_Dev/status/1976163362016043509?s=20 ただの弊社福岡オフィスのご紹介に見えますか? よくご覧ください、添付されている写真に、なんとも素敵な代替テキスト(alt)が付与されています。 ![KINTOテクノロジーズの10月9日のX投稿に4枚の写真が添付されていて、すべてに「ALT」ボタンが表示されている様子のキャプチャー](/assets/blog/authors/emim/2025-12-02-SNSA11y/2025-12-02-SNSA11y_post1.png =600x) ![ビルの写真に「KINTOテクノロジーズの福岡オフィスが入居する福岡大名ガーデンシティのビル外観」と代替テキストが付いているのをポップアップしたところ](/assets/blog/authors/emim/2025-12-02-SNSA11y/2025-12-02-SNSA11y_post2.png =500x) 社内のチャットに「team-accessibility」というものがあり、普段から情報共有などを行っています。しかしこのポストを行ってくれたのは、そこで前のめりで発言している方でもなかったので、わたしは仰天しました。 そんな方でも適切な代替テキストを付けてくれるとは!!! 今回お話しを伺ったのは、以下の2人です。 竹中さん 経歴:SIer企業で開発やPMを12年経験した後、銀行の内製開発でスクラムマスターを担当 現在は技術広報のマネージャーを務める ポストをされた張本人 ゆかちさん 経歴:1社目は旅行業界、コロナ禍の打撃を受けIT業界へ KINTOテクノロジーズではもともと経理事務を担当〜時を経て技術広報専任となり、イベント運営やSNSの発信などを担当している アクセシビリティについて今とても関心を高めている人 (身内ですが敬称付きで統一して掲載しています。) このポストの代替テキストについて この4枚の投稿に付けられた代替テキスト。とあるスクリーンリーダーユーザーは「クオリティが高くて素敵ですね!とても読みやすい(聞きやすい)です」「抽象的な表現なのですが、そよ風が吹くような代替テキスト」と評してくれました。 そこで、どうして今回代替テキストを付けようと思ったか、竹中さんに聞いてみると、以下のような意見をいただきました。 ―― (竹中さん)アクセシビリティは聞いたことがあったけれど、内容についてはあまり理解していませんでした。今回は、X投稿時にたまたま「ALT」と書かれたボタンが目立っていたことと、「team-accessibility」のチャットで日々アクセシビリティの情報に触れており、世間でも注目が高まっていると聞いていたので、設定しようと思いました。 普段から誰に届くともわからず伝えていたことが、こんな所でサブリミナル効果を発揮するとは! 記述方法など困らなかったか?も聞いてみました。 ―― (竹中さん)記載の必要があることはわかったけれど具体がわからなかったため、直前にネット検索を行い「簡潔に、本文に書いていない内容を入れない」ことを意識しました。投稿されるまでどんな感じで入るのかわからなかった為、投稿されたものを確認して「こうなるのか」と初めてわかりました。 改めて、普段何気なく利用していると気付かない機能だということがわかりました。アクセシビリティに慣れている人だと、「ALT」と書かれたボタンを目視できたりもしますが、そこも気付かない人も居るということを意識する必要がありそうです。 「代替テキスト」を通しての気付き 「アクセシビリティを普段意識してない」人が「代替テキストをつける」世界線、とても素敵ではないですか?個人的にびっくり(ほっこり)したので「誰が付けたか社内で探し出したよ」という旨をポストしたら、結構な「いいね」をいただきました。 https://x.com/emim/status/1977921547869561149?s=20 これらの反響があったことを、更に改めて共有をしたら感動してくれたのが、ゆかちさんです。ゆかちさんも普段は(エンジニアではないこともあり)アクセシビリティのチャットには出てこない方です。 2人にあらためて、この反響について尋ねてみました。 ―― (竹中さん)話題になるんだ?とびっくりしました。代替テキストだけで反応があることに、なるほどと思いました。 ―― (ゆかちさん)シンプルに「代替テキストを付けられる」ことを把握していなかったし、なおかつこれをつけることで助かる人がいるんだな、ということを初めて知りました。アクセシビリティという概念全体が今まで気にしていなかったジャンルだし、自分でもできることがあるのかなと気になりだしました。 この言葉を聞いて、ちょっとしたことでもフィードバックとして伝えると「人の行動に影響を与える」ということが明らかになったように思う回答を得られました。また、さらにこのような意見も加えてくれました。 ―― (ゆかちさん)大事なことだとは思うけれど、何をしていいのかがわからないため、初心者でもわかる学習機会がもっとあるといいですね。 昨今、アクセシビリティ界隈では過去に比べ、確かに勉強会は増えてきています。それでも前提を飛ばした上級者向けになってきているのも業界課題です。 一方で、デジタル庁が10月にまとめて公開してくれた「 デジタル社会推進標準ガイドライン 」に、広報担当者(アクセシビリティ初心者)向けにまとめた「DS-672.1 ウェブアクセシビリティ広報向けガイドブック」などがあります。 https://www.digital.go.jp/resources/standard_guidelines こういった公開資料を利用して、技術広報メンバー向けの勉強会などを企画しようと考えています。きちんとデジタル庁の担当の方にも確認をしたら「いくらでも使ってください」との回答をいただきました。余裕があったら、外部の方も招待する形での勉強会などもやってみたいと考えていますので、来年の我々に乞うご期待を!
アバター