📚

採用を加速させるLLMの使い方

2024/12/06に公開

READYFORでエンジニアリングマネージャー(以下EM)をしている @resqnet です。
今年で入社して6年目になりました。

どの職種においても課題は沢山あると思いますが、スタートアップのEMにとって最も悩まされるタスクの一つが採用です。そしてEMをしていると「採用」は避けて通れない大きな課題です。特にスタートアップでは、限られたリソースの中で採用を進める必要があります。場合によっては採用チームのリソースも無いことでしょう。

さらに、スタートアップでは人事チームがエンジニア採用に特化した体制を常に持てるわけではありません。リソースが潤沢になるのは資金調達直後の限られたタイミングに過ぎず、それ以外のフェーズでは、EM自身が採用プロセスに深く関与しなければならないことも少なくありません。そんな中、一部のプロセスにおいてLLMを使うことで負荷が大きく下がったのでその取り組みをアドカレのネタにしました。きっとエンジニア採用に限らず活かせるはずです。

媒体採用の話

まず初めにエンジニア採用にはざっくり以下の手段があります。

  • エージェント採用
  • 媒体採用
  • リファラル採用

利用された経験があるかと思うので説明は不要かと思いますが、この記事では媒体採用を中心に話を進めます。また、採用ブランディングなどの認知プロセスについては言及しません。

媒体採用の大変さと課題

突然届くスパムのような求人メール。
謎の派遣会社からの一方的な連絡。
「とりあえず大量送信しました感」がにじみ出たメッセージ。

こうしたメールを受け取ったことがある人は多いのでは無いでしょうか?
(私が送っていたら本当に、本当に申し訳ありません許して下さい。)

これらの背景には、採用担当者が苦肉の策で大量送信をせざるを得ない場合があると今では考えています。

では、どのような課題がその背景にあるのでしょうか。

採用媒体で条件を指定しないと約10万人ほどいます。しかし中には登録しただけの場合や何も活動していない人もかなり多いため、弊社条件で絞って行くとマッチする人は150人程度になります。(2019年頃にReactエンジニアの採用を進めていた時はもっと少なかったのを覚えています)

それでも最初は「150人もいるじゃん!」と思いました。

以下は、その後、採用担当が直面するリアルな状況です。

  • 対象者の絞り込み
    登録者10万人 → バックエンド希望者7,000人 → 最終的に条件に合うのは約150人前後。

  • スカウト送信の成功率
    スカウトを送った中の5%程度が返信し、そのうち70%が面談に進みます。

  • 採用のハードル
    1人採用するには、10〜15人程度と面談すると言われており、そのためには約400通ものスカウトを送らなければなりません。

約400通ものスカウトを送らなければなりません
150人しか居ないのに約400通ものスカウトを送らなければなりません。

直接マッチしている候補者はわずか150人前後、これでは400通のスカウトを送るには足りないのです。

必然的に条件を広げざるを得ません。

また、単に「間口を広げれば解決」とはいかず、媒体採用のフロー上避けられないリソース問題が深く関わってきます。

媒体採用のフロー

  1. 採用担当が媒体で検索し、母集団を形成。
  2. スカウトメールを送信し、候補者と接触。
  3. 返信が来たら面談を調整。

このプロセスの中で、特に母集団形成スカウト送信に課題が集中しており、以下のような理由により母集団形成のプロセスが難しいと感じています。

  • レジュメのフォーマットが統一されていない
  • 職務経歴書の内容が不揃い
  • LLMによるドーピングレジュメ

採用媒体ごとに異なり比較が難しく、薄すぎる場合もあれば冗長すぎる場合も...
更に最近では候補者もLLMを利用している場合が多く、見た目は完璧だが、実際に面談するとスキルギャップがあるなんてことも多いです。

これらにより母集団を形成するためのスクリーニングにかかる時間は以前よりも時間がかかっているように感じます。しかし間口を広げるとスクリーニングに必要な時間は膨大になります。

そのためスクリーニング、パーソナライズを諦めた結果「スパムのような爆撃」が行われるのではと考えています。

LLMで採用プロセスを最適化する3つの方法

これらに対しLLMを活用したプロセスに取り入れることで、上記のスクリーニング時間が大幅に短縮可能です。

主に実践する内容は以下の3つです。
1. レジュメのフォーマット統一
2. レジュメからマッチ度を評価
3. スカウトポイントの要約

エンジニア以外がスクリーニングする場合、スカウト数がKPIにセットされていることも多く、1分程度でレジュメを見て、書いてあることからワードを拾ってるパターンが多いです。これだと昨今のLLMで書かれたレジュメを見るのは難しい様に感じます。

またLLMを利用したところで人の目は必ず必要になります。それでも、見るべきポイントを絞ることができ評価粒度も比較的統一出来るため、レジュメ精査の質と時間が大幅に向上する実感を得られました。

マッチプロセス

基本的なプロセスは人間が行った場合と大きく違いはありません、要約や評価などをLLMに頼ることとしました。

