cxumol commited on
Commit
73906d6
1 Parent(s): 1b4d3b3

fix: bugs around debug mode

Browse files
Files changed (6) hide show
  1. .gitignore +3 -2
  2. app.py +8 -6
  3. config.py +1 -1
  4. taskAI.py +10 -7
  5. taskNonAI.py +8 -0
  6. util.py +4 -4
.gitignore CHANGED
@@ -1,7 +1,8 @@
1
  *_secret.py
2
  *_secret.py*
3
- **test_result.pdf
4
- **letter.typ
 
5
 
6
  .local/
7
  .ruff_cache/
 
1
  *_secret.py
2
  *_secret.py*
3
+ **/test_result.pdf
4
+ **/letter.typ
5
+ **/cover_letter_*.typ
6
 
7
  .local/
8
  .ruff_cache/
app.py CHANGED
@@ -6,6 +6,7 @@ from util import mylogger
6
  from util import stream_together
7
  from util import checkAPI
8
  from taskNonAI import extract_url, file_to_html, compile_pdf
 
9
 
10
  ## load data
11
  from _data_test import mock_jd, mock_cv
@@ -32,7 +33,7 @@ def init():
32
  ## Config Functions
33
 
34
 
35
- def set_same_cheap_strong(set_same: bool, cheap_base, cheap_key):
36
  # setup_zone = gr.Accordion("AI setup (OpenAI-compatible LLM API)", open=True)
37
  if set_same:
38
  return (
@@ -92,9 +93,9 @@ def run_refine(api_base, api_key, api_model, jd_info, cv_text):
92
  yield result
93
 
94
 
95
- def run_compose(api_base, api_key, api_model, min_jd, min_cv):
96
  strongAPI = {"base": api_base, "key": api_key, "model": api_model}
97
- taskAI = TaskAI(strongAPI, temperature=0.6, max_tokens=4000)
98
  info("Composing letter with CoT ...")
99
  result = ""
100
  for response in taskAI.compose_letter_CoT(jd=min_jd, resume=min_cv):
@@ -112,7 +113,7 @@ def finalize_letter_txt(api_base, api_key, api_model, debug_CoT):
112
  yield result
113
 
114
 
115
- def finalize_letter_pdf(api_base, api_key, api_model, jd, cv, cover_letter_text):
116
  cheapAPI = {"base": api_base, "key": api_key, "model": api_model}
117
  taskAI = TaskAI(cheapAPI, temperature=0.1, max_tokens=100)
118
  meta_data = next(taskAI.get_jobapp_meta(JD=jd, CV=cv))
@@ -122,12 +123,13 @@ def finalize_letter_pdf(api_base, api_key, api_model, jd, cv, cover_letter_text)
122
  pdf_context,
123
  tmpl_path="typst/template_letter.tmpl",
124
  output_path=f"/tmp/cover_letter_by_{pdf_context['applicantFullName']}_to_{pdf_context['companyFullName']}.pdf",
 
125
  )
126
 
127
 
128
  with gr.Blocks(
129
  title=DEMO_TITLE,
130
- theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky", neutral_hue="slate"),
131
  ) as app:
