영어 논문을 읽는 밤이면 저는 늘 산만해졌습니다. PDF 뷰어에서 한 문단을 읽다 막히면, 번역기 탭으로 넘어가 문장을 붙여 넣고, 결과를 확인한 뒤 다시 뷰어로 돌아옵니다. 그 몇 초 사이에 따라가던 흐름이 끊기고, 어디까지 이해했는지를 다시 더듬어야 했죠. 창을 오가는 시간보다 끊긴 맥락을 되짚는 시간이 더 아까웠습니다.
손에 든 자료가 아직 세상에 안 나온 연구면 더 조심스러웠죠. 초안이나 미발표 데이터를 번역기나 챗봇 창에 붙여 넣는 게 괜히 불안했습니다. 그 글이 어디에 저장되고 무엇에 쓰일지 알 수 없으니까요. 그래서 마음먹었습니다. 논문 읽는 데 필요한 걸 한 화면에 모으고, 그 데이터가 노트북 밖으로 나가지 않는 나만의 도구를 만들자고요.
바이브 코딩으로 시작은 쉬웠습니다. AI에게 시키니 하루 만에 그럴듯한 프로토타입이 만들어졌죠. 하지만 진짜 일은 그다음이었습니다. 데모에서 한 번 돌아가는 코드와 날마다 곁에 두고 쓰는 도구 사이의 간극은 생각보다 컸습니다. 그 간극을 좁히는 동안 AI의 제안과 AI가 만든 코드는 문제가 발생해, 의도했던 사용 시나리오가 안정적으로 유지되지 못했습니다.

실제로 문제가 발생할 때는 AI가 비슷한 답변을 줬습니다. 다단 구조를 인식하기 위해 화면을 반으로 자르라거나, 리소스가 부족하니 GPU를 늘리라고 하거나, 세션 대기 시간을 늘리라는 등 하나같이 문제의 원인을 찾기보단, 문제를 돌아가라는 답변이었죠. 이 글은 프로덕트 개발 과정에서 맞닥뜨린 네 번의 갈림길에서 AI의 답을 되돌려보내고 다른 길을 찾았던 기록입니다. 그 판단들이 쌓여, 지금도 매일 켜는 도구가 된 과정에 대해 살펴보겠습니다.
처음 도구가 무너진 건 2단의 다단 구조로 작성된 논문에서였습니다. 대부분의 학술 논문은 한 페이지를 좌우 두 단으로 나눠 쓰는데, 여기서 텍스트를 뽑으면 좌우 문장이 한 줄씩 번갈아 섞여 나왔습니다. 멀쩡한 문장이 중간에서 엉뚱한 문장으로 이어지니, 번역기에 넣으면 뜻 모를 말이 됐고 검색도 될 리 없었죠.

AI에게 물었더니 페이지의 가로 방향으로 정확히 반으로 나누라고 했습니다. 가운데를 기준으로 왼쪽 글자는 왼쪽 단, 오른쪽 글자는 오른쪽 단으로 묶으면 된다는 거였죠. 그날 열어둔 논문 한 편에서는 잘 돌아갔습니다. 문제는 다음 논문을 열자마자 다시 드러났습니다. 논문마다 좌우 여백도, 두 단 사이 간격도 제각각이었거든요. 어떤 논문은 가운데가 왼쪽으로 쏠려 있었고, 어떤 논문은 페이지를 가로지르는 제목을 달고 있었습니다. 정확히 절반으로 섹션을 나누는 것은 딱 그 한 편의 논문에만 맞는 방식이었죠.
그래서 반으로 자르는 대신, 페이지가 스스로 경계를 인식하게 해보자고 생각했습니다. 논문을 보다 보니, 다단 구조의 특징이 있었습니다. 두 단 사이에는 글자가 없는 빈 세로띠가 있었죠. 그 빈 곳만 찾으면 되는 문제였습니다. 페이지 가로를 200개의 얇은 칸으로 나누고, 각 칸에 글자가 몇 개가 있는 세어봤습니다. 글자가 빽빽한 칸은 단의 본문이고, 텅 빈 칸이 이어지는 구간이 두 단을 가르는 자리입니다. 페이지 가운데 언저리에서 가장 넓게 빈 구간을 찾아, 그 한복판을 경계선으로 삼았습니다.

