TECH PLAY

KINTOテクノロジーズ

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

975

参照:「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の実装において高い正確性を実現できます。 本記事を通じて、自動推論の理解が少しでも深めていただけたら嬉しいです。
はじめに こんにちは、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テクノロジーズでは、まだまださまざまな部署・職種で一緒に働ける仲間を募集しています! 詳しくは こちら からご確認ください!
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 こういった公開資料を利用して、技術広報メンバー向けの勉強会などを企画しようと考えています。きちんとデジタル庁の担当の方にも確認をしたら「いくらでも使ってください」との回答をいただきました。余裕があったら、外部の方も招待する形での勉強会などもやってみたいと考えていますので、来年の我々に乞うご期待を!
This article is the Day 1 entry of the KINTO Technologies Advent Calendar 2025:santa::christmas_tree: I'm okapi from the Mobile team in the QA Group. When creating images with AI, have you ever experienced telling the AI exactly what you want through prompts, only to have your intent misunderstood, forcing you to redo it multiple times? This time, I've created a prompt that can generate tech blog cover images in a single generation! This approach can be applied to other image creation tasks as well. Please give it a try! How to Use AI Image‑generation to Use Microsoft Copilot *Basically, any generative AI tool that supports image generation will work (e.g., ChatGPT, DALL-E, Midjourney, etc.) Prompt Modification Points There are only 4 points to modify in the prompt! Modification Points Content 1. Display Title Setting Paste your tech blog title into Display Title 2. Style Selection Choose 1 from ■Style and delete the other 2 3. Overview Setting Paste your tech blog content into ■Tech Blog Content 4. Previous Cover Image Attachment Attach your previously used cover image to the prompt Actual Prompt Used Please create a cover image for a tech blog. Please use the same format as the image used in the previous tech blog I'm attaching. ■Purpose I want to create a cover image for the following blog article. ■Display Title (Paste article title here) ■Notes ・Please ensure there are no unnatural Japanese expressions. ・Please keep the image style and layout the same as before. ・The title should be placed in the center or a prominent position ■Style (Choose one from the following 3) ・Tech feel (technical atmosphere) ・AI and machine learning style (collaboration with generative AI) ・Motion graphics style (even as a still image) ■Tech Blog Content (Paste article overview here) Previous Cover Image Attached Images Actually Created for This Article For this article, I created images in the 3 patterns mentioned above. Style Generated Image Tech feel (technical atmosphere) AI and machine learning style (collaboration with generative AI) Motion graphics style (even as a still image) Key Points of Prompt Design Explained Point Explanation 1. Minimize modification points By narrowing down the parts that need modification in the prompt to 4 points, I created a template that anyone can easily use with copy-paste. 2. Attach reference images Simply saying "with the same atmosphere as before" in words doesn't accurately convey to AI. By attaching actual images, you can share ambiguous parts that are difficult to express such as layout, color scheme, and font feel. 3. Structured instructions By clearly categorizing information into purpose, title, notes, style, and content, it becomes easier for AI to understand the priority of each element. 4. Specify elements to avoid By including negative instructions like "avoid unnatural Japanese expressions," it becomes easier to select natural Japanese. 5. Present options (style) Instead of leaving everything to AI, by making it a format where you choose from 3 styles, simply adding this option makes the prompt customizable. Not Complete (Still Not Perfect) This template can mass-produce images while maintaining reproducibility just by replacing 4 points: title, style, overview, and reference image. Actually, There Are 2 Issues Although I could generate images in one shot, upon closer inspection, the following 2 problems were found. Issue 1: Japanese Typos Despite stating "avoid unnatural Japanese expressions" in the prompt, typos occurred. Tech feel (technical atmosphere): It's written as "自動化化", where "化" is repeated The display of "開発" is distorted AI and machine learning style (collaboration with generative AI): It's written as "機械学", where "習" is missing Issue 2: Logo Modification I wanted the reference image's logo to be reproduced, but the generative AI subtly modified the logo. At first glance they look the same, but upon closer inspection, you can see that the design has subtly changed . From a copyright and brand guidelines perspective, we want to avoid logo modifications . Tried to Improve Response to Issue 1: Japanese Typos I adjusted the prompt notes so the Japanese wouldn’t sound awkward, and tried out a few versions. Trial Changes Result ① ・Please use correct kanji and phrases for Japanese text. ・Ensure no typos or meaningless Japanese is included. ・Make Japanese text in the image natural and readable. No change - Same typos occurred ② ・Avoid unnatural Japanese expressions. ・Please review that kanji are correct before creating. All kanji disappeared - Text stopped displaying ③ Attached image with "Please correct 自動化化 in the upper left to 自動化" Not corrected - Original typo remained Response to Issue 2: Logo Modification To accurately reproduce the logo, I tried the following prompt adjustments. Additional prompt: I want the logo below to be exactly the same, so please attach the logo and use the attached file(rogo.png). As a result, a logo close to the original was created, but the angle and thickness were slightly different. Conclusion While image creation itself can be done in one shot, there were the following 2 issues: Japanese kanji expressions - Difficult to control perfectly with prompts alone Accurate logo reproduction - AI automatically modifies it Through this trial and error process, I think we can understand the characteristics and limitations of generative AI and find more effective ways to use it. Practical operational method: Leave layout, atmosphere, and background generation to the image‑generation AI (AI's strong point) Add logos later with image editing tools (humans control accurately) With this combination, I found that we can leverage the strengths of image-generation AI while ensuring a certain level of quality . This tech blog cover image was created using the above operation with the logo manually pasted. If anyone has a prompt that can accurately reproduce kanji and logos in AI-generated images, please let me know! I’ll write more articles when I find new and interesting AI applications, along with challenges and solutions.
This article is the Day 1 entry for the KINTO Technologies Advent Calendar 2025 🎅🎄 Hello, I'm Yukachi (@ukcpo) from the Developer Relations Group! KINTO Technologies is doing an Advent Calendar again this year☆* This is the fifth year for our Advent Calendar! This year, we have two series on free topics with a total of 50 articles planned! @ card In the past, the Developer Relations Group would individually reach out to each division to gather contributions for the Advent Calendar, but this time, most of the participants were recruited through a Slack channel call for submissions\(^^)/ By the way, half of the 50 total articles will be written by first-time contributors! It's wonderful to see so many members taking on the challenge, thinking "why not give it a try"! As a side note, since last year's Advent Calendar, the Developer Relations Group has been hosting a casual 30-minute consultation session every day via Slack huddle during the writing period. Being able to pop into the huddle and quickly ask questions when you're a bit stuck lowers the barrier to seeking help, so from our perspective as the hosts, it feels like a great initiative. What do you think!? We're happy to receive questions like "This is my first time writing, so is it okay to ask a basic question...?" ![スクリーンショット](/assets/blog/authors/uka/advent/advent1.png =400x) Our manager who's great at creating an approachable atmosphere With all that said, we hope you will enjoy our company's "real" through the Advent Calendar! Updates will be posted daily on our official X account (@KINTOTech_Dev) . If any articles catch your interest, please give them a read♩ One more month left this year—let's do our best\(^^)/
この記事は KINTOテクノロジーズ Advent Calendar 2025 の1日目の記事です🎅🎄 はじめに QAグループのMobileチームのokapiです。 AIで画像を作成するとき、「こういう画像が欲しい!」とプロンプトで伝えたはずなのに、なぜか意図が伝わらず、何度もやり直す羽目に…。そんな経験、ありませんか? そこで今回は、テックブログのカバー画像を「一度の生成」で作れるプロンプトを作成しました! この考え方は、他の画像作成にも応用可能です。ぜひ活用してみてください。 使い方 使用する画像生成AI Microsoft Copilot ※画像生成に対応した生成AIツールであれば、基本的にどれでもOKです(例:ChatGPT、DALL-E、Midjourneyなど) プロンプトの修正箇所 プロンプトの修正箇所は、たったの4点だけ! 修正箇所 内容 1. 表示タイトルの設定 執筆したテックブログのタイトルを「表示タイトル」に貼り付ける 2. スタイルの選択 「■スタイル」から1つ選び、残り2つを削除 3. 概要の設定 執筆したテックブログの内容を「■テックブログの記載内容」に貼り付ける 4. 以前のカバー画像添付 以前使っていたカバー画像を「プロンプト」に添付 実際に使ったプロンプト テックブログのカバー画像を作成してください。 添付している前回のテックブログで使用した画像と同じ形式でお願いします。 ■目的 以下のブログ記事のカバー画像を作成したいです。 ■表示タイトル (ここに記事のタイトルを貼り付け) ■注意点 ・日本語表現に不自然な点が入らないようにしてください。 ・画像のテイストやレイアウトは前回と同様でお願いします。 ・タイトルが中央または目立つ位置に配置されていること ■スタイル (下記3つからお好きなのを選ぶ) ・テック感(技術的な雰囲気) ・AIと機械学習系(生成AIとの協働) ・モーショングラフィック風(静止画でも) ■テックブログの記載内容 (ここに記事の概要を貼り付け) 添付した以前のカバー画像 本記事で実際に作った画像 本記事では、上記3パターンのスタイルで作りました。 スタイル 生成された画像 テック感(技術的な雰囲気) AIと機械学習系(生成AIとの協働) モーショングラフィック風(静止画でも) プロンプト設計のポイント解説 ポイント 解説 1. 修正箇所を最小限に設定 プロンプト内で修正が必要な箇所を「4点に絞り込む」ことで、誰でも簡単にコピペで使いやすいテンプレートとしました。 2. 参考画像の添付 言葉だけで「前回と同じ雰囲気で」と伝えても、AIには正確に伝わりません。実際の画像を添付することで、レイアウト・配色・フォント感などの「伝えるのが難しい曖昧な部分」を共有できます。 3. 構造化された指示 目的・タイトル・注意点・スタイル・内容と、情報を明確に分類することで、AIが各要素の優先度を理解しやすくなります。 4. 避けたい要素の明記 「日本語表現に不自然な点が入らないように」という否定形の指示を入れることで、自然な日本語を選定しやすくなります。 5. 選択肢を提示(スタイル) AIに完全に任せるのではなく、3つのスタイルから選ぶ形式にすることで、ここを増やすだけで、カスタマイズが可能なプロンプトとなります。 おわらない(まだ完璧ではない) 今回のテンプレートは「タイトル・スタイル・概要・参考画像」の4点を差し替えるだけで、再現性を保ったまま量産できます。 実は課題が2つあります 一発で画像生成できたものの、よく見ると以下の2つの問題が発生していました。 課題1: 日本語の誤字 「日本語表現に不自然な点が入らないように」とプロンプトに記載しているにも関わらず、誤字が発生していました。 テック感(技術的な雰囲気): 「自動化 化 」となっている(「化」が重複) 「開発」が表示崩れ AIと機械学習系(生成AIとの協働): 「機械学」となっている(「習」が抜けている) 課題2: ロゴの改変 参考画像のロゴを再現してほしかったのですが、生成AIによってロゴが微妙に改変されてしまいました。 一見すると同じように見えますが、よく見ると 細部のデザインが変わっている ことがわかります。 著作権やブランドガイドラインの観点から、 ロゴの改変は避けたい ところです。 改善を試みました 課題1への対応: 日本語の誤字 プロンプトの注意点(日本語表現に不自然な点が入らないように)をカスタマイズして複数パターンを試してみました。 試行 変更内容 結果 ① ・日本語の文字は、正しい漢字・語句を使用してください。 ・誤字脱字や意味不明な日本語が含まれないようにしてください。 ・画像内の日本語テキストは、自然で読みやすい表現にしてください。 変わらず - 同様の誤字が発生 ② ・日本語表現に不自然な点が入らないようにしてください。 ・作成する前に漢字は正しい漢字となっているかレビューしてから作成してください。 漢字自体が全部消される - テキストが表示されなくなる ③ 画像を添付の上、「左上の『自動化化』→『自動化』に修正してください」 修正されず - 元の誤字がそのまま残る 課題2への対応: ロゴの改変 ロゴを正確に再現するため、以下のプロンプト調整を試しました。 追加プロンプト:ロゴを添付の上、下のロゴは全く同じとしたいので、添付している(rogo.png)を貼り付けて使ってください。 を追加した結果、本物に近いロゴができますが、微妙に角度や太さが異なりました。 おわりに 画像作成自体は一発でできるものの、以下の2つの課題がありました: 日本語の漢字表現 - プロンプトだけでは完璧な制御が難しい ロゴの正確な再現 - AIが自動的に改変してしまう このようなトライ&エラーを繰り返すことで、生成AIの特性や限界を理解でき、より効果的な活用方法を見つけられると感じています。 現実的な運用方法: 画像生成AIに「レイアウト・雰囲気・背景」の生成を任せる(画像生成AIの得意分野) 「ロゴ」は画像編集ツールで後から追加(人間が正確に制御) この組み合わせで、 画像生成AIの強みを活かしつつ、一定の品質も担保できる と分かりました。 本テックブログカバー画像は、上記運用で手動でロゴを貼り付けて作りました。 もし「AIで生成した画像内の漢字やロゴを正確に再現できるプロンプト」をお持ちの方がいらっしゃいましたら、ぜひ教えてください! 今後も「おもしろいAIの活用方法」や「つまずいた課題と解決策」を見つけたら、記事を執筆していきます。
この記事は KINTOテクノロジーズ Advent Calendar 2025 の 1 日目の記事です🎅🎄 こんにちは、技術広報の ゆかち(@ukcpo) です! KINTOテクノロジーズは今年もアドベントカレンダーを実施します☆* アドベントカレンダー、今年で 5 回目! 今年はフリーテーマで 2 シリーズ、計 50 記事公開予定です! @ card 過去アドベントカレンダーは技術広報から各部署へ個別で声がけをして集めておりましたが、 今回はSlackチャンネルでの公募でほとんどの数が集まりました\(^^)/ ちなみに合計 50 記事中半分のメンバーが執筆デビューとなります! せっかくなので・・とチャレンジしてくれるメンバーが多くて素敵!!! 余談ですが、去年のアドベントカレンダーからアドベントの執筆時期は技術広報メンバーが毎日 30 分、 Slackのハドルにてゆるっと相談会を開いております。 ちょっと困った時にハドルに入ればサクッと聞けちゃうの、 相談のハードルが下がると思うので開催している側として良い取り組みな気がしていますがいかがでしょう!? " 初の執筆なので初歩的な質問なのですがいいですか・・? " などちょこちょこ相談いただけて嬉しいです! ![スクリーンショット](/assets/blog/authors/uka/advent/advent1.png =400x) 相談しやすい雰囲気をつくるのが得意なマネ そんなこんなで弊社の "リアル" をアドベントカレンダーを通して楽しんでいただけたら嬉しいです! 更新内容は 公式X(@KINTOTech_Dev) にて毎日更新予定です。 気になる記事があればぜひ目を通してみてください♩ 今年もあと1ヶ月、頑張りましょう〜\(^^)/
AI画像編集ツール徹底比較!どれが一番自然? Google・OpenAI・ByteDanceの最新モデルを検証【2025秋】 はじめに 2025年10月24日(金)、弊社の大阪テックラボにてCO-LAB Tech Night vol.4に発表者として参加してきました。「gpt-image-1 Gemini 2.5 Flash Image Seedream4.0の一貫性を検証」と題しまして、その時に発表した内容の一部をこちらでも共有させていただきたいと思います。 内容としてはOpenAI、Google、ByteDanceが提供する画像生成・編集機能を使って同じお題で画像編集を行い、どの様な出力結果になるのかを検証するといったものになります。一部以前にこちらのテックブログでも披露した内容と重なりますが、新たにSeedream4.0も加えて改めて出力結果を検証しております。 どのツールがいいのかなと悩んでいる方はぜひ参考にしていただければと思います。 改めて、gpt-image-1とは? こちらはOpenAIが2025年3月に発表し、GPT-4oに標準搭載された画像生成・編集が可能な機能です。APIなどでは「gpt-image-1」というモデル名で参照されることもあります。 input_fidelity というパラメータが提供されており、「high」に設定することで編集時の被写体やキャラクターの一貫性を格段に上がると話題になりました。参照機能なし、インペイント機能あり。 Gemini 2.5 Flash Imageとは? 通称Nano Bananaと呼ばれ被写体やキャラクターの一貫性を維持する能力に特化してチューニングされていると言われています。発表当初はその性能の高さにSNSで非常に話題になりました。提供開始は2025年8月。参照機能あり、インペイント機能なし。 Seedream4.0とは? 特徴としては4K出力という巨大な画像が出力可能で、スピードも速いことからプロの商用ワークフローにも耐えうると言われています。こちらも被写体やキャラクターの一貫性の高さがSNSなどで話題に上りました。2025年9月に提供開始されました。参照機能あり、インペイント機能あり。 AIの弱点:被写体やキャラクターの一貫性の維持 画像の一部分だけを編集したい場合においても、画像全体が生成し直されてしまうことから一貫性を保つことが難しく、生成を繰り返しているうちに元の画像から徐々に見た目が変化してしまうことが起こりますが、そうした「弱点」は現在克服されつつあります。 というわけで、これら3つのツールの出力画像を比較検証してみたいと思います。 ちなみに、Gemini2.5 Flash Image、Seedream4.0に関しては参照機能(別の画像を参考に元の画像を変更する機能)を使用しておりますが、gpt-image-1は参照機能がないので、元の画像に被写体を重ねるといった方法で編集を行なっております。ですので、プロンプトはなるべく同じ内容にしつつもgpt-image-1は若干違う内容で行いました。 また、平均10枚前後を出力してその中で最も良好な結果を示した出力を掲載しています。 お題01:空白のラベルに文字を記載する 空白のラベルに商品名を記載するというお題です。 ![ボトル](/assets/blog/authors/aoshima/image-edit-evaluation/01/sample01.jpg =50%x) 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/01/gpt.png =50%x) 文字の太さにばらつきがありますが、破綻は見られませんでした。全体的に一貫性が保たれています。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/01/gemini.png =50%x) 文字の太さ、大きさも問題なく、特に破綻は見られませんでした。全体的に一貫性が保たれています。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/01/seedream.png =50%x) 文字の太さ、大きさも問題なく、特に破綻は見られませんでした。全体的に一貫性が保たれています。ちなみにこちらのみセリフ体ですね。 お題02:透明な花瓶追加 右下の空間に透明な花瓶を追加してもらうというお題です。 ![ボトル](/assets/blog/authors/aoshima/image-edit-evaluation/02/sample01.jpg =50%x) 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/02/gpt.png =50%x) 花瓶の配置、角度、光の透過具合など特に違和感なく出力されています。菜の花と花瓶についても一貫性が保たれています。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/02/gemini.png =50%x) こちらも花瓶の配置、角度、光の透過具合など特に違和感なく出力されています。菜の花と花瓶についてはこちらも一貫性が保たれています。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/02/seedream.png =50%x) 花瓶の配置はちょっと遠くになってしまい花瓶の一部が切れてしまっていますが、角度、光の透過具合など特に違和感なく出力されています。菜の花と花瓶についてはこちらも一貫性が保たれています。 お題03:家族を車の後部座席に座らせる 家族写真に映っている人々を車の後部座席に座っている様に編集するというお題です。 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/03/gpt.png =50%x) 顔の一貫性は維持されていないようです。そして全体的にやや黄色味がかってしまっています。ただ人物とシートの大きさのバランスはいいと思います。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/03/gemini.png =50%x) こちら、12枚生成しましたが破綻なく出力されることはありませんでした。またシートに対して人物が小さいです。ただし顔の一貫性についてはかなり維持されています。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/03/seedream.png =50%x) こちらはシートに対して人物がやや小さい気がします。しかし顔の一貫性はまあまあ保たれている気がします。 お題04:男女をソファに座らせる 別々の画像の男女を仲良くソファに座っている様に編集するというお題です。 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/04/gpt.png =50%x) 構図が被写体により過ぎています。そして今回も顔の一貫性は保たれていないようです。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/04/gemini.png =50%x) 一見よさそうなのですが、ソファに対して人物が小さいようです。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/04/seedream.png =50%x) こちらは構図、ソファと人物の大きさのバランス、顔の一貫性、いずれも問題ないようです。 お題05:スーツ姿の男性をバイクに跨らせる スーツ姿の男性をバイクに跨らせるというお題です。 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/05/gpt.png =50%x) またもや構図が被写体により過ぎています。そして顔のAI感が強いです。バイクのディテールに対する一貫性は一部維持されていない様です。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/05/gemini.png =50%x) 全体的なバランスが良いです。また人物の顔、スーツの柄、バイクのディテールに対して一貫性が維持されています。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/05/seedream.png =50%x) 夕焼けが強く出てしまっています。そして人物がバイクに対してやや大きいですね。 お題06:雑誌のタイトルを日本語へ変更する 雑誌のタイトルを日本語へ変更するというお題です。 ![雑誌を持っている女性](/assets/blog/authors/aoshima/image-edit-evaluation/06/sample01.jpg =50%x) 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/06/gpt.png =50%x) タイトルに関しては漢字もひらがな(やや太い?)も問題ないかなといったところでしょうか。タイトル以外の細かい部分は完全に破綻しています。そして全体的な色味が黄色っぽくなっている感じがします。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/06/gemini.png =50%x) こちらはタイトル、細かい見出しは全く日本語になっておりませんが、それ以外の一貫性は完全に維持されているようです。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/06/seedream.png =50%x) こちらは日本語は成立していますが、ひらがなが太字になってしまっています。細かい文字も他と同じく日本語の体(てい)をなしていません。また、腕時計もなくなってしまっていますし、袖も短くなってしまっております。 お題07:真上からのアングルに変更 真横から撮影された花瓶に入ったひまわりを真上からのアングルに変更するというお題です。 ![ひまわり](/assets/blog/authors/aoshima/image-edit-evaluation/07/sample01.jpg =50%x) 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/07/gpt.png =50%x) アングルは真上からになっていますが、花が不自然に整列されてしまっています。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/07/gemini.png =50%x) アングルはやや斜め上からですが、花の配置や高さなど元の画像からの一貫性が非常に高く感じます。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/07/seedream.png =50%x) こちらも同じくアングルはやや斜め上からですが、花の配置など一貫性が高く感じます。ただし、中央の奥にある花の高さは明らかに違ってしまっているようです。 お題08:男性の髪型をイラストに合わせて変更 男性の髪型をイラストの髪型に合わせて変更し、4枚並べて出力するというお題です。 ![男性](/assets/blog/authors/aoshima/image-edit-evaluation/08/sample01.jpg =50%x) 出力結果の比較 gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/08/gpt.png =50%x) 一見悪くないのですが、元の画像と比べると顔の皮膚の感じがのっぺりとしており、ほくろなどが消えてしまっています。 Gemini2.5 Flash Image ![Gemini2.5 Flash Image](/assets/blog/authors/aoshima/image-edit-evaluation/08/gemini.png =50%x) 全てではないですがほくろなどが維持されていたりなど、顔の一貫性は高く感じますが、色が変わってしまっているなど髪型の再現度は低く感じます。 Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/08/seedream.png =50%x) 今まで高い一貫性を見せてきたSeedream4.0ですが、髪型に関しては完全に指示に背く結果となってしまっているようです。出力結果の中には髪型が長髪化してしまうなど、指示から大きく外れるものも見られました。 この様に比較検証を行なってみると、それぞれの癖などが分かってくるので非常に興味深いと感じました。ただしこの様に各社ごとのバラツキや傾向など今は顕著に見えておりますが、すぐにその差も分からないくらいに精度を上げてくるのではないかと思います。 最後に安全な画像生成・編集について この様に画像編集を簡単に行える様になってきましたが、簡単であるがゆえに意図せずとも間違った使い方をしてしまう可能性があります。 著作権を侵害する様なアウトプットをしない 生成の過程で著作権を侵害する恐れのあるものを参照しない こういったことはもちろんのこと、 未成年者など社会的立場の弱い人々に配慮する 差別や侮辱といった暴力から他者の尊厳を守る ディープフェイクなどによる誤情報の拡散をしない 画像生成・編集を行う際には、この様な配慮も忘れないことを心がけたいと思っております。
AI Image Editing Tool Comparison! Which is the Most Realistic Image Generator through Testing the Latest Models: Google, OpenAI, and ByteDance? [Fall 2025] Introduction On Friday, October 24, 2025, I participated as a presenter at CO-LAB Tech Night vol.4 held at our Osaka Tech Lab. At this event, I presented on the topic of testing the consistency of gpt-image-1, Gemini 2.5 Flash Image, and Seedream 4.0. Since Google recently released Nano Banana pro, I have updated some of the content and would like to share it here as well. The content involves using image generation and editing features provided by OpenAI, Google, and ByteDance to perform image editing on the same tasks and examine what kind of output results we get. If you are wondering which tool is the most suitable for you, I hope this will be helpful. What is gpt-image-1? The model is released in March 2025 and integrated as standard in GPT-4o with a feature capable of image generation and editing. In APIs and other contexts, it is sometimes called by its model name gpt-image-1. A parameter called input_fidelity is enabled. Setting the parameter to high helps significantly improve the consistency of subjects and characters in image editing, which gathered attention. The model has no reference feature but inpainting feature. What is Gemini 3 Pro Image? Commonly known as Nano Banana, it is said to be tuned specifically to maintain consistency of subjects and characters. At its launch, the model with high performance become a hot topic on social media. Upgraded to 3 Pro, the model’s text generation feature is apparently enhanced. Nano Banana was released in August 2025 with reference feature support but without inpainting. What is Seedream 4.0? The model includes a feature to output massive 4K images with fast speed, as well as a capability of handling professional commercial workflows. Particularly, the model’s high subject and character consistency also became a topic on social media. The model was released in September 2025 with both reference and inpainting feature support. AI's Weakness: Maintaining the Subject and Character Consistency When you want to edit only part of an image, AI models often regenerate the entire image, which makes it difficult to maintain the image consistency. Iterated generation by AI models causes gradual changes in the original image; however, such weakness is currently being overcome. So, I would like to compare and examine the output images from these three tools. By the way, for Gemini 3 Pro Image and Seedream 4.0, I used the reference feature (a function that modifies the original image by referencing another image). On the other hand, for gpt-image-1, which does not have a reference feature, I performed editing by overlaying subjects onto the original image. Therefore, while I tried to keep the prompts for all the tools as similar as possible, I applied the ones with slightly different content for gpt-image-1. In this comparison experiment, I generated an average of around 10 images and am posting the output that showed the best results among them. Task 01: Writing Texts on a Blank Label The task is to write a product name on a blank label on a bottle in the following image. ![ボトル](/assets/blog/authors/aoshima/image-edit-evaluation/01/sample01.jpg =50%x) Comparison of Output Results gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/01/gpt.png =50%x) The text is somewhat distorted, but no major breakdown was observed. Overall consistency is maintained. Gemini 3 Pro Image ![Gemini 3 Pro Image](/assets/blog/authors/aoshima/image-edit-evaluation/01/gemini.jpg =50%x) The thickness and size of the text are fine, and no particular breakdown was observed. Overall consistency is maintained. Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/01/seedream.jpg =50%x) The thickness and size of the text are fine, and no particular breakdown was observed. Overall consistency is maintained. Task 02: Change to a Top-Down Angle The task is to change an image of sunflowers in a vase photographed from the side to a top-down angle. ![ひまわり](/assets/blog/authors/aoshima/image-edit-evaluation/02/sample01.jpg =50%x) Comparison of Output Results gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/02/gpt.png =50%x) The angle is from directly above the sunflowers. Their petals somehow feel artificial. Gemini 3 Pro Image ![Gemini 3 Pro Image](/assets/blog/authors/aoshima/image-edit-evaluation/02/gemini.jpg =50%x) The angle is from directly above the sunflowers, but they may look slightly small. The texture of the petals looks realistic. Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/02/seedream.jpg =50%x) The image is ended up being slightly from above at an angle, but the texture of the flowers and the detail where just a sunflower on the right side has changed direction have been reproduced. Task 03: Seat a Family in the Back Seat of a Vehicle The task is to edit the people in a family photo as if they are seated in the back seat of a vehicle. Comparison of Output Results gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/03/gpt.png =50%x) Facial consistency does not appear to be maintained. And the overall color of the image appeared somewhat yellowish. However, the balance between the people and seat sizes seems good. Gemini 3 Pro Image ![Gemini 3 Pro Image](/assets/blog/authors/aoshima/image-edit-evaluation/03/gemini.png =50%x) The balance, color tone, and other aspects appear to be output with quite high quality. Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/03/seedream.jpg =50%x) The color tone came out dark, but I felt the consistency of the people and the balance were quite high quality. Task 04: Seat a Man and Woman on a Sofa The task is to edit a man and woman from separate images to appear sitting together on a sofa. Comparison of Output Results gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/04/gpt.png =50%x) The composition is too close to the subjects, and the people feel a bit large in comparison to the sofa. Facial consistency does not appear to be maintained. Gemini 3 Pro Image ![Gemini 3 Pro Image](/assets/blog/authors/aoshima/image-edit-evaluation/04/gemini.jpg =50%x) At first glance it looks good, but the people are small in comparison to the sofa. Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/04/seedream.jpg =50%x) There is seemingly no problem with all the composition, balance between sofa and people sizes, and facial consistency. Task 05: Convert English Title on a Magazine to Japanese The task is to convert an English magazine title to Japanese. ![雑誌を持っている女性](/assets/blog/authors/aoshima/image-edit-evaluation/05/sample01.jpg =50%x) Comparison of Output Results gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/05/gpt.png =50%x) The title font has become quite bold, but it does not appear to be broken. The cover page photo has also changed to the one showing a castle, giving it a Japanese feel. Gemini 3 Pro Image ![Gemini 3 Pro Image](/assets/blog/authors/aoshima/image-edit-evaluation/05/gemini.jpg =50%x) Since the model’s text generation feature was enhanced, the output is quite good. The magazine in the image even includes the Japanese subtitle below the title. Moreover, the subtitle clearly makes sense as Japanese. The cover shows cherry blossoms in full bloom alongside red autumn leaves, which is an odd seasonal mismatch. Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/05/seedream.jpg =50%x) The title font is not particularly broken, but unnecessary punctuation is inserted. Small text in the cover has broken down. The photo shows a harbor town-like location with cherry blossoms, giving it a Japanese feel. Task 06: Change a Man's Hairstyle Based on an Illustration The task is to restyle a man's hair to match three different hairstyles from an illustration, outputting the results side by side. Comparison of Output Results gpt-image-1 ![gpt-image-1](/assets/blog/authors/aoshima/image-edit-evaluation/06/gpt.png =50%x) There seems that subjects tend to be output large. Facial consistency does not appear to be maintained. Also, the long-permed hairstyle could not be reproduced. Gemini 3 Pro Image ![Gemini 3 Pro Image](/assets/blog/authors/aoshima/image-edit-evaluation/06/gemini.jpg =50%x) The face with long permed hair leans slightly toward an illustrated look, but overall the hairstyles from the illustration have been reproduced well. Seedream4.0 ![Seedream4.0](/assets/blog/authors/aoshima/image-edit-evaluation/06/seedream.jpg =50%x) Seedream 4.0, which had shown high consistency so far, did not produce results according to instructions when it came to hairstyles. Conducting this kind of comparative examination reveals the quirks of each, which I found very interesting. While the differences and tendencies of each model are now clearly visible, I think the models will quickly improve their accuracy to the level where these differences become indistinguishable. Finally, about Safe Image Generation and Editing As image editing becomes increasingly easy, the risk of unintentional misuse also grows. To this end, we should NOT: Produce output that infringes copyright; or Reference things that may infringe copyright in the generation process. Of course, as well as these, we should also: Show consideration for minors and others in socially vulnerable positions; Protect the dignity of others from violence such as discrimination and insult; or Not spread misinformation through deepfakes. I would like to take the above points in consideration when performing image generation and editing.
Introduction Hello, I'm Nomura, who joined in August 2025! In this article, I interviewed four new members who joined KINTO Technologies Corporation (KTC) in August 2025 about their impressions right after joining to compile their responses. I hope this provides useful information for those interested in KTC, especially those considering joining us, as well as serving as a reflection for the members who participated. I would be happy if you could get interested in our company through learning from the article about the corporate culture, atmosphere, and candid voices of its new employees. Hikaru Matsukura - MatsuIchi Self-introduction I'm in the Mobile App Development Group, where I'm responsible for developing the backend that mobile apps access. I mainly work in the domain of Dealer Digital Transformation (DX) projects. How is your team structured? We work as one team with members from the Digital Transformation Development Group, Digital Transformation Solution Group, and Digital Transformation Engagements Group to deliver products to accelerate DX. What was your first impression of KTC when you joined it? Were there any surprises? Being able to have discussions with the Cloud Infrastructure Team about infrastructure matters is extremely helpful and encouraging. What is the atmosphere like on-site? Everyone proactively takes initiative and moves forward—it's amazing! I’ll do my best, not to falling behind. How did you feel about writing a blog post? I thought like, "So it's finally my turn!" (lol) I'd like to continue to share information if I get any chance to post my blogs in the future. Question from Yusuke Miwa to Hikaru Matsukura Are you a cat person or a dog person? I like both! My wife's family has a cat, so I probably have more opportunities to interact with cats. Hiroki Nomura Self-introduction I belong to the AI First Group, which is a cross-functional organization. I’m based in Nagoya. In my previous job, I worked as a system integrator (SIer) for an automotive company, particularly focusing on applying generative AI to automotive control areas. How is your team structured? We do everything AI-related. As a cross-functional group, we also develop systems and tools related to generative AI. We're involved in developing and supporting KINTO products and internal generative AI apps, as well as Toyota Group projects. Additionally, using the knowledge shared across the whole company, we are providing generative AI training externally. Last month, we held the Vibe Coding Week event, and we've been implementing internal initiatives to master generative AI. What was your first impression of KTC when you joined it? Were there any surprises? Unlike the atmosphere of traditional Japanese companies, here you continuously think for yourself and keeping up a cycle of input and output. In terms of tools used in KTC, I was surprised by the difference between Teams culture and Slack culture! I really appreciate the culture where everyone actively posts. What is the atmosphere like on-site?? We focus on achieving what we want to accomplish by any means necessary, rather than thinking about the means first! How did you feel about writing a blog post? This is a culture that was not at my previous job. I personally write technical blogs, so I feel comfortable with it. By producing output, I can objectively reflect on my own activities, so it's very positive! Question from Hikaru Matsukura to Hiroki Nomura What do you recommend us as a local specialty food in Nagoya? I used to frequently go to Nan House, a buffet-style Indian restaurant in Higashiura-cho (that is currently closed, though). I like hitsumabushi. It's so expensive that I can't enjoy it often, though... There's Sumiyaki Una Fuji Meieki Branch on the 1st floor of the building where our office is located. Please try it when you come to Nagoya! Yusuke Miwa - Y.M Self-introduction I belong to the New Vehicle Subscriptions Product Management Group, working at the Muromachi office. In my previous job, I worked as a PM to develop a furusato nozei (hometown tax) website, planning and driving projects. How is your team structured? Our group has 7 members, and I communicate with developers and people from different divisions. In practice, I manage QCD for new vehicle subscription development projects and other miscellaneous projects to complete them. What was your first impression of KTC when you joined it? Were there any surprises? Communication among engineers in KTC is more active than at my previous company. Not only do they publish tech blogs, but they also actively communicate with others during project development and requirements confirmation, which is very helpful! What is the atmosphere like on-site? Rather than developing products simply by following requirements, we thoroughly understand the product users’ intent behind their requests and continuously try to define the requirements and build the structures to produce maximum effect. How did you feel about writing a blog post? This is my first experience to share information externally, so I'm nervous (lol). Question from Hiroki Nomura to Yusuke Miwa What are you most into these days, whether for work or personal life? On the personal side, I'm into watching baseball! I used to go to baseball games only a few times a year, but this year I'm going once or twice a month! Closing Thanks to everyone for sharing their impressions after joining KTC! KINTO Technologies is constantly welcoming new members! We'll continue to feature more blog posts to introduce new members from various divisions going forward, so we hope you to look forward to them. And we are still recruiting members to work with us in various divisions and positions! Please check here for details!
はじめに こんにちは、2025年8月入社の野村です! 本記事では、KINTO テクノロジーズ株式会社(以下、KTC)に2025年8月に入社した新メンバー4名の入社直後の感想をお伺いし、まとめました。 KTCに興味のある方、特に入社を検討されている方や、今回参加したメンバーの振り返りとして、有益な情報をお届けします。 KTCの社風や雰囲気、新入社員の生の声を通じて、当社の魅力を感じていただければ幸いです。 松倉一光 (Hikaru Matsukura) - MatsuIchi 自己紹介 モバイルアプリ開発Gにてモバイルアプリがアクセスするバックエンドの開発を担当しています。 主に販売店DXの領域で活動しています。 所属チームの体制は? DX開発G、DXソリューションG、DXエンゲージメントGの方々とOneTeamでDXプロダクトの提供に取り組んでいます。 KTCへ入社したときの第一印象?ギャップはあった? クラウドインフラチームにインフラ周りの相談ができるのはすごく頼りになり有難いです。 現場の雰囲気はどんな感じ? みなさん主体的にどんどん動いていてすごい!置いていかれないように頑張ります。 ブログを書くことになってどう思った? ついに来たか~、思いました(笑) 今後も機会があれば情報発信していきたいです。 三輪 優介さん ⇒ 松倉一光さんへの質問 猫派ですか?犬派ですか? どっちも好きです! 妻の実家で猫飼ってるので、猫と触れ合う機会の方が多いかも。 野村 宏樹(Hiroki Nomura) 自己紹介 AIファーストGという横串組織に所属しています。私の拠点は名古屋です。 前職は自動車会社のSIerとして、とくに自動車制御領域への生成AI活用に取り組んでいました。 所属チームの体制は? AIに関わることならなんでもやります。 横ぐし組織として、生成AIに関する仕組みやツールの整備もしています。 KINTOのプロダクトや社内生成AIアプリの開発やサポート、トヨタグループの案件にも携わっていますね。 さらに、横串組織として溜まったナレッジをもとに、社外に向けて生成AI研修もしています。 先月はVibeCodingWeekも実施しており、生成AIを使いこなすための社内施策も実施していました。 KTCへ入社したときの第一印象?ギャップはあった? JTCてきな会社の雰囲気とは逆で、自ら考えてInputとOutputをし続けていくところです。 ツール観点でいうと、Teams文化とSlack文化はここまで違うのか!と驚きました。皆さんが積極的に投稿する文化がとてもいいなと感じてます。 現場の雰囲気はどんな感じ? 手段先行になるのではなく、やりたいことを実現できるための手段は問わない! ブログを書くことになってどう思った? 前職ではなかった文化です。個人的に技術ブログを書いているので抵抗はないですね。 Outputをすることで、客観的に自身の活動の振り返りができるので、とてもPositiveです! 松倉一光さん ⇒ 野村 宏樹さんへの質問 おすすめ名古屋めしはなんですか!? 自分は昔(今はもうなくなっちゃいましたが)東浦町にあったナンハウスの食べ放題によく行ってました。 私はひつまぶしが好きですねー。高いので頻度高く食べれるものではないですが... オフィスの1Fに「炭焼 うな富士 名駅店」がありますね。名古屋にいらしたときにはぜひ! 三輪 優介(Yusuke Miwa) - Y.M 自己紹介 新車サブスク開発プロダクトマネジメントGに所属してます。室町オフィスで勤務です。 前職では、ふるさと納税サイトの開発PMとしてプロジェクト計画・推進を行なっておりました。 所属チームの体制は? グループとしては7名ですが、開発者や様々な部署の方とコミュニケーションを取ってます。 実務としては、新車サブスク開発におけるプロジェクト案件やハギレ案件のQCD管理を行い、プロジェクトを完遂させる動きをしてます。 KTCへ入社したときの第一印象?ギャップはあった? エンジニアの皆さんのコミュニケーションが以前所属していた会社よりも活発で、テックブログの発信はもちろん、プロジェクト開発や要件確認の場面でも積極的にコミュニケーションを取ってくださるので、とても助かっています! 現場の雰囲気はどんな感じ? 要求を鵜呑みにして開発するのではなく、要求意図をしっかり理解して最大効果が出るような要件定義と体制構築を日々試行錯誤してます。 ブログを書くことになってどう思った? 外部発信するという経験は初めてなので、緊張してますw 野村 宏樹さん ⇒ 三輪 優介さんへの質問 仕事でもプライベートでも、一番ハマっていることは? プライベートですが、野球観戦にハマってます!毎年数回程度しか行ってなかったのですが、今年は月に1~2回の頻度で行ってます! さいごに みなさま、入社後の感想を教えてくださり、ありがとうございました! KINTOテクノロジーズでは日々、新たなメンバーが増えています! 今後もいろんな部署のいろんな方々の入社エントリが増えていきますので、楽しみにしていただけましたら幸いです。 そして、KINTO テクノロジーズでは、まだまださまざまな部署・職種で一緒に働ける仲間を募集しています! 詳しくは こちら からご確認ください!
Hello, I'm Uehara, a backend engineer in the FACTORY E-commerce Development Group at KINTO Technologies. We will be participating in Techbook Fest 19 as the KINTO Technologies Writing Club formed by volunteer engineers from KINTO Technologies. ※This activity is conducted by volunteers for the purpose of sharing technical knowledge and is not an official corporate activity. What is Techbook Fest? Techbook Fest is a doujinshi convention exclusively for technical books. It will be held for 16 days from November 15 (Sat) to November 30 (Sun), 2025, both in an online marketplace and at a physical venue. The offline event will be held on November 16 (Sun) at Ikebukuro Sunshine City Exhibition Hall D. It will bring together knowledge from a wide range of technical fields and provide a space where participants with a keen interest in technology can interact directly. At the physical venue, you can pick up books and talk directly with the authors, while at the online venue, you can access technical books from anywhere in Japan. The appeal lies in gaining access to valuable knowledge available only here, including specialized technologies and practical know‑how that are seldom addressed in commercial publishing. For more details, please check the official Techbook Fest website. https://techbookfest.org/event/tbf19 New Book to be Distributed For distribution at Techbook Fest 19, volunteers from within KINTO Technologies formed the KINTO Technologies Writing Club and wrote the technical book "TECH BOOK By KINTO Technologies Vol.01." It is an omnibus-format book that compiles chapters written by each author on their respective themes. ![表紙](/assets/blog/authors/uehara/2025-10-24-techbookfest19-announcement/cover.webp =300x) Cover of the new book "TECH BOOK By KINTO Technologies Vol.01." This book covers a wide range of topics, from testing methods in Go language to AWS security, generative AI, infrastructure automation, mobile development, and organizational building. Chapter 1: Best Practices for Unit Testing with Go Chapter 2: Why Using IAM Roles Provides Higher Security Than IAM Users Chapter 3: Let's Implement Responsible Generative AI Using AWS Chapter 4: Practical Frontend Error Monitoring Learned from New Relic Implementation Chapter 5: Past, Present, and Future of Cloud Infrastructure Automation and Integrated Management Using Infrastructure as Code Chapter 6: Getting Started with iOS App Development Chapter 7: Toyota Group's In-House Development Organization Pursuing Collaboration with AI Through Both Culture and Technology Chapter 8: Creating an Autonomous Organization with the Power of Facilitation Chapter 9: Let's Create an MCP Server with Azure Functions Chapter 10: Balancing AWS Governance and Agility Chapter 11: Making AWS Operations Easier Using Generative AI Chapter 12: Recommendations for Home K8s Starting (About) Two Laps Behind Chapter 13: Let's Try a Hands-On Session with AWS IoT Greengrass Using a Sample Application We will distribute this book, packed with content, for free ! Recommended For Those interested in development using Go language or AWS Those who want to learn about mobile app development or infrastructure automation Those who want to know practical applications of generative AI Those interested in organizational building or team management Those interested in in-house development within the Toyota Group We hope that the technical challenges and solutions covered in each chapter will serve as a reference for those facing similar problems. Venue We will exhibit at both physical and online venues. Physical Venue Date and Time: Sunday, November 16, 2025 Location: Ikebukuro Sunshine City Exhibition Hall D (Cultural Center Building 2F) Booth Number: O15 Floor map at Techbook Fest 19. The KINTO Technologies Writing Club booth is "お15" Online Venue Saturday, November 15 - Sunday, November 30, 2025 Electronic version PDF will be distributed at the online marketplace Conclusion If you visit the Techbook Fest 19 physical venue, please come see us at booth お15. For those who live far away and cannot attend in person, we plan to publish the electronic version on the Techbook Fest online marketplace, so please take a look!
こんにちは、KINTOテクノロジーズのFACTORY EC開発グループでバックエンドエンジニアをやっている上原です。 技術書典19に、KINTOテクノロジーズの有志エンジニアによるサークル「KINTOテクノロジーズ執筆部」として参加します。 ※本活動は有志による技術知識の共有を目的としており、企業の公式活動ではありません。 技術書典とは? 技術書典は、技術書限定の同人誌即売会イベントです。2025年11月15日(土)から11月30日(日)の16日間、オンラインマーケットとオフライン会場で開催されます。 オフライン会場は11月16日(日)に池袋サンシャインシティ 展示ホールDで開催されます。幅広い技術分野の知見が集まり、技術に対してアンテナが高い参加者同士が直接交流できる場となっています。 オフライン会場では実際に著者と対話しながら本を手に取ることができ、オンライン会場では全国どこからでも技術書を入手できます。 商業出版では扱われにくい専門的な技術や実践的なノウハウなど、ここでしか得られない貴重な知見に出会えるのが魅力です。 詳細は技術書典の公式サイトでもご確認いただけます。 https://techbookfest.org/event/tbf19 今回頒布する新書 技術書典19での頒布に向けKINTOテクノロジーズの社内有志でKINTOテクノロジーズ執筆部を結成し、技術本「TECH BOOK By KINTO Technologies Vol.01」を執筆しました。 それぞれの著者がそれぞれのテーマで書き上げた章たちを一冊にまとめた、オムニバス形式の本となっています。 ![表紙](/assets/blog/authors/uehara/2025-10-24-techbookfest19-announcement/cover.webp =300x) 新書「TECH BOOK By KINTO Technologies Vol.01」の表紙。 本書では、Go言語でのテスト手法から、AWSセキュリティ、生成AI、インフラ自動化、モバイル開発、組織づくりまで、幅広いテーマを扱っています。 第1章 Goで学ぶ単体テストのベストプラクティス 第2章 なぜIAM Roleを使うことが、IAMユーザよりセキュリティが高いのか 第3章 「責任ある生成AI」をAWSを使って実装してみよう 第4章 New Relic導入から学ぶフロントエンドエラー監視の実践 第5章 Infrastructure as Codeを利用したクラウドインフラ自動化・統合管理の過去・現在・未来 第6章 今から始めるiOSアプリ開発 第7章 トヨタグループ内製開発組織が追求する、カルチャー×技術両輪によるAIとの協業 第8章 ファシリテーションの力で自律した組織を作りたい 第9章 AzureFunctionsでMCPサーバーを作ってみよう 第10章 AWSのガバナンスとアジリティの両立 第11章 AWSの運用を生成AIを活用して楽にする 第12章 (多分)2周遅れくらいから始めるおうちK8sのススメ 第13章 AWS IoT Greengrassをサンプルアプリを使ってハンズオンしてみよう たっぷり内容が詰まった本書を 無料 で配布します! こんな方におすすめ Go言語やAWSを使った開発に興味がある方 モバイルアプリ開発やインフラ自動化について学びたい方 生成AIの実践的な活用方法を知りたい方 組織づくりやチームマネジメントに関心がある方 トヨタグループの内製開発に興味がある方 各章で扱う技術的な課題と解決方法が、同じような問題に直面している方々の参考になれば幸いです。 会場 オフライン・オンライン会場ともに出展します。 オフライン会場 開催日時: 2025年11月16日(日) 場所: 池袋サンシャインシティ 展示ホールD(文化会館ビル2F) ブース番号: お15 技術書典19でのフロアマップ。KINTOテクノロジーズ執筆部のブースは「お15」になります オンライン会場 2025年11月15日(土)~11月30日(日) オンラインマーケットにて電子版pdfが配布される おわりに 技術書典19 オフライン会場へお越しの際は、ぜひブース「お15」でお会いしましょう。 遠方にいて現地に行けない方も、電子版を技術書典オンラインマーケットで公開予定ですので、ぜひご覧ください!
はじめに こんにちは!KINTO テクノロジーズ、クラウドセキュリティ G の大高です。 普段は、クラウド環境のガードレール整備と CSPM や脅威検知を利用した監視やカイゼン活動に取り組んでいます。 私たちのグループでは、日々の業務の一環として、 Amazon GuardDuty が検知した脅威に関して、ログの詳細分析を行い、正常な操作であるかを確認する場面があります。このログ分析の効率化を目指して、Amazon Q Developer を活用した検証をしてみました。 Amazon GuardDuty の脅威検知の対応 KINTO テクノロジーズでは、 AWS 環境の脅威検知に Amazon GuardDuty を利用しています。 Amazon GuardDuty が脅威を検知すると、Slack のチャンネルに通知が届くようになっており、この通知を受けて、セキュリティ担当者がログの詳細分析を行います。必要に応じて、対象のユーザーにヒアリングを行いながら、 JIRA チケットに経過を記録し、対応状況を管理しています。 現在の Amazon GuardDuty の脅威検知対応のフロー 今回の取り組みでは、脅威検知後の対応フローのうち、ログ分析のタスクを Amazon Q Developer を活用して、効率化することを目指しています。 AI によるログ分析を組み込んだ場合の Amazon GuardDuty の脅威検知対応のフロー Amazon Q Developer とは Amazon Q Developer は、 AWS が提供する開発アシスタント AI です。 AWS を活用したクラウド開発に特化しており、以下のような様々なユースケースに対応しています。 AWS 環境のリソース、イベント、アクティビティの調査 AWS 環境のトラブルシューティング 開発・デバッグ・コーディング支援 ドキュメントの作成 Amazon Q Developer は、デフォルトの状態でも Amazon GuardDuty で検知した脅威について、関連ログを分析することができます。例えば、Amazon GuardDuty で検知された脅威の Findings ID とともにログ調査を依頼すると、以下のように Amazon Q Developer は Built-In のツールを使用して、自律的に調査と分析を行ってくれます。 (以下は、 Amazon Q Developer がログ調査を行う過程を一部抜粋したものです。) Amazon Q Developer が自律的にログ分析を行う様子 Amazon Q Developer が生成したレポートには、操作を行なったユーザーや影響を受けたリソースなど検知した脅威の概要がまとめられており、推奨される対応の提案も含まれています。 デフォルトの状態で Amazon Q Developer が出力した分析レポート しかし、実際の業務では、操作を行なったユーザーの認証の状態や操作時間のログも含めて、確認・分析を行い正常な動作であるかを判断しており、上記の内容に加え、さらなる追加の調査が必要です。 また、詳細なログ調査・分析を行うために、Amazon Q Developer とチャットによるやり取りを重ねる中で、様々な課題が見えてきました。 ログ分析を行う上での課題 Amazon Q Developer は、デフォルトの状態でも Amazon GuardDuty で検知した脅威の分析を行うことができますが、実務レベルのログの詳細分析を行うためには多くの課題があります。 1. AWS API のスロットリング制限 Amazon Q Developer は、 Built-In のツールや MCP サーバーにより提供されているツールを使用して実行した API リクエストにエラーレスポンスが返ってきた場合、自律的に修正を行い、再度、 API リクエストを送信します。 例えば、 API リクエストの文法が誤っていた場合は、自律的にコマンドの修正を行い、再度、リクエストを送信します。しかし、 API リクエストの再送が多くなると、スロットリングの上限に達してしまい、最終的に必要な情報を取得できないまま、不正確な回答や期待とは異なる回答を生成してしまうことがあります。 Amazon Q Developer で実行した API リクエストがスロットリングの上限に到達してしまい、情報の取得ができない。 2. 収集しているログの制限 Amazon Q Developer は AWS CloudTrail に保存されているログを検索・分析できますが、そもそもログが取得されていない場合は分析することができません。 AWS CloudTrail では、デフォルトで管理イベント( IAM ユーザーの作成や EC2 インスタンスの起動など)のログは自動的に収集されますが、データイベント( S3 バケットへのファイルアップロードや Lambda 関数の実行など)のログは手動で設定を有効化しなければ収集されません。そのため、Amazon GuardDuty が S3 バケットや Lambda 関数への不審なアクセスを検知した場合でも、事前にデータイベントのログ収集を有効化していなければ、Amazon Q Developer で AWS CloudTrail ログを検索しても詳細な分析に必要な情報を取得することができません。 Amazon Q Developer がログ分析に必要な情報を取得できなければ、推測による不正確な回答の生成をしてしまう可能性があります。 AWS CloudTrail でデータイベントの取得を有効化していないが、 Amazon Q Developer はデータイベントの検索を試みる。 AWS 環境のログの収集・保存にはコストがかかるため、会社や組織の予算に合わせて、意図的にログの収集・保存を実施していない場合もあります。現在の AWS 環境のログの収集状況を考慮して、 Amazon GuardDuty で検知した脅威の分析を行う際に、Amazon Q Developer がどのログを活用することができるかについて整理する必要があります。 3. コンテキストウィンドウの制限 生成 AI が一度に処理することができるテキストの長さのことをコンテキストウィンドウと言い、生成 AI の「記憶力」を指し示します。コンテキストウィンドウは、トークンという単位でカウントされており、過去のやり取りを含めて一定のトークン数の範囲内であれば過去のやりとりの内容を踏まえた受け答えを生成することができます。 非常に長い文章をプロンプトに入力したり、生成 AI と長いやり取りを続けたりして、トークン数の合計がコンテキストウィンドウの範囲を超えると、Amazon Q Developer は、過去に与えた指示を忘れてしまい、期待した回答をしてくれなかったり、不正確な情報を出力してしまったりする可能性が高まります。 コンテキストウィンドウを超過すると、 Amazon Q Developer が自動的に過去情報を要約して、詳細な内容は忘れてしまう Amazon Q Developer もコンテキストウィンドウの制限があり、 200K トークンまでとなっています。(2025年10月16日現在) トークン数のカウントには、 API のリクエストやレスポンスにより得られた情報も含まれます。無駄なプロンプトの入力を避けるだけでなく、不要な API コールを繰り返して、不要なトークンを消費しないように注意する必要があります。 4. 出力の一貫性の課題 生成 AI は、全く同じプロンプトを与えたとしても、生成する回答の出力フォーマットや含まれる情報が毎回異なります。Amazon Q Developer にログ分析のレポートを生成するように指示した場合でも、レポートに含まれる情報が毎回異なったり、レポートの構成が毎回異なったりする可能性があります。 このような場合、必要な情報を得ることができず、レポートに基づいて対応を検討したりすることが難しくなります。 課題を克服するための工夫 このように Amazon Q Developer で Amazon GuardDuty が検知した脅威を詳細分析するには、多くの課題があります。 これらの課題を乗り越えて、Amazon Q Developer を期待通りに動作をさせるために、以下の2つのアプローチで改善が必要です。 Amazon Q Developer がログの詳細分析に必要となる情報を効率的に収集して、正確な情報に基づく回答を生成できるようにする。 Amazon Q Developer にログの詳細分析を実施する手順や必要な操作に関する詳細な指示を与えて、実際の分析業務と同様にタスクを実行させる。 具体的には Amazon Q Developer に、MCP 連携とプロンプトの調整を行うことで改善を図りました。 1. MCP 連携 MCP( Model Context Protocol )とは、生成 AI が外部のツールやデータソースと連携を行うために定められた標準プロトコルです。Amazon Q Developer は、MCP に対応しています。連携したい MCP サーバーを設定ファイルに記入して、 Amazon Q Developer を起動することで、外部のツールの利用や、データ参照を行うことができます。 Amazon GuardDuty で検知した脅威に関するログの詳細分析を行うために、 Amazon Q Developer に以下のツールを連携しています。 ツール 内容 Built-in ローカル環境へのファイルの読み書きや、API による AWS リソースの操作が実行できるツールがデフォルトで提供されています。 AWS Knowledge MCP Server AWS に関する最新のニュースやブログの情報を含む AWS ナレッジにアクセスするためのツールが提供されています。 AWS Documentation MCP Server AWS に関するドキュメント情報にアクセスするためのツールが提供されています。 CloudTrail MCP Server AWS CloudTrail を使用してログを検索したり、分析を行うためのツールが提供されています。 Splunk MCP Server Splunk に保存されているログを検索したり、分析を行うためのツールが提供されています。 弊社では、Splunk Cloud を利用して SIEM ( Security Information and Event Management ) を構築しており、AWS CloudTrail のログや Entra ID の認証ログといったさまざまなログが集約されています。 Splunk 社が提供する Splunk MCP Server と連携し、 Splunk Cloud の検索クエリを実行することができるツールを提供することで、Amazon Q Developer が AWS CloudTrail に保存されたログに限らず、横断的に詳細分析を行うことができるようにしています。 2. プロンプトエンジニアリング プロンプトエンジニアリングとは、生成 AI に与える入力情報を工夫することで、意図した応答を引き出したり、回答精度の向上を図る手法です。生成 AI に対して、指示の背景や追加の情報を提供したり、タスクの遂行に関する詳細な指示を与えることで、期待した回答を生成するように調整を行っていきます。 Amazon GuardDuty で検知した脅威に関するログの詳細分析では、 Amazon Q Developer に以下のような情報をプロンプトで提供することで回答精度の改善を図っています。 項目 記載内容 例 ログ調査に必要となるシステム構成の情報 ・どんなログが保存されているか ・どこに保存されているかなど CloudTrail のログは、Splunk Cloud に保存されています。 ログの調査手順を明示的に指示 ・ログ分析の際に使用するツール ・何を調査するかなど ・ログ分析の際は、Splunk MCP Server を優先的に使用してください。 ・操作時間帯が 09:00 〜 18:00 の時間帯は正常な動作であり、低リスクと判断してください。 出力形式(レポートの内容)を具体的に指定 ・レポートの出力形式の指定 ・レポートに含める情報の指定 ・レポートは、Markdown 形式で作成してください。 ・レポートは、以下の章立てで作成してください。: (以下、章立てを記載) 実行するコマンドの具体的サンプルを提供 Splunk の検索クエリなど 認証状況の確認では、以下のコマンドを実行してください。 'index=example userPrincipalName="<メールアドレス>" | stats count by status.errorCode, status.failureReason | sort -count' 禁止事項の明記 ・実行をしてはいけない操作は何か ・出力に含めてはいけない情報は何か ・自分で日付から曜日を計算しないでください。 ・推測や仮定に基づく関連付けはしないでください。 プロンプトエンジニアリングにおいては、生成 AI に与える情報量が少ないと、出力内容の調整に効果が出ないことがあります。プロンプトに含む条件や指示内容を生成 AI を使用しながらできる限り詳細に記載することで、適切な情報量になるようにプロンプトを調整しました。 工夫による改善と成果 上記の調整を実施後、改めて Amazon Q Developer にログ分析を依頼した結果、生成されたレポートが以下になります。プロンプトに入力した指示は、デフォルト状態の Amazon Q Developer に分析を依頼したときと同じ文章を入力しています。 # GuardDuty Finding 詳細分析レポート **Finding ID**: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx **検出リージョン**: ap-northeast-1 **AWSアカウントID**: 000000000000 **AWSアカウント名**: (不明) **分析日時**: 2025-10-16 17:44:14 ## 1. エグゼクティブサマリー ### 重要な発見 - **実際のユーザー特定**: User Name( < User Email Address > ) - **外部委託ユーザー**: 「xxxx-」プレフィックスにより外部委託会社のユーザーと判定 - **誤検知の可能性**: 高い(正規ユーザーによる通常業務の可能性) - **CloudTrailログ**: 対象AWSアカウントのログがSplunkに存在せず、詳細な認証チェーン追跡は不可 ### 緊急対応の要否判断 - **緊急度**: 低 - **理由**: 正規ユーザーによる通常業務時間帯での活動、Entra ID認証も正常 ### 主要調査結果の要点 - Finding発生時刻: 2025年9月9日 13:20:05 JST(月曜日の通常業務時間) - 操作内容: S3バケット「< S3-Bucket-Name >」からのGetObject API実行 - ユーザー: 外部委託会社の User Name - 異常性: GuardDutyの機械学習による異常検知だが、業務時間帯での正規操作 ### 推奨アクション(優先度付き) 1. **優先度: 低** - 当該ユーザーへの業務内容確認(念のため) 2. **優先度: 低** - S3アクセスログの詳細確認(必要に応じて) 3. **優先度: 中** - GuardDutyの機械学習ベースライン調整検討 ## 2. 脅威の概要 **脅威タイプ**: Exfiltration:S3/AnomalousBehavior **重要度**: 8.0(高) **検出内容**: IAMエンティティによる異常なS3 API呼び出し GuardDutyが検出した「異常な」S3 GetObject API呼び出しは、機械学習ベースラインからの逸脱を示しているが、実際には正規ユーザーによる通常業務時間帯での操作と判定される。 ## 3. 脅威の詳細 ### 攻撃手法の詳細分析 - **API**: S3 GetObject - **対象バケット**: < S3-Bucket-Name > - **認証方式**: AuthHeader(正規のAWS認証) - **User Agent**: aws-sdk-rust/1.3.8(開発ツール使用) ### 攻撃者のプロファイリング 実際の攻撃者ではなく、正規ユーザー「 User Name 」による操作: - 外部委託会社(「xxx-」プレフィックス)のユーザー - 通常業務でAWS SSOを使用 - 開発環境(sandbox)での作業実績あり ### 攻撃の時系列分析 - **2025-09-09 13:20:05 JST**: S3 GetObject API実行 - **2025-09-09 13:29:43 JST**: GuardDutyによる検出 - **同日同時刻**: Entra IDでのAWS SSOログイン成功を確認 ### 影響範囲の詳細評価 - **影響を受けたリソース**: S3バケット「< S3-Bucket-Name >」 - **データ漏洩リスク**: 低(sandboxバケット、正規ユーザー) - **横展開リスク**: なし - **継続的脅威**: なし ### 類似攻撃パターンとの比較 GuardDutyの機械学習による異常検知であり、実際の攻撃パターンとは異なる: - 正規認証チェーンの使用 - 通常業務時間帯での操作 - 開発環境での作業 ### セキュリティ制御の回避手法 回避手法は使用されておらず、全て正規の認証・認可プロセスを経由: - Identity Center経由のSSOログイン - 適切なロール権限での操作 - 正規のAWS SDK使用 ## 4. プリンシパル情報 **ユーザー名**: < Role Name > **実際のユーザー**: User Name( < User Email Address > ) **ユーザータイプ**: AssumedRole(Identity Center経由) **アクセスキーID**: < Access Key ID > **プリンシパルID**: < Principle ID> ### ユーザー属性詳細 - **氏名**: User Name - **所属**: 外部委託会社(「xxx-」プレフィックス) - **メールアドレス**: < User Email address> - **ロール**: < Role Name > ## 5. 異常性分析 ### 時間帯による異常性評価 - **Finding発生日**: 月曜日(通常業務日) - **Finding発生時刻**: 13:20:05 JST(通常業務時間) - **異常性レベル**: **低** - 通常業務時間帯での活動 - **リスク評価**: 低リスク ### 地理的異常性 - **アクセス元**: 日本(< ISP Name >) - **想定業務拠点**: 日本国内 - **異常性**: なし - 想定される業務拠点からのアクセス ### 操作パターンの異常性 - **使用ツール**: aws-sdk-rust(開発ツール) - **操作内容**: S3 GetObject(データ取得) - **対象**: sandboxバケット(開発環境) - **異常性**: 低 - 開発業務での通常操作 ### 認証チェーンの異常性 - **Entra ID認証**: 正常(エラーコード0) - **Identity Center**: 正常なSSOセッション - **AWS認証**: 正規のAssumedRole - **異常性**: なし - 全て正規プロセス ### Entra ID認証ログ分析結果 #### 実行したSplunkクエリと結果 **1. 基本情報取得**: index=< example > userPrincipalName="< User Email Address >" | table userDisplayName userPrincipalName **結果**: ユーザー名「 User Name 」を確認 **2. 30日行動パターン**: index=< example > userPrincipalName="< User Email Address >" earliest=-30d | stats count, values(location.city), values(deviceDetail.browser), values(appDisplayName) by userPrincipalName **結果**: - 総ログイン回数: 59回 - アクセス地域: < Access Area > - ブラウザ: Chrome 138.0.0 - 利用アプリ: AWS SSO, Microsoft 365 Support Service, Microsoft Account Controls V2, Microsoft Teams Web Client **3. 時間パターン分析**: index=< example > userPrincipalName="< User Email Address >" earliest=-7d | eval weekday=strftime(_time, "%A"), hour=strftime(_time, "%H") | stats count by weekday, hour | sort weekday, hour **結果**: - 平日・休日問わず16時台のアクセスが多い - 深夜0時台のアクセスも複数回確認(金曜、木曜、火曜、水曜) - 通常業務時間外のアクセスパターンも存在 **4. 認証状況**: index=< example > userPrincipalName="< User Email Address >" | stats count by status.errorCode, status.failureReason | sort -count **結果**: - 成功ログイン: 28回(エラーコード0) - 認証失敗: 確認されず - 認証成功率: 100% **5. Finding発生時刻周辺の認証ログ**: index=< example > userPrincipalName="< User Email Address >" (Finding発生時刻周辺) **結果**: - IPv6アドレス(< IPv6 Address >)からのAWS SSOログイン成功 - 日本国内からのアクセス - 認証成功(エラーコード0) ### 本人性確認結果 **振舞い分析による本人確認**: - **通常パターンとの一致**: 高い一致度 - **アクセス地域**: 通常の< Access Area >からのアクセス - **利用アプリケーション**: AWS SSOの通常利用 - **認証成功率**: 100%(異常な失敗なし) - **本人らしさ**: **高い** - 通常の行動パターンと一致 ## 6. リスク評価 ### 総合リスク評価: **低** #### 根拠 1. **正規ユーザーによる操作**: 実在する外部委託ユーザーによる操作 2. **通常業務時間**: 月曜日13:20の通常業務時間帯 3. **正規認証プロセス**: Identity Center経由の正常な認証チェーン 4. **開発環境**: sandboxバケットでの開発作業 5. **本人性確認**: Entra IDログで本人の通常パターンと一致 #### 誤検知の可能性 **高い** - GuardDutyの機械学習ベースラインが、このユーザーの通常パターンを十分に学習していない可能性 #### セキュリティ影響 - **データ漏洩リスク**: 極低 - **システム侵害リスク**: なし - **横展開リスク**: なし ## 7. 詳細調査 ### CloudTrailログ存在確認結果 **対象AWSアカウント(000000000000)のCloudTrailログがSplunkに存在しないため、詳細な認証チェーン追跡は困難** 実行したクエリ: index=< example > sourcetype=aws:cloudtrail account=000000000000 | head 1 結果: 0件のログが見つかりました ### ソースIPアドレスからの候補ユーザー調査 **地理的近接性による候補ユーザー調査**: - Finding詳細のIPアドレス(< IP Address >)での直接検索: 0件 - 実際の認証ログではIPv6アドレス(< IPv6 Address >)を使用 - 両方とも日本国内からのアクセスで地理的整合性あり ### 追加調査すべき点 1. **GuardDutyベースライン調整**: 外部委託ユーザーの通常パターン学習 2. **S3アクセスログ**: 必要に応じてバケットレベルでの詳細ログ確認 3. **ユーザー業務確認**: 当該時刻での業務内容確認(念のため) ### 不明な点や疑問点 1. GuardDutyが「異常」と判定した具体的な学習ベースライン 2. 同一バケットでの他ユーザーのアクセスパターン 3. aws-sdk-rustツール使用の業務上の必要性 ## 8. 推奨対応策 ### 即座の対応(優先度: 低) 1. **ユーザー確認**: User Name 氏への業務内容確認(念のため) 2. **継続監視**: 同様のパターンでの再発監視 ### 短期対応(1週間以内) 1. **GuardDuty調整**: 外部委託ユーザーの正常パターン学習促進 2. **アクセスパターン分析**: sandboxバケットでの通常アクセスパターン確立 ### 中長期対応(1ヶ月以内) 1. **ベースライン最適化**: GuardDutyの機械学習ベースライン調整 2. **監視ルール見直し**: 外部委託ユーザー向けの監視ルール策定 3. **ドキュメント整備**: 正常な開発作業パターンの文書化 ### 対応不要と判断される理由 - 正規ユーザーによる通常業務時間帯での正当な操作 - 全ての認証プロセスが正常 - セキュリティリスクが極めて低い - 誤検知の可能性が高い **結論**: このFindingは誤検知と判定され、セキュリティインシデントとしての対応は不要。GuardDutyのベースライン調整を推奨。 デフォルト状態の Amazon Q Developer に Amazon GuardDuty で検知した脅威のログ分析を依頼した場合と比較して、次のような改善が見られました。 MCP 連携やプロンプトエンジニアリングにより Amazon Q Developer を調整したことで、ログ分析の出力に含まれる情報とその内容を期待するものに近づけることができました。 プロンプト内で生成するレポートのフォーマットを指定したことで、複数回の検証でも出力内容に必要な情報が安定して含まれるようになりました。 Splunk Cloud 上のログデータを利用して、検知した脅威のリスク判定を行うこともできており、より実務に近い分析を行うことができました。 一方で、以下の観点では、さらなる改善が必要になります。 複数回の検証を行う中で、不正確な情報を含むレポートが生成されることもありました。 プロンプト内で明確な指示をしているにもかかわらず、指示を無視した出力がされることがありました。 Splunk Cloud 上に AWS CloudTrail のログが取り込まれていない場合に、CloudTrail MCP Server が提供するツールを使用して、直接 AWS CloudTrail のログを検索せずに、Entra ID のログのみでリスク判断を行なうことがありました。 完全にハルシネーションを排除することはできず、引き続き、出力内容の検証と実行した操作をモニタリングしながら、 Amazon Q Developer の調整を継続的に行う必要があります。 まとめ Amazon Q Developer を活用したログ分析は、デフォルトの状態では課題も多いですが、 MCP 連携やプロンプトの調整を行うことにより、実際にログ分析に活用できそうな応答を生成できるようになりました。しかし、その生成内容を完全に信用することはできず、回答の正確さや実際に操作した内容などについては、引き続き、調整と改善を行なっていく必要がありそうです。 また、今回は Amazon Q Developer をログ分析に活用するための方法に焦点を当てて検証を行ったため、本記事では言及していませんが、生成 AI のセキュアな活用という観点で、MCP サーバの最小権限の管理や認証情報の管理など、実際に運用を行なっていくためには、まだまだ改善すべき課題はあります。 引き続き、 Amazon Q Developer の活用方法を模索しながら、セキュリティ運用の改善・強化に取り組んでいきたいと思います。
Exploring Threat Analysis in Amazon GuardDuty with Amazon Q Developer Introduction Hello! I'm Otaka from KINTO Technologies' Cloud Security Group. My daily work involves establishing cloud environment guardrails and monitoring and improvement activities using CSPM and threat detection. As part of our group's daily operations, we perform detailed log analysis when Amazon GuardDuty detects threats to verify whether they represent normal operations. To streamline this log analysis process, I conducted a verification using Amazon Q Developer. Responding to Threat Detection with Amazon GuardDuty At KINTO Technologies, we use Amazon GuardDuty for threat detection in our AWS environment. When Amazon GuardDuty detects a threat, notifications are sent to a Slack channel. Upon receiving these notifications, the security team performs detailed log analysis. When necessary, we interview the relevant users and record progress in JIRA tickets to manage response status. Current Amazon GuardDuty threat detection response flow In this initiative, we aim to improve efficiency in the log analysis task within the threat detection response flow by utilizing Amazon Q Developer. Amazon GuardDuty threat detection response flow incorporating AI-based log analysis What is Amazon Q Developer Amazon Q Developer is a development assistant AI provided by AWS. It specializes in cloud development using AWS and supports various use cases such as: Investigating resources, events, and activities in AWS environments Troubleshooting AWS environments Development, debugging, and coding assistance Document creation Amazon Q Developer can analyze related logs for threats detected by Amazon GuardDuty even in its default state. For example, when requesting log investigation along with the Findings ID of a threat detected by Amazon GuardDuty, Amazon Q Developer autonomously conducts investigation and analysis using built-in tools as shown below. (The followings are excerpts of the log investigation process performed by Amazon Q Developer.) Amazon Q Developer autonomously performing log analysis The report generated by Amazon Q Developer summarizes the detected threats, including the user who performed the operation and affected resources, and also includes recommended response suggestions. Analysis report output by Amazon Q Developer in default state In actual operations, we also check and analyze the authentication status of the user who performed the action and the log of the operation time to determine whether it was a normal activity. In addition to the above, further investigation is required. Also, while conducting detailed log investigation and analysis through repeated interactions with Amazon Q Developer via chat, various challenges became apparent. Challenges in Log Analysis While Amazon Q Developer can analyze threats detected by Amazon GuardDuty even in its default state, there are many challenges in performing practical-level detailed log analysis. 1. AWS API Throttling Limits When Amazon Q Developer receives error responses to API requests executed using built-in tools or tools provided by MCP servers, it autonomously makes corrections and resends the API request. For example, if API request syntax is incorrect, it autonomously corrects the command and resends the request. However, if API request retries become too frequent, the throttling limit may be reached, which can prevent the necessary information from being obtained and ultimately lead to inaccurate or unexpected responses. API requests executed by Amazon Q Developer reach throttling limits, preventing information retrieval. 2. Collected Log Limitations Amazon Q Developer can search and analyze logs stored in AWS CloudTrail, but cannot analyze logs that haven't been collected in the first place. AWS CloudTrail automatically collects management event logs (such as IAM user creation or EC2 instance launches) by default, but data event logs (such as file uploads to S3 buckets and Lambda function executions) are not collected unless manually enabled. Therefore, even when Amazon GuardDuty detects suspicious access to S3 buckets or Lambda functions, if data event log collection hasn't been enabled beforehand, searching AWS CloudTrail logs with Amazon Q Developer cannot retrieve information necessary for detailed analysis. If Amazon Q Developer cannot obtain information necessary for log analysis, it may generate inaccurate responses based on speculation. Amazon Q Developer attempts to search data events even though data event collection is not enabled in AWS CloudTrail. Since collecting and storing logs in AWS environments incurs costs, organizations may intentionally not collect or store certain logs based on budget constraints. It's necessary to organize which logs Amazon Q Developer can utilize when analyzing threats detected by Amazon GuardDuty, considering the current log collection status in the AWS environment. 3. Context Window Limitations The length of text that generative AI can process at once is called the context window, representing the AI's memory.Context windows are counted in units called tokens, and within a certain token count range including past interactions, the AI can generate responses based on previous conversation content. When extremely long text is input into prompts or long interactions continue with the generative AI, and the total token count exceeds the context window range, Amazon Q Developer forgets previously given instructions, potentially failing to provide expected responses or outputting inaccurate information. When context window is exceeded, Amazon Q Developer automatically summarizes past information and forgets detailed content Amazon Q Developer also has context window limitations, currently set at 200K tokens (as of October 16, 2025). Token counting includes information obtained through API requests and responses. It's necessary to not only avoid unnecessary prompt input but also avoid repeating unnecessary API calls that consume unnecessary tokens. 4. Output Consistency Challenges Generative AI produces different output formats and included information each time, even when given identical prompts. When instructing Amazon Q Developer to generate log analysis reports, the information included in reports and report structure may differ each time. In such cases, it becomes difficult to obtain necessary information and consider responses based on reports. Approaches to Overcome Challenges As shown above, there are many challenges in performing detailed analysis of threats detected by Amazon GuardDuty using Amazon Q Developer. To overcome these challenges and make Amazon Q Developer operate as expected, improvements are needed through two approaches: Enable Amazon Q Developer to efficiently collect information necessary for detailed log analysis and generate responses based on accurate information. Provide Amazon Q Developer with detailed instructions regarding procedures for conducting detailed log analysis and necessary operations, enabling it to execute tasks similar to actual analysis work. Specifically, improvements were made through MCP integration and prompt adjustments for Amazon Q Developer. 1. MCP Integration MCP (Model Context Protocol) is a standard protocol defined for generative AI to integrate with external tools and data sources. Amazon Q Developer supports MCP. By specifying the MCP servers to integrate in the configuration files and launching Amazon Q Developer, you can use external tools and reference data. For detailed log analysis of threats detected by Amazon GuardDuty, the following tools are integrated with Amazon Q Developer. Tool Description Built-in Tools for reading/writing files to local environments and operating AWS resources via API are provided by default. AWS Knowledge MCP Server Tools for accessing AWS knowledge including latest AWS news and blog information are provided. AWS Documentation MCP Server Tools for accessing AWS documentation information are provided. CloudTrail MCP Server Tools for searching and analyzing logs using AWS CloudTrail are provided. Splunk MCP Server Tools for searching and analyzing logs stored in Splunk are provided. At our company, we have built a SIEM (Security Information and Event Management) using Splunk Cloud, aggregating various logs such as AWS CloudTrail logs and Entra ID authentication logs. By integrating with the Splunk MCP Server provided by Splunk and providing tools that can execute Splunk Cloud search queries, Amazon Q Developer can perform detailed cross-sectional analysis not limited to logs stored in AWS CloudTrail. 2. Prompt Engineering Prompt engineering is a technique for eliciting intended responses and improving response accuracy by carefully designing the input provided to generative AI. By providing generative AI with instruction background and additional information, or giving detailed instructions regarding task execution, adjustments are made to generate expected responses. For detailed log analysis of threats detected by Amazon GuardDuty, response accuracy is improved by providing Amazon Q Developer with the following information through prompts. Item Description Example System configuration information necessary for log investigation - What logs are stored - Where they are stored, etc. CloudTrail logs are stored in Splunk Cloud. Explicitly instruct log investigation procedures - Tools to use for log analysis - What to investigate, etc. - Prioritize using Splunk MCP Server for log analysis. - Consider operations between 09:00 and 18:00 as normal and low risk. Specify output format (report content) - Specify report output format - Specify information to include in report - Create reports in Markdown format. - Create reports with the following chapter structure: (chapter structure listed below) Provide specific command execution samples Splunk search queries, etc. Execute the following command to verify authentication status. 'index=example userPrincipalName=" " | stats count by status.errorCode, status.failureReason | sort -count' Clearly state prohibitions - What operations should not be executed - What information should not be included in output - Do not calculate day of week from dates yourself. - Do not make associations based on speculation or assumptions. In prompt engineering, insufficient information provided to generative AI may not effectively adjust output content. Prompts were adjusted to the appropriate level of detail by specifying conditions and instructions using generative AI. Improvements and Results from Adjustments After implementing the above adjustments, the following report was generated when requesting log analysis from Amazon Q Developer again. The instructions input to the prompt were the same text as when requesting analysis from Amazon Q Developer in its default state. # GuardDuty Finding Detailed Analysis Report **Finding ID**: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx **Detection Region**: ap-northeast-1 **AWS Account ID**: 000000000000 **AWS Account Name**: (Unknown) **Analysis Date/Time**: 2025-10-16 17:44:14 ## 1. Executive Summary ### Key Findings - **Actual User Identification**: User Name (< User Email Address >) - **Outsourced User**: Identified as outsourced company user by "xxxx-" prefix - **False Positive Possibility**: High (possibility of normal work by legitimate user) - **CloudTrail Logs**: Target AWS account logs do not exist in Splunk, detailed authentication chain tracking unavailable ### Emergency Response Necessity Assessment - **Urgency**: Low - **Reason**: Activity by legitimate user during normal business hours, Entra ID authentication also normal ### Key Investigation Results Summary - Finding occurrence time: September 9, 2025 13:20:05 JST (Monday during normal business hours) - Operation content: GetObject API execution from S3 bucket "< S3-Bucket-Name >" - User: User Name from outsourced company - Abnormality: Anomaly detected by GuardDuty machine learning, but legitimate operation during business hours ### Recommended Actions (Prioritized) 1. **Priority: Low** - Confirm work content with relevant user (just in case) 2. **Priority: Low** - Verify S3 access logs in detail (if necessary) 3. **Priority: Medium** - Consider adjusting GuardDuty machine learning baseline ## 2. Threat Overview **Threat Type**: Exfiltration:S3/AnomalousBehavior **Severity**: 8.0 (High) **Detection Content**: Anomalous S3 API calls by IAM entity The "anomalous" S3 GetObject API call detected by GuardDuty indicates deviation from machine learning baseline, but is actually determined to be operation by legitimate user during normal business hours. ## 3. Threat Details ### Detailed Attack Method Analysis - **API**: S3 GetObject - **Target Bucket**: < S3-Bucket-Name > - **Authentication Method**: AuthHeader (legitimate AWS authentication) - **User Agent**: aws-sdk-rust/1.3.8 (development tool usage) ### Attacker Profiling Not an actual attacker, but operation by legitimate user "User Name": - User from outsourced company ("xxx-" prefix) - Uses AWS SSO for normal work - Track record of work in development environment (sandbox) ### Attack Timeline Analysis - **2025-09-09 13:20:05 JST**: S3 GetObject API execution - **2025-09-09 13:29:43 JST**: Detection by GuardDuty - **Same date/time**: Confirmed successful AWS SSO login in Entra ID ### Detailed Impact Scope Assessment - **Affected Resources**: S3 bucket "< S3-Bucket-Name >" - **Data Leakage Risk**: Low (sandbox bucket, legitimate user) - **Lateral Movement Risk**: None - **Persistent Threat**: None ### Comparison with Similar Attack Patterns Anomaly detection by GuardDuty machine learning, differs from actual attack patterns: - Use of legitimate authentication chain - Operation during normal business hours - Work in development environment ### Security Control Bypass Techniques No bypass techniques used, all through legitimate authentication/authorization processes: - SSO login via Identity Center - Operation with appropriate role permissions - Use of legitimate AWS SDK ## 4. Principal Information **User Name**: < Role Name > **Actual User**: User Name (< User Email Address >) **User Type**: AssumedRole (via Identity Center) **Access Key ID**: < Access Key ID > **Principal ID**: < Principle ID> ### Detailed User Attributes - **Name**: User Name - **Affiliation**: Outsourced company ("xxx-" prefix) - **Email Address**: < User Email address> - **Role**: < Role Name > ## 5. Abnormality Analysis ### Time-based Abnormality Assessment - **Finding Occurrence Date**: Monday (normal business day) - **Finding Occurrence Time**: 13:20:05 JST (normal business hours) - **Abnormality Level**: **Low** - Activity during normal business hours - **Risk Assessment**: Low risk ### Geographic Abnormality - **Access Source**: Japan (< ISP Name >) - **Expected Business Location**: Within Japan - **Abnormality**: None - Access from expected business location ### Operation Pattern Abnormality - **Tool Used**: aws-sdk-rust (development tool) - **Operation Content**: S3 GetObject (data retrieval) - **Target**: sandbox bucket (development environment) - **Abnormality**: Low - Normal operation in development work ### Authentication Chain Abnormality - **Entra ID Authentication**: Normal (error code 0) - **Identity Center**: Normal SSO session - **AWS Authentication**: Legitimate AssumedRole - **Abnormality**: None - All legitimate processes ### Entra ID Authentication Log Analysis Results #### Executed Splunk Queries and Results **1. Basic Information Retrieval**: index=< example > userPrincipalName="< User Email Address >" | table userDisplayName userPrincipalName **Result**: Confirmed user name "User Name" **2. 30-Day Behavior Pattern**: index=< example > userPrincipalName="< User Email Address >" earliest=-30d | stats count, values(location.city), values(deviceDetail.browser), values(appDisplayName) by userPrincipalName **Result**: - Total login count: 59 times - Access region: < Access Area > - Browser: Chrome 138.0.0 - Apps used: AWS SSO, Microsoft 365 Support Service, Microsoft Account Controls V2, Microsoft Teams Web Client **3. Time Pattern Analysis**: index=< example > userPrincipalName="< User Email Address >" earliest=-7d | eval weekday=strftime(_time, "%A"), hour=strftime(_time, "%H") | stats count by weekday, hour | sort weekday, hour **Result**: - Many accesses at 16:00 hour regardless of weekdays/holidays - Multiple accesses at midnight 0:00 hour confirmed (Friday, Thursday, Tuesday, Wednesday) - Access patterns outside normal business hours also exist **4. Authentication Status**: index=< example > userPrincipalName="< User Email Address >" | stats count by status.errorCode, status.failureReason | sort -count **Result**: - Successful logins: 28 times (error code 0) - Authentication failures: Not confirmed - Authentication success rate: 100% **5. Authentication Logs Around Finding Occurrence Time**: index=< example > userPrincipalName="< User Email Address >" (Around Finding occurrence time) **Result**: - Successful AWS SSO login from IPv6 address (< IPv6 Address >) - Access from within Japan - Authentication successful (error code 0) ### Identity Verification Results **Identity Confirmation through Behavior Analysis**: - **Match with Normal Patterns**: High degree of match - **Access Region**: Access from normal < Access Area > - **Applications Used**: Normal use of AWS SSO - **Authentication Success Rate**: 100% (no abnormal failures) - **User authenticity**: **High** - Matches normal behavior patterns ## 6. Risk Assessment ### Overall Risk Assessment: **Low** #### Rationale 1. **Operation by Legitimate User**: Operation by actual outsourced user 2. **Normal Business Hours**: Normal business hours on Monday at 13:20 3. **Legitimate Authentication Process**: Normal authentication chain via Identity Center 4. **Development Environment**: Development work in sandbox bucket 5. **Identity Verification**: Matches legitimate user's normal patterns in Entra ID logs #### False Positive Possibility **High** - GuardDuty's machine learning baseline may not have sufficiently learned this user's normal patterns #### Security Impact - **Data Leakage Risk**: Extremely low - **System Compromise Risk**: None - **Lateral Spread Risk**: None ## 7. Detailed Investigation ### CloudTrail Log Existence Confirmation Results **Detailed authentication chain tracking is difficult because CloudTrail logs for target AWS account (000000000000) do not exist in Splunk** Executed query: index=< example > sourcetype=aws:cloudtrail account=000000000000 | head 1 Result: 0 logs found ### Candidate User Investigation from Source IP Address **Candidate User Investigation by Geographic Proximity**: - Direct search with IP address from Finding details (< IP Address >): 0 results - Actual authentication logs use IPv6 address (< IPv6 Address >) - Both accesses from within Japan with geographic consistency ### Points Requiring Additional Investigation 1. **GuardDuty Baseline Adjustment**: Learning normal patterns for outsourced users 2. **S3 Access Logs**: Verify detailed logs at bucket level if necessary 3. **User Work Confirmation**: Confirm work content at relevant time (just in case) ### Unclear Points or Questions 1. Specific learning baseline that GuardDuty determined as "abnormal" 2. Access patterns of other users to same bucket 3. Business necessity for using aws-sdk-rust tool ## 8. Recommended Countermeasures ### Immediate Response (Priority: Low) 1. **User Confirmation**: Confirm work content with User Name (just in case) 2. **Continuous Monitoring**: Monitor for recurrence with similar patterns ### Short-term Response (Within 1 week) 1. **GuardDuty Adjustment**: Promote learning of normal patterns for outsourced users 2. **Access Pattern Analysis**: Establish normal access patterns for sandbox bucket ### Medium to Long-term Response (Within 1 month) 1. **Baseline Optimization**: Adjust GuardDuty machine learning baseline 2. **Monitoring Rule Review**: Establish monitoring rules for outsourced users 3. **Documentation**: Document normal development work patterns ### Reasons for Determining No Response Necessary - Legitimate operation by legitimate user during normal business hours - All authentication processes normal - Security risk extremely low - High possibility of false positive **Conclusion**: This Finding is determined to be a false positive, requiring no response as a security incident. Recommend adjusting GuardDuty baseline. Compared to requesting log analysis of threats detected by Amazon GuardDuty from Amazon Q Developer in its default state, the following improvements were observed. By adjusting Amazon Q Developer through MCP integration and prompt engineering, the information included in log analysis output and its content could be brought closer to expectations. By specifying report format to generate in prompts, necessary information was stably included in output content across multiple verifications. Risk assessment of detected threats could be performed using log data on Splunk Cloud, enabling analysis closer to actual practice. However, further improvements are needed in the following aspects: Through multiple verifications, reports containing inaccurate information were sometimes generated. Despite clear instructions in prompts, outputs ignoring instructions sometimes occurred. When AWS CloudTrail logs were not imported to Splunk Cloud, risk assessment was sometimes performed using only Entra ID logs without searching AWS CloudTrail logs directly using tools provided by CloudTrail MCP Server. Hallucinations cannot be completely eliminated, and continuous adjustment of Amazon Q Developer is necessary while continuing to verify output content and monitor executed operations. Conclusion While log analysis using Amazon Q Developer has many challenges in its default state, by performing MCP integration and prompt adjustments, responses that appear usable for actual log analysis could be generated. However, the generated content cannot be completely trusted, and continued adjustment and improvement regarding response accuracy and actually performed operations appear necessary. Additionally, since this verification focused on methods of utilizing Amazon Q Developer for log analysis, this article does not address other aspects. From the perspective of securely leveraging generative AI, there remain many challenges to be improved for practical operation, such as enforcing minimum privilege management for MCP servers and handling credential management. I will continue to explore utilization methods for Amazon Q Developer while working on improving and strengthening security operations.
ITオリエンテーションとその改善について はじめに KINTOテクノロジーズ(KTC)のコーポレートITでは、グループ会社のIT業務支援も行っています。 私は現在、グループ会社にてヘルプデスク業務の支援を担当しており、その中で新規入場者向けのITオリエンテーション(以下、オリエン)も実施しています。 その中で、PCとスマホの取り扱い方法や業務環境の説明や初回のパスワード変更をサポートしております。 他社のIT部門がどのようにオリエンを行っているかを知る機会は少ないと体感しているので、少しでも参考になればと思い記事にまとめました。 背景と課題 オリエンは月に1〜2回の頻度で実施されています。 これまでは、担当者の経験と知識に依存した運用で、資料はなく、簡単なメモ(いわゆる“あんちょこ”)のみが存在していました。 もともと1人でヘルプデスク業務を担っていたため、資料化の必要性がなかったという背景があります。 しかし、属人化から脱却し、組織として対応できる体制を目指すために私がジョインし、改善に取り組むことになりました。 (正直なところ、オリエンを担当した初日、「これはつらい…」と感じたのが一番のきっかけです(笑)) オリエンのゴール設定 業務においてPCがない環境というのは考えられないため、よりスムーズに業務に入っていただけるように 「会社PC・スマホを使える状態になっている」「自社のおおまかなPC環境を理解している」 状態になってもらう。をゴールとして設定しました。 改善についても、参加者がよりゴールに達成しやすくなるように行いました。 実施した改善 改善は主に以下の2点に取り組みました。 1. 資料化 これまでのメモをベースに、説明内容を体系的に整理し、資料として整備しました。 システムの説明や注意点を網羅し、説明漏れがないように構成しています。 また、略称が多く使われる環境のため、資料内で都度解説を加えるようにしました。 さらに、以下の工夫も取り入れています。 アイスブレイクの工夫 入場者は緊張していることが多く、理解力や質問のしやすさに影響します。 そこで、ヘルプデスクメンバーの自己紹介を資料に盛り込み、趣味や好きなことを「ゆるめ」に紹介することで、親しみやすさとリラックス感を演出しています。 支援先は堅めな会社なので、盛り込むのには勇気が必要でしたが、より効果的に行うにはこれが必要だ。という信念をもとに対応しました。 想いの表明 我々が「社内のサポートメンバーとして、ビジネスを止めないために活動している」ということを、熱量高めに伝えるようにしています。 「困ったら遠慮なく頼ってほしい」というメッセージは、問い合わせの増加による稼働超過。過度な依存につながる懸念もありますが、サポート部門としての存在価値は、頼られることにあると考えています。 良好な関係性が築けていないと、我々起因で仕組みを変えたりする際のヒアリングや、課題解決がスムーズに進みません。 また、親密感を持ってもらうことは、結果的に不要な問い合わせを減らしたり、トラブルの解決時間削減の効果もあると感じています。 講師もわかりやすい資料化 属人化脱却のためには、講師をやる難易度も下げる必要があります。 そのため、参加者にとってもわかりやすいだけでなく、講師にとってもわかりやすい資料にする必要があります。 その際に工夫したのは下記の2点です。 説明の流れを意識 オリエンの中で、PCのパスワード変更があるのですが、それをスムーズにするため、以下の順序で資料を整理しました。 業務スマホのロック解除して使える状態にする スマホの初期パスコード変更と生体認証設定 PCへのログイン(スマホを利用した多要素認証解除) PCパスワードの変更 この順番にすることで、必要となる操作が一本道となり、参加者も講師も迷いなく進められます。 1ページ1コンテンツ 1ページに詰め込みすぎず、「このページで何を説明するか」を明確にしました。 これも参加者は理解しやすく、講師も話す内容を把握しやすくなります。 2. 振り返りの仕組み オリエン終了後、なるべく早くチームで振り返り会を実施しています。 記憶が新しいうちに、KPTA(Keep・Problem・Try・Action)のフレームワークを使って小さなところでも改善点を洗い出し、次回に活かしています。 改善案(Action)はチームのタスク管理に組み込み、漏れがないようにしております。 この振り返りで改善をし続けています。 具体的には、下記のようなActionを実行しました。 順番を入れ替えることで重複の説明を排除した 参加者にも簡単な自己紹介をしてもらうことで、同期間のコミュニケーションを促進した オリエンテーションの内容 改善後のオリエンでは、以下のような内容を説明・実施しています。 時間としては、1時間をとってもらっております。 雑談や質問の時間もとっており、はじめての緊張を和らげつつ、自社の環境について知ってもらう時間としております。 メンバーの自己紹介 業務環境(VDI)の説明 初回ログインとパスワード変更 機器取り扱いの注意点 サポート窓口の案内 やってみて感じたこと 参加者からは「今までこんなに楽しい説明会はなかった」といった感想をいただくことが増えました。 特に、我々の自己紹介をきっかけに声をかけてもらえることが多くなり、関係性の構築にもつながっています。 また、特定のメンバーに依存せず、誰でもオリエンを担当できる体制が整い、組織としての対応力も向上しました。 さいごに オリエンの参加者は、緊張や不安を抱えていることが多いです。 業務に欠かせないPCやスマホも、会社ごとに環境が異なるため、初めてその会社の機器に触れた際に戸惑うところが多いのは当然だと思います。 KTCのようなITのプロ集団でも戸惑われる方がそれなりにいらっしゃるので、IT専業ではないグループ会社ではなおさらです。 また、活躍を期待されて入場しているので「うまくできない自分を見せるわけにはいかない」という心理はとても強く働き、質問や相談がしづらくなります。 だからこそ、 「誰でも最初はうまくできない」「ITのプロである私も失敗した」 ということを積極的に伝え、安心して頼ってもらえる雰囲気づくりを心がけています。 ITオリエンテーションは様々な立場の方に実施するため、我々の存在を認知してもらい、頼ってもらえる関係性を築いていくための第一歩になるものだと思います。 こういうところから、組織でのIT活用の促進、ひいては事業の加速にまでつながっていると信じています。
はじめに QAグループのWebチームのroです。 実際のQA案件業務においては、中古車のテストデータを大量に生成する必要がありますが、その手順は複雑で注意点も多く、作業には多くの時間を要していました。 この課題を解決するため、Playwrightを活用し、中古車データ生成の全工程を自動化しようと思っています。 この取り組みを社内の「Vibe Coding」イベントにおいて実施し、Vibe Codingの手法を用いて中古車データ自動生成用のPythonスクリプトを実装しました。 Vibe Codingとは? Vibe Codingとは、生成AIと人間のエンジニアが協働して行う新しい開発スタイルです。 AIが「コード生成」や「調査」といった重い作業を引き受け、人間は「品質の検証・改善」に集中することで、高品質かつ効率的な開発を行えます。 Vibe Codingは、以下3つの特徴があります: 自由度が高い:テーマやルールが緩く、各自好きな技術や課題に取り組める 交流の場:普段関わらないメンバーとも一緒に作業でき、知識やスキル共有のきっかけになる モチベーション向上:一人でやるよりも集中しやすく、楽しみながら成果を出せる 中古車データ自動作成のスクリプトの流れ 中古車データ自動作成のスクリプトのシステム構成図 スクリプト実装中に頑張ったこと 実際に中古車データ自動作成用のスクリプトを実装中、いくつかの課題に直面しましたが、試行錯誤と視点の転換を重ねることで、最終的に問題を解決することができました。 1. S3バケットに中古車契約の情報ファイルをアップロードする S3バケットに中古車契約の情報ファイルをアップロードする手順があります。これまで、この手順は中古車チームに依頼して操作してもらっていました。今回は、この手順も自動化するために、事前に中古車チームとクラウドチームに相談し、S3へのアクセス権限を付与していただきました。S3の接続権限を得たことで、Pythonを用いてS3へのファイル自動アップロードを実現できるようになりました。 コードの例: # SSOでログイン済みのプロファイルを指定 session = boto3.Session(profile_name="your_profile_name") # S3クライアント作成 s3 = session.client("s3") # アップロード先情報 bucket_name = "your-bucket-name" local_file_path = r"path/to/your/local/file.txt" s3_object_key = "path/to/s3/object.txt" try: s3.upload_file(local_file_path, bucket_name, s3_object_key) print(f"{local_file_path} を S3バケット '{bucket_name}' の '{s3_object_key}' にアップロードしました。") except Exception as e: print(f"アップロードに失敗しました: {e}") 2. 輸送依頼のExcelファイルの自動生成 本来は、サイトから輸送依頼用のExcelファイルをダウンロードし、その中から不要な内容を削除したうえで、中古車契約の情報を再入力し、再度サイトへアップロードする必要がありました。しかし、この煩雑な手順は時間がかかるうえ、ミスが発生しやすいという課題がありました。今回、Pythonを活用することで、不要な内容を自動削除し、必要な情報を抽出してExcelへ自動記入し、最後にサイトへ自動アップロードすることが可能になりました。これまで手作業で行っていた面倒な作業から解放できました! コードの例: # 元のCSVファイルパス folder = r"C:\Users\×××\Downloads" pattern = os.path.join(folder, "req_000550_4*.csv") files = glob.glob(pattern) # 正規表現で「req_000550_」+19桁の英数字に一致するファイルを抽出 regex = re.compile(r"req_000550_[0-9A-Za-z]+\.csv$") target_files = [f for f in files if regex.search(os.path.basename(f))] input_file = target_files[0] #print(f"対象ファイル: {input_file}") # ===== CSV読み込み ===== df = pd.read_csv(input_file, header=None) # ===== セル更新 ===== df.iloc[1, 1] = display_text1 # B2 df.iloc[1, 2] = display_text3_formatted # C2 df.iloc[1, 3] = display_text2 # D2 # G2, H2, I2, J2 → 列番号は G=6, H=7, I=8, J=9(0始まり) today_str = datetime.now().strftime("%Y%m%d") for col in [6, 7, 8, 9]: df.iloc[1, col] = today_str # ===== 保存ファイル名作成 ===== safe_display_text3 = display_text3.replace(":", "-").replace(" ", "_") new_filename = f"req_000550_{safe_display_text3}.csv" output_file = os.path.join(folder, new_filename) # ===== CSV保存 ===== df.to_csv(output_file, index=False, header=False, encoding="utf-8-sig") print(f"更新完了: {output_file}") await page.get_by_role("button", name="輸送状況を登録する").click() new_filename = f"req_000550_{safe_display_text3}.csv" filePath = rf"C:/Users/×××/Downloads/{new_filename}" route = os.path.abspath(filePath) async with page.expect_file_chooser() as fc_info: await page.get_by_role("button", name="受付状況更新ファイル(CSV形式 UTF-8/CRLF/BOMあり) prepended action").click() file_chooser = await fc_info.value await file_chooser.set_files(route) await page.get_by_role("button", name="アップロードする").click() await page.get_by_role("button", name="OK").click() 3. 各システム間におけるデータ反映時間の把握 1つの中古車データに対して、あるシステム上での操作が完了した後、一定の時間を待たなければ、他のシステムで次の操作を行うことができません。この待機時間の管理は容易ではありませんが、自動化スクリプトを活用することで、正確に待機時間を把握し、時間になれば自動的に次の操作へ進めることが可能になりました。これにより、業務効率が大幅に向上しました。 感想 今回の成果物は、今後の案件業務に活用できそうです。 従来200台の中古車データの作成に約2ヶ月かかっていたが、自動化により工数を削減することを期待できそうです。 成果 今回は、中古車データ生成の自動化を実現しただけでなく、今後につながる有益な経験と発想を得ることができました。 この経験を活かし、今後のQA業務においても、自動化できる業務をさらに発掘し、段階的に実現していきたいと考えています。 おわりに 今回のVibe Coding Weekでは、中古車データの自動作成のチャレンジを実施し、短期間で成果を得ることができました。 今後も自動化、AIに力を入れて、データ自動作成だけではなく、テスト設計、テスト実施も自動化にしていきたいと思います。