내가 Cursor for 로톡 변호사를 만들며 배운것

by 김준호

1. 무엇을 만들었나

Cursor가 코드를 자동완성해 주듯이, 로톡에서 변호사님들이 답변을 쓰실 때 ai가 자동으로 답변 초안을 작성해주는 크롬 익스텐션을 만들었습니다.

https://www.botoai.co/

크롬 익스텐션으로 만든 이유는 변호사님들이 원래 작업하시던 방식 그대로 작업하시면서 동시에 ai 자동 완성 서비스를 이용하실 수 있도록 하는 방법이 익스텐션 방식 뿐이기 때문입니다.


2. 시스템 아키텍쳐


239de6c4ba1a9b276ecefc59df906668dea711f4eebeb925aa1360bc6f00b388?w=1200


저희 시스템을 개괄하면, 1) 로톡 질문이 주어지면 웹 검색을 하기 위한 검색어를 추출합니다. 2) 추출된 검색어로 구글, 네이버에서 검색하고 3) 검색 결과를 질문 원문과의 관련성을 기준으로 다시 한 번 정렬합니다. 4) 검색 결과중 가장 관련성이 높은 문서를 이용해 답변을 생성합니다.

그래서 큰 틀에서 보면 웹 검색을 기반으로 답변을 생성하는 퍼플렉시티 등과 비슷하게 동작합니다.

차이점은 1) 저희는 법률 분야에서 검색어를 더 잘 추출하기 위한 특화된 설계를 가지고 있고 2) 사용자 입력을 미리 알 수 없고 그때그때 실시간으로 답변을 생성해야 되는 그것들과 달리, 로톡에서는 질문이 등록되는 시점과 변호사님이 답변을 작성하는 시점 사이에 시간 간격이 있고 그래서 저희는 로톡에 질문이 등록됐을 때 충분한 시간을 갖고 미리 질문을 분석해 놓습니다. 그래서 예를 들면 웹 검색 결과를 가지고 바로 답변을 생성하는 것이 아니라, 질문과의 관련성을 기준으로 한 번 더 정렬해서 좀 더 관련성이 높은 문서로 답변을 생성합니다.

법률 분야에서 검색어를 더 잘 추출하기 위해 특화된 설계는, 로톡에 올라오는 모든 질문을 커버할 수 있는 상세한 분류체계도(택소노미)를 만들고 이것을 이용해 검색어를 추출하는 것입니다. 제가 이렇게 접근한 이유는 이 문제(일반인의 법률 질의에 답변하는 것)의 본질이 질문의 법적 쟁점을 정확한 법률 개념과 용어로 정리하는 것이라고 판단하였기 때문입니다.

법률 질문의 절대 다수는 이미 이전에도 질문되고 답변된 것들로서 대부분 학계와 실무에서 이미 다 논의되고 정리된 이슈들입니다. 이미 다 정리가 되었기 때문에 그 정리된 자료를 찾을 수만 있으면 되는 것인데 여기서 핵심은 그렇게 정리된 문서들이 법률 개념과 용어들로 작성된 것이어서 높은 품질의 관련 문서를 찾으려면 그 용어들을 이용해 검색해야 된다는 점입니다.

그리고 언어 모델은, 적어도 아직까지는, 법률 질문에서 법적 쟁점을 정확한 법률 용어로 정리하는 작업을 잘 하지 못합니다. 그 작업을 더 정확히, 잘 수행하도록 도와주기 위해서 모든 법 분야의(민법, 형법, 행정법 등) 거의 모든 주제, 쟁점을 망라하는 거대한 분류체계도를 만들고 언어모델로 하여금 그 분류체계도를 참고하여 단계별로(step-by-step) 쟁점을 정리하도록 하면 훨씬 결과가 좋아진다는 것을 확인하였고 그렇게 구현하였습니다.

이렇게 어떤 문제에 특화된 장치를 만들어서 문제에 접근하는 방식에 있어서는 이것이 과연 올바른 방법인가, 여기 또 한 명의 어리석은 자가 the bitter lesson을 잊은 것인가 하는 고민이 많이 되었습니다. 이 이야기는 긴 이야기니 뒤에 따로 하겠습니다.


3. 기술 스택

랭그래프: 질문을 분석하고 웹 검색을 수행하는 전체 워크플로우는 랭그래프 프레임워크를 이용해 구현하였고 랭그래프 플랫폼으로 호스팅하고 있습니다. 랭체인 모듈들을 많이 쓰고 있고 랭스미스도 이용하고 있는데 문서화가 좀 부실한 것만 빼면 꽤 만족스럽습니다.

