Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion be_repo/modules/job_recommendation_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def job_recommend(resume_text, user_id):
password=NEO4J_PASSWORD
)

node_label = "JTitle" # Adjust as needed; could be dynamic based on user input or other criteria

# Initialize Controller Components
resume_processor = ResumeProcessor()
retrieval_engine = RetrievalEngine(resume_processor, neo4j_model)
Expand All @@ -40,7 +42,7 @@ def job_recommend(resume_text, user_id):
view = CLIView()

# Perform Mixed Retrieval
similar_docs, graph_results = retrieval_engine.perform_mixed_retrieval(resume_text, node_label='JTitle')
similar_docs, graph_results = retrieval_engine.perform_mixed_retrieval(resume_text, node_label=node_label)

if not similar_docs and not graph_results:
return 'No job recommendations found based on your resume.'
Expand Down
34 changes: 20 additions & 14 deletions be_repo/modules/recommendation_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,42 @@ def merge_results(self, vector_docs, graph_results):

# Process vector similarity results
for doc in vector_docs:
comp = doc.metadata.get("comp", "")
resp = doc.metadata.get("resp", "")
job_title = f"{resp} at {comp}".strip()
if job_title:
combined_jobs[job_title] = combined_jobs.get(job_title, 0) + 1
# Exclude 'id' and get all other non-empty metadata properties
metadata = {k: v for k, v in doc.metadata.items() if k != 'id' and v}
# Create a description string from the non-empty properties
job_description = ', '.join(f"{k}: {v}" for k, v in metadata.items())
if job_description:
combined_jobs[job_description] = combined_jobs.get(job_description, 0) + 1

# Process graph traversal results
# Access the context from intermediate steps
intermediate_steps = graph_results.get('intermediate_steps', [])
if len(intermediate_steps) > 1:
context = intermediate_steps[1].get('context', [])
for job in context:
job_title = job.get('job_title', '')
company = job.get('company', '')
if job_title and company:
combined_job = f"{job_title} at {company}"
combined_jobs[combined_job] = combined_jobs.get(combined_job, 0) + 1
# Exclude 'id' and get all other non-empty properties
job_data = {k: v for k, v in job.items() if k != 'id' and v}
# Create a description string
job_description = ', '.join(f"{k}: {v}" for k, v in job_data.items())
if job_description:
combined_jobs[job_description] = combined_jobs.get(job_description, 0) + 1

# Include the 'result' from 'graph_results' directly
graph_result_text = graph_results.get('result', '').strip()
if graph_result_text:
combined_jobs[graph_result_text] = combined_jobs.get(graph_result_text, 0) + 1

# Convert to sorted list based on combined score
sorted_jobs = sorted(combined_jobs.items(), key=lambda item: item[1], reverse=True)
return [job for job, score in sorted_jobs]

def generate_recommendations(self, vector_docs, graph_results):
"""
Generate a ranked list of job recommendations by merging vector and graph results.

Parameters:
vector_docs (List[Document]): Documents from vector similarity search.
graph_results (dict): Results from graph traversal.

Returns:
List[str]: Ranked list of unique job recommendations.
"""
Expand Down
63 changes: 30 additions & 33 deletions be_repo/modules/retrieval_engine.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# retrieval_engine.py

from langchain_neo4j import GraphCypherQAChain
from langchain_openai import ChatOpenAI
from langchain.chains.retrieval import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains.retrieval import create_retrieval_chain
from configs.openai_key import get_openai_api_key # New import
from langchain.prompts import PromptTemplate


class RetrievalEngine:
def __init__(self, resume_processor, neo4j_model):
"""
Expand All @@ -19,18 +21,15 @@ def __init__(self, resume_processor, neo4j_model):

# Initialize Language Model (already initialized in Neo4jModel)
self.llm = self.neo4j_model.llm

# Initialize GraphCypherQAChain (already initialized in Neo4jModel)
self.graph_chain = self.neo4j_model.get_graph_chain()

# Define the PromptTemplate with 'context' as input variable
prompt = PromptTemplate(
template="""
You are an expert Cypher query writer for a Neo4j graph database.
template="""
You are an assistant that matches resumes to relevant job descriptions.

Given the user's question, generate an efficient Cypher query that:
- extract entities and relationships from the following resume.
- Focus solely on the resume content.
Given the user's resume, find the most relevant job descriptions.

**Entities to Extract:**
- **Education (Edu):** Details about degrees, fields of study, institutions, start and end years, GPA.
Expand All @@ -40,29 +39,19 @@ def __init__(self, resume_processor, neo4j_model):
- **Certifications (Cert):** Certification names, issuing organizations, expiration dates.
- **Soft Skills (SSkill):** Non-technical skills like leadership, communication.

**Relationships to Identify:**
- **UTILIZES_SKILL:** A Work Experience (WE) node utilizes a Skill (Skill) node.
- **USES_TECH:** A Project (Proj) node uses a Skill (Skill) node as a technology.
- **REL_TO (Proj to Skill):** A Project (Proj) node is related to a Skill (Skill) node.
- **REL_TO (Skill to Skill):** A Skill (Skill) node is similar to another Skill (Skill) node.

**Resume:**
\"\"\"
{context}
\"\"\"
""",
"""

self.prompt_template = PromptTemplate(
template=template,
input_variables=["input"]
)

# Create a documents chain
self.combine_docs_chain = create_stuff_documents_chain(self.llm, prompt=prompt)

# Initialize Retrieval Chain
# Default node_label is 'JD'; can be adjusted as needed
self.retrieval_chain = create_retrieval_chain(
self.neo4j_model.get_retriever(node_label="JD"),
self.combine_docs_chain
)
self.combine_docs_chain = create_stuff_documents_chain(self.llm, self.prompt_template)

def perform_mixed_retrieval(self, resume_text, node_label="JD"):
"""
Expand All @@ -77,30 +66,38 @@ def perform_mixed_retrieval(self, resume_text, node_label="JD"):
"""
# Process resume into a Document
doc = self.resume_processor.process_resume(resume_text)

if not doc:
return [], {}

# Store the Document in the appropriate vector store
self.neo4j_model.store_documents([doc], node_label=node_label)

# Access the schema property correctly
schema = self.neo4j_model.graph.get_schema

# Get the retriever for the given node label
retriever = self.neo4j_model.get_retriever(node_label=node_label)

# Create the retrieval chain with the retriever and the combine_docs_chain
retrieval_chain = create_retrieval_chain(
retriever,
self.combine_docs_chain
)

# Perform vector similarity search
similar_docs_result = self.retrieval_chain.invoke({"input": resume_text}) # Corrected to 'context'
similar_docs_result = retrieval_chain.invoke({"input": resume_text}) # Corrected to 'context'
similar_docs = similar_docs_result.get("output", [])
print("similar_docs_result:", similar_docs_result)
print("Keys in similar_docs_result:", similar_docs_result.keys())

for doc in similar_docs:
print("Document Metadata:", doc.metadata)

query = (f"Based on the following resume, recommend relevant job positions based on skills and experience, "
f"while ignoring the location: {resume_text}")
query = f"Based on the following resume, recommend relevant job positions: {resume_text}"
graph_response = self.graph_chain.invoke({"query": query, "schema": schema})
# After graph query
print("Graph Response:")
print(graph_response)

return similar_docs, graph_response
return similar_docs, graph_response
6 changes: 3 additions & 3 deletions be_repo/modules/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def display_recommendations(self, recommendations):
Display job recommendations to the user.
"""
if not recommendations:
return 'No job recommendations found based on your resume.'
res = '\nRecommended Jobs for You:\n'
return "No job recommendations found based on your resume."
res = "\nRecommended Jobs for You:\n"
for idx, job in enumerate(recommendations, start=1):
res += f'{idx}. {job}\n'
res += f"{idx}. {job}\n"
return res