drmjh commited on
Commit
0aa2975
·
1 Parent(s): 7549871

Minimal working example SPIA2024

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__
2
+ .venv
3
+ wandb
SPIA2024/config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "wandb_args" : {
3
+ "project": "spiaclass2024",
4
+ "tags": [
5
+ "dev",
6
+ "demo",
7
+ "class 01"
8
+ ]
9
+ },
10
+ "model_args" : {
11
+ "model": "gpt-4-1106-preview",
12
+ "temperature": 0.0
13
+ }
14
+ }
SPIA2024/initial_message.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Hello! My name is SurveyGPT, a conversational AI designed to help improve interview and survey research.
2
+
3
+ Today I will be asking you some questions about your background and expectations.
4
+
5
+ To begin, could you tell me what you hope to get out of this course?
SPIA2024/system_message.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are an AI designed to help professors better understand the needs of their classes by fielding chat-based interviews with students at the beginning of the semester.
2
+ Your job is to conduct an interview with students where you ask them about their expectations of the course and previous relevant experience in order to produce interview transcripts that the professor can analyze and create an educational experience that is tailored to students needs, abilities and expectations.
3
+
4
+ The class you are assisting with this semester is an Introduction to Machine Learning for Public Policy, taught by Professor Brandon Stewart at the School of Public and International Affairs (SPIA) at Princeton University. This is a one-semester applied introduction to machine learning for policy makers and analysts, with an emphasis on building strong foundations and core intutions for applying machine learning in social science and policy settings.
5
+
6
+ Your instructions for conducting the interview are as follows:
7
+ - Begin by asking the student what they hope to get out of the course.
8
+ - If there are ambiguities in their answer, ask probing questions to elicit further information.
9
+ - Once the conversation makes clear what the student hopes to get out of the course, ask about their background in quantitative and technical work or projects.
10
+ - Finally, ask any other questions that you think are helpful.
11
+ - Maintain a polite and professional demeanor.
12
+ - If asked questions that you do not have the information about, such as about accommodations or assignments, advise the student to direct their question to the Professor instead.
app.py CHANGED
@@ -1,14 +1,16 @@
1
  """
2
- Cognitive Debriefing App - Respondent Interface
3
 
4
  Author: Dr Musashi Hinck
5
 
6
 
7
- Respondent-facing app. Reads arguments from request (in form of shareable link)
8
 
9
- Change Log:
10
-
11
- - 2024.01.16: Continuous logging to wandb, change name of run to `userid`
 
 
12
 
13
  """
14
  from __future__ import annotations
@@ -20,6 +22,7 @@ import wandb
20
  import gradio as gr
21
  import openai
22
 
 
23
  from base64 import urlsafe_b64decode
24
 
25
  logger = logging.getLogger(__name__)
@@ -28,90 +31,68 @@ from utils import PromptTemplate, convert_gradio_to_openai, seed_openai_key
28
 
29
 
30
  # %% Initialization
 
31
  if os.environ.get(f"OPENAI_API_KEY", "DEFAULT") == "DEFAULT":
32
  seed_openai_key()
33
  client = openai.OpenAI()
34
 
35
 
36
  # %% (functions)
37
- def decode_config(config_dta: str) -> dict[str, str | float]:
38
- "Read base64_url encoded json and loads into configuration"
39
- config_str: str = urlsafe_b64decode(config_dta)
40
- config: dict = json.loads(config_str)
41
- return config
42
-
43
-
44
- def load_config(request: gr.Request):
45
- "Read parameters from request header"
46
- config = decode_config(request.query_params["dta"])
47
- survey_question = config["question"]
48
- survey_template = config["template"]
49
- initial_message = config["initial_message"]
50
- model_args = {"model": config["model"], "temperature": config["temperature"]}
51
- userid = config["userid"]
52
- return survey_question, survey_template, initial_message, model_args, userid
53
-
54
-
55
- # Post-loading
56
- def update_template(question: str, template: PromptTemplate | str) -> str:
57
- """
58
- Updates templates. Currently only accepts a "question" variable, but can add future templating in the future.
59
- """
60
- if isinstance(template, str):
61
- template = PromptTemplate(template)
62
- if "question" in template.variables:
63
- return template.format(question=question)
64
- else:
65
- return str(template)
66
-
67
-
68
- def reset_interview() -> tuple[list[list[str | None]], gr.Button, gr.Button]:
69
- wandb.finish()
70
- gr.Info("Interview reset.")
71
- return (
72
- [],
73
- gr.Button("Start Interview", visible=True),
74
- gr.Button("Reply", visible=False),
75
- gr.Button("Save Survey", visible=False, variant="secondary"),
76
- gr.Button("Save and Exit", visible=False, variant="stop"),
77
  )
 
 