언어모델: 구글 제미나이 2.5 플래시와 2.0 플래시를 섞어서 사용하고 있습니다. 한국어 능력에 있어서는 OpenAI 모델들보다 훨씬 성능이 좋은 듯합니다.

파이어크롤: 추출된 검색어로 구글 검색을 수행할 때 사용합니다. 검색 결과가 PDF 파일일 때 잘 처리를 못하는 경우가 있는등 약간 아쉬운 점이 있지만 대체로 만족스럽습니다. 네이버 검색은 네이버 검색 API를 이용하고 있습니다.

구글 클라우드 스케쥴러, 클라우드 빌드: 저희는 로톡에 등록되는 질문을 주기적으로 수집해오는데 구글 클라우드 스케쥴러와 클라우드 빌드를 이용하고 있습니다. 클라우드 빌드는 깃헙 액션 등 다른 경쟁 서비스보다 가격이 가장 저렴해서 선택했고 만족스럽게 쓰고 있습니다.

파이어스토어, 파이어베이스 함수: 질문을 분석한 결과는 파이어스토어에 저장하고 파이어베이스 함수를 통해 클라이언트에 데이터를 제공합니다.

프론트엔드: 크롬 익스텐션이 파이어베이스 함수에 데이터를 요청하고 파이어베이스 함수는 파이어스토어에 저장된 질문 분석 결과를 응답으로 보내줍니다.


4. 몇 가지 배운 점들

개인적으로 이번 프로젝트를 통해 가장 크게 배운 점은 텍스트 데이터를 바라보는 새로운 눈을 갖게 된 점입니다.

