ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

take aan instruciton set instead of new line delimiting (#274)

Co-authored-by: paul miller <paul.miller>

authored by

Paul Miller
paul miller
and committed by
GitHub
3f4bb94a 77d7fabf

+24 -56
+19 -7
internal/ai/client.go
··· 148 148 } 149 149 } 150 150 151 - func (c *Client) Regenerate(ctx context.Context, newInstruction string, conversationID string) (*ShoppingList, error) { 151 + func (c *Client) Regenerate(ctx context.Context, instructions []string, conversationID string) (*ShoppingList, error) { 152 152 if conversationID == "" { 153 153 return nil, fmt.Errorf("conversation ID is required for regeneration") 154 154 } 155 155 client := openai.NewClient(option.WithAPIKey(c.apiKey)) 156 + messages := cleanInstuctions(instructions) 156 157 157 158 params := responses.ResponseNewParams{ 158 159 Model: c.model, 159 160 //only new input 160 161 Input: responses.ResponseNewParamsInputUnion{ 161 - OfInputItemList: []responses.ResponseInputItemUnionParam{user(newInstruction)}, 162 + OfInputItemList: messages, 162 163 }, 163 164 Store: openai.Bool(true), 164 165 Conversation: responses.ResponseNewParamsConversationUnion{ ··· 207 208 } 208 209 209 210 // is this dependency on krorger unncessary? just pass in a blob of toml or whatever? same with last recipes? 210 - func (c *Client) GenerateRecipes(ctx context.Context, location *locations.Location, saleIngredients []kroger.Ingredient, instructions string, date time.Time, lastRecipes []string) (*ShoppingList, error) { 211 + func (c *Client) GenerateRecipes(ctx context.Context, location *locations.Location, saleIngredients []kroger.Ingredient, instructions []string, date time.Time, lastRecipes []string) (*ShoppingList, error) { 211 212 messages, err := c.buildRecipeMessages(location, saleIngredients, instructions, date, lastRecipes) 212 213 if err != nil { 213 214 return nil, fmt.Errorf("failed to build recipe messages: %w", err) ··· 248 249 } 249 250 250 251 // buildRecipeMessages creates separate messages for the LLM to process more efficiently 251 - func (c *Client) buildRecipeMessages(location *locations.Location, saleIngredients []kroger.Ingredient, instructions string, date time.Time, lastRecipes []string) (responses.ResponseInputParam, error) { 252 + func (c *Client) buildRecipeMessages(location *locations.Location, saleIngredients []kroger.Ingredient, instructions []string, date time.Time, lastRecipes []string) (responses.ResponseInputParam, error) { 252 253 var messages []responses.ResponseInputItemUnionParam 253 254 //constants we might make variable later 254 255 messages = append(messages, user("Prioritize ingredients that are in season for the current date and user's state location "+date.Format("January 2nd")+" in "+location.State+".")) ··· 279 280 } 280 281 281 282 // Additional user instructions (if any) 282 - if instructions != "" { 283 - messages = append(messages, user(instructions)) 284 - } 283 + 284 + messages = append(messages, cleanInstuctions(instructions)...) 285 285 286 286 return messages, nil 287 287 } ··· 294 294 _, err := client.Models.List(ctx) 295 295 return err 296 296 } 297 + 298 + func cleanInstuctions(instructions []string) []responses.ResponseInputItemUnionParam { 299 + var responses []responses.ResponseInputItemUnionParam 300 + for _, i := range instructions { 301 + i = strings.TrimSpace(i) 302 + if i == "" { 303 + continue 304 + } 305 + responses = append(responses, user(i)) 306 + } 307 + return responses 308 + }
+5 -18
internal/recipes/generator.go
··· 23 23 ) 24 24 25 25 type aiClient interface { 26 - GenerateRecipes(ctx context.Context, location *locations.Location, ingredients []kroger.Ingredient, instructions string, date time.Time, lastRecipes []string) (*ai.ShoppingList, error) 27 - Regenerate(ctx context.Context, newinstruction string, conversationID string) (*ai.ShoppingList, error) 26 + GenerateRecipes(ctx context.Context, location *locations.Location, ingredients []kroger.Ingredient, instructions []string, date time.Time, lastRecipes []string) (*ai.ShoppingList, error) 27 + Regenerate(ctx context.Context, newinstructions []string, conversationID string) (*ai.ShoppingList, error) 28 28 AskQuestion(ctx context.Context, question string, conversationID string) (string, error) 29 29 Ready(ctx context.Context) error 30 30 } ··· 60 60 if p.ConversationID != "" && (p.Instructions != "" || len(p.Saved) > 0 || len(p.Dismissed) > 0) { 61 61 slog.InfoContext(ctx, "Regenerating recipes for location", "location", p.String(), "conversation_id", p.ConversationID) 62 62 // these should both always be true. Warn if not because its a caching bug? 63 - instructions := p.Instructions 63 + instructions := []string{p.Instructions} 64 64 var dismissedTitles []string 65 65 for _, dismissed := range p.Dismissed { 66 66 dismissedTitles = append(dismissedTitles, dismissed.Title) 67 67 } 68 68 if len(p.Dismissed) > 0 { 69 - instructions += " Did not like " + strings.Join(dismissedTitles, "; ") 69 + instructions = append(instructions, "Did not like "+strings.Join(dismissedTitles, "; ")) 70 70 } 71 71 // TODO pipe through dismissed and saved so we dont mess with instructions. Also format dismissed titles with toon? 72 72 shoppingList, err := g.aiClient.Regenerate(ctx, instructions, p.ConversationID) ··· 91 91 return nil, fmt.Errorf("failed to get staples: %w", err) 92 92 } 93 93 94 - instructions := mergeInstructions(p.Directive, p.Instructions) 94 + instructions := []string{p.Directive, p.Instructions} 95 95 96 96 shoppingList, err := g.aiClient.GenerateRecipes(ctx, p.Location, ingredients, instructions, p.Date, p.LastRecipes) 97 97 if err != nil { ··· 106 106 p.ConversationID = shoppingList.ConversationID 107 107 slog.InfoContext(ctx, "generated chat", "location", p.String(), "duration", time.Since(start), "hash", hash) 108 108 return shoppingList, nil 109 - } 110 - 111 - func mergeInstructions(directive string, instructions string) string { 112 - directive = strings.TrimSpace(directive) 113 - instructions = strings.TrimSpace(instructions) 114 - 115 - if directive == "" { 116 - return instructions 117 - } 118 - if instructions == "" { 119 - return directive 120 - } 121 - return directive + "\n\n" + instructions 122 109 } 123 110 124 111 func (g *Generator) AskQuestion(ctx context.Context, question string, conversationID string) (string, error) {
-31
internal/recipes/server_prompt_test.go
··· 1 - package recipes 2 - 3 - import ( 4 - "testing" 5 - ) 6 - 7 - func TestMergeInstructions(t *testing.T) { 8 - t.Run("profile only", func(t *testing.T) { 9 - got := mergeInstructions("Always include one vegetarian meal.", "") 10 - want := "Always include one vegetarian meal." 11 - if got != want { 12 - t.Fatalf("expected %q, got %q", want, got) 13 - } 14 - }) 15 - 16 - t.Run("request only", func(t *testing.T) { 17 - got := mergeInstructions("", "No shellfish") 18 - want := "No shellfish" 19 - if got != want { 20 - t.Fatalf("expected %q, got %q", want, got) 21 - } 22 - }) 23 - 24 - t.Run("profile and request", func(t *testing.T) { 25 - got := mergeInstructions("Always include one vegetarian meal.", "No shellfish") 26 - want := "Always include one vegetarian meal.\n\nNo shellfish" 27 - if got != want { 28 - t.Fatalf("expected %q, got %q", want, got) 29 - } 30 - }) 31 - }