214 lines
9.4 KiB
C#
214 lines
9.4 KiB
C#
using Fitness_core_BE.Helpers;
|
|
using System.Text.Json;
|
|
|
|
namespace Fitness_core_BE.AI
|
|
{
|
|
public class MealPlanOrchestrator
|
|
{
|
|
private readonly IAiClient _aiClient;
|
|
private readonly ModificationGuard _guard;
|
|
|
|
public MealPlanOrchestrator( IAiClient aiClient, ModificationGuard guard)
|
|
{
|
|
_aiClient = aiClient;
|
|
_guard = guard;
|
|
}
|
|
|
|
public async Task<object> GeneratePlanAsync(object payload)
|
|
{
|
|
var data = DeserializePayload.Deserialize(payload);
|
|
|
|
decimal bmi = DataExtractor.GetRequiredDecimal(data, "bmi");
|
|
string country = DataExtractor.GetRequiredString(data, "country");
|
|
string province = DataExtractor.GetRequiredString(data, "province");
|
|
string goal = DataExtractor.GetRequiredString(data, "goal");
|
|
decimal budget = DataExtractor.GetRequiredDecimal(data, "budgetPerWeek");
|
|
int mealsPerDay = DataExtractor.GetRequiredInt(data, "mealsPerDay");
|
|
bool isVegetarian = DataExtractor.GetRequiredBool(data, "isVegetarian");
|
|
|
|
// Basic validation
|
|
if (bmi < 10 || bmi > 60)
|
|
throw new Exception("Invalid BMI");
|
|
|
|
var systemPrompt = @"
|
|
You are a certified nutritionist.
|
|
Your response must be STRICTLY a JSON object.
|
|
|
|
Output format rules:
|
|
1. The first character of your response MUST be {
|
|
2. The last character MUST be }
|
|
3. Do not include ``` or any markdown formatting
|
|
4. Do not include any explanation text
|
|
5. Do not include comments
|
|
6. Do not prefix with ""Here is""
|
|
7. Do not suffix with any text
|
|
|
|
Return ONLY valid JSON.
|
|
|
|
Structure:
|
|
{
|
|
name: string,
|
|
description: string,
|
|
mealItems: [
|
|
{
|
|
name: string,
|
|
description: string,
|
|
calories: number,
|
|
protein: number,
|
|
carbohydrates: number,
|
|
fats: number,
|
|
servingSize: string,
|
|
mealItems: [
|
|
{ food: string, quantity: string }
|
|
],
|
|
preparationNotes: string
|
|
}
|
|
]
|
|
}
|
|
";
|
|
|
|
var jsonResponse = await _aiClient.GenerateJsonAsync(
|
|
systemPrompt,
|
|
data
|
|
);
|
|
|
|
// Validate JSON before returning
|
|
try
|
|
{
|
|
var parsed = JsonSerializer.Deserialize<object>(jsonResponse);
|
|
if (parsed == null)
|
|
throw new Exception("Invalid AI JSON");
|
|
}
|
|
catch
|
|
{
|
|
throw new Exception("AI returned invalid JSON structure");
|
|
}
|
|
|
|
return JsonSerializer.Deserialize<object>(jsonResponse)!;
|
|
}
|
|
|
|
public async Task<object> UpdatePlanAsync(object payload)
|
|
{
|
|
var data = DeserializePayload.Deserialize(payload);
|
|
|
|
if (!data.ContainsKey("currentPlan"))
|
|
throw new Exception("Current plan missing");
|
|
|
|
string modification =
|
|
DataExtractor.GetRequiredString(data, "modification");
|
|
|
|
_guard.Validate(modification);
|
|
|
|
var systemPrompt = @"
|
|
You are a professional nutrition AI.
|
|
|
|
You will receive:
|
|
1) The current meal plan in valid JSON format.
|
|
2) A user modification request in free text.
|
|
|
|
Your task:
|
|
- Carefully analyze the modification request.
|
|
- Modify ONLY the specific elements explicitly requested by the user.
|
|
- Do NOT change anything else.
|
|
|
|
VERY IMPORTANT RULES:
|
|
- Do NOT change the plan name unless the user explicitly asks.
|
|
- Do NOT change the number of meals unless requested.
|
|
- Do NOT modify calories, protein, carbs, fats unless the request requires it.
|
|
- Do NOT rewrite descriptions unless necessary for the modification.
|
|
- Do NOT change serving sizes unless required.
|
|
- Do NOT add or remove meals unless explicitly requested.
|
|
- Do NOT modify ingredients that are unrelated to the request.
|
|
- Preserve every unchanged value exactly as provided.
|
|
|
|
Scope control:
|
|
If the request only affects one ingredient, update only that ingredient.
|
|
If the request does not affect the plan, return it unchanged.
|
|
|
|
Preserve the exact internal structure of the meal plan.
|
|
Do NOT rename fields.
|
|
Do NOT remove required properties.
|
|
Do NOT change the JSON schema.
|
|
|
|
Return your response strictly in this JSON format:
|
|
|
|
{
|
|
""reason"": string,
|
|
""modified"": boolean,
|
|
""currentPlan"": {
|
|
""name"": string,
|
|
""description"": string,
|
|
""mealItems"": [
|
|
{
|
|
""name"": string,
|
|
""description"": string,
|
|
""calories"": number,
|
|
""protein"": number,
|
|
""carbohydrates"": number,
|
|
""fats"": number,
|
|
""servingSize"": string,
|
|
""mealItems"": [
|
|
{ ""food"": string, ""quantity"": string }
|
|
],
|
|
""preparationNotes"": string
|
|
}
|
|
]
|
|
},
|
|
""updatedPlan"": {
|
|
""name"": string,
|
|
""description"": string,
|
|
""mealItems"": [
|
|
{
|
|
""name"": string,
|
|
""description"": string,
|
|
""calories"": number,
|
|
""protein"": number,
|
|
""carbohydrates"": number,
|
|
""fats"": number,
|
|
""servingSize"": string,
|
|
""mealItems"": [
|
|
{ ""food"": string, ""quantity"": string }
|
|
],
|
|
""preparationNotes"": string
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
STRICT OUTPUT RULES:
|
|
- Return ONLY valid raw JSON.
|
|
- No markdown.
|
|
- No explanations.
|
|
- First character must be {.
|
|
- Last character must be }.
|
|
- No trailing commas.
|
|
- No extra commentary.
|
|
|
|
If modification cannot be applied:
|
|
- Set ""modified"": false
|
|
- Keep currentPlan and updatedPlan identical.
|
|
- Explain reason clearly.
|
|
";
|
|
var jsonResponse = await _aiClient.GenerateJsonAsync(
|
|
systemPrompt,
|
|
data
|
|
);
|
|
|
|
try
|
|
{
|
|
var parsed = JsonSerializer.Deserialize<object>(jsonResponse);
|
|
if (parsed == null)
|
|
throw new Exception("Invalid JSON");
|
|
}
|
|
catch
|
|
{
|
|
throw new Exception("AI returned invalid JSON");
|
|
}
|
|
|
|
return JsonSerializer.Deserialize<object>(jsonResponse)!;
|
|
}
|
|
|
|
}
|
|
|
|
}
|