セーフィー株式会社のブログ - TECH PLAY

TECH PLAY

セーフィー株式会社

セーフィー株式会社 の技術ブログ

256

はじめまして。セーフィー株式会社の池淵峻一です。2025 年 3 月より AI Studio チームで、バックエンドの開発を担当しています。 Safie AI Studio は、セーフィーのカメラ映像に対して AI 解析を導入・開発できるプラットフォームです。人検知や車番認識といった映像 AI ソリューションを、35 万台以上のカメラに展開できる基盤を提供しています。私はこのプラットフォームのバックエンド——API サーバー、データベース、非同期ワークフローなどの開発に携わっています。 この記事では、MLOps 基盤を作る上で AI コーディングアシスタント Claude Code を導
こんにちは、セーフィーの金原(@masakane55)です。 これまではサイバーセキュリティの担当としてブログを書いていましたが、最近はマネジメントとしての比重も増えてきました。 改めてマネジメントの視点で、サイバーセキュリティの世界でどのようにキャリアを作っていくか、私なりの考え方を整理してみたので、ブログでお伝えしたいと思います。 はじめに 早速ですが、読者のみなさんは、自分のキャリアを「戦略的」に考えたことはありますか? サイバーセキュリティの世界は技術の進歩が速く、覚えるべきことも膨大です。場当たり的に学習を進めるだけでは、変化の波に飲み込まれてしまうかもしれません。 そもそ
はじめに みなさん、こんにちは。開発本部 アプリケーション開発部 Webフロント第2グループの佐々木大翔です。 普段は TypeScript や React などの枠組みの中で開発することが多く、DOM を直接触るような実装や Canvas での描画はほとんど未経験でした。 そこで今回は、Canvas を使って「マウス操作でグラフの見え方が変わる」アプリを個人開発してみて、学べたことをまとめます。 アプリを作ったきっかけ 所属グループで「もくもく開発勉強会」を実施しており、低レイヤー寄りの領域(DOM操作や Canvas 描画)も触って表現力を鍛えよう、という流れがありました。
はじめに こんにちは!セーフィー株式会社でサーバーサイドエンジニアをしている坂上(@Bobtaroh)です。 突然ですが、日々の開発業務の中で、「PRのCIは全てパスしたのに、マージしたらmainブランチが壊れた」なんてこと、経験したことありませんか?Git上ではテキストのコンフリクトが発生しなければマージ可能です。しかしコードの意味的な整合性が壊れ、マージ後にテストが失敗してしまうケースがあります。このような問題はSemantic Conflict(意味的な衝突)と呼ばれます。 この記事では、Semantic Conflictを実際に再現し、GitHubの機能であるMerge Qu
作業の合間に窓の外を眺めることはありますか? 筆者の席は会社のビルの西側で、午後になると大きな窓から強烈な西日が射し込んできます。 それがあまりに強烈なので、いつもブラインドが閉まっていて外があまり見えません。 そのせいでオフィス内の移動中などに窓から見えた空が真っ暗で驚くということがよくあります。それで困るというわけではありませんが、少し味気ない感じがします。 空の色で時間の移ろいを感じたい。 隙間から覗くこともある というわけで、机に置いて空の色で時間帯を知らせてくれるデバイスを作ることにしました。それだけでもつまらないので、机の上にあったら嬉しいポモドーロタイマーの機能も付けてみ
こんにちは、セーフィーのリアーキ推進Gの金成と申します。 今回は、自分たちのチームで開発している認証サーバーのデプロイ改善について話させてください。 課題感 認証サーバーのリリースは、影響するサービスが多く、より安全にリリースすることが求められていました。具体的には、下記のような課題がありました。 意図せずに破壊的な変更がリリースされる可能性がある 本番のみで発生するエラーを事前に検出できない可能性がある 影響範囲をコントロールできるようにしたい etc こういった課題を解決するために、まず安全にリリースできるとはどんな状態であれば良いのかから考えてみました。 安全なリリース
こんにちは、セーフィーの武田です。 2026年1月21日、ウイングアーク1stさんと共に 「VPoEの役割と組織開発の未来」 をテーマにしたVPoEクロストークイベントを開催しました。 https://x.com/SafieDev/status/2013909310783504413 オンライン・オフライン合わせて30名以上の方にご参加いただき、QAセッションでは40件を超える質問が飛び交うなど、非常に熱気あふれるイベントとなりました。今回はその舞台裏と、当日語られたエッセンスを凝縮してお届けします! 開催の背景:二人のVPoE、異なる歩みと共通の問い 今回のイベントが実現したきっか
はじめに こんにちは、セーフィー株式会社 AI開発部でテックリードを務めます橋本です。 先日、AI開発部において、技術力の向上およびメンバー間の連携強化を目的とした1日完結型のハッカソンを開催いたしました。私たちAI開発部は普段から画像を用いたアルゴリズム開発を行っていることもあり、今回のテーマには画像分類タスクを選定しました。 具体的には、Kaggleのコンペティション「Paddy Doctor: Paddy Disease Classification」を題材とし、水田の稲の画像から9種類の病気と正常な状態を分類するモデルを作成しました。当日は13名のメンバーが3つのチームに分か
はじめに こんにちは、たかぎ@hitsanです。 最近、趣味でSystemVerilogを使ったハードウェア開発に入門しています。 ハードウェア開発では信号の値を見るために波形を確認するのですが既存のツールはGUIを介して確認するものがほとんどです。ですが私は開発環境をターミナル内で完結させたいのでCLIで波形を確認することができるツールを作りました。 今回はAIを使って開発したのでAIを使った自分用のツールのつくり方を紹介します。 前提知識 この章では今回の記事に必要なハードウェア開発の前提知識について少しお話します。 SystemVerilogはデジタル回路の設計・検証に使用
はじめに こんにちは。エンジニアリングオフィスの山崎(@ymzaki_m4)です! 開発組織の進化を支える幅広い活動をしている私たちですが、ふと気づくことがありました。 「私たち自身も、一つのチームとして成長し続ける必要があるのでは?」 そこで着目したのが「振り返り」の手法です。2024年から現在まで、私たちは振り返りの手法を進化させ続けてきました。特に2025年は 「感情」 を振り返りに組み込むことで、チームの心理的安全性が高まり、より建設的な議論ができるようになった実感があります。 この記事では、エンジニアリングオフィスが実践してきた振り返りの進化の過程と、そこから得た学びをお伝
はじめに こんにちは! セーフィーの開発本部エンジニアリングオフィスで26卒内定者インターンをしている吉田・水野・小石川です。 今回はインターン活動の一環で2025/11/15(土)に開催された「BTCONJP2025」というカンファレンスに私たちインターン生がゼロからWebアプリを作成し展示させていただいたので、その技術的挑戦と当日のトラブル対応について振り返りたいと思います。 当日の写真 導入・背景 自己紹介 吉田🧖)開発本部で内定者インターンをしている吉田和司です!結構多趣味で最近はドローンに興味を持って資格を取ったりしました。他にもサウナが大好きで、この前は名古屋の
はじめに こんにちは!セーフィー株式会社でサーバーサイドエンジニアをしている坂上(@Bobtaroh)です。 セーフィー開発本部では2025年9月に「生成AI利用推進プロジェクト」を立ち上げ、20名以上のメンバーが自発的に参加し、以下の5つの軸で活動しています。 情報収集活性化 技術検証・導入 ナレッジDB管理 生成AI利用活性化 ←今回はここ! 社外発信 プロジェクトの詳細はこちらの記事をご覧ください。 今回は「生成AI利用活性化」の一環として実施したClaude Codeライブコーディングについてご紹介します。 ライブコーディングイベントについて フロントエンドエンジニア
はじめに こんにちは。情報システムグループの後藤です。 今回は、社内で長らく課題となっていた「オフィス無線LANの接続不安定問題」に対し、内製での調査・切り分けと、外部ベンダーによるサイトサーベイを組み合わせて改善まで導いたプロジェクト全貌をご紹介します。 「Web会議が頻繁に切れる」「特定のエリアだけ通信が遅い」……。 多くの企業で聞かれる悩みと思いますが、弊社も例外ではありませんでした。 今回、どのような調査・切り分けを実施し、解決に至ったのか。 具体的なデータとともに振り返ります。 ネットワーク設計・運用に関心のある方やコーポレートインフラで同様の悩みを抱えている方にとって参考
はじめに こんにちは!セーフィー株式会社でデバイス開発をしている杉本です。 前回はコンセプトの立案からプロトタイプのユーザー検証まで行うプロト開発WGの活動を紹介しましたが、今回は自分の職務であるハードウェア開発プロジェクトマネージャー(以下、HW開発PM)について紹介させていただきます! ハードウェア開発プロジェクトマネージャーとは セーフィーにおけるHW開発PMという職種は、ハードウェアエンジニアと開発PMの両方の役割を担っています。 ハードウェアエンジニア 開発パートナーと連携して、設計、性能評価、信頼性試験、法規制対応、量産品質の確保までハードウェア開発を推進する
こんにちは、セーフィー株式会社 開発本部 テックブログ編集長の土井です。 これまで「Safie Engineers' Blog」として、はてなブログにて技術発信を行ってきましたが、この度、 テックブログのプラットフォームを「Zenn Publication」へ移行いたしました。 新しいブログはこちらになります。ぜひフォローをお願いします! zenn.dev なぜZennへ移行するのか? 1. ブログコンテンツを「エンジニア個人の集まり場」へ 2. 運用負荷を下げ、中身に集中できる体制へ 過去の記事とURLについて これからの「Safie Engineers' Blog」 なぜZennへ移行するのか? 今回の移行には、大きく2つの理由があります。 1. ブログコンテンツを「エンジニア個人の集まり場」へ これまで、記事はあくまで「会社の発信」としての側面が強かったのですが、今後は 「エンジニア個人のアウトプット」 の集合体としての側面もより大切にしたいと考えています。 Zenn Publication機能では、執筆した記事が会社のページだけでなく、執筆者個人のZennアカウントにも紐付きます。これにより、社外への発信がそのままエンジニア自身のポートフォリオや実績(資産)として蓄積されるようになります。 エンジニアが自分の言葉で語り、それが個人のキャリアやブランディングにも繋がる。そんなWin-Winな関係を目指しています。 2. 運用負荷を下げ、中身に集中できる体制へ テックブログの運営は、社内の有志メンバーによる少人数体制で行っています。 はてなブログはカスタマイズ性が高く、表現の自由度が非常に高い素晴らしいプラットフォームです。その反面、現在の私たちの体制では、記事ごとのトンマナ(統一感)を保つためのデザイン調整やサムネ、URL、カテゴリ、概要など様々な確認作業に多くのコストがかかっていました。 特にアドベントカレンダーなど、記事の公開ペースが速いタイミングでは、運営メンバーが1記事ずつ手作業でレイアウト調整や装飾を行う負荷が高まっていました。 Zennは、記事のレイアウトや装飾があらかじめ統一されています。 「誰が書いても、読みやすい記事になる」 ため、運営側での見栄えの調整作業が不要になります。 また、今回はあえてGitHub連携などは行わず、ZennのWebエディタ上で完結する運用を選びました。 Zennには標準で強力な「レビュー機能」が備わっており、プレビューを見ながらコメントや修正提案が可能です。エンジニアだけでなく、デザイナーや広報など、Git操作に慣れていないメンバーも含め、誰でも簡単に執筆・レビュー・公開ができる「シンプルで使いやすい体験」を重視しています。 過去の記事とURLについて これまで、はてなブログで公開してきた記事については、引き続きアーカイブとして公開を継続します。 旧ブログ: Safie Engineers' Blog (Hatena) (※ https://safie.hatenablog.com/ ) ただし、執筆者本人が自身のZennアカウントでの公開を希望する場合や、現在も参照頻度が高い人気記事などについては、一部Zennへの移行(転載やアップデート)も検討しております。 なお、旧ブログへのアクセスについてはリンク切れが起きないようリダイレクト処理等の準備を進めておりますが、今後はZennでの発信がメインとなります。 これからの「Safie Engineers' Blog」 新しいZenn Publicationでの 記事公開は、近日中に開始する予定です。 運用コストを下げ、記事作成のハードルを下げた新しい環境で、セーフィーのエンジニアたちが日々直面している課題や、その解決策をリアルタイムにお届けしていきます。 映像データ処理の技術 エッジAI・IoTデバイス開発 開発組織のカルチャー など、多岐にわたるトピックを発信していきますので、楽しみにお待ちください。 今後とも、セーフィーのエンジニアリングチームをよろしくお願いいたします。 zenn.dev safie.co.jp はじめまして!このアカウントは、セーフィーの技術情報やエンジニアの取り組みを発信するアカウントです🎉 エンジニアの皆さんが気になる情報をお届けします。 ぜひお気軽にフォローしてください! #セーフィー #エンジニア #開発者 #技術広報 — セーフィー技術広報 (@SafieDev) 2025年11月7日
こんにちは、サーバサイドエンジニアの伊林です。 この記事では、直近にセーフィーで行ったOSS活動について共有します。 セーフィーでのOSS活動について セーフィーのバックエンドは、主にPython、Go、Javaで実装されており、多くのOSSの力を借りてシステムが成り立っています。OSS開発者の方々には日々、感謝の正拳突きが止まりません。 自分はOSS活動の経験は浅いですが、チーム内でOSS活動を積極的に取り組んでいこうという目標設定をたてたこともあり、いくらか取り組んでみました。 また、自分以外の方が取り組んだ内容についてもせっかくなので合わせて紹介しようかと思います。 ※ OSS
こんにちは!エンジニアリングオフィスの横道( @m_yokomichi )です。 セーフィーでは、AI活用を開発生産性向上の重要な戦略と位置づけ、組織全体で活用を推進しています。その第一歩として、Claude Code座学勉強会を開催しました。基本的な使い方から今後の展開まで、取り組みのご紹介させていただきます。 はじめに:勉強会開催の背景 利用方法がわからないという課題 勉強会の概要 勉強会の内容:3つのポイントと一部機能紹介 ポイント1: AI開発ツールの使い分け ポイント2: 5分で始める導入方法 ポイント3: 基本的な使い方とTips 一部機能紹介: Claude Code Skills 参加者の反応:アンケート結果 満足度: 平均4.2点 / 5点 今後知りたいテーマ 参加者の声 今後の展開:ハンズオンと活用促進 次のステップ:実践の場を増やす 継続的な情報共有 まとめ:組織全体で取り組む生成AI活用 はじめに:勉強会開催の背景 利用方法がわからないという課題 セーフィーでは、Claude Maxプランを配布しており、Claude Codeの利用が可能になっています。誰でも使える状態ですが、「使ってみたいけど、どう使えばいいかわからない」という声が多く聞かれました。 GitHub Copilotは利用が進んでいる一方で、Claude Codeについて調査してみると、以下の理由で利用が進んでいない状態ということが分かりました。 他のAIツールとの違いがわかりにくい どんな場面で使うべきかわからない 基本的な設定方法が不明 こうした課題を解決し、利用のハードルを下げるため、座学形式の勉強会を企画しました。Claude Codeの特徴やメリットを理解してもらい、まずは「使い始める」きっかけを作ることが目的です。 なお、この勉強会の企画は、生成AI利用促進プロジェクトの一環で実施しており、そのプロジェクトの全体像については別記事で紹介させていただきます。 勉強会の概要 形式 : オンライン座学形式(30分) 対象 : 開発者全員 目的 : Claude Codeの基本理解と導入支援 「まだ使えていない人」が安心して第一歩を踏み出せるよう、基礎から丁寧に解説する内容としました。 勉強会の内容:3つのポイントと一部機能紹介 勉強会では、以下の3つのポイントを中心に解説しました。 ポイント1: AI開発ツールの使い分け 開発現場には様々なAIツールが登場していますが、それぞれに得意・不得意があります。勉強会では、 4つの開発手法 (コード支援型、Vibe Coding、Agentic Coding、スペック駆動)を整理し、それぞれの特徴と使い分けを解説しました。 Claude Codeは「Agentic Coding」型のツールで、コードベース全体を理解して複数ファイルを横断した作業が得意です。「細かいコード補完」ではなく、「複雑なタスクを自律的に処理する」のが特徴で、テスト実行とエラー修正を自動で繰り返すこともできます。 ポイント2: 5分で始める導入方法 「すぐに始められる」ことを重視し、導入手順を簡潔に紹介しました。 3ステップで始める Claude Maxプランの利用申請 社内ガイドラインに従って申請 インストール Homebrew:  brew install anthropics/claude/claude curl: 公式サイトからスクリプト実行 NPM:  npm install -g @anthropic-ai/claude-code 初期設定 claude コマンドでログイン API認証を完了 /init コマンドで CLAUDE.md を作成 このステップを進めるだけで、プロジェクトの構造を分析して CLAUDE.md ファイルを自動生成してくれます。このファイルがあることで、Claude Codeの応答精度が大きく向上します。 始める手間が少ないことで利用ハードルが低いことを伝えています。 ポイント3: 基本的な使い方とTips 実際にどんなタスクができるのか、具体例を示しました。 できること プロジェクト概要の把握(「このプロジェクトは何?」) ディレクトリ構造の理解(「構造を説明して」) コード変更(「○○機能を追加して」) Git操作(「コミットメッセージを考えて」) より良い回答を得るためのTips 具体的に指示する : 「バグを修正して」→「ログイン時に空白画面になるバグを修正して」 段階的に指示する : 複雑なタスクは分割する Planモードを活用 : 探索・計画してから実装 注意点 AIに全てを任せず、最終判断は人間が行う コマンド実行は都度確認する 機密情報の取り扱いに注意 Claude Codeの基本的な使い方を説明することで、何ができるか知ってもらい、利用イメージを膨らませていただけるようにしました。 一部機能紹介: Claude Code Skills 勉強会では、Claude Codeをより活用できる Skills についても紹介しました。 基本情報だけではなく、活用事例を紹介することによって色々できることを知ってもらい、より興味を持ってもらうことが狙いです。 参加者の反応:アンケート結果 勉強会後のアンケートから、参加者の反応を紹介します。 満足度: 平均4.2点 / 5点 高い満足度をいただき、「まだ使えていない人」に寄り添った内容が評価されました。 今後知りたいテーマ 知りたいという声が多かったものをいくつか紹介します。 Claude Code 使い方の深掘り Claude Codeの機能 GitHub Copilotの機能 プロンプトの書き方 チームでの利用方法 開発本部での活用事例 参加者の声 「Claude Code Skills は興味深かったです」 「すごくわかりやすかったです!とりあえずClaude 利用申請を今から開始しようと思います」 「開発における生成AIの使い分けがわかりやすかったです!複数組み合わせることもテクニックとして紹介されていたので今後活用してみようと思いました」 「勉強会がこれから導入する人向けの話になっていたので、全然使えてなかった私からしたらとても分かりやすくて助かりました」 特に印象的だったのは、「これなら自分にもできそう」という前向きな反応でした。 一方で「座学だけでは物足りない」といった声もあったので、ハンズオン形式で体験しながら理解するコンテンツも企画しようと思います。 今後の展開:ハンズオンと活用促進 次のステップ:実践の場を増やす 今回の座学勉強会で「基本的な理解」は深まりましたが、「実際に使ってみないとわからない」という課題も浮き彫りになりました。 そこで、今後は以下のような活動を計画しています: 1. ライトニングトーク会 Claude Code を活用できている人による実例紹介 具体的なユースケースの共有 2. ハンズオン形式の勉強会 実際にClaude Codeを操作しながら学ぶ 困ったときのトラブルシューティング 4. ナレッジデータベースの構築 活用事例の蓄積 Tips・プロンプト集の整備 よくある質問(FAQ)の作成 継続的な情報共有 勉強会だけではなく、Slackでの情報発信なども発信し、継続的な学びと実践の場を提供していきます。 まとめ:組織全体で取り組む生成AI活用 今回の勉強会は、セーフィーが進める生成AI活用推進の第一歩でした。 生成AIは「使える人だけが使う」のではなく、「組織全体で戦略的に活用する」ことが重要です。 座学形式で基本を学び、アンケートで今後のニーズを把握し、ハンズオン形式へと発展させる。こうした段階的なアプローチで、全員が生成AIを活用できる環境を整えていきます。 「わからない」から「使える」へ。 そして「使える」から「使いこなす」へ。その最初の一歩を、一緒に踏み出せたことを嬉しく思います。
はじめに こんにちは。セーフィーでプロダクト開発をしています大町です。 最近は、ChatGPT → Gemini → Claude →(たまにGrok)のように、利用するLLMサービスを気分に応じて使い回しています。 最近、次々とLLMの新しいモデルが出ていますが、以下のようなことを思う時があります。 もともとChatGPTを使っていたけどGeminiで性能のいいモデルができたので乗り換えたい アイデア出しはChatGPTで、調べ物はGeminiでといったように使い分けたい → でもサービスを変えるとコンテキストがなくなるので使いづらい そんなエンジン(LLM)だけ変えたいけどデータは変わらないままにしたい!という要望を解決するためのシステムをプロトタイプとして作って社内の勉強会で発表しました。このブログではその内容を共有したいと思います。 はじめに 今回紹介するシステムの概要 DB設計 MCPツールの実装 デモ 課題とその解決策 終わりに 今回紹介するシステムの概要 システムを一言で説明すると、自前でデータ基盤(DB)を用意して、そのデータ基盤に対して各LLMがMCPツールを使ってチャットのデータを入れたり、または必要に応じてとってきたりする、というものです。 MCPは、LLMが外部ツールを共通のインターフェースで呼び出すための仕組みで、今回は複数のLLMから同じデータ基盤を扱うための接点として利用しています。 以下がシステムのアーキテクチャ図です。 登場人物は次の3つです LLMサービス : ChatGPTやClaudeなど。MCPに対応していれば何でもOKです。 DB : 今回は手軽なSQLiteを採用しました。 MCPサーバー : 今回のシステムの核となる部分です。 DB設計 今回のシステムでは、LLMがMCPツールを使ってユーザーの回答に応じてDBから情報をとってきます。その時に、文章の意味を考慮する必要があるので、生のテキストに加えて文章の埋め込みベクトルもDBに保存するようにしています。 erDiagram entries { INTEGER id PK "自動増分" TEXT text "保存するメモ内容" TEXT source "発言者 (デフォルト: unknown)" TIMESTAMP created_at "作成日時" INTEGER dim "ベクトルの次元数" BLOB embedding "ベクトルデータ" } MCPツールの実装 ここが今回のシステムの中心になります。 今回はミニマム実装ということで、MCPのツールとしてingestとrecallというツールを定義しました。 ingest … データを保存する ユーザー入力時にLLMがデータをDBに入れるためのツールです。 以下がシーケンス図です。ユーザーの発言をベクトル化してDBに保存します。 sequenceDiagram autonumber participant User as ユーザー participant LLM as LLM (Claudeなど) participant API as 共通API (MCP) participant DB as データベース Note over User, DB: 【Ingest】記憶を保存する User->>LLM: 「昨日の夕飯はカレーだった」と発言 Note right of LLM: ユーザーの意図を汲み<br/>保存ツールを選択 LLM->>API: ingest(text="昨日の夕飯は...", source="you") activate API Note right of API: テキストをEmbedding<br/>(ベクトル化) API->>DB: INSERT (テキスト, ベクトル, 作成日時) DB-->>API: 保存完了 (ID返却) deactivate API LLM-->>User: 「保存しました」と応答 コード @ mcp.tool () async def ingest (text: str , source: str = "you" ) -> dict : """ メモを保存するツール。 ユーザーが「覚えて」「メモして」などと依頼したときに利用する。 入力テキストをベクトル化してデータベースに登録する。 """ vec = embed([text])[ 0 ] # 正規化済み db.insert_entry(text=text, source=source or "unknown" , vec=vec) return { "text" : text, "source" : source} vec = embed([text])[0] の部分で与えられたテキストに対応する埋め込みベクトルを作っています。埋め込みベクトルとは、単語・文や画像などのデータを意味や関係性を捉えた数値のリスト(ベクトル・座標)です。 意味や関係性の情報を保持するので、例えば、犬と猫は近くて犬とコンピュータは遠くなります。 embedの中身は以下です。学習済みのモデルを使ってテキストをベクトル化し、正規化までしています。 import os import numpy as np from sentence_transformers import SentenceTransformer MODEL_NAME = os.getenv( "EMBED_MODEL" , "sentence-transformers/all-MiniLM-L6-v2" ) _model = SentenceTransformer(MODEL_NAME) def embed (texts): vecs = _model.encode(texts, normalize_embeddings= True ) return np.asarray(vecs, dtype=np.float32) recall … データを検索する ユーザーが質問した際に、DBにあるデータを検索し、レスポンスするツールです。 シーケンス図:ユーザーの質問文をベクトル化するところまでは ingest と同じです。質問文のベクトルと、DBに格納されたデータのベクトルとの類似度を計算し、検索を行います。 sequenceDiagram autonumber participant User as ユーザー participant LLM as LLM (ChatGPTなど) participant API as 共通API (MCP) participant DB as データベース Note over User, DB: 【Recall】記憶を検索・共有する User->>LLM: 「昨日の夕飯なに?」と質問 Note right of LLM: 質問に答えるために<br/>検索ツールを選択 LLM->>API: recall(query="昨日の夕飯なに?") activate API Note right of API: クエリをベクトル化し<br/>全データと類似度計算 API->>DB: 全エントリのベクトル取得 DB-->>API: データ返却 API->>API: 類似度が高い順に抽出<br/>(閾値以下は除外) API-->>LLM: 検索結果 (JSON: "昨日の夕飯はカレー...") deactivate API LLM-->>User: 「昨日の夕飯はカレーでした」と回答 コード @ mcp.tool () async def recall (query: str ) -> dict : # 設定値 limit = 5 # 最大5件返す min_score = 0.35 # 類似度の下限値。これを下回った場合は返さない。 rows = db.fetch_all_entries() if not rows: return { "query" : query, "hits" : []} q = embed([query])[ 0 ] # DBの全ベクトルを展開 vecs = np.vstack([db.from_blob(r[ "embedding" ], r[ "dim" ]) for r in rows]) # 内積計算(正規化済みなのでコサイン類似度) scores = (vecs @ q).astype( float ) idx = np.argsort(-scores) hits = [] for i in idx[:limit]: s = float (scores[i]) if s < min_score: break r = rows[i] hits.append({ "id" : r[ "id" ], "text" : r[ "text" ], "source" : r[ "source" ], "created_at" : str (r[ "created_at" ]), "score" : s, }) return { "query" : query, "hits" : hits} ingestより処理が長くなっていますが、処理の内容としては質問文とDBにあるデータの類似度を計算し、類似度の高いものをレスポンスしています。 scores = (vecs @ q) で、質問文のベクトル q と全データのベクトル行列 vecs の積を取ることで、一度に内積(コサイン類似度)を計算しています。 今回はプロトタイプなのでNumpyで全件計算していますが、プロダクトとしてスケールさせるなら sqlite-vss などのベクトル検索拡張を使うのが良いでしょう。 デモ 実際に動かしてみました。Claudeでingestして、ChatGPTでrecallしています。 ingest by Claude recall by ChatGPT 課題とその解決策 実際に使ってみて感じたのですが、検索のところが難しいということがわかりました。 いくつか課題とその解決策を挙げてみました。 いつの「昨日の夕飯」かわからない問題 課題: ベクトル検索は意味の近さを探すため、「日時」などのメタ情報を考慮しません。そのため、「昨日の夕飯」と聞いているのに、文脈が似ていれば1年前のデータを取ってくる可能性があります。 解決策: recall ツールの引数に date_range などのフィルタ条件を追加し、LLM側で「昨日=2025-10-17」のように具体的な日付に変換して渡してもらうことで解決できそうです。 「質問」と「答え」の形が違う問題 課題: ユーザーの質問「昨日の夕飯は何?(疑問形)」と、DB内の記録「昨日の夕飯はカレー(平叙文)」では、文章のベクトルが必ずしも近くならない場合があります。 解決策: 単純な類似度検索だけでなく、LLMに「この質問に対する答えとしてありそうな文章」を仮想的に生成させてから検索にかける(HyDE: Hypothetical Document Embeddingsのようなアプローチ)か、データ保存時に「どんな質問に対する答えか」も一緒に保存するなどの工夫が考えられます。 終わりに 今回のシステムは、LLMのサービスを変えてもコンテキスト(データ)を維持できるようにするためのシステムでした。LLMは素晴らしい頭脳(モデル)を持っていますが、あくまでも一般的な知識に基づいたものでよりよく使うためには個別のコンテキストを与える必要があります。LLMのサービス側でコンテキストを持つことでその問題を解決していますが、そのことによりベンダーロックインも発生します。 今回のシステムではLLMサービスが提供しているモデルとデータを切り離すことで、モデルに関しては置き換え可能にし、データは自分の元で一元管理することで自由に閲覧・操作できる構成を考えました。 今回作ったのはプロトタイプでそのままでは使い物になりませんが、MCPやRAGを使ったシステムの1つのアイデアとしてみていただければ幸いです。
セーフィーで情報システムを担当している松尾です。 先日開催された「 BTCONJP 2025 (Business Technology Conference Japan 2025)」に参加してきました。 今回は、当日の現地の様子や、実際にセッションを聞いて感じた、これからの情報システムの役割についてレポートしたいと思います。 また、今回は個人的な事情で託児サービスを利用しての参加となったのですが、そこで自社のプロダクトを実際に体験することになり、非常に感慨深い1日となりました。そのあたりも含めてお伝えできればと思います。 会場の熱気と「人数カウント」 託児サービスで感じた「映像の力」とユーザー体験 セッションレポート AI前提の業務「再構築」と現場と共にあること 情シスは「橋渡し役」へ 情報システムの未来に向けて 会場の熱気と「人数カウント」 当日はセーフィーもスポンサーとしてブース出展を行いました。 今回は単なる展示だけでなく、セミナールームや会場内にセーフィーのカメラを設置させていただき、 リアルタイム人数カウント を実施しました。 会場での配信の様子 「今、どのエリアにどれくらいの人がいるのか?」 「このセッションには何人集まっているのか?」 これらを映像データから可視化することで、カンファレンスの熱量を定量的に見ることが出来ます。自社の技術がイベント運営の裏側でどう活きるのかを実際に見ることが出来て、とても興味深い体験でした。 また、会場内はブースを回ると記念品が貰える「スポンサーブース スタンプラリー」、参加者同士の交流を促す「情シスビンゴチャレンジ」、お悩みを相談できる「情シスカフェ&お悩み相談」など、参加型のコンテンツが沢山あり、コミュニティとしての一体感を強く感じるイベントでした。 託児サービスで感じた「映像の力」とユーザー体験 今回の個人的なハイライトの一つが「託児サービス」です。 預け先の都合がつかず、今回は子供と一緒に会場に入り、WAKE Career様がスポンサーをされている託児サービスを利用させていただきました。 この託児サービスではセーフィーのカメラが利用されており、保護者は自分のスマホから託児の様子を確認できるようになっています。子供たちの顔には自動的に 動物のスタンプ が追従して表示されるため、プライバシーを守る事ができます。 これにより、自分の子供や他のお子さんのプライバシーはしっかり守られつつ、 「あ、楽しそうに遊んでるな」 「保育士さんと一緒にお弁当を食べているな」 といった様子をリアルタイムで確認することが出来ました。 当日の託児の様子 初めての場所、初めての保育士さんで不安もありましたが、セミナーの合間にスマホから子供の状況を確認できたことで、安心してイベントに参加することができました。 自社のプロダクトの価値をいちユーザーとして感じることができたことも貴重な経験でした。 今回の出展は26年新卒のメンバーも参加していました! セッションレポート ここからは、聴講したセッションについての学びをまとめます。 BTCONJPのテーマは名前の通り、「ビジネステクノロジー(BT)」であり、ITを単なるツールとして扱うのではなく、「ビジネスを加速させ、競争力を生み出す源泉」とするものだと考えています。 皆様のセッションはどれも素晴らしく非常に学びになるものでしたが、その中でも「ビジネステクノロジー」の観点から、自分たち情報システムの立ち位置や今後を考えるうえで特に印象に残ったセッションを中心にレポートします。 AI前提の業務「再構築」と現場と共にあること AIの導入については現在多くの企業が進め方を検討していたり、すでに導入を進めている状態であると思います。 株式会社IVRyの植田 裕介さん、NOT A HOTEL株式会社の遠藤 祐介さんのAIに関連するセッションで感じたのは、 「今の業務フローのままAIを足すのではなく、AIを前提にプロセス自体を再構築せよ」 というメッセージでした。 今後は、情シスが自ら現場のビジネスプロセスに入り込み、 「そもそもこの業務は必要なのか?」「AIに任せるならどういうフローが最適か?」 を現場と一緒に考え、ゼロから作り直す動きが必要だと改めて感じました。 セッションの中では、AI を使って課題を解決するために、 社内を横断するチーム を立ち上げることが必要になるともお話されていました。 このような動きは、NTTドコモビジネス株式会社の小林 泰大さんと、株式会社マキタの高山 百合子さんのセッションで述べられていた、情報システムの中に留まるのではなく、積極的に外に出ていき現場の声を聞く、ということに繋がっていると感じました。 実際に上記のセッションでは、 直接現場と話しに行くことが重要であり、課題を共有し、共に手を動かすことで信頼関係が築ける と述べていました。 お互いに相手のせいにせず、情シス側も「自分事」として現場と一緒にビジネスを作り、加速していくことがと必要であると感じました。 情シスは「橋渡し役」へ 📢 情シスカンファレンス BTCONJP 2025 11/15(土) 開催 ✨ セッションのご紹介です! ──────────────────── SaaSを買わない未来 ― 作り出す情シスの挑戦 ──────────────────── 株式会社クラウドネイティブ 代表取締役社長 文部科学省… pic.twitter.com/9QXhIMYdOm — #BTCONJP Business Technology Conference Japan (@btconjp) 2025年10月8日 株式会社クラウドネイティブの齊藤 愼仁さんのセッションでは、情報システムの更に未来の話についても言及がありました。 SaaSの値上げが進む中、 「市民開発」 が進む未来においては、 今まではSaaSとして提供されていたアプリを従業員自身が作っていく流れが加速していく と思われます。 その中で、情報システムは SaaSを提供するだけの管理者ではなく、いかに現場の理解力を高めていけるか、伴走者になれるが重要であると 語られていました。 情報システムの未来に向けて 今回、様々な企業のセッションを聴講したなかで、登壇者の皆さんが語る結論は、立場や状況は異なりますが、同じ方向を向いていると感じました。 これからの情報システムは、 エンジニアリングを武器に、現場のビジネス課題を深く理解し、現場と共に事業を加速させるパートナーになる ことが求められていると改めて感じました。 独立行政法人 情報処理推進機構(IPA)産業サイバーセキュリティセンターの登 大遊さんのセッションでは、日本の労働人口減少とそれに伴う競争力の強化といったマクロな課題についても触れられました。 IPA 登大遊氏 ( @dnobori ) による基調講演のセッション内容が公開となりました! https://t.co/51GoG35Fre 情シスカンファレンス #BTCONJP 2025【11/15(土) 開催まであと11日!】 — #BTCONJP Business Technology Conference Japan (@btconjp) 2025年11月3日 セッションでは、現在の日本のデジタル産業を以下の3つに分類しており、日本は労働集約的で利益率の低い「(c) 活用支援(SIer等)」に偏っており、(a) と (b)が不足している状況であると述べられていました。 (a) デジタル基盤製品・サービス: OS・インターネット基盤・セキュリティ基盤・クラウド基盤等 (b) デジタル応用汎用製品・サービス: 汎用アプリ・汎用プラットフォームサービス・クラウド型 SaaS サービス等 (c) デジタル活用支援サービス: IT コンサル・受託ソフト開発・SIer 系 今後の日本で競争力を上げるためには、 (a) デジタル基盤製品・サービス と (b) デジタル応用汎用製品・サービス のような人数によってスケールしないビジネスや会社を作る必要があります。 セーフィーが目指す 「映像プラットフォーム」 は、この(a)と(b)のようなプラットフォームに位置づけられるような会社を目指しており、労働人口減少という社会課題の解決に対しても遠隔から現場をサポートするようなサービスを提供しています。 私たちがこの事業を強くし、世界で戦えるプラットフォームへと成長させることで、 「日本を強くする」 ことにも繋がっていけばいいなと、改めて思いました。 今回の学びを活かし、ビジネスを加速させる情報システムとして、既存の枠にとらわれずに今後もチャレンジしていきたいと思います。 運営の皆様、登壇者の皆様、会場でお会いした皆様、子供を預かってくださった保育士の皆様、本当にありがとうございました。
メリー・クリスマス、セーフィーCTOの森本です。 この記事は Safie Engineers' Blog! Advent Calendar 25日目の記事です。 一昨年 創業以来10年の開発組織の振り返り について掲載し、2年が経ちます。 ありがたい事に会社の事業、開発組織も順調に新しい取り組みを交えつつ大きく成長している事を実感している今日この頃です。 これもメンバーの皆さんの頑張りの賜物であると感謝しています。 そのおかげもあって、自分の時間を手を動かす事に使うことも少しずつ出来るようになってきましたので、今回は少し技術よりの内容について書く事にします。 はじめに MCPサーバー活用例 w/ Claude Desktop LLMとMCPサーバーのやり取り MCPサーバーを使用しない、従来のやり取り MCPサーバーを介する場合のやり取り MCPツールをカスケードで活用する場合 MCPツールの情報をLLMに設定するには まとめ 最期に はじめに 生成AI周りでは様々なモデルやそれにまつわるツールの急速な発展により、業務効率化、創造性の向上、情報アクセスの簡易化などの点に於いて、驚くべきスピードで多種多様な恩恵がもたらされているのは今更言うまでもありません。 もちろん当社でも早い段階から、一部メンバーが様々なツールの開発業務への活用やプロダクトへの応用検討を進めてくれていましたが、これを更に加速する為チームを組成し、 組織としての活用 を進めています。 また、これも言うまでもないですが、MCP(Model Context Protocol)により多種多様な外部サービスと柔軟に連携ができるようになり、ACP(Agent Communication Protocol)、A2A(Agent-to-Agent)などによりエージェント間のコミュニケーションも広がりを見せています。 一例として 当社社員が以下で紹介 してくれていますが、柔軟なテキストベースのインプットによりサービスの制御が可能となっています。 MCPを活用すれば、LLMを経由して当社サービスを柔軟に制御する事が出来るだけでなく、他のサービスとの連携も非常に簡単に行えます。 上記記事にある通り、当社カメラから画像を取得しその要約をすることも簡単に出来ますし、要約した内容をインカムで通知すると言った他のサービスとの連携も簡単です。 例)インカムーSafie連携 今更ですが、一気にサービスの拡張性を伸ばしてくれる技術という事で強く可能性を感じています。 ただ、カスタムのMCPサーバーを活用する為にはClaude Desktopが必要なの(他にも候補はありますが)がちょっと勿体ないのと、内部的にLLMとMCPサーバーがどのようにやり取りしているのか知りたかったので、MCPサーバーを自前のサーバー上で制御できる簡易なサンプルを作り中身を見てみました。 MCPサーバー活用例 w/ Claude Desktop Claude Desktopでは入力されたプロンプトによって、柔軟にMCPサーバーが提供するツールが使えるかどうか判断し、必要に応じ適切にパラメータをセットし実行してくれます。 例えば以下のようなカスタムのMCPサーバーを準備して、Claude Desktopに組み込み(※このあたりの手順は省きます)、「こんばんはとあいさつしてね」とClaude Desktopのプロンプトを入力すると、MCPサーバーの提供する機能を適切に実行し、そのレスポンスを返してくれます。 from mcp.server.fastmcp import FastMCP from pydantic import BaseModel, Field class GreetingMessage (BaseModel): message: str = Field( None , description= "あいさつのメッセージ" ) # Create an MCP server mcp = FastMCP( "Greeting" ) @ mcp.tool () def greeting (message: str | None = Field( description= "あいさつのメッセージ" , default= None )) -> GreetingMessage: """ あいさつメッセージを返します """ return GreetingMessage(message=f "Hello, MCP! {message}" ) def run (): mcp.run() よく知られた挙動ですが、内部でどうやっているか見てみました。 LLMとMCPサーバーのやり取り MCPサーバーを使用しない、従来のやり取り ※自分なりの解釈が相当入っていますが、そこはご容赦ください。 ※llmプロキシとしてLiteLLMを利用し、モデルはclaude-sonnet-4 を使用しています。 最初に従来のLLMへのチャットメッセージは以下のようなリクエストとレスポンスで成立しています。 response = client.chat.completions.create( model=request.model, messages=messages, tools=mcp_manager.tools if request.use_mcp else None , max_tokens=request.max_tokens, temperature=request.temperature, ) messages = [ { "role" : "assistant" , "content" : "あなたは柔軟に入力内容を類推できるAIアシスタントです。質問への回答は出来る限り完結に回答してくれます。" }, { "role" : "user" , "content" : "こんばんはとあいさつしてね" } ] tools = [] ※LLMに出来る限り簡潔にレスポンスを返して欲しかったので、上記のようなアシスタントロールのメッセージを最初にセットしています。 すると以下のようなレスポンスを返してくれます。 ChatCompletion( id = 'chatcmpl-b49350d7-d24a-43c4-a2a3-77343982f33d' , choices=[ Choice( finish_reason= 'stop' , index= 0 , logprobs= None , message=ChatCompletionMessage( content= 'こんばんは!' , refusal= None , role= 'assistant' , annotations= None , audio= None , function_call= None , tool_calls= None ) ) ], created= 1766406680 , model= 'apac.anthropic.claude-sonnet-4-20250514-v1:0' , object = 'chat.completion' , service_tier= None , system_fingerprint= None , usage=CompletionUsage( completion_tokens= 9 , prompt_tokens= 83 , total_tokens= 92 , completion_tokens_details= None , prompt_tokens_details=PromptTokensDetails( audio_tokens= None , cached_tokens= 0 , cache_creation_tokens= 0 ), cache_creation_input_tokens= 0 , cache_read_input_tokens= 0 ) ) LLMがchatAPIで渡されたプロンプトメッセージ「こんばんはとあいさつしてね」に対して、「こんばんは!」というメッセージを返しています。 MCPサーバーを介する場合のやり取り 上記に対し、LLMが入力されたプロンプトを元に最適なMCPツールを選択し必要に応じてパラメータをセットし実行するためには、サポートしているMCPツールの情報をインプットしてやる必要があります。 messages = { "role" : "assistant" , "content" : "あなたは柔軟に入力内容を類推できるAIアシスタントです。質問への回答は出来る限り完結に回答してくれます。" }, tools = [ { "type" : "function" , "function" : { "name" : "greeting" , "description" : "あいさつメッセージを返します" , "parameters" : { "type" : "object" , "properties" : { "message" : { "type" : "string" , "description" : "message parameter" , "default" : null } }, "required" : [] } } } ] 先程との違いは明確で、toolsパラメーターにMCPサーバーが提供するツールの情報をセットして、chatAPIを実行しています。 すると以下のようなレスポンスを返してくれます。 ChatCompletion( id = 'chatcmpl-63876e3e-fb1d-4910-81a9-8fea53190dff' , choices=[ Choice( finish_reason= 'tool_calls' , index= 0 , logprobs= None , message=ChatCompletionMessage( content= '' , refusal= None , role= 'assistant' , annotations= None , audio= None , function_call= None , tool_calls=[ ChatCompletionMessageFunctionToolCall( id = 'tooluse_rSY9i9tdRKmTZewG_-lfqg' , function=Function( arguments= '{"message": "こんばんは"}' , name= 'greeting' ), type = 'function' , index= 0 ) ] ) ) ], XXXXXXXXXXXXXXXX ) レスポンスも先程とは異なり、tool_callsパラメータがセットされています。 パラメータにはchatAPIで渡したプロンプトとtoolsパラメータの内容を元にLLMが選択した最適な実行すべきMCPツールが設定されています。 LLMはあくまで実行すべきツールを提示してくれるだけなので、実際のツール実行部分はLLMの範疇外なので対応が必要です。 上記ではgreetingというツールをmessageに記載されているパラメータで実行すべきとなっているので、実行します。 {   'message' : 'Hello, MCP! こんばんは' } 実行結果は自身で準備したMCPツールの通りとなります。 今回はレスポンスとして「Hello, MCP! こんばんは」が返ってきていますが、 プロンプトから入力したテキストが正しくパラメータとして渡され、ツールも想定通りに実行されています。 ※あくまでLLMは実行すべきと判断した内容を返してくれるだけで、ツールの実行は自身でやっています。 次にLLMにMCPツールの実行結果をチャットAPIで渡してやります。 messages = [ { "role" : "assistant" , "content" : "あなたは柔軟に入力内容を類推できるAIアシスタントです。質問への回答は出来る限り完結に回答してくれます。" }, { "role" : "user" , "content" : "こんばんはとあいさつしてね" }, { "role" : "assistant" , "content" : "" , "tool_calls" : [ { "id" : "tooluse_rSY9i9tdRKmTZewG_-lfqg" , "type" : "function" , "function" : { "name" : "greeting" , "arguments" : "{ \" message \" : \" こんばんは \" }" } } ] }, { "role" : "tool" , "tool_call_id" : "tooluse_rSY9i9tdRKmTZewG_-lfqg" , "content" : "{ \" message \" : \" Hello, MCP! こんばんは \" }" } ] ※toolsは重複するので記載を省きます。 LLMが前のステップで返してきたtool_call_idに合わせてツールのレスポンスをセットしチャットメッセージを実行します。これによりLLMがMCPツールが実行された事と、その結果を受け取る事ができます。 ChatCompletion( id = 'chatcmpl-32ae0203-9b39-4b33-9a71-6ba9c9ac1cdb' , choices=[ Choice( finish_reason= 'stop' , index= 0 , logprobs= None , message=ChatCompletionMessage( content= 'こんばんは!いかがお過ごしですか?' , refusal= None , role= 'assistant' , annotations= None , audio= None , function_call= None , tool_calls= None ) ) ], XXXXXXXXXXXXXXXX ) するとLLMが今までのやり取りとMCPツールの実行結果をベースにレスポンスを返してくれます。 今回は「こんばんは!いかがお過ごしですか?」です。若干例が分かりにくいですね。笑。 更に連続でMCPツールが実行出来る場合はtool_callsに次に実行すべきMCPツールの情報がセットされるのですが、今回は特に設定されて無いのでこれでチャットプロセスは完了となります。 MCPツールをカスケードで活用する場合 では冒頭でご紹介した、当社メンバーが作成したSafie API MCPツールを使って、カスケードでMCPツールを実行する例を見てみましょう。 先程のMCPツールも読み込みつつ、「昨日の10時の画像に人が映っているかどうか調べて」というプロンプトを入力します。 尚、時間情報が含まれる場合はタイムゾーンを解釈するようなプロンプトメッセージを付加しています。 いきなり長くなりますが、LLMへ投げるプロンプトは以下の通りとなります。 messages = [ { "role" : "assistant" , "content" : "あなたは柔軟に入力内容を類推できるAIアシスタントです。質問への回答は出来る限り完結に回答してくれます。 \n\n ## 日時情報 \n 現在の日時: 2025-12-22T13:51:54.528550+00:00 \n 現在の日付: 2025-12-22 \n\n 相対的な日付表現(今 日、明日、昨日など)は上記の現在日時を基準として \n 言語から類推できるタイムゾーンに変換の上 \n ISO 8601拡張形式(タイムゾーン付き)で解釈してください。" }, { "role" : "user" , "content" : "昨日の10時の画像に人が映っているかどうか調べて" } ] tools = [ { "type" : "function" , "function" : { "name" : "list_devices" , "description" : " アクセス権限のあるデバイスの一覧を取得します " , "parameters" : { "type" : "object" , "properties" : { "item_id" : { "type" : "integer" , "description" : "item_id parameter" , "default" : null } }, "required" : [] } } }, { "type" : "function" , "function" : { "name" : "get_device_image" , "description" : " 指定されたデバイスから画像を取得します timestampを指定しない場合、API実行時点の最新画像が取得できます " , "parameters" : { "type" : "object" , "properties" : { "device_id" : { "type" : "string" , "description" : "device_id parameter" }, "timestamp" : { "type" : "string" , "description" : "timestamp parameter" , "default" : null } }, "required" : [ "device_id" ] } } }, { "type" : "function" , "function" : { "name" : "list_device_media" , "description" : " 指定されたデバイスで録画されている映像(メディア)の一覧を取得します timestampを指定しない場合、API実行時点の最新画像が取得できます 制限: - start/endの取得最大範囲は 1日(86400秒)です " , "parameters" : { "type" : "object" , "properties" : { "device_id" : { "type" : "string" , "description" : "device_id parameter" }, "start" : { "type" : "string" , "description" : "start parameter" }, "end" : { "type" : "string" , "description" : "end parameter" } }, "required" : [ "device_id" , "start" , "end" ] } } }, { "type" : "function" , "function" : { "name" : "get_device_location" , "description" : " 指定されたデバイスの現在のGPS位置情報を取得します。GPSに対応していないデバイスでは利用できません デバイスに手動設定された位置情報を取得することはできません " , "parameters" : { "type" : "object" , "properties" : { "device_id" : { "type" : "string" , "description" : "device_id parameter" } }, "required" : [ "device_id" ] } } }, { "type" : "function" , "function" : { "name" : "get_device_thumbnail" , "description" : " 指定されたデバイスの最新サムネイルを取得します " , "parameters" : { "type" : "object" , "properties" : { "device_id" : { "type" : "string" , "description" : "device_id parameter" } }, "required" : [ "device_id" ] } } }, { "type" : "function" , "function" : { "name" : "list_device_standard_events" , "description" : " 指定されたデバイスの標準イベント情報一覧を取得します 「標準イベント」とは以下の5つのイベントの総称です - 接続検知 - 切断検知 - モーション検知 - サウンド検知 - 人検知 制限: - start/endの取得最大範囲は 1日(86400秒)です " , "parameters" : { "type" : "object" , "properties" : { "device_id" : { "type" : "string" , "description" : "device_id parameter" }, "start" : { "type" : "string" , "description" : "start parameter" }, "end" : { "type" : "string" , "description" : "end parameter" }, "event_types" : { "type" : "array" , "enum" : [ "connect" , "disconnect" , "motion" , "sound" , "person" ], "description" : "event_types parameter connect, disconnect, motion, sound, person" , "default" : null } }, "required" : [ "device_id" , "start" , "end" ] } } }, { "type" : "function" , "function" : { "name" : "greeting" , "description" : " あいさつメッセージを返します " , "parameters" : { "type" : "object" , "properties" : { "message" : { "type" : "string" , "description" : "message parameter" , "default" : null } }, "required" : [] } } } ] 入力が時間情報を含むプロンプトの場合は、タイムゾーンやフォーマットを指定するメッセージを補完しています。 toolsパラメータが長くなっていますが、こちらはサポートするMCPツールをリスト化して設定しています。 こちらのメッセージに対するレスポンスは以下となります。 ChatCompletion( id = 'chatcmpl-211b219f-f4af-4e42-8ac1-c6195b2ed713' , choices=[ Choice( finish_reason= 'tool_calls' , index= 0 , logprobs= None , message=ChatCompletionMessage( content= '昨日の10時の画像を調べるために、まずアクセス可能なデバイスの一覧を取得します。' , refusal= None , role= 'assistant' , annotations= None , audio= None , function_call= None , tool_calls=[ ChatCompletionMessageFunctionToolCall( id = 'tooluse_QDchcs-OTn60bSHhwYflUw' , function=Function( arguments= '{}' , name= 'list_devices' ), type = 'function' , index= 1 ) ] ) ) ], XXXXXXXXXXXXXXXX ) LLMは指定されたプロンプトを実行するために、まずはアクセス可能なカメラの一覧取得を行うべきと判断し、list_deviceAPIを実行するようレスポンスを返してくれます。素晴らしく柔軟ですね!! 先程の例と同じで、実行すべきMCPツールのメソッドが指定されているので、そのとおりに実行します。 { 'result' : [ { 'device_id' : 'xxxxxxxx' , 'serial' : 'xxxxxxxx' , 'setting' : { 'name' : 'XXXXXXXX' }, 'status' : { 'video_streaming' : True }, 'model' : { 'description' : 'One (SF-1)' } } ] } 指定された通りにMCPツールのメソッドを実行し上記のレスポンスを得ます。 同様にこちらのレスポンスを含めてLLMにチャットを送信します。 messages = [ { 'role' : 'assistant' , 'content' : 'あなたは柔軟に入力内容を類推できるAIアシスタントです。質問への回答は出来る限り完結に回答してくれます。 \n\n ## 日時情報 \n 現在の日時: 2025-12-22T14:05:27.941094+00:00 \n 現在の日付: 2025-12-22 \n\n 相対的な日付表現(今日、明日、昨日など)は上記の現在日時を基準として \n 言語から類推できるタイムゾーンに変換の上 \n ISO 8601拡張形式(タイムゾーン付き)で解釈してください。' }, { 'role' : 'user' , 'content' : '昨日の10時の画像に人が映っているかどうか調べて' }, { 'role' : 'assistant' , 'content' : '昨日の10時の画像を調べるために、まずアクセス可能なデバイスの一覧を取得させていただきます。' , 'tool_calls' : [ { 'id' : 'tooluse_zVu7PAOFSHqr5GYrKxGM7A' , 'type' : 'function' , 'function' : { 'name' : 'list_devices' , 'arguments' : '{"item_id": null}' } } ] }, { 'role' : 'tool' , 'tool_call_id' : 'tooluse_zVu7PAOFSHqr5GYrKxGM7A' , 'content' : '{"result": [{"device_id": "xxxxxxxx", "serial": "xxxxxxxx", "setting": {"name": "XXXXXXXX"}, "status": {"video_streaming": true}, "model": {"description": "One (SF-1)"}}]}' } ] ※toolsは重複するので記載を省きます。 上記メッセージへのレスポンスは以下となります。 ChatCompletion( id = 'chatcmpl-be9de40c-a119-4548-930a-b75abff8f898' , choices=[ Choice( finish_reason= 'tool_calls' , index= 0 , logprobs= None , message=ChatCompletionMessage( content= 'デバイスが1台見つかりました。昨日(2025-12-21)の10時の画像を取得して確認します。' , refusal= None , role= 'assistant' , annotations= None , audio= None , function_call= None , tool_calls=[ ChatCompletionMessageFunctionToolCall( id = 'tooluse_dGHdMM4PQt-fAdNBmMTEhg' , function=Function( arguments= '{"device_id": "xxxxxxxx", "timestamp": "2025-12-21T10:00:00+09:00"}' , name= 'get_device_image' ), type = 'function' , index= 1 ) ] ) ) ], XXXXXXXXXXXXXXXX ) すると今度はtool_callsに次に実行すべきMCPツールが指定されています。 get_device_imageを指定のパラメータ付きで実行しろと言うことですね。 言われたとおりにツールを実行します。 get_device_imageは指定されたパラメータを元に、サムネイルデータを取得します。 取得したサムネイルデータをBase64形式に変換し、先程の例と同様にLLMにメッセージとして入力します。(※画像データは重いので記載しません) messages = [ { 'role' : 'assistant' , 'content' : 'あなたは柔軟に入力内容を類推できるAIアシスタントです。質問への回答は出来る限り完結に回答してくれます。 \n\n ## 日時情報 \n 現在の日時: 2025-12-22T14:05:27.941094+00:00 \n 現在の日付: 2025-12-22 \n\n 相対的な日付表現(今日、明日、昨日など)は上記の現在日時を基準として \n 言語から類推できるタイムゾーンに変換の上 \n ISO 8601拡張形式(タイムゾーン付き)で解釈してください。' }, { 'role' : 'user' , 'content' : '昨日の10時の画像に人が映っているかどうか調べて' }, { 'role' : 'assistant' , 'content' : '昨日の10時の画像を調べるために、まずアクセス可能なデバイスの一覧を取得させていただきます。' , 'tool_calls' : [ { 'id' : 'tooluse_zVu7PAOFSHqr5GYrKxGM7A' , 'type' : 'function' , 'function' : { 'name' : 'list_devices' , 'arguments' : '{"item_id": null}' } } ] }, { 'role' : 'tool' , 'tool_call_id' : 'tooluse_zVu7PAOFSHqr5GYrKxGM7A' , 'content' : '{"result": [{"device_id": "xxxxxxxx", "serial": "xxxxxxxx", "setting": {"name": "XXXXXXXX"}, "status": {"video_streaming": true}, "model": {"description": "One (SF-1)"}}]}' }, { 'role' : 'assistant' , 'content' : 'デバイスが1台見つかりました。昨日(2025-12-21)の10時の画像を取得して確認します。' , 'tool_calls' : [ { 'id' : 'tooluse_dGHdMM4PQt-fAdNBmMTEhg' , 'type' : 'function' , 'function' : { 'name' : 'get_device_image' , 'arguments' : '{"device_id": "xxxxxxxx", "timestamp": "2025-12-21T10:00:00+09:00"}' } } ] }, { 'role' : 'tool' , 'tool_call_id' : 'tooluse_dGHdMM4PQt-fAdNBmMTEhg' , 'content' : 'get_device_image' }, { 'role' : 'user' , 'content' : [ { 'type' : 'image_url' , 'image_url' : { 'url' : 'data:image/jpeg;base64,XXXXXXXX' } }, { 'type' : 'text' , 'text' : '以降のMCPツール呼び出しで画像が必要な場合は、画像データをMCPツールのargumentsに設定してください。' } ] } ] ※toolsは重複するので記載を省きます レスポンスは以下の通りです。 ChatCompletion( id = 'chatcmpl-57a6d457-306a-4f27-b14e-a4696f11172d' , choices=[ Choice( finish_reason= 'stop' , index= 0 , logprobs= None , message=ChatCompletionMessage( content= '昨日(2025-12-21)の10時頃の画像を確認しました。 \n\n 画像を見る限り、**人は映っていません**。 \n\n 画像には以下が写っています: \n - 赤い自動販売機 \n - 商品が陳列された木製の棚 \n - 白いタイル張りの床 \n - 右下に白い機器(エアコンの室外機のようなもの) \n\n 店舗や事務所のような場所の監視カメラの映像のようですが、この時点では人の姿は確認できませんでした。' , refusal= None , role= 'assistant' , annotations= None , audio= None , function_call= None , tool_calls= None ) ) ], XXXXXXXXXXXXXXXX ) これ以上MCPツールを実行する必要が無いので、tool_callsはNoneとなっています。 また、LLMは設定した画像データをベースに適切にプロンプトで指定された内容を処理したレスポンスを返しています。 入力されたプロンプトに基づいて、MCPサーバーを設定しつつ最適なレスポンスを導き出す手法は上記の繰り返しです。 こうやってみると適切な情報をセットしてやる必要はありますが、それらの情報を元にLLMが如何に柔軟にテキスト情報を処理し、必要に応じ最適なMCPツールを選択しレスポンスを生成しているかがよく分かります。 同様に様々なサービスのMCPツールを組み込むだけで、容易にLLMを介したサービス間自動連携が実現できます。 MCPツールの情報をLLMに設定するには 各メソッドの情報を抽出し、登録する必要があります。 参考程度ですが、軽く書いてみたので貼っておきます。 複数のMCPサーバーを統合管理できるようにもしてみました。 import json import logging from mcp.server.fastmcp import FastMCP class MCP_Manager (): def __init__ (self): #self._mcps = {} self._tools = {} self.logger = logging.getLogger(__name__) async def register_tool (self, mcp: FastMCP): """指定されたMCPサーバーをMcpManagerに登録する""" for tool in await mcp.list_tools(): doc = tool.description description = doc.replace( ' \n ' , '' ) if doc else f "{tool.name}" properties = {} # 簡単なパラメータ推論(実際にはより詳細な実装が必要) required = tool.inputSchema.get( "required" , []) for param_name, param in tool.inputSchema[ "properties" ].items(): self.logger.info(f "Param : {param}" ) if param_name != 'self' : param_type = "string" # デフォルト # 一旦地道に実装する(もっとよいやり方があると思うが) _any = param.get( "anyOf" ) if _any is not None : param = _any[ 0 ] if "items" in param.keys(): def_name = param[ "items" ][ "$ref" ].split( "/" )[- 1 ] properties[param_name] = { "type" : param[ "type" ], "enum" : tool.inputSchema[ "$defs" ][def_name][ "enum" ], "description" : f "{param_name} parameter {', '.join(tool.inputSchema[" $defs "][def_name][" enum "])}" } else : properties[param_name] = { "type" : param[ "type" ], "description" : param[ "description" ] if description in param.keys() else f "{param_name} parameter" , } else : if param[ "type" ] == int : param_type = "integer" elif param[ "type" ] == float : param_type = "number" elif param[ "type" ] == bool : param_type = "boolean" properties[param_name] = { "type" : param_type, "description" : param[ "description" ] if description in param.keys() else f "{param_name} parameter" , } if param_name not in required: # Default値は現状は参考程度にしかならないが一応セット properties[param_name][ "default" ] = param.get( "default" , None ) self._tools[tool.name] = { "mcp" : mcp, "name" : tool.name, "description" : description, "properties" : properties, "required" : required } self.logger.info(f "Registered tool: {tool.name} with properties: {properties} and required: {required}" ) @ property def tools (self): """登録されたツールの一覧をBedrock API仕様に準拠する形で返却する""" return [{ "type" : "function" , "function" : { "name" : v[ "name" ], "description" : v[ "description" ], "parameters" : { "type" : "object" , "properties" : v[ "properties" ], "required" : v[ "required" ] } }} for k,v in self._tools.items()] async def call_tool ( self, tool_name: str , tool_args: dict ): """指定されたツールを実行し、結果を返す""" self.logger.info(f "Function call detected: {tool_name}" ) # FastMCPツールを実行 if tool_name in self._tools.keys(): try : # パラメーターの補完を行う (default値はclaudeでは補完されない) for k,v in self._tools[tool_name][ "properties" ].items(): if k not in tool_args.keys(): if "default" in v: tool_args[k] = v[ "default" ] self.logger.info(f "Executing tool: {tool_name} with args {tool_args}" ) # 後で必要性に応じ修正する tool_result = await self._tools[tool_name][ "mcp" ].call_tool( tool_name, tool_args ) self.logger.info(f "Tool Result: {tool_result} Type: {type(tool_result)} Len: {len(tool_result)}" ) return tool_result except Exception as e: self.logger.error(f "Tool execution error: {e}" ) error_message = f "ツール '{tool_name}' の実行中にエラーが発生しました: {str(e)}" #return error_message raise Exception (error_message) return {} まとめ MCPの活用により、様々なツール、サービスを柔軟且つ統一的に活用する事が可能となります。 更に最近ではより上位のレイヤーでのコミュニケーションが可能となるエージェント間コミュニケーション・プロトコルも流行ってきている。 主要な候補としてACP、A2Aが存在していますが、今後A2Aに統合されるような話もあるようです。 これらの活用により、生成AIの活用の幅が更に広がる事が期待されます。 尚、上記の実装例も生成AIを活用して作成しています。(本当に便利です) 一方でそのままでは活用できず、細かな修正を加える必要はありました。 ※やはり盲目的に使うのではなく、内容を理解しつつ適切に活用すべきだと改めて感じています。 最期に セーフィーでは開発効率向上やプロダクトの価値向上に向けて、積極的に生成AI活用の検討も進めています。 最新の技術も組み合わせつつ、更なる成長へ向けて様々な開発に関わる取り組みを行っています。それらに一緒に関わってくれるエンジニアさんを絶賛募集しています!!!! safie.co.jp