fix: enforce English canonical text in AI-generated dish and recipe data

RecognizeDish() and buildRecipePrompt() were generating text in the
user's language and storing it in base tables, violating the project
rule that base tables always hold English canonical text.

- RecognizeDish(): hardcode English for dish_name; enrichDishInBackground()
  now correctly translates FROM English into all other languages
- buildRecipePrompt(): remove langName lookup, hardcode English for all
  text fields; drop unused locale import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-18 16:57:34 +02:00
parent 39193ec13c
commit 0f533ccaeb
2 changed files with 6 additions and 23 deletions

View File

@@ -7,7 +7,6 @@ import (
"strings" "strings"
"github.com/food-ai/backend/internal/adapters/ai" "github.com/food-ai/backend/internal/adapters/ai"
"github.com/food-ai/backend/internal/infra/locale"
) )
// goalNames maps internal goal codes to English descriptions used in the prompt. // goalNames maps internal goal codes to English descriptions used in the prompt.
@@ -57,18 +56,6 @@ func (c *Client) GenerateRecipes(ctx context.Context, req ai.RecipeRequest) ([]a
} }
func buildRecipePrompt(req ai.RecipeRequest) string { func buildRecipePrompt(req ai.RecipeRequest) string {
lang := req.Lang
if lang == "" {
lang = "en"
}
langName := "English"
for _, l := range locale.Languages {
if l.Code == lang {
langName = l.EnglishName
break
}
}
goal := goalNames[req.UserGoal] goal := goalNames[req.UserGoal]
if goal == "" { if goal == "" {
goal = "weight maintenance" goal = "weight maintenance"
@@ -100,7 +87,7 @@ func buildRecipePrompt(req ai.RecipeRequest) string {
strings.Join(req.AvailableProducts, "\n") + "\n" strings.Join(req.AvailableProducts, "\n") + "\n"
} }
return fmt.Sprintf(`You are a chef and nutritionist. Generate %d recipes in %s. return fmt.Sprintf(`You are a chef and nutritionist. Generate %d recipes in English.
User profile: User profile:
- Goal: %s - Goal: %s
@@ -114,7 +101,7 @@ Requirements for each recipe:
- Include approximate macros per serving - Include approximate macros per serving
IMPORTANT: IMPORTANT:
- All text fields (title, description, ingredient names, units, step descriptions, tags) MUST be in %s. - All text fields (title, description, ingredient names, units, step descriptions, tags) MUST be in English.
- The "image_query" field MUST always be in English (it is used for stock-photo search). - The "image_query" field MUST always be in English (it is used for stock-photo search).
Return ONLY a valid JSON array without markdown or extra text: Return ONLY a valid JSON array without markdown or extra text:
@@ -131,7 +118,7 @@ Return ONLY a valid JSON array without markdown or extra text:
"steps": [{"number": 1, "description": "...", "timer_seconds": null}], "steps": [{"number": 1, "description": "...", "timer_seconds": null}],
"tags": ["..."], "tags": ["..."],
"nutrition_per_serving": {"calories": 420, "protein_g": 48, "fat_g": 12, "carbs_g": 18} "nutrition_per_serving": {"calories": 420, "protein_g": 48, "fat_g": 12, "carbs_g": 18}
}]`, count, langName, goal, req.DailyCalories, restrictions, cuisines, productsSection, perMealCalories, langName) }]`, count, goal, req.DailyCalories, restrictions, cuisines, productsSection, perMealCalories)
} }
func parseRecipesJSON(text string) ([]ai.Recipe, error) { func parseRecipesJSON(text string) ([]ai.Recipe, error) {

View File

@@ -111,11 +111,7 @@ Return ONLY valid JSON without markdown:
// RecognizeDish uses the vision model to identify a dish and estimate its nutritional content. // RecognizeDish uses the vision model to identify a dish and estimate its nutritional content.
// Returns 35 ranked candidates so the user can correct mis-identifications. // Returns 35 ranked candidates so the user can correct mis-identifications.
func (c *Client) RecognizeDish(ctx context.Context, imageBase64, mimeType, lang string) (*ai.DishResult, error) { func (c *Client) RecognizeDish(ctx context.Context, imageBase64, mimeType, lang string) (*ai.DishResult, error) {
langName := langNames[lang] prompt := `You are a dietitian and culinary expert.
if langName == "" {
langName = "English"
}
prompt := fmt.Sprintf(`You are a dietitian and culinary expert.
Look at the dish photo and suggest 3 to 5 possible dishes it could be. Look at the dish photo and suggest 3 to 5 possible dishes it could be.
Even if the first option is obvious, add 24 alternative dishes with lower confidence. Even if the first option is obvious, add 24 alternative dishes with lower confidence.
@@ -128,7 +124,7 @@ For each candidate specify:
Sort candidates by descending confidence. First — most likely. Sort candidates by descending confidence. First — most likely.
Return dish_name values in %s. Return dish_name values in English.
Return ONLY valid JSON without markdown: Return ONLY valid JSON without markdown:
{ {
"candidates": [ "candidates": [
@@ -151,7 +147,7 @@ Return ONLY valid JSON without markdown:
"confidence": 0.65 "confidence": 0.65
} }
] ]
}`, langName) }`
text, err := c.generateVisionContent(ctx, prompt, imageBase64, mimeType) text, err := c.generateVisionContent(ctx, prompt, imageBase64, mimeType)
if err != nil { if err != nil {