フリーランス案件への応募時に使用する提案文を、生成AIで作成・管理できるWebシステムを開発しました。
一般的なAI提案文作成ツールとの違いは、募集文だけをAIへ渡して文章を生成するのではなく、事前に登録した本人の職務経歴、スキル、資格、自己PR、仕事への考え方などを参照する点です。
募集内容に関連する経歴をベクトル検索で抽出し、その情報だけを根拠として提案文を生成します。
このシステムで特に重視したのは、文章を過剰に良く見せることではありません。
本人の経歴にない実績を書かず、実際の経験を根拠に、その人らしい提案文を作ることです。
職務経歴書PDFの取込、職務経歴の管理、提案方針の設定、提案文の生成・編集・保存、応募後に使用するメッセージテンプレートまで、フリーランス案件への応募業務を一つのシステムにまとめました。
現在はローカル環境で稼働しており、一般公開しているデモ環境はありません。本記事では、システムを開発した背景、主な機能、技術構成、設計上の工夫について紹介します。

なぜAIで作った提案文は不自然になりやすいのか
ChatGPTなどの生成AIに案件の募集文を貼り付ければ、短時間で応募用の提案文を作成できます。
しかし、生成された文章を確認すると、次のような問題が起こることがあります。
- 実際には経験していない業務を経験済みのように書く
- 募集文を言い換えただけの文章になる
- 「貢献できます」「尽力します」といった一般論が多くなる
- 案件と直接関係のない経歴まで詰め込まれる
- 本人が普段使わない表現や文体になる
- 本人が重視している提案方針が反映されない
- 稼働時間や参画可能時期などの重要な条件が抜ける
- 文章は整っているものの、具体的な根拠がない
この原因は、生成AIを使うこと自体にあるわけではありません。
一般的な生成AIは、応募者本人がどのような仕事を経験し、何を得意とし、どのような立場でプロジェクトに関わってきたのかを知りません。
本人に関する情報が不足した状態で「この案件への提案文を書いて」と指示されれば、募集文と一般的な応募文のパターンをもとに文章を補完します。
その結果、読みやすくはあっても、具体性のない文章や、本人の実態と異なる文章が生成されやすくなります。
問題は「AIが書くこと」ではなく「本人を知らないAIが書くこと」
AIを使って提案文を作ること自体が問題なのではないと考えています。
問題になるのは、本人の経験、考え方、現在の状況を理解していないAIが、本人の代わりに文章を作ることです。
応募者が過去に参画したプロジェクト、担当した役割、使用した技術、得意な業務、自己PR、仕事に対する方針をAIが参照できれば、生成される文章は大きく変わります。
例えば、案件で次のような経験が求められているとします。
- 顧客との要件調整
- PythonによるAPI開発
- プロジェクト管理
- 生成AIを活用した機能開発
この場合、登録されているすべての経歴をAIへ渡すのではなく、募集内容と関連性の高い経歴だけを検索します。
そのうえで、検索された経歴を根拠として提案文を生成します。
これにより、一般論ではなく、次のような具体的な内容を提案文へ反映しやすくなります。
- どのようなプロジェクトに参画したか
- どのような役割を担当したか
- どの技術を使用したか
- 顧客やチームとどのように関わったか
- 今回の案件へどのように経験を活かせるか
最終的な確認は本人が行いますが、最初から本人の情報を踏まえて文章が生成されるため、AIが作成した文章を大幅に書き直す工数を減らせます。
目指したのは、AIが作った文章を人間が一から書き直す仕組みではありません。
本人が書いた文章に近く、最小限の確認と修正だけで応募に使用できる提案文を作る仕組みです。
開発したAI提案文生成システムの概要
今回開発したシステムでは、ユーザーごとに次の情報を登録できます。
- 職務経歴
- 参画プロジェクト
- プロジェクトで担当した役割
- 使用した技術やスキル
- 資格
- 人柄や強み
- 興味のある分野
- 自己PR
- 希望する文体
- 提案文の構成方針
- 現在の稼働可能時間
- 参画可能時期
- 希望する働き方
- 応募時に伝える必要がある制約
登録した情報のうち、案件の募集内容と関連性が高いものだけをベクトル検索で抽出し、生成AIへ渡します。
システム全体の基本的な流れは次のとおりです。
職務経歴・プロフィールを登録
↓
案件の募集文を貼り付け
↓
募集文をEmbeddingへ変換
↓
類似する職務経歴・プロフィールをベクトル検索
↓
関連性の高い情報だけを取得
↓
提案方針と取得した経歴を生成AIへ送信
↓
本人の事実に基づいた提案文を生成
↓
画面上で確認・編集
↓
クリップボードへコピーして応募
↓
必要に応じてテンプレートからフォローメッセージを作成
主な機能
| 機能 | 内容 |
|---|---|
| 認証 | メールアドレスとパスワードによるサインアップ・ログイン、JWT認証、保護ルート |
| 職務経歴管理 | プロジェクト名、役割、概要などの登録・編集・削除。保存時にEmbeddingを付与 |
| プロフィール管理 | 資格、人柄、興味、自己PRなどを1行単位で登録 |
| 提案方針設定 | 文体、文章構成、強調方法などをユーザー単位で保存 |
| 現在の状況管理 | 稼働時間、参画可能時期、働き方などの条件を保存 |
| 職務経歴書PDF取込 | PDFからテキストを抽出し、LLMでプロジェクトやスキルへ構造化 |
| 取込プレビュー | AIが分類した内容を確認・修正してからデータベースへ登録 |
| 提案文生成 | 募集文と関連する経歴を検索し、応募用の提案文を生成 |
| 提案文管理 | 生成した文章の一覧、詳細確認、編集、コピー、削除 |
| メッセージテンプレート | 応募後の連絡やフォローに使用する定型文を管理 |
職務経歴をベクトル検索して案件に近い情報だけを取得
このシステムでは、登録されているすべての職務経歴を毎回LLMへ渡しているわけではありません。
職務経歴を登録する際に、プロジェクト概要や担当内容をEmbeddingへ変換し、ベクトルデータとしてPostgreSQLへ保存します。
案件へ応募するときは、貼り付けられた募集文もEmbeddingへ変換します。
そのうえで、pgvectorを使用して募集文と職務経歴のコサイン類似度を計算し、関連性の高い経歴を上位から取得します。
募集文
↓
Embeddingへ変換
↓
pgvectorで類似度検索
↓
関連性が高い職務経歴を上位から取得
↓
取得した経歴だけをLLMへ渡す
すべての経歴を渡さない理由は、入力できる文章量やAPI利用料を抑えるためだけではありません。
案件と関係のない経歴が大量に含まれると、AIが重要な情報を判断しにくくなります。
その結果、関連性の低い経験を無理に提案文へ含めたり、本当に強調すべき経験が埋もれたりする可能性があります。
案件に近い情報だけを取得することで、次の効果を狙っています。
- 提案文の具体性を高める
- 関係のない経歴が入ることを防ぐ
- 案件ごとに強調する実績を変える
- 入力トークン数を減らす
- API利用料を抑える
- AIが参照する情報を確認しやすくする
- 生成結果の根拠を明確にする
RAGの考え方を、社内文書検索やFAQ検索ではなく、個人の職務経歴と案件のマッチングに応用した構成です。
製品の核は「盛らない」こと
このシステムでは、提案文を魅力的に見せることよりも、事実と異なる内容を書かないことを優先しています。
生成時には、次のような制約を設けています。
- 登録されていない実績を書かない
- 経験していない技術を経験済みと表現しない
- 関連性の低い経歴を無理に案件へ結び付けない
- 不明な単価や稼働条件を推測しない
- 情報が不足している項目は省略する
- 入力が必要な項目は「要記入」として残す
- 募集文に書かれている内容を本人の実績として扱わない
- ユーザーから指示された場合でも、登録情報にない実績は追加しない
生成AIは、不足している情報を自然な文章で補完する性質があります。
一般的な文章作成では便利ですが、職務経歴や応募文では、自然に見える誤情報が混ざる原因になります。
特に提案文では、実務経験、使用技術、担当範囲、稼働条件など、事実関係が重要です。
そのため、このシステムでは、文章の華やかさよりも情報の出所を優先しています。
AIが生成した提案文は自動送信せず、本人が画面上で確認してからコピーします。
AIにすべてを任せるのではなく、事実に基づく下書きをAIが作り、最終判断を本人が行う設計です。
提案方針をユーザー単位と案件単位の二層で管理
同じ職務経歴を持っていても、提案文の書き方には個人差があります。
例えば、次のような違いがあります。
- 結論から簡潔に書きたい
- 経歴を時系列で説明したい
- 技術力より顧客対応力を強調したい
- 丁寧だが堅すぎない文章にしたい
- 箇条書きを多く使いたい
- 稼働条件を最初に明記したい
- 未経験分野について正直に伝えたい
- 相手の課題に対する理解を最初に示したい
そこで、提案方針を二層に分けて管理しています。
全体の提案方針
ユーザーが普段使用したい文体、構成、表現方法などを保存します。
毎回同じ指示を入力しなくても、その人が通常使用する文章の方針を提案文へ反映できます。
例えば、次のような方針を登録できます。
丁寧だが過度に堅くしない。
最初に応募理由を簡潔に記載する。
関連する経験を具体的に示す。
経験のない内容は正直に伝える。
抽象的な意欲表現を増やしすぎない。
今回の作成方針
案件ごとに追加する個別の指示です。
例えば、次のような内容を指定できます。
今回は要件定義と顧客折衝の経験を中心に書く。
AIの商用開発経験は限定的であることを正直に伝える。
個人開発で取り組んでいるRAGとOpenAI APIの経験を補足する。
文章は700文字以内にする。
全体方針と今回の方針が矛盾する場合は、今回の方針を優先します。
ただし、「登録されていない実績を書かない」というルールは、案件ごとの指示によって上書きされないようにしています。
職務経歴書PDFを読み込んで経歴を自動登録
提案文の精度を高めるためには、本人の経歴を詳しく登録する必要があります。
一方で、過去に参画したプロジェクトを一件ずつ手入力するのは大きな負担です。
既に職務経歴書を作成している場合、同じ情報をもう一度システムへ入力することになります。
そこで、既存の職務経歴書PDFを読み込み、登録用のデータへ変換する機能を実装しました。
処理の流れは次のとおりです。
職務経歴書PDFをアップロード
↓
pypdfでテキストを抽出
↓
LLMで内容を解析
↓
参画プロジェクト・役割・スキル・資格などに分類
↓
取込前のプレビューを表示
↓
ユーザーが内容を確認・編集
↓
確認したデータだけを一括登録
AIが分類した結果を、そのままデータベースへ保存しないことも重要な設計方針です。
PDFのレイアウトや記載方法によっては、プロジェクトの区切り、担当期間、役割、使用技術などを正しく認識できない場合があります。
そのため、必ずプレビュー画面を表示し、人間が内容を確認・修正してから登録します。
自己PRについては、AIが過度に要約すると本人らしい表現が失われるため、できる限り原文に近い状態で保持する方針としています。