이렇게 하니 논문마다 다른 경계를 그때그때 잡아냈죠. AI가 준 ‘정확히 절반’은 지우지 않았습니다. 페이지가 너무 단순해 빈 구간을 못 찾을 때만 꺼내 쓰는 마지막 기본값으로 활용했죠. 일부 논문에서는 그게 맞는 방법이었으니까요. 여기서 배운 건, AI의 답이 눈앞의 예시 하나에는 맞아떨어져도 그게 규칙까지 되진 못한다는 점이었습니다. 논문마다 달라지는 걸 프로그램이 스스로 감당하게 만드는 일, 그게 프로토타입과 실제로 동작하는 프로덕트를 가르는 첫 갈림길이었습니다.
논문을 프로덕트에 처음 넣던 날, 백엔드가 아무 말 없이 멈췄습니다. 논문 전체를 검색하려면 글을 잘게 쪼갠 뒤 각 조각을 숫자 뭉치로 바꿔 저장해둬야 합니다(벡터화). 이 변환을 로컬에 올린 작은 모델에게 맡겼는데, 조각들을 한꺼번에 넘기자 화면이 그대로 멈췄습니다. 심지어 발생한 에러에는 로그도 없이 그냥 멈춰 있었죠. 죽었는지 동작하는 중인지조차 알 수 없는 상태가 된 겁니다.
AI와 대화를 하며, 한참을 들여다본 끝에 원인이 좁혀졌습니다. 조각을 여러 개 묶어 보내면, 이 모델을 돌리는 엔진 안에서 조용히 멈춰버리는 버그가 있었던 거죠(사용 엔진: nomic-embed-text). 한 번에 많이 보낼수록 멈추기 때문에 조각을 넣는 방식 자체를 바꿔야 했습니다.
AI에게 설명하니 GPU를 늘리거나, 클라우드 임베딩으로 갈아타라고 습니다. 틀린 처방은 아니었지만, 이 프로젝트는 돈 안 드는 개인 도구로 만들자는 게 출발점이었습니다. 그래서 하드웨어를 더 사거나 클라우드에 요금을 내는 순간, 애초에 이걸 왜 만드는지가 흐려졌습니다.
그래서 반대로 갔습니다. 한 번에 여러 조각을 보내던 걸 한 조각씩만 보내도록 바꿨죠. 묶음을 없애니 버그가 걸리던 상황 자체가 사라졌고, 백엔드는 멈추지 않고 끝까지 제 일을 했습니다. 대신 하나씩 처리하니 느려졌죠. 그래서 사용자가 그 시간을 답답하게 느끼지 않도록, 조각 하나를 처리할 때마다 남은 시간을 계산해 익스텐션으로 실시간으로 보냈습니다. ‘34개 중 12개 처리, 약 18초 남음’ 같은 문구가 진행 막대와 함께 뜨니, 굳은 화면을 바라보던 때와는 체감이 전혀 달랐습니다. 성능을 양보하고 안정성을 챙긴 자리에 진행 막대 하나만으로 사용 경험을 개선한 겁니다.

Ollama라는 도구를 깔면 명령어 한 줄로 작은 임베딩 모델과 소형 언어 모델을 노트북에 내려 받고 로컬 방식으로 활용할 수 있어요. 2B 수준의 경량 모델은 고사양 그래픽카드가 없어도 돌아가고, 호출할 때마다 요금이 붙지 않으니 프롬프트를 수백 번 바꿔가며 실험해도 부담이 없습니다. 로컬 모델로 뼈대를 잡고 무거운 작업만 온라인 모델에 맡기면, 성능과 비용을 함께 챙길 수 있어요.

