cxumol commited on
Commit
32e427d
1 Parent(s): 74164b2

improve prompts, templates, debugs, etc.

Browse files
Files changed (6) hide show
  1. app.py +45 -38
  2. config.py +2 -2
  3. data_test.py +4 -4
  4. taskAI.py +7 -7
  5. taskNonAI.py +4 -2
  6. template_letter.tmpl +4 -4
app.py CHANGED
@@ -49,25 +49,26 @@ def run_refine(api_base, api_key, api_model, jd_info, cv_text):
49
  cheapAPI = {"base": api_base, "key": api_key, "model": api_model}
50
  taskAI = TaskAI(cheapAPI, temperature=0.2, max_tokens=2048) # max_tokens=2048
51
  info("API initialized")
52
- gen = (
53
  taskAI.jd_preprocess(input=jd),
54
  taskAI.cv_preprocess(input=cv),
55
  )
56
- info("tasks initialized")
57
- result = [""] * 2
58
- while 1:
59
- stop: bool = True
60
- for i in range(len(gen)):
61
- try:
62
- result[i] += next(gen[i]).delta
63
- stop = False
64
- except StopIteration:
65
- # info(f"gen[{i}] exhausted")
66
- pass
 
 
 
 
67
  yield result
68
- if stop:
69
- info("tasks done")
70
- break
71
 
72
  def run_compose(api_base, api_key, api_model, min_jd, min_cv):
73
  strongAPI = {"base": api_base, "key": api_key, "model": api_model}
@@ -78,28 +79,34 @@ def run_compose(api_base, api_key, api_model, min_jd, min_cv):
78
  result += response.delta
79
  yield result
80
 
81
- def finalize_letter_txt(api_base, api_key, api_model, debug_CoT, jd, cv):
82
  cheapAPI = {"base": api_base, "key": api_key, "model": api_model}
83
  taskAI = TaskAI(cheapAPI, temperature=0.2, max_tokens=2048)
84
  info("Finalizing letter ...")
85
- gen = stream_together(
86
- taskAI.purify_letter(full_text=debug_CoT),
87
- taskAI.get_jobapp_meta(JD=jd, CV=cv),
88
- )
89
- for result in gen:
90
  yield result
 
 
 
 
 
91
 
92
- def finalize_letter_pdf(meta_data, cover_letter_text):
 
 
 
93
  pdf_context = json.loads(meta_data)
94
  pdf_context["letter_body"] = cover_letter_text
95
- return compile_pdf(pdf_context,tmpl_path="template_letter.tmpl",output_path=f"/tmp/cover_letter_{pdf_context['applicant_full_name']}_to_{pdf_context['company_full_name']}.pdf")
96
 
97
  with gr.Blocks(
98
  title=DEMO_TITLE,
99
  theme=gr.themes.Base(primary_hue="blue", secondary_hue="sky", neutral_hue="slate"),
100
  ) as app:
101
  intro = f"""# {DEMO_TITLE}
102
- > You provide job description and résumé. I write Cover letter for you!
103
  Before you use, please fisrt setup API for 2 AI agents': Cheap AI and Strong AI.
104
  """
105
  gr.Markdown(intro)
