En este artículo, quiero compartir un logro reciente en el desarrollo de una herramienta automatizada para generar cartas de presentación personalizadas para propuestas en Upwork. El objetivo principal fue crear una solución rápida y eficiente que analice automáticamente a través de agentes de CrewAI una oferta de trabajo y un perfil de LinkedIn, para generar un texto atractivo que resalte las habilidades más relevantes del candidato. Un ejercicio similar al que hice en «AI Agents: Encuentra trabajo con este sistema de agentes» pero en aquel se trata de generar un Currículum y en el que desarrollaremos ahora de hacer una propuesta personalizada y adaptada para cualquier oferta de trabajo en Upwork.
Desafío Inicial
El problema que buscaba resolver era simple pero crucial por la pereza que me da: generar cartas de presentación que se adapten perfectamente a las necesidades de cada oferta de trabajo en Upwork. Muchas veces, los freelancers se ven en la necesidad de crear propuestas repetitivas y poco personalizadas, lo que reduce las posibilidades de captar la atención del cliente. En este caso el resultado es una propuesta adpatada a tus skills y a los requerimientos de la oferta en concreto.
Enfoque y Solución
Opté por una solución basada en Streamlit, una librería de Python que facilita la creación de interfaces web interactivas de manera rápida. Utilizando un conjunto de herramientas y agentes inteligentes de CrewAI diseñados para interactuar con los perfiles de LinkedIn y las ofertas de Upwork. Con esto logramos automatizar la generación de cartas de presentación a partir de dos datos clave que introduce el usuario: la URL del perfil de LinkedIn y la URL de la oferta de trabajo en Upwork.
Estructura del Proyecto
El flujo de la aplicación sigue tres pasos clave:
- Análisis de la oferta de trabajo: Utilizamos herramientas de scraping propias del framework de CrewAI para extraer la descripción de la oferta y analizar los requisitos clave, tales como habilidades, experiencia y competencias necesarias.
- Perfilado del candidato: A partir del perfil de LinkedIn del usuario, recopilamos información relevante que pueda alinearse con los requisitos de la oferta. Esto incluye experiencia, educación y habilidades destacadas.
- Generación de la carta de presentación: Una vez recopilada toda la información, un “agente” especializado en estrategias de cartas de presentación crea una carta adaptada al contexto del trabajo y las competencias del candidato.
IMPORTANTE: es necesario abrir la url de la oferta de Upwork en incógnito para asegurarse de que sea accesible por el agente y no únicamente si tienes la sesión iniciada.
Código
Para este desarrollo vamos a necesitar un setup inicial que implica:
- Instalación de las dependencias necesarias
- Definir claves API en un archivo .env de Openai para la IA generativa y SerperAPI para el research en la web.
!pip install crewai==0.28.8 crewai_tools==0.1.6 langchain_community==0.0.29 streamlit
import streamlit as st
from crewai import Agent, Task, Crew
from utils import get_openai_api_key, get_serper_api_key
from crewai_tools import ScrapeWebsiteTool, SerperDevTool
import time
import os
# Set up API keys
openai_api_key = get_openai_api_key()
serper_api_key = get_serper_api_key()
Streamlit UI Set up
# Set up Streamlit UI
st.title('Upwork Cover Letter Generator')
job_posting_url = st.text_input('Enter the Upwork Job Posting URL')
linkedin_profile_url = st.text_input('Enter your LinkedIn Profile URL')
CrewAI Agents Set up
- Creamos los agentes, definimos sus roles, tools y tareas según el framework de CrewAI.
- Definimos condicionales para que no falten los datos necesarios
- Definimos un tiempo para que el script no quede en loop sin generar una respuesta concreta
# Add a button to trigger the script
if st.button('Generate Cover Letter'):
if job_posting_url and linkedin_profile_url:
try:
# Start timer
start_time = time.time()
st.write("Initializing tools...")
# Initialize tools
search_tool = SerperDevTool()
scrape_tool = ScrapeWebsiteTool()
st.write("Creating agents...")
# Create agents
researcher = Agent(
role="Upwork Job Posting Researcher",
goal="Analyze job postings and extract key requirements, skills, and experiences "
"that are necessary for the job.",
tools=[scrape_tool, search_tool],
verbose=True,
backstory=(
"As a Job Researcher, your expertise lies in analyzing job postings and identifying "
"the most important qualifications and skills. This analysis will support creating a "
"tailored and effective cover letter."
)
)
profiler = Agent(
role="Personal Profiler for IT experts applying to Upwork jobs",
goal="Create a detailed personal and professional profile for the candidate, "
"with a focus on tailoring it for job proposals on Upwork.",
tools=[scrape_tool, search_tool],
verbose=True,
backstory=(
"You specialize in creating profiles that emphasize key skills, achievements, "
"and experiences. Your focus is to craft a professional narrative that supports "
"the Upwork proposal using the LinkedIn profile."
)
)
resume_strategist = Agent(
role="Cover Letter Strategist for Upwork proposals",
goal="Create a personalized and persuasive cover letter for a job proposal "
"that highlights the candidate's most relevant skills and experiences. "
"Ensure the cover letter is aligned with the job description and focuses on "
"why the candidate is the ideal fit for the job.",
tools=[scrape_tool, search_tool],
verbose=True,
backstory=(
"With expertise in crafting tailored cover letters, you excel at emphasizing "
"key skills and experiences that match job requirements. Your focus is on creating "
"a brief, compelling narrative that positions the candidate as the best fit for "
"the Upwork job posting."
)
)
st.write("Defining tasks...")
# Create tasks
research_task = Task(
description=(
"Analyze the job posting url provided ({job_posting_url}) "
"to extract key skills, experiences, and qualifications required. "
"The goal is to create a list of key points that can be used to tailor the cover letter."),
expected_output=(
"A structured list of job requirements, including necessary skills, qualifications, "
"and experiences, that will help tailor the cover letter."),
agent=researcher,
async_execution=True
)
profile_task = Task(
description=(
"Compile a detailed personal and professional profile using the candidate’s LinkedIn profile ({linkedin_profile_url}) "
"Focus on identifying key skills and experiences that align with the Upwork "
"job description."),
expected_output=(
"A professional profile that can be used to enhance and tailor the Upwork cover letter."),
agent=profiler,
async_execution=True
)
resume_strategy_task = Task(
description=(
"Using the profile and Upwork job requirements obtained from previous tasks, "
"craft a concise and personalized cover letter. Highlight the candidate’s key "
"skills, experiences, and qualifications that match the job description. "
"Make sure the cover letter is professional and persuasive without fabricating any information."
),
expected_output=(
"A cover letter that highlights the candidate's most relevant skills and "
"experiences, effectively presenting them as the best fit for the Upwork job."
),
output_file="tailored_cover_letter.md",
context=[research_task, profile_task],
agent=resume_strategist
)
# Create and run the crew
st.write("Running crew...")
job_application_crew = Crew(
agents=[researcher, profiler, resume_strategist],
tasks=[research_task, profile_task, resume_strategy_task],
verbose=True
)
job_application_inputs = {
'job_posting_url': job_posting_url,
'linkedin_profile_url': linkedin_profile_url
}
result = job_application_crew.kickoff(inputs=job_application_inputs)
st.write("Cover letter generated successfully!")
# Display result
st.write("Loading cover letter...")
with open('tailored_cover_letter.md', 'r') as f:
cover_letter = f.read()
st.markdown(cover_letter)
# End timer and display execution time
end_time = time.time()
execution_time = end_time - start_time
st.write(f"Execution completed in {execution_time:.2f} seconds.")
except Exception as e:
st.error(f"An error occurred: {e}")
else:
st.error('Please enter both the job posting URL and your LinkedIn profile URL.')
Ejecución en tu servidor
Una vez que tienes el código y todas las instalaciones necesarias pones a correr el servidor de Streamlit:
streamlit run your-file-name.py (asegurate de poner el nombre exacto de tu archivo)
y voilá:
Resultados Obtenidos
El resultado es una herramienta que genera cartas de presentación en cuestión de segundos, completamente alineadas con las necesidades de cada oferta de trabajo. Al reducir la necesidad de escribir manualmente cada propuesta, se ahorra tiempo y se asegura una mayor disponibilidad en las aplicaciones enviadas.
Proximamente iré mejorando esta herramienta para que responda preguntas adicionales y encuentre de manera automática algunos trabajos interesantes. ¿Alguna idea para sumar?
Este proyecto es un gran ejemplo de cómo se puede aplicar el framework de CrewAI y el trabajo de agentes para automatizar problemas cotidianos de una manera eficiente y escalable. Si tienes alguna mejora que proponer es bienvenida y si quieres que hablemos de algun desarrollo IA solo tienes que decir hola! =)