diff --git a/be_repo/modules/job_recommendation_system.py b/be_repo/modules/job_recommendation_system.py index ee84e71b4..79f84e1d3 100644 --- a/be_repo/modules/job_recommendation_system.py +++ b/be_repo/modules/job_recommendation_system.py @@ -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) @@ -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.' diff --git a/be_repo/modules/recommendation_generator.py b/be_repo/modules/recommendation_generator.py index 767063c7d..da367a511 100644 --- a/be_repo/modules/recommendation_generator.py +++ b/be_repo/modules/recommendation_generator.py @@ -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. """ diff --git a/be_repo/modules/retrieval_engine.py b/be_repo/modules/retrieval_engine.py index 0cb62aac2..83e2b6a83 100644 --- a/be_repo/modules/retrieval_engine.py +++ b/be_repo/modules/retrieval_engine.py @@ -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): """ @@ -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. @@ -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"): """ @@ -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 \ No newline at end of file diff --git a/be_repo/modules/view.py b/be_repo/modules/view.py index f6bd1264b..d427efa2c 100644 --- a/be_repo/modules/view.py +++ b/be_repo/modules/view.py @@ -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