Deepak Pant
commited on
Commit
Β·
c6d9110
1
Parent(s):
c2e4879
Updated poetry setup
Browse files- .devcontainer/devcontainer.json +2 -7
- Makefile +4 -8
- poetry.lock +0 -0
- pyproject.toml +3 -1
- resume_maker_ai_agent/app.py +47 -38
- resume_maker_ai_agent/app2.py +0 -64
- resume_maker_ai_agent/crew.py +10 -22
- resume_maker_ai_agent/services/app_service.py +23 -22
.devcontainer/devcontainer.json
CHANGED
@@ -15,10 +15,7 @@
|
|
15 |
},
|
16 |
// Use 'postCreateCommand' to run commands after the container is created.
|
17 |
"postCreateCommand": "./.devcontainer/postCreateCommand.sh",
|
18 |
-
"forwardPorts": [
|
19 |
-
7860,
|
20 |
-
8000
|
21 |
-
],
|
22 |
// Configure tool-specific properties.
|
23 |
"customizations": {
|
24 |
"vscode": {
|
@@ -37,9 +34,7 @@
|
|
37 |
"tamasfe.even-better-toml" // TOML
|
38 |
],
|
39 |
"settings": {
|
40 |
-
"python.testing.pytestArgs": [
|
41 |
-
"tests"
|
42 |
-
],
|
43 |
"python.testing.unittestEnabled": false,
|
44 |
"python.testing.pytestEnabled": true,
|
45 |
"python.defaultInterpreterPath": "/workspaces/resume-maker-ai-agent/.venv/bin/python",
|
|
|
15 |
},
|
16 |
// Use 'postCreateCommand' to run commands after the container is created.
|
17 |
"postCreateCommand": "./.devcontainer/postCreateCommand.sh",
|
18 |
+
"forwardPorts": [7860, 8000],
|
|
|
|
|
|
|
19 |
// Configure tool-specific properties.
|
20 |
"customizations": {
|
21 |
"vscode": {
|
|
|
34 |
"tamasfe.even-better-toml" // TOML
|
35 |
],
|
36 |
"settings": {
|
37 |
+
"python.testing.pytestArgs": ["tests"],
|
|
|
|
|
38 |
"python.testing.unittestEnabled": false,
|
39 |
"python.testing.pytestEnabled": true,
|
40 |
"python.defaultInterpreterPath": "/workspaces/resume-maker-ai-agent/.venv/bin/python",
|
Makefile
CHANGED
@@ -37,7 +37,7 @@ help: ## Display this help message
|
|
37 |
# Installation and Setup
|
38 |
# =============================
|
39 |
.PHONY: bake-env
|
40 |
-
bake-env:
|
41 |
@echo "π Creating virtual environment using pyenv and poetry"
|
42 |
@poetry install --all-extras
|
43 |
@poetry run pre-commit install || true
|
@@ -84,13 +84,9 @@ lint: ## Run code quality tools
|
|
84 |
@echo "π Linting code with pre-commit"
|
85 |
@poetry run pre-commit run -a
|
86 |
@echo "π Static type checking with mypy"
|
87 |
-
# @echo "π Sorting imports with isort"
|
88 |
-
# @poetry run isort resume_maker_ai_agent/
|
89 |
-
# @echo "π Linting code with Ruff"
|
90 |
-
# @poetry run ruff format resume_maker_ai_agent/
|
91 |
@poetry run mypy
|
92 |
@echo "π Checking for obsolete dependencies with deptry"
|
93 |
-
@poetry run deptry .
|
94 |
@echo "π Checking for security vulnerabilities with bandit"
|
95 |
@poetry run bandit -c pyproject.toml -r resume_maker_ai_agent/ -ll
|
96 |
|
@@ -132,7 +128,7 @@ bake-and-publish: bake publish ## Build and publish to PyPI
|
|
132 |
.PHONY: update
|
133 |
update: ## Update project dependencies
|
134 |
@echo "π Updating project dependencies"
|
135 |
-
@poetry update
|
136 |
@poetry run pre-commit install --overwrite
|
137 |
@echo "Dependencies updated successfully"
|
138 |
|
@@ -142,7 +138,7 @@ update: ## Update project dependencies
|
|
142 |
.PHONY: run
|
143 |
run: ## Run the project's main application
|
144 |
@echo "π Running the project"
|
145 |
-
@poetry run streamlit run $(PROJECT_SLUG)/
|
146 |
|
147 |
.PHONY: docs-test
|
148 |
docs-test: ## Test if documentation can be built without warnings or errors
|
|
|
37 |
# Installation and Setup
|
38 |
# =============================
|
39 |
.PHONY: bake-env
|
40 |
+
bake-env: ## Install the poetry environment and set up pre-commit hooks
|
41 |
@echo "π Creating virtual environment using pyenv and poetry"
|
42 |
@poetry install --all-extras
|
43 |
@poetry run pre-commit install || true
|
|
|
84 |
@echo "π Linting code with pre-commit"
|
85 |
@poetry run pre-commit run -a
|
86 |
@echo "π Static type checking with mypy"
|
|
|
|
|
|
|
|
|
87 |
@poetry run mypy
|
88 |
@echo "π Checking for obsolete dependencies with deptry"
|
89 |
+
# @poetry run deptry .
|
90 |
@echo "π Checking for security vulnerabilities with bandit"
|
91 |
@poetry run bandit -c pyproject.toml -r resume_maker_ai_agent/ -ll
|
92 |
|
|
|
128 |
.PHONY: update
|
129 |
update: ## Update project dependencies
|
130 |
@echo "π Updating project dependencies"
|
131 |
+
@poetry update
|
132 |
@poetry run pre-commit install --overwrite
|
133 |
@echo "Dependencies updated successfully"
|
134 |
|
|
|
138 |
.PHONY: run
|
139 |
run: ## Run the project's main application
|
140 |
@echo "π Running the project"
|
141 |
+
@poetry run streamlit run $(PROJECT_SLUG)/app.py --server.port 7860
|
142 |
|
143 |
.PHONY: docs-test
|
144 |
docs-test: ## Test if documentation can be built without warnings or errors
|
poetry.lock
ADDED
The diff for this file is too large to render.
See raw diff
|
|
pyproject.toml
CHANGED
@@ -8,8 +8,9 @@ requires-python = ">=3.10,<=3.13"
|
|
8 |
dependencies = [
|
9 |
"crewai[tools]>=0.86.0,<1.0.0",
|
10 |
"streamlit >=1.41.1",
|
11 |
-
# "PyPDF2 >=3.0.1",
|
12 |
# "python-docx >=1.1.2",
|
|
|
|
|
13 |
]
|
14 |
|
15 |
[project.urls]
|
@@ -82,6 +83,7 @@ module = [
|
|
82 |
"crewai.*",
|
83 |
"crewai_tools.*",
|
84 |
"bs4.*",
|
|
|
85 |
"resume_maker_ai_agent.crew",
|
86 |
]
|
87 |
ignore_missing_imports = true
|
|
|
8 |
dependencies = [
|
9 |
"crewai[tools]>=0.86.0,<1.0.0",
|
10 |
"streamlit >=1.41.1",
|
|
|
11 |
# "python-docx >=1.1.2",
|
12 |
+
"pypdf (>=5.1.0,<6.0.0)",
|
13 |
+
"pysqlite3-binary >=0.5.4",
|
14 |
]
|
15 |
|
16 |
[project.urls]
|
|
|
83 |
"crewai.*",
|
84 |
"crewai_tools.*",
|
85 |
"bs4.*",
|
86 |
+
"streamlit.*",
|
87 |
"resume_maker_ai_agent.crew",
|
88 |
]
|
89 |
ignore_missing_imports = true
|
resume_maker_ai_agent/app.py
CHANGED
@@ -1,53 +1,62 @@
|
|
1 |
-
import
|
2 |
|
|
|
|
|
3 |
import streamlit as st
|
4 |
|
5 |
-
|
|
|
6 |
|
7 |
-
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
8 |
|
|
|
|
|
|
|
9 |
|
10 |
-
|
11 |
-
st.
|
12 |
|
13 |
-
#
|
14 |
-
st.
|
15 |
|
16 |
-
|
|
|
17 |
|
18 |
-
if
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
22 |
|
23 |
-
|
24 |
-
|
|
|
25 |
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
|
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
# Create columns
|
37 |
-
col1, col2, col3, col4 = st.columns([1, 2, 2, 2])
|
38 |
|
39 |
-
|
40 |
-
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
st.markdown(f"*{artists} | {release_date}*")
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
except Exception as e:
|
52 |
-
print(f"An error occurred: {e!s}")
|
53 |
-
continue
|
|
|
1 |
+
# import PyPDF2
|
2 |
|
3 |
+
# from docx import Document
|
4 |
+
# from docx.shared import Inches
|
5 |
import streamlit as st
|
6 |
|
7 |
+
# import tempfile
|
8 |
+
from resume_maker_ai_agent.services.app_service import run
|
9 |
|
|
|
10 |
|
11 |
+
def main() -> None:
|
12 |
+
print("main......")
|
13 |
+
st.set_page_config(page_title="Resume Maker AI", page_icon="π")
|
14 |
|
15 |
+
st.title("Resume Maker AI")
|
16 |
+
st.write("Customize your resume for specific job descriptions using AI")
|
17 |
|
18 |
+
# File upload
|
19 |
+
uploaded_file = st.file_uploader("Upload your resume (PDF)", type="pdf")
|
20 |
|
21 |
+
# Job description input
|
22 |
+
job_description = st.text_area("Enter the job description:", height=200)
|
23 |
|
24 |
+
if st.button("Customize Resume") and uploaded_file is not None and job_description:
|
25 |
+
with st.spinner("Customizing your resume..."):
|
26 |
+
try:
|
27 |
+
# Customize resume
|
28 |
+
customized_resume = run(uploaded_file, job_description)
|
29 |
|
30 |
+
# Display customized resume
|
31 |
+
st.subheader("Customized Resume")
|
32 |
+
st.write(customized_resume)
|
33 |
|
34 |
+
# Create download button
|
35 |
+
# doc_buffer = create_docx(customized_resume)
|
36 |
+
# st.download_button(
|
37 |
+
# label="Download Customized Resume",
|
38 |
+
# data=doc_buffer,
|
39 |
+
# file_name="customized_resume.docx",
|
40 |
+
# mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
41 |
+
# )
|
42 |
|
43 |
+
except Exception as e:
|
44 |
+
st.error(f"An error occurred: {e!s}")
|
|
|
|
|
45 |
|
46 |
+
# Add instructions and tips
|
47 |
+
with st.expander("How to use"):
|
48 |
+
st.write("""
|
49 |
+
1. Upload your current resume in PDF format
|
50 |
+
2. Paste the job description you're targeting
|
51 |
+
3. Click 'Customize Resume' to generate a tailored version
|
52 |
+
4. Review the customized resume
|
53 |
+
5. Download the result as a Word document
|
54 |
+
""")
|
55 |
|
56 |
+
# Footer
|
57 |
+
st.markdown("---")
|
58 |
+
st.markdown("Built with Streamlit and Crew AI")
|
|
|
59 |
|
60 |
+
|
61 |
+
if __name__ == "__main__":
|
62 |
+
main()
|
|
|
|
|
|
resume_maker_ai_agent/app2.py
DELETED
@@ -1,64 +0,0 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
# import PyPDF2
|
3 |
-
import io
|
4 |
-
# from docx import Document
|
5 |
-
# from docx.shared import Inches
|
6 |
-
import openai
|
7 |
-
# import tempfile
|
8 |
-
|
9 |
-
from resume_maker_ai_agent.services.app_service import run, create_docx
|
10 |
-
|
11 |
-
|
12 |
-
def main():
|
13 |
-
print("main......")
|
14 |
-
st.set_page_config(page_title="Resume Maker AI", page_icon="π")
|
15 |
-
|
16 |
-
st.title("Resume Maker AI")
|
17 |
-
st.write("Customize your resume for specific job descriptions using AI")
|
18 |
-
|
19 |
-
# File upload
|
20 |
-
uploaded_file = st.file_uploader("Upload your resume (PDF)", type="pdf")
|
21 |
-
|
22 |
-
# Job description input
|
23 |
-
job_description = st.text_area("Enter the job description:", height=200)
|
24 |
-
|
25 |
-
if st.button("Customize Resume") and uploaded_file is not None and job_description:
|
26 |
-
with st.spinner("Customizing your resume..."):
|
27 |
-
try:
|
28 |
-
# Customize resume
|
29 |
-
customized_resume = run(
|
30 |
-
uploaded_file, job_description)
|
31 |
-
|
32 |
-
# Display customized resume
|
33 |
-
st.subheader("Customized Resume")
|
34 |
-
st.write(customized_resume)
|
35 |
-
|
36 |
-
# Create download button
|
37 |
-
doc_buffer = create_docx(customized_resume)
|
38 |
-
st.download_button(
|
39 |
-
label="Download Customized Resume",
|
40 |
-
data=doc_buffer,
|
41 |
-
file_name="customized_resume.docx",
|
42 |
-
mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
43 |
-
)
|
44 |
-
|
45 |
-
except Exception as e:
|
46 |
-
st.error(f"An error occurred: {str(e)}")
|
47 |
-
|
48 |
-
# Add instructions and tips
|
49 |
-
with st.expander("How to use"):
|
50 |
-
st.write("""
|
51 |
-
1. Upload your current resume in PDF format
|
52 |
-
2. Paste the job description you're targeting
|
53 |
-
3. Click 'Customize Resume' to generate a tailored version
|
54 |
-
4. Review the customized resume
|
55 |
-
5. Download the result as a Word document
|
56 |
-
""")
|
57 |
-
|
58 |
-
# Footer
|
59 |
-
st.markdown("---")
|
60 |
-
st.markdown("Built with Streamlit and Crew AI")
|
61 |
-
|
62 |
-
|
63 |
-
if __name__ == "__main__":
|
64 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resume_maker_ai_agent/crew.py
CHANGED
@@ -1,9 +1,9 @@
|
|
|
|
|
|
1 |
from crewai import Agent, Crew, Process, Task
|
2 |
from crewai.project import CrewBase, agent, crew, task
|
3 |
|
4 |
-
from resume_maker_ai_agent.models.response_models import MusicDetails
|
5 |
from resume_maker_ai_agent.tools.custom_tool import search_tool
|
6 |
-
import warnings
|
7 |
|
8 |
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
9 |
|
@@ -25,9 +25,7 @@ class ResumeMakerAIAgent:
|
|
25 |
:return: An instance of the Agent class
|
26 |
"""
|
27 |
|
28 |
-
return Agent(config=self.agents_config["job_researcher"],
|
29 |
-
tools=[search_tool],
|
30 |
-
verbose=True)
|
31 |
|
32 |
@agent
|
33 |
def profiler(self) -> Agent:
|
@@ -39,9 +37,7 @@ class ResumeMakerAIAgent:
|
|
39 |
:return: An instance of the Agent class
|
40 |
"""
|
41 |
|
42 |
-
return Agent(config=self.agents_config["profiler"],
|
43 |
-
tools=[search_tool],
|
44 |
-
verbose=True)
|
45 |
|
46 |
@agent
|
47 |
def resume_strategist(self) -> Agent:
|
@@ -54,9 +50,7 @@ class ResumeMakerAIAgent:
|
|
54 |
:return: An instance of the Agent class
|
55 |
"""
|
56 |
|
57 |
-
return Agent(config=self.agents_config["resume_strategist"],
|
58 |
-
tools=[search_tool],
|
59 |
-
verbose=True)
|
60 |
|
61 |
@task
|
62 |
def research_task(self) -> Task:
|
@@ -68,10 +62,7 @@ class ResumeMakerAIAgent:
|
|
68 |
:return: An instance of the Task class
|
69 |
"""
|
70 |
|
71 |
-
return Task(
|
72 |
-
config=self.tasks_config["research_task"],
|
73 |
-
async_execution=True
|
74 |
-
)
|
75 |
|
76 |
@task
|
77 |
def profile_task(self) -> Task:
|
@@ -83,13 +74,10 @@ class ResumeMakerAIAgent:
|
|
83 |
:return: An instance of the Task class
|
84 |
"""
|
85 |
|
86 |
-
return Task(
|
87 |
-
config=self.tasks_config["profile_task"],
|
88 |
-
async_execution=True
|
89 |
-
)
|
90 |
|
91 |
@task
|
92 |
-
def
|
93 |
"""
|
94 |
Creates the profile task.
|
95 |
|
@@ -100,8 +88,8 @@ class ResumeMakerAIAgent:
|
|
100 |
"""
|
101 |
|
102 |
return Task(
|
103 |
-
config=self.tasks_config["
|
104 |
-
context=[research_task, profile_task],
|
105 |
)
|
106 |
|
107 |
@crew
|
|
|
1 |
+
import warnings
|
2 |
+
|
3 |
from crewai import Agent, Crew, Process, Task
|
4 |
from crewai.project import CrewBase, agent, crew, task
|
5 |
|
|
|
6 |
from resume_maker_ai_agent.tools.custom_tool import search_tool
|
|
|
7 |
|
8 |
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
9 |
|
|
|
25 |
:return: An instance of the Agent class
|
26 |
"""
|
27 |
|
28 |
+
return Agent(config=self.agents_config["job_researcher"], tools=[search_tool], verbose=True)
|
|
|
|
|
29 |
|
30 |
@agent
|
31 |
def profiler(self) -> Agent:
|
|
|
37 |
:return: An instance of the Agent class
|
38 |
"""
|
39 |
|
40 |
+
return Agent(config=self.agents_config["profiler"], tools=[search_tool], verbose=True)
|
|
|
|
|
41 |
|
42 |
@agent
|
43 |
def resume_strategist(self) -> Agent:
|
|
|
50 |
:return: An instance of the Agent class
|
51 |
"""
|
52 |
|
53 |
+
return Agent(config=self.agents_config["resume_strategist"], tools=[search_tool], verbose=True)
|
|
|
|
|
54 |
|
55 |
@task
|
56 |
def research_task(self) -> Task:
|
|
|
62 |
:return: An instance of the Task class
|
63 |
"""
|
64 |
|
65 |
+
return Task(config=self.tasks_config["research_task"], async_execution=True)
|
|
|
|
|
|
|
66 |
|
67 |
@task
|
68 |
def profile_task(self) -> Task:
|
|
|
74 |
:return: An instance of the Task class
|
75 |
"""
|
76 |
|
77 |
+
return Task(config=self.tasks_config["profile_task"], async_execution=True)
|
|
|
|
|
|
|
78 |
|
79 |
@task
|
80 |
+
def resume_strategy_task(self) -> Task:
|
81 |
"""
|
82 |
Creates the profile task.
|
83 |
|
|
|
88 |
"""
|
89 |
|
90 |
return Task(
|
91 |
+
config=self.tasks_config["resume_strategy_task"],
|
92 |
+
context=[self.research_task(), self.profile_task()],
|
93 |
)
|
94 |
|
95 |
@crew
|
resume_maker_ai_agent/services/app_service.py
CHANGED
@@ -1,18 +1,19 @@
|
|
1 |
-
import
|
2 |
-
import
|
3 |
-
# from docx import Document
|
4 |
-
import io
|
5 |
|
|
|
6 |
from resume_maker_ai_agent.crew import ResumeMakerAIAgent
|
7 |
-
from streamlit.runtime.uploaded_file_manager import UploadedFile
|
8 |
|
9 |
|
10 |
-
def
|
11 |
-
"""Extract text content from uploaded PDF file."""
|
12 |
-
pdf_reader = PyPDF2.PdfReader(pdf_file_path)
|
13 |
text = ""
|
14 |
-
|
15 |
-
|
|
|
|
|
|
|
|
|
|
|
16 |
return text
|
17 |
|
18 |
|
@@ -27,24 +28,24 @@ def run(pdf_file_path: UploadedFile, job_description: str) -> str:
|
|
27 |
"""
|
28 |
|
29 |
print("Extracting text from PDF")
|
30 |
-
resume_text =
|
31 |
|
32 |
# Run the crew
|
33 |
print("Running the crew")
|
34 |
inputs = {"resume_text": resume_text, "job_description": job_description}
|
35 |
result = ResumeMakerAIAgent().crew().kickoff(inputs=inputs)
|
36 |
|
37 |
-
return result.raw
|
38 |
|
39 |
|
40 |
-
def create_docx(content):
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
1 |
+
import pypdf
|
2 |
+
from streamlit.runtime.uploaded_file_manager import UploadedFile
|
|
|
|
|
3 |
|
4 |
+
# from docx import Document
|
5 |
from resume_maker_ai_agent.crew import ResumeMakerAIAgent
|
|
|
6 |
|
7 |
|
8 |
+
def read_pdf(uploaded_file: UploadedFile) -> str:
|
|
|
|
|
9 |
text = ""
|
10 |
+
try:
|
11 |
+
pdf_reader = pypdf.PdfReader(uploaded_file)
|
12 |
+
|
13 |
+
for page in pdf_reader.pages:
|
14 |
+
text += page.extract_text()
|
15 |
+
except Exception as e:
|
16 |
+
print(f"An unexpected error occurred: {e}")
|
17 |
return text
|
18 |
|
19 |
|
|
|
28 |
"""
|
29 |
|
30 |
print("Extracting text from PDF")
|
31 |
+
resume_text = read_pdf(pdf_file_path)
|
32 |
|
33 |
# Run the crew
|
34 |
print("Running the crew")
|
35 |
inputs = {"resume_text": resume_text, "job_description": job_description}
|
36 |
result = ResumeMakerAIAgent().crew().kickoff(inputs=inputs)
|
37 |
|
38 |
+
return str(result.raw)
|
39 |
|
40 |
|
41 |
+
# def create_docx(content) -> bytes | None:
|
42 |
+
# """Create a Word document with the content."""
|
43 |
+
# # doc = Document()
|
44 |
+
# # doc.add_paragraph(content)
|
45 |
|
46 |
+
# # # Save to bytes buffer
|
47 |
+
# # buffer = io.BytesIO()
|
48 |
+
# # doc.save(buffer)
|
49 |
+
# # buffer.seek(0)
|
50 |
+
# # return buffer
|
51 |
+
# return None
|