优化求职之路:如何利用ai代理自动化你的求职过程并找到最佳职位
求职与人工智能代理
用户: 人工智能找到最适合我个人资料的职位。 人工智能: 正在处理..
目录
简介
实践:实现人工智能驱动的求职引擎
求职可能是困难且耗时的。浏览职位列表可能需要几周甚至几个月,才能最终找到一份工作。
求职者的挣扎 — 作者提供的图片
所需的时间取决于许多因素,如专业技能、角色需求、市场等,但根据美国劳工统计局的失业数据(包括那些被动寻找工作的人),2024年3月的中位数持续时间为21.6周 ~ 5个月。
在人的年龄尺度上,考虑到普通求职者寻找新工作的时间,这就是花费在筛选数百份申请上的大量不可挽回的生命时间。
提交的申请数量是获得工作的成功因素之一。一项研究结果将获得合适职位所需的平均申请数量定位为100–200+份申请,尽管这可能会因市场、经济状况和申请者的专业技能而有所不同。申请数量和寻找工作的努力之巨大,正是我们构建高效、可扩展解决方案,节省个人数千小时的动力所在。
每个求职者面临的常见挑战(原因..)
求职的挣扎 — 作者使用OpenAI的DALLE-3生成的图像
在一个充满职位发布的市场中,找到最适合自己技能和要求的角色是具有挑战性的。如果你曾经处于这样的境地,你就知道在数百个职位列表中筛选和匹配技能、薪资范围、先决条件等是多么令人压力和疲惫。这个过程花费的时间越长,个人的积极性就越低,放弃搜索而选择妥协的可能性就越高。
找到合适角色所需的时间因个人经验、申请时机以及其技能在求职市场的需求程度而异。还有其他不可控因素,例如影响他们申请的行业和组织的因素。
我们在本文中构建人工智能驱动的求职引擎所面临的挑战主要集中在优化求职和匹配流程,从而解决以下关键挑战:
关键挑战
- 职位广告搜索:传统上,筛选职位始于选择职位发布的正确源平台。根据特定标准过滤职位,并通过消化所有内容来评估最终结果。为了更好地覆盖,许多人选择在多个平台上工作。当需要在多个来源之间权衡并跟踪每个职位时,困难就开始了。
- 职位广告评估:职位发布是否符合我之前的经验和技能?我是否具备所需的工作年限?它的位置是否合适?我需要会哪些语言?薪资范围是否满足我的需求?角色的开始日期是什么时候?在评估职位广告是否符合个人要求时,会出现一些这样的问题。
- 组织评估:求职筛选中的一个常见步骤是研究组织。评估组织的一些常见标准包括员工评价、市场表现、声誉等。可以根据组织所处的市场引入更多的基准或标准。如果你正在寻找跨市场的机会,这一步可能会非常耗时,因为需要考虑的因素很多。
- 职位筛选:在做出最终决定之前,经过筛选数十个或数百个职位列表后,你决定筛选出少数几个。无论选择何种方法,都需要基于某些标准的评分策略来对列表进行排序。这些是基于个人偏好和优先级的主观评分。你可以想象完成这项任务所需的职位列表之间的混合和匹配量。
⚠️ 注意: 在求职过程中,个人所经历的挑战远大于上述列表中提到的挑战。讨论的列表是针对文章内容和解决方案范围的。
列出的挑战包括重复任务、需要强大的推理能力,以及大量内容的消化,以得出明确的可操作列表。
这些任务完美符合采用大语言模型驱动的代理的关键特征。
人工智能代理在优化求职流程中的关键作用(是什么..)
代理工作流 — 作者提供的图像
想象一下,雇佣数百名能够筛选职位广告、深入评估广告公司及其职位发布,并最终量身定制最符合您需求的推荐的最终结果。这是一个完全基于自动化和推理的端到端流程,显著减少了任何人花费在筛选在线职位广告上的时间。
让我们分析人工智能代理在自动化和个性化求职及推荐方面的能力。我们将在后面的部分深入构建引擎。现在,我们关注人工智能代理能带来什么。
1. 代理可以使用工具
代理能够利用定义好的工具来完成他们的任务。这涉及到能够提供更好研究能力的工具,例如提供访问互联网和使用预定义API请求数据的能力。这扩展了代理与远超其训练数据的大量工具接口的能力,并进一步提供了用户编排代理如何使用选定工具包解决特定任务的能力。
2. 代理可以消化大量信息
随着人工智能模型上下文窗口的增大(1ml+),甚至达到无限上下文窗口,代理可以消化和推理的信息量不断增长。正确分布后,代理可以分析无限量的信息,并向用户提供最终的摘要版本。
3. 代理可以推理
虽然人工智能代理的推理能力可能存在局限性,但越来越多的新发布的改进和优化模型正在缩小这一差距。考虑到分析职位广告并根据某些参数将其与用户查询进行比较的任务,这一任务对于一个高度训练的人工智能代理来说,可能被视为一个简单的分析任务。
4. 代理可以总结和结构化结果
能够处理大量信息的工具是有价值的,但当它无法以高效和结构化的方式传递这些信息时,其价值就会降低。人工智能代理容易出现幻觉,这可能影响信息的总结和报告。提供一个经过良好设计的提示和额外的验证步骤可以确保代理以正确和可靠的结果完成其任务。
5. 代理可以协作
代理的背景、角色和任务的不同提供了一种变异性,支持从不同的角度解决问题。代理之间的协作利用了这种能力,以优化最终交付给用户的结果。
6. 代理是可扩展的
动态分配大量代理以并行执行任务是极其强大的。当问题可以被分解为更小的部分并由不同的代理处理时,这种方法显得尤为有效。可以将大量代理分配给一批职位发布,每个代理负责处理流程中的一个步骤,并将结果传递给下一个步骤。最终,代理可以收集结果以进行最终摘要和结构。这是构建快速和可扩展的职位广告在多个渠道或来源中分布处理的一种方法。
我们如何将所有这些能力整合在我们的用例中?
引入人工智能代理的能力到流程中(如何实现..)
为了确保一个利用所有人工智能能力的解决方案,我们在流程的每一步引入代理。
代理任务 — 作者生成的图像
- 代理将使用工具来检索关于职位广告的信息。初始职位发布将根据用户查询进行检索。
- 代理将根据用户的简历和查询为职位发布提供评分及其背后的理由。
- 代理将使用互联网访问工具来研究关于职位广告来源组织的信息,并基于此为组织提供评分。
- 代理将最终总结结果并根据预定义模型结构化结果。
下一部分将深入探讨利用列出的人工智能代理能力来构建求职引擎。
实践教程:构建一个人工智能驱动的求职引擎
在当前章节中,我们将逐步讲解完整的实现过程,并最终分析最终结果和潜在的改进。
项目结构
项目结构确保功能和组件之间有清晰的分离,提供了更简单的方法来修改和适应代码以满足您的需求。它由以下部分组成:
- 一个
configs
目录,包含配置代理(角色、背景、背景故事)和任务(描述和预期输出)所需的所有配置和参数 - 一个
data
目录,包含测试求职引擎所需的所有数据 - 一个
models
目录,包含预期输出模式的定义模型 - 一个
utils
目录,包含所需的支持函数 agents_factory.py
和tasks_factory.py
用于根据定义的配置动态生成代理和任务的实例。
project/
├── configs
│ └── agents.yml
│ └── tasks.yml
│
├── data
│ ├── sample_jobs.json
│ └── sample_resume.txt
│
├── models
│ └── models.py
│
├── utils
│ └── utils.py
│
├── .env
│
├── agents_factory.py
├── tasks_factory.py
│
└── main.py
框架
该项目将利用 crewAI框架 来构建端到端的应用程序。它提供了一个简单的接口来构建具有分配任务和特定工具的人工智能代理。它与其他可用的人工智能框架集成得非常好,因此非常适合本文的范围。
首先确保有一个Python环境(在我的情况下使用python-3.11.9),并安装所需的框架和所有工具。
pip install 'crewai[tools]'
安装 crewai[tools]
包应涵盖运行应用程序所需的所有包。
工具
代理工具 — 作者生成的图像
代理将需要一组工具来完成其分配的任务。工具分配给代理将取决于我们如何定义请求流架构以及信息如何从一个代理传输到另一个代理。
为了测试我们的用例,需要两个工具,这两个工具都已在crewAI中可用。
文件读取工具
在生产规模上,当可以管理积累大量可靠的API以提供职位数据时,首选的代理工具是那些能够与多个提供者接口并根据所选参数获取职位信息的工具。
在我们应用的范围内,我们将使用一个已经检索到的JSON响应_sample_jobs.json_
,该响应包含职位详细信息列表,以保持专注于展示解决方案。
生成的简历_sample_resume.txt_
也可用于测试代理提供的评分。
Serper开发工具
代理将被指派收集有关组织的信息并向用户提供评分反馈。该工具通过使代理能够在互联网上搜索来支持该过程。要使用该工具,请确保在serper.dev上创建一个帐户以获取所需的API密钥,并将其加载到环境中。
确保在.env
文件中定义API密钥。
工具可以直接导入:
from crewai_tools import FileReadTool, SerperDevTool
添加工具并将其分配给代理使得以新功能扩展应用范围变得非常快速和简单。
数据
一个 _sample_jobs.json_
包含一个由人工智能合成的示例职位广告列表的 JSON 响应,作为我们用例的测试数据。
虽然文章中呈现的职位数据是 JSON 格式,但它可以轻松地以任何其他格式供代理解析,例如以简单文本、PDF 或 Excel 文件存储的个人收集的职位描述数据文档。
充分利用搜索流程需要代理使用工具与职位平台的应用程序接口进行交互,以实现数据摄取流程的完全自动化和可扩展性。这些官方求职 API 提供者的例子包括 Glassdoor 或 Jooble。
💡 用户 重要 确保收集任何职位数据时遵守提供者设定的服务条款。
{
"jobs": [
{
"id": "VyxlLGIsICxELGUsdixlLGwsbyxwLGUscixELGUsbSxhLG4sdCxTLHkscixhLGMsdSxzLGUsLCwgLE4=",
"title": "Web Developer",
"company": "Apple",
"description": "As a Web Developer at CQ Partners, you will be a leader in the structuring, maintaining, and facilitating of websites and web-based applications...",
"image": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQAkPEjEwMeJizfsnGN-qUAEw8pmPdk357KIzsi&s=0",
"location": "Syracuse, NY",
"employmentType": "Full-time",
"datePosted": "17 hours ago",
"salaryRange": "",
"jobProvider": "LinkedIn",
"url": "https://www.linkedin.com/jobs/view/web-developer-at-demant-3904417702?utm_campaign=google_jobs_apply&utm_source=google_jobs_apply&utm_medium=organic"
},
{
"id": "VyxlLGIsICxELGUsdixlLGwsbyxwLGUsciwsLCAsVSxYLC8sVSxJLCwsICxCLHIsYSxuLGQsaSxuLGc=",
"title": "Web Developer, UX/UI, Branding, Graphics",
"company": "Adobe",
"description": "Degree required: Bachelor’s degree in relevant field...",
"image": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSV7-v1EkEhWtAh8W8WaqPD6vMQG2uBi0GOOOmb&s=0",
"location": "Columbia, MD",
"employmentType": "Full-time",
"datePosted": "1 day ago",
"salaryRange": "",
"jobProvider": "LinkedIn",
"url": "https://www.linkedin.com/jobs/view/web-developer-ux-ui-branding-graphics-at-adg-creative-3903771314?utm_campaign=google_jobs_apply&utm_source=google_jobs_apply&utm_medium=organic"
}
]
}
此外,还有一个 _sample_resume.txt_
文件,其中包含为经验丰富的数据科学家生成的简历。
您可以在 Github 上获取文件的完整内容。
任务
任务分配给配备了合适工具和技能的代理来完成。
每个任务都有一个描述、一个预期输出以及一个负责完成它的代理。
job_search:
description: | 找到满足以下要求的职位列表: {query} expected_output: 以有效的JSON格式结构化输出找到的职位列表及其所有信息。确保字段名称保持不变。
job_rating:
description: | 使用你的工具找到简历文件信息。 根据简历信息,对你收到的职位提供额外的评分。 评分在1到10之间,10是最佳匹配。每个职位都应该有一个评分。 另外添加一个rating_description字段,解释评分背后的理由,用1到2句话说明。 确保所有职位的信息也在输出中保持不变。 expected_output: 以有效的JSON格式结构化输出找到的职位列表及其各自的评分。确保字段名称保持不变。
evaluate_company:
description: | 使用你的工具找到职位公司的信息。 信息可以包括公司文化评论、公司财务报告和股票表现。 为公司提供一个额外的评分,字段名为company_rating。 评分在1到10之间,10是最佳评分。每个职位都应该有一个评分。 另外添加一个company_rating_description字段,解释评分背后的理由,用1到2句话说明。 确保所有职位的信息也在输出中保持不变。 expected_output: 以有效的JSON格式结构化输出找到的职位列表及其各自的评分,确保所有信息根据此模型结构化 {output_schema}
structure_results:
description: | 使用所有上下文来根据最终报告的需要结构化输出。 expected_output: 以有效的JSON格式结构化输出找到的职位列表及其各自的评分。确保你提供的最终输出是有效的JSON,符合模式 {output_schema}
一个 TasksFactory
类用于生成所有所需的任务。
from textwrap import dedent
from typing import Optional
from crewai import Agent, Task
from utils.utils import load_config
class TasksFactory:
def __init__(self, config_path):
self.config = load_config(config_path)
def create_task(
self,
task_type: str,
agent: Agent,
query: Optional[str] = None,
output_schema: Optional[str] = None,
):
task_config = self.config.get(task_type)
if not task_config:
raise ValueError(f"No configuration found for {task_type}")
description = task_config["description"]
if "{query}" in description and query is not None:
description = description.format(query=query)
expected_output = task_config["expected_output"]
if "{output_schema}" in expected_output and output_schema is not None:
expected_output = expected_output.format(output_schema=output_schema)
return Task(
description=dedent(description),
expected_output=dedent(expected_output),
agent=agent,
)
代理
代理具有角色、目标和背景故事,以最佳方式完成选定的任务。
job_search_expert:
- role: 最佳求职代理
- goal: 通过找到最佳职位发布和广告来给所有请求者留下深刻印象
- backstory: 最有经验的求职分析师,在职位市场上拥有丰富的专业知识。
job_rating_expert:
- role: 最佳职位评分代理
- goal: 通过提供准确的评分来凭借您找到与简历信息最匹配的职位的能力给所有请求者留下深刻印象
- backstory: 最有经验的职位评分专家,在求职匹配和评分方面拥有丰富的专业知识。
company_rating_expert:
- role: 最佳公司评估代理
- goal: 找到有关公司的所有重要信息,以评估职位适合性
- backstory: 最有经验的公司信息查找和评估专家,在公司评估和深入研究方面拥有丰富的专业知识。
summarization_expert:
- role: 最佳输出验证和总结者
- goal: 确保任务所需的最终输出符合任务要求
- backstory: 最有经验的报告专家,知道如何根据需要和正确的结构报告输出。
类似于 TasksFactory
,AgentsFactory
类用于根据其配置生成所有需要的人工智能代理。
from typing import Any, List, Optional
from crewai import Agent
from utils.utils import load_config
class AgentsFactory:
def __init__(self, config_path):
self.config = load_config(config_path)
def create_agent(
self,
agent_type: str,
llm: Any,
tools: Optional[List] = None,
verbose: bool = True,
allow_delegation: bool = False,
) -> Agent:
agent_config = self.config.get(agent_type)
if not agent_config:
raise ValueError(f"No configuration found for {agent_type}")
if tools is None:
tools = []
return Agent(
role=agent_config["role"],
goal=agent_config["goal"],
backstory=agent_config["backstory"],
verbose=verbose,
tools=tools,
llm=llm,
allow_delegation=allow_delegation,
)
大语言模型
关于选择大语言模型的选项范围不断增加,几乎每周都会发布新的模型。在这个设置中,我们将专门使用Azure开放人工智能模型 gpt-4–32k版本0613。但是,请随意调整和测试代码以适应您首选的大语言模型。
要使用Azure开放人工智能模型,需要一个API密钥和端点。Azure提供了很好的文档来设置和部署这些模型。
我们将所需的环境变量添加到 .env
文件中:
OPENAI_API_VERSION = <>
AZURE_OPENAI_KEY = <>
AZURE_OPENAI_ENDPOINT = <>
使用以下代码导入并使用选定的模型。
from langchain_openai import AzureChatOpenAI
import os
azure_llm = AzureChatOpenAI(
azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_key=os.environ.get("AZURE_OPENAI_KEY"),
deployment_name="gpt4",
streaming=True,
temperature=0
)
输出模型
为了确保代理生成最终一致的输出,我们定义了我们期望的输出模型。
from typing import List, Optional
from pydantic import BaseModel
class Job(BaseModel):
id: Optional[str]
location: Optional[str]
title: Optional[str]
company: Optional[str]
description: Optional[str]
jobProvider: Optional[str]
url: Optional[str]
rating: Optional[int]
rating_description: Optional[str]
company_rating: Optional[int]
company_rating_description: Optional[str]
class JobResults(BaseModel):
jobs: Optional[List[Job]]
整合一切:团队
之前的图表展示了代理将处理的事件顺序,以实现期望的输出。一个任务的完成会交给下一个优化了特定角色以处理该任务的代理。因此,配置一个顺序流程符合我们的目的。您始终可以选择重构设计,并通过代理协作和并行处理选择不同的任务处理策略。
最后,我们在 main.py
中创建搜索团队,以创建、分配和运行所有人工智能代理。
import json
import os
from textwrap import dedent
from crewai import Crew, Process
from crewai_tools import FileReadTool, SerperDevTool
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI
from pydantic import ValidationError
from agents_factory import AgentsFactory
from models.models import JobResults
from tasks_factory import TasksFactory
load_dotenv()
class JobSearchCrew:
def __init__(self, query: str):
self.query = query
def run(self):
azure_llm = AzureChatOpenAI(
azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_key=os.environ.get("AZURE_OPENAI_KEY"),
deployment_name="gpt4",
streaming=True,
temperature=0,
)
resume_file_read_tool = FileReadTool(file_path="data/sample_resume.txt")
jobs_file_read_tool = FileReadTool(file_path="data/sample_jobs.json")
search_tool = SerperDevTool(n_results=5)
agent_factory = AgentsFactory("configs/agents.yml")
job_search_expert_agent = agent_factory.create_agent(
"job_search_expert", tools=[jobs_file_read_tool], llm=azure_llm
)
job_rating_expert_agent = agent_factory.create_agent(
"job_rating_expert", tools=[resume_file_read_tool], llm=azure_llm
)
company_rating_expert_agent = agent_factory.create_agent(
"company_rating_expert", tools=[search_tool], llm=azure_llm
)
summarization_expert_agent = agent_factory.create_agent(
"summarization_expert", tools=None, llm=azure_llm
)
response_schema = json.dumps(JobResults.model_json_schema(), indent=2)
tasks_factory = TasksFactory("configs/tasks.yml")
job_search_task = tasks_factory.create_task(
"job_search", job_search_expert_agent, query=self.query
)
job_rating_task = tasks_factory.create_task(
"job_rating", job_rating_expert_agent
)
evaluate_company_task = tasks_factory.create_task(
"evaluate_company",
company_rating_expert_agent,
output_schema=response_schema,
)
structure_results_task = tasks_factory.create_task(
"structure_results",
summarization_expert_agent,
output_schema=response_schema,
)
crew = Crew(
agents=[
job_search_expert_agent,
job_rating_expert_agent,
company_rating_expert_agent,
summarization_expert_agent,
],
tasks=[
job_search_task,
job_rating_task,
evaluate_company_task,
structure_results_task,
],
verbose=1,
process=Process.sequential,
)
result = crew.kickoff()
return result
if __name__ == "__main__":
print("## 欢迎来到求职团队")
print("-------------------------------")
query = input(
dedent("""
提供您所寻找职位的特征列表:
""")
)
crew = JobSearchCrew(query)
result = crew.run()
print("正在验证最终结果..")
try:
validated_result = JobResults.model_validate_json(result)
except ValidationError as e:
print(e.json())
print("数据输出验证错误,正在重试...")
print("\n\n########################")
print("## 结果 ")
print("########################\n")
print(result)
在任务执行后,我们进行验证检查,以确保 JSON 输出与定义的模型模式相符。此外,我们还可以潜在地添加更多验证循环,以确保代理返回所需的结构。我们不会详细讨论实现这个完整过程的细节,不过如果您感兴趣,可以查看这篇很棒的文章。
结果分析
我们简单地运行代码
对于我们的初始代理查询,我们输入以下内容:
提供您寻找的职位的特征列表:
在美国的机器学习和数据科学职位,薪资范围为 $100K- $170K
查询可以进一步扩展,包括地点、开始日期、行业等以及许多其他合适的参数,以过滤和查找职位列表。
输出:
终端结果显示,第一个代理成功执行并使用工具检索职位列表内容。
输出 1 — 作者生成
然后代理成功过滤列表,并根据输入查询找到正确的职位。
输出 2 — 作者生成
我将跳过显示代理进度的详细调试信息,直接进入最终结果。
输出 3 — 作者生成
结果包含在配置任务中请求的所有字段,以及有效的最终 JSON 结果结构。我们还观察到提供的评分及其背后的推理,如指示所示。
让我们进一步测试这些评分是否确实反映了与用户简历的匹配过程,我们将查询调整如下:
提供您寻找的职位的特征列表:
在美国的网页开发职位
我们观察到,针对网页开发角色的请求确实降低了评分,而简历则针对机器学习和数据科学职位进行了优化。
源代码
整个项目的源代码可以在 GitHub 上获取。
摘要
本文讨论了引入人工智能代理对传统耗时的求职过程产生的巨大影响。特别是在评估和匹配要求与在线职位广告的阶段。我们探讨了求职的关键挑战,并将人工智能代理定位为能够在几秒钟内完成任务,而这些任务通常需要人类几周甚至几个月才能完成。最后,我们实现了创建和自动化人工智能代理所需的代码,这些代理致力于为用户找到合适的个性化职位,利用多种工具完成他们的任务。
未来几个月和几年,人工智能将不可避免地融入求职引擎和平台,最终导致快速且极具个性化的职位匹配过程。然而,能够控制自己人工智能驱动的求职引擎的设计和架构,为求职带来了灵活性和更大的优势。
潜在改进
本文讨论和实现的用例仅代表可以添加到人工智能驱动的求职引擎的潜在功能的一部分。我相信,求职平台最终将不可避免地整合这些工具,并利用其庞大的客户基础。将其提升到一个新水平的潜在方式是构建您自己的跨平台解决方案,以满足您的需求。拥有将所选模型集成并根据您的具体要求调整代理和任务的灵活性。
一些额外的功能,值得深入研究并在此基础上构建:
- 规模:通过整合大量代理和工具,与多个求职平台API进行交互。
- 个性化:根据职位要求自动化申请流程,调整和改进简历和求职信的任务。
- 更好的准备:根据职位规范生成潜在面试问题列表,并与代理进行模拟面试。
- 改善薪资谈判:通过对所发布职位和技能进行广泛的市场调研。
- 更好的机会:通过对广告的持续监控,实现实时处理和评估。
- ….