@@ -114,7 +121,7 @@ with gr.Blocks(
114
  cheap_base = gr.Textbox(
115
  value=CHEAP_API_BASE, label="API BASE"
116
  )
117
- cheap_key = gr.Textbox(value=CHEAP_API_KEY, label="API key")
118
  cheap_model = gr.Textbox(value=CHEAP_MODEL, label="Model ID")
119
  gr.Markdown(
120
  "---\n**Strong AI**, a thoughtful wordsmith, generates perfect cover letters to make both you and recruiters happy."
@@ -137,17 +144,17 @@ with gr.Blocks(
137
  )
138
  with gr.Group():
139
  gr.Markdown("## Applicant - CV / Résumé")
140
- with gr.Row():
141
- cv_file = gr.File(
142
- label="Allowed formats: " + " ".join(CV_EXT),
143
- file_count="single",
144
- file_types=CV_EXT,
145
- type="filepath",
146
- )
147
- cv_text = gr.TextArea(
148
- label="Or enter text",
149
- placeholder="If attempting to both upload a file and enter text, only this text will be used.",
150
- )
151
  with gr.Column(scale=2):
152
  gr.Markdown("## Result")
153
  with gr.Accordion("Reformatting", open=True) as reformat_zone:
@@ -179,8 +186,8 @@ with gr.Blocks(
179
  ).then(fn=lambda:[gr.Accordion("Expert Zone", open=True),gr.Accordion("Reformatting", open=False)],inputs=None, outputs=[expert_zone, reformat_zone]
180
  ).then(fn=run_compose, inputs=[strong_base, strong_key, strong_model, min_jd, min_cv], outputs=[debug_CoT]
181
  ).then(fn=lambda:gr.Accordion("Expert Zone", open=False),inputs=None, outputs=[expert_zone]
182
- ).then(fn=finalize_letter_txt, inputs=[cheap_base, cheap_key, cheap_model, debug_CoT, jd_info, cv_text], outputs=[cover_letter_text, debug_jobapp]
183
- ).then(fn=finalize_letter_pdf, inputs=[debug_jobapp, cover_letter_text], outputs=[cover_letter_pdf])
184
 
185
 
186
  if __name__ == "__main__":
 
49
  cheapAPI = {"base": api_base, "key": api_key, "model": api_model}
50
  taskAI = TaskAI(cheapAPI, temperature=0.2, max_tokens=2048) # max_tokens=2048
51
  info("API initialized")
52
+ gen = stream_together(
53
  taskAI.jd_preprocess(input=jd),
54
  taskAI.cv_preprocess(input=cv),
55
  )
56
+ # result = [""] * 2
57
+ # while 1:
58
+ # stop: bool = True
59
+ # for i in range(len(gen)):
60
+ # try:
61
+ # result[i] += next(gen[i]).delta
62
+ # stop = False
63
+ # except StopIteration:
64
+ # # info(f"gen[{i}] exhausted")
65
+ # pass
66
+ # yield result
67
+ # if stop:
68
+ # info("tasks done")
69
+ # break
70
+ for result in gen:
71
  yield result
 
 
 
72
 
73
  def run_compose(api_base, api_key, api_model, min_jd, min_cv):
74
  strongAPI = {"base": api_base, "key": api_key, "model": api_model}
 
79
  result += response.delta
80
  yield result
81
 
82
+ def finalize_letter_txt(api_base, api_key, api_model, debug_CoT):
83
  cheapAPI = {"base": api_base, "key": api_key, "model": api_model}
84
  taskAI = TaskAI(cheapAPI, temperature=0.2, max_tokens=2048)
85
  info("Finalizing letter ...")
86
+ result=""
87
+ for response in taskAI.purify_letter(full_text=debug_CoT):
88
+ result += response.delta
 
 
89
  yield result
90
+ # gen = stream_together(
91
+ # taskAI.purify_letter(full_text=debug_CoT),
92
+ # )
93
+ # for result in gen:
94
+ # yield result
95
 
96
+ def finalize_letter_pdf(api_base, api_key, api_model, jd, cv, cover_letter_text):
97
+ cheapAPI = {"base": api_base, "key": api_key, "model": api_model}
98
+ taskAI = TaskAI(cheapAPI, temperature=0.1, max_tokens=100)
99
+ meta_data = next(taskAI.get_jobapp_meta(JD=jd, CV=cv))
100
  pdf_context = json.loads(meta_data)
101
  pdf_context["letter_body"] = cover_letter_text
102
+ return meta_data, compile_pdf(pdf_context,tmpl_path="template_letter.tmpl",output_path=f"/tmp/cover_letter_by_{pdf_context['applicantFullName']}_to_{pdf_context['companyFullName']}.pdf")
103
 
104
  with gr.Blocks(
105
  title=DEMO_TITLE,
106
  theme=gr.themes.Base(primary_hue="blue", secondary_hue="sky", neutral_hue="slate"),
107
  ) as app:
108
  intro = f"""# {DEMO_TITLE}
109
+ > You provide job description and résumé. I write Cover letter for you!
110
  Before you use, please fisrt setup API for 2 AI agents': Cheap AI and Strong AI.
111
  """
112
  gr.Markdown(intro)
 
121
  cheap_base = gr.Textbox(
122
  value=CHEAP_API_BASE, label="API BASE"
123
  )
124
+ cheap_key = gr.Textbox(value=CHEAP_API_KEY, label="API key", type="password")
125
  cheap_model = gr.Textbox(value=CHEAP_MODEL, label="Model ID")
126
  gr.Markdown(
127
  "---\n**Strong AI**, a thoughtful wordsmith, generates perfect cover letters to make both you and recruiters happy."
 
144
  )
145
  with gr.Group():
146
  gr.Markdown("## Applicant - CV / Résumé")
147
+ # with gr.Row():
148
+ cv_file = gr.File(
149
+ label="Allowed formats: " + " ".join(CV_EXT),
150
+ file_count="single",
151
+ file_types=CV_EXT,
152
+ type="filepath",
153
+ )
154
+ cv_text = gr.TextArea(
155
+ label="Or enter text",
156
+ placeholder="If attempting to both upload a file and enter text, only this text will be used.",
157
+ )
158
  with gr.Column(scale=2):
159
  gr.Markdown("## Result")
160
  with gr.Accordion("Reformatting", open=True) as reformat_zone:
 
186
  ).then(fn=lambda:[gr.Accordion("Expert Zone", open=True),gr.Accordion("Reformatting", open=False)],inputs=None, outputs=[expert_zone, reformat_zone]
187
  ).then(fn=run_compose, inputs=[strong_base, strong_key, strong_model, min_jd, min_cv], outputs=[debug_CoT]
188
  ).then(fn=lambda:gr.Accordion("Expert Zone", open=False),inputs=None, outputs=[expert_zone]
189
+ ).then(fn=finalize_letter_txt, inputs=[cheap_base, cheap_key, cheap_model, debug_CoT], outputs=[cover_letter_text]
190
+ ).then(fn=finalize_letter_pdf, inputs=[cheap_base, cheap_key, cheap_model, jd_info, cv_text, cover_letter_text], outputs=[debug_jobapp, cover_letter_pdf])
191
 
192
 
193
  if __name__ == "__main__":
config.py CHANGED
@@ -5,11 +5,11 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or ""
5
 
6
  CHEAP_API_BASE = os.getenv("CHEAP_API_BASE") or OPENAI_API_BASE
7
  CHEAP_API_KEY = os.getenv("CHEAP_API_KEY") or OPENAI_API_KEY
8
- CHEAP_MODEL = os.getenv("CHEAP_MODEL") or "gpt-4"
9
 
10
  STRONG_API_BASE = os.getenv("STRONG_API_BASE") or OPENAI_API_BASE
11
  STRONG_API_KEY = os.getenv("STRONG_API_KEY") or OPENAI_API_KEY
12
- STRONG_MODEL = os.getenv("STRONG_MODEL") or "mixtral-8x7b"
13
 
14
  IS_SHARE = bool(os.getenv("IS_SHARE")) or False
15
 
 
5
 
6
  CHEAP_API_BASE = os.getenv("CHEAP_API_BASE") or OPENAI_API_BASE
7
  CHEAP_API_KEY = os.getenv("CHEAP_API_KEY") or OPENAI_API_KEY
8
+ CHEAP_MODEL = os.getenv("CHEAP_MODEL") or "gpt-3.5-turbo"
9
 
10
  STRONG_API_BASE = os.getenv("STRONG_API_BASE") or OPENAI_API_BASE
11
  STRONG_API_KEY = os.getenv("STRONG_API_KEY") or OPENAI_API_KEY
12
+ STRONG_MODEL = os.getenv("STRONG_MODEL") or "gpt-4"
13
 
14
  IS_SHARE = bool(os.getenv("IS_SHARE")) or False
15
 
data_test.py CHANGED
@@ -66,10 +66,10 @@ References
66
  Available upon request.
67
  """
68
  pdf_context = {
69
- "company_full_name": "Mastercard",
70
- "job_title": "Project Management Intern",
71
- "applicant_full_name": "Dorothy Gale",
72
- "applicant_contact_information": "123 Main St., Emerald City, KS 12345, (123) 456-7890, dorothy@wizardofoz.com",
73
  "letter_body": "text,\n\ntest test"
74
  }
75
 
 
66
  Available upon request.
67
  """
68
  pdf_context = {
69
+ "companyFullName": "Mastercard",
70
+ "jobTitle": "Project Management Intern",
71
+ "applicantFullName": "Dorothy Gale",
72
+ "applicantContactInformation": "123 Main St., Emerald City, KS 12345, (123) 456-7890, dorothy@wizardofoz.com",
73
  "letter_body": "text,\n\ntest test"
74
  }
75
 
taskAI.py CHANGED
@@ -13,7 +13,7 @@ EXTRACT_INFO = ChatPromptTemplate(
13
  [
14
  ChatMessage(
15
  role="system",
16
- content="You are a content extractor. You never paraphrase; you only reduce content at the sentence level. Your mission is to extract {to_extract} from user input. Make sure output is complete without missing parts. Output is in a clean text format",
17
  ),
18
  ChatMessage(role="user", content="{input}"),
19
  ]
@@ -35,7 +35,7 @@ JSON_API = ChatPromptTemplate(
35
  [
36
  ChatMessage(
37
  role="system",
38
- content="You are an AI JSON API. You convert user input into a JSON object. API returns exactly in this template: {template}",
39
  ),
40
  ChatMessage(role="user", content="{content}"),
41
  ]
@@ -51,7 +51,7 @@ LETTER_COMPOSE = ChatPromptTemplate(
51
 
52
  Before officially write the letter, think step by step. First, list what makes a perfect cover letter in general, and in order to write a perfect cover letter, what key points do you have to learn from the RESUME and JOB_DESCRIPTION. Then, carefully analyze the given RESUME and JOB_DESCRIPTION, take a deep breath and propose 3 best tactics to convince recruiter believe the applicant fit for the role. Ensure your thoughts are express clearly and then write the complete cover letter.""",
53
  ),
54
- ChatMessage(role="user", content="<RESUME>\n{resume}\n</RESUME>\n\n<JOB_DESCRIPTION>\n{jd}</JOB_DESCRIPTION>\n"),
55
  ]
56
  )
57
 
@@ -82,7 +82,7 @@ class TaskAI(OpenAILike):
82
  )
83
 
84
  def jd_preprocess(self, input: str):
85
- return self.stream_chat(EXTRACT_INFO.format_messages(to_extract="information directly related to job description", input=input))
86
 
87
  def cv_preprocess(self, input: str):
88
  return self.stream_chat(SIMPLIFY_MD.format_messages(input=input))
@@ -91,9 +91,9 @@ class TaskAI(OpenAILike):
91
  return self.stream_chat(LETTER_COMPOSE.format_messages(resume=resume, jd=jd))
92
 
93
  def get_jobapp_meta(self, JD, CV):
94
- meta_JD = self.chat(JSON_API.format_messages(template=keys_to_template(["company_full_name", "job_title"]), content=JD)).message.content
95
  # yield meta_JD
96
- meta_CV = self.chat(JSON_API.format_messages(template=keys_to_template(["applicant_full_name", "applicant_contact_information"]), content=CV)).message.content
97
  # yield meta_JD+'\n'+meta_CV
98
  try:
99
  meta_JD = json.loads(meta_JD.strip())
@@ -106,5 +106,5 @@ class TaskAI(OpenAILike):
106
  yield json.dumps(meta, indent=2)
107
 
108
  def purify_letter(self, full_text):
109
- return self.stream_chat(EXTRACT_INFO.format_messages(to_extract="the letter part from greeting to sign-off, and remove applicant's name at end", input=full_text))
110
 
 
13
  [
14
  ChatMessage(
15
  role="system",
16
+ content="You are a content extractor. You never paraphrase; you only reduce content at the sentence level. Your mission is to extract {to_extract} from user input. Reformat the extraction in a clean style if extraction looks messey.",
17
  ),
18
  ChatMessage(role="user", content="{input}"),
19
  ]
 
35
  [
36
  ChatMessage(
37
  role="system",
38
+ content="You are a JSON API. Your mission is to convert user input into a JSON object exactly in this template: {template}",
39
  ),
40
  ChatMessage(role="user", content="{content}"),
41
  ]
 
51
 
52
  Before officially write the letter, think step by step. First, list what makes a perfect cover letter in general, and in order to write a perfect cover letter, what key points do you have to learn from the RESUME and JOB_DESCRIPTION. Then, carefully analyze the given RESUME and JOB_DESCRIPTION, take a deep breath and propose 3 best tactics to convince recruiter believe the applicant fit for the role. Ensure your thoughts are express clearly and then write the complete cover letter.""",
53
  ),
54
+ ChatMessage(role="user", content="<RESUME>\n{resume}\n</RESUME>\n\n<JOB_DESCRIPTION>\n{jd}</JOB_DESCRIPTION>\n<ANALYSIS_REPORT>"),
55
  ]
56
  )
57
 
 
82
  )
