산문봇 AI 티 방어 코드를 단일 모듈로 통합한 새벽
목차
새벽 내내 손댄 건 새로운 기능이 아니었다. 이미 동작하는 코드들 사이에 쌓인 기술 부채를 하나씩 걷어내는 시간이었다. 가장 오래 걸린 건 humanizer 인라인 코드 정리였고, 그다음엔 SEO 파이프라인 쿼터 확장, 포트폴리오 카드 재편까지 다녔다. 단 하나의 주제로 묶기엔 좀 산만하게 보일 수 있는데, 뒤에서 얘기하겠지만 결국 같은 방향의 작업들이었다.
복붙이 쌓아올린 빚, 7개 산문봇 humanizer 일원화
문제가 시작된 건 산문봇을 처음 만들던 시점으로 거슬러올라간다. 다이어트 정보를 자동으로 써주는 봇, 장례 관련 정보를 써주는 봇, 보험 비교 글을 써주는 봇... 이런 봇들이 늘어날수록 공통된 문제가 있었다. LLM이 생성한 텍스트를 그냥 내보내면 안 된다. 표현이 너무 반듯하고, 특정 패턴이 반복되고, 한국어 문맥에서 어색한 AI 냄새가 났다. 그래서 각 봇에 후처리 로직을 추가하기 시작했다.
처음엔 단순했다. "또한", "마지막으로", "이를 통해" 같은 AI가 즐겨 쓰는 단어를 자연스러운 표현으로 바꾸거나, 문단 첫 단어가 반복되지 않도록 살짝 비틀어주는 정도였다. 이 로직이 처음 diet/generate.py에 들어갔고, 다음 봇을 만들 때 그 파일을 열어서 해당 블록을 복붙했다. 빠르게 결과물이 필요한 시기엔 그게 맞는 선택이었다.
6개월이 지났다. 봇은 7개가 됐고, humanizer 로직은 각자 다른 버전이 되어 있었다. insurance 봇에서 새로운 표현 패턴을 발견해 규칙을 하나 추가하면 pet 봇에는 그 규칙이 없다. funeral 봇에서 버그를 고치면 lotto 봇엔 아직 그 버그가 살아있다. 7개 파일이 각자의 역사를 가지고 조금씩 달라져 있었다. 동기화하는 사람이 나뿐이니 빠뜨리는 게 생길 수밖에 없었고, 실제로 몇 번 그런 일이 있었다.
이번에 정리한 구조는 이랬다. 공통 humanizer 모듈 하나를 기준으로 잡고, 각 봇의 generate.py는 그걸 import해서 결과물을 통과시키기만 한다. 인라인에 있던 처리 블록은 전부 제거했다.
# 전환 전 - 각 generate.py에 30줄 이상이 인라인으로
output = text
output = re.sub(r'\b또한\b', '그리고', output)
output = re.sub(r'\b마지막으로\b', '끝으로', output)
output = re.sub(r'^(먼저|우선)\s', '일단 ', output, flags=re.MULTILINE)
# ... 이하 반복
# 전환 후
from common.humanizer import humanize
output = humanize(text)
기계적인 작업이었다. 파일 7개를 하나씩 열고, 인라인 블록을 들어내고, import 추가하고, 출력 결과를 비교했다. 결과물이 전환 전과 동일하게 나오는지 확인하는 과정이 사실 제일 신경 쓰였다. 규칙을 제대로 공통 모듈에 다 옮겼는지, 빠진 게 있는지.
| 봇 이름 | 파일 | 상태 |
|---|---|---|
| diet | diet/generate.py | 전환 완료 |
| funeral | funeral/generate.py | 전환 완료 |
| insurance | insurance/generate.py | 전환 완료 |
| lotto | lotto/generate.py | 전환 완료 |
| opsvoro | opsvoro/generate.py | 전환 완료 |
| pet | pet/generate.py | 전환 완료 |
| blog-writer | blog/scripts/write_post.py | 전환 완료 |
blog-writer는 처음 배치에서 빠졌다. 나머지 6개 봇이 */generate.py 패턴으로 모여있는데 blog-writer는 blog/scripts/write_post.py 경로라 첫 grep에서 걸리지 않았다. 6개 처리하고 커밋 올린 뒤 확인차 전체 repo를 다시 훑다가 발견했다. 그래서 커밋이 두 개로 나뉘었다. 처음에 경로 패턴을 조금 더 넓게 검색했으면 한 번에 끝낼 수 있었을 텐데, 지나고 나면 항상 당연한 얘기다.
작업의 마무리는 CLAUDE.md 수정이었다. "전 산문봇은 humanizer import를 사용할 것, 인라인 humanizer 복붙 금지"를 문서에 명시했다. 코드를 고쳤다고 끝이 아니다. 새 봇을 만들 때 옛날 관성대로 인라인으로 넣는 실수를 막으려면 가이드라인이 필요하고, AI 어시스턴트가 새 봇 코드를 짜줄 때도 이 규칙이 컨텍스트로 주입되도록 CLAUDE.md에 박아두는 게 제일 확실하다.
idx_ping 라운드로빈, publish 쿼터를 하루 570건으로
humanizer 정리가 마무리되고 나서 SEO 파이프라인 쪽을 손봤다. idx_ping은 새로 생성된 URL을 외부 색인 API에 밀어넣는 자동화 스크립트다. 콘텐츠가 발행되면 검색엔진이 알아서 크롤링하기를 기다리는 것보다 직접 신호를 보내주는 편이 색인 속도가 훨씬 빠르다. 자동 생성 파이프라인이 하루에 수백 건씩 콘텐츠를 뱉는 구조에서 이건 선택이 아니라 필수 레이어다.
문제는 단일 프로젝트 계정으로 운영할 때 일별 publish 쿼터가 190건으로 막혀있다는 거였다. 파이프라인이 하루에 생성하는 URL이 그 이상이면 나머지는 대기열에 쌓이거나 그냥 누락된다. 색인 지연이 생기면 새 콘텐츠가 검색 결과에 뜨는 시점이 밀리고, 트래픽이 늦게 들어오기 시작한다. 콘텐츠 생성 속도는 늘었는데 색인 처리량이 그걸 못 따라가는 상황이었다.
해결책은 멀티프로젝트 라운드로빈이었다. 여러 프로젝트를 등록해두고 ping 요청을 순차적으로 분산하면, 각 프로젝트가 개별 쿼터를 가지기 때문에 실질적인 처리량이 선형으로 늘어난다. 3개 프로젝트로 분산하면 190 x 3 = 570건/day. 기존의 정확히 3배다.
이번 작업에서 코드 변경보다 더 신경 쓴 건 문서화였다. hedvion-CLAUDE.md에 라운드로빈 방식, 프로젝트 역할, 쿼터 수치를 명시했다. 이런 설정이 코드에만 묻혀있으면 나중에 "왜 이렇게 되어있지?"라고 의아해하다가 건드리면 안 되는 걸 건드리게 된다. 숫자 하나를 바꾸더라도 왜 그 숫자인지 남겨두는 게 인프라 문서의 역할이다.
라운드로빈 전환 이후 실제 쿼터 소진 패턴은 며칠 데이터를 더 봐야 알 수 있다. 이론적으론 3배지만, 실제 URL 발행 속도와 쿼터 소진 주기가 맞는지 수치로 확인해야 진짜 효과를 측정할 수 있다.
포트폴리오 카드 재편과 자동 생성 파이프라인
이 새벽에 병행한 작업이 둘 더 있었다. 포트폴리오 사이트에서 ExcelStocks 프로젝트를 단일 카드에서 5개 카드로 분리했다. 기존에 하나의 카드에 묶여있던 프로젝트가 실제로는 다양한 인터페이스 형태를 지원한다. Excel 스타일 외에 Word, Slack, VSCode, Terminal 형태로도 동작하는데, 이걸 하나의 카드로 표현하면 각 형태의 존재 자체를 방문자가 알기 어렵다.
그래서 각 disguise 형태별로 카드를 분리하고, 썸네일도 형태에 맞게 각각 제작해서 넣었다.
- excelstocks-excel (기존)
- excelstocks-word (신규)
- excelstocks-slack (신규)
- excelstocks-vscode (신규)
- excelstocks-terminal (신규)
방문자 입장에서 스크롤하다 보면 "아, 이 툴이 슬랙 UI로도 되는구나, VSCode 레이아웃도 있구나"를 눈으로 바로 확인할 수 있게 됐다. 카드 수가 늘어나 페이지가 다소 길어지는 건 감수할 만한 트레이드오프라고 봤다.
심리 테스트 콘텐츠 자동 생성은 수동 작업이 아니었다. psy 프로젝트에 설정해둔 daily 스케줄러가 새벽에 돌면서 elevator-type, love-movie-genre, moving-day-type 세 가지 테스트를 자동으로 만들고 커밋까지 올렸다. 커버 이미지(webp)와 테스트 정의 JSON이 세트로 생성됐다. 이쪽은 내가 손댄 게 없다. 파이프라인이 스스로 돌아간 결과가 git log에 남은 것이고, 그게 정상이다.
새벽 작업의 공통 맥락
세 덩어리 작업을 놓고 보면 공통된 방향이 보인다.
humanizer SSOT 전환은 여기저기 흩어진 중복 코드를 하나로 모은 것이다. idx_ping 라운드로빈은 단일 계정에 집중된 병목을 여러 프로젝트로 분산해 처리량을 늘린 것이다. ExcelStocks 카드 분리는 반대로 하나에 묶여있던 정보를 여러 카드로 펼쳐 가시성을 높인 것이다. 방향이 달라 보여도 "뭉칠 건 뭉치고, 펼칠 건 펼친다"는 축으로 읽으면 하나의 흐름이다.
자동화 인프라가 어느 정도 궤도에 오르면 이런 구조 정리 작업이 반복적으로 찾아온다. 처음엔 빠르게 만들어야 하니까 인라인으로, 단일 계정으로, 단일 카드로 치고 나가는 게 맞다. 나중에 규모가 커지면 그 결정들이 하나씩 부채가 되어 돌아온다. 이번 새벽에 갚은 부채가 몇 개 있었고, 다음엔 그만큼 덜 막힐 것이다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.