앞서 말한 대로, 논문 한 편을 통째로 요약하는 무거운 작업은 성능이 더 나은 온라인 무료 모델에 맡겼습니다. 그런데 이 긴 요약만 유독 늘 같은 자리에서 죽었죠. 화면에 남는 건 524 에러로, 짧은 질문은 멀쩡한데 출력이 길어지는 요청만 골라서 끊겼습니다.
524는 내 서버가 보낸 신호가 아니었습니다. 온라인 모델을 서비스하는 OpenRouter 쪽에서 온 거였어요. OpenRouter의 주소는 Cloudflare라는 서비스가 뒤에 있는데, 이 서비스는 뒤쪽 서버가 120초 동안 아무 응답도 안 내놓으면 죽은 걸로 보고 연결을 끊어버린다는 것을 알게 되었습니다. 로컬 모델을 쓸 땐 이런 일이 없었습니다. 노트북 안에서 바로 대화하니 중간에 낀 게 없었거든요. 무거운 요약을 바깥으로 내보내면서부터 이 문제가 생긴 겁니다.
AI에게 물었더니 세션 대기 시간을 늘리는 클라이언트 타임아웃 자체를 늘리라고 했습니다. 그런데 ‘그렇게 진행해’를 입력하다 손이 멈췄습니다. 연결을 끊는 쪽은 내 클라이언트가 아닌데, 내가 기다리는 시간을 늘린다고 OpenRouter 앞의 Cloudflare가 524 오류를 내놓지 않을 이유가 없었거든요. 그래도 혹시나 해서 넣어봤고, 결과는 똑같았습니다. 여전히 120초에서 524였죠.
관문이 연결을 끊는 기준은 따로 있었습니다. 뒤쪽 서버가 120초 동안 아무 데이터도 안 보내면 죽었다고 판단합니다. 그러니 클라이언트에서 아무리 오래 기다려도 소용이 없었죠. 뒤집어 생각하니 방법이 보였습니다. 답변 일부라도 시간 내에 도착하면, 그 연결이 살아있는 것으로 본다는 것을 알게 된 거죠.

그래서 응답을 다 만든 뒤 한 번에 돌려주던 방식을 버렸습니다. 모델이 글자를 만들어내는 족족 밖으로 흘려보내는 스트리밍 방식으로 바꿨죠. 목적은 하나였습니다. 관문에 이 연결이 아직 살아있다는 신호를 끊기지 않게 보내는 것. 겉보기엔 이 함수도 예전처럼 완성된 답 하나를 돌려줍니다. 달라진 건 그 답을 만드는 동안 연결이 끊기지 않는다는 점이죠. 몇 분씩 걸리는 긴 요약도 524 없이 끝까지 도착했습니다.
이 일로 한 가지를 배웠는데요. AI의 답을 ‘어디에서 의심해야 하는가’입니다. AI가 말한 방법도 나름 맞는 말이었지만, 문제를 클라이언트 쪽에서만 바라봤습니다. 연결이 정확히 어디서 끊기는지를 내가 알고 있어야, 그 답이 엉뚱한 곳을 겨누고 있다는 걸 알 수 있었습니다.
같은 클라우드 모델에서 또 다른 지연도 만났어요. OpenRouter 무료 모델의 추론(reasoning) 모드를 켜니 답이 오기까지 30초를 넘겼습니다. 유료 모델로 바꾸면 간단했지만 사전에 계획한 ‘비용 제로’ 원칙에 어긋났죠. 그래서 추론 기능을 끄고, 대신 프롬프트마다 정답 예시를 한두 개 넣어줬어요(Few-Shot 프롬프팅). 모델이 스스로 오래 고민하게 두는 대신, 따라 할 예시를 제공하는 것입니다.
이 프로덕트의 핵심은 노트북LM처럼 논문 내용을 사전에 학습하고, 학습한 데이터를 기반으로 답변을 하는 것이었죠. 사용자의 질문과 관련된 내용을 논문 안에서 찾아내야, AI가 그 근거를 읽고 제대로 답하거든요. 단어가 똑같지 않아도 의미가 가까운 정보를 찾아내는 게 중요했습니다. 이런 검색을 흔히 검색증강생성, RAG라고 부릅니다.
AI에게 물으니 전용 벡터 데이터베이스를 깔라고 했습니다. 의미를 숫자로 바꾼 데이터를 전문적으로 저장하고 검색해주는 별도 프로그램이죠(예: PostgreSQL, Chroma 등). 검색량이 많은 대형 서비스라면 당연한 선택입니다. 그런데 제가 다루는 건 1주간 논문 몇 편이 전부였고, 이 정도 데이터를 위해 무거운 데이터베이스를 노트북에 늘 띄워 관리하는 건 배보다 배꼽이 큰 일이었습니다.

