0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

私が自分のために仕事を見つけるAIエージェントを作った

Posted at

注意: この記事は英語から日本語に翻訳されました。
Note: This article has been translated from English to Japanese.


"Enterキーを押すかクリックして、画像をフルサイズで表示します。"

申し訳ありませんが、翻訳するテキストが提供されていません。翻訳が必要な具体的な内容を教えていただければ、喜んでお手伝いします。

「私が自分のために仕事を見つけるAIエージェントを構築しました」

"アリンダム・マジュムダー"

"アリンダム・マジュムダー"

10分で読めます

"·"

"2025年6月2日"

翻訳するテキストが提供されていません。翻訳が必要な具体的な内容をお知らせいただければ、その内容を日本語に翻訳いたします。

"1"

"Listen"

Japanese translation: "リッスン"

"共有"

現在の市場では、自分に合った仕事を見つけるのは非常に難しいです!

最近、OpenAI Agents SDKを探索し、MCPエージェントとエージェントワークフローを構築していました。

私の学びを実践するために、実際の一般的な問題を解決してみようと思いました。

だから、私に合った仕事を見つけるためのマルチエージェントジョブ検索ワークフローを構築しました!

"Enterを押すかクリックして画像をフルサイズで表示します"

申し訳ありませんが、翻訳するテキストが空白のようです。翻訳が必要な具体的なテキストを提供してください。

この記事では、このエージェントワークフローを構築する方法をお見せします!

「始めましょう!」

エージェントの動作原理

"Enterを押すかクリックして画像をフルサイズで表示します"

申し訳ありませんが、翻訳するテキストが空白のため、翻訳できる内容がありません。具体的なテキストを提供していただければ、喜んで翻訳いたします。

「先に進む前に、私たちの Workflow Agent がどのように機能するかを理解しましょう!」

  • まず、ユーザーは自分のLinkedInプロフィールを提供します。
  • 最初のエージェントは、BrightDataのMCPサーバーを使用して、あなたのプロフィール、経験、スキル、キャリアパスを分析します。
  • 分析に基づいて、あなたの興味と経験に合ったドメインを予測します。
  • 次に、BrightDataのMCPサーバーを使用して、Y Combinatorの求人ボードから現在の求人情報をスクレイピングします。
  • 最後に、あなたのプロフィールサマリーと、あなたに合わせたトップジョブマッチのリストを含む詳細なレポートを生成します。

各タスクに対して別々のエージェントを使用しました。

はい、いくつかをまとめることもできましたが…私の実験からは、それがエージェントを混乱させ、幻覚を増加させるだけだったことがわかりました。🙃

動画の方が好みであれば、こちらをご覧ください:👇🏼

使用したツール

このプロジェクトを構築するために使用した主なツールは以下の通りです。

シンプルで信頼性の高いオプションを探していたところ、これらがその仕事に完璧であることがわかりました。

1. Nebius AI Studio

"Enterキーを押すか、クリックして画像をフルサイズで表示します"

申し訳ありませんが、翻訳するテキストが空白のため、翻訳できる内容がありません。具体的なテキストを提供していただければ、喜んで翻訳いたします。

コストやパフォーマンスをあまり気にせずに、単一のワークフロー内でLLMsを複数回実行するための予算に優しい方法を探していました。

That’s when I found "Nebius AI Studio".

オープンソースのAIモデルを簡単に実行でき、私のニーズには非常に良いパフォーマンスでした。高速で手頃な価格であり、あまりお金をかけずに強力なモデルを使用したい場合には非常にうまく機能します。

2. BrightDataのMCPサーバー

"Enterキーを押すかクリックして、画像をフルサイズで表示します。"

I'm sorry, but there is no text provided for translation. Please provide the text you would like me to translate.

LLMの大きな制限の一つは?通常、リアルタイムデータへのライブアクセスがないことです。

MCPは、LLMsに外部ツールへのアクセスを提供することでこの問題を解決します。

For this project, I used "Bright Data"’s MCP (Model Context Protocol) Server.

I found it while looking for ways to give my agent internet access. With this MCPサーバー, our agent can:

  • ウェブサイトを検索および閲覧する
  • 地理的制限やCAPTCHAを回避する
  • ブロックされずにデータをスクレイピングする

このプロジェクトでは、ウェブからリアルタイムの情報が必要だったため、完璧に機能しました。

3. OpenAI Agents SDK

"Enterキーを押すかクリックして画像をフルサイズで表示します"

申し訳ありませんが、翻訳するテキストが空白のため、翻訳できる内容がありません。具体的なテキストを提供していただければ、喜んで翻訳いたします。

「私はすでにOpenAI Agents SDKを探求し、MCPエージェントやエージェントワークフローの構築方法を学んでいたので、このプロジェクトの基盤としてそれを使用することに決めました。」

