๐ฑ ์ฑ๋ด ๋ฉ์ธ ์๋ฒ์ ๋ํด ์์๋ณด์ ~ 1ํ
๐ค Intro
์ ๋ฒ ์ํคํ ์ฒ ์๋ฆฌ์ฆ์ ์ด์ ์๋ฒ ๋ฏ์ด๋ณด๊ธฐ 1ํ์ด๋ค.
๊ฐ๋ฐ์ ์งํํ๋ฉด์ ์ฌ๋ฌ ๋ถ๋ถ๋ค์ ๊ณ ๋ฏผํ๊ณ ์๋ฒ๋ฅผ ์ค๊ณํ๋๋ฐ, ๋ค์ ์ดํด๋ณด๋ฉด์ ์๋ฌ๊ฐ ์์ผ๋ฉด ๊ณ ์น๊ธฐ๋ ํด๋ณด๊ณ โฆ.์ด์ฐธ์ ํ ๋ฒ ์ญ ์ ๋ฆฌํ๋ ค๊ณ ๊ธ์ ์ฐ๊ฒ ๋์๋ค.
๐ฉถ Start
Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import com.ohgoodteam.ohgoodpay.recommend.dto.ChatStartRequestDTO;
import com.ohgoodteam.ohgoodpay.recommend.dto.datadto.llmdto.BasicChatResponseDTO;
import com.ohgoodteam.ohgoodpay.recommend.service.ChatService;
import com.ohgoodteam.ohgoodpay.recommend.util.ApiResponseWrapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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;
/**
* AI ์ถ์ฒ ์ฑํ
์ปจํธ๋กค๋ฌ
*/
@Tag(name = "Chat")
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/chat")
public class ChatController {
private final ChatService chatService;
@PostMapping()
public ApiResponseWrapper<BasicChatResponseDTO> chat(
@RequestBody ChatStartRequestDTO request,
@AuthenticationPrincipal(expression = "customerId") Long customerId) {
try {
BasicChatResponseDTO response = chatService.chat(
// customerId,
request.getCustomerId(),
request.getSessionId(),
request.getInputMessage()
);
return ApiResponseWrapper.ok(response);
} catch (IllegalArgumentException e) {
return ApiResponseWrapper.error(400, e.getMessage());
} catch (RuntimeException e) {
// ๋ก๊น
๋ง ์ถ๊ฐํด์ ์ถ์ ๊ฐ๋ฅํ๋๋ก ์ค์
log.warn("Chat service runtime error: {}", e.getMessage());
return ApiResponseWrapper.error(500, "์ผ์์ ์ธ ์๋น์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค");
} catch (Exception e) {
log.error("Unexpected error in chat controller", e);
return ApiResponseWrapper.error(500, "์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค");
}
}
}
์ฑ๋ด์ ๋ฉ์ธ ์ปจํธ๋กค๋ฌ์ด๋ค. ์ฑ๋ด์ ๊ฒฝ์ฐ๋ ๋จ์ผ API๋ก ์ฒ๋ฆฌํ๋, ํ๋ก์ฐ์ ๋ฐ๋ฅธ ์์ฒญ๋ง ๋ด๋ถ์์ ๊ฐ๋ฆฌ๊ธฐ ๋๋ฌธ์ Controller๊ฐ ํ๋์ด๋ค.
@AuthenticationPrincipal(expression = โcustomerIdโ) Long customerId) ๋ฐฉ์์ผ๋ก JWT์์ customerId๋ฅผ ๋ฝ์์ฌ ์ ์๋๋ก ํ์๋ค.
ApiResponseWrapper๋ฅผ ํตํด ์๋ต์ ๊ตฌ์ฑํ๋๋ก ํ๋๋ฐ, ์ด๋ฅผ ํตํด ์ข ๋ ๊ตฌ์กฐํ๋ ํํ๋ก json ์๋ต์ด ๊ฐ๋๋ก ๊ตฌ์ฑํด์ ํ๋ก ํธ์์ ์ฌ์ฉํ๊ธฐ ํธํ๋๋ก ๋ง๋ค์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* API ์๋ต์ ํ์คํํ๊ธฐ ์ํ ๋ํผ ํด๋์ค
* @param <T>
*/
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ApiResponseWrapper<T> {
private boolean success;
private int code;
private String message;
private T data;
public static <T> ApiResponseWrapper<T> ok(T data) {
return ApiResponseWrapper.<T>builder()
.success(true)
.code(200)
.message("OK")
.data(data)
.build();
}
public static <T> ApiResponseWrapper<T> error(int code, String message) {
return ApiResponseWrapper.<T>builder()
.success(false)
.code(code)
.message(message)
.build();
}
}
๋ค์์ API ์๋ต์ ์ฌ์ฉํ๋ Wrapper ํด๋์ค์ด๋ค.
data์ ์๋ต์ ํ์ํ ๋ด์ฉ์ด ๋ค์ด๊ฐ๋๋ก ๊ตฌ์ฑํ์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฑ๊ณต ์ํ & ์ฝ๋ & ์๋ฒ ๋ฉ์ธ์ง์ ๋ฐ๋ก ๋ถ๋ฆฌํด๋์ด data๋ฅผ ํ๋ก ํธ์์ ์ฌ์ฉํ๊ธฐ ์ฝ๋๋ก ๊ตฌ์ฑํ๋ค.
์ด๋ฐ์์ผ๋ก ๊ตฌ์ฑํ ์, ์๋ต์ ๋ค์๊ณผ ๊ฐ์ ํํ๋ก ๋ณด๋ด์ง๊ฒ ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"success": true,
"code": 0,
"message": "string",
"data": {
"sessionId": "string",
"message": "string",
"newHobby": "string",
"products": [
{
"rank": 0,
"name": "string",
"price": 0,
"image": "string",
"url": "string",
"category": "string"
}
],
"summary": "string",
"flow": "string"
}
}
Service
Main Chat Service
์๋น์ค์ ๊ฒฝ์ฐ๋, ์ญํ ์ ๋ฐ๋ผ ๋ถ๋ฆฌํ์๊ธฐ์ controller์ ์ฐ๊ฒฐ๋๋ ๋ฉ์ธ ์๋น์ค๊ฐ ์๊ณ , LLM ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋(์ฆ, Fast api์ ์ฐ๊ฒฐ๋๋) ์๋น์ค, redis Cache๋ฅผ ๊ด๋ฆฌํ๋ ์๋น์ค, ํ๋ก์ฐ ๊ด๋ฆฌ ์๋น์ค, ์ ๋ ฅ ๊ฒ์ฆ ์๋น์ค ์ด๋ ๊ฒ ๋ค์ฏ ๊ฐ์ง์ ์๋น์ค๊ฐ ์กด์ฌํ๋ค.
์ฐ์ ๋ฉ์ธ ์๋น์ค๋ถํฐ ์ดํด๋ณด์
1
2
3
4
5
6
7
8
9
10
11
import com.ohgoodteam.ohgoodpay.recommend.dto.datadto.llmdto.BasicChatResponseDTO;
/**
* ์ฑํ
๊ป๋ฐ๊ธฐ ์๋ฒ ์๋น์ค ์ธํฐํ์ด์ค
*
* interface ๋ถ๋ฆฌ๋ก ์ถํ ๊ตฌํ์ฒด ๋ณ๊ฒฝ ์ฉ์ด
*/
public interface ChatService {
// ์ฑํ
์์ฑ
BasicChatResponseDTO chat(Long customerId, String sessionId, String message);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import com.ohgoodteam.ohgoodpay.common.repository.CustomerRepository;
import com.ohgoodteam.ohgoodpay.recommend.dto.CustomerContextWrapper;
import com.ohgoodteam.ohgoodpay.recommend.dto.ValidationResult;
import com.ohgoodteam.ohgoodpay.recommend.dto.datadto.llmdto.*;
import com.ohgoodteam.ohgoodpay.recommend.service.fastapi.LlmService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* ์ฑํ
๊ป๋ฐ๊ธฐ ์๋ฒ ์๋น์ค ๊ตฌํ์ฒด
*
* ๊ณ ๊ฐ๊ณผ์ ์ค์๊ฐ ์ฑํ
, LLM ์๋ต ์์ฑ, ์ถ์ฒ ์๋น์ค ์ฐ๋์ ๋ด๋น
* Redis ์บ์๋ฅผ ํตํ ์ฑ๋ฅ ์ต์ ํ ๋ฐ ์ธ๋ถ FastAPI ์๋ฒ์์ ํต์ ์ฒ๋ฆฌ
*/
@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class ChatServiceImpl implements ChatService {
private final CustomerRepository customerRepository; // DB ์ง์ ์ ๊ทผ์ฉ (update)
private final ChatCacheService chatCacheService; // ์บ์ฑ ์๋น์ค
private final LlmService llmService; // FastAPI LLM ์ฐ๋ ์๋น์ค
private final ValidCheckService validCheckService; // ์
๋ ฅ ๊ฒ์ฆ ์๋น์ค
private final FlowService flowService; // ํ๋ก์ฐ ๊ด๋ฆฌ ์๋น์ค
/**
* ์ฑํ
์ฒ๋ฆฌ
*/
@Override
@Transactional(readOnly = true)
public BasicChatResponseDTO chat(Long customerId, String sessionId, String message) {
log.info("์ธ์
์์ด๋์ ๊ณ ๊ฐ ์์ด๋ ์ฒดํฌ sessionId: {} for customerId: {}", sessionId, customerId);
// 0. flow๋ฅผ redis์์ ๊ฐ์ ธ์ค๊ธฐ
String currentFlow = chatCacheService.getFlowBySession(sessionId);
// start ํ๋ก์ฐ๋ ์ ํจ์ฑ ๊ฒ์ฆ ์์ด ๋ฐ๋ก LLM ์ฒ๋ฆฌ
if (currentFlow.equals("start")) {
// 2. ํ๋ก์ฐ ์ ํ ๋ฐ ์ ์ฒ๋ฆฌ
String nextFlow = flowService.getNextFlow(currentFlow);
chatCacheService.saveCntBySession(sessionId, 1); //count ์ด๊ธฐํ
chatCacheService.saveFlowBySession(sessionId, nextFlow); //nextflow ์ด๊ธฐํ
log.info("์บ์ฑ๋์ด ์๋ ๋ฐ๋ ํ๋ก์ฐ ํ์ธํ๊ธฐ : {}", chatCacheService.getFlowBySession(sessionId));
// 3. ์บ์ฑ๋ ๋ฐ์ดํฐ ์์ง
CustomerContextWrapper context = collectCachedData(customerId, sessionId);
// 4. LLM ์์ฒญ
BasicChatResponseDTO response = requestToLLM(sessionId, context, message, nextFlow);
// 5. ์๋ต ํ์ฒ๋ฆฌ
processAfterResponse(response, customerId, sessionId);
return response;
}
// ์ผ๋ฐ ํ๋ก์ฐ๋ ์ ํจ์ฑ ๊ฒ์ฆ ํ ์ฒ๋ฆฌ
ValidInputResponseDTO validated = validCheckService.validateInput(customerId, sessionId, message, currentFlow);
ValidationResult validationResult = processValidationResult(validated, customerId, sessionId, currentFlow);
if (validationResult.shouldReturn()) { //๋ฐ๋ก ๋ฆฌํด ํด์ผ ํ๋ค๋ฉด, ํ๋ก์ฐ ๋ณํ ์์ด ์ฒ๋ฆฌ
return validationResult.getResponse();
}
// 2. ํ๋ก์ฐ ์ ํ ๋ฐ ์ ์ฒ๋ฆฌ
String nextFlow = flowService.getNextFlow(currentFlow);
chatCacheService.saveCntBySession(sessionId, 1); //count ์ด๊ธฐํ
chatCacheService.saveFlowBySession(sessionId, nextFlow); //nextflow ์ด๊ธฐํ
log.info("์บ์ฑ๋์ด ์๋ ๋ฐ๋ ํ๋ก์ฐ ํ์ธํ๊ธฐ : {}", chatCacheService.getFlowBySession(sessionId));
// validํ์ง ์์์ ํด๋น ํ๋ก์ฐ๋ฅผ ์คํตํด์ผ ํ ๊ฒฝ์ฐ, ์ ๋ณด๋ฅผ ์ ์ฅํ๋ฉด ์ ๋๊ธฐ ๋๋ฌธ์ด๋ค.
if (validated.isValid()) {
saveFlowSpecificData(customerId, sessionId, nextFlow, validated);
}
// 3. ์บ์ฑ๋ ๋ฐ์ดํฐ ์์ง
CustomerContextWrapper context = collectCachedData(customerId, sessionId);
// 4. LLM ์์ฒญ
BasicChatResponseDTO response = requestToLLM(sessionId, context, message, nextFlow);
// flow๊ฐ recommendation or re_recommendation ์ผ ๊ฒฝ์ฐ, products ์บ์ฑ ๋ฐ ์๋ต ์ฒ๋ฆฌ
if("recommendation".equals(response.getFlow()) || "re_recommendation".equals(response.getFlow())){
response = handleRecommendationFlow(sessionId, response);
// TODO : ์บ์ฑ ํ ์ํ ๋ชจ์๋ ๊ฒฝ์ฐ์ ๋ํ ํด๊ฒฐ์ฑ
๋ฐ ๋ก์ง ์ ์ ํ์.
}
// 5. ์๋ต ํ์ฒ๋ฆฌ, ๊ฐ์ ๋ก ๋์ด๊ฐ ๊ฒฝ์ฐ๋ ์ ์ฅ ๋ฌด์ํ๋ค.
if (validated.isValid()) {
processAfterResponse(response, customerId, sessionId);
}
return response;
}
/**
* ๊ฒ์ฆ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ ๋ฐ ์ฌ์๋ ๋ก์ง
*/
private ValidationResult processValidationResult(ValidInputResponseDTO validated, Long customerId, String sessionId, String currentFlow) {
if (!validated.isValid()) {
int cnt = chatCacheService.getCntBySession(sessionId);
// ๋ ๋ฒ ์ดํ๋ก ์ ํจํ์ง ์์ ์๋ต์ด ๋ค์ด์์๊ฒฝ์ฐ, ๋ค์ ๊ฐ์ ํ๋ก์ฐ๋ฅผ ์คํ
if (cnt < 2) {
chatCacheService.saveCntBySession(sessionId, cnt + 1);
BasicChatResponseDTO errorResponse = BasicChatResponseDTO.builder()
.sessionId(sessionId)
.message(validated.getMessage())
.newHobby("")
.products(null)
.build();
return ValidationResult.returnResponse(errorResponse);
} else if(cnt == 2) {
// cnt == 2๋ฉด ๋ค์ ํ๋ก์ฐ๋ก ๊ฐ์ ์งํํ๊ณ LLM ์์ฒญ ๊ณ์ ์งํ
log.info("Validation ์คํจ {}๋ฒ, ๋ค์ ํ๋ก์ฐ๋ก ๋์ด๊ฐ์ LLM ์์ฒญ ์งํ", cnt);
return ValidationResult.continueFlow();
}
}
log.info("------------isValid True๋ก ์ธํด ํ๋ก์ฐ๋ฅผ ๊ทธ๋๋ก ์ด์ด๋๊ฐ๋ค.----------");
return ValidationResult.continueFlow();
}
/**
* ์บ์ฑ๋ ๋ฐ์ดํฐ ์์ง
*/
private CustomerContextWrapper collectCachedData(Long customerId, String sessionId) {
return CustomerContextWrapper.builder()
.customerInfo(chatCacheService.getCustomerInfo(customerId))
.hobby(chatCacheService.getHobby(customerId))
.mood(chatCacheService.getMoodBySession(sessionId))
.balance(chatCacheService.getBalance(customerId))
.summary(chatCacheService.getSummaryBySession(sessionId))
.build();
}
/**
* ํ๋ก์ฐ๋ณ ํน์ ๋ฐ์ดํฐ ์ ์ฅ
*/
private void saveFlowSpecificData(Long customerId, String sessionId, String nextFlow, ValidInputResponseDTO validated) {
// ํน์ ํ๋ก์ฐ์์ mood ์ ์ฅ, ์ ํจํ ๊ฒฝ์ฐ์ ์บ์ฑํ๊ณ ์๋ ๊ฒฝ์ฐ์ ์ ์ฅ ์์ด ํ๋ก์ฐ๋ง ๋๊ธฐ๋๋ก ํ๋ค.
if ("hobby_check".equals(nextFlow) && validated != null) {
chatCacheService.saveMoodBySession(sessionId, validated.getInputMessage());
}
}
/**
* LLM ์์ฒญ ์ฒ๋ฆฌ
*/
private BasicChatResponseDTO requestToLLM(String sessionId, CustomerContextWrapper context, String message, String nextFlow) {
return llmService.generateChat(
sessionId,
context.getCustomerInfo(),
context.getMood(),
context.getHobby(),
context.getBalance(),
message,
context.getSummary(),
nextFlow
);
}
/**
* ์๋ต ํ์ฒ๋ฆฌ (DB ์
๋ฐ์ดํธ, ์บ์ฑ)
*/
private void processAfterResponse(BasicChatResponseDTO response, Long customerId, String sessionId) {
// ์ทจ๋ฏธ์ ๊ฒฝ์ฐ๋ db์ ์ ์ฅํ๋ ๊ฐ์ผ๋ก, ์ฐจํ llm์ด ํ ๋ฒ ์ฒ๋ฆฌํ ๋ค์ ์ ์ฅํ ์ ์๋๋ก ๋ฆฌํฉํฐ๋ง ํ๊ธฐ ์ํด ์์ฒญ ํ ์ฒ๋ฆฌํ๋๋ก ํ์๋ค.
if (!response.getNewHobby().equals("")) {
customerRepository.updateHobbyByCustomerId(customerId, response.getNewHobby());
chatCacheService.saveHobby(customerId, response.getNewHobby()); //์ทจ๋ฏธ ์ ์ฅ
log.info("์ทจ๋ฏธ DB ์
๋ฐ์ดํธ ์๋ฃ - customerId: {}, hobby: {}", customerId, response.getNewHobby());
}
// ์บ์ฑ
// ์์ฝ๋ณธ ์ ์ฅ
if (response.getSummary() != null && !response.getSummary().isEmpty()) {
chatCacheService.saveSummaryBySession(sessionId, response.getSummary());
}
}
}
์ธํฐํ์ด์ค์๋ Controller์์ ์ฌ์ฉํ๋ ๋ก์ง์ธ chat๋ง ๋๊ณ , ๋ด๋ถ ์๋น์ค๋ค์ ์ธํฐํ์ด์ค์ ์ ์ํ์ง ์์๋ค.
์ด๋ ISP (Interface Segregation Principle) : ํด๋ผ์ด์ธํธ๋ ์ฌ์ฉํ์ง ์๋ ๋ฉ์๋์ ์์กดํ์ง ๋ง์์ผ ํ๋ค ๋ผ๋ ์์น์ ์ค์ํ๊ธฐ ์ํจ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๋ด๋ถ ๋ฉ์๋๋ค์ ์ ์ ํ ์จ๊ฒจ์ ์บก์ํ๋ฅผ ํด๋๋ ๊ฒ์ด ์ฐจํ ์ ์ง๋ณด์ ์ธก๋ฉด์์๋ ํธ๋ฆฌํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ฌ๊ธฐ๋ ๋ก์ง์ด ์ข ๋ณต์กํ๋ฏ๋ก method ํ๋ํ๋ ๋ฏ์ด๊ฐ๋ฉด์ ๋ณด๋๋ก ํ์.
๊ฒ์ฆ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ ๋ฐ ์ฌ์๋ ๋ก์ง
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* ๊ฒ์ฆ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ ๋ฐ ์ฌ์๋ ๋ก์ง
*/
private ValidationResult processValidationResult(ValidInputResponseDTO validated, Long customerId, String sessionId, String currentFlow) {
if (!validated.isValid()) {
int cnt = chatCacheService.getCntBySession(sessionId);
// ๋ ๋ฒ ์ดํ๋ก ์ ํจํ์ง ์์ ์๋ต์ด ๋ค์ด์์๊ฒฝ์ฐ, ๋ค์ ๊ฐ์ ํ๋ก์ฐ๋ฅผ ์คํ
if (cnt < 2) {
chatCacheService.saveCntBySession(sessionId, cnt + 1);
BasicChatResponseDTO errorResponse = BasicChatResponseDTO.builder()
.sessionId(sessionId)
.message(validated.getMessage())
.newHobby("")
.products(null)
.build();
return ValidationResult.returnResponse(errorResponse);
} else if(cnt == 2) {
// cnt == 2๋ฉด ๋ค์ ํ๋ก์ฐ๋ก ๊ฐ์ ์งํํ๊ณ LLM ์์ฒญ ๊ณ์ ์งํ
log.info("Validation ์คํจ {}๋ฒ, ๋ค์ ํ๋ก์ฐ๋ก ๋์ด๊ฐ์ LLM ์์ฒญ ์งํ", cnt);
return ValidationResult.continueFlow();
}
}
log.info("------------isValid True๋ก ์ธํด ํ๋ก์ฐ๋ฅผ ๊ทธ๋๋ก ์ด์ด๋๊ฐ๋ค.----------");
return ValidationResult.continueFlow();
}
์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ถ๊ฐํ๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฉ์๋๋ฅผ ์ถ๊ฐํ์๋ค.
์ด๋ ์ฌ์ฉ์์ ํ์ฌ ์ ๋ ฅ์ด ํ์ฌ ํ๋ก์ฐ์ ์๋ง์ ์ ๋ ฅ์ธ์ง๋ฅผ ํน์ธํ๊ธฐ ์ํ ๋ฉ์๋์ด๋ค.
ValidInputResponseDTO๋ผ๋ validation ๊ฒฐ๊ณผ๋ฌผ์ ๊ฐ์ง๊ณ ํ๋จํ๋๋ฐ, ์ด๋ฅผ ์ ๊น ์ดํด๋ณด์.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* FAST API - ํ์ฌ ์ฌ์ฉ์์ input์ด ์ ํจํ์ง ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋ณด๋ด๋ DTO
*/
@Getter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ValidInputResponseDTO {
private Long customerId;
private String sessionId;
private String inputMessage; // ์ฌ์ฉ์๊ฐ ๋ณด๋ธ ๋ฉ์์ง, ํ ๋ฒ๋ ๋ณด๋ผ๋ ์ฌ์ฌ์ฉ ํ๊ธฐ ์ํจ์ด๋ค.
@JsonProperty("isValid") // fast api๋ json ์ง๋ ฌํ๋ฅผ ๋ง์ถ๊ธฐ ์ํจ์ด๋ค.
private boolean isValid; // ์
๋ ฅ์ด ์ ํจํ์ง ์ฌ๋ถ
private String message; // LLM์ด ์์ฑํ ์๋ต ๋ฉ์์ง, ์ ํจํ์ง ์์ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ ์์ .
private String flow; // ๋ณด๋๋ ๋ํ ํ๋ฆ (๋ค์ ํ๋ฆ์ผ๋ก ๊ฐ๋๋ก ํ๊ธฐ ์ํจ์ด๋ค. ์ด๊ฒ ์์ด์ผ ๋ฐ์์ redis์ ํ๋ก์ฐ ์ ์ฅํ๊ณ ๋ค์ llm ์์ฒญ ๊ฐ๋ฅ)
}
๋ค์๊ณผ ๊ฐ์ด ํ์ฌ ํ๋ก์ฐ์ LLM์ด ์์ฑํ ๋ฉ์ธ์ง, ๊ทธ๋ฆฌ๊ณ LLM์ด ํ๋จํ๊ธฐ์ โํ์ฌ ํ๋ก์ฐ์ ์๋ง์์งโ๋ฅผ ํ๋จํ๋ค.
๋ค์ ๋์๊ฐ์ ์ดํด๋ณด์๋ฉด, Validํ ์ ๋ ฅ์ผ ๊ฒฝ์ฐ์๋ ๋ฐ๋ก ์กฐ๊ฑด ์ฒดํฌ ์์ด ๋์ด๊ฐ์ง๋ง, Validํ ์ ๋ ฅ์ด ์๋๋ผ๊ณ ๋์ฌ ๊ฒฝ์ฐ์๋ ์ด ํ์๊ฐ โ2๋ฒ ์ด์์ธ์งโ๋ฅผ ์ดํด๋ณธ๋ค. ์ฆ, ์ฒซ๋ฒ์งธ๋ก ์ ๋ ฅํ ๊ฐ์ด ์ด์๊ฐ์ด ์๋ ๊ฒฝ์ฐ์ ๊ธฐํ๋ฅผ ์ฃผ์ง๋ง, ๋ ๋ฒ์งธ ๋ถํฐ๋ ํด๋น ํ๋ก์ฐ๋ฅผ ๊ทธ๋ฅ ์คํตํ๊ธฐ ์ํด ์ด ์กฐ๊ฑด์ ๋ฃ์ ๊ฒ์ด๋ค.
์ด๋ฅผ ํตํด ์ฌ์ฉ์ ๊ฒฝํ์ ์ข๊ฒ ํ๋, ์ด์ ๊ฐ์ ๊ณ์ ์ ๋ ฅํ๋ ๊ฒ์ ๋ฐฉ์งํ๊ณ ์ ํ์๋ค.
๋ ๋ฒ ์ดํ์ผ ๊ฒฝ์ฐ์๋ error ๋ฉ์ธ์ง๋ฅผ ๋ด๋ณด๋ด๊ณ ValidationResult.returnResponse(errorResponse)๋ฅผ ๋ด๋ณด๋ด๋ ๊ฒ์ผ๋ก ๋์ด ์๋๋ฐ ์ด ๋ถ๋ถ๋ ํ ๋ฒ ์ดํด๋ณด๋๋ก ํ์.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Validation ๊ฒฐ๊ณผ๋ฅผ ๋ด๋ DTO
*/
@AllArgsConstructor
@Getter
public class ValidationResult {
private final boolean shouldReturn;
private final BasicChatResponseDTO response;
public static ValidationResult continueFlow() {
return new ValidationResult(false, null);
}
public static ValidationResult returnResponse(BasicChatResponseDTO response) {
return new ValidationResult(true, response);
}
public boolean shouldReturn() {
return shouldReturn;
}
}
์ด๋ ํ๋ก์ฐ๋ฅผ ๊ณ์ํ ์ง, ์๋๋ฉด ์๋ฌ ์๋ต์ ๋ด๋ณด๋ด๊ณ ํด๋น service๋ฅผ ์ข ๋ฃํด์ ๋ค์ ์ ํจ์ฑ ๊ฒ์ฆ์ ํ๊ฒ ํ ์ง๋ฅผ ํ๋จํ๊ธฐ ์ํธ DTO์ด๋ค.
returnResponse์ผ ๊ฒฝ์ฐ์๋ ๋ฐ๋ก ์๋ต์ ๋ด๋ณด๋ด๋๋ฐ, ์ด๋ ์ฐ๋ฆฌ๊ฐ ์ฌ์๋ต์ ๋ฐ์์ผ ํ๋ ๊ฒฝ์ฐ, LLM์์ ๋ฐ์ ๋ฉ์ธ์ง๋ฅผ ๊ทธ๋๋ก ๋ด๋ณด๋ด๊ธฐ ๋๋ฌธ์ (์ฆ, ๋ฉ์ธ์ง ์์ฑ์ ์์ฒญํ์ง ์์) ๋ค์๊ณผ ๊ฐ์ด continueFlow() & returnResponse(BasicChatResponseDTO response)๋ฅผ ๋๋ ์ ๊ตฌ์ฑํ ๊ฒ์ด๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
/**
* ์บ์ฑ๋ ๋ฐ์ดํฐ ์์ง
*/
private CustomerContextWrapper collectCachedData(Long customerId, String sessionId) {
return CustomerContextWrapper.builder()
.customerInfo(chatCacheService.getCustomerInfo(customerId))
.hobby(chatCacheService.getHobby(customerId))
.mood(chatCacheService.getMoodBySession(sessionId))
.balance(chatCacheService.getBalance(customerId))
.summary(chatCacheService.getSummaryBySession(sessionId))
.build();
}
์๋ฌ ์ฒดํน์ด ๋๋ฌ์ ๊ฒฝ์ฐ, ์ฆ ํด๋น ํ๋ก์ฐ๋ฅผ ๊ณ์ ์ด์ด๋๊ฐ๋ ๊ด์ฐฎ๋ค๋ ํ๋ฝ์ ๋ฐ์์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์๋ต ์์ฑ์ ํ์ํ ๋ชจ๋ ์บ์ฑ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋ค.
๋ชจ๋ ์บ์ฑ ๋ฐ์ดํฐ๋ CustomerContextWrapper๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ฌถ์ด์ ์์ฑ๋ฐ ๊ด๋ฆฌ๊ฐ ํธ๋ฆฌํ๋๋ก ํ์๋ค.
1
2
3
4
5
6
7
8
9
/**
* ํ๋ก์ฐ๋ณ ํน์ ๋ฐ์ดํฐ ์ ์ฅ
*/
private void saveFlowSpecificData(Long customerId, String sessionId, String nextFlow, ValidInputResponseDTO validated) {
// ํน์ ํ๋ก์ฐ์์ mood ์ ์ฅ, ์ ํจํ ๊ฒฝ์ฐ์ ์บ์ฑํ๊ณ ์๋ ๊ฒฝ์ฐ์ ์ ์ฅ ์์ด ํ๋ก์ฐ๋ง ๋๊ธฐ๋๋ก ํ๋ค.
if ("hobby_check".equals(nextFlow) && validated != null) {
chatCacheService.saveMoodBySession(sessionId, validated.getInputMessage());
}
}
ํน์ ํ๋ก์ฐ ์์์๋ ๊ฐ์ ์บ์ฑํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ์, ํน์ ํ๋ก์ฐ ์์์ ๊ฐ์ ์ ์ฅํ๋ ๋ฉ์๋๋ฅผ ์์ฑํ์๋ค.
hobby_check๊ฐ ๋ค์ ํ๋ก์ฐ์ผ ๊ฒฝ์ฐ์๋, ์ด์ ํ๋ก์ฐ๊ฐ mood_check์ด๋ฏ๋ก saveMoodBySession๋ฅผ ํตํด session ๊ด๋ฆฌ ๊ฐ์ผ๋ก ์ ๋ ฅ๋ฐ์ mood๋ฅผ ์ ์ฅํด์ค๋ค.
์ฐธ๊ณ ๋ก mood ๊ฐ์ ๊ฒฝ์ฐ๋ ์ธ์ ์์๋ง ์๋ ๊ฐ์ด๋ผ ๋ฐ๋ก db์ ์ ์ฅ์ ์ํด์ llm ์์ฒญ ํ ์ฒ๋ฆฌ๊ฐ ์๋ ์์ฒญ ์ ์ฒ๋ฆฌ ๊ฐ์ผ๋ก ๋นผ๋์๋ค. ์์ฒญ ํ ์ฒ๋ฆฌ ๊ฐ์ผ๋ก ํ๋ฉด response์ ๋ ์ธ๋ฐ์์ด mood๋ฅผ ์ถ๊ฐํด์ผ ํ๊ธฐ์..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* LLM ์์ฒญ ์ฒ๋ฆฌ
*/
private BasicChatResponseDTO requestToLLM(String sessionId, CustomerContextWrapper context, String message, String nextFlow) {
return llmService.generateChat(
sessionId,
context.getCustomerInfo(),
context.getMood(),
context.getHobby(),
context.getBalance(),
message,
context.getSummary(),
nextFlow
);
}
์ด ๋ถ๋ถ์ด LLM์ ์์ฒญํ๋ ๋ถ๋ถ์ด๋ค.
llmService์์ generateChat์ ํตํด LLM์ ๋ฉ์ธ์ง๋ฅผ ์์ฒญํ๋ค.
llmService๋จ์ ์ฐจํ fast api ์๋ฒ ๊ตฌ์กฐ์ ํจ๊ป ๋ณด๊ธฐ ์ํด ์ฐ์ ์ ๋์ด๊ฐ๋๋ก ํ๊ฒ ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* ์๋ต ํ์ฒ๋ฆฌ (DB ์
๋ฐ์ดํธ, ์บ์ฑ)
*/
private void processAfterResponse(BasicChatResponseDTO response, Long customerId, String sessionId) {
// ์ทจ๋ฏธ์ ๊ฒฝ์ฐ๋ db์ ์ ์ฅํ๋ ๊ฐ์ผ๋ก, ์ฐจํ llm์ด ํ ๋ฒ ์ฒ๋ฆฌํ ๋ค์ ์ ์ฅํ ์ ์๋๋ก ๋ฆฌํฉํฐ๋ง ํ๊ธฐ ์ํด ์์ฒญ ํ ์ฒ๋ฆฌํ๋๋ก ํ์๋ค.
if (!response.getNewHobby().equals("")) {
customerRepository.updateHobbyByCustomerId(customerId, response.getNewHobby());
chatCacheService.saveHobby(customerId, response.getNewHobby()); //์ทจ๋ฏธ ์ ์ฅ
log.info("์ทจ๋ฏธ DB ์
๋ฐ์ดํธ ์๋ฃ - customerId: {}, hobby: {}", customerId, response.getNewHobby());
}
// ์บ์ฑ
// ์์ฝ๋ณธ ์ ์ฅ
if (response.getSummary() != null && !response.getSummary().isEmpty()) {
chatCacheService.saveSummaryBySession(sessionId, response.getSummary());
}
}
์ด ๋ถ๋ถ์ด ์๋ต ํ ๋ฐ์ดํฐ๋ค์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ก์ง์ด๋ค.
์ทจ๋ฏธ๊ฐ ๋ณ๋๋ ๊ฒฝ์ฐ์๋ newHobby๋จ์ ๊ฐ์ด ๋ค์ด์ค๊ธฐ ๋๋ฌธ์ ์กฐ๊ฑด์ ๋ค์๊ณผ ๊ฐ์ด ํ๋จํ๋๋ก ํ๋ค.
์ทจ๋ฏธ ๋ณ๋ ํ์๋ ๋ ๋์ค์ db์ ๋ชจ๋ ์ ์ทจ๋ฏธ๋ฅผ ์ ๋ก๋ ํด์ค๋ค.
์์ฝ๋ณธ ์ญ์ LLM์ด ์์ฑํด์ฃผ๋ ์์ฝ๋ณธ์ ์ ์ฅํด์ผ ํ๋ฏ๋ก, summary์ ๊ฐ์ด ์์ ๊ฒฝ์ฐ ๋ ๋์ค์ ์์ฝ๋ณธ์ ๋ฃ์ด์ค๋ค.
์ฌ์ค ์ง๊ธ ๊ตฌ์กฐ์์๋ DB์ ์์ฝ๋ณธ์ ์ ์ฅํ ํ์๋ ์์ ๊ฒ ๊ฐ์ผ๋, ํ์ฌ DB์ ์์ฝ๋ณธ์ ์ ์ฅํ๋ ํ ์ด๋ธ์ด ์์ผ๋ฏ๋ก, ํฅํ ์ด ๋ถ๋ถ์ ์์ฝ๋ณธ์ DB์ ์ ๋ฐ์ดํธ ํ๋ ๋ก์ง์ ์ถ๊ฐํ ์์ ์ด๋ค. ์ฐ์ ์์๋ ์๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* ์ถ์ฒ ํ๋ก์ฐ ์ฒ๋ฆฌ (์ํ ์บ์ฑ ๋ฐ ์๋ต ์ฌ์์ฑ)
*/
private BasicChatResponseDTO handleRecommendationFlow(String sessionId, BasicChatResponseDTO response) {
if ("recommendation".equals(response.getFlow())) {
// ์ ์ํ ์บ์ฑ
chatCacheService.saveProductsBySession(sessionId, response.getProducts());
// TODO : ์บ์ฑ ํ ์ํ ๋ชจ์๋ ๊ฒฝ์ฐ์ ๋ํ ํด๊ฒฐ์ฑ
๋ฐ ๋ก์ง ์ ์ ํ์.
}
return BasicChatResponseDTO.builder()
.sessionId(response.getSessionId())
.message(response.getMessage())
.newHobby(response.getNewHobby())
.products(chatCacheService.getProductsBySession(sessionId)) // 3๊ฐ ์ ํ
.summary(response.getSummary())
.flow(response.getFlow())
.build();
}
์ถ์ฒ์ด๋ ์ฌ ์ถ์ฒ ์ํ์ผ ๊ฒฝ์ฐ, ๋ ๋์ค์์ ์ํ์ ๊ฐ์ ธ์์ ์๋ตํด์ผ ํ๊ธฐ์ ๋ค์๊ณผ ๊ฐ์ method๋ฅผ ๋ง๋ จํ์๋ค.
chatCacheService.getProductsBySession(sessionId) ์์๋ ์ ์ฝ์กฐ๊ฑด์ด ์์ด์ ์ํ์ 3๊ฐ์ฉ ๊ฐ์ ธ์ค๋๋ก ๋์ด ์๋๋ฐ, ํด๋น ๋ถ๋ถ์ ๋ํ ๊ฑด ์ฐจํ ํฌ์คํ ์์ ๋ค๋ฃจ๋๋ก ํ๊ฒ ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* ์ฑํ
์ฒ๋ฆฌ
*/
@Override
@Transactional(readOnly = true)
public BasicChatResponseDTO chat(Long customerId, String sessionId, String message) {
log.info("์ธ์
์์ด๋์ ๊ณ ๊ฐ ์์ด๋ ์ฒดํฌ sessionId: {} for customerId: {}", sessionId, customerId);
// 0. flow๋ฅผ redis์์ ๊ฐ์ ธ์ค๊ธฐ
String currentFlow = chatCacheService.getFlowBySession(sessionId);
// start ํ๋ก์ฐ๋ ์ ํจ์ฑ ๊ฒ์ฆ ์์ด ๋ฐ๋ก LLM ์ฒ๋ฆฌ
if (currentFlow.equals("start")) {
// 2. ํ๋ก์ฐ ์ ํ ๋ฐ ์ ์ฒ๋ฆฌ
String nextFlow = flowService.getNextFlow(currentFlow);
chatCacheService.saveCntBySession(sessionId, 1); //count ์ด๊ธฐํ
chatCacheService.saveFlowBySession(sessionId, nextFlow); //nextflow ์ด๊ธฐํ
log.info("์บ์ฑ๋์ด ์๋ ๋ฐ๋ ํ๋ก์ฐ ํ์ธํ๊ธฐ : {}", chatCacheService.getFlowBySession(sessionId));
// 3. ์บ์ฑ๋ ๋ฐ์ดํฐ ์์ง
CustomerContextWrapper context = collectCachedData(customerId, sessionId);
// 4. LLM ์์ฒญ
BasicChatResponseDTO response = requestToLLM(sessionId, context, message, nextFlow);
// 5. ์๋ต ํ์ฒ๋ฆฌ
processAfterResponse(response, customerId, sessionId);
return response;
}
// ์ผ๋ฐ ํ๋ก์ฐ๋ ์ ํจ์ฑ ๊ฒ์ฆ ํ ์ฒ๋ฆฌ
ValidInputResponseDTO validated = validCheckService.validateInput(customerId, sessionId, message, currentFlow);
ValidationResult validationResult = processValidationResult(validated, customerId, sessionId, currentFlow);
if (validationResult.shouldReturn()) { //๋ฐ๋ก ๋ฆฌํด ํด์ผ ํ๋ค๋ฉด, ํ๋ก์ฐ ๋ณํ ์์ด ์ฒ๋ฆฌ
return validationResult.getResponse();
}
// 2. ํ๋ก์ฐ ์ ํ ๋ฐ ์ ์ฒ๋ฆฌ
String nextFlow = flowService.getNextFlow(currentFlow);
chatCacheService.saveCntBySession(sessionId, 1); //count ์ด๊ธฐํ
chatCacheService.saveFlowBySession(sessionId, nextFlow); //nextflow ์ด๊ธฐํ
log.info("์บ์ฑ๋์ด ์๋ ๋ฐ๋ ํ๋ก์ฐ ํ์ธํ๊ธฐ : {}", chatCacheService.getFlowBySession(sessionId));
// validํ์ง ์์์ ํด๋น ํ๋ก์ฐ๋ฅผ ์คํตํด์ผ ํ ๊ฒฝ์ฐ, ์ ๋ณด๋ฅผ ์ ์ฅํ๋ฉด ์ ๋๊ธฐ ๋๋ฌธ์ด๋ค.
if (validated.isValid()) {
saveFlowSpecificData(customerId, sessionId, nextFlow, validated);
}
// 3. ์บ์ฑ๋ ๋ฐ์ดํฐ ์์ง
CustomerContextWrapper context = collectCachedData(customerId, sessionId);
// 4. LLM ์์ฒญ
BasicChatResponseDTO response = requestToLLM(sessionId, context, message, nextFlow);
// flow๊ฐ recommendation or re_recommendation ์ผ ๊ฒฝ์ฐ, products ์บ์ฑ ๋ฐ ์๋ต ์ฒ๋ฆฌ
if("recommendation".equals(response.getFlow()) || "re_recommendation".equals(response.getFlow())){
response = handleRecommendationFlow(sessionId, response);
// TODO : ์บ์ฑ ํ ์ํ ๋ชจ์๋ ๊ฒฝ์ฐ์ ๋ํ ํด๊ฒฐ์ฑ
๋ฐ ๋ก์ง ์ ์ ํ์.
}
// 5. ์๋ต ํ์ฒ๋ฆฌ, ๊ฐ์ ๋ก ๋์ด๊ฐ ๊ฒฝ์ฐ๋ ์ ์ฅ ๋ฌด์ํ๋ค.
if (validated.isValid()) {
processAfterResponse(response, customerId, sessionId);
}
return response;
}
์, ์ด์ ๋ชจ๋ method์ ๋ํด์ ์ดํด ๋ณด์์ผ๋ฏ๋ก, ์ด๋ฅผ ์ข ํฉํ chat ๋ฉ์๋๋ฅผ ๋ณด๋๋ก ํ์.
์ค์ํ ๊ฒ์, ํ๋ก์ฐ๋ฅผ ๊ฐ์ ธ์์ ์ด๋ฅผ ํตํด ์ ๋ ฅ ๊ฐ๊ณผ ํจ๊ป ๋ณด๋ด โ์๋ต ์ ํจ์ฑ ๊ฒ์ฆโ์ LLM์ ๋จผ์ ์์ฒญํ ๋ค์์, ์ ํจํ๊ฑฐ๋ ๋ ๋ฒ ์ด์ ํ๋ ธ์ ๊ฒฝ์ฐ LLM์ ๋ฉ์ธ์ง ์์ฑ์ ์์ฒญํ๋ ๊ฒ์ด๋ค.
๋ํ, flow๊ฐ start์ผ ๊ฒฝ์ฐ์๋ ์ฌ์ฉ์ ์ ๋ ฅ๊ฐ์ด ์์ผ๋ฏ๋ก ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํ ์ ์๊ธฐ์ ๋ฐ๋ก ์กฐ๊ฑด์ผ๋ก ๋นผ์ฃผ์๋ค.
์ผ๋ฐ ํ๋ก์ฐ์ ๊ฒฝ์ฐ. validationResult.shouldReturn() ์ด์ด์ ๋ฐ๋ก ๋ฆฌํดํด์ผ ํ๋ ๊ฒฝ์ฐ (์ ํจ์ฑ ๊ฒ์ฆ ๋ก์ง ํ ๋ฒ ํ๋ ธ์ ๊ฒฝ์ฐ ์ฌ๊ธฐ์ ๊ฑธ๋ฆฐ๋ค.) ๋ฐ๋ก returnํด์ ์ฌ์ฉ์๊ฐ ๋ค์ ์ ๋ ฅํ ์ ์๋๋ก ํ๋ค.
์ ํจ์ฑ ๊ฒ์ฆ์ ํต๊ณผํ์ ๊ฒฝ์ฐ, chat์ ๋ณด๋ด๊ธฐ ์ํ ์ค๋น๋ฅผ ํ๋๋ฐ ์ด๋, ์ ํจ์ฑ ๊ฒ์ฆ์ด ๋ ๋ฒ ์ด์ ํ๋ ค์ ์คํตํด์ผ ํ๋ ์ํ์ผ ๊ฒฝ์ฐ ํ๋ฆฐ ์๋ต์ ์บ์ฑํ๋ฉด ์๋๊ธฐ์ ๋ค์์ ์กฐ๊ฑด์ ์ค ๊ฒ์ด๋ค.
1
2
3
if (validated.isValid()) {
saveFlowSpecificData(customerId, sessionId, nextFlow, validated);
}
๊ทธ ๋ค์์๋ ์บ์ฑ๋ ์์๋ค์ ์์งํด์ LLM์ ์์ฒญ์ ๋ณด๋ธ๋ค.
๊ทธ ํ์ ์์ฒญ ํ ์ฒ๋ฆฌํด์ผ ํ ๊ฒ๋ค์ ์ฒ๋ฆฌํ๋๋ฐ, ์ด ๊ณผ์ ์์ ์ํ๊ฐ โrecommendationโ์ด๋ โre_recommendationโ์ธ ๊ฒฝ์ฐ์๋ ์บ์ฑ๋์ด ์๋ ์ํ๋ค์ ๋ํด์ ์๋ตํ ์ ์๋๋ก ํ๋ค.
๋ํ, ๋ง์ง๋ง ์๋ต ํ ์ฒ๋ฆฌ์ ๊ฒฝ์ฐ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ์ ๋ก ๋์ด๊ฐ์ ๊ฐ์ด ์๋ ๊ฒฝ์ฐ๋ ๋ฌด์ํ๋๋ก ์ฒ๋ฆฌํ๋ค.
ํ๊ณ โฆ
๋๋ฆ ๋ง์ด ์ ๊ฒฝ์จ์ ๋ง๋ ์๋น์ค์ด๊ธด ํ์ง๋ง, ๋๋ฆ๋๋ก์ ์์ฌ์์ ์กด์ฌํ๋ค.
์ฐ์ ๋ฉ์ธ chat() ๋ฉ์๋์ ์กฐ๊ฑด ๋ถ๊ธฐ๊ฐ ๊ณผ๋ํ๊ฒ ์ง์ค๋์ด ์์๋ณด๊ธฐ๊ฐ ์ด๋ ต๋ค.
์ฌ์ค ์ด๋ง์ ๋ 1์ฐจ ๋ฆฌํฉํฐ๋ง๋ ๋ฉ์๋ ๋ณ๋ก ๋ถ๋ฆฌํ๊ฑด๋ฐ๋ ์ด๋ ๊ฒ ๋ฉ์ธ ๋ฉ์๋์ if ์กฐ๊ฑด๋ถ๊ธฐ๋ผ๋์ง, ํ๋ก์ฐ๋ณ ๋ฉ์๋ ๋ผ๋์งโฆ์ด ๋ชจ๋ ๊ฒ ๋๋ฌด ์ธ๋ถ์ ์ผ๋ก ๋ง์ด ์ชผ๊ฐ์ ธ์๋ค.
๋ํ start ํ๋ก์ฐ์ ์ผ๋ฐ ํ๋ก์ฐ์ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ฐจ์ด๊ฐ ์์์๋ ํ ๋ฉ์๋์ ๋ชฐ๋ ค์์ด ๊ฐ์์ฑ์ด ๋๋ฌด ์์ข๋ค.
๊ทธ๋์ ํฅํ ๋ฆฌํฉํฐ๋ง ์์๋ ํ๋ก์ฐ ํ์ ๋ณ๋ก ๋ฉ์๋๋ฅผ ์ ์ํด์ ๊ฐ์์ฑ์ด ์ข๊ฒ ๋ง๋ค ์์ ์ด๋ค.
๋ํ ๋ฉ์ธ ๋ฉ์๋๋ ๋ผ์ฐํฐ์ ์ญํ ๋ง ์ํํ๊ณ , ํ๋ก์ฐ๋ณ ํต์ฌ ๋ก์ง๋ค์ ํ๋ก์ฐ๋ณ ๋ฉ์๋ ์์์ ๋์๊ฐ ์ ์๋๋ก ํ์ฌ ์์ง๋๋ฅผ ๋์ฌ๋ณด๋ ค๊ณ ํ๋ค.
์ค๋ฒ ์์ง๋์ด๋ง์ ๋ํ ๊ณ ์ฐฐ
์ด์ ๋ ์์ค๊น์ง ์ฑ๋ด์ ์ฌํ ๊ตฌํ ํ๋ฉด์ ๋๋ฆ๋๋ก์ ๊ณ ๋ฏผ์ด ๊ต์ฅํ ๋ง์๋ค.
์ผ๋ฐ์ ์ธ ์ฑ๋ด์ ๊ฒฝ์ฐ๋ ์ด ์ ๋๊น์ง ์ฌํ ๊ตฌํ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ด ์๊ธฐ ๋๋ฌธ์ด๋คโฆ
๊ณผ์ฐ ์ฑ๋ด์ ์ด์ ๋ ํ์ ๋ค์ด๋๊ฒ ์ฌ๋ฐ๋ฅธ ์ ํ์ธ๊ฐ? ์ค๋ฒ ์์ง๋์ด๋ง์ด ์๋๊น? ์์งํ ์ ๋ง์ ๊ณ ๋ฏผ์ ํ๋ค.
์ด ๊ณ ๋ฏผ์ ๋ด๊ฐ ์ฌ๋ฆฐ ํ PR์๋ ๊ทธ๋๋ก ๋ น์์๋คโฆ.
[sunjung] feat: ์ฑํ ํ๋ก์ฐ ๊ด๋ฆฌ๋ฅผ boot ๋ฉ์ธ ์๋ฒ์ ์์
๊ทธ๋ฌ๋ ์ฐ๋ฆฌ ์์ ๊ณผ์ ์ ๋ฉ์ธ ์๋ฒ๋ฅผ spring boot๋ก ์งํํด์ผ ํ๋ค๋ฉด, ์๋ฒ๊ฐ์ ์ญํ ์ ๋ช ํํ ํ๋ ๊ฒ์ด ๋ง๊ธฐ์ ์ด๋ฐ ๋ฐฉ์์ด ์ ๋ ์ค๋ฒ ์์ง๋์ด๋ง์ด ์๋, ์ํคํ ์ฒ์ ํ์ ์๊ตฌ์ฌํญ์ ํด๋นํ๋ค๊ณ ์๊ฐํ๋ค.
fast api ํ๋๋ก๋ง ์งํํ๋ค๋ฉด ํ๋กฌํํฐ ์์ง๋์ด๋ง์ผ๋ก ๋๋ถ๋ถ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ฒ ์ง๋ง, boot์๋ฒ๋ฅผ ๋ฉ์ธ์ผ๋ก ๋๊ณ ์ ํ๋ค๋ฉด ์ด๋ ๊ฒ ๋ฉ์ธ ์๋ฒ์์ ํ๋ก์ฐ ๊ด๋ฆฌ๋ฅผ ์ฑ ์์ง๊ฒ ํ๊ณ , redis๋ฅผ ์ด์ฉํด์ ์๋ฒ ์ํ๋ฅผ statelessํ๊ฒ ์ ์งํ๋๊ฒ ํฅํ ํ์ฅ์ฑ์ ๊ต์ฅํ ์ ๋ฆฌํ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค.
๋ํ, ๊ธ์ต๊ถ์์๋ ๋ณด์์ ์ํด ์ค์ ๋ก ์ฝ์ด ๋ฑ ํน ์๋น์ค์ ai ์๋น์ค๋ฅผ ๋๋ ์ ์ด์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๊ธฐ์ ํฉ๋ฆฌ์ ์ธ ๋ฐฉ์์ด์๋ค๊ณ ์๊ฐํ๋ค. ๊ฒฐ๊ตญ ์ฐ๋ฆฌ๋ ๊ธ์ต ๋๋ฉ์ธ์ ์๋น์ค์ด๋ฏ๋ก
๋๋จธ์ง ๋ก์ง์ ๋ํ ๋ถ์์ ๋ค์ ํฌ์คํ ์์~