고갱
[인공지능] 졸업 작품 #4, LLM + RAG 본문
벌써 2024년이 끝나고 2025년이 시작되었다는게 믿기지가 않는다.
한 건 많이 없는 것 같은데 시간은 야속하게도 빠르게 흘러가는 느낌이다.
원래 사용하던 GPU 서버가 초기화되어서 관리자가 세팅을 마칠 때까지 진행을 못하기 때문에 그냥 지금까지 한 것이라도 글로 기록하고 잠깐 휴식기라도 겸사겸사 가지려고 한다.
초기화되는 줄 모르다가 모든 파일이 날아갔었는데 다행히 시놀로지 나스에서 파일을 복구하였다.

그리고 오늘은 중요한 문제를 언급하는데 관심이 있다면 "앞으로 무엇을 해야할까?" 를 읽어보면 좋을 것 같다.
🤔 주요 변경점?
1. 역참조 개념 추가
이게 무슨 말인가 하면 기존에는 해당 조항이 어떤 조항을 참조하는지 references 만 기록해주었다면,
이제는 어떤 조항이 이 문서를 필요로 하는지도 기록한다는 점이다. (dereferences)
이것이 왜 필요하냐면, 예를 들어 아래와 같은 조항이 있다고 가정해보자. (예시로 만든 조항이고 실존하지 않음)
20조(자가격리) 전염병에 걸리면 자가격리를 해야한다.
80조(처벌) 20조를 어기면 1년 이하의 징역형에 처한다.
이런 경우 80조는 20조를 참조한다고 볼 수 있지만, 20조를 가져와도 80조 정보가 없기 때문에 처벌 수위를 알 수 없다.
즉 "전염병에 걸렸는데 자가격리를 하지 않으면 처벌 수위가 어떻게 되나?" 라고 질문하여도 처벌 수위를 가져올 수 없다.
그래서 이제 위와 같은 문제를 해결하기 위해서 문서에서 역참조의 개념을 추가해주었다.



역참조 개념이 없는 기존 답변의 경우에는 관련 근거 조항을 보면 처벌 조항을 받아오지 못해 구체적 처벌 수위가 명시되지 않은 반면에, 역참조 개념이 추가된 새로운 답변의 경우 22조를 가져올 때 역참조 문서인 77조도 가져오기 때문에 구체적인 처벌 수위를 명시해줄 수 있는 모습이다.
2. 메타데이터의 복잡성 해결
이전에 적었던 문제점 중 하나인 메타데이터의 복잡성 문제를 해결하였다.
이전 게시물을 보지 않았던 사람을 위해 다시 복기하자면,
Document 내에 메타데이터에 저장할 수 있는 데이터 형은 오로지 str, int, float, bool 만 해당되는데, 현재 내가 저장하고 있는 데이터 형에는 배열형이 있는 문제가 있었다.


ValueError: Expected metadata value to be a str, int, float or bool, got [] which is a list in upsert.
Try filtering complex metadata from the document using langchain_community.vectorstores.utils.filter_complex_metadata.
그대로 저장하면 위와 같은 오류가 발생하기 때문에 해결해야 한다.
여기서 보이는 filter_complex_metadata는 단지 지원되지 않는 데이터 형을 지워버리는 기능이기 때문에 피해야한다.

이를 해결하기 위해 Vector 데이터베이스에 저장하기 전 배열로 저장해두었던 데이터를 순회하며 문자형으로 변환해주는 작업을 거치게 해두었다.
이후 검색기에서 다시 json.loads 함수를 통해 문자형을 다시 배열로 만들어 사용하게끔 구현함으로써 해결해주었다.
3. 커스텀 검색기 생성
사실 이건 지난 주에 추가된 사항이긴 하지만, 그땐 다소 빈약했고 이제는 어느정도 성능이 나와서 기재한다.