83
 
84
  def jd_preprocess(self, input: str):
85
+ return self.stream_chat(EXTRACT_INFO.format_messages(to_extract="the job description part`", input=input))
86
 
87
  def cv_preprocess(self, input: str):
88
  return self.stream_chat(SIMPLIFY_MD.format_messages(input=input))
 
91
  return self.stream_chat(LETTER_COMPOSE.format_messages(resume=resume, jd=jd))
92
 
93
  def get_jobapp_meta(self, JD, CV):
94
+ meta_JD = self.chat(JSON_API.format_messages(template=keys_to_template(["companyFullName", "jobTitle"]), content=JD)).message.content
95
  # yield meta_JD
96
+ meta_CV = self.chat(JSON_API.format_messages(template=keys_to_template(["applicantFullNname", "applicantContactInformation"]), content=CV)).message.content
97
  # yield meta_JD+'\n'+meta_CV
98
  try:
99
  meta_JD = json.loads(meta_JD.strip())
 
106
  yield json.dumps(meta, indent=2)
107
 
108
  def purify_letter(self, full_text):
109
+ return self.stream_chat(EXTRACT_INFO.format_messages(to_extract="the cover letter section starting from 'Dear Hiring Manager' or similar to 'Sincerely,' or similar ", input=full_text))
110
 