plantuml
  @startuml
  actor 採用担当
  participant 媒体ページ
  participant LLM

  採用担当 -> 媒体ページ: 条件検索
  媒体ページ -> 採用担当: 候補者リスト
  採用担当 -> LLM: 候補者のレジュメを要約・フォーマット統一
  LLM -> 採用担当: 要約結果
  採用担当 -> LLM: 募集要項と候補者情報で評価
  LLM -> 採用担当: 評価結果
  採用担当 -> 採用担当: 評価結果を保存
  採用担当 -> LLM: スカウトポイント抽出
  LLM -> 採用担当: 抽出結果
  採用担当 -> 採用担当 : パーソナライズ
  採用担当 -> 媒体ページ: スカウト送信
  @enduml
  • レジュメのフォーマット統一
    各媒体から取得したレジュメをLLMに投入し、フォーマットを統一します。
    これにより、候補者同士の比較が容易になり、その後のマッチ度を測る際にも同じ粒度で比較しやすくなります。
    実際の運用でも採用媒体は複数運用をするかと思います。
参考プロンプト
# 職務経歴書
{{resume}}

# 指示
職務経歴書から経歴を要約してください

# 正しい出力例
【強み】バックエンド開発

【経験】
・フロントエンド:2年
・バックエンド:3年 
・React:2年

【ビジネス経験】
・株式会社なんとか商事:2年 【主担当:バックエンド1年】
・株式会社とりあえず開発:2年 【主担当:フロントエンド2年】

【スキル】
・DBを利用した開発経験
・GitHub等の, CI/CD構築経験

【人物像】
・プロダクト価値向上に貢献し、新しいアイデアを提案し実現する意欲がある方

# 出力
  • 母集団形成・評価時間の短縮
    LLMを使い、レジュメから募集要件へのマッチ度を評価します。
    条件を広げてもペルソナにマッチする候補者を自動抽出可能にします。
    候補者抽出の精度が向上するだけでなく、CIでレジュメ評価が可能になるので他の作業時間を大幅に確保出来るようになります。
参考プロンプト
以下は、READYFORというクラウドファンディングプラットフォームの募集要項です

# 募集要件
{{job_description}}

# 候補者の情報

## 経歴書
{{resume}}

# 指示
内容から募集要項にマッチしているかを判定してください。
マッチしている度を高・中・低・無しで判定してください。

・A評価:即戦力、マッチ度が高い人
A評価:評価基準

・B評価:即戦力になる可能性がある、自走できるバックエンド経験があり、社会人経験もある方
B評価:評価基準

・C評価:それ以外
C評価:評価基準

# 正しい出力例
【マッチ度:B評価】【強み:バックエンド開発】【バックエンド経験年数:1年】
募集要件に対してマッチ度が高いです、特にRailsの経験が5年ほどあるためすぐに開発に取り書かれることでしょう。リーダー経験もあり他部署を巻き込んだ取り組みを推進することが出来ます。

【実務経験】
・なんとかソフトウェアでRails経験が5年ありリーダーとして導いた

【Rails開発で活かせそうな実務経験】
・CRUDを使ったDB開発とパフォーマンスチューニング経験
・ActiveRecord周りの改修

【経験】
・プロジェクトマネジメント:8年
・SRE:1年

【ビジネス経験】
・なんとか株式会社:プロジェクトマネージャー(2023年2月 ~ 現在)
・なんだそれ株式会社:プロジェクトマネージャー(2018年9月 ~ 2023年1月)

【スキル】
・Linux、AWS、Heroku、OCI、MySQL、Oracle
・PHP、JavaScript、JQuery、TypeScript、AngularJS、CakePHP

# 出力
  • スカウトポイント要約
    候補者のレジュメを解析し、「スカウトメールに記載すべきポイント」を自動で要約します。
    1通あたりのメール作成時間が大幅な短縮、見るべきポイントが既にわかっているため、スカウト文章に迷いが無くなります。
参考プロンプト
以下は、READYFORというクラウドファンディングプラットフォームの求人募集要項です

# 業務内容
{{job_description}}

# マッチ評価
{{match}}

# 指示
スカウトポイントを抽出してください

# 正しい出力例
株式会社なんとかでのフルスタックエンジニアとしてのご経験や、TypeScript, Vue.js, Ruby on Railsを用いたWebアプリケーションの開発に携わられた点が魅力的です。
業務内容の◯◯で活かせるスキルがあります。
特に◯◯商事ですのリーダー経験は、◯◯で即戦力です。

# 出力

LLM技術スタックと実装

やりたいことは媒体ページの情報取得とデータの保存で、特にデータを誤魔化したり無理やり取得する必要は無く、ブラウザ上の情報を取得するだけで十分です。

アプリケーションのポイントとしては以下の二点です。

  • プロンプトの最適化を都度行う必要があること
  • 募集要件が変わっても対応出来ること

