自然言語で検索できる求人サービスをGPTのFunction Callingで作ってみた。
結果
こんな感じのものができた。
ちなみに、求人情報はQiita Jobsからスクレイピングした。
(登録しなくても、求人情報全部見れたから)
質問内容
GPTによって生成された検索用パラメーター
{
"required_skills": "AWS,Kubernetes",
"minimum_salary": 1100,
"job_positions": "インフラエンジニア",
"locations": "東京都",
"welfare_benefits": "交通費支給",
"remote_work_option": True,
}
作ってみた感想
正直、求人検索に関しては、一般的な求人サイトの検索用のフォームがあれば、それで十分だから、今回作ったものは需要なさそうw
あと転職エージェントの代替を目指すなら、普通に結果として求人情報を返すのではなく、自然言語で返してくれたほうがそれっぽい。
だから、別の方法も色々試してみようと思う。
GPT-3.5とかGPT-4でファインチューニングできるようになるらしいし、できるようになったら試してみようと思う。
LangChainも触ってみる。
本当はAWS上にデプロイしようと思ってて、ドメインも取得して、terraformでリソースも生成してたんだけど、求人情報を定期的に取得して更新するバッチ処理とか、その他いろいろなものをガチで作ろうとすると、しんどいなと思ったのでやめた。w
まあ、Function Callingの実験はできたから、良しとする。
ただ、FunctionCallingのおかげで、様々なものとのインテグレーションが楽になったのは間違いない。これから、どんどん使われていくと思う。
詳細
作ろうと思った背景
最近、ハードコアに働ける場所を探していて、そのために正社員転職しようと思い、転職活動をしている。
その時、転職エージェントを使う機会もあったが、転職エージェントのやっていることは、求職者からヒアリングして、それに合った求人を紹介すること。
あれ?転職エージェントの仕事って、LLMでもできんじゃね?て思ったのがきっかけ。
使用したGPTのモデル
gpt-3.5-turbo-16k-0613
スクレイピングしたデータを構造化(MySQLに入れるデータに変換)するために使った。function callingの実験をするために使ったけど、スクレイピングしてる時点である程度構造化できてるので、別に使わなくてもよかった気がした。ただ、求人情報のデータ(今回はmarkdown形式)をそのまま入れるだけで、いい感じにしてくれるので、使い道は結構ありそう。GPT-4のほうが精度良かったけど、コストが高くて現実的ではなかった。あとトークン足りなかった。
gpt-4-0613
ユーザーが入力した情報から、OpenSearchの検索用のパラメータを作成する(Function Calling)のに使った。GPT-3でもよかったが、精度が低かったのでgpt-4にした。
技術スタック
python
django
見た目とか、もろもろの機能とかをDjangoで実装した。大した内容じゃないから、flaskでやればよかったかも。
MySQL
求人サイトからスクレイピングしたデータいれてる
OpenSearch
検索用。基本的に、MySQLのデータをそのままコピーしただけ。
その他ライブラリ
scrapy
スクレイピングのためにいれた。
openai
今回の主役となるGPTのAPI(Function Calling)を使うために導入
プロンプト
実験的にやってみただけなので、参考程度にw
ちなみに、enumのオプションは一切使ってない。GPTの可能性を狭めるかなと思ったのと、検索はOpenSearchのほうがいい感じにやってくれると思ったから。
もちろん、正確な値を求めるようなケースでは、設定したほうが絶対いい。
求人情報解析(非構造化データから構造化データへの変換)のプロンプト
functions = [
{
"name": "insert_job_table",
"description": "markdown形式の求人情報を解析して、テーブルに登録する",
"parameters": {
"type": "object",
"properties": {
"job_title": {
"type": "string",
"description": "求人タイトル",
},
"required_skills": {
"type": "string",
"description": "必要スキル(技術)。複数ある場合はカンマ区切り。ex. React,Angular,AWS",
},
"minimum_salary": {
"type": "integer",
"description": "最小年収(単位は万)。 ex. 650",
},
"maximum_salary": {
"type": "integer",
"description": "最大年収(単位は万)。 ex. 1400",
},
"positions": {
"type": "string",
"description": "募集ポジション・職種。複数ある場合はカンマ区切り。 ex. バックエンドエンジニア,インフラエンジニア",
},
"locations": {
"type": "string",
"description": "勤務地。複数ある場合はカンマ区切り。 ex. 東京都,北海道/札幌市",
},
"qualifications": {
"type": "string",
"description": "応募資格・あると望ましいスキル・求める人物像。複数ある場合はカンマ区切り。 ex. データベースの使用経験,コードレビューの経験,能動的に作業できる人",
},
"welfare_benefits": {
"type": "string",
"description": "待遇・福利厚生。複数ある場合はカンマ区切り。 ex. 住宅手当,交通費支給",
},
"company_cultures": {
"type": "string",
"description": "企業文化(特徴・風習)。複数ある場合はカンマ区切り。 ex. スタートアップ文化,エンジニアファースト",
},
"employment_categories": {
"type": "string",
"description": "雇用区分。複数ある場合はカンマ区切り。 ex. 正社員,契約社員",
},
"work_system": {
"type": "string",
"description": "勤務制度。複数ある場合はカンマ区切り。 ex. フレックスタイム制",
},
"remote_work_option": {
"type": "boolean",
"description": "リモートワークが可能かどうか。不明な場合は[false]。 ex. true",
},
"company_name": {
"type": "string",
"description": "会社名。 ex. 株式会社Google",
},
"company_capital": {
"type": "integer",
"description": "会社資本金。単位は円。 ex. 120000000",
},
"company_founded_date": {
"type": "string",
"description": "会社設立年月日。「YYYY-MM-DD」のフォーマット。 ex. 2019-04-01",
},
"company_employee_count": {
"type": "integer",
"description": "会社従業員数。 ex. 30",
},
},
"required": [
"job_title",
"required_skills",
"minimum_salary",
"maximum_salary",
"positions",
"locations",
"qualifications",
"welfare_benefits",
"company_cultures",
"employment_categories",
"work_system",
"remote_work_option",
"company_name",
# "company_capital",
# "company_founded_date",
# "company_employee_count",
],
},
}
]
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo-16k-0613",
messages=[
{
"role": "system",
"content": "ユーザーから、与えられた求人情報(markdown形式)の解析をして、テーブルに登録してください。",
},
{"role": "user", "content": job_markdown_raw_data}, # 求人情報の元データ
],
functions=functions,
function_call={"name": "insert_job_table"},
)
求人検索時(検索用のパラメーター生成)のプロンプトは下記
functions = [
{
"name": "search_jobs",
"description": "ユーザーから与えられた情報をもとに求人を検索する。",
"parameters": {
"type": "object",
"properties": {
"required_skills": {
"type": "string",
"description": "求人で必要と明記されているスキル(技術)。複数ある場合はカンマ区切り。ex. React,Angular,AWS",
},
"minimum_salary": {
"type": "integer",
"description": "最小年収(単位は万円)。 ex. 650",
},
"maximum_salary": {
"type": "integer",
"description": "最大年収(単位は万円)。 ex. 1400",
},
"job_positions": {
"type": "string",
"description": "募集ポジション・職種。複数ある場合はカンマ区切り。 ex. バックエンドエンジニア,インフラエンジニア",
},
"locations": {
"type": "string",
"description": "勤務地。複数ある場合はカンマ区切り。 ex. 東京都,北海道",
},
"qualifications": {
"type": "string",
"description": "ユーザーが保持している資格や、今までの経験。 ex. AWS Solution Architect Professional保有,データベースの使用経験,コードレビューの経験,能動的に作業できる人",
},
"welfare_benefits": {
"type": "string",
"description": "待遇・福利厚生。複数ある場合はカンマ区切り。 ex. 住宅手当,交通費支給",
},
"company_cultures": {
"type": "string",
"description": "企業文化(特徴・風習)。複数ある場合はカンマ区切り。 ex. スタートアップ文化,エンジニアファースト",
},
"employment_categories": {
"type": "string",
"description": "雇用区分。複数ある場合はカンマ区切り。 ex. 正社員,契約社員",
},
"work_system": {
"type": "string",
"description": "勤務制度。複数ある場合はカンマ区切り。 ex. フレックスタイム制",
},
"remote_work_option": {
"type": "boolean",
"description": "リモートワークが可能かどうか。デフォルトは[true]。 ex. true",
},
"company_name": {
"type": "string",
"description": "会社名の検索で使うキーワード。 ex. 株式会社Google",
},
"minimum_company_capital": {
"type": "integer",
"description": "最小会社資本金。単位は円。 ex. 120000000",
},
"maximum_company_capital": {
"type": "integer",
"description": "最大会社資本金。単位は円。 ex. 120000000",
},
"start_company_founded_date": {
"type": "string",
"description": "検索範囲の開始日。会社設立年月日。「YYYY-MM-DD」のフォーマット。 ex. 2019-04-01",
},
"end_company_founded_date": {
"type": "string",
"description": "検索範囲の終了日。会社設立年月日。「YYYY-MM-DD」のフォーマット。 ex. 2019-04-01",
},
"minimum_company_employee_count": {
"type": "integer",
"description": "最小会社従業員数。 ex. 30",
},
"maximum_company_employee_count": {
"type": "integer",
"description": "最大会社従業員数。 ex. 30",
},
},
},
}
]
completion = openai.ChatCompletion.create(
model="gpt-4-0613",
messages=[
{
"role": "system",
"content": "ユーザーから、情報をもとに、求人を検索してください。",
},
{"role": "user", "content": question}, # ユーザーからの質問
],
functions=functions,
function_call={"name": "search_jobs"},
)