taskNonAI.py CHANGED
@@ -18,8 +18,10 @@ def extract_url(url: str) -> Optional[str]:
18
  cmd = f"""shot-scraper javascript -b firefox \
19
  "{url}" "
20
  async () => {{
 
21
  const readability = await import('https://cdn.skypack.dev/@mozilla/readability');
22
- return (new readability.Readability(document)).parse();
 
23
  }}"
24
  """
25
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
@@ -43,7 +45,7 @@ def date():
43
  f"%B %d{'th' if 4 <= current_date.day <= 20 or 24 <= current_date.day <= 30 else ['st', 'nd', 'rd'][current_date.day % 10 - 1]} , %Y")
44
 
45
  def typst_escape(s):
46
- return s.replace('@','\@').replace('#','\#')
47
 
48
  def compile_pdf(context: dict, tmpl_path: str, output_path="/tmp/cover_letter.pdf"):
49
  with open(tmpl_path, "r", encoding='utf8') as f:
 
18
  cmd = f"""shot-scraper javascript -b firefox \
19
  "{url}" "
20
  async () => {{
21
+ const sleep = duration => new Promise(resolve => setTimeout(() => resolve(), duration));
22
  const readability = await import('https://cdn.skypack.dev/@mozilla/readability');
23
+ await sleep(3000);
24
+ return new readability.Readability(document).parse();
25
  }}"
26
  """
