Skip to content

Commit a6ecfd4

Browse files
authored
Merge pull request #93 from iamseojin/gpt-service-develop
[feat] GPT 파싱 후 레시피 저장 시 재료·단계 저장 로직 및 addIngredient/addStep 헬퍼 메서드 추가
2 parents 11b784a + a122078 commit a6ecfd4

5 files changed

Lines changed: 70 additions & 26 deletions

File tree

yechef/src/main/java/sejong/capston/yechef/domain/Gpt/service/GptService.java

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public RecipeParseResultDto parseRecipe(String rawRecipe) {
4848
4949
],
5050
"steps": [
51-
{ "stepNumber": 1, "action": "<단일 행위>", "description": "<상세 설명>" },
51+
{ "stepNumber": 1, "action": "<단일 행위>", "ingredients": <단계에쓰인재료들>, "description": "<상세 설명>" },
5252
5353
]
5454
}
@@ -60,6 +60,8 @@ public RecipeParseResultDto parseRecipe(String rawRecipe) {
6060
4. 출력은 반드시 JSON만 포함되도록 하세요. 설명이나 마크다운 블록 없이 반환합니다.
6161
5. steps 배열의 각 항목에는 "stepNumber" 필드가 반드시 있어야 하며, 그 값은 **1부터 시작해 1씩 순차 증가**해야 합니다.
6262
절대로 0이거나 중복되면 안 됩니다. (예: 1, 2, 3, …)
63+
6. steps 내부 키 "ingredients": 해당 레시피 단계에 설명(description)에 언급된 재료명만 한국어로 배열에 담아 반환
64+
(수식어 제거, "물" 제외)
6365
""";
6466

6567
// GPT 호출
@@ -95,9 +97,17 @@ public RecipeParseResultDto parseRecipe(String rawRecipe) {
9597
List<RecipeStepDetailDto> steps = new ArrayList<>();
9698
int index = 1;
9799
for (JsonNode stepNode : root.path("steps")) {
100+
101+
// 단계별 재료(문자열 리스트) 추출
102+
List<String> stepIngredients = new ArrayList<>();
103+
for (JsonNode ing : stepNode.path("ingredients")) {
104+
stepIngredients.add(ing.asText());
105+
}
106+
98107
steps.add(RecipeStepDetailDto.builder()
99-
.stepNumber(stepNode.path("order").asInt(index++))
108+
.stepNumber(stepNode.path("stepNumber").asInt(index++))
100109
.action(stepNode.path("action").asText(""))
110+
.ingredients(stepIngredients)
101111
.description(stepNode.path("description").asText(""))
102112
.build()
103113
);
@@ -123,15 +133,15 @@ public String simplePrompt(String prompt) {
123133

124134
public RecipeStepDetailDto parseRecipeSteps(Long recipeId, int stepNumber) {
125135
String systemPrompt = """
126-
You are an AI that converts a single Korean recipe step description into a JSON object.
127-
◆ Input: a Korean sentence describing one cooking step.
128-
◆ Output: pure JSON only, with exactly these fields:
129-
- "stepNumber": the original step number (integer)
130-
- "action": a short English verb phrase describing the action
131-
- "ingredients": a JSON array of English ingredient names mentioned in the description,
132-
with adjectives removed and excluding "water"
133-
No other keys or commentary.
134-
""";
136+
당신은 단일 한국어 조리 단계 설명을 받아 JSON 객체로 변환하는 AI입니다.
137+
◆ 입력: 한국어로 된 하나의 조리 단계 설명 문장
138+
◆ 출력: 순수 JSON만 반환하며, 반드시 아래 세 가지 필드만 포함해야 합니다.
139+
- "stepNumber": 원래 단계 번호 (정수)
140+
- "action": 해당 조리 동작을 짧은 한국어 동사구(예: "썰기", "볶기")로 표현
141+
- "ingredients": 설명에 언급된 재료명만 한국어로 배열에 담아 반환
142+
(수식어 제거, "물" 제외)
143+
그 외의 키, 주석, 설명 문구는 절대 포함하지 마세요!
144+
""";
135145

136146
Recipe recipe = recipeRepository.findById(recipeId)
137147
.orElseThrow(() -> BaseException.from(ErrorCode.RECIPE_NOT_EXIST));
@@ -169,15 +179,16 @@ public RecipeStepDetailDto parseRecipeSteps(Long recipeId, int stepNumber) {
169179

170180
public String parseRecipeStepsTest(Long recipeId, int stepNumber) {
171181
String systemPrompt = """
172-
You are an AI that converts a single Korean recipe step description into a JSON object.
173-
◆ Input: a Korean sentence describing one cooking step.
174-
◆ Output: pure JSON only, with exactly these fields:
175-
- "stepNumber": the original step number (integer)
176-
- "action": a short English verb phrase describing the action
177-
- "ingredients": a JSON array of English ingredient names mentioned in the description,
178-
with adjectives removed and excluding "water"
179-
No other keys or commentary.
180-
""";
182+
당신은 단일 한국어 조리 단계 설명을 받아 JSON 객체로 변환하는 AI입니다.
183+
◆ 입력: 한국어로 된 하나의 조리 단계 설명 문장
184+
◆ 출력: 순수 JSON만 반환하며, 반드시 아래 세 가지 필드만 포함해야 합니다.
185+
- "stepNumber": 원래 단계 번호 (정수)
186+
- "action": 해당 조리 동작을 짧은 한국어 동사구(예: "썰기", "볶기")로 표현
187+
- "ingredients": 설명에 언급된 재료명만 한국어로 배열에 담아 반환
188+
(수식어 제거, "물" 제외)
189+
그 외의 키, 주석, 설명 문구는 절대 포함하지 마세요!
190+
""";
191+
181192

182193
Recipe recipe = recipeRepository.findById(recipeId)
183194
.orElseThrow(() -> BaseException.from(ErrorCode.RECIPE_NOT_EXIST));

yechef/src/main/java/sejong/capston/yechef/domain/Ingredient/Ingredient.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ public Ingredient(
4646
this.recipe = recipe;
4747
}
4848

49+
public static Ingredient of(
50+
String originalName,
51+
String originalAmount,
52+
Recipe recipe
53+
) {
54+
return Ingredient.builder()
55+
.originalName(originalName)
56+
.originalAmount(originalAmount)
57+
.recipe(recipe)
58+
.build();
59+
}
60+
4961
public static Ingredient of(
5062
String originalName,
5163
String alternativeName,

yechef/src/main/java/sejong/capston/yechef/domain/Recipe/Recipe.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ public enum RecipeType {
6565
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
6666
private Image sourceImage;
6767

68+
public void addIngredient(Ingredient ing) {
69+
ingredients.add(ing);
70+
ing.setRecipe(this);
71+
}
72+
73+
public void addStep(RecipeStep step) {
74+
recipeSteps.add(step);
75+
step.setRecipe(this);
76+
}
77+
6878
@Builder
6979
public Recipe(
7080
Long id,

yechef/src/main/java/sejong/capston/yechef/domain/Recipe/service/RecipeService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
import org.springframework.stereotype.Service;
88
import org.springframework.transaction.annotation.Transactional;
99
import org.springframework.web.multipart.MultipartFile;
10+
import sejong.capston.yechef.domain.Gpt.dto.IngredientDto;
1011
import sejong.capston.yechef.domain.Gpt.dto.RecipeParseResultDto;
1112
import sejong.capston.yechef.domain.Gpt.service.GptService;
1213
import sejong.capston.yechef.domain.Image.Image;
1314
import sejong.capston.yechef.domain.Image.repository.ImageRepository;
1415
import sejong.capston.yechef.domain.Image.service.ImageService;
16+
import sejong.capston.yechef.domain.Ingredient.Ingredient;
1517
import sejong.capston.yechef.domain.Ingredient.service.IngredientService;
1618
import sejong.capston.yechef.domain.Member.Member;
1719
import sejong.capston.yechef.domain.Member.repository.MemberRepository;
@@ -24,6 +26,8 @@
2426
import sejong.capston.yechef.domain.Recipe.dto.RecipeDto;
2527
import sejong.capston.yechef.domain.Recipe.dto.RecipeStepDto;
2628
import sejong.capston.yechef.domain.Recipe.repository.RecipeRepository;
29+
import sejong.capston.yechef.domain.RecipeSteps.RecipeStep;
30+
import sejong.capston.yechef.domain.RecipeSteps.dto.RecipeStepDetailDto;
2731
import sejong.capston.yechef.domain.RecipeSteps.service.RecipeStepService;
2832
import sejong.capston.yechef.global.exception.BaseException;
2933
import sejong.capston.yechef.global.exception.error.ErrorCode;
@@ -213,6 +217,17 @@ public Recipe createFromOcr(Long memberId, RecipeParseResultDto dto, Image image
213217
image // 저장된 이미지 그대로 사용
214218
);
215219

220+
// DTO → 엔티티 변환 시
221+
for (IngredientDto ingredientDto : dto.getIngredients()) {
222+
Ingredient ing = Ingredient.of(ingredientDto.getName(), ingredientDto.getQuantity(), recipe);
223+
recipe.addIngredient(ing);
224+
}
225+
for (RecipeStepDetailDto stepDto : dto.getSteps()) {
226+
RecipeStep step = RecipeStep.of(stepDto.getStepNumber(), stepDto.getAction(), stepDto.getIngredients(),
227+
stepDto.getDescription(), recipe);
228+
recipe.addStep(step);
229+
}
230+
216231
recipeRepository.save(recipe);
217232
memberRecipeRepository.save(MemberRecipe.of(member, recipe));
218233
ingredientService.saveIngredients(dto.getIngredients(), recipe);

yechef/src/main/java/sejong/capston/yechef/domain/RecipeSteps/dto/RecipeStepDetailDto.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@ public class RecipeStepDetailDto {
1616
private String description; // “떡 300g을 먹기 좋은 크기로 썬다”
1717
private List<String> ingredients;
1818

19-
public RecipeStepDetailDto(int stepNumber, String action, String description) {
20-
this.stepNumber = stepNumber;
21-
this.action = action;
22-
this.description = description;
23-
}
24-
2519
public static RecipeStepDetailDto from(RecipeStep step) {
2620
return RecipeStepDetailDto.builder()
2721
.stepNumber(step.getStepNumber())
2822
.description(step.getDescription())
23+
.action(step.getAction())
24+
.ingredients(step.getIngredients())
2925
.build();
3026
}
3127

0 commit comments

Comments
 (0)