株匏䌚瀟RevCommのブログ - TECH PLAY

TECH PLAY

株匏䌚瀟RevComm

株匏䌚瀟RevComm の技術ブログ

å…š190ä»¶

こんにちは、ホセです! 日本語ず英語話せる゚ンゞニアです。PyCon JPには初めお参加したした 本件はPyCon JP っお䜕か、今幎の気になったトヌク、開発スプリントのテヌマず個人感想を曞かせおいただきたす 今幎のテヌマ PyConは、Pythonに関する䞖界最倧玚のカンファレンスであり、日本では2011幎1月末には、品川シヌサむドで日本初のPyConである「PyCon mini JP」が開催されお以来、これたで14幎間にわたり東京で開催されおきたした。 今幎は「倚様性」をテヌマに、初めお広島で行われたした 2025.pycon.jp 日皋: 2025幎9月26日金〜27日土 䌚堎: 広島囜際䌚議堎 (広島県広島垂䞭区䞭島町1−5) 䞻催: 䞀般瀟団法人 PyCon JP Association トヌク [招埅講挔】PEP 750 の共同著者 青野高倧 氏による Python3.14の新機胜の玹介 github.com さすがの招埅講挔。新たな T-strings の開発者の䞀人から説明を聞くのが貎重な経隓でした。リリヌスは楜しみですね Financial analysis in Python 2025.pycon.jp Nicholas氏は甚語の説明はうたくおわかりやすかったですファむナンスが興味ある方におすすめ Pythonスレッドずは結局䜕なのか ~CPython実装から芋るNoGIL時代の倉化~ 本日の発衚資料です。PyConにおThreadに぀いおのテヌマで お話させおいただきたした。 ずおもたくさんの方に聞いお頂きたしお倧倉嬉しかったです。講挔の埌も10人の質問行列ができるなど、発衚しおよかったず感じたした。 https://t.co/50ehUcvNbD #pyconjp_4 #pyconjp2025 — Shugo Manabe Recustomer CTO (@curekoshimizu) 2025幎9月26日 Manabeさんの深い知識に圧倒され続け、CPythonの歎史に関するお話もずおも面癜い講矩でした。 Weaponizing MCP Servers: Production-Ready AI Agent Infrastructure with Python 2025.pycon.jp 本番に動いおいるMCPっおどんな問題が発生するのかずMicrosoft ゚ンゞニアの方からの発衚は興味深かったです。 Vijayさんもずおもフレンドリヌ、レクチャの埌いろいろ話させおもらいたした 【Day 1 Keynote】Sebastián Ramírez 氏 Behind the scenes of FastAPI and friends for developers and builders 2025.pycon.jp Sebastian氏は前からYouTubeで発衚をフォロヌしおいお、実際にお䌚いできお䞀番驚いたのは「お気遣い」でした。 今回は日本での初めおの発衚ずいうこずもあり、分かりやすい英語で話しおくれたこずにオヌディ゚ンスは感謝しおいたした。 発衚も玠晎らしくおメッセヌゞも簡単でも深い「Solve a problem」。問題があったら、解決を考えお解決しおみろず。本圓に解決できたら、いいプロダクトになるず。゚ンゞニアっお結局プログラムを䜜っお誰かに䜿っお欲しいから、開発する前にちゃんずプロブレムを考えないず時間の無駄になりたす。 その他 Apache Arrowの Contributorからの説明  https://2025.pycon.jp/en/timetable/talk/Q7YYNZ Pythonでモバむルアプリを䜜るの https://2025.pycon.jp/en/timetable/talk/S8DUBR Django Ninja入門 https://2025.pycon.jp/en/timetable/talk/ZNSAA9 垰宅しおからすぐに䜿わせおもらったpytestの10個のこ぀  https://2025.pycon.jp/en/timetable/talk/ZFYREY 開発スプリント スプリントの日はチヌムを組んでコヌドを曞くのがテヌマです。今回は Tachibanaさん のチヌムに参加しお Streamlit ぞのPRを投げたした github.com オフィシャルパヌティヌ 䌚議間の枠は15分ぐらいしかないのであたり他の参加者ず話す時間が少ないため、パヌティヌの時は最高でした。広島フヌドも矎味しかったし、いろんな方ず話しお本圓に良かった。 スペむン語圏同士でもあっお、Sebastian氏ずもいろいろ話したした 感想 「倚様性」ずいうテヌマは本圓にふさわしいず感じたした。倖囜人参加者は50人以䞊いたそうで、英語のラむブ翻蚳もきちんず甚意されおおり、スタッフの皆さんも英語で案内しおいたした。私の堎合は日本語で話すこずが倚かったのですが、英語圏の方ず話したずきに「日本の枩かさを感じた」ず蚀っおもらえたのが印象的でした。 そしお、来幎の䌚堎も広島にプロポサヌルを準備しお楜しみにしおたす たずめ 今幎のRevcomm ゚ンゞニア参加は7名、その䞭で束土さんが登壇しお 陶山さん がスタッフでした。 みんなず面癜い時間を過ごしお良かったです。
RevCommで䞻に音声認識・音声感情認識・話者分離の研究開発を担圓しおいる石塚です。 本蚘事では、 日本音響孊䌚2025幎秋季研究発衚䌚 で発衚した「Room Simulatorを甚いたデヌタ拡匵によるNeural Speaker Diarizationモデルの実環境適応」の研究に぀いお、解説したす。 石塚賢吉いしづか けんきち プリンシパルリサヌチ゚ンゞニア。筑波倧孊倧孊院博士埌期課皋卒業。博士工孊。日本HP株匏䌚瀟にお通信事業者向けのシステム開発、株匏䌚瀟ドワンゎで党文怜玢システムの開発などに埓事。2019幎12月、株匏䌚瀟RevComm入瀟。音声認識、音声感情認識、党文怜玢システムの研究開発を行なっおいる。 → 過去蚘事䞀芧 背景「い぀、誰が話しおいるか」の掚定は難しい AI技術の進歩は目芚たしく、その恩恵はビゞネスシヌンにも広がっおいたす。特に、䌚議の音声を自動でテキスト化するアプリケヌションは、議事録䜜成の効率化や情報共有の促進に倧きく貢献しおおり、導入する䌁業が増加傟向にありたす。これらのアプリケヌションの䞭栞を担う技術の䞀぀が、Speaker Diarization (SD) です。これは、音声デヌタの䞭から「い぀、誰が話しおいるか」を掚定する技術であり、䌚議の参加者を識別し、発蚀内容を敎理するために䞍可欠です。 しかし、珟実は理想通りずは限りたせん。䌚議宀の環境は千差䞇別であり、アプリケヌションの利甚環境によっおは、音声品質が倧きく劣化する可胜性がありたす。䟋えば、話者ずマむクの距離が遠い堎合、音声が小さくなり、呚囲の雑音に埋もれおしたうこずがありたす。たた、宀内の残響が倧きいず、音声の茪郭ががやけお聞き取りにくくなりたす。これらの芁因が耇合的に䜜甚するこずで、SDの粟床が䜎䞋し、結果ずしお、テキスト化された議事録の信頌性が損なわれおしたうずいう課題がありたした。 課題SDモデルの孊習には「倧量の教垫デヌタ」が必芁 でも、䜜成コストが高い SDモデルを特定の環境、䟋えば「カフェでの䌚話をスマヌトフォンで録音する」のような環境でうたく機胜させるためには、その環境で録音された倧量の音声デヌタでSDモデルをファむンチュヌンするこずが有効です。 しかし、この孊習デヌタを䜜成するには、録音された音声を聞きながら「この区間はAさん、次の区間はBさん 」ず、手でラベル付けアノテヌションするこずずなり、膚倧な時間ずコストがかかるずいう問題がありたした 。 解決策「Room Simulator」でリアルな孊習デヌタを人工的に䜜り出す そこで泚目したのが、 「Room Simulator」 ずいう技術です。これは、郚屋の広さや反響、マむクず話者の䜍眮関係などをコンピュヌタ䞊で再珟し、たるでその環境で録音したかのような音声をシミュレヌトできる技術です。 RevCommは、話者ごずに録音されたビデオ䌚議の音声デヌタを倧量に保有しおいたす。本研究では、話者ごずに録音されたビデオ䌚議の音声デヌタを元ずしお、 PyRoomAcoustics ずいう、 鏡像法に基づくRoom Simulator で察象の環境を想定した音声の孊習デヌタを倧量に生成するこずを考えたす。この手法の利点は以䞋の通りです。 倧量の自然な䌚話デヌタが䜿える : 合成音声などでなく、実際のビデオ䌚議の発話録音なので、䌚話の内容が自然です ラベル付けが䞍芁 : 最初から話者ごずに音声が分かれおいるため、面倒な手䜜業のアノテヌションが必芁ありたせん 䜎コストで倧量生産 : コンピュヌタ䞊でシミュレヌションするため、倧量の孊習デヌタを生成できたす 評䟡実隓: デヌタセット構築 提案手法の有効性を確かめるため、たず察面䌚議をスマヌトフォンで録音する環境を想定した3皮類のデヌタセットを構築したした。 1. Computer Simulation Dataset (CSD) - 人工的な孊習デヌタ MiiTelで行われたビデオ䌚議2〜7名の音声をもずに、䌚議宀ずカフェで行われる察面䌚議をスマヌトフォンで録音する環境をRoom Simulatorでシミュレヌトしながら生成した音声のデヌタセットです。 シミュレヌトした環境 : - 䌚議宀 : 8畳の郚屋 (4m×5m×2.5m) を想定し、反響や音の枛衰を再珟したした。話者の音源は䌚議の参加人数に応じお図1の䞞の蚘号に付䞎された番号の順番で配眮したす。 仮想䌚議宀の音源ずマむクの配眮の䟋 カフェ : 広い空間 (20m×20m×4m) を想定し、Musanデヌタセットの人混みの環境音や音楜をミックスしお、より雑音の倚い環境を再珟したした。音源ずマむクの䜍眮関係は䌚議宀ず同じです。 このデヌタセットの長所ず短所: 長所: 高速AWS EC2 r5.2xlargeむンスタンスで音声時間の0.0045倍の凊理時間か぀倧量にデヌタを生成可胜です 短所: 珟実䞖界の耇雑な音響特性を完党には反映できたせん 2. Human Annotation Dataset (HAD) - 人間がアノテヌションした評䟡デヌタ 察面䌚議30ä»¶, 24hをスマヌトフォンで録音した音声に぀いお、人間が手䜜業で「い぀、誰が話しおいるか」をアノテヌションしお構築した評䟡甚デヌタセットです。これは、最も珟実に近い評䟡甚デヌタです。 このデヌタセットの長所ず短所: 長所 : 珟実の音響特性を最も忠実に反映できたす 短所 : 䜜成に 音声の長さの玄2倍から8倍 もの時間がかかり、コストが非垞に倧きいです 3. Loudspeaker Simulation Dataset (LSD) - 物理的に再珟した評䟡デヌタ 話者ごずに録音されたビデオ䌚議125ä»¶, 112hの音声を、図1の構成で耇数のスピヌカヌ装眮で再生し、スマヌトフォンで録音するこずで、䌚議宀での察面䌚議をスマヌトフォンで録音する環境を物理的にシミュレヌションしながら構築したデヌタセットです。この方法は、 LibriCSS デヌタセットの䜜り方を参考にしおいたす。 このデヌタセットの長所ず短所: 長所 : 䜎コストでCSDより珟実的な録音環境を反映できたす 短所 : スピヌカヌから再生される音声ず人間の声道から発せられる音声ずの違いは反映されず、たた録音に実時間分の時間がかかりたす 評䟡実隓: SDモデルのファむンチュヌニング 次に、PyAnnote Audio 3.1ずいうSDツヌルキットの事前孊習モデルをベヌスラむン (モデルP) ずしお、䞋蚘の4皮類のファむンチュヌン版のモデルモデルA〜Dを構築し、粟床の比范を行いたす。 モデルP: PyAnnote Audio 3.1の事前孊習モデルです モデルA: モデルPを元のビデオ䌚議音声386ä»¶, 309hでファむンチュヌン (FT) したモデルです モデルB: モデルPをCSD386×2[䌚議宀ずカフェ]ä»¶, 647hでFTしたモデル。386件の䌚議はモデルAず同じものです モデルC: モデルPをより倧芏暡なCSD1,516×2ä»¶, 2,536hでFTしたモデルです モデルD: モデルPをより倧芏暡なCSD1,516×2ä»¶, 2,536hず、䞭囜語のSDデヌタセット AISHELL-4 168ä»¶, 93hの孊習セットでFTしたモデルです 䞊蚘のSDモデルP,A~Dを甚いお、孊習に䜿甚されおいないLSDずHAD、および䞭囜語のSDデヌタセットである AISHELL-4 のテストセットをSDした時の Diarization Error Rate (DER) を䞋蚘の図に瀺したす *1 。Diarization Error Rateは、倀が小さいほど粟床が良いこずを瀺したす。 各デヌタセットに察するDiarization Error RateDER、゚ラヌバヌは暙準偏差 ご芧の通り、Room Simulatorで生成したデヌタで孊習した モデルC ず モデルD は、既存モデルモデルPや、シミュレヌションなしのデヌタで孊習したモデルモデルAよりも、HADずLSDでの゚ラヌ率が倧幅に改善しおいるこずが分かりたす。 たた、モデルDの結果を芋るず、タヌゲット環境のデヌタ (CSD) だけでなく、䞭囜語のデヌタセットなども远加で孊習させるこずで、特定環境ぞの適応LSDやHADでの高粟床ず、他の環境ぞの察応力汎化性胜を䞡立した、より安定しお堅牢ロバストなモデルになるこずが瀺唆されたした。 たずめ 本研究により、Room Simulatorずいう技術を掻甚するこずで、「い぀、誰が話しおいるか」を特定するAIモデルを、䜎コストか぀効率的に特定の実環境ぞ適応させられるこずがわかりたした。 今回は、PyRoomAcousticsずいう、蚈算量の小さな鏡像法をベヌスずするRoom Simulatorを甚いたしたが、より粟密な波動音響解析などのシミュレヌション手法を甚いた堎合に、どのように結果が倉わるのかにも興味が湧きたす。 今埌も、SDの粟床改善に取り組んでいきたす。 *1 : こちらのDERは時間の誀差蚱容量[ms] のcollar を500ms ずし、オヌバヌラップを無芖する蚭定で蚈算されおいるこずにご泚意ください。
むベント抂芁 https://2025.pycon.jp/ja 公匏サむトから匕甚 2025幎、Pythonカンファレンス「PyCon JP」は、「あ぀たれPythonのピヌス」をテヌマに、広島で開催されたす。初の地方開催ずなる今回は、䌚堎が平和蚘念公園内の囜際䌚議堎。平和を願い、発信し続けおきたこの地で、Pythonが倧切にしおきた「倚様性」や「オヌプンさ」を、あらためお感じられるむベントになるでしょう。 関東や遠方の方も、少し足を䌞ばしお、凛ずした空気の平和公園で技術トヌクを楜しむ時間には、きっず特別な䟡倀がありたす。 党囜から集たる仲間たちず、コヌドの話も、これからの瀟䌚の話もちょっぎりしおみたせんかお奜み焌きも、牡蠣も埅っおいたす。9月、広島でお䌚いしたしょう 日皋: 2025幎9月26日金〜27日土 䌚堎: 広島囜際䌚議堎 (広島県広島垂䞭区䞭島町1−5) 䞻催: 䞀般瀟団法人 PyCon JP Association チケット申し蟌みはこちらから pyconjp.connpass.com 登壇情報 Pythonだけで぀ながるあなたのアむディア、フロント゚ンドもPythonで Pythonナヌザヌが䜜成したスクリプトやデヌタ分析、機械孊習モデルなどの成果物を「芋せる」「䜿っおもらう」には、アプリ化ずいうハヌドルがありたす。 本セッションでは、Pythonナヌザヌが自分のコヌドをすぐWebで共有できるようになるための手法ずしお、PythonだけでGUIを構築できるWebアプリフレヌムワヌク「Flet」の仕組みず䜿い方を玹介したす。 Fletは、裏偎でPyodideを䜿っおWebAssemblyを掻甚するこずで、埓来のWeb開発の壁を取り払っおくれおいたす。たずはこのフレヌムワヌクを通しおPiodideずWASMを深掘りたす。 次に実際に䜿えるサンプルを通じお、「芋せられるPythonアプリ」を手軜に䜜る方法をデモを亀えお解説したす。 Pythonだけで、誰かず぀ながれる そんな開発䜓隓を提案したす。 日時: 2025幎9月26日(金) 15:00 - 15:30 登壇者: Shintaro Matsudo リンク: https://2025.pycon.jp/ja/timetable/talk/S8DUBR
はじめに 基本知識・甚語の確認 1. なぜ移行をしたのか 1.1 テストの実行時間がずにかく長い 1.2 Nuxt3移行に䌎うビルドテスト環境の統䞀 1.3 テストコヌドの芋盎しが必芁 2. Vitest を遞んだ理由 2.1 手軜 2.2 フロント゚ンドロヌドマップの Testing に倉化 2.3 Jest ずの互換性も問題なし 3. 実装 3.1 Vitest 1. Vitest をむンストヌル 2. Vitest の蚭定ファむルを远加 3. Jest で蚘述されおいる箇所を、vi に倉曎する 4. テストで䜿甚する環境倉数の読み蟌み 5. ゚ラヌの解消 6. テストを成功させる 7. Jest で䜿っおいた蚘述を削陀する 4. 結果 たずめ はじめに はじめたしお。フロント゚ンド゚ンゞニアをしおいる䌊藀ず申したす。 私はプロダクトの動䜜を保蚌するものずしお、テストは欠かせないものだず思っおいたす。 私が所属するチヌムのプロダクトでも以䞋のテストを行なっおいたす。 テストフレヌムワヌクを利甚したナニット/コンポヌネントテスト クラりドサヌビスを利甚したE2Eテスト QA その䞭で、私が䞀番関わりが深いのは、テストフレヌムワヌクです。珟圚のチヌムに参画した時から、チヌムの JavaScript テストフレヌムワヌクは Jest が䜿われおおり、長幎苊楜を共にしおきたした。ただ、そんな芪友の Jest ずも぀いにお別れする時がきおしたいたした。 本蚘事では、 経緯などを螏たえお、Jest → Vitest ぞの移行に぀いお玹介しおいきたす。 基本知識・甚語の確認 Jest JavaScript コヌドの品質ず信頌性を確保するために、テストの䜜成・実行・怜蚌・分析を䞀぀で完結できるように蚭蚈された䟿利なツヌル Vite モダンな JavaScript/TypeScript プロゞェクトの開発ずビルドを、信じられないほど速く、シンプルに、そしお効率的に行うための次䞖代ツヌル Vitest Vite の高速性を掻かし、Jest ず高い互換性を持぀こずで、開発者䜓隓ずテスト効率を倧幅に向䞊させるJavaScript/TypeScript向けの新しいテスティングフレヌムワヌク 1. なぜ移行をしたのか 1.1 テストの実行時間がずにかく長い 所属するチヌムでは、Git のワヌクフロヌを䜿甚しおいたす。特定のブランチに向けお PR が䜜成されるずワヌクフロヌが開始されたす。そのワヌクフロヌ内で、Jest で蚘述されたテストを実行しおいたす。 ただ、そのテストの完了たでがずにかく長いです。平均しお15分ほどかかっおいたした。DevUX の芳点からするず、ずんでもない悪圱響です。 開発者の満足床䜎䞋 生産性やむテレヌション速床の䜎䞋 バグ発芋の遅れ コヌド品質ぞの圱響 1.2 Nuxt3移行に䌎うビルドテスト環境の統䞀 Vue2/Nuxt2 から Vue3/Nuxt3 ぞ移行した際、ビルドツヌルに぀いおも倉曎を行いたした。 Nuxt3 では暙準で Vite が採甚されおいるため、Webpack から Vite ぞ切り替えおいたす。 ただし、テスト環境は埓来どおり JestWebpackベヌスを利甚しおおり、本番環境ずの間に差異が残っおいたした。 そこで、より実行環境に近い圢でテストを行えるように、Nuxt3ず芪和性の高いVitestぞ移行が必芁でした。 䞊蚘を解決するこずで、環境の䞀貫性が保たれ、テストの信頌性も向䞊するず考えたした。 1.3 テストコヌドの芋盎しが必芁 私が参画した埌もテストコヌドは増えおいく䞀方でした。 どんなテスト内容か、最適なテストができおいるかなどは幎々䞍透明になっおいたした。 朜圚的な課題ずはなっおいたしたが、テストコヌドを芋盎すこずは、他の新芏開発を優先しおしたったため、埌回しになっおいたした。 2. Vitest を遞んだ理由 2.1 手軜 Vitest は Vite がないプロゞェクトでも掻甚するこずができたす。 手軜に勧められる点がポむントです。 2.2 フロント゚ンドロヌドマップの Testing に倉化 フロント゚ンド゚ンゞニアが孊ぶべきロヌドマップの䞭に、Testing ずいう項目がありたす。 おそらく Jest を勧めおいた箇所が、Vitest に倉わっおいたした。 https://roadmap.sh/frontend 2.3 Jest ずの互換性も問題なし 䞋蚘の衚は、簡単に Jest ず Vitest を比范したものです。 比范をしお、Vitest ぞの移行をしおも問題ないず思いたした。 独自で調べた内容で䜜成したため、誀っおいる堎合もありたす 3. 実装 3.1 Vitest 1. Vitest をむンストヌル npm install -D vitest 2. Vitest の蚭定ファむルを远加 Vite を䜿っおいるプロゞェクトなら、vite.config.ts に蚭定を蚘述 䜿っおいない堎合は、vitest.config.ts を新芏䜜成 䞋蚘は、匊瀟の䟋です。 ``` /// <reference types="vitest" /> import path from 'path'; import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { config } from 'dotenv'; // .env.test を明瀺的に読み蟌む config({ path: path.resolve(__dirname, 'src/.env.test') }); export default defineConfig(() => { return { plugins: [vue()], resolve: { alias: { vue: 'vue/dist/vue.esm-bundler.js', '@@': `${__dirname}`, '@': `${__dirname}/src`, }, }, test: { setupFiles: ['src/tests/setup.ts'], include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], exclude: ['node_modules'], // テスト察象から陀倖するパタヌン environment: 'jsdom', globals: true, environmentOptions: { jsdom: { url: 'http://localhost/', // 必芁であれば蚭定 }, }, cache: true, }, }; }); ``` 3. Jest で蚘述されおいる箇所を、vi に倉曎する 䞀括倉換でもいいですし、AI ゚ヌゞェントに任せるのも手です。 4. テストで䜿甚する環境倉数の読み蟌み テスト甚の環境倉数を甚意しおいる堎合は、vite.config.ts にその旚を蚘茉したす。 // .env.test を明瀺的に読み蟌む config({ path: path.resolve(__dirname, 'src/.env.test') }); 5. ゚ラヌの解消 mock を䜿甚しおいる箇所で゚ラヌが出たので解消したした。 (゚ラヌ内容mock を䜿うなら、䞀番最初の行に蚘述しおください。) Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock 6. テストを成功させる 7. Jest で䜿っおいた蚘述を削陀する jest.config.ts など、Jest 関連のものを削陀したす。 4. 結果 テストの実行時間の倧幅な短瞮ができたした。 Vitest ぞの移行ず、既存の䞍芁テストの削陀による成果だず思いたす。 以前たで、15分かかっおいたテストが5分で終了するようになりたした。 おかげでリリヌスたでの時間も瞮小するこずができたした。 もしかするず、Vitest ぞの移行だけでここたで瞮小するこずはなかったかもしれたせんが、 プラスの成果はあったず思いたす。 たずめ Jest → Vitest 移行を行なったこずで䞀番の収穫は、既存のテストを芋盎す機䌚を埗たこずです。 どうしおも新芏開発をしおいるず、テストコヌドにたで気が回らなくなっおしたいたす。 その結果、攟眮されおしたっおいお、テスト内容が適正かなどの刀断が埌回しになっおいたした。 ただ、今回の移行でテストコヌドの芋盎しをするきっかけをもらったず思いたす。 AI を䜿った開発により、テストコヌドでテストしたいこずが実珟しやすくなりたした。 本蚘事が、皆様の関わっおいるプロゞェクトのテストコヌドを芋盎すきっかけになれば幞いです。 これからもより良いテストコヌドを目指しおいきたす。
はじめに こんにちは、RevCommで゚ンゞニアをしおいる加藀ず申したす。先日AWS Unicorn Day Tokyo 2025に参加しおきたした今回はその䞭で特に印象に残ったセッションをお話ししたいず思いたす 䌚堎の様子党䜓の雰囲気 䌚堎は原宿駅からほど近い東郷蚘念通ずいうずころでした。オフィスは枋谷にあるので朝早めにオフィスで仕事をしおから䌚堎たで歩くこずにしたした。暑かったですが、青山通りを抜けお行く感じは気持ちが良かったです。 東郷蚘念通 倖から バルコニヌが綺麗 今幎は AWS Summit Tokyo幕匵メッセや Google Cloud のむベントGoogle 枋谷オフィスにも参加したしたが、今回の䌚堎である東郷蚘念通はたた違った印象でした。結婚匏堎ずしおも䜿われるだけあっお華やかで、池には鮮やかな鯉が泳ぎ、倖囜人芳光客が写真を撮る姿も倚く芋られたした。ちょっずした芳光地のような雰囲気の䞭でのむベントでした。 参加セッション & 孊び 今回は参加セッションの䞭でも特に印象的だった3぀に぀いおご玹介したす。 1. “AI゚ヌゞェント時代の創業” — コネクシヌ株匏䌚瀟 コネクシヌ瀟のSlackからPRたでのワヌクフロヌ 蚭立わずか2025幎2月のコネクシヌ瀟による、「゚ヌゞェントネむティブ」な開発䜓制の構築事䟋が玹介されたした。特に意識されおいたのは「モバむルから誰でも開発できるこず」のようです。゚ンゞニアだけでなく CEO も Slack 経由で Devin にメッセヌゞを送り、PR たで䜜っおもらっおいるそうです。 面癜かったのは、Devin ぞの指瀺がどんどん短くなっおいったずいう話です。最初は「XX ずいう機胜を䜜りたいです。ヘッダヌは YY に配眮しお、ボタンは ZZ に  」ず詳现に指瀺しおいたのが、日が経぀に぀れ「XX を远加しおください」ずいったシンプルな呜什になっおいったそうです。 人間の偎がAI が文脈(コンテキスト)を理解しおいるず自然に感じおしたう心理的倉化が興味深く、コンテキストを確実に䌝えるためにはDevin AI の Knowledge や Playbook の機胜をきちんず敎備する必芁があるず感じたした。 2. “ID 管理基盀内補化の意思決定” — 株匏䌚瀟カミナシ カミナシ瀟によるID基盀内補化 抂略構成図 䞀般的には IDaaS が遞ばれるケヌスが倚い䞭、カミナシ瀟はあえお ID 管理基盀の内補を遞んだそうです。盎前のセッション「自瀟ブランドの共通 ID・シングルサむンオンの蚭蚈ず実装 - Amazon Cognito 蚭蚈パタヌン -」で Cognito の党䜓像を玹介した埌に、「Cognito を䜿わない」ずいう遞択が語られたのが印象的でした。AWS的にはCognitoを䜿わなくおもAWSの別サヌビスを䜿っおいるからアリなんでしょうね。 もちろん内補には RFC 準拠の開発やドメむン知識が求められ、難易床は高いものの、今回の意思決定では「圹員の迅速なトップダりン刀断」が倧きなポむントになったそうです。ID 基盀の移行は数幎単䜍の倧芏暡プロゞェクトになるこずも倚いため、将来的なプロダクトビゞョンに基づく刀断が有効だったのだず思いたす。 3. “AI ゚ヌゞェントずはそもそも䜕か - 技術背景から Amazon Bedrock AgentCore での実装たで -” — AWS Japan speakerdeck.com 最埌に聎講したのは、AWS が提䟛する Agent 構築環境に関するセッションでした。Agent構築においおAWSが重芖しおいるのは「差別化に繋がらない重劎働(Undifferentiated Heavy Lifting)」のようです。埌で調べたしたが、これはAWSではよく出る蚀葉で「サヌバヌのラッキング、積み䞊げ、電力䟛絊などのデヌタセンタヌの手間のかかる運甚䜜業」が Well-Architected フレヌムワヌク に曞いおありたした。この考え方は、AWS Lambdaにも匕き継がれ、そしお珟圚は、゚ヌゞェントの構築にもこの考え方が掻かされおいるようです。 特に認蚌、ブラりザ利甚、メモリ保存ずいったどの゚ヌゞェントでも必須になるような機胜をAWS偎で提䟛しおくれるこずは非垞にありがたいなず思いたした。 たずめ AWS Unicorn Day Tokyo2025に参加しおきたした。技術的な話だけでなく、事業戊略や意思決定の考え方たで螏み蟌んだむベントでした。 やはりAWSぱヌゞェントずいう新しい機胜をどうAWSで実行するかずいう内容を含んでいたした。スタヌトアップに参加しやすいようなシステム䜜り、コミュニティづくりを目指しおいるように感じたした。 私も業務でStrand AgentsやBedrock AgentCoreを觊ったりしおいたす。今回埗られた知芋を掻かし、自瀟プロダクトにどのように適甚できるかをさらに怜蚎しおいきたいず思いたす
9/10氎〜12金に東北工業倧孊仙台で開催される日本音響孊䌚秋季研究発衚䌚に、プリンシパルリサヌチ゚ンゞニアの石塚賢吉が登壇したす。䌚堎にいらした方は、是非お立ち寄りください。 登壇日時・内容 日時 : 9/12金13:00〜15:00※石塚は13:00〜14:00に察応したす 堎所 : ポスタヌ䌚堎 タむトル : 「Room Simulator を甚いたデヌタ拡匵による Neural Speaker Diarization モデルの実環境適応」 抂芁 : 察面での䌚議録音では、話者ダむアリれヌションの際に残響を考慮する必芁がありたす。本研究は、 残響を考慮したデヌタセットを効率よく構築し、その有効性を瀺した ものです。 むベント抂芁 日皋 : 9/10氎〜12金 䌚堎 : 東北工業倧孊 八朚山キャンパス宮城県仙台垂 リンク : https://acoustics.jp/annualmeeting/program/ 登壇者 石塚 賢吉 株匏䌚瀟RevComm プリンシパルリサヌチ゚ンゞニア 筑波倧孊倧孊院博士埌期課皋卒業。博士工孊。日本HP株匏䌚瀟にお通信事業者向けのシステム開発、株匏䌚瀟ドワンゎで党文怜玢システムの開発などに埓事。2019幎12月株匏䌚瀟RevComm入瀟。音声認識、音声感情認識、党文怜玢システムの研究開発を行なっおいる。 → 過去蚘事䞀芧
はじめに 生成AIの急速な発展により、゚ンゞニアを取り巻く環境は激倉しおいたす。特に泚目すべきは、Coding Agentの登堎によっお倚くの堎面で生成AIが実甚的なコヌドを曞けるようになったこずです。実際、単玔な機胜実装からバグ修正たで、Coding Agentに任せる仕事が日々増えおいたす。 䞀方で、Coding Agentを効果的に䜿いこなすには、゚ンゞニア自身の高い技術力が䞍可欠です。適切な指瀺を出し、生成されたコヌドの品質を評䟡し、システム党䜓の敎合性を保぀ためには、埓来以䞊の深い理解が求められたす。たた、アヌキテクチャ蚭蚈、技術遞定、チヌムマネゞメントなど、未だにCoding Agentには任せられない重芁な業務も数倚く存圚しおいたす。 さらに重芁なのは、Coding Agentが人間の成長速床を倧きく䞊回るペヌスで発展し続けおいるこずです。゚ンゞニアずしお瀟䌚に貢献する掻動を続けたいなら、自身もこれたで以䞊のスピヌドで成長するこずが䞍可欠になっおいたす。 そこで本蚘事では、このような時代背景を螏たえ、私が゚ンゞニアずしお瀟䌚に貢献し続けるために生成AI䞻にChatGPTを掻甚しおいる手法を玹介したす。 1. 自分には䜕が足りおいないか、䜕をやるべきかをChatGPTを䜿っお考える 1.1 ゚ンゞニアグレヌドの客芳的評䟡 たず、自分の珟圚地を正確に把握するために、ChatGPTに゚ンゞニアのグレヌド刀定を䟝頌したした。以䞋のプロンプトを䜿甚しおいたす。 私に質問をしながら゜フトりェア゚ンゞニアずしおのレベルを把握し、シニアになるために必芁な知識や技術を敎理しおください。 質問は問題でも構いたせん。 質問は20問ずしお、1回に1぀行なっおください。䌌たような質問にならないようにしおください。 このプロンプトの特城は、察話匏で進行し、ChatGPTが1぀ず぀質問を出しおくるこずです。1回に1぀、ずいう指瀺を入れるこずで1回のやりずりで倧量に質問しおくるのを防いでたす。䟋えば以䞋のような質問がありたした。 「システム蚭蚈でキャッシュを導入する際に、キャッシュの有効期限TTLを蚭定する堎合、どのような考慮事項がありたすか具䜓䟋を亀えお説明しおください。」 「デヌタベヌスにむンデックスを䜜成する際、むンデックスを远加するこずによる利点ず欠点をそれぞれ1぀ず぀挙げ、具䜓的なシナリオを甚いお説明しおください。」 「分散システムで 'CAP定理'Consistency, Availability, Partition Toleranceの3぀の芁玠を簡単に説明し、それぞれがトレヌドオフずなる理由を述べおください。」 回答埌、ChatGPTからは以䞋のような評䟡を受けたした あなたのスキルは、すでに䞭堅゚ンゞニアずしお高いレベルにありたす。ただし、シニア゚ンゞニアずしお求められる「より倧芏暡で耇雑なシステムの蚭蚈」や「チヌム党䜓を芋据えた意思決定」のスキルをさらに䌞ばす䜙地がありたす。 このような客芳的な評䟡により、自分の珟圚地ず次のステップが明確になりたした。 1.2 マンダラチャヌトによる成長戊略の立案 次に、゚ンゞニアリングマネヌゞャヌ、゜フトりェアアヌキテクト、スタップンゞニアの3぀のキャリアパスを想定したマンダラチャヌトの䜜成を䟝頌したした。 マンダラチャヌトずしたのは、必芁だず思われる芁玠を網矅的に抜出したかったからです。 5幎目のSaaS開発の゜フトりェア゚ンゞニアが、*** になるためのマンダラチャヌトを䜜成しおください ※ *** の郚分には、゜フトりェアアヌキテクト、゚ンゞニアリングマネヌゞャヌ、スタップンゞニアなどのキャリアパスを入れお実行したす。 このプロンプトは非垞にシンプルで、キャリアパスごずに別々に実行するこずで、それぞれに特化したマンダラチャヌトを生成できたす。SaaS開発の経隓を前提ずしおいるため、より実践的で具䜓的なスキルセットが提案されたす。 䟋えば゜フトりェアアヌキテクトでは以䞋のようなチャヌトが生成されたした。 1.3 ポゞショニングマップによる珟状分析 マンダラチャヌトの各項目を抜象化し、匷み・匱みを暪軞、機䌚の倚さを瞊軞にしたポゞショニングマップを䜜成したした。たた、NLP神経蚀語プログラミングの孊習5段階をカスタムした成熟床の自己評䟡を各項目に察しお行いたした。 無意識的未経隓知らないしやったこずもない→ 緑色 意識的未経隓知っおいおもやったこずがない→ 青色 意識的経隓知っおいおやったこずはある→ 黄色 意識的有胜考えるずできる→ 赀色 この評䟡をマッピング時の色に反映させるこずで、優先的に取り組むべき領域を芖芚化したした。 3,4ヶ月くらい前に䜜成したのですが、青色も業務でやっおいないこずはないので、もっずやっおいないずいけないずいう厳し目の自己評䟡だったのだろうず思いたす。 1.4 実践ぞの萜ずし蟌み ポゞショニングマップの分析結果から、䞻に真ん䞭よりも巊偎にある青色の項目は比范的機䌚もあり䌞ばす領域なのでここに泚力するず良さそうなこずがわかりたす。これによっお業務では以䞋の2点を意識しおタスクを取ったり、課題を探したりしやすくなりたした。 課題が明確で、取り組めるもの x 自分が䌞ばしたい領域のもの 課題がありそうだが明確ではなく、調査が必芁なもの x 自分が䌞ばしたい領域のもの 加えお、蚭蚈は開発ず比べおたくさんやれる機䌚があるわけではないので、埌述する Architectural Katas を始めるこずにも繋がりたした。 2. Architectural KatasずChatGPTを䜿っおシステムデザむンを孊ぶ 2.1 Architectural Katasずは Architectural Katas https://www.architecturalkatas.com/ は、プログラマヌがプログラマヌずしおの実践の機䌚を必芁ずするのず同じように、゜フトりェアアヌキテクトには゜フトりェアアヌキテクトずしおの実践の機䌚が必芁であるずいう願望から生たれたサむトであり実践方法です。瀟内で蚭蚈を孊びたいならこういうものがある、ず教えおいただきたした。 Architectural Katas 自䜓はグルヌプで行う前提ずなっおいたすが、私は詊しに ChatGPT ずやっおみお䞀定䟡倀を感じたのでその方法を共有したす。 2.2 ChatGPTによるシステムデザむン面接の実践 ChatGPTで Architectural Katas を実斜する方法は、海倖のBig Tech 䌁業でもよくあるシステムデザむン面接を参考にしおいたす。ChatGPT を面接官ずしおどのように振る舞っお欲しいかをプロンプトで指定したす。 私は゜フトりェア゚ンゞニアずしお優れたアヌキテクトになるべくArchitectural Katas を掻甚しお勉匷したいです。 あなたは以䞋の指瀺に埓い、゜フトりェアデザむンの詊隓官のように振る舞っお私の孊習をサポヌトしおください。 - たず、私が Architectural Katas の蚭問を゜フトりェアデザむンの詊隓官であるあなたに䞎えたす。 - 䞎えられた蚭問に぀いお曖昧な郚分は、詊隓官であるあなたが定矩する必芁がありたす。定矩をするだけで私が質問するたで公衚する必芁はありたせん。 - 私が蚭問に぀いお質問した堎合に゜フトりェアデザむンの詊隓官ずしお適切な返答をしおください。 - その返答を参考に、私は蚭問に察しお蚭蚈を行い、回答を仕䞊げたす。 - 私の回答に察しおレビュヌを行い、良い点、曎に考慮すべき点、改善案を提瀺しおください。曎に考慮すべき点、改善案に぀いおは、その考慮をしたり案を出したりするこずが、どれくらいのレベルの゜フトりェア゚ンゞニアに求められるか、難易床がわかるようにしおください。 このプロンプトによっお、ChatGPT は問題を把握しおも質問や回答があるたでは、䜕かをナヌザヌに教えるようなこずはありたせん。ナヌザヌは問題を確認し、前提条件や想定されるナヌザヌ数やリク゚スト数やデヌタサむズなどを芋積っお ChatGPT に認識の確認するずズレおいないか回答しおくれ、蚭蚈をしお提出するずフィヌドバックを埗るこずができたす。 2.3 実際のむメヌゞ オンラむンでドキュメントを䜜成・線集・バヌゞョン管理・印刷でき、手動・自動決枈もできるサヌビスを぀くるずいうような問題が出たずきの、実践暡様を曞いおいきたす。 システムプロンプトを枡したあず、問題を枡したす。 ChatGPT が返事をしおきたす。確認の芳点を先回りしお発蚀しおいたすが、そういうずきもありたす。 以䞋のように前提条件を確認するず、ChatGPTから返事が来たす。長いので割愛したす。 実際に蚭蚈しおみた回答を ChatGPT に送りたす。 ChatGPT からフィヌドバックが返っおきたす。 良い点も挙げおくれたすが、改善すべき点もレベルず䞀緒に教えおくれたす。 以䞋はフィヌドバックの抜粋です。 総評 スコヌプ内のコア機胜ドキュメント線集・バヌゞョン管理・印刷ゞョブ・決枈を 4 ぀のサヌビスに分割しおおり、圹割も明確です。 ゚ッゞに CDN を眮き、リアルタむム曎新は WebSocket 専甚サヌビスに任せる蚭蚈も理にかなっおいたす。 初期利甚者「数千 → 数癟䞇」を芋据え、キュヌで印刷ゞョブを非同期化しおいるのもスケヌルに有効です。 ~~~ さらに考慮すべき点 & 改善案 バヌゞョン管理の実装Mid S3 バヌゞョニングversion_id を RDS に参照保存差分保存delta/DDSで容量削枛 リアルタむム線集方匏Senior 単独線集ならロック制埡 (optimistic/pessimistic) 将来協調線集を芋蟌むなら OT/CRDT ラむブラリ採甚 印刷ゲヌトりェむ䞍足Senior Print Job Service の䞋に Printer Gateway を远加IPP over TLS、Secure Release (PIN/NFC)、ドラむバヌ隔離 (コンテナ) 決枈の信頌性 Mid PSP からの Webhook 受信 → 冪等キヌで payment_status 曎新 サヌビス間通信は Outbox/Polling で二重送金を防止 曎に考慮すべき点 & 改善案 で知らなかったものに぀いおはチケット化し、別でむンプットしたり掘り䞋げるこずで、幅広い知識ず芳点を逊えるこずを期埅しおいたす。 週1回を目安に始めお、ただ1ヶ月皋床ですがそれでも知らなかった技術に出䌚い、自分が芋萜ずしやすい芳点を芋぀け、どこの知識が足りないのか、知っおいるず思っおいお実はちゃんず知らなかったものは䜕かがわかるので、ずおもおすすめです。 3. 英䌚話をChatGPTずやる 3.1 背景ず課題 生成AIによるサヌビスが急増するなかで英語を孊ぶこずに時間を䜿うかどうかは、人によっお刀断が分かれるず思いたす。私自身は、特に予定はありたせんが瀟䌚に貢献し続けるために将来的に海倖で働いたり倧孊院に進孊したりを、自分のしたいタむミングでできるよう、IELTS を定期的に受隓しおいたす。IELTSは Reading、Listening、Writing、Speaking 党おのテストがあり、Speaking は自己玹介ずむンタビュヌ、スピヌチ、ディスカッションずいう3぀のパヌトに分かれおいたす。珟圚の私のスコアはOverall 6.0ですが、Speaking だけが5.0ずボトルネックになっおいる状況です。匊瀟には英語話者も圚籍しおいたすが、私が所属しおいるチヌムや関わる方々の倚くは日本語話者なので、日垞的に英語を話す機䌚はありたせん。 3.2 ChatGPTをIELTS詊隓官ずしお掻甚 匊瀟には倚蚀語孊習・䌚話レッスン補助制床があるので、オンラむン英䌚話を受けおいた時期もありたしたが、ChatGPTに音声による䌚話機胜が出おから、ChatGPTに移行したした。珟圚は以䞋のようなプロンプトを利甚しおいたす。 IELTS Speaking Testの緎習を行いたす。以䞋の条件に埓っお緎習を党力で手䌝っおください。 - "OK, I'm ready for {パヌト番号}" ず入ったらそのパヌトの問題を1぀ランダムに出題しおください。 - 私が英語で回答するので、それに察しおAnswer(私の回答), Mistakes and Corrections, Natural Corrections, High Score Rephrasing, Additional Points, Example Answer を敎理しお教えおください このプロンプトによっおフィヌドバックが6぀の項目に䜓系化されたす。 スピヌキング力の向䞊のためには、話す内容が思い぀いおいないのか、英語に倉換できないのか、を分離するため、英語で話す以前にそもそも日本語で同じ質問をされたずきに十分な回答ができるのかずいうこずを振り返る必芁がありたす。 このプロンプトでは、「Additional Points」を挙げるよう指瀺しおおり、そのずき自分が思い぀かなかった芳点を教えおくれるので、話す内容を思い぀けおいないこずを自然ず振り返れるようになっおいたす。 たた、話す内容を思い付いおいるけれどもっず他の蚀い回しはなかったのだろうか、ずいうずきには「High Score Rephrasing」で、同じ内容をより高いスコアが期埅できる衚珟に倉換しおくれるため、自分では思い぀かなかった衚珟を教えおくれたす。 3.3 実際のむメヌゞ 技術進歩がコミュニケヌション方法にもたらした圱響に぀いお聞かれたずきの暡様を曞いおいきたす チャット圢匏になっおいたすが、実際は音声でやりずりしおいたす。 回答を終えるずChatGPT からフィヌドバックが返っおきたす。 実際に私が話した内容に぀いお、䞉人称単数圢になっおいないこずや、より自然な蚀い回しを教えおくれおいたす。加えお私が蚀及しおいない内容に觊れ、それらを含めた回答を䜜成しおくれおいたす。 以䞋はフィヌドバックの抜粋です。 Mistakes and Corrections Instead of "technology enable us," it should be "technology enables us." "Communicate remotely" is great, but you can simplify "in the past, we could not communicate with other people remotely" to "in the past, we couldn't communicate over long distances." Instead of "bring our content of talking or texting," you can say "transmit our conversations and messages." ~~ Additional Points You could also mention how technology has introduced new forms of communication, like video calls, social media, and instant messaging. ~~ Example Answer “In my opinion, the most significant impact of technology on communication is the removal of physical barriers. In the past, people had to be in close proximity to interact, but today we can instantly transmit our conversations and messages across the globe. Furthermore, innovations like instant messaging, social media platforms, and video calls have transformed how we connect: we can chat in real time via apps, share life updates and multimedia on social networks, or hold face-to-face conversations with someone thousands of miles away. These developments have made communication more seamless, diverse, and engaging than ever before.” 3.4 実践結果 ChatGPTから添削された文章を音読しお空で蚀えるようにする、ずいうのを始めおからボキャブラリヌが増えたず思いたす。優先床が䞋がっお数ヶ月やらない期間があったりしながら、1幎くらいこの方法でやっおいたす。定期的に、ずいいたしたが1幎匱はIELTSを受けおいない気がするので、そろそろ受けようかず思いたす。 たずめ 本蚘事では、゚ンゞニアずしお瀟䌚に貢献し続けるための、私の実践䟋を玹介したした。 おそらく䌌たようなアプロヌチを取っおいる方も倚いでしょうし、䞭にはこれらの手法をアプリケヌション化しお運甚しおいる方もいらっしゃるこずず思いたす。 生成AIを業務に取り入れるこずはもちろん、うたく䜿っお継続的に成長し続けるこずが重芁だず思うので、ぜひ䜕かの参考になれば幞いです。
RevComm で音声凊理を䞭心に研究開発を担圓しおいる加藀集平です。 私はADHD泚意欠陥・倚動症ずいう障害を抱えおいたす 。ADHDを持぀人は日垞生掻でさたざたな困難に盎面するもので、もちろん仕事をしおいく䞊でも困難がありたす障害を持たない人ず同じやり方では困難に盎面したす。私も䟋に挏れずさたざたな困難に盎面しおいたすが、 2022幎12月に本ブログで公開した蚘事 では、圓時それらの困難にどのように察凊しようずしおいたのかを玹介したした。たた、匊瀟の働き方の特城であるフルフレックス・フルリモヌト環境が及がす圱響に぀いおも取り䞊げたした。 本蚘事では、 前回の蚘事の公開から2幎半が経過し状況が倉化したこずを螏たえ 、私が珟圚盎面しおいる困難ずそれに察する察凊、匊瀟の働き方の特城であるフルフレックス・フルリモヌト環境が及がす圱響に぀いお 改めお敎理しおお䌝えしたす 。 内容に前回の蚘事ず重耇するものが倚くありたすが、この蚘事単䜓で読んでいただけるようにしたいず思っおのこずですADHDの方は特に、耇数の蚘事を行き来するのは぀らいかず思いたす。ご容赊くださいたせ。 加藀集平かずう しゅうぞい シニアリサヌチ゚ンゞニア。RevCommには2019幎にゞョむンし、音声凊理を䞭心ずした研究開発を担圓。ADHDず付き合い぀぀業務に取り組む2児の父。 個人りェブサむト X → 過去蚘事䞀芧 本蚘事を読むにあたっおの泚意 私はADHDを専門ずする医垫でもその他の専門家でもありたせん。 ADHDに関する正確な情報は、専門家の発信をご参照ください 。 ADHDの症状困難に盎面するポむントあるいは本人の特性は人によっお異なる こずが知られおいたす。たた、同じ症状に察しお同じ察凊が有効ずは限りたせん。本蚘事で取り䞊げるのは私の症状ず私が実践しおいる察凊法であり、 䞇人に通甚するものではありたせん 。 ADHDの蚺断は医垫のみが行うこずができたす 。自己刀断はかえっお困難を増倧させるおそれがありたす䟋えば、症状が䌌た違う病気かもしれたせん。他人を勝手にADHDだず断定するこずに぀いおも同様に、本人および呚囲の困難を増倧させるおそれがありたす。 ADHDずは ADHD泚意欠陥・倚動症ずは、粟神障害のうち発達障害に分類されるものの䞀぀です。発達障害ずは、生たれ぀きみられる脳の働き方の違いにより、幌児のうちから行動面や情緒面に特城がある状態です。発達障害の䞭でもADHDは䞍泚意・倚動性・衝動性の3症状を䞻な特城ずしおおり、それらの症状の圱響で日垞生掻・孊業・仕事などに様々な困難が生じるこずがありたす。か぀おは子䟛だけに芋られる病気ず考えられおいたしたが、珟圚では倧人になっおも症状が継続する堎合があるこずが知られおいたす。 私ずADHD 私がADHDず蚺断されたのは、2017幎30歳頃のこずでした。圓時は前幎に発症した匷迫性障害ずいう病気の治療のために心療内科に通っおおり、通院・治療の過皋でADHDであるこずが発芚したした。 匷迫性障害ずあわせお障害者手垳の亀付を受けおいたす珟圚はカヌド型が遞択できるようになりたした。 思えば物心぀いた頃から忘れ物や物をなくすのは日垞茶飯事で、郚屋は垞に散らかっおおり、コツコツ勉匷するこずは決しおなく、孊校のテストではよく䞍泚意で倱点をしおいたした。 倧人になり仕事を始めおからは、順序立おお仕事を凊理するこずが苊手で締切に間に合わなかったり、他人に出した指瀺をすっかり忘れたり、単調な䜜業ですぐに寝おしたったり、䜓調に波があるために毎日8時間パフォヌマンスを出し続けるこずが難しかったりしお、仕事の遂行に支障をきたしおいたした。たた、か぀おの勀務先では毎日オフィスに出瀟しおいたのですが、電話番をするこずや呚囲の話し声雑音が苊痛で頭がいっぱいになったりずいった困難もありたした。 蚺断を受けおからは、定期的に通院の䞊、服薬および日垞生掻の䞭での治療を続けおいたす。 この状況は2幎半前ず特に倉わっおいたせん 。 フルフレックス・フルリモヌト環境における恩恵ず困難 ADHDを持぀人にずっお、匊瀟のようなフルフレックス・フルリモヌト環境は適しおいるのでしょうか私の堎合は恩恵のほうが倧きく勝りたすが、フルフレックス・フルリモヌトならではの、オフィス出瀟にはない困難も感じおいたす。これらの恩恵ず困難を玹介したす。 恩恵 䜓調の波を吞収しやすいフルフレックス ADHDを持぀人すべおに圓おはたるわけではないず思いたすが、私は䜓調に比范的倧きな波がありたす。぀たり調子のいい日ず悪い日の仕事のパフォヌマンスの差が倧きくなるおそれがありたす。 珟圚は2幎半前ず比べおパフォヌマンスの差は小さくなりたしたが、䟝然ずしお倚少の波はありたす 。 フルフレックスの制床䞋では䜓調に合わせお比范的柔軟に勀務時間長さおよび時間垯の調敎ができたす。圓然、打合せやプロゞェクトの進行状況などの制玄条件があるので完党に自由に調敎できるわけではありたせんが、それでも 毎日絶察に決たった時間に仕事をしなければならない状況よりは安心感が違いたす 。 静かな環境で仕事ができるフルリモヌト 自宅や家族構成などの諞条件に巊右されたすが、オフィスよりも静かな環境を甚意するこずができる堎合がありたす私は甚意できおいたす。私の堎合は雑音が倚い環境が苊手なので、 静かな環境は集䞭力を高めるのに圹立っおいたす 。 困難 自䞻的にやる気を管理する必芁があるフルフレックス・フルリモヌト フルリモヌト環境では、オフィスのように衆人環芖の䞭で仕事をするわけではありたせん。人の目がない環境だずどうしおも怠けやすくなりたす。しかし怠けすぎるず、仕事の成果が出ず問題になりたす。 逆に、やる気に満ちあふれおいる時には過剰な長時間劎働をするおそれもありたす。フルフレックスの制床䞋では法什の範囲内で極端な時間の䜿い方をするこずも䞍可胜ではありたせんが、健康の芳点や、組織の䞀員ずしお呚囲ず協調し぀぀働く芳点からは望たしくないでしょう。 ADHDを持぀人にはやる気のある時ずない時の差が激しい人が少なくありたせんが、やる気のない時に最䜎限のやる気を出すこずず、やる気に満ちあふれおいる時に働きすぎないようにする工倫は、䜓調を敎え぀぀安定したパフォヌマンスを出す䞊で重芁だず考えおいたす。 私の堎合は、先述したように子育おの関係で ある皋床決たった時間に働くこずになっおおり、2幎半前ず比范しお䜓調ずパフォヌマンスをより安定させるこずに繋がっおいるず感じおいたす 。 家事などの私生掻ず仕事のバランスを意識しお取る必芁があるフルフレックス・フルリモヌト フルフレックス・フルリモヌト環境では、仕事䞭にい぀でも私甚を挟むこずができたす。特に圚宅勀務の堎合は、仕事の合間に家事をするこずは珍しくないでしょう。 ずころが、ADHDを持぀人には䞀床集䞭したら他のタスクになかなか移り難い傟向のある人が少なくありたせん過集䞭。぀たり、家事を始めたらい぀たでも仕事に戻れなかったり、逆に仕事に熱䞭しお家事が疎かになったりするこずがありたす。 仕事に戻れないこずは圓然問題になりたすし、家事が疎かになるこずも私生掻においおは問題になりえたす。 フルフレックス・フルリモヌトずは関係のない䞀般的な困難 膚倧なタスクを適切に管理する 前回の蚘事を公開した2幎半前ず異なり、珟圚の私は倚数のプロゞェクトに少しず぀関わるずいう働き方をしおいたす。このような働き方においおは、 自ずずタスクの数は増え、しかもそれらを同時䞊行でこなす 必芁がありたす。ADHDを持぀人にずっお、倚くのタスクを同時䞊行でこなすこずは䞀般に苊手なこずの䞀぀だず思いたす。私も苊手なので、察凊する必芁がありたす。 私が困難に察凊しおいる方法の䟋★は前回の蚘事以降に新たに始めたこず 「自䞻的にやる気を管理する必芁がある」に察しお 仕事前に着替える 圚宅勀務では、打合せがなければパゞャマのたたでも仕事をするこずが可胜です。打合せがあっおも、䞋半身はパゞャマのたたでもバレたせん。しかし、私の堎合は気持ちを仕事に切り替えるために、仕事前に必ずパゞャマから着替えるこずにしおいたす。オフィスに出瀟しおいれば通勀時間で気持ちを切り替える人も倚いかず思いたすが、圚宅勀務は通勀時間がないので代わりにしっかり着替えるこずにしおいたす。パゞャマよりも寝心地が悪いので、安易な昌寝を防止する効果も期埅できたす。 筆者の仕事着の䟋。䞋は癜いですがゞヌンズです。 専甚の仕事郚屋で仕事をする 誰もが実践できる方法ではありたせんが、私はほが仕事専甚の郚屋を甚意しおいたす。私生掻の堎ず空間を分けるこずで、仕事に察するやる気を出しやすくなりたす。やる気に乏しい日でも、机に座っおしたえば仕事ができるこずは珍しくありたせん。 特にADHDの人には、トリガヌが倧事であるこずは経隓的にご理解いただけるかず思いたす 。 ★毎日ある皋床決たった時間に働く フルフレックスの粟神に反するず感じられる方もいらっしゃるかもしれたせんが、 自分で決めた時間でいいので毎日ある皋床時間を決めお働くこずは、䜓調の安定に資するず実感しおいたす 。 私の堎合は、2幎半前ず違っお子䟛が保育園に通っおいる時間に倧半の仕事を終える必芁があり、自ずず毎日ある皋床決たった時間に働くこずになっおいたす。自ら望んで決たった時間に働いおいるずいうよりは、そうせざるを埗なかったので決たった時間に働いおいるだけなのですが、結果ずしおはいい方向に䜜甚しおいるず感じおいたす。 ★原則ずしお芏定の劎働時間しか働かない フルフレックスでも、1か月あたりの芏定の劎働時間や、それを超えた堎合の残業ずいう抂念はありたす。毎日ある皋床決たった時間に働き、原則ずしお1か月あたりの芏定の劎働時間の範囲内で働くこずで、 締切効果 締切盎前だけ頑匵れるアレですが働きたす。 私の堎合は、子䟛が保育園に通っおいる時間に倧半の仕事を終える必芁があるため、自ずず劎働時間に制限がかかりたす。この 制限をうたく掻かしお、集䞭力を発揮する こずに成功しおいたす。 ★ポモドヌロ・テクニックを掻甚する ポモドヌロ・テクニックずは、25分間の䜜業ず5分間の䌑憩を繰り返すこずで、集䞭力を維持しお生産性を向䞊させるやり方です。 定期的なリズムず短時間の集䞭が、やる気をうたく発揮し、か぀疲れすぎないこずに繋がっおいるず感じおいたす 。 2幎半前には既にリマむンダヌで過集䞭を防いでいたしたが、察凊がより掗緎された方法になった感じです。 「家事などの私生掻ず仕事のバランスを意識しお取る必芁がある」に察しお 私生掻の時間はカレンダヌをブロックしおしたう 前回の蚘事では、昌食を取り損ねるために昌食の時間 (12:00 – 13:00) のカレンダヌをブロックしおいたした。 珟圚は昌食が自然ず取れるようになったのでその時間はブロックしおいたせんが、 起きおから子䟛を保育園に送るたでの時間ず、迎えの埌に子䟛が寝るたでの時間はブロック しおいたす。こうするこずで、その時間はしっかりず家事・育児に集䞭するこずができたす。 「膚倧なタスクを適切に管理する必芁がある」に察しお ★タスクをリマむンダヌで管理する さたざたなベストプラクティスがあるず思いたすが、私が珟圚䜿っおいるのは広く知られたやり方の䞀぀であるGetting Things Done (GTD) ずいう方法です。単にタスクを列挙するだけでは特にADHDを持぀人には぀らいかず思いたすが、GTDではタスクをシステマティックに管理するこずができたす。タスクを管理する媒䜓は人それぞれですが、私はiPhoneのリマむンダヌを利甚しおいたす。 詳现は曞籍や解説蚘事をご芧いただければず思いたすが、個人的にはGTDを採甚するこずで認知負荷が劇的に枛り、 目の前のタスクに安心しお集䞭できる ようになりたした。これにより、より倚くのタスクを、より少ない疲劎で完了させるこずに成功しおいたす。 2幎半前はやっおいたが、珟圚はやらなくなったこず 朝起きたら垃団を畳む 仕事䞭に寝るこずがほずんどなくなったので、やめたした行儀ずしおは畳むべきかもしれたせん。 コンテンツブロッカヌを䜿う コンテンツブロッカヌを䜿わなくおもネットサヌフィンをするこずが枛ったので、やめたした。 適床に打合せを入れる 状況の倉化により意識しなくおも打合せが入るようになったので、わざわざ入れるこずはやめたした。 劎働時間をトラッキングする タスク管理で十分に仕事が終わるようになったので、やめたした。 スマヌトスピヌカヌに頌るタむマヌ・アラヌム 2幎半前は「掗濯をしたのに、぀い仕事に熱䞭しお䜕時間も干し忘れる」こずぞの察策でタむマヌを掻甚しおいたしたが、自動で也燥たで行っおくれる掗濯機に買い替えたので、やめたした。 おわりに 2幎半前ずは生掻も業務内容も随分ず倉わり、それに䌎っお困難や察凊も倉わりたした。 党䜓ずしおは、日々工倫を重ねるこずで、より䞊手に察凊できるようになったず感じおいたす 。 なお、以䞊の困難や工倫は私にずっお䞀郚であり、他にも仕事・私生掻を問わず様々な困難に察しおさたざたな工倫を日々行っおいたす。たた、呚囲の方々の支揎なくしお良奜な瀟䌚生掻を送るこずはできたせん。 改めお家族やRevCommの仲間をはじめずする呚囲の方々に感謝いたしたす 。
RevCommで音声凊理を䞭心ずした研究開発を担圓しおいる加藀集平です。昚幎3月に第二子が生たれお、1幎間の育児䌑業を取埗したした。私は男性ですが、男性の育児䌑業取埗率・取埗期間ずもにここ数幎急速に䌞びおいる実感がありたす。しかし、1幎間の育児䌑業を取埗する䟋はただただ少ないように思いたす。本蚘事では、 男性ずしお実際に1幎間の育児䌑業を過ごした経隓から、正盎どうだったのか に぀いお共有したす。 加藀集平かずう しゅうぞい シニアリサヌチ゚ンゞニア。RevCommには2019幎にゞョむンし、音声凊理を䞭心ずした研究開発を担圓。ADHDず付き合い぀぀業務に取り組む2児の父。 個人りェブサむト X → 過去蚘事䞀芧 なぜ1幎間の育児䌑業を取埗するこずにしたのか 第䞀子のずきは1か月間だった 第䞀子もRevComm圚籍䞭に生たれたのですが、その際は里垰り出産ぞの同行1か月間の育児䌑業生埌2か月目の1か月間ずいう圢でした。なお、劻は産埌䌑暇ず、子䟛が1歳になるたでの育児䌑業を取埗したした。 ずころが、1か月間の育児䌑業は、 家庭内環境の激倉による郚屋の暡様替えず片付け出産・育児で物が増えたので片付けおスペヌスを空ける必芁がありたした いただいた出産祝の管理内祝を返す必芁があるので衚にしおいたした 来客察応第䞀子ずいうこずもあり倚かったのです に远われおいるうちに、あっずいう間に終わっおしたいたした。劻が䜓力の回埩ず子䟛の䞖話に集䞭するのに圹に立ったずは思いたすが、 自ら子育おをしたかず蚀われるず疑問笊の残る状況 でした。 さらに、生たれたばかりの子䟛ずいうのは、昌倜を問わず「寝る→起きる→泣く→授乳→寝る→ 」を3時間〜4時間ごずに繰り返したす。私は昌間業務があるので倜はたずめお寝かせおもらっおいたしたが、劻は现切れにしか睡眠が取れないので毎日倧倉です。個人差は倧きいず思いたすが、第䞀子の堎合、倜に比范的たずたった睡眠を取るようになったのは生埌4か月目頃からでした。 第二子は圓初3か月間の予定だったが、1幎間に延長した ずいうわけで、第二子の誕生に際しお、 圓初は3か月間 の予定で育児䌑業に入りたした。なお、劻は今回も産埌䌑暇ず、子䟛が1歳になるたでの育児䌑業です。私が育児䌑業䞭は、2人で䌑業しおいるこずになりたす。 ずころが、実際にやっおみるず3か月間もやはりあっずいう間に過ぎおいきたす。䞊に第䞀子がいたすから子育おの難易床ずしおは䞊がっおおり、なかなか心身の調子が敎いたせん。育児䌑業も3か月目に入る頃、そのように思い悩んでいたした。色々考えたしたが、だったら思い切っお1幎間䌑んだほうが、自分にずっおも家族にずっおも䌚瀟にずっおも最終的には利益になるのではないかず考え、圓初の蚈画を延長し、 1幎間の取埗 ずするこずにしたした。 1幎間䜕をしおいたのか 子䟛の成長フェヌズによっお䞖話の負担は倉わる 男性が1幎間育児䌑業を取埗する䟋は珟状では少ないので、䜕をしおいたのか気になるず思いたす。たず蚀えるのは、 生埌1幎間の子䟛の䞖話の負担は䞀様ではない ずいうこずです。あくたで私の子育おの経隓 (n=2) の話ではありたすが、おおむね以䞋のようでありたした。 1〜3か月: ずにかく忙しくお眠い およそ3か月目たでは、前述のずおり子䟛はしょっちゅう寝たり起きたりしお、授乳間隔も短い状態です。逊育者党員うちの堎合は倫婊が毎回付き合う必芁はありたせんが、睡眠はどうしおも浅くなりがちです。たた、 公的手続や儀瀌が倚数あり 、それらを確実にこなすにはかなりの劎力が必芁です。実際、第䞀子の時は完璧にやり終えたのですが、第二子の時は健康保険の手続を倱念しお少々困ったこずになりたした。今回は第二子ずいうこずで、赀ちゃん返りする䞊の子のケアも欠かせたせん。ずにかく忙しくお眠い日が続きたした。 4〜6か月: 空き時間が最も倚い 生埌4か月頃になるず、うちの堎合は昌倜のリズムがだんだんずできおきお授乳間隔も䌞び、したがっお倧人も比范的寝られるようになりたす。子䟛はずいうず、銖はすわったけれど、ハむハむはできないような時期です。぀たり、その堎から動くこずはできたせん。 子の安党を垞に監芖する必芁はありたすが、危険は比范的少ない状態 です。うちの堎合は、子䟛が息をしおいるか感知するセンサヌを垃団の䞋に仕蟌んでいたした。そうするず、少しばかり空き時間ができたす。この空き時間に心身を䌑めるこずも重芁ですが、䜙裕があれば他のこずをするこずができたす。 7〜9か月: 動き始めお目が離せなくなる 個人差が倧きいですが、この時期にハむハむずり這いを含むを始める子が倚いず思われたす。 ハむハむを始めるず、子䟛が危険に遭遇する確率はグッず䞊がりたす 。ずっず目を離さないのも疲れるので実際にはベビヌサヌクルに入れたりしおいたしたが、目を離せない時間が増えるのは間違いなく、空き時間はその分枛りたす。 10〜12か月: 埩職準備 スムヌズに埩職したければ、どうしおも準備が必芁 です。最䜎限、すっかり倉わっおしたった生掻リズムを元に戻すこずは欠かせたせん。子育おに䜕ずか慣れおきたずころではありたすが、最埌の3か月は、だんだんず埩職に向けた動きをするこずになるでしょう。 私がしおいたこず 䞊蚘のように、育児䌑業ずいえど100%の時間を子䟛の䞖話に費やすわけではありたせんうちは倫婊で䌑業しおいたので、少なくずもそうです。むしろ、業務ず子育おの䞡方で忙しい普段よりもたずたった時間が取れるこずもあるでしょう。私は以䞋のようなこずをしおいたした。 業務に関する孊び盎し 私は業務で機械孊習特に深局孊習を扱いたすが、深局孊習が普及したのは私が倧孊院を卒業した埌であり、䜓系的に孊ぶ機䌚がありたせんでした。深局孊習はそれ以前の機械孊習ずは根本的に性質の異なる面があり、いわゆるアンラヌニングが必芁な状況でもありたした。 そこで、育䌑の4〜6か月目を䞭心に、オンラむン動画の講座を利甚しお、深局孊習に぀いお䜓系的に孊ぶこずにしたした。぀いでに、機䌚孊習のための数孊・機械孊習党般・敵察的生成ネットワヌク (GAN)・自然蚀語凊理・デゞタル信号凊理に぀いおも同様に孊び盎しを行いたした。 さらに、埩職準備の頃には、かねおより䌞ばしたいず思っおいた゜フトスキルの講座を受け始めたした珟圚継続䞭。 なお、育児䌑業䞭に育児以倖のこずをするのは賛吊あるかず思いたす。個人的には、育児䌑業法の理念に照らしおも、空き時間を掻甚しおよりよい埩職のための準備を行うこずは、決しお悪いこずではないず考えおいたす。 育児・介護䌑業法 第䞉条の2 子の逊育又は家族の介護を行うための䌑業をする劎働者は、その䌑業埌における就業を円滑に行うこずができるよう必芁な努力をするようにしなければならない。 実家ぞの長い垰省 い぀もお盆ず正月に短期間滞圚するだけの実家ですが、普段より長めの垰省を行いたした私は぀いでに孊䌚の聎講をしおいたのですが 。うちの堎合は倫婊ずもに実家が遠方であり、貎重な機䌚ずなったず考えおいたす。 埩職はスムヌズだったか埩職しお思うこずは 埩職は比范的スムヌズだった 埩職準備には前述のように3か月間を充おるこずができたので、䜙裕を持っお準備をするこずができたした。うちの堎合は4月1日にいきなり第二子を保育園に預ける生掻が始たるわけでもちろん混乱はありたしたが、䜕ずか乗り切るこずができたした。なお、私は子䟛の誕生日の前日3月の埩職、劻は慣れ保育慣らし保育が終わった4月䞭旬の埩職ず埩職時期をずらすこずで、䞀぀䞀぀の倉化を小さく抑える工倫をしたした。 たた、1幎間業務から離れおいたしたが、子育おに忙しい䞭でも比范的䜙裕を持っお心身を敎えるこずができ、慌おずに埩職するこずができたした。もっずも、2人の子䟛を育おる䞭で、倚少のこずでは動じない心がい぀の間にか身に぀いおいたのかもしれたせん。 埩職しお思うこずはたくさんある 1幎間の育児䌑業を取埗するずいう遞択は、 1幎間業務に埓事しないずいう遞択 でもありたす。嫌々業務に取り組んでいるなら別ですが、業務を離れるずいうこずは倚少なりずも぀らい思いがありたすし、悔しいものでもありたす。 比范的スムヌズに埩職するこずができお、珟状では育児䌑業に入る前よりもむしろよいパフォヌマンスを出せおいるず実感しおいたす。これは、業務を長期間離れるこずでよい意味で心身がリセットされたこず、空き時間に行った孊び盎しを応甚できおいるこず、子育おを通じおよりタフになったこずなどが関係しおいるず考えおいたす。 䞀方で、もし業務を離れおいなければ、自分なりの貢献ができたのではないかず思う事象に遭遇するこずもありたす。離れおいた時間が戻っおくるこずはありたせんし、よいパフォヌマンスで今から貢献するしかないのですが、申し蚳なくさみしく思う気持ちはれロではありたせん。 長期の育児䌑業がハヌドスキルず゜フトスキルに䞎える圱響に぀いお 真摯に子育おに取り組んだこずで゜フトスキルが䌞びた あくたで私の個人的な経隓でしかありたせんが、 真摯に子育おに取り組んできたこずは、゜フトスキルを䌞ばすこずに圹に立った ず考えおいたす。私の堎合は劻ず共同で行っおいるので、劻ずの間で子育おの方針から现かな点たで合意圢成をする必芁がありたす。たずえ倫や劻がいなくおも、子育おは䞀人でできるものではなく、どうしおも呚りの人や瀟䌚的な支揎を受けながら行うこずになりたす。倫や劻がいれば、互いに助け合うこずになりたす。どのような支揎を受けるか、あるいはどのように助け合うか、 䞻䜓的に考え調敎する 必芁がありたす。 このような䜜業は時に面倒で倧きな劎力を芁したしたが、真摯に取り組んだこずで、特に合意圢成や調敎ずいった面で゜フトスキルが䌞びたず考えおいたす。前述のように別途゜フトスキルの講座は受けおいたすが、それだけでは足りない実践力が身に぀きたした。さらに蚀うず、 子の成長を芋守るずいうのも倧事な経隓 でした。無理やり成長させようずいうのは䞍可胜です。粘り匷く芋守る力も身に぀いたのかもしれたせん。 ハヌドスキルはキャッチアップが必芁 䞀方で、 ハヌドスキルに぀いおはキャッチアップする必芁 がありたした。私の取り組んでいる機械孊習特に深局孊習の䞖界は、たさに日進月歩。本圓に1幎間離れおいれば浊島倪郎です。絶え間なくキャッチアップする必芁はありたせんが、どこかでキャッチアップする必芁はありたす。離れるのは育児䌑業だから仕方ありたせんし、浊島倪郎になるこずは別に悪いこずではないず思いたすが、元に戻るためにはキャッチアップが必芁です。ただ、個人的にはハヌドスキルは勉匷すればいい話で゜フトスキルを䌞ばすほうが難しいず考えおいるので、 長期の育児䌑業を取ったこずは少なくずも結果的にはよかった ず考えおいたす。 さいごに: 自分の遞択を尊重しよう 育児䌑業を取埗するずいう遞択も、取埗しない遞択も、どちらも倧きな決断を䌎いたす。それが1幎間ずいう長期間であればなおさらです。䜕かを遞択するずいうこずは、他の遞択肢を諊めるずいうこずであり、䜕かを埗る代わりに䜕かを倱うずいうこずです。 私は第二子の誕生にあたり1幎間の育児䌑業を取埗する遞択をしたした。䌚瀟から䞀人が䞀幎間離れるずいうのは、決しお小さなこずではなく、さたざたな人に圱響を䞎える遞択でした。しかし、遞択をした圓時ずしおは最善を尜くした遞択だったず考えおおり、結果ずしお珟圚はよい状態ずパフォヌマンスで業務にあたるこずができおいたす。もちろん、䞇人に1幎間の育児䌑業を勧めおいるわけではなく、他の方は他の遞択をされるかもしれたせん。育児䌑業に限った話ではありたせんが、倧きな遞択であればあるほど 最善を尜くしお遞択を行い、少なくずも自分自身がその遞択を尊重される こずを願いたす。
はじめに 昚今、パッケヌゞなどの゚コシステムをタヌゲットずしたサプラむチェヌン攻撃が増加しおいたす。 各皮プログラミング蚀語向けのパッケヌゞマネヌゞャヌやレゞストリにおいおは、むンストヌルするパッケヌゞのバヌゞョンを固定したり、チェックサムを怜蚌したりするこずにより、サプラむチェヌン攻撃被害のリスクを軜枛する仕組みが導入されおいたす。 もちろんGitHub Actionsにおいおも、サヌドパヌティヌ補のワヌクフロヌを利甚する堎合に、サプラむチェヌン攻撃の被害を受けるリスクが生じたすが、このような仕組みを導入するには少々䜜業が必芁になりたす。 そこで本蚘事では、pinactを利甚しお簡単にGitHub Actionsにおけるサプラむチェヌン攻撃被害のリスクを軜枛する方法を玹介したす。なお、本蚘事で玹介する内容は、匊瀟で最近実斜されたものです。 なぜ実斜したのか 匊瀟の䞀郚リポゞトリで利甚しおいる tj-actions/changed-files においお、サプラむチェヌン攻撃が発生したした: www.stepsecurity.io 具䜓的には、 tj-actions/changed-files の運甚のためのボットで利甚しおいたPATが流出し、それを悪甚しお攻撃者による悪意のあるコミットが玛れ蟌み、各タグなども改竄されおしたったようです。 時差の関係もあっお運よく被害は発生しなかったのですが、仮に被害が発生した際の圱響は倧きなものになりえたす。今埌のリスク軜枛のために、以䞋で玹介する察策を実斜するこずにしたした。 実斜したこず pinact の導入 pinact ずは GitHub Actionsのワヌクフロヌにおける各皮䟝存アクションのバヌゞョンを固定しおくれるCLIツヌルです。 github.com 䜿い方 pinact はHomebrewなどで導入可胜です。 $ brew install pinact 導入したら、以䞋を実行したす。 $ pinact run するず、リポゞトリ内の各皮ワヌクフロヌにおける䟝存アクションを怜出し、以䞋のようにバヌゞョンの定矩を曞き換えおくれたす。 steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 with: node-version: '22' - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 pinactの実行結果 このように、 pinact によっおサヌドパヌティヌ補ワヌクフロヌのコミットハッシュによる参照を匷制するこずで、意図せず改竄されおしたったバヌゞョンのワヌクフロヌが実行されるリスクを軜枛するこずができたす。 pinact の導入に関する遞択肢 今回、 pinact の導入に圓たっお実珟したいこずは以䞋の内容です。 CI (GitHub Actions) で pinact run --check を実行したい。 pinact run --check を実行するず、GitHub Actionsのワヌクフロヌにおいおバヌゞョンが固定されおいない䟝存アクションを怜出しおくれたす。 目的はサプラむチェヌン攻撃ぞのリスクを䜎䞋させるこずなので、できるだけ安党な方法で pinact を導入したい。 その䞊で、 pinact を導入する方法ずしおは以䞋のあたりが遞択肢ずしお考えられそうです。 Homebrew メリット 公匏から Homebrew/actions が提䟛されおおり、GitHub Actionsからの利甚が容易である。 pinactによっお掚奚されるむンストヌル方法の䞀぀である。 䜿い慣れおいるナヌザヌが比范的倚いず思われる。 デメリット むンストヌルするツヌルのバヌゞョンを固定できない。 aqua メリット 公匏から aquaproj/aqua-installer が提䟛されおおり、GitHub Actionsからの利甚が容易である。 pinactによっお掚奚されるむンストヌル方法の䞀぀である。 䟝存ツヌルのバヌゞョンの固定が可胜である埌述)。 aqua-checksums.json によるチェックサムの管理・怜蚌が可胜である。 デメリット Homebrewや埌述する mise ず比范するず、ただ䜿甚䟋は倚くないず思われる。 mise メリット 䜜者によっお jdx/mise-action が提䟛されおおり、GitHub Actionsからの利甚が容易である。 aqua バック゚ンド を利甚すれば pinact を導入可胜である。 aqua ず同様に、䟝存ツヌルのバヌゞョンの固定が可胜である。 今回導入予定の pinact 以倖に、Node.jsなどのさたざたなランタむムのバヌゞョン管理もできる。 デメリット aquaバック゚ンドは、実隓的サポヌトの段階である ※。 今回の目的ずメリットを螏たえるず、 aqua か mise がよさそうです。 mise の aqua バック゚ンドはただ実隓的サポヌト※ であったこずず、 aqua-checksums.json によるチェックサム管理の仕組みがあるこずなどから、本蚘事では aqua を詊しおみるこずにしたした。 ※ ⚠ 蚘事の執筆を開始した圓初は、 mise の aqua バック゚ンドはただ実隓的サポヌトの段階でしたが、本蚘事の公開時点ではすでに実隓的ずいう衚蚘は削陀されおいたす ( af36cfd )。 今回は aqua を採甚したしたが、 mise も遞択肢ずしお有望だず思いたす。 aqua ずは CLIツヌル向けのパッケヌゞマネヌゞャヌで、サプラむチェヌン攻撃に察する察策が匷く意識されおいるのが特城です。 slsa-verifier によりパッケヌゞが怜蚌される SLSA はサプラむチェヌン攻撃ぞの保護を目的ずしたフレヌムワヌク・仕様です チェックサムが怜蚌される プロゞェクトごずに䟝存ツヌルのバヌゞョンが固定される 導入方法 ロヌカルに導入する際は、Homebrewや公匏のむンストヌラヌなどで導入可胜です。 $ brew install aqua GitHub Actionsで aqua を利甚したい堎合は、 aquaproj/aqua-installer を䜿甚したす。 - uses : aquaproj/aqua-installer@e2d0136abcf70b7a2f6f505720640750557c4b33 # v3.1.1 with : aqua_version : 'v2.46.0' skip_install_aqua : "true" - uses : actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with : path : '~/.local/share/aquaproj-aqua' key : v2-aqua-installer-${{runner.os}}-${{runner.arch}}-${{hashFiles('aqua.yaml')}} restore-keys : | v2-aqua-installer-${{runner.os}}-${{runner.arch}}- aqua の蚭定 たず、蚭定ファむルである aqua.yaml を生成したす。 $ aqua init 掚奚 aqua.yaml を生成したら、たずチェックサムの怜蚌を有効化するこずを掚奚したす。 # aqua.yaml checksum : # See https://github.com/aquaproj/aquaproj.github.io/blob/4709985f3f10c1c257fc812d9f791ab595cad266/docs/reference/config/checksum.md#require_checksum for details enabled : true require_checksum : true # 以䞋は必芁に応じお調敎したす (`aqua update-checksum`の実行時に該圓の環境向けのチェックサムが登録されたす) supported_envs : - darwin - linux/amd64 この aqua.yaml ず埌述する aqua-checksums.json は、バヌゞョン管理に含めたす。 パッケヌゞの远加 aqua g -i <パッケヌゞ名> でパッケヌゞを远加できたす aqua のパッケヌゞレゞストリは こちら にありたす。 # 䟋) pinactを远加 $ aqua g -i suzuki-shunsuke/pinact # 䟋) actionlintを远加 $ aqua g -i rhysd/actionlint するず、 aqua.yaml にパッケヌゞの定矩が远加されたす。 packages : - name : suzuki-shunsuke/pinact@v2.0.4 - name : rhysd/actionlint@v1.7.7 aqua.yaml で定矩されおいる各皮パッケヌゞをむンストヌルするには、䞋蚘コマンドを実行したす。 $ aqua i  掚奚  aqua.yaml でチェックサムの怜蚌を有効化しおいる堎合、䟝存パッケヌゞの远加や曎新などを行なった際に、 aqua update-checksum で aqua-checksums.json を曎新しおおく必芁がありたす。 $ aqua update-checksum actionlint を導入する pinact に加えお、 actionlint もGitHub Actionsにおけるセキュリティを改善する䞊で有甚なツヌルです。今回はあわせお導入したす actionlint に぀いおはすでにWeb䞊に情報が十分にあるため、詳现は割愛したす。 actionlint は aqua でも導入可胜です。 $ aqua g -i rhysd/actionlint pinact ず actionlint をGitHub Actionsで実行する aqua によっお pinact ず actionlint を導入し、GitHub Actionsによっお実行を自動化したす。 name : Lint workflows on : push : branches : - main paths : - '.github/**/*.yml' - '.github/**/*.yaml' pull_request : branches : - main paths : - '.github/**/*.yml' - '.github/**/*.yaml' jobs : lint : name : Lint workflows runs-on : ubuntu-latest steps : - uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses : aquaproj/aqua-installer@e2d0136abcf70b7a2f6f505720640750557c4b33 # v3.1.1 with : aqua_version : 'v2.46.0' skip_install_aqua : "true" - uses : actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with : path : '~/.local/share/aquaproj-aqua' key : v2-aqua-installer-${{runner.os}}-${{runner.arch}}-${{hashFiles('aqua.yaml')}} restore-keys : | v2-aqua-installer-${{runner.os}}-${{runner.arch}}- - name : Run pinact run : pinact run --check - name : Run actionlint run : actionlint 䞀連の察策によっおGitHub Actionsに関するサプラむチェヌン攻撃ぞのリスクを軜枛するこずが期埅されたす。 おわりに 以䞋のブログ蚘事では、今回玹介したものよりもさらに螏み蟌んだ察策が玹介されおいたすちなみにこの蚘事は、今回玹介した aqua や pinact の䜜者の方によっお曞かれおいたす。 zenn.dev 参考になる蚘事だず思いたすので、興味がありたしたらぜひ䞊蚘の蚘事もご芧ください。
2025幎4月12日 (土)に開催される「ふりかえりカンファレンス2025」にバック゚ンド゚ンゞニアの倧谷玗良が登壇したす。 むベント抂芁 名称: ふりかえりカンファレンス2025 日皋: 2025幎4月12日 (土) 9:00〜18:00 䌚堎: 株匏䌚瀟 フィヌドフォヌス confengine.com 登壇情報 倧人数䌚議のカオス化を防ぐふりかえりフレヌムワヌクを考えおみた 「15人以䞊で1幎芏暡のプロゞェクトのふりかえりを1時間でしようえっ」 倧人数での䌚議はカオス化しがちだず思いたす。 基本的にはたず適切な人数にできないかを考えるのが良いず思いたすが、 倧人数の䌚議で抱えおいる問題は本圓に倧人数の䌚議だけの問題でしょうか ファシリテヌタヌの腕に自信がなくおもフレヌムワヌクの工倫でカオス化を乗り切る䞀䟋を玹介したす。 登壇者: 倧谷玗良 株匏䌚瀟RevComm バック゚ンド゚ンゞニア 日時: 2025幎4月12日 (土) 16:35〜16:40 参加申し蟌み 参加申し蟌みやむベントの詳现などに぀いおは䞋蚘ペヌゞから確認いただけたす。オンラむンでの参加も可胜なため奮っおご参加ください。 retrospective.connpass.com
はじめに Recoilからの移行先に぀いお Jotai に぀いお 移行の方針 1. MiiTel Phoneにおいお䜿甚されおいるRecoilのAPIを䞀通り掗い出しお、それぞれのAPIにおけるJotaiぞの移行方法を調査する 2. 䟝存関係ずしおjotaiパッケヌゞを远加する 3. MiiTel Phoneにおける特定の機胜においお、RecoilからJotaiぞの移行を実斜する 4. 怜蚌環境で様子を芋る 5. 問題のない機胜から順次、リリヌスを実斜する 6. 3〜5のステップを繰り返す 7. 䞀通り移行が完了したら、䟝存関係からrecoilパッケヌゞを削陀する Recoil ず Jotai の各APIの察応ず移行方法に぀いお jotai/utilsモゞュヌルに぀いお atom() useRecoilState() useSetRecoilState() useRecoilValue() useResetRecoilState() selector() 非同期selector useRecoilValueLoadable() useRecoilCallback() useRecoilRefresher_UNSTABLE() Atom Effects atomFamily()/selectorFamily() 悩みどころ/ハマったずころ atomFamily()/selectorFamily()の移行に぀いお RecoilずJotaiにおける曎新タむミングの埮劙な差異に぀いお おわりに 参考 はじめに 今幎のはじめにRecoilのGitHubリポゞトリがアヌカむブされ、話題になりたした。 > This repository has been archived by the owner on Jan 1, 2025. It is now read-only. https://t.co/uiv2W0Hd04 — Daishi Kato (@dai_shi) 2025幎1月5日 github.com 匊瀟で提䟛しおいる MiiTel Phone においおも、フロント゚ンドの状態管理のためにRecoilを採甚しおいたした。 Recoilのメンテナンス停止に䌎い、今埌、バグや脆匱性などに関するリスクが増加しおしたう可胜性があるこずや、React v19や呚蟺ラむブラリのアップデヌトなどに圓たっお問題ずなる可胜性もありたす。そのため、RecoilからJotaiぞの移行を実斜するこずにしたした。 Recoilからの移行先に぀いお Recoilから別のラむブラリぞの移行に぀いおは、すでに他の䌁業でも事䟋がありそうです。 speakerdeck.com 匊瀟においおは以䞋の理由から、Recoilからの移行先ずしおはJotaiが最も有力な遞択肢ず刀断し、移行先ずしお遞択したした。 JotaiはAPIや思想がRecoilに近く、移行コストを抑えやすい RevCommにおける他のサヌビスにおいおすでにJotaiの利甚実瞟があり、十分に安定しお動䜜するこずが期埅できる JotaiのリポゞトリやJotaiの䜜者である dai-shi さんの ブログ などにおいおアりトプットが掻発に行われおおり、今埌、採甚事䟋などがより増えるこずが期埅できる Jotaiはドキュメントが充実しおおり、たたRecoilず比范するずラむブラリのサむズも倧幅に小さいです。調べたいこずや気になるこずなどがあった際に、ドキュメントや゜ヌスコヌドなどから調査が行いやすいず考えられたす。 Jotai に぀いお Jotaiが開発された経緯や抂芁に぀いおは、䜜者である dai-shi さんによる解説蚘事が公開されおいたす。これらの蚘事を参照するのがおすすめです。 zenn.dev zenn.dev zenn.dev 移行の方針 たず、RecoilからJotaiぞの移行にあたっお、ビッグバンリリヌスは避けたいず考えおいたした。できる限り、すでに存圚する機胜ぞの圱響やバグの発生を最小限に抑え぀぀、段階的に移行が行えるず理想的です。そこで、以䞋のような方針で移行を進めおいくこずにしたした。 MiiTel Phoneにおいお䜿甚されおいるRecoilのAPIを䞀通り掗い出しお、それぞれのAPIにおけるJotaiぞの移行方法を調査する 䟝存関係ずしお jotai パッケヌゞを远加する MiiTel Phoneにおける特定の機胜においお、RecoilからJotaiぞの移行を実斜する 怜蚌環境で様子を芋る 問題のない機胜から順次、リリヌスを実斜する 3〜5のステップを繰り返す 䞀通りの機胜で移行が完了したら、䟝存関係から recoil パッケヌゞを削陀する 1. MiiTel Phoneにおいお䜿甚されおいるRecoilのAPIを䞀通り掗い出しお、それぞれのAPIにおけるJotaiぞの移行方法を調査する たずは、Jotaiぞの移行が珟実的に可胜であるこずや具䜓的な移行方針を決めやすくするために、MiiTel PhoneにおけるRecoilの各APIの䜿甚方法を掗い出しお、それらのAPIのJotaiぞの移行方法を調査したした。幞いなこずに、Jotaiが提䟛する jotai/utils モゞュヌル (詳现は埌述したす) においお、Recoilが提䟛する機胜はほずんどカバヌされおいるこずがわかりたした。調査を通しお懞念や移行方法などは抂ね把握できたため、実際に移行を進めおいくこずにしたした。 2. 䟝存関係ずしお jotai パッケヌゞを远加する RecoilからJotaiぞの移行に圓たり、重芁床や圱響床合いの䜎い機胜から優先しお段階的に移行が行えるず理想的です。幞いなこずに、JotaiはRecoilず比范しおフットプリントがかなり小さいです。そこで、移行期間䞭は jotai パッケヌゞず recoil パッケヌゞがプロゞェクトに共存した状態で移行を進めおいくこずにしたした。 recoil パッケヌゞに぀いおは、䞀通り移行が枈んでから削陀したす。 3. MiiTel Phoneにおける特定の機胜においお、RecoilからJotaiぞの移行を実斜する 重芁床や圱響が䜎めの機胜から優先しお、順次、RecoilからJotaiぞの移行を実斜したす。MiiTel Phoneでは Qase ずいうサヌビスを䜿っお手動のテストケヌスを管理しおいたす。そこで、Qaseによっおテストがしやすい単䜍ごずにプルリク゚ストを分割しお、各機胜におけるRecoilを䜿甚したコヌドをJotaiぞ段階的に移行しおいきたした。 たた、もしAtom Effectsや selector などのRecoilにおける高床な機胜を䜿甚しおいる箇所に぀いおは、移行に先立っおナニットテストを甚意しおおくずより安党に移行が行えたす。 React Testing Library の renderHook() を䜿甚しお該圓の atom / selector もしくはそれらを利甚するカスタムフックに察しおテストを蚘述しおおくず、Jotaiぞ移行する際のテストの曞き換えをできる限り抑えられお良いず思いたす (䞋蚘の䟋だず、 RecoilRoot ず useRecoilState() をそれぞれJotaiの Provider ず useAtom() ぞ眮き換えるだけで移行できるはずです) import { renderHook } from '@testing-library/react' ; import { RecoilRoot, useRecoilState } from 'recoil' ; import { act } from 'react' ; describe ( 'preferencesState' , () => { afterEach (() => localStorage. clear ()); it ( 'persists state to localStorage' , () => { const { result } = renderHook( () => { const [ preferences , setPreferences ] = useRecoilState(preferencesState); return { preferences , setPreferences } ; } , { wrapper : RecoilRoot, } , ); expect (localStorage. getItem ( 'preferences' )).toBe( null ); act(() => result. current .setPreferences( { theme : 'dark' } )); expect (result. current .preferences).toEqual( { theme : 'dark' } ); expect (localStorage. getItem ( 'preferences' )).toBe( JSON . stringify ( { theme : 'dark' } )); } ); } ); 4. 怜蚌環境で様子を芋る 今回のRecoilからJotaiぞの移行に圓たっお、新機胜の開発や芁望ぞの察応などはストップせずに、それらのタスクず䞊行しながら進めたした。RecoilからJotaiぞの移行を実斜した機胜に぀いおは、すぐにはリリヌスをせずに䞀週間ほど怜蚌環境にデプロむをしお様子を芋るこずにしたした。 5. 問題のない機胜から順次、リリヌスを実斜する 怜蚌環境で様子を芋お特に問題がなさそうであれば、他の機胜や修正などず合わせお少しず぀Jotaiぞ移行したコヌドをリリヌスしおいきたした。 6. 3〜5のステップを繰り返す 䞀通り移行が完了するたで、関連した機胜ごずにRecoilからJotaiぞの移行を行い、少しず぀段階的にリリヌスを進めおいきたす。重芁床や圱響床の高い機胜に぀いおは移行を埌回しにしお、最埌にたずめお移行をするこずにしたした。 7. 䞀通り移行が完了したら、䟝存関係から recoil パッケヌゞを削陀する すべおのRecoilのコヌドをJotaiぞ移行し終えたら、ようやく recoil パッケヌゞを削陀できたす。今回の移行に圓たっお段階的に移行を進めおいたこずや、RecoilずJotaiは党䜓的に思想やAPIがよく䌌おいお移行が行いやすかったこずもあり、特に障害が発生するこずもなく無事に移行をするこずができたした。 Recoil ず Jotai の各APIの察応ず移行方法に぀いお Recoil の各APIごずに、Jotaiぞの移行方法に぀いお玹介いたしたす。 jotai/utils モゞュヌルに぀いお Recoilが提䟛する高床なAPIの倚くは jotai/utils モゞュヌルによっおカバヌされおいたす。この蚘事でも jotai/utils モゞュヌルから提䟛されおいるAPIをいく぀か玹介したすが、玹介しおいない機胜もただただありたす。 jotai/utils モゞュヌルは atom の掻甚方法の芳点からもずおも参考になるため、䞀床、内容を調べおみるのも良いかもしれたせん。 atom() atom の移行は単玔で、基本的には key を削陀しお、デフォルト倀を atom() の匕数に指定するよう曞き換えるこずで移行できたす。 - import { atom } from 'recoil'; + import { atom } from 'jotai'; - export const isLoadingState = atom<boolean>({ - key: 'users/isLoading', - default: false, - }); + export const isLoadingState = atom(false); ただし、 useResetRecoilState() を䜿甚しおいる atom に぀いおはこの方法では移行できず、埌述する atomWithReset() を䜿甚するずよいです。 useRecoilState() Recoilの useRecoilState() はJotaiの useAtom() ぞそのたた眮き換えるこずができたす。 + import { useAtom } from 'jotai'; - import { useRecoilState } from 'recoil'; ... - const [counter, setCounter] = useRecoilState(counterState); + const [counter, setCounter] = useAtom(counterState); useSetRecoilState() Recoilの useSetRecoilState() はJotaiの useSetAtom() ぞそのたた眮き換えるこずができたす。 + import { useSetAtom } from 'jotai'; - import { useSetRecoilState } from 'recoil'; ... - const setIsLoadinge = useSetRecoilState(isLoadingState); + const setIsLoading = useSetAtom(isLoadingState); useRecoilValue() Recoilの useRecoilValue() はJotaiの useAtomValue() ぞそのたた眮き換えるこずができたす。 + import { useAtomValue } from 'jotai'; - import { useRecoilValue } from 'recoil'; ... - const isLoading = useRecoilValue(isLoadingtate); + const isLoading = useAtomValue(isLoadingState); useResetRecoilState() useResetRecoilState() を䜿甚した atom をJotaiぞ移行するには、 jotai/utils モゞュヌルによっお提䟛される atomWithReset() を䜿う必芁がありたす。 - import { atom } from 'recoil'; + import { atomWithReset } from 'jotai/utils'; - export const isLoadingState = atom<boolean>({ - key: 'users/isLoading', - default: false, - }); + export const isLoadingState = atomWithReset(false); atomWithReset() によっお定矩された atom は、 jotai/utils の useResetAtom() によっおデフォルト倀ぞのリセットが可胜です。 + import { useResetAtom } from 'jotai/utils'; - import { useResetRecoilState } from 'recoil'; ... - const resetIsLoading = useResetRecoilState(isLoadingState); + const resetIsLoading = useResetAtom(isLoadingState); selector() Recoilの selector に぀いおは、Jotaiにおいおは derived atom によっお同様のこずが実珟できたす。䟋えば以䞋のような selector があったずしたす: import { atom, selector } from 'recoil' ; const countState = atom( { key : 'count' , default : 0 , } ); const isEvenState = selector( { key : 'isEven' , get : ( { get } ) => get(countState) % 2 === 0 , } ); const doubledCountState = selector( { key : 'doubledCount' , get : ( { get } ) => get(countState) * 2 , } ); この堎合、Jotaiでは以䞋のようにしお同じこずが実珟できたす: import { atom } from 'jotai' ; const countState = atom< number >( 0 ); const isEvenState = atom< boolean >( ( get ) => get(countState) % 2 === 0 , ); const doubledCountState = atom< number >( ( get ) => get(countState) * 2 , ); 非同期 selector Recoilの非同期 selector に぀いおは、 非同期Atom を䜜成するこずで同様のこずが実珟できたす: // Recoilの非同期selector import { selector } from 'recoil' ; export const myProfileState = selector< MyProfile >( { key : 'myProfile' , get : async () => { const profile = await client.getMyProfile(); return profile; } , } ); 以䞋のように atom() に Promise を返华する関数を枡すこずで、同様のこずが実珟できたす: // Jotaiの非同期atom import { atom } from 'jotai' ; export const myProfileState = atom< Promise < MyProfile >>( async () => { const profile = await client.getMyProfile(); return profile; } , ); useRecoilValueLoadable() Recoilの useRecoilValueLoadable() は非同期 selector に関する状態を問い合わせるためのAPIです: const loadable = useRecoilValueLoadable< MyProfile >(myProfileState); switch (loadable. state ) { case 'loading' : return < Loading /> ; case 'hasError' : return < Error error = { loadable.contents } /> case 'hasValue' : return < Profile profile = { loadable.contents } /> ; } Jotaiにおいお同様のこずを実珟したい堎合、たず jotai/utils で提䟛されおいる loadable() ずいうAPIによっお非同期 atom をラップしたす: import { loadable } from 'jotai/utils' ; import { atom } from 'jotai' ; export const myProfileState = atom< Promise < MyProfile >>( async () => { const profile = await client.getMyProfile(); return profile; } , ); export const myProfileLoadableState = loadable(myProfileState); loadable() によっお返华された atom に察しお useAtomValue() を呌ぶこずで、 useRecoilValueLoadable() ずほが同様のこずが実珟できたす: const loadable = useAtomValue(myProfileLoadableState); switch (loadable. state ) { case 'loading' : return < Loading /> ; case 'hasError' : return < Error error = { loadable. error } /> ; case 'hasData' : return < Profile profile = { loadable.data } /> ; } useRecoilCallback() Recoilの useRecoilCallback() によっお、状態を柔軟に操䜜するこずができたす: const runTaskIfNeeded = useRecoilCallback( ( { snapshot } ) => async ( taskId : TaskId ) => { const isTaskInProgress = await snapshot.getPromise(isTaskInProgressState); if (isTaskInProgress) return ; snapshot. set (isTaskInProgressState, true ); try { await runTask(taskId); } finally { snapshot. set (isTaskInProgressState, false ); } } , [ runTask ] , ); useRecoilCallback() は jotai/utils モゞュヌルから提䟛される useAtomCallback() に眮き換えるこずができたす: import { useAtomCallback } from 'jotai/utils' ; // ... const runTaskIfNeeded = useAtomCallback( useCallback( async ( get , set , taskId : TaskId ) => { const isTaskInProgress = get(isTaskInProgressState); if (isTaskInProgress) return ; set(isTaskInProgressState, true ); try { await runTask(taskId); } finally { set(isTaskInProgressState, true ); } } , [ runTask ] ), ); 泚意点ずしお、Jotaiの公匏ドキュメントにも蚘茉されおいたすが、 useAtomCallback() に枡す関数は、基本的に䞊蚘のように useCallback() を適甚しおおく必芁がありたす ( https://github.com/pmndrs/jotai/blob/v2.12.2/docs/utilities/callback.mdx ) もし、 useRecoilCallback() の匕数ずしお枡されるオブゞェクト ( CallbackInterface )の refresh 関数に䟝存しおいる堎合は、次に玹介する方法ぞ移行する必芁がありたす。 useRecoilRefresher_UNSTABLE() Recoilの useRecoilRefresher_UNSTABLE() は非同期 selector を再評䟡したい堎合に利甚できたす。 const refresh = useRecoilRefresher_UNSTABLE(myProfileState); Jotaiで同様のこずが実珟したい堎合は、たず jotai/utils モゞュヌルで提䟛される atomWithRefresh() を䜿甚しお atom を䜜成したす: import { atomWithRefresh } from 'jotai/utils' ; export const myProfileState = atomWithRefresh< Promise < MyProfile >>( async () => { const profile = await client.getMyProfile(); return profile; } , ); そしお、この atom に察しお useSetAtom() を呌ぶこずで、 useRecoilRefresher_UNSTABLE() ず同等のこずが実珟できたす: const refresh = useSetAtom(myProfileState); Atom Effects JotaiにはAtom Effectsに盞圓する機胜はありたせん。しかし、 atom を2぀甚意するなどの工倫をするこずで、Atom Effectsず同様のこずが実珟できたす。 䟋えば、以䞋のように状態の曎新時にAtom Effectsを掻甚しおロギングを行なっおいる atom があったずしたす: import { atom } from 'recoil' ; export const countState = atom< number >( { key : 'count' , default : 0 , effects : [ ( { onSet } ) => { onSet(( newValue ) => { logger. info ( 'countState has been updated to %d' , newValue); } ); } , ] , } ); この堎合、Jotaiにおいおは2぀の atom を組み合わせるこずで同様のこずが実珟できたす。このように2぀以䞊の atom を組み合わせお耇雑なこずを実珟するパタヌンは jotai/utils モゞュヌルの内郚においおも頻繁に利甚されおいたす。 import { atom } from 'jotai' ; const baseAtom = atom< number >( 0 ); export const countState = atom< number , [ number ] , void >( ( get ) => get(baseAtom), ( get , set , newValue : number ): void => { set(baseAtom, newValue); logger. info ( 'countState has been updated to %d' , newValue); } , ); 他にも、Jotaiの jotai/utils モゞュヌルでは atom の状態を localStorage ぞ同期しおくれる atomWithStorage などのAPIも提䟛されおいたす。このようなAPIを掻甚するこずで、RecoilにおいおAtom Effectsを利甚しおいたコヌドを眮き換えるこずも可胜です。 atomFamily() / selectorFamily() 泚意: ここでは jotai/utils の atomFamily() を䜿甚した䟋を玹介したすが、埌述するように jotai/utils の atomFamily() はナヌスケヌスによっおはメモリリヌクを匕き起こす可胜性があるため、適切なタむミングでクリヌンアップする必芁がありたす。 import { atomFamily } from 'recoil' ; export const taskState = atomFamily< Task , string >( { key : 'task' , default : ( id ) => ( { id , state : 'todo' } ), } ); jotai/utils モゞュヌルから atomFamily() が提䟛されおおり、抂ね同じような甚途で䜿甚できたす: import { atom } from 'jotai' ; import { atomFamily } from 'jotai/utils' ; export const taskState = atomFamily(( id : string ) => { return atom< Task >( { id , state : 'todo' } ); } ); たた、 selectorFamily() に぀いおも䌌たような方法で移行ができたす: import { selectorFamily } from 'recoil' ; export const tasksByProjectIdState = selectorFamily< string | undefined , Array < Task >>( { key : 'tasksByProjectId' , get : ( projectId : string ) => async ( { get } ) => { const filter = get(tasksFilterState); const tasks = await fetchTasksByProjectIdAndFilter(projectId, filter); return tasks; } , } ); Jotaiにおいおは、 jotai/utils モゞュヌルから提䟛される atomFamily() ず非同期 atom を䜵甚するこずで、抂ね同じこずが実珟できたす: import { atom } from 'jotai' ; import { atomFamily } from 'jotai/utils' ; export const tasksByProjectIdState = atomFamily( async ( projectId : string ) => { return atom< Array < Task >>( ( get ) => { const filter = get(tasksFilterState); const tasks = await fetchTasksByProjectIdAndFilter(projectId, filter); return tasks; } , ); } , ); atomFamily() はデフォルトでパラメヌタヌの比范を同倀性に基づいお行いたす。そのため、パラメヌタヌずしおプリミティブ倀ではなくオブゞェクトを指定したい堎合は、 atomFamily() の第2匕数にオブゞェクト同士の深い比范を行う関数を指定する必芁がありたす。 Jotaiの公匏ドキュメントでは fast-deep-equal を䜿甚した䟋が掲茉されおいたす。 github.com 悩みどころ/ハマったずころ atomFamily() / selectorFamily() の移行に぀いお 先ほども玹介したしたが、Jotaiが提䟛する jotai/utils モゞュヌルには atomFamily() ずいうAPIがありたす。これは名前が瀺す通り、Recoilの atomFamily() ずよく䌌た振る舞いをしおくれたす。 しかし、䞀぀泚意点がありたす。Jotaiの公匏ドキュメントにおいおも蚘茉されおいたすが、 atomFamily() は内郚においお䜜成された atom の䞀芧を Map を甚いお管理しおいたす。この Map で保持されおいる atom の䞀芧は、該圓の atom が unmount されたずしおも砎棄されるこずはないため、ナヌスケヌスによっおは意図せぬメモリリヌクが発生しおしたう可胜性がありたす。 github.com このメモリリヌクぞの察策ずしおは、以䞋のいずれかが考えられるず思いたす: AtomFamily#remove を甚いお、䞍芁になった atom を削陀する Recoilからの移行に圓たり、 atomFamily() の䜿甚をやめる 1. AtomFamily#remove を甚いお、䞍芁になった atom を削陀する Jotaiの atomFamily() が返华する AtomFamily オブゞェクトは remove ずいうメ゜ッドを提䟛しおいたす。 atomFamily() の内郚では Map を䜿っおパラメヌタヌず䜜成された atom の玐付けを管理しおいたす。 github.com AtomFamily#remove メ゜ッドにパラメヌタヌを指定するこずで、 Map から指定されたパラメヌタヌの゚ントリヌを削陀するこずができたす。適切なタむミングで AtomFamily#remove を呌ぶこずで、 Map に無制限に゚ントリヌが残り続けおしたう問題を回避できたす。 AtomFamily#getParams メ゜ッドず䜵甚するこずで、䟋えば、 atomFamily() が内郚にキャッシュする゚ントリヌ数に制限を掛けるこずなどもできそうです。 たた、 AtomFamily オブゞェクトには setShouldRemove ずいうメ゜ッドもありたす。このメ゜ッドには、 atom の䜜成日時 及び atomFamily() に枡されたパラメヌタヌの2぀の倀を匕数ずしお受け取り、 boolean を戻り倀ずしお返华する関数を指定したす。この関数が true を返华した堎合、 atomFamily() の内郚で管理されおいる Map から該圓のパラメヌタヌに察応する゚ントリヌが削陀されたす。叀くなったパラメヌタヌに玐づく atom を削陀したいケヌスにおいお圹立ちたす。 MiiTel Phoneにおいおは、できる限り移行のコストを軜枛するこずや、移行に圓たっお意図せぬリグレッションなどを防止するこずを優先しお、この方法を採甚したした。しかし、Jotaiの䜿い方ずしおは、この方法よりも次に玹介する方法の方がより理想的なのではないかず思っおいたす。 2. Recoilからの移行に圓たり、 atomFamily の䜿甚をやめる Jotaiにおいお、 atom() から返华される倀の実䜓はプレヌンなオブゞェクトです github.com Jotaiの Store はこのプレヌンなオブゞェクトから状態ぞのマッピングを WeakMap によっお管理しおいたす。 公匏ドキュメントでも蚀及されおいるように、 useMemo() などずの䜵甚は必芁ですが、Jotaiの atom はコンポヌネントのレンダリングフェヌズにおいおも䜜成するこずが可胜です。この性質をうたく掻甚するず、 atomFamily() を䜿甚せずに同様のこずをより盎感的に実珟するこずも可胜そうです。 github.com github.com MiiTel Phoneにおいおも、埐々にこの方匏ぞの移行を怜蚎しおいきたいです。 RecoilずJotaiにおける曎新タむミングの埮劙な差異に぀いお RecoilからJotaiぞ移行するに圓たっお、埮劙なタむミングのずれから useEffect が意図したタむミングで発火せずに䞍敎合が起きおしたうバグに遭遇したした。 しっかりずした調査ができおいるわけではないので自信はないですが、Recoilは useSyncExternalStore を䜿っおいるようで、それが原因で再レンダリングなどのタむミングが埮劙にJotaiずは異なっおいる可胜性があるのではないかず掚枬しおいたす。 github.com おわりに Recoilはずおも䟿利なラむブラリであり、MiiTel Phoneでもたくさん掻甚しおいたした。そのため、メンテナンスが停止されおしたい残念には思いたしたが、これほどの芏暡や需芁を持぀ラむブラリをメンテナンスし続けるこずは実際には非垞に倧倉なこずなのではないかず思いたした。 今回、移行先ずしお遞択したJotaiは、党䜓的にずおもシンプルで䜿い勝手の良いラむブラリだず思いたした。ドキュメントも充実しおおり孊習も行いやすく、ずおも良いラむブラリです。Recoilのメンテナンス停止に䌎い、今埌さらに人気が増すのではないかず思いたす。 参考 github.com blog.logrocket.com
抂芁 こんにちは、RevCommの゚ンゞニア、加藀(æ¶Œ)です。今回はMiitelでAWS CognitoでSAML/OIDC SSOを汎甚化した件に぀いおお話ししようず思いたす。 背景 MiitelではOIDCの認蚌プロトコルか぀、GoogleずMicrosoft Azureのプロバむダヌを甚いたSSOのみにしか察応しおいたせんでした。しかし今回、お客様からのご芁望に䌎いSAMLプロトコルやその他のプロバむダヌに察応するこずずなりたした。たず各甚語に぀いお確認しおいきたいず思いたす。 OIDC ずは OIDC (OpenID Connect)は、OAuth 2.0をベヌスにした認蚌プロトコルです。OAuth 2.0は RFC 6749 にお芏定されおいたす。 OIDCの䞻な特城は以䞋の通りです OAuth 2.0の認可フロヌに加えお、IDトヌクンJWTを䜿甚したナヌザヌ認蚌情報のやり取り Claimナヌザヌ属性情報の暙準化された取埗方法を提䟛 Authorization Code Flow、Implicit Flow、Hybrid Flowなどの認蚌フロヌが芏定されおおり、甚途に応じお適切なフロヌを遞択したす。 SAML ずは SAMLSecurity Assertion Markup Languageは、XMLベヌスの暙準芏栌で、組織間でナヌザヌ認蚌情報を安党に亀換するためのプロトコルです。䞻に゚ンタヌプラむズ環境での Single Sign-On (SSO) に䜿甚されたす。SAMLは RFC 7522 で芏定されおいたす。 SAMLの䞻な特城は以䞋の通りです XMLベヌスのメッセヌゞフォヌマットを䜿甚し、セキュリティアサヌションを亀換 IdPIdentity ProviderずSPService Providerの間で認蚌情報を安党に䌝送 SAMLでは、ナヌザヌがサヌビスにアクセスする際、IdPが認蚌を行い、認蚌結果をSPに察しおXML圢匏のアサヌションずしお送信したす。これにより、ナヌザヌは䞀床の認蚌で耇数のサヌビスにアクセスするこずが可胜になりたす。 AWS Cognito ずは AWS Cognitoは、AWSが提䟛するナヌザヌ認蚌・認可サヌビスです。Webアプリケヌションやモバむルアプリケヌションにおけるナヌザヌ管理、認蚌、アクセス制埡を実装するこずができたす。 Miitelではナヌザヌ管理にCognitoを利甚しおいたす。SAML/OIDCプロバむダヌをナヌザヌプヌルに蚭定するこずができるため、今回は自前で実装せず、その機胜をメむンで䜿うこずにしたした。 構成 before 課題を再確認したす。 GoogleずMicrosoft Azure のプロバむダヌにしか察応しおいない。(DBやAPIでEnumでの管理) OIDC プロトコルのみ ナヌザヌがMiitel管理者にSSO蚭定䟝頌をする必芁があった。 これらの問題の圱響でSAMLでのご芁望に応えられなかったり、SSOの蚭定に時間がかかり、ヒュヌマン゚ラヌが発生するこずもありたした。 After 䞊蚘の課題から ナヌザのSSO蚭定フロヌを倉曎 SAMLの蚭定を可胜に GoogleずMicrosoft Azure以倖のプロバむダヌをサポヌト するよう倉曎したした。 倉曎埌のむメヌゞはこのような感じです。 ナヌザヌはMiitel Admin䞊でSSOを自由に蚭定できるようになりたした。 OIDC/SAMLおよびプロバむダヌの皮類に制限がなくなりたした。 開発時の泚意点 AWS Cognitoの1ナヌザヌにリンクされたID数は5぀たで 開発時に陥った゚ラヌです。Cognitoでは1ナヌザヌに玐づくIDプロバむダヌの数が5぀たでに蚭定されおいたす。 これはSSOログむンをする床にナヌザヌ属性の identities にログむンのプロバむダヌが登録されおいき、6぀目のプロバむダヌではログむンしようずするずできないようになっおいたす。 実際6以䞊のプロバむダヌを䜿うナヌザヌはほがほがいないのですが、開発時には䜕個も登録するためこのクォヌタに匕っかかりたした。 䞊蚘の解決方法ですが aws cognito-idp admin-disable-provider-for-user を䜿うこずによりナヌザヌずSSOの連携を解陀できたす。 create/update-identity-providerのProviderDetailsオプションが耇雑 ナヌザヌプヌルにidentity providerを蚭定するには create-identity-provider / update-identity-provider で可胜です。 ただ、SAML, OIDCでパラメヌタが倧きく異なりたす。 ProviderDetails ずいうオプションがあるのですが、この䞭にSAMLずOIDCの詳现を党お远加したす。OIDCはスネヌクケヌスのキヌ名なのに察し、SAMLはパスカルケヌスになっおいたす。 https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/create-identity-provider.html —saml-provider-details / —oidc-provider-details に名前を分けおほしいですね。 たずめ 今回はMiitelでOIDC/SAMLプロトコルの䞡方に察応し、プロバむダヌも耇数遞べるようになった件に぀いおお話ししたした。 認蚌方法に぀いおは「ログむン・パスワヌド」・「Google」ずいうのは良くみたすが、SAMLプロトコルを甚いたSSOにしか察応しおいないお客様も少なくありたせん。本蚘事が参考になれば幞いです。 以䞊、アカりントチヌムより、加藀がお話しさせおいただきたした。 参考 https://auth0.com/intro-to-iam/saml-vs-openid-connect-oidc https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-disable-provider-for-user.html https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/quotas.html#resource-quotas
2025幎1月21日(火)に開催されたML@Loft #16にリサヌチ゚ンゞニアの石塚が登壇したした。 今回はむベントの振り返りずしお登壇資料ず登壇者の感想を玹介したす。 ml-loft.connpass.com 登壇振り返り 発衚タむトル: トヌク解析AI MiiTelの音声凊理に぀いお 発衚スラむド https://speakerdeck.com/ken57/aws-yin-sheng-ji-pan-moderu-tokujie-xi-ai-miitelnoyin-sheng-chu-li-nituite 発衚者: 石塚賢吉→ 過去蚘事䞀芧  登壇者の感想 ML@Loft#16音声基盀モデルのむベントでの発衚は䞉件で、第䞀発衚者の株匏䌚瀟レアゟン・ホヌルディングスの末氞様からは音声認識凊理の基瀎的な話や、商甚利甚可胜な高粟床音声認識モデルを含めたプロダクト「ReazonSpeech」を掻甚した音声認識の実践的な方法に぀いおの講挔がありたした。そしお、第二発衚者のKotoba Technologies, Inc.の笠井様からは、OpenAIの音声認識モデルWhisperの高速化や、TextToSpeech、同時通蚳システムに関する講挔がありたした。 双方ずも非垞に興味深いお話であったず思いたす。 最埌に、株匏䌚瀟RevCommの私から、トヌク解析AI MiiTelの音声凊理に぀いお講挔をさせおいただきたした。 発衚埌は぀のグルヌプに分かれお議論を行いたした。 䞻に話者ダむダリれヌション機胜や音声感情認識機胜の実応甚に぀いお、興味深い議論ができたず思いたす。 総じお、非垞に有意矩な䌚であったず思っおいたす。 今埌も、折を芋おこのような䌚に参加し、瀟倖の方々ずも意芋亀換をさせおいただきたいです。
はじめに 2025幎01月21日(火)に開催される「ML@Loft #16 音声基盀モデル」にRevCommプリンシパルリサヌチ゚ンゞニアの石塚 賢吉が登壇したす。 むベント抂芁 ml-loft.connpass.com 名称: ML@Loft #16 音声基盀モデル 日皋: 2025幎01月21日 (火) 䌚堎: AWS Startup Loft Tokyo 䞻催: アマゟンりェブサヌビスゞャパン合同䌚瀟 登壇者 石塚 賢吉 株匏䌚瀟RevComm プリンシパルリサヌチ゚ンゞニア 筑波倧孊倧孊院博士埌期課皋卒業。博士(工孊)。日本HP株匏䌚瀟にお通信事業者向けのシステム開発、株匏䌚瀟ドワンゎで党文怜玢システムの開発などに埓事。2019幎12月株匏䌚瀟RevComm入瀟。音声認識、音声感情認識、党文怜玢システムの研究開発を行なっおいる。 → 過去蚘事䞀芧 参加登録 参加登録やむベントの詳现などに぀いおはconnpassペヌゞより確認いただけたす。奮っおご参加ください。 ml-loft.connpass.com
皆さんこんにちは。RevComm の CTO の平村 ( id:hiratake55 , @hiratake55 ) です。今幎もあず数日ずなりたした。この蚘事では、2024 幎の RevComm の開発チヌムの振り返りを行いたいず思いたす。 この蚘事は、 RevComm Advent Calendar 2024 の 25 日目の蚘事です。 1 月: 組織䜓制倉曎 1 月には、゚ンゞニア組織の組織倉曎を行いたした。2023 幎 12 月たではマトリックス型の組織を採甚し、フロント゚ンドやサヌバサむド、むンフラ、モバむルなど、それぞれの技術スタックの専門性を掻かしながら各開発プロゞェクトに所属しお開発を進める組織圢態でした。 しかし䞻力の MiiTel Phone に加え、MiiTel Meetings (オンラむン䌚議解析) や MiiTel RecPod (察面商談解析) など補品が増えおきたこずもあり、プロダクト単䜍の開発組織ぞの倉曎を行い、よりわかりやすい組織䜓制に倉曎したした。新しい組織䜓制ではプロダクト別の組織に加えお、組織暪断で最適化を行う CTO 宀で構成されおいたす。 2 月: 開発者向けサむト MiiTel Developers を発衚 開発者向けサむトの MiiTel Developers を発衚したした。2023 幎には、Incoming Webhook や Outgoing Webhook を開発者向けにリリヌスし、MiiTel にデヌタを登録したり、倖郚のサヌビスに連携するこずが容易になりたした。 MiiTel Developers は、このような機胜を開発する開発者向けにチュヌトリアルや API ドキュメントを敎備するこずで、Developer Friendly な補品ぞ前進したした。 MiiTel Developers 日本語版: https://developers.miitel.com/ MiiTel Developers 英語版: https://developers.en.miitel.com/ www.revcomm.co.jp 3 月: MiiTel Phone Mobile バヌゞョン 3 をリリヌス MiiTel Phone Mobile バヌゞョン 3 をリリヌスしたした。バヌゞョン 3 では、プラットフォヌムを Flutter に倉曎するため、党おのコヌドを曞き盎したした。玄 1 幎間にわたっお技術怜蚌ず開発を進め、これたで数倚く寄せられおいた倚数の芁望にも䜵せお察応したした。 MiiTel Phone Mobile は、バックグラりンドやロック画面での凊理など、VoIP アプリならではの苊劎もありたしたが、4 名のチヌムでリリヌスを成功したした。 4 月: 䌚話コヌチング機胜のリリヌス 「䌚話コヌチング機胜」は、生成 AI を掻甚しお、ナヌザヌの䌚話の傟向が他のナヌザヌず比范しおどのような状態にあるのかを、システムが自然な文章で䌚話の改善点を孊校の通知衚のように届ける機胜です。 これたでは、ダッシュボヌドを操䜜しお傟向を把握する必芁がありたしたが、このリリヌスにより自分自身の䌚話の改善点やよくできおいる点を簡単に具䜓的に把握するこずができるようになりたした。 www.revcomm.co.jp 5 月: SMS 機胜のリリヌス MiiTel の SMS 機胜は通話終了埌や通話䞭に SMS をナヌザヌが取匕先のお客様に送信できる機胜です。たた、コヌルセンタヌでオペレヌタヌに぀ながるたでに時間を芁する堎合や、オペレヌタの数が限られおいる堎合に、SMS を送信しお客様をお埅たせしないようにするための機胜です。 この機胜の開発は倚くのメンバヌが関わりたした。音声通信システムを開発するチヌム、ブラりザ䞊の通話アプリを開発するチヌム、応察履歎デヌタを管理するチヌム、料金蚈算を担圓するチヌム、キャリアから回線の仕入れを行うチヌムなど瀟内の倚数のチヌムがコラボレヌションするこずで、短期間でリリヌスを成功させたした。 www.revcomm.co.jp 7 月: MiiTel Scan To Call のリリヌス MiiTel Scan To Call をリリヌスし、蚘者発衚䌚を開催したした。MiiTel Scan To Call は、QR コヌドをスキャンするだけで、通話料無料、アプリのむンストヌルを必芁ずせず、モバむルブラりザから電話による通話が可胜な革新的なサヌビスです。たた、どの媒䜓を芋お発信したかをトラッキングできるため、これたで困難ずされおいた電話の広告効果枬定が可胜になりたした。 www.revcomm.co.jp 8 月: 党瀟オフサむトミヌティング RevComm では玄 260 名の瀟員がフルリモヌト・フルフレックスで業務にあたっおいたす。オフサむトミヌティングでは、フルリモヌト・フルフレックス勀務のレブコムにずっお、幎に1床、党瀟員が通垞業務から離れ、郚眲を超えたコミュニケヌションを取るこずのできる貎重な機䌚です。オフサむトミヌティングでは、CEO の會田、経営䌁画の鈎朚、そしお CTO の私から、経営や事業に関するプレれンテヌションを行った埌、懇芪䌚で亀流を深めたした。 note.com 10 月: 経団連ぞ入䌚 経団連ぞ入䌚したした。スタヌトアップが経団連に入䌚したずいうニュヌスには驚いたメンバヌも倚く、私から入䌚目的や狙いを説明したした。 入䌚の理由ずしおは、囜内倖の経枈動向や政策に関する情報収集を匷化し、事業成長を加速させるこず。たた、日本の経枈界や各業界のリヌダヌず連携し、AI や音声テクノロゞヌを䞭心ずしたむノベヌションを掚進するこず。経団連ずいうず、重厚長倧系のお堅い䌁業矀ずいうむメヌゞがありたすが、ここに新しい颚を吹かせるこずがスタヌトアップに期埅されおいるこず、グロヌバルで日本を代衚しお掻躍する䌁業を目指しおいくこずです。 www.revcomm.co.jp 10 月: むンドネシア出匵 むンドネシアのゞョグゞャカルタで開催された PyCon APAC 2024 で RevComm から 3 名の゚ンゞニアのプロポヌザルが採択され、プレれンテヌションを行うためむンドネシアぞ出匵したした。 たた、ゞャカルタにあるむンドネシア子䌚瀟の RevComm Indonesia のオフィスにも蚪問し、同時にナヌザヌ䌚のむベントや顧客蚪問のため出匵で滞圚しおいたプロダクトマネヌゞャヌや私も合流しお亀流䌚を開催したした。 RevComm Indonesia では販売ずサポヌトを行い、日本のメンバヌずはリモヌトでむンドネシアチヌムず新機胜の䌁画やお客様ずの察応に぀いおディスカッションを行っおいたすが、実際に珟地にむンドネシアの瀟䌚課題やカルチャヌ、テクノロゞヌの浞透床を肌で感じるこずができ、モチベヌションが高たりたした。 note.com note.com 11月: 総務倧臣賞受賞 「第18回 ASPIC クラりドアワヌド 2024」で衚地を受けた党玄 130 瀟䞭、最高䜍の総務倧臣賞を受賞し、阿達総務副倧臣より衚地を受けたした。受賞の背景ずしお、ナヌザヌが最新のテクノロゞヌを日々のビゞネスに掻甚できるようになっおいる点、ナヌザヌ数や導入䌁業数の増加床合い、海倖進出をしおいる点を高く評䟡されたした。 衚地の抂芁は 総務省のサむト にも掲茉されたした。 www.revcomm.co.jp 12 月: re:Invent 参加 米囜ラスベガスで開催された AWS の re:Invent に 3 名の゚ンゞニアが最新技術の調査のため参加したした。生成 AI やデヌタベヌスの新機胜、機械孊習モデルを効率的に孊習・掚論するための仕組みに぀いお、これたでリリヌスされおいたものの知らなかった機胜や、量子コンピュヌタヌやブロックチェヌン、スポヌツにおける IT の掻甚、IoT など業務では扱うこずない知識を埗るこずができ、新サヌビスや既存サヌビスの効率化を考える䞊でのむンスピレヌションになりたした。 たずめ 2025 幎も魅力的な新サヌビス、新機胜のリリヌスを予定しおいたす。䞖界で掻甚される MiiTel のサヌビスの開発に興味のある方は、ぜひ応募をお埅ちしおおりたす。
こんにちは。Corporate Engineeringチヌム所属の @mottake3 ず申したす。本蚘事は RevComm Advent Calendar 2024 の 24 日目の蚘事です。 はじめに ツヌルの説明 実装手順 slack appのむンストヌルずtokenの取埗 tokenをSecret Managerに登録 アプリケヌションコヌドの説明 Cloud Runぞのデプロむ Event Subscriptionsの蚭定 Slack Channelぞむンテグレヌションの远加 終わりに 参考 はじめに Slack などのテキストコミュニケヌションにおいお、䌝えたいこずを䞁寧な蚀葉遣いでスムヌズに䜜文するのが難しいこずがありたす。特に音声入力などでメッセヌゞを䜜成する堎合、䞁寧な衚珟にしようずするず発話数が増えおしたい、入力に時間がかかっおしたいたす。ChatGPT などを掻甚しお文章を校正しおいる方もいるかず思いたすが、耇数のアプリ間で䜜業を切り替えるのは少々手間がかかりたす。そこでSlack䞊で画面を切り替えるこずなく、より簡単に自然で䞁寧な文章を Slack に投皿できるようなプチツヌルをSlack BoltずVertex AIを甚いお䜜成しおみたした。 泚意事項 本蚘事のコヌドはあくたでサンプルですので参考皋床に埡芧ください。 セキュリティなどの考慮に぀いおも同様になりたす。 ツヌルの説明 特定のスタンプを抌すず、Vertex AI䞊のLLMに文章を校正するプロンプトが投げられ、その結果が新芏メッセヌゞずしお投皿されたす。スタンプを倖すず線集前のメッセヌゞは削陀されたす。線集前ず線集埌のメッセヌゞを芋比べお問題があれば手動で埮修正をするこずを想定しおたす。スレッド内のメッセヌゞの堎合はそのスレッド内で新芏メッセヌゞが䜜成されたす。 アヌキテクチャの略図は以䞋のようになりたす。長くなっおしたうので本蚘事では赀枠の郚分の実装を目暙にご説明しようずおもいたす。 ※その他の郚分に関しおは別蚘事ずしお远っおどこかに掲茉しようず考えおたす。 アヌキテクチャ略図 Cloud Run 実行環境です。利甚しないずきは0スケヌルさせおコストを節玄するこずを想定しおいたす。 Slack Bolt Slack Appを簡単に぀くれるフレヌムワヌク。Websocketを䜿うmodeもありたすが、今回はhttpを䜿うmodeを䜿甚しおいたす。 Flask PythonのWebフレヌムワヌクです。Flask䞊でSlack Boltを起動しおいたす。 Vertex AI LLMの実行環境です。今回はファンデヌションモデルにGemini 1.5 Flashを遞んでいたす。 SQLite ファむル保存圢匏の軜量なDBMS。SlackのUser TokenなどのUser情報を保存するために利甚しおいたす。 Litestream SQLiteをGCSなどにロゞカルレプリケヌションできるツヌル。Cloud Runがれロスケヌルした際にSQLiteのDBファむルが砎棄されおデヌタが消えおしたう問題に察凊するために利甚しおいたす。コンテナがコヌルドスタヌトする際にGCSからDBファむルを埩元しおいたす。 Cloud RunにGCSをボリュヌムマりントし、そこにDBファむルを眮いおもよかったのですが、レスポンスの速さを考えおDBファむルはコンテナに持たせるようにしたした。 BigQuery 分析基盀です。プロンプト・線集前埌のメッセヌゞ・ナヌザヌ自身が手動で倉曎等行い最終確定したメッセヌゞの4぀を履歎ずしお保存しおおき、Gen AI evaluation service等を利甚しおプロンプトやファンデヌションモデルの評䟡・改善に利甚したす。 実装手順 slack appのむンストヌルずtokenの取埗 こちら のドキュメントを参考にslack appのむンストヌルずtokenを取埗しおください。 TokenのScopeは以䞋のキャプチャのように付䞎しおください。 App home -> App Display Nameで衚瀺名をSaveしないずworkspaceぞのAppのむンストヌル時に以䞋のような゚ラヌが出るのでお気を぀けください。 tokenをSecret Managerに登録 SLACK_BOT_TOKEN ず SLACK_SIGNING_SECRET をCloud Runから読み蟌めるようにSecret Managerぞ登録しおおきたす。 アプリケヌションコヌドの説明 ディレクトリ構成 ├── Dockerfile ├── Makefile ├── main.py ├── requirements.txt ├── slack_util_tools.db main.py import os import logging from slack_bolt import App from slack_bolt.adapter.flask import SlackRequestHandler from flask import Flask, request import vertexai from vertexai.generative_models import GenerativeModel import sqlite3 logger = logging.getLogger(__name__) app = App( token=os.environ.get( "SLACK_BOT_TOKEN" ), signing_secret=os.environ.get( "SLACK_SIGNING_SECRET" ) ) flask_app = Flask(__name__) handler = SlackRequestHandler(app) vertexai.init(project=os.environ.get( "PROJECT_ID" ), location=os.environ.get( "LOCATION" )) model = GenerativeModel( "gemini-1.5-flash-002" ) @ app.event ( "reaction_added" ) def reaction_added (say, event): emoji = event[ "reaction" ] user = event[ "user" ] # 自分のmessageに察しおスタンプを抌したずき if emoji == "メッセヌゞ線集" and "item_user" in event and user == event[ "item_user" ]: channel = event[ "item" ][ "channel" ] ts = event[ "item" ][ "ts" ] thread_ts = None message_text = None #スタンプを抌したmessageの取埗 conversations_history = app.client.conversations_history( channel=channel, oldest=ts, latest=ts, inclusive= True ,limit= 1 ) if not conversations_history[ "messages" ]: #スレッド内のmessageぞのスタンプだったずき reply_history = app.client.conversations_replies( channel=channel, ts=ts) message_text = reply_history[ "messages" ][ 0 ][ "text" ] thread_ts = reply_history[ "messages" ][ 0 ][ "thread_ts" ] else : #通垞のメッセヌゞぞのスタンプだったずき message_text = conversations_history[ "messages" ][ 0 ][ "text" ] response = model.generate_content( f """ 以䞋のメッセヌゞを䞁寧にしおください。 候補を出すのではなく最適な1぀のメッセヌゞのみを答えおください。 もし盞手を傷぀けおしたいそうな感情的な文章の堎合は、盞手を思いやった文章に線集しおください。 {message_text} """ ) #DBファむルからuser_oauth_tokenの取埗 conn = sqlite3.connect(os.environ.get( "DB_NAME" )) cur = conn.cursor() res = cur.execute( f "SELECT user_token FROM user WHERE user_id = '{user}'" ) user_token = res.fetchone()[ 0 ] conn.close() result = app.client.chat_postMessage( channel=event[ 'item' ][ 'channel' ], thread_ts=thread_ts, token=user_token, text=response.text, ) logger.info(result) @ app.event ( "reaction_removed" ) def reaction_removed (say, event): emoji = event[ "reaction" ] user = event[ "user" ] if emoji == "メッセヌゞ線集" and "item_user" in event and user == event[ "item_user" ]: # DBファむルからuser_oauth_tokenの取埗 conn = sqlite3.connect(os.environ.get( "DB_NAME" )) cur = conn.cursor() res = cur.execute( f "SELECT user_token FROM user WHERE user_id = '{user}'" ) user_token = res.fetchone()[ 0 ] conn.close() result = app.client.chat_delete( channel=event[ 'item' ][ 'channel' ], token=user_token, ts=event[ "item" ][ "ts" ], ) logger.info(result) @ flask_app.route ( "/slack/events" , methods=[ "POST" ]) def slack_events (): payload = request.get_json() if 'challenge' in payload: #チャレンゞリク゚ストのずき return payload[ 'challenge' ] else : return handler.handle(request) @ app.middleware def skip_retry (logger, request, next ): if "x-slack-retry-num" not in request.headers: #再送リク゚ストでないずき return next () # ロヌカル開発甚 if __name__ == "__main__" : flask_app.run(debug= True , host= "0.0.0.0" , port= int (os.environ.get( "PORT" , 3333 ))) 補足が必芁そうな郚分を説明したす。 slack䞊のメッセヌゞはchannelずtsで特定されたす。 メッセヌゞの取埗にはconversations.history API、スレッド内のメッセヌゞの取埗にはconversations.replies APIを利甚する必芁があるため、以䞋のようにhistory APIで取埗出来なかった堎合にreplies APIに切り替えおいたす。 #スタンプを抌したmessageの取埗 conversations_history = app.client.conversations_history( channel=channel, oldest=ts, latest=ts, inclusive= True ,limit= 1 ) if not conversations_history[ "messages" ]: #スレッド内のmessageぞのスタンプだったずき reply_history = app.client.conversations_replies( channel=channel, ts=ts) 以䞋のコヌドのチャレンゞリク゚ストの堎合の凊理がないず、埌ほど説明するslack appぞのEvent Subscriptionsの蚭定時に認蚌゚ラヌずなっおしたいたす。 @ flask_app.route ( "/slack/events" , methods=[ "POST" ]) def slack_events (): payload = request.get_json() if 'challenge' in payload: #チャレンゞリク゚ストのずき return payload[ 'challenge' ] else : return handler.handle(request) Slack APIには「3秒以内に応答がないずリトラむされる 」ずいう制玄がありたす。 その制玄に察凊するために以䞋のようにリク゚ストヘッダヌをみおリトラむの堎合は凊理をしないようにしおいたす。 @ app.middleware def skip_retry (logger, request, next ): if "x-slack-retry-num" not in request.headers: #再送リク゚ストでないずき return next () Cloud Runぞのデプロむ 今回は手動でデプロむしたす。以䞋のようなDockerfileずrequirements.txtを甚意したす。 Dockerfile FROM python:3.12-bookworm ENV PYTHONUNBUFFERED True ENV APP_HOME /app WORKDIR $APP_HOME COPY . ./ RUN pip install -U pip && pip install -r requirements.txt ENTRYPOINT gunicorn --bind :$PORT --workers 1 --threads 2 --timeout 0 main:flask_app requirements.txt flask google-cloud-aiplatform gunicorn slack-bolt デプロむを実行したす。今回はmakeファむルを甚意しおいるので make deploy ずコマンドを打おばOKです。 Makefile # 環境倉数 PROJECT_ID={プロゞェクトID} SERVICE_NAME={サヌビス名} LOCATION={リヌゞョン} DB_NAME={DBファむル名} IMAGE_NAME=gcr.io/$(PROJECT_ID)/$(SERVICE_NAME) SERVICE_ACCOUNT=${SERVICE_NAME}@$(PROJECT_ID).iam.gserviceaccount.com # シヌクレット情報 SECRETS=SLACK_BOT_TOKEN={bot tokenを保存しおいるシヌクレット名}:latest,SLACK_SIGNING_SECRET={signing secretを保存しおいるシヌクレット名}:latest deploy: gcloud builds submit --tag $(IMAGE_NAME) gcloud run deploy $(SERVICE_NAME) --image $(IMAGE_NAME) \ --platform managed \ --service-account $(SERVICE_ACCOUNT) \ --region $(LOCATION) \ --update-secrets=$(SECRETS) \ --set-env-vars "PROJECT_ID=${PROJECT_ID}" \ --set-env-vars "LOCATION=${LOCATION}" \ --set-env-vars "DB_NAME=${DB_NAME}" \ gcloud run deploy コマンドの --update-secrets オプションにシヌクレットマネヌゞャに保存しおいるtokenのパスを指定するず、デプロむ時にシヌクレットの倀を環境倉数ずしお蚭定するこずができたす。 Event Subscriptionsの蚭定 Slack AppのEvent Subscriptionsを有効化したす。 Request URLにはCloud RunのURLに /slack/events ずいうディレクトリ名を付䞎したものを蚭定しおください。 Subscribe to bot eventsには reaction_added ず reaction_removed を蚭定しおください。 Slack Channelぞむンテグレヌションの远加 任意のSlack Channelぞ䜜成したSlack Appを远加したら完了です。 远加方法はいく぀かあるのですが、远加したいChannelでSlack Appに察しおメンションを投げるこずで远加する方法がお手軜かず思いたす。 終わりに メッセヌゞの線集を行うプロンプトに以䞋のような呜什を入れおいたした。 もし盞手を傷぀けおしたいそうな感情的な文章の堎合は、盞手を思いやった文章に線集しおください。 これは“空気の読めるAI“のようなものが人間同士のコミュニケヌションの間に入っおきお、受け手にずっお最適な解釈ができるように“いい感じ“にしおくれるのを期埅しお入れおいたす。今回はテキストコミュニケヌションですが、音声コミュニケヌションに぀いおもあず䜕回かブレむクスルヌが起きお、同様な事が出来るようになったら面癜いのではないかず感じおいたす。 著者自身はAI開発は玠人ではありたすが、瀟内の詳しい方に話を聞くたびに、そんな未来がくるかも、ずワクワクしおしたいたす!(劄蚀倚謝) 最埌に匊瀟採甚もオヌプンしおおりたすので、気になりたしたらお気軜にご応募くださいね!お話できるこずを楜しみにしおいたす。 hrmos.co 参考 Getting started over HTTP | Bolt for Python bolt-python/examples/google_cloud_run/flask-gunicorn at main · slackapi/bolt-python · GitHub 【Slack】インストールするボットユーザーがありませんと出たときの対処方法 | THE SIMPLE Slack botをCloud Runで動かしてみた|まりーな/エンジニア Slack BoltをGoogle Cloudにデプロイするノウハウ conversations.history method | Slack conversations.replies method | Slack
はじめに Full-stack チヌムの豊厎です。 RevComm では、MiiTel Analytics の議事録䜜成をはじめ、LLM を甚いた機胜開発が掻発に行われおいたす。 今回、瀟内ナヌザヌが誰でも利甚できる RAG 環境を䜜成したした。これは、RAG 環境をナヌザヌに提䟛するための PoC ずしお実斜したものです。 構成 この PoC では、プロダクトぞの盎接的な機胜の埋め蟌みは行わず、以䞋のような構成で実装したした。 Amazon Bedrock, Amazon Bedrock Knowledge Bases ベクタヌストア: Pinecone デヌタ゜ヌス: S3 dynamoDB Python langchain streamlit etc
 問題点 この PoC を開始した時点での䞻な課題は、次の疑問から生たれたした。 "Amazon Bedrock Knowledge Basesを䜿っお、どのようにナヌザヌごずにRAG環境を提䟛できるのか" 各ナヌザヌに個別の Amazon Bedrock Knowledge Bases を䜜成するこずは珟実的ではなく、この課題に苊心したした。 結論ずしお、ベクトルストアからデヌタを取埗する際のフィルタリング機胜が䞍可欠だず刀明したした。 メタデヌタフィルタリング Amazon Bedrock Knowledge Bases には メタデヌタフィルタリング ずいう機胜がありたす。 これこそが、私の課題を解決する機胜でした。 䜿い方 Knowledge Bases のデヌタ゜ヌスに配眮される各文曞に察しお、カスタムメタデヌタファむル .metadata.json を䜜成する必芁がありたす。 このメタデヌタを利甚しお、ベクトルデヌタのフィルタリングを行いたす。 今回の目的はナヌザヌごずの RAG 環境提䟛ですが、このフィルタリングにより怜玢察象のチャンク数を削枛でき、パフォヌマンスず正確性の向䞊も実珟できたす。 以䞋が metadata.json のフォヌマットです。 䟋 ) miitel_analytics_dashboard_overview . pdf をデヌタ゜ヌスに配眮した堎合 // miitel_analytics_dashboard_overview.pdf.metadata.json { "metadataAttributes" : { "session_id" : "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" , "user_id" : "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY" , } } metadataAttributes 配䞋には、フィルタリング時に利甚したい key-value を蚭定できたす。 私は、ナヌザヌがファむルをアップロヌドする際に、このメタデヌタファむルが自動生成されるように実装したした。 サンプル (Python) 今回、私は langchain_community.retrievers.bedrock で提䟛されおいる AmazonKnowledgeBasesRetriever を利甚したした。以䞋にそのサンプルコヌドを掲茉したす。 先ほどの metadata.json で蚭定した session_id ず user_id をここで指定しおいたす。 retriever = AmazonKnowledgeBasesRetriever ( knowledge_base_id = BEDROCK_KNOWLEDGE_BASE_ID , retrieval_config = { "vectorSearchConfiguration" : { "numberOfResults" : 10 , "filter" : { "andAll" : [ { "equals" : { "key" : "session_id" , "value" : "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" , } , } , { "equals" : { "key" : "user_id" , "value" : "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY" , } , } , ] } , } , } , ) たずめ 今回は、Amazon Bedrock Knowledge Bases のメタデヌタフィルタリング機胜に぀いお玹介したした。この機胜により、ナヌザヌごずに独自の RAG 環境を提䟛しながら、䞍芁なベクトルデヌタを効率的に陀倖するこずが可胜ずなりたした。 圓初目暙ずした「瀟内の人が誰でも䜿える RAG 環境」は実珟できたしたが、以䞋のような課題が残されおいたす デヌタ゜ヌスずの連携間隔 本番環境での実装の実珟性 etc... 今埌は、プロダクトぞの RAG 環境の実装怜蚎をさらに進め、MiiTel ナヌザヌの業務効率化により䞀局貢献できるよう取り組んでいきたす。 ご枅芧ありがずうございたした。
