株式会社RevCommのブログ - TECH PLAY

TECH PLAY

株式会社RevComm

株式会社RevComm の技術ブログ

全195件

はじめに 2026年10月29日から2026年10月30日にかけて開催される FlutterKaigi 2026 に RevComm のエンジニア 横内 優作 が登壇します。 イベント概要 2026.flutterkaigi.jp 日程: 2026年10月29日 (木) 〜 2026年10月30日 (金) 会場: 浜松町コンベンションホール 主催: FlutterKaigi 実行委員会 チケット申し込みはこちらをご参照ください。 medium.com 登壇情報 FlutterでオンデバイスAI音声議事録はどこまで作れるのか 〜iOS/Androidの録音・文字起こし・要約の実装と限界〜 録音から文字起こし、要約までをすべて端末内で完結させる音声議事録アプリを、Flutterでどこまで作れるのか。 本セッションでは、実際に動くサンプルアプリの実装と実測データをもとに、その可能性と限界を検証します。対象はiOS/Androidです。 文字起こしには自社開発のAIモデルを端末上で動かし、要約にはiOSのFoundation Models、AndroidのGemini NanoといったOS標準のオンデバイスAIを利用します。 日時: 2026年10月29日 (木) 13:25 - 13:55 登壇者: 横内 優作 協賛について RevCommでは、音声解析AI「MiiTel」シリーズの開発のなかで、モバイルアプリケーションの開発に Flutter を活用しています。 音声×AIというドメインかつリアルタイム性が求められるアプリケーション開発においては、様々な技術的課題に取り組む必要があります。そうした経験などを共有することで、少しでもコミュニティーに貢献できれば幸いです。 当日はブースも出展予定のため、ぜひお立ち寄りください! おわりに ぜひ発表やブースにお越しいただけると幸いです! 当日は会場でお会いできることを楽しみにしています!
はじめに 3rd partyチームのマネージャー 松下です。 本記事では、私が所属している3rd partyチームを紹介します。 このチームでは、RevCommのプロダクト「MiiTel」において、Salesforce・HubSpot・Slackなどといった外部システムをつなぐ機能を中心に開発しています。 私たちのチームは、今年1月に組織され、以後2名の体制です。 少人数のチームではありますが、アプリケーションとクラウドインフラの両方について、開発から保守運用、監視や障害対応、技術的負債への解消といった幅広い領域を担当しています。 そのため、単に「どう実装するか」だけではなく、どうすれば少人数でも安定して開発を続けられるか、問題が起きたときに素早く切り分けられるか、といったことを日々考えながら開発しています。 この記事では、2名という少人数のチームでどのように開発に向き合っているのか、普段の業務で重視している考え方と併せ紹介します。 担当を固定しない 2名のチームであるため、「この領域はこの人しか分からない」という状態はできるだけ避けるようにしています。得意分野や詳しい領域はそれぞれありますが、実装、レビュー、インフラ、障害調査などを完全に分担して担当することはしていません。 影響範囲が広いものや複雑な変更の場合は、実装前に設計を相談し、早期のリスクの低減を図っています。障害や問い合わせがあった場合も、状況に応じて2名で調査します。少人数のチームでは、個人の生産性を最大化すること以上に、チーム全体として止まらない状態を作ることを重視しています。 現状、障害対応やオンコールも基本的には私たち2名で担っています。ただし、2名だけで全てを抱えているわけではなく、必要な場面では他チームや組織に支援を要請できる体制になっています。 その上で、日常的な対応や改善は自分たちの判断で進められる裁量があります。業務フローの改善や体制の構築自体に、自分たちで主体的に関わっていけるのは、この規模のチームならではの面白さです。 実装だけでなく、運用できることを重視する 生成AIの登場によって、動くコードを作成すること自体のハードルは大きく下がりました。一方で、生成されたコードがそのまま本番運用に耐えられるとは限りません。正常系だけを見れば問題なく動いていても、実際の運用ではさまざまな状況が発生します。 たとえば、以下のような点です。 外部サービスがタイムアウトしたときにどうなるか 同じ処理が再実行されても問題ないか 想定外のデータが渡されたときにどのように振る舞うか 障害が発生したときにログから影響範囲の特定や原因を追えるか リリース後にレスポンスタイムや処理負荷が悪化していないか こうした観点を生成AIへのプロンプトや開発環境のハーネスに組み込むことで、補うことはできます。しかし、新規の設計や機能において、あらかじめすべての観点を用意しておくことは困難です。 そのため、私たちは生成されたコードが「動くか」だけではなく、異常時にどう振る舞うべきかを設計段階で考え、コードレビューでも確認することを重視しています。 また、リリース後も、本番環境のデータ量やアクセスパターンなど様々な要因により、パフォーマンスの低下が引き起こされる可能性があります。そのため、監視やログを通じてリリース後の状態も確認し、改善につなげます。 生成AIによって実装の速度は上げられるようになりましたが、そのコードを安心して運用できる状態にすることは、現時点では引き続きエンジニアの重要な仕事だと考えています。 技術選定や既存の設計にも踏み込む 前述の通り、少人数のチームであるからこそ、決められた仕様を実装するだけではなく、既存の設計の見直しや技術選定の段階から関わる機会も多くあります。 新しい機能を作る際には、既存の構成をそのまま踏襲するのではなく、要件や運用を踏まえて、どのような構成が適しているかを考えます。たとえば、同期処理にするのか非同期処理にするのか、既存の仕組みを拡張するのか新しく作るのか、どこまでマネージドサービスを利用するのか、といった判断も開発の一部です。 もちろん、自由に技術を選べるという意味ではありません。既存システムとの整合性や運用コスト、チームで継続して保守できるかまで含めて判断します。 実装方法だけではなく、どう作るのがよいかを考えるところから関われるのは、今のチームの特徴の一つです。 どんな人と一緒に働きたいか このチームでの働き方が向いているのは、次のような方だと考えています。 アプリケーション・インフラ・運用設計・障害対応まで幅広く関わりたい方 設計や技術選定の段階から意見を出したい方 自分の裁量やチームへの影響を実感しながら働きたい方 逆に、担当領域を明確に分けて専門性を深めたい方や、決められた仕様を正確に設計実装することを重視したい方には、今のチームのスタイルは合わない可能性があります。 おわりに まだ小さなチームだからこそ、開発の進め方や技術的な判断に、一人ひとりの考えが反映されやすい環境です。これからチームを大きくしていく中でも、より良い設計や運用、開発の進め方を自分たちで考えていけるチームにしていきたいと思っています。 この記事を読んで、こうした環境で一緒に開発してみたいと感じてもらえたらうれしいです。 バックエンドエンジニアの採用情報
はじめに 先日、社内で AI Coding Agent Hackathon というイベントを開催いたしました。本記事では、そのイベントの様子を紹介します。 目的 RevComm には「AI活用推進室 (AI Promotion Office)」という組織があります。AI活用推進室における活動の一環として、「AI Coding Agents Optimization Guide」というガイドラインを策定しています。このガイドラインは、AI コーディングエージェントの価値を最大化するために、各チームが段階的に AI コーディングエージェントの活用を進めていけるような指針を定めています。具体的には、まず AGENTS.md の導入などから始め、最終的にはサブエージェントやスキル、MCP などの活用やコスト削減のための方法論などの応用的な活用法を段階的に取り入れていくためのプラクティスを整備しています。 この AI Coding Agents Optimization Guide を実践を通じて組織的に学ぶ場として、ハッカソンを開催しました。 実施したこと 概要 開催時間: 3 時間 使用した AI コーディングエージェント: Claude Code または Codex 題材 今回のハッカソンではタイピングゲームの開発を題材としました。普段、フロントエンド開発に携わっているメンバーも参加しやすいよう、ランタイムとしては Node.js を選定しました。 当日の進行をスムーズにするために、あらかじめテンプレートリポジトリを整備しました。各参加者はこのテンプレートをベースにリポジトリを作成することで、すぐに開発に取り掛かることができます。 メトリクスの収集・可視化 各種メトリクスを収集・可視化することでトークン使用量などのコストを意識して課題に取り組めるようにするために、ダッシュボードを作成しました。このダッシュボードは Claude Code や Codex などのコーディングエージェントを OpenTelemetry を介して Datadog と連携させることによって実現されています。テンプレートリポジトリの Markdown ドキュメントに Datadog と各種コーディングエージェントの連携方法を記載しておき、スムーズにセットアップできるようにしました。 Datadog によるメトリクスの可視化 課題の着手 テンプレートリポジトリには、取り組むべきタスクを詳細に記載した Markdown ドキュメントが含まれています。 タスクの一覧 各タスクには priority が設定されており、それに応じて最終的な採点結果が加算されます。 タスクの例 成果発表 最後に成果発表を実施しました。各自で取り組んだ内容や工夫したことに加えて、普段の開発環境やコーディングエージェントの活用方法についての共有も実施しました。 成果発表の様子 スコアと分析結果の共有 各メンバーのスコアの採点と全体的な分析結果の共有を実施しました。スコアは runn と Playwright を活用して、自動で採点できる仕組みを構築しました。また、ハッカソンに参加いただいた各メンバーが今後、より効果的に AI コーディングエージェントを活用できるようにするため、Datadog で収集したコスト情報とスコアのプロットや、Datadog で収集した情報から判明した良い活用法などについても全体で共有しました。 フィードバック まとめ 成果発表の場を通して、参加者の方々がさまざまな工夫や活用法などを実践されていることがわかりました。 Git worktree や herdr / cmux などを活用してセッションを並列実行することで、タスクをより効率的にこなす。 claude-code-orchestra を活用したマルチエージェントのオーケストレーション /grill-me スキルの活用 自分自身、まだ試したことのないコーディングエージェントの活用法などを知ることもできたため、今後、実践していきたいです。 今回のように、普段の業務と異なる題材で気軽に試せる場を設けることで、組織全体として AI コーディングエージェントの知見を共有する機会になったと感じています。今後もこのような取り組みを通じて、より実践的な AI 活用を進めていきます。
TL;DR My peer review workflow moves from understanding the paper to checking its claims, then writing and justifying a recommendation. The principle behind it is simple: ask what I would want to hear if I were the author. Useful feedback explains what I noticed, why it matters, and what needs clarification. The same habits help me review reports and plans at work. TL;DR 1. Why I’m writing this 2. One principle, three questions 3. The routine, step by step Step 0. Check the reviewing conditions Step 1. Read section by section, with notes Step 2. Check the frame Step 3. Identify what is different Step 4. Audit the numbers and settings Step 5. Cross-check the references Step 6. Flag strong language throughout Step 7. Summarize, then judge 4. From notes to a review What I try to make each comment do 5. The other side of the table: feedback I received 6. Where tools fit and what remains my responsibility 7. Beyond the conference 8. My closing checklist References 1. Why I’m writing this A paper represents months, sometimes years, of work. A reviewer has much less time to understand it and respond. I keep that imbalance in mind: a review may be brief, but it should never be careless. I received a Best Reviewer Award at Interspeech 2026, which recognized roughly the top 5% of reviewers. I review for Interspeech and ICASSP as an outside volunteer, on my own time. That recognition prompted me to reflect on my routine and share it, for two reasons: For peer reviewers : a practical starting point for reading a paper, checking its claims, and turning observations into useful feedback. For work : how the same habits can help us review reports and plans, with care for both the evidence and the people behind it. The Interspeech reviewer handbook [1] sets out the criteria. Here, I focus on applying them. If you review papers, technical reports, or proposals, I hope this is a useful starting point. I describe my process only, not the contents of any paper I reviewed. 2. One principle, three questions Before thinking about scores, I ask myself: If I were the author, what would I want to hear? I would want the reviewer to understand what I was trying to do, recognize what worked, and explain what did not. I would not want empty praise, but I would not want my effort dismissed either. That is the balance I aim for: respecting the authors does not mean agreeing with their claims, and being honest does not require being rude or harsh. That principle guides how I write. To assess the paper, I turn Interspeech’s three reviewing criteria [1] into three questions: Originality: What does the work add beyond what is already known? Technical correctness: Do the methods and evidence support the conclusions? Clarity: Can readers understand what was done and what was found? I keep these questions separate. A clear explanation does not make a result correct, and an unfamiliar idea is not necessarily new. 3. The routine, step by step My reviewing workflow. Watching for overclaims is an ongoing check, not a separate pass. Step 0. Check the reviewing conditions Before reading, I check the venue’s rules on confidentiality, anonymity, conflicts of interest, and tool use. I do not try to identify anonymous authors. If I have a conflict or cannot assess the work competently, I contact the meta-reviewer rather than guessing. Step 1. Read section by section, with notes I read and take notes without scoring yet, first understanding the problem, approach, evidence, and conclusion. My notes separate questions the paper answers later from concerns that remain. Step 2. Check the frame Is the problem clear? Is the objective coherent? Does the conclusion answer the question introduced at the beginning? Comparing the opening with the conclusion tells me where to look more closely. I also consider the type of contribution, since methods, datasets, replications, and surveys demonstrate their value differently. Step 3. Identify what is different I look at the main idea, figures, and prior work. What is different about the method, resource, analysis, or setting? “Interesting to me” is not the same as “original.” Complexity or a strong benchmark number does not prove novelty either, and I judge the project the authors presented, not the one I wish they had done. Step 4. Audit the numbers and settings I check whether the tables, comparisons, and experimental conditions support the claims. Are the baselines appropriate? Are important settings described? Do the reported results match the explanation in the text? The question is not only whether a number is impressive. It is what that number demonstrates under the conditions studied. Step 5. Cross-check the references I compare the work with existing papers and check whether the cited sources exist and support the claims they are cited for. I have encountered references I could not find. If I question originality, I also need to cite specific prior work rather than rely on a vague memory. Step 6. Flag strong language throughout Words such as “always,” “eliminates,” or “best” make me pause. They may be justified, but they require evidence matching their scope. My rule is simple: if you claim it, show the proof. I hold my own review to the same standard. Step 7. Summarize, then judge I summarize the paper in my own words to test whether I can explain its contribution without repeating the abstract. Only then do I settle on scores and a recommendation, taking a stand instead of defaulting to the middle of the scale, as the handbook asks [1]. The review should explain the reasoning, not just announce the outcome. 4. From notes to a review I use the handbook’s four-part structure [1] to organize the writing: Part of the review What I put here Summary My understanding of the problem, approach, and main findings Major strengths and weaknesses Contributions and concerns that affect the central claims Minor issues Presentation details, missing explanations, and inconsistencies Justification Why my assessment leads to the recommendation I look for genuine strengths rather than trying to fill a quota. When a concern matters, I explain it directly instead of hiding it behind polite but vague language. For presentation issues, I focus on helping readers understand the work, not making the paper sound as though I wrote it. What I try to make each comment do Make praise as specific as criticism. Rather than stopping at “well written” or “strong results,” I explain what helped me understand the work or what the analysis contributes. Tie each concern to something checkable. I identify a claim, comparison, or inconsistency, explain its consequence, and say what would clarify it: a clearer explanation, additional evidence, or a narrower claim. This guides the authors without making my recommendation conditional on a revision. Distinguish a limitation from a central flaw. A paper need not solve every related problem. Does the issue undermine the contribution or define its boundaries? That matters more than how many concerns I can list. The pattern I aim for is: What I observed, why it matters, and what would clarify it. For example (an invented one), instead of “The evaluation is insufficient, therefore the author's claim is too weak” I try to write: “The introduction claims robustness across recording conditions, but Table 2 reports one condition. That matters because the central claim is broader than the evidence. Could the authors narrow the claim or add a second condition?” For the overall recommendation, I add one question: how much does the issue change my confidence in the central contribution? 5. The other side of the table: feedback I received Some of the most useful reviews I received felt more like a discussion than a verdict, and two points in particular stayed with me. One concerned how I framed the problem: my wording could make a difficult problem sound simpler than it really was, while the setting I used to simulate it could give readers a misleading impression. The other questioned whether I had enough evidence to call a design choice the best option and suggested an ablation to test whether that claim was justified. What helped me move forward was not simply being told what was wrong, but understanding why it mattered and how I could address it. That experience reinforced two habits I now bring to my own reviews: looking closely at how a problem is framed and asking whether the evidence supports the claims. 6. Where tools fit and what remains my responsibility Tool use starts with the venue’s rules, not with what a tool can do. Interspeech 2026’s policy [1][2] says manuscripts must never be uploaded to public or commercial GenAI platforms, and that use is permitted only on firewall-protected or institutionally approved systems that guarantee confidentiality. The reviewer form further limits AI assistance to grammar editing and polishing text written by the reviewer. GenAI cannot assess originality, novelty, or correctness, its outputs are not scientific evidence, any use must be acknowledged in the review, and the reviewer stays fully responsible. I followed these rules. I used GenAI only to edit grammar and polish the review summary I had written myself, never to assess a paper or decide a recommendation. The system was firewall-protected and did not use inputs for training, and I acknowledged the use in my reviews. In the workplace context, our Research team has written about AI as a proofreader (in Japanese) [3]: useful for typos and logical slips, but limited by missing context and unreliable references. An internal workflow is not permission to apply the same tools to a confidential submission. Where allowed, tools may help with mechanics. Understanding the contribution, weighing the evidence, and explaining the judgment remain the reviewer’s responsibility. 7. Beyond the conference At work, I use many of the same habits on reports and plans: check the problem statement, ask whether the evidence supports the claims, identify real strengths, and keep the feedback respectful. For a plan, there are no results to audit yet. Instead, I ask whether the expected result is clear, what evidence would support it, and what outcome would show that the idea did not work. 8. My closing checklist Before submitting a review, I ask: Did I follow the confidentiality and anonymity rules? Can I explain the contribution in my own words? Did I separate originality, correctness, and clarity? Are my concerns tied to evidence, and is my praise specific? Did I distinguish central issues from limitations and minor details? Does my recommendation follow from the written assessment? Would I find this feedback fair and useful if I were the author? For me, a good review is not the one with the most criticism or the most generous praise. It helps the authors and decision-makers understand what the work establishes, where uncertainty remains, and why the recommendation follows. That is what I try to offer, and what I hope to receive when it is my turn to be reviewed. References Interspeech 2026 Reviewer Handbook, “Reviewing instructions.” https://wiki-is.isca-speech.org/en/Technical-Programme/Reviewer-handbook/Reviewing-instructions ISCA, “Code of Conduct for Reviewers and Meta-Reviewers.” https://isca-speech.org/https/www.isca-speech.org/Code-of-Conduct-for-Reviewers-and-Meta-Reviewers RevComm Tech Blog (in Japanese), “心理的安全性を高め、工数を減らす「校正者」としてのAIレビュー導入実践事例,” Dec 2025. https://tech.revcomm.co.jp/use-of-ai-in-review-process
はじめに 3rd party チームの武藤です。 Salesforce では、外部システムとの連携を担う認証・認可の仕組みとして、これまで 接続アプリケーション(CA)が広く使われてきました。しかし Salesforce の方針転換に伴い、 今後は外部クライアントアプリケーション(ECA)への移行 が求められています。 今回のように、Salesforce のリリースに応じて CA や ECA の設定変更が必要になるケースがよくあります。しかし、CA や ECA の設定変更は Salesforce の管理画面から直接行われることが多く、次のような課題を抱えています。 誰が・いつ・なぜ設定を変更したのかが追いにくい 環境(開発/ステージング/本番)ごとに手作業で同じ設定を入力する必要があり、設定ミスや漏れ、環境ごとの設定値のずれが起きやすい レビューの仕組みがなく、変更の妥当性を第三者が事前に確認できない Salesforce の第二世代管理パッケージ(2GP)にも対応したい この課題を解決するため、Salesforce ECA を IaC のように設定値のコード管理、反映前の差分チェック、自動反映できる仕組みを構築しました。本記事では、その設計と仕組みを紹介します。 設計方針 1. 共通設定 + 環境差分というデータ構造 全環境で共通する設定と、環境ごとに異なる設定を分離し、共通設定をベースに環境差分をマージして最終的な設定を組み立てる構成にしました。 設定ファイルを共通化することで、各環境間で意図しない設定差異が発生しにくくなります。 また、この構成には以下のようなメリットもあります。 環境間で共通の設定を二重管理しなくてよい 環境差分だけを見れば「その環境固有の設定は何か」が一目で分かる 環境ごとに異なることが多い設定値の例 アプリケーション名 IP 制限などの環境固有設定 新しい環境を追加する際も、差分ファイルを1つ増やすだけで済む 2. PR 作成・更新時に、実際の Salesforce 上の設定との差分を確認できる仕組み IaC 的である以上、変更を適用する前に差分チェックが可能である必要があります。 そこで、PR 作成・更新のタイミングで、PR 上の設定内容と Salesforce org 上に実際に存在する ECA の設定値を突き合わせ、その差分を PR にコメントして可視化する仕組みも作りました。 これにより、変更反映後に何がどう変わるのかを認識しやすくなります。 仕組み CI/CD をワークフローの役割ごとに分割 単一の CI ワークフローに全部を詰め込むのではなく、目的ごとに CI を分割しました。 定常CI :構文エラーや生成処理の失敗、Salesforce への接続確認を自動検証 PR 差分レポート :ECA の設定値ファイル変更時に、実際の Salesforce 上の設定と比較した差分を PR コメントとして自動通知 自動デプロイ :ECA の設定値変更を含む PR のマージ時に、マージ先のブランチに対応する環境へ自動デプロイ 2GP パッケージ作成 :手動実行により、Dev Hub でパッケージやパッケージバージョンを作成 この分離により、「レビュー段階で確認したいこと」と「マージ後に確実に実行したいこと」を明確に切り分けています。 PR差分レポートの仕組み 差分チェック対象の ECA をどう特定しているか Salesforce には複数の ECA が存在し得るため、「PR の環境に対応する 1 件の ECA だけ」を正しく特定して比較する必要があります。この仕組みは次の手順で成り立っています。 PRの対象ブランチから、比較する環境を決定する :どの環境用の ECA と比較すべきかを、PRのマージ先ブランチから機械的に判定する PR上の設定(共通設定+環境差分)をマージし、対象の識別名を取り出す :マージ後の設定から、ECA本体の識別名(fullName)と、それに紐づく OAuth 設定の識別名を読み取る 取得対象を絞り込んだ retrieve 用マニフェストを都度組み立てる :ECA本体は特定した1件だけを、OAuth設定は名称の揺れを吸収するためワイルドカードで指定し、その場限りの package.xml を生成する 取得結果から、対象の ECA に紐づく OAuth 設定だけを絞り込む :ワイルドカードで複数件取得される可能性があるため、取得結果の中から「対象 ECA を参照している OAuth 設定ファイル」を中身の参照関係で特定する デプロイの仕組み デプロイ時は念のためデプロイ時の差分をログとして残しつつ、生成済みのメタデータパッケージをまるごと Metadata API に渡します。PR の時点で差分もレビュー済みという前提ではありますが、PR 作成からマージまでに間があった時のため、念のため差分を記録として残しています。 2GP 用パッケージ作成 この仕組みにより、次のメリットがあります。 2GP で実施したい下記のような作業を、任意選択によって必要なものだけを自動実行できる パッケージ作成 パッケージバージョン作成 パッケージのリリース プッシュアップグレード 指定 org へのパッケージバージョンの強制適用 まとめ Salesforce のアプリケーション管理は、これまで手動オペレーションに依存しがちな領域でした。今回、その設定を GitHub Workflow で自動反映できるようにしつつ、2GP への対応も実現できました。 運用後に新たな課題なども出てくるかもしれませんが、そちらはまた別の記事でご紹介できればと思います。
はじめに 1. 注目した次世代ツールの魅力 1.1 Oxlint ── 「今すぐ、設定ゼロで爆速化」 1.2 Biome ── 「これ 1 つで全てが片付く、究極の一本化」 1.3 Deno ── 「セットアップ不要、最高にクリーンな開発体験」 1.4 Ezno ── 「型チェックまで Rust 化する、超未来のコンパイラ」 2. Vue特有の壁: の静的解析問題 3. チームでの議論:「設定ファイル2つは本当に必要か?」 4. 結果 5. 今後の展望 まとめ はじめに フロントエンドエンジニアをしている伊藤と申します。 昨今は、Rust 製のツールが数多く生み出されており、Linter もその波に乗っていると思います。 弊社でも移行をしたブログを書いており、是非下記の記事も読んでみてください。 https://tech.revcomm.co.jp/ai-lint-formatter-oxc 史上稀に見る大波が来ている時代に、私も颯爽と波に乗ろうとしました。 そしてどういった結論に至ったかご紹介させていただきます。 注: 本記事の検証は2026年9月10日時点、Oxlint v1.82.0 / Biome v2.5.12 で行っています。この領域は変化が速いため、お読みいただく時点では状況が変わっている可能性があります。 1. 注目した次世代ツールの魅力 1.1 Oxlint ── 「今すぐ、設定ゼロで爆速化」 最大の魅力:既存のプロジェクトにノーリスクで追加できる メリット ESLint の 50〜100 倍高速 : 数万行のコードも一瞬でスキャンが終わります。 設定ファイルが不要 : インストールして実行するだけで、プロ級のバグ検出ルールが最初から動きます。 既存の ESLint と併用可能 : 重い処理(バグチェック)は Oxlint に任せ、複雑なカスタムルールだけ ESLint に残す「いいとこ取り」ができます。 1.2 Biome ── 「これ 1 つで全てが片付く、究極の一本化」 最大の魅力:Linter と Formatter が融合したオールインワン構造 メリット ESLint と Prettier の多くの用途を一本化できる : 個別にプラグインを組み合わせる煩わしさから解放されます(ただし後述の通り、Vue の <template> 部分は現状 ESLint との併用が前提になる場面が残っています)。 設定の衝突ゼロ : Linter と Formatter が同じ思想で作られているため、「Linter と Prettier のルールがバッティングして動かない」という定番のストレスがありません。 圧倒的な構文解析の正確さ : エラーがあっても壊れたコードを器用にパースし、親切なエラーメッセージを出してくれます。 1.3 Deno ── 「セットアップ不要、最高にクリーンな開発体験」 最大の魅力:最初からすべてが揃っている「環境」そのもの メリット : ツール選定やインストールの手間がゼロ : deno lint と deno fmt が最初から内蔵されています。 package.json に大量の依存関係を書く必要がありません。 標準で Web 標準に準拠 : ブラウザと同じ API をそのままサポートしているため、環境ごとの差異に悩まされません。 Node.js との互換性も強力 : 近年は Node.js のパッケージもそのまま動くため、敷居が非常に低くなっています。 注意: Deno は Linter 単体のツールではなく、ランタイムとツールチェーンそのものです。既存の Node.js プロジェクトに deno lint だけを差し込むのではなく実行環境ごと移行する話になるため、Oxlint / Biome / Ezno とは比較の土俵・移行規模が異なる点に注意してください。 1.4 Ezno ── 「型チェックまで Rust 化する、超未来のコンパイラ」 最大の魅力:JavaScript の限界を超える、圧倒的に賢く高速な型解析 メリット : TypeScript(tsc)より遥かに高速 : 現在、フロントエンド開発で一番時間がかかる「型チェック」のプロセスを Rust で爆速にします。 型推論がスマート : コードの型をより深く、正確に推論してくれるため、開発者が型定義を細かく書く手間を減らせます。 バグを未然に防ぐ先進性 : 副作用(Side Effects)の検知など、従来の TypeScript よりもさらに一歩進んだ安全なコード検証が可能です。 注意: ただし Ezno は本記事執筆時点(2026年9月)でまだ feature-complete ではなく、tsc との完全互換を目指しているわけでもありません。既存の大規模プロジェクトの型チェックをそのまま置き換えられる段階ではなく、将来性に注目したい実験的プロジェクトという位置付けです。 どれも高速性を売りにしており、React では非常に乗り換えやすく恩恵も受けられます! 実際に私もローカルでBiomeの設定を試してみましたが、かなり早かったです。 ではそのまま導入して、Rust の恩恵を受けようとしました。 ただ、Vue になると少し話が変わってくるんです・・・ 2. Vue特有の壁: の静的解析問題 Vue は、HTML5 の標準仕様である <template> を、独自の便利な形に拡張して使っています。 要素に v-if や v-for を付けたり、 <template> を .vue ファイルの枠組みとして使ったりしています。 普段の開発をしている中で、 v-if や v-for を使わないケースはほぼないと思います。 当然ですが、それらの値もチェックしてくれると思っていました。 ただ、 <template> 内は、Oxlint, Biome ともにチェックしてくれないんです。 実際に最新版(Oxlint v1.82.0 / Biome v2.5.12、2026年9月時点)で試した結果を、以下のサンプルコードとあわせてご紹介します。 <template > < div > <!-- ❌ 1. 壁:Linterがここを見ないため、存在しない変数(タイポ)をスルーする --> < p > {{ userNema }} </ p > <!-- ❌ 2. 壁:Linterがここを見ないため、「下で使われている」と認識できない --> < p > {{ unusedMessage }} </ p > </ div > < / template> <script setup lang="ts"> import { ref } from 'vue' const userMessage = ref ( 'こんにちは' ) // 🔴 <script> 内は見ているので、「定義したのに使われていない!」と怒られる // (実際には上の template で使いたいのに、Linterが template を読めないせいで孤立する) const unusedMessage = ref ( 'これはエラーになります' ) < / s cript> Oxlint(v1.82.0) デフォルトでも vue-plugin オプションを付けても、このサンプルに対しては何も警告を出しませんでした。 userNema のタイポも unusedMessage の誤検知も発生しない代わりに、テンプレート側の静的解析自体がまだ行われていない、というのが実態でした。 Biome(v2.5.12) デフォルト設定ではスクリプト側の noUnusedVariables ルールが .vue ファイルにも適用され、 userMessage・unusedMessage の両方を「未使用」としてエラーにします( unusedMessage は実際にはテンプレート側で使われているので、これは誤検知です)。 ただし html.experimentalFullSupportEnabled を有効にすると、Biomeはテンプレート内での参照を認識できるようになり、 unusedMessage の誤検知は解消されました(本当に未使用な userMessage は引き続き正しく検出されます)。 一方で、フラグを有効にしてもテンプレート内の userNema のようなタイポを検出するルールはまだ無く、不正な変数参照を見逃す問題は解消されません。 上記の検証からわかる通り、テンプレート内の静的解析はRust製Linter単体ではまだ完結できません。Biomeは実験的フラグ付きで一部の誤検知を解消できるようになってきていますが、テンプレート内のタイポ検出のような踏み込んだチェックまでは、依然として eslint-plugin-vue に頼る必要があります。 つまり、必要なVue固有のルールと各ツールの対応状況次第では、OxlintまたはBiomeとESLintを併用する必要があるということです。 3. チームでの議論:「設定ファイル2つは本当に必要か?」 Rust製Linter を導入するか、一旦は ESLint のままにするか。 どちらを選択するか検討するため、メリット・デメリットを出してみました。 メリット 圧倒的な高速化 : 従来のJavaScriptなどで書かれたLinter(ESLintなど)に比べ、数倍〜数十倍高速に動作します。大規模なコードベースでも ミリ秒単位 で処理が終わるため、CI/CDの実行時間を大幅に短縮し、開発者の待ち時間をほぼゼロにします。 開発の快適化 : 保存時の自動整形や pre-commit フックでの実行が劇的に速くなります。 簡単な導入 : Rust製ツールはコンパイル済みのシングルバイナリとして配布されることが多いため、環境構築やCIでのセットアップが非常にシンプルになります。 デメリット 二重管理 :(一番の懸念) Rust製フロントエンドLinter(BiomeやOxcなど)は、 .vue ファイルの構文(HTMLテンプレートやVue固有の構文ルール)を完全にサポートしていません。 そのため、Vue固有のルール( vue/valid-template-root など)をチェックするには、結局従来の ESLint(eslint-plugin-vue) を残さなければならなくなります TypeScript/JavaScript部分: 爆速のRust製Linter(Biomeなど)でチェック Vueファイル・テンプレート部分: 従来のESLintでチェック プラグインエコシステムの未熟さ : 歴史のあるESLintなどのように、コミュニティが作った無数のサードパーティ製プラグインを自由に追加することが難しい場合があります。独自のルールを追加したい場合のハードルが高めです。 ルールや構文の追従ラグ: 言語(特にJavaScript/TypeScriptやPython)の新機能や新しい構文が登場した際、Rust側でのパーサ(構文解析器)の対応にわずかなタイムラグが生じることがあります。 4. 結果 結果は、デメリット部分の二重管理の懸念部分が大きく、現状のESLint(eslint-plugin-vue)で管理する方を選びました。 手元の計測で速度については、申し分ないことが立証済みでした。 導入するメリットも大いに感じているものの、2つのLinterを併用する点を上回るほどではないと判断しました。 5. 今後の展望 Biome は既に html.experimentalFullSupportEnabled フラグで Vue SFC の実験的サポートを始めており、Oxlint も vue-plugin オプションなどVue向けの機能を増やしてきています。 「サポートされるのを待つ」というよりも、「実験的サポートがどこまで安定するか・テンプレート内のタイポ検出のような踏み込んだチェックまでカバーされるか」を継続的にウォッチしていく、というのが今の立ち位置です。 各プロジェクトの現状を、ご紹介いたします。 Biome v2.4(2026年2月)で Vue・Svelte・Astro 対応を実験的機能からプロダクション標準に近づける取り組みが発表されました。既存のリンティングルールを強化し、HTMLライクな言語でも機能するようにする方針です。 https://biomejs.dev/ja/blog/roadmap-2026/#2026-roadmap Oxlint 公式ドキュメント上の制限の記載に、Vue も含まれており、改善してくれるはずだと思います。そして、改善のための issue があり開発が進められているのがわかります。 https://github.com/oxc-project/oxc/issues/23207 https://oxc.rs/compatibility.html そしてなんといっても、Oxlint の開発を支援しているのが Vue の開発者である Evan You さんが立ち上げた VoidZero という会社なんです。 現在のフロントエンドの「ツールの断片化(Linter、Formatter、テストツール、ビルドツールがバラバラで設定が面倒かつ遅い)」という課題を根本から解決するためVite+(ヴィートプラス)が開発され、その中に Oxlint も含まれています。 Vue の開発者が、Vue を見放すはずがないんです!弊社が Oxlint にするのも近いですね! まとめ Rust 製のツールは非常に高速で、すぐにでも乗り換えたいと思ってしまいます。 ただ、メリット・デメリットや今後の展望を踏まえて、検討するのが大事だなと今回の移行作業を通じて感じました。 どのツールにするかもしっかり検討したいです。 AI で開発を行うと、簡単に実行できるため、検討するのをサボりがちになってしまいます。 そういう意味でも今回はいい勉強になったと思います。 お読みいただきありがとうございました。 次は、Oxlint に移行した話でお会いしましょう。
Introduction This article is based on the original Japanese version: https://tech.revcomm.co.jp/report-pyconjp-2026 . We attended PyCon JP 2026, held from August 21 to 22, 2026. 2026.pycon.jp Our backend engineer, Rei Suyama, gave a talk at the conference. This year, RevComm also participated as a Gold Sponsor of PyCon JP 2026. In this post, we'll look back on the event and share some of our highlights. Looking Back on the Talk A Deep Dive into ASGI Middleware: Mastering Simple yet Profound ASGI Middleware Overview: A solid understanding of ASGI is essential for developing full-fledged web applications in Python. In this session, we focused on ASGI middleware, which could be said to encompass nearly all of the mechanisms behind ASGI itself. By understanding how ASGI middleware works and exploring the many ways it can be used, we learned how to put it to effective use. Speaker: Rei Suyama Reflections on the Talk This wasn't my first time speaking at PyCon JP, but I was still so nervous right before the talk that my colleagues at the sponsor booth kept telling me, "You've been looking pretty restless all day!" 😄 Fortunately, I had practiced the presentation many times right up until the last minute, so everything went smoothly overall. After the talk, some attendees told me, "It was really interesting—I learned a lot of things I didn't know before." Hearing that made me glad once again that I had decided to give the talk. My talk was scheduled for the afternoon of the second day. If I get another chance to speak at PyCon JP, I'd love to have an earlier time slot—ideally before the party! 😄 Running the Sponsor Booth This year, we had a booth at PyCon JP as a sponsor. Thank you very much to everyone who stopped by! PyCon JP 2026 RevComm Booth At the RevComm booth, we displayed videos and panels designed to give visitors a visual overview of the features of MiiTel, our product developed and provided by RevComm. Just before PyCon JP 2026, on August 13, the book Practical Introduction to Python Web Development: Building Web APIs and Asynchronous Processing with FastAPI, written by our speaker Rei Suyama, was also published. We had copies available at our booth as well. If you're interested in developing web applications with FastAPI or Python, we hope you'll check it out! gihyo.jp Sessions That Left an Impression Python and language learning: How did Python help me get the JLPT N1 This talk by Jose covered his 10-year journey from coming to Japan as an English-speaking student to eventually passing the JLPT N1 while leading a Japanese-speaking engineering team. With Japanese language proficiency becoming increasingly important for engineers from overseas working in Japan, particularly in light of recent changes to visa requirements, I found the session especially timely and meaningful. What impressed me most was how Jose connected his own language-learning experience directly to his expertise as a software engineer. Rather than relying on conventional textbooks or repetitive memorization, he demonstrated how Python and the latest LLMs can be used to create learning tools tailored to his individual needs. He explained that he previously wrote web scrapers to collect learning materials from sources such as news websites. Today, however, LLMs make it possible to generate this kind of content on demand. I was also impressed by how he used open-source libraries such as genanki for creating flashcards and onsei for pronunciation analysis to build a personalized learning environment around his own weaknesses—all through programming. The talk changed the way I think about language learning. It showed me that programming skills can be used to "hack" the complex process of learning a new language. Beyond the specific techniques introduced in the talk, I came away thinking that Python could enable much more creative approaches to all kinds of language-learning goals. 2026.pycon.jp Retry Is Not a Strategy: Classifying and Recovering from AI Agent Failures in Python When developing LLM agents, we often have to deal with silent failures whose causes are difficult to identify, as well as unintended loops. Cyrus Mante's talk addressed exactly these kinds of challenges. Rather than simply retrying from the beginning and hoping that "it'll work this time," Cyrus introduced an open-source library called triage . The library allows developers to analyze an agent's execution steps and identify the specific cause of a failure. For example, it can help determine whether the agent called the wrong tool, got stuck in a loop, or encountered a problem with an external API. What I found particularly valuable was its approach to recovery. Based on the type of error that occurred, the system can automatically determine whether to roll back the agent's state, force it to re-plan, or escalate the issue to a human. I also strongly agreed with the idea of keeping this kind of routing logic explicit in Python code rather than delegating it to an increasingly bloated LLM prompt. It was a very practical approach to building robust AI systems, and I'd love to try incorporating some of these ideas into our own agents. 2026.pycon.jp Let's Have Fun Programming with Pyxel! This was the keynote on the second day, given by Takashi Kitao , the creator of Pyxel. github.com One point that particularly stood out to me was the idea of intentionally placing constraints on a library in order to pursue its core value. I felt that this is an important perspective not only when developing libraries, but also when building products. The talk also covered many other fascinating topics, including the vision for Pyxel v3 and its potential uses and significance in the age of AI. I had never used Pyxel before, but after listening to the talk, I'm definitely looking forward to giving it a try! Conclusion This was my first time attending PyCon JP, and the event was much larger and more energetic than I had imagined. There were more attendees and sessions than I expected, and I could really feel the enthusiasm throughout the event. Many of the sessions and keynotes covered fascinating topics, and overall, I found it to be a fantastic event. This was also RevComm's first time running a full-scale booth at a tech event, so we'd like to thank everyone who took the time to stop by and visit us! Announcement On Wednesday, September 9, from 7:00 PM to 8:30 PM JST, RevComm will be hosting an unofficial PyCon JP 2026 after-event together with Sansan, Inc. and Nealle Inc. The event will be held online, so please feel free to join us! sansan.connpass.com
はじめに 2026年8月21日から2026年8月22日にかけて開催された PyCon JP 2026 に参加しました。 2026.pycon.jp 弊社からはバックエンドエンジニアの陶山 嶺が登壇しました。また、今年は弊社 RevComm もゴールドスポンサーとして PyCon JP 2026 に協賛いたしました。 今回はイベントの振り返りを紹介します。 登壇振り返り 詳解ASGIミドルウェア シンプルながらも奥が深いASGIミドルウェアを使いこなす 概要: Pythonで本格的なWebアプリケーションを開発するためには、ASGIに対する理解は極めて重要です。本セッションではそのASGIのしくみの全てが詰まっていると言っても過言ではないASGIミドルウェアに焦点を当てて紹介します。アイディア次第で用途が広がるASGIミドルウェアのしくみを理解し、使いこなしていきましょう。 登壇者: 陶山 嶺 登壇の感想 PyCon JPでの登壇はこれまでにも何度かありますが、それでもやはり直前はスポンサーブースにいる同僚たちから「今日ずっとソワソワしていますね」と言われるくらい緊張していました笑 発表自体は直前まで何度も練習していたこともあり、全体的に上手く進めることができてよかったです。登壇後には「知らない話も聞けてすごく面白かったです」と言っていただけて発表して良かったなと改めて感じました。 今回の自分の発表時間帯は2日目午後だったので、次回があればパーティよりも前に発表できたら嬉しいです!笑 ブース運営について 今年はスポンサーとしてブースを出展させていただきました。 ブースまでお越しいただいた皆様、大変ありがとうございました。 PyCon JP 2026 RevComm ブース ブースには弊社で開発・提供している MiiTel の特徴を視覚的に理解いただけるよう、動画とパネルを展示しました。 また、ちょうど PyCon JP 2026 開催直前の2026年8月13日に、登壇者の陶山 嶺が執筆した「Python Web開発実践入門 ―― FastAPIによるWeb API開発と非同期処理」が発売されたため、ブースにも展示しました。FastAPI や Python による Web アプリケーション開発にご興味がありましたら、ぜひご覧ください! gihyo.jp 印象に残ったセッション Python and language learning: How did Python help me get the JLPT N1 英語を母語とする学生として来日し、やがて日本語のエンジニアリングチームを率いながら JLPT N1 に合格するまでの 10 年間の歩みについてのJose 氏のトークです。海外出身のエンジニアが日本で働くうえで日本語力の重要性が高まっている昨今のビザ要件もあり、このセッションは非常にタイムリーで意義深いものに感じました。 特に印象的だったのは、Jose 氏が自身の言語学習の経験を、ソフトウェアエンジニアとしての専門性と直接結びつけていた点です。一般的な教科書や単調な暗記に頼るのではなく、Python や最新の LLM を活用することで、自分に最適化された学習ツールを作れることを示してくれました。以前は学習素材を集めるために Web スクレイパーを書いて、ニュースサイトなどからデータを収集していたそうですが、現在では LLM を使うことで、そのようなコンテンツをその場で生成できるようになっています。 また、フラッシュカード作成のための genanki や、発音分析のための onsei といったオープンソースライブラリを活用し、自分の弱点に合わせたパーソナルな学習環境を、まさにプログラムによって構築していた点も印象的でした。この話を通じて、プログラミングのスキルを使って、新しい言語を学ぶという複雑なプロセスを「ハック」できるのだと視点が変わりました。紹介された具体的な方法にとどまらず、Python を活用すれば、さまざまな言語学習の目標に対して、もっと創造的なアプローチができるのではないかと感じました。  2026.pycon.jp Retry Is Not a Strategy: Classifying and Recovering from AI Agent Failures in Python LLM エージェントを開発していると、原因が分かりにくいサイレントな失敗や、意図しないループへの対処に悩まされることがあります。Cyrus Mante 氏のトークは、まさにそのような課題に正面から向き合う内容でした。単に最初からリトライして「今度はうまくいくはず」と期待するのではなく、Cyrus 氏は triage というオープンソースライブラリを紹介していました。 このライブラリを使うことで、開発者はエージェントの実行ステップを分析し、失敗の原因を具体的に特定できます。たとえば、誤ったツールを呼び出したのか、ループに陥っていたのか、外部 API で問題が発生したのか、といった切り分けが可能になります。 さらに優れていると感じたのは、復旧方法の扱いです。発生したエラーの種類に応じて、システムがエージェントの状態をロールバックするのか、再計画を強制するのか、あるいは人間にエスカレーションするのかを自動的に判断します。また、このようなルーティングロジックを肥大化した LLM プロンプトに任せるのではなく、Python のコードとして明確に保つという考え方にも強く共感しました。堅牢な AI システムを構築するうえで非常に実践的なアプローチであり、自分たちのエージェントにもぜひ取り入れて試してみたいと感じました。  2026.pycon.jp Pyxelで、プログラミングを遊ぼう! Pyxel の作者である北尾崇氏 ( https://github.com/kitao ) による 2 日目の基調講演です。 github.com 本質的な価値を追求するためにライブラリに意図的に制約を加えている、という点はライブラリの開発だけでなくプロダクト開発においても重要な視点なのではないかと感じました。その他にも、Pyxel v3 の構想や AI 時代における活用や意義など、興味深い内容がたくさんありました。 Pyxel は今まで使ったことがなかったのですが、今回の発表を聞いてぜひ遊んでみたいと思いました。 おわりに 筆者は PyCon JP に参加したのは今回が初めてのことでしたが、想像以上に参加者やセッション数が多く、非常に熱気を感じるイベントでした。セッションや基調講演にも興味深いテーマの発表が多く、素晴らしいイベントであったと感じています。 RevComm において Tech イベントでの本格的なブース運営は初めてという状況ではありましたが、ブースまで足を運んでいただいてありがとうございました。 お知らせ 2026年9月9日 (水) 19:00〜20:30 にSanSan株式会社様・株式会社ニーリー様・弊社の共同で PyCon JP 2026 の非公式アフターイベントを開催いたします!オンラインで開催いたしますため、ぜひお気軽にご参加ください! sansan.connpass.com
はじめに 2026年8月21日から2026年8月22日にかけて開催される PyCon JP 2026 に RevComm のエンジニア陶山 嶺が登壇します。 イベント概要 2026.pycon.jp 日程: 2026年8月21日 (金) 〜 2026年8月22日 (土) 会場: 広島国際会議場 主催: 一般社団法人 PyCon JP Association チケット申し込みはこちらから pyconjp.connpass.com 登壇情報 詳解ASGIミドルウェア シンプルながらも奥が深いASGIミドルウェアを使いこなす Pythonで本格的なWebアプリケーションを開発するためには、ASGIに対する理解は極めて重要です。 本セッションではそのASGIのしくみの全てが詰まっていると言っても過言ではないASGIミドルウェアに焦点を当てて紹介します。 アイディア次第で用途が広がるASGIミドルウェアのしくみを理解し、使いこなしていきましょう。 日時: 2026年8月22日(土) 14:30 - 15:00 登壇者: Rei Suyama リンク: https://2026.pycon.jp/ja/talks/7PAZYQ 協賛の背景 RevCommでは、音声解析AI「MiiTel」シリーズの開発のなかで、WebアプリケーションのバックエンドやAPI開発から、音声認識・自然言語処理の研究開発、LLMを活用した機能開発まで、さまざまな場面でPythonを使っています。日々の開発も研究も、Pythonコミュニティが積み上げてきたライブラリや知見に支えられており、PyCon JPは、2021年以降毎年RevCommのエンジニアが登壇してきた、私たちにとってとても縁の深いカンファレンスです。 今回、初めてスポンサーとして協賛するにあたって、大切にしたいことが2つあります。 1. コミュニティから受け取ってきたものを、発信で返していくこと 音声×AIというドメインには、RevCommならではの技術的なチャレンジや悩みどころがたくさんあります。そうした経験をブログや登壇を通じて共有し、コミュニティに少しでも貢献できたらうれしいです。今回の陶山の登壇も、その取り組みのひとつです。 2. エンジニアの登壇を、会社としても後押ししていくこと これまでは主に社内からPyCon JPに登壇、参加するエンジニアたちを支援してきましたが、スポンサーとしての協賛は今年が初めてです。エンジニアが登壇や発信に挑戦しやすい環境を整えながら、会社としても継続して関わっていけたらと思っています。 ブースでは、MiiTelを支える技術や開発の裏側についてエンジニアがお話しします! 気軽な質問も大歓迎ですので、ぜひお立ち寄りください。 おわりに ぜひ発表やブースにお越しいただけると幸いです! 会場でお会いできることを楽しみにしています!
はじめに こんにちは。RevComm でエンジニアをしている 林 です。 AI コーディングエージェントの普及でコードを書くコストは大きく下がり、ボトルネックはレビューと定型的な保守作業に移りました。 Addy Osmani 氏も「 Agentic Code Review 」で、レビューが PR の作成スピードに追いつかず、コードが読まれないままマージされ始めている現状を書いています。 MiiTel Phone(ブラウザ上で通話できる AI 搭載 IP 電話アプリ)の開発リポジトリでは月に 300〜600 件の PR がマージされます。その中には、振る舞いを変えないリファクタリング、依存パッケージの更新、ドキュメントの修正のような、判断の型が決まっているのにレビューの待ち時間だけは通常の PR と変わらないものが相当数含まれます。 こうした定型的な判断を GitHub Actions 上の AI に任せ、人間のレビューを本質的な変更に集中させる取り組みから、4 つの事例を紹介します。 いずれも Claude Code を GitHub Actions 上で動かす構成で、事例 1〜3 は公式の claude-code-action 、事例 4 は GitHub Agentic Workflows (gh-aw) を利用しています。 ※ 記事中のコードや設定は説明のために抜粋・簡略化しています。 設計原則を先に 4 つの事例は使うツールも対象も違いますが、設計は同じ型に収まっています。作業の大半はプロンプトを書くことではなく、「AI に何をさせないか」を決めることでした。 先に原則を挙げます。以降の事例は、これがどう具体化されているかとして読んでもらえればと思います。 判定は AI、実行は決定的なステップとして 。AI の出力は構造化された判定 or 成果物にとどめ、承認・マージ・クローズ・チケット起票といった副作用は、スキーマ検証を通った出力に対して後続のステップが実行します 自動化の終点を、人間の判断が残る場所に置く 。AI が進めるのは承認、コミットの push、draft PR の起票までで、マージするかどうかは人間が判断します いつでも止められるようにする 。設定をひとつ変えるだけで自動承認を止められるキルスイッチ、作成者がラベルを付けた PR だけを対象にするオプトイン方式、AI の実行回数・実行時間・同時実行数の上限。まず判定だけを動かして精度を確認し、納得してから副作用を有効化します AI への入力を信頼しない 。PR 本文やリリースノートはサードパーティ由来のテキストとして扱い、渡さないか信頼境界の外に置きます 運用で精度を上げる前提を組み込む 。週次のスポットチェック、誤判定を反例としてプロンプトに追記していく運用、リポジトリ固有の観点を自然言語で注入できる入り口、そして「処理しなかったもの」のログを確認可能にしておきます 事例 1: PR のレビュー要否を AI が判定する AI に任せるのはレビューではなく分類 きっかけはチームの振り返りで出た「テストコードやドキュメントだけの変更なら、レビューなしでマージしてよいのでは」という話でした。 前提として、PR を open すると GitHub Copilot や Devin が自動でレビューする設定はすでに入っています。ただし、これらは人間のレビューに観点を足す補助であって、承認までの待ち時間はそこまで変わりません。 今回のボトルネックはまさにこの待ち時間なので、AI レビューを強化して人間のレビューを置き換える方向は取りませんでした。 方針の参考にしたのは Findy Tech Blog の「 不要なレビューをAIにまかせてAIコーディングの環境改善を加速した 」です。要点は、AI を「レビュアー」ではなく「人間のレビューが不要な PR かどうかの分類器」として使うこと、そして Kent Beck 氏の『 Tidy First? 』でいう振る舞いを変えない整頓(tidying)だけを自動承認の対象にすることです。 レビューの代行が品質保証の責任ごと AI に移してしまうのに対し、分類は「人間がレビューすべき PR を人間に確実に届ける」ための振り分けです。 3 レイヤーのレビューポリシー 「AI で自動化」と言いつつ、最初のレイヤーに AI は登場しません。 レイヤー 内容 仕組み レイヤー 0 ドキュメントのみの変更の自動承認 ファイルパスによる機械的な判定(AI 不要) レイヤー 1 人間レビュー必須領域の定義 ポリシードキュメント レイヤー 2 振る舞いを変えない整頓の自動承認 AI 分類器 + 抜き取りの事後レビューと監査 レイヤー 0 は、変更ファイルがすべて docs/ 配下または *.md の PR を機械的に承認します。AI で判定できることでも、決定的に判定できるならそちらを選びます。誤判定リスクがなくコストもゼロだからです。 ただし、同じ Markdown でも .github/ 配下と CLAUDE.md / AGENTS.md は対象外です。これらの無人承認を許すと、自動承認システム自体の改変が無人で通ってしまいます。 レイヤー 1 は、AI の判定にかかわらず必ず人間がレビューする領域の明文化です。通話のような中核機能、feature flag、外部システムとの連携、個人情報や認証、テストの削除、依存パッケージ、CI 設定などが該当します。間違えると影響が大きい領域と、自動化自体の安全性を支える領域(テスト・CI)を並べています。 分類器の実装 レイヤー 2 が本題の AI 分類器です。PR の作成者が ai-classify ラベルを付けたときだけ動きます。分類器は『Tidy First?』の構造的リファクタリングを調整した分類表を持っています。 T1: ファイル・ディレクトリの移動、リネーム(import パスの機械的追随を含む) T5: 変数・関数・コンポーネントのリネーム T7: 未使用コード・ファイルの削除 A1: 既存 feature flag の targeting(どの環境・ユーザーに配布するかの設定)のみの変更(振る舞いは変わるが、チームが明示的にレビュー不要と合意した低リスク変更) NG2: 通話系コードに触れる変更(即 HUMAN_REVIEW) ポイントは、除外条件(NG)を「変更の性質」ではなく「触れた領域」で判定させていることです。通話系ファイルの変更が import パスの機械的な追随だけだったとしても NG2 に倒します。「機械的な変更だから実質は該当しない」という楽観的な判断を AI にさせないためです。 また、迷ったら UNKNOWN と出力させ、 誤って APPROVE するコストは誤って HUMAN_REVIEW とするコストよりはるかに大きい という原則もプロンプトに明記しています。 出力は自然言語のコメントではなく、構造化された判定ファイルです。 { " verdict ": " APPROVE ", " t_categories ": [ " T1 ", " T5 " ] , " reasons ": [ " 判定理由を簡潔に列挙する " ] , " summary ": " 1〜2 文の判定サマリ ", " sampling_used ": false } 後続のステップが jq でこのファイルのスキーマを検証し、検証を通った APPROVE 判定に対してのみ承認を実行します。 - name : Approve if : steps.verdict.outputs.verdict == 'APPROVE' && vars.AI_CLASSIFY_APPROVE_ENABLED == 'true' run : | gh pr review "$PR_NUMBER" --approve --body "$body" gh pr edit "$PR_NUMBER" --add-label ai-approved AI_CLASSIFY_APPROVE_ENABLED は GitHub の repository variable を使ったキルスイッチで、 true でないときは判定コメントの投稿だけを行います。導入初期は切ったまま判定精度を確認し、問題ないことを確認してから有効化しました。 分類器に渡すのは差分とファイル内容だけで、PR 本文とコメントは渡しません。ただし、diff 中のコードコメントには同じ誘導を書けるため、この経路まで消えるわけではありません。 そこは後段のスキーマ検証と、マージ判断が人間に残っていることでブロックします。承認後に push があれば自身の承認を取り消して再分類し、自動マージはどのレイヤーでも行いません。 運用と、PR の作り方への波及 自動承認された PR は週次で数件を人間が事後レビューしています。Addy Osmani 氏の整理を借りれば、すべての差分を人間が見る形から、抜き取り確認と監査で仕組み全体を見張る形への移行です(彼の記事の言葉では human in the loop から human on the loop へ )。誤判定が見つかれば、反例として分類器プロンプトの除外条件に追記します。 効果はレビューが速くなることにとどまりませんでした。分類器が APPROVE を出せるのは PR 全体が整頓で完結しているときだけなので、リファクタリングと機能変更が混ざった PR は HUMAN_REVIEW に倒れます。 つまり、作成者から見ると、混ぜれば全体がレビュー待ちになり、分ければリファクタリング部分を先に流せる。リネームやファイル移動を先行 PR に切り出して ai-classify を付け、その上に振る舞いを変える PR を積み重ねれば(stacked PR として)、人間に届くのは本質的な変更だけの小さな差分になります。 『Tidy First?』の「整頓と振る舞いの変更を分けよ」という規律は、分けること自体のコスト(PR が増え、レビュー待ちも複数回になる)のせいで努力目標になりがちでした。分類器が整頓側の待ち時間をゼロにしたことで、規律を守るほうが速いという構造に変わっています。 自動化が規律を強制するのではなく、規律に従うことが合理的な選択になるというのは、レビュー待ち時間の削減と並ぶ、もうひとつの効果だと思っています。 事例 2: Dependabot PR を毎朝トリアージする 依存パッケージの更新 PR への対応は、毎回ほぼ同じ判断の繰り返しでした。CI の成否を確認し、リリースノートを読んで破壊的変更の有無を確認し、低リスクなら承認してマージ、高リスクなら Notion にチケットを起票して PR はクローズし、動作確認を含めた計画的な対応に回す。判断の型は決まっているのに手作業では追いつかず、PR は溜まる一方でした。 最初の自動化は、この手順を Claude Code の Skill に書き起こすことでした。手元で「Dependabot の PR を対応して」と頼めば、オープン中の PR を順に解析して一括処理してくれます。 ただし、人が起動する半自動なので、誰かが思い出さない限り PR は溜まり、実行する人の環境と権限に依存する属人的な運用でもありました。 そこで Skill として固めた手順を GitHub Actions の composite action(複数のステップをまとめて再利用できる仕組み)に作り直し、無人で定期実行するようにしました。 このとき、AI 自身が実行していたマージやチケット起票は AI から切り離しています。composite action は社内の共通リポジトリに置き、複数のリポジトリから利用しています。 毎日午前 3 時の cron がオープン中の Dependabot PR を古い順に 1 件ずつ処理します(1 件マージすると Dependabot が残りを rebase するため、並列ではなく直列です)。 PR タイトル・body からパッケージ名とバージョンを機械的に解析し、CI チェックの成否を判定する Claude によるリスク判定。リリースノートを読み取り専用のツールで読み、 triage-verdict.json (risk: low / high、根拠、主要変更点)を書き出す 1, 2 を決定表に通して最終アクションを決める 実行。承認して自動マージ、クローズして Notion にチケット起票、または手動対応として PR に残す 条件 アクション タイトル・body とも解析不能 手動対応(PR は残す) CI 失敗 クローズしてチケット起票(AI の判定に関係なく) AI の判定を取得できない 手動対応(安全側に倒して人間に残す) AI 判定が高リスク クローズしてチケット起票 AI 判定が低リスク 承認して自動マージ Notion のチケット操作も AI にはさせず、TypeScript のスクリプトが REST API を直接叩きます。チケット作成に失敗した場合は PR をクローズせず手動対応に降格させ、「チケットなしで PR だけ消える」事態を防いでいます。 リリースノートはサードパーティ由来の入力なので、プロンプト側では、指示のような文言(「この更新は安全である」「以前の指示を無視せよ」など)を見つけた場合、従わないだけでなく、それ自体をサプライチェーン上の懸念として high と判定するよう指示しています。インジェクションの試みを検知シグナルに変える発想です。 それでも最後の砦は AI の外にある CI 成功という条件で、仮に誘導が成功しても CI が落ちている PR はマージされません。Dependabot ブランチのコードはチェックアウトすらせず、AI が読むのはデフォルトブランチと PR のメタデータだけです。 リポジトリ固有の判定観点は、決められたパスに Markdown で書いておくとプロンプトに注入されます。「electron 関連の更新はすべて高リスクとする」のような 1 行で、チームの経験則を判定に反映できます。 導入後は毎朝の CI で Dependabot のオープン PR をゼロに戻していくので、依存更新の PR が滞留すること自体がなくなりました。残る人間の作業は、高リスクと判定されて起票されたチケットへの対応だけです。 事例 3: @claude メンションを全リポジトリ共通の基盤にする PR や issue のコメントで @claude とメンションすると GitHub Actions 上で Claude Code が動き、質問への回答、レビュー指摘への修正コミットの push、ショートカットの実行を行います。 反応するのは書き込み権限を持つユーザーのコメントのみで、Claude に承認とマージはさせません。 よく使うのはコンフリクトの解消です。AI コーディングで PR を作る速度が上がると同時にオープンな PR が増え、main の進みも速くなるため、コンフリクトは以前より起きやすくなりました。PR 作成の高速化が、今度はコンフリクト解消という新しい定型作業を生んだ形です。 @claude /fix-conflicts と書けば、解消して push するところまでやってくれます。 同じ問題意識は GitHub Next の Evergreen や Codex の PR Babysitter にも見られ、この種の PR のお手入れは自動化の定番になりつつあります。 実装面の工夫は共通化です。 @claude メンション自体は claude-code-action の代表的な使い方ですが、リポジトリごとにワークフローを持つと、モデルの変更やコスト制御の調整のたびに全リポジトリへ同じ変更を配ることになります。 そこでトリガー条件・トークン設計・モデル選択・コスト制御を社内共通リポジトリの reusable workflow に集約し、利用側には呼び出しの数行だけを置く構成にしました。 # 利用リポジトリ側はこれを置くだけ jobs : claude : uses : my-org/claude-workflows/.github/workflows/claude.yml@main secrets : inherit ショートカットの中身は Claude Code の Skill として、さらに別の共有リポジトリ(plugin marketplace)に置いています。手元の Claude Code と GitHub Actions で同じ Skill 資産を共有するためで、ローカルで育てたワークフローがそのまま @claude メンションでも使えます。 事例 4: 定期メンテナンスを自然言語で書く (GitHub Agentic Workflows) gh-aw は、ワークフローを「YAML frontmatter + 自然言語プロンプト」の Markdown ファイルとして書き、 gh aw compile で実行可能なワークフロー( .lock.yml )にコンパイルする gh CLI の拡張機能です。 --- on : schedule : weekly on sunday around 20:00 # fuzzy schedule workflow_dispatch : permissions : contents : read engine : id : claude max-turns : 50 timeout-minutes : 30 safe-outputs : create-pull-request : title-prefix : '[main] ' draft : true --- # Docs Drift Audit You are auditing this repository's developer documentation for drift against the actual code, and fixing what has drifted. ... 事例 1〜3 が AI の判定や応答を自前のワークフローに組み込む構成だったのに対し、gh-aw はスケジュールで自律的に動き、リポジトリを調べて成果物(PR など)まで作るエージェントです。 その分、安全装置も枠組み側に強く組み込まれていて、エージェント本体は読み取り専用のトークンで実行され、書き込みは frontmatter の safe-outputs で宣言したものしかできません。 外部通信もファイアウォールコンテナで制限され、 weekly on sunday around 20:00 のようなあいまいな時刻指定はコンパイラが具体的な時刻に解決して、多数のリポジトリで cron が同時刻に集中するのを避けます。現在 2 本が週次で動いています。 ドキュメントと実コードの乖離監査 (docs-drift-audit) 1 本目は、開発ドキュメント( docs/ 配下、 AGENTS.md 、 CLAUDE.md など)と実コードの乖離を監査するワークフローです。参照先のパスが存在するか、記載コマンドが package.json の scripts と一致するか、feature flag 名やワークフロー名が実在するか、規約の記述が設定ファイルと矛盾していないか。確認できた乖離を修正して 1 本の draft PR にまとめ、乖離がなければ何もせず終了します。 これをわざわざ AI にやらせるのは、ドキュメントが人間向けであると同時に AI 自動化の入力でもあるからです。事例 1 の分類器プロンプトも、事例 3 の @claude が参照する開発ガイドも、鮮度が落ちればそのまま自動化全体の精度劣化につながります。 一方でコード側の変化は速く、手動での追従は現実的に漏れます。週次の監査で、月曜の始業前に修正 PR が用意されている状態を作りました。 プロンプトには「何を直さないか」も同じ分量で書いています。意図的に将来の姿を書いているドキュメントは直さない。リポジトリ外への参照は検証できないので触らない。文体の好みは直さない。そして確信の持てない修正は PR から外すのではなく、PR 本文の「要判断」セクションで曖昧さとレビュー時の確認点を説明させます。 期限切れ feature flag の掃除 (feature-flag-cleanup) MiiTel Phone では、新機能や通話まわりのような壊れたときの影響が大きい変更を feature flag で囲い、OFF のまま main にマージしたうえで、QA や社内テナントでの先行利用を経てから全環境で有効化することがあります。問題が起きたときも、コードを巻き戻す代わりにフラグを OFF に戻すだけで引き返せます。 flag は使い捨てで、安定したら flag と不要になった旧コードを削除する決まりで、放置すると誰も触れない謎のフラグが負債として残るため、各フラグに削除期限も設定しています。 期限切れフラグを Slack に通知して CI を fail させるワークフローはあったのですが、通知されても掃除の PR を作る作業が残るため対応が滞りがちで、一時は 15 件が同日に失効を迎える状況になっていました。 そこで通知の一歩先、削除 PR の作成までを自動化しました。対象は「配布設定が全環境で有効化済みで、かつ期限切れ」のフラグだけです。条件付き配布中のフラグの削除はリリース判断そのものなので、AI には触らせず人間に残します。 フラグの削除は定義を 1 行消して終わりではありません。ガードで分岐していたコードの無条件化に加え、それによって死んだコードの連鎖的な削除までがセットです。この作業パターンをそのままプロンプトに落とし込み、最後に grep でフラグ名の残存ゼロを検証させています。 処理しなかった期限切れフラグも、理由つきで実行ログに残させています。自動化が「何をやらなかったか」を隠すと、カバーされているように見えて漏れる領域ができるためです。 claude-code-action と gh-aw の使い分け どちらも GitHub Actions 上で Claude を動かす仕組みで、どちらでも実装できる仕事は多くあります。ただし対等な選択肢ではありません。 gh-aw は安全装置が組み込まれた専用ツールです。Markdown を書くだけで動く代わりに枠が決まっていて、副作用は safe-outputs で宣言できるものに限られます。外部サービスへの起票のような語彙にない副作用や、AI の出力を自分のコードで検証・分岐してから実行する構成は組めず、イベントに反応する対話的な用途も不得意です。 claude-code-action は自前のワークフローに AI ステップを埋め込む部品なので、トリガーも後段の処理も副作用も自由に設計できる代わりに、権限の最小化・出力の検証・キルスイッチはすべて自分で用意することになります。 タスクが gh-aw の枠に収まるなら gh-aw のほうが少ない記述で安全に済み、はみ出すなら claude-code-action で自分で設計する、という使い分けです。 さいごに 4 つの事例は別々のツールに見えますが、やっていることは 1 つです。判断の型が決まっている作業を AI に寄せ、人間のレビューを本質的な変更に再配分する。そしてそれを安全に成立させているのは、モデルの賢さよりも、判定と実行の分離・人間の最終判断・キルスイッチといった CI 側の設計でした。 この記事が、CI と AI の連携を設計する際の参考になれば幸いです。 参考文献 Agentic Code Review - Addy Osmani 不要なレビューをAIにまかせてAIコーディングの環境改善を加速した - Findy Tech Blog Kent Beck『Tidy First?: A Personal Exercise in Empirical Software Design』 anthropics/claude-code-action GitHub Agentic Workflows (gh-aw) Evergreen - GitHub Next
はじめに 昨今、仕様駆動開発 (Spec-Driven Development) の名前を耳にする機会が増えてきたと感じています。MiiTel Phone チームにおいても数ヶ月ほど OpenSpec というツールを使用して仕様駆動開発を試みていました。結論として、OpenSpec の採用は取りやめることにしました。今回は OpenSpec をベースに仕様駆動開発を試してみて感じた課題や所感について共有いたします。 前提 フロントエンド開発における採用です 新規開発ではなく、既存プロダクトの開発における導入です ⚠️ 注意書き 本記事は仕様駆動開発の手法や OpenSpec などのツールそのものを非難する意図はありません。 あくまで「MiiTel Phone チームでは合わなかった」というだけであり、 仕様駆動開発が有益であるかどうかは、チームの文化や開発フローなどによっても異なってくると思います。 そのため、最終的には実際にツールや手法などを試してから判断いただくことを推奨いたします。 📚 仕様駆動開発 (Spec-Driven Development) について 概ね「まず Spec を書き、それをベースに AI エージェントにコードを書いてもらう手法」のことを指すことが多いのではないかと思います。 Spec は AI エージェントと人の双方に対して信頼できる情報源として振る舞うことが想定されます。 martinfowler.com OpenSpec について OpenSpec は Node.js 製の仕様駆動開発のためのフレームワークです。 github.com OpenSpec を採用したのは以下の理由からです。 まずは既存のツールを使用して進めた方が自分達で仕組みを整備するよりも手間が少ないと考えたこと Node.js 製であるため、フロントエンド開発においては普段使用しているパッケージマネージャーを使用して導入・管理ができること シンプルであり、導入にあたっての心理的障壁が比較的低いこと 様々な AI エージェント (Claude Code, Gemini, Codex など) をサポートしていること 使い方 # インストール $ mise use npm:@fission-ai/openspec@latest $ openspec init # Claude Code での利用例 $ claude > /opsx:new 仕様駆動開発を試みた背景 仕様駆動開発を試みたのは、下記が目的でした。 AI コーディングエージェントは実装や仕様の背景などに関する知識を保持していないことに加え、セッションごとに記憶がリセットされてしまう。必要なコードが削除・改変されてしまうなど、意図せぬコード生成が行なわれてしまうケースをできる限り防止したい。 新規メンバーでもプロジェクトのキャッチアップを行いやすくするために、暗黙知をきちんとドキュメント化する仕組みがあると便利ではないかと考えていました。 OpenSpec により仕様駆動開発を実践し、Git リポジトリ内に要求や過去の設計における意思決定の背景などが自然と蓄積されることにより、これらの課題が自然に解消されることを目論んでいました。そして、仕様駆動開発を支援してくれる特定のツールを採用することによって実践のハードルを低下させつつ、チーム内で方法を統一できるようにすることを期待して、まずは導入コストが低いと思われる OpenSpec を導入することにしました。 OpenSpec ってどうなの? まず OpenSpec においては openspec/specs/<spec>/spec.md に信頼できる情報源としての仕様が蓄積されます。具体的には以下のようなフォーマットの Markdown によって仕様が記述されます。 # Example Specification ## Purpose This app supports a simple, reliable TODO management experience for individuals. ## Requirements ### Requirement: Creating and managing TODO items The TODO app MUST allow a user to create, view, and complete TODO items. #### Scenario: Creating a new TODO - **GIVEN** a user is signed in to the TODO app - **WHEN** the user enters a title and submits the form - **THEN** a new TODO item is created with status "Incomplete" - **AND** the new TODO item appears in the TODO list #### Scenario: Completing a TODO - **GIVEN** a TODO item exists with status "Incomplete" - **WHEN** the user marks the TODO item as completed - **THEN** the TODO item's status becomes "Completed" - **AND** the TODO item is visually distinguished as completed in the list 実装の開始時に /opsx:new コマンドを実行すると、OpenSpec は対話的にやり取りしたを内容ベースに openspec/changes/<change> ディレクトリへ以下のようなファイルを作成してくれます。 design.md - 実装予定の機能に関する設計をまとめたドキュメント proposal.md - 実装予定の機能に関するハイレベルな要約をまとめたドキュメント tasks.md - 実装時に行うべきタスクをチェックリスト形式でまとめたドキュメント <spec>/spec.md - 本実装を進めることによって openspec/specs/<spec>/spec.md に対して生じる仕様への差分をまとめたドキュメント 開発が完了したら、 /opsx:archive コマンドを実行することで、 openspec/changes/<change>/<spec>/spec.md が openspec/specs/<spec>/spec.md へマージされた後、 openspec/changes/<change> の各ドキュメントが openspec/changes/archive/<change> へ移動されます。 これにより、 openspec/specs/<spec>/spec.md が常に最新化されつつ、過去の実装における意思決定の背景や仕様に関する変化の過程などが openspec/changes/archive/<change> へ蓄積されてゆきます。 まず実装に取り掛かる前に design.md を作成してくれる点は良い点に感じました。事前にチーム内で design.md をレビューすることで、実装に取り掛かる前に考慮漏れがないか検討することができます。 また、OpenSpec は tasks.md のチェックリストに順次チェックをつけながら開発を進めてくれるため、途中まで開発を進めてもらった状態で一度セッションを中断し、後から新しくセッションを作り直して開発を再開してもらうような進め方などもやりやすかったです。 ただし、後述する課題により、少なくとも MiiTel Phone チームの開発においては「わざわざ OpenSpec などのツールを使ってもあまり期待した効果は得られなさそう」というのが率直な結論でした。 OpenSpec がマッチしないと感じた理由について なぜいまいちマッチしないのか考えたところ、4 つほど要因があるのではないかと思いました。 フロントエンド開発との相性がそこまでよろしくない 手間に見合うほどの効果を実感できなかった 他の手法によって代替可能であること 仕組みとしてはやや貧弱であること 1. フロントエンド開発との相性がそこまでよろしくない フロントエンド開発における関心ごとの大半はプレゼンテーションレイヤーに偏りがちです。ドメインレイヤーの関心ごとなどと比較して、自然言語によって「何が正しいか」を形式的に定義・記述することが難しいケースが多いと感じました。 また、プレゼンテーションレイヤーはドメインレイヤーにおける制約や振る舞いなどと比べて変化しやすく、頻繁に Spec の見直しが必要となってしまうケースが多いです。結果として手間がかかると感じてしまうケースがありました。 逆にドメインに関する関心ごとの比率が高いバックエンド開発においての方が仕様駆動開発との相性は良いのではないかと感じました。 2. 手間に見合うほどの効果を実感できなかった ※ そもそも MiiTel Phone チームにおいては、OpenSpec の導入以前から、要件定義の段階で要求や制約などをあらかじめ Notion ドキュメントにまとめてから開発に取り掛かるケースが多かったです。 OpenSpec における openspec/specs/<spec>/spec.md はあらかじめフォーマットが規定されているため、一貫したフォーマットを維持できるなどのメリットもあります。しかし、使い慣れている Notion ドキュメントと比較するとどうしても表現力に制限があると感じるケースが多々ありました。また、全体的にコーディングエージェントに生成してもらった文章はやや冗長になりがちで、人が仕様や要求を理解するためのドキュメントとしてはあまり効果を実感しづらかったという事情もあります。 OpenSpec がドキュメントを生成してくれることにより事前にレビューができるなどのメリットはあるものの、逆に更なるレビューや編集の必要性が発生することにより、却って手間がかかってしまうケースがあるとも感じました。 ※ ただし、この課題は OpenSpec の問題というよりは、MiiTel Phone チームにおける開発プロセスとの相性の問題であると思います。 3. 他の手法で代替可能であること OpenSpec をベースに仕様駆動開発を進めることで、以下のようなメリットが得られます。 過去の設計や実装における意思決定の履歴をリポジトリに記録として残すことができる。 開発に取り掛かる前に OpenSpec が生成した design.md をチーム内でレビューすることで、考慮漏れを探すことができる。 ただし、過去の意思決定の履歴をリポジトリへ記録することは、Git のコミットメッセージをきちんと記述することによって代替可能です。そしてコミットメッセージをきちんと記述することによるコストはコーディングエージェントによって大幅に緩和されています。 また、過去の意思決定の記録は、リポジトリ内に残さずとも、ADR を Notion データベースなどで管理しておき、Notion MCP などによって検索できるように整備しておくことによって代替可能です。 そして、過去の設計や実装における意思決定の履歴がリポジトリ内にファイルとして蓄積されていく ( openspec/changes/archive ) ことにより、コーディングエージェントが意図せぬタイミングで古いドキュメントを参照してしまい、却ってコード生成の精度が低下してしまうというデメリットが発生する懸念もあることに気づきました。 4. 仕組みとしてはやや貧弱であること ドキュメントはとても重要なものであるものの、仕様駆動開発における Spec はあくまで静的なドキュメントでしかありません。 そのため、AI エージェントが生成したコードが正しく仕様に則っている状況を維持し続けるためには、結局、信頼性の高いテストコードやコードレビューなどが必要です。 コストを掛けて投資をしていくのであれば、テストコードの信頼施の改善や拡充などにより力を入れ、テストコードを仕様として扱ってゆく方針で進めた方が効果が高いのではないか、という結論に至りました。 tech.revcomm.co.jp ただし、過去の意思決定の記録を残すために ADR を記述することは依然として価値があるとは感じており、このようなプラクティスは今後も実践し続ける想定です。 今後について 最終的に OpenSpec はいまいちチームにおける開発の進め方とマッチせず、メリットを活かし切ることもうまくできなかったこともあり、廃止をすることに決定しました。逆に OpenSpec が想定するやり方にチームの開発プロセスを合わせるという方法もありそうですが、そこまでして導入するメリットも薄い、との判断に至りました。 Notion で ADR や要求などをまとめたドキュメントを用意し、必要に応じて Notion MCP を活用して必要なドキュメントを参照できるようにすれば十分ではないかというのが結論です。 OpenSpec を試していく中で良かったと感じたこととしては、まず、要件や要求などをまとめたドキュメントを用意し、それに基づいて開発を進めるプロセスそのものはそこまで悪くはないと感じました。技術的な詳細はコーディングエージェントに委ねつつ、人はよりハイレベルなポイントに対して注力しやすくなります。 今後は ADR のより積極的な活用や、Notion との連携強化、テストコードの拡充や信頼性の改善などを進めていくことで、より効果的にコーディングエージェントを活用していきたいと考えています。
Introduction Hello, Jose here. This time, I joined Anthropic on the second day of its two-day event focused on developers and founders using Claude Code. The conference was organized into a fixed schedule following three tracks: Founder stage (founders using Claude to solve industry problems) Builders stage (people with little or no programming background shipping software using Claude) Workshops (Hands-on sessions about the Anthropic ecosystem). There are other excellent reports of the event ( [1] [2] [3] ), so this time I’ll write a collection of lessons from the talks I joined. Most AI projects by large corporations end in failure According to a 2025 MIT study, 95% of AI projects by major corporations yield zero return. RAND reports an 80% failure rate, while Gartner reports that more than 60% of AI projects were abandoned by 2026. The presenters at Tsukumo Labs’ talk “The last mile is the spec” argue that part of the problem lies in the lack of specifications. And as a tool to solve that problem, they are developing Tsukumo Log, an app that can use computer logs to reproduce and automate tasks (sending emails, writing and running scripts, etc). Some of their findings during the implementation were: With modern AI (LLMs), you can usually use computer operation logs to reproduce and automate operations, but high quality test data is critical: what are you feeding Claude and how can you interpret the results it gives? In agentic development, we should focus on the inverse process: define the expected answers given by Claude and backtrack them with a myriad of hypothesis.. Should Claude manage everything? In the old days of software engineering (five years ago), the business side waited for code to be written (Can engineering get to this by Friday?). Yet now, code can be written at neck-breaking speed. The problem has shifted to decision-making: what do we ship and who should be responsible for that shipment? Myrealtrip’s CTO, Wonjin Hur, experienced this first-hand when her team shifted to LLMs.They were working on a voice agent to handle customer calls asking for airline refund policies. Originally, they left Claude handle the whole workflow: parsing the data, computing the refund, and answering customers in natural language. Yet, this opened their system to a new class of hard-to-debug bugs. So they changed their focus. They separated the deterministic Python code for parsing and calculating the refund amount and let Claude handle the calls using that information. The results are revealing: they reduced expensive (and unnecessary) reasoning, with the added benefit of predictability for their business logic. Sometimes Claude isn’t wrong; the client is. In their book Pragmatic Programmer, Andrew Hunt and David Thomas argue we should actively use assertions to check for the impossible. This is exactly what happened to Gahee Seo at Federation. She described an unexpected challenge of building an HS Code classifier: customers could write in terms that only they understand! Misclassifications occurred because the input was ambiguous. The lesson? Build an input quality gate or put a human in the loop for ambiguous input. Final thoughts The event was great. We had a chance to talk to Anthropic engineers coming from San Francisco, and the banquet was delicious. Anthropic had a good sense for the omiyages, too! Claude plushie
はじめに こんにちは。Research Engineerの髙瀬です。 最近のAI開発では、モデルが生成したテキストの品質を人間が手動でチェックする代わりに、別のLLM(大規模言語モデル)に採点させる「LLM-as-a-Judge」という手法が広く使われるようになっています。 以前の記事でも、LLM-as-a-JudgeをGPT-4とChatGPTで実際のタスクに適用し、「実務採用前に精度検証をすること」の重要性を紹介しました。 tech.revcomm.co.jp 今回はその一歩先の問いを扱います。 「評価者のLLMが、自分自身の回答を不当に高く評価してしまうのではないか?」 従来の人手評価と比べて、LLM-as-a-Judgeには以下のメリットがあります。 圧倒的なスピード:数千件のサンプルをすぐに採点できる コスト効率:専門家による人手評価より大幅に安価 しかし「LLMが別のLLMを採点する」という設定には、この根本的な問いが付きまといます。この問題を「セルフバイアス(自己優遇バイアス)」と呼びます。 本記事で紹介する論文は、このセルフバイアスを統計的に厳密に測定するフレームワークを提案したものです(昨年8月発表)。 元論文: Play Favorites: A Statistical Method to Measure Self-Bias in LLM-as-a-Judge 著者: Evangelia Spiliopoulou, Riccardo Fogliato ほか(Amazon Web Services) 公開日: 2025年8月12日 LLM-as-a-Judge とは何か まず前提知識として「LLM-as-a-Judge」を整理しましょう。LLM評価のシナリオを具体的にイメージすると、こうなります。 あるプロンプト(質問や指示)に対して、複数のLLM(例:GPT-4oとClaude)がそれぞれ回答を生成する その回答の品質を、別の評価者LLMが「5段階で何点か」「役に立つか否か」などの軸で採点する 採点結果をもとに、どのモデルが優れているかを判定する この仕組みが研究・開発現場で広まっているのは、数千件・数万件のテキストを迅速に評価できるためです。しかし、採点者自身が「被採点者でもある」状況が生まれると、採点の公正性が問われます。 「えこひいき」問題:2種類のバイアス この論文が注目するのは、以下の2種類のバイアスです。 ① セルフバイアス(自己優遇バイアス) 自分が生成した回答に、不当に高いスコアをつける傾向です。 たとえばGPT-4oが評価を担当するとき、GPT-4oが生成した回答とClaude 3.5 Sonnetが生成した回答が同じ品質であっても、GPT-4oは自分の回答をより高く採点してしまうかもしれません。これがセルフバイアスです。 ② ファミリーバイアス(同族優遇バイアス) 自分と同じ「ファミリー(開発元・系統)」のモデルが生成した回答に高いスコアをつける傾向です。 たとえばClaude 3.5 Sonnetが評価者のとき、同じAnthropicが開発したClaude v2やClaude 3 Sonnetの回答を、他社モデルより高く評価してしまう現象です。 これらのバイアスは、モデルの比較評価や性能ランキングを歪め、正しい開発判断を妨げるリスクがあります。 既存手法の問題点 「セルフバイアスがある」と言いたいなら、シンプルに「自分の回答への点数が高ければバイアスあり」と判断できそうに思えます。しかし、これには2つの落とし穴があります。 落とし穴①:品質の差を見落とす もしGPT-4oが本当に高品質な回答を生成しているなら、評価者のGPT-4oが自分の回答に高い点をつけるのは正当な評価であり、バイアスではありません。「自分に高い点 = バイアス」とは言い切れないのです。 落とし穴②:採点スタイルの違いを無視する もう一つの比較方法は「人間の採点とLLMの採点を比べて、自分の回答だけ点数が高ければバイアスあり」というものです。しかしこれも単純ではありません。ある評価者LLMが全体的に人間より高めに採点するクセ(採点スタイルの違い)があった場合、その差分をバイアスと誤認してしまいます。 既存の多くの研究は、これらの問題を適切に切り分けられていませんでした。 提案手法:回帰モデルで「本当のバイアス」を測る 本論文における提案手法は、「評価者LLMのスコアを線形回帰でモデル化し、その係数(重み)を最小二乗法で推定することでバイアスを定量化する」というアイデアです。 これを数式で表すと以下のようになります。 Equation(1): Modeling approach 左辺の ​は「評価者LLM が、プロンプト 、評価軸 、モデル の回答につけたスコア」です。右辺はアンダーブレースの通り3つのグループに分かれています。 Human alignment は、評価モデル固有の採点スタイルを補正する項です。これには、採点基準が全体的に「甘いか辛いか」を示す項 と、人間が判定した品質の差をどの程度スコアに反映させるかという感度 が含まれます。 Self-bias は、評価者が自分自身の生成した回答を採点する際にのみ適用されるバイアス項であり、本解析において最も重要な指標となります。 Family-bias は、採点者と同一の開発元(ファミリー)に属するモデルによる回答に対して加算される偏りを示します。 このモデルにおいて鍵となるのが、セルフバイアス係数である の値です。この係数の正負によって、以下の3通りの解釈が可能になります。 :本当の品質が同じでも、自分の回答を高く評価する :セルフバイアスなし :逆に自分の回答に厳しい(ネガティブセルフバイアス) なぜ人間の参照スコアが必要なのか モデルに登場する「Human alignment」(第三者による品質評価)が重要な役割を果たします。例えば次の3つのシナリオを考えましょう。 シナリオ 評価者LLMの 回答品質 他モデルの 回答品質 参照スコアなしで見た差 A 低品質 高品質 差が小さい → バイアスが過小評価される B 同程度 同程度 差が明確 → バイアスが正確に見える C 高品質 低品質 差が大きい → バイアスが過大評価される シナリオAの補足として、 評価者LLMの回答は低品質でも、セルフバイアスによってスコアが底上げされます。一方、他モデルの高品質な回答には底上げがないため、この2つの力が打ち消し合い、スコアの差が実際のバイアスより小さく見えます。シナリオCはこの逆です また、論文の Figure 1 はこの3つのシナリオを可視化したものです。青い点が評価者LLM自身の回答、オレンジが他モデルの回答で、縦軸のズレ がセルフバイアスを表します。 Figure 1: セルフバイアスの3シナリオ(論文 Figure 1 より) 人間の参照スコアを「品質の基準」として組み込むことで、品質差の影響を取り除き、純粋なバイアスだけを測定できるようになります。 統計的有意性の確認 推定した が「本当にゼロではない(バイアスが実在する)」かどうかは、90%信頼区間(有意水準10%)を使って検証しています。信頼区間がゼロを含まない場合に「統計的に有意なバイアスあり」と結論づけます。通常は95%信頼区間が使われることも多いですが、本論文では計量経済学の実証研究で慣例的に用いられる10%水準を採用しています。また、不均一分散に対して頑健標準誤差(White標準誤差)を使用しており、モデルの仮定が一部崩れても推論が有効になるよう工夫されています。 実験設定 データセットの規模 596件のプロンプト(質問応答・要約タスク) 9つのLLMが各プロンプトに回答を生成(同一モデルが生成者にも評価者にもなる) → 計 5,364件の回答 各回答を9つの評価者LLMすべてが採点 使用したモデル ファミリー モデル Claude(Anthropic) Claude v2, Claude 3 Sonnet, Claude 3.5 Sonnet GPT(OpenAI) GPT-3.5 Turbo, GPT-4o Llama(Meta) Llama 3 8B, Llama 3 70B Mistral Mistral 7B, Mistral Large 評価軸(6次元) 各回答は以下の6つの観点でスコアリングされます。 評価軸 内容 完全性 必要な情報がすべて含まれているか 簡潔性 無関係な内容を含まず焦点が絞れているか 論理的堅牢性 論旨の流れが明確か 論理的正確性 事実的に正確か 有用性 ユーザーにとって役立つか 忠実性 入力内容を正確に反映しているか(主に要約タスク) 人間によるアノテーション 本実験では、LLM-as-a-Judgeのスコアからセルフバイアスを切り分けるために「回答の本当の品質」を示す参照スコアが必要です。その参照スコアとして人間アノテーターの採点結果を使用しています。 各回答は、インハウスの一般アノテーター3名が全評価軸についてLikertスケール(5段階・3段階・7段階)に基づいて採点しました。 さらにアノテーターの品質検証として、別の専門家チームが210件のサンプルに対して正解スコアの範囲(gold range)を設定し、一般アノテーターのスコアがその範囲内に収まるかを確認しました。バイナリの正誤ではなく専門家のスコアレンジへの収まり具合で品質を評価しており、結果は評価軸ごとに84〜95%、平均91%が範囲内に収まり、アノテーターの品質は高水準であることが確認されました。 実験結果 Figure 3: セルフバイアスとファミリーバイアスの推定値(論文 Figure 3 より) セルフバイアスの測定結果 Figure 3(左)はモデルごとのセルフバイアス推定値(90%信頼区間つき)を示しています。GPT系とClaude 3.5 Sonnet(青紫)の推定値が正方向に、Llama 3 8B(黄)が負方向に偏っており、本論文ではこれらのモデルでセルフバイアスの存在が明確に論じられています。 GPT-4oとClaude 3.5 Sonnetは、より高性能なモデルであるにもかかわらず、顕著なセルフバイアスを示しました。逆にLlama 3 8Bは「ネガティブセルフバイアス」、つまり自分の回答に厳しすぎる傾向が確認されました。評価軸「忠実性」において特に顕著で、人間評価では他モデルとの差が小さいにもかかわらず、Llama 3 8Bは自分の回答の忠実性を著しく低く評価していると解釈されています。 ファミリーバイアスの測定結果 Figure 3(右)はファミリーごとのバイアス推定値(90%信頼区間つき)を示しています。 ClaudeファミリーとGPTファミリーには明確なファミリーバイアスが確認されました。Claude 3.5 Sonnetは、Claude v2やClaude 3 Sonnetの回答を、他社モデルより高く評価します。GPTモデルも同様に、GPT系モデルの回答を優遇する傾向があります。本論文では、LlamaとMistralにはファミリーバイアスが見られないと解釈されてます。 小さな差でも大きな影響がある 実験では各モデルのスコアが0.90〜1.00の狭いレンジに集中していました。そのような状況では、セルフバイアスの大きさ(約0.02ポイント)がモデルランキングの順位を変えてしまうほど影響力を持ちます。 たとえば「Claude 3.5 SonnetとGPT-4oのどちらが優れているか」を比較するとき、スコア差がわずか0.02程度であれば、えこひいきによる底上げだけで結論が逆転するリスクがあります。 実践的な示唆 この研究から、LLM-as-a-Judgeを実務で使う際の重要な指針が得られます。 ① 独立した参照スコアを確保する 人間アノテーションや信頼できる第三者モデルのスコアが入手可能な場合は、論文の手法を使ってセルフバイアスとファミリーバイアスを推定し、スコアから差し引くことができます。これにより、補正された公正な評価スコアが得られます。 ② 複数ファミリーの評価者LLMを使う 人間によるアノテーションが一切利用できない場合は、異なるファミリーの評価者LLM(例:GPT、Claude、Llamaを組み合わせる)を使うことで、バイアスを相互に打ち消し合う効果が期待できます。人間によるアノテーションはコストがかかるため、現実的な選択肢として、コストパフォーマンスに優れています。 ③ 評価軸・タスクごとにバイアスが異なることを意識する セルフバイアスは質問応答タスクで大きく、要約タスクでは小さい傾向がある 「論理的正確性」や「忠実性」などの評価軸では、モデルによってバイアスの大きさが変わる 単一の評価軸に依存せず、複数軸での分析を行うことが望ましい Figure 4 は評価軸別(左)・タスク種別(右)のセルフバイアスの分布を示しています。Llama 3 8B の忠実性(×)が特に左に外れていることや、質問応答タスク(●)の方が要約タスク(▲)より全体的にバイアスが大きい傾向が読み取れます。 Figure 4: 評価軸・タスク別のセルフバイアス(論文 Figure 4 より) まとめ 本論文のポイントを振り返ります。 LLM-as-a-Judgeには「セルフバイアス」と「ファミリーバイアス」が存在する 既存手法は品質差や採点スタイルの違いを適切に制御できていなかった 線形回帰モデルによる統計的フレームワークを使えば、バイアスを正確に分離・定量化できる GPT-4oとClaude 3.5 Sonnetには顕著なセルフバイアスとファミリーバイアスがある一方、Llama 3 8Bは逆に自分に厳しすぎる「ネガティブセルフバイアス」を示す バイアスの大きさは小さく見えても、スコアが密集しているモデル比較では大きな影響を持つ LLMを使った自動評価は今後さらに広まっていくと考えられます。LLM-as-a-Judgeを利用する際には、単純に「LLMに採点させればOK」と思うのではなく、どのモデルがどんなえこひいきをするのかを理解し、適切に補正しながら使うことが重要になります。 参考資料 [1] E. Spiliopoulou et al., "Play favorites: A statistical method to measure self-bias in LLM-as-a-judge," arXiv preprint arXiv:2508.06709, 2025.
はじめに こんにちは、RevComm AI Div. の id:tmotegi です。 RevComm では Copilot ・要約・コーチングなど、LLM を活用した機能が急速に増えています。当初は1〜2つのサービスが LLM を直接呼び出すだけでしたが、新機能のリリースと並行して利用するプロバイダーも広がり、気づけば各サービスが個別に Claude(Bedrock)、OpenAI(Azure)を呼び出す構成になっていました。 しばらくはそれでも回っていましたが、LLM を利用するチームが増え、利用するモデルやプロバイダーも広がるにつれ、いくつかの問題が表面化してきました。 クォータ・レートリミット対応がサービスごとに分散 ─ 以前はクォータの上限が低く、サービスごとに複数アカウントを作成してレートリミットを回避していました。レートリミットに引っかからないよう、各チームが個別にリトライや切り替え処理を書いており、同じエラーハンドリングコードが各所に散在していました。 新モデル対応のコストが高い ─ 新しいモデルが出るたびに、複数サービスでクライアントコードやモデル名を個別に更新する必要がありました。 コストの内訳が見えない ─ 「どの機能が、どのテナントで、どのモデルをどれくらい使っているか」がプロバイダーをまたいで把握できず、プロバイダーの請求書を見ても機能別・テナント別の内訳がわかりませんでした。コスト増の原因を特定するだけで時間がかかっていました。 国内処理の適用漏れリスク ─ 顧客要件でデータを国外に出せない機能では、利用できるモデルが制限されます。その制約を各サービスで個別に管理していたため、適用漏れが起きる潜在的なリスクがありました。 これらを解決するために、社内の LLM 呼び出しを一本化する LLM Gateway を開発しました。 目的は大きく2つです。 マルチプロバイダーの統一 ─ 単一インターフェイスで Bedrock / Azure OpenAI / Vertex AI を透過的に扱い、クライアント側のコードを変えずにモデルを切り替えられるようにすること。 コストの可視化 ─ リクエストごとの利用状況を記録し、テナント・機能・モデル別にコストを集計できるようにすること。 最初から自分たちで実装すると決めていたわけではありません。まずは既製の LiteLLM Proxy Server をそのまま使うことを検討し、最終的に litellm をライブラリとして使って自分たちで実装する形に落ち着きました。 なぜ LiteLLM Proxy Server を最初の選択肢にしたのか? LLM Gateway の構築を決めたとき、まず検討したのは LiteLLM Proxy Server です。100 以上のプロバイダーを OpenAI API 互換の単一エンドポイントで束ねる OSS で、フォールバック制御・コスト追跡・Observability 連携が標準で揃っています。 はてな や 弁護士ドットコム など、国内企業の技術ブログでも導入事例が多く公開されており、LLM Gateway が必要になった企業が最初に検討する選択肢です。 LiteLLM Proxy Server が標準で持つ機能は非常に充実しています。 OpenAI API 互換エンドポイント(既存の OpenAI クライアントを向き先だけ変えてそのまま使える) 複数プロバイダーへのルーティングとロードバランシング リクエストボディで fallbacks フィールドを指定するフォールバック制御 コスト追跡・予算管理(モデルごとのトークン単価を設定しコストを自動計算) Langfuse、Datadog などの Observability 連携 特に「リクエストにフォールバック順序を書ける」機能は、私たちが必要としていた要件と重なっていました。 { " model ": " claude-sonnet-4 ", " messages ": [ ... ] , " fallbacks ": [ " gpt-4o ", " gemini-2.0-flash " ] } クライアントがフォールバック順序をリクエストに含めて送るだけで、LiteLLM Proxy Server 側がエラー時に次のモデルへ切り替えてくれます。導入するだけで多くの機能が手に入り、運用面の作り込みも不要になる魅力的な選択肢でした。 なぜ自分たちで実装したのか 「国内処理の制約」と「フォールバック戦略」を連動させる複雑なルーティングロジックを、LiteLLM Proxy Server の async_pre_call_hook などのフック処理内に無理に詰め込みたくなかった、というのが大きな理由です。 LiteLLM Proxy Server には async_pre_call_hook でリクエストを受け取り、 data["model"] を書き換えることで動的なルーティングを実現するカスタムハンドラーがあります。私たちの要件もこれで実装できます。ただし私たちのルーティングには、単純なモデル切り替えにとどまらない要件が重なっていました。 リクエスト単位で国内処理を強制できる ─ 特定のリクエストは日本国内リージョンで動作するモデルのみ使用する フォールバック先も国内リージョンのモデルに限定する ─ 国内処理が必要なリクエストがフォールバックしても、海外リージョンには流れない クライアントがフォールバック順序を指定できる ─ 「まず Claude、だめなら OpenAI」という優先順位をリクエストに書ける 要件1だけなら async_pre_call_hook でモデルを国内リージョンのものに差し替えれば済みます。要件3だけなら LiteLLM Proxy Server 標準の fallbacks フィールドをそのまま使えます。問題は1・2・3の 組み合わせ です。「国内処理を要求しつつ、かつクライアント指定の優先順位でフォールバックし、かつフォールバック先も国内に限定する」という連動した制御は、カスタムハンドラーとフォールバック機構をまたいだロジックになります。 async_pre_call_hook はシンプルなモデル切り替えやロギングには向いていますが、複数の制約を連動させるルーティングには向きません。そこで、この部分は専用のロジックとして自前で持つことにしました。 なお、LiteLLM Proxy Server は litellm という Python ライブラリをベースに作られており、litellm は単体でもライブラリとして利用できます。そこで Proxy Server は使わず、 litellm をプロバイダー抽象化のライブラリとして使い、ビジネスロジックは自分たちで設計・実装する 方が長期的な見通しが良いと判断し、自分たちで実装することを選びました。 litellm をどのようにライブラリとして組み込んだか? EKS 上の LLM Gateway アーキテクチャ図 FastAPI ベースの Python サービスとして実装し、DDD のレイヤー構成を採用しています。 litellm はインフラストラクチャ層の LLMClient クラス内だけで使い、ドメイン層はどのプロバイダーを使っているかを知りません。Bedrock / Azure OpenAI / Vertex AI の 3 プロバイダーに対応しながらも、上位レイヤーのビジネスロジックはプロバイダーの違いを意識せずに済みます。 class LLMClient: async def create_completion( self, deployment: ModelDeployment, request: ChatCompletionRequest ) -> ChatCompletion: response = await litellm.acompletion(model=model_string, ...) return ChatCompletion.model_validate(response.model_dump()) litellm が担うのは「Bedrock / Azure / Vertex AI の差異を吸収する HTTP クライアント」という役割だけです。各プロバイダーは認証方式と API の形式が異なりますが、litellm がその差異を吸収します。 フォールバックの判断やモデル選択といったルーティングロジックは、すべて自分たちのドメイン層に置いています。レイヤーを分けることでプロバイダーへの依存がコードベース全体に広がるのを防ぎ、ルーティングロジックの単体テストも書きやすくなりました。 設計で工夫したこと ① フォールバック戦略をクライアントが選べるようにする フォールバック戦略は auto / specified / none の 3 種類から、リクエストごとに revcomm_options.fallback_strategy で指定します。処理の特性に合わせて可用性・品質・エラー伝播の挙動を選べるようにした設計です。 戦略 動作 使いどころ auto 同一モデルの別リージョン・別アカウントに自動でフォールバック とにかく止めたくない処理 specified クライアントが指定した順番でフォールバック モデルの品質差が許容できない処理 none フォールバックしない(エラーをそのまま返す) ─ auto は可用性を優先するモードです。あるアカウントの Bedrock がレートリミットに引っかかった場合、同一モデルを提供する別アカウント・別リージョンのデプロイメントへ自動で切り替えます。国内処理を要求しているリクエストでは、切り替え先も国内処理に対応したデプロイメントに限定されます。 specified はクライアントが「まず Claude、だめなら OpenAI」という形で優先順位を明示するモードです。出力品質に敏感な処理では、意図しない品質の異なるモデルへの切り替えを避けたいためこちらを使います。 none はエラーをそのまま返します。 なお、フォールバックが発動するのは、レートリミットやタイムアウト・サーバーエラーなどの一時的なエラーに限られています。リクエスト内容の不備(不正なパラメータ、コンテキストウィンドウ超過など)ではフォールバックせず、そのままエラーを返します。意図しないモデルへの切り替えが起きるリスクを抑えるためです。 国内処理を要求するリクエストでは、フォールバック先も国内リージョンのモデルに絞られます。国内制約とフォールバック順序が連動して動く部分です。 ② データが国内で処理されることを保証する 顧客との契約でデータの国内処理が義務付けられているケースでは、一つでも適用漏れがあるとビジネスリスクになります。LLM Gateway ではモデル設定に国内・グローバルのフラグを持たせ、ゲートウェイがリクエスト単位で国内処理を強制します。この判断をアプリケーション側に委ねず、ゲートウェイで一元管理する設計です。 モデルごとのリージョン・プロバイダー設定は YAML で管理しています。 gateway_region: ap-northeast-1 deployments: - model_name: claude-sonnet-4 provider: bedrock region_scope: regional # 国内リージョン region: ap-northeast-1 - model_name: gpt-4o provider: azure region_scope: global # グローバルリージョン region: japaneast region_scope: regional のモデルが「国内処理」として扱われます。リクエストで国内処理を要求した場合、ゲートウェイが該当するモデルを選択します。 この設計の利点は、国内処理の判定ロジックがゲートウェイ内に閉じていることです。新しいモデルを追加するときも、YAML に region_scope を適切に設定するだけで国内処理の制約が適用されます。環境ごとの設定差分は別ファイルで管理しているため、呼び出し元のサービスは環境を意識せず同じリクエストを送るだけで済みます。 ③ モデルを切り替えても呼び出し元のコードを変えずに済むようにする ゲートウェイは OpenAI Chat Completions API 互換のインターフェースを提供しています。既存の OpenAI クライアントはエンドポイントの向き先を変えるだけで移行でき、モデルを切り替えるときも model フィールドの値を変えるだけです。RevComm 固有の制御は revcomm_options フィールドに集約しています。OpenAI クライアントからは extra_body={"revcomm_options": {...}} として渡すため、標準 API との後方互換性を維持しています。 { " model ": " claude-sonnet-4 ", " messages ": [ ... ] , " revcomm_options ": { " require_domestic_processing ": true , " prefer_prompt_caching ": true , " fallback_strategy ": " specified ", " fallback_models ": [ " gpt-4o ", " gemini-2.0-flash " ] , " labels ": { " tenant_id ": " tenant-123 ", " service ": " transcription ", " team ": " asr " } } } オプションを省略すれば通常の OpenAI API リクエストとほぼ同じ形式です。 レスポンスには revcomm_metadata を付与し、実際に使ったプロバイダーやフォールバックの発生有無を呼び出し元に返します。 { " revcomm_metadata ": { " actual_provider ": " bedrock ", " actual_model ": " claude-sonnet-4 ", " fallback_used ": true , " fallback_reason ": " RateLimitError: ... ", " gateway_latency_ms ": 42 , " domestic_processing ": true } } フォールバックが透過的に起きると、呼び出し元からは「なぜこのモデルが使われたのか」が見えなくなります。メタデータをレスポンスに含めることで、デバッグ時にフォールバックの発生をすぐに確認でき、コスト分析にも活用できます。フォールバックが頻発している場合は、特定のプロバイダーや時間帯に問題がある兆候として検知できます。 ④ 誰がどのモデルをどれだけ使っているかを把握できるようにする LLM Gateway のもうひとつの柱がコストの可視化です。LiteLLM Proxy Server には Cost Tracking 機能が標準で備わっていますが、litellm をライブラリとして使う場合はリクエストごとのコスト計算値は得られるものの、保存・集計・可視化は自前で用意する必要があります。リクエストごとのトークン数・プロバイダー・モデル・レイテンシを PostgreSQL に記録し、Redash でテナント別・機能別に可視化しています。ロギングはレスポンスを返した後にバックグラウンドで行うため、ゲートウェイのレイテンシには影響しません。 labels は JSONB カラムで、リクエスト時にオプションとして渡したキーバリューをそのまま保存します。JSONB にしているのは、ラベルのキーをあらかじめ固定せず、各サービスが必要なディメンションを自由に付与できるようにするためです。 SELECT labels->> ' tenant_id ' AS tenant_id, actual_model, SUM (total_tokens) AS total_tokens FROM llm_usage_logs WHERE created_at >= now() - INTERVAL ' 30 days ' GROUP BY labels->> ' tenant_id ' , actual_model ORDER BY total_tokens DESC ; テナント別・サービス別・チーム別など、集計軸をスキーマ変更なしに追加できます。「新しいチームが使い始めたのでチーム別集計を出したい」という要望があれば、そのチームが team ラベルをリクエストに含めるだけで対応できます。テーブル定義の変更もマイグレーションも不要です。 各サービスは推論リクエスト時に tenant_id や service をラベルとして渡すだけで、コスト集計の基盤が整います。プロバイダーの請求書を見ていてもわからなかった「どのテナントの、どの機能が、どのモデルをどれくらい使っているか」が一本のクエリで出せるようになりました。 テナント別・機能別コストを可視化するアナリティクスダッシュボードのイメージ 振り返って LiteLLM Proxy Server のカスタムハンドラーでも、技術的には実現できたはずです。ルーティングロジックとラベル集計を自社のドメインロジックとして持ったことで、要件の変化にコードで素直に対応できています。 フォールバックロジックとモデル選択ロジックを外部依存のない純粋な Python クラスで実装したため、単体テストも書きやすくなりました。「国内処理を要求した場合に region_scope: global のモデルは候補から除外される」「フォールバック戦略が none のときはエラーがそのまま伝播する」といったロジックを、LiteLLM Proxy Server を起動せずにテストできます。 一方で、コスト管理 UI や API キーのセルフサービス発行など、LiteLLM Proxy Server が標準で持つ運用機能は自前で用意する必要がありました。現時点では、コスト集計は Redash のダッシュボードで対応していますが、API キーのセルフサービス発行など長期的には整備が必要な領域もあります。 カスタム性が単純なうちは LiteLLM Proxy Server で十分です。 まとめ 各サービスに分散していた LLM 呼び出しを一本化するため、マルチプロバイダー統一とコスト可視化を目的に社内 LLM Gateway を開発しました LiteLLM Proxy Server を検討しましたが、国内処理制約 × フォールバック戦略の連動が設計想定外になるため、自分たちで実装することにしました litellm はプロバイダー抽象化のライブラリとして使い、ルーティングロジックは自分たちのドメインとして設計しました リクエストに付与する labels(テナント ID・サービス名など)を PostgreSQL の JSONB カラムに記録することで、スキーマを変えずに集計軸を追加できるようにしました
Background The release of DeepSeekMath[1] and DeepSeek-R1[2] brought Group Relative Policy Optimization (GRPO) into the spotlight, and it quickly became one of the most widely adopted post-training algorithms in the open-source LLM community. GRPO's significance lies in making Reinforcement Learning with Verifiable Rewards (RLVR) practical at scale. In domains like math reasoning and code generation, correctness can be checked directly: a solution is either right or wrong, and a unit test either passes or fails. GRPO turns these discrete, verifiable signals into an effective training signal for large language models, and the strong reasoning results from DeepSeek-R1 made it one of the de facto recipes for RL-based post-training. How does GRPO work How does GRPO work As shown in the figure, GRPO training can be split into three steps: Sampling. For each question q, the policy from the previous update samples a group of G candidate answers {o₁, …, o_G}. Sampling within a group is what gives GRPO its name. All subsequent computation is relative to this group. Reward / Advantage. Each answer is scored independently by a reward model or a rule-based reward function, producing per-sample rewards {r₁, …, r_G}. These rewards are then normalized within the group (subtracting the group mean and dividing by the group standard deviation) to produce the advantages {A₁, …, A_G}. The group itself serves as the baseline, so no separate value network is required. Update. The advantages drive the policy gradient term, while a KL divergence between the current policy and a frozen reference model keeps the update from drifting too far. These two terms are combined into the GRPO loss, which is then used to update the policy for the next iteration. Different variants Original GRPO[1,2] Published by DeepSeek as part of DeepSeekMath and later adopted in DeepSeek-R1, GRPO is the foundation that all later variants build on. For each prompt q, the old policy samples a group of G candidate outputs. Each output is scored by a reward function, and the advantage is computed by normalizing the reward against the group's mean and standard deviation. The group itself serves as the baseline, so no separate value network is required. The training objective applies token-level importance-sampling ratios with PPO-style clipping, averaged first within each response (by 1/|o_i|) and then across the group (by 1/G). GRPO Dr.GRPO[3] Published by Sea AI Lab, the National University of Singapore, and Singapore Management University, Dr. GRPO ("GRPO Done Right") observes that the original GRPO objective introduces two systematic biases: a response-level length bias from the 1/|o_i| normalization, which inflates short-response gradients and dilutes long ones, and a question-level difficulty bias from the std denominator in the advantage, which over-weights easy questions. Dr. GRPO removes both terms, dropping 1/|o_i| from the loss and std from the advantage; the rest of the algorithm is left unchanged. Dr.GRPO DAPO[4] Published by ByteDance, DAPO (Decoupled Clip and Dynamic Sampling Policy Optimization) refines GRPO with a set of practical fixes discovered while scaling RL training to long chain-of-thought reasoning. It introduces four key techniques: Clip-Higher. The upper and lower clipping bounds are decoupled into ε_low and ε_high, with a larger upper bound. This gives low-probability tokens more room to grow, promoting exploration and avoiding the entropy collapse commonly seen in long-CoT training. Dynamic Sampling. Groups in which all responses are either fully correct or fully incorrect produce zero advantage and waste compute. DAPO filters out such degenerate groups and resamples until the batch contains enough informative groups, improving both training efficiency and stability. Token-Level Policy Gradient Loss. Instead of averaging the loss per response and then across the group (as in GRPO), DAPO sums over all tokens in the group and divides by the total token count. This prevents long responses from being unfairly down-weighted. This is a critical fix in long-CoT scenarios where response lengths vary by orders of magnitude. Overlong Reward Shaping. Responses that exceed the maximum length are no longer assigned an arbitrary penalty. DAPO instead applies a soft, length-aware reward shaping that reduces reward noise and stabilizes training near the length limit. Together, these changes make DAPO substantially more stable than vanilla GRPO at scale, and it has become a common starting point for open-source long-CoT RL pipelines. DAPO GSPO[5] Published by Alibaba, GSPO (Group Sequence Policy Optimization) argues that GRPO's token-level importance ratio is ill-justified: applying off-policy correction independently at every token accumulates high-variance noise across long sequences, and PPO-style clipping makes the instability worse. GSPO replaces it with a sequence-level importance ratio s_i(θ), defined as the geometric mean of the per-token ratios over the full response. Clipping is then applied once per sequence rather than per token, which lowers gradient variance and proves especially valuable for MoE models where routing changes amplify token-level noise. GSPO Conclusion With the success of GRPO and RLVR, RL post-training has become a core part of the LLM training pipeline. Given a well-designed reward, an LLM can be continually improved through a simple sample–reward–update loop. In practice, however, the high variance inherent to sampling makes GRPO training prone to instability, and a wave of variants (Dr. GRPO, DAPO, GSPO, and others) has emerged to address different facets of this problem. As reward design and training algorithms continue to co-evolve, RL post-training is likely to remain one of the most active and impactful directions in LLM development. Reference [1] Shao, Zhihong, et al. "Deepseekmath: Pushing the limits of mathematical reasoning in open language models." arXiv preprint arXiv:2402.03300 (2024). [2] Guo, Daya, et al. "Deepseek-r1: Incentivizing reasoning capability in llms via reinforcement learning." arXiv preprint arXiv:2501.12948 (2025). [3] Liu, Zichen, et al. "Understanding r1-zero-like training: A critical perspective." arXiv preprint arXiv:2503.20783 (2025). [4] Yu, Qiying, et al. "DAPO: An Open-Source LLM Reinforcement Learning System at Scale." arXiv preprint arXiv:2503.14476 (2025). [5] Zheng, Chujie, et al. "Group sequence policy optimization." arXiv preprint arXiv:2507.18071 (2025).
Research Engineerの石塚です。スペインのバルセロナで開催されたIEEE International Conference on Acoustics, Speech, and Signal Processing 2026 (ICASSP 2026)という国際会議に参加し、「AUTOMATIC ESTIMATION OF SPEAKER DIARIZATION ERROR RATE BASED ON FEATURES OF AUDIO QUALITY AND SPEAKER DISCRIMINABILITY」というタイトルで研究発表を行ってきました。本レポートでは、ICASSP 2026の概要と発表した研究について紹介します。 ICASSP 2026とは ICASSP 2026とは、音声・音響・信号処理の分野において最大規模を誇る国際会議IEEE International Conference on Acoustics, Speech, and Signal Processingの年次大会であり、2026年5月4日から8日にかけて、スペインのバルセロナにあるバルセロナ国際会議場で開催されました。今回の大会が掲げるメインテーマは「Where Signals Meet Intelligence」であり、これは長年培われてきた伝統的な信号処理技術と、近年の飛躍的な進化を遂げている人工知能(AI)やデータサイエンスとの融合を表しています。 技術的な議論の対象は非常に多岐にわたり、音声認識や言語処理、画像・ビデオ解析といった馴染み深い領域から、次世代通信、さらには脳・コンピュータ・インターフェースといった最先端の分野までを幅広く網羅しています。また、大学や研究機関といったアカデミアのみならず、世界をリードする企業が数多く参加する、産学連携の巨大なプラットフォームとしての側面も持っています。 ICASSP 2026 RevComm Researchでは、業務で行っていた研究開発の成果の一部を論文としてまとめ、本国際会議に投稿して受理されたため、ポスター発表を行ってきました。以降は、その研究内容について説明します。 発表論文: AUTOMATIC ESTIMATION OF SPEAKER DIARIZATION ERROR RATE BASED ON FEATURES OF AUDIO QUALITY AND SPEAKER DISCRIMINABILITY Speaker Diarization技術とその課題 会議の自動議事録作成などにおいては、「誰がいつ話したか」を特定するSpeaker Diarization(SD)の技術が欠かせません。しかし、この技術は、下記のような環境に応じて、その難易度が大きく変わるという特徴があります。 高レベルの環境ノイズや反響 マイクの品質の低さ 声の特徴が似ている複数の話者の存在  そして、単一の静的なSDシステムで、あらゆる現実の音響環境において、高い精度と、計算コストのバランスを維持するのは簡単ではありません。常時、大規模で高精度なSDモデルを使うこともできますが、それではクリーンで簡単な環境においては計算コストの無駄になる可能性があります。そこでより実用的な解決策となるのが、事前に対象の音声をSDした時のエラー率を推定する適応戦略です。つまり、難易度の高い音声のときだけ、より堅牢なSDアルゴリズムに動的に切り替えるシステムとなります。この実行時の戦略によって、クリアな音声に対する不必要な計算コストを最小限に抑えつつ、ダイアライゼーションの精度を確保することができます。また、システムの運用時においても、事前にSDした時のエラー率を推定することができれば、対象の環境での推定エラー率が高いときにユーザにマイクなどの録音環境の改善を促したりといったこともできるでしょう。 論文の提案:Diarization Error Rateを自動で予測する! 音声認識システムにおける単語エラー率(WER)の自動推定は広く研究されていますが、話者ダイアライゼーションのエラー率であるDiarization Error Rate(DER)の自動推定はまだあまり開拓されていない分野です。Diarization Error Rateは 下記の式 で計算されます。 ここでの は、非音声を音声と誤って認識した部分の長さ、 は音声を非音声と誤って認識した部分の長さ、 は話者を誤って推定した部分の長さ、 は正解の音声の総時間です。 本記事で紹介する論文では、正解データを必要とせず、音声信号から直接SDアルゴリズムのDERを自動的に推定する手法を提案しています。その推定の鍵となるのが、Diarizationのパフォーマンスに影響を与える「2つの要因」を捉える特徴量を抽出するアプローチです。 音声品質の特徴量: バックグラウンドノイズなどの環境要因によって引き起こされる音声信号の劣化を捉えるように設計されています。 話者の音響的な識別可能性の特徴量: 対象となるSDモデルが生成する話者クラスターの分離可能性を評価するもので、潜在的な話者の混同エラーに関連付けられます。つまり、対話に参加している話者らの声質が似ていて聞き分けるのが難しい状態になっていないかを確認します。 提案手法:DER(Diarization Error Rate)の自動推定アーキテクチャ 本論文が提案するシステムは、正解データを用いることなく、入力された対話の音声信号から直接DERを推定する手法です。このシステムは、ダイアライゼーションの精度低下を招く要因を定量化するため、「音声品質(Audio Quality)」と「話者の識別可能性(Speaker Discriminability)」の2つの特徴量抽出モジュールを実行します。そして、抽出された特徴量をもとに回帰モデルで最終的なDERの予測値を出力するという構成になっています。 DER の自動推定アーキテクチャ 音声品質特徴量(Audio Quality Features) 背景ノイズなどの環境要因による音声信号の劣化を捉えるため、以下の2つの指標を抽出します 。 VAD Difference Rate(VAD検出差分率): 軽量なVoice Activity Detection(Weak VAD)と、ノイズに対して堅牢なDNNベースのVAD(Robust VAD)による音声区間検出結果の差分を利用した特徴量です。ノイズの多い環境下では、軽量な信号処理によるVADなどはノイズを音声として誤検出しやすいため、検出される総音声長が堅牢なVADよりも長くなる傾向があります 。この差分率が高いほど音声品質が低く、結果としてDERが高くなると仮定しています。本特徴量の概念図を下記に示します。 概念図 DNSMOS: 音声の知覚的品質(Mean Opinion Score)を予測するモデルであるDNSMOSを利用します。対象のSDシステムから得られた各音声セグメントのスコアを算出し、セグメントの長さに基づいた加重平均を取ることで、録音全体の品質スコアを求めています。 話者の識別可能性特徴量(Speaker Discriminability Features) SDの話者クラスタリングの難易度を測るため、対象のSDアルゴリズムが生成する話者埋め込み(Speaker Embeddings)から、以下のクラスタリング評価指標を算出します。つまり、声質が似ているなどで、聞き分けるのが難しい状態になっていないかを確認します。 Davies-Bouldin Index (DBI): クラスターの凝集度(クラスター内の分散)と分離度(クラスター間の距離)を考慮して、クラスタリングの品質を定量化する指標です 。マクロな視点から全体のクラスター構造を評価し、DBIが低い(クラスターが密集し、かつ他と十分に離れている)ほど話者の音響的特徴が明確であり、DERが低くなると仮定します。 Silhouette Score: 各データポイント(埋め込み表現)が、自身が属するクラスターにどれだけ適合しているか(凝集度)と、最も近い隣接クラスターのすべてのデータポイントからどれだけ離れているか(分離度)を比較する指標です 。DBIがマクロな指標であるのに対し、こちらは各データポイントの妥当性をミクロな視点から評価するものであり、両者は相補的に機能します。 回帰モデルによるDER推定 上記2つのモジュールから得られた計4つの特徴量(VAD Difference Rate、DNSMOS、DBI、Silhouette Score)を特徴量として結合し、回帰モデル(本研究では主にSupport Vector Regressionを使用)に入力します 。これにより、事前の正解アノテーションなしで最終的な予測DERを出力することが可能となります 。 評価実験 今回の提案手法でどの程度の精度でDERを予測できるのかを確かめるため、以下のような条件で検証が行われました。 検証に使ったデータセット 実験には、話者ダイアライゼーションの評価でよく使われる2つの公開データセットを組み合わせて使用しました 。 VoxConverse: テレビ番組の動画から構成されるデータセットです 。 MSDWild: 日常会話の動画から構成されるデータセットです 。VoxConverseと比較すると、声の重なりやノイズが多く、ダイアライゼーションを高精度で行うのがより難しいデータになっています 。 対象となるSDモデル エラー率予測のターゲットとして、仕組みの異なる2つの事前学習済みモデルを使用しました 。 pyannote.audio 3.1 (Pyan): End-to-End Neural DiarizationとVector Clusteringを組み合わせたEEND-VC手法を採用したSpeaker Diarizationツールキットです。 Wespeaker (Wesp): 従来型のアルゴリズムを採用したSpeaker Diarizationツールキットです。 実験結果 まず、VoxConverseとMSD Wildの合計899件のテスト音声データについて、音声の各特徴量と、音声をSDした時のDERの構成要素とのピアソンの積率相関係数を確認しました。相関係数は絶対値が1に近いほど「予測と実際の結果がピッタリ連動している」ことを意味します。結果を見ると、特徴量によって、DERのどの構成要素と関係が強いのかが異なっていることがわかります。 結果 さらに、VoxConverseとMSD Wildの学習音声データでSupport Vector Regressionの回帰モデルを構築します。そして、テスト音声データについて、回帰モデルが算出した「推定エラー率」と「実際のエラー率」のピアソンの積率相関係数を算出しました 。その結果、相関係数は以下のようになりました。 pyannote.audio 3.1 (Pyan)の場合:0.806 Wespeaker (Wesp)の場合:0.800 0.8以上という数値は一般的に「非常に強い相関がある」と評価されるため、かなり的確にエラー率を予測できることがわかりました。また、各特徴量とDERの間の相関の値より、SVRで構築したモデルによる推定値との相関の値の方が大きくなっていることからも、4つの特徴量を組み合わせることで、より高い推定精度が得られていることがわかります。下記に推定エラー率と実際のエラー率の散布図を示します。 推定エラー率と実際のエラー率の散布図 まとめ 本記事では、国際会議ICASSP 2026にてRevComm Researchが発表した、Diarization Error Rate自動推定技術について紹介しました。本研究では、「音声品質」と「話者の識別可能性」という2種類の特徴量を抽出し、回帰モデルを用いて事前のエラー率を予測する手法を提案しました。評価実験の結果、推定値と実際のエラー率の間で約0.8という高い相関が確認され、高精度な予測が可能であることが実証されました。この技術を活用することで、音声の難易度に応じたモデルの動的な切り替えによる計算コストの削減や、ユーザーへの録音環境の改善提案など、より効率的で実用的なシステム運用への応用が期待できます。 今後もRevComm Researchでは、実社会の課題解決に繋がる研究開発を推進し、機会があればこのような国際的な場でも成果を発信していきたいと考えています。
Hi, I'm Jose. I work as a Full Stack Software Engineer at Revcomm and also help with Developer Relations. We organize events, write blog posts, and give talks across Japan and overseas. Right before the New Year, I spoke with an R&D manager. He said R&D would submit a paper to ICASSP 2026 and asked if I could join if it was accepted. It sounded like a great opportunity to learn and catch up on the latest in Digital Signal Processing and Speech-Language Processing. Yet, hearing ICASSP made me pause. The name sounded familiar. Back in 2016, when I was working on my thesis, my professor suggested I look for conferences to submit to. ICASSP in Shanghai was one option, but since my thesis was about automatic ontological generation, it didn't seem like the right fit for a signal processing conference. I submitted it somewhere else, but I don't remember much after that. Fast forward—the paper was accepted, and by May 1st, I was on a flight to Barcelona. Preparations Barcelona Poster and talk recommendations Lessons (TLDR;) Preparations At Revcomm, I get to work with AI in lots of ways. We use Claude Code for vibe coding, rely on GitHub Copilot for code reviews, and experiment with new ways to improve our development pipelines. For example, we recently implemented a pipeline to analyze audit logs using BigQuery MCP, so customer support members can request specific logs via prompting. This change reduced the time to retrieve audit logs from hours to just a few minutes and has already helped the support team resolve customer issues more quickly, along with reducing the burden on the tech side. We also hold internal tech meetings where teams share what they know. I've heard R&D talks about Speaker Diarization, generative error correction, and fake detection. The topics themselves weren't new to me, but I wondered how much value I could really get from the conference. So, what did I do? I decided to focus on what I wanted to learn. With AI tools today, it's easier than ever to find the information you need quickly. First, I used Google Deep Research to look up the latest trends in Speaker Diarization and other relevant topics at the company. Starting in April, I read one or two interesting papers each day until the conference proceedings were released. The ICASSP proceedings came out on May 4, and they were huge, more than 2GB and over 4,000 papers. To avoid missing any relevant sessions, I set up a workflow using Claude Cowork: I uploaded the proceedings index and my own reading list from the past month. Then, I asked Claude to filter out the presentations for a three-hour period from the tasks I was most interested in. As context, I also included information from the papers I’d read. I iterated over step 2 with different parameter values. For instance, I wanted to connect with Japanese researchers, so I searched for presentations by Japanese universities. I also searched for researchers whom I have interacted with before. With this approach, to my surprise, I discovered that my former Computer Science professor was a co-author on one of the papers. With the information provided, I manually created a schedule for each day and checked with my colleagues to avoid unnecessary overlap and cover more ground. Barcelona ICASSP 2026 ran from May 4 to May 8, in the middle of Japan’s Golden Week. The first day was all about tutorials. We used that day to acclimatize and print the poster. Our first real day was May 5. Barcelona is truly one of Europe’s major tech hubs—friendly, warm, safe, and full of energy. Our Airbnb was about 15 minutes on foot or 13 minutes by bus, which is pretty typical for Barcelona. The conference venue was impressive and felt more like a Hilton hotel. Barcelona International Convention Center We registered, picked up badges, and received a Barcelona Hello Card for free metro transport. The conference offered two main experiences: walking the poster area or attending track presentations. Each day, I spent an hour on posters and used the remaining time on presentations. The schedule was packed, lunch ran an hour to an hour and a half, and coffee breaks were around 3:30 pm, with work zones also available for those who needed them (a lot actually). The next day, on May 6, we had our poster presentation first thing at 9:00 a.m. It went really well. We got questions from university students and professors from all over the world. It helped that the poster’s position was easier to see. Poster print. Beautiful That evening, I attended the Young Professionals Networking Event at La Mar, a seaside restaurant with a fantastic view of yachts docked on the port. Although the website said it was a ten-minute walk from the main venue, I got lost along the way. Fortunately, I wasn’t alone, and several of us ended up arriving together, starting conversations before we even entered. The remaining days settled into a routine of an early breakfast at 7:00 and a walk to the conference by 9:00 a.m. Lar mar's view Poster and talk recommendations Here were a few standout posters and talks. Planning ahead helped maximize relevant takeaways, but some spontaneous choices were also impactful. What Statistics and AI Offer Each Other? by Emmanuele Candès By integrating statistical insights into AI processes—specifically for data collection, quality control, and uncertainty quantification—we can build more reliable and efficient AI systems that adhere to the high standards of scientific research. Fully unsupervised score ensembling (FUSE) only slightly outperforms supervised learning. Throw more compute, throw more data, and things will go well. A related lecture on this is live on YouTube: https://www.youtube.com/watch?v=Nj9hcoHhglw KAME: A tandem architecture for enhancing knowledge in real-time speech-to-speech conversational AI. The engineers at Sanaka AI were very generous in explaining their work. They also wrote a blog that summarizes it:  https://pub.sakana.ai/kame/ Entangling  classical- and quantum-domain information/signal processing by Lajos Hanzo I attended this plenary talk because I was curious about quantum computing and expected to be overwhelmed by technical jargon. I was pleasantly surprised. Professor Hanzo was very expressive and connected well with the audience. As people were leaving for lunch, he asked, “I know you are hungry, but just before you go, how many of you were interested in Quantum after this talk?” I raised my hand, and I wasn't the only one. Signal Quality Assessment in the Era of Foundation Models Video quality, playback smoothness, and network performance are the main challenges in signal quality assessment. This topic really resonated with me as an engineer. https://ieeexplore.ieee.org/document/11460679 Scaling an Audio-Visual Quality Assessment Dataset via Crowdsourcing People often focus on the weakest aspect of a system but judge it based on its strongest feature. https://ieeexplore.ieee.org/document/11463693 And many more. See this year’s proceedings here: https://ieeexplore.ieee.org/xpl/conhome/11460365/proceeding Lessons (TLDR;) When I joined  a local conference  in 2016, Machine learning and Data Science were driving AI. In NLP, I saw papers on vectorization, autoencoders, LDA, and more. A decade later, LLMs, agents, and quantum computing are popular. Still, the vibe is the same. People are excited, talking passionately. Methods have evolved, but the people remain. I won’t wait another decade to go again (ICL? Maybe next year in Toronto). Here’s what I learned: Go to networking events—they are the best way to meet people and learn from peers. My strategy was to look for paper authors or poster presenters, as it was easier to start a conversation around them. Overall, everyone was friendly, so that made things easier. If you are unsure how to start a conversation, asking someone what brought them to the event is a good opener. If you're an engineer, tutorials are great. You'll feel most at home. Pick your track ahead and know what to see. It can get overwhelming, so use AI tools to prioritize. Big conferences also have industry tracks. Don't hesitate to ask questions. The people presenting posters are always happy to answer. It's the people who make these trips worthwhile. You meet researchers, engineers, and entrepreneurs, make connections, start collaborations, and maybe make friends. Write notes on each session you attend. Even a brief summary is enough. If possible, categorize them by tags. Without this, I wouldn’t have written this article. Thanks to Revcomm for the opportunity. You can read our R&D paper at the link below. It proposes a novel method to estimate the speaker diarization error rate. https://ieeexplore.ieee.org/document/11463892 Thank you for reading. You can find me on  LinkedIn .
RevCommの熊谷です。 AIエージェントを使った開発が当たり前になり、コーディングの速度は確かに上がりました。一方で、フロントエンドは自由度が高いぶん、油断するとすぐに 設計がブレる という次の問題も見えてきます。今回は、それをどうやって抑えているかを書いてみます。私たちのチームではルールを文章で書き下すよりも、「参考実装を指す」ことを軸にしています。 困っていたこと 紹介するのは、MiiTel Admin という管理画面での話です。自社サービス「MiiTel(ミーテル)」を支える、Vue 3 + Nuxt 4 + TypeScript で作られた Web アプリケーション。ミーテルの成長とともに機能が増え続け、現在は 92 ページの大規模な管理画面になっています(ここ半年で 8 ページ増えてる...!)。 このプロダクトはフロントエンド専任ではない開発者(バックエンド・モバイル・音声 AI エンジニア)も気軽に機能追加に入ってくれます。それ自体はありがたいのですが、そのぶん設計のブレが起きやすい環境でもあります。 エージェント導入前は、既存のページをコピペして少し直すような書き方が多かったんですよね。コピペにはもちろん問題もあるのですが、結果として構成が大きく外れにくく、書く速度もそこまで速くなかったので、設計のずれはレビューで拾えていました。 ところが、エージェントが入ってから一気に状況が変わりました。 みんな書く速度が一気に上がった 設計のブレがレビューで追いつかない量で発生するようになった 開発者ごとに使うエージェント・モデルが違う(Copilot / Claude / Cursor / Devin、モデルもいろいろ) このまま放っておくとカオスになる、というのが課題感でした。 基本方針 打ち手はいくつかあるのですが、根っこの方針はシンプルです。 エージェント・モデルは開発者の好きなものを使ってOK(強制しない) ただし「何を見て書くか」だけは全エージェント共通にする 人間がレビューで都度直すのではなく、書かれる前に方向を揃える エージェントを「設計のブレ防止装置」として使うイメージです。本業で使い慣れたエージェントをそのまま使える方が、開発者にとっても嬉しいですしね。 やっていること エージェント向けのドキュメント整備は ルールを文章で書き下す アプローチが多いと思いますが、私たちはむしろ 参考実装そのものを指す アプローチを軸にしています。文章のルールはどうしても実装とズレていきますが、コードは常に最新だからです。以下の 3 つは、その方針から派生した工夫です。 1. リファレンス実装と「参考の優先順位」を明示する 1ページを徹底的にリファクタしてお手本にする、というやり方です。 前回のブログ でも書いたパターンですね。 現在は SMS テンプレート機能 src/pages/sms-template/ を参照実装にしていて、エージェントが見る .github/copilot-instructions.md にも明記しています。 ## 作業の進め方(最優先) - 変更行数が合計 20 行を超える場合は必ず守ること。 1. コードを書く前に対応するスキルを呼び出す 2. 使用するスキルが 3 つを超える場合は、作業分割をユーザーに提案する — ユーザーが「そのまま進めて」と回答すれば継続してよい 3. 既存のコンポーネント構造・設計に従う 4. 明示的な指示がない場合は新しい設計パターンの導入はしない 5. 判断に迷った場合は、一般的なベストプラクティスに従うよりも、既存コードの設計・パターンに合わせることを優先する ## 参考実装の優先順位 1. `src/pages/sms-template/`(composable 分離・Pinia Setup Store・vee-validate・i18n が揃ったパターン) 2. `src/pages/softphone-setting/`(Pinia 移行済みの最新実装) 3. 同カテゴリの既存画面(同じユースケース・UI 構成) 4. `src/composables/`、`src/components/organisms/` の既存実装 参考実装の場所さえ明示しておけば、エージェントは常に最新のコードを見にいってくれます。ドキュメントの更新が遅れても、自然と最新パターンに揃っていくというのが、このやり方のいちばん効いている点です。 「既存パターンを優先する」と明文化しておくと、エージェントがプロジェクト側に寄せてくれやすくなります。バックエンドエンジニアもフロントを触る体制なので明示的に書いていますが、フロント専任メンバーで固まっているチームでは、ここまで強く書く必要はないかもしれません。 2. コンテキストに乗せる情報を絞る ドキュメントは育てると肥大化しがちです。とくに先ほどの参考実装アプローチでは、エージェントが skill / instructions に加えて実コードまで読みにいくので、コンテキストはなおさら膨らみやすい。コンテキストが膨らむと、 エージェントの注意がそれて、長いルールほど読み飛ばされやすくなる 。動作そのものも遅くなります。なので、「何を読ませないか」もセットで設計しています。 トピックごとに skill に分割: state-management / i18n-rules / vee-validate-form など。関連する作業のときだけ読み込ませる 小規模変更ではスキップ可能に: 「変更行数が 20 行を超える場合は必ず skill を呼ぶ」のように、軽微な編集では読み込みを省略できる逃げ道を書いておく 人間向けドキュメントと分離: 設計ドキュメント ( docs/architecture.md など) は人間向けに保ち、エージェント向けは簡潔な技術用語の羅列に近い形に 最後の点は試行錯誤がありました。最初は copilot-instructions.md から docs/architecture.md を参照させていたのですが、結局コンテキストが膨らむだけで効果が薄かった。エージェント向けには「読みやすさ」より「短さ」を優先して、技術用語の羅列に近い形に分離したら、必要な情報だけ拾ってもらえるようになりました。 3. ライブラリの標準パターンに素直に乗る 2 はドキュメントを絞る工夫でしたが、もう一歩進めて、 そもそもドキュメントを書かなくて済むようにする こともできます。エージェントがすでに知っている公式パターンに乗せれば、プロジェクト固有のルールを書く必要がありません。 これは、考え方が大きく変わったところです。 以前は「Nuxt/Vue 固有機能をバックエンドエンジニアにまで覚えてもらうのは忍びない」と思っていて、Vanilla 寄りの実装で吸収していました。たとえばページの表示条件は、各ページで router.push を使って書いていました。 // src/pages/sms-templates.vue (抜粋) const router = useRouter(); const { isSmsAvailable } = usePermission(); onMounted(() => { if (!isSmsAvailable.value) router.push('/'); }); 今は Nuxt の definePageMeta + middleware に置き換えました。各ページは宣言を 1 行書くだけです。 // src/pages/sms-templates.vue (抜粋) definePageMeta({ permission: (ctx: TenantContext) => ctx.isSmsAvailable, }); 判定の本体は middleware に集約。全ページぶんが、ざっくりこの数行で完結します。 // middleware/permission.global.ts (抜粋) export default defineNuxtRouteMiddleware(to => { const ctx = usePermission(); if (!to.meta.permission?.(ctx)) return navigateTo('/'); }); この設計は Vue Router 公式の route.meta + navigation guard パターン をほぼそのまま踏襲したもので、プロジェクト固有の語彙も permission / TenantContext の 2 つに絞れているので、エージェントは公式パターンの知識をほぼそのまま使って書けます。 振り返ると、MiiTel Admin は SSR を採用していないこともあり、これまで Nuxt らしい機能を十分に活かせていませんでした。それがエージェントの登場で逆転しました。 エージェントが補完してくれるなら、標準パターンに乗る方がブレない 。学習コストの高い固有機能も気軽に取り入れられるようになり、Nuxt を選んだメリットをようやく実感できているところです。 ルールを長く運用する工夫 一次ソースを 1 箇所に集約する ドキュメントは長く運用すると、コピーが増えてあちこちでずれていく、というのが起きがちです。一次ソースを 1 ヶ所に集約して、各エージェントからは同じファイルを参照させるようにしています。 .github/copilot-instructions.md と .github/instructions/*.instructions.md を起点に、Claude 用 .claude/skills/ や Cursor からも同じファイルを参照。コピーを作らないので更新がずれません。一番制約がきつい Copilot(2026 年 5 月時点では外部ファイルを参照できず、 .instructions.md 自体に内容を直接書く必要があります)に合わせて書いておけば、他のエージェントでも破綻しにくい、というのも分かってきました。 たとえば Claude 用の .claude/skills/i18n-rules/SKILL.md は、 .github/instructions/ 配下の対応するファイルを cat するだけのシンプルな中継ファイルです。 --- name: i18n-rules description: Use this skill when creating or editing i18n files (...). --- !`cat ${CLAUDE_SKILL_DIR}/../../../.github/instructions/i18n-rules.instructions.md` これで Claude も Copilot も結局は同じ .github/instructions/i18n-rules.instructions.md を見ているので、更新は 1 箇所で済みます。 PR レビューでルールを育てる それでも、思ったような PR が出てこないことはあります。レビューで違和感を覚えるときって、実装した本人も「なんか変だな」と感じていることが結構あるんですよね。そんな時は、本人が使っているエージェント環境で、そのまま 「今回どの skill / instructions 読んだ?参考実装は何を見た?」と聞いてもらいます。自分の環境で再現するより速いし、本人もその場で原因を切り分けやすい。ルール自体が足りないのか、ルールへの導線が悪いのか、参考実装が古いのか。「あ、これ参照されてなかったね、書き足しておくね」みたいに、雑談ベースでドキュメント改善を回しています。 原因の切り分けが終わったら、その流れでエージェントに .github/instructions/ の修正 PR まで作ってもらうこともあります。違和感を覚えた人が、その場でルールを直す側に回れるのは、エージェント時代ならではの良さだなと感じています。 おわりに エージェント時代のフロントエンド開発で効いたのは、 「何を見て書くか」を揃えること だなと感じています。エージェントを厳しく縛るのではなく、既存設計に自然に合流できる道を整える。それができれば、速く作りながら長く保守できるプロダクトに近づけるはずです。 「スピードは出るようになったけど、設計がブレ始めた」というフェーズに入ったチームの参考になれば嬉しいです。
はじめに こんにちは。RevComm でエンジニアをしている 林 です。 MiiTel Phone では長らく ESLint + Prettier をフロントエンドの lint / formatter として使ってきましたが、約 1,300 ファイル規模の React + TypeScript プロジェクトで CI と AI コーディングループのフィードバックがどうしても重くなってきていました。 今回、Rust 製ツールチェインである Oxc ベースの Oxlint / Oxfmt へ完全移行したので、その経緯と方法、得られたメリット・デメリットについて紹介します。 Oxc とは Oxc (The JavaScript Oxidation Compiler) は、パーサー・リンター (Oxlint)・フォーマッター (Oxfmt)・トランスパイラーといった JS/TS ツールチェインを Rust で書き直す OSS プロジェクトです。今回扱うのは次の 2 つ。 Oxlint: ESLint 互換のルールセットを持つ Rust 製リンター。 公式ドキュメント では ESLint 比 50〜100 倍速いとしている。JS Plugins (Alpha) と Type-Aware Linting (Alpha) も順次拡張中 Oxfmt: 2026-02 に Beta。Prettier v3.8 の JS/TS conformance 100% を達成し、出力互換を保ったまま高速化できる 開発主体は VoidZero Inc. — Vue.js / Vite の作者 Evan You 氏が 2024 年に設立したスタートアップです。Vite / Vitest / Rolldown / tsdown / Oxc を開発しています。 Biome ではなく Oxc を選んだ理由 似た立ち位置のツールに Biome がありますが、決め手は速度や Prettier 互換ではなく、周辺ツール全体を束ねる存在 ( Vite+ ) でした。 観点 Oxc (Oxlint / Oxfmt) Biome 開発主体 VoidZero (Vite 作者の会社) Biome コミュニティ 周辺ツールとの統合 Vite+ で build / test / lint / format が同じロードマップに乗る 独立型 規則互換性 ESLint ルール名 / Prettier 出力と互換重視 独自ルールセット中心 Vite+ は Vite / Vitest / Oxlint / Oxfmt / Rolldown / tsdown を vp という単一 CLI に束ねた統合ツールチェインです。 今回のプロジェクトも将来的に Vite+ への合流を視野に入れており、その時点で Oxlint / Oxfmt に乗っておいて良かったと言える状態を作りたかったのが Oxc 採用の主な動機です。 Biome に乗ると、将来 Vite+ に揃えたいときに lint / format をもう一度乗り換えるコストが再発します。 移行理由 1. CI / pre-commit / editor の速度がボトルネックになっていた 全件 lint で 13 秒、format チェックで 5 秒というのは、単体で見ればまだ我慢できる範囲です。ただし PR ごとに何度も走り、ローカルの保存時 lint や pre-commit hook でも体感に効いてくるため、合計すると無視できない時間になっていました。 2. AI コーディング時代のフィードバックループ Claude Code や Codex などの AI エージェントがほとんどのコードを書くようになると、lint は人間による最終レビューの代替手段から、 AI がコードを書く途中でその場でミスを弾いて修正する仕組み に役割を変えます。 具体的には、エージェントの編集ごとに lint と formatter を回し、エラーを次のターンに即フィードバックして自己修正させるという使い方です。 ESLint の 1 ファイルあたり 1.72 秒という所要時間ではこのループに乗せづらく、Oxlint の 0.43 秒で初めて現実的になりました。 AGENTS.md / CLAUDE.md の自然言語ガイドは読んだ後に指示が守られるかどうかはお祈りレベルの保証しかなく、絶対に止めたい違反はツール側で機械的に弾いた方が確実です。 lint にその役割を担わせる場合、速度がそのままループの成立可否になります。加えて、一度入れたルールはその後の全セッションに効くので、AI が書くコード量が増えるほど 1 ルールあたりの効果が積み重なっていきます。 3. 設定の簡素化と依存削減 ESLint 設定は eslint.config.js が 220 行、プラグインが 11 個ぶら下がっていました。Oxlint の native plugin で同等のカバレッジが取れるなら、設定もぐっとシンプルになります。 @eslint/compat のような互換レイヤーや、フォーマッター衝突回避のための周辺パッケージも整理できます。 4. Prettier 互換性が確保された Oxfmt が Prettier v3.8 互換 100% を達成したことで、フォーマッターを乗り換えると全ファイルに差分が出るという移行最大の壁が消えました。これがなければ移行コストが見合わなかったので、Beta リリースを待ってから着手する予定でした。 移行方法 Oxc は単一ツールではなく個別のツール群なので、Prettier → Oxfmt と ESLint → Oxlint をそれぞれ独立した PR で進めました。先に Oxfmt 側を済ませてから Oxlint に取りかかる順番です。 Prettier から Oxfmt 1. 差分ゼロ確認 まずローカルで src 配下の全ファイル (約 1,300 ファイル) について、 prettier --write の結果と oxfmt の結果が一致するか git diff で確認しました。 Beta とはいえ Prettier conformance テストが通っているので、現行コードに対しては実用上問題なしと判断しています。 2. 設定ファイル変換 Oxfmt は Prettier 設定からの自動変換コマンドを持っているので、これを使います。 npx oxfmt --migrate=prettier 生成された .oxfmtrc.json の例 ↓ { " $schema ": " ./node_modules/oxfmt/configuration_schema.json ", " semi ": true , " trailingComma ": " all ", " singleQuote ": true , " printWidth ": 120 , " tabWidth ": 2 } .prettierrc / .prettierignore は削除します。 3. scripts / hooks / codegen の置き換え // package.json (抜粋) { " scripts ": { " lint:oxfmt ": " oxfmt --check 'src/**/*.{ts,tsx,mdx}' ", " fix:oxfmt ": " oxfmt 'src/**/*.{ts,tsx,mdx}' " } } pre-commit hook の Prettier エントリも oxfmt に置き換え、 graphql-codegen の afterAllFileWrite や独自 codegen スクリプトで prettier --write を呼び出していた箇所も npx oxfmt に差し替えます。CI ワークフローのジョブ名もここで変更しました。 4. eslint-config-prettier はこの段階では据え置き 公式ドキュメント の推奨に従い、この段階では eslint-config-prettier をそのまま残しました。フォーマッタと衝突する ESLint ルール ( indent など) を無効化する役割は、Prettier でも Oxfmt でも変わらないためです。 なお ESLint 本体を撤去する後続の Oxlint 移行 PR で、同パッケージも併せて削除しています。 ESLint から Oxlint Prettier 移行と違ってこちらは挙動の互換性を見るだけではなく、ルールセットの取捨選択が必要です。方針として「native plugin 主軸、必要なものだけ JS Plugins (Alpha) で補う」を取りました。 1. 完全移行か併用か Oxlint 公式ドキュメントは「小〜中規模は完全置換、大規模は ESLint との併用を推奨」としています。併用したい場合は eslint-plugin-oxlint で Oxlint が拾えるルールを ESLint 側で重複排除し、両者を直列で回すのが定石です。 { " scripts ": { " lint ": " oxlint && eslint . " } } ちなみに今回は 1,300 ファイル規模ながら完全移行を選びました。主な理由は以下です。 速度メリットが消える: 全件 lint で oxlint 0.35s + eslint 12.96s ≈ 13.31s となり、現状とほぼ変わらず、AI ループ用 hook に乗せる速度を取り戻せない 設定の二重管理: eslint-plugin-oxlint で重複排除すると、独自設定 ( no-restricted-globals のカスタム設定など) が無効化されたり、既存の inline // eslint-disable-next-line ... が "Unused eslint-disable directive" として警告化されたりした ロードマップとの整合: 将来 Vite+ に揃えたい以上、併用は足がかりにもならない 「完全移行で破綻するほど大規模か」「速度を hook ループに取り戻したいか」が判断の分岐点で、以降のステップは完全移行ルートの内容です。 2. プラグインの仕分け 既存の ESLint プラグインを、native で代替可能 / JS Plugins で残す / 捨てる の 3 つに分類します。 既存の ESLint plugin 対応方針 typescript-eslint native typescript plugin react / react-hooks native react plugin (react-hooks 同梱) jest native jest plugin unused-imports typescript/no-unused-vars で代替 react-compiler eslint-plugin-react-hooks v6+ に統合されたため native でカバー testing-library / jest-dom oxlint jsPlugins (Alpha) 経由で src/__tests__/** 限定で残す storybook 撤退。 storybook build と Chromatic 視覚回帰で代替 import ( import/order ) oxfmt の sortImports に寄せる (後述) JS Plugins は ESLint プラグインをそのまま読み込めて便利ですが、Alpha かつ Oxlint の Rust native で速いという売りを部分的に削るレイヤなので、メインでは使いませんでした。 テスト系の 2 プラグインだけは「テスト信頼性に直接効くので確実に止めたい」「スコープが __tests__ 配下に閉じている」という条件が揃ったので、限定的に採用しています。 3. 自動変換 + 手調整 flat config からの自動変換は oxlint-migrate が使えます。 npx @oxlint/migrate <optional-eslint-flat-config-path> 生成された JSON に対して、native plugin で対応するルール名 ( @typescript-eslint/... → typescript/... など) の最終調整と、JS Plugins / overrides の追加を手で行います。最終的に出来上がった構成の骨子は次のような形です。 { " $schema ": " ./node_modules/oxlint/configuration_schema.json ", " plugins ": [ " typescript ", " oxc ", " react ", " jest " ] , " jsPlugins ": [ " eslint-plugin-testing-library ", " eslint-plugin-jest-dom " ] , " categories ": { " correctness ": " error ", " suspicious ": " error ", " pedantic ": " off ", " perf ": " off ", " restriction ": " off ", " style ": " off ", " nursery ": " off " } , " rules ": { // ... プロジェクト固有の制約ルール ... } , " overrides ": [ { " files ": [ " src/__tests__/**/*.{ts,tsx} " ] , " rules ": { " testing-library/no-node-access ": " error ", " jest-dom/prefer-in-document ": " error " // ... 他のテスト系ルール ... } } ] } plugins キーを書くと Oxlint のデフォルト plugin セットを上書きします。 typescript / oxc はデフォルトで有効ですが、明示的に書いておかないと意図せず外れる事故が起きやすいので注意が必要です。 もう一つ押さえておきたいのが categories と rules の関係です。 categories は Oxlint のルールを 7 種類 (correctness / suspicious / pedantic / perf / restriction / style / nursery) で一括 ON/OFF する仕組みで、今回は correctness (明らかなバグ) と suspicious (疑わしいコード) のみ error 、それ以外は off にしています。 優先順位は rules > categories なので、 categories.correctness: "error" で一括有効化したうえで、違反が多くて即修正できないルールだけを rules: { "no-extra-boolean-cast": "off" } で個別に逃がす、という使い方ができます。 次の「違反ルールは一時 off」はまさにこの優先順位を使った運用です。 4. 違反ルールは一時 off → follow-up で再有効化 Oxlint デフォルトの correctness カテゴリには、ESLint で off にしていたルールも一部入っています。本 PR で既存コードに手を入れない方針を取ったため、違反が出るルールは一度 off にしておき、別 PR で違反修正 + 再有効化する形にしました。 具体的には no-extra-boolean-cast , no-unneeded-ternary , no-useless-rename , no-useless-catch , jest/require-to-throw-message , react/jsx-key などです。1 PR にまとめると差分が膨大になって reviewer の負担になるので、ルールごとに小さく切り出すと進めやすかったです。 再有効化漏れを防ぐため、一時 off にしたルールはトラッキングチケットに「ルール名 / 違反件数 / 担当 / follow-up PR」のチェックリストで一覧化し、各 follow-up PR から逆リンクを張る運用にしています。 一方で oxlint-migrate の出力には Oxlint で未対応のルールも並びます。今回は @typescript-eslint/no-floating-promises のような type-aware 系 (本移行のスコープ外として別途段階導入する想定) と、 storybook/* のように代替手段ありで撤退と判断したものが中心でした。 未対応ルールは .oxlintrc.json に書いても効かないため、設定からは削除しています。 5. CI / pre-commit の切り替え // package.json (抜粋) { " scripts ": { " lint:oxlint ": " oxlint --deny-warnings src ", " fix:oxlint ": " oxlint --fix --deny-warnings src " } } Prettier から Oxfmt への移行段階で残していた eslint-config-prettier も含め、ESLint 関連 devDependencies はすべて削除しています。 6. import order は Oxfmt の sortImports に寄せる eslint-plugin-import の import/order ルールに相当する機能は Oxlint native にはありません。Oxfmt の sortImports で customGroups / groups を指定すれば、 pathGroups 相当のグループ分けが再現できるので、こちらに寄せました。 // .oxfmtrc.json (sortImports 部のみ抜粋) { " sortImports ": { " customGroups ": [ { " groupName ": " internal-modules ", " elementNamePattern ": [ " <your-dir-1>/** ", " <your-dir-2>/** " ] } ] , " groups ": [ " builtin ", " external ", " internal-modules ", [ " parent ", " sibling ", " index " ] , " style ", " unknown " ] , " newlinesBetween ": true , " order ": " asc " } } これにより lint と format の責務がきれいに分かれ、import の並び替えは Oxfmt の --fix で完結するようになりました。なお sortImports を有効化したコミットは全ファイルに差分が出るので、 .git-blame-ignore-revs に登録して git blame の汚染を防いでいます。 移行したことによるメリット 効果を数字でまとめると次の通りです。 項目 移行前 (ESLint / Prettier) 移行後 (Oxlint / Oxfmt) 倍率 全件 lint (約 1,300 ファイル) 12.96s 0.35s 約 37 倍 全件 format チェック 5.2s 0.5s 約 10 倍 単一ファイル lint 1.72s 0.43s 約 4 倍 設定行数 (lint) eslint.config.js 約 220 行 + 11 プラグイン .oxlintrc.json 約 80 行 — 数字には表れない次のような効果もありました。 単一ファイル lint が hook に乗る速度に収まり、AI エージェントの編集ループ内で常時走らせられるようになった Claude Code の PostToolUse hook で確実に止められる品質チェックをかけられるようになり、エージェントが見逃すエラーを一段減らせた ESLint 10 への移行で必要になる @eslint/compat のような互換レイヤが不要になった devDependencies が ESLint 本体 + 11 プラグイン分減り、 npm install 時間と node_modules サイズが改善 import order を Oxfmt 側に寄せたことで、lint と format の責務分離がクリアになった 移行したことによるデメリット JS Plugins が Alpha なので、 testing-library / jest-dom を残している部分は互換性が壊れるリスクを抱えている type-aware ルール ( no-floating-promises など) は速度トレードオフで今回意識的に lint 層から外している。Alpha 卒業待ちというより、現状の type-aware は 1 ファイルあたり数秒オーダーで hook ループのミリ秒単位の速さに載らないという判断。該当する欠陥は tsc と統合テストでカバーし、lint 層の速度を死守する方針 ESLint / Prettier に比べてコミュニティが小さく、トラブル時の情報量が少ない さいごに 今回の移行は単なる lint / formatter の乗り換えではなく、AI のフィードバックループそのものを速くするための改善でした。lint を hook ループに載せられるか否かが、AI が書くコードの品質に直接効いてくる時代になっています。 次に視野に入れているのは Type-Aware Linting です。たとえば no-floating-promises は AI が書いたコードで起きがちな await 漏れを検出できるルールで、 tsc だけでは取りこぼしやすく本来 lint で止めたい類の問題です。ただし現状の type-aware lint は速度的にまだ重く、hook の高速ループには乗せづらいため、当面は tsc とテストでカバーしつつ常時 hook には組み込まない方針でいく予定です。 AI 時代のフロントエンド開発環境として、これからもフィードバックループ全体の最適化を続けていきたいと思います。 参考文献 Oxc - The JavaScript Oxidation Compiler VoidZero - The Future of JavaScript Tooling Vite+ - The Unified Toolchain for the Web Oxlint Linter Guide Oxlint Configuration Oxlint JS Plugins (Alpha) Oxlint Type-Aware Linting Oxfmt Beta announcement (2026-02-24) Migrate from Prettier - Oxc oxlint-migrate (GitHub) eslint-plugin-oxlint (GitHub)
はじめに 初めまして、RevComm の 楽桑 と申します。 MiiTel Call Center (CC) フロントエンドで React 18 → 19 のアップグレードを実施した。単なるバージョンアップではなく、 Semantic UI の完全削除 と Recoil から Jotai への移行 もまとめて片付けた。 この記事では、AI Agent(Claude Code)を 効率化ツール として活用し、このプロセスをどう加速させたかを紹介する。 背景 プロジェクト構成 Next.js 14 (Pages Router) + Static Export → S3 配信 UI: Semantic UI (semantic-ui-react) + MiiTel Design System (MDS) + Emotion CSS-in-JS 状態管理: Recoil リアルタイム: Apollo Client + GraphQL Subscriptions (WebSocket) React 19 アップグレードの動機 React 19 の新機能が欲しい、というより 周辺事情が同じ方向を指していた : 依存ライブラリが次々と React 19 を前提にし始めた Semantic UI が React 19 未対応かつメンテ停滞。他のアップデートの足枷になっていた Recoil は公式アーカイブ済み ついでに React 19 本体の改善(型、Actions など)も取り込める 1. React 19 本体より、周辺作業のほうが重かった 1-1. 実際に時間を使ったのは実装以外の仕事だった React 19 本体の breaking changes 対応は、実はそこまで重くなかった。 useRef の引数必須化、 RefObject<T | null> の型変更、 Symbol → string の明示変換 — いずれも機械的な修正で、数時間で片付く。 実際に時間を使ったのは 実装以外の仕事 だった: 30 以上ある依存ライブラリの React 19 対応状況を一つずつ調べる Semantic UI の撤去後に全画面で発生する CSS リグレッションを特定・修正する Recoil から Jotai への移行で、60 個以上の atom と多数の hooks 呼び出しを漏れなく置換する セッションをまたいで「前回どこまで進んだか」を記録・引き継ぐ React のメジャーアップグレードは「バージョンを上げる作業」より「上げるために必要な調査と確認」のほうが圧倒的にコストが大きい。 1-2. AI Agent を入れて変わったこと Claude Code を導入して変わったのは、コードを書く速度もだが、それ以上に 判断に入るまでの時間 が短くなった。 「このライブラリは React 19 に対応しているか?」「この CSS の差分は直すべきか受け入れるべきか?」— こういった判断の前提となる情報収集・整理・比較を AI に任せることで、人間は「何を採用するか」「その差分を受け入れるか」の意思決定に集中できた。 2. Claude Code に任せたのは「判断以外」 「判断以外」という境界にたどり着いたのは、最初からではない。まず丸投げして失敗し、そこから「何を任せて、何を任せないか」を学んだ。 2-1. 最初の失敗 ― ボタン移行を “丸投げ” したら崩壊した 最初は役割分担を決めず、AI に全部任せてみるところから始めた。 「Semantic UI の Button を全部 MDS の Button に置き換えて。」 Claude Code は長時間かけて大量のファイルを書き換えた。diff はそれっぽく見えたが、ブラウザで開くとボタンのサイズがページごとにバラバラ、アイコンが消えている箇所があり、Primary と Secondary が入れ替わっている箇所まであった。 実際のコミット履歴がその過程を物語っている: 3/5 refactor: Migrate button components to MDS ← 初回の一括移行 3/5 fix: Match reset and edit button styles ← 即座にスタイル崩れ発覚 3/5 fix: correct edit button text color 3/5 fix: match edit button layout and height 3/10 fix: resolve interactive element nesting 3/11 feat: introduce SecondaryButton component ← ラッパーコンポーネントが必要と判明 3/13 feat: enhance button styling (8コミット連続) 3/14 feat: replace SecondaryButton with StyledButton 3/17 feat: replace LinkIconButton with SupportLinkButton 3/17 fix: unify LinkButton primary colors ← 12日後、やっと安定 合計: 42 コミット / 36 ファイル / +968 -677 行 Semantic UI の Button は props が多彩で、MDS とは一対一で対応しない。つまりこの変換は機械的な一括置換ではなく、各使用箇所で “MDS ではどう表現するか” を決める判断の集合だった: Props マッピング(実際の PR より): label → children ← 機械的 icon → startIcon ← 機械的 isLoading → loading ← 機械的 isFullWidth → fullWidth ← 機械的 color = "alert" → variant = "negative" + CSS 上書き ← 判断が必要 color = "plain" → variant = "default" + 白背景上書き ← 判断が必要 color = "secondary" → SecondaryButton ラッパー新規作成 ← 判断が必要 isLinkButton → LinkButton / LinkIconButton 新規作成 ← 判断が必要 判断が必要な箇所を丸投げしたので、AI はそれっぽく見える変換を量産しただけになった。 2-2. 学び ― “丸投げ” ではなく “分解して委譲” する この失敗から、移行作業は 3 ステップに組み替えた: 対応が必要なファイルを AI に洗い出させる(この段階では置換しない) ファイルごとに対応コストを分類する(機械的置換 / props 読み替え判断 / 代替なし・自前実装) コスト大きい方から着手し、人間のレビューを必ず入れる この流れに変えた瞬間、型崩れはほぼ消えた。AI Agent は “網羅” と “分類” には強いが、判断が混ざった実装を丸ごと投げると表面的な正しさで走ってしまう。判断は人間が先に固めて、AI にはその方針に沿った実装だけを任せる。これが一番効いた基本動作だった。 2-3. 依存ライブラリ調査を AI に任せる package.json を起点に、全依存ライブラリの React 19 対応状況を横断調査させた。 Claude Code に投げたプロンプト(要約): package.json を読んで、全依存ライブラリの最新バージョンと React 19 対応状況を調査。対応済み / 未対応 / 要調査 で分類した表を作って。 未対応のものは issue やリリースノートへのリンクも付けて。 返ってきた整理表: ライブラリ 状態 備考 react-hook-form ✅ 対応済み v7.52.0 emotion ✅ 対応済み - react-konva ✅ 対応済み v19.0.1 Semantic UI ❌ 未対応 メンテ停滞 Recoil ❌ アーカイブ済み Jotai へ置き換え react-draggable ⚠️ findDOMNode 依存 自前実装必要 この表があるだけで 意思決定のコストが激減する 。人間は「何から片付けるか」を決めるだけでよくなった。 2-4. Recoil → Jotai の置き換えを AI と分担する Recoil は Meta が公式にアーカイブ済みで、React 19 の concurrent features との相性も怪しくなっていた。このタイミングで Jotai に乗り換えた。 両者とも「atom 単位で状態を管理する」思想は同じなので、API のマッピングは素直。ただし atom が 60 個以上、 useRecoilState / useRecoilValue / useSetRecoilState の呼び出しはそれ以上ある。機械的に置換できる部分を最大化しつつ、微妙に違う箇所だけ個別判断、という進め方にした。 // Before: Recoil import { atom, useRecoilState } from 'recoil' ; const selectedAgentAtom = atom< Agent | null >( { key : 'selectedAgent' , // 一意な key が必須 default : null , } ); const [ agent , setAgent ] = useRecoilState(selectedAgentAtom); // After: Jotai import { atom, useAtom } from 'jotai' ; const selectedAgentAtom = atom< Agent | null >( null ); // key 不要 const [ agent , setAgent ] = useAtom(selectedAgentAtom); 移行の進め方: grep で atom({ / selector({ / useRecoilState / useRecoilValue / useSetRecoilState を全検出 ファイル単位で Claude Code に変換を指示、diff をレビュー 型チェック & テスト → 次のファイルへ この規模の置換はまさに「網羅性が必要な機械的作業」で、AI との分担が効く典型例。 人間がやると「このファイルは変えたっけ、こっちはまだだ」となりがちな作業を、AI が一切こぼさずに進めてくれる。 2-5. Storybook MCP でコンポーネントのバリエーションを網羅する Semantic UI → MDS の移行では、「Semantic UI の Dropdown を MDS では何に置き換えるのか」「どんな props があるのか」「どの Story で確認できるのか」を調べる段階で時間を取られる。 Storybook MCP( @storybook/mcp / @storybook/addon-mcp )は、この「コンポーネントを知る」段階を AI Agent に任せるツール。Storybook 上のコンポーネント一覧、各コンポーネントの API・props・Story 情報を AI に提供する。 人間 : 「Dropdown を MDS に移行して」 AI Agent : 1 . Storybook MCP で Dropdown のドキュメント・props・Story 一覧を取得 → MDS Select / Menu が候補、Single / Multi / Searchable のバリエーションがあると把握 2 . Storybook MCP で各 Story のプレビュー URL を取得 3 . chrome - devtools MCP でその URL を開き、getComputedStyle でベースライン取得 4 . コードを置き換え 5 . 再度 chrome - devtools MCP で計測し、差分を比較 Storybook MCP が「何があるか、どこで見られるか」を提供し、chrome-devtools MCP が「実際に開いて計測する」。この 2 つの MCP の組み合わせで、AI Agent がコンポーネントのドキュメント読みからビジュアル検証までを一貫して行える。 2-6. 進捗記録を AI に任せる 作業セッションが終わるたびに、Claude Code に 進捗サマリを Notion に書かせる 運用にしている。 以下は実際に Notion に蓄積された記録の抜粋。Semantic UI 移行状況は、セッションごとに自動更新される: Semantic UI → MDS 移行状況(4/10 更新) ✅ 完了: MDS 代替あり(16ファイル) Popup系(6ファイル)→ MDS Tooltip / Popup Tab系(4ファイル)→ MDS Tabs Dropdown系(6ファイル)→ MDS Select / Menu Input系(2ファイル)→ MDS Input ✅ 完了: MDS 代替なし(4ファイル) List(2ファイル)→ HTML ul/li Statistic(1ファイル)→ HTML div + Emotion CSS Transition(1ファイル)→ CSS animation + useFadeAnimation hook ✅ semantic-ui-react / fomantic-ui-css パッケージ削除済み MDS canary テストの結果もイテレーションごとに記録される: イテレーション 変更内容 結果 1 回目 peer deps + testing-library 更新 ✅ 型チェックパス / ✖ 1 test suite 失敗(React 内部 API に依存したビルド構成が React 19 でエラー) 2 回目 styled-components 6.4.0 ✖ 同じエラー継続 3 回目 vite.config external に react/jsx-runtime 追加 ✅ 117/117 test suites 全パス こういう表が 作業の副産物として Notion に残る 。後から見返す・チームに共有する・引き継ぐ、のどれも低コストでできる。手動で議事録を書く必要がなく、作業しているだけでドキュメントが蓄積される。 この蓄積は人間が確認するときの根拠になるだけでなく、 AI Agent にとっても重要なコンテキスト になる。次のセッションで Claude Code が前回の記録を読み込むことで、「前回どこまで進んだか」「どのアプローチがうまくいった / いかなかったか」を踏まえて作業を再開できる。人間が「これどうだったっけ」と振り返る根拠であり、AI が次の一手を打つための判断材料でもある。 3. 視覚リグレッションを AI と一緒に潰す 3-1. 一番つらかったのは、テストでは拾えない崩れだった main (修正前) PR (移行後) Semantic UI を剥がすと、 コード上は何も壊れていないのに全画面の見た目が微妙に崩れる 。 fomantic-ui-css (Fomantic UI は Semantic UI のコミュニティフォークで、CSS テーマ部分を提供するパッケージ) がグローバルに撒いていた reset CSS、Lato フォント、line-height などが一斉に消えるため。 pnpm ts → PASS、 pnpm build → PASS、 pnpm e2e:run → 40/40 passed。しかしブラウザで開くと、チェックボックスのサイズが違う、日付入力の幅がずれている、行間が微妙に変わっている。 ビルド成功 ≠ UI 正常 。 3-2. 目視の根性論ではなく、差分の観測に寄せた Chrome-devtools MCP(MCP = Model Context Protocol。AI Agent が外部ツールを操作するための標準プロトコル)を使い、main と PR で同じ要素の getComputedStyle を自動取得して比較する方式にした。「何が違うか」を先に機械的に洗い出してから、人間が判断する流れ。 3-3. reset CSS 欠落で input margin が復活した Semantic UI はコンポーネントライブラリであると同時に、 fomantic-ui-css (Fomantic UI は Semantic UI のコミュニティフォークで、CSS テーマ部分を提供するパッケージ)というグローバル CSS もバンドルしていた。この CSS には normalize / reset ルール( input { margin: 0 } 、 body { overflow-x: hidden } など)が含まれており、アプリ全体が暗黙的に依存していた。Semantic UI のコンポーネントを全て MDS に置き換えた後、 fomantic-ui-css ごと削除したことで、これらのリセットルールが消失した。 上記二つの画像の場合, chrome-devtools MCP 経由で getComputedStyle を main / PR で比較。 実際のアウトプット: // main (/callcenter/report/) - label 内部構造 { " tag ":" LABEL ", " rect ": { " w ": 30 ," h ": 30 } , " margin ":" 0px " } { " tag ":" INPUT ", " rect ": { " w ": 30 ," h ": 30 } , " margin ":" 0px ", " border ":" 1px solid rgb(23, 100, 233) " } { " tag ":" SPAN ", " rect ": { " w ": 14 ," h ": 21 } , " text ":" 日 " } // PR (/callcenter_preview_4868/report/) - 同じ label { " tag ":" LABEL ", " rect ": { " w ": 37 ," h ": 36 } , " margin ":" 0px " } { " tag ":" INPUT ", " rect ": { " w ": 30 ," h ": 30 } , " margin ":" 3px 3px 3px 4px ", " border ":" 1px solid rgb(23, 100, 233) " } { " tag ":" SPAN ", " rect ": { " w ": 14 ," h ": 21 } , " text ":" 日 " } INPUT の margin が 0px vs 3px 3px 3px 4px 。fomantic が提供していた input { margin: 0 } が消え、ブラウザデフォルトの margin が復活していた。label のサイズが 30×30 → 37×36 に膨張。 reset.css に追加して解決。 3-4. 大事だったのは「直す差分」と「受け入れる差分」を分けること Semantic UI と MDS は別物なので、MDS に寄せた時点で見た目が変わる箇所は必ずある。検出された差分のうち、 直さない差分のほうが件数は多い 。 直すべき 受け入れるべき 機能の破壊(クリック領域、要素の重なり) デザイントークン由来の変化(色・余白・書体・ラディウス) 意図しない結果(リセット CSS 欠落、フォールバック) MDS 側の改善(focus ring、ARIA 属性) ユーザーが混乱するレイアウト崩れ UX の改善 ピクセル一致ではなく、 「意図しない破壊がないこと」 を目的にした。 3-5. ここでの AI と人間の境界 AI は差分検出と原因特定の補助が得意。一方、その差分が仕様か不具合かを判断するのは人間の仕事。この分担が、視覚リグレッション対応では特に効いた。AI が差分をピクセル単位で絞り込み、人間がその箇所を重点的に目視確認する。 4. 手順と判断基準を Skill にする アップグレード作業の佳境に入った頃、社内の別チームが Claude Code の Skill を使って移行作業を標準化していることを知った。そのアプローチを参考に、今回の Semantic UI → MDS 移行にも Skill を導入した。 4-1. 単発で使うだけでは、再現性が出ない コンポーネント移行は似た作業の繰り返しになる。そのたびにプロンプトを考えるのは非効率だし、個人技のままだと品質もぶれやすい。 4-2. Semantic UI → MDS 移行の流れを Skill 化した Claude Code の Skill(再利用可能なカスタムコマンド) として、移行ワークフローを標準化した。 実行すると AI Agent が以下を自動で進める: 対象コンポーネントの使用箇所と Storybook を網羅的に検索 chrome-devtools MCP でベースラインの getComputedStyle とスクリーンショットを取得 MDS の対応コンポーネントに置き換え 移行後の getComputedStyle とスクリーンショットを取得し、ベースラインと比較 差分を分類して対応 4-3. Skill に埋め込んだのは、手順だけではない 差分を 3 層に分類する判断基準まで含めて標準化した: 自動修正層: 意図しない破壊(リセット CSS 欠落など)→ AI が修正 人間判断層: デザイントークン由来かもしれない差分 → レポートして人間に判断を戻す 記録のみ層: MDS 仕様として正しい差分 → ログに残す 手順だけでなく 判断基準を埋め込む ことで、別メンバーが別コンポーネントを移行するときも同じ品質で作業できる。この Skill は社内マーケットプレイスに登録しており、チーム内の誰でも同じコマンドで移行作業が回せる。 4-4. AI 活用を「うまく使える人」依存にしない AI を使うこと自体ではなく、 AI が機能する作業設計のほうが重要 だった。再利用可能な形にしておくことで、プロンプトの書き方や AI への指示の仕方を個人に依存させない。 5. まとめ 5-1. AI Agent が速くしたのは、実装そのものより「判断に入るまで」 調査、整理、比較、記録のコストを大きく下げられた。React 19 本体の breaking changes 対応よりも、依存ライブラリ調査・CSS リグレッション検証・レビュー対応の効率化のほうが AI Agent の貢献が大きかった。 5-2. 実務で AI Agent を使うなら、まず任せる範囲を決める AI に任せる: 網羅・比較・記録のような、正確さと量が求められる作業 人間が担う: 意図と責任が伴う判断 この境界を先に決めると、使い方がぶれにくい 5-3. 今回の学び AI は万能ではないが、 作業の前後にある重い仕事 にはかなり効く 特に、大規模移行のような「調査と確認が多い仕事」と相性がよかった 単発活用より、 手順と判断基準を仕組みに落とす ほうが持続的に効く ※ 今回の Skill はアップグレード作業の佳境に入った頃に導入したため、恩恵は限定的だった。最初からあれば、繰り返し作業の品質と速度をもっと引き上げられたはずだ。 使用ツール: Claude Code / chrome-devtools MCP / GitHub CLI / Notion MCP