そのためプロンプトの管理、最適化とアプリケーションを分離しプロンプトのバージョン管理のためにLangfuseを利用しました。Langfuseではバージョン管理の他に評価、実行回数、コスト等を確認することが出来ます。

アプリケーション構成

JSの挙動などを考慮するのが面倒なのでC#のWebViewで実現しました。他の選択肢としてはChromeExtension等でも良いかと思います、また中間サーバーをFastAPIで組んだ理由は様々なEndpointからLangfuseとやりとりを考えた際にLangFuseがPythonのSDKを提供しておりシンプルなRESTのみで構築が出来るためです。

媒体規約への配慮

採用媒体の利用規約では、自動的なデータ取得を規制している場合があります。そのため完全な自動処理ではなく、手動処理を前提としたクリップボード経由でのデータやり取りを可能にしています。この方式により、規約を遵守しながら効率的に情報を扱うことができます。

シーケンス図


この図は、媒体からのデータ取得から評価、保存までの一連のプロセスを示しています

plantuml
@startuml
participant 媒体
actor WebView
participant FastAPI
participant LangFuse
participant LLM
database 保存先

WebView -> 媒体: 候補者情報取得リクエスト
媒体 -> WebView: 候補者情報レスポンス

loop データ取得から保存までのループ
    WebView -> 媒体: データ取得リクエスト
    WebView -> FastAPI: サマリと評価をリクエスト
    FastAPI -> LangFuse: プロンプトリクエスト
    LangFuse -> FastAPI: プロンプトレスポンス
    FastAPI -> LLM: サマリと評価実行
    LLM -> FastAPI: サマリと評価結果
    FastAPI -> 保存先: データ保存
    FastAPI -> WebView: 保存完了レスポンス
    WebView -> 媒体: 必要に応じてメモに保存
end
@enduml

RPAブラウザのクラス図


plantuml
@startuml


class Candidate {
  + resumeData: string
  + summary: string
  + etc...
}

abstract class AbstractResumeScraper {
  + Scrape(candidateId: string): Candidate
  + Save(candidateId: string, summary: string): bool
  + GetCandidateList(): List<Candidate>
}



class ConcreteObeject {
  + Scrape(candidateId: string): Candidate
  + Save(candidateId: string, summary: string): bool
  + GetCandidateList(): List<Candidate>
}

AbstractResumeScraper <|-- ConcreteObeject

class CandidateProcessor {
  + EvaluateWithLLM(candidate: Candidate): bool
  + GenerateScoutMessage(resume: string): string
}

class LangFuse {
  + ProcessResume(input: string): string
}

CandidateProcessor --> LangFuse

interface ScraperFactory {
  + CreateScraper(platform: string): AbstractResumeScraper
}

class DefaultScraperFactory {
  + CreateScraper(platform: string): AbstractResumeScraper
}

class PlatformManager {
  + GetCandidateInfo(platform: string, candidateId: string): Candidate
  + FetchCandidateList(platform: string): List<Candidate>
}

PlatformManager --> ScraperFactory
ScraperFactory <|.. DefaultScraperFactory

class MainForm {
  + FetchCandidateList(platform: string): List<Candidate>
  + EvaluateCandidates(candidate: Candidate)
  + GenerateScoutMessage(resume: string)
}

MainForm --> PlatformManager
MainForm --> CandidateProcessor

@enduml

効果

実際にブラウザを作成し、候補者データの取得から評価送信までを試験的に行いました。その結果、候補者のサマリ要約・評価に約30秒、スカウト文面の作成・送信に約30秒と、1名あたり合計で1~2分程度でスクリーニングからスカウト作成までを完了できることがわかりました。単にキーワードを拾うだけでなく、募集要件への具体的なマッチ度やその根拠を明示した形でスカウト文を作成できるため、スカウトの質が向上しました。

また、この取り組みによって、媒体を使い始めたばかりの初期段階で、大幅に手間を減らせたのが大きな成果でした。さらに、通常の運用においても、週ごとの送信上限や候補者のアクティブ状況に応じて送信対象が増減する中でも、媒体運用全体にかかる手間を大幅に軽減しました。

そして、全体を通して最も大きな効果として感じたのは、脳のスイッチングコストが大きく軽減されたことです。

まとめ

採用活動はEMにとって重要な業務の1つですが、スタートアップでは限られたリソースの中で行う必要があり、非常に負担の大きいタスクです。そんな中、LLMを活用することで、母集団形成やスクリーニング、スカウトメール作成といったプロセスを効率化し、精度の高い採用活動を実現する手助けとなる可能性を実感しました。

一方で、採用活動をより効率化するためには、各採用媒体が統一されたレジュメフォーマットや共通データベースを提供される未来があると嬉しいです。
例えば、賃貸物件情報が共通データベースを通じて流通しているように、採用活動でも候補者データをシームレスに活用できる仕組みが整えば...と妄想しています。

またLLMを活用した採用の話など採用についての意見交換が出来ると嬉しいのでXから気軽にDMください。採用についていろいろ悩んでます。

Discussion