CustomRetriever 라는 BaseRetriever를 상속한 클래스를 만들어주고, 필요한 함수를 오버라이딩 해주었다.
여기서 BaseRetriever는 langchain_core.retrievers 내부에 있는 클래스이다.
BaseRetriever — 🦜🔗 LangChain documentation
BaseRetriever class langchain_core.retrievers.BaseRetriever[source] Bases: RunnableSerializable[str, list[Document]], ABC Abstract base class for a Document retrieval system. A retrieval system is defined as something that can take string queries and retur
python.langchain.com
위 페이지를 읽고 만들었는데, 이 문서를 찾는데 생각보다 오래걸려 복잡했다.. 얼마나 많은 검색을 통해 찾았는지..
아무튼 지난 주에는 역참조 문서를 검색하는 기능이 없었지만, 이제는 역참조 문서도 검색하는 기능을 추가 구현하였다.
검색기를 통해 문서 검색을 시작하면 우선 검색을 통해 가장 유사도가 높은 문서를 가져오고 이 문서의 메타데이터를 통해 연결된 나머지 문서를 가져오게끔 검색기를 구현하였다.
다행히 $in 필터를 지원하기 때문에 생각보다 구현은 쉬웠다.
(지원하지 않아도 어렵진 않았겠지만 반복문으로 하나하나 검색해야 하기 때문에 보다 가독성이 떨어졌을 것이다.)
이후 모든 결과를 병합하여 돌려보냄으로써 과정이 끝난다.
4. 현행법 여부 판별
이건 이번에 발견한 문제인데, 검색기에서 간혹 중복되는 문서가 나오는 문제점이 있었다.
정확히는 같은 문서 번호를 가진 문서가 2개가 있었다.
위에서 메타데이터 복잡성 문제를 해결하던 중에 알게되었는데 json.dumps 함수를 통해 문자형으로 바꿔주던 중 이상하게 일부 문서에서 json.dumps 가 두 번 거쳐진 결과가 있었던 것이다.
예를 들어 references 필드에 ["11-1", "12-1"] 이라는 문자형 값이 있어야 한다고 치면,
간혹 어떤 문서에서는 "[\"11-1\", \"12-1\"]" 와 같이 저장되있는 것이었다. (외부 큰 따옴표 포함)

왜 이렇게 나오는가 했더니 아래와 같이 두 개로 나뉘어진 조항이 있었던 것이다.


이런 경우 결국 동일한 article_number로 가져온 문서에 대해 json.dumps 함수가 두 번 거쳐지기 때문에 결과가 이상했던 것이다.
따라서 이를 해결하기 위해 현재 시행중인 법령인지를 검사하는 함수를 만들어주고, 메타데이터를 생성해주는 과정에서 현재 시행중인 법령이 아닌 경우에 해당 조항을 건너뛰게끔 구현함으로써 해결해주었다.
🤔 사소한 변경점
1. 메타데이터 로직 변경


문서 번호 검색 및 참조를 처리하는 과정도 다소 변화가 있다.
우선, 참조와 역참조 문서를 처리하기 위해 초기 메타데이터를 설정할 때 references와 dereferences 필드를 빈 배열로 설정해주도록 한다.
이렇게 되면 기존에 참조 문서를 처리하기 위해 정규식을 이용한 findall 함수를 사용할 필요가 없기 때문에 search 함수로 변경해주었다.
text 필드는 이후 절차에서 역참조 문서와 참조 문서를 처리하기 위해 임시로 넣어둔 것이고, 이후 절차에서 꺼내서 처리한 후 삭제한다.
2. 참조 처리 변경 (범위 부분 구현)


메타데이터 로직에서 임시로 넣어둔 text 필드를 pop 함수로 꺼내서 처리해주는 과정이다.
(?<!」)(?<!」\s)제(\d+)조(?:의(\d+))?
이처럼 findall 함수에서 이전과 달라진 정규식도 있는데 이는 "「간호법」 제18조" 나 "「의료법」 제3조제2항" 과 같이 다른 법을 참조하는 경우는 무시하기 위해 이처럼 적용하였다.
이는 추후에 해결해야할 문제이긴 하지만, 당장에는 아예 제외처리 하였다.




또한 여기에서 "제(\d+)조부터\s*제(\d+)조" 정규식을 통한 findall 함수를 통해 범위도 처리하게끔 구현해두었다.
이 과정에서 해당 조항이 존재하지 않는 경우에는 추가하지 않게 처리하였다.
당장에는 문제가 없지만, 조금 더 지켜봐야 될 것 같다.
❓ 어떤 문제점?
1. 일부 참조 문서의 오류
이건 어렵지 않은 문제지만, 아래 사진과 같이 "[종전 제xx조의x은 제xx조의x로 이동]" 과 같은 내용이 포함되어 있는 경우에 발생하는 문제다. (또는 그런 형식이 포함된 경우)