아래 이미지는 문병로 교수님의 강의(https://youtu.be/zg8PnKVBB-w?si=0-O_GpEHWkfSkVc5&t=386) 중 한 장면인데요, 우리가 이미지에 행렬을 곱해서 이미지를 변형하는 것은 자연스럽게 생각합니다. 우리는 이미지를 늘릴 수도 있고, 추상화시킬 수도 있고, 캐리커쳐화 할 수도 있고, 생각할 수 있는 어떤 변형이든 자유자재로 가할 수 있습니다. 이미지에 적당한 행렬을 곱함으로써요.


e26dde7f6aef7066da8f7671d7aeae4d11fee09bc9d16b783caa5f853ff2c02f?w=1200


그런데 가만히 생각해보면 텍스트 데이터라고 해서 다를 것이 없습니다. 텍스트 데이터도 임베딩 모델을 통해 임베딩 벡터로 변환할 수 있고, 그 임베딩 벡터에 어떤 행렬을 곱해 다른 벡터로 공간 변환할 수 있습니다. 벡터 공간 상의 한 점에서 다른 한 점으로 이동하는 것인데 어떤 행렬을 곱하느냐에 따라 그 변환은 텍스트를 요약하는 작업이 될 수도 있고, 질문에 답변하는 것이 될 수도 있는, 우리가 원하는 어떤 텍스트 처리 작업도 모두 수행할 수 있는 것입니다.

즉, 텍스트를 입력 받아 텍스트를 출력하는 모든 텍스트 처리 작업들이 본질적으로는 이미지에 행렬을 곱해 원하는 형태로 이미지를 변형하는 것과 다르지 않다는 사실을 알게 되었습니다.

물론 아직까지 텍스트를 임베딩 벡터로 변환할 수는 있어도 임베딩 벡터를 다시 텍스트로 변환할 수는 없습니다. 하지만 텍스트를 다루는 것이 본질적으로는 하나의 공간 변환이라고 바라보게 되면서 two-tower 모델을 약간 변형한 mini two-tower 모델을 시도해볼 수 있었습니다.


Extreme classification과 mini two-tower 모델:

Q&A 데이터에서 질문에 가장 관련성이 높은 답변을 찾아내는 작업은 Extreme (multi label) classification 문제로 다루어지기도 합니다. 만약 이 작업을 사람이 수동으로 한다면 어떻게 할 수 있을까요? 아마도 태그를 이용하는 방식이 가장 효과적일 겁니다. 학습 데이터의 여러 특징들을 표현할 수 있는 태그 목록을 만든 다음 학습 데이터의 질문에도 태그를 붙이고 답변에도 태그를 붙입니다. 그다음 학습되지 않은 새로운 질문이 들어오면 그 질문에도 동일한 방식으로 태그를 붙이고나서 답변들 중 같은 태그가 붙은 데이터를 찾는 것입니다.

그런데 이렇게 질문에 태그를 붙이는(즉, 질문을 태그로 변환하는) 것이나 답변에서 태그를 붙이는 것 모두 행렬곱, 즉 공간 변환이기 때문에 우리는 이 작업을 벡터 공간에서 수행할 수도 있고 곱할 행렬 값 역시 딥러닝 학습을 통해 발견해낼 수 있습니다.

Two-tower 모델은 개인화된 추천 영역에서 가장 널리 사용되는 아키텍쳐입니다. Two tower는 user 타워와 candidate 타워를 말하는데 둘 다 딥러닝 방식의 임베딩 모델입니다. user 타워는 유저와 관련된 여러 정보들, 예컨대 프로필 정보, 과거 구매 이력, 시청 이력 등의 정보를 입력으로 받아 그 유저를 나타내는 임베딩 벡터를 출력하는 모델이고, candidate 타워는 그 유저와 매칭하려는 대상(예를 들어 유튜브에서는 비디오 영상이 되고 우버 이츠에서는 음식점이나 메뉴가 되는)의 여러 정보들을 입력으로 받아 마찬가지로 그 대상을 잘 표현하는 임베딩 벡터를 출력하는 모델입니다. 그리고 user 모델의 임베딩 값과 candidate 모델의 임베딩 값이 유사도가 높으면 그 대상을 해당 유저에게 추천해주는 방식입니다.

제가 생각한 mini two-tower 모델은 user 모델과 candidate 모델을 바닥부터 새로 학습시키는게 아니라 OpenAI나 Gemini의 텍스트 임베딩 모델을 이용해 질문과 답변을 임베딩한 다음, 이 값을 다시 공간 변환하는 작은 모델만 새로 학습시키는 방식입니다. 텍스트 임베딩된 벡터는 이미 그 텍스트의 의미와 뉘앙스를 정확히 담고 있을 것이기 때문에 남은 작업은 그 텍스트를 태그로 변환하는 것인데 그 태그 변환에 해당하는 공간 변환을 담당하는 작은 모델을 학습시키는 접근입니다.

a8fb183652c5f58f037e1b73f0585ad39330b30a30e18c7ad34d78e55af971d9?w=1200

그래서 공개된 Q&A 데이터셋을 하나 찾아서 아주 단순한 모델을 학습시켜 보았습니다. Two tower 모델을 Q&A 데이터에 적용할 때는 질문을 user로, 답변을 candidate로 보고 새로운 질문이 들어왔을 때 답변 데이터 중 그 질문과 가장 관련성이 높은 답변을 찾는 문제로 적용합니다. 저의 mini two-tower 모델에서는 질문 타워와 답변 타워 모두 임베딩을 입력으로 받아서 임베딩을 출력하는 모델들로서, 유효한 질-답 쌍(즉, 실제 질문과 답변 쌍)에 대해서는 두 모델의 임베딩 출력값들의 유사도가 높도록 하고, 유효하지 않은 질-답 쌍에(즉, 질문과 엉뚱한 답변 쌍) 대해서는 두 임베딩 출력값의 유사도가 낮도록 학습 목표를 설정하였습니다. 모델의 내부 구조는 아주 단순하게 두 개의 fully-connected layer로 구성하여 학습시켜 보았습니다.

하지만 아쉽게도 학습 결과는 좋지 못했습니다. 5 epoch 정도가 되면 overfitting이 계속 발생해서 모델 구조를 수정해보거나 학습률을 조정해보거나 했는데 결과는 달라지지 않았습니다. 제가 내린 잠정적인 결론은 텍스트 임베딩 모델의 성능이 충분히 좋지 못해서, 그래서 제가 학습시키는 모델들의 입장에서는 입력 데이터들이 충분히 깨끗하지 않아서 잘 학습되지 않는다고 정리했습니다.

노면 유도선과 생각의 길:

스크린샷 2025-07-21 오전 11.50.03.png 출처: https://youtu.be/qJSNxfTLbp8?si=1Bb85aoWAyIJxRIj&t=280

이제는 언어 모델들의 성능이 매우 좋아져서, 특히 작년 말부터 등장한 추론(reasoning) 모델들의 성능은 정말 뛰어나서 웬만한 법률 질문에도 꽤 그럴싸한 답변을 만들어냅니다. 그리고 잘 대답하지 못하는 문제에 대해서도 정말 그 내용을 몰라서 답변을 못한것인지, 아니면 정답의 내용은 알고 있지만 제시된 문제를 풀어나가는 방향을 잘못 선택해서 그런 것인지를 확인해보면 후자인 경우가 대부분입니다. 즉, 최신 언어 모델들은 방대한 사전 학습 과정을 거치면서 웬만한 내용에 대해서는 전부 다 학습을 하였고 그 내용들을 이미 알고는 있습니다. 언어 모델이 약한 부분, 아직까지 잘 하지 못해서 도움이 필요한 부분은 어떤 문제를 풀어갈 때 방대한 탐색 공간 중에서 문제 해결을 위한 올바른 방향을 잘 선택해나가야 하는 부분입니다. 지금 언어 모델이 종종 잘못된 답을 내놓는 것은 너무 많은 그럴 듯한 길 속에서 쉽게 잘못된 길을 택하기 때문입니다.

그런 측면에서 분류체계도를 만들고 언어 모델로 하여금 그것을 참고하여 추론하게 하는 것은 마치 우리가 운전을 할 때 노면 유도선을 따라 길을 헷갈리지 않고 목적지로 갈 수 있듯이, 언어 모델로 하여금 방대한 탐색 공간을 모두 탐색하지 않고 생각의 길이 나아갈 방향을 인도해 주는 것과 같습니다. 언어 모델은 방대한 지식과 함께 논리적인 사고 능력도 갖추고 있기 때문에 생각을 전개해나갈 방향만 잘 잡아준다면 꽤 괜찮은 어시스턴트의 역할을 수행해낼 수 있습니다. 그리고 일반인들의 법률 상담 문제는 정답이 없는 열린 문제가 아닌 닫힌 문제이기 때문에 이런 접근이 유효할 수 있습니다.

The Bitter Lesson에 관하여:

그래서 결국 저는 질문을 태그로 변환하는 작업, 다른 말로 하면 질문에서 법적 쟁점을 추출하여 정확한 법률 용어로 정리하는 작업을 벡터 공간에서 수행하는게 아니라 언어 모델과 분류체계도를 이용해 (토큰 공간에서) 수행하는 것으로 방향을 바꿨습니다.

그런데 이렇게 사람의 지식을 이용해 문제에 접근하는 방식은 단기적으로는 좋은 성과를 낼 수 있어도 장기적으로는 좋은 결과를 만들기 어렵다는 Richard S. Sutton 교수님의 “the bitter lesson”을 접하게 되면서 이 방법에 대해 다시 고민하게 되었습니다(아래 이미지는 OpenAI 정형원 박사님 강의 중 한 슬라이드입니다).


ef1da04821dc4b6d991b429d7f3935102b6838bcf82d70a28f424ec2a9caf51e?w=1200

여러 고민들을 했지만 이 문제에 대한 저의 결론은 1) 연구의 영역과 비즈니스의 영역은 다르다. 2) 인간의 지식을 활용하여 성능을 향상시키는 것과 연산량을 때려 박아 일반적인 방법으로(특정 문제에 특화된 방법이 아닌 방법으로) 문제를 해결하는 것이 반드시 상충되는 것은 아니다 라고 정리하였습니다.