78
 
79
 
80
  def initialize_interview(
81
- system_message: str, first_question: str, model_args: dict[str, str | float]
82
- ) -> tuple[list[list[str | None]], gr.Textbox, gr.Button, gr.Button]:
83
  "Read system prompt and start interview"
84
- if len(first_question) == 0:
85
- first_question = call_openai(
86
  [], system_message, client, model_args, stream=False
87
  )
88
- # Use fixed prompt
89
- chat_history = [[None, first_question]]
 
90
  return (
91
- chat_history,
92
  gr.Textbox(
93
- placeholder="Type response here.", interactive=True, show_label=False
94
- ),
95
- gr.Button(variant="primary", interactive=True),
96
- gr.Button("Start Interview", visible=False),
97
- gr.Button("Save and Exit", visible=True, variant="stop"),
 
 
 
98
  )
99
 
100
 
101
  def initialize_tracker(
102
  model_args: dict[str, str | float],
103
- question: str,
104
- template: PromptTemplate,
105
- userid=str,
106
  ) -> None:
107
  "Initializes wandb run for interview"
108
  run_config = model_args | {
109
- "question": question,
110
- "template": str(template),
111
  "userid": userid,
112
  }
113
  wandb.init(
114
- project="cognitive-debrief", name=userid, config=run_config, tags=["dev"]
 
 
 
115
  )
116
 
117
 