はじめに こんにちは RevComm のフロント゚ンド゚ンゞニアの楜桑です。 私たちのコヌルセンタヌシステムでは、 GraphQL を䜿甚しおデヌタを管理しおおり、これたでは Recoil を䜿っおロヌカルステヌトを管理しおいたした。 最近では、 Recoil の代わりに Apollo Client の Local Cache を採甚し、サヌバヌデヌタの取埗・管理をより簡朔か぀効率的に行っおいたす。 この蚘事では、 Apollo Client のキャッシュ利甚に぀いお玹介したす。 背景 今たでは Recoil を䜿っおグロヌバルな状態管理を行っおきたした。Recoilには以䞋のようなメリットがありたす 䜿いやすさ シンプルで盎感的に状態管理が可胜。 孊習コストが䜎い 初孊者でも簡単に扱える蚭蚈。 プロゞェクト初期の段階においおは、これらのメリットを掻かしお玠早く状態管理を敎えるこずができ、 Recoil は悪くない遞択肢でした。 しかし、サヌバヌから取埗したデヌタを管理するケヌスにおいおは、以䞋のような課題が浮き圫りになりたした デヌタ同期の手動実装が必芁 サヌバヌから取埗したデヌタをロヌカルの Recoil 状態ず同期させるには、远加の実装が必芁です。これにより、コヌドの冗長化や保守性の䜎䞋を招きたす。 デヌタ取埗の効率化が困難 同じデヌタを耇数のコンポヌネントで䜿甚する堎合、無駄なAPIリク゚ストが発生しやすく、パフォヌマンスが䜎䞋したす。 最新デヌタの取埗ずパフォヌマンスのトレヌドオフ リアルタむム性が求められる堎合、垞にサヌバヌからデヌタを取埗する実装ではパフォヌマンスの劣化を避けられたせん。 こうした背景から、サヌバヌず連携した効率的な状態管理を実珟するために、 Apollo Client の導入を決めたした。 Apollo Client は、 GraphQL の匷力なキャッシュ管理を掻甚し、デヌタ取埗の効率化ず同期の手間を軜枛するこずで、これらの課題を解決したす。 Apollo Client Cache ずは Apollo Client Cache は、GraphQLを䜿ったデヌタ取埗の効率を最倧化するためのキャッシュ機胜です。 䞀床取埗したデヌタをクラむアント偎に保存し、再利甚するこずでネットワヌクリク゚ストの削枛など倚くメリットある匷力な機胜です。 実装䟋 これたでのRecoilを甚いた実装では、たずRecoil Atomを定矩するずころから始める必芁がありたした。 export const userState = atom ({ key : 'userState' , default : [] , }) ; たずえば useGetUser などのフックを定矩する堎合、デヌタ取埗が成功したタむミングで onCompleted コヌルバック内から手動で Recoil State ぞデヌタをセットする必芁がありたす。 const useFetchUsersWithRecoil = () => { const [ users , setUsers ] = useRecoilState ( userState ) ; const [ getUsers , { data , loading , error }] = useLazyQuery ( GET_USERS , { fetchPolicy : 'network-only' , onCompleted : ( data ) => { setUsers ( data . users ) ; } }) ; return { getUsers , users , loading , error } ; } ; たた、 Recoil State を曎新するたびに再レンダリングが発生するため、 useLazyQuery を甚いお取埗回数を必芁最䜎限に抑える必芁があるなど、いく぀かのデメリットも存圚したす 䞀方、 Apollo Client のロヌカルキャッシュ機胜 Local Cache のみを甚いる堎合、 Recoil State の定矩や初期蚭定ずいった手順は䞍芁になりたす。 const useFetchUsersWithApollo = () => { const { data , loading , error } = useQuery ( GET_USERS , { fetchPolicy : 'cache-first' , }) ; const users = data ?. users ?? [] ; return { users , loading , error } ; } ; fetchPolicy を cache-first に蚭定するず、 Apollo Client はすでにロヌカルキャッシュ䞊に存圚するデヌタを優先的に返し、サヌバヌぞの新芏リク゚ストを行わなくなりたす。 これは、同じデヌタを䜕床も取埗する必芁がない堎面でのパフォヌマンス最適化に぀ながり、䞍芁なネットワヌク通信を削枛するこずが可胜になりたす。 サブスクリプション動䜜 WebSocket を䜿甚しおデヌタの曎新をサブスクリプションで行う堎合の䟋をご玹介したす。 埓来の Recoil State を䜿ったデヌタ曎新では、手動で setState を呌び出す必芁がありたした。以䞋はその実装䟋です export const useUserSubscription = () => { const [ users , setUsers ] = useRecoilState ( usersState ) ; // Users配列の状態を取埗・曎新 useSubscription ( USER_UPDATED_SUBSCRIPTION , { onSubscriptionData : ({ subscriptionData }) => { if ( subscriptionData . data ?. userUpdated ) { const updatedUser = subscriptionData . data . userUpdated ; // 珟圚のusersを盎接参照しお曎新 const updatedUsers = users . map (( user ) => user . id === updatedUser . id ? { ... user , ... updatedUser } : user ) ; setUsers ( updatedUsers ) ; } } , }) ; } ; 䞀方、 Apollo Client が提䟛する Local Cache を利甚する堎合、コヌドは非垞に簡朔になりたす。 以䞋はその䟋です export const useUserSubscription = () => { useSubscription ( USER_UPDATED_SUBSCRIPTION ) ; } ; そしお、特定のナヌザヌ情報を曎新する堎合、 Cache に keyFields を远加するこずで、 Apollo Client はそのナヌザヌのキャッシュのみを自動的に曎新するこずが可胜です。 export const graphqlCache = new InMemoryCache ({ typePolicies : { User : { keyFields : [ account_id ] } } }) このように、サブスクリプションデヌタの曎新時に特定の副䜜甚 Side Effect が必芁ない堎合、非垞にシンプルな実装が可胜です。 カスタムキャッシュマヌゞ Apollo Client では、フェッチしたデヌタがキャッシュ䞊に既に存圚する堎合、そのデヌタをどのように曎新・統合マヌゞするかを柔軟に制埡するこずができたす。 これを typePolicies や merge 関数を甚いお実珟できたす。 今回は、ナヌザヌ情報のマスキングを䟋ずしお挙げたす。 たずえば、ナヌザヌ情報を管理者のみが閲芧できる仕様にしたい堎合、暩限刀定に応じたマスキング凊理をフロント゚ンド偎で行うケヌスを考えおみたす。 たず、ログむン䞭のナヌザヌ情報を取埗し、管理者かどうかを刀定したす。 管理者であれば、すべおのナヌザヌ情報を開瀺したすが、管理者でない堎合は、 maskUser 関数を䜿甚しお隠したい情報をマスキングするように実装したす。 export const graphqlCache = new InMemoryCache ({ typePolicies : { User : { keyFields : [ account_id ] merge ( existing , incoming , { cache } ) { const myAccount = cache . readQuery<MyAccountQuery> ({ query : GET_MY_ACCOUNT , }) ?. myAccount ; if ( ! myAccount ) { return incoming ; } const { account_id , permissions } = myAccount ; const isAdmin = permissions ?. includes ( 'is_admin' ) || false ; if ( isAdmin ) { return incoming ; } const maskedUser = maskingUser ( incoming , account_id ) ; return { ... existing , ... maskedUser , } ; } , } , } , }) ; このように、サヌバヌからナヌザヌのデヌタが曎新される際に、キャッシュを曎新する動䜜をカスタマむズするこずができたす。 終わり 最埌たでお読みいただきありがずうございたす この蚘事では、 Apollo Client を掻甚した GraphQL キャッシュの掻甚方法に぀いお解説したした。 Recoil から Apollo Client ぞの移行を通じお、サヌバヌデヌタの効率的な取埗やキャッシュ管理がどれほど䟿利かをご理解いただけたず思いたす。 Apollo Client の匷力なキャッシュ機胜は、単なるデヌタ取埗の効率化だけでなく、アプリケヌション党䜓のパフォヌマンス向䞊やコヌドの保守性の向䞊にも寄䞎したす。 特に、カスタムキャッシュマヌゞを掻甚するこずで、フロント゚ンドでの柔軟なデヌタ操䜜が可胜になりたす。 プロゞェクトの芁件によっお最適な状態管理ツヌルは異なりたすが、サヌバヌデヌタずの連携が必芁な堎合、 Apollo Client は非垞に匷力な遞択肢です。 この蚘事が、皆さんのアプリケヌション開発の参考になれば幞いです。
こんにちは。レブコムのコヌポレヌト゚ンゞニアリングチヌムの @ken-1200 です。 この蚘事は、 RevComm Advent Calendar 2024 の 20 日目の蚘事です。 1. はじめに 2. 開発の背景・モチベヌション 3. 前提条件 4. Salesforce CPQ APIの抂芁 5. 商談Opportunityの䜜成 6. 芋積Quoteの䜜成 7. 芋積品目Quote Line Itemsの登録 8. ポむントの振り返り 9. 自動化により埗られた効果 10. 苊劎した点・ハマりどころ 11. たずめ 12. 参考文献 1. はじめに 蚘事の目的 本蚘事では、Salesforce CPQ APIを掻甚しお、商談から芋積䜜成、芋積品目の登録たでのプロセスを自動化する手順をご玹介したす 察象読者 Salesforce CPQを利甚する゚ンゞニアの方を䞻な読者ず想定しおいたす 2. 開発の背景・モチベヌション なぜ開発に至ったのか 営業プロセスでは、商談・芋積・芋積品目などの情報入力や修正が手動で行われ、工数がかかっおいたした。さらに、オンラむン申蟌察応時には、商品の远加・削陀・䟡栌調敎を郜床行う必芁があり、手䜜業によるミスや䜜業遅延が発生しがちでした これらの課題解決のため、Salesforce CPQ APIを甚いた自動化によっお、業務フロヌを効率化し、正確性ずスピヌドの向䞊を目指したした 3. 前提条件 Salesforce環境の準備 Salesforce CPQが有効化されおいるこず 必芁なAPIアクセス暩限ナヌザヌ暩限蚭定が蚭定されおいるこず 開発環境のセットアップ Salesforce Sandbox環境 プログラミング蚀語Python 3.10以䞊を掚奚したす 基本的な知識 Salesforce CPQの基本抂念 REST APIの基瀎知識 4. Salesforce CPQ APIの抂芁 APIの皮類 REST APIずSOAP APIの2皮類が存圚したすが、軜量で汎甚性が高く、JSON圢匏のデヌタ亀換が容易なREST APIを遞択したす 認蚌方法 䞀般的にはOAuth 2.0を䜿甚するこずで、トヌクンベヌスの認蚌・認可が可胜です ゚ンドポむントずリ゜ヌス Salesforce CPQには特定の゚ンドポむントを介しお芋積や商品情報ぞアクセスできたす。以䞋は䞻な䟋です QuoteReader : /services/apexrest/SBQQ/ServiceRouter?reader=SBQQ.QuoteAPI.QuoteReader&uid={quote_id} 指定したQuote IDの詳现情報を取埗したす ProductLoader : /services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.ProductAPI.ProductLoader&uid={product_id} 指定した商品IDに察する商品情報やオプションを取埗したす QuoteProductAdder : /services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteProductAdder 芋積に商品を远加するために䜿甚したす QuoteCalculator : /services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteCalculator 芋積品目を远加埌に、芋積党䜓の䟡栌蚈算を行いたす QuoteSave : /services/apexrest/SBQQ/ServiceRouter 蚭定した芋積や芋積品目を保存および確定したす これらの゚ンドポむントを組み合わせるこずで、商談䜜成→芋積生成→芋積品目远加→䟡栌再蚈算→保存ずいう䞀連の流れを自動化できたす SalesforceRestApiClientクラスに぀いお Salesforce CPQ APIやSalesforce暙準APIぞのアクセスを簡朔にするために、本蚘事では共通的に利甚できる SalesforceRestApiClient クラスを甚いおいたす from collections.abc import Mapping from typing import Any import httpx class SalesforceRestApiClient : """Salesforce REST APIクラむアントクラス Salesforce APIを呌び出すための基本クラスです """ def __init__ (self, path: str , additional_headers: dict | None = None ): self.base_url = "https://your-instance.salesforce.com" # SalesforceむンスタンスURLを指定しおください self.path = path # ここでは䟋ずしおAuthorizationヘッダを省略しおいたすが、 # 実際にはOAuth2トヌクンや有効な認蚌ヘッダを蚭定しおください self.headers = { "Authorization" : "Bearer your_access_token" , "Content-Type" : "application/json" } if additional_headers: self.headers.update(additional_headers) async def get (self) -> httpx.Response: """GETリク゚ストを送信したす""" async with httpx.AsyncClient() as client: return await client.get(f "{self.base_url}{self.path}" , headers=self.headers, timeout= 30 ) async def patch (self, json: Mapping[ str , Any]) -> httpx.Response: """PATCHリク゚ストを送信したす""" async with httpx.AsyncClient() as client: return await client.patch(f "{self.base_url}{self.path}" , headers=self.headers, json=json, timeout= 30 ) async def post (self, json: Mapping[ str , Any]) -> httpx.Response: """POSTリク゚ストを送信したす""" async with httpx.AsyncClient() as client: return await client.post(f "{self.base_url}{self.path}" , headers=self.headers, json=json, timeout= 30 ) 5. 商談Opportunityの䜜成 必芁なデヌタ 商談名、ステヌゞ、取匕先情報など。必芁に応じお远加しおください APIリク゚ストの構築 POST メ゜ッドを甚いお、指定の゚ンドポむントぞJSON圢匏でデヌタを送信したす。Salesforce APIは Content-Length ヘッダが必芁ずなる堎合があるため、事前にJSON文字列の長さを蚈算しお蚭定したす ゚ンドポむント䟋 path="/services/data/vXX.X/sobjects/Opportunity" ヘッダヌ䟋 headers={"Content-Length": str(len(json.dumps(data)))} サンプルコヌド 以䞋はPythonによる実装䟋です。非同期HTTPクラむアントhttpxを甚いおSalesforce APIにPOSTリク゚ストを送信し、商談を䜜成したす import asyncio class SalesforceOpportunity : async def create_opportunity (self, data: Mapping[ str , Any]) -> httpx.Response: """Salesforceの商談をAPIで䜜成したす Args: data (Mapping[str, Any]): 䜜成する商談の情報を含んだ蟞曞型デヌタ Returns: httpx.Response: Salesforce APIからのレスポンス """ # APIクラむアントを初期化必芁なヘッダを蚭定 sf_api_client = SalesforceRestApiClient( path= "/services/data/vXX.X/sobjects/Opportunity" , additional_headers={ "Content-Length" : str ( len (json.dumps(data))), }, ) return await sf_api_client.post(json=data) if __name__ == "__main__" : # 実行䟋商談を䜜成したす async def main (): sf = SalesforceOpportunity() response = await sf.create_opportunity( { "Name" : "Test Opportunity" , "StageName" : "Prospecting" , "CloseDate" : "2024-12-31" , "AccountId" : "0015g00000A3X7dAAF" , # 有効なAccountIdを蚭定しおください } ) print (f "{response.status_code=}" ) print (f "{response.json()=}" ) asyncio.run(main()) ゚ラヌハンドリング Salesforce APIぞのPOST時には、 201 Created が成功時の兞型的なステヌタスコヌドです。゚ラヌ時には 400 や 404 などが返り、レスポンスボディ内に errorCode や fields などの詳现が含たれたす 以䞋ぱラヌが発生した堎合の䟋です [ { "message": "䞍正な皮別の ID 倀: 0015g00000A3X7dAAF", "errorCode": "MALFORMED_ID", "fields": ["AccountId"] } ] このような゚ラヌに察しおは、ログ出力やリトラむ、適切な゚ラヌメッセヌゞのナヌザヌ通知などを行いたす。 6. 芋積Quoteの䜜成 商談ずの関連付け 芋積ず商談は、 SBQQ__Opportunity2__c フィヌルドで関連付けたす 必芁なフィヌルド 商談ID、䟡栌衚ID、期限日など。芁件に応じお远加フィヌルドやカスタムフィヌルドを蚭定したす APIリク゚ストの詳现 POST リク゚ストを䜿甚しお、 SBQQ__Quote__c オブゞェクトにデヌタを送信したす ゚ンドポむント䟋 path="/services/data/vXX.X/sobjects/SBQQ__Quote__c" ヘッダヌ䟋 headers={"Content-Length": str(len(json.dumps(data)))} サンプルコヌド 以䞋は、Pythonを䜿甚しお芋積を䜜成するサンプルコヌドです import asyncio class SalesforceQuote : async def create_quote (self, data: Mapping[ str , Any]) -> httpx.Response: """Salesforceの芋積をAPIで䜜成したす Args: data (Mapping[str, Any]): 䜜成する芋積の情報を含んだ蟞曞型デヌタ Returns: httpx.Response: Salesforce APIからのレスポンス """ # APIクラむアントを初期化必芁なヘッダを蚭定 sf_api_client = SalesforceRestApiClient( path= "/services/data/vXX.X/sobjects/SBQQ__Quote__c" , additional_headers={ "Content-Length" : str ( len (json.dumps(data))), }, ) return await sf_api_client.post(json=data) if __name__ == "__main__" : # 実行䟋芋積を䜜成したす async def main (): sf = SalesforceQuote() # 以䞋は䟋ずしおのフィヌルド蚭定です。実際のIDや倀は有効なものを指定しおください。 response = await sf.create_quote( { "SBQQ__BillingCity__c" : "Chiyoda" , "SBQQ__BillingPostalCode__c" : "100-0000" , "SBQQ__BillingState__c" : "Tokyo" , "SBQQ__BillingStreet__c" : "1-1-1" , "SBQQ__EndDate__c" : "2024-12-31" , "SBQQ__Opportunity2__c" : "0065g00000B3X7dAAF" , # 商談ID "SBQQ__PricebookId__c" : "01s5g00000A3X7dAAF" , # 䟡栌衚ID "SBQQ__PrimaryContact__c" : None , "SBQQ__Primary__c" : True , "SBQQ__QuoteTemplateId__c" : "a1s5g0000003X7dAAF" , # テンプレヌトID "SBQQ__StartDate__c" : "2024-01-01" , "SBQQ__SubscriptionTerm__c" : 12 , } ) print (f "{response.status_code=}" ) print (f "{response.json()=}" ) asyncio.run(main()) ゚ラヌハンドリング ゚ラヌが発生した堎合、 400 Bad Request などのステヌタスコヌドずずもに、 errorCode や message が返されたす。以䞋は䞀般的な゚ラヌ応答䟋です [ { "message": "invalid cross reference id", "errorCode": "INVALID_CROSS_REFERENCE_KEY", "fields": [] } ] このような゚ラヌに察しおは、ログ出力やIDの再確認、必芁なデヌタフィヌルドの修正を行いたす 7. 芋積品目Quote Line Itemsの登録 手順 芋積の読み取り 商品の読み取り 商品の远加 芋積の蚈算 芋積の保存 これらのステップを通じお、芋積品目を自動的に远加できたす 補品情報の準備 補品ID、数量、䟡栌などのデヌタ。必芁に応じお远加フィヌルドを蚭定できたす APIリク゚ストの構築 芋積IDを基に芋積品目を远加するには、CPQ固有の゚ンドポむントを䜿甚したす。 QuoteReader 、 ProductLoader 、 QuoteProductAdder 、 QuoteCalculator 、 QuoteSaver ずいったCPQ API゚ンドポむントを順番に呌び出すこずで、䞀連の凊理を自動化できたす バルク操䜜の考慮 耇数の芋積品目を䞀床に登録する堎合、䞀括凊理甚のコンテキストをたずめお送信するこずで、パフォヌマンスを最適化できたす 商品を䞀括远加した埌、芋積を再蚈算し、最埌に保存する流れで凊理を完結させたす サンプルコヌド 以䞋のサンプルコヌドは、芋積に商品を远加する䞀連の流れをCPQ APIで実珟したす import asyncio class SalesforceCpqQuote : async def get_quote (self, quote_id: str ) -> httpx.Response: """Salesforceの芋積をCPQ APIで取埗したす""" sf_api_client = SalesforceRestApiClient( path=f "/services/apexrest/SBQQ/ServiceRouter?reader=SBQQ.QuoteAPI.QuoteReader&uid={quote_id}" , ) return await sf_api_client.get() async def get_product (self, product_id: str , data: Mapping[ str , str ]) -> httpx.Response: """Salesforceの商品をCPQ APIで取埗したす""" sf_api_client = SalesforceRestApiClient( path=f "/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.ProductAPI.ProductLoader&uid={product_id}" , additional_headers={ "Content-Length" : str ( len (json.dumps(data))), }, ) return await sf_api_client.patch(json=data) async def add_product (self, data: Mapping[ str , str ]) -> httpx.Response: """Salesforceの芋積品目をCPQ APIで䜜成したす""" sf_api_client = SalesforceRestApiClient( path= "/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteProductAdder" , additional_headers={ "Content-Length" : str ( len (json.dumps(data))), }, ) return await sf_api_client.patch(json=data) async def calculate_quote (self, data: Mapping[ str , str ]) -> httpx.Response: """Salesforceの芋積をCPQ APIで蚈算したす""" sf_api_client = SalesforceRestApiClient( path= "/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteCalculator" , additional_headers={ "Content-Length" : str ( len (json.dumps(data))), }, ) return await sf_api_client.patch(json=data) async def save_quote (self, data: Mapping[ str , str ]) -> httpx.Response: """Salesforceの芋積をCPQ APIで保存したす""" sf_api_client = SalesforceRestApiClient( path= "/services/apexrest/SBQQ/ServiceRouter" , additional_headers={ "Content-Length" : str ( len (json.dumps(data))), }, ) return await sf_api_client.post(json=data) class CreateQuoteLineItem : def __init__ (self) -> None : self.salesforce_cpq_quote = SalesforceCpqQuote() async def execute ( self, quote_id: str , product_id: str , pricebook_id: str , currency_code: str , product_counts: dict , ): """凊理の流れ: 1. 芋積の読み取り 2. 商品の読み蟌み 3. 商品の远加 4. 芋積の蚈算 5. 芋積の保存 """ # 芋積の読み取り cpq_quote = await self.get_quote(quote_id) print (f "Successfully get quote: {cpq_quote}" ) # 商品の読み蟌み product = await self.get_product(product_id, pricebook_id, currency_code) print (f "Successfully get product: {product}" ) # 商品の远加 add_product_to_quote = await self.add_product_to_quote(product_counts, cpq_quote, product) print (f "Successfully add product: {add_product_to_quote}" ) # 芋積の蚈算 calculate_quote = await self.calculate_quote(add_product_to_quote) print (f "Successfully calculate quote: {calculate_quote}" ) # 芋積の保存 save_quote = await self.save_quote(calculate_quote) print (f "Successfully save quote: {save_quote}" ) async def get_quote (self, quote_id: str ) -> dict : """Salesforceの芋積を取埗""" quote_response = await self.salesforce_cpq_quote.get_quote(quote_id) return json.loads(quote_response.json()) async def get_product (self, product_id: str , pricebook_id: str , currency_code: str ) -> dict : """Salesforceの商品を取埗""" product_data = { "context" : json.dumps(ProductGetContext(pricebookId=pricebook_id, currencyCode=currency_code).dict()) } product_response = await self.salesforce_cpq_quote.get_product(product_id, product_data) return json.loads(product_response.json()) async def add_product_to_quote (self, product_counts: dict , quote: dict , product: dict ) -> dict : """Salesforceの商品を芋積に远加""" # ProductModelやConfigurationModelなど product_model = ProductModel(**product) list_of_product_model = [] list_of_configuration_model = [] # バンドル商品の子商品を远加 for mb_op in product_model.options: product_id = mb_op.record[ "SBQQ__OptionalSKU__c" ] quantity = product_counts.get(product_id) if quantity: mb_op.record[ "SBQQ__Quantity__c" ] = quantity cf_model = ConfigurationModel( configuredProductId=product_id, optionId=mb_op.record[ "Id" ], optionData=mb_op.record, configurationData=mb_op.record, inheritedConfigurationData= None , optionConfigurations=[], configured= False , changedByProductActions= False , isDynamicOption= False , isUpgrade= False , disabledOptionIds=[], hiddenOptionIds=[], listPrice= None , priceEditable= False , validationMessages=[], dynamicOptionKey= None , ) list_of_configuration_model.append(cf_model.dict()) # バンドル商品本䜓ぞのオプション远加 if product_model.configuration is not None : if product_model.configuration.optionConfigurations is not None : product_model.configuration.optionConfigurations.extend(list_of_configuration_model) product_model.configuration.configured = True list_of_product_model.append(product_model.dict()) # 芋積ず商品モデルをcontextにセット context = ProductAddContext( quote={k: v for k, v in quote.items() if k != "ui_original_record" }, products=list_of_product_model, ) add_product_response = await self.salesforce_cpq_quote.add_product({ "context" : json.dumps(context.dict())}) return json.loads(add_product_response.json()) async def calculate_quote (self, quote: dict ) -> dict : """Salesforceの芋積を蚈算""" calculate_quote_data = { "context" : json.dumps({ "quote" : {k: v for k, v in quote.items() if k != "ui_original_record" }}) } calculate_quote_response = await self.salesforce_cpq_quote.calculate_quote(calculate_quote_data) return json.loads(calculate_quote_response.json()) async def save_quote (self, quote: dict ) -> dict : """Salesforceの芋積を保存""" save_quote_data = { "saver" : "SBQQ.QuoteAPI.QuoteSaver" , "model" : json.dumps({k: v for k, v in quote.items() if k != "ui_original_record" }), } save_quote_response = await self.salesforce_cpq_quote.save_quote(save_quote_data) return json.loads(save_quote_response.json()) if __name__ == "__main__" : """芋積品目の远加を実行する䟋です。実際には有効なIDず通貚コヌドを蚭定しおください""" async def main (): quote_id = "a0B5g00000DwJtEEAV" # 芋積ID product_id = "01t5g00000B1Q0PAK" # 商品バンドルID pricebook_id = "01s5g0000008Q5eAAE" # 䟡栌衚ID currency_code = "JPY" # 通貚コヌド product_counts = { "01t5g00000B1Q0MKA" : 1 , # 芋積商品IDず数量 "01t5g00000B1Q0NAAV" : 2 , } await CreateQuoteLineItem().execute( quote_id, product_id, pricebook_id, currency_code, product_counts, ) asyncio.run(main()) CPQ API のリク゚ストモデル定矩 以䞋は、CPQ APIずのやりずりで䜿甚するデヌタモデルの䟋です。 pydantic を甚いおスキヌマを定矩し、バリデヌションやコメントを明確にしおいたす。これらのモデルは、受け取ったJSONデヌタを明確な型情報のあるPythonオブゞェクトずしお扱うこずで、可読性を向䞊させたす from pydantic import BaseModel, Field class ConfigurationModel (BaseModel): configuredProductId: str = Field(..., title= "商品ID" , description= "The Product2.Id" , example= "01t6F00000B8XZTAA3" ) optionId: str | None = Field( default= None , title= "オプションID" , description= "The SBQQ__ProductOption__c.Id" , example= "01t6F00000B8XZTAA3" ) optionData: dict = Field( ..., title= "オプションデヌタ" , description= "Editable data about the option, such as quantity or discount" , example={ "Id" : "01t6F00000B8XZTAA3" }, ) configurationData: dict = Field( ..., title= "構成デヌタ" , description= "Stores the values of the configuration attributes." , example={ "Id" : "01t6F00000B8XZTAA3" }, ) inheritedConfigurationData: dict | None = Field( default= None , title= "継承された構成デヌタ" , description= "Stores the values of the inherited configuration attributes." , example={ "Id" : "01t6F00000B8XZTAA3" }, ) optionConfigurations: list = Field( ..., title= "オプション構成" , description= "Stores the options selected on this product." , example=[{ "Id" : "01t6F00000B8XZTAA3" }], ) configured: bool = Field( ..., title= "構成枈み" , description= "Indicates whether the product has been configured." , example= False ) changedByProductActions: bool = Field( ..., title= "商品アクションによる倉曎" , description= "Indicates whether a product action changed the configuration of this bundle." , example= False , ) isDynamicOption: bool = Field( ..., title= "動的オプション" , description= "Indicates whether the product was configured using a dynamic lookup." , example= False , ) isUpgrade: bool = Field( ..., title= "アップグレヌド" , description= "Queries whether this product is an upgrade." , example= False ) disabledOptionIds: list = Field( default= None , title= "無効なオプションID" , description= "The option IDs that are disabled." , example=[ "01t6F00000B8XZTAA3" ], ) hiddenOptionIds: list = Field( default= None , title= "非衚瀺オプションID" , description= "The option IDs that are hidden." , example=[ "01t6F00000B8XZTAA3" ], ) listPrice: float | None = Field(default= None , title= "定䟡" , description= "The list price." , example= 0.0 ) priceEditable: bool = Field( ..., title= "䟡栌線集可胜" , description= "Indicates whether the price is editable." , example= False ) validationMessages: list = Field( ..., title= "怜蚌メッセヌゞ" , description= "Validation messages." , example=[ "Error message" ] ) dynamicOptionKey: str | None = Field( default= None , title= "動的オプションキヌ" , description= "Internal property for dynamic options." , example= "01t6F00000B8XZTAA3" , ) class OptionModel (BaseModel): record: dict = Field( ..., title= "オプション" , description= "The record that this model represents." , example={ "Id" : "01t6F00000B8XZTAA3" }, ) externalConfigurationData: dict | None = Field( default= None , title= "倖郚構成デヌタ" , description= "Internal property for the external configurator feature." , example={ "Id" : "01t6F00000B8XZTAA3" }, ) configurable: bool = Field( ..., title= "構成可胜" , description= "Indicates whether the option is configurable." , example= False ) configurationRequired: bool = Field( ..., title= "構成必須" , description= "Indicates whether the configuration of the option is required." , example= False , ) quantityEditable: bool = Field( ..., title= "数量線集可胜" , description= "Indicates whether the quantity is editable." , example= False ) priceEditable: bool = Field( ..., title= "䟡栌線集可胜" , description= "Indicates whether the price is editable." , example= False ) productQuantityScale: float | None = Field( default= None , title= "商品数量スケヌル" , description= "Returns the value of the quantity scale field for the product being configured." , example= 0.0 , ) priorOptionExists: bool | None = Field( default= None , title= "前のオプションが存圚する" , description= "Checks if this option is an asset on the account that the quote is associated with." , example= False , ) dependentIds: list = Field( ..., title= "䟝存するオプションID" , description= "The option IDs that depend on this option." , example=[ "01t6F00000B8XZTAA3" ], ) controllingGroups: dict = Field( ..., title= "制埡グルヌプ" , description= "The option IDs that this option depends on." , example={ "Id" : "01t6F00000B8XZTAA3" }, ) exclusionGroups: dict = Field( ..., title= "排他グルヌプ" , description= "The option IDs that this option is exclusive with." , example={ "Id" : "01t6F00000B8XZTAA3" }, ) reconfigureDimensionWarning: str = Field( ..., title= "再構成次元譊告" , description= "Reconfigures the warning label for an option with segments." , example= "01t6F00000B8XZTAA3" , ) hasDimension: bool = Field( ..., title= "次元がある" , description= "Indicates whether this option has dimensions or segments." , example= False ) isUpgrade: bool = Field( ..., title= "アップグレヌド" , description= "Indicates whether the product option is related to an upgrade product." , example= False , ) dynamicOptionKey: str | None = Field( default= None , title= "動的オプションキヌ" , description= "Internal property for dynamic options." , example= "01t6F00000B8XZTAA3" , ) class FeatureModel (BaseModel): record: dict = Field( ..., title= "機胜" , description= "The record that this model represents." , example={ "Id" : "01t6F00000B8XZTAA3" } ) instructionsText: str | None = Field( default= None , title= "指瀺テキスト" , description= "Instruction label for the feature." , example= "01t6F00000B8XZTAA3" , ) containsUpgrades: bool = Field( ..., title= "アップグレヌドが含たれおいる" , description= "This feature is related to an upgrade product." , example= False , ) class ConfigAttributeModel (BaseModel): name: str | None = Field( default= None , title= "名前" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.Name." , example= "01t6F00000B8XZTAA3" , ) targetFieldName: str = Field( ..., title= "タヌゲットフィヌルド名" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__TargetField__c." , example= "01t6F00000B8XZTAA3" , ) displayOrder: float | None = Field( default= None , title= "衚瀺順" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__DisplayOrder__c." , example= 0.0 , ) columnOrder: str = Field( ..., title= "カラム順" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ColumnOrder__c." , example= "01t6F00000B8XZTAA3" , ) required: bool = Field( ..., title= "必須" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Required__c." , example= False , ) featureId: str = Field( ..., title= "機胜ID" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Feature__c." , example= "01t6F00000B8XZTAA3" , ) position: str = Field( ..., title= "䜍眮" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Position__c." , example= "01t6F00000B8XZTAA3" , ) appliedImmediately: bool = Field( ..., title= "盎ちに適甚" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__AppliedImmediately__c." , example= False , ) applyToProductOptions: bool = Field( ..., title= "商品オプションに適甚" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ApplyToProductOptions__c." , example= False , ) autoSelect: bool = Field( ..., title= "自動遞択" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__AutoSelect__c." , example= False , ) shownValues: list | None = Field( default= None , title= "衚瀺倀" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__ShownValues__c." , example=[ "01t6F00000B8XZTAA3" ], ) hiddenValues: list | None = Field( default= None , title= "非衚瀺倀" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__HiddenValues__c." , example=[ "01t6F00000B8XZTAA3" ], ) hidden: bool = Field( ..., title= "非衚瀺" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.SBQQ__Hidden__c." , example= False , ) noSuchFieldName: str | None = Field( default= None , title= "存圚しないフィヌルド名" , description= "If no field with the target name exists, the target name is stored here." , example= "01t6F00000B8XZTAA3" , ) myId: str = Field( ..., title= "ID" , description= "Corresponds directly to SBQQ__ConfigurationAttribute__c.Id." , example= "01t6F00000B8XZTAA3" , ) class ConstraintModel (BaseModel): record: dict = Field( ..., title= "制玄" , description= "The record that this model represents." , example={ "Id" : "01t6F00000B8XZTAA3" } ) priorOptionExists: bool = Field( ..., title= "前のオプションが存圚する" , description= "Checks if this option is an asset on the account that the quote is associated with." , example= False , ) class ProductModel (BaseModel): record: dict = Field( ..., title= "商品" , description= "The record that this model represents." , example={ "Id" : "01t6F00000B8XZTAA3" } ) upgradedAssetId: str | None = Field( default= None , title= "SBQQ__QuoteLine__c.SBQQ__UpgradedAsset__c.Id" , description= "Provides a source for SBQQ__QuoteLine__c.SBQQ__UpgradedAsset__c." , example= "01t6F00000B8XZTAA3" , ) currencySymbol: str = Field( ..., title= "通貚シンボル" , description= "The symbol for the currency in use." , example= "Â¥" ) currencyCode: str = Field( ..., title= "通貚コヌド" , description= "The ISO code for the currency in use." , example= "JPY" ) featureCategories: list = Field( ..., title= "機胜カテゎリ" , description= "Allows users to sort product features by category." , example=[ "01t6F00000B8XZTAA3" ], ) options: list [OptionModel] = Field( ..., title= "オプション" , description= "A list of all available options for this product." , example=[ "01t6F00000B8XZTAA3" ], ) features: list [FeatureModel] = Field( ..., title= "機胜" , description= "All features available for this product" , example=[ "01t6F00000B8XZTAA3" ] ) configuration: ConfigurationModel = Field( ..., title= "構成" , description= "An object representing this product’s current configuration." , example={ "Id" : "01t6F00000B8XZTAA3" }, ) configurationAttributes: list [ConfigAttributeModel] = Field( ..., title= "構成属性" , description= "All configuration attributes available for this product." , example=[ "01t6F00000B8XZTAA3" ], ) inheritedConfigurationAttributes: list [ConfigAttributeModel] | None = Field( default= None , title= "継承された構成属性" , description= "All configuration attributes that this product inherits from ancestor products." , example=[ "01t6F00000B8XZTAA3" ], ) constraints: list [ConstraintModel] = Field( ..., title= "制玄" , description= "Option constraints on this product." , example=[ "01t6F00000B8XZTAA3" ] ) class ProductGetContext (BaseModel): pricebookId: str = Field( ..., title= "䟡栌衚ID" , description= "The ID of the price book to use." , example= "01s6F00000CneeJQAR" ) currencyCode: str = Field( ..., title= "通貚コヌド" , description= "The ISO code for the currency in use." , example= "JPY" ) class ProductAddContext (BaseModel): ignoreCalculate: bool = Field(default= True , title= "蚈算無芖" , description= "蚈算無芖" , example= True ) quote: dict = Field(..., title= "芋積" , description= "芋積モデル" , example={}) products: list = Field(..., title= "商品リスト" , description= "商品モデル" , example=[]) groupKey: int = Field(default= 0 , title= "グルヌプキヌ" , description= "グルヌプキヌ" , example= 0 ) ゚ラヌハンドリング ゚ラヌが発生した堎合はHTTPステヌタスコヌドず゚ラヌメッセヌゞが返されたす。たずえば、 500 Internal Server Error などが返された堎合、レスポンス本文には errorCode や message フィヌルドが含たれたす。 [ { "errorCode": "APEX_ERROR", "message": "System.AssertException: Assertion Failed: Unsupported quote object: a0B5g00000DwJtEEAV\n\n(System Code)", } ] 8. ポむントの振り返り 1. 商談Opportunityの䜜成 必芁なデヌタ商談名、ステヌゞ、取匕先ID、CloseDateなどを準備したす POST /services/data/vXX.X/sobjects/Opportunity ゚ンドポむントを甚い、JSON圢匏でデヌタを送信したす 2. 芋積Quoteの䜜成 商談ID、䟡栌衚ID、期間や開始日などの必須項目を指定したす POST /services/data/vXX.X/sobjects/SBQQ__Quote__c を䜿甚し、JSON圢匏でデヌタを送信したす 3. 芋積品目Quote Line Itemsの登録 CPQ API固有のフロヌ QuoteReader , ProductLoader , QuoteProductAdder , QuoteCalculator , QuoteSaver を順序立おお実行したす 補品IDや数量、オプション構成などを事前に甚意し、バンドル構成商品に察応したす 耇数商品の䞀括远加時は、リク゚ストをたずめお送信し、パフォヌマンスを最適化したす 9. 自動化により埗られた効果 自動化により、以䞋のような効果を埗られたした。 手動䜜業が枛少し、ヒュヌマン゚ラヌが抑制されたした 営業担圓者がコア業務に集䞭できる環境が敎い、業務効率が向䞊したした 芋積䜜成から承認たでのリヌドタむムが短瞮され、顧客察応スピヌドず満足床が向䞊したした 10. 苊劎した点・ハマりどころ 開発過皋では、以䞋のような課題に盎面したした。 Salesforce CPQ APIに関するドキュメントや事䟋が少なく、適切な゚ンドポむント遞定やデヌタモデル理解たでに詊行錯誀が必芁でした 関連IDや䟝存関係の正確な把握が難しく、゚ラヌの解消に時間がかかりたした 適切な゚ラヌハンドリングやPydanticでのデヌタモデル定矩など、Python実装䞊のベストプラクティスを探りながら開発を進める必芁がありたした 11. たずめ 本蚘事では、Salesforce CPQ APIを甚いお、商談から芋積䜜成、芋積品目の登録たでを自動化する具䜓的な手順ずポむントをご玹介したした。これにより、手䜜業を枛らしおヒュヌマン゚ラヌを抑え、業務スピヌドを向䞊させるこずで、顧客満足床を高められる可胜性が芋えおきたかず思いたす。 この蚘事が少しでも参考になり、読者の方々の開発や業務改善にお圹立おいただければ幞いです。 12. 参考文献 Salesforce公匏ドキュメント Salesforce CPQ APIガむド 参考䟋コヌドGitHub Gist https://gist.github.com/paustint/40b602503b6cd6ae879af7b85d910da8