-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimprov.py
executable file
·342 lines (260 loc) · 8.95 KB
/
improv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#!python3
import json
import sys
import time
from typing import List
import typer
from icecream import ic
from openai import OpenAI
from pydantic import BaseModel
from rich.console import Console
from rich.text import Text
from openai_wrapper import choose_model, num_tokens_from_string, setup_gpt
client = OpenAI()
# TODO consider moving this to openai_wrapper
# make a fastapi app called server
console = Console()
# By default, when you hit C-C in a pipe, the pipe is stopped
# with this, pipe continues
def keep_pipe_alive_on_control_c(sig, frame):
sys.stdout.write(
"\nInterrupted with Control+C, but I'm still writing to stdout...\n"
)
sys.exit(0)
# Load your API key from an environment variable or secret management service
gpt_model = setup_gpt()
app = typer.Typer(no_args_is_help=True)
def ask_gpt(
prompt_to_gpt="Make a rhyme about Dr. Seuss forgetting to pass a default paramater",
tokens: int = 0,
u4=True,
debug=False,
):
return ask_gpt_n(prompt_to_gpt, tokens=tokens, u4=u4, debug=debug, n=1)[0]
def ask_gpt_n(
prompt_to_gpt="Make a rhyme about Dr. Seuss forgetting to pass a default paramater",
tokens: int = 0,
u4=True,
debug=False,
n=1,
):
text_model_best, tokens = choose_model(u4)
messages = [
{"role": "system", "content": "You are a really good improv coach."},
{"role": "user", "content": prompt_to_gpt},
]
input_tokens = num_tokens_from_string(prompt_to_gpt, "cl100k_base") + 100
output_tokens = tokens - input_tokens
if debug:
ic(text_model_best)
ic(tokens)
ic(input_tokens)
ic(output_tokens)
start = time.time()
responses = n
response_contents = ["" for x in range(responses)]
for chunk in client.chat.completions.create(
model=text_model_best,
messages=messages,
max_tokens=output_tokens,
n=responses,
temperature=0.7,
stream=True,
):
if "choices" not in chunk:
continue
for elem in chunk["choices"]: # type: ignore
delta = elem["delta"]
delta_content = delta.get("content", "")
response_contents[elem["index"]] += delta_content
if debug:
out = f"All chunks took: {int((time.time() - start)*1000)} ms"
ic(out)
# hard code to only return first response
return response_contents
class Fragment(BaseModel):
player: str
text: str
reasoning: str = ""
def __str__(self):
if self.reasoning:
return f'Fragment("{self.player}", "{self.text}", "{self.reasoning}")'
else:
return f'Fragment("{self.player}", "{self.text}")'
def __repr__(self):
return str(self)
# a static constructor that takes positional arguments
@staticmethod
def Pos(player, text, reasoning=""):
return Fragment(player=player, text=text, reasoning=reasoning)
default_story_start = [
Fragment.Pos("coach", "Once upon a time", "A normal story start"),
]
def print_story(story: List[Fragment], show_story: bool):
# Split on '.', but only if there isn't a list
coach_color = "bold bright_cyan"
user_color = "bold yellow"
def wrap_color(s, color):
text = Text(s)
text.stylize(color)
return text
def get_color_for(fragment):
if fragment.player == "coach":
return coach_color
elif fragment.player == "student":
return user_color
else:
return "white"
console.clear()
if show_story:
console.print(story)
console.rule()
for fragment in story:
s = fragment.text
split_line = len(s.split(".")) == 2
# assume it only contains 1, todo handle that
if split_line:
end_sentance, new_sentance = s.split(".")
console.print(
wrap_color(f" {end_sentance}.", get_color_for(fragment)), end=""
)
console.print(
wrap_color(f"{new_sentance}", get_color_for(fragment)), end=""
)
continue
console.print(wrap_color(f" {s}", get_color_for(fragment)), end="")
# if (s.endswith(".")):
# rich_print(s)
example_1_in = [
Fragment.Pos("coach", "Once upon a time", "A normal story start"),
Fragment.Pos("student", "there lived "),
Fragment.Pos("coach", "a shrew named", "using shrew to make it intereting"),
Fragment.Pos("student", "Sarah. Every day the shrew"),
]
example_1_out = example_1_in + [
Fragment.Pos(
"coach", "smelled something that reminded her ", "give user a good offer"
)
]
example_2_in = [
Fragment.Pos(
"coach", "Once Upon a Time within ", "A normal story start, with a narrowing"
),
Fragment.Pos("student", "there lived a donkey"),
Fragment.Pos("coach", "who liked to eat", "add some color"),
Fragment.Pos("student", "Brocolli. Every"),
]
example_2_out = example_2_in + [
Fragment.Pos("coach", "day the donkey", "continue in the format"),
]
def prompt_gpt_to_return_json_with_story_and_an_additional_fragment_as_json(
story_so_far: List[Fragment],
):
# convert story to json
story_so_far = json.dumps(story_so_far, default=lambda x: x.__dict__)
return f"""
You are a professional improv performer and coach. Help me improve my improv skills through doing practice.
We're playing a game where we write a story together.
The story should have the following format
- Once upon a time
- Every day
- But one day
- Because of that
- Because of that
- Until finally
- And ever since then
The story should be creative and funny
I'll write 1-5 words, and then you do the same, and we'll go back and forth writing the story.
The story is expressed as a json, I will pass in json, and you add the coach line to the json.
You will add a third field as to why you added those words in the line
Only add a single coach field to the output
You can correct spelling and capilization mistakes
The below strings are python strings, so if using ' quotes, ensure to escape them properly
Example 1 Input:
{example_1_in}
Example 1 Output:
{example_1_out}
--
Example 2 Input:
{example_2_in}
Example 2 Output:
{example_2_out}
--
Now, here is the story we're doing together. Add the next coach fragment to the story, and correct spelling and grammer mistakes in the fragments
--
Actual Input:
{story_so_far}
Ouptut:
"""
def get_user_input():
console.print("[yellow] >>[/yellow]", end="")
return input()
@app.command()
def json_objects(
debug: bool = typer.Option(False),
u4: bool = typer.Option(False),
):
"""
Play improv with GPT, prompt it to extend the story, but story is passed back and forth as json
"""
story = default_story_start
while True:
print_story(story, show_story=True)
user_says = get_user_input()
story += [Fragment(player="student", text=user_says)]
prompt = (
prompt_gpt_to_return_json_with_story_and_an_additional_fragment_as_json(
story
)
)
json_version_of_a_story = ask_gpt(
prompt_to_gpt=prompt,
debug=debug,
u4=u4,
)
# convert json_version_of_a_story to a list of fragments
# Damn - Copilot wrote this code, and it's right (or so I think)
story = json.loads(json_version_of_a_story, object_hook=lambda d: Fragment(**d))
if debug:
ic(json_version_of_a_story)
ic(story)
input("You can inspect, and then press enter to continue")
@app.command()
def text(
debug: bool = typer.Option(False),
u4: bool = typer.Option(True),
):
"""
Play improv with GPT, prompt it to extend the story, where story is the text of the story so far.
"""
prompt = """
You are a professional improv performer and coach. Help me improve my improv skills through doing practice.
We're playing a game where we write a story together.
The story should have the following format
- Once upon a time
- Every day
- But one day
- Because of that
- Because of that
- Until finally
- And ever since then
The story should be creative and funny
I'll write 1-5 words, and then you do the same, and we'll go back and forth writing the story.
When you add words to the story, don't add more then 5 words, and stop in the middle of the sentance (that makes me be more creative)
The story we've written together so far is below and I wrote the last 1 to 5 words,
now add your words to the story (NEVER ADD MORE THEN 5 WORDS):
--
"""
story = [] # (isCoach, word)
while True:
if debug:
ic(prompt)
coach_says = ask_gpt(prompt_to_gpt=prompt, debug=debug, u4=u4)
story += [Fragment.Pos("coach", coach_says)]
prompt += coach_says
print_story(story, show_story=False)
user_says = get_user_input()
prompt += f" {user_says} "
story += [Fragment.Pos("student", user_says)]
if __name__ == "__main__":
app()