@@ -166,7 +147,7 @@ def call_openai(
166
  def user_message(
167
  message: str, chat_history: list[list[str | None]]
168
  ) -> tuple[str, list[list[str | None]]]:
169
- "Displays user message immediately."
170
  return "", chat_history + [[message, None]]
171
 
172
 
@@ -183,6 +164,7 @@ def bot_message(
183
  + messages
184
  + [{"role": "user", "content": user_msg}]
185
  )
 
186
  response = client.chat.completions.create(
187
  messages=messages, stream=True, **model_args
188
  )
@@ -195,58 +177,65 @@ def bot_message(
195
  yield chat_history
196
 
197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  # LAYOUT
199
  with gr.Blocks() as demo:
200
- gr.Markdown("# Cognitive Debriefing Prototype")
201
 
202
- # Hidden values
203
- surveyQuestion = gr.Textbox(visible=False)
204
- surveyTemplate = gr.Textbox(visible=False)
205
  initialMessage = gr.Textbox(visible=False)
206
  systemMessage = gr.Textbox(visible=False)
207
  modelArgs = gr.State(value={"model": "", "temperature": ""})
208
- userid = gr.Textbox(visible=False, interactive=False)
209
 
210
- ## RESPONDENT
211
- chatDisplay = gr.Chatbot(
212
- show_label=False,
213
  )
 
 
 
 
214
  with gr.Row():
215
  chatInput = gr.Textbox(
216
  placeholder="Click 'Start Interview' to begin.",
 
217
  interactive=False,
218
  show_label=False,
219
  scale=10,
220
  )
221
  chatSubmit = gr.Button(
222
  "",
223
- variant="secondary",
224
  interactive=False,
225
  icon="./arrow_icon.svg",
 
226
  )
227
- startInterview = gr.Button("Start Interview", variant="primary")
228
  resetButton = gr.Button("Save and Exit", visible=False, variant="stop")
229
 
230
  ## INTERACTIONS
231
  # Start Interview button
232
- startInterview.click(
233
  load_config,
234
- inputs=None,
235
- outputs=[
236
- surveyQuestion,
237
- surveyTemplate,
238
- initialMessage,
239
- modelArgs,
240
- userid,
241
- ],
242
- ).then(
243
- update_template,
244
- inputs=[surveyQuestion, surveyTemplate],
245
- outputs=[systemMessage],
246
- ).then(
247
- update_template,
248
- inputs=[surveyQuestion, initialMessage],
249
- outputs=initialMessage,
250
  ).then(
251
  initialize_interview,
252
  inputs=[systemMessage, initialMessage, modelArgs],
@@ -255,13 +244,26 @@ with gr.Blocks() as demo:
255
  chatInput,
256
  chatSubmit,
257
  startInterview,
 
258
  resetButton,
259
  ],
260
  ).then(
261
- initialize_tracker, inputs=[modelArgs, surveyQuestion, surveyTemplate, userid]
 
 
 
 
 
 
 
 
 
 
 
262
  )
263
 
264
- # "Enter" on textbox
 
265
  chatInput.submit(
266
  user_message,
267
  inputs=[chatInput, chatDisplay],
@@ -274,8 +276,7 @@ with gr.Blocks() as demo:
274
  ).then(
275
  save_interview, inputs=[chatDisplay]
276
  )
277
-
278
- # "Submit" button
279
  chatSubmit.click(
280
  user_message,
281
  inputs=[chatInput, chatDisplay],
@@ -289,9 +290,17 @@ with gr.Blocks() as demo:
289
  save_interview, inputs=[chatDisplay]
290
  )
291
 
 
292
  resetButton.click(save_interview, [chatDisplay]).then(
293
  reset_interview,
294
- outputs=[chatDisplay, startInterview, resetButton],
 
 
 
 
 
 
 
295
  show_progress=False,
296
  )
297
 
 
1
  """
2
+ General-Purpose LM Interview Interface
3
 
4
  Author: Dr Musashi Hinck
5
 
6
 
7
+ Version Log:
8
 
9
+ - 2024.01.29: prototype without separate launching interface for demoing in SPIA class.
10
+ - Remove URL decoding
11
+ - Read sysprompt and initial_message from file
12
+ - Begins with user entering name/alias
13
+ - Azure OpenAI?
14
 
15
  """
16
  from __future__ import annotations
 
22
  import gradio as gr
23
  import openai
24
 
25
+ from pathlib import Path
26
  from base64 import urlsafe_b64decode
27
 
28
  logger = logging.getLogger(__name__)
 
31
 
32
 
33
  # %% Initialization
34
+ CONFIG_DIR: Path = Path("./SPIA2024")
35
  if os.environ.get(f"OPENAI_API_KEY", "DEFAULT") == "DEFAULT":
36
  seed_openai_key()
37
  client = openai.OpenAI()
38
 
39
 
40
  # %% (functions)
41
+ def load_config(
42
+ path: Path,
43
+ ) -> tuple[str, str, dict[str, str | float], dict[str, str | list[str]]]:
44
+ "Read configs, return inital_message, system_message, model_args, wandb_args"
45
+ initial_message: str = (path / "initial_message.txt").read_text().strip()
46
+ system_message: str = (path / "system_message.txt").read_text().strip()
47
+ cfg: dict[str, str] = json.loads((path / "config.json").read_bytes())
48
+ model_args: dict[str, str | float] = cfg.get(
49
+ "model_args", {"model": "gpt4", "temperature": 0.0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  )
51
+ wandb_args: dict = cfg.get("wandb_args")
52
+ return initial_message, system_message, model_args, wandb_args
53
 
54
 
55
  def initialize_interview(
56
+ system_message: str, initial_message: str, model_args: dict[str, str | float]
57
+ ) -> tuple[gr.Chatbot, gr.Textbox, gr.Button, gr.Button]:
58
  "Read system prompt and start interview"
59
+ if len(initial_message) == 0: # If empty inital message, ask the LM to write it.
60
+ initial_message = call_openai(
61
  [], system_message, client, model_args, stream=False
62
  )
63
+ chat_history = [
64
+ [None, initial_message]
65
+ ] # First item is for user, in this case bot starts interaction.
66
  return (
67
+ gr.Chatbot(visible=True, value=chat_history),
68
  gr.Textbox(
69
+ placeholder="Type response here. Hit 'enter' to submit.",
70
+ visible=True,
71
+ interactive=True,
72
+ ), # chatInput
73
+ gr.Button(visible=True, interactive=True), # chatSubmit
74
+ gr.Button(visible=False), # startInterview
75
+ gr.Textbox(visible=False), # userBox
76
+ gr.Button(visible=True), # resetButton
77
  )
78
 
79
 
80
  def initialize_tracker(
81
  model_args: dict[str, str | float],
82
+ system_message: PromptTemplate,
83
+ userid: str,
84
+ wandb_args: dict[str, str | list[str]],
85
  ) -> None:
86
  "Initializes wandb run for interview"
87
  run_config = model_args | {
88
+ "system_message": str(system_message),
 
89
  "userid": userid,
90
  }
91
  wandb.init(
92
+ project=wandb_args["project"],
93
+ name=userid,
94
+ config=run_config,
95
+ tags=wandb_args["tags"],
96
  )
97
 
98
 
 
147
  def user_message(
148
  message: str, chat_history: list[list[str | None]]
149
  ) -> tuple[str, list[list[str | None]]]:
150
+ "Display user message immediately"
151
  return "", chat_history + [[message, None]]
152
 
153
 
 
164
  + messages
165
  + [{"role": "user", "content": user_msg}]
166
  )
167
+ # API call
168
  response = client.chat.completions.create(
169
  messages=messages, stream=True, **model_args
170
  )
 
177
  yield chat_history
178
 
179
 
180
+ def reset_interview() -> (
181
+ tuple[
182
+ list[list[str | None]], gr.Chatbot, gr.Textbox, gr.Button, gr.Button, gr.Button
183
+ ]
184
+ ):
185
+ wandb.finish()
186
+ gr.Info("Interview reset.")
187
+ return (
188
+ gr.Chatbot(visible=False, value=[]), # chatDisplay
189
+ gr.Textbox(visible=False), # chatInput
190
+ gr.Button(visible=False), # chatSubmit
191
+ gr.Textbox(value=None, visible=True), # userBox
192
+ gr.Button(visible=True), # startInterview
193
+ gr.Button(visible=False), # resetButton
194
+ )
195
+
196
+
197
  # LAYOUT
198
  with gr.Blocks() as demo:
199
+ gr.Markdown("# StewartLab LM Interviewer")
200
 
201
+ # Config values
202
+ configDir = gr.State(value=CONFIG_DIR)
 
203
  initialMessage = gr.Textbox(visible=False)
204
  systemMessage = gr.Textbox(visible=False)
205
  modelArgs = gr.State(value={"model": "", "temperature": ""})
206
+ wandbArgs = gr.State(value={"project": "", "tags": []})
207
 
208
+ ## Start interview by entering name or alias
209
+ userBox = gr.Textbox(
210
+ placeholder="Enter name or alias and hit 'enter' to begin.", show_label=False
211
  )
212
+ startInterview = gr.Button("Start Interview", variant="primary", visible=True)
213
+
214
+ ## RESPONDENT
215
+ chatDisplay = gr.Chatbot(show_label=False, visible=False)
216
  with gr.Row():
217
  chatInput = gr.Textbox(
218
  placeholder="Click 'Start Interview' to begin.",
219
+ visible=False,
220
  interactive=False,
221
  show_label=False,
222
  scale=10,
223
  )
224
  chatSubmit = gr.Button(
225
  "",
226
+ variant="primary",
227
  interactive=False,
228
  icon="./arrow_icon.svg",
229
+ visible=False,
230
  )
 
231
  resetButton = gr.Button("Save and Exit", visible=False, variant="stop")
232
 
233
  ## INTERACTIONS
234
  # Start Interview button
235
+ userBox.submit(
236
  load_config,
237
+ inputs=configDir,
238
+ outputs=[initialMessage, systemMessage, modelArgs, wandbArgs],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  ).then(
240
  initialize_interview,
241
  inputs=[systemMessage, initialMessage, modelArgs],
 
244
  chatInput,
245
  chatSubmit,
246
  startInterview,
247
+ userBox,
248
  resetButton,
249
  ],
250
  ).then(
251
+ initialize_tracker, inputs=[modelArgs, systemMessage, userBox, wandbArgs]
252
+ )
253
+ startInterview.click(
254
+ load_config,
255
+ inputs=None,
256
+ outputs=[initialMessage, systemMessage, modelArgs, wandbArgs],
257
+ ).then(
258
+ initialize_interview,
259
+ inputs=[systemMessage, initialMessage, modelArgs],
260
+ outputs=[chatDisplay, chatInput, chatSubmit, startInterview, resetButton],
261
+ ).then(
262
+ initialize_tracker, inputs=[modelArgs, systemMessage, userBox, wandbArgs]
263
  )
264
 
265
+ # Chat interaction
266
+ # "Enter"
267
  chatInput.submit(
268
  user_message,
269
  inputs=[chatInput, chatDisplay],
 
276
  ).then(
277
  save_interview, inputs=[chatDisplay]
278
  )
279
+ # Button
 
280
  chatSubmit.click(
281
  user_message,
282
  inputs=[chatInput, chatDisplay],
 
290
  save_interview, inputs=[chatDisplay]
291
  )
292
 
293
+ # Reset button
294
  resetButton.click(save_interview, [chatDisplay]).then(
295
  reset_interview,
296
+ outputs=[
297
+ chatDisplay,
298
+ chatInput,
299
+ chatSubmit,
300
+ userBox,
301
+ startInterview,
302
+ resetButton,
303
+ ],
304
  show_progress=False,
305
  )
306