27
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
 
45
  f"%B %d{'th' if 4 <= current_date.day <= 20 or 24 <= current_date.day <= 30 else ['st', 'nd', 'rd'][current_date.day % 10 - 1]} , %Y")
46
 
47
  def typst_escape(s):
48
+ return str(s).replace('@','\@').replace('#','\#')
49
 
50
  def compile_pdf(context: dict, tmpl_path: str, output_path="/tmp/cover_letter.pdf"):
51
  with open(tmpl_path, "r", encoding='utf8') as f:
template_letter.tmpl CHANGED
@@ -1,15 +1,15 @@
1
  #import "template_base.typ": *
2
  #show: letter.with(
3
  sender: [
4
- ${applicant_contact_information}
5
  ],
6
  recipient: [
7
  Hiring Manager \
8
- ${company_full_name} \
9
  ],
10
  date: [${date_string}],
11
- subject: [Cover Letter for ${job_title}],
12
- name: [${applicant_full_name}],
13
  )
14
 
15
  ${letter_body}
 
1
  #import "template_base.typ": *
2
  #show: letter.with(
3
  sender: [
4
+ ${applicantContactInformation}
5
  ],
6
  recipient: [
7
  Hiring Manager \
8
+ ${companyFullName} \
9
  ],
10
  date: [${date_string}],
11
+ subject: [Cover Letter for ${jobTitle}],
12
+ name: [${applicantFullName}],
13
  )
14
 
15
  ${letter_body}