募集文の形式に合わせて出力方法を変更
フリーランス案件によって、求められる応募形式は異なります。
一般的な提案文を求める案件もあれば、次のように回答項目が指定されている案件もあります。
1. 簡単な自己紹介
2. 類似案件の経験
3. 使用できる技術
4. 週の稼働可能時間
5. オンライン面談の可否
このような案件に対して通常の長文を生成すると、発注者が確認したい情報を探しにくくなります。
また、指定項目への回答が抜けていれば、募集要項を十分に読んでいないと判断される可能性もあります。
そこで、募集文に番号付きの回答項目がある場合は、原則として同じ番号と順番で回答します。
回答項目が指定されていない場合は、次のような自然文の構成で提案文を生成します。
- 応募理由
- 関連する職務経歴
- 案件に活かせるスキル
- 稼働条件
- 面談やスキルシートに関する案内
固定された一つのテンプレートを使うのではなく、募集文の形式に合わせて、発注者が確認しやすい提案文を生成する設計です。
提案文の生成後も同じシステム内で管理
提案文は、生成して終わりではありません。
実際の応募では、生成した文章を確認・修正して送信し、その後に面談日程の調整、追加質問への回答、スキルシートの送付、応募後のフォローなどが発生します。
今回のシステムでは、次の操作を一つのプロダクト内で行えます。
- 提案文の生成
- 生成結果の保存
- 提案文の編集
- クリップボードへのコピー
- 過去に作成した提案文の確認
- 不要な提案文の削除
- フォローメッセージのコピー
- メッセージテンプレートの登録・編集
例えば、次のような定型文を登録できます。
- 応募後の補足連絡
- 面談候補日の回答
- スキルシート送付時のメッセージ
- 面談後のお礼
- 選考状況を確認するフォロー
- 辞退連絡
提案文の生成機能だけを独立させず、案件へ応募する前後のコピペ作業を一連のワークフローとして設計しています。
使用した技術
| 領域 | 使用技術 |
|---|---|
| フロントエンド | Next.js App Router、React、TypeScript、Tailwind CSS |
| データ取得・状態管理 | TanStack Query |
| API | FastAPI、SQLAlchemy async、Pydantic |
| Python環境 | uv |
| データベース | PostgreSQL 16、pgvector |
| 認証 | JWT(HS256)、bcrypt |
| 生成AI | OpenAI Chat API |
| 生成モデル | gpt-4o-mini |
| Embedding | text-embedding-3-small |
| PDF解析 | pypdf |
| 開発環境 | Docker Compose、Makefile |
| リポジトリ構成 | pnpm monorepo |
| CI | GitHub Actions |
フロントエンドとAPIを分離し、Next.jsからFastAPIを呼び出す構成です。
職務経歴やプロフィール情報はPostgreSQLへ保存し、ベクトルデータについてはpgvectorで管理しています。
開発で担当した範囲
今回のシステムは個人開発として、次の工程を一通り担当しました。
- 課題の整理
- ユースケースの設計
- 要件定義
- 画面構成の検討
- データベース設計
- 認証設計
- API設計
- フロントエンド開発
- バックエンド開発
- OpenAI API連携
- Embeddingとベクトル検索の実装
- PDF解析
- プロンプト設計
- Dockerによる開発環境構築
- GitHub ActionsによるCI構築
- 動作確認とテスト
単に生成AIのAPIを呼び出すだけではなく、AIへどの情報を渡すか、どの情報を渡さないか、人間による確認をどこに入れるかまで含めて設計しました。
今後の拡張|案件探しから提案文作成までの自動化
現在のシステムでは、ユーザーが案件の募集文を貼り付け、その内容に合った提案文を生成します。
今後は、提案文の生成だけでなく、案件を探す工程から自動化できると考えています。
例えば、クラウドワークスやランサーズなどの案件情報を、各サービスの利用規約や提供されているAPI、許可された取得方法に配慮したうえで定期的に取得します。
取得した案件情報に対して、登録済みの職務経歴、スキル、希望条件、稼働可能時間などとの適合度を計算します。
案件情報を定期取得
↓
募集内容をEmbeddingへ変換
↓
本人の職務経歴・スキルとの類似度を計算
↓
希望単価・稼働時間・勤務条件で絞り込み
↓
適合度の高い案件を一覧表示
↓
案件ごとに関連する経歴を抽出
↓
本人の提案方針に沿った提案文を生成
この仕組みを実装すれば、ユーザーは多数の案件を一件ずつ確認する必要がなくなります。
毎日取得された案件の中から、自分の経験や条件に合うものだけを確認できます。
さらに、案件ごとに関連する職務経歴やプロフィールを自動で選定し、原則として大幅なブラッシュアップを必要としない提案文を事前に作成できます。
人間が行う作業は、次のような最終判断に絞られます。
- 本当に応募したい案件かを確認する
- 提案文の事実関係を確認する
- 案件固有の質問へ回答する
- 必要に応じて表現を微調整する
- 応募を実行する
フリーランスにとって、案件探しと提案文作成は重要な営業活動です。
一方で、募集案件の確認、応募条件の判定、類似実績の選定、提案文の作成を毎回手作業で行うと、多くの時間が必要になります。
案件の取得、マッチング、提案文の下書きまでを自動化できれば、フリーランスが営業活動に使う時間を大幅に削減できます。
削減できた時間を、次の活動へ振り分けられます。
- 受注案件の開発
- 技術学習
- ポートフォリオの改善
- 顧客とのコミュニケーション
- 自社サービスの開発
- 既存顧客への追加提案
- 休息や生活時間の確保
単に提案文を早く作るのではなく、案件を探して応募するまでの営業プロセス全体を短縮することが、このシステムの将来的な方向性です。
ただし、完全な自動応募を目的としているわけではありません。
案件への応募は、相手企業との契約や信頼関係につながる重要な行為です。
そのため、案件の選定や提案内容の最終確認は本人が行い、AIは情報収集、候補抽出、文章作成を支援する役割とするのが適切だと考えています。
この仕組みを応用できる業務
今回開発した仕組みは、フリーランスの提案文作成以外にも応用できます。
営業提案書の作成
過去の導入実績、対応可能な業務、製品情報などを検索し、顧客の相談内容に合わせた提案文を生成できます。
人材紹介会社の推薦文作成
求職者の職務経歴や面談内容から、求人に関連する経験を抽出し、企業向けの推薦文作成を支援できます。
社内人材のプロジェクト推薦
社員のスキルや参画履歴をベクトル検索し、新規プロジェクトに合う人材と、その根拠を提示できます。
補助金・助成金申請の文章作成
企業情報、過去の事業、計画内容などを登録し、申請項目に関連する情報を参照しながら下書きを生成できます。
問い合わせ返信の作成
顧客情報、過去の対応履歴、社内ルールなどを検索し、状況に合った返信文を作成できます。
報告書や議事録からの文書生成
過去の報告内容やプロジェクト情報を参照し、定期報告書や説明資料の下書きを生成できます。
営業案件のマッチング
企業の実績、対応可能な技術、予算、地域、対応時期などを登録し、問い合わせ内容に合う担当者や提案内容を自動で選定できます。
重要なのは、生成AIへすべての情報を無条件に渡すのではなく、入力内容に関連する根拠を検索し、その情報だけを使って文章を生成することです。
AI文章生成システムで重要なのは生成前の設計
生成AIを使えば、文章そのものは短時間で作れます。
しかし、業務で継続的に使用するためには、文章生成以外の部分を設計する必要があります。
- 元となる情報をどのように登録するか
- PDFや既存資料をどのように取り込むか
- 関連情報をどのように検索するか
- AIへ渡してよい情報をどう制御するか
- 虚偽や推測をどう防止するか
- 生成結果を誰が確認するか
- 修正した文章をどう管理するか
- 個人情報や機密情報をどう扱うか
- 既存の業務フローへどう組み込むか
- AIが誤った場合にどう修正できるようにするか
今回の開発では、AIが文章を書く部分だけでなく、その前後の業務フローを含めてWebシステム化しました。
生成AIを業務へ導入する場合、モデルの性能だけでなく、参照するデータ、検索方法、プロンプト、人間による確認工程を一体として設計することが重要です。
FAQ
AIで作った提案文をそのまま送信できますか?
最小限の確認だけで使用できる品質を目指していますが、送信前には本人が内容を確認する運用を前提としています。
ベクトル検索やプロンプトによって誤情報を減らしても、生成AIの出力が常に正しいとは限りません。
実務経験、使用技術、稼働条件、参画可能時期、案件固有の質問などを最終確認してから使用します。
ChatGPTに職務経歴書を貼り付ける方法との違いは何ですか?
毎回職務経歴書全体を貼り付けるのではなく、募集内容に関連する職務経歴やプロフィールだけをベクトル検索で取得します。
また、職務経歴の管理、提案方針、現在の稼働状況、PDF取込、生成履歴、メッセージテンプレートまで一つのシステムで管理できます。
単発の文章生成ではなく、継続的な応募業務を効率化するためのWebシステムとして設計しています。
職務経歴書PDFは自動で登録されますか?
PDFからテキストを抽出し、LLMがプロジェクト、役割、スキル、資格などへ分類します。
ただし、解析結果をそのまま登録するのではなく、プレビュー画面で本人が確認・修正してから一括登録します。
AIが経験を勝手に追加することはありませんか?
登録されていない経歴を書かないよう、生成時のプロンプトに明示的な制約を設けています。
また、案件との関連性が低い経歴を無理に結び付けないようにしています。
ただし、生成AIの出力を完全に保証するものではないため、提案文を送信する前の人間による確認は必要です。
案件に関係のない経歴までAIへ送信されますか?
すべての職務経歴を送信するのではなく、募集文と類似度が高い経歴をベクトル検索で抽出し、上位の情報だけをAIへ渡します。
これにより、案件と関係のない経歴が提案文へ混ざることを減らしています。
案件を自動で探すこともできますか?
現在は募集文を手動で貼り付ける方式です。
今後は、規約や提供されている取得手段に配慮しながら案件情報を定期取得し、職務経歴、スキル、希望条件との適合度が高い案件を自動で抽出する機能へ拡張できます。
案件の抽出と同時に提案文の下書きを作成することで、案件探しから応募準備までの時間を短縮できます。
システムをオンラインで試せますか?
現在はローカル環境で稼働しており、一般公開しているデモ環境はありません。
本記事に掲載している画面は、ローカル環境で動作しているシステムのスクリーンショットです。
AI・RAGを活用した業務システム開発について
hiro-dev-labでは、生成AIを単体で利用するだけでなく、既存データ、PDF、データベース、外部API、業務フローと組み合わせたWebシステムの設計・開発に対応しています。
例えば、次のようなご相談が可能です。
- 自社データを参照する文章生成システムを作りたい
- 社内資料を検索するRAGを導入したい
- PDFから必要な情報を抽出・登録したい
- ChatGPTで行っている作業をWebシステム化したい
- 生成AIが事実と異なる内容を書く問題を減らしたい
- AIの出力を人が確認してから登録する業務フローを作りたい
- OpenAI APIを既存システムへ組み込みたい
- 案件や問い合わせ内容に合う実績を自動で検索したい
- 営業提案や応募文の作成業務を効率化したい
- AIを利用した業務自動化のPoCを実施したい
AIを導入すること自体を目的にせず、現在の業務、使用しているデータ、確認が必要な工程を整理したうえで、実際に運用できる仕組みを検討します。
要件が固まっていない段階でも、業務ヒアリングから業務フロー、機能、画面、データ構造を整理できます。
生成AI、RAG、PDF解析、ベクトル検索を活用したWebシステムについて、お気軽にご相談ください。