소셜 봇 AI 슬롭을 걷어내고 팩트가드를 붙인 하루
목차
아침 일찍 소셜 자동 포스팅 봇 로그를 열어보니 생성된 카피가 영 마음에 안 들었다. 내용이 틀린 건 아닌데, 뭔가 광고 문자 같은 느낌이 났다. "놓치지 마세요!", "한정 기회!", 이모지 떡칠, 라벨 접두어... 전형적인 AI 슬롭(AI slop)이었다. 이걸 그대로 블루스카이에 올리면 계정 신뢰도가 쌓이기는커녕 스팸 느낌만 줄 게 뻔했다. 이 시간대 작업 대부분은 거기서 출발했다.
AI가 "알고 있는 것"을 믿지 말 것
LLM한테 콘텐츠 생성을 맡길 때 제일 무서운 건 모델이 학습 데이터에서 긁어온 외부 지식을 팩트처럼 끼워넣는 것이다. 특히 vtuber나 피규어 관련 콘텐츠에서 이게 심했다. 구독자 수를 틀리게 쓴다거나, 탤런트의 옛날 이름(졸업 전 활동명 등)을 그대로 가져온다거나. DB에 실측값이 있는데도 모델이 "알고 있는 값"을 우선하는 현상이 반복됐다.
해결 방향은 명확했다. 프롬프트 레벨에서 아예 차단하는 것이다.
- 날짜를 넘길 때
2026-07-03형식 대신2026-07-03 (목)처럼 요일까지 명시해서 모델이 날짜를 "계산"할 여지를 없앴다. - 팩트 이외 디테일을 추가하지 말 것, 옛 이름/구 명칭을 쓰지 말 것을 시스템 프롬프트에 명시적으로 박았다.
- 팩트시트(fact sheet)를 확장해서 DB에서 끌어온 실측값(구독자 수, 캐릭터명, 레어리티, 리세일가)을 모델에 직접 주입하도록 했다.
프롬프트에 "사실 외의 것을 추가하지 말라"고 쓰는 게 효과가 있냐고 할 수 있는데, 실제로는 꽤 유효하다. 모델이 "빈 공간을 채우려는" 성향이 있는데, 팩트시트가 촘촘할수록 그 빈 공간이 줄어들고, 금지 지시까지 겹치면 외부 지식 유입 빈도가 눈에 띄게 떨어진다. 완전히 막을 순 없지만, 레이어를 하나 더 두는 것 자체가 의미 있다.
팩트가드와 스타일라이저를 분리하는 구조
이번 feat 커밋의 핵심은 콘텐츠 생성 파이프라인을 두 단계로 분리한 것이다.
[팩트시트 조합] -> [팩트가드 검증] -> [스타일라이저 LLM] -> [폴백 처리] -> [포스트]
기존에는 LLM 하나가 팩트 선별과 문체 가공을 동시에 했다. 그러다 보니 LLM이 "문장을 예쁘게 만들려고" 사실을 살짝 과장하거나, DB에 없는 수식어를 붙이는 문제가 생겼다. 분리하면 팩트가드 단계에서 출력값을 검증할 수 있고, 스타일라이저는 검증된 팩트만 가지고 문체를 다듬는 역할만 한다.
폴백(fallback)도 달았다. 스타일라이저 LLM 호출이 실패하거나 출력이 팩트가드를 통과하지 못하면, 팩트시트에서 직접 템플릿 문장을 조합해서 올린다. LLM에 의존하는 파이프라인에서 폴백이 없으면 장애 전파가 너무 쉽다.
해시태그 facet도 이번에 붙였다. 블루스카이는 해시태그를 그냥 텍스트로 쓰면 안 되고, AT 프로토콜 기준으로 facet 객체를 별도로 붙여야 태그가 제대로 인식된다. 기존에는 해시태그를 텍스트에 그냥 넣고 있었는데, 이번에 bluesky.py 쪽에서 facet 생성 로직을 추가해서 태그가 실제로 클릭 가능하게 됐다.
팩트시트 확장 내용을 정리하면 이렇다:
| 항목 | 기존 | 확장 후 |
|---|---|---|
| 기본 가격 | 정가만 | 정가 + 리세일가 |
| 탤런트 정보 | 이름만 | 이름 + 구독자 수 |
| 피규어 정보 | 시리즈 | 시리즈 + 캐릭터명 + 레어리티 |
| 통화 | 암묵적 | retail_currency 컬럼 명시 |
AI 슬롭 카피, 전면 제거
팩트가드가 구조적인 해결책이라면, AI 슬롭 제거는 더 직접적인 작업이었다. content.py와 post_daily.py에서 이모지, 라벨 접두어("오늘의 피규어:", "추천 아이템:" 같은 것들), 광고성 문구를 죄다 걷어냈다.
기존 출력이 대충 이런 느낌이었다면:
🔥 오늘의 추천 피규어! ✨
[시리즈명] 한정 레어 등장! 놓치지 마세요 👀
정가: XX엔 | 리세일: XX엔
#figure #anime #rare
목표는 이거였다:
[시리즈명] [캐릭터명] [레어리티] — 정가 XX엔, 리세일 YY엔
#figure #anime
이모지가 나쁜 건 아니지만, 자동 생성된 이모지 떡칠은 사람이 쓴 글과 구분이 너무 쉽다. 그리고 "놓치지 마세요" 같은 문구는 읽는 사람 입장에서 신뢰를 오히려 깎는다. 팩트만 냉정하게 나열하는 쪽이 오히려 더 유용해 보인다.
이 작업이 생각보다 손이 많이 갔던 이유는, 기존 카피 생성 로직이 이미 여러 군데에 퍼져 있어서 한 곳만 고쳐서는 안 됐기 때문이다. content.py에서 팩트시트를 조합하는 단계, post_daily.py에서 최종 텍스트를 구성하는 단계, 각각에서 슬롭이 유입되는 경로가 달랐다.
자잘한 버그들, 근데 무시하면 큰일 나는 것들
bb_figure 테이블에서 통화 컬럼을 currency로 읽고 있었는데, 실제 컬럼명이 retail_currency였다. 그래서 통화 정보가 None으로 들어오거나 기본값 처리가 잘못 되는 케이스가 있었다. 팩트시트 확장 과정에서 쿼리를 다시 뜯어보다가 발견했다. 스키마와 코드가 어긋나 있는 채로 한동안 돌아갔던 것 같다.
vtuber 포스트 제목 중복 문제도 있었다. 제목을 [탤런트명] - [내용] 형식으로 만드는데, 내용 자체에 이미 탤런트명이 포함된 경우 제목이 [탤런트명] - [탤런트명] 관련 내용 이렇게 됐다. 중복 감지 로직을 post_daily.py에 추가해서, 내용 문자열에 탤런트명이 이미 있으면 앞에 이름을 붙이지 않도록 했다. 간단한 수정이지만, 출력 텍스트 품질에서 꽤 티가 나는 부분이다.
문서도 실측값으로
마지막으로 hedvion-CLAUDE.md에서 gamehotdeals 관련 GSC(Google Search Console) 상태를 정정했다. 문서에 "미등록" 상태로 적혀 있었는데, 실제로 확인해보니 이미 등록됐고 사이트맵 제출까지 완료된 상태였다. stale한 문서가 그대로 남아 있으면 나중에 봇이나 작업자가 "아직 등록 안 됐네" 하고 불필요한 작업을 다시 할 수 있다. 실측 결과 나오는 즉시 문서에 반영하는 게 맞다.
이 작업들을 통틀어 보면, 오전 내내 한 가지 문제를 여러 각도에서 쪼았던 셈이다. "자동화된 콘텐츠인데 사람이 쓴 것처럼 보이게" 하는 게 아니라, "자동화된 콘텐츠인데 팩트가 정확하고 군더더기가 없게" - 방향이 달라 보이지만 결국 둘 다 신뢰성 문제다. LLM을 파이프라인에 넣을 때 제일 신경 써야 할 게 이 부분이고, 오늘 그 구조를 다듬는 데 오전을 썼다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.