-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsa_cook.go
More file actions
419 lines (357 loc) · 12 KB
/
sa_cook.go
File metadata and controls
419 lines (357 loc) · 12 KB
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
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
//nolint:lll
package schema
import (
"regexp"
amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
am "github.com/pancsta/asyncmachine-go/pkg/machine"
"github.com/pancsta/secai"
sa "github.com/pancsta/secai/agent_llm/schema"
"github.com/pancsta/secai/examples/cook/states"
"github.com/pancsta/secai/shared"
)
var sp = shared.Sp
var ss = states.CookStates
// ///// ///// /////
// ///// PROMPTS
// ///// ///// /////
// Comments are automatically converted to a jsonschema_description tag.
// RESOURCES DATA
// TODO enum for keys (use state names opt?)
var LLMResources = sa.ParamsGenResources{
Phrases: map[string]string{
"NoCookingNoJokes": "No jokes without cooking.",
"IngredientsPicking": "Tell me what cooking ingredients do you have at hand, we need at least %d to continue.",
"IngredientsPickingEnd": "OK, I have the ingredients I need.",
"RecipePicking": "Ok, let me check my books for what we could make...",
"ResumeNeeded": "You have to Resume for me to do anything.",
"WokenUp": "OK I'm ready to start",
"CookingStarted": "You chose %s as the recipe which we will cook, got it. I will plan all the cooking steps nicely for us. Just press those buttons on the right, once I'm done.",
"CharacterReady": sp(`
Welcome to Cook - your AI-powered cooking assistant! It will help you pick a meal from the ingredients you have, and then you can cook it together (wink wink).
`),
ss.StoryMealReady: "We made it, the meal is ready! You can enjoy it now. I hope you had fun cooking with us.",
"ReqLimitReached": "You have reached the limit of %d requests per session. Please come back later.",
},
}
// TODO static resources
// JOKES
type PromptGenJokes = secai.Prompt[ParamsGenJokes, ResultGenJokes]
func NewPromptGenJokes(agent shared.AgentBaseAPI) *PromptGenJokes {
return secai.NewPrompt[ParamsGenJokes, ResultGenJokes](
agent, ss.GenJokes, `
- You're a database of jokes
`, `
1. Generate the requested amount of jokes.
`, `
- Use the character's personality to make the jokes more interesting.
- Avoid using the same joke twice.
- Avoid jokes about religion, violence, and minors.
- Pick jokes touching the time and / or the profession of the character.
- Ignore the IDs field.
`)
}
type ParamsGenJokes struct {
// The number of jokes to generate.
Amount int
}
type ResultGenJokes struct {
// List of jokes, max 2 sentences each.
Jokes []string
IDs []int64
}
// INGREDIENTS
type PromptIngredientsPicking = secai.Prompt[ParamsIngredientsPicking, ResultIngredientsPicking]
func NewPromptIngredientsPicking(agent shared.AgentBaseAPI) *PromptIngredientsPicking {
return secai.NewPrompt[ParamsIngredientsPicking, ResultIngredientsPicking](
agent, ss.StoryIngredientsPicking, `
- You're a database of cooking ingredients.
`, `
1. Extract the ingredients from the user's prompt.
2. Output the amount per each, assume a default value if not specified.
3. If results are not valid, include a redo message for the user.
4. Include previous ingredients in the result, unless user changes he's mind.
`, `
- Always returns the extracted ingredients, even if the number is less then required.
- Customize the redo message with character's personality.
`)
}
type Ingredient struct {
Name string
Amount int
Unit string
}
type ParamsIngredientsPicking struct {
// The minimum number of ingredients needed.
MinIngredients int
// Text to extract ingredients from.
Prompt string
// List of ingredients extracted from prompts till now.
Ingredients []Ingredient
}
type ResultIngredientsPicking struct {
Ingredients []Ingredient
// A message to be shown to the user if the results are not valid.
RedoMsg string
}
// RECIPE
type PromptRecipePicking = secai.Prompt[ParamsRecipePicking, ResultRecipePicking]
func NewPromptRecipePicking(agent shared.AgentBaseAPI) *PromptRecipePicking {
return secai.NewPrompt[ParamsRecipePicking, ResultRecipePicking](
agent, ss.StoryRecipePicking, `
- You're a database of cooking recipes.
`, `
1. Suggest recipes based on user's ingredients.
2. If possible, find 1 extra recipe, which is well known, but 1-3 ingredients are missing.
3. Summarize the propositions using the character's personality.
`, `
- Limit the amount of recipes to the requested number (excluding the extra recipe).
- Include an image URL per each recipe
`)
}
type Recipe struct {
Name string
Desc string
Steps string
}
type ParamsRecipePicking struct {
// List of available ingredients.
Ingredients []Ingredient
// The number of recipes needed.
Amount int
}
type ResultRecipePicking struct {
// List of proposed recipes
Recipes []Recipe
// Extra recipe with unavailable ingredients.
ExtraRecipe Recipe
// Message to the user, summarizing the recipes. Max 3 sentences.
Summary string
}
// STEPS
type PromptGenSteps = secai.Prompt[ParamsGenSteps, ResultGenSteps]
func NewPromptGenSteps(agent shared.AgentBaseAPI) *PromptGenSteps {
p := secai.NewPrompt[ParamsGenSteps, ResultGenSteps](
agent, ss.GenSteps, `
- You're a cooking process planner.
`, `
1. Extract actionable steps from the cooking recipe and represent them as binary flags called "states". Each step can represent either a long-running action (eg WaterHeatingUp), a short-running action (WaterBoiling), a fact (WaterBoiled). Each state can relate to any other state via Require, Remove, and Add relation.
1. The final and mandatory state is called MealReady.
1. Not all the states have to be connected with relations.
1. Put the time length of procedures (if given) inside Tags as "time:5m" to wait for 5min.
1. Index the steps using a tag "idx:4" for the 5th step in the input. Steps which can't be active at the same time should have Remove relation between them.
Example "make turkish coffee":
- WaterHeatingUp
- Remove: WaterBoiling, WaterBoiled
- Tags
- idx:0
- WaterBoiling
- Remove: WaterHeatingUp, WaterBoiled
- Tags
- idx:0
- WaterBoiled
- Remove: WaterBoiling, WaterHeatingUp
- Tags
- idx:0
- final
- GroundCoffeeInMug
- Tags
- idx:1
- WaterInMug
- Tags
- idx:2
- MealReady
- Auto: true
- Require: GroundCoffeeInMug, WaterInMug
Example "re-heat meal":
- OvenPreheated
- Tags
- idx:0
- MealBaking
- Require: OvenOn
- Tags
- time:5m
- idx:1
- MealBaked
- Tags
- idx:2
- MealReady
- Auto: true
- Require: MealBaked
`, `
2 states CAN'T require and remove each other - these relations are for a single point in time. Skip empty fields (null, false). Start the "idx:" counter from 1. If the same "idx" tag is present for more than 1 state, pick a final state from the same group "idx" group and mark it with a "final" tag (eg WaterBoiled is a final state for WaterBoiling).
`)
// short history
p.HistoryMsgLen = 1
return p
}
type ParamsGenSteps struct {
Recipe Recipe
}
type ResultGenSteps struct {
Schema am.Schema
}
// STEP COMMENTS
type PromptGenStepComments = secai.Prompt[ParamsGenStepComments, ResultGenStepComments]
func NewPromptGenStepComments(agent shared.AgentBaseAPI) *PromptGenStepComments {
return secai.NewPrompt[ParamsGenStepComments, ResultGenStepComments](
agent, ss.GenStepComments, `
- You're a cooking show host.
`, `
1. Comment on each of the provided steps. Keep the tone of the character's personality.
2. Use the full recipe as a context.
`, `
Preserve indexes of the steps in the result.
`)
}
type ParamsGenStepComments struct {
Steps []string
Recipe Recipe
}
type ResultGenStepComments struct {
// Comments for each step.
Comments []string
}
// COOKING
type PromptCookingStarted = secai.Prompt[ParamsCookingStarted, ResultCookingStarted]
func NewPromptCookingStarted(agent shared.AgentBaseAPI) *PromptCookingStarted {
return secai.NewPrompt[ParamsCookingStarted, ResultCookingStarted](
agent, ss.StoryCookingStarted, `
- You're a person who is cooking.
`, `
1. Answer questions about the cooking process. Keep the tone of the character's personality.
2. Use the full recipe and steps as a context.
`, `
Answering is optional. Dont answer rhetorical questions or vague statements. Sometimes simply acknowledge the question.
`)
}
type ParamsCookingStarted struct {
Recipe Recipe
ExtractedSteps []string
}
type ResultCookingStarted struct {
// Max 2 sentences, min 3 words.
Answer string
}
// TEMPLATE
// type PromptTemplate = secai.Prompt[ParamsTemplate, ResultTemplate]
//
// func NewPromptTemplate(agent secai.AgentAPI) *PromptTemplate {
// return secai.NewPrompt[ParamsTemplate, ResultTemplate](
// agent, ssC.Template, `
// - You're a text and speech database.
// `, `
// Translate the provided phrases to the expected form, following the character's personality. Keep a humoristic and entertaining tone of a cooking tv show.
// `, `
// Translate each phrase into 3 different versions. Generate at maximum twice the word count of the original text. Keep the %d and other substitutions in the right place.
// `)
// }
//
// type ParamsTemplate struct {
// Phrases map[string]string
// }
//
// type ResultTemplate struct {
// Phrases map[string][]string
// }
// ///// ///// /////
// ///// STORIES
// ///// ///// /////
var StoryWakingUp = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryWakingUp,
Title: "Waking Up",
Desc: "The waking up story is the bot starting on either cold or warm boot.",
},
Agent: shared.StoryActor{
Trigger: amhelp.Cond{
Not: am.S{ss.Ready},
},
},
}
var MatchSteps = regexp.MustCompile(`^Step`)
var MatchIngredients = regexp.MustCompile("^Ingredient")
var StoryJoke = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryJoke,
Title: "Joke",
Desc: "In this story the bot tells a joke when asked to, based on the character.",
},
// Either 1st time or the current clocks for steps (sum) are equal or greater than ticks of this story's state.
CanActivate: func(s *shared.Story) bool {
mem := s.Memory.Mach
stepStates := mem.StateNamesMatch(MatchSteps)
stepsNow := mem.Time(stepStates).Sum(nil) + s.Epoch
freq := 1.5
// freq := 2.0
return s.Tick == 0 || float64(stepsNow)*freq >= float64(s.Tick)
},
}
var StoryIngredientsPicking = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryIngredientsPicking,
Title: "Ingredients Picking",
Desc: "The bot asks the user what ingredients they have at hand.",
},
Agent: shared.StoryActor{
Trigger: amhelp.Cond{
Is: am.S{ss.Ready},
Not: am.S{ss.IngredientsReady, ss.StoryWakingUp},
},
},
}
var StoryRecipePicking = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryRecipePicking,
Title: "Recipe Picking",
Desc: "The bot offers some recipes, based on the ingredients.",
},
Agent: shared.StoryActor{
Trigger: amhelp.Cond{
Is: am.S{ss.Ready, ss.IngredientsReady},
Not: am.S{ss.RecipeReady},
},
},
}
var StoryCookingStarted = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryCookingStarted,
Title: "Cooking Started",
Desc: "The main story, the bot translates the recipe into actionable steps, then comments while the user completes them.",
},
Agent: shared.StoryActor{
Trigger: amhelp.Cond{
Is: am.S{ss.Ready, ss.RecipeReady},
},
},
Memory: shared.StoryActor{
Trigger: amhelp.Cond{
Not: am.S{states.MemMealReady},
},
},
}
var StoryMealReady = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryMealReady,
Title: "Meal Ready",
Desc: "This story is the end of the flow, the recipe should have been materialized by now.",
},
Memory: shared.StoryActor{
Trigger: amhelp.Cond{
Is: am.S{states.MemMealReady},
},
},
}
var StoryMemoryWipe = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryMemoryWipe,
Title: "Memory Wipe",
Desc: "The bot will clean both short term and long term memory.",
},
}
var StoryStartAgain = &shared.Story{
StoryInfo: shared.StoryInfo{
State: ss.StoryStartAgain,
Title: "Start Again",
Desc: "The session will re-start, keeping the bot's memory.",
},
}