ahuang11 commited on
Commit
8e96671
·
verified ·
1 Parent(s): 72abde9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +162 -131
app.py CHANGED
@@ -1,149 +1,180 @@
1
  import os
2
- import re
 
3
 
4
- import colorcet
5
- import numpy as np
 
6
  import panel as pn
7
- import tastymap as tm
8
- from openai import AsyncOpenAI
9
 
10
  pn.extension()
 
11
 
 
 
12
 
13
- def color_by_logprob(text, log_prob):
14
- tmap = tm.cook_tmap(colormap_input.value, colors_input.value)
15
- colors = tmap.to_model("hex")
16
- linear_prob = np.round(np.exp(log_prob) * 100, 2)
17
- # select index based on probability
18
- color_index = int(linear_prob // (100 / (len(colors) - 1)))
19
- # Generate HTML output with the chosen color
20
- if "'" in text:
21
- html_output = f'<span style="color: {colors[color_index]};">{text}</span>'
22
- else:
23
- html_output = f"<span style='color: {colors[color_index]}'>{text}</span>"
24
- return html_output
25
-
26
-
27
- def send_default_prompt(instance, event):
28
- instance.send(default_input.value)
29
-
30
-
31
- def custom_serializer(content):
32
- pattern = r"<span.*?>(.*?)</span>"
33
- matches = re.findall(pattern, content)
34
- if not matches:
35
- return content
36
- return matches[0]
37
-
38
-
39
- async def respond_to_input(contents: str, user: str, instance: pn.chat.ChatInterface):
40
- print(contents)
41
- if api_key_input.value:
42
- aclient.api_key = api_key_input.value
43
- elif not os.environ["OPENAI_API_KEY"]:
44
- instance.send("Please provide an OpenAI API key", respond=False, user="ChatGPT")
45
- # add system prompt
46
- if system_input.value:
47
- system_message = {"role": "system", "content": system_input.value}
48
- messages = [system_message]
49
- else:
50
- messages = []
51
- # gather messages for memory
52
- if memory_toggle.value:
53
- messages += instance.serialize(custom_serializer=custom_serializer)
54
- else:
55
- messages.append({"role": "user", "content": contents})
56
-
57
- # gather widget inputs values
58
- instance.send(
59
- f"The inputs are {model_selector.value=}, "
60
- f"{temperature_input.value=}, {system_input.value=}, "
61
- f"{memory_toggle.value=}",
62
- respond=False,
63
- user="System",
64
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- # call API
67
- response = await aclient.chat.completions.create(
68
- model=model_selector.value,
69
- messages=messages,
70
- stream=True,
71
- logprobs=True,
72
- temperature=temperature_input.value,
73
- max_tokens=max_tokens_input.value,
74
- seed=seed_input.value,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  )
76
- # stream response
77
- message = ""
78
- joint_log_prob = 0
79
  async for chunk in response:
80
- choice = chunk.choices[0]
81
- content = choice.delta.content
82
- log_probs = choice.logprobs
83
- if content and log_probs:
84
- log_prob = log_probs.content[0].logprob
85
- joint_log_prob += log_prob
86
- message += color_by_logprob(content, log_prob)
87
- yield pn.chat.ChatMessage(message, user=model_selector.value)
88
- joint_linear_prob = np.round(np.exp(joint_log_prob) * 100, 2)
89
- yield pn.chat.ChatMessage(
90
- message + f"\n\nJoint Probability: {joint_linear_prob}%",
91
- user=model_selector.value,
92
- )
93
 
94
 
95
- aclient = AsyncOpenAI()
96
- api_key_input = pn.widgets.PasswordInput(
97
- name="API Key",
98
- placeholder="sk-...",
99
- )
100
- system_input = pn.widgets.TextAreaInput(
101
- name="System Prompt",
102
- value="",
103
- rows=1,
104
- auto_grow=True,
105
- resizable="height",
106
- )
107
- default_input = pn.widgets.TextAreaInput(
108
- name="Default Prompt", value="", rows=1, auto_grow=True, resizable="height"
109
- )
110
- model_selector = pn.widgets.Select(
111
- name="Model",
112
- options=["gpt-3.5-turbo", "gpt-4"],
113
  )
114
- colormap_input = pn.widgets.ColorMap(name="ColorMap", options=colorcet.palette)
115
- colors_input = pn.widgets.IntInput(name="Number of Colors", start=1, value=6)
116
- temperature_input = pn.widgets.FloatInput(
117
- name="Temperature", start=0, end=2, step=0.01, value=1
 
 
 
 
 
118
  )
119
- max_tokens_input = pn.widgets.IntInput(name="Max Tokens", start=0, value=256)
120
- seed_input = pn.widgets.IntInput(name="Seed", start=0, end=100, value=0)
121
- memory_toggle = pn.widgets.Toggle(name="Include Memory", value=False, margin=(22, 5))
122
- chat_interface = pn.chat.ChatInterface(
123
- callback=respond_to_input,
124
- callback_user="ChatGPT",
125
- callback_exception="verbose",
126
- button_properties={
127
- "default": {"callback": send_default_prompt, "icon": "mail-fast"}
128
- },
129
- show_rerun=False,
130
  )
131
-
132
- sidebar = pn.Column(
133
- api_key_input,
134
- system_input,
135
- default_input,
136
- model_selector,
137
- colormap_input,
138
- colors_input,
139
- temperature_input,
140
- max_tokens_input,
141
- seed_input,
142
- memory_toggle,
143
  )
144
- main = pn.Column(chat_interface)
145
  template = pn.template.FastListTemplate(
146
- sidebar=sidebar,
147
- main=main,
 
 
148
  )
149
- template.show()
 
1
  import os
2
+ import operator
3
+ from typing import TypedDict, Annotated, Sequence
4
 
5
+ from langchain_openai.chat_models import ChatOpenAI
6
+ from langchain_core.messages import BaseMessage, SystemMessage
7
+ from langgraph.graph import StateGraph, END
8
  import panel as pn
 
 
9
 
10
  pn.extension()
11
+ os.environ["LANGCHAIN_TRACING_V2"] = "true"
12
 
13
+ JOB_DESCRIPTION = """
14
+ Quansight has its roots in the Python data science community. Our founders have had significant involvement in creating and maintaining NumPy, SciPy, Jupyter, Spyder, Dask, Conda, Numba, and other projects, as well as PyData NumFOCUS, and Anaconda. Our mission is to connect companies to open-source communities to create sustainable solutions that benefit the whole ecosystem.
15
 
16
+ We accomplish this mission by providing various services ranging from open-source software development to training and consulting. We believe in a culture of do-ers, learners, and collaborators. We are looking for people who are motivated, humble, curious, and respectful of others.
17
+
18
+ We are looking for enthusiastic and highly motivated software developers with extensive experience in the Scientific Python and PyData ecosystems to help support Quansight’s growing consulting business. This is an excellent opportunity for you to apply your software development skills to the open-source, Python, data-science, AI, big data and visualization ecosystems, and to help customers apply these tools to solve practical business problems.
19
+
20
+ What you’ll do
21
+ As a Senior PyData Software Engineer, you will be a key member of our team solving problems for our clients using tools from the Python open-source ecosystem. As appropriate, you will help maintain and push developments back upstream to open source projects. We are especially interested in people with a strong technical background who have experience or have an interest in becoming technical leads. Our client projects vary widely across business domains and technologies, being comfortable with growing and learning new technologies will be important to fitting in at Quansight. Key areas we touch on when building solutions for clients include algorithm development, data engineering, data science, machine learning/AI, visualization, packaging, infrastructure and integration.
22
+
23
+ Requirements:
24
+ Fluency in Python
25
+ Extensive experience with the Scientific Python and Data Science ecosystems, i.e. Pandas, NumPy, Scikit-Learn, etc.
26
+ Experience applying the PyData stack to data and scientific projects
27
+ Familiarity with software engineering best practices, including unit tests, code review, version control, CI/CD, etc.
28
+ Superior written and verbal communication skills, including writing and reviewing documentation
29
+ Ability to make technical and architectural decisions with minimal oversight.
30
+ Additionally one or more of the following skills will help you stand out:
31
+
32
+ Contributions to open source projects
33
+ Skills in other programming languages
34
+ Experience with Generative AI / LLM's
35
+ Experience with Visualization tools like Panel, Streamlit, Dash etc.
36
+ Experience with DevOps and Infrastructure-as-Code approaches
37
+ Experience with Python packaging and Conda environments
38
+ Experience with advanced Python data libraries such as Dask and Numba
39
+ Experience working in a client-facing environment
40
+ Exposure to building data engineering pipelines using Prefect, Airflow and similar tools
41
+ Practical Experience in MLOps approaches
42
+ Ability to provide technical leadership to others
43
+ Why should you join?
44
+ You'll become essential to a small, collaborative, fully distributed accessibility and engineering team. We strive to provide a working environment that gives you room to learn and grow.
45
+ """.strip()
46
+
47
+ RESUME_BULLETS = """
48
+ o Investigated the cause of anomalous sea surface temperatures following tropical cyclones (TCs) in the Eastern Tropical Pacific
49
+ o Deduced, quantitatively, that TCs tend to suppress low clouds there, enhancing shortwave fluxes, inducing a warming anomaly
50
+ o Demonstrated that long-short-term-memory models improved predictions at long leads but not at short leads
51
+ """.strip()
52
+
53
+
54
+ class AgentState(TypedDict):
55
+ messages: Annotated[Sequence[BaseMessage], operator.add]
56
+ original_bullets: str
57
+ job_description: str
58
+ keywords_count: int
59
+
60
+
61
+ async def revise(state: AgentState):
62
+ instructions = """
63
+ Understand the critique first and identify what keywords from the critique
64
+ needs to be bolded. Then apply the actionable steps to revise the resume bullets.
65
+
66
+ Revisions should be similar length as the original bullets.
67
+
68
+ Ensure the keywords from the critique are not awkwardly
69
+ placed, not deceitful, and the bullets are coherent and in proper sentence casing.
70
+ """
71
+ messages = [SystemMessage(instructions)] + state["messages"][-2:]
72
+ response = await model.ainvoke(messages)
73
+ return {"messages": [response]}
74
+
75
+
76
+ async def critique(state: AgentState):
77
+ job_description = state["job_description"]
78
+ keywords_slider = state["keywords_count"]
79
+ instructions = f"""
80
+ You are a resume reviewer, optimizing for ATS.
81
+ Review the job description and first list out
82
+ at least `{keywords_slider}` critical
83
+ ATS keywords from the `Job Description`.
84
+
85
+ If previously `Critique`d, verify if the changes were accurately made;
86
+ if not, rephrase original critique to be more actionable.
87
+
88
+ Determine whether the resume bullets have at least
89
+ {keywords_slider} keywords from the `Job Description` in bold.
90
+
91
+ If not, provide actionable steps to revise (but do not do the revision),
92
+ e.g. explicitly telling what keywords to bold from `Job Description` or
93
+ what to rephrase to include reflecting the `Job Description`.
94
+
95
+ Finally, if all changes are made properly, then
96
+ output "Looks good!".
97
 
98
+ Here's the `Job Description`:
99
+ '''
100
+ {job_description}
101
+ '''
102
+
103
+ If no changes are needed, simply output "Looks good!";
104
+ else output the critique starting with `Critique`: and keywords.
105
+ """
106
+ messages = [SystemMessage(instructions)] + state["messages"][-2:]
107
+ response = await model.ainvoke(messages)
108
+ return {"messages": [response]}
109
+
110
+
111
+ def continue_revising(state: AgentState):
112
+ last_message = state["messages"][-1].content
113
+ return "looks good" not in last_message.lower()
114
+
115
+
116
+ async def respond(content: str, user: str, instance: pn.chat.ChatInterface):
117
+ if not job_input.value:
118
+ instance.stream(
119
+ user="Exception", value="Please provide a job description.", respond=False
120
+ )
121
+ return
122
+
123
+ response = app.astream(
124
+ {
125
+ "original_bullets": resume_input.value,
126
+ "messages": [content],
127
+ "job_description": job_input.value,
128
+ "keywords_count": keywords_slider.value,
129
+ }
130
  )
 
 
 
131
  async for chunk in response:
132
+ for user, output in chunk.items():
133
+ message = output["messages"][-1].content
134
+ if user != "__end__":
135
+ instance.stream(user=user.title(), value=message)
 
 
 
 
 
 
 
 
 
136
 
137
 
138
+ model = ChatOpenAI()
139
+ # add components
140
+ workflow = StateGraph(AgentState)
141
+ workflow.add_node("critique", critique)
142
+ workflow.add_node("revise", revise)
143
+
144
+ # add connections
145
+ workflow.set_entry_point("critique")
146
+ workflow.add_edge("revise", "critique")
147
+ workflow.add_conditional_edges(
148
+ "critique", continue_revising, {True: "revise", False: END}
 
 
 
 
 
 
 
149
  )
150
+ app = workflow.compile()
151
+
152
+ # create UI
153
+ keywords_slider = pn.widgets.IntSlider(
154
+ start=1,
155
+ end=10,
156
+ value=3,
157
+ name="Keywords to Match",
158
+ sizing_mode="stretch_width",
159
  )
160
+ job_input = pn.widgets.TextAreaInput(
161
+ value=JOB_DESCRIPTION,
162
+ name="Job Description",
163
+ resizable="height",
164
+ auto_grow=True,
165
+ sizing_mode="stretch_width",
 
 
 
 
 
166
  )
167
+ resume_input = pn.widgets.TextAreaInput(
168
+ placeholder="Paste in the bullets you want revised.",
169
+ resizable="height",
170
+ rows=4,
171
+ value=RESUME_BULLETS,
 
 
 
 
 
 
 
172
  )
173
+ interface = pn.chat.ChatInterface(callback=respond, widgets=[resume_input])
174
  template = pn.template.FastListTemplate(
175
+ main=[interface],
176
+ sidebar=[keywords_slider, job_input],
177
+ sidebar_width=500,
178
+ title="Resume Reviser",
179
  )
180
+ template.servable()