それは非常にシンプルで柔軟だと感じました。実際のタスクである就職活動を解決できるマルチエージェントのセットアップを作成するために必要なものそのものでした。

軽量なツールで、エージェントベースのアプリを簡単に作成するのに役立ちます。これを使って、私は:

  • LLMに指示とツールを与える
  • エージェントが互いに作業を渡せるようにする
  • モデルにデータを送信する前に基本的なチェックを追加する

マルチステップまたはマルチエージェントアプリを構築している場合、非常に便利です。

4. UIのためのStreamlit

"Enterキーを押すかクリックして画像をフルサイズで表示します"

申し訳ありませんが、翻訳するテキストが提供されていないようです。翻訳が必要な具体的なテキストを教えていただければ、喜んでお手伝いします。

最後に、UIを構築するための迅速でクリーンな方法が必要でした。

As a Python dev, "Streamlit" was the obvious choice.

Python開発者にとって、Streamlitはアプリケーションの直感的なUIを構築するための最適な選択肢です。

数行のコードを書くことで、LinkedInのURLを入力し、ワークフローを実行できる完全に機能するダッシュボードができました。

全体のプロセスが非常に簡単になりました。

"ジョブ検索エージェントの構築"

話はこれくらいにして、エージェントの構築を始めましょう!🔥

前提条件

このプロジェクトを実行する前に、次のことを確認してください:

プロジェクト構造

プロジェクトをクリーンでモジュール化された状態に保つために、次のように整理しました:


# フォルダー構造 👇🏼

job_finder_agent/  
├── app.py              # Streamlit web interface  

├── job_agents.py       # AI agent definitions and analysis logic  

├── mcp_server.py       # Bright Data MCP server management  

├── requirements.txt    # Python dependencies  

├── assets/            # Static assets (images, GIFs)  

└── .env              # Environment variables (create this)

こちらは簡単な内訳です:

  • app.py : Streamlitアプリのエントリーポイントです。ここでユーザーはツールと対話します。
  • job_agents.py : エージェント関連のロジックとワークフローを含みます。
  • mcp_server.py : MCPサーバーを初期化します。
  • assets/: UIで使用されるビジュアルやメディアを保持します。
  • .env : APIキーなどの機密データを保存します。このファイルは.gitignoreに含めるようにしてください。

この構造により、デバッグ、拡張、さらには新しいエージェントを後からプラグインするのが非常に簡単です。

エージェントの作成 🤖

最初に、プロジェクトに必要なエージェントを作成します。そのために、job_agents.pyファイルに移動しましょう。

ここでは、必要な Modules をインポートします:


# # 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 Toolsを使用していません。なぜなら、関数呼び出しを行っていないからです。*

