TECH PLAY

電通総研

電通総研 の技術ブログ

837

こんにちは。 電通 総研ITの寺尾です。 プラグイン 開発の機能実装について、今回は ラインマーカー の実装についてお話します。 前回はこちら: IntelliJプラグイン開発の始め方~アクション機能編~ ラインマーカーとは エディタ画面上のガター(行番号の表示される列)に表示されるアイコンを、 IntelliJ では ラインマーカー や ガターアイコン と呼びます。 インタフェースの実装や、オーバーライドメソッドの実装先へのジャンプで、アイコンをクリックする方も多いのではないでしょうか。 アクションによるファイルジャンプとは別に、今回はラインマーカーによるファイルジャンプ機能の実装をご紹介します。 公式ドキュメント ラインマーカープロバイダー 実装前に知っておくこと 前回と同様に、本記事でもKotlinでの実装例を提示します。 Kotlinプロジェクトに関する内容は、アクション機能編の 実装前に知っておくこと をご参照ください。 実装 ゴール 今回はラインマーカーのクリックで、DAOメソッドから SQL ファイルへのジャンプ機能の実装を目指します。 事前準備 ラインマーカーはプロバイダークラスを実装して実現します。 まずは以下の準備から始めていきましょう。 RelatedItemLineMarkerProvider のサブクラス プラグイン 設定登録 今回のラインマーカー処理は、 RelatedItemLineMarkerProvider のサブクラスで実装します。 plugin.xml には、以下のように<codeInsight.lineMarkerProvider>タグを使って登録します。 プロバイダークラスは IntelliJ の拡張ポイントとして登録するため、 タグ内に記述します。 codeInsight.lineMarkerProvider : ラインマーカーのプロバイダークラスを登録するタグ language : ラインマーカー表示処理対象とするファイルタイプ implementationClass : 実装したプロバイダークラスの完全修飾クラス名 <extensions defaultExtensionNs = "com.intellij" > <codeInsight . lineMarkerProvider language = "JAVA" implementationClass = "org.domaframework.doma.intellij.gutter.dao.DaoMethodProvider" /> </extensions> それでは、実装を始めていきましょう! 各オーバーライドメソッドでは、ガターに表示するアイコン設定とラインマーカーの登録処理を実装します。 ラインマーカーのアイコン ラインマーカーに表示するアイコンは、デフォルトで用意されている Icon や独自に用意した画像のアイコンタイプから設定できます。 getIcon デフォルトで用意されているアイコンや任意のアイコンを以下のように設定します。 // デフォルトで用意されているアイコン設定例 override fun getIcon(): Icon = AllIcons.General.Mouse 参考:独自のアイコンを登録する 独自に用意した画像をアイコンとして使用する場合、ファイルタイプとアイコンを表すオブジェクト、クラスを用意します。 「 Doma Tools」では、カスタム言語として SQL ファイルを扱うための実装の一環で、ファイルタイプ定義を生成しています。詳細は以下公式ドキュメントをご参照ください。 カスタム言語の実装について: カスタム言語サポート 独自のア イコン画 像について: アイコンの使用 class SqlFileType private constructor () : LanguageFileType(SqlLanguage.INSTANCE) { override fun getName(): String = "DomaSql" override fun getDescription(): String = "Doma sql template" override fun getDefaultExtension(): String = "sql" override fun getIcon(): Icon = SqlIcon.FILE companion object { @JvmField val INSTANCE: SqlFileType = SqlFileType() } } 次にアイコンタイプを表すオブジェクトを定義します。 object SqlIcon { @JvmField val FILE: Icon = getIcon( "/icons/SqlFile.svg" , SqlFileType :: class .java) } 独自で用意したアイコンも同じように、 Icon 型プロパティで設定できます。 override fun getIcon(): Icon = SqlIcon.FILE ナビゲーション機能を持つラインマーカーの登録 collectNavigationMarkers では、ラインマーカーがクリックされた時に呼び出される処理ではなく、 どのような機能を持つラインマーカーを登録するか を実装します。 メソッドに渡されるファイル内のPSI要素の条件をチェックして、対象にラインマーカーを紐づけた情報を生成します。 今回は簡単に「 メソッド要素(PsiMethod)である 」という条件を満たす要素に、「 SQL ファイルにジャンプする 」ラインマーカーを生成します。 アクション機能と異なり、このプロバイダークラスは「 Java ファイルを対象に動作する」設定で登録しているため、ファイルタイプチェックの実装はしません。 collectNavigationMarkers // PsiElement型引数eの型をチェック val method = e as? PsiMethod if (method != null ){ // ラインマーカー生成処理 } 引数 e がラインマーカーの対象であることを確認後、DAOメソッドから SQL ファイルにジャンプするラインマーカーを生成します。 if (method != null ){ // 一意な要素にラインマーカーを生成するため // 要素の識別子トークンを持つPsiNameIdentifierOwnerに変換 val owner = e as? PsiNameIdentifierOwner ?: return val identifier = owner.nameIdentifier ?: return val sqlFile : PsiElement? = // アクション機能と同じように、ジャンプ先のSQLファイル情報を取得 // ラインマーカービルダーオブジェクトの生成 val builder: NavigationGutterIconBuilder<PsiElement> = NavigationGutterIconBuilder .create(icon) // getIcon()から値を取得 .setTooltipText( "SQLへ移動" ) .setTarget(sqlFile) // ラインマーカー情報を保持する引数`result`にラインマーカーオブジェクトを設定 result.add(builder.createLineMarkerInfo(identifier)) } 参考:ナビゲーション以外の機能を持たせる 上記の例では、単純に指定した対象へのファイルジャンプを行うナビゲーション処理を行っています。 アイコンクリック時にファイルジャンプ以外の処理を実行したい場合、生成するラインマーカーオブジェクトを RelatedItemLineMarkerInfo 型として作成します。 val marker = RelatedItemLineMarkerInfo( identifier, identifier.textRange, icon, { "SQLへ移動" }, getHandler(e.project, sqlFile), // クリック時処理のハンドラーを設定 GutterIconRenderer.Alignment.RIGHT, ) { // 必要に応じて関連要素のCollectiopn<GotoRelatedItem?>を返す処理を実装 } result.add(marker) クリック時に実行する処理のハンドラーは、以下のように実装できます。 private fun getHandler(project: Project ,sqlFile: PsiFile): GutterIconNavigationHandler<PsiElement> = GutterIconNavigationHandler { _: MouseEvent?, _: PsiElement? -> // 任意の処理とファイルジャンプ処理 FileEditorManager.getInstance(project).openFile(sqlFile.virtualFile, true ) } 動かしてみる ラインマーカーの表示とクリック時の挙動を確認してみましょう。 デバッグ 起動方法は前回と同じく、 デバッグ起動する で環境を起動し、DAOファイルを開いてラインマーカーの表示を確認します。 アイコンが表示されたら、クリックして SQL ファイルへのジャンプを試してみましょう! 参考:「 Doma Tools」実装コード 「 Doma Tools」の同様のラインマーカーは以下コードで実装しています。 DaoMethodProvider さいごに 今回はアクションと同じくファイルジャンプをラインマーカーで実装する方法をご紹介しました。 独自のアイコンを表示させると、「自分で作った プラグイン 感」を一層強く感じます。 ワンクリックで処理を呼び出せるラインマーカーで、いろいろな便利機能を実装してみてください! レビューも投稿していただけますと大変励みになります🙇‍♀️ Doma Tools マーケットプレイスページ 本 プラグイン は OSS として Domaコミュニティ へ寄贈されています。 不具合修正や機能要望、ディスカッションにもぜひご参加ください。 採用ページ 執筆: @terao.haruka 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
こんにちは。 製造エンジニアリング本部エンジニアリング クラウド 技術部の竹田です。 普段の業務では自動車会社様向けに CAEの クラウド 環境構築・運用保守 を担当しています。 パブリッククラウド である AWS 、Azure、OCI といった クラウド ベンダーを活用し、お客様のCAE解析業務を支援しています。 この記事では、 電通 総研に入社して3年目社員 である私がこれからエンジニアリング本部に配属される新人や、 電通 総研に興味を持っている就活生の方向けに、 部や社内の学習環境、担当業務、そして 電通 総研の強みについて紹介したいと思います。 👉 弊社CAE教材 目次 はじめに 業務をする上での学習環境と海外出張 担当案件とやりがいについて 電通総研の強み さいごに はじめに 社会人歴: 3年目 担当ソリューション: 製造業向け クラウド CAE環境の構築・運用保守業務を担当。 インフラ・ ミドルウェア 部分を主に担当しています。 私が所属する部ではCAEアプリケーションでの解析業務自体はしておらず、他部門が担当しています。 業務をする上での学習環境と海外出張 電通 総研に入社し、研修期間が終了するとそれぞれの部に配属され、業務がスタートするのですが、ほとんどの人が自分たちが扱うソリューションの知識がゼロもしくは浅い状態だと思います。そこで部に入って学習を進めていくわけですが、そちらの学習制度について共有します。 私は パブリッククラウド を扱うメンバーとして アサイ ンされましたので、まずは AWS ,Azure,OCI周りの知識を習得する必要がありました。私自身、知識習得という面で一番効率よく、又、証明にもなるものが 「資格取得」 だと考えています。 電通 総研では AWS の資格取得について、受験費用負担という制度があり(2025/06現在)、私もこの制度を使って現在資格を取得中です。現在、私はアソシエイトレベル3つ、プロフェッショナルレベル1つ、 スペシャリテ ィレベル2つの資格を 保有 しており、最終的には AWS 領域で全て(12個)の資格を取得する目標を立てています。部の中には全て取得している方が2名在籍されている他、 部のメンバー全員が資格取得に向けて積極的に取り組んでおり、切磋琢磨できる環境が整っています。 また、情報収集といったところで各ベンダーのイベントの参加も積極的に参加しており、参加できる環境にもあります。自分自身、配属後、たくさんのイベントに参加させてもらっているのですが、特に2024年12月には AWS reInventという AWS がラスベガスで開催する世界最大のイベントに参加しました。 こちらのイベントに参加し、現在の クラウド 全般のトレンドや技術知識取得はもちろんのこと、現地で日本の クラウド を扱うエンジニアにもお会いすることができ、たくさんの刺激を受けました。( AWS reInventではすべてスケールが大きくて驚きました。最終日のrePlayというお祭りでは、世界的に有名なDJであるZEDDが登場し場を盛り上げていました。) 毎日刺激を受けながら、日々成長している実感があり、 クラウド 領域に当初から興味のあった私にとっては、とても有意義な毎日を送っています。 私と一緒に AWS reInventをまわった宮崎君が 電通 総研テックブログを書いてますのでよろしければぜひご参考に。 👉 AWS reInventの雰囲気 担当案件とやりがいについて 私の担当ソリューションは 「 クラウド HPC」 という領域です。配属当初は、CAEの検証環境を構築し、受け渡す「構築業務」を担当していました。 CAEに関わる ライセンスサーバ と 計算マシン の関係性を、実際に手を動かしながら学ぶことで理解が深まりました。最近では構築業務だけでなく、設計業務の一部 も任されるようになり、実際に本番環境で稼働している システム構成の作成 を担当しました。 スケジュール通りに環境を顧客に引き渡せたことは、何より顧客の役に立ててうれしいですし、自信につながりました。 私のこの仕事に対するやりがいは、 現在もしくは将来的に世の中で走る車の設計部分で自分の手掛けた環境が何らかの形でかかわっている という部分です。これまで 電通 総研に入社して、多くの関連知識を勉強させてもらいましたが、これからもさらに知識を習得し、多くのお客様の支援を担当したいと思っています。 電通 総研の強み 最後に入社して3年目社員からみた 電通 総研の強みについて紹介します。 電通 総研の強みはたくさんあると思いますが私が強く感じる部分は 「個々人に合った裁量のある仕事ができる」 ことだと思います。案件の規模も大小様々あるので自分のレベルに応じた仕事内容を上司から アサイ ンしてもらえています。一歩一歩、できることを増やしていき、最終的に多くのことをできるようにすることは、成長という側面において、一番理想的な方法だと考えています。 さいごに 今回は新入社員・就活生向けに社内学習環境と担当案件、 電通 総研の強みについてお伝えしました。私自身3年目という身なので 電通 総研の魅力を伝えきれていないかもしれないのですが、参考にしてもらえれば幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 新卒採用サイト 執筆: @takeda.taiki 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
みなさんこんにちは。 エンタープライズ 第一本部の鈴木です。 この記事では、以前記事にまとめたFeature Flag(フィーチャーフラグ)を取り入れたアプリケーションを、勉強を兼ねて AWS 上に構築してみたため、記事にまとめていきます。 Feature Flagについては、以前私がまとめた以下のリンク先の記事を参考にしていただければ幸いです。 Feature Flagという開発手法についてまとめる 1.はじめに 2.AWS AppConfigについて 3.構築内容 3-1.アプリケーション 3-2.AWS Lambda AppConfig 4.動作検証 5.まとめ 1.はじめに 今回は、以下2つの目的をもとに簡単なデモアプリを作成しています。 Feature Flagを導入したアプリケーションを実装してみる。 Feature Flagを管理することができる AWS のサービス「 AWS AppConfig」の使用感を確認する。 また、今回構築するシステムの全体構成は以下のとおりです。 今回重要となってくるのがAppConfigという AWS のサービスです。詳しくは次章でまとめますが、Feature Flagの管理に利用しています。 また、アプリケーションはLambdaにホストします。今回の目的はFeature Flagを用いたアプリケーションを実装することであり、構成をシンプルにするため、CloudFront + Lambdaのサーバーレス構成としています。LambdaはECSなどサーバーが必要なサービスと比較して、実行回数と実行時間に応じて料金が発生するため、今回のようなとりあえず試してみるミニマムな開発では良い選択だと思います。また、アプリケーションはコンテナで作成しているため、ECSなどサーバーありのホストに移行する際も比較的容易に移行できるのもメリットです。 アプリケーションは、フロントエンド/バックエンドともにNext.jsで構築しています。主要なパッケージのバージョンは以下のとおりです。 パッケージ バージョン Next.js 15.3.1 node.js v22-slim @ aws - sdk /client-appconfigdata 3.798.0 アプリケーションはLambdaにホストしていますが、通常LambdaではWebアプリケーションをそのまま動作させることができません。しかし、 Lambda Web Adapter という AWS が提供するツールを導入することで、実装をほとんど変更することなくWebアプリケーションをLambda上で実行できるようにしています。本筋からは外れるため詳細は記載しませんが、「構築手順」に実装方法は記載しているため、参考にしてみてください。 肝心のFeature Flagは、アプリケーションから AWS SDK を用いてAppConfigから取得しています。実装や設定方法は「構築手順」に記載しています。 2. AWS AppConfigについて この章では、先ほどから登場しているAppConfigについて紹介します。 AWS AppConfigとは、Feature Flagを管理する AWS のサービスで、開発者が完全なコードをデプロイすることなく、アプリケーションの動作を迅速かつ安全に変更することができます。 2019年12月に、 AWS Systems Managerの機能の1つとしてリリースされたサービスで、もともと Amazon ・ AWS 内で使われていた仕組みを一般向けにリリースしたサービスのようです。 詳しく知りたい方は、以下のドキュメントを参照してください。 参考: AWS AppConfig とは何ですか? - aws.amazon.com Feature Flagの管理にAppConfigを利用するメリットとしては、以下が挙げられます。 Feature Flagの設定・バージョン・環境の管理を自前で実装することなく、マネージドに管理することができる。 CloudWatchと連携することで、予期せぬ動作を検知して、自動で ロールバック することが可能である。 例えば、Feature Flagのリリース後にCloudWatch のアラームがトリガーされた場合、AppConfig が自動的に変更を ロールバック するように設定できる。これにより、アプリケーション更新によるユーザーへの影響を最小限に抑えることが可能である。 Feature Flagごとにバリアントを設定することができ、特定のユーザーIDや地理的位置などの条件をもとに評価を返却することができる。また、コンテキスト値に基づいて トラフィック を分割することも可能で、 カナリア リリースやA/Bテストの実現が可能である。 バリアントについては「 マルチバリアント機能フラグの概念と一般的なユースケースを理解する - aws.amazon.com 」を参照。 Feature Flagのリリース戦略をリリース単位で指定することができる。 リリース戦略はあらかじめ AWS 側で用意している戦略( 定義済みのデプロイ戦略の使用 - aws.amazon.com )と自分でカスタマイズ可能な戦略( デプロイ戦略の使用 - aws.amazon.com )を選択することができる。 3.構築内容 3-1.アプリケーション 今回は以下の3つのFeature Flagを持つ、簡単なTODOアプリを作成しました。 isNewFeatureEnabled( true の時、「新機能が有効になっています」と文言が表示される) isTodoReorderEnabled( true の時、TODOリストを入れ替えられる機能を有効にする) isTodoMemoEnabled( true の時、メモ機能を有効にする) アプリケーションはサーバーサイド レンダリング で、Feature Flagは初期表示の際に appconfig.ts を用いてAppConfigから取得するようにしています。 // app/page.tsx import getAppConfigConfiguration from "./appconfig" ; import TodoApp from "./components/TodoApp" ; export default async function Home () { const featureFlags = await getAppConfigConfiguration(); const initialTodos = [ { id : 1 , text : "お肉を買う" } , { id : 2 , text : "ニンジンを買う" } , { id : 3 , text : "ジャガイモを買う" } , { id : 4 , text : "ルーを買う" } , ] ; return < TodoApp initialTodos = { initialTodos } featureFlags = { featureFlags } /> ; } // appconfig.ts import { AppConfigDataClient, GetLatestConfigurationCommand, StartConfigurationSessionCommand, StartConfigurationSessionCommandInput, } from "@aws-sdk/client-appconfigdata"; // エラー等でFeature Flagが取得できなかった場合のデフォルト値 const FALLBACK_CONFIG = { isTodoReorderEnabled: { enabled: false }, isTodoMemoEnabled: { enabled: false }, isNewFeatureEnabled: { enabled: false }, }; async function getAppConfigConfiguration() { try { // AppConfig クライアント用の設定値を環境変数から取得する。 const region = process.env.AWS_REGION || "ap-northeast-1"; const applicationIdentifier = process.env.APPCONFIG_APPLICATION_ID; const environmentIdentifier = process.env.APPCONFIG_ENVIRONMENT_ID; const configurationName = process.env.APPCONFIG_CONFIGURATION_NAME; // AppConfig クライアントを生成 const client = new AppConfigDataClient({ region }); // AppConfig とのセッションを作成する。 const sessionParams: StartConfigurationSessionCommandInput = { ApplicationIdentifier: applicationIdentifier, EnvironmentIdentifier: environmentIdentifier, ConfigurationProfileIdentifier: configurationName, }; const session = await client.send( new StartConfigurationSessionCommand(sessionParams) ); if (!session?.InitialConfigurationToken) { console.warn( "AppConfig セッションの初期トークンが取得できませんでした。" ); return FALLBACK_CONFIG; } // AppConfigからFeature Flagを取得する。 const configCommand = new GetLatestConfigurationCommand({ ConfigurationToken: session.InitialConfigurationToken, }); const response = await client.send(configCommand); if (!response.Configuration) { console.error( "AppConfig から構成を取得できませんでした" ); return FALLBACK_CONFIG; } // 取得した値をJSON形式に変換する。 const config = JSON.parse(response.Configuration.transformToString()); console.info("AppConfig の構成取得に成功:", config); return config; } catch (error) { console.error("AppConfig 構成取得中にエラーが発生しました:", error); return FALLBACK_CONFIG; } } export default getAppConfigConfiguration; また、アプリケーションをLambda上で動作させるにあたり Standaloneモード でビルドしています。これは、Lambdaのファイルサイズ上限(250MB)を超えないようにサイズを小さくしたり、初期化を早くしてLambdaのコールドスタートの影響をなるべく小さくしたりするために採用しています。 Standaloneモードでビルドするためには、 next.config.ts に output:standalone を追加します。 // next.config.ts import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", }; export default nextConfig; LambdaにはDockerビルドしたコンテナをデプロイし、そのコンテナを起動するようにします。 以下にDockerfileと、サーバーを起動させるために使用する スクリプト run.sh の実装例を載せておきます。 補足として、DockerfileにLambda Web Adapter用の設定をしています。 このように COPY コマンドの設定を1行付け足すことで、Next.jsのような フレームワーク を使用したWebアプリケーションをLambda上で動作させることができます。 本筋から外れるため説明は省略させていただきますが、詳しく知りたい方は以下のドキュメントを参照してください。 参考: Lambda Web Adapter でウェブアプリを (ほぼ) そのままサーバーレス化する - aws.amazon.com // Dockerfile FROM node:22-slim AS builder WORKDIR /app COPY . . EXPOSE 3000 RUN npm install && npm run build # ----------------------------- FROM node:22-slim AS runner # Lambda Web Adapter用の設定 COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter ENV PORT=3000 NODE_ENV=production WORKDIR /app COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/run.sh ./run.sh RUN ln -s /tmp/cache ./.next/cache RUN chmod +x ./run.sh RUN apt-get update && apt-get install -y bash ENTRYPOINT ["./run.sh"] # run.sh #!/bin/bash [ ! -d ' /tmp/cache ' ] && mkdir -p /tmp/cache exec node server.js 3-2. AWS この章では、AppConfigとLambdaを中心に、 AWS 環境の構築例を紹介させていただきます。 ※ CloudFrontまわりの設定は本筋から外れるため、設定内容を省略させていただきます。 Lambda はじめにLambda関数を設定します。 コンソール画面からLambdaを開き、「関数を作成」から新しい関数を作成します。 以下のように「コンテナイメージ」を選択し、ECRにデプロイ済みのコンテナイメージを選択して作成します。 次に、Lambda実行ロールへAppConfigに対するアクセス権限を追加します。 具体的には、実行ロールにAppConfigに対する権限を付与するポリシーを追加します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " AllowAppconfigForLambda ", " Effect ": " Allow ", " Action ": [ " appconfig:StartConfigurationSession ", " appconfig:GetLatestConfiguration " ] , " Resource ": " arn:aws:appconfig:<region>:<account-id>:application/* " } ] } AppConfig 次にAppConfigを設定します。 コンソール画面からAppConfigを開き、設定を作成します。 「設定オプション」を「機能フラグ」にして、「設定プロファイル名」を入力して次へ進みます。 次にFeature Flagを設定します。 以下の図のように、値を設定します。「フラグ名」はAppConfig上でFeature Flagを識別する名前で、「フラグキー」はアプリケーションから設定を取得する際のキーとなる値です。 「バリアント」については「基本フラグ」を選択していますが、「マルチバリアントフラグ」を設定すると、特定のユーザーIDや地理的位置などの条件をもとに評価を返却したり、コンテキスト値に基づいて トラフィック を分割したりすることも可能です。 最後にフラグ値をオン/オフで選択します。 必要なFeature Flagを設定したら「保存してデプロイを続ける」をクリックして、デプロイに進みます。 デプロイの画面に進んだら、まずはじめに「環境を作成」から環境を作成します。 環境を判別できる名前を付けたら「環境を作成」をクリックします。今回は「dev」としています。 デプロイの画面に戻ってきたら、「環境」に先ほど作成した環境名が設定されていることを確認します。 そして、デプロイ戦略を選択します。デフォルトでいくつかデプロイ方法が選択できますが、「デプロイ戦略を作成」から自分でデプロイ戦略を作成することができます。今回は「AppConfig.AllAtOnce」を選択します。 「デプロイを開始」をクリックしてFeature Flagの設定をデプロイします。デプロイは数秒で完了します。これでアプリケーションからFeature Flagの設定を取得できる状態となりました。 4.動作検証 構築が完了したため、動作検証します。 まずはじめに、設定したFeature Flagが全てオフ(false)の場合で確認します。 アプリケーションを起動すると「3-1.アプリケーション」で記載したフラグが全てfalseの場合と一致しています。 CloudWatchに出力されているログを確認すると、AppConfigから取得したフラグ値が全てfalseとなっていることが確認できます。 では設定を変更して、機能をリリースします。 AppConfigから、先ほど作成したアプリケーションの設定を開きます。 各フラグの値をオン(true)に変更し、「Save version」をクリックして設定を保存します。 その後に遷移する画面で、保存したバージョンの設定値が表示されていることを確認して、「デプロイを開始」をクリックします。 デプロイの画面で、環境名、バージョン、デプロイ戦略を選択して「デプロイを開始」をクリックします。 デプロイは数秒で完了します。状態が「ベーキング」となったことを確認して、アプリケーションの動作検証をします。 アプリケーションを起動すると「3-1.アプリケーション」で記載したフラグが全てtrueの場合と一致しています。 CloudWatchに出力されているログを確認すると、AppConfigから取得したフラグ値が全てtrueとなっていることが確認できます。 以上のように、アプリケーションコードのデプロイを挟まずに、AppConfigからFeature Flagを更新するだけで新機能をリリースすることができました。 5.まとめ 今回は簡単なデモアプリを作成して、Feature Flagの実装やAppConfigの使用感を検証しました。 まずFeature Flagについては、アプリケーションコードのデプロイをすることなく、AppConfigの更新のみで新機能の有効/無効を切り替えられるのはとても良いと思いました。 機能のリリースとデプロイの分離ができることで、任意のタイミングでデプロイを挟まずに機能リリースができるため、開発サイクルを早めることができると思いました。 AppConfigについては、Feature Flagのバージョン管理や環境管理が可能で、 AWS を利用している場合はとても良いと思いました。 プロジェクトで AWS を利用している場合、Feature Flagを導入する際には、選択肢の1つに含める価値は十分あるのではないでしょうか。 最後まで読んでいただきましてありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @suzuki.takuma 、レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
こんにちは。 電通 総研ITの寺尾です。 前回ご紹介した プラグイン 開発環境構築に続き、今回から実際に機能実装についてお話していきます。 本記事でご紹介する機能は アクション機能 です。 前回はこちら: IntelliJプラグイン開発の始め方~環境構築編~ アクション機能とは IntelliJ におけるアクションとは、メニューやショートカットで呼び出せる機能全般を指します。 普段 IntelliJ を使う際に、ショートカットキーによるファイル検索、Gitウィンドウでの操作、Runメニューからの デバッグ 起動などをすることはないでしょうか。 実はこれらも、 IntelliJ 上ではアクションとして実装された機能となっています。 「 Doma Tools」で実装したアクションを例に、任意のメニューに独自のアクションを追加する方法についてご説明します。 公式ドキュメント カスタムアクションの作成 実装前に知っておくこと 実装言語 公式ドキュメントでは Java コードのサンプルが提示されていますが、「 Doma Tools」ではテンプレートをベースにKotlinで実装しています。 Kotlinは Java と非常によく似た実装が可能な言語ですが、以下の特徴を持つKotlinを採用しています。 Kotlinでプラグインを開発する利点 null安全性:変数宣言時に、Null許容かを明示的に指定できます 型安全ビルダー:静的に必須パラメータをチェックし、 コンパイル が成功する状態でのみ、ビルド処理が呼び出せるようにします コールバック: IntelliJ プラットフォームが多用するコールバックを、Kotlinは ラムダ式 によって容易に実装できます 他にも保守性やパフォーマンスに効果のある機能を持ち、 IntelliJ プラグイン 開発において非常に相性の良い言語となっています。個人的にも、null安全性は開発において特に便利な点だと感じています。 これから プラグイン 開発をされる方も、Kotlinで実装してみてください! Kotlinプロジェクトに関する詳細は、 Kotlin をご参照ください。 【補足】 簡単なKotlinコードをウェブ上で実行することもできますので、この機会にKotlinにも触れてもらえればと思います! Play Kotlin PsiElement IntelliJ プラグイン の実装ではそのほとんどで PsiElement のオブジェクトを扱います。 これはファイルや ディレクト リ、ファイル内のメソッド名やパラメータなどあらゆる要素を表す基本的な型になっており、 IntelliJ 上のあらゆる要素はこの型によるツリー構造を持っています。 サブタイプも多く実際には複雑なツリー構造を持つ場合もあるため、開発を通して扱い方を覚えていきましょう。 公式ドキュメント 参考:PSIツリー構造の確認方法 デバッグ 環境では、 ツール > View Psi Structure of Current File から、PSIツリー構造を確認できます。 また PsiViewer プラグイン を導入することで、 デバッグ 環境以外でもPSIツリー構造を確認できるようになります。 実装 ゴール 今回例に挙げるアクションは、DAOファイルから SQL ファイルにジャンプする機能です。 アクションが有効なカーソル位置の判定や、ファイルジャンプまでの実装方法についても見ていきます。 事前準備 アクション機能の実装に必須なものは以下の2つになります。 AnAction のサブクラス プラグイン 設定登録 プラグイン 設定でアクションとして登録できるクラスは AnAction のサブクラスとなっており、 アクション機能の登録は plugin.xml に タグを使って登録します。 前回ご紹介した アクション機能の登録例 と同じ内容ですが再度掲載します。 actions : アクションメニューをグループ化するタグ group : アクショングループの設定。グループ名やどのメニューにアクションを追加するなどを設定 action : アクション情報を記載するタグ id : アクション機能の識別ID(任意のID) class : 実装したAnActionのサブクラスの完全修飾クラス名 text : アクション表示名 description : アクションの説明 keyboard-shortcut : デフォルトのショートカットキーを登録するタグ <actions> <group id = "org.domaframework.doma.intellij.DomaToolGroupActions" text = "Doma Tools" popup = "true" icon = "AllIcons.Nodes.AbstractClass" > <add-to-group group-id = "EditorPopupMenu" anchor = "last" /> <action id = "org.domaframework.doma.intellij.action.JumpToSQLFromDao" class = "org.domaframework.doma.intellij.action.dao.JumpToSQLFromDaoAction" text = "Jump to SQL" description = "Jump from DAO file to SQL file" > <keyboard-shortcut keymap = "$default" first-keystroke = "alt D" /> </action> </group> </actions> 準備が整いましたら、実装を始めていきましょう! 参考:Plugin DevKitを使う Plugin DevKit を使うことで、簡単に上記の下準備ができます。 ファイル > 新規 > Plugin DevKit > Action 表示制御の実装 AnAction クラスで主に使用するオーバーライドメソッドについて説明します。 紹介するメソッドの役割は、それぞれアクション表示制御、アクション実行スレッドの設定、処理本体です。 まずは表示制御から見ていきます。 update メニュー表示時やショートカット実行時、まずはこのメソッドが呼び出され、アクションの条件を満たしているかを制御します。 引数で渡される AnActionEvent 型が持つ presentation.isEnabledAndVisible を、最終的に true とすることがアクション実行条件を満たすことになります。 例えば、以下の条件をチェックするロジックを実装します。 アクションを呼び出したのが Java ファイル上か カーソル位置がDAOメソッド名の上にあるか SQL ファイルへのジャンプが必要なDAOメソッドか 以下は実装例です。ファイル情報やカーソル位置の取得は、他機能の実装でもよく使うロジックになります。 ファイルタイプの判定 アクションの呼び出しが Java ファイル上であるかを判定するため、ファイル情報を取得してファイルタイプをチェックします。 // eはupdateに渡されるAnActionEvent型引数 // アクションを呼び出したファイル情報をPsiFileオブジェクトで取得 val currentFile = e.getData(CommonDataKeys.PSI_FILE) ?: return val file: PsiFile = currentFile ?: return // PsiFileからVirtualFile型の要素を取得し、FileTypeManagerによってファイルタイプを取得 val fileType = FileTypeManager.getInstance().getFileTypeByFile(file.virtualFile) val isDaoFileType = when (fileType.name) { "JAVA" , "Kotlin" , "CLASS" -> true else -> false } カーソル位置の取得 DAOメソッドを対象にアクションを実行するため、カーソル位置の要素をチェックします。 // editorにエディタ画面情報を格納 val editor = e.getData(CommonDataKeys.EDITOR) ?: return // editor.caretModel.offsetで、エディタ画面上のカーソル位置の要素をPsiElement型オブジェクトとして取得 val element = file.findElementAt(editor.caretModel.offset) ?: return SQL ファイルへのジャンプが必要なDAOメソッドか は、実際にはメソッドに付与された アノテーション タイプなどを細かくチェックする必要がありますが、 今回は簡単に カーソル位置の要素がDAOメソッドか のチェック方法を例にします。 // 取得したelementが、メソッド要素であることをチェック val isDaoMethod = element is PsiMethod 最後に、チェックした結果を AnActionEvent のプロパティに設定して表示制御のロジックは完成です。 e.presentation.isEnabledAndVisible = isDaoMethod アクションスレッドの設定 update() をバックグラウンド(BGT)とイベントディスパッチ(EDT)のどちらのスレッドで呼び出すかを指定します。 書き込み処理など編集を加える処理はEDT、その他は基本的にBGTを設定します。 ※6/20 getActionUpdateThread() が update() に影響するメソッドであることをご指摘いただいたため、明記させていただきました。 詳細は 公式ドキュメント をご参照ください。 getActionUpdateThread 今回はファイルジャンプのみを行うためBGTで設定します。 override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT アクション処理の実装 アクションが呼び出された時の本体処理を実装します。 actionPerformed 必要な情報を再度取得してファイルジャンプ処理を実装します。 // カーソル位置のDAOメソッド要素を取得 val editor = e.getData(CommonDataKeys.EDITOR) ?: return val element = currentFile?.findElementAt(editor.caretModel.offset) ?: return // PsiMethodの子要素が取得される可能性もあるため、念のため親のPsiMethodを取得 val method = PsiTreeUtil.getParentOfType(element, PsiMethod :: class .java) ?: return val daoFile = method.containingFile.virtualFile // 以下はプロジェクトの構成情報を扱うために必要なオブジェクト val project = e.project ?: return val module = project.getModule(daoFile) // ジャンプ先のファイル情報を取得し、ファイルジャンプ ファイルジャンプのロジック ここからは少し複雑なロジックになるため、ファイル情報を取得するために必要なコアな実装例のみを記載します。 ファイルパスを取得 Doma のルール上、DAOメソッドに関連する SQL ファイルは、リソース ディレクト リ内の以下に配置されていることが前提となります。 【 SQL ファイルの配置先】 {リソースディレクトリ}/META-INF/{DAOパッケージパス}/{DAO名}/{DAOメソッド名}.sql まずは基となるDAOメソッドのファイルパスを取得します。 カーソル位置から取得した要素オブジェクトから、以下のようにファイル情報を取得できます。 // DAOメソッド名 val methodName = element.name // プロジェクトのルートパス。「{プロジェクトディレクトリ}/src/main」のようなパスを取得 val contentRoot = this .psiProject.getContentRoot(daoFile)?.path // contentRootが取得できていれば、収集した情報でSQLファイル用の相対パスを生成 val sqlFilePath = contentRoot.let { // DAOファイルの絶対パス val daoFilePath = daoFile.path // DAOファイルのパスを文字列変換し、SQLファイル用の相対パスを生成 // 例)「src/main/java/example/dao/ExampleDao.java」を変換 daoFilePath .replace( "java" , "resources/META-INF" ) .replace( ".java" , "/" ) .plus( " $methodName .sql" ) // src/main/resources/META-INF/example/dao/ExampleDao/exampleMethodName.sql } SQL ファイルを取得 リソース ディレクト リ内から 相対パス でファイルを取得する場合、 ResourceFileUtil を使用します。 以下の処理では、DAOファイルと同じモジュール内のリソース ディレクト リから、 相対パス に一致する SQL ファイルを VirtualFile オブジェクトとして返します。 第3引数は検索スコープを設定します。 getModuleScope には、テストリソース ディレクト リとしてマークされた ディレクト リを検索対象とするかのフラグを設定します。この例では対象外とするため false で設定します。 sqlFilePath は META-INF/ から始まる 相対パス です。 val targetSql = ResourceFileUtil.findResourceFileInScope( sqlFilePath.replace( "//" , "/" ), project, module.getModuleScope( false ), ) SQL ファイルへジャンプ 最後はファイルジャンプの実装です。 対象ファイルにジャンプする処理自体は非常にシンプルで、 FileEditorManager を使用して以下のように実装します。 FileEditorManager.getInstance(project).openFile(targetSql, true ) これでDAOメソッドから SQL ファイルをエディタ画面上で開くことができるようになりました! 動かしてみる デバッグ 起動して、想定通りにファイルジャンプアクションが動作するか試してみましょう。 前回ご紹介した デバッグ起動する で、 IntelliJ の デバッグ 画面を起動してください。 画面が起動したら、以下手順で動作確認してみましょう。 任意の Doma プロジェクトを作成し、DAOと SQL ファイルを実装 DAOメソッド名上の右クリックでポップアップメニューを表示 登録したジャンプアクションを選択 SQL ファイルにジャンプ もしアクションが表示されない場合や、アクションを実行してもファイルジャンプできない場合 開発画面側で ブレークポイント を設定し、DAOや SQL ファイル情報が正しく取得できているか等を確認してください。 参考: 「 Doma Tools」の実装コード 「 Doma Tools」の同様のジャンプアクションは以下コードで実装しています。 JumpToSQLFromDaoAction さいごに 今回は IntelliJ プラグイン のアクション機能実装に関するお話でした。 アクション機能は「 Doma Tools」に実装した中では個人的に比較的簡単な機能となっており、最初に実装するにはちょうど良い難易度でした。 この記事ではファイルジャンプ機能を例に挙げましたが、 SQL ファイルの生成など様々なアクションも実装できますので、「 Doma Tools」や他の プラグイン も参考にして好みの機能を追加してみてください! 今後も他機能の実装に関する記事を投稿していきますので、ぜひご覧ください。 レビューも投稿していただけますと大変励みになります🙇‍♀️ Doma Tools マーケットプレイスページ 本 プラグイン は OSS として Domaコミュニティ へ寄贈されています。 不具合修正や機能要望、ディスカッションにもぜひご参加ください。 採用ページ 執筆: @terao.haruka 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
こんにちは。 電通 総研ITの寺尾です。 前回に続き、以前ご紹介した IntelliJ 向け プラグイン 「 Doma Tools」の開発経験を基にした、 プラグイン 開発方法についてお話していきたいと思います。 前回はこちら: IntelliJプラグイン「Doma Tools」のご紹介~OSSなプラグイン開発~ まず今回は、環境構築からリリースまでの大まかな流れをご説明します。 ※Gradle、 GitHub の基本的な知識があると本記事の内容が理解しやすくなります ※記事内の GitHub リポジトリ のリンクは、本記事作成時点での最新のコミットをリンクしています IntelliJ プラグイン を作ってみよう 日本国内から マーケットプレイス にあげられている IntelliJ 向け プラグイン は、比較的少ない傾向にあります。 「 プラグイン 開発に興味はあるけど始め方がわからない」、「 マーケットプレイス に出すって難しそう」 …と感じられている方に、本記事が取っ掛かりになれば幸いです。 前提 プラグイン 開発で用意するものは以下になります。 IntelliJ IDEA(Ultimate、Community Editionのどちらか) Gitコマンドが扱える環境 開発する プラグイン の GitHub リポジトリ 1.環境構築 「 Doma Tools」はJetBrainsが用意している IntelliJプラットフォームプラグインテンプレート をベースにしています。 まずはこの リポジトリ をクローンすることで、基本的なプロジェクト構成を構築できます。 プロジェクトはGradleプロジェクトとして構成されており、ビルド、リリースを実行するためのGradleタスクや GitHub Actionsも、テンプレートに用意されています。 GitHub Actions: .github/workflows Gradleビルド スクリプト : build.gradle.kts 参考 Gradleとは? Gradle Gradle User Manual GitHub Actionsとは? GitHub Actions ドキュメント 2. プラグイン 設定 プラグイン の名前やサポートするバージョンなどを、自身の プラグイン の設定に変更します。 プラグイン の設定は以下のファイルを編集して管理します。 IntelliJ公式ドキュメント plugin. xml plugin.xml は、 マーケットプレイス 上の プラグイン 識別IDや プラグイン 名、開発元情報、 プラグイン で提供する機能を設定するファイルです。 実装をしても、このファイルへの設定を忘れると機能を利用できないためお忘れずに! 例えば、「 Doma Tools」ではアクション機能を プラグイン で提供する場合は以下のように記述しています。 actions : アクションメニューをグループ化するタグ group : アクショングループの設定。グループ名やどのメニューにアクションを追加するなどを設定 action : アクション情報を記載するタグ id : アクション機能の識別ID(任意のID) class : 実装したAnActionのサブクラスの完全修飾クラス名 text : アクション表示名 description : アクションの説明 keyboard-shortcut : デフォルトのショートカットキーを登録するタグ <actions> <group id = "org.domaframework.doma.intellij.DomaToolGroupActions" text = "Doma Tools" popup = "true" icon = "AllIcons.Nodes.AbstractClass" > <add-to-group group-id = "EditorPopupMenu" anchor = "last" /> <action id = "org.domaframework.doma.intellij.action.JumpToSQLFromDao" class = "org.domaframework.doma.intellij.action.dao.JumpToSQLFromDaoAction" text = "Jump to SQL" description = "Jump from DAO file to SQL file" > <keyboard-shortcut keymap = "$default" first-keystroke = "alt D" /> </action> </group> </actions> 拡張ポイントの実装は extensions タグ内に記述します。以下はコード検査機能の設定例です。 inspectionToolProvider : コード検査機能を登録するタグ implementation : 実装したInspectionToolProviderサブクラスの完全修飾クラス名 <extensions defaultExtensionNs = "com.intellij" > <inspectionToolProvider implementation = "org.domaframework.doma.intellij.inspection.dao.provider.SqlFileExistProvider" /> </extensions> gradle.properties gradle.properties は、ビルド スクリプト で参照されるプロジェクトプロパティを設定するファイルです。 テンプレートでは、ビルド時の プラグイン バージョンやプラットフォームバージョンの設定をこのプロパティファイルから参照します。 【補足】 一部のプロパティは plugin.xml と同様の項目を表すものがありますが、ビルド スクリプト によって gradle.properties の内容で設定が参照、上書きされます。 build.gradle.kts build.gradle.kts は、Gradleのビルド スクリプト を実装するファイルです。 プロジェクトの依存関係や、 IntelliJ プラットフォーム プラグイン のビルド、テスト、リリースに関わる設定も記載されています。 テンプレートではリリースに必要な プラグイン 情報の収集処理などが実装されており、基本的には追加の実装なしで利用することが可能です。 【補足】 Doma Toolsでは、リリースノートや CHANGELOG.md 内容の更新、次リリースの仮バージョン生成など、運用に関わる処理を build.gradle.kts と GitHub Actionsの追加実装で自動化しています。 参考:パッケージのバージョン管理 build.gradle.kts に記載するライブラリや プラグイン バージョンは、Gradleのバージョンカタログで管理することがおすすめです。 カタログファイル libs.versions.toml にまとめて記載しておくことで、依存パッケージのバージョン管理ツールが自動で最新バージョンへのアップデートを提案してくれます。 【補足】 Doma Toolsでは Renovate を採用しており、依存パッケージに最新バージョンがリリースされると、カタログファイルに記載のバージョンを更新するPull Requestを作成してくれます。 3.実装 アクションやコード補完など IntelliJ に追加したい機能を実装します。 公式ドキュメントや OSS の プラグイン コードも参考にしてみましょう。 機能によって、 IntelliJ プラットフォームで指定されたサブクラスを作成します。 継承するクラスや実装するインタフェースの一覧は 公式ドキュメント をご参照ください。 機能実装やテストに関する詳細な説明は、順次投稿していく予定です。 先に基本的な実装方法が気になる方は、 IntelliJ のサンプルプロジェクトやドキュメントをご覧ください。 IntelliJプラットフォームプラグインサンプルプロジェクト ガターアイコン機能の実装 コード補完機能の実装 参考:開発で役立つ プラグイン Plugin DevKit : IntelliJ プラットフォーム プラグイン の開発で推奨されている プラグイン です。アクション機能の実装に必要な設定やクラスを簡単に生成できるようになります。 PsiViewer : カスタム言語を使った機能を実装する際、エディタ上のファイル内の要素情報を確認する際に便利な プラグイン です。 GitHub Copilot : プラグイン 実装に限らず、ロジックの実装に悩んだ時の相談相手や リファクタリング 、ビルドまでの一貫したタスクなどの開発作業を依頼できます。 参考:コードの品質を高める プロジェクトの品質を向上させるために、コードフォーマットを統一します。 「 Doma Tools」では以下 プラグイン を使用しています。 spotless google-java-format ktlin また、 タイプミス や変数のアクセスレベルなどを検査することもプロジェクトの品質向上につながります。 IntelliJ はQodanaと連携したコード解析が可能です。詳細は 公式ドキュメント をご参照ください。 参考: OSS とコミュニティの活用 IntelliJ Platform Explorer 拡張ポイントやインタフェースを実装している OSS リポジトリ を検索できるサイトです。 JetBrains Platform IntelliJ プラットフォーム プラグイン 開発者の集うコミュニティです。 実装の悩みについては、こちらでア イデア を探してみましょう。 4.動作確認 実装した プラグイン 機能を デバッグ 環境で確認します。 デバッグ 起動する Run Plugin を デバッグ モードで起動し、開発した プラグイン をインストールした想定の仮想的な IntelliJ 環境を立ち上げます。 別ウィンドウで起動する IntelliJ 上で実際に プラグイン 機能を試し、バグ調査や動作確認が行えます。 【補足】 Run Plugin 起動設定は、テンプレートプロジェクトですでに 設定 が用意されているため、デフォルトで選択可能です。 5.(省略可) プラグイン のビルドファイルを生成する プラグイン のビルドファイルは、Gradleタスクを実行することで生成できます。 ZIPファイルの生成はリリース作業上必須ではありませんが、何らかの理由で後述のリリースができない場合や、内部的に プラグイン ファイルを共有する際の方法としてご参考ください。 ローカルで生成する ローカル環境で ./gradlew buildPlugin を実行することで、プロジェクト内の build/distributions にZIPファイルが生成されます。 生成されたZIPファイルを IntelliJ の 設定 > プラグイン 画面からインストールすることで、 プラグイン が利用可能になります。 GitHub Actionsで生成する テンプレートプロジェクトで用意されている GitHubActionsのビルドワークフロー を実行することで、 GitHub 上からも同様のZIPファイルがダウンロードできます。 テンプレートでは、Pull Request作成時、またはmainブランチにプッシュされた時にこのワークフローが実行されます。 生成されたファイルは、 GitHub リポジトリ の Actionsタブ > Build > 実行されたワークフロー > Summary を選択し、画面下部のArtifactからダウンロードします。 6. マーケットプレイス に公開する JetBrains マーケットプレイス への プラグイン リリースでは、Gradleタスク publishPlugin を実行します。 このタスクにはアカウント設定やリリース公開 トーク ンが必要になります。 ここでは、ローカルや GitHub Actionsで必要な作業のみを記載しますので、 事前にアカウント登録や トーク ン生成の詳細な手順は 公式ドキュメント を参照し、JetBrainsアカウント情報と トーク ンを作成してください。 リリース作業で用意するもの JetBrainsアカウント( マーケットプレイス に公開する プラグイン を管理するアカウント) プラグイン 公開 トーク ン プラグイン 署名用の 秘密鍵 プラグイン 署名用の証明書 プラグイン の署名情報を生成する プラグイン 署名に使用する 秘密鍵 と証明書生成コマンドは 公式ドキュメント を参照してください。 ここで生成された 秘密鍵 、パスワードなどはハードコードせず、 環境変数 として設定します。 後で確認できるように保管しておきましょう。 【補足】 文字列として登録するため、「 Doma Tools」ではGradleタスクを実装し Base64 に変換しています。 Base64に変換するためのGradleタスク実装 シークレット 環境変数 を設定 GitHub の 環境変数 設定 署名情報は非公開にして扱う必要があるため、 GitHub で扱う際は リポジトリ シークレットとして登録します。 GitHub の リポジトリ 設定の Sercrets and variables > Actions > Sercretsタブ > Repository secrets で、以下を登録します。 環境変数 名 値 CERTIFICATE_CHAIN 証明書チェーン(chain.crtを Base64 化) PRIVATE_KEY 秘密鍵 (private.pemを Base64 化) PRIVATE_KEY_PASSWORD 秘密鍵 のパスワード PUBLISH_TOKEN JetBrainsアカウントページで生成したToken ローカルでの 環境変数 設定 ローカルからリリースする場合、以下のようにGradleタスク publishPlugin の起動設定を用意し、 Enviroment variables へ GitHubの環境変数設定 と同様の 環境変数 を設定してください。 この方法で設定した 環境変数 は、起動設定と同じ /.run 内の XML ファイルに保存されます。 XML ファイルを外部に公開しないように、 リポジトリ へのプッシュ時などの取り扱いにご注意ください。 リリース公開 mainブランチへのマージ忘れがなければ、以上で準備完了です。 リリース作業は非常に簡潔で、 GitHub 上のリリースノート公開をするだけで自動で マーケットプレイス にリリースされます。 リリース処理の実体はテンプレートで用意されている GitHub Actionsとなっており、そのまま利用可能です。 「 Doma Tools」では、このワークフロー実行時に次回リリースの仮バージョンを生成するなどの実装を追加しています。 GitHub のリリース画面を開く 下書き状態のリリースノートの編集を編集する リリースノートの内容を必要に応じて編集する リリースタグを マーケットプレイス にリリースしたいバージョンと同じ名前で設定する 画面下部で Set as the latest release にチェックを入れ、 Publish release を押下 リリースノートが公開されると同時に、 GitHub Actionsの releaseワークフロー が起動します。 ワークフロー正常終了後、自身の マーケットプレイス 管理画面や IntelliJ の プラグイン 検索画面で、最新バージョンの プラグイン がアップロードされていることを確認しましょう! 【補足】 マーケットプレイス へ プラグイン をアップロードすると、JetBrainsによる審査が開始されます。 審査完了後に、正式に マーケットプレイス 画面で最新バージョンが表示されます。 その他 マーケットプレイス に関する詳細は、 マーケットプレイス公式ドキュメント をご参照ください。 参考: マーケットプレイス チャネルの使い分け JetBrainsの マーケットプレイス には チャネル という概念があります。 普段 マーケットプレイス 画面の検索で表示される プラグイン は、デフォルトの「Stable」チャネルにアップロードされているものになります。 例えば、開発者内に限定で マーケットプレイス にあげたい場合や、テスター向けのバージョンを限定公開したい場合などには、任意のチャネルにアップロードすることが可能です。 IntelliJ公式ドキュメント マーケットプレイス 画面で直接アップロードする publishPlugin を実行せずとも、 プラグインのビルドファイルを生成する で生成したZIPファイルを、 マーケットプレイス 管理画面から直接アップロードすることでもリリースが可能です。 管理画面右上の「Upload Update」ボタンを押すと、アップロードする プラグイン ファイルとチャネルの設定ダイアログが表示されます。 「 Chanel 」で任意の文字列を入力するか、既存のチャネルを選択することでアップロード先を選択できます。 一般的に、ベータ版は「beta」、先行リリース版は「 eap 」とチャネル名が名付けられます。 Gradleタスクでもチャネルを自動制御する 実はテンプレートの publishPlugin 設定でも、 プラグイン バージョンを基にリリース先のチャネルを切り替えられるようになっています。 Gradleタスク publishPlugin 実行時、 セマンティック バージョニング に従ってプレリリース識別子を取得し、識別子名と同じチャネルにリリースされます。 (プレリリース識別子がないバージョンは、デフォルトの「Stable」チャネルにリリースされます) プラグインバージョンの識別とチャネル判定処理 【例】 プラグイン バージョン:1.1.0-beta.123.0でリリースした場合 、 チャネル「beta」にver1.1.0-beta.123.0の プラグイン が登録されます。 IntelliJ 上から任意のチャネルの プラグイン を取得する 任意のチャネルにアップロードされた プラグイン は、 IntelliJ に設定を追加することでそのバージョンを参照することも可能になります。 IntelliJ 上で、 設定 > プラグイン を開き、上部の歯車アイコンから ManagePluginRepositories... を選択すると、参照したいチャネルのURLを登録する画面が表示されます。 ここに任意のチャネルのURLを追加することで、任意のチャネルにリリースされた プラグイン バージョンが使用可能になります。 さいごに 今回は プラグイン 開発の始め方ということで、環境構築からリリースまでの簡単な流れをご紹介しました。 開発を始めてから、 IntelliJ プラットフォームのルールや GitHub Actionsの動きなど分からないことがたくさんありましたが、 他の OSS やコミュニティの投稿などを参考にしていくことで段々と機能を実現していけるようになりました。 今後も「 Doma Tools」をベースにした機能実装方法について投稿していきますので、引き続きよろしくお願いいたします。 ご興味を持たれた方は、是非 プラグイン 開発に挑戦してみてください! レビューも投稿していただけますと大変励みになります🙇‍♀️ Doma Tools マーケットプレイスページ 本 プラグイン は OSS として Domaコミュニティ へ寄贈されています。 不具合修正や機能要望、ディスカッションにもぜひご参加ください。 採用ページ 執筆: @terao.haruka 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
はじめまして。 電通 総研ITの寺尾です。 本記事では、私が開発に携わったJetBrainsの IntelliJ 向けの プラグイン 「 Doma Tools for IntelliJ 」(以下、「 Doma Tools」と表記)についてご紹介します。 「Doma Tools」とは Domaとは? 主要機能紹介 ファイルジャンプ コード検査 DAOメソッドに紐づくSQLファイルチェック 未使用の引数チェック 不正なバインド変数チェック コード補完 SQLフォーマット(プレビュー機能) さいごに 「 Doma Tools」とは 「 Doma Tools」は、 IntelliJ IDEA上で Doma を用いた開発を支援するためのツールです。 IntelliJ IDEAのUltimateやCommunity Editionで動作し、 Doma のDAOや SQL に焦点を当てた便利な機能を提供します。 Doma とは? Doma は、 Java やKotlinで実装されたアプリケーションにおいて、データの取得や更新を実現する OSS の フレームワーク です。 詳細は、公式のドキュメントをご参照ください。 Doma 主要機能紹介 ver1.0.0時点での「 Doma Tools」のメイン機能の一部について紹介させていただきます。 その他の機能については、 プラグイン リポジトリ の README をご参照ください。 ファイルジャンプ 大きなプロジェクトになると、DAO名から対応する SQL ファイルを探す作業がかなりの手間になります。 似たようなメソッド名が多いとなおのこと。 IntelliJ の検索アクションを利用しても開くファイルを間違えてしまうかもしれません😭 「 Doma Tools」ではアクションとガターアイコンにより、一発でDAOインタフェースファイルと SQL ファイルを相互移動できるようにします。 DAOメソッド名上でメニューを開き、 Doma Tools > Jump to SQL を選択、またはショートカットキーで対応する SQL ファイルを開くことができます。 DAOメソッドの行に表示されるガターアイコンをクリックすることでも、 SQL ファイルにジャンプ可能です。 その逆も然り、 SQL ファイル上でも同様にアクションやガターアイコンからDAOメソッド定義箇所にジャンプすることができます。 コード検査 ビルド時に初めて Doma にエラーを出されてしまい、地道な修正作業が始まる……。 コーディング時に問題箇所が分からないとストレスになることはないでしょうか。 この プラグイン では Doma のルールに違反している箇所をエラーハイライトし、コーディングしながら問題箇所も発見できるコード検査機能を提供します。 DAOメソッドに紐づく SQL ファイルチェック DAOメソッドに対応する SQL ファイルが存在しない場合、メソッド名が赤文字でハイライトされます。 同時にクイックフィックスが提案され、ワンクリックで対応するリソース ディレクト リに SQL ファイルが生成されます📝 未使用の引数チェック SQL 内で使用されていないDAOメソッドパラメーターをエラーハイライトし、実装漏れを発見できるようになります。 不正なバインド変数チェック SQL ファイル内で、DAOメソッドのパラメーターにないバインド変数や、存在しないフィールド、メソッドの呼び出しなどをエラーハイライトします。 コピペしたままで混入した、誤った変数名やクラス名も即座に発見できます🔍 コード補完 SQL 内でバインド変数のコーディングを行う際、「こんな変数名だったっけ…」となりDAOメソッドやEntityクラスの定義を見に行くことはないでしょうか? コード補完機能により、DAOメソッドのパラメーターから変数名候補を提供します。 また、パラメーターの型に基づいたフィールド、メソッドも補完されます。 静的フィールド、メソッドへのアクセス でも同様に、クラス名からstaticなプロパティの補完までサポートします。 SQL フォーマット( プレビュー機能 ) フォーマットを統一することが大事なことは分かっていても、外部のフォーマットツールの選定は大変…。ということもあるでしょう😔 「 Doma Tools」でも、 SQL フォーマットルールを統一できる機能の提供を目指しています。 こちらはまだプレビュー版となります。現在は基本的なクエリのフォーマットをサポートしています。 SQL キーワードをレベル分けし、レベルやキーワードの組み合わせによって改行、インデントルールを指定しています。 そのほか、複数のカラム行が見やすくなるような改行や、サブクエリグループを視覚的に判別しやすくなるようなフォーマッターの開発を進めています。 さいごに 初めての プラグイン 開発、そして初めてのKotlin実装でしたが、無事リリースができたことを嬉しく思います。 プラグイン 開発において課題解決に試行錯誤を重ね、実装だけでなく OSS とすることを意識した今回のプロジェクトからは、 技術者として多くの学びを得られました😊 今後は「 Doma Tools」の開発経験を基にした、具体的な プラグイン 機能実装についてもご紹介していきますので、引き続きご覧いただけますと幸いです。 これからも多くの Doma プロジェクト開発者の貢献に繋がるようアップデートを重ねていきますので、 IntelliJ + Doma を使った開発をされている方はぜひご活用ください! レビューも投稿していただけますと大変励みになります🙇‍♀️ Doma Tools マーケットプレースページ 本 プラグイン は OSS として Domaコミュニティ へ寄贈されています。 不具合修正や機能要望、ディスカッションにもぜひご参加ください。 採用ページ 執筆: @terao.haruka 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
こんにちは、スマート ソサエティ センターの飯田です。 はじめに NotebookLMでは、文章をアップロードすると、 マインドマップ 形式で内容を可視化することができます。 この機能により、情報の整理や関連性の把握が容易になります。 デジタル庁が公開している ChatGPT 等の生成 AI の業務利用に関する申合せ(第2版) を入力してみると下記のような結果が得られます。 NotebookLMでこのような見せ方ができるということは、Geminiでもナレッジグラフ生成ができるのでは?と思い、Geminiをつかって、どこまでナレッジグラフを抽出できるかを試してみてみました。 そして、抽出した結果をGraphRAGに入力し、どのような情報が得られるかを確認してみました。 ※プリミティブな実装しかしていないので、本当はもっと性能がでるかもしれないですが、ご容赦ください。 対象のデータ 対象のデータは、デジタル庁の「ChatGPT等の生成AIの業務利用に関する申合せ(第2版)」です。 このドキュメントは、 自治 体における生成AIの業務利用に関する ガイドライン を提供しており、AIの利用に関する重要な情報が含まれています。 このドキュメントをもとに、ナレッジグラフを抽出し、GraphRAGでの情報検索を行います。 全体の処理の流れ 流れとしては以下のような流れです Geminiを使用してドキュメントからナレッジグラフを抽出 抽出したナレッジグラフを可視化・結果の確認 抽出したナレッジグラフをGraphRAGに入力 GraphRAGを使用して情報検索を行い、関連情報を取得 ナレッジグラフの抽出 ナレッジグラフの抽出には、GraphRAGに繋げることも意識して、langchainのlangchain_experimental.graph_transformersを使ってみました。 langchainにGraphRAGの機能が追加されているのを知りませんでした。 便利になりましたね。 実装の一部としてはこんな感じです llm_transformer = LLMGraphTransformer(llm=llm) graph_documents = llm_transformer.convert_to_graph_documents(documents) 抽出結果 graph.draw_ graphviz でグラフの可視化をすると下記のような結果が得られました。 いい感じに抽出できていそうです。 また、ネットワークとしては、下記のような形になっています。 [( 'Agreement On Business Use Of Generative Ai (2Nd Edition)' , 'Digital Society Promotion Council Secretariat' , 'ISSUED_BY' ), ( 'Agreement On Business Use Of Generative Ai (2Nd Edition)' , 'Generative Ai' , 'CONCERNS' ), ( 'Agreement On Business Use Of Generative Ai (2Nd Edition)' , 'Government Ministries And Agencies' , 'FOR' ), ( 'Agreement On Business Use Of Generative Ai (2Nd Edition)' , 'Ai Strategy Council' , 'BASED_ON' ), ( 'Agreement On Business Use Of Generative Ai (2Nd Edition)' , 'Ai Strategy Team' , 'BASED_ON' ), ( 'Chatgpt' , 'Generative Ai' , 'IS_A' ), ( 'Chatgpt' , 'Contract-Based Cloud Service' , 'CLASSIFIED_AS' ), ( 'Government Ministries And Agencies' , 'Contract-Based Cloud Service' , 'MANAGES' ), ( 'Contract-Based Cloud Service' , 'Confidential Information' , 'CANNOT_HANDLE' ), ( 'Contract-Based Cloud Service' , 'Government Unified Standards' , 'DEFINED_BY' ), ( 'Shadow It' , 'Information Leakage' , 'INCREASES' ), ( 'Institutions, Etc.' , 'Government Unified Standards' , 'MENTIONED_IN' ), ( 'Institutions, Etc.' , 'Security Measures' , 'RELATED_TO' ), ( 'Institutions, Etc.' , 'Data Handling' , 'RELATED_TO' ), ( '約款型クラウドサービス' , '要機密情報' , 'CANNOT_HANDLE' ), ( '約款型クラウドサービス' , '要機密情報を含まない情報の取扱い' , 'USED_FOR' ), ( '生成Ai' , '約款型クラウドサービス' , 'CAN_BE_USED_VIA' ), ( '生成Ai' , '個別契約等に基づく利用' , 'CAN_BE_USED_VIA' ), ( '個別契約等に基づく利用' , 'クラウドサービス関連規程' , 'REQUIRES_COMPLIANCE_WITH' ), ( '個別契約等に基づく利用' , '機密性2情報' , 'CAN_HANDLE' ), ( '個別契約等に基づく利用' , '機密性2情報取扱いのための措置' , 'REQUIRES' ), ( '個別契約等に基づく利用' , 'Ai戦略チーム' , 'REQUIRES_UNDERSTANDING_FROM' ), ( '個別契約等に基づく利用' , '組織の規程' , 'REQUIRES_APPROVAL_BY' ), ( '個別契約等に基づく利用' , '要機密情報を含まない情報の取扱い' , 'USED_FOR' ), ( '職員等' , '約款型クラウドサービス' , 'SHOULD_BE_INFORMED_ABOUT' ), ( '職員等' , '要機密情報' , 'SHOULD_BE_INFORMED_ABOUT' ), ( '職員等' , 'クラウドサービス' , 'USES' ), ( '職員等' , '定型約款や規約等' , 'REVIEWS' ), ( '職員等' , '利用申請の許可権限者' , 'APPLIES_TO' ), ( '関係省庁' , '個別契約等に基づく利用' , 'CONSIDERS' ), ( '関係省庁' , 'Ai戦略チーム' , 'OBTAINS_UNDERSTANDING_FROM' ), ( '要機密情報を含まない情報の取扱い' , '組織の規程' , 'REQUIRES_APPROVAL_BY' ), ( '要機密情報を含まない情報の取扱い' , 'Ai戦略チーム' , 'DOES_NOT_REQUIRE_REPORT_TO' ), ( '統括情報セキュリティ責任者' , '運用規程' , 'ESTABLISHES' ), ( '運用規程' , 'クラウドサービス' , 'GOVERNS_USAGE_OF' ), ( 'クラウドサービス' , '要機密情報' , 'CANNOT_HANDLE' ), ( '利用申請の許可権限者' , 'クラウドサービス' , 'APPROVES_USAGE_OF' ), ( '利用申請の許可権限者' , 'クラウドサービス管理者' , 'APPOINTS' ), ( 'クラウドサービス管理者' , '適切な措置' , 'TAKES' ), ( '適切な措置' , '安全な利用' , 'ENSURES' ), ( '安全な利用' , '要機密情報を取り扱わないクラウドサービス' , 'OF' )] この得られたグラフの情報をつかって、LangChainのGraphQAChainをつかって質問をしてみます。 下記のような実装のイメージです # グラフとLLMを使ってQAチェーンを作成 chain = GraphQAChain.from_llm( llm=llm, graph=graph, verbose= False ) question = input ( "質問を入力してください " ) result=chain.run(question) print (f " \n [回答]: {result}" ) その結果は下記の通りです。 このケースでは、そこそこいい感じに取れていますね。 ChatGPTについて詳しくまとめてください [回答]: 提供された知識に基づくと、ChatGPTについては以下のことがわかります。 ChatGPTはGenerative Aiです。 ChatGPTはContract-Based Cloud Serviceに分類されます。 サンプルを実装したときの感想 GraphRAGは数年前に話題になってから気になっており、ようやく触れたというカタチなのですが、 実装はLangChainのおかげて、シンプルに実装できたと思います。 ただ、プロダクションに投入するか?と言われると、このまま持っていくのは難しそうという印象を受けました。 1つは、処理時間の問題。ネットワークのグラフ構造を抽出する部分は、処理に時間がかかりました。 また、質問に対する精度についても、検索結果にヒットしないことも多く、一般的なRAGを超えるようにするには使い方とデータの与え方が難しいなという感想です。 グラフデータベースに慣れていないというのもあり、一般的なRAGに比べると、”検索”で何をどう検索しているかの感覚が、通常のデータベースと異なってくるので、直感的な理解が難しいなとも思いました。 うまく、GraphRAGを使いこなせるようになれるといいなと思いました。 おまけ シンプルにLLMにプロンプトで指示するだけでもある程度のグラフ構造を取得できます。 情報の理解や、グラフ構造による分析をしていくには、Geminiで前処理をしてしまうのは、 コストパフォーマンスがよくグラフ情報の抽出ができるかなと思うので、ナレッジグラフの抽出はどこかで使えたらいいなと思っています。 以下は、Geminiアプリの canvas でネットワークを可視化したもの 最後まで読んでいただき、ありがとうございました。 ↓ のスターを押していただけると嬉しいです。励みになります。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @iida.michitaka7b3a3dd24e9c454b 、レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
はじめまして。製造エンジニアリング本部 SPDM技術部の冨永です。 本記事では、2024年4月に新卒で 電通 総研に入社してから約1年、部署に配属されてから半年が経過した私の現在の業務内容や働き方を中心に紹介したいと思います。あくまで私が所属している部署の話にはなりますが、読者の皆様には本記事を通して、 電通 総研での業務内容や働き方について少しでもご理解いただければ幸いです。 1. 電通総研の志望理由と入社の決め手 2. 現在の業務について 2.1. 業務内容 2.2. 業務のやりがいや難しさ 2.3. 今後の野望 3. 働き方 3.1. 1日のスケジュール 3.2. こだわり 4. 最後に 1. 電通 総研の志望理由と入社の決め手 本題に入る前に私が 電通 総研(当時ISID)を志望した理由をお話したいと思います。私が就活生時代に掲げていた「就活の軸」は以下2点でした。 ① 顧客業務の効率化に寄与する仕事をして、組織全体の持つ力を最大限に引き出し、社会の変革の一端を担う当事者になりたい。 ② 自分らしく、そして楽しく働きたい。 特に②は譲れない軸でした。仮に20-60歳まで働き、90歳まで生きるとした場合、その中で仕事が占める割合は意外と1~2割程度らしいですが、平日のほとんどを仕事に費やすことになります。だからこそ、自分らしく、楽しく働ける環境が自分の人生を充実させるために重要だと考えていました。そして、個人的に②に関しては 電通 総研でしか実現できないと感じました。というのも、就活生時代に 電通 総研主催の社員座談会に参加したのですが、その座談会の場で1人の社員の方が「 電通 総研の社長になりたい。」と本気で語られていたことや、社員の皆さんが年次関係なく楽しく交流されている姿を見て、「 電通 総研であれば夢を持って仕事ができそう!」とか、ほかの会社とは違う雰囲気(おそらく人間魅力)を感じました。就活期間中、他社から内定をいただき悩みましたが、自分が楽しく働いている姿を最も強くイメージすることができたこともあり、 電通 総研への入社を決意しました。 2. 現在の業務について ここでは私の現在の業務について紹介します。 2.1. 業務内容 現在は、製造エンジニアリング本部SPDM技術部で製造業のお客様向けに、 電通 総研のSPDM(Simulation Process and Data Management)製品である「 i-SPiDM(アイ・エスピーディーエム) 」の導入支援に携わっています。昨今、市場競争の激化や顧客ニーズの多様化、労働者不足など、製造業を取り巻く環境がより一層厳しくなっており、製品 開発プロセス のさらなる効率化とリードタイムの短縮が強く求められています。i-SPiDMは製品開発における検証業務の効率化と品質向上に寄与することができ、現在多くの製造業のお客様にご利用いただいている製品となっています。部に配属されてから約6ヵ月が経過し、自動車 OEM をはじめとした様々な製造業のお客様向けに、案件関係メンバと顧客業務の ヒアリ ングを行い、業務プロセスに適したi-SPiDM運用方法の検討および提案を実施しています。また、i-SPiDMのプリセールス(製品紹介や製品デモ)も実施するなど、技術者として手を動かすだけでなく、お客様と直接会話をするような場面にも多く携わっています。 2.2. 業務のやりがいや難しさ 様々な製造業のお客様へi-SPiDMという製品の導入を通して、私の軸でもあった「変革の一端を担う当事者になりたい」その思いを実現できており、大きなやりがいを感じています。同じ製造業であってもお客様毎に業務フローは異なっており、異なるニーズに応じたカスタマイズや、抜け漏れない要件の抽出など、日々新しい課題に取り組むことは非常に難しくもありますが、刺激的な毎日を送ることができていると感じています。 2.3. 今後の野望 私の今後の野望は “SPDMツールのスタンダードをi-SPiDMにする” です。 i-SPiDMはデータ管理や解析ジョブ管理機能を持ち、検証作業を効率化する強力なツールです。私自身、大学の研究室でデータ管理や解析ジョブ管理に課題感を持っていました。深夜にシミュレーションを実行するためだけに研究室に行き、翌朝シミュレーション結果を取得する。そんなブラックすぎる時間を過ごしていた時もありました。私の事例は極端かもしれませんが、現在でも、多くの企業や大学の研究室で大なり小なり同様の課題があると認識しています。なので、i-SPiDMの利用によって検証業務に携わられている全ての方々に非効率な作業から解放されてほしいと思っています。 3. 働き方 ここでは私の現在の働き方を紹介します。 3.1. 1日のスケジュール 以下は、私の出社した日のスケジュールの一例です。 9:30 出社:メールチェックやその日のタスクの確認を行います。 10:00 - 12:00 顧客会議:お客様との打ち合わせを行います。i-SPiDM運用案などをお客様にご提示し擦り合わせを実施します。 12:00 - 13:00 昼食:同僚と一緒にランチを楽しみながら、リフレッシュします。 13:00 - 14:00チームMTG:お客様との会議で得られた情報をチームメンバと整理します。 14:00 - 15:00 事前会議:別日に実施されるお客様との会議に向けて、事前に アジェンダ 検討や打ち合わせのゴールを明確にします。 15:00 - 16:00顧客会議:お客様向けにi-SPiDMプリセールスを実施します。 16:00 - 16:15 休憩 :(コンビニでおやつを買って糖分補給…) 16:30 - 17:30 社内定例:私が所属するユニットでは新人主催で合同勉強会を隔月で実施しています。その打ち合わせをユニットの同期メンバと行います。 17:30 翌日の作業確認:翌日の予定・作業を確認します。 18:00 退勤: この日は会議が多めの1日でした。社内または顧客会議が多くある日は頭を切り替えるのが大変です。(^^; 3.2. こだわり 私自身、面直の会話が好きなので、出社の頻度が高め(Ave. 週4出社)です。少し前時代的考えなのかもしれませんが、対面でのコミュニケーションは話し相手の細かなニュアンスや表情を読み取ることができ、より深い理解を得ることができると感じています。また、オフィスでの雑談やランチタイムの交流も、仕事のモチベーションを高める大切な要素です。帰宅後は手帳に残タスクの書き出しやタスクの優先順位付けを行っています。脳は多くのエネルギーを消費するため、省エネな性格だとか何かの本で読んだ記憶があります。なので、無理に頭の中で整理しようとはせず、書き出してビジュアル化することを心掛けるようにしています。 4. 最後に ここまで読んでいただいた皆様、お付き合いいただきありがとうございました。 To:社外の方 この記事を通じて、私の現在の業務内容や働き方を少しでもお伝えでき、 電通 総研について理解を深める一助になることができましたら幸いです。 To:社内の方 電通 総研に入社し1年、素敵な147名の同期と先輩方に出会い、この上なく恵まれた環境で仕事ができることに日々感謝しています。これからも楽しみながら自己成長を続け、会社に貢献できるよう努力してまいりますので、今後ともどうぞよろしくお願いいたします!! 執筆: @tominaga.ds 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
スマート ソサエティ センターの飯田です。 突然ですが、今回のエントリーでは、 コードを1行も書かずに、ア イデア を形にする衝撃 をお伝えします。 前回のブログ でお見せしたGeminiと音声通話を連携させるデモ実装、実は ソースコード を1行も書いていません tech.dentsusoken.com デザインのクオリティも問題ない、さらにはGeminiのような生成AIの組み込みまでが、コーディング作業なしにできちゃう世界になってきてます。 これまでは、「こんなサービスが欲しい」というア イデア を思いつくと、下記のようなステップで最初のMVP(Minimum Viable Product)を制作していました。 Figma でのUIデザイン まず Figma を使い、画面遷移や見た目のイメージを具体化(いわゆる「紙芝居」) 価値の概念実装 「紙芝居」だけでは伝えきれないサービスの核心的価値、特にAIによる分析の「凄さ」などは、別途、概念実証(PoC)のための簡易的な実装を行う 新規事業開発において最も重要なのは「いかに早く仮説を構築し、顧客からのフィードバックを得るか」、 つまり「いかに早く失敗し、学習するか」だと考えています。このサイクルを高速化することこそ、成功の鍵を握ります。 私自身、プログラミング実装のスキルは持っていますが、得意ではなく、決してスピーディーとは言えません苦笑 そのため、このプロセスには、それなりの時間と労力が必要でした。 それが、Firebase Studioの登場により、AIがコーディングをしてくれるようになり、実装の時間が大幅に短縮されました。 Firebase Studioとは何か? 何ができるのか? FirebaseStudioは2025/4月に Google が発表した クラウド ベースのエージェント開発環境です。 2025/6/1現在、Firebase Studio はプレビュー版ですので、ご注意ください https://firebase.google.com/docs/studio?hl=ja ざっくりの使い方は以下のとおりです アプリのベースとなる機能や実現したいことをプロンプトで入力する 機能やデザインのトンマナ、使用する フレームワーク などの確認があるので、問題なければ作成をする 修正や機能を加えていく。右下のプロンプトで指示を入れていきます UIの言語を英語から日本語に変更したい場合の例 AIが理解してくれて、 ソースコード の修正を行ってくれます。 どこにどういう変更をいれたのかも教えてくれます タブからエレメント指定モードにすれば、特定の要素に対する指示もできます ちなみに、GeminiはGemini API を入力をすれば、すぐに連携してくれます 右上のPublishボタンをおすと、新しくFirebaseプロジェクトが作られて、AppHostingにデプロイされます 参考:FirebaseStudio公式のプロンプトテクニック  https://firebase.google.com/docs/studio/prompting?hl=ja 「FirebaseStudio」の魅力 ノーコード/ローコードでのUI構築 Figma のようなデザインツールでUIを設計する感覚で、あるいはそれ以上の速さで、実際に動作するフロントエンドを構築できます。 デザインの再現性も高く、プロトタイプの段階から高品質なユーザー体験を提供可能です。 Gemini AIのシームレスな統合 Geminiをはじめとする生成AIモデルを、複雑なコーディングなしにアプリケーションに組み込むことができます。これにより、これまで概念実証に留まっていたAI活用ア イデア も、手軽に実サービスに近い形で検証できるようになります。 開発サイクルの劇的短縮 上記の特徴により、ア イデア の着想から実際にユーザーが触れるプロトタイプ完成までの時間が圧倒的に短縮されます。これは、「早く失敗し、早く学ぶ」というリーンな 開発プロセス を強力に後押しします。 FirebaseStudioがもたらす、MVP開発の パラダイム シフト このMVPの検証を回すための時間と苦労が、「FirebaseStudio」によってガラリと変わりました。 FirebaseStudioによって、 Figma でUIデザインを描くよりも速く、かつ、実際に動作するAI組込みのプロトタイプが完成してしまう のです。 MVPやプロトタイプの開発のあり方そのものが、根本から変わるほどの インパク トを感じました。 ア イデア の着想から検証までのリードタイムが劇的に短縮され、動くWebアプリを顧客に提示でき、より多くの挑戦と、より質の高い フィードバックループ ができます。 今後、重点的に検証していきたいポイント ただし、MVPで終わらず、プロダクションコードにそのままもっていけるか?その実用性を確かめるためには、さらに以下の点を深掘りして見ていく必要があると考えています。 生成されるコードの品質 ノーコード/ローコードで生成されたコードが、どの程度の品質と保守性を備えているのか。 データベースとの連携 実際のアプリケーション開発に不可欠なデータベース(Firebase Realtime DatabaseやCloud Firestoreなど)との連携は、どの程度スムーズに行えるのか。 プロダクション利用へのハードル 作成したプロトタイプを、実際の製品コードとして利用していく際に、技術的な障壁や制約がないか。 ご注意 ログイン認証とかをそのまま指示してしまうと、 JavaScript に埋め込む方式で実装してくる可能性もあるので注意してください これらの検証を通じて、「FirebaseStudio」が持つ真のポテンシャルを見極めていきたいと考えています。 思考が即座に形になる未来 最後に、私が「FirebaseStudio」とGeminiの組み合わせで最も感動した体験を少しだけ。 それは、最初のプロンプトもGeminiに考えてもらい、編集もさらに 音声入力 で『ここのボタンの色を赤に変えて』『このリストに検索機能を追加して』といったプロンプトをいれていくという開発スタイルです。 アプリ開発 を行うときに、「なんかこんな感じのモノ」が欲しいというように、イメージはあるが 言語化 できないということがあると思います。その 言語化 もGeminiがサポートしてくれますし、音声入力を使うことで、頭の中にあるア イデア がリアルタイムに具現化されていく開発体験になりました。 「アプリを実装している」感覚ではなく、 「どういうアプリがいいかを議論していたら、そのまま議論のアプリが作られてた」みたいな感覚 です。 「FirebaseStudio」は、新規事業開発の「 自由の翼 」を与えてくれる存在だなぁと思いました。 最後まで読んでいただき、ありがとうございました。 ↓ のスターを押していただけると嬉しいです。励みになります。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @iida.michitaka7b3a3dd24e9c454b 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
はじめに スマート ソサエティ センターの飯田です。 Google の生成AI「Gemini」を音声通話と組み合わせ、電話対応の負担を抑えるプロトタイプを作ってみました。 本記事では、その取り組みと、そこから見えてきた生成AI活用の具体的な可能性についてご紹介します。 背景:深刻化する電話業務の負担と、DXによる解決への期待 顧客からの理不尽な要求や迷惑行為は、近年深刻な社会問題となっています。 従業員の精神的な負担増大や 離職率 の上昇を引き起こし、企業経営にとって看過できない問題となっています。 このような状況を受け、各企業には電話業務軽減への具体的な対策強化が急務となっています。 厚生労働省 も対策の重要性を指摘し、「カスタマーハラスメント対策企業マニュアル」を公開するなど、社会全体での取り組みが進められています。 (参考: 厚生労働省 カスタマーハラスメント対策企業マニュアル ) Geminiを音声コミュニケーションと連携させ、電話対応業務の負荷を下げるプロトタイプを2つ作ってみました! 開発したプロトタイプ1:Geminiと電話で会話できるシステム デモはこちら https://youtube.com/shorts/sWuTqs8g_gU 電話システムとGeminiを直接連携させたプロトタイプを開発しました。 電話応対業務は 感情労働 の側面が強く、AIによる一次対応やオペレーター業務の一部代替は、 従業員の負担軽減と業務効率化に繋がるといいなという想いでつくりました。 アーキテクチャ : このシステムの構成はシンプルで、以下のような形です 電話回線: ユーザーからの電話 Twilio: 電話回線とWebアプリケーションを接続するためのPaaS。着信処理や音声ストリームの制御。 今回は TwiML Bin を利用。 https://www.twilio.com/ja-jp WebSocket (WS): Twilioとバックエンドアプリケーション間でリアルタイムな双方向通信を確立。 バックエンドアプリケーション ( Python on ngrok): WebSocket経由でTwilioから音声データを受け取り、Gemini API へ連携。開発段階では、ローカル環境を外部公開するために ngrok を使用。 Gemini: 受け取ったテキストに基づき、自然な応答メッセージを生成し、 音声合成 (別途実装想定)を通じてユーザーへ返す。 技術的考察と今後の課題: 実装してみたところ、現時点では、Geminiのリアルタイム性を最大限に活かすための Gemini LIVE API などの連携を完全に最適化できないので、若干の応答遅延が発生するケースが見られました。 また、UXの観点からも、より自然でスムーズな対話を実現するためには改善の余地があります。 TwilioからWebSocket経由で音声データを受け取るまでの 区間 にも遅延要因は存在するため、 システム全体のレイテンシを低減するには、ネットワーク設計を含めた総合的な最適化が求められるでしょう。 これらの課題解決は、実用的なAI電話応対システムを構築するうえで重要なポイントとなると思います。 また、以下のようなところも工夫の余地がありそうです RAG (Retrieval Augmented Generation) の活用: 社内マニュアルや過去の対応事例といった独自の知識ベースをGeminiと連携させることで、より文脈に即した的確な深刻度判定や、オペレーターへの具体的な対応アド バイス 生成など、高度な機能実装が期待できます。 Context Caching の最適化: 対話の文脈情報を効率的にキャッシュし再利用することで、Geminiへのリク エス トを最適化し、特にリアルタイム性が重視される検知システムにおける応答速度の向上に寄与する可能性があります。 開発したプロトタイプ2:リアルタイムのオペレーター負担検知Webアプリケーション youtu.be オペレーターと顧客の会話をリアルタイムで分析し、職員負担の兆候を即座に検知するWebアプリケーションも作ってみました。 このシステムは、従業員の保護を強化するとともに、応対品質の向上にも寄与することを目的としています。会話内容からGeminiがオペレーターの負担の深刻度や原因を推論し、オペレーター支援や早期対応に繋がります。 主な機能と特徴: リアルタイム 音声認識 とテキスト化 ブラウザのマイクを通して入力された音声をリアルタイムでテキストに変換。 Geminiによる深刻度判定: 変換されたテキストをGeminiに送信し、会話内容に基づいて「深刻度」と「原因の分類」を推論。状況の客観的な把握を支援。 アラート通知: オペレーター判定の深刻度が高い場合には、管理画面や担当者にアラートを通知し、迅速な介入や エス カレーション。 アーキテクチャ : このWebアプリケーションは、主にフロントエンド技術で構築されており、処理フローは以下のとおりです。 音声取得: ブラウザの Web Speech API を利用してユーザーの音声をリアルタイムに取得。 音声認識 (Speech-to-Text): 同じく Web Speech API の機能で、取得した音声をテキストデータに変換。 負担度判定: テキストデータをGemini API に送信し、負担度の判定(深刻度、原因分類など)をリク エス トします。この際、プロンプトには定義や判断基準となる事例をFew-shotとして含めることで、判定精度の向上を図っています。 結果表示とアラート: Geminiからのレスポンスに基づき、負担度の判定結果を画面に表示。一定以上の深刻度と判定された場合には、アラートを発する仕組み。 まとめ:音声通話とGeminiが開くDXと、より良い顧客コミュニケーションの未来 本記事で紹介したプロトタイプは、音声コミュニケーションとGeminiをはじめとする生成AI技術の組み合わせが持つワクワク感を伝えられたと思います。 顧客コミュニケーション基盤のDXを推進し 、同時にオペレーターの負担対策という喫緊の課題にも対応できる大きな可能性が見えてきました。 同様の課題認識を持つ方々や、新たな技術活用を模索されている方々にとって、何らかの参考となれば幸いです。 従来は 定量 化や即時対応が難しかったコミュニケーションの内容や感情の機微を、Geminiがリアルタイムに分析・評価することで、 電話応対業務の大幅な効率化、従業員の負担軽減、そしてデータに基づいた 顧客満足度 の向上 といったDXの具体的な成果が期待できます。 さらに、様々な社内システムや顧客データと連携することで、 業務プロセス全体の変革や、新たな顧客価値の創出 など、DXのさらなる深化が期待できるのではないでしょうか。 音声コミュニケーションは、AIとの融合によって、よりインテリジェントで価値あるものへと進化を遂げるでしょう。 皆さんは、Geminiのような生成AIと音声通話を組み合わせることで、どのような業務変革や新しい価値創造が生まれると考えますか?ぜひ、ご意見やア イデア をお聞かせください! 最後まで読んでいただき、ありがとうございました。 ↓ のスターを押していただけると嬉しいです。励みになります。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @iida.michitaka7b3a3dd24e9c454b ( Shodo で執筆されました )
こんにちは。クロス イノベーション 本部 クラウド イノベーション センターの柴田です。 この記事では Visual Studio Code で GitHub Copilot の agent mode と Remote Development を併用した場合に MCP サーバーがどこで実行されるのかをご紹介します。 はじめに GitHub Copilot とは Remote Development とは 前提条件 MCP サーバーはどこで実行されるのか おわりに 参考資料 はじめに GitHub Copilot とは GitHub Copilot は GitHub が提供するコーディングアシスタントです。 AI を活用してコードの補完や生成など様々な機能を提供します。 version 1.99 からは MCP クライアントとしても利用できるようになりました。 Remote Development とは Remote Development は Visual Studio Code の 拡張機能 の 1 つです。 ソースコード を開いたりコマンドや 拡張機能 を実行したりといった Visual Studio Code の各種操作を、ローカル環境と同じようにコンテナ・リモートマシン・ Windows Subsystem for Linux などのリモート環境上で行うことができます。 1 前提条件 この記事は以下の環境を前提とします。 Visual Studio Code version 1.100.2 Remote - SSH version 0.120.0 GitHub Copilot version 1.322.0 この記事の内容の動作確認は Remote Development のうち Remote - SSH でのみ行っています。 MCP サーバーはどこで実行されるのか GitHub Copilot の agent mode と Remote Development を併用した場合に MCP サーバーはどこで実行されるのでしょうか。 microsoft/vscode#243687 には以下のようにコメントされています。 VS Code will run the tool wherever it is configured. If it is configured in your user settings, then it will run on the VS Code 'host'. If they are configured in your remote settings or in workspace settings, then they will be run in the remote. つまり先ほどの質問への答えは以下のようになります。 MCP サーバの構成をどこに記述したか MCP サーバがどこで実行されるか ユーザー設定(ローカルの settings.json ) ローカル環境 リモート設定(リモートの settings.json ) リモート環境 ワークスペース設定(ワークスペースの .vscode/mcp.json ) ワークスペース のある環境(≒ リモート環境) もし MCP サーバーが期待通り起動しない場合は、 MCP サーバーの設定がどこに記述されているか、そして MCP サーバーがどこで実行されているかを確認してみるとよいでしょう。 おわりに この記事では Visual Studio Code で GitHub Copilot の agent mode と Remote Development を併用した場合に MCP サーバーがどこで実行されるのかをご紹介しました。最後までお読みいただき、ありがとうございました。 参考資料 MCP servers run on local machine, not in remote · Issue #10920 · microsoft/vscode-remote-release Vscode in WSL using other environment to connect to MCP Servers · Issue #245050 · microsoft/vscode Allow running and detecting servers on WSL2 · Issue #243687 · microsoft/vscode 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @shibata.takao 、レビュー: @kobayashi.hinami ( Shodo で執筆されました ) https://code.visualstudio.com/docs/remote/remote-overview より引用。 ↩
こんにちは。クロス イノベーション 本部 クラウド イノベーション センターの柴田です。 最近 Model Context Protocol ( MCP ) が注目を集めていますね。 一部の MCP サーバーは実行に資格情報を必要とします。例えば以下は https://github.com/makenotion/notion-mcp-server の設定例です。 環境変数 OPENAPI_MCP_HEADERS に Notion の integration secret を設定する必要があります。 { " servers ": { " notionApi ": { " command ": " npx ", " args ": [ " -y ", " @notionhq/notion-mcp-server@v1.7.0 " ] , " env ": { " OPENAPI_MCP_HEADERS ": " { \" Authorization \" : \" Bearer ntn_**** \" , \" Notion-Version \" : \" 2022-06-28 \" } " } } } } しかし MCP クライアントの設定ファイルに資格情報を平文でハードコーディングするのはセキュリティの観点から望ましくありません。そこでこの記事では MCP クライアントとして Visual Studio Code を使用する場合に MCP サーバーの実行に必要な資格情報をパラメーター化する方法をご紹介します。 入力変数を利用して資格情報をパラメーター化する おわりに 参考資料 入力変数を利用して資格情報をパラメーター化する Visual Studio Code では 入力変数 を利用して Task や Debugging の設定ファイルの値をパラメーター化できます。同様に MCP サーバーの設定でも入力変数を利用して実行に必要な資格情報をパラメーター化できます。 先ほどの Notion MCP サーバーの設定を入力変数を使って書き直すと以下のようになります。ハードコーディングしていた integration secret がパラメーター化されています。 { " inputs ": [ { " description ": " Notion Integration Secret ", " id ": " notion-integration-secret ", " password ": true , " type ": " promptString " } ] , " servers ": { " notionApi ": { " command ": " npx ", " args ": [ " -y ", " @notionhq/notion-mcp-server@v1.7.0 " ] , " env ": { " OPENAPI_MCP_HEADERS ": " { \" Authorization \" : \" Bearer ${input:notion-integration-secret} \" , \" Notion-Version \" : \" 2022-06-28 \" } " } } } } 早速 MCP サーバーを起動してみましょう。 すると integration secret を入力するプロンプトが表示されます。 integration secret を入力して MCP サーバーを起動しました。試しに適当なプロンプトを入力してみます。 MCP サーバーが正常に動作していることが確認できました。 inputs ブロックを見ると値が格納されていることがわかります( password=true なので表示される値は * に置換されています)。 カーソルを合わせると入力変数の値を編集またはクリアできます。 入力した値は Visual Studio Code によって永続化されます。試しに PC を再起動してみましたが integration secret は残っていました。 Visual Studio Code の公式ドキュメント には以下のように書かれています。 // 💡 Inputs are prompted on first server start, then stored securely by VS Code. 具体的にどうセキュアに保存されるのかは確認できませんでした。「入力した資格情報は Visual Studio Code によってローカルに保存される」ということは認識しておきましょう。 おわりに この記事では Visual Studio Code で MCP サーバーの実行に必要な資格情報をパラメーター化する方法をご紹介しました。最後までお読みいただき、ありがとうございました。 参考資料 Use MCP servers in VS Code (Preview) Variables reference 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @shibata.takao 、レビュー: @handa.kenta ( Shodo で執筆されました )
こんにちは。 エンタープライズ 第三本部 マーケティング IT部の熊倉です。 このブログでは、 高速に動作する分散処理エンジン「 Apache Spark」 と オープンテーブルフォーマット「Delta Lake」 を基盤としたレイクハウス環境を構築できるDatabricks上で管理しているデー タセット に対して、 名寄せ 処理を行うアプローチについて紹介します。 実際のノートブックの処理についても紹介しようと思っていますが、想定よりも内容が多くなってしまったので、 名寄せ の概要を紹介する「概要編」、 ソースコード など具体的な 名寄せ 処理の具体的な内容を紹介する「 決定論 的マッチング編」「確率的マッチング編」の三部作にしようと思います。 Databricksで実現するデータ名寄せ【概要編】 Databricksで実現するデータ名寄せ【決定論的マッチング編】 Databricksで実現するデータ名寄せ【確率的マッチング編】(本記事) 本記事は確率的マッチング編となります。 名寄せ 処理の概要について説明したブログ記事も公開しておりますので、 名寄せ 処理にまだ馴染みのない方は、ぜひそちらもご覧いただけると幸いです。 本記事ではZinggというライブラリを利用し、確率的マッチングを行う具体的な処理内容や流れについて説明します。Zinggの概要については「Databricksで実現するデータ 名寄せ 【概要編】」で紹介しておりますので、こちらも合わせてご覧ください。 また、本記事の内容はZinggが公開しているExampleとDatabricksが公開しているソリューション アクセラ レータを参考にしております(Example + ソリューション アクセラ レーター + 自分の方でDatabricksの設計に合わせて加筆変更を加えたような形) 適宜こちらのページも参考にしていただきながら本記事の方も読んでいただけると良いかと思います。 Zingg https://github.com/zinggAI/zingg/blob/main/examples/databricks/FebrlExample.ipynb https://www.zingg.ai/documentation-article/identity-resolution-on-databricks-for-customer-360 Databricks https://notebooks.databricks.com/notebooks/RCG/product-er-with-images/index.html#product-er-with-images_1.html では、早速データの 名寄せ を開始していきましょう! Zinggを利用した名寄せ処理の概要 1. ライブラリをインストールする 1. クラスターにライブラリをインストールする 1. ZinggのJARファイルをGitHubからダウンロードします。 2. 適当なVolumeにJARファイルを配置する 3. クラスターの設定からライブラリをインストールする 2. ノートブックでZinggのPythonパッケージをインストールする 2. 名寄せ処理に必要なモジュールやライブラリをインストールする ライブラリのインストール Zinggで利用するワークスペースを整理 名寄せ処理で利用する関数の定義、モジュールのimport 3. Zinggの設定(Config)を定義 4. Zinggのインプット、アウトプット先を設定する 5. フィールドの属性を指定する 6. パラメータを設定する 7. トレーニングデータ作成ジョブを実行する 8. ラベリングに使用するデータセットを作成する 9. ラベリングを行う 10. モデルのトレーニング 11. マッチング結果を確認する まとめ Zinggを利用した 名寄せ 処理の概要 Zinggを利用した 名寄せ は次のステップで行います。 まず最初に 名寄せ 対象デー タセット の項目の属性(テキストや数値項目なのか、どのようなマッチングをしたいかなど)をそれぞれ定義します 定義した内容を元にデー タセット から一致している可能性の高いペアをサンプリングします サンプリングされたペア候補を元に、ユーザがペアの一致、不一致、不明を判断し、ラベリングを行います モデルのト レーニン グに十分な数が集まるまでサンプリングとラベリングを繰り返します 教師データを元にモデルを作成し、 クラスタ ー化を行います クラスタ ー化されたデー タセット ではペアの一致確率が確認できます 一致確率が確認できるので、「XX%以上は一致、XX ~ YY%はユーザで改めて手動マッチング、YY%以下は別人とする」といったようなオペレーションが可能です では、次から実際にDatabricksでZinggの 名寄せ 処理を行う方法について、具体的に紹介していきます。 1. ライブラリをインストールする Zinggをノートブックで利用するためには、次の2ステップが必要になります。 (ノートブックにアタッチする) クラスタ ーにライブラリをインストールする ノートブックでzinggの Python パッケージをインストールする 手順を順番に紹介します。 1. クラスタ ーにライブラリをインストールする 1. ZinggのJARファイルを GitHub からダウンロードします。 以下の GitHub の リポジトリ から、最新のZingg JARファイルをダウンロードします。(25/5月現在0.4.0が最新) https://github.com/zinggAI/zingg/releases 2. 適当なVolumeにJARファイルを配置する ダウンロードしたJARファイルを、任意のVolumeにアップロードします。 クラスタ ーのライブラリは ワークスペース 、Volume、S3などの クラウド ストレージ等に配置されてある必要があるので、Volume以外の場所が良い場合は適宜配置先を変更してください。 https://docs.databricks.com/aws/en/libraries/cluster-libraries 3. クラスタ ーの設定からライブラリをインストールする クラスタ ー設定ページを開き、「ライブラリ」タブから「新規をインストール」を選択し、ステップ2でVolumeに配置したJARファイルを指定してインストールします。 2. ノートブックでZinggの Python パッケージをインストールする 1. でインストールした クラスタ ーをアタッチしたノートブック上で、以下のセルを実行し、Zinggの Python パッケージをインストールします。 %pip install zingg dbutils.library.restartPython() 2. 名寄せ 処理に必要なモジュールやライブラリをインストールする Zinggの他に、ノートブックで使用するその他のライブラリやモジュール、変数をあらかじめインストールまたは準備しておきます。 ライブラリのインストール データを表形式で整形して表示するための Python パッケージ tabulate をインストールします !pip install tabulate dbutils.library.restartPython() Zinggで利用する ワークスペース を整理 Zinggで 名寄せ 対象とするデー タセット の格納場所や、処理結果の出力先となるパスを指定します。 ただし、ここに関して少し注意が必要で、Zinggは Apache Sparkで動作する 名寄せ ライブラリですが、Databricksとネイティブに統合されているわけではないため、実装する際に気をつけなければいけないポイントがあります。 具体的には次のような点に注意が必要です。 Zinggで 名寄せ したいデー タセット はDatabricks上では外部テーブルとして登録されている必要がある Zinggで作成されるデー タセット や、ト レーニン グデータ、モデルなどはUnity Catalogのテーブルとして登録することができないため、DBFSやボリュームなどに作成される ZinggとDatabricks、 クラウド ストレージの関係をまとめると次のような図になります。 そのため、事前に 「インプットしたいデー タセット を外部テーブルとして作成」し、「Zinggの出力先となるVolumeを作成」しておく必要があります。 本記事では作成方法については割愛させていただきますが、以下ページを参考に作成していただけると良いかと思います。 https://docs.databricks.com/aws/ja/tables/external https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-volumes ノートブックでは次のように変数にパスを格納しておきます。 # Zinggで作成されるデータセットや、トレーニングデータ、モデルが格納されるVolume # catalog, schema, volumeはそれぞれ適当な値を指定してください。 zinggDir = "/Volumes/catalog/schema/volume" # モデルのID。名寄せの処理ごとに編集してください。 modelId = "hogehoge-model" # 後述の名寄せ処理でラベル付けした情報が格納されるフォルダ MARKED_DIR = zinggDir + "/" + modelId + "/trainingData/marked/" UNMARKED_DIR = zinggDir + "/" + modelId + "/trainingData/unmarked/" 名寄せ 処理で利用する関数の定義、モジュールのimport 後続の処理で利用する関数を定義したり、モジュールを先にimportします。 import pandas as pd import numpy as np import time import uuid from tabulate import tabulate from ipywidgets import widgets, interact, GridspecLayout import base64 import pyspark.sql.functions as fn ##this code sets up the Zingg Python interface from zingg.client import * from zingg.pipes import * def cleanModel (isFull= False ): if (isFull): dbutils.fs.rm(zinggDir + "/" + modelId, recurse= True ) else : dbutils.fs.rm(MARKED_DIR, recurse= True ) # drop unmarked data dbutils.fs.rm(UNMARKED_DIR, recurse= True ) return # assign label to candidate pair def assign_label (candidate_pairs_pd, z_cluster, label): ''' The purpose of this function is to assign a label to a candidate pair identified by its z_cluster value. Valid labels include: 0 - not matched 1 - matched 2 - uncertain ''' # assign label candidate_pairs_pd.loc[ candidate_pairs_pd[ 'z_cluster' ]==z_cluster, 'z_isMatch' ] = label return def count_labeled_pairs (marked_pd): ''' The purpose of this function is to count the labeled pairs in the marked folder. ''' n_total = len (np.unique(marked_pd[ 'z_cluster' ])) n_positive = len (np.unique(marked_pd[marked_pd[ 'z_isMatch' ]== 1 ][ 'z_cluster' ])) n_negative = len (np.unique(marked_pd[marked_pd[ 'z_isMatch' ]== 0 ][ 'z_cluster' ])) return n_positive, n_negative, n_total # setup widget available_labels = { 'No Match' : 0 , 'Match' : 1 , 'Uncertain' : 2 } # dbutils.widgets.dropdown('label', 'Uncertain', available_labels.keys(), 'Is this pair a match?') 3. Zinggの設定(Config)を定義 Zinggではモデルを作成する際、必要な設定情報はすべて Config (実体は JSON オブジェクト)を介して渡すことになります。 https://docs.zingg.ai/latest/stepbystep/configuration #Zinggを動作させるの必要なConfigの作成をサポートするクラス args = Arguments() # モデル名やZinggの作業ワークスペースをセット args.setModelId(modelId) args.setZinggDir(zinggDir) 4. Zinggのインプット、アウトプット先を設定する インプット( 名寄せ 対象デー タセット の格納先)とアウトプット(処理結果の出力先)を設定します # 名寄せ対象のデータセット # 前述しましたが外部テーブルとして登録されてある必要があります。 input_path = spark.sql( "DESCRIBE DETAIL catalog.schema.table" ).select( 'location' ).collect()[ 0 ][ 'location' ] # Pipeというクラスより、インプットのデータセットを指定する inputPipe = Pipe(name= 'input' , format = 'delta' ) inputPipe.addProperty( 'path' , input_path ) # Argumentsに追加 args.setData(inputPipe) # アウトプットとなるデータセットやモデルが格納されるDBFS上のパス output_path = zinggDir + "/" + modelId + "/outputData" # Pipeというクラスより、アウトプットのデータセットを指定する outputPipe = Pipe( "resultOutput" , format = "delta" ) outputPipe.addProperty( "path" , output_path) # Argumentsに追加 args.setOutput(outputPipe) 5. フィールドの属性を指定する 次に、Zinggがマッチング処理の中でフィールドの取り扱いについて判断するために、使用するフィールド毎に属性を指定します。 マッチのタイプは以下の中から指定できます。詳細はZinggのドキュメントを参照してください。 https://docs.zingg.ai/latest/stepbystep/configuration/field-definitions マッチタイプ 説明 適用対象 FUZZY タイポ、略語、その他のバリエーションを含む大まかなマッチ。 string, integer, long, double, date EXACT バリエーションを許容しない。国コード、PINコード、およびバリエーションが想定されないその他のカテゴリ変数に適しています。 string, integer, long, date, boolean DONT_USE 出力には表示されますが、計算は行われません。出力に必要なIDなどのフィールドに役立ちます。 showConcise がtrueに設定されている場合、DONT_USEフィールドはラベル付け中にユーザーに表示されません。 any EMAIL @文字の前のメールのID部分のみをマッチします。 any PINCODE xxxxx-xxxxのようなPINコードをマッチします(xxxxx付き)。 string NULL_OR_BLANK デフォルトでは、Zinggはnullをマッチとして扱いますが、FUZZYのような別のマッチタイプを持つフィールドにこれ(NULL_OR_BLANK)を追加すると、Zinggはnull値の機能を構築して学習します。 string, integer, long, date, boolean TEXT 2つの文字列間の単語の重複を比較します。 タイプミス があまりない記述的なフィールドに適しています。 string NUMERIC 文字列から数字を抽出し、両方の文字列間で同じものがいくつあるかを比較します(例:アパート番号)。 string NUMERIC_WITH_UNITS 製品コードまたは単位付きの数字を抽出します(例:16gbの文字列)。両方の文字列間で同じものがいくつあるかを比較します。 string ONLY_ ALPHABETS _EXACT アルファベット文字のみを見て、それらが完全に同じであるかどうかを比較します。文字列内の数字が問題ではない場合(例:建物を調べているが、フラット番号を無視したい場合)。 string ONLY_ ALPHABETS _FUZZY 文字列内の数字を無視し、あいまいな比較を実行します。 タイプミス のある住所などのフィールドに役立ちます。NUMERICを使用して番地を個別に調べたい場合。 string MAPPING_(FILENAME) タイプミス 、略語、および指定された値に対するその他のバリエーションとのマッチング。ニックネーム、性別、および会社略語などの場合に使用できます。たとえば、 IBM 、Int. Biz. MachineをInternational Business Machineに、M、0、MrをMaleに(つまり、複数のエンティティを単一のエンティティに) マッピング する場合。 string ノートブックでは次のように設定します。 # フィールド毎にマッチタイプを指定します。 # 名寄せ処理をしたいデータセット毎に実際は設定してください rec_id = FieldDefinition( "rec_id" , "string" , MatchType.DONT_USE) name = FieldDefinition( "name" , "string" , MatchType.FUZZY) ## Match Typeは複数指定可能です。 email = FieldDefinition( "email" , "string" , [MatchType.FUZZY,MatchType.EMAIL]) dob = FieldDefinition( "dob" , "string" , [MatchType.FUZZY,MatchType.NUMERIC]) address = FieldDefinition( "address" , "string" , MatchType.FUZZY) phone = FieldDefinition( "phone" , "string" , [MatchType.FUZZY,MatchType.NUMERIC]) # フィールドの定義をArrayにする fieldDefs = [rec_id, name, email, dob, address, phone] # Argumentsに設定 args.setFieldDefinition(fieldDefs) 6. パラメータを設定する 次に、Spark上で分散処理を行う際のパラメータを設定します。具体的には numPartitions と labelDataSampleSize を設定します。 numPartitions numPartitions は、データを クラスタ 間でどのように分割するかを定義します。 Zinggドキュメント を参照して、データと クラスタ サイズに応じて変更してください。 labelDataSampleSize labelDataSampleSize では、後述する findTrainingData 処理でサンプルする割合を指定できます。サンプリングする割合が大きいほうが、ラベリングする際にケースとして相応しいペアが出力できる可能性が高くなりますが、処理の時間が長くなりすぎる場合もあるので、1/10に減らすなど調整を行ってください。 # Sparkのパーティション数。ドキュメントより推奨はコア数 × 20~30とのこと num_partitions = sc.defaultParallelism * 20 args.setNumPartitions( num_partitions ) # モデルの学習に使用するデータの割合。 # サンプルサイズを小さく保ち、十分なエッジケースを迅速に検出できるよう、0.0001~0.1の間で調整してください。 # サンプルサイズが大きいと、ジョブがfindTrainingDataサンプルを精査するのに時間がかかります。サイズが小さすぎると、Zinggが適切なエッジケースを見つけられない可能性があります。 args.setLabelDataSampleSize( 0.05 ) 7. ト レーニン グデータ作成ジョブを実行する ここまでの設定を用いて、ト レーニン グデータ(ラベリング候補ペア)を作成するジョブ( findTrainingData フェーズ)を実行します。 options = ClientOptions([ClientOptions.PHASE,"findTrainingData"]) # トレーニングデータを特定するジョブを実行 zingg = ZinggWithSpark(args, options) zingg.initAndExecute() 8. ラベリングに使用するデー タセット を作成する ト レーニン グデータの作成が終わったら、ラベリングに使用するデー タセット を作成します。 ちなみに、ステップ7と8の処理(ト レーニン グデータ作成とラベリング用デー タセット 準備)は、 findAndLabel という単一のフェーズ指定で連続実行も可能です。しかし、ト レーニン グデータの作成には時間がかかる場合があるため、ここでは途中から再開しやすいようにステップを分けて記述しています。 options = ClientOptions([ClientOptions.PHASE, "label" ]) # ラベリングに使用するデータセットを作成するジョブを実施 zingg = ZinggWithSpark(args, options) zingg.init() 次に、ラベリングの候補となるペアが何件あるか確認します。 # get candidate pairs candidate_pairs_pd = getPandasDfFromDs(zingg.getUnmarkedRecords()) # if no candidate pairs, run job and wait if candidate_pairs_pd.shape[ 0 ] == 0 : print ( 'No unlabeled candidate pairs found. Run findTraining job ...' ) else : # get list of pairs (as identified by z_cluster) to label z_clusters = list (np.unique(candidate_pairs_pd[ 'z_cluster' ])) # identify last reviewed cluster last_z_cluster = '' # none yet # print candidate pair stats print ( '{0} candidate pairs found for labeling' .format( len (z_clusters))) ラベリングの候補としていくつペアあるか出力されます。(画像ではラベリングの候補として20ペアあることがわかります) 9. ラベリングを行う いよいよ、抽出された候補ペアに対して実際にラベリングを行います。 ノートブック上に候補ペアがHTML形式で表示され、各ペアに対して「Uncertain(不明)」「Match(一致)」「No Match(不一致)」のいずれかをボタンを押すことでラベリング作業を行います。 (Databricksがノートブック機能を提供している強みですね!) # Label Training Set # define variable to avoid duplicate saves ready_for_save = False # user-friendly labels and corresponding zingg numerical value # (the order in the dictionary affects how displayed below) LABELS = { 'Uncertain' : 2 , 'Match' : 1 , 'No Match' : 0 } # GET CANDIDATE PAIRS # ======================================================== #candidate_pairs_pd = get_candidate_pairs() n_pairs = int (candidate_pairs_pd.shape[ 0 ]/ 2 ) # ======================================================== # DEFINE IPYWIDGET DISPLAY # ======================================================== display_pd = candidate_pairs_pd.drop( labels=[ 'z_zid' , 'z_prediction' , 'z_score' , 'z_isMatch' , 'z_zsource' ], axis= 1 ) # define header to be used with each displayed pair html_prefix = "<p><span style='font-family:Courier New,Courier,monospace'>" html_suffix = "</p></span>" header = widgets.HTML(value=f "{html_prefix}<b>" + "<br />" .join([ str (i)+ "  " for i in display_pd.columns.to_list()]) + f "</b>{html_suffix}" ) # initialize display vContainers = [] vContainers.append(widgets.HTML(value=f '<h2>Indicate if each of the {n_pairs} record pairs is a match or not</h2></p>' )) # for each set of pairs for n in range (n_pairs): # get candidate records candidate_left = display_pd.loc[ 2 *n].to_list() candidate_right = display_pd.loc[( 2 *n)+ 1 ].to_list() # define grid to hold values html = '' for i in range (display_pd.shape[ 1 ]): # get column name column_name = display_pd.columns[i] # if field is image if column_name == 'image_path' : # define row header html += '<tr>' html += '<td><b>image</b></td>' # read left image to encoded string l_endcode = '' if candidate_left[i] != '' : with open (candidate_left[i], "rb" ) as l_file: l_encode = base64.b64encode( l_file.read() ).decode() # read right image to encoded string r_encode = '' if candidate_right[i] != '' : with open (candidate_right[i], "rb" ) as r_file: r_encode = base64.b64encode( r_file.read() ).decode() # present images html += f '<td><img src="data:image/png;base64,{l_encode}"></td>' html += f '<td><img src="data:image/png;base64,{r_encode}"></td>' html += '</tr>' elif column_name != 'image_path' : # display text values if column_name == 'z_cluster' : z_cluster = candidate_left[i] html += '<tr>' html += f '<td style="width:10%"><b>{column_name}</b></td>' html += f '<td style="width:45%">{str(candidate_left[i])}</td>' html += f '<td style="width:45%">{str(candidate_right[i])}</td>' html += '</tr>' # insert data table table = widgets.HTML(value=f '<table data-title="{z_cluster}" style="width:100%;border-collapse:collapse" border="1">' +html+ '</table>' ) z_cluster = None # assign label options to pair label = widgets.ToggleButtons( options=LABELS.keys(), button_style= 'info' ) # define blank line between displayed pair and next blankLine=widgets.HTML(value= '<br>' ) # append pair, label and blank line to widget structure vContainers.append(widgets.VBox(children=[table, label, blankLine])) # present widget display(widgets.VBox(children=vContainers)) # ======================================================== # mark flag to allow save ready_for_save = True ノートブックのアウトプットでは次のようなHTMLが表示されます。 実際にレコードが一致しているか、一致していないかを人の目で1つひとつチェックを行い、ボタンを押してラベリングを行っていきます。 ステップ8で作成されたペア分のチェックが終了したら、次のセルを実行し、内容を保存します。 if not ready_for_save: print ( 'No labels have been assigned. Run the previous cell to create candidate pairs and assign labels to them before re-running this cell.' ) else : # ASSIGN LABEL VALUE TO CANDIDATE PAIRS IN DATAFRAME # ======================================================== # for each pair in displayed widget for pair in vContainers[ 1 :]: # get pair and assigned label html_content = pair.children[ 1 ].get_interact_value() # the displayed pair as html user_assigned_label = pair.children[ 1 ].get_interact_value() # the assigned label # extract candidate pair id from html pair content start = pair.children[ 0 ].value.find( 'data-title="' ) if start > 0 : start += len ( 'data-title="' ) end = pair.children[ 0 ].value.find( '"' , start+ 2 ) pair_id = pair.children[ 0 ].value[start:end] # assign label to candidate pair entry in dataframe candidate_pairs_pd.loc[candidate_pairs_pd[ 'z_cluster' ]==pair_id, 'z_isMatch' ] = LABELS.get(user_assigned_label) # ======================================================== # SAVE LABELED DATA TO ZINGG FOLDER # ======================================================== # make target directory if needed dbutils.fs.mkdirs(MARKED_DIR) # save label assignments # save labels zingg.writeLabelledOutputFromPandas(candidate_pairs_pd,args) # count labels accumulated marked_pd_df = getPandasDfFromDs(zingg.getMarkedRecords()) n_pos, n_neg, n_tot = count_labeled_pairs(marked_pd_df) print (f 'You have accumulated {n_pos} pairs labeled as positive matches.' ) print (f 'You have accumulated {n_neg} pairs labeled as not matches.' ) print ( "If you need more pairs to label, re-run the cell for 'findTrainingData'" ) # ======================================================== # save completed ready_for_save = False 上記のセルを実行すると、これまでにラベリングしたペアの総数(一致、不一致ごとの件数)が出力され、進捗を確認できます。 Zinggのドキュメント では、最低でもマッチと判断できるペアが30~40必要とあり、マッチと判断した数が足りない場合は、あらためて「7. ト レーニン グデータ作成ジョブ実行する」から繰り返し、十分な教師データを収集します。 また、候補となるペアの質が低い場合(明らかに別の人がリストアップされている場合など)、 labelDataSampleSize の割合が小さい可能性があるので、必要に応じてこちらも検討してみてください。 10. モデルのト レーニン グ 十分な数のラベリング済み教師データが収集できたら、それらを基にマッチングモデルを学習します( trainMatch フェーズ) options = ClientOptions([ClientOptions.PHASE, "trainMatch" ]) #トレーニングジョブを実施します(時間かかります) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() 11. マッチング結果を確認する モデル学習が完了したら、そのモデルを用いて 名寄せ 対象の全データに対して予測を実行し、どのレコードペアが同一エンティティとして判断されたかを確認します。 予測結果が出力されるデー タセット は、前述のZingg設定( outputPipe )で指定したパスに格納されます。 この出力デー タセット には、入力データに加えて、Zinggが付与した クラスタ IDや類似度スコアなどの情報が含まれます。 from pyspark.sql.types import StructType, StructField, IntegerType, FloatType, StringType # テーブルの取得 outputDF = spark.read.load(zinggDir + "/" + modelId + "/outputData/" ) # 内容の確認 display(outputDF.orderBy( "z_cluster" ).head( 25 )) 具体的には、以下の列が追加されています。 z_minScore そのレコードが同じ クラスタ 内の別のレコードと持つ最小の類似性スコアを表します。 z_maxScore そのレコードが同じ クラスタ 内の別のレコードと持つ最大の類似性スコアを表します。 z_cluster Zinggによって割り当てられた クラスタ ーのID。同一エンティティと判定されたレコード群には、共通のz_cluster IDが付与されます。 z_cluster をそのまま一意のエンティティを表すユニークIDとして使用できますが、 z_minScore や z_maxScore を元に 閾値 をユーザ側で定義(XX%以上は一致とする。XX ~ YY%はユーザで改めて目検。YY%以下は別人とする等々...)し、任意のオペレーションをすることもできます。 (詳細については、以下のZingg公式ドキュメントもご参照ください) https://www.zingg.ai/documentation-article/step-by-step-identity-resolution-with-python-and-zingg 以上で 名寄せ の一連のステップが全て終了しました! まとめ 本ブログ記事では、Zinggライブラリを用いた確率的マッチングの具体的な手法を紹介しました。 今回取り上げたZinggのような教師あり 機械学習 を活用する 名寄せ ライブラリも、Databricksのノートブック機能より、データ準備からラベリング、モデル学習、推論までを一貫したワークロードとして効率的に実行できます。 また、今回の記事では1つのデー タセット 内で 名寄せ する方法について取り上げましたが、他にもZinggを使用して、増分するデー タセット に対して継続的に 名寄せ 処理を実施できたり ( runIncremental 、 エンタープライズ 版で利用可能)、複数のデー タセット 間で一致し得るレコードを抽出も実行可能です( link )ので、もし興味がある方は是非公式ドキュメントの方もご覧ください。 三記事にわたり 名寄せ の概要からDatabricks上でSplinkによる 決定論 的手法、Zinggによる確率的手法まで 名寄せ のアプローチについて紹介しましたが、いかがだったでしょうか? 今回の一連の記事が、皆様のデータ活用や 名寄せ 業務の一助となれば幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @kumakura.koki 、レビュー: @kinjo.ryuki ( Shodo で執筆されました )
こんにちは。 エンタープライズ 第三本部 マーケティング IT部の熊倉です。 このブログでは、 高速に動作する分散処理エンジン「 Apache Spark」 と オープンテーブルフォーマット「Delta Lake」 を基盤としたレイクハウス環境を構築できるDatabricks上で管理しているデー タセット に対して、 名寄せ 処理を行うアプローチについて紹介します。 実際のノートブックの処理についても紹介しようと思っていますが、想定よりも内容が多くなってしまったので、 名寄せ の概要を紹介する「概要編」、 ソースコード など具体的な 名寄せ 処理の具体的な内容を紹介する「 決定論 的マッチング編」「確率的マッチング編」の三部作にしようと思います。 Databricksで実現するデータ名寄せ【概要編】 Databricksで実現するデータ名寄せ【決定論的マッチング編】(本記事) Databricksで実現するデータ名寄せ【確率的マッチング編】 本記事は 決定論 的マッチング編となります。 名寄せ 処理の概要について説明したブログ記事も公開しておりますので、 名寄せ 処理にまだ馴染みのない方は、ぜひそちらもご覧いただけると幸いです。 本記事ではSplinkというライブラリを利用し、 決定論 的マッチングを行う具体的な処理内容や流れについて説明していきますが、Pysparkの文法など基本的な内容については割愛させていただきます。(Splinkの概要については前編の概要編で紹介してますのでこちらも合わせてご覧ください) では、早速データの 名寄せ を開始していきましょう! 1. ライブラリをインストールする 2. 名寄せを行いたいデータセットを取り込む 3. ルールを定義する ルールの定義①: SQLで定義する ルールの定義②: block_onを利用する 注意が必要なルール: 等価結合以外の条件 4. マッチ数を確認する cartesianについて match_keyについて ネクストアクション 5. マッチングの実施 6. クラスターを作成する まとめ 1. ライブラリをインストールする 決定論 的マッチング処理における最終的なゴールは、 SQL のWHERE句に相当する条件の組み合わせを作成 することです。 SQL を記述することでも、もちろん 決定論 的マッチングは実現可能ですが、この記事では再利用性や可読性を考慮し、 Python の OSS 名寄せ ライブラリであるSplinkを用いた手法を紹介します。 まずは最初にライブラリをインストールします %pip install splink 2. 名寄せ を行いたいデー タセット を取り込む まず、 名寄せ 対象のデー タセット を取り込みます。 df = spark.read.table( "catalog.schema.table" ) (本記事ではUnity Catalogに登録されているテーブルから読み込んでいますが、もちろんファイルから直接データを読み込むことも可能です) 3. ルールを定義する 次にマッチングに使用するルールを定義します。 決定論 的マッチングは「ルールベースマッチング」とも呼ばれるように、このルール定義のステップが処理の根幹であり、最も重要なステップです。 ルールの定義は大きく分けて2つあります。 ルールの定義①: SQL で定義する ルールの定義方法の1つ目は、 SQL で定義する方法になります。 具体的には次のような形でルールを定義します。 rules = [] rules.append( "l.name = r.name AND l.email = r.email" ) SQL 文中のlおよびrというテーブル エイリアス は、同一テーブルをクロス結合(自己結合)する際の左テーブル (l) と右テーブル (r) をそれぞれ指します。 SQL 文の中でANDやORを使用して複雑な条件も設定できますが、OR条件はパフォーマンスが低下する可能性があるため、Splinkでは直接の使用は非推奨とされています。 OR条件のように複数の条件のいずれかに合致する場合を扱いたい場合は、各条件を個別のルールとしてrulesリスト(配列)に追加することで、同等の処理を実現できます。 具体的には、 rules.append( "l.name = r.name OR l.email = r.email" ) というルールを設定したい場合、 rules.append( "l.name = r.name" ) rules.append( "l.email = r.email" ) のようにリストに追加します。(ルールはリストに格納された順に評価されます) 補足すると、OR条件を含む結合は非等価結合となり、テーブルの直積に近い処理が必要になるため、一般的にパフォーマンスが低下します。Splinkでは、複数のルールをリストに格納することで、内部的にUNIONとNOTを組み合わせた処理に変換し、OR条件と同等の結果をより効率的に得られるように工夫されています。(詳細はSplinkの公式ドキュメントをご参照ください) 詳細については、以下Splink公式ドキュメントに紹介されているので、こちらも合わせてご確認ください。 https://moj-analytical-services.github.io/splink/topic_guides/blocking/performance.html ルールの定義②: block_onを利用する ルールの定義方法の2つ目は、Splinkで用意されている block_on 関数の利用になります from splink import block_on rules.append(block_on( "dob" , "address" )) Splinkの関数 block_on は、 SQL の l.dob = r.dob AND l.address = r.address と変換されます。 注意が必要なルール: 等価結合以外の条件 単純な列の一致比較(等価結合)以外、例えばLevenstein距離(編集距離)が特定の 閾値 以上であるといった条件(非等価結合)を用いる場合、多くの SQL エンジンでは、全レコードの総当たりに近いペア計算が発生し、計算量が膨大になる傾向があります。このような非等価結合を含むルールを設定する際は、パフォーマンスへの影響に十分な注意が必要です。 要件として、マッチングルールとして設定する必要がある場合もあるかと思いますが、注意が必要な条件になります。 4. マッチ数を確認する マッチング処理を行いレコードを突合する前に、3. で設定したルールで何件のレコードがマッチされるか確認できます。 from splink import SparkAPI from splink.blocking_analysis import cumulative_comparisons_to_be_scored_from_blocking_rules_data db_api = SparkAPI(spark_session=spark) # ruleの配列には以下を格納 # "l.name = r.name AND l.email = r.email" # block_on("dob", "address") count = cumulative_comparisons_to_be_scored_from_blocking_rules_data( table_or_tables=df, blocking_rules=rules, db_api=db_api, link_type= "dedupe_only" , unique_id_column_name= "id" ) display(count) 出力結果は以下のようになります。 ルールはリスト( rules )に入っている順に評価されるので、上記の ソースコード の例では "l.name = r.name AND l.email = r.email" block_on("dob", "address") の順に評価されます。 出力結果より、 "l.name = r.name AND l.email = r.email" のルールでは4345件がマッチと判断され、 block_on("dob", "address") のルールでは348件マッチと判断され、合計で4693件のペアがマッチと判断されたことが分かります。 注 : 「 block_on("dob", "address") のルールでは348件マッチ」とありますが、これは block_on("dob", "address") のルール 単体 でヒットしたペアが348件という訳ではなく、 "l.name = r.name AND l.email = r.email" のルールでヒットしなかったペアのうち、348件が2. のルールにヒットした、ということになります。 ちなみに、リストに格納されるルールの順を逆にすると以下のような出力結果になります。(ダミーデータの都合上、マッチするペアがほぼ一緒だったりしますが...) cartesianについて cartesian カラムに78,131,250という数値が入っていますが、デー タセット 内で作成可能な全てのユニークなペアの総数になります。これは(レコードの総数)* (レコードの総数 -1 )/ 2 で求められます。 (今回使用したダミーデータのレコード総数は12,501件であるため、作成可能なペアの総数は 12,501 × 12,500 / 2 = 78,131,250 となります) match_keyについて 出力結果には match_key カラムがありますが、これは各ルールに割り当てられた識別キー(インデックス)です。 後述のマッチング結果を確認する中で改めて登場します。 ネクス トアクション マッチ数を確認し必要に応じて3. で設定したルールの見直しを行います。 マッチ候補となるペア数は、後続のマッチング処理における計算リソースや処理時間に直接影響するため、「マッチ数が過大になっているルールはないか(処理負荷が高すぎないか)」、逆に「マッチ数が極端に少なく、効果の薄いルールはないか」といった観点で確認します。 基本的には、ルールをより厳しくする方向(例:リストに格納するルール数を調整する、ANDで条件を追加するなど)で再定義を検討しますが、実際にはプロジェクトごとの要件(精度、再現率、処理時間など)を総合的に加味して調整していくことになります。 5. マッチングの実施 ルールの定義が完了したら、実際にマッチングを行い突合したレコードを確認します。 settings = SettingsCreator( link_type= "dedupe_only" , blocking_rules_to_generate_predictions=rules, # ユニークな列を指定します。 unique_id_column_name= "id" , ) linker = Linker(df, settings, db_api=db_api) # マッチングの実施 df_predict = linker.inference.deterministic_link() df_predict_spark = df_predict.as_spark_dataframe() df_predict_spark はマッチングで突合されたレコードのペアが格納されたデータフレームになります。 display(df_predict_spark.count()) display(df_predict_spark.head( 15 )) ペアの情報のほか、出力結果には match_key カラムが追加されています。 こちらは「4. マッチ数を確認する」で確認できる match_key に対応しており、どのルールによって検出されたペアなのかを確認することができます。 (図の例だと、52行目のペアはmatch_keyが0、つまり"l.name=r.name AND l.email = r.email"というルールによってヒットしたペアで、53行目のペアはmatch_keyが1、つまり block_on("dob", "address") というルールによってヒットしたことが分かります) 6. クラスタ ーを作成する 同一ペアの検出は5. のステップで完了しましたが、ステップ6ではさらに進んで、同一と判定されたエンティティ(レコード群)に対して新しいユニークIDを付与します(この処理を クラスタリング 、または クラスタ ー化と呼びます)。 from pyspark.sql.functions import col # クラスター化の実行 clusters = linker.clustering.cluster_pairwise_predictions_at_threshold(df_predict) df_cluster_spark = clusters.as_spark_dataframe() # 結果の確認 display(df_cluster_spark.orderBy(col( "cluster_id" )).head( 15 )) cluster_id カラムが追加され、同一エンティティと判定されたレコード群ごとにユニークなIDが付与されていることが確認できます。 名寄せ 処理後のデー タセット では、この付与された cluster_id を新たなユニークIDとして扱うことになります。 以上で 名寄せ の一連のステップが全て終了しました! まとめ 本ブログではSplinkを使用した 決定論 的マッチングの具体的な手法について簡単に紹介しました。 処理自体は比較的シンプルかなと思いますが(冒頭で書いたように SQL のWHERE句を作るのが最終的なゴールなので)、実際のプロジェクトでは、このルール定義部分で難航することが多いのかなと思います。 以上、Databricks上で実現するデータ 名寄せ 【 決定論 的マッチング】編でした。 次回はDatabricks上で実現するデータ 名寄せ 【確率的マッチング】編になります! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @kumakura.koki 、レビュー: @akutsu.masahiro ( Shodo で執筆されました )
こんにちは。 エンタープライズ 第三本部 マーケティング IT部の熊倉です。 このブログでは、 高速に動作する分散処理エンジン「 Apache Spark」 と オープンテーブルフォーマット「Delta Lake」 を基盤としたレイクハウス環境を構築できるDatabricks上で管理しているデー タセット に対して、 名寄せ 処理を行うアプローチについて紹介します。 実際のノートブックの処理についても紹介しようと思っていますが、想定よりも内容が多くなってしまったので、 名寄せ の概要を紹介する「概要編」、 ソースコード など具体的な 名寄せ 処理の具体的な内容を紹介する「 決定論 的マッチング編」「確率的マッチング編」の三部作にしようと思います。 Databricksで実現するデータ名寄せ【概要編】(本記事) Databricksで実現するデータ名寄せ【決定論的マッチング編】 Databricksで実現するデータ名寄せ【確率的マッチング編】 本記事は概要編で、 名寄せ 処理について概要、Databricksで 名寄せ 処理を行うメリットについて紹介します。 名寄せ 処理の基本をご存知の方は、本記事を読み飛ばしていただき、「 決定論 的マッチング編」または「確率的マッチング編」からお読みいただいても構いません 1. はじめに 1.1 名寄せとは 1.2 Entity Resolution と Identity Resolution 1.3 日本の名寄せ処理事情 1.4 国内の名寄せソリューション 2. 名寄せ処理の概要 2.1 名寄せ処理のステップ 2.2 偽陽性と偽陰性 2.3 決定論的マッチングと確率的マッチング 1. 決定論的マッチング 2. 確率的アイデンティティ解決 決定論的マッチングと確率的マッチングの選択 3. Databricks上での名寄せ 3.1 Databricksの利用 3.2 テックブログで取り上げるライブラリ Splink Zingg 1. はじめに 1.1 名寄せ とは 複数のレコードから、同一のエンティティ(現実世界で一意の顧客や製品など)を特定する操作を 名寄せ (英語だとEntity Resolution等)と呼びます。 1つのデー タセット 内で発生している重複を解消する場合や、共通IDが存在しない複数のデー タセット 間でユニークなエンティティを特定したい場合に 名寄せ 処理は実施されます。 エンティティは、企業が個別の単位として測定したいものなら何でも該当します。 (例えば、B2C企業だと「顧客」や「製品」、「 サブスクリプション 」、 B2B 企業だと「チーム」「企業」などが挙げられます) 1.2 Entity Resolution と Identity Resolution エンティティが「顧客」の場合の 名寄せ は、 アイデンティティ 解決(Identity Resolution)とも呼ばれることもあります。 アイデンティティ 解決の主要な目的の一つは、顧客に関するあらゆる情報を統合し、多角的な顧客理解を可能にする「カスタマー360」(360度顧客ビュー)の構築です。これの実現は、 マーケティング 効果の向上、顧客体験の最適化、効率的な顧客対応に不可欠です。 歴史的に見ると、このための機能は、金融機関における初期の顧客情報システムであるMCIFのようなデータベース マーケティング システムから始まり、その後、 CRM 、CDPといった様々なシステムで開発・実装されてきました。 1.3 日本の 名寄せ 処理事情 日本の情報が格納されたデー タセット に対して 名寄せ を行いたい場合、日本語特有の課題が存在します。 例えば、 ひらがな、カタカナ、漢字と文字の種類が多いこと 例(類似度について) 例えば、「Robert」と「Robelt」という文字列ペアと、「渡邉」と「渡辺」という文字列ペアは、どちらも編集距離(ここでは、1文字置換のコストを1とします)は同じ1です。しかし、単純に「編集距離/文字列長」で誤り率を評価すると、「Robert / Robelt」(6文字中1文字の違い)は約16.7%ですが、「渡辺/渡邉」(2文字中1文字の違い)は50%となり、後者の方が不一致度が大きく評価される傾向があります。このように、短い文字列(特に漢字2文字など)では、1文字の違いが全体に与える影響が相対的に大きくなってしまいます。 漢字については複数の読み方が存在しており、ふりがなの変換も難しいこと 例: 東海林(とうかいりん、しょうじ)など 住所の体系がかなり複雑なこと 日本の住所の正規化、 名寄せ の難易度の高さについて度々取り上げられ話題になります。 例: 「日本の住所のヤバさ」知れ渡る 正規化・名寄せ問題、Twitterトレンドに 1.4 国内の 名寄せ ソリューション 上記のような理由で日本語での 名寄せ は他の言語に比べると難しい傾向にあります。そのため、国内では従来より ゴールデンレコード(信頼できるソース)となる辞書データを所有 しており、 データの正規化を行える 企業が 名寄せ ソリューションを提供してきた背景があります。 例: 中小企業の辞書データを所有する企業が提供する会社の 名寄せ ソリューションや、住所辞書を所有する企業が提供する人の 名寄せ ソリューションなど 2. 名寄せ 処理の概要 2.1 名寄せ 処理のステップ 名寄せ 処理のステップは大きく 「対象データの分析」「 名寄せ の計画・設計」「対象データの前処理」「対象データのマッチング」 に分かれます。 対象データの分析 名寄せ を行いたいデー タセット について、項目ごとにカーディナリティや存在するデータの傾向などを分析・調査します。 名寄せ の計画・設計 名寄せ 処理のスケジュールや目的を設定します。目的によって、 名寄せ 処理の誤りをどの程度許容できるか(あるいは全く許容できないか)が異なるため、あらかじめ明確に定義し、関係者間で合意を得ておくことが必要です。 また、ルールベースで 名寄せ 処理を行う場合、この段階で 名寄せ ルールを定めます。 例)氏名と生年月日、住所が一致した場合は同一人物とする 対象データの前処理 データの内容は基本的に直接改変せず、後述のマッチング処理を効率良く行うために、表記揺れを吸収するようなデータの整形を行います。ただし、豊富な辞書データ(正本データ)を 保有 する従来型 名寄せ サービスなどでは、この段階で正本データに基づき表記の修正まで実施することもあります。 対象データのマッチング 前処理が完了した対象データに対してマッチング処理を行います。 また、対象データに対してマッチング処理を行った結果、(一般的には)同一とされたエンティティに対して新しいユニークなIDを付与します。 マッチング手法には ルールベースの 決定論 的マッチング と エンティティの一致度を確率で表現する確率論的マッチング が存在します。これらの詳しい処理内容については後述します。 2.2 偽陽性 と 偽陰性 ある情報を”分類”する際、その予測結果と実際の値(正解)パターンは以下の4つに分類できます。 真陽性 (True Positive, TP): 実際に陽性であるものを正しく陽性と予測できたケース 例: 実際に同一人物のレコードを同一人物と判断 真陰性 (True Negative, TN): 実際に陰性であるものを正しく陰性と予測できたケース 例: 実際に別人物のレコードを別人物と判断 偽陽性 (False Positive, FP): 実際には陰性であるものを、 誤って 陽性と予測してしまったケース 例: 実際は別人物なのにレコードを同一人物と判断 偽陰性 (False Negative, FN): 実際には陽性であるものを、 誤って 陰性と予測してしまったケース 例: 実際は同一人物なのにレコードを別人物と判断 名寄せ の目的によって、 偽陽性 と 偽陰性 のどちらを許容するか(反対に許容できないか)を合意しておくことが重要です。 2.3 決定論 的マッチングと確率的マッチング マッチングの手法は大きく、 決定論 的マッチング と 確率論的マッチング の 2 種類に分かれます。 1. 決定論 的マッチング 決定論 的マッチングは、ルール(条件)を定義し、エンティティを同一と見なす 名寄せ 手法です。 主な特徴 ルールベースのマッチング 事前に定義された厳格なルール(例:「氏名と生年月日が完全に一致する」「メールアドレスが完全に一致する」など)に基づいてレコードを比較し、条件に合致した場合にのみ同一エンティティと 判断 します。 利点 高精度 誤判定( 偽陽性 )が少なく、信頼性が高いです。特にパーソナライズされたコミュニケーションなど、精度が重視される用途に適しています。 計算コスト 確率的モデルと比較して、ルールベースのマッチングは計算量が少なく済む場合があります。 解釈可能性 なぜデータが結びつけられたのか、ルールに基づいて明確に説明できます。 欠点・課題 再現率の低さ(見逃しの多さ) ルールが厳格であるため、名前の表記揺れ(例: 「渡辺」 と 「渡邉」)、入力ミス、データの欠損(例: 郵便番号がない)などがあると、実際には同一人物であってもルールに合致せず、見逃してしまう( 偽陰性 )可能性が高くなります。 ルール設定の複雑さ 偽陰性 を低くしようと、より多くのパターンに対応しようとすると、ルールが非常に複雑になります まとめ 決定論 的マッチングは、明確な情報に基づいて高い精度で顧客情報を統合する強力な手法ですが、データの完全性や表記の揺れに弱く、すべての関連データを見つけ出す(再現率) 点では限界があることを理解して利用する必要があります。 2. 確率的 アイデンティティ 解決 確率論的名前解決とは、ルールに一致するかどうかだけに頼るのではなく、複数の情報や不完全な情報を統計的に評価し、異なるレコードが同じエンティティを指している「確率」を計算する手法です。 あいまい一致(Fuzzy Matching)と呼ばれることもあります。 主な特徴 証拠に基づくアプローチ 氏名、生年月日、住所、メールアドレス、位置情報など複数の属性や情報の一致率を算出し、「証拠」として利用します。 単一の属性(例:氏名が同じ、メールアドレスが同じ)だけでは一致と判断せず、複数の証拠を組み合わせ総合的に評価します。 各属性の一致・不一致が、どの程度「同一エンティティである」という結論を支持するか(または否定するか)を重み付けし、総合的な一致確率を算出します。 例えば 、 「田中 一郎、生年月日1990/05/09、郵便番号000 1234」というレコードと、 「田中 市朗、生年月日1990/05/09、郵便番号AB12 3CD」というレコードがあった場合、 名前は完全一致しないものの、他の属性が一致しているため、一致確率(例:98.21%)は高い のように確率をそれぞれ算出します。 閾値 の設定を後から設定でき、柔軟性がある 一致する確率が算出されるので、ユーザーが「どの程度の確率であれば同一エンティティとみなすか」という 閾値 をマッチングを行った後から設定できます。 これにより、 ユースケース に応じて精度と網羅性のバランスを調整できます。 例えば、同一人物であるにも関わらず誤って別人としてしまうこと( 偽陰性 )を避けたい場合は 閾値 を低めに、別人なのに同一人物と判断してしまうこと( 偽陽性 )を下げたい場合は 閾値 を高めに設定することが可能です。 利点 (ルールベースの 決定論 的マッチングに比べ)より多くのレコードを検出できる 確率論的手法は完全一致しないデータ間でも関連性を見つけ出すことができるため、 決定論 的手法よりも多くのレコードを統合できる可能性があります。 しかし、その性質上、(別人を同一人物として判断してしまう) 偽陽性 は 決定論 的手法よりも高くなる可能性があります。 実際にマッチングされた結果を見ながら、同一かどうかの判断ができる 偽陰性 、 偽陽性 の調整を後から実施できる ルールベースの 決定論 的マッチングの場合、あらかじめルールを定義する必要があり、その定義したルールで目的の 名寄せ ができたかどうかは結果が出力されないと判断ができない。(判断自体も難しい) 欠点・課題 解釈可能性が低い 確率によって一致かどうかを判断するため、究極的に言えば分類結果が誤っている可能性が常に存在する。 決定論 的マッチングは「一致とする」と判断してしまうアプローチなので、上記の点は明確です。 偽陽性 が高くなる場合がある 一致確率を基に判断する性質上、(別人を同一人物として判断してしまう) 偽陽性 は 決定論 的手法よりも高くなる可能性があります。 前述のように 閾値 の調整を行うことである程度調整は可能です。 まとめ 確率論的名前解決は、複数の不確実な情報から統計的に同一性を推定する柔軟なアプローチであり、より多くのデータを統合できる可能性がありますが、メリットとデメリットを理解し、 閾値 をどこに設定するかが重要となる手法です。 決定論 的マッチングと確率的マッチングの選択 上記のように、 決定論 的マッチングと確率的マッチングではそれぞれメリットデメリットが存在しており、どちらに優位性があるという訳ではありません。 例えば、 名寄せ した結果を基にユーザーに対してメールや電話による1on1の施策を行いたい場合、別人同士を同一人物として判断してしまう 偽陽性 が許容されないため、ルールベースの 決定論 的マッチングが適しています。一方、広告のオーディエンスとして利用するなど、ある程度の誤りが許容できる場合(機会損失の方が大きいと考えられる場合)は、確率論的マッチングの方が適しています。 名寄せ した結果どうしたいか(どのような利用を想定しているか)によって、どちらかを選択したり、 あるいは組み合わせて (氏名、住所、電話番号が一致する人は 決定論 的な考え方で同一人物とするが、他の属性の組み合わせについては確率的な考え方で 名寄せ を行うなど) 名寄せ 処理を行なっていくことになります。 3. Databricks上での 名寄せ 3.1 Databricksの利用 Databricksは、 Apache Sparkを基盤とする統合データ分析プラットフォームで、データエンジニアリング、 SQL や Python を利用した分析、 機械学習 、AIモデル開発といったデータ関連のワークロードを一元的に提供するプラットフォームです。 Databricksはデータエンジニアリングを行う主要なインターフェースの一つとしてNotebook環境を提供しています。 Notebookでは Python や Scala 、 SQL などがサポートされており、これらの言語を使用し、データを操作したり分析したりすることができます。 また、膨大な Python ライブラリ(pandas、NumPy、scikit-learn、TensorFlow、PyTorch等)をシームレスに利用することも可能で、 名寄せ 処理においては、企業がサービスとして提供しているもの以外に、 OSS として利用可能なライブラリも存在するため、それらを活用することでDatabricks上で 名寄せ 処理を実現できます。 Databricks上で 名寄せ 処理を行うメリットとして次のようなものがあります。 大量データに対して高速に実施可能 DatabricksはSpark(およびSparkを基に独自実装したPhoton)が動作するため、大量データの分散処理に強みがあります。 クラウド プラットフォームが提供する インスタンス 上に展開されるため、 インスタンス サイズを調整することで処理速度のチューニングも可能です。 Python の豊富なライブラリが利用可能 Sparkは Python や Scala で処理が記述でき、Notebookからpipによるライブラリのインストールが自由にできます。 Python はデータ分析の分野で利用実績が非常に多く、データ処理に関するライブラリ( Unicode 正規化ライブラリなど)の豊富さは他言語と比較して突出しており、コミュニティに蓄積された豊富なノウハウを 名寄せ 処理に活用できます。 また、 API を呼び出すことも可能なため、例えば住所正規化処理を提供している外部サービスに対してリク エス トを送信し、値の正規化を行うなど、柔軟な設計が可能です。 ELT 処理のワークロード内で実現可能 Databricksでは、 Python , SQL でETLを実現できるDelta Live Tableという機能や、データパイプライン(ジョブ)を管理できるWorkflowという機能が提供されています。 これらを組み合わせることで、複数システムからのデータインポート、突合から変換、 名寄せ 処理による重複排除、アウトプット作成まで一貫して行うことができます。 3.2 テックブログで取り上げるライブラリ 「 決定論 的マッチング編」「確率的マッチング編」では実際にDatabricksで 名寄せ 処理を実装する例を紹介していきます。 名寄せ のライブラリについては 決定論 的マッチングでは「 Splink 」、確率的マッチングでは「 Zingg 」というライブラリを使用していきたいと思います。 どちらもSpark上で動作する 機械学習 を活用した 名寄せ ライブラリで、確率的マッチングを行うことができます。 (Splinkについてはルールベースの 決定論 的マッチングも実施可能なので、 決定論 マッチングの例で利用しています) 教師なし学習 によるアプローチをとるのがSplink、 教師あり学習 によるアプローチを取るのがZinggとなっています。 Splink Splinkは英 国司 法省が開発した 名寄せ ライブラリです。 教師データなし学習によるアプローチを取っており、氏名、住所、生年月日といった複数の属性情報に対し、一致・不一致を判断する信頼度による重み付けを行い、2つのレコードの一致確率を算出(≒ 名寄せ )することができます。 「 Fellegi - Sunter 」モデルという理論に基づいて2つのレコードの一致確率を求めるのですが、その中でパラメータチューニング作業が求められるため、実装としてはやや難易度が高くなる傾向にあります。(実装には「 Fellegi - Sunter 」モデルの理解、基本的な 機械学習 の理解、そのデー タセット の理解が必要) メインとなる 名寄せ 処理(モデル作成など)以外に 名寄せ マッチングに必要な分析機能なども提供されており、 名寄せ のステップでいう対象データの分析を行う API が用意されています。 Databricksでは以前、ARC(Auto Record Linkage)というレコードの 名寄せ を行えるライブラリが提供されていました。 内容としてはSplinkをラップし、Databricks利用に特化したものだったようですが、現在更新が停止しており、利用が非推奨となっています(代わりにSplinkを直接使って欲しいと記載あり) https://www.databricks.com/blog/improving-public-sector-decision-making-simple-automated-record-linking https://github.com/databricks-industry-solutions/auto-data-linkage Zingg Zinggは、 機械学習 (ML)を活用した オープンソース の 名寄せ (エンティティ解決)ライブラリです。 顧客、製品、 サプライヤー など様々なエンティティに対応し、異なるデータソース間や単一データソース内の重複レコードや同一実体を特定・結合できます。 名寄せ 処理のステップは少しユニークで、対象となるデー タセット からまずラベリングを行い(2レコードが同一か別かを判断)、ラベリングした教師データを元にト レーニン グを行い、モデルを作成し、実際のデー タセット に対して 名寄せ を行う、といった流れを取ります。 Databricksや Snowflake 、Cassandra、S3、Azure、主要 RDBMS など、多くのデータソースやファイル形式(Parquet, Avro, JSON , CSV など)に対応しており、特にDatabricksに関しては、すぐにDatabricks上で処理を実行できるNotebookのサンプルなども提供されています。 Databricksが提供する特定の ユースケース に特化したテンプレート(ソリューション アクセラ レーターと呼ばれます)で利用されていたり、 AWS の公式ブログやHightouchのドキュメントで取り上げられるなど、( 名寄せ ライブラリの中では )比較的 知名度 の高いライブラリとなっています。( GitHub stars 1k) https://www.databricks.com/jp/solutions/accelerators/product-matching-with-ml https://aws.amazon.com/jp/blogs/big-data/entity-resolution-and-fuzzy-matches-in-aws-glue-using-the-zingg-open-source-library/ https://hightouch.com/blog/what-is-entity-resolution 「Databricksで実現するデータ 名寄せ 【概要編】」については以上になります。 本記事が 名寄せ 処理を理解する上で、少しでもお役に立てれば幸いです。 「 決定論 的マッチング編」、「確率的マッチング編」では実際にDatabricks上で実装する例について紹介しますので、こちらもぜひご覧ください! 以上、最後までご覧いただきありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @kumakura.koki 、レビュー: @akutsu.masahiro ( Shodo で執筆されました )
みなさんこんにちは。 エンタープライズ 第一本部の鈴木です。 この記事では、Feature Flag(フィーチャーフラグ)を用いた開発手法についてまとめていきます。Feature Flagは昔からある開発手法の一つですが、調査する中で面白い手法だと思ったため、改めてまとめたいと思います。 1.はじめに 2.メリット/デメリット 2-1.メリット リリースサイクルとデプロイの分離 安全なデプロイ 本番環境での容易な検証と実験 2-2.デメリット コードベースの乱雑化とテスト(CI)の複雑化 未使用のFeature Flagがコードベースに残ることで、技術負債を抱える可能性がある 3.Feature Flagを用いた開発のライフサイクル 4.開発のプラクティス 未使用のFeature Flagの定期的な削除 Feature Flagの命名と整理 決定点と決定ロジックを分離する 5.まとめ 6.参考文献 1.はじめに まずはじめに、Feature Flagについて説明します。 Feature Flagとは、新たなコードをデプロイすることなく、実行時に特定の機能をオンまたはオフにするソフトウェア開発手法です。 フィーチャートグルやフィーチャースイッチなどとも呼ばれることがあります。 以下に簡単なTypeScriptの実装を掲載しています。 // app/page.tsx const featureFlag = true ; export default function Home () { return ( { featureFlag ? ( < div > < p > 🎉 新機能が有効になっています !</ p > </ div > ) : ( < div > < p > 🚧 新機能は無効になっています 。</ p > </ div > ) } ); } このように、特定の機能やコードブロックが実行されるかどうかを条件文にして、それをフラグによってオンまたはオフを切り替えます。 この例では分かりやすいように featureFlag という変数をハードコードしていますが、通常は設定ファイルやデータベース、管理サービスなどで保持します。 上記の通り、概要自体はとても単純ですが、Feature Flagを開発に取り入れることで得られるメリットは多くあります。 次章では、Feature Flagを開発に取り入れることで得られるメリット/デメリットについてまとめます。 2.メリット/デメリット 2-1.メリット リリースサイクルとデプロイの分離 Feature Flagを採用することの1番のメリットは、リリースサイクルとデプロイの分離ができることだと思います。 Feature Flagによって、実装したコードをデプロイするタイミングと、その機能をユーザーに提供するタイミングを分離することができます。 言い換えると、機能リリースタイミングに依存せずに開発チームは任意のタイミングでコードをデプロイすることが可能になります。 デプロイの独立性が高まることで、開発チームは迅速に短いスパンでコード変更をメインブランチにマージしたり、本番環境にデプロイしたりしやすくなります。 また、ユーザーへの機能リリースは、任意のタイミングでFeature FlagをONにすることで、コードのデプロイに依存せずにリリースが可能になります。これによりリリースのタイミングとコードデプロイを合わせなくても良くなります。 安全なデプロイ Feature Flagによって安全に本番環境へデプロイすることができます。 開発者はユーザーが新しく開発した機能を利用できない状態でデプロイすることができます。 また、Feature Flagは「 キルスイッチ 」としても機能します。仮に本番環境で新機能を有効化して問題が発生した場合、問題のある機能をピンポイントで、 ロールバック なしに迅速に無効化することができます。 以上から、機能リリースのリスクを抑えつつ、安全に本番環境へデプロイすることができます。 本番環境での容易な検証と実験 Feature Flagによって本番環境を用いて、以下のような検証がしやすくなります。 ①本番環境の トラフィック やデータ量を用いた動作検証 通常、検証環境では本番同等の トラフィック やデータ量を完全に再現して検証することができないため、本番環境で予想外のインシデントが発生する可能性があります。検証用ユーザー限定でFeature Flagを有効化すれば、本番環境の トラフィック やデータ量など実運用環境でシステムを検証することができます。 ② カナリア リリースによる検証 カナリア リリースとは、新バージョンと旧バージョンのアプリケーションを並行稼働させ、新バージョンを段階的に少数のユーザーに提供し、問題がないことを確認してから全体にリリースする手法です。炭鉱で カナリア を用いて有毒ガスを検知していたことに由来します。 一部のユーザーでのみFeature Flagを有効化してリリースすることで、新機能のリリースの影響が全ユーザーに及ぶリスクを回避しながらリリースすることができます。 仮に問題が発生した場合は、Feature Flagを無効にすることで迅速に ロールバック が可能となります。また、リリース後に問題が発生しない場合は、Feature Flagを有効にするユーザーの割合を増やすことで比較的安全に全体へのリリースが可能となります。 ③A/Bテストによる検証 Feature Flagによって、本番環境で新機能と既存機能を比較し、データに基づいて意思決定を行うことができます。 例えば、ユーザーの半分は新機能を有効化し、残りは無効化にします。そして本番環境で特定のメトリクス(アプリの使用率、コンバージョン率など)やユーザーフィードバックを収集します。それらを分析して、新機能を完全にリリースするか、改善するか、削除するかなど、実データに基づいた精度の高い意思決定をすることができます。 2-2.デメリット コードベースの乱雑化とテスト(CI)の複雑化 Feature Flagを採用すると、条件分岐(if文)の実装が増えることになります。 これによってコードベースが乱雑になり、冗長な実装となり得ます。 また、条件分岐が増えることでテスト(CI)の複雑性が高まります。単純に新機能と旧機能(もしくは新機能がない状態)をそれぞれ検証する必要が生じます。複数のFeature Flag同士で依存関係がある場合は、テストケース数が指数関数的に増加するため、設計時に注意する必要があります。 未使用のFeature Flagがコードベースに残ることで、技術負債を抱える可能性がある Feature Flagの実装でコードベース上に不要なコードが残ることにより、技術負債を抱えることがあります。 適切にFeature Flagが管理されて、検証後にはきちんと削除すれば問題はありません。しかし、検証期間が長期にわたったり、削除漏れがたびたび起こったりすると不要なコードが残り続けることになります。 3.Feature Flagを用いた開発のライフサイクル Feature Flagを用いた開発のライフサイクルの一例は以下のとおりです。 Feature Flagを含む機能実装・検証 コードベースでFeature Flagを定義して、機能を実装する。 Feature Flagを定義ファイルや管理システムに設定する。 検証環境などで動作検証する。 本番環境へデプロイ、Feature Flagの有効化 開発したコードを本番環境にデプロイする。 任意のタイミングでFeature Flagを有効化して機能をリリースする。この時、リリース戦略に合わせて新機能を有効にするユーザーの割合やターゲットグループを設定する。 動作やパフォーマンスの検証 新機能の動作に問題がないことを確認する。 主要な指標(コンバージョン率、ユーザーエンゲージメントなど)や、ユーザーからのフィードバックを収集する。収集した情報を分析することで、実装した機能を完全にリリースするか改善するかの判断材料にする。 Feature Flagの削除 機能が完全にリリースされたり、廃止されたりした場合、技術負債を防ぐためにFeature Flagを削除する。 コードベースのみでなく設定ファイルや管理システムからも削除する。 4.開発のプ ラク ティス Feature Flagを開発に導入する際、以下の点に気をつける必要があります。 未使用のFeature Flagの定期的な削除 「2.メリット/デメリット > 2-2.デメリット」に記載したとおり、Feature Flagを削除しないでコードベースに残したままにすると、技術負債を抱え続けることになります。 対策としては、以下が考えられます。 機能実装の バックログ に「Feature Flagの削除」を含める。 Feature Flagに有効期限を持たせる。例えば少し乱暴ではあるが、期限切れのFeature Flagを用いたCIは失敗するように設定するなどが考えられる。 Feature Flagの 命名 と整理 ファイル名やクラス名などと同様に、Feature Flagにも一貫した 命名規則 を確立しておくことが重要です。 曖昧な 命名 をしたFeature Flagが乱立すると、何の機能に対するフラグかわからなくなります。結果的にリリース時の設定ミスにより意図しない機能が有効化されてしまったり、不要なFeature Flagがコードベースに残ってしまったりすることが考えられます。 各フラグについて、一貫した 命名 で、目的と想定動作を文書化して管理しておくことが重要です。 決定点と決定ロジックを分離する 少し実装寄りの話になりますが、Feature Flagによる機能の有効/無効をチェックする場所(決定点)と、Feature Flagの状態を決定する場所(決定ロジック)を分離するのが良いとされています。 例えば以下の実装を見てみましょう。 // featureFlags.ts (決定ロジック) export type FeatureFlagName = "isNewFeature1Enabled" | "isNewFeature2Enabled"; type FeatureFlagStates = Record<FeatureFlagName, boolean>; // 例: 設定ファイルやAppConfigのような管理サービスなどから取得した値 const featureFlagConfig: FeatureFlagStates = { isNewFeature1Enabled: true, isNewFeature2Enabled: false, }; // 決定ロジック export function isFeatureEnabled(flag: FeatureFlagName): boolean { // 引数で指定されるフラグ名に設定されたBoolean型を返却する。 return featureFlagConfig[flag]; } // app/page.tsx(決定点) import { isFeatureEnabled } from "./featureFlags" ; export default function Home () { return ( { isFeatureEnabled ( "isNewFeature1Enabled" ) && ( < div > 🎉 新機能1が有効です </ div > ) } { isFeatureEnabled ( "isNewFeature2Enabled" ) && ( < div > 🎉 新機能2が有効です </ div > ) } ); } このように決定点と決定ロジックを分離することで以下のメリットがあります。 ①メンテナンス性が向上する。 決定点と決定ロジックを分離することで、フラグの判定基準が変わってもフラグの利用箇所を修正する必要がありません。 そのため、フラグの利用箇所を探して一律変更する必要がなく、決定ロジックのみ修正すれば良くなります。 ②テストが容易になる。 決定点と決定ロジックを分離することで、それぞれ単体でのテストが可能となります。 仮に決定点と決定ロジックが同時に記述されていた場合、テストケースが複雑になり、ロジック変更の際に ソースコード やテストケースの修正に労力がかかります。そのため デザインパターン の「依存性の注入」の原則の通り、決定点と決定ロジックは分離して管理するのが良いと考えられます。 5.まとめ 今回はFeature Flagについて、概要からメリット・デメリット、そして開発に取り入れる際のプ ラク ティスをまとめました。 Feature Flagについて調査する前は、単に「if文で新機能を有効/無効にするだけでしょう」と雑に理解していましたが、いざ調べると多くのメリットがあることがわかりました。 特に、リリースサイクルとデプロイの分離が実現できることはとても魅力的と感じました。 一方で実際にプロジェクトに導入する場合は、開発サイクルの改善やプロジェクト内の教育など、それなりの労力がかかります。そのため、プロジェクトに導入したときのメリット・デメリットをきちんと整理したうえで判断していくのが大切だと思いました。 この記事には載せていませんが、理解を深めるためFeature Flagを利用したデモアプリを構築してみました。 今後記事にまとめる予定のため、興味がある方はそちらも読んでいただけるとありがたいです!最後まで読んでいただきありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 6.参考文献 What are feature flags? - optimizely.com How to Use Feature Flags in Azure DevOps for Seamless Feature Management - www.featbit.co Feature Toggles (aka Feature Flags) 執筆: @suzuki.takuma 、レビュー: @handa.kenta ( Shodo で執筆されました )
はじめに こんにちは、クロス イノベーション 本部エンジニアリングテク ノロ ジー センターの徳山です。 この記事は、2025年5月 20日 に公開されたばかりの Terraform MCP Server を GitHub Copilot in VS Code ( 以降、Copilotと記載 ) で使用してみた体験談です。Terraform MCP Server の導入前のコード生成状況から導入内容、導入後のコード生成結果までを紹介します。 Terraform MCP Server は、 LLM (大規模 言語モデル ) の AIエージェントへ Terraform のプロバイダーとモジュールの検出やプロバイダーのリソース詳細情報取得などの機能を提供する MCP サーバです。 Terraform MCP Server の導入前の Copilot は、Terraform の AzureRM には存在しない架空のプロパティを提案してきていましたが、導入後はシャキッと AzureRM にもとづくコードを提案してくれるようになりました。 1. Terraform MCP Server 導入前 Copilotへの依頼内容 Copilot に今回依頼する内容は、こちらの azurerm_logic_app_workflow のサンプルコードへワークフロー定義を追加してもらいたいというものです。 サンプルコード Copilot へ依頼した結果 ( Terraform MCP Serverがないとき ) Copilot へ依頼すると以下のコードが提案されましたが、 definition というプロパティは terraform の AzureRM に存在しないためにコード上でエラーになっています。 提案されたコード エラー内容 2. Terraform MCP Server を導入 Terraform MCP Serverは、先日2025年5月 20日 にHashicopから公開された MCP サーバです。 今回は Building the Docker Image locally にしたがって Docker Image で導入しました。 導入手順の make docker-build の後、 VS Code で mcp . json へ以下のとおり記載し、 MCP サーバが停止していれば起動します。 mcp . json Terraform MCP Server 起動 Copilot Chat を開いて Agentモードで Tools を確認した結果は以下のとおりで、Terraform MCP Serverが利用できる状態であることを確認できます。 Agent Tools (一覧の下の方がTerraform MCP Server) 3. Terraform MCP Server 導入後 azurerm_logic_app_workflow のサンプルコードへのワークフロー定義の追加を再度依頼しましたが、導入後でも導入前と同等のコードを再び提案してくるため、少しプロンプトを工夫して Copilot に AzureRM を参照してもらうようにしました。 依頼した際に Copilot が resolveProviderDocID や getProviderDocs の使用許可を求めてきてくれたら許可し、それで terraform の AzureRM を参照してくれるようになりました。 当記事の作成にあたって何度かプロンプトを試した中では、「terraformの公式情報を参照して」や「AzureRMの情報を参照して」などと具体的な条件を Copilot へ示すと話が早いように思います。 導入後に依頼1 : 別の Tools をまだ使用して期待とは異なる回答 導入後の依頼2 : Terraform MCP Server の resolveProviderDocID や getProviderDocs を使用し始めてコード生成 実際に生成されたコード ( Terraform MCP Server があるとき ) resource "azurerm_logic_app_workflow" "daily_trigger" { name = "daily-trigger-workflow" location = azurerm_resource_group.eode_datapf.location resource_group_name = azurerm_resource_group.eode_datapf.name workflow_schema = "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#" workflow_parameters = jsonencode({ "definition" = { "triggers" = { "Recurrence" = { "type" = "Recurrence" "recurrence" = { "frequency" = "Day" "interval" = 1 "startTime" = "2025-05-22T01:00:00Z" } } } "actions" = { "Trigger_Container_App_Job" = { "type" = "Http" "inputs" = { "method" = "POST" "uri" = azurerm_container_app_job.generate_tpcds_refresh_data.manual_trigger_url "headers" = { "Content-Type" = "application/json" } } } } "contentVersion" = "1.0.0.0" } }) } 上記の生成されたコードでは、コード上のエラーは発生していません。 このまま terraform apply するとエラーになってしまう内容ではありますが、存在しないプロパティを生成してくることはひとまずなくなりました。 さいごに Terraform MCP Server を導入することで GitHub Copilot の生成内容の精度向上を体験できました。 導入後も導入前と同様に definition という間違ったプロパティを提案することがあるのですが、これは GitHub Copilot for Azure の Tools を使用してAzureの公式ドキュメントを参照している影響と推察しています。 根拠は、Copilot の稼働時に @azure get code gen best practices という GitHub Copilot for Azure の Tools を利用しているログが確認できることと、 Azure公式情報のこちらのページ のとおり Azure Logic Apps のワークフロー定義には definition が存在しているためです。 当然のことではありますが、目的や用途に応じて MCP サーバや Agent の Tools を適切に選別して切り替えることが重要であるとあらためて感じました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 執筆: @shikarashika 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
こんにちは。コーポレート本部 サイバーセキュリティ推進部の耿です。 2025年3月に tj-actions や reviewdog など有名な サードパーティ アクションを巻き込んだ サプライチェーン 攻撃が発生しました。 GitHub や GitHub Actions の仕様を熟知したうえで悪用した、非常に巧妙な攻撃手法です。 ( 詳細 ) GitHub Actions の action の利用は、自組織の GitHub リポジトリ で第 三者 が書いたコードの実行を許すことに他なりません。その便利さと引き換えに一定のセキュリティリスクを孕んでいることを意識し、正しく対策を考えることが重要です。 今回発生したような侵害への防止策として、 GitHub Actions の action を利用する場合のバージョン指定はバージョンタグではなく、フルコミットハッシュ(SHA)を利用して「ピン留め」すること( 参考 )が、より一層推奨されるようになっています。Gitのコミットハッシュは、 リポジトリ 内のオブジェクトを階層的に SHA-1 ハッシュ化したものなので、コードが書き換わるとコミットハッシュも勝手に変わります。つまりコミットハッシュでバージョン指定することにより、実行されるコードを事実上固定することができます。 しかし、コミットハッシュによるバージョン指定だけで話を終えるのは不十分です。運用まで考えたときに、コミットハッシュで指定したバージョンの更新方法にも気を配らないと、全くリスク低減にならない可能性があります。 GitHub Actions の action をバージョン更新する際の運用負荷とセキュリティリスク低減のバランスについて考えてみます。 (おさらい)フルコミットハッシュ(SHA)によるバージョン指定 バージョン更新の課題 解決策1:セキュリティアップデート以外は更新を自動化しない 解決策2:セキュリティアップデート以外は自動更新を一定期間待機する 自動更新の待機期間をどれぐらいにするべきなのか? (おさらい)フルコミットハッシュ(SHA)によるバージョン指定 様々なところで言われていることなので詳細は省きますが、 GitHub Actions の action に付けられたGitタグは書き換え可能なので、バージョンタグでバージョン指定すると、実行されるコードは可変です。 ❌ - uses : actions/checkout@v4 アクションのREADMEや技術記事などでバージョンタグによる例が記載されていても、自分で利用するときにはフルコミットハッシュに書き換えるようにしましょう。 ✅ - uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 バージョン更新の課題 フルコミットハッシュでバージョン指定をしても解決されない課題があります。どのようにバージョン更新を行うかという課題です。 特に多くの GitHub Actions の action を使っている場合や、多くの リポジトリ を管理している場合は、バージョンの更新を手作業で実施するのは手間がかかります。手間を減らすために、Dependabot や Renovate を利用して更新を自動化することがよく行われています。(Dependabot と Renovate は、フルコミットハッシュでバージョン指定していても更新のPRを作成できます。) ここで留意しておきたいのは、更新を自動化することにより、 新しいバージョンが侵害されていた場合には影響を受けやすくなってしまう ということです。今回 tj-actions/changed-files が侵害を受けた件でも、バージョンタグが(悪意のあるコミットを指すように)更新されたことを検知し、多くの リポジトリ で動いている Renovate がバージョン更新の PR を出していました( 参考 )。さらに、 GitHub Actions のワークフローでよく使われる push の イベントトリガー では、更新されたブランチ(つまり更新された新しいバージョン)のワークフローファイルが実行されます。つまり自動作成された PR をマージせずとも、PR が自動作成されただけで侵害されたバージョンが自分の リポジトリ 上で実行される可能性があるということです。 まとめると、以下の攻撃が成立し得ます: 利用している GitHub Actions の action が侵害され、リリースされる。あるいは更新された新しいバージョンに 脆弱性 が含まれる DependabotやRenovateがバージョン更新を検知し、PRを作る push イベントをトリガーに実行されるワークフローでは新しいバージョン(侵害されたバージョン or 脆弱性 のあるバージョン)の GitHub Actions の action が実行する 自分の リポジトリ が被害を受ける GitHub Actions の action の更新の自動化には気を遣わなければならないと考えています。 解決策1:セキュリティアップデート以外は更新を自動化しない 自動化せずにアップデートを手作業で行えばリスクは低減できますが、運用負荷が上がってしまいます。 特に GitHub Actions のセキュリティアップデートを自動化しない場合、対応までの時間が伸びてしまいます。 両者のバランスを考えたときに、(一定のセキュリティリスクが発生することを承知の上で)「セキュリティアップデートのみを自動化し、それ以外のアップデートは自動化しない」方針が考えられます。 セキュリティアップデートのみの自動化は次の方法で実現できます。 Dependabotの場合 Dependabot Version UpdatesではなくDependabot Security Updatesを利用する Renovateの場合 脆弱性 のみ自動更新するように設定する( 参考 ) セキュリティアップデート以外を自動化せずに更新する部分については、完全に手作業でコミットハッシュをコピー&ペーストしていくのは大変なので、ローカルツールの pinact などでコミットハッシュを更新するのも良いでしょう。 解決策2:セキュリティアップデート以外は自動更新を一定期間待機する Renovateには、セキュリティアップデート以外の更新をすぐにせずに指定期間待機してからPRを作成する機能があります。一定期間待機することで、その間に攻撃や 脆弱性 が検知・解決されることを期待するという考え方です。 Renovateの場合: minimumReleaseAge パラメータ https://docs.renovatebot.com/configuration-options/#minimumreleaseage Dependabot Version Updatesにも、最近のアップデートで同じような機能がベータ版として出ています。ただし現時点ではまだ GitHub Actionsはサポートされていないようです。使えるようになったら試してみたいと思います。 Dependabotの場合:cooldown パラメータ(今後に期待) https://github.com/dependabot/dependabot-core/issues/3651 (2025/7/10追記)Dependabotのcooldownパラメータが正式リリースされましたので、 解説記事 を公開しました。 自動更新の待機期間をどれぐらいにするべきなのか? これは答えが出ない問題です。 GitHub Actions の action による悪さを「100%」回避するには次のどちらかしかないと思っています。 GitHub Actions の action をそもそも利用しない GitHub の公式 action( actions/checkout など)が侵害されるリスクも考慮し、利用しないとすると、「 GitHub ActionsによるCI/CDを実施しない」こととほぼ同義になると思います GitHub Actions の action のバージョン更新をするときに「コードの内容を全て読み」、「動きを完全に理解」したうえで更新する このどちらも現実的ではないのは明らかです。 GitHub Actions の action を利用することの便利さを享受するには、一定のリスクを受容するしかないと考えています。待機期間の判断も同じです。今回の侵害の件では tj-actions に対する侵害は数日で対応されましたが、 reviewdog に対する侵害の対応には1週間以上かかったようです。どれぐらいの期間を置けば侵害や 脆弱性 が検知され、受容できる程度までリスクが低減するかは程度問題でしかありません。 リポジトリ の重要度と運用の手間で判断することになるでしょう。 それでもあえて個人的な考えを述べるならば、1週間程度は少なくとも待ちたい気持ちがあります。(根拠はなく、ただの個人的な感覚であることを強調しておきます。) お読みいただきありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 執筆: @kou.kinyo 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
初めまして。エンタプライズ第一本部の佐藤です。 前回は MCP サーバーの立ち上げ方の記事を作成しましたが、今回はこのサーバーに接続するClientを用いて接続側の実装をするという試みです。 前回記事はこちら 【初心者向け】2年目エンジニアが実践したMCPサーバー構築ガイド2025 既出の内容かつ不正確な部分もあるかもしれませんが、私の個人的な検証記録として残しておきたいという目的でこの記事を作成します。 最近 MCP サーバーが盛り上がってますが、サーバーへ接続する方法も知りたかったので本記事を作成します。 前回の記事では以下の図の赤枠の部分を実装しました。 上側の水色の矢印部分にあたるツール呼び出し部はClaudeDesktopに内包されていたので、前回はこの部分のロジックを意識せずにツールを実行できるかという部分を検証していました。 さて、ClaudeDesktopから呼び出せるのも良いのですが、通信規格を統一したというところの良さを実感しつつ、なぜツールが選択できているのかを詳しく見ていきます。 では、例のごとく MCP QuickStartの For Client Developers を参考にClientの実装を行いましょう。 まずは環境構築です。 上から順に、 作業 ディレクト リの作成 仮想環境を有効化 仮想環境に必要な依存関係を追加 main.pyを削除(client.pyで作業するため) client.pyを作成 という操作をしています。 client.pyが今回、コードを作成する部分です。 # Create project directory uv init mcp-client cd mcp-client # Create virtual environment uv venv # Activate virtual environment # On Windows: .venv\Scripts\activate # On Unix or MacOS: source .venv/bin/activate # Install required packages uv add mcp anthropic python-dotenv # Remove boilerplate files rm main.py # Create our main file touch client.py 次はAnthropicの API キーを取得して.envファイルに書いておきます。 ファイルの作成 # Create .env file touch .env この.envファイルを編集して以下の記述を追加。 your key hereの部分は API キーを記載します。 API キーの取得方法はこちらで、 APIのページ に遷移して、StartBuildingを押下し、GetAPIkeysを選択します。 Create Keyを選択して、任意の名前をつけてAddを押下。 次のポップアップでキーが出るのでコピーしてください。 【注意】この API キーは厳重に保管し、使用しなくなった場合は削除をしてください。流出すると思わぬ課金の原因になります。 ANTHROPIC_API_KEY=<your key here> 次に以下のコマンドで.gitignoreにこのファイルを追加します。 構成管理に含めないことで API キーがリモートレポジトリに保存されないようにします。 echo ".env" >> .gitignore 以上で環境構築は終了です。 次にclient.py内で作業を行っていきます。 import asyncio from typing import Optional from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from anthropic import Anthropic from dotenv import load_dotenv load_dotenv() # load environment variables from .env class MCPClient : def __init__ (self): # Initialize session and client objects self.session: Optional[ClientSession] = None self.exit_stack = AsyncExitStack() self.anthropic = Anthropic() async def connect_to_server (self, server_script_path: str ): """Connect to an MCP server Args: server_script_path: Path to the server script (.py or .js) """ is_python = server_script_path.endswith( '.py' ) is_js = server_script_path.endswith( '.js' ) if not (is_python or is_js): raise ValueError ( "Server script must be a .py or .js file" ) command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, args=[server_script_path], env= None ) stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) await self.session.initialize() # List available tools response = await self.session.list_tools() tools = response.tools print ( " \n Connected to server with tools:" , [tool.name for tool in tools]) ここまでがサーバーとの接続を担う部分です。標準入出力を用いるようにしています。 async def process_query (self, query: str ) -> str : """Process a query using Claude and available tools""" messages = [ { "role" : "user" , "content" : query } ] response = await self.session.list_tools() available_tools = [{ "name" : tool.name, "description" : tool.description, "input_schema" : tool.inputSchema } for tool in response.tools] # Initial Claude API call response = self.anthropic.messages.create( model= "claude-3-5-sonnet-20241022" , max_tokens= 1000 , messages=messages, tools=available_tools ) ここまでで入力された質問に関する解析を行っています。 具体的には関数(ツール)の説明文と引数の型の情報をもつ使用可能なツールをLLMに渡しています。 図のようなイメージでツール情報と質問文をLLMに投げちゃってます! # Process response and handle tool calls final_text = [] assistant_message_content = [] for content in response.content: if content.type == 'text' : final_text.append(content.text) assistant_message_content.append(content) elif content.type == 'tool_use' : tool_name = content.name tool_args = content.input # Execute tool call result = await self.session.call_tool(tool_name, tool_args) final_text.append(f "[Calling tool {tool_name} with args {tool_args}]" ) この部分でさっき使えるツール情報と質問文を投げた結果を用いています。 イメージは以下の図で天気の質問なら、天気取得ツールを使った方が良いですねとなっているということです。 ここでresultに使用すべきツールとして”use_tool”フラグがついた関数を呼び出した結果を格納し、LLMに渡すメッセージの一部として格納しています。 assistant_message_content.append(content) messages.append({ "role" : "assistant" , "content" : assistant_message_content }) messages.append({ "role" : "user" , "content" : [ { "type" : "tool_result" , "tool_use_id" : content.id, "content" : result.content } ] }) # Get next response from Claude response = self.anthropic.messages.create( model= "claude-3-5-sonnet-20241022" , max_tokens= 1000 , messages=messages, tools=available_tools ) final_text.append(response.content[ 0 ].text) return " \n " .join(final_text) ここで最初の質問文である「今日の天気は?」と利用した天気取得ツールからのレスポンス「晴れ」を用いて、回答を出力します。 イメージは以下の図の通りです。 ここまでで、LLMへのリク エス トは2回しています。 まとめると、1つのツールを呼びだすにはツールの判定、質問文と結果のセットで回答生成というプロセスを背後で行うということです。 async def chat_loop (self): """Run an interactive chat loop""" print ( " \n MCP Client Started!" ) print ( "Type your queries or 'quit' to exit." ) while True : try : query = input ( " \n Query: " ).strip() if query.lower() == 'quit' : break response = await self.process_query(query) print ( " \n " + response) except Exception as e: print (f " \n Error: {str(e)}" ) async def cleanup (self): """Clean up resources""" await self.exit_stack.aclose() async def main (): if len (sys.argv) < 2 : print ( "Usage: python client.py <path_to_server_script>" ) sys.exit( 1 ) client = MCPClient() try : await client.connect_to_server(sys.argv[ 1 ]) await client.chat_loop() finally : await client.cleanup() if __name__ == "__main__" : import sys asyncio.run(main()) 最後に、先ほどの回答生成プロセスの実体であるprocess_query関数で回答を生成するようにしています。 これでサーバーを起動する側の準備が整いました。 前回の続きでサーバーを立てていればweather.pyが利用できるはずなので、こちらを指定してclient.pyでweather.pyを起動します。 #pathは自分の環境に設定してください uv run client.py path/to/weather.py このコマンドを実行すると、以下のようにコンソールでLLMを使用でき、 MCP サーバーにも接続できています。 実際に使用可能なツールとして、add、get_alerts、get_forecastの3つが表示されました。 Queryの後に「 サクラメント の天気は?」のように適当な指示文を渡すと回答が生成されます。 この検証を応用してインタフェースを変えることもできそうですね! 以上で作業を終わります。 ここまで読んでくださり、ありがとうございました! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.yu 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
はじめに 初めまして。 エンタープライズ 第一本部、2年目の佐藤悠です。 Datadogは様々な環境でのモニタリングが可能で、監視対象の統合に向いておりオンプレミスと クラウド 環境のハイブリッドの場合や複数 クラウド の組み合わせなど、インテグレーションの多様さで柔軟に対応できるのが強みであると思います。 個人的には使いやすくて、デザインが好きなので気に入っています。 この記事ではAmazonLinuxのマシンイメージを使用したEC2 インスタンス のメトリクスを、手順のスクショや図解をもとに ダッシュ ボードに表示するところまで実施します。 作業手順 無料トライアルを開始します。 以下のサイトにアクセスし、画面中央にある無料トライアルボタンを押下します。 https://www.datadoghq.com/ 以下の画面で必要事項の入力が求められるので、これを埋めます。 一応、リージョンが日本になっていることを確認してください。 入力後サインアップのボタンを押下することで以下のアンケートの画面に遷移します。 全て任意の内容なので実施はお任せします。画面下部までスクロールしNextのボタンを押下します。 遷移先では以下の画面になります。 今回は AWS 上のマシンイメージがAmazonLinuxのEC2 インスタンス を立ち上げて監視対象にします。 左側の Linux を選択して、コマンドをコピーしておきます。 では、 AWS でEC2を立ち上げます。 今回はコマンドをEC2上で実行する必要があるので、セッションマネージャー(以降:SSM)でEC2に接続する設定も同時に行います。 インスタンス 名などは任意のものを使用しますが、OSに関しては指定します。 インスタンス タイプもデモなので最も小さいサイズのものにしています。 ネットワーク設定はSSMを使用するため、アウトバウンドの443ポートを許可しています。 Datadogの ネットワークトラフィック の資料を参考にするとAgentを使用する ユースケース に限定すれば、アウトバウンドの443ポートの解放のみで動作します。 ネットワーク設定に関する詳細はリンク先の資料をご確認ください。 次に、以下の部分の高度な設定を編集して、SSMに必要なロールを持つ インスタンス プロファイルをアタッチします。 ない場合には赤枠の部分から作成します。 遷移先の画面でロールの作成を押下して以下の画面に遷移します。 ラジオボタン で ユースケース を選択して、System Manager用のロールを作成出来ればOKです。 ここで任意のロール名をつけて先ほどのEC2 インスタンス の画面で インスタンス プロフィールに作成したものを選択すれば、準備完了です。 プロフィールが表示されない場合は少し時間をあけてリロードボタンを押下すれば出ます。 インスタンス が立ち上がったことを確認し、作成した インスタンス IDを押下し、 インスタンス 概要の画面で右上の接続ボタンを押下します。 タブの切り替えでセッションマネージャーを選択し、接続を押下します。 (接続ボタンがグレーになっているときは、ネットワークの設定、ロール、またはポリシーの設定ができていない場合が多いです) インスタンス に接続後は CUI の画面で先ほどのDatadogで表示されていたコマンドを実行します。 このままコピー&ペーストして実行でOKです。 これでDatadogのエージェントがEC2 インスタンス にインストールできました。 現時点での構成を図にして解説します。 今回はデモなので図のような構成になっています。 実際にはEC2をプライベートサブネットに配置し、NAT ゲートウェイ を使用して外部と通信するのが望ましいですが、コストの関係で実施していません。 では、Datadog画面左下のIntegrationsにカーソルを合わせ赤枠のView Agentsを押下して実際にデータが取得できているかを確認しましょう。 遷移先でView Agentsを選択し表示を見ると以下のとおりでAgentをインストールしたEC2 インスタンス がDataDogと接続できていることが確認できました。 以下の画像で考えると インスタンス IDがHOSTNAMEと一致しているかで確認できます。 次に画面左側のツールの赤枠、Infrastructureを押下します。 ここで遷移した先に、監視対象のEC2 インスタンス が表示されています。 右赤枠のどこでもいいので押下すると詳細な表示を確認することができます。 表示される インスタンス に関する詳しいメトリクスは以下のようになっています。 ここで取得した内容を ダッシュ ボードに表示します。 これは環境ごとの ダッシュ ボードを作成し、一元管理することによって見通しが良くなることが期待できます。 以下の画像のように、開発・ STG ・PRODのように分離することが実際の運用ではありそうですね。 では実際に作成します。 左側ツール赤枠にカーソルを合わせNew Dashboard を押下します。 以下の表示で名前と Dashboard の概観を決定します。 以下の画面に遷移したら右側上赤枠の検索窓でHOSTと検索し、右下赤枠のHosts Overviewを押下します。 以下の画面で左側赤枠のPowerpack filtersで先ほどのhostを選択します。 右下のconfirmを押下して Dashboard の ウィジェット に追加します。 画面のようにモニタリング内容を Dashboard に追加することができました。 モニタリングしているCPUの使用率を高めて、画面に反映されるか確認します。 EC2 インスタンス に接続して以下のコマンドを実行します。 sudo yum install stress stress -c 1 CPUに負荷をかけるstressをインストールして、実際にCPUに負荷をかけます。 この結果がすぐにモニターに反映されUsageが上昇しました。 これでEC2 AmazonLinuxを監視し、選択した内容を ダッシュ ボードに追加することができましたね! まとめ 複雑化し続ける クラウド アーキテクチャ ーをDatadogで一元管理! 14日間はホスト数に制限はありますが、全ての機能が無料で使えます。 以降は機能に制限はかかりますが検証用には使えるのでありがたいですね! 無料期間のスタート ダッシュ に本記事を参考にしていただければ幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.yu 、レビュー: @yamada.y ( Shodo で執筆されました )