사실 이건 그냥 정규식을 통해 지워주면 되는 부분이라 어렵지 않을 것으로 예상 중이다.
❗ 앞으로 무엇을 해야할까?
문제점으로 기술하지는 않았으나 RAG 과정에서 다소 변경해야할 점이 있다.
지금 하고 있는 방법은 'BAAI/bge-m3' 모델을 사용해 임베딩을 하고, 이를 검색기로 가져오는 방법을 사용하고 있는데,
문서의 유사도 검색이 아쉬운 결과를 보이고 있다.
이는 임베딩 모델이 문장의 맥락이나 의도 등을 파악하지 못해서 나타나는 문제로 추정 중이다.
홍길동씨는 2024년 12월 30일 학교에서 커피 한잔 마셨다.
예를 들어 위와 같은 문장으로 법령을 검색하면 아래와 같은 조항이 검색 결과로 도출되는데 이는 맥락이나 의도 측면에서 관련성이 떨어진다고 볼 수 있다. 단지 문장의 유사도만을 계산하여 나온 결과인 것이다. (아마 학교라는 단어에서 유사도가 높게 측정되었지 않았을까 유추하고 있다.)
제31조(예방접종 완료 여부의 확인) ① 특별자치시장ㆍ특별자치도지사 또는 시장ㆍ군수ㆍ구청장은 초등학교와 중학교의 장에게 「학교보건법」 제10조에 따른 예방접종 완료 여부에 대한 검사 기록을 제출하도록 요청할 수 있다. <개정 2023. 6. 13.>
② 특별자치시장ㆍ특별자치도지사 또는 시장ㆍ군수ㆍ구청장은 「유아교육법」에 따른 유치원의 장과 「영유아보육법」에 따른 어린이집의 원장에게 보건복지부령으로 정하는 바에 따라 영유아의 예방접종 여부를 확인하도록 요청할 수 있다. <개정 2010. 1. 18., 2011. 6. 7., 2023. 6. 13.>
③ 특별자치시장ㆍ특별자치도지사 또는 시장ㆍ군수ㆍ구청장은 제1항에 따른 제출 기록 및 제2항에 따른 확인 결과를 확인하여 예방접종을 끝내지 못한 영유아, 학생 등이 있으면 그 영유아 또는 학생 등에게 예방접종을 하여야 한다. <개정 2023. 6. 13.>
이를 해결하기 위해서 임베딩 모델의 변경 및 리랭커 도입이 필요하다고 판단하고 있다.
임베딩을 통해 상위 문서를 여러개 가져오고 리랭커를 통해 관련성이 높은 문서를 가져오게끔 하는 방식으로 말이다.
아무래도 정확도가 중요한 서비스이다 보니까 임베딩 기반의 리랭커보다는 LLM 기반의 리랭커 (또는 LM 기반) 를 도입하여 보다 높은 정확도를 뽑아내고자 하는 것이 목표이다.

위는 예상 프롬프트이고 이를 하나하나 모두 비교하는 형식으로 가면 정확도는 뛰어나겠지만 시간상 좋은 방법이 아닐 수 있으니 여러가지 방법 중 하나를 택하고자 한다.
아마 논문에 나온 방법 중 하나를 선택해보지 않을까 싶기도 한데, 이건 조금 더 고민해보아야 겠다.
논문에 나온 방법은 전부 비교 (All pair compairisons), Sorting Algorithm을 이용한 정렬, Sliding Window를 이용한 정렬이 있었는데 여기서 흥미로운 점은 Sliding Window를 이용한 정렬이 성능이 괜찮았던 점이다.
(정확히는 시간 복잡도가 낮으면서 성능이 괜찮았다는 점, 정확도는 전부 비교를 따라갈 수는 없다..)
참고로 위에서 언급한 예상 프롬프트는 논문에서 참조하였다. (논문은 하단에 기재하겠다.)

그 외에는 위에서 언급한 문제점 해결인데, 어려운 문제는 아니라서 별도로 다시 다루진 않았다.
👍 참고 자료
검색기 문서
BaseRetriever — 🦜🔗 LangChain documentation
BaseRetriever class langchain_core.retrievers.BaseRetriever[source] Bases: RunnableSerializable[str, list[Document]], ABC Abstract base class for a Document retrieval system. A retrieval system is defined as something that can take string queries and retur
python.langchain.com
참고 논문
Qin, Z., Jagerman, R., Hui, K., Zhuang, H., Wu, J., Yan, L., Shen, J., Liu, T., Liu, J., Metzler, D., Wang, X., & Bendersky, M. (2023). Large Language Models are Effective Text Rankers with Pairwise Ranking Prompting. arXiv preprint arXiv:2306.17563. https://doi.org/10.48550/arXiv.2306.17563
Large Language Models are Effective Text Rankers with Pairwise Ranking Prompting
Ranking documents using Large Language Models (LLMs) by directly feeding the query and candidate documents into the prompt is an interesting and practical problem. However, researchers have found it difficult to outperform fine-tuned baseline rankers on be
arxiv.org
'인공지능 > 졸업작품' 카테고리의 다른 글
| [인공지능] 졸업 작품 #6, LoRA 파인튜닝 과정 (0) | 2025.03.08 |
|---|---|
| [인공지능] 졸업 작품 #5, LLM 한계점 극복 (0) | 2025.02.05 |
| [인공지능] 졸업 작품 #3, LLM + RAG (0) | 2024.12.22 |
| [인공지능] 졸업 작품 #2, LLM + RAG (0) | 2024.11.20 |
| [인공지능] 졸업 작품 기획 (0) | 2024.11.16 |
