first commit
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
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)!;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user