Requesting 21 full recipes in one prompt exceeds the token budget and returns 403 Forbidden. Replace the single-call approach with three concurrent GenerateRecipes calls (breakfast ×7, lunch ×7, dinner ×7), then assemble the 7-day plan from the results. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
2.6 KiB
Go
98 lines
2.6 KiB
Go
package gemini
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"sync"
|
||
)
|
||
|
||
// MenuRequest contains parameters for weekly menu generation.
|
||
type MenuRequest struct {
|
||
UserGoal string
|
||
DailyCalories int
|
||
Restrictions []string
|
||
CuisinePrefs []string
|
||
AvailableProducts []string
|
||
}
|
||
|
||
// DayPlan is the AI-generated plan for a single day.
|
||
type DayPlan struct {
|
||
Day int `json:"day"`
|
||
Meals []MealEntry `json:"meals"`
|
||
}
|
||
|
||
// MealEntry is a single meal within a day plan.
|
||
type MealEntry struct {
|
||
MealType string `json:"meal_type"` // breakfast | lunch | dinner
|
||
Recipe Recipe `json:"recipe"`
|
||
}
|
||
|
||
// GenerateMenu produces a 7-day × 3-meal plan by issuing three parallel
|
||
// GenerateRecipes calls (one per meal type). This avoids token-limit errors
|
||
// that arise from requesting 21 full recipes in a single prompt.
|
||
func (c *Client) GenerateMenu(ctx context.Context, req MenuRequest) ([]DayPlan, error) {
|
||
type mealSlot struct {
|
||
mealType string
|
||
fraction float64 // share of daily calories
|
||
}
|
||
|
||
slots := []mealSlot{
|
||
{"breakfast", 0.25},
|
||
{"lunch", 0.40},
|
||
{"dinner", 0.35},
|
||
}
|
||
|
||
type mealResult struct {
|
||
recipes []Recipe
|
||
err error
|
||
}
|
||
|
||
results := make([]mealResult, len(slots))
|
||
var wg sync.WaitGroup
|
||
|
||
for i, slot := range slots {
|
||
wg.Add(1)
|
||
go func(idx int, mealType string, fraction float64) {
|
||
defer wg.Done()
|
||
// Scale daily calories to what this meal should contribute.
|
||
mealCal := int(float64(req.DailyCalories) * fraction)
|
||
r, err := c.GenerateRecipes(ctx, RecipeRequest{
|
||
UserGoal: req.UserGoal,
|
||
DailyCalories: mealCal * 3, // prompt divides by 3 internally
|
||
Restrictions: req.Restrictions,
|
||
CuisinePrefs: req.CuisinePrefs,
|
||
Count: 7,
|
||
AvailableProducts: req.AvailableProducts,
|
||
})
|
||
results[idx] = mealResult{r, err}
|
||
}(i, slot.mealType, slot.fraction)
|
||
}
|
||
wg.Wait()
|
||
|
||
for i, res := range results {
|
||
if res.err != nil {
|
||
return nil, fmt.Errorf("generate %s: %w", slots[i].mealType, res.err)
|
||
}
|
||
if len(res.recipes) == 0 {
|
||
return nil, fmt.Errorf("no %s recipes returned", slots[i].mealType)
|
||
}
|
||
// Pad to exactly 7 by repeating the last recipe.
|
||
for len(results[i].recipes) < 7 {
|
||
results[i].recipes = append(results[i].recipes, results[i].recipes[len(results[i].recipes)-1])
|
||
}
|
||
}
|
||
|
||
days := make([]DayPlan, 7)
|
||
for day := range 7 {
|
||
days[day] = DayPlan{
|
||
Day: day + 1,
|
||
Meals: []MealEntry{
|
||
{MealType: slots[0].mealType, Recipe: results[0].recipes[day]},
|
||
{MealType: slots[1].mealType, Recipe: results[1].recipes[day]},
|
||
{MealType: slots[2].mealType, Recipe: results[2].recipes[day]},
|
||
},
|
||
}
|
||
}
|
||
return days, nil
|
||
}
|