132
  intro = f"""# {DEMO_TITLE}
133
  > You provide job description and résumé. I write Cover letter for you!
@@ -226,7 +228,7 @@ with gr.Blocks(
226
  outputs=[expert_zone, reformat_zone],
227
  ).success(
228
  fn=run_compose,
229
- inputs=[strong_base, strong_key, strong_model, min_jd, min_cv],
230
  outputs=[debug_CoT],
231
  ).success(
232
  fn=lambda: gr.Accordion("Expert Zone", open=False),
 
6
  from util import stream_together
7
  from util import checkAPI
8
  from taskNonAI import extract_url, file_to_html, compile_pdf
9
+ from taskAI import TaskAI
10
 
11
  ## load data
12
  from _data_test import mock_jd, mock_cv
 
33
  ## Config Functions
34
 
35
 
36
+ def set_same_cheap_strong(set_same: bool, cheap_base, cheap_key, cheap_model):
37
  # setup_zone = gr.Accordion("AI setup (OpenAI-compatible LLM API)", open=True)
38
  if set_same:
39
  return (
 
93
  yield result
94
 
95
 
96
+ def run_compose(api_base, api_key, api_model, min_jd, min_cv, is_debug):
97
  strongAPI = {"base": api_base, "key": api_key, "model": api_model}
98
+ taskAI = TaskAI(strongAPI, is_debug=is_debug, temperature=0.6, max_tokens=4000)
99
  info("Composing letter with CoT ...")
100
  result = ""
101
  for response in taskAI.compose_letter_CoT(jd=min_jd, resume=min_cv):
 
113
  yield result
114
 
115
 
116
+ def finalize_letter_pdf(api_base, api_key, api_model, jd, cv, cover_letter_text, is_debug):
117
  cheapAPI = {"base": api_base, "key": api_key, "model": api_model}
118
  taskAI = TaskAI(cheapAPI, temperature=0.1, max_tokens=100)
119
  meta_data = next(taskAI.get_jobapp_meta(JD=jd, CV=cv))
 
123
  pdf_context,
124
  tmpl_path="typst/template_letter.tmpl",
125
  output_path=f"/tmp/cover_letter_by_{pdf_context['applicantFullName']}_to_{pdf_context['companyFullName']}.pdf",
126
+ is_debug=is_debug,
127
  )
128
 
129
 
130
  with gr.Blocks(
131
  title=DEMO_TITLE,
132
+ theme=gr.themes.Soft(primary_hue="sky", secondary_hue="emerald", neutral_hue="stone"),
133
  ) as app:
134
  intro = f"""# {DEMO_TITLE}
135
  > You provide job description and résumé. I write Cover letter for you!
 
228
  outputs=[expert_zone, reformat_zone],
