이전에 에티오피아에서 학생들에게 전자정부표준프레임워크를 소개해주는 과목을 맡아서 교수님을 보조했었는데 졸업 전에 인수인계 겸 그 학생들의 캡스톤 과목 실습문제를 만드는 기회를 얻게 되었다.
이 캡스톤 과목에서 사용할 실습문제는 학교에서 실제로 내가 진행했던 졸업프로젝트를 간략화 해서 만드는 것으로 결정되었다.
그래서 GPT를 끌어와서 사용해보는 파트, OCR로 글을 읽고 요약하는 파트, 긴 단락의 주요 단어를 시각화하는 파트, 총 3가지 파트로 나눠서 실습문제를 만들기로 했다.
eGovFramework에서 기본적으로 웹 세팅하는 것은 이전에 포스팅했던 블로그를 참고하면 된다.
이번엔 학생들에게 무언가 결과물이 나올 수 있게 페이지들은 대충 bootstrap으로 껍질만 씌워뒀다.

GPT는 theokanning 라이브러리를 사용한다.
ChatGPT를 불러오기 위한 일부 파라미터 (모델명, 토큰 등)을 미리 맞춰두고 GPT를 사용하기 위한 함수를 구성한다.
public String useGPT(String prompt, String content) {
OpenAiService service = new OpenAiService(gptKey,Duration.ofMinutes(9999)); // OpenAI 서비스 연결
List<ChatMessage> message = new ArrayList<ChatMessage>(); // GPT에게 보낼 메세지 어레이
message.add(new ChatMessage("user", prompt));
message.add(new ChatMessage("user", content));
ChatCompletionRequest completionRequest = ChatCompletionRequest.builder() // OpenAI의 모델 선택하여 메세지 전달후 결과 텍스트 받기
.messages(message)
.model(gptModel) // 터보 3.5모델 사용.
.maxTokens(4000) // 입력과 출력중 출력에 할당되는 최대 토큰 값. 현재 입출력 최대 토큰 16,385
.temperature((double) 0.3f) // 답변의 자유도 설정
.build();
return service.createChatCompletion(completionRequest).getChoices().get(0)
.getMessage().getContent();
}
이 함수를 이용해서 간단하게 POST방식으로 model에 결과를 추가하고 보내도록 구성하면 된다.
@RequestMapping(value = "/useGPT.do", method = RequestMethod.POST)
public String doGPT(String prompt, String content, Model model) {
String result = "";
UseGPT gpt = new UseGPT(gptKey, gptModel, Integer.parseInt(gptMaxInputToken), Integer.parseInt(gptMaxOutputToken));
result = gpt.useGPT(prompt, content);
System.out.println("result: " + result);
/* 결과들을 웹페이지 모델에 요소들로 추가 */
model.addAttribute("resultContent", result);
return "GPT";
}

OCR은 tess4j 라이브러리를 사용했다.
tess4j 라이브러리는 maven에서 다음과 같이 dependency를 추가할 수 있다.
<dependency>
<groupId>net.sourceforge.tess4j</groupId>
<artifactId>tess4j</artifactId>
<version>4.5.4 </version>
</dependency>
하지만 모델 파일은 따로 다운받아야 한다.
그 뒤 Tesseract를 사용할 때 path를 지정해주면 사용이 가능하다.
tesseract.setDatapath("C:/Program Files/Tesseract-OCR/tessdata");
OCR을 쓰기 위해선 이미지 파일을 받아와야 한다.
웹에서 이미지를 받으면 Controller에서 MultipartFile로 받아와야 한다.
단 MultipartFile을 사용하려면 사전 세팅이 필요하다.
@MultipartConfig을 class 상단에 적어야한다.
@MultipartConfig(fileSizeThreshold = 1024 * 1024, maxFileSize = 1024 * 1024 * 5, maxRequestSize = 1024 * 1024 * 5 * 5)
public class OpenAIController {
...
}
tomcat서버의 context.xml 설정이 필요하다.
<Context allowCasualMultipartParsing="true" path="/">
...
</Context>
위 두 함수를 이용해 ocr 결과를 model로 보낸다.
@RequestMapping(value = "/useOCR.do", method = RequestMethod.POST)
public String doOCR(@RequestParam("image") MultipartFile file, Model model) {
try {
String filePath = saveUploadedFile(file);
String ocrResult = performOCR(filePath);
model.addAttribute("ocrResult", ocrResult);
} catch (Exception e) {
model.addAttribute("error", "Error processing the image: " + e.getMessage());
}
return "OCR";
}

Visual 파트에선 textarea에서 문단 내용을 받고 그 내용에서 주요 단어들의 빈도수를 JSON 형식으로 치환한 뒤 그걸 라이브러리를 통해 wordcloud로 이미지를 도출하는 것이 목적이다.
여기선 ECharts + WordCloud 플러그인을 사용하기로 하였다.
다음과 같이 CDN으로 echarts-wordcloud를 추가한다
<head>
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts-wordcloud@2.1.0/dist/echarts-wordcloud.min.js"></script>
</head>
이 부분은 잘 다듬은 프롬프트를 통해 GPT에서 처리가 가능하다.
텍스트에서 키워드를 추출하고 각 키워드의 빈도를 계산하여 JSON 형식({예: 3})으로 표시하세요.
위 형식에서 쉼표 제거, 키워드 개수 조정 등 다양한 조건들을 조정해주는 프롬프트를 추가하면 보다 정확하게 JSON 형식으로 키워드를 추출할 수 있다.
@RequestMapping(value = "/useVisual.do", method = RequestMethod.POST)
public String visual(String content, Model model) {
String jsonTag = "";
String prompt = Prompt.getPrompt();
UseGPT gpt = new UseGPT(gptKey, gptModel, Integer.parseInt(gptMaxInputToken), Integer.parseInt(gptMaxOutputToken));
jsonTag = gpt.useGPT(prompt, content);
System.out.println("jsonTag: " + jsonTag);
model.addAttribute("jsonTag", jsonTag);
return "Visual";
}
jsp파일에서 ECharts + WordCloud을 통해 JSON 데이터를 JavaScript 객체로 변환하는 과정을 추가하면 구현할 수 있다.
<script>
var jsonTag = ${jsonTag};
var words = Object.entries(jsonTag).map(([word, value]) => ({ name: word, value }));
var chart = echarts.init(document.getElementById('wordCloud'));
var option = {
series: [{
type: 'wordCloud',
shape: 'circle',
sizeRange: [12, 50],
rotationRange: [-90, 90],
gridSize: 2,
textStyle: { fontFamily: 'Arial', fontWeight: 'bold' },
data: words
}]
};
chart.setOption(option);
</script>

이 화면에서 내용을 입력하면,

jsonTag가 위와 같이 Console에서 확인 할 수 있으며,

다음과 같이 이미지가 나오게 된다.
기능 구현이 대강 잡혀있긴 하지만 실제로 학생들이 따라해보고 작동시켜보기 위해선 세세한 부분들을 좀 더 보완해야 한다.
아무래도 학생들이 구현할 때 따라하기 쉽도록 코드를 짜려다보니 신경쓸 부분들이 좀 있었던 것 같다.