검증 가능한 정답이 있는 수학이나 코딩 같은 분야에서는 test time computation을 늘리는 추론 모델들이 등장하여 모델의 성능을 상당히 향상시켰지만, 그렇지 않은 분야에서는 여전히 늘어난 computation을 어떻게 활용할 수 있을지 아직까지 확실하지 않습니다. 법률 분야는 수학이나 코딩과 달리 검증 가능한 정답이 있는 분야라고 보기 어렵습니다.

혹자는 변호사시험의 객관식 문제들은 정답이 있으니 그 데이터를 이용해 법률 분야에 특화된 추론 모델을 학습시킬 수 있지 않을까 생각할 수 있지만 커스텀한 추론 모델을 학습시키는데 적당한 학습 데이터는 수학 문제나 스도쿠처럼 더 오래 생각하는 것이 문제를 푸는데 실제로 도움이 되는 성질의 문제들이어야 합니다. Noam Brown이 예시로 들었던 것처럼 부탄의 수도가 어디인지 같은 문제에 있어서는 더 오래 생각한다고 해서 문제를 푸는데 별 도움이 되지 않습니다. 제가 생각하기에 변호사시험의 객관식 문제들은 그것과 마찬가지로 10초 안에 정답이 생각나지 않으면 10분을 생각해도 정답을 찾아내기 어려운 문제들입니다.

