Fix method invocation in LoggerChatModel
This commit resolves an issue where the LoggerChatModel class was incorrectly
attempting to call instances of AIModel directly as if they were callable objects.
Changes include:
- Modified __call__ method to explicitly use the invoke method when calling AI models.
- Updated constructor documentation to clarify the type of object expected.
- Added additional debug logging for better traceability of method entry and exit points.
These changes ensure that the LoggerChatModel class aligns with the intended design
patterns and correctly utilizes the AIModel instances, improving the maintainability
and robustness of the codebase.
This commit is contained in:
parent
9ef928569b
commit
7d7110253a
6 changed files with 67 additions and 210 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -13,3 +13,6 @@ chrome_profile
|
||||||
answers.json
|
answers.json
|
||||||
data*
|
data*
|
||||||
*virtual
|
*virtual
|
||||||
|
data_folder/*.yaml
|
||||||
|
app_log.log
|
||||||
|
venv
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
remote: [true/false]
|
|
||||||
|
|
||||||
experienceLevel:
|
|
||||||
internship: [true/false]
|
|
||||||
entry: [true/false]
|
|
||||||
associate: [true/false]
|
|
||||||
mid-senior level: [true/false]
|
|
||||||
director: [true/false]
|
|
||||||
executive: [true/false]
|
|
||||||
|
|
||||||
jobTypes:
|
|
||||||
full-time: [true/false]
|
|
||||||
contract: [true/false]
|
|
||||||
part-time: [true/false]
|
|
||||||
temporary: [true/false]
|
|
||||||
internship: [true/false]
|
|
||||||
other: [true/false]
|
|
||||||
volunteer: [true/false]
|
|
||||||
|
|
||||||
date:
|
|
||||||
all time: [true/false]
|
|
||||||
month: [true/false]
|
|
||||||
week: [true/false]
|
|
||||||
24 hours: [true/false]
|
|
||||||
|
|
||||||
positions:
|
|
||||||
- position1
|
|
||||||
- position2
|
|
||||||
|
|
||||||
locations:
|
|
||||||
- Country1
|
|
||||||
- Country2
|
|
||||||
|
|
||||||
applyOnceAtCompany: [true/false]
|
|
||||||
|
|
||||||
distance: 100
|
|
||||||
|
|
||||||
company_blacklist:
|
|
||||||
- Company1
|
|
||||||
- Company2
|
|
||||||
|
|
||||||
titleBlacklist:
|
|
||||||
- word1
|
|
||||||
- word2
|
|
||||||
|
|
||||||
job_applicants_threshold:
|
|
||||||
min_applicants: 0
|
|
||||||
max_applicants: 100
|
|
||||||
|
|
||||||
llm_model_type: openai
|
|
||||||
llm_model: gpt-4o
|
|
||||||
llm_api_url: https://api.pawan.krd/cosmosrp/v1
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
personal_information:
|
|
||||||
name: "[Your Name]"
|
|
||||||
surname: "[Your Surname]"
|
|
||||||
date_of_birth: "[Your Date of Birth]"
|
|
||||||
country: "[Your Country]"
|
|
||||||
city: "[Your City]"
|
|
||||||
address: "[Your Address]"
|
|
||||||
phone_prefix: "[Your Phone Prefix]"
|
|
||||||
phone: "[Your Phone Number]"
|
|
||||||
email: "[Your Email Address]"
|
|
||||||
github: "[Your GitHub Profile URL]"
|
|
||||||
linkedin: "[Your LinkedIn Profile URL]"
|
|
||||||
|
|
||||||
education_details:
|
|
||||||
- education_level: "[Your Education Level]"
|
|
||||||
institution: "[Your Institution]"
|
|
||||||
field_of_study: "[Your Field of Study]"
|
|
||||||
final_evaluation_grade: "[Your Final Evaluation Grade]"
|
|
||||||
start_date: "[Start Date]"
|
|
||||||
year_of_completion: "[Year of Completion]"
|
|
||||||
exam:
|
|
||||||
exam_name_1: "[Grade]"
|
|
||||||
exam_name_2: "[Grade]"
|
|
||||||
exam_name_3: "[Grade]"
|
|
||||||
exam_name_4: "[Grade]"
|
|
||||||
exam_name_5: "[Grade]"
|
|
||||||
exam_name_6: "[Grade]"
|
|
||||||
|
|
||||||
experience_details:
|
|
||||||
- position: "[Your Position]"
|
|
||||||
company: "[Company Name]"
|
|
||||||
employment_period: "[Employment Period]"
|
|
||||||
location: "[Location]"
|
|
||||||
industry: "[Industry]"
|
|
||||||
key_responsibilities:
|
|
||||||
- responsibility_1: "[Responsibility Description]"
|
|
||||||
- responsibility_2: "[Responsibility Description]"
|
|
||||||
- responsibility_3: "[Responsibility Description]"
|
|
||||||
skills_acquired:
|
|
||||||
- "[Skill]"
|
|
||||||
- "[Skill]"
|
|
||||||
- "[Skill]"
|
|
||||||
|
|
||||||
- position: "[Your Position]"
|
|
||||||
company: "[Company Name]"
|
|
||||||
employment_period: "[Employment Period]"
|
|
||||||
location: "[Location]"
|
|
||||||
industry: "[Industry]"
|
|
||||||
key_responsibilities:
|
|
||||||
- responsibility_1: "[Responsibility Description]"
|
|
||||||
- responsibility_2: "[Responsibility Description]"
|
|
||||||
- responsibility_3: "[Responsibility Description]"
|
|
||||||
skills_acquired:
|
|
||||||
- "[Skill]"
|
|
||||||
- "[Skill]"
|
|
||||||
- "[Skill]"
|
|
||||||
|
|
||||||
projects:
|
|
||||||
- name: "[Project Name]"
|
|
||||||
description: "[Project Description]"
|
|
||||||
link: "[Project Link]"
|
|
||||||
|
|
||||||
- name: "[Project Name]"
|
|
||||||
description: "[Project Description]"
|
|
||||||
link: "[Project Link]"
|
|
||||||
|
|
||||||
achievements:
|
|
||||||
- name: "[Achievement Name]"
|
|
||||||
description: "[Achievement Description]"
|
|
||||||
- name: "[Achievement Name]"
|
|
||||||
description: "[Achievement Description]"
|
|
||||||
|
|
||||||
certifications:
|
|
||||||
- name: "[Certification Name]"
|
|
||||||
description: "[Certification Description]"
|
|
||||||
- name: "[Certification Name]"
|
|
||||||
description: "[Certification Description]"
|
|
||||||
|
|
||||||
languages:
|
|
||||||
- language: "[Language]"
|
|
||||||
proficiency: "[Proficiency Level]"
|
|
||||||
- language: "[Language]"
|
|
||||||
proficiency: "[Proficiency Level]"
|
|
||||||
|
|
||||||
interests:
|
|
||||||
- "[Interest]"
|
|
||||||
- "[Interest]"
|
|
||||||
- "[Interest]"
|
|
||||||
|
|
||||||
availability:
|
|
||||||
notice_period: "[Notice Period]"
|
|
||||||
|
|
||||||
salary_expectations:
|
|
||||||
salary_range_usd: "[Salary Range]"
|
|
||||||
|
|
||||||
self_identification:
|
|
||||||
gender: "[Gender]"
|
|
||||||
pronouns: "[Pronouns]"
|
|
||||||
veteran: "[Yes/No]"
|
|
||||||
disability: "[Yes/No]"
|
|
||||||
ethnicity: "[Ethnicity]"
|
|
||||||
|
|
||||||
legal_authorization:
|
|
||||||
eu_work_authorization: "[Yes/No]"
|
|
||||||
us_work_authorization: "[Yes/No]"
|
|
||||||
requires_us_visa: "[Yes/No]"
|
|
||||||
requires_us_sponsorship: "[Yes/No]"
|
|
||||||
requires_eu_visa: "[Yes/No]"
|
|
||||||
legally_allowed_to_work_in_eu: "[Yes/No]"
|
|
||||||
legally_allowed_to_work_in_us: "[Yes/No]"
|
|
||||||
requires_eu_sponsorship: "[Yes/No]"
|
|
||||||
|
|
||||||
work_preferences:
|
|
||||||
remote_work: "[Yes/No]"
|
|
||||||
in_person_work: "[Yes/No]"
|
|
||||||
open_to_relocation: "[Yes/No]"
|
|
||||||
willing_to_complete_assessments: "[Yes/No]"
|
|
||||||
willing_to_undergo_drug_tests: "[Yes/No]"
|
|
||||||
willing_to_undergo_background_checks: "[Yes/No]"
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
email: myemaillinkedin@gmail.com
|
|
||||||
password: ImpossiblePassowrd10
|
|
||||||
llm_api_key: 'sk-11KRr4uuTwpRGfeRTfj1T9BlbkFJjP8QTrswHU1yGruru2FR'
|
|
||||||
4
main.py
4
main.py
|
|
@ -9,7 +9,7 @@ from selenium.webdriver.chrome.service import Service as ChromeService
|
||||||
from webdriver_manager.chrome import ChromeDriverManager
|
from webdriver_manager.chrome import ChromeDriverManager
|
||||||
from selenium.common.exceptions import WebDriverException, TimeoutException
|
from selenium.common.exceptions import WebDriverException, TimeoutException
|
||||||
from lib_resume_builder_AIHawk import Resume,StyleManager,FacadeManager,ResumeGenerator
|
from lib_resume_builder_AIHawk import Resume,StyleManager,FacadeManager,ResumeGenerator
|
||||||
from src.utils import chromeBrowserOptions
|
from src.utils import chrome_browser_options
|
||||||
from src.gpt import GPTAnswerer
|
from src.gpt import GPTAnswerer
|
||||||
from src.linkedIn_authenticator import LinkedInAuthenticator
|
from src.linkedIn_authenticator import LinkedInAuthenticator
|
||||||
from src.linkedIn_bot_facade import LinkedInBotFacade
|
from src.linkedIn_bot_facade import LinkedInBotFacade
|
||||||
|
|
@ -149,7 +149,7 @@ class FileManager:
|
||||||
|
|
||||||
def init_browser() -> webdriver.Chrome:
|
def init_browser() -> webdriver.Chrome:
|
||||||
try:
|
try:
|
||||||
options = chromeBrowserOptions()
|
options = chrome_browser_options()
|
||||||
service = ChromeService(ChromeDriverManager().install())
|
service = ChromeService(ChromeDriverManager().install())
|
||||||
return webdriver.Chrome(service=service, options=options)
|
return webdriver.Chrome(service=service, options=options)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
94
src/gpt.py
94
src/gpt.py
|
|
@ -70,7 +70,8 @@ class AIAdapter:
|
||||||
llm_model_type = config['llm_model_type']
|
llm_model_type = config['llm_model_type']
|
||||||
llm_model = config['llm_model']
|
llm_model = config['llm_model']
|
||||||
llm_api_url = config['llm_api_url']
|
llm_api_url = config['llm_api_url']
|
||||||
print('Using {0} with {1} from {2}'.format(llm_model_type, llm_model, llm_api_url))
|
print('Using {0} with {1} from {2}'.format(
|
||||||
|
llm_model_type, llm_model, llm_api_url))
|
||||||
|
|
||||||
if llm_model_type == "openai":
|
if llm_model_type == "openai":
|
||||||
return OpenAIModel(api_key, llm_model, llm_api_url)
|
return OpenAIModel(api_key, llm_model, llm_api_url)
|
||||||
|
|
@ -79,7 +80,7 @@ class AIAdapter:
|
||||||
elif llm_model_type == "ollama":
|
elif llm_model_type == "ollama":
|
||||||
return OllamaModel(api_key, llm_model, llm_api_url)
|
return OllamaModel(api_key, llm_model, llm_api_url)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported model type: {model_type}")
|
raise ValueError(f"Unsupported model type: {llm_model_type}")
|
||||||
|
|
||||||
def invoke(self, prompt: str) -> str:
|
def invoke(self, prompt: str) -> str:
|
||||||
return self.model.invoke(prompt)
|
return self.model.invoke(prompt)
|
||||||
|
|
@ -99,7 +100,8 @@ class LLMLogger:
|
||||||
logger.debug("Parsed reply received: %s", parsed_reply)
|
logger.debug("Parsed reply received: %s", parsed_reply)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
calls_log = os.path.join(Path("data_folder/output"), "open_ai_calls.json")
|
calls_log = os.path.join(
|
||||||
|
Path("data_folder/output"), "open_ai_calls.json")
|
||||||
logger.debug("Logging path determined: %s", calls_log)
|
logger.debug("Logging path determined: %s", calls_log)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error determining the log path: %s", str(e))
|
logger.error("Error determining the log path: %s", str(e))
|
||||||
|
|
@ -118,18 +120,22 @@ class LLMLogger:
|
||||||
}
|
}
|
||||||
logger.debug("Prompts converted to dictionary: %s", prompts)
|
logger.debug("Prompts converted to dictionary: %s", prompts)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error converting prompts to dictionary: %s", str(e))
|
logger.error(
|
||||||
|
"Error converting prompts to dictionary: %s", str(e))
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
logger.debug("Prompts are of unknown type, attempting default conversion")
|
logger.debug(
|
||||||
|
"Prompts are of unknown type, attempting default conversion")
|
||||||
try:
|
try:
|
||||||
prompts = {
|
prompts = {
|
||||||
f"prompt_{i + 1}": prompt.content
|
f"prompt_{i + 1}": prompt.content
|
||||||
for i, prompt in enumerate(prompts.messages)
|
for i, prompt in enumerate(prompts.messages)
|
||||||
}
|
}
|
||||||
logger.debug("Prompts converted to dictionary using default method: %s", prompts)
|
logger.debug(
|
||||||
|
"Prompts converted to dictionary using default method: %s", prompts)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error converting prompts using default method: %s", str(e))
|
logger.error(
|
||||||
|
"Error converting prompts using default method: %s", str(e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -144,7 +150,8 @@ class LLMLogger:
|
||||||
output_tokens = token_usage["output_tokens"]
|
output_tokens = token_usage["output_tokens"]
|
||||||
input_tokens = token_usage["input_tokens"]
|
input_tokens = token_usage["input_tokens"]
|
||||||
total_tokens = token_usage["total_tokens"]
|
total_tokens = token_usage["total_tokens"]
|
||||||
logger.debug("Token usage - Input: %d, Output: %d, Total: %d", input_tokens, output_tokens, total_tokens)
|
logger.debug("Token usage - Input: %d, Output: %d, Total: %d",
|
||||||
|
input_tokens, output_tokens, total_tokens)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.error("KeyError in parsed_reply structure: %s", str(e))
|
logger.error("KeyError in parsed_reply structure: %s", str(e))
|
||||||
raise
|
raise
|
||||||
|
|
@ -159,7 +166,8 @@ class LLMLogger:
|
||||||
try:
|
try:
|
||||||
prompt_price_per_token = 0.00000015
|
prompt_price_per_token = 0.00000015
|
||||||
completion_price_per_token = 0.0000006
|
completion_price_per_token = 0.0000006
|
||||||
total_cost = (input_tokens * prompt_price_per_token) + (output_tokens * completion_price_per_token)
|
total_cost = (input_tokens * prompt_price_per_token) + \
|
||||||
|
(output_tokens * completion_price_per_token)
|
||||||
logger.debug("Total cost calculated: %f", total_cost)
|
logger.debug("Total cost calculated: %f", total_cost)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error calculating total cost: %s", str(e))
|
logger.error("Error calculating total cost: %s", str(e))
|
||||||
|
|
@ -178,12 +186,14 @@ class LLMLogger:
|
||||||
}
|
}
|
||||||
logger.debug("Log entry created: %s", log_entry)
|
logger.debug("Log entry created: %s", log_entry)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.error("Error creating log entry: missing key %s in parsed_reply", str(e))
|
logger.error(
|
||||||
|
"Error creating log entry: missing key %s in parsed_reply", str(e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(calls_log, "a", encoding="utf-8") as f:
|
with open(calls_log, "a", encoding="utf-8") as f:
|
||||||
json_string = json.dumps(log_entry, ensure_ascii=False, indent=4)
|
json_string = json.dumps(
|
||||||
|
log_entry, ensure_ascii=False, indent=4)
|
||||||
f.write(json_string + "\n")
|
f.write(json_string + "\n")
|
||||||
logger.debug("Log entry written to file: %s", calls_log)
|
logger.debug("Log entry written to file: %s", calls_log)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -194,23 +204,24 @@ class LLMLogger:
|
||||||
class LoggerChatModel:
|
class LoggerChatModel:
|
||||||
|
|
||||||
def __init__(self, llm: Union[OpenAIModel, OllamaModel, ClaudeModel]):
|
def __init__(self, llm: Union[OpenAIModel, OllamaModel, ClaudeModel]):
|
||||||
|
|
||||||
self.llm = llm
|
self.llm = llm
|
||||||
logger.debug("LoggerChatModel successfully initialized with LLM: %s", llm)
|
logger.debug(
|
||||||
|
"LoggerChatModel successfully initialized with LLM: %s", llm)
|
||||||
|
|
||||||
def __call__(self, messages: List[Dict[str, str]]) -> str:
|
def __call__(self, messages: List[Dict[str, str]]) -> str:
|
||||||
|
|
||||||
logger.debug("Entering __call__ method with messages: %s", messages)
|
logger.debug("Entering __call__ method with messages: %s", messages)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
logger.debug("Attempting to call the LLM with messages")
|
logger.debug("Attempting to call the LLM with messages")
|
||||||
reply = self.llm(messages)
|
# Ensure you're using invoke to call the model
|
||||||
|
reply = self.llm.invoke(messages)
|
||||||
logger.debug("LLM response received: %s", reply)
|
logger.debug("LLM response received: %s", reply)
|
||||||
|
|
||||||
parsed_reply = self.parse_llmresult(reply)
|
parsed_reply = self.parse_llmresult(reply)
|
||||||
logger.debug("Parsed LLM reply: %s", parsed_reply)
|
logger.debug("Parsed LLM reply: %s", parsed_reply)
|
||||||
|
|
||||||
LLMLogger.log_request(prompts=messages, parsed_reply=parsed_reply)
|
LLMLogger.log_request(
|
||||||
|
prompts=messages, parsed_reply=parsed_reply)
|
||||||
logger.debug("Request successfully logged")
|
logger.debug("Request successfully logged")
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
|
|
@ -246,7 +257,8 @@ class LoggerChatModel:
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Unexpected error occurred: %s", str(e))
|
logger.error("Unexpected error occurred: %s", str(e))
|
||||||
logger.info("Waiting for 30 seconds before retrying due to an unexpected error.")
|
logger.info(
|
||||||
|
"Waiting for 30 seconds before retrying due to an unexpected error.")
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -279,11 +291,13 @@ class LoggerChatModel:
|
||||||
return parsed_result
|
return parsed_result
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.error("KeyError while parsing LLM result: missing key %s", str(e))
|
logger.error(
|
||||||
|
"KeyError while parsing LLM result: missing key %s", str(e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Unexpected error while parsing LLM result: %s", str(e))
|
logger.error(
|
||||||
|
"Unexpected error while parsing LLM result: %s", str(e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -299,7 +313,8 @@ class GPTAnswerer:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_best_match(text: str, options: list[str]) -> str:
|
def find_best_match(text: str, options: list[str]) -> str:
|
||||||
logger.debug("Finding best match for text: '%s' in options: %s", text, options)
|
logger.debug(
|
||||||
|
"Finding best match for text: '%s' in options: %s", text, options)
|
||||||
distances = [
|
distances = [
|
||||||
(option, distance(text.lower(), option.lower())) for option in options
|
(option, distance(text.lower(), option.lower())) for option in options
|
||||||
]
|
]
|
||||||
|
|
@ -325,10 +340,12 @@ class GPTAnswerer:
|
||||||
def set_job(self, job):
|
def set_job(self, job):
|
||||||
logger.debug("Setting job: %s", job)
|
logger.debug("Setting job: %s", job)
|
||||||
self.job = job
|
self.job = job
|
||||||
self.job.set_summarize_job_description(self.summarize_job_description(self.job.description))
|
self.job.set_summarize_job_description(
|
||||||
|
self.summarize_job_description(self.job.description))
|
||||||
|
|
||||||
def set_job_application_profile(self, job_application_profile):
|
def set_job_application_profile(self, job_application_profile):
|
||||||
logger.debug("Setting job application profile: %s", job_application_profile)
|
logger.debug("Setting job application profile: %s",
|
||||||
|
job_application_profile)
|
||||||
self.job_application_profile = job_application_profile
|
self.job_application_profile = job_application_profile
|
||||||
|
|
||||||
def summarize_job_description(self, text: str) -> str:
|
def summarize_job_description(self, text: str) -> str:
|
||||||
|
|
@ -336,7 +353,8 @@ class GPTAnswerer:
|
||||||
strings.summarize_prompt_template = self._preprocess_template_string(
|
strings.summarize_prompt_template = self._preprocess_template_string(
|
||||||
strings.summarize_prompt_template
|
strings.summarize_prompt_template
|
||||||
)
|
)
|
||||||
prompt = ChatPromptTemplate.from_template(strings.summarize_prompt_template)
|
prompt = ChatPromptTemplate.from_template(
|
||||||
|
strings.summarize_prompt_template)
|
||||||
chain = prompt | self.llm_cheap | StrOutputParser()
|
chain = prompt | self.llm_cheap | StrOutputParser()
|
||||||
output = chain.invoke({"text": text})
|
output = chain.invoke({"text": text})
|
||||||
logger.debug("Summary generated: %s", output)
|
logger.debug("Summary generated: %s", output)
|
||||||
|
|
@ -460,31 +478,37 @@ class GPTAnswerer:
|
||||||
r"(Personal information|Self Identification|Legal Authorization|Work Preferences|Education Details|Experience Details|Projects|Availability|Salary Expectations|Certifications|Languages|Interests|Cover letter)",
|
r"(Personal information|Self Identification|Legal Authorization|Work Preferences|Education Details|Experience Details|Projects|Availability|Salary Expectations|Certifications|Languages|Interests|Cover letter)",
|
||||||
output, re.IGNORECASE)
|
output, re.IGNORECASE)
|
||||||
if not match:
|
if not match:
|
||||||
raise ValueError("Could not extract section name from the response.")
|
raise ValueError(
|
||||||
|
"Could not extract section name from the response.")
|
||||||
|
|
||||||
section_name = match.group(1).lower().replace(" ", "_")
|
section_name = match.group(1).lower().replace(" ", "_")
|
||||||
|
|
||||||
if section_name == "cover_letter":
|
if section_name == "cover_letter":
|
||||||
chain = chains.get(section_name)
|
chain = chains.get(section_name)
|
||||||
output = chain.invoke({"resume": self.resume, "job_description": self.job_description})
|
output = chain.invoke(
|
||||||
|
{"resume": self.resume, "job_description": self.job_description})
|
||||||
logger.debug("Cover letter generated: %s", output)
|
logger.debug("Cover letter generated: %s", output)
|
||||||
return output
|
return output
|
||||||
resume_section = getattr(self.resume, section_name, None) or getattr(self.job_application_profile, section_name,
|
resume_section = getattr(self.resume, section_name, None) or getattr(self.job_application_profile, section_name,
|
||||||
None)
|
None)
|
||||||
if resume_section is None:
|
if resume_section is None:
|
||||||
logger.error("Section '%s' not found in either resume or job_application_profile.", section_name)
|
logger.error(
|
||||||
raise ValueError(f"Section '{section_name}' not found in either resume or job_application_profile.")
|
"Section '%s' not found in either resume or job_application_profile.", section_name)
|
||||||
|
raise ValueError(f"Section '{
|
||||||
|
section_name}' not found in either resume or job_application_profile.")
|
||||||
chain = chains.get(section_name)
|
chain = chains.get(section_name)
|
||||||
if chain is None:
|
if chain is None:
|
||||||
logger.error("Chain not defined for section '%s'", section_name)
|
logger.error("Chain not defined for section '%s'", section_name)
|
||||||
raise ValueError(f"Chain not defined for section '{section_name}'")
|
raise ValueError(f"Chain not defined for section '{section_name}'")
|
||||||
output = chain.invoke({"resume_section": resume_section, "question": question})
|
output = chain.invoke(
|
||||||
|
{"resume_section": resume_section, "question": question})
|
||||||
logger.debug("Question answered: %s", output)
|
logger.debug("Question answered: %s", output)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def answer_question_numeric(self, question: str, default_experience: int = 3) -> int:
|
def answer_question_numeric(self, question: str, default_experience: int = 3) -> int:
|
||||||
logger.debug("Answering numeric question: %s", question)
|
logger.debug("Answering numeric question: %s", question)
|
||||||
func_template = self._preprocess_template_string(strings.numeric_question_template)
|
func_template = self._preprocess_template_string(
|
||||||
|
strings.numeric_question_template)
|
||||||
prompt = ChatPromptTemplate.from_template(func_template)
|
prompt = ChatPromptTemplate.from_template(func_template)
|
||||||
chain = prompt | self.llm_cheap | StrOutputParser()
|
chain = prompt | self.llm_cheap | StrOutputParser()
|
||||||
output_str = chain.invoke(
|
output_str = chain.invoke(
|
||||||
|
|
@ -495,7 +519,8 @@ class GPTAnswerer:
|
||||||
output = self.extract_number_from_string(output_str)
|
output = self.extract_number_from_string(output_str)
|
||||||
logger.debug("Extracted number: %d", output)
|
logger.debug("Extracted number: %d", output)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.warning("Failed to extract number, using default experience: %d", default_experience)
|
logger.warning(
|
||||||
|
"Failed to extract number, using default experience: %d", default_experience)
|
||||||
output = default_experience
|
output = default_experience
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -511,17 +536,20 @@ class GPTAnswerer:
|
||||||
|
|
||||||
def answer_question_from_options(self, question: str, options: list[str]) -> str:
|
def answer_question_from_options(self, question: str, options: list[str]) -> str:
|
||||||
logger.debug("Answering question from options: %s", question)
|
logger.debug("Answering question from options: %s", question)
|
||||||
func_template = self._preprocess_template_string(strings.options_template)
|
func_template = self._preprocess_template_string(
|
||||||
|
strings.options_template)
|
||||||
prompt = ChatPromptTemplate.from_template(func_template)
|
prompt = ChatPromptTemplate.from_template(func_template)
|
||||||
chain = prompt | self.llm_cheap | StrOutputParser()
|
chain = prompt | self.llm_cheap | StrOutputParser()
|
||||||
output_str = chain.invoke({"resume": self.resume, "question": question, "options": options})
|
output_str = chain.invoke(
|
||||||
|
{"resume": self.resume, "question": question, "options": options})
|
||||||
logger.debug("Raw output for options question: %s", output_str)
|
logger.debug("Raw output for options question: %s", output_str)
|
||||||
best_option = self.find_best_match(output_str, options)
|
best_option = self.find_best_match(output_str, options)
|
||||||
logger.debug("Best option determined: %s", best_option)
|
logger.debug("Best option determined: %s", best_option)
|
||||||
return best_option
|
return best_option
|
||||||
|
|
||||||
def resume_or_cover(self, phrase: str) -> str:
|
def resume_or_cover(self, phrase: str) -> str:
|
||||||
logger.debug("Determining if phrase refers to resume or cover letter: %s", phrase)
|
logger.debug(
|
||||||
|
"Determining if phrase refers to resume or cover letter: %s", phrase)
|
||||||
prompt_template = """
|
prompt_template = """
|
||||||
Given the following phrase, respond with only 'resume' if the phrase is about a resume, or 'cover' if it's about a cover letter.
|
Given the following phrase, respond with only 'resume' if the phrase is about a resume, or 'cover' if it's about a cover letter.
|
||||||
If the phrase contains only one word 'upload', consider it as 'cover'.
|
If the phrase contains only one word 'upload', consider it as 'cover'.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue