Spaces:
Running
Running
Richard
commited on
Commit
·
a65fd1a
1
Parent(s):
4be2fb0
Add more improvements
Browse files- Variable generation
- Ratings
- Dockerfile
- Hugging Face meta
- Dockerfile +33 -0
- README.md +9 -0
- components/table.py +73 -17
- llm.py +28 -3
- 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="#
|
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 "#
|
|
|
|
|
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,
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
me.
|
74 |
-
|
75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
43 |
) -> dict[str, str]:
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
450 |
-
state.prompt_variables[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 |
|