일단 기록용으로 Ai 파트 짠 코드들 나열부터 하겠습니다
AiController.java
package com.p1.nomnom.ai.controller;
import com.p1.nomnom.ai.dto.request.AiRequestDto;
import com.p1.nomnom.ai.dto.response.AiResponseDto;
import com.p1.nomnom.ai.entity.Ai;
import com.p1.nomnom.ai.service.AiService;
import com.p1.nomnom.store.service.StoreService;
import com.p1.nomnom.ai.GeminiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/api/nom/ai")
public class AiController {
private final AiService aiService;
private final GeminiService geminiService;
private final StoreService storeService; // StoreService를 추가합니다.
@Autowired
public AiController(AiService aiService, GeminiService geminiService, StoreService storeService) {
this.aiService = aiService;
this.geminiService = geminiService;
this.storeService = storeService; // StoreService 주입
}
// AI 상품 설명 자동 생성
@PostMapping("/foods/description")
public AiResponseDto generateFoodDescription(@RequestBody AiRequestDto requestDto) {
try {
// Gemini로 텍스트 생성 요청 (question, descriptionHint, keyword를 포함)
String generatedDescription = geminiService.generateContent(
requestDto.getQuestion(),
requestDto.getDescriptionHint(),
requestDto.getKeyword()
);
// storeId로 storeName을 찾기
String storeName = storeService.getStoreNameById(requestDto.getStoreId());
// AI 응답을 DB에 저장
Ai aiEntity = new Ai();
aiEntity.setQuestion(requestDto.getQuestion()); // 받아온 질문을 저장
aiEntity.setAnswer(generatedDescription);
aiEntity.setFoodName(requestDto.getFoodName());
aiEntity.setDescriptionHint(requestDto.getDescriptionHint());
aiEntity.setKeyword(requestDto.getKeyword());
aiEntity.setStoreId(requestDto.getStoreId()); // store_id는 UUID로 변환
aiService.save(aiEntity); // DB에 저장 aiService - save 메서드
// 응답 객체 반환
return new AiResponseDto(
aiEntity.getQuestion(),
aiEntity.getFoodName(),
aiEntity.getStoreId(),
storeName,
aiEntity.getDescriptionHint(),
aiEntity.getKeyword(),
generatedDescription
);
} catch (Exception e) {
e.printStackTrace();
return new AiResponseDto("Error generating description", "", null, "", "", "", "Error generating description");
}
}
}
AiRequestDto.java
package com.p1.nomnom.ai.dto.request;
import lombok.Getter;
import lombok.Setter;
import java.util.UUID;
@Getter
@Setter
public class AiRequestDto {
private String question;
private String foodName;
private String descriptionHint;
private String keyword;
private UUID storeId; // storeId 추가
}
AiResponseDto.java
package com.p1.nomnom.ai.dto.response;
import lombok.Getter;
import lombok.Setter;
import java.util.UUID;
@Getter
@Setter
public class AiResponseDto {
private String question; // AI가 받은 질문
private String foodName; // 음식 이름
private UUID storeId; // storeId (UUID 형식)
private String storeName; // storeName - 입력받은 storeId로 이름 찾아오기
private String descriptionHint; // 설명 힌트
private String keyword; // 설명 키워드
private String generatedDescription; // AI가 생성한 설명
// 기본 생성자 추가
public AiResponseDto() {
}
// 생성자 추가
public AiResponseDto(String question, String foodName, UUID storeId, String storeName,
String descriptionHint, String keyword, String generatedDescription) {
this.question = question;
this.foodName = foodName;
this.storeId = storeId;
this.storeName = storeName;
this.descriptionHint = descriptionHint;
this.keyword = keyword;
this.generatedDescription = generatedDescription;
}
}
Ai.java
package com.p1.nomnom.ai.entity;
import com.p1.nomnom.common.entity.BaseEntity;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import java.util.UUID;
@Getter
@Setter
@Entity
@Table(name = "p_ai")
public class Ai extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
@Column(name = "question", nullable = false, columnDefinition = "TEXT")
private String question;
@Column(name = "answer", nullable = false, columnDefinition = "TEXT")
private String answer;
@Column(name = "food_name", nullable = false, columnDefinition = "TEXT")
private String foodName;
@Column(name = "store_id")
private UUID storeId;
@Column(name = "description_hint", columnDefinition = "TEXT")
private String descriptionHint;
@Column(name = "keyword", columnDefinition = "TEXT")
private String keyword;
}
AiRepository.java
package com.p1.nomnom.ai.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.p1.nomnom.ai.entity.Ai;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.UUID;
@Repository
public interface AiRepository extends JpaRepository<Ai, UUID> {
// 질문을 포함하고 storeId로 검색하는 메소드
Page<Ai> findByQuestionContainingAndStoreId(String question, UUID storeId, Pageable pageable);
// storeId로만 검색하는 메소드
Page<Ai> findAllByStoreId(UUID storeId, Pageable pageable);
}
AiService.java -> Interface로 분리해서 Service 파일을 두개로 쓰는게 훨씬 깔끔한듯 해요 어떤게 있나 확인할때도 AiService 인터페이스랑 레포지토리랑 컨트롤러만 보면 구성을 알수있으니
package com.p1.nomnom.ai.service;
import com.p1.nomnom.ai.dto.request.AiRequestDto;
import com.p1.nomnom.ai.dto.response.AiResponseDto;
import com.p1.nomnom.ai.entity.Ai;
import org.springframework.data.domain.Sort;
import java.util.List;
import java.util.UUID;
public interface AiService {
AiResponseDto getAiAnswer(AiRequestDto requestDto);
void save(Ai aiEntity);
List<AiResponseDto> searchAi(UUID storeId, String question, int pageSize, Sort sort);
}
AiServiceImpl.java
package com.p1.nomnom.ai.service;
import com.p1.nomnom.ai.dto.request.AiRequestDto;
import com.p1.nomnom.ai.dto.response.AiResponseDto;
import com.p1.nomnom.ai.entity.Ai;
import com.p1.nomnom.ai.repository.AiRepository;
import com.p1.nomnom.store.service.StoreService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class AiServiceImpl implements AiService {
private final AiRepository aiRepository;
private final StoreService storeService;
//@Autowired 대신 @RequiredArgsConstructor
// public AiServiceImpl(AiRepository aiRepository, StoreService storeService) {
// this.aiRepository = aiRepository;
// this.storeService = storeService;
// }
@Transactional
@Override
public AiResponseDto getAiAnswer(AiRequestDto requestDto) {
String answer = "AI로부터 받은 답변"; // AI API 호출 결과
Ai aiEntity = new Ai();
aiEntity.setQuestion(requestDto.getQuestion());
aiEntity.setAnswer(answer);
aiEntity.setFoodName(requestDto.getFoodName());
aiEntity.setDescriptionHint(requestDto.getDescriptionHint());
aiEntity.setKeyword(requestDto.getKeyword());
aiRepository.save(aiEntity);
// storeId로 storeName을 조회
UUID storeId = requestDto.getStoreId();
String storeName = storeService.getStoreNameById(storeId);
return new AiResponseDto(
aiEntity.getQuestion(),
aiEntity.getFoodName(),
storeId,
storeName,
aiEntity.getDescriptionHint(),
aiEntity.getKeyword(),
answer
);
}
@Override
public void save(Ai aiEntity) {
aiRepository.save(aiEntity);
}
@Override
@Transactional
public List<AiResponseDto> searchAi(UUID storeId, String question, int pageSize, Sort sort) {
Pageable pageable = PageRequest.of(0, pageSize, sort);
Page<Ai> pageResult;
if (question != null && !question.isEmpty()) {
pageResult = aiRepository.findByQuestionContainingAndStoreId(question, storeId, pageable);
} else {
pageResult = aiRepository.findAllByStoreId(storeId, pageable);
}
return pageResult.stream()
.map(ai -> new AiResponseDto(ai.getQuestion(), ai.getFoodName(), ai.getStoreId(),
storeService.getStoreNameById(ai.getStoreId()), ai.getDescriptionHint(), ai.getKeyword(),
ai.getAnswer()))
.toList();
}
}
GeminiService.java -> 이부분이 젤 중요한데 생각보다 여기서 에러는 적었고 테이버베이스 타입에서 varchar로 값을 주는 바람에 계속 입력받은 값이 너무 크다고 긴 에러를 띄운거였어요
package com.p1.nomnom.ai;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.*;
import java.io.IOException;
@Service
public class GeminiService {
@Value("${google.api.key}")
private String apiKey; // API 키를 application.properties에서 주입받음
// question과 descriptionHint, keyword를 바탕으로 설명을 생성하는 메서드
public String generateContent(String question, String descriptionHint, String keyword) {
// 요청 텍스트 생성
String fullRequestText = "질문: " + question + " " + descriptionHint + " " + keyword + "에 대해 설명해주세요.";
// Gemini로 요청 보내기
return sendRequestToGemini(fullRequestText);
}
// Gemini API로 요청을 보내는 메서드
private String sendRequestToGemini(String prompt) {
// REST API 호출을 위한 설정
String url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=" + apiKey;
RestTemplate restTemplate = new RestTemplate();
// 요청 데이터 준비
String requestBody = "{\"contents\": [{\"parts\": [{\"text\": \"" + prompt + "\"}]}]}";
// 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// HTTP 요청 설정
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
// API 호출 및 응답 처리
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
// 응답 본문 반환
return response.getBody();
}
}
오늘 에러를 고친 방법은 어이없게도
DB의 타입값 변경입니다
원래제가 테이터 명세서에 작성한 것을 보면
이렇게 작성이 되어있습니다 값이 들어가야하는 부분은 전부 varchar로 해두고 제한까지 놨죠 근데 제한둔걸 잊어버리고 왜 안들어가지... 하고 씨름중이었으니...어이가없네요
근데 이거도 하나 하나 변경했는데 어떤거에서 공간이 부족한지 몰라서 그랬습니다 그리고 왜 인지는 모르겠지만
기록을 보면 분명히 alter column으로 type을 TEXT로 바꿨는데 적용이 안돼서 몇번 더 한 기록이 있습니다
이건 왜 이런지 모르겠어요 새로고침 적용이 안된건지
아무든 결과적으로는 type을 TEXT로 변경하니 정상 작동했고 postman으로 테스트 했을 때
결과도 잘 나왔습니다
이제 기능 작동하는거 확인했으니 프로젝트 요구사항에 있는
<입력 텍스트의 글자수를 제한합니다. 또한 실제 요청 텍스트 마지막에 “답변을 최대한 간결하게 50자 이하로” 라는 텍스트를 요청시에 삽입하여 사용량을 줄이는 처리를 추가합니다. >
요청 텍스트 마지막에 글 삽입해서 Gemini 답변 글을 줄이는걸 해야겠네요
question의 마지막에 넣으면 되는거겠죠
근데
이런 요구사항도 있는데 그럼 Ai도 검색기능을 만들어야겠네요
p_ai로 저장되는 question이랑 answer값들이 있으니
그럼 anwser 값들을 검색해야하는건가
끝
250219TIL - Git 연습하기 (0) | 2025.02.19 |
---|---|
250218 TIL - AI 활용 비즈니스 프로젝트 - P1_Git에서 feature_ai 브랜치 PR (0) | 2025.02.18 |
250214 TIL - AI 활용 비즈니스 프로젝트 - P1_Day3 - postgres, store 기능 구현 (0) | 2025.02.14 |
250213 TIL - AI 활용 비즈니스 프로젝트 - P1_Day2 (0) | 2025.02.13 |
250212 TIL - Ch.1 AI 활용 비즈니스 프로젝트 (0) | 2025.02.12 |