이건 연구의 영역이긴 합니다만 만약 법률 분야에서 커스텀한 추론 모델을 학습시킨다면 어떤 데이터로 학습시켜볼 수 있을까 고민해보았습니다. 한 가지 아이디어는 법률 서적에서 문장들을 추출해낸 다음 언어 모델에게 추출한 문장과 책의 목차를 주면서 이 문장이 목차 중 어느 섹션에서 가져온 문장인지를 추론하게 하는 방식을 시도해볼 수 있지 않을까 생각합니다. 이렇게 학습 목표를 설정하면 1) 명확한 정답이 있는 문제가 되고 2) 충분히 많은 학습 데이터, 책 한권에서도 수천, 수만개의 학습 데이터를 확보할 수 있습니다. 이렇게 학습된 모델을 모든 법률 관련된 문제를 푸는데 바로 쓸 수는 없겠지만, 어떤 텍스트가 우리 법체계 중 어느 영역에 관련된 텍스트인지를 판단하는 일은 꽤 정확히 해낼 수 있을 것이고, 그 정보는 정확한 답변을 생성하는데 상당히 유용하게 사용될 수 있을 것입니다.


5. 트러블 슈팅

이번 프로젝트를 하면서 겪었던 가장 큰 트러블은 랭체인의 ChatGoogleGenerativeAI 클래스를 이용해 structured output을 생성할 때 자꾸 오류가 발생한 일입니다. 더 구체적으로는 출력 구조에 배열이 포함돼있을 때 문제가 발생했는데 랭체인 깃헙에도 관련된 이슈들이 좀 등록이 돼있었습니다.

디버깅해본 결과 정확한 문제의 원인은 응답 청크를 스트리밍으로 받을 때, 문제가 발생하는 경우들에서는 구글이 마지막에 빈 청크를 한 번 더 보내고 있었고 랭체인 ChatGoogleGenerativeAI 클래스는 그런 경우를 제대로 처리하지 못하게 설계되어 있었습니다. 마지막에 빈 청크를 더 보내는 구글이 잘못한 것인지, 빈 청크가 오면 제대로 처리를 못하는 랭체인의 오류인지 까지는 확인하지 못했습니다.

저는 랭체인 BaseChatModel을 상속하는 커스텀한 ChatModel 클래스를 만들어서 마지막에 빈 청크가 오는 경우에도 잘 처리되도록 구현하여 문제를 해결하였습니다.


6. 크게 도움된 자료들

이번 프로젝트를 진행하면서 개인적으로는 머신 러닝을 많이 공부했는데 저에게 특히 도움이 많이 되었던 자료들을 몇 가지 공유합니다.

서울대 데이터 사이언스 대학원 이준석 교수님 유튜브 채널(https://www.youtube.com/@LeeJoonseok): 실제 수업에서 사용하신 강의 자료와 실제 강의를 유튜브로 공유해주시는데, L1, L2 정규화 같은 머신 러닝의 기초적인 내용부터 트랜스포머의 구조에 대한 상세한 설명까지 여러 영상에서 큰 도움을 받았습니다.

Algorithmic Simplicity(https://www.youtube.com/@algorithmicsimplicity): 특히 CNN에 대해 다룬 영상(https://www.youtube.com/watch?v=8iIdWHjleIs)이 크게 도움됐는데 CNN이 왜 그렇게 잘 동작했는지, 나아가 트랜스포머가 등장하기 전에는 왜 사람들이 RNN으로 텍스트 데이터를 처리하려고 했는지 이해할 수 있게 되었고 덕분에 트랜스포머에 대해서도 더 깊이 있게 이해할 수 있게 되었습니다.

Welch Labs(https://www.youtube.com/@WelchLabsVideo): 이 채널에도 재밌고 유익한 영상들이 많은데 개인적으로는 Mechanistic Interpretability를 다룬 영상에서(https://www.youtube.com/watch?v=UGO_Ehywuxc) 특히 많이 배웠습니다.

keyword
작가의 이전글저랑 같이 온라인 피트니스 사업해보실 분?