展示个人项目:用 AI Agent 自动扫描、筛选和投递工作。实际应用 Agent 自动化重复任务的案例。
在继续之前,让我们理解我们的工作流智能体如何工作!
首先,用户提供他们的 LinkedIn 个人资料
第一个智能体使用 BrightData 的 MCP 服务器分析你的个人资料、经验、技能和职业道路。
基于分析,它预测与你的兴趣和经验相匹配的领域
然后它使用 BrightData 的 MCP 服务器从 Y Combinator 工作板上抓取当前职位空缺
最后,它生成了一份详细报告,包含你的个人资料摘要 + 为你定制的顶级工作机会列表。
我为这些任务中的每一个都使用了独立的智能体。
是的,我本来可以把几个任务结合在一起……但根据我的实验,这只会让智能体困惑,增加幻觉。🙃
如果你更喜欢视频,你可以看这个:👇🏼
我在寻找简单可靠的选项,这些工具对这项工作来说是完美的。
我在寻找一种经济实惠的方式来在单个工作流中多次运行 LLM,而不必太担心成本或性能。
这时我发现了 Nebius AI Studio。
它让你可以轻松运行开源 AI 模型,其性能对我需要的东西来说是非常好的。如果你想使用强大的模型而不花费太多,它很快、价格实惠,并且效果非常好。
LLM 的一大限制?它们通常无法实时访问实时数据。
MCP 通过让 LLM 访问外部工具来解决这个问题。
对于这个项目,我使用了 BrightData 的 MCP(模型上下文协议)服务器。
当我寻找方式给我的智能体提供互联网访问时,我发现了它。使用这个 MCP 服务器,我们的智能体可以:
搜索和浏览网站
绕过位置限制或验证码
抓取数据而不被阻止
对于这个项目,我需要从网络获取实时信息,它工作得非常完美。
查看 BrightData 的 MCP 服务器
由于我已经在探索 OpenAI Agents SDK,并学习如何构建 MCP 智能体和智能体工作流,我决定将其用作这个项目的基础。
我发现它非常简单灵活,正是我为创建能够实际解决像求职这样的真实任务的多智能体设置所需的。
它是一个轻量级工具,可以帮助你轻松创建基于智能体的应用。有了这个,我可以:
给我的 LLM 提供指令和工具
让智能体相互传递工作
在向模型发送数据前添加基本检查
如果你正在构建多步骤或多智能体应用,它超级有用。
查看 OpenAI Agents SDK
最后,我需要一种快速而干净的方式来构建 UI。
作为一个 Python 开发者,Streamlit 是显而易见的选择。
对于 Python 开发者来说,Streamlit 是为应用构建直观 UI 的首选方案。
只需几行代码,我就有了一个功能齐全的仪表板,我可以输入 LinkedIn URL 并运行工作流。
这使整个过程变得非常简单。
足够的讨论,让我们开始构建智能体吧!🔥
运行这个项目前,确保你有:
Python 3.10 或更高版本
BrightData 账户和 API 凭证
Nebius AI Studio 账户和 API 密钥
为了保持代码整洁和模块化,我们这样组织项目:
# 文件夹结构 👇🏼
job_finder_agent/
├── app.py # Streamlit 网页界面
├── job_agents.py # AI 智能体定义和分析逻辑
├── mcp_server.py # BrightData MCP 服务器管理
├── requirements.txt # Python 依赖
├── assets/ # 静态资源(图像、GIF)
└── .env # 环境变量(需要创建)
这是一个简单的概览:
app.py :Streamlit 应用的入口点。这是用户与工具交互的地方。
job_agents.py :包含所有智能体相关逻辑和工作流。
mcp_server.py :初始化 MCP 服务器。
assets/ :存放 UI 中使用的任何视觉或媒体。
.env :存储敏感数据如 API 密钥,确保此文件包含在你的 .gitignore 中。
使用这个结构,调试、扩展或稍后插入新智能体都超级简单。
首先,我们将创建项目所需的智能体。为此,让我们进入 job_agents.py 文件。
这里,我们将导入所需的模块:
# job_agents.py 👇🏼
import os
import logging
import asyncio
from agents import (
Agent,
OpenAIChatCompletionsModel,
Runner,
set_tracing_disabled,
)
from agents.mcp import MCPServer
from openai import AsyncOpenAI
logger = logging.getLogger(__name__)
我们现在将定义一个异步函数 run_analysis,启动整个智能体工作流。
async def run_analysis(mcp_server: MCPServer, linkedin_url: str):
logger.info(f"Starting analysis for LinkedIn URL: {linkedin_url}")
api_key = os.environ["NEBIUS_API_KEY"]
base_url = "https://api.studio.nebius.ai/v1"
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
set_tracing_disabled(disabled=True)
这是我们的第一个,也是最重要的智能体之一。它分析用户的 LinkedIn 个人资料,提取相关的职业见解。
linkedin_agent = Agent(
name="LinkedIn Profile Analyzer",
instructions=f"""You are a LinkedIn profile analyzer.
Analyze profiles for:
- Professional experience and career progression
- Education and certifications
- Core skills and expertise
- Current role and company
- Previous roles and achievements
- Industry reputation (recommendations/endorsements)
Provide a structured analysis with bullet points and a brief executive summary.
""",
mcp_servers=[mcp_server],
model=OpenAIChatCompletionsModel(
model="meta-llama/Llama-3.3-70B-Instruct",
openai_client=client
)
)
💡 注意:这个智能体使用 mcp_server,因为它使用 BrightData 的抓取引擎从 LinkedIn 中拉取数据。
现在我们已经分析了个人资料,我们需要预测用户最适合哪个领域。
工作建议智能体将根据上一个智能体的分析来建议用户的首选领域
job_suggestions_agent = Agent(
name="Job Suggestions",
instructions=f"""You are a domain classifier that identifies the primary professional domain from a LinkedIn profile.
""",
model=OpenAIChatCompletionsModel(
model="meta-llama/Llama-3.3-70B-Instruct",
openai_client=client
)
)
💡 注意:这里我们没有使用 MCP 工具,因为它不进行任何函数调用。
然后,我们将创建一个工作板 URL 生成器。这个智能体接受领域并为 Y Combinator 工作板构建 URL。
url_generator_agent = Agent(
name="URL Generator",
instructions=f"""You are a URL generator that creates Y Combinator job board URLs based on domains.
""",
model=OpenAIChatCompletionsModel(
model="meta-llama/Llama-3.3-70B-Instruct",
openai_client=client
)
)
接下来,我们正在构建工作搜索智能体。这个智能体将访问生成的 URL 并提取真实的工作列表。
Job_search_agent = Agent(
name="Job Finder",
instructions=f"""You are a job finder that extracts job listings from Y Combinator's job board.
""",
mcp_servers=[mcp_server],
model=OpenAIChatCompletionsModel(
model="meta-llama/Llama-3.3-70B-Instruct",
openai_client=client
)
)
💡 注意:这个智能体再次使用 MCP 服务器,因为它需要对 YC 的工作板进行实时抓取调用。
有时 YC 工作链接在身份验证重定向后面 🤷🏻♂️。这个智能体清理和简化这些 URL。
如果你使用不需要清理的工作板,你可以跳过这个智能体。
url_parser_agent = Agent(
name="URL Parser",
instructions=f"""You are a URL parser that transforms Y Combinator authentication URLs into direct job URLs.
""",
model=OpenAIChatCompletionsModel(
model="meta-llama/Llama-3.3-70B-Instruct",
openai_client=client
)
)
最后,我们将把所有东西结合在一起。这个智能体将用户的个人资料、领域预测和工作结果总结成一份干净的 markdown 报告。
summary_agent = Agent(
name="Summary Agent",
instructions=f"""You are a summary agent that creates comprehensive career analysis reports.
现在所有的智能体都准备好了,让我们构建实际的工作流,将它们按逻辑顺序连接起来。
在 Agents SDK 中,要创建工作流,你需要将前一个智能体的结果作为输入传递,并指定 starting_agent 作为处理当前任务的智能体。
代码看起来像这样:
# Get LinkedIn profile analysis
logger.info("Running LinkedIn profile analysis")
linkedin_result = await Runner.run(starting_agent=linkedin_agent, input=query)
logger.info("LinkedIn profile analysis completed")
# Get job suggestions
logger.info("Getting job suggestions")
suggestions_result = await Runner.run(starting_agent=job_suggestions_agent, input=linkedin_result.final_output)
logger.info("Job suggestions completed")
# Get specific job matches
logger.info("Getting job link")
job_link_result = await Runner.run(starting_agent=url_generator_agent, input=suggestions_result.final_output)
logger.info("Job link generation completed")
# Get job matches
logger.info("Getting job matches")
job_search_result = await Runner.run(starting_agent=Job_search_agent, input=job_link_result.final_output)
logger.info("Job search completed")
# Parse URLs to get direct job links
logger.info("Parsing job URLs")
parsed_urls_result = await Runner.run(starting_agent=url_parser_agent, input=job_search_result.final_output)
logger.info("URL parsing completed")
# Create a single input for the summary agent
logger.info("Generating final summary")
summary_input = f"""LinkedIn Profile Analysis:
{linkedin_result.final_output}
Job Suggestions:
{suggestions_result.final_output}
Job Matches:
{parsed_urls_result.final_output}
Please analyze the above information and create a comprehensive career analysis report in markdown format."""
# Get final summary with a single call
summary_result = await Runner.run(starting_agent=summary_agent, input=summary_input)
logger.info("Summary generation completed")
return summary_result.final_output
现在,我们初始化 MCP 服务器!
我们首先创建一个 mcp_server.py 文件,它将处理与 MCP 服务器设置和访问相关的所有内容。
首先,我们导入必要的模块,包括用于处理异步逻辑的 asyncio 和来自 Agents SDK 的 MCPServerStdio:
import os
import logging
import asyncio
from agents.mcp import MCPServerStdio
logger = logging.getLogger(__name__)
_mcp_server = None
接下来,我们创建 initialize_mcp_server 函数来初始化具有凭证的 MCP 服务器:
async def initialize_mcp_server():
"""Initialize MCP server."""
global _mcp_server
if _mcp_server:
return _mcp_server
try:
server = MCPServerStdio(
cache_tools_list=False,
params={
"command": "npx",
"args": ["-y", "@brightdata/mcp"],
"env": {
"API_TOKEN": os.environ["BRIGHT_DATA_API_KEY"],
"WEB_UNLOCKER_ZONE": "mcp_unlocker",
"BROWSER_AUTH": os.environ["BROWSER_AUTH"],
}
}
)
await asyncio.wait_for(server.__aenter__(), timeout=10)
_mcp_server = server
return server
except Exception as e:
logger.error(f"Error initializing MCP server: {e}")
return None
💡 注意:确保你已正确在 .env 中设置环境变量(BRIGHT_DATA_API_KEY、BROWSER_AUTH),否则服务器将无法启动。
此函数完成几个重要的任务:
为了保持代码整洁,我们添加了两个实用函数:
一个等待服务器就绪(wait_for_initialization)
另一个检索服务器实例(get_mcp_server)
async def wait_for_initialization():
"""Wait for MCP initialization to complete."""
return await initialize_mcp_server() is not None
def get_mcp_server():
"""Get the current MCP server instance."""
return _mcp_server
最后,我们将汇总所有内容,并使用 Streamlit 为此智能体创建 UI。
首先,我们导入所有必要的模块并设置 Streamlit 应用配置:
import streamlit as st
import asyncio
import os
import logging
import nest_asyncio
import base64
from dotenv import load_dotenv
from job_agents import run_analysis
from mcp_server import wait_for_initialization, get_mcp_server
nest_asyncio.apply()
load_dotenv()
logger = logging.getLogger(__name__)
# Set page config
st.set_page_config(
page_title="LinkedIn Profile Analyzer",
page_icon="🔍",
layout="wide"
)
接下来,我们将使用 asyncio 来异步调用 run_analysis() 函数,一旦 MCP 服务器初始化完成。
当用户点击"Analyze Profile"按钮时触发此函数。
# Initialize session state
if 'analysis_result' not in st.session_state:
st.session_state.analysis_result = ""
if 'is_analyzing' not in st.session_state:
st.session_state.is_analyzing = False
async def analyze_profile(linkedin_url: str):
try:
if not await wait_for_initialization():
st.error("Failed to initialize MCP server")
return
result = await run_analysis(get_mcp_server(), linkedin_url)
st.session_state.analysis_result = result
except Exception as e:
logger.error(f"Error analyzing LinkedIn profile: {str(e)}")
st.error(f"Error analyzing LinkedIn profile: {str(e)}")
finally:
st.session_state.is_analyzing = False
我们还初始化了一些会话状态变量来管理交互之间的应用状态。
现在是有趣的部分,也就是主 UI 🤩!我们将创建一个简单的 UI 来与我们的智能体交互:
def main():
# Load and encode images
with open("./assets/bright-data-logo.png", "rb") as bright_data_file:
bright_data_base64 = base64.b64encode(bright_data_file.read()).decode()
# Create title with embedded images
title_html = f"""
<div style="display: flex; align-items: center; gap: 0px; margin: 0; padding: 0;">
<h1 style="margin: 0; padding: 0;">
Job Searcher Agent with
<img src="data:image/png;base64,{bright_data_base64}" style="height: 110px; margin: 0; padding: 0;"/>
</h1>
</div>
"""
st.markdown(title_html, unsafe_allow_html=True)
st.markdown("---")
# Sidebar
with st.sidebar:
st.image("./assets/Nebius.png", width=150)
api_key = st.text_input("Enter your API key", type="password")
st.divider()
st.subheader("Enter LinkedIn Profile URL")
linkedin_url = st.text_input("LinkedIn URL", placeholder="https://www.linkedin.com/in/username/")
if st.button("Analyze Profile", type="primary", disabled=st.session_state.is_analyzing):
if not linkedin_url:
st.error("Please enter a LinkedIn profile URL")
return
if not api_key:
st.error("Please enter your API key")
return
st.session_state.is_analyzing = True
st.session_state.analysis_result = ""
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(analyze_profile(linkedin_url))
finally:
loop.close()
# Results section
if st.session_state.analysis_result:
st.subheader("Analysis Results")
st.markdown(st.session_state.analysis_result)
# Loading state
if st.session_state.is_analyzing:
st.markdown("---")
with st.spinner("Analyzing profile... This may take a few minutes."):
st.empty()
if __name__ == "__main__":
main()
就是这样!我们现在拥有一个完全功能的 Streamlit UI,连接到通过 MCP 实现浏览器自动化和网页抓取的多智能体 AI 系统。
现在一切都已设置好,让我们本地运行这个应用!
首先,创建虚拟环境:
python -m venv venv
source venv/bin/activate # On Windows, use: venv\Scripts\activate
接下来,安装 requirements.txt 中列出的所有必要包:
pip install -r requirements.txt
最后,运行应用:
streamlit run app.py
现在转到 http://localhost:8501:
我们的 AI 智能体已经准备就绪,可以分析 LinkedIn 个人资料,并像施展魔法一样找到匹配的职位。🪄
我们已经成功构建了一个多智能体工作流,它使用 MCP 服务器为我们寻找职位。
完整代码请见:Github 仓库
如果你觉得这篇文章有帮助,请分享给你的同行。
此外,关注我以获取更多此类内容:
关注 Arindam Majumder
如需付费合作,请发送邮件至:arindammajumder2020@gmail.com。
感谢阅读!
部分评论可能仅对已登录的访客可见。登录后即可查看所有评论。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。