Creating and Registering CrewAI based Job descriptions creator
This documentation explains how to build CrewAI agents that generate tailored job descriptions, register them on Fetch.ai’s Agentverse, and enable collaboration between agents for dynamic data sharing. Below, you'll see the main.py script containing the workflow logic run_job_posting_workflow, as well as sample scripts for a Job Description Agent and a User Agent.
Overview
Purpose
This project showcases:
- Creating CrewAI agents for complex tasks (e.g., analyzing company requirements and generating job descriptions).
- Integrating with Fetch.ai’s Agentverse, enabling seamless communication and data exchange.
- Using autonomous agents to streamline job description creation while aligning with company culture and specific role requirements.
Prerequisites
Environment Setup
To begin creating and registering CrewAI agents, ensure you have the following:
- Python 3.10+ or higher
- A virtual environment (recommended)
- Fetch.ai SDK for agent creation and registration
- CrewAI Framework for defining tasks and workflows
Key Components
- Job Description Agent: A specialized CrewAI agent that processes input (company domain, hiring needs, etc.) to generate a job description.
- User Agent: Acts as a client to discover and interact with the Job Description Agent.
- Agent Collaboration: Multiple agents communicate via Agentverse, orchestrating tasks through CrewAI workflows.
Environment Variables
Create a .env file with your API keys:
SERPER_API_KEY=<your_serper_api_key>
OPENAI_API_KEY=<your_openai_api_key>
AGENTVERSE_API_KEY=<your_agentverse_api_key>
Replace the placeholders with your actual keys.
Project Directory & Configuration Files
For a clean, maintainable setup, we recommend the following structure:
crewai_job_descriptions/
├── .env
├── config/
│ ├── agents.yaml
│ └── tasks.yaml
├── crew.py
├── jd_agent.py
├── main.py
├── requirements.txt
├── user_agent.py
├── job_description_example.md
└── README.md
1. requirements.txt
Purpose: Declares project dependencies so you (or anyone) can install them quickly with pip install -r requirements.txt.
Create a file called requirements.txt at the root of your project (alongside jd_agent.py and main.py). Example contents:
flask==2.2.5
flask-cors==3.0.10
fetchai==0.16.3
crewai==0.100.1
crewai_tools==0.33.0
python-dotenv==1.0.0
Install everything at once via:
pip install -r requirements.txt
2. Crew.py
Purpose: Defines your JobPostingCrew class, specifying the Agents and Tasks.
Place crew.py at the root directory. It contains the code:
from typing import List
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
# Check our tools documentations for more information on how to use them
from crewai_tools import SerperDevTool, ScrapeWebsiteTool, WebsiteSearchTool, FileReadTool
from pydantic import BaseModel, Field
web_search_tool = WebsiteSearchTool()
seper_dev_tool = SerperDevTool()
file_read_tool = FileReadTool(
file_path='./job_description_example.md',
description='A tool to read the job description example file.'
)
class ResearchRoleRequirements(BaseModel):
"""Research role requirements model"""
skills: List[str] = Field(..., description="List of recommended skills ...")
experience: List[str] = Field(..., description="List of recommended experience ...")
qualities: List[str] = Field(..., description="List of recommended qualities ...")
@CrewBase
class JobPostingCrew:
"""JobPosting crew"""
agents_config = 'config/agents.yaml'
tasks_config = 'config/tasks.yaml'
@agent
def research_agent(self) -> Agent:
return Agent(
config=self.agents_config['research_agent'],
tools=[web_search_tool, seper_dev_tool],
verbose=True
)
@agent
def writer_agent(self) -> Agent:
return Agent(
config=self.agents_config['writer_agent'],
tools=[web_search_tool, seper_dev_tool, file_read_tool],
verbose=True
)
@agent
def review_agent(self) -> Agent:
return Agent(
config=self.agents_config['review_agent'],
tools=[web_search_tool, seper_dev_tool, file_read_tool],
verbose=True
)
@task
def research_company_culture_task(self) -> Task:
return Task(
config=self.tasks_config['research_company_culture_task'],
agent=self.research_agent()
)
@task
def research_role_requirements_task(self) -> Task:
return Task(
config=self.tasks_config['research_role_requirements_task'],
agent=self.research_agent(),
output_json=ResearchRoleRequirements
)
@task
def draft_job_posting_task(self) -> Task:
return Task(
config=self.tasks_config['draft_job_posting_task'],
agent=self.writer_agent()
)
@task
def review_and_edit_job_posting_task(self) -> Task:
return Task(
config=self.tasks_config['review_and_edit_job_posting_task'],
agent=self.review_agent()
)
@task
def industry_analysis_task(self) -> Task:
return Task(
config=self.tasks_config['industry_analysis_task'],
agent=self.research_agent()
)
@crew
def crew(self) -> Crew:
"""Creates the JobPostingCrew"""
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
process=Process.sequential,
verbose=True,
)
3. config/agents.yaml
Purpose: Specifies each agent’s role, goal, and backstory.
Create a folder named config/ at your project root. Inside it, create agents.yaml:
research_agent:
role: >
Research Analyst
goal: >
Analyze the company website and provided description...
backstory: >
Expert in analyzing company cultures and identifying key values...
writer_agent:
role: >
Job Description Writer
goal: >
Use insights from the Research Analyst to create...
backstory: >
Skilled in crafting compelling job descriptions...
review_agent:
role: >
Review and Editing Specialist
goal: >
Review the job posting for clarity, engagement...
backstory: >
A meticulous editor ensuring every piece of content is polished...
4. config/tasks.yaml
Purpose: Defines the text prompts (description) and expected output for each task.
In the same config/ folder, make a second file called tasks.yaml:
research_company_culture_task:
description: >
Analyze {company_domain} and {company_description}...
expected_output: >
A comprehensive report detailing the company's culture...
research_role_requirements_task:
description: >
Based on {hiring_needs}, list recommended skills, experience...
expected_output: >
A list of recommended skills, experiences, and qualities...
draft_job_posting_task:
description: >
Draft a job posting for {hiring_needs}, using {specific_benefits}...
expected_output: >
A detailed, engaging job posting that includes an introduction...
review_and_edit_job_posting_task:
description: >
Review the job posting for {hiring_needs} for clarity...
expected_output: >
A polished, error-free job posting with final approval in markdown.
industry_analysis_task:
description: >
Conduct an in-depth analysis of {company_domain}'s industry...
expected_output: >
A detailed analysis highlighting major industry trends, opportunities...
Main.py (Workflow Definition)
Below is an example main.py script containing the run_job_posting_workflow function that the Job Description Agent calls to generate job descriptions. It uses Crew to structure tasks and (optionally) a local SQLite database for storage or logging.
import sys
from crew import JobPostingCrew
import sqlite3
async def run_job_posting_workflow(company_domain, company_description, hiring_needs, specific_benefits):
"""
Executes the job posting workflow with proper connection handling and debugging output.
Args:
company_domain (str): The company's domain.
company_description (str): A description of the company.
hiring_needs (str): Description of the role being hired for.
specific_benefits (str): Specific benefits offered for the role.
Returns:
str: The raw output of the 'industry_analysis_task' if found, else None.
"""
try:
# Initialize the JobPostingCrew
job_posting_crew = JobPostingCrew().crew()
# Define the inputs
inputs = {
'company_domain': company_domain,
'company_description': company_description,
'hiring_needs': hiring_needs,
'specific_benefits': specific_benefits,
}
# Execute the job posting process
result = job_posting_crew.kickoff(inputs=inputs)
# Debugging output
print("Result type:", type(result))
print("Attributes:", dir(result))
# Attempt to retrieve and print tasks_output
if hasattr(result, 'tasks_output') and result.tasks_output:
print("Tasks Output:")
for task_output in result.tasks_output:
print(f"Task Name: {task_output.name}")
if task_output.name == "industry_analysis_task":
return task_output.raw
print("Task 'review_and_edit_job_posting_task' not found in tasks_output.")
else:
print("No 'tasks_output' attribute found or it's empty.")
except Exception as e:
print(f"Error occurred: {e}")
return None
The run_job_posting_workflow function is invoked by the Job Description Agent’s webhook to handle incoming requests and generate the job description.
Job Description Agent Script
A Job Description Agent is a specialized CrewAI agent that creates detailed job descriptions based on:
- Company Domain
- Company Description
- Hiring Needs
- Specific Benefits
It registers itself with Agentverse, processes incoming requests (via a Flask webhook), and sends the generated output back to the requester.
Script Breakdown (JD_Agent.py)
Importing Required Libraries
The script imports essential libraries for agent registration, communication, and Flask-based HTTP handling:
import os
import logging
import asyncio
from uagents_core.crypto import Identity
from flask import Flask, request, jsonify
from fetchai.registration import register_with_agentverse
from fetchai.communication import parse_message_from_agent, send_message_to_agent
from main import run_job_posting_workflow
Setting Up Flask and Logging
The script initializes Flask for handling webhooks and sets up logging to monitor activities:
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Flask app for webhook
flask_app = Flask(__name__)
# Identity for the agent
jd_agent_identity = None
content = ''
Webhook Endpoint
The /webhook endpoint handles incoming requests and processes inputs to generate a job description:
@flask_app.route('/webhook', methods=['POST'])
async def webhook():
global content
global jd_agent_identity
try:
# Parse the incoming message
data = request.get_data().decode('utf-8')
message = parse_message_from_agent(data)
company_domain = message.payload.get("company_domain", "")
company_description = message.payload.get("company_description", "")
hiring_needs = message.payload.get("hiring_needs", "")
specific_benefits = message.payload.get("specific_benefits", "")
agent_address = message.sender
# Run the job posting workflow
content = await run_job_posting_workflow(
company_domain=company_domain,
company_description=company_description,
hiring_needs=hiring_needs,
specific_benefits=specific_benefits
)
# Send the generated content back to the requesting agent
payload = {'content': content}
send_message_to_agent(jd_agent_identity, agent_address, payload)
return jsonify({"status": "job_description_sent"})
except Exception as e:
logger.error(f"Error in webhook: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
Registering the Agent
The init_agent function registers the Job Description Agent with Fetch.ai's Agentverse, providing metadata such as agent title, description, and use cases:
def init_agent():
global jd_agent_identity
try:
jd_agent_identity = Identity.from_seed("Job Description Agent Seed", 0)
register_with_agentverse(
identity=jd_agent_identity,
url="http://localhost:5008/webhook",
agentverse_token=os.getenv("AGENTVERSE_API_KEY"),
agent_title="Job Description Creation Agent",
readme="""
<description>Job Description creator to create JD according to the company's career website and description using CrewAI.</description>
<use_cases>
<use_case>Generates job descriptions for specified roles.</use_case>
</use_cases>
<payload_requirements>
<description>Expects company domain, description, hiring needs, and specific benefits.</description>
<payload>
<requirement>
<parameter>company_domain</parameter>
<description>Career Page for the company</description>
<parameter>company_description</parameter>
<description>Description of the company</description>
<parameter>hiring_needs</parameter>
<description>Role for which the person is needed</description>
<parameter>specific_benefits</parameter>
<description>Benefits offered with the role</description>
</requirement>
</payload>
</payload_requirements>
"""
)
logger.info("Job Description Creator Agent registered successfully!")
except Exception as e:
logger.error(f"Error initializing agent: {e}")
raise
Running the Agent
The script starts the Flask server on port 5008:
if __name__ == "__main__":
init_agent()
flask_app.run(host="0.0.0.0", port=5008, debug=True)