필요한 계산을 뜯어보니 사실 단순했습니다. 질문을 숫자로 바꾸고, 저장해둔 논문 조각들의 숫자와 방향이 얼마나 비슷한지를 재서 가까운 순으로 뽑으면 되는 일이었죠. 그래서 데이터베이스 대신 처리 구조를 직접 짰습니다. 파이썬의 수치 계산 라이브러리로 이 비교를 옮기니, 150줄 남짓한 코드로 엔진이 완성됐습니다. 따로 설치할 것도, 늘 켜둬야 할 프로그램도 없었죠. 규모에 맞는 최소한의 도구를 고른 셈입니다.
이렇게 네 번의 갈림길을 지나고 나서야, 데모가 아니라 실제로 손에 잡히는 도구의 모습이 갖춰졌습니다. 완성된 도구가 어떻게 움직이는지 몇 가지만 보여드릴게요.
논문을 읽으면서 생각한 연구 아이디어, 의견을 노트에 붙여 넣으면, 7초 만에 논문 기반의 평가가 만들어집니다. 처음엔 점수 매기기, 상세 평가, 한 줄 요약을 차례대로 불렀더니 15초가 걸렸습니다. 그런데 순서가 꼭 필요한 건 첫 단계뿐이었죠. 점수가 정해지면 상세 평가와 한 줄 요약은 서로 볼 일이 없어 동시에 돌려도 됐거든요. 둘을 나란히 실행하니 15초가 7초로 줄었습니다.

어려운 단어를 저장해도 읽던 흐름이 끊기지 않습니다. 모르는 단어를 저장하면, 기존에는 AI가 문맥을 분석하는 동안 화면이 몇 초씩 멈췄습니다. 읽다 말고 사용자를 기다리게 했죠. 그래서 저장을 누른 즉시 ‘분석 중’ 카드를 먼저 띄우고, 무거운 분석은 뒤에서 따로 돌리도록 만들었습니다. 사용자는 단어만 등록하고 곧장 읽던 곳으로 돌아갈 수 있고, 분석이 끝나면 카드가 조용히 채워집니다.

마지막은 모델을 고르는 방식입니다. 무거운 온라인 작업에 쓰는 OpenRouter 무료 모델은 종류가 자주 바뀝니다. 어제 되던 게 오늘 사라지기도 하고, 더 나은 게 새로 열리기도 하죠. 그래서 모델 이름을 코드에 박아두는 대신, 지금 무료로 쓸 수 있는 모델이 뭔지를 OpenRouter에 직접 물어보게 했습니다. 돌아온 목록에서 이름 끝에 ‘free’가 붙은 것만 골라내면, 그게 곧 지금 공짜로 쓸 수 있는 모델입니다. 이 목록을 익스텐션에 띄워두니, 쓰던 모델이 막혀도 다른 무료 모델로 바로 갈아탈 수 있었죠.

크롬 익스텐션은 코드를 고칠 때마다 관리 페이지에서 새로고침을 눌러야 반영됩니다. 수십 번 반복하면 그 자체가 일이죠. 백엔드는 로컬 서버를 자동 재시작 모드로 띄워 저장 즉시 반영되게 하고, 익스텐션 새로고침도 한 번에 처리하도록 묶어두면, 고치고 확인하는 사이클이 몇 초로 줄어듭니다.
네 번의 갈림길을 지나온 이 논문 학습 도구로는, 지금도 논문 다섯 편을 인덱싱하고 채점한 노트와 저장한 단어를 쌓아가며 사용하고 있습니다. 노트북 수준에서도 리소스 자원을 거의 먹지 않고, 작성 중인 논문이 노트북 밖으로 나가지도 않죠. 처음 목표했던 ‘매일 쓰는 프라이빗 도구’에 만들어진 셈입니다.
돌아보면 네 번의 갈림길에서 바이브 코더로서 제가 한 일은 매번 같았습니다. AI가 제시한 가장 편한 답이 제가 개발하고자 하는 방향성과 맞는지 검토하는 것이죠. 화면을 반으로 자르라는 답도, GPU를 늘리라는 답도, 타임아웃을 늘리라는 답도, 데이터베이스를 깔라는 답도, 그 자리에서는 다 말이 됐습니다. 하지만 매일 쓸 도구에는 맞지 않았죠.
AI는 앞으로 점점 더 그럴듯한 답을 내놓고, 알아서 프로덕트를 뚝딱 만들 겁니다. 그래서 그 답이 원래 기획 방향성과 맞는지를 판단하는 몫은 우리에게 남을 겁니다. 바이브 코딩이 무너지는 자리는 거의 정해져 있었습니다. 방향키를 잡는 우리가 AI의 답을 그대로 받을지, 아님 되돌려보낼지 정하는 바로 그 순간이죠. 바이브 코딩으로 끝까지 만든다는 건 그 순간에 방황하지 않고, 초기 방향성을 유지하는 것입니다. 실력의 문제라기보다는 판단의 문제가 되지 않을까요?
ⓒ요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.