devlog.md

[블로그, 인스타 업로드_자동화] 바이브코딩 개발일지 - 로컬 LLM 파서 버그 추적기_06

한/영 경제뉴스 블로그 자동화 프로젝트를 이어서 작업했다. 오늘은 새 기능을 추가하기보다, 실물 검증을 반복하면서 로컬 LLM이 만들어내는 출력 포맷 버그를 끝까지 추적해 고치는 데 하루를 다 썼다.

커밋부터: 유실 위험 정리

HANDOFF.md를 읽고 git status부터 확인했더니 커밋이 딱 1건뿐이었다. 인스타 배포 스크립트, ECOS 수집기, 중복 방지 로직, 테마 XML까지 대부분이 untracked 상태였다. 먼저 .gitignore로 비밀값 파일(.env, token.json 등)이 제대로 빠지는지 재확인하고, launchd/ 폴더에 등록된 plist 2개 중 1개(dailypipeline)가 리포에는 없다는 것도 발견해서 ~/Library/LaunchAgents에서 복사해 채웠다. files.zip은 이미 커밋한 테마 XML과 내용이 같은 걸 diff로 확인하고 커밋에서 뺐다. 이렇게 정리한 뒤 첫 커밋을 만들었다.

회귀 테스트로 안전망 깔기

이 프로젝트는 로컬 소형 모델(exaone3.5)이 출력 포맷을 자주 어겨서, 오프라인 회귀 테스트(test_pipeline.py)를 항상 먼저 돌리고 시작한다. 65건 전부 통과를 확인하고 나서야 실제 수정에 들어갔다.

애드센스 대비 META 규칙 조이기

검증 스크립트(tools/verify_prompts.py)의 투자조언 탐지 정규식이 "투자 판단을 위한 직접적 조언 없이…" 같은 면책 문구를 오탐하는 걸 먼저 고쳤다. 매치 주변 앞뒤 60자에 부정어가 있으면 넘어가도록 했다. 그리고 한글 시황 글의 META에 "투자와 경제 동향 이해에 필요한 통찰력을 얻으세요" 같은 경계선 문구가 계속 나오는 문제를 사용자에게 물어봤더니, 프롬프트를 더 조이는 쪽으로 결정이 났다. 05_generate_content.py의 4개 프롬프트 변형 전부에 "투자/invest/trading 단어 자체 금지", "독자를 부르는 2인칭 권유형 금지" 규칙을 넣었다.

실물 검증이 드러낸 <p> 중첩 버그 두 개

규칙을 넣고 실물로 돌렸더니 <p> 중첩 버그가 다시 나왔다. 이전 세션에 이미 두 경로를 고쳤는데 또 발생한 거라 코드를 다시 읽었다. 원인은 _unwrap_bad_paragraph()가 블록의 "첫" <p>만 검사한다는 점이었다. 문단이 여러 개인 블록에서 뒤쪽 문단이 리스트나 소제목을 감싸면, 첫 번째 닫는 태그가 먼저 나오니 정상으로 오판했다. _split_leading_paragraph()로 첫 문단을 떼어내고 나머지를 재귀 처리하도록 고쳤다. 한 번 더 돌렸더니 네 번째 경로가 나왔다 — 여는 <p>가 줄 끝에 혼자 있고 빈 줄로 문단이 갈리면, 닫는 태그 없는 덩어리가 뒤 블록을 통째로 삼켰다. 닫는 태그가 없으면 무조건 겉 <p>를 벗기도록 바꿨다. 실패한 실물 HTML을 logs/verify_failures/에 저장하도록 검증 스크립트를 고쳐둔 덕분에 원인을 바로 찾을 수 있었다.

도입 문단이 사라진 진짜 이유

인수인계 문서에는 "영문 개념글에서 도입 문단이 가끔 빠진다"고만 적혀 있었는데, 실제로 덤프를 열어보니 다른 문제였다. 모델이 TITLE/META 헤더 줄을 통째로 빼먹으면 "첫 줄 = 제목" 폴백이 도입 문단 전체(181자)를 제목으로 먹어버렸다. 제목이 문단이 되고 본문은 소제목부터 시작하니 도입 문단이 없어 보인 것뿐이었다. _looks_like_title()을 추가해서 제목답지 않은 첫 줄(100자 초과 또는 여러 문장)은 본문에 남기고 첫 소제목으로 제목을 만들도록 바꿨다. 프롬프트에도 "답변은 반드시 TITLE: 줄로 시작하라"를 명시했다.

형태만 베끼라고 했더니 오히려 나빠진 프롬프트

META 예시 문장을 "형태만 베끼고 주제는 바꿔라"고 지시했더니 통과율이 4/8에서 2/8로 오히려 떨어졌다. 모델이 복사를 아예 멈추고 원래 쓰던 "Explore…" 식 문체로 돌아간 거였다. 그래서 예시를 파이썬에서 실제 지표명으로 채운 슬롯 템플릿으로 바꿨다. 한글 조사(이/가) 자동 선택 함수도 추가했다. 이 방식으로 최종 검증에서는 META 규칙 위반이 0/8이 됐다. 다만 슬롯을 그대로 베낀 사례가 8건 중 4건 나와서, 특히 매일 나가는 영문 시황 META가 실행마다 완전히 동일했던 건 다음 세션에서 템플릿에 실제 수치를 넣는 식으로 더 손봐야 한다.

Trouble Shooting

증상원인해결 방법
<p>가 <ul>/<h2>를 감싸는 중첩이 수정 후에도 재발_unwrap_bad_paragraph()가 블록의 첫 <p>만 검사해 뒤쪽 문단의 잘못된 감싸기를 못 잡음_split_leading_paragraph() 추가로 첫 문단을 떼어내고 나머지를 재귀 처리
같은 세션에서 또 다른 형태의 <p> 중첩 발생여는 <p>가 줄 끝에 혼자 있고 빈 줄로 블록이 갈리면 닫는 태그 없는 덩어리가 뒤 블록을 통째로 삼킴닫는 </p>가 없으면 무조건 겉 태그를 벗기도록 규칙 변경
영문 개념글에서 도입 문단이 사라진 것처럼 보임실제로는 모델이 TITLE/META 헤더를 빼먹어 "첫 줄=제목" 폴백이 도입 문단을 제목으로 삼킴_looks_like_title()로 제목답지 않은 첫 줄은 본문에 남기고 첫 소제목으로 제목 생성
META 프롬프트 수정 후 통과율이 오히려 하락(4/8→2/8)"형태만 베끼고 주제는 바꿔라"는 추상적 지시라 모델이 복사를 멈추고 기본 문체로 회귀실제 데이터로 채운 슬롯 템플릿 예시로 교체

느낀 점 / 다음에 할 일

오늘 확실히 배운 건, 이 프로젝트의 파서 버그가 전부 "첫 요소만 검사한다"는 같은 종류의 가정에서 나왔다는 점이다. 다음에 비슷한 버그를 만나면 그 지점부터 의심하면 될 것 같다. 또 하나는 로컬 소형 모델에게 추상적인 지시("형태만 베껴라")는 오히려 역효과를 낸다는 것 — 구체적인 예시를 파이썬으로 채워서 주는 쪽이 훨씬 안정적이었다. 다음 세션에서는 중단됐던 마지막 검증 결과(8건 중 7건 통과, 도입 문단 1건 실패는 재확인 필요)를 마저 점검하고, 매일 나가는 시황 글의 META가 매번 똑같이 나오는 문제를 실제 수치를 끼워 넣는 방식으로 해결할 계획이다.

댓글