-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGptService.java
More file actions
320 lines (270 loc) · 18 KB
/
GptService.java
File metadata and controls
320 lines (270 loc) · 18 KB
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
package com.kwcapstone.AI;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.List;
import java.util.Map;
import reactor.core.publisher.Mono;
@Service
@RequiredArgsConstructor
public class GptService {
//필드로 선언해줘야함 -> open api 호출할 때 ㅎ필요한 요청 틀임
private final WebClient openAiWebClient;
private final GptConfig gptConfig;
//토큰 계산하는 함수
public int estimateMaxTokens(String promptText) {
final int totalLimit = 4096;
int promptTokens = promptText.trim().split("\\s+").length;
int estimatedTokens = (int) (promptTokens * 1.5);
// 응답에 할당할 수 있는 최대 토큰 수
int remaining = totalLimit - estimatedTokens;
// 안전한 최소 보장 범위 (100~1500 사이 제한)
return Math.max(100, Math.min(remaining, 1500));
}
public int estimateMindMapMaxTokens(String promptText){
final int totalLimit = 4096;
int wordCount = promptText.trim().split("\\s+").length;
int estimatedTokens = (int) (wordCount * 1.5);
// 응답에 할당할 수 있는 최대 토큰 수
int remaining = totalLimit - estimatedTokens;
// 안전한 최소 보장 범위 (100~1500 사이 제한)
return Math.max(100, Math.min(remaining, 1500));
}
//요약본
public String callSummaryOpenAI(String prompt) {
int maxTokens = estimateMaxTokens(prompt);
String promptMessage = """
아래 회의 스크립트는 아이디어 회의 중 일부야. \s
회의 내용을 간결하게 요약해줘.
응답은 반드시 아래 JSON 형식으로 출력해줘:
{
"title": "핵심 주제를 대표하는 간결한 제목",
"content": "회의의 핵심 흐름과 논의된 주요 사항을 정리한 본문 내용"
}
주의사항:
- title은 15자 이내로, 내용을 대표할 수 있는 요약 문구로 작성해줘.
- content는 3~4문장 이내로 회의 핵심 내용을 압축해서 설명해줘.
- '더 도와드릴까요?' 같은 멘트는 절대 포함하지 마.
- 이 형식을 꼭꼭 지켜줘. Json 형식이며 필드는 꼭 title과 content여야 해.
""".formatted(maxTokens);
Map<String, Object> requestBody = Map.of(
"model", gptConfig.getModel(),
"messages", List.of(
Map.of("role", "system", "content", promptMessage),
Map.of("role", "user", "content", prompt) //원문 내용
),
"temperature", 0.3, //창의성(무작위성)을 조절하는 파라미터
"max_tokens", maxTokens
);
// block()을 써서 동기적으로 받기
return openAiWebClient.post()
.uri("/chat/completions") // gpt 한테 보내는 엔드포인트
.bodyValue(requestBody) //요청 본문 담을 데이터
.retrieve() // 응답을 받아오는 단계 -> 비동기 응답 객체로 흐름이 바뀜
.bodyToMono(Map.class) // JSON으로 받음
.map(response -> {
List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
return message.get("content").toString().trim();
})
.onErrorResume(e -> Mono.just("Error: " + e.getMessage()))
.block(); // block은 동기식으로 기다리기 (필요 시 비동기 방식으로 분리 가능)
}
public String callMainOpenAI(String prompt) {
int maxTokens = estimateMaxTokens(prompt);
String promptMessage = """
다음 텍스트는 현재 진행 중인 아이디에이션 과정의 스크립트야.
오로지 이 스크립트만을 보고 주제를 파악하고 현재 진행되고 있는 이 대화의 주요 키워드 5개를 파악해야해.
주요 키워드만으로 구성된 JSON 배열로 추출해줘.
주요 키워드 외의 다른 말은 필요 없어.
**응답으로 와야할 JSON 형식 (이건 그저 형식일 뿐, 대화의 요지를 파악 못했을 때 이 5개를 보내라는 의미는 아니야.)
["React", "블록체인", "GPU", "그래픽AI", "클라우드"]
""".formatted(maxTokens);
Map<String, Object> requestBody = Map.of(
"model", gptConfig.getModel(),
"messages", List.of(
Map.of("role", "system", "content", promptMessage),
Map.of("role", "user", "content", prompt) //원문 내용
),
"temperature", 0.3, //창의성(무작위성)을 조절하는 파라미터
"max_tokens", maxTokens
);
// block()을 써서 동기적으로 받기
return openAiWebClient.post()
.uri("/chat/completions") // gpt 한테 보내는 엔드포인트
.bodyValue(requestBody) //요청 본문 담을 데이터
.retrieve() // 응답을 받아오는 단계 -> 비동기 응답 객체로 흐름이 바뀜
.bodyToMono(Map.class) // JSON으로 받음
.map(response -> {
List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
return message.get("content").toString().trim();
})
.onErrorResume(e -> Mono.just("Error: " + e.getMessage()))
.block(); // block은 동기식으로 기다리기 (필요 시 비동기 방식으로 분리 가능)
}
public int estimateRecommendResponseTokens(String promptText) {
int promptTokenEstimate = (int) (promptText.trim().split("\\s+").length*1.4);
int totalMax = 4096;
int remaining = totalMax - promptTokenEstimate - 100;
return Math.min(Math.max(remaining, 100), 1000);
}
public String callRecommendedKeywords(String prompt) {
int maxTokens = estimateRecommendResponseTokens(prompt);
String fullPrompt = """
아래 회의 스크립트는 아이디어 회의 중 일부야.
지금 논의된 주제를 바탕으로, 다음 회의에서 더 발전시켜볼 만한 창의적이고, 새로운 아이디어 키워드 5개를 추천해줘. 제약사항은 다음과 같아.
1. 기존의 내용을 요약하지 않아야 함.
2. 이전 내용을 반복하지 않아야 함.
3. 키워드는 5글자 이내여야 함.
4. "더 필요하신거 있으신가요" 과 같은 답변 이어서 하면 안됨.
5. 반드시 JSON 배열로만 추출할 것. 예: ["React", "블록체인", "GPU", "그래픽AI", "클라우드"]
6. 스크립트 부족하다고 같은 추천 키워드만 보내지 마.
""" + prompt;
Map<String, Object> requestBody = Map.of(
"model", gptConfig.getModel(),
"messages", List.of(
Map.of("role", "user", "content", fullPrompt)
),
"temperature", 0.3,
"max_tokens", maxTokens
);
return openAiWebClient.post()
.uri("/chat/completions") // gpt 한테 보내는 엔드포인트
.bodyValue(requestBody) //요청 본문 담을 데이터
.retrieve() // 응답을 받아오는 단계 -> 비동기 응답 객체로 흐름이 바뀜
.bodyToMono(Map.class) // JSON으로 받음
.map(response -> {
List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
String content = message.get("content").toString().trim();
return content;
})
.onErrorResume(e -> Mono.just("Error: " + e.getMessage()))
.block();
}
//주요 노드 추가하기
public String callMindMapNode(String prompt){
int maxTokens = estimateMindMapMaxTokens(prompt);
String promptMessage = """
다음 스크립트는 회의에서 논의된 내용이야.
이 내용에서 주요 아이디어나 핵심 개념을 기준으로 마인드맵 노드를 구성하려고 해.
이때 내가 Json 구조를 같이 보냈다면 기존의 node 구조를 보낸거야.
기존의 node 구조를 바탕으로 다음 텍스트에 맞는 노드들을 뻗어나가는 형식으로 해야해.
다음 조건이 제일 중요해.
*** 기존의 node 구조의 틀과 내용을 크게 변경하면 안된다.
각 노드는 1~2단어 또는 짧은 문장으로 구성되어야 하고,
하나의 노드는 하나의 주제나 개념을 담고 있어야 해.
각 노드는 다음과 같은 정보가 필요해:
- id: 노드 고유 ID (아무 값이나 string으로 설정해도 좋아)
- label: 노드에 들어갈 핵심 키워드 또는 문장 요약
- parentId: 부모 노드의 ID (루트 노드는 null)
- position : 마인드맵을 React Flow에서 시각화할 때 사용할 x, y 좌표
**주의할 점**
- position은 React Flow에서 사용되는 좌표야. x는 좌우, y는 상하 위치를 의미하고 숫자(px) 단위로 줘.
- 루트 노드는 (x=0, y=0)에서 시작하고, 자식 노드는 y 간격 50~70 정도씩 아래로 배치하고, 형제 노드는 x 간격 150 정도씩 떨어뜨려서 배치해줘.
- 물론 내가 말한 x, y 간격이 텍스트 길이 때문에 겹칠 것 같다면 너가 적당히 너무 멀지 않도록 간격 조절해줘.
- 그리고 노드에서 또 뻗어나아가는 노드 가지들이 있을 텐데 그 각각의 가지 사이의 간격도 엄청 띄우지 않도록 해줘.
- 처음 노드 생성할 때 제일 중심이 되는 노드(input) 를 중심으로 밑으로만 자식 노드가 생성 되는 것이 아닌 중간 노드 위의 위치에도 자식 노드가 생성되도록 신경써줘.
- 즉, 계층 구조를 시각적으로 표현하기 좋도록 적절한 위치 값을 계산해서 넣어줘.
- 마인드맵은 밑으로만 내려가는 계층 구조가 아니라 위, 아래, 옆으로 자식 노드를 펼치는 형태야, 계층 구조를 유지하되, 자식 노드들의 위치를 아래만 위치하는 것이 아닌 위 아래 좌우로 퍼져있는 마인드맵을 완성시켜줘야해.
- 또한, 루트 노드는 하나만 있어야 해. 이게 이 대화의 메인 주제가 되는거야.
- 기존 노드 변경할 필요가 없다고 했지만, 내용 상, 해당 노드의 키워드가 적절치 않고 지금 내용이 더 옳다면 노드는 가만히 두 되, 키워드 정도만 변경하는건 허용해. 물론 무차별적으로 많이 변경하는 것은 안 돼.
결과는 JSON 배열 형태로 줘. 예시는 다음과 같아:
[
{ "id": "1", "label": "졸업작품 아이디에이션", "parentId": null, "position": { "x": 0, "y": 0 } },
{ "id": "2", "label": "실시간 마인드맵", "parentId": "1", "position": { "x": 0, "y": 0 } },
{ "id": "3", "label": "토레타 챌린지", "parentId": "1", "position": { "x": 0, "y": 0 } },
{ "id": "4", "label": "구현 난이도", "parentId": "2", "position": { "x": 0, "y": 0 } }
]
이렇게 계층 구조를 JSON 배열로 표현해줘.
""".formatted(maxTokens);
Map<String, Object> requestBody = Map.of(
"model", gptConfig.getModel(),
"messages", List.of(
Map.of("role", "system", "content", promptMessage),
Map.of("role", "user", "content", prompt) //원문 내용
),
"temperature", 0.3, //창의성(무작위성)을 조절하는 파라미터
"max_tokens", maxTokens
);
// block()을 써서 동기적으로 받기
return openAiWebClient.post()
.uri("/chat/completions") // gpt 한테 보내는 엔드포인트
.bodyValue(requestBody) //요청 본문 담을 데이터
.retrieve() // 응답을 받아오는 단계 -> 비동기 응답 객체로 흐름이 바뀜
.bodyToMono(Map.class) // JSON으로 받음
.map(response -> {
List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
return message.get("content").toString().trim();
})
.onErrorResume(e -> Mono.just("Error: " + e.getMessage()))
.block(); // block은 동기식으로 기다리기 (필요 시 비동기 방식으로 분리 가능
}
public String modifyMainOpenAI(String prompt) {
int maxTokens = estimateMaxTokens(prompt);
String promptMessage = """
다음 텍스트는 현재 진행 중인 아이디에이션 과정에서 수정된 node야.
Json 형태로 node 구조를 보냈는데 이를 보고 주제를 다시 파악하고 현재 아이디에이션에 대한 주요 키워드를 5개 알려줘.
주요 키워드만 대답해주면 돼. "더 필요하신거 있으신가요" 과 같은 답변 이어서 하지마. JSON 배열로 추출해줘.
""".formatted(maxTokens);
Map<String, Object> requestBody = Map.of(
"model", gptConfig.getModel(),
"messages", List.of(
Map.of("role", "system", "content", promptMessage),
Map.of("role", "user", "content", prompt) //원문 내용
),
"temperature", 0.3, //창의성(무작위성)을 조절하는 파라미터
"max_tokens", maxTokens
);
// block()을 써서 동기적으로 받기
return openAiWebClient.post()
.uri("/chat/completions") // gpt 한테 보내는 엔드포인트
.bodyValue(requestBody) //요청 본문 담을 데이터
.retrieve() // 응답을 받아오는 단계 -> 비동기 응답 객체로 흐름이 바뀜
.bodyToMono(Map.class) // JSON으로 받음
.map(response -> {
List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
return message.get("content").toString().trim();
})
.onErrorResume(e -> Mono.just("Error: " + e.getMessage()))
.block(); // block은 동기식으로 기다리기 (필요 시 비동기 방식으로 분리 가능)
}
public String modifyRecommendedKeywords(String prompt) {
int maxTokens = estimateRecommendResponseTokens(prompt);
String fullPrompt = """
다음 텍스트는 현재 진행 중인 아이디에이션 과정에서 수정된 node야.
Json 형태로 node 구조를 보냈는데 이를 보고 주제를 파악해서, 다음 회의에서 더 발전시켜볼 만한 창의적이고, 새로운 아이디어 키워드 5개를 추천해줘. 제약사항은 다음과 같아.
1. 기존의 내용을 요약하지 않아야 함.
2. 이전 내용을 반복하지 않아야 함.
3. 키워드는 5글자 이내여야 함.
4. "더 필요하신거 있으신가요" 과 같은 답변 이어서 하면 안됨.
5. 반드시 JSON 배열로만 추출할 것. 예: ["React", "블록체인", "GPU", "그래픽AI", "클라우드"]
""" + prompt;
Map<String, Object> requestBody = Map.of(
"model", gptConfig.getModel(),
"messages", List.of(
Map.of("role", "user", "content", fullPrompt)
),
"temperature", 0.3,
"max_tokens", maxTokens
);
return openAiWebClient.post()
.uri("/chat/completions") // gpt 한테 보내는 엔드포인트
.bodyValue(requestBody) //요청 본문 담을 데이터
.retrieve() // 응답을 받아오는 단계 -> 비동기 응답 객체로 흐름이 바뀜
.bodyToMono(Map.class) // JSON으로 받음
.map(response -> {
List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get("choices");
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
String content = message.get("content").toString().trim();
return content;
})
.onErrorResume(e -> Mono.just("Error: " + e.getMessage()))
.block();
}
}