次に、ジョブボードの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.Ensure your response is well-formatted markdown that can be directly displayed.""",
model=OpenAIChatCompletionsModel(
model="meta-llama/Llama-3.3-70B-Instruct",
openai_client=client
)
)


Streamlitやマークダウンレンダラーで見栄えの良いレポートを作成するので、簡単に共有したり、後で保存したりできます!😉

# ワークフローの作成

すべてのエージェントが準備できたので、論理的な順序でそれらを接続する実際のフローを構築しましょう。

Agents SDKでは、ワークフローを作成するには、前のエージェントの結果を入力として渡し、`starting_agent`を現在のタスクを処理するエージェントとして指定する必要があります。

「コードではこのように見えます:」

# LinkedInプロファイル分析を取得する

    logger.info("LinkedInプロファイル分析を実行中")  
    linkedin_result = await Runner.run(starting_agent=linkedin_agent, input=query)  
    logger.info("LinkedInプロファイル分析が完了しました")  
# 求人提案を取得する  

    logger.info("求人提案を取得中")  
    suggestions_result = await Runner.run(starting_agent=job_suggestions_agent, input=linkedin_result.final_output)  
    logger.info("求人提案が完了しました")  
# 特定の求人マッチを取得する  

    logger.info("求人リンクを取得中")  
    job_link_result = await Runner.run(starting_agent=url_generator_agent, input=suggestions_result.final_output)  
    logger.info("求人リンクの生成が完了しました")  
# 求人マッチを取得する  

    logger.info("求人マッチを取得中")  
    job_search_result = await Runner.run(starting_agent=Job_search_agent, input=job_link_result.final_output)  
    logger.info("求人検索が完了しました")  
# URLを解析して直接の求人リンクを取得する  

    logger.info("求人URLを解析中")  
    parsed_urls_result = await Runner.run(starting_agent=url_parser_agent, input=job_search_result.final_output)  
    logger.info("URL解析が完了しました")  
# サマリーエージェント用の単一入力を作成する  

    logger.info("最終サマリーを生成中")  
    summary_input = f"""LinkedInプロファイル分析:  
    {linkedin_result.final_output}  
    求人提案:  
    {suggestions_result.final_output}  
    求人マッチ:  
    {parsed_urls_result.final_output}  
    上記の情報を分析し、マークダウン形式で包括的なキャリア分析レポートを作成してください。"""  
# 単一呼び出しで最終サマリーを取得する  

    summary_result = await Runner.run(starting_agent=summary_agent, input=summary_input)  
    logger.info("サマリー生成が完了しました")  
    return summary_result.final_output

MCPサーバーの初期化

今、MCPサーバーを初期化します!

mcp_server.py ファイルを作成し、MCP サーバーのセットアップおよびアクセスに関連するすべての処理を行います。

最初に、asyncロジックを処理するためのasyncioや、Agents SDKからのMCPServerStdioなど、必要なモジュールをインポートします。

import os  
import logging  
import asyncio  
from agents.mcp import MCPServerStdio  
  
logger = logging.getLogger(__name__)  
_mcp_server = None

次に、MCPサーバーを認証情報で初期化するinitialize_mcp_server関数を作成します。

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)を正しく設定していることを確認してください。そうしないと、サーバーが起動しません。

この関数はいくつかの重要なことを行います:

  • サーバーが既に起動している場合、再初期化を防ぎます。
  • 新しい MCP サーバーインスタンスを起動します。
  • タイムアウトを処理し、失敗を適切にログに記録します。

「クリーンに保つために、2つのユーティリティ関数を追加します。」

  • サーバーが準備完了になるまで待つためのもの (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の作成"

最後に、すべてを組み立てて、このエージェントのためのUIをStreamlitを使用して作成します。

まず最初に、必要なモジュールをすべてインポートし、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__)

# ページ設定を行う  

st.set_page_config(  
    page_title="LinkedIn Profile Analyzer",  
    page_icon="🔍",  
    layout="wide"  
)

次に、MCPサーバーが初期化されたら、asyncioを使用して非同期にrun_analysis()関数を呼び出します。

この関数は、ユーザーが「Analyze Profile」ボタンをクリックしたときにトリガーされます。

# セッションステートの初期化

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("MCPサーバーの初期化に失敗しました")
return
result = await run_analysis(get_mcp_server(), linkedin_url)
st.session_state.analysis_result = result
except Exception as e:
logger.error(f"LinkedInプロファイルの分析中にエラーが発生しました: {str(e)}")
st.error(f"LinkedInプロファイルの分析中にエラーが発生しました: {str(e)}")
finally:
st.session_state.is_analyzing = False

アプリのインタラクション間でステータスを管理するために、いくつかのセッション状態変数も初期化しました。

さて、楽しい部分、つまりメインUIの時間です 🤩!エージェントと対話するためのシンプルなUIを作成します。

def main():  
    # 画像を読み込み、エンコードする

    with open("./assets/bright-data-logo.png", "rb") as bright_data_file:  
        bright_data_base64 = base64.b64encode(bright_data_file.read()).decode()         
    # 埋め込まれた画像を含むタイトルを作成する

    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("---")  
    # サイドバー

    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()  
    # 結果セクション

    if st.session_state.analysis_result:  
        st.subheader("Analysis Results")  
        st.markdown(st.session_state.analysis_result)  
    # 読み込み状態

    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()

それで完了です!これで、MCPを介してブラウザの自動化とスクレイピングに接続された、完全に機能するStreamlit UIを持つマルチエージェント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

Now go to The text "http://localhost:8501" does not require translation as it is a URL.:

"Enterキーを押すかクリックして、画像をフルサイズで表示します"

申し訳ありませんが、翻訳するテキストが提供されていません。翻訳が必要な具体的なテキストを教えていただければ、喜んでお手伝いします。

私たちのエージェントは、LinkedIn プロフィールを分析し、まるで魔法のように仕事のマッチを見つける準備が整いました。🪄

それだけです!🎉

私たちは、MCPサーバーを使用して私たちのために仕事を見つけるマルチエージェントワークフローを成功裏に構築しました。

You’ll find the Full Code Here: "Github リポジトリ"

この記事が役に立ったと思ったら、仲間と共有してください。

For paid collaboration, mail me at: arindammajumder2020@gmail.com.

ご覧いただきありがとうございます!

申し訳ありませんが、提供されたテキストは翻訳する内容がありません。別のテキストを提供していただければ、喜んで翻訳いたします。

This story is published on "生成的AI". Connect with us on LinkedIn.

Subscribe to our ニュースレター and "YouTube" channel to stay updated with the latest news and updates on generative AI. Let’s shape the future of AI together!

申し訳ありませんが、翻訳するテキストが空白のため、翻訳できる内容がありません。具体的なテキストを提供していただければ、喜んで翻訳いたします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?