229
  ).success(
230
  fn=run_compose,
231
+ inputs=[strong_base, strong_key, strong_model, min_jd, min_cv, is_debug],
232
  outputs=[debug_CoT],
233
  ).success(
234
  fn=lambda: gr.Accordion("Expert Zone", open=False),
config.py CHANGED
@@ -14,7 +14,7 @@ STRONG_MODEL = os.getenv("STRONG_MODEL") or "gpt-4"
14
  IS_SHARE = bool(os.getenv("IS_SHARE")) or False
15
  IS_DEBUG = bool(os.getenv("IS_DEBUG")) or False
16
 
17
- DEMO_TITLE = "Cover Letter Generator"
18
  DEMO_DESCRIPTION = "This is a demo of the OpenAI API for generating cover letters. The model is trained on a dataset of cover letters and job descriptions, and generates a cover letter based on the job description and the applicant's CV. The model is fine-tuned on the OpenAI API, and is able to generate cover letters that are tailored to the job description and the applicant's CV. The model is able to generate cover letters for a wide range of jobs, and is able to generate cover letters that are tailored to the job description and the applicant's CV. The model is able to generate cover letters for a wide range of jobs, and is able to generate cover letters that are tailored to the job description and the applicant's CV. The model is able to generate cover letters for a wide range of jobs, and is able to generate cover letters that are tailored to the job description and the applicant's CV."
19
 
20
  CV_EXT = [".typ", ".tex", ".html", ".docx", ".rst", ".rtf", ".odt", ".txt", ".md"]
 
14
  IS_SHARE = bool(os.getenv("IS_SHARE")) or False
15
  IS_DEBUG = bool(os.getenv("IS_DEBUG")) or False
16
 
17
+ DEMO_TITLE = "CoverPilot: AI-Powered Cover Letter Generator"
18
  DEMO_DESCRIPTION = "This is a demo of the OpenAI API for generating cover letters. The model is trained on a dataset of cover letters and job descriptions, and generates a cover letter based on the job description and the applicant's CV. The model is fine-tuned on the OpenAI API, and is able to generate cover letters that are tailored to the job description and the applicant's CV. The model is able to generate cover letters for a wide range of jobs, and is able to generate cover letters that are tailored to the job description and the applicant's CV. The model is able to generate cover letters for a wide range of jobs, and is able to generate cover letters that are tailored to the job description and the applicant's CV. The model is able to generate cover letters for a wide range of jobs, and is able to generate cover letters that are tailored to the job description and the applicant's CV."
19
 
20
  CV_EXT = [".typ", ".tex", ".html", ".docx", ".rst", ".rtf", ".odt", ".txt", ".md"]
taskAI.py CHANGED
@@ -37,7 +37,7 @@ JSON_API = ChatPromptTemplate(
37
  [
38
  ChatMessage(
39
  role="system",
40
- content="You are a JSON API. Your mission is to convert user input into a JSON object exactly in this template: {template}",
41
  ),
42
  ChatMessage(role="user", content="{content}"),
43
  ]
@@ -65,7 +65,8 @@ Before officially write the letter, think step by step. First, list what makes a
65
 
66
  ## tasks
67
  class TaskAI(OpenAILike):
68
- def __init__(self, api: dict[str, str], is_debug: bool, **kwargs):
 
69
  log = logger.info
70
 
71
  def guess_window_size(model=api["model"]):
@@ -82,8 +83,8 @@ class TaskAI(OpenAILike):
82
  log(f"use context window size: {window_size} for {model}")
83
  return window_size
84
 
85
- checkAPI(api_base, api_key)
86
- self.is_debug = is_debug
87
 
88
  super().__init__(
89
  api_base=api["base"],
@@ -93,6 +94,7 @@ class TaskAI(OpenAILike):
93
  context_window=guess_window_size(),
94
  **kwargs,
95
  )
 
96
 
97
  def _debug_print_msg(self, msg):
98
  if not self.is_debug:
@@ -114,7 +116,7 @@ class TaskAI(OpenAILike):
114
 
115
  def compose_letter_CoT(self, resume: str, jd: str):
116
  msg = LETTER_COMPOSE.format_messages(resume=resume, jd=jd)
117
- _debug_print_msg(msg)
118
  return self.stream_chat(msg)
119
 
120
  def get_jobapp_meta(self, JD, CV):
@@ -136,7 +138,8 @@ class TaskAI(OpenAILike):
136
  try:
137
  meta_JD = json.loads(meta_JD.strip())
138
  meta_CV = json.loads(meta_CV.strip())
139
- except:
 
140
  raise ValueError(
141
  f"AI didn't return a valid JSON string. Try again or consider a better model for CheapAI. \n{meta_JD}\n{meta_CV}"
142
  )
@@ -148,7 +151,7 @@ class TaskAI(OpenAILike):
148
  def purify_letter(self, full_text):
149
  return self.stream_chat(
150
  EXTRACT_INFO.format_messages(
151
- to_extract="the cover letter section starting from 'Dear Hiring Manager' or similar to 'Sincerely,' or similar ",
152
  input=full_text,
153
  )
154
  )
 
37
  [
38
  ChatMessage(
39
  role="system",
40
+ content="You are a JSON API. Your mission is to convert user input into a valid and complete JSON object STRICTLY in this template: {template}. The output should be completely a plain json without nested structure. Never summerize, paraphrase or do anything else, just extract the information from the input and fill in the template.",
41
  ),
42
  ChatMessage(role="user", content="{content}"),
43
  ]
 
65
 
66
  ## tasks
67
  class TaskAI(OpenAILike):
68
+ is_debug = False
69
+ def __init__(self, api: dict[str, str], is_debug=False, **kwargs):
70
  log = logger.info
71
 
72
  def guess_window_size(model=api["model"]):
 
83
  log(f"use context window size: {window_size} for {model}")
84
  return window_size
85
 
86
+ checkAPI(api_base=api["base"], api_key=api["key"])
87
+
88
 
89
  super().__init__(
90
  api_base=api["base"],
 
94
  context_window=guess_window_size(),
95
  **kwargs,
96
  )
97
+ self.is_debug = is_debug
98
 
99
  def _debug_print_msg(self, msg):
100
  if not self.is_debug:
 
116
 
117
  def compose_letter_CoT(self, resume: str, jd: str):
118
  msg = LETTER_COMPOSE.format_messages(resume=resume, jd=jd)
119
+ self._debug_print_msg(msg)
120
  return self.stream_chat(msg)
121
 
122
  def get_jobapp_meta(self, JD, CV):
 
138
  try:
139
  meta_JD = json.loads(meta_JD.strip())
140
  meta_CV = json.loads(meta_CV.strip())
141
+ except Exception as e:
142
+ print(e)
143
  raise ValueError(
144
  f"AI didn't return a valid JSON string. Try again or consider a better model for CheapAI. \n{meta_JD}\n{meta_CV}"
145
  )
 
151
  def purify_letter(self, full_text):
152
  return self.stream_chat(
153
  EXTRACT_INFO.format_messages(
154
+ to_extract="the cover letter section starting from 'Dear Hiring Manager' or similar to 'Sincerely,' or similar",
155
  input=full_text,
156
  )
157
  )
taskNonAI.py CHANGED
@@ -52,6 +52,12 @@ def _date() -> str:
52
  def _typst_escape(s) -> str:
53
  return str(s).replace("@", "\@").replace("#", "\#")
54
 
 
 
 
 
 
 
55
 
56
  def compile_pdf(
57
  context: dict, tmpl_path: str, output_path="/tmp/cover_letter.pdf", is_debug=False
@@ -62,6 +68,8 @@ def compile_pdf(
62
  tmpl = Template(f.read())
63
  context = {k: _typst_escape(v) for k, v in context.items()}
64
  context.update({"date_string": _date()})
 
 
65
  letter_typ = tmpl.safe_substitute(context)
66
  with open(letter_src_filepath, "w", encoding="utf8") as f:
67
  f.write(letter_typ)
 
52
  def _typst_escape(s) -> str:
53
  return str(s).replace("@", "\@").replace("#", "\#")
54
 
55
+ def _ensure_no_signature_in_body(cover_letter_body: str) -> str:
56
+ if not cover_letter_body.strip().endswith(","):
57
+ # remove last line
58
+ cover_letter_body = "\n".join(cover_letter_body.split("\n")[:-1])
59
+ print(cover_letter_body)
60
+ return cover_letter_body
61
 
62
  def compile_pdf(
63
  context: dict, tmpl_path: str, output_path="/tmp/cover_letter.pdf", is_debug=False
 
68
  tmpl = Template(f.read())
69
  context = {k: _typst_escape(v) for k, v in context.items()}
70
  context.update({"date_string": _date()})
71
+ context["letter_body"]=_ensure_no_signature_in_body(context["letter_body"])
72
+
73
  letter_typ = tmpl.safe_substitute(context)
74
  with open(letter_src_filepath, "w", encoding="utf8") as f:
75
  f.write(letter_typ)
util.py CHANGED
@@ -36,16 +36,16 @@ def is_valid_url(url: str) -> bool:
36
 
37
  def is_valid_openai_api_key(api_base: str, api_key: str) -> bool:
38
  headers = {"Authorization": f"Bearer {api_key}"}
39
-
40
- response = requests.get(api_base, headers=headers)
41
-
42
  return response.status_code == 200
43
 
44
 
45
  def checkAPI(api_base: str, api_key: str):
46
  if not is_valid_openai_api_key(api_base, api_key):
47
  raise ValueError(
48
- "Invalid API key or less possibly OpenAI's (or AI provider's) fault. Did you setup your AI APIs properly? If you don't have any API key, try get one from https://beta.openai.com/account/api-keys"
49
  )
50
 
51
 
 
36
 
37
  def is_valid_openai_api_key(api_base: str, api_key: str) -> bool:
38
  headers = {"Authorization": f"Bearer {api_key}"}
39
+ test_url = f"{api_base}/models"
40
+ response = requests.get(test_url, headers=headers)
41
+ print(response.json())
42
  return response.status_code == 200
43
 
44
 
45
  def checkAPI(api_base: str, api_key: str):
46
  if not is_valid_openai_api_key(api_base, api_key):
47
  raise ValueError(
48
+ "API not available. Maybe it's OpenAI's (or AI provider's) fault, or you setup your AI APIs icorrectly. If you don't have any API key, try get one from https://beta.openai.com/account/api-keys"
49
  )
50
 
51