ai cooking
0
fork

Configure Feed

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

Getting simpler wine style (#314)

* some normalization

* more promptengineering

authored by

Paul Miller and committed by
GitHub
3926000b f40ac99e

+97 -8
+61 -8
internal/ai/client.go
··· 125 125 126 126 # Output Format 127 127 - Each recipe includes: 128 - - Title 129 - - Description: Try to sell the dish and add some flair. 130 - - Estimated cook time (for example: "35 minutes") 131 - - Estimated total cost in dollars (for example: "$18-24") 132 - - Ingredient list: should include quantities and price if in input. 128 + - title: A short catchy name for the dish. 129 + - description: Try to sell the dish and add some flair. 130 + - cook_time: Estimated cook time (for example: "35 minutes") 131 + - cost_estimate: Estimated total cost in dollars (for example: "$18-24") 132 + - instructions: should include quantities and price if in input. 133 133 - Step-by-step instructions starting with prep. Don't prefix with numbers. 134 - - Estimated Calorie count and other nutrient health tips. 135 - - Optional wine pairing guidance. 136 - - Two or less simple, brief wine styles that can be searched for. No embelishment in this field. 134 + - health: Estimated Calorie count and other nutrient health tips. 135 + - drink_pairing: the wine pairing suggestion mentioned in instructions 136 + - wine_styles: Two or fewer consumer-recognizable wine styles for search (for example: "Pinot Noir", "Sauvignon Blanc", "Cabernet Sauvignon"). 137 + - wine_styles must only contain searchable style names: no regions, no parenthetical notes, no commas, no "or", no "*-style blend" phrasing. 137 138 138 139 # Planning & Verification 139 140 - Reference your checklist to ensure variety in cooking methods and cuisines ··· 146 147 if err := json.Unmarshal([]byte(resp.OutputText()), &shoppingList); err != nil { 147 148 return nil, fmt.Errorf("failed to parse AI response: %w", err) 148 149 } 150 + normalizeWineStyles(&shoppingList) 149 151 if resp.Conversation.ID == "" { 150 152 return nil, fmt.Errorf("failed to get conversation ID") 151 153 } ··· 369 371 } 370 372 return responses 371 373 } 374 + 375 + func normalizeWineStyles(shoppingList *ShoppingList) { 376 + if shoppingList == nil { 377 + return 378 + } 379 + for i := range shoppingList.Recipes { 380 + shoppingList.Recipes[i].WineStyles = normalizeRecipeWineStyles(shoppingList.Recipes[i].WineStyles) 381 + } 382 + } 383 + 384 + func normalizeRecipeWineStyles(styles []string) []string { 385 + if len(styles) == 0 { 386 + return nil 387 + } 388 + cleaned := make([]string, 0, min(len(styles), 2)) 389 + seen := map[string]struct{}{} 390 + for _, style := range styles { 391 + normalized := normalizeWineStyle(style) 392 + if normalized == "" { 393 + continue 394 + } 395 + key := strings.ToLower(normalized) 396 + if _, ok := seen[key]; ok { 397 + continue 398 + } 399 + seen[key] = struct{}{} 400 + cleaned = append(cleaned, normalized) 401 + if len(cleaned) == 2 { 402 + break 403 + } 404 + } 405 + if len(cleaned) == 0 { 406 + return nil 407 + } 408 + return cleaned 409 + } 410 + 411 + func normalizeWineStyle(style string) string { 412 + style = strings.TrimSpace(style) 413 + if style == "" { 414 + return "" 415 + } 416 + if idx := strings.IndexAny(style, "(["); idx >= 0 { 417 + style = strings.TrimSpace(style[:idx]) 418 + } 419 + style = strings.TrimSpace(strings.TrimSuffix(style, ".")) 420 + if style == "" { 421 + return "" 422 + } 423 + return strings.Join(strings.Fields(style), " ") 424 + }
+36
internal/ai/recipe_test.go
··· 1 1 package ai 2 2 3 3 import ( 4 + "slices" 4 5 "testing" 5 6 ) 6 7 ··· 56 57 t.Fatalf("expected hash length of 24, got %d", len(hash)) 57 58 } 58 59 } 60 + 61 + func TestNormalizeWineStyle(t *testing.T) { 62 + tests := []struct { 63 + name string 64 + in string 65 + want string 66 + }{ 67 + {name: "plain style", in: "Pinot Noir", want: "Pinot Noir"}, 68 + {name: "parenthetical region hint", in: "Sauvignon Blanc (New Zealand or Loire)", want: "Sauvignon Blanc"}, 69 + {name: "trailing punctuation", in: " Riesling. ", want: "Riesling"}, 70 + {name: "bracket hint", in: "Chardonnay [California]", want: "Chardonnay"}, 71 + {name: "empty", in: " ", want: ""}, 72 + } 73 + 74 + for _, tc := range tests { 75 + t.Run(tc.name, func(t *testing.T) { 76 + if got := normalizeWineStyle(tc.in); got != tc.want { 77 + t.Fatalf("normalizeWineStyle(%q): got %q want %q", tc.in, got, tc.want) 78 + } 79 + }) 80 + } 81 + } 82 + 83 + func TestNormalizeRecipeWineStyles(t *testing.T) { 84 + got := normalizeRecipeWineStyles([]string{ 85 + " Pinot Noir (WA or Oregon) ", 86 + "pinot noir", 87 + "Sauvignon Blanc (New Zealand or Loire)", 88 + "Riesling", 89 + }) 90 + want := []string{"Pinot Noir", "Sauvignon Blanc"} 91 + if !slices.Equal(got, want) { 92 + t.Fatalf("unexpected normalized wine styles: got %#v want %#v", got, want) 93 + } 94 + }