この記事は Howtelevision Advent Calendar 2025 の11日目の記事です。
こんにちは!25新卒で入社した木元です。
今年の5Valuesがテーマということで、私が入社して最初にOwnershipを持ってリリースした「職務経歴書PDF自動インポート機能」について紹介したいと思います。
私が所属しているチームでは、中途転職サービスの外資就活ネクストを運営しています。 転職サービスを利用したことがある方なら、一度は「職務経歴の入力が面倒」だと感じたことがあるのではないでしょうか。特に複数のサービスを併用している場合、そのすべてを最新の状態に維持し続けるのは非常に困難です。
そこで私たちは、ご自身の職務経歴書PDFをアップロードしていただくだけで、外資就活ネクストのの職務経歴を自動で埋めることができる機能をリリースしました。
今回は、この機能を支えるバックエンド技術(Google Vision API × AWS Bedrock)の設計と実装について解説します。
機能の全体像:PDFが「構造化データ」になるまで
ユーザー体験としては「PDFをドラッグ&ドロップして待つだけ」というシンプルなものですが、裏側では複数の技術要素を組み合わせたパイプライン処理が動いています。
- セキュリティチェック: アップロードされたファイルのウイルススキャン
- OCR(文字認識): Google Vision API でPDFからテキスト情報を抽出
- 構造化(LLM): AWS Bedrock (Claude 3.5 Sonnet) でテキストを意味のあるデータに変換
- 保存: Railsアプリケーションでバリデーションを行いDBに保存
これらをSidekiqを用いた非同期処理として実装し、安全かつユーザーを待たせないUXを実現しています。ここからは、各フェーズでどのような技術的工夫を行ったのか紹介します。
1. ウイルスチェック
外部からファイルを受け取る機能において、セキュリティは最優先事項です。 スキャンエンジンにはオープンソースの ClamAV を採用し、AWS Lambda上で実行しています。
S3にアップロードされたPDFは、イベントトリガーでLambdaを起動し即座にウイルススキャンが行われます。もし感染が疑われるファイルだった場合は、後続のOCR処理などは一切行わず、即座に処理を中断して破棄する仕組みになっています。これにより、システム内部への脅威侵入を防いでいます。
また、正常に処理が完了したファイルについても、「用が済んだら即削除」 を徹底しています。
履歴書は個人情報の塊です。OCRとAIによる解析が終わった瞬間、GCS上の実体ファイルはもちろん、データベース上のファイル参照パスも nil に更新して完全に消去する実装を入れています。万が一の漏洩リスクを最小限にするために重要です。
2. OCR処理
ウイルスチェックをパスした安全なファイルは、次のステップである文字認識に進みます。AWS Textractなどの利用も検討しましたが、検証で最も性能が良かった Google Vision API を採用しました。
こだわりポイント:DOCUMENT_TEXT_DETECTION の採用
履歴書は段組みや表組みが多く、単純な行単位の読み取りでは文脈が崩れてしまいがちです。そこで、文書構造の解析に特化した DOCUMENT_TEXT_DETECTION モードを使用しました。これにより、複雑なレイアウトでも高い精度でテキストを抽出できるようになりました。
また、外部APIを利用する以上、ネットワークエラー等は避けられません。一時的なエラー(500系など)は自動でリトライしつつ、ファイル形式不正などのユーザーエラー(400系)は即座にフィードバックを返すよう、エラーハンドリングを細かく設計しています。
3. AIによる構造化:Claude 3.5 Sonnet × Tool Use
OCRで得られたテキストは、まだ単なる「文字の羅列」です。これを「会社名」「在籍期間」「職務内容」といったデータベースで扱える形に変換するのが、この機能の肝となるAIパートです。
モデルには、日本語性能と追従性が非常に高い Claude 3.5 Sonnet(AWS Bedrock経由)を採用しました。 ※なお、利用しているGoogle Vision APIおよびAWS Bedrockは、入力データをモデルの学習に使用しない規約となっており、情報の機密性は担保されています。
プロンプトエンジニアリングの工夫
LLMに正確な仕事をしてもらうため、システムプロンプト(指示書)にはかなりのこだわりを詰め込んでいます。
- 日付の正規化: 「2024年11月〜現在」という表記を
{ joined_at: "2024-11-01", leaved_at: null }のように、システムが解釈可能な形式に変換するルールを明記。 - Tool Use (Function Calling) の活用: LLMに自由記述させるのではなく、「
extract_career_dataという関数を、引数(JSON)付きで実行してください」と指示することで、出力フォーマットの揺れを防ぎ、プログラムで確実にパースできるJSONを得ています。
# 実際のプロンプトの一部(日付変換指示) ## 日付変換ルール 以下の表記を適切にYYYY-MM-DD形式に変換してください: - "2024年11月~継続中" → joined_at: "2024-11-01", leaved_at: null - "2018年7月~2022年8月" → joined_at: "2018-07-01", leaved_at: "2022-08-31"
4. データ保存:信頼性を担保する堅実な実装
AIが出力したデータといえども、100%信頼してそのままDBに入れるわけにはいきません。Rails側のService層では、以下のような防御的実装を行っています。
- 厳格なバリデーション: 必須項目(会社名など)が欠けていないかチェック。
- トランザクションの強制: 「既存の経歴を削除して新しいデータを保存する」という処理の整合性を保つため、トランザクション内でしか実行できないようコードレベルで制限をかけています。
最後に:Ownershipを持ってやりきってみて
入社直後からこのようなチャレンジングな機能開発を任せてもらえたことは、自分にとって大きな成長機会となりました。 チームの先輩方の手厚いレビューやサポートもあり、これまではBackendとFrontendの実装経験しかありませんでしたが、今回初めてTerraformを用いてのインフラリソース定義から関わることができ、機能全体のオーナーシップを持つことができました。
今後も新しい技術を積極的に取り入れ、ユーザーにとって価値のある機能を届けていきたいと思います!
We're Hiring!
今回ご紹介した取り組みに共感いただけるエンジニアの方がいらっしゃいましたら、ぜひ私たちのチームにジョインしませんか?
弊社では、一緒に技術的な課題に取り組んでくれるエンジニアを募集中です! 現在募集中の採用情報の詳細は下記をご覧ください。
開発チームの最新情報はXでも発信しています。ぜひフォローをお願いします!