TECH PLAY

GitHub

イベント

マガジン

技術ブログ

こんにちは、プロダクトエンジニアリング部の江口です。 私は普段 LIFULL HOME'S 賃貸マーケットの技術負債解消に取り組むエンジニアリングチームのリーダーをしています。 本記事では、賃貸マーケットで長年の課題だった「ユーザー行動ログ送信処理」の技術負債解消について紹介します。技術的な取り組みだけでなく、エンジニアとして技術負債にどう向き合っていくべきかを考えるきっかけにもなった経験をお伝えできればと思います。 何が問題だったのか なぜこれまで解決できなかったのか どうアプローチしたか 難易度を下げてから勝負する 依存先の調査 古いログへの依存を一つずつ断ち切る 実装・テスト・移行での工夫 プロジェクト推進の難しさ やり遂げて思ったこと:技術負債解消とROI 終わりに 何が問題だったのか 賃貸物件の詳細ページは過去にリニューアルされ、新システムへ移行済みでした。しかし、ユーザー行動ログの送信処理だけは旧システムに残ったままで、新システムから旧システムのエンドポイントを叩くことでこの処理を委譲していました。つまり、詳細ページを1回表示するたびに、バックエンドへのリクエストが実質2倍発生している状態です。 これまでの行動ログ送信処理 これによって、負荷が集中する繁忙期には、DBのパフォーマンスの低下も懸念されました。もちろん、私たちも新システム側でクローラー判定を行い旧システムへのリクエストを抑制したり、ログの生成に関係のないバックエンドへのリクエストを停止したりと、できる限りの負荷軽減策を講じてきました。しかし、これらはあくまで対症療法に過ぎません。この状況を根本から解消するため、私たちのチームがこの問題に決着をつけることになりました。 これまでの試行錯誤の歴史については、ぜひこちらの記事もご覧ください。 www.lifull.blog なぜこれまで解決できなかったのか 過去にもログ送信処理の移植は試みられてきましたが、以下の理由により断念されてきました。 膨大な移植コスト : 約10年前のレガシーなロジックが複雑に絡み合い、2〜3名で1年かけても終わらない規模 品質担保の壁 : 不動産会社が掲載効果を分析するためのレポート機能に関わるため、集計結果の変動を許容しづらい ログの完全一致が困難 : 旧システムと新システムでは物件情報データの取得経路に微妙な違いがある どうアプローチしたか 難易度を下げてから勝負する このプロジェクトで最も重要だった判断は、「難しい問題を頑張って解く」のではなく、「問題の難易度そのものを下げる」という方針を取ったことです。 具体的には、ユーザー行動ログに依存する周辺システムを、社内でデファクトスタンダードとなっているユーザー行動解析基盤であるTealiumへ先に移行させることで、古いログへの依存を一つずつ断ち切っていきました。 これにより、新システムへの移行時に保証すべきログの属性値(スキーマ)を大幅に削減し、「守るべき範囲」を意図的に狭めることで、テスト工数と不確実性を下げました。 依存先の調査 まず、ログが格納されたS3のバケットポリシーから、アクセスが許可されているアカウントやロールを洗い出しました。中には過去に使われていたと思われるAWSアカウントからの許可や、AWSアカウントレベルでの広いアクセス許可が残っており、具体的にどのシステムからのアクセスなのかがポリシーだけでは判別できないケースもありました。そうした許可については、CloudTrailで実際のアクセス有無を確認しながら整理を進めています。その上で、S3の接続情報をキーにGitHub上のリポジトリを横断検索し、該当するコードを地道に追っていきました。それでも埋まらない部分は、社内のConfluence・Jira・チャットログ・GitHub Discussionsを当たり、マーケットの垣根を越えて担当者に直接確認することで、一つずつ依存先を明らかにしていきました。 最終的に、ログに直接依存しているシステムは20以上にのぼり、さらにBigQueryにも連携されていたため、その先ではアナリストも分析に利用している状況でした。 この調査は、このプロジェクトで最も骨が折れたパートです。 アーキテクチャ図 古いログへの依存を一つずつ断ち切る 依存先が明らかになった後は、そのうち賃貸の物件詳細ページのログに直接関わるシステムについて、Tealium経由のデータに切り替えてもらう調整に入りました。しかし、これが一筋縄ではいきませんでした。レガシーなシステムであるがゆえに、主管部署の担当者自身がシステムの仕様を十分に把握できていないケースが多く、「何のデータをどう使っているのか」を一緒に紐解くところから始める必要がありました。 アナリストに対しては、BigQuery上のデータ切り替えについて告知を行いました。実態としては、アナリスト自身もどのデータを使っているか明確に把握しきれていない状況でしたが、データを外部に販売しているような重要度の高い用途については、個別にしっかりヒアリングを行い、影響がないことを確認しました。 また、依存先の洗い出しを促進するため、プロジェクトの進捗や影響範囲を社内に定期的に周知し、「自分のシステムも該当するかもしれない」と気づいてもらえるよう働きかけました。 実装・テスト・移行での工夫 テストでは、旧システムから出力されるログと新システムから送信されるログを突き合わせて比較する方針を取りました。完全一致しない項目については、後続のシステムで実際に使用されていないことを一つずつ確認しています(今後、使われていない項目は削除していく予定です)。 特に重要だったのは、不動産会社向けレポート機能への影響確認です。集計バッチの出力結果を旧ログ・新ログそれぞれで比較し、差分がないことを検証しました。ただ、テスト環境自体にパフォーマンスの課題やCI/CDの課題があり、動作確認を回すだけでもかなりの労力がかかりました。 移行は段階的ではなく、一括で切り替えました。二重にログを保持し続けるコストや、切り戻し時に古いログで後続システムを再実行する運用の複雑さを考慮し、この方式を選択しています。万が一の失敗に備え、リカバリー方針と関係者への対応フローを事前に策定しておくことで、リスクをコントロールしました。 プロジェクト推進の難しさ このプロジェクトは、技術的な難しさ以上に、推進そのものに大きな負荷がかかるものでした。 自チームでのログ送信処理の移行作業に加え、旧ログに依存する周辺システムについて、Tealiumへのリアーキテクチャのサポートとコードレビューを自ら担いました。各システムの技術スタックも事情も異なる中で、設計方針の提示からレビュー、問題発生時の判断までを一手に引き受ける形です。 さらに、主管部署の担当者がシステムの仕様を把握しきれていないケースでは、仕様の調査や整理から伴走する場面も少なくありませんでした。マーケットや部署の垣根を越えた調整を重ね、関係者全員が同じゴールに向かえる状態を作ることも、このプロジェクトにおける自分の重要な役割でした。 やり遂げて思ったこと:技術負債解消とROI この取り組みをやり遂げた今、「これは本当に事業に必要だったのか?」と自問することがあります。 バックエンドへの負荷が実質2倍という状態は健全ではなく、実際に繁忙期にはDBへの負荷が問題になっていました。解消すべき課題だったのは間違いありません。一方で、技術負債の解消にはそれなりのコストがかかり、その間ユーザーに直接価値を届ける開発に割けるリソースは減ります。そのバランスをどう取るかは、リーダーとして常に問い続けるべきテーマだと感じるようになりました。 この経験を通じて、技術負債解消は「エンジニアが気持ちよく開発するため」だけではなく、事業のROIを踏まえた上で優先度を判断すべきものだという考えに変わりました。今回のケースはDB負荷という差し迫った理由があったからこそ正当化できましたが、常にそうとは限りません。 今後は、技術負債の解消を特別なプロジェクトとして切り出すのではなく、日常の開発の中で自然に改善していける状態を目指したいと考えています。エンジニアとしての本分は、技術力でユーザーに価値を届け、事業の優位性を築くことにあるはずです。プロダクト開発に軸足を置きながら、健全なコードベースを維持していく——そのサイクルを回せるチームでありたいと思っています。 終わりに 本記事では、LIFULL HOME'S 賃貸マーケットにおける技術負債解消の取り組みについて紹介しました。 このプロジェクトの作業は泥臭いものの連続でしたが、それを完遂できたときの達成感は大きく、負債解消へのインパクトも確かなものでした。同時に、「技術的に正しいこと」と「事業として正しいこと」の間で考え続けることの大切さも学びました。 LIFULL では、こうした課題に一緒に向き合い、ともに成長していける仲間を募集しています。興味を持っていただけた方は、ぜひ以下のページもご覧ください。 hrmos.co hrmos.co
G-gen の奥田です。当記事は、2026年3月19日に開催された Agentic AI Summit 2026 Spring で筆者と弊社の今野が登壇したセッション「Gemini Enterprise と ADK で実現する業務特化型エージェント開発の最前線 — ユーザー認証連携における Google Workspace 操作の実装方法と事例を公開」のレポートです。 セッションの概要 なぜ AI エージェントにユーザー認証が必要なのか サービスアカウントのリスク OAuth 2.0 によるユーザー認証連携 デモ : 議事録カレンダー登録エージェント デモの概要 Google Workspace への応用 なぜ ADK を選んだのか コードの実装 構成図とソースコードのディレクトリ構成 エージェントの定義 認証トークンの取得とカレンダー API の呼び出し 開発中に遭遇した落とし穴 ADK の State オブジェクト LiteLLM のセキュリティに関する注意 Gemini Enterprise へのデプロイ 参考リンク セッションの概要 当セッションでは、AI エージェントが Google Workspace のサービスを操作する際に不可欠な ユーザー認証連携 をテーマに、実装方法と事例を紹介しました。 セッション前半では、AI エージェントにおける認証の重要性を具体的なシナリオで解説し、議事録から Google Calendar にイベントを自動登録するエージェントのライブデモを行いました。後半では、 Gemini Enterprise 、 Agent Development Kit (以下、 ADK )、 Agent Engine を組み合わせたアーキテクチャと、 OAuth 2.0 連携の実装ステップを紹介しました。 参考 : Gemini Enterprise 参考 : Agent Development Kit(ADK) 参考 : Agent Engine 参考 : OAuth 2.0 Agentic AI Summit 2026 Spring での実績は以下のとおりです。 合計 オンライン オフライン CSAT G-gen セッション 741 名 556 名 185 名 3.9 なぜ AI エージェントにユーザー認証が必要なのか サービスアカウントのリスク AI エージェントを業務で利用する場合、「エージェントが誰として外部サービスにアクセスするのか」が重要な設計課題です。 たとえば、社員 A さんが AI エージェントに「来週の火曜日に会議を入れて」と依頼したとします。エージェントは A さんの代わりに Google Calendar を操作する必要があります。このとき、システム共通のサービスアカウントに権限を一括付与して動かすと、エージェントは A さんだけでなく B さんや他の組織の C さんのカレンダーにもアクセスできてしまいます。 技術的には動作しますが、業務での利用には適しません。AI エージェントは人間と違い、バックグラウンドで大量のデータに触れることができます。だからこそ「誰として動いているのか」を明確に設計することが不可欠です。 OAuth 2.0 によるユーザー認証連携 この課題の解決策が OAuth 2.0 を使ったユーザー認証連携です。 仕組みはシンプルです。ユーザーが一度ログインして権限を許可すると、エージェントはそのユーザーのアクセストークンを使って API を呼び出します。エージェントはそのユーザーの権限の範囲内でしか動作しません。 A さんの AI エージェントが行った操作は「A さんが自分の権限で実行した」という記録です。コンプライアンスの観点でも重要なポイントです。 デモ : 議事録カレンダー登録エージェント デモの概要 セッションでは、議事録や ToDo メモからタスクを抽出し、Google Calendar にイベントを自動登録するエージェントのライブデモを行いました。 デモの流れは以下のとおりです。 Gemini Enterprise 上でエージェントを起動し、OAuth 認証を完了する 直近のカレンダー予定を確認する 日時とタイトルを入力してイベントを追加する 約 6,300 文字の架空の議事録(ポータルサイトプロジェクト)をエージェントに渡す エージェントが議事録から 9 個の ToDo を抽出し、カレンダーに一括登録する 不要なプライベートの予定を削除する デモで約 6,300 文字の議事録を使用したのは、 Gemini の従来の強みである大規模コンテキストウィンドウを利用するため です。長文の議事録であっても分割せずに一度で処理できることを示す狙いがありました。 エージェントは 9 個のタスクを抽出しましたが、そのうち 2 つは会議のアイスブレイク時に話題に上がったプライベートな内容(入学式の話)でした。エージェントがこれらを業務タスクと同列に扱ってカレンダーに登録しました。 デモ後半では「プライベートの予定を削除」と入力するだけで、エージェントが自律的に予定の性質を判断し、該当するイベントのみを削除しました。これは、エージェントが文脈を理解し、カレンダーの内容を自律的に評価した実践的な事例です。 このエージェントの導入効果として、 議事録 1 件あたりのカレンダー登録作業を手動10分から自動で1分程度に短縮 できます。タスクの抜け漏れを防ぐ実用的なエージェントです。 Google Workspace への応用 今回のデモでは Google Calendar を題材にしましたが、同じ OAuth 2.0 連携の仕組みを使うことで、Google Workspace の各サービスにも応用できます。 Gmail では、エージェントがユーザー本人としてメールボックスに下書きを作成したり、宛先・件名・本文を含むメールを送信できます。たとえばミーティングの議事録をまとめて関係者にフォローアップメールを自動送信するといった使い方が可能です。 Google ドキュメント や Google スライド では、箇条書きや構成案を渡すだけでドキュメントやスライドの雛型を作成できます。ユーザーはブラッシュアップやクリエイティブな作業に集中できます。 Google ドライブ では、ユーザー自身がアクセス権を持つファイルだけを検索できるため、他者のファイルに触れるリスクがありません。社内ドキュメントをナレッジベースとして RAG 構成と組み合わせる使い方が効果的です。 共通するポイントは、すべての操作がユーザー本人の権限の範囲内で動作することです。 なぜ ADK を選んだのか エージェント開発のフレームワークには LangChain や CrewAI など複数の選択肢があります。今回 ADK を採用した理由は大きく2つです。 1つ目は、 Google Cloud のエコシステムで完結させたかった ことです。フロントエンドの Gemini Enterprise、実行基盤の Agent Engine、そして開発フレームワークの ADK をすべて Google Cloud のサービスで統一することで、認証トークンの受け渡しやデプロイがマネージドサービス間でシームレスに連携します。前述のとおり、 tool_context.state を通じた OAuth トークンの自動受け渡しは、この統一されたエコシステムがあってこそ実現できる仕組みです。 2つ目は、 ADK と OAuth 2.0 を組み合わせた実装事例がまだ少なかった ことです。ADK 自体の利用例は増えていますが、Gemini Enterprise の OAuth 連携と組み合わせて Google Workspace を操作するパターンは、2026年4月現在でも公開事例が限られています。今回のセッションとデモを通じて、この実装パターンを広く共有したいという意図がありました。 コードの実装 構成図とソースコードのディレクトリ構成 構成図は以下です。 ソースコードは GitHub で公開しています。 参考 : G-gen-Tech-Blog/agentic-ai-summit-2026-spring-session-report リポジトリのディレクトリ構成は以下です。 . ├── __init__.py ├── agent.py # エージェント定義 ├── tools.py # ツール関数(Calendar API 操作) ├── config.py # 設定値の管理 ├── requirements.txt # 依存パッケージ ├── .env_example # 環境変数のサンプル ├── .gitignore └── README.md エージェントの定義 以下は ADK でエージェントを定義する agent.py の全体です。49 行でエージェントの定義が完結しています。 # agent.py from google.adk.agents.llm_agent import Agent from . import tools from google.adk.models.lite_llm import LiteLlm root_agent = Agent( name= "calendar_agent" , model=LiteLlm( "vertex_ai/gemini-3-flash-preview" , vertex_location= "global" ), description= "議事録を解析して Google Calendar にイベントを登録するエージェント" , instruction= """あなたは議事録からTodoを抽出して Google Calendar に登録するアシスタントです。 以下の手順で作業してください。 1. ユーザーから議事録を受け取る。 - ファイルパスが渡された場合は read_todo_file ツールでファイルを読み込む。 2. テキスト内容を解析し、議事録からTodoとそれに関係する情報を抽出する: - 日付・時刻(今年は2026年。月/日 の形式なら 2026年として解釈する) - イベントタイトル - 説明(あれば) - 終日イベントかどうか 3. 抽出した各イベントについて create_calendar_event ツールを呼び出して登録する。 - 日時は ISO 8601 形式にする(例: 2026-02-20T14:00:00) - 終日イベントの場合は日付のみ(例: 2026-03-01)、end_datetime は翌日にする - 時間の指定がない場合は終日イベントとして扱う - 終了時刻の指定がない場合は開始から1時間後を終了時刻とする - タイムゾーンは Asia/Tokyo 4. Todo テキストに参加者(メールアドレス)の記載があれば attendees に指定する。 ユーザーが参加者を指定した場合は、そのメールアドレスを追加する。 5. 登録結果をユーザーに報告する。 注意事項: - すべての日時は日本時間 (JST, Asia/Tokyo) を基準とする。 時刻の指定がある場合は日本時間として解釈すること。 - テキストは自由形式。「2/20 14:00 美容院」のようなフォーマットもあれば、 文章形式の場合もある。柔軟に解析すること。 - 日付が曖昧な場合は確認を求める。 """ , tools=[ tools.read_todo_file, tools.create_calendar_event, tools.list_calendar_events, tools.update_calendar_event, tools.delete_calendar_event, ], ) Agent クラスの model に LiteLlm で gemini-3-flash-preview (2026年4月現在)を指定しています。 instruction にはシステムプロンプトとして議事録の解析手順を記述し、 tools に呼び出し可能なツール関数を登録しています。 ADK では LiteLlm を使うことで Gemini 以外のモデル(Claude、GPT など)にも切り替えられます。 参考 : ADK LiteLLm ドキュメント 認証トークンの取得とカレンダー API の呼び出し 今回の構成で最も注目すべきポイントは、Gemini Enterprise・ADK・Agent Engine の3つのサービスが連携してユーザーの認証トークンをシームレスに受け渡す仕組みです。 通常、OAuth 2.0 を使ったアプリケーション開発では、トークンの取得・保存・リフレッシュ・有効期限の管理といった複雑な認証フローを開発者自身が実装する必要があります。しかし、Google Cloud のエコシステムではこの課題が解消されています。ユーザーが Gemini Enterprise 上で OAuth 認証を完了すると、取得されたアクセストークンは Agent Engine のセッションに自動で保存されます。ADK のツール関数では、引数として渡される ToolContext の state からそのトークンをわずか1行で取得できます。 この「Gemini Enterprise(認証 UI)→ Agent Engine(トークン管理)→ ADK(トークン利用)」という一連の流れが、Google Cloud のマネージドサービス間で自動的に実現される点が、このエコシステムの大きなメリットです。 以下の get_calendar_service 関数がその実装の核心です。 from google.oauth2.credentials import Credentials from googleapiclient.discovery import build from google.adk.tools import ToolContext def get_calendar_service (tool_context: ToolContext): """ Agent Engine 環境で Google Calendar サービスを認証・生成します。 """ # 1. 環境変数から管理画面で設定した ID を取得 auth_id = os.getenv( "AUTH_ID" ) # 2. トークンの取得を試みる access_token = tool_context.state.get(auth_id) if not access_token: access_token = tool_context.state.get( "authentication" ) # 3. トークンが見つからない場合 if not access_token: raise ValueError ( "認証トークンが取得できませんでした。" "チャット画面で Google ログインを完了させているか確認してください。" ) # 4. 取得したトークンでカレンダーサービスを構築 creds = Credentials(token=access_token) return build( "calendar" , "v3" , credentials=creds) ポイントは tool_context.state.get(auth_id) のわずか1行です。この1行の背後では、以下の処理が Google Cloud のマネージドサービスによって自動的に行われています。 Gemini Enterprise がユーザーに OAuth 同意画面を表示し、認可コードを取得する Agent Engine が認可コードをアクセストークンに交換し、セッションの state に保存する ADK のツール関数が ToolContext 経由でそのトークンを取得する 開発者が実装するのは tool_context.state.get(auth_id) でトークンを取り出し、 Credentials に渡す部分だけです。トークンの取得フロー、リフレッシュ、有効期限の管理はすべてマネージドサービス側が担います。 認証フローの実装が、Google Cloud のマネージドサービス連携により数行のコードに集約されています。 以下は、メインのツール関数である create_calendar_event です。 def create_calendar_event ( summary: str , start_datetime: str , end_datetime: str , tool_context: ToolContext, description: str = "" , timezone: str = "Asia/Tokyo" , attendees: list [ str ] | None = None , ) -> dict : """Google Calendar にイベントを作成する。""" service = get_calendar_service(tool_context) s_dt = start_datetime.strip() e_dt = end_datetime.strip() # 終日イベントかどうかの判定 is_all_day = ( "T" not in s_dt) and ( ":" not in s_dt) if is_all_day: s_dt = s_dt.replace( "/" , "-" ) e_dt = e_dt.replace( "/" , "-" ) event_body = { "summary" : summary, "description" : description, "start" : { "date" : s_dt}, "end" : { "date" : e_dt}, } else : if " " in s_dt: s_dt = s_dt.replace( " " , "T" ) if " " in e_dt: e_dt = e_dt.replace( " " , "T" ) event_body = { "summary" : summary, "description" : description, "start" : { "dateTime" : s_dt, "timeZone" : timezone}, "end" : { "dateTime" : e_dt, "timeZone" : timezone}, } if attendees: event_body[ "attendees" ] = [{ "email" : email} for email in attendees] event = service.events().insert(calendarId= "primary" , body=event_body).execute() return { "status" : "success" , "event_id" : event[ "id" ], "summary" : event[ "summary" ], "html_link" : event[ "htmlLink" ], } get_calendar_service(tool_context) を呼び出すことで、ログイン中のユーザーの権限で Google Calendar API を操作しています。終日イベントと時間指定イベントを date と dateTime で区別し、 attendees パラメータで参加者の追加にも対応しています。 開発中に遭遇した落とし穴 ADK の State オブジェクト 開発中、認証トークンの中身を確認しようとした際に以下のエラーに遭遇しました。 # やりたかったこと:state の中身を一覧表示したい print (tool_context.state.keys()) 上記の様に入力すると、以下の結果になります。 AttributeError: 'State' object has no attribute 'keys' ADK の State オブジェクトは、標準の Python 辞書( dict )ではなく、Python の MutableMapping プロトコルにも準拠していません。辞書のように値の取得や代入はできますが、 .keys() や .items() といった標準的なメソッドは持っていません。これはバグではなく、会話状態の差分追跡を確実に行うための意図的な設計です。 注意すべき点として、ADK には以下2つの state が存在します。 session.state tool_context.state ( context.state ) session.state は標準の Python 辞書( dict )であるため .keys() や .items() が通常どおり使えます。 一方、ツール関数の引数として渡される tool_context.state は前述の State オブジェクトであり、これらのメソッドは使えません。 session.state.keys() は正常に動作するのに tool_context.state.keys() ではエラーになるため、両者を混同するとデバッグ時に混乱を招きます。 ツール関数内では tool_context.state を使うため、 .get() メソッドで安全に値を取り出す必要があります。 # 正しい方法:.get() で安全に取得 access_token = tool_context.state.get(auth_id) 操作は .get() や直接代入( tool_context.state[key] = value )に留めることが推奨されます。ADK を使ったエージェント開発を始める方は、この仕様を事前に把握しておくとデバッグ時間を節約できます。 参考 : Context - Agent Development Kit (ADK) LiteLLM のセキュリティに関する注意 LiteLLM v1.82.7 / v1.82.8 は、サプライチェーン攻撃(TeamPCP)により侵害されました(2026年3月24日)。本デモの GitHub リポジトリのサンプルコードでは v1.83.0(攻撃後の正規リリース、2026年3月31日公開)にピン留めしています。 詳細は以下を参照してください。 参考 : LiteLLM 公式セキュリティアップデート 参考 : GitHub Issue #24518(タイムライン) Gemini Enterprise へのデプロイ 作成したエージェントを Agent Engine にデプロイした後、Gemini Enterprise のコンソールからエージェントを追加できます。画面からの操作のみでデプロイが完了します。 ユーザーごと、またはグループごとにエージェントの利用権限を管理することも可能です。組織全体への安全な展開が実現できます。 参考リンク 参考 : Agentic AI Summit 2026 Spring 参考 : 登壇資料 参考 : G-gen-Tech-Blog/agentic-ai-summit-2026-spring-session-report(ソースコード) 以下の動画でセッションの実演をご覧いただけます。 奥田 梨紗 (記事一覧) クラウドソリューション部データインテリジェンス課 Google Cloudの可能性に惹かれ、2024年4月G-genにジョイン。 Google Cloud Partner Top Engineer 2025&2026 Follow @risa_hochiminh
こんにちは。AI LabチームのHan Kil Roです。サービスに必要なAIモデルやソリューションを開発するチームで業務に携わっています。最近、LINEヤフー社内で実施された Orchestrati...

動画

書籍