Richard commited on
Commit
a65fd1a
·
1 Parent(s): 4be2fb0

Add more improvements

Browse files

- Variable generation
- Ratings
- Dockerfile
- Hugging Face meta

Files changed (5) hide show
  1. Dockerfile +33 -0
  2. README.md +9 -0
  3. components/table.py +73 -17
  4. llm.py +28 -3
  5. main.py +14 -7
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build Docker image for deployment.
2
+
3
+ FROM python:3.10.14-bullseye
4
+
5
+ RUN apt-get update && \
6
+ apt-get install -y \
7
+ # General dependencies
8
+ locales \
9
+ locales-all && \
10
+ # Clean local repository of package files since they won't be needed anymore.
11
+ # Make sure this line is called after all apt-get update/install commands have
12
+ # run.
13
+ apt-get clean && \
14
+ # Also delete the index files which we also don't need anymore.
15
+ rm -rf /var/lib/apt/lists/*
16
+
17
+ ENV LC_ALL en_US.UTF-8
18
+ ENV LANG en_US.UTF-8
19
+ ENV LANGUAGE en_US.UTF-8
20
+
21
+ # Install dependencies
22
+ COPY requirements.txt .
23
+ RUN pip install -r requirements.txt
24
+
25
+ # Create non-root user
26
+ RUN groupadd -g 900 mesop && useradd -u 900 -s /bin/bash -g mesop mesop
27
+ USER mesop
28
+
29
+ # Add app code here
30
+ COPY . /srv/mesop-prompt-tuner
31
+ WORKDIR /srv/mesop-prompt-tuner
32
+
33
+ CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--timeout", "60", "main:me"]
README.md CHANGED
@@ -1,3 +1,12 @@
 
 
 
 
 
 
 
 
 
1
  # Mesop Prompt Tuner
2
 
3
  Prompt tuner UI built using [Mesop](https://google.github.io/mesop/). This is a
 
1
+ ---
2
+ title: Mesop Prompt Tuner
3
+ emoji: 🎸
4
+ colorFrom: red
5
+ colorTo: orange
6
+ sdk: docker
7
+ app_port: 8080
8
+ ---
9
+
10
  # Mesop Prompt Tuner
11
 
12
  Prompt tuner UI built using [Mesop](https://google.github.io/mesop/). This is a
components/table.py CHANGED
@@ -1,10 +1,12 @@
 
 
1
  import mesop as me
2
 
3
  _NUM_REQUIRED_ROWS = 3
4
 
5
 
6
  @me.component
7
- def prompt_eval_table(prompt):
8
  """Creates a grid table for displaying and comparing different prompt version runs."""
9
  # Add a row for each variable
10
  num_vars = len(prompt.variables)
@@ -17,15 +19,17 @@ def prompt_eval_table(prompt):
17
  if num_vars
18
  else "1fr 20fr 1fr",
19
  margin=me.Margin.all(15),
 
20
  )
21
  ):
22
  # Render first row. This row only displays the Prompt version.
23
  for i in range(table_size):
24
  with me.box(
25
  style=me.Style(
26
- background="#fff",
27
  border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
28
  color="#000",
 
29
  font_weight="bold",
30
  padding=me.Padding.all(10),
31
  )
@@ -38,18 +42,27 @@ def prompt_eval_table(prompt):
38
  # Render second row. This row only displays the headers of the table:
39
  # variable names, model response, avg rating.
40
  header_row = [""] + prompt.variables + ["Model response"] + [""]
41
- for header_text in header_row:
42
  with me.box(
43
  style=me.Style(
44
  background="#FFF",
45
  border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
46
- color="#0063FF" if header_text and header_text != "Model response" else "#333",
 
 
47
  padding=me.Padding.all(10),
48
  )
49
  ):
50
  # Handle the variable header case.
51
  if header_text and header_text != "Model response":
52
  me.text("{{" + header_text + "}}")
 
 
 
 
 
 
 
53
  else:
54
  me.text(header_text)
55
 
@@ -60,16 +73,59 @@ def prompt_eval_table(prompt):
60
  + [example["variables"][v] for v in prompt.variables]
61
  + [example["output"], example.get("rating", "")]
62
  )
63
- for col_index, row in enumerate(content_row):
64
- with me.box(
65
- style=me.Style(
66
- background="#fff",
67
- border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
68
- color="#000",
69
- padding=me.Padding.all(10),
70
- )
71
- ):
72
- if col_index == 0 or not row:
73
- me.text(row)
74
- else:
75
- me.markdown(row)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Callable
2
+
3
  import mesop as me
4
 
5
  _NUM_REQUIRED_ROWS = 3
6
 
7
 
8
  @me.component
9
+ def prompt_eval_table(prompt, on_select_rating: Callable | None = None):
10
  """Creates a grid table for displaying and comparing different prompt version runs."""
11
  # Add a row for each variable
12
  num_vars = len(prompt.variables)
 
19
  if num_vars
20
  else "1fr 20fr 1fr",
21
  margin=me.Margin.all(15),
22
+ overflow_x="scroll",
23
  )
24
  ):
25
  # Render first row. This row only displays the Prompt version.
26
  for i in range(table_size):
27
  with me.box(
28
  style=me.Style(
29
+ background="#FFF",
30
  border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
31
  color="#000",
32
+ font_size=15,
33
  font_weight="bold",
34
  padding=me.Padding.all(10),
35
  )
 
42
  # Render second row. This row only displays the headers of the table:
43
  # variable names, model response, avg rating.
44
  header_row = [""] + prompt.variables + ["Model response"] + [""]
45
+ for i, header_text in enumerate(header_row):
46
  with me.box(
47
  style=me.Style(
48
  background="#FFF",
49
  border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
50
+ color="#0063FF" if header_text and header_text != "Model response" else "#444",
51
+ font_size=13,
52
+ font_weight="bold",
53
  padding=me.Padding.all(10),
54
  )
55
  ):
56
  # Handle the variable header case.
57
  if header_text and header_text != "Model response":
58
  me.text("{{" + header_text + "}}")
59
+ elif i == table_size - 1:
60
+ avg_rating = _calculate_avg_rating_from_prompt(prompt)
61
+ if avg_rating is not None:
62
+ with me.tooltip(message="Average rating"):
63
+ me.text(f"{avg_rating:.2f}", style=me.Style(text_align="center"))
64
+ else:
65
+ me.text("")
66
  else:
67
  me.text(header_text)
68
 
 
73
  + [example["variables"][v] for v in prompt.variables]
74
  + [example["output"], example.get("rating", "")]
75
  )
76
+ for col_index, content in enumerate(content_row):
77
+ if col_index == len(content_row) - 1:
78
+ with me.box(
79
+ style=me.Style(
80
+ background="#FFF",
81
+ border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
82
+ color="#000",
83
+ padding=me.Padding.all(10),
84
+ )
85
+ ):
86
+ me.select(
87
+ value=content,
88
+ options=[
89
+ me.SelectOption(label="1", value="1"),
90
+ me.SelectOption(label="2", value="2"),
91
+ me.SelectOption(label="3", value="3"),
92
+ me.SelectOption(label="4", value="4"),
93
+ me.SelectOption(label="5", value="5"),
94
+ ],
95
+ on_selection_change=on_select_rating,
96
+ key=f"rating_{prompt.version}_{row_index}",
97
+ style=me.Style(width=60),
98
+ )
99
+ elif col_index == 0 or not content:
100
+ with me.box(
101
+ style=me.Style(
102
+ background="#FFF",
103
+ border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
104
+ color="#000",
105
+ font_size=14,
106
+ padding=me.Padding.all(10),
107
+ text_align="center",
108
+ )
109
+ ):
110
+ me.text(content)
111
+ else:
112
+ with me.box(
113
+ style=me.Style(
114
+ background="#FFF",
115
+ border=me.Border.all(me.BorderSide(width=1, style="solid", color="#DEE2E6")),
116
+ color="#000",
117
+ font_size=14,
118
+ padding=me.Padding.all(10),
119
+ max_height=300,
120
+ min_width=300,
121
+ overflow_y="scroll",
122
+ )
123
+ ):
124
+ me.markdown(content)
125
+
126
+
127
+ def _calculate_avg_rating_from_prompt(prompt) -> float | None:
128
+ ratings = [int(response["rating"]) for response in prompt.responses if response.get("rating")]
129
+ if ratings:
130
+ return sum(ratings) / float(len(ratings))
131
+ return None
llm.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
 
3
  import google.generativeai as genai
@@ -19,6 +20,23 @@ For custom user input, you can leave placeholder variables. For example, if you
19
  variable named EMAIL, it would like {{{{EMAIL}}}} in the resulting prompt.
20
  """.strip()
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  def _make_model(model_name: str, temperature: float) -> genai.GenerativeModel:
24
  return genai.GenerativeModel(
@@ -39,10 +57,17 @@ def generate_prompt(task_description: str, model_name: str, temperature: float)
39
 
40
 
41
  def generate_variables(
42
- prompt: str, variables: dict[str, str], model_name: str, temperature: float
43
  ) -> dict[str, str]:
44
- # model = _make_model(model_name, temperature)
45
- pass
 
 
 
 
 
 
 
46
 
47
 
48
  def run_prompt(prompt_with_variables: str, model_name: str, temperature: float) -> str:
 
1
+ import json
2
  import os
3
 
4
  import google.generativeai as genai
 
20
  variable named EMAIL, it would like {{{{EMAIL}}}} in the resulting prompt.
21
  """.strip()
22
 
23
+ _GENERATE_VARIABLES_PROMPT = """
24
+ Your job is to generate data for the given placeholders: {placeholders}.
25
+
26
+ The generated data should reflect the name of the placeholder.
27
+
28
+ Render the output as JSON.
29
+
30
+ Here is an example output for these placeholders: STORY, FEEDBACK_TYPE
31
+
32
+ {{
33
+ "STORY": "Generated data for a story",
34
+ "FEEDBACK_TYPE": "Type of feedback to provide on the story"
35
+ }}
36
+
37
+ Again, please generate outputs for these placeholders: {placeholders}
38
+ """.strip()
39
+
40
 
41
  def _make_model(model_name: str, temperature: float) -> genai.GenerativeModel:
42
  return genai.GenerativeModel(
 
57
 
58
 
59
  def generate_variables(
60
+ prompt: str, variable_names: list[str], model_name: str, temperature: float
61
  ) -> dict[str, str]:
62
+ model = _make_model(model_name, temperature)
63
+ output = (
64
+ model.generate_content(
65
+ _GENERATE_VARIABLES_PROMPT.format(placeholders=", ".join(variable_names))
66
+ )
67
+ .text.removeprefix("```json")
68
+ .removesuffix("```")
69
+ )
70
+ return json.loads(output)
71
 
72
 
73
  def run_prompt(prompt_with_variables: str, model_name: str, temperature: float) -> str:
main.py CHANGED
@@ -271,7 +271,7 @@ def app():
271
  with me.box(style=me.Style(grid_column="1 / -2")):
272
  prompt = _find_prompt(state.prompts, state.version)
273
  if prompt:
274
- mex.prompt_eval_table(prompt)
275
 
276
  with mex.icon_sidebar():
277
  if state.mode == "Prompt":
@@ -439,15 +439,15 @@ def on_click_generate_prompt(e: me.ClickEvent):
439
 
440
 
441
  def on_click_generate_variables(e: me.ClickEvent):
442
- """Generates values for the given empty variables.
443
-
444
- TODO: Implement this logic.
445
- """
446
  state = me.state(State)
447
  variable_names = set(_parse_variables(state.prompt))
 
 
 
448
  for name, value in state.prompt_variables.items():
449
- if name in variable_names and not value:
450
- state.prompt_variables[name] = "Generate variable " + name
451
 
452
 
453
  def on_click_mode_toggle(e: me.ClickEvent):
@@ -456,6 +456,13 @@ def on_click_mode_toggle(e: me.ClickEvent):
456
  state.mode = "Eval" if state.mode == "Prompt" else "Prompt"
457
 
458
 
 
 
 
 
 
 
 
459
  # Generic event handlers
460
 
461
 
 
271
  with me.box(style=me.Style(grid_column="1 / -2")):
272
  prompt = _find_prompt(state.prompts, state.version)
273
  if prompt:
274
+ mex.prompt_eval_table(prompt, on_select_rating=on_select_rating)
275
 
276
  with mex.icon_sidebar():
277
  if state.mode == "Prompt":
 
439
 
440
 
441
  def on_click_generate_variables(e: me.ClickEvent):
442
+ """Generates values for the given empty variables."""
 
 
 
443
  state = me.state(State)
444
  variable_names = set(_parse_variables(state.prompt))
445
+ generated_variables = llm.generate_variables(
446
+ state.prompt, variable_names, state.model, state.model_temperature
447
+ )
448
  for name, value in state.prompt_variables.items():
449
+ if name in variable_names and name in generated_variables:
450
+ state.prompt_variables[name] = generated_variables[name]
451
 
452
 
453
  def on_click_mode_toggle(e: me.ClickEvent):
 
456
  state.mode = "Eval" if state.mode == "Prompt" else "Prompt"
457
 
458
 
459
+ def on_select_rating(e: me.SelectSelectionChangeEvent):
460
+ state = me.state(State)
461
+ _, prompt_version, response_index = e.key.split("_")
462
+ prompt = _find_prompt(state.prompts, int(prompt_version))
463
+ prompt.responses[int(response_index)]["rating"] = e.value
464
+
465
+
466
  # Generic event handlers
467
 
468