Spaces:
Running
Running
improve prompts, templates, debugs, etc.
Browse files- app.py +45 -38
- config.py +2 -2
- data_test.py +4 -4
- taskAI.py +7 -7
- taskNonAI.py +4 -2
- 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 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
|
|
|
|
|
|
|
|
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
|
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 |
-
|
86 |
-
|
87 |
-
|
88 |
-
)
|
89 |
-
for result in gen:
|
90 |
yield result
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
-
def finalize_letter_pdf(
|
|
|
|
|
|
|
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/
|
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 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
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
|
183 |
-
).then(fn=finalize_letter_pdf, inputs=[
|
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-
|
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 "
|
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 |
-
"
|
70 |
-
"
|
71 |
-
"
|
72 |
-
"
|
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, [email protected]",
|
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.
|
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
|
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="
|
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(["
|
95 |
# yield meta_JD
|
96 |
-
meta_CV = self.chat(JSON_API.format_messages(template=keys_to_template(["
|
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
|
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 |
-
|
|
|
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 |
-
${
|
5 |
],
|
6 |
recipient: [
|
7 |
Hiring Manager \
|
8 |
-
${
|
9 |
],
|
10 |
date: [${date_string}],
|
11 |
-
subject: [Cover Letter for ${
|
12 |
-
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}
|