Appearance
おすすめ求人サジェスト
概要
おすすめ求人サジェストは、求職者の経歴・スキル・希望条件を分析し、DB 上の約 3,000 件の求人からベクトル類似度検索(Embedding(テキストを数値ベクトルに変換する技術)+ pgvector(PostgreSQL のベクトル検索拡張))と LLM(大規模言語モデル)精査を組み合わせて、最適な求人を最大 5 件提案する機能です。
転職エージェントが求職者の面談後に「この求職者にはどんな求人が合うか」を手動で調べる作業を、AI が自動化します。ボタン 1 回で、数千件の求人の中から最適な候補を約 12〜15 秒で提示します。
ユースケース
- 面談シートを記入した後、すぐにおすすめ求人を確認したい
- 類似経歴の求人をまとめて確認し、エージェントの提案の幅を広げたい
- 職務経歴書のアップロード後に、自動でマッチング候補を取得したい
主要数値
| 項目 | 内容 |
|---|---|
| 目的 | 求職者に最適な求人を AI で自動提案し、エージェント業務を効率化 |
| 使用 API | Gemini 3 Flash Preview(カテゴリ判定・LLM 精査)+ gemini-embedding-001(Embedding 生成) |
| 実行方法 | 求職者詳細画面の面談シートモーダル内「おすすめ求人を探す」ボタン |
| 処理時間 | 約 12〜15 秒(非同期処理、ポーリング(定期問い合わせ)で結果取得) |
| 推薦件数 | 最大 5 件 |
| 求人 DB 件数 | 約 3,000 件 |
| コスト/回 | 約 $0.00165 |
全体像(ざっくり理解する)
システム全体を 3 つの段階に分けて説明します。まずここで全体像をつかみ、次のセクションで詳細に入ります。
Step 1: ユーザーがボタンを押す
求職者詳細画面のモーダルにある「おすすめ求人を探す」ボタンを押すと、ブラウザが API サーバーにリクエストを送ります。API はすぐに「受け付けました(202 Accepted)」と返し、裏側で AI 処理を非同期で開始します。ユーザーは処理の完了を待たずに画面を操作し続けられます。
Step 2: AI が候補求人を探す(ベクトル検索 + LLM 精査)
バックグラウンドでは 2 段階の絞り込みが行われます。まず Embedding とカテゴリ判定を使って DB 上の 3,000 件から候補 15 件を高速に抽出します。次に、その 15 件を Gemini Flash(生成 AI モデル)が精読し、求職者のプロフィールとの適合度を 1〜100 点でスコアリングして上位 5 件を選出します。
Step 3: 結果が表示される(ポーリングで自動更新)
フロントエンド(ブラウザ)は 3 秒ごとに API へ結果を問い合わせ続けます(ポーリング)。処理が完了すると自動的に結果カードが表示され、ポーリングは停止します。
全体フロー図
処理パイプライン(詳細フロー)
ボタン押下から DB 保存まで、処理は 7 つのステップで構成されています。
Step 1: 求職者データ収集
candidates、candidate_documents、candidate_notes の 3 テーブルから Promise.all を使って並列でデータを取得します。
💡 ポイント:
Promise.allを使うことで、3 つの DB クエリが同時に実行されます。順番に実行すると 3 倍の時間がかかるところを、最も遅いクエリ 1 つ分の時間で完了します。
Step 2: プロフィールテキスト生成
取得したデータを、AI が読みやすい構造化テキストに変換します。
💡 ポイント: Embedding は「テキスト同士の意味的な近さ」を比較する技術です。AI が求職者と求人の類似度を計算するには、まず両者を同じ形式のテキストに揃える必要があります。
本人の意向・志望を最も重要な情報として LLM に伝えるため、面談メモと自己 PR をテキストの先頭に配置しています(LLM の primacy effect を活用)。
生成されるテキストの例:
【面談メモ(本人の意向・志望)】
Zoom面談: PMに挑戦したい、リモート勤務希望
【自己PR】
チームリードとして...
【希望条件】
居住地: 愛知県名古屋市中区
希望年収: 800万円
現年収: 650万円
【スキル・強み】
- Java, Spring Boot, AWS
【職歴】
- 株式会社ABC / バックエンドエンジニア / マイクロサービス開発
【学歴・資格】
- 東京大学 工学部
- AWS Solutions ArchitectStep 3+4: Embedding 生成 + カテゴリ判定(並列実行)
2 つの Gemini API 呼び出しを Promise.all で並列実行します。
💡 ポイント: Embedding とカテゴリはどちらも AI 処理ですが、互いに依存していないため同時に実行できます。直列に実行すると約 3〜4 秒かかるところを、約 1〜2 秒に短縮しています。
- Embedding 生成:
gemini-embedding-001モデルを使用し、プロフィールテキストを 3,072 次元(3,072 個の数値からなる配列)のベクトルに変換します。求人のベクトルとの距離(コサイン距離)が近いほど、意味的に類似した求人と判断されます。 - カテゴリ判定: Gemini Flash でプロフィールから「バックエンド」「フロントエンド」「PM」などの職種カテゴリを最大 3 つ抽出します。このカテゴリが次のベクトル検索のフィルタとして使われます。
Step 5: 統合ベクトル検索
PostgreSQL(Supabase)の RPC(Remote Procedure Call)関数 match_jobs_by_embedding_v2 を 1 回呼び出すだけで、以下の 3 つのフィルタを同時に適用して上位 15 件を取得します。
💡 ポイント: 3 つのフィルタを SQL 内で一括処理することで、DB への往復が 1 回で済みます。従来は JavaScript 側でループしてフィルタリングしていたため、データ転送量が多くなっていました。
| フィルタ | 内容 |
|---|---|
| カテゴリフィルタ | job_category_mappings テーブルで判定されたカテゴリに紐づく求人のみを対象にする。NULL の場合は全求人が対象 |
| 年収フィルタ | 希望年収がある場合は min_salary ≤ 希望年収 の求人を通過。現年収のみの場合は現年収 × 0.8〜1.5 の範囲と重なる求人を通過 |
| ベクトル検索 | ai_description_embedding カラム(3,072 次元)でコサイン距離順にソートし上位 15 件を返す |
Step 5b: フォールバック
カテゴリフィルタを適用した結果が 5 件未満の場合、カテゴリフィルタを外して再検索します。
💡 ポイント: ニッチな経歴(例:組み込みエンジニア、ゲーム系)の求職者は、カテゴリにマッチする求人数が少ない場合があります。フォールバックにより、どんな求職者にも必ず十分な候補を返せるようにしています。
Step 6: LLM 精査
15 件の候補求人とプロフィールテキストを Gemini Flash(生成 AI)に送信し、各求人への適合度を 1〜100 点の絶対評価でスコアリングします。上位 5 件が最終推薦結果となります。
💡 ポイント: ベクトル検索は「テキストの意味的な近さ」を測るものであり、「この求職者のキャリア志向に合うか」「必要経験年数を満たすか」といった文脈的な判断が苦手です。LLM 精査ステップがその役割を担い、推薦の精度を高めています。
Step 7: 結果保存
上位 5 件のスコアと推薦理由を candidate_job_recommendations テーブルに保存し、ステータスを completed に更新します。
処理時間の内訳
| ステップ | 所要時間 | 備考 |
|---|---|---|
| 求職者データ収集 | ~100ms | 3 クエリ並列 |
| プロフィールテキスト生成 | ~1ms | CPU 処理 |
| Embedding + カテゴリ判定 | ~1-2s | 並列実行 |
| 統合ベクトル検索 | ~200-500ms | 1 回の RPC |
| LLM 精査 | ~10-12s | 15 件のスコアリング |
| 合計 | ~12-15s |
フロントエンド UI
状態遷移
UI は 4 つの状態を持ちます。ボタン押下から結果表示まで、状態は自動的に遷移します。
各状態の表示内容
| 状態 | 表示内容 |
|---|---|
| 初期 | 「おすすめ求人を探す」ボタン(Sparkles アイコン) |
| 処理中 | スピナー +「求人をマッチング中...(30 秒ほどお待ちください)」 |
| 完了 | 結果カード最大 5 件 +「再検索」ボタン |
| 失敗 | エラーメッセージ +「再試行」ボタン |
結果カードの表示要素
各推薦求人は以下の情報を含むカード形式で表示されます。
| 要素 | 内容 |
|---|---|
| スコアバッジ | 80 点以上=緑、60 点以上=黄、それ以下=グレー |
| 求人タイトル | 求人詳細画面へのリンク付き |
| 企業名 | テキスト表示 |
| 年収範囲 | min_salary〜max_salary 万円 |
| 推薦理由 | AI 生成テキスト(100 字程度) |
ポーリング設定
React Query(サーバー状態管理ライブラリ)の refetchInterval を使って、ステータスが processing の間だけ自動ポーリングを行います。
| 設定 | 値 |
|---|---|
| ポーリング間隔 | 3 秒 |
| ポーリング条件 | status === 'processing' の間のみ |
| 停止タイミング | completed または failed になった時点で自動停止 |
表示場所
求職者詳細画面の面談シートモーダル内に JobRecommendationPanel コンポーネントとして配置されています。
API エンドポイント
POST /api/v1/candidates/:id/job-recommendations
サジェスト処理を非同期で開始します。処理の開始を受け付けた時点で即座にレスポンスを返します(Fire-and-forget パターン)。
- レスポンスステータス:
202 Accepted
json
{
"data": {
"id": 123,
"status": "processing"
}
}GET /api/v1/candidates/:id/job-recommendations
最新のサジェスト結果を取得します。フロントエンドはこのエンドポイントを 3 秒ごとにポーリングして結果を確認します。
処理中の場合:
json
{
"data": {
"id": 123,
"status": "processing",
"results": null,
"error": null,
"created_at": "2026-02-18T10:00:00Z",
"completed_at": null
}
}完了時:
json
{
"data": {
"id": 123,
"status": "completed",
"results": [
{
"job_id": 45,
"score": 92,
"reason": "バックエンド開発経験が活かせ、年収も希望範囲内...",
"job_title": "バックエンドエンジニア",
"company_name": "株式会社ABC",
"min_salary": 800,
"max_salary": 1200
}
],
"error": null,
"created_at": "2026-02-18T10:00:00Z",
"completed_at": "2026-02-18T10:00:14Z"
}
}失敗時:
json
{
"data": {
"id": 123,
"status": "failed",
"results": null,
"error": "マッチする求人が見つかりませんでした",
"created_at": "2026-02-18T10:00:00Z",
"completed_at": "2026-02-18T10:00:05Z"
}
}マッチングアルゴリズム詳細
このセクションでは、候補者のプロフィールから「おすすめ求人」を導き出すアルゴリズムの内部処理を詳しく解説します。
プロフィールテキストの構成
マッチングの起点は、候補者の情報をひとつの「プロフィールテキスト」に統合する処理です。複数のテーブルに分散して保存されているデータを収集し、LLM が理解しやすい自然言語の文章に変換します。
データソーステーブル
| セクション(テキスト出力順) | ソーステーブル | 参照フィールド |
|---|---|---|
| 面談メモ(本人の意向・志望) | candidate_notes | title + content |
| 自己 PR | candidate_documents | career_sheet.self_pr または resume.self_pr |
| 希望条件(居住地) | candidate_documents | resume.address または career_sheet.address |
| 希望条件(年収) | candidates | expected_salary, current_salary |
| スキル・強み | candidate_documents | career_sheet.parsed_data.skills |
| 職歴 | candidate_documents | career_sheet.work_history または resume.work_history |
| 学歴・資格 | candidate_documents | resume.education + resume.certifications |
career_sheet(キャリアシート)と resume(職務経歴書)の両方を参照し、どちらか一方しかない場合でも情報を最大限に活用します。
カテゴリ判定
Gemini Flash(軽量かつ高速な Google の LLM)を使って、候補者がどの職種カテゴリに属するかを最大 3 つ自動判定します。このカテゴリ情報は、後続のベクトル検索の絞り込み条件として使用されます。
対象カテゴリ(20 種)
バックエンド、フロントエンド、SRE/インフラ、モバイル、データサイエンス、PM、コンサル、QA、セキュリティ、社内 SE、セールス、AI/ML、組み込み/IoT、ゲーム、デザイナー、データエンジニア、クラウドアーキテクト、DevOps、テックリード/EM、その他
プロンプト概要
LLM への指示には、以下の 判定基準(優先度順) を明示しています。
| 優先度 | 判定基準 | 説明 |
|---|---|---|
| 1(最優先) | 本人の意向・志望 | 面談メモや自己 PR で「○○をやりたい」「○○に挑戦したい」と記載がある場合、過去の経歴に関わらずその意向に対応するカテゴリを必ず含める |
| 2 | スキル・経験との整合性 | 保有スキルや職歴から適性のあるカテゴリを選ぶ |
| 3 | 経歴だけで判断しない | 過去に SE だったとしても本人が PM を志望していれば PM 系カテゴリを優先する |
💡 ポイント: 従来は「最も関連するカテゴリ」を選ぶだけの指示でしたが、これでは職歴のテキスト量に引きずられて過去の経歴中心の分類になりがちでした。判定基準を優先度付きで明示し、さらにプロフィールテキスト自体も面談メモを先頭に配置することで(primacy effect)、本人の意向が確実にカテゴリに反映されるようになっています。
ネットワークエラーや LLM の応答異常が発生した場合、カテゴリ判定は空配列 [] を返します。これは致命的なエラーとして扱わず、後続のベクトル検索はカテゴリフィルタなしで続行します。
統合ベクトル検索(match_jobs_by_embedding_v2)
ベクトル検索とは(初学者向け解説)
「ベクトル検索」は、テキストを意味的に比較する技術です。仕組みを順を追って説明します。
- テキストを数値の列(ベクトル)に変換する: 例えば「Java 開発経験あり」というテキストは、
[0.12, -0.45, 0.78, ...]のような 3,072 個の数値の列に変換されます。この変換を Embedding(埋め込み)と呼びます。 - 似た意味のテキスト同士は数値も近くなる: 「Java エンジニア募集」と「Java 開発経験あり」は意味が近いため、変換後の数値の列も近い値になります。
- 近い順に並べる: 候補者のプロフィールテキストをベクトルに変換し、すべての求人のベクトルと比較して「近い順(コサイン距離順)」に並べることで、意味的に一致する求人を効率よく見つけられます。
3 つのフィルタを 1 回の DB クエリに統合
通常、「カテゴリで絞る」「年収で絞る」「ベクトルで並べる」を別々に DB へ問い合わせると 3 回の DB 通信が発生します。match_jobs_by_embedding_v2 はこれを 1 回の PostgreSQL RPC にまとめ、高速化を実現しています。
フィルタ 1: カテゴリフィルタ
job_category_mappings テーブルを参照し、候補者の判定カテゴリと一致する求人に絞り込みます。カテゴリ判定が失敗して空配列となった場合は、このフィルタはスキップされ全求人が対象になります。
フィルタ 2: 年収フィルタ
| 保有データ | フィルタロジック |
|---|---|
| 希望年収あり | jobs.min_salary ≤ 希望年収 |
| 現年収のみあり | jobs.min_salary ≤ 現年収 × 1.5 かつ jobs.max_salary ≥ 現年収 × 0.8 |
| どちらもなし | フィルタなし(全求人が対象) |
「現年収のみ」の場合は、現年収の 80%〜150% の範囲と重なる求人を対象とします。これにより、大幅な年収ダウンや非現実的な年収アップの求人を自動的に除外します。
フィルタ 3: ベクトル検索
jobs.ai_description_embedding カラム(3,072 次元のベクトル)と候補者プロフィールの Embedding をコサイン距離で比較し、類似度が高い順に上位 15 件を取得します。
フォールバック戦略
カテゴリフィルタを適用した状態でベクトル検索を実行した結果、取得件数が 5 件未満の場合は、カテゴリフィルタを外して再検索します。
特殊な経歴を持つ候補者(例:組み込み/IoT エンジニアや希少な業界ドメインの経験者)の場合、一致するカテゴリの求人数そのものが少なく、十分な候補が得られないことがあります。フォールバックにより、どのような経歴の候補者にも一定数以上の求人候補を提示できます。
勤務地の考慮(ソフトフィルタ)
現在、勤務地は SQL レベルのハードフィルタではなく、以下の 2 つの方法で ソフトフィルタ として機能しています。
| レイヤー | 方法 | 効果 |
|---|---|---|
| Embedding | プロフィールテキストに 居住地: 愛知県... を含める | 求人側の work_locations がエンベディングに含まれているため、地理的に近い求人のベクトル類似度が自然に高くなる |
| LLM 精査 | 評価基準に「勤務地の通勤可能性」を明示し、引っ越し必須の求人はスコアを大幅に下げる指示 | 最終スコアリングで遠方の求人が上位 5 件に残りにくくなる |
💡 ポイント: 「ソフトフィルタ」とは、完全に除外するのではなく、スコアを下げて結果に残りにくくする方式です。リモート勤務可の求人は居住地に関わらず高スコアになり得るため、ハードフィルタ(完全除外)だと有望な求人を見逃すリスクがあります。
将来の改善: SQL レベルの勤務地フィルタ
求人の structured_requirements.work_locations のフォーマット(「東京23区」「関東」「東京都」等)が正規化された段階で、RPC 関数にハードフィルタとして p_location パラメータを追加する予定です。これにより、ベクトル検索の段階で通勤不可能な求人を完全に除外でき、15 件の候補枠をより有効に活用できるようになります。
LLM 精査(スコアリング)
ベクトル検索で取得した最大 15 件の候補求人を、Gemini Flash に渡して精査・スコアリングします。ベクトル検索は「意味の近さ」で並べますが、年収条件やキャリア志向、勤務地の通勤可能性など複合的な要素を加味した評価は LLM が得意とするため、2 段階の評価構造を採用しています。
評価基準の優先順位
| 優先度 | 評価基準 | 備考 |
|---|---|---|
| 最重要 | スキル・経験の一致度 | |
| 高 | キャリア志向との整合性 | |
| 高 | 勤務地の通勤可能性 | 引っ越し必須の求人はスコアを大幅に下げる。リモート可の求人は居住地に関わらず評価 |
| 中 | 年収条件の妥当性 | |
| 低 | 業界・ドメインの親和性 |
LLM に送信される求人データフィールド
| フィールド | 説明 |
|---|---|
title | 求人タイトル |
company_name | 企業名 |
ai_description | AI 生成の求人説明文(300 字) |
min_salary | 最低年収(万円) |
max_salary | 最高年収(万円) |
required_skills | 必須スキルリスト |
target_seniority | 対象レベル |
industry_domain | 業界ドメイン |
min_experience_years | 必須経験年数 |
work_locations | 勤務地(都道府県・エリア) |
work_style | 勤務形態(フルリモート等) |
出力形式
LLM は各求人に対して以下の JSON 形式でスコアと推薦理由を出力します。
json
{
"job_id": 45,
"score": 92,
"reason": "バックエンド開発経験が活かせ、Spring Bootの実務経験が必須要件と一致。希望年収800万円に対して年収レンジ750〜900万円と合致。"
}score: 1〜100 の絶対評価(100 が最高マッチ)reason: 推薦理由(100 字程度の日本語)
スコア順に並べた上位 5 件が最終的なおすすめ求人として提示されます。
データモデル
candidate_job_recommendations テーブル
マッチング処理の結果を永続化するテーブルです。非同期処理のため、ステータス管理と結果 JSON の両方を保持します。
| カラム | 型 | 説明 |
|---|---|---|
id | serial | 主キー(自動採番) |
candidate_id | integer | 求職者 ID(candidates テーブルの外部キー) |
status | varchar(20) | 処理ステータス(processing / completed / failed) |
results | jsonb | スコアリング結果の JSON 配列 |
source_summary | text | マッチングに使用したプロフィールテキスト全文 |
error | text | エラー発生時のメッセージ |
created_at | timestamptz | レコード作成日時 |
completed_at | timestamptz | 処理完了日時 |
source_summary を保存しておくことで、どのプロフィール情報をもとに結果が生成されたかをあとから確認・デバッグできます。
results JSON 構造
json
[
{
"job_id": 45,
"score": 92,
"reason": "バックエンド開発経験が活かせ、年収も希望範囲内..."
},
{
"job_id": 12,
"score": 87,
"reason": "AWS経験が必須要件と一致し、リモート勤務可能..."
}
]依存テーブル一覧
| テーブル | 役割 |
|---|---|
jobs | 求人マスタ。ai_description_embedding(3,072 次元ベクトル)を保持 |
job_categories | 職種カテゴリのマスタデータ(20 種類) |
job_category_mappings | 求人とカテゴリの多対多リレーションを管理する中間テーブル |
candidates | 求職者基本情報。expected_salary と current_salary を保持 |
candidate_documents | 職務経歴書・キャリアシートのパース済み JSON データを保持 |
candidate_notes | エージェントが記録した面談メモ |
ER 図
Gemini API 連携
使用モデル一覧
本機能では、処理フェーズによって異なる Gemini モデルを使い分けています。
| 用途 | モデル | Temperature | 出力形式 |
|---|---|---|---|
| カテゴリ判定 | gemini-3-flash-preview | 0.0 | application/json |
| Embedding 生成 | gemini-embedding-001 | — | 3,072 次元ベクトル |
| LLM 精査 | gemini-3-flash-preview | 0.0 | application/json |
- Temperature 0.0: 出力のランダム性を排除し、同じ入力に対して常に同じ出力を返すように設定しています。スコアリングの一貫性を保つために重要です。
- Embedding 生成: テキストをベクトルに変換する API のため Temperature の概念はなく、常に決定論的な出力を返します。
コスト目安
| 項目 | 1 回あたり |
|---|---|
| カテゴリ判定 | ~$0.00015 |
| Embedding 生成 | 実質無料 |
| LLM 精査 | ~$0.00150 |
| 合計 | ~$0.00165 |
月 500 回実行で約 $0.83。コストの大半は LLM 精査が占めており、LLM へ送る求人件数を 30 件から 15 件に削減したことでコストも半減しています。
パフォーマンス最適化
処理の各ステップで以下の最適化を施しています。
並列化(Promise.all の活用)
直列に実行すると待ち時間が積み重なる処理を、Promise.all で並列実行しています。
- DB クエリの並列化: 候補者基本情報・候補者書類・面談メモの 3 件の DB クエリを同時に発行
- AI 処理の並列化: Embedding 生成とカテゴリ判定を同時に発行
逐次実行: [DBクエリ×3] → [Embedding] → [カテゴリ判定] → [検索]
並列実行: [DBクエリ×3(同時)] → [Embedding + カテゴリ判定(同時)] → [検索]統合 RPC
カテゴリフィルタ・年収フィルタ・ベクトル検索の 3 つのロジックを 1 つの PostgreSQL RPC 関数にまとめ、アプリケーションサーバーと DB サーバー間の往復通信を最小化しています。
LLM 入力件数削減
ベクトル検索の結果を LLM に渡す件数を、従来の 30 件から 15 件に削減しました。LLM のトークン消費量と API コストの両方を削減しつつ、精度への影響は軽微です。
プロンプト圧縮
LLM 精査フェーズで LLM に渡す求人データのフォーマットを、1 件あたり 7 行から 4 行に圧縮しました。15 件分で最大 45 行のトークンを削減できます。
求人エンベディング(事前バッチ処理)
おすすめ求人サジェストでは、候補者のプロフィールをリアルタイムでエンベディングする一方、求人側のエンベディングは事前にバッチ処理で生成・DB に保存しておきます。
なぜ非対称なのか
- 求人データ: 一度登録された求人の内容は頻繁に変わらないため、事前にベクトル化して DB に保存する
- 候補者データ: 書類アップロードや面談メモの追記により常に最新化されるため、都度リアルタイムで生成する
実行方法
bash
cd backend && npx tsx src/scripts/embed-jobs.ts定期実行やCI/CDでの自動実行は設定されておらず、以下のタイミングで手動実行します。
| タイミング | 操作 |
|---|---|
| 新しい求人を追加した後 | そのまま実行(未エンベディングの求人のみ処理) |
ai_description を更新した後 | 該当求人の ai_description_embedding を NULL にしてから実行 |
| Embedding モデルを変更した後 | 全求人の ai_description_embedding を NULL リセットしてから実行 |
処理の仕組み
スクリプトは jobs テーブルから ai_description が存在するが ai_description_embedding が NULL の有効求人を取得し、バッチサイズ単位で順次処理します。冪等設計のため、何度実行しても既にエンベディング済みの求人はスキップされます。
エンベディング対象データ
buildJobEmbeddingText() が ai_description と structured_requirements(jsonb)から以下のフィールドを結合してテキストを生成し、Embedding モデルに渡します。
| テキストセクション | ソースカラム | 例 |
|---|---|---|
| ポジション概要 | structured_requirements.position_summary | バックエンドAPIの設計・開発を担当 |
| 必須スキル | structured_requirements.required_skills | Java、Spring Boot、AWS |
| 業界 | structured_requirements.industry_domain | Web系自社開発 |
| 勤務地 | structured_requirements.work_locations | 東京都、大阪府 |
| 勤務形態 | structured_requirements.work_style | フルリモート |
| 対象レベル | structured_requirements.target_seniority | ミドル |
| 年収 | structured_requirements.min_salary / max_salary | 600万〜900万 |
| 英語 | structured_requirements.english_requirement | ビジネス |
| 経験年数 | structured_requirements.min_experience_years | 3年以上 |
| キャリアパス | structured_requirements.career_paths | テックリード、マネジメント |
| 推しポイント | structured_requirements.selling_points | 自社サービス / フルリモート |
| 環境 | structured_requirements.environment | 10名チーム、アジャイル |
| 人物像キーワード | structured_requirements.culture_keywords | 自走力、チームワーク |
| チーム規模 | structured_requirements.team_size | 中規模 |
| エージェント備考 | structured_requirements.agent_notes | 30代前半まで |
| 理想人物像 | jobs.ai_description | (AI 生成の約300字テキスト) |
生成されるテキストの例:
【ポジション概要】バックエンドAPIの設計・開発を担当
【必須スキル】Java、Spring Boot、AWS
【業界】Web系自社開発
【勤務地】東京都
【勤務形態】フルリモート
【年収】600万〜900万
【理想人物像】マイクロサービスアーキテクチャの経験があり、チームリードとして...DB カラムとインデックス
| 項目 | 内容 |
|---|---|
| テーブル | jobs |
| カラム | ai_description_embedding |
| 型 | vector(3072) |
| インデックス | HNSW(halfvec_cosine_ops) |
| キャスト | halfvec(3072) へキャストしてインデックスを利用 |
なぜ HNSW + halfvec なのか
pgvector の IVFFlat インデックスは最大 2,000 次元までしか対応していません。gemini-embedding-001 の出力は 3,072 次元のため、最大 4,000 次元まで対応する HNSW インデックスを halfvec(半精度浮動小数点)キャストと組み合わせて使用しています。
関連ファイル
| 役割 | パス |
|---|---|
| API ルート | backend/src/interfaces/routes/candidates.ts |
| サービス(コアロジック) | backend/src/application/services/JobRecommendationService.ts |
| リポジトリインターフェース | backend/src/domain/repositories/IJobRecommendationRepository.ts |
| リポジトリ実装 | backend/src/infrastructure/repositories/supabase/JobRecommendationRepository.ts |
| Gemini クライアント | backend/src/infrastructure/external/gemini/GeminiClient.ts |
| エンティティ定義 | backend/src/domain/entities/JobRecommendation.ts |
| 求人エンベディングバッチ | backend/src/scripts/embed-jobs.ts |
| 統合 RPC(SQL) | supabase/migrations/20260218010000_match_jobs_by_embedding_v2.sql |
| Embedding 3072次元移行 | supabase/migrations/20260218100000_upgrade_embedding_to_3072.sql |
| フロントエンド API | frontend/src/lib/api/job-recommendations.ts |
| React Query フック | frontend/src/hooks/useJobRecommendations.ts |
| UI コンポーネント | frontend/src/components/JobRecommendationPanel.tsx |
| 表示画面(統合先) | frontend/src/components/InterviewSheetModal.tsx |