이번 글에서는 Spring Boot와 WebClient를 활용하여 LLM을 통해 크롤링 데이터를 정제하는 방법에 대해 다뤄보겠습니다. 특히, 그래픽카드 정보를 JSON 형식으로 반환받는 예제를 통해, 추후 프로젝트에 추가될 AI 기반 데이터 정제 기술의 PoC를 진행하였습니다.
이번 프로젝트에서는 Ollama와 Llama3 모델을 사용하여 LLM을 구축하였습니다. Ollama는 LLM 모델을 제공하는 플랫폼으로, REST API를 통해 다양한 AI 모델을 손쉽게 활용할 수 있습니다. Llama3는 최신의 대규모 언어 모델로, 텍스트 데이터를 기반으로 한 다양한 작업에 뛰어난 성능을 보입니다.
Ollama는 Mac, Linux 등을 지원합니다. 저의 경우 개발 환경이 Linux, Mac, Windows로 다양하며, 추후에는 Linux 환경에 세팅할 예정입니다. 오늘은 Windows OS를 사용하여 진행하였습니다.
Ollama 설치 후 터미널을 엽니다.
ollama run llama3
Docker와 CLI가 비슷한 방식으로 자동으로 Llama3 이미지를 다운로드하고 실행합니다.
기본 포트는 11434입니다. 로컬호스트에서 REST API를 지원하며, 간단한 예제를 통해 사용 방법을 설명하겠습니다.
Request:
POST http://localhost:11434/api/generate
Content-Type: application/json
{
"model": "llama3",
"prompt": "what makes people happy?",
"stream": false
}
Response:
{
"model": "llama3",
"created_at": "2024-06-13T14:09:01.8260694Z",
"response": "What a great question! Happiness is a complex and multifaceted phenomenon that can be influenced by various factors...",
"done": true,
"done_reason": "stop",
"context": [
128006, 882, 128007, 271, 12840, 3727, 1274, 6380, 30, 128009, 128006, 78191, ...
],
"total_duration": 5832490500,
"load_duration": 1032900,
"prompt_eval_duration": 207415000,
"eval_count": 389,
"eval_duration": 5623223000
}
응답 데이터에서 "context"는 매우 길기 때문에 중략 표기(...)로 줄였습니다.
"stream": false
stream
옵션을 사용하면 응답이 한 글자씩 스트리밍 방식으로 전달됩니다. 그러나 여기서는 이 옵션을 false
로 설정하여 한 번에 모든 응답을 받을 수 있도록 하였습니다.
"format": "json"
응답값의 형식을 JSON 규격에 맞도록 반환합니다. 이 옵션은 REST API에서 유용하게 사용되며, 응답 데이터를 쉽게 파싱할 수 있도록 합니다.
크롤링을 할때, 자연어로 되어있는 경우에 데이터의 정제가 굉장히 어렵습니다. 이를 Json 타입으로 반환받고 원하는 parameter로 매핑하여 반환 받는 예제를 선보이려고 합니다.
이제 프로젝트의 테스트 코드 및 관련 사항을 살펴보겠습니다. 각 테스트 메서드는 특정 그래픽카드 정보가 포함된 텍스트를 LLM에 요청하고, 응답을 검증합니다.
@SpringBootTest
class LLMSampleTest(
@Autowired val webClient: WebClient,
) {
val objectMapper = ObjectMapper()
val defaultPrompt = """
response GraphicCard info of next paragraph with JSON format.
parameters only be 'price', 'model'.
this is sample json format.
"model":"RTX 4070 SUPER MIRACLE X3 WHITE D6X 12GB", "price":475000
and if you cant find the gpu model then return null
and if you cant find the gpu price then return null
and if the price is not only gpu price but also some of mixed items price, then return null all parameters
paragraph:
""".trimIndent()
@Test
@DisplayName("게시글에 포함된 가격 840000원을 반환해야하며, 모델의 이름 일부를 반환해야 한다")
fun ollamaTest1() {
val requestBody = requestBody(item)
val response = requestExecute(requestBody)
val result = objectMapper.readTree(response.body).get("response")
println(result)
assertTrue(result.get("price").asText() == "840000")
assertTrue(result.get("model").asText().contains("RTX") && result.get("model").asText().contains("4070"))
}
@Test
@DisplayName("게시글에 포함된 가격 270000원을 반환해야하며, 모델의 이름 일부를 반환해야한다")
fun ollamaTest2() {
val requestBody = requestBody(item2)
val response = requestExecute(requestBody)
val result = objectMapper.readTree(response.body).get("response")
println(result)
assertTrue(result.get("price").asText() == "270000")
assertTrue(result.get("model").asText().contains("RTX") && result.get("model").asText().contains("3060"))
}
@Test
@DisplayName("게시글에 GPU모델 뿐만 아니라 복합적인 물품이 포함된 가격만 존재할 경우 price는 null이어야 한다")
fun ollamaTest3() {
val requestBody = requestBody(item3)
val response = requestExecute(requestBody)
val result = objectMapper.readTree(response.body).get("response")
println(result)
assertTrue(result.get("price").isNull)
}
private fun requestBody(value: String) = mapOf(
"model" to "llama3",
"prompt" to "$defaultPrompt $value",
"stream" to false,
"format" to "json"
)
private fun requestExecute(requestBody: Map<String, Any>) = webClient.post()
.uri("/generate")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.retrieve()
.toEntity(String::class.java)
.block()
}
private const val item = """
이엠텍 지포스 RTX 4070 SUPER MIRACLE X3 WHITE D6X 12GB 미개봉
840,000원
"""
private const val item2 = """
갤럭시 GALAX 지포스 RTX 3060 EX WHITE OC V2 D6 12GB
270,000원
"""
private const val item3 = """
편집용 게이밍 노트북 HP오멘 rtx3060, i7-12700H, 램 16gb, 512gb ssd
1,200,000원
"""
val defaultPrompt = """
response GraphicCard info of next paragraph with JSON format.
parameters only be 'price', 'model'.
this is sample json format.
"model":"RTX 4070 SUPER MIRACLE X3 WHITE D6X 12GB", "price":475000
and if you cant find the gpu model then return null
and if you cant find the gpu price then return null
and if the price is not only gpu price but also some of mixed items price, then return null all parameters
paragraph:
""".trimIndent()
이 prompt는 LLM에게 주어진 텍스트에서 특정 정보를 추출하도록 지시하는 명령어입니다. 각 부분을 상세히 분석하여 설명하겠습니다.
"response GraphicCard info of next paragraph with JSON format."
의미: LLM에게 다음 문단에서 그래픽카드 정보를 JSON 형식으로 반환하라는 명령입니다.
용도: 그래픽카드와 관련된 정보를 JSON 형식으로 일관되게 추출하기 위해 사용됩니다.
"parameters only be 'price', 'model'."
의미: JSON 응답에서 'price'와 'model'이라는 두 가지 매개변수만 포함하도록 지시합니다.
용도: 필요한 정보만 정확하게 추출하도록 하여, 불필요한 데이터가 포함되지 않도록 합니다.
"this is sample json format."
의미: LLM에게 다음에 제공되는 형식이 샘플 JSON 형식임을 알립니다.
용도: LLM이 응답 형식을 이해하고, 샘플을 참고하여 유사한 형식으로 데이터를 반환하도록 합니다.
"model":"RTX 4070 SUPER MIRACLE X3 WHITE D6X 12GB", "price":475000"
의미: 샘플 JSON 형식으로, 모델과 가격이 각각 'RTX 4070 SUPER MIRACLE X3 WHITE D6X 12GB'와 '475000'으로 설정되어 있습니다.
용도: LLM이 어떤 형식과 구조로 데이터를 반환해야 하는지 명확하게 이해하도록 도와줍니다.
"and if you cant find the gpu model then return null"
의미: 그래픽카드 모델을 찾을 수 없을 경우, 모델 값에 null을 반환하라는 지시입니다.
용도: 데이터의 정확성을 유지하고, 모델 정보가 없는 경우 이를 명확하게 표시하기 위해 사용됩니다.
"and if you cant find the gpu price then return null"
의미: 그래픽카드 가격을 찾을 수 없을 경우, 가격 값에 null을 반환하라는 지시입니다.
용도: 가격 정보가 없는 경우 이를 명확하게 표시하여 데이터의 신뢰성을 높입니다.
"and if the price is not only gpu price but also some of mixed items price, then return null all parameters"
의미: 가격 정보가 그래픽카드 외의 다른 항목들과 섞여 있을 경우, 모든 매개변수 값에 null을 반환하라는 지시입니다.
용도: 정확한 가격 정보를 보장하고, 혼동을 방지하기 위해 사용됩니다.
"paragraph:"
의미: 이 prompt의 마지막 부분으로, 실제 텍스트 문단이 뒤따르게 됩니다.
용도: LLM에게 어떤 문단에서 정보를 추출할지 지정합니다.
prompt는 LLM에게 명확하고 구조화된 방식으로 그래픽카드 정보를 추출하도록 지시합니다. JSON 형식으로 응답을 요청함으로써 데이터를 표준화하고, 모델과 가격 정보를 정확하게 추출하도록 하며, 불명확하거나 혼합된 데이터를 걸러내는 역할을 합니다. 이를 통해 크롤링 데이터를 정제하는 데 있어 중요한 부분을 담당합니다.
ollamaTest1 메서드는 이엠텍 지포스 RTX 4070 그래픽카드가 포함된 텍스트를 테스트합니다.
- item 변수에 그래픽카드와 가격 정보가 담긴 텍스트를 설정합니다.
- requestBody 메서드를 통해 LLM에 보낼 요청 본문을 만듭니다.
- requestExecute 메서드를 호출하여 WebClient를 통해 LLM API에 POST 요청을 보냅니다.
- 응답을 JSON 형식으로 파싱하여 response 객체를 생성합니다.
- 반환된 가격이 "840000"인지, 모델명이 "RTX"와 "4070"을 포함하는지 확인합니다.
- 이 테스트는 올바르게 작동할 경우 해당 텍스트에서 정확한 가격과 모델명을 추출해내는지 검증합니다.
ollamaTest2 메서드는 갤럭시 GALAX RTX 3060 그래픽카드가 포함된 텍스트를 테스트합니다. 테스트 절차는 ollamaTest1과 유사합니다.
- item2 변수에 RTX 3060과 가격 정보가 담긴 텍스트를 설정합니다.
- 반환된 가격이 "270000"인지, 모델명이 "RTX"와 "3060"을 포함하는지 확인합니다.
- 이 테스트는 해당 텍스트에서 정확한 가격과 모델명을 추출해내는지 검증합니다.
ollamaTest3 메서드는 복합적인 물품이 포함된 가격 정보가 있는 텍스트를 테스트합니다.
- item3 변수에 편집용 게이밍 노트북(GPU가 포함된 )과 가격 정보가 담긴 텍스트를 설정합니다.
- requestBody와 requestExecute 메서드를 사용하여 요청을 보냅니다.
- 응답에서 반환된 price 값이 null인지 확인합니다.
- 이 테스트는 복합적인 물품이 포함된 경우, LLM이 가격 정보를 null로 반환하는지 확인합니다.
참고로 아래 코드에 해당되는 크롤링 데이터는 블로깅을 위해 간소화 한것으로 실제로는 굉장히 깁니다. 하나의 예시만 보이겠습니다.
private const val example = """
이엠텍 지포스 RTX 4070 SUPER MIRACLE X3 WHITE D6X 12GB 미개봉
840,000원
"""
const val actual =
" 이전글 다음글목록\n" +
"[컴퓨터] 노트북 \n" +
"편집용 게이밍 노트북 HP오멘 rtx3060, i7-12700H, 램 16gb, 512gb ssd\n" +
"프로필 사진\n" +
"jyc15\n" +
"중고나라 회원 \n" +
"구매문의\n" +
"2024.06.13. 21:53조회 4\n" +
"댓글 0URL 복사\n" +
"상품이미지\n" +
"디지털/가전 > 노트북\n" +
"판매(안전) 편집용 게이밍 노트북 HP오멘 rtx3060, i7-12700H, 램 16gb, 512gb ssd\n" +
"\n" +
"1,200,000원\n" +
"상품 상태\n" +
"사용감 있음\n" +
"결제 방법\n" +
"네이버페이 송금, 네이버페이 안전결제 \n" +
"(무통장, 계좌간편결제 중 선택 가능하며 구매자가\n" +
"수수료 부담) 기타 결제 방식은 판매자와 협의\n" +
"\n" +
"배송 방법\n" +
"직거래\n" +
"거래 지역\n" +
"부평동\n" +
"판매자\n" +
"jy******@naver.com 010-89**-77** 연락처 보기 \n" +
"본인인증 완료\n" +
"\n" +
"거래 후기1건0건0건\n" +
"\n" +
"구매 문의\n" +
"구매 문의 채팅\n" +
"\n" +
"N pay\n" +
"안전결제\n" +
"\n" +
"N pay\n" +
"무료 송금\n" +
"네이버페이 송금은 에스크로 기능이 제공되지 않으며, 판매자에게 결제금액이 바로 전달되는 ‘일반결제'입니다. \n" +
"\n" +
"직접결제 시 아래 사항에 유의해주세요.\n" +
"카페 구매문의 채팅이나 전화 등을 이용해 연락하고 외부 메신저 이용 및 개인 정보 유출에 주의하세요.\n" +
"계좌이체 시 선입금을 유도할 경우 안전한 거래인지 다시 한번 확인하세요.\n" +
"불확실한 판매자(본인 미인증, 해외IP, 사기의심 전화번호)의 물건은 구매하지 말아주세요.\n" +
"\n" +
"네이버에 등록된 판매 물품과 내용은 개별 판매자가 등록한 것으로서, 네이버카페는 등록을 위한 시스템만 제공하며 내용에 대하여 일체의 책임을 지지 않습니다.\n" +
"\n" +
"\uD83D\uDCA5파격할인! 로봇청소기\uD83E\uDD16 8만원 대 ▶https://tracking.joongna.com/dh1\n" +
"\uD83D\uDCF2 중고나라 앱 다운받기 ▶https://tracking.joongna.com/a1\n" +
"거래 전! 카카오톡 간편 사기 조회 ▶https://tracking.joongna.com/kat\n" +
"───────────────────────\n" +
"※ 게시글 수집 및 이용 안내 확인 ▶ https://web.joongna.com/post-policy\n" +
"※ 카페 상품 게시글은 자동으로 중고나라 앱/사이트에 노출합니다. 노출을 원하지 않으실 경우 고객센터로 문의 바랍니다.\n" +
"※ 중고나라는 통신판매의 당사자가 아니며 상품정보, 거래에 관한 의무와 책임은 각 판매자에게 있습니다.\n" +
"※ 중고나라 이용정책을 반드시 확인해 주세요. ▶ https://cafe.naver.com/joonggonara/998699127\n" +
"\n" +
"... 더보기\n" +
" \n" +
"\n" +
" \n" +
"\n" +
"노트북 외관 및 사양 인증사진입니다\n" +
"\n" +
"HP오멘 게이밍 노트북입니다\n" +
"\n" +
"\u200B\n" +
"\n" +
"성능은\n" +
"\n" +
"\u200B\n" +
"\n" +
"그래픽카드 : RTX3060\n" +
"\n" +
"CPU : i7-12700H\n" +
"\n" +
"램 : 16gb\n" +
"\n" +
"용량 : 512gb SSD\n" +
"\n" +
"\u200B\n" +
"\n" +
"입니다.\n" +
"\n" +
"\u200B\n" +
"\n" +
"구입당시 170만원에 구입했고 약 1년 정도 사용했습니다.\n" +
"\n" +
"관심있으시면 연락주세요~\n" +
"\n" +
"태그\n" +
"#게이밍노트북#게임용노트북#게임용#편집용#편집용노트북#편집노트북#rtx3060#i7#노트북#고사양노트북\n" +
"\n" +
"구매 문의\n" +
"구매 문의 채팅\n" +
"\n" +
"N pay\n" +
"안전결제\n" +
"\n" +
"N pay\n" +
"무료 송금\n" +
"주의하세요!\n" +
"\n" +
"외부 메신저를 통해 결제 링크를 전달하거나 현금 결제를 유도할 경우\n" +
"위험 거래일 가능성이 높으니 절대 주의하세요.\n" +
"\n" +
"프로필 사진jyc15님의 게시글 더보기 \n" +
" 댓글0\n" +
" 공유\n" +
"신고\n" +
"클린봇이 악성 댓글을 감지합니다.\n" +
"설정\n" +
"댓글관심글 댓글 알림\n" +
"등록\n" +
"댓글을 입력하세요\n" +
"블루뽀이\n" +
"댓글을 남겨보세요\n" +
"선택된 파일 없음등록\n" +
" 글쓰기목록 TOP\n" +
"'rtx 3060' 검색결과\n" +
"이 카페 글\n" +
"이 키워드 새글 구독하기\n" +
"등록\n" +
"[판매] GTX1660, GTX1080, RTX2060, RTX3060 컴퓨터 팝니다!\t대전아이티월드\t21:54\n" +
"[매입][채굴기] RTX 채굴기 체굴기 3080 3070 3060 등 전국출장매입전문업체 퍼스트컴몰입니다.\t퍼스트컴\t21:02\n" +
"[매입][매입][채굴기]RTX 3060 3070 3080 3090 4090 등 RX,GTX 매입,A4000,A5000,서버\t잇츠 PC매입\t20:56\n" +
"[매입][채굴기] RTX 채굴기 체굴기 3080 3070 3060 등 전국출장매입전문업체 퍼스트컴몰입니다.\t퍼스트컴\t20:46\n" +
"레노버 리전 5 프로 Legion 5 Pro 16ARH7H 6800H, RTX3060 게이밍노트북\tIQ84\t20:20\n" +
"페이징 이동12345\n" +
"전체 카페 글\n" +
"RTX3060이 RTX4070이 되는 마법!!!댓글[41]\t\t2024.06.09.\n" +
"RTX 3060 OC댓글[5]\t\t2024.06.07.\n" +
"MSI 지포스 RTX 3060 벤투스 2X OC D6 8GB 43% 핫딜\t\t2024.06.11.\n" +
"RTX 3060 TI 이엠텍 지포스 STORM X Dual OC 구매 리뷰 후기 정리\t\t2024.06.09.\n" +
"RTX 4060 vs RTX 3060 12GB 신형 vs 기존 주류 그래픽카드 비교댓글[3]\t\t2024.05.28.\n" +
"페이징 이동12345\n" +
"레이어 닫기\n" +
"[출처] 편집용 게이밍 노트북 HP오멘 rtx3060, i7-12700H, 램 16gb, 512gb ssd (중고나라) | 작성자 jyc15"
"""
이번 프로젝트에 LLM을 도입하기위한 PoC를 진행해보았습니다. 결과는 완벽하지는 않지만 꽤나 훌륭한 성능이라고 볼 수 있습니다. 물론 정제된 데이터를 2차 정제하거나, 데이터의 이용방식이 전통적인 db 체계와는 달라질 수 있을 것 같습니다. LLM에서 사용하는 유사도의 개념을 활용한다면 극적인 효과를 볼 수 있을 것 으로 판단 됩니다.