자동화 파이프라인8 분 읽기

내 자동답글 봇의 코드 대부분은 '답글을 달지 않는' 쪽이었다

Threads에서 쓰던 댓글 응대 스크립트를 인스타그램 4개 계정으로 옮겼습니다. 기능은 두 줄로 설명되는데, 216줄 대부분은 엉뚱한 때 움직이지 않게 막는 코드였습니다. 육십갑자의 '병신년'을 악플로 읽지 않게 하는 처리도 그 안에 있습니다.

#instagram#moderation#automation#llm#safety
왼쪽에는 댓글을 분류해 숨기거나 답글을 다는 흐름, 오른쪽에는 그 흐름을 둘러싼 안전장치 목록이 놓인 대비 이미지
기능은 분류·숨김·답글 세 단계였지만, 코드 대부분은 그 세 단계가 엉뚱한 때 실행되지 않게 막는 데 쓰였습니다.

기능은 두 줄이었습니다

Threads에서 돌리던 댓글 응대 스크립트를 인스타그램 4개 계정에 옮기는 작업이었습니다. 하는 일은 단순합니다. 들어온 댓글을 스팸·악플과 정상 댓글로 나눈 뒤, 스팸·악플은 숨김(hide) 처리하고 정상 댓글에는 Claude가 쓴 답글을 답니다.

설명은 두 줄인데 커밋은 파이썬 216줄, 테스트 45줄, launchd plist 36줄이 됐습니다. 216줄을 다시 읽어 보니 분류나 답글 생성 코드는 일부였습니다. 대부분은 '하지 말아야 할 때 하지 않게 만드는' 코드였습니다.

여러분의 자동화에서는 실제 기능 코드와, 그 기능이 엉뚱한 때 실행되지 않게 막는 코드 중 어느 쪽이 더 깁니까?

이 자동화는 공개된 자리에 글을 쓰고, 다른 사람이 쓴 글을 숨깁니다. 한 번 잘못 움직이면 조용히 되돌리기 어려운 종류입니다. 그래서 이 글은 기능보다 안전장치 목록에 관한 이야기입니다.

함정은 대부분 '조용히 잘못 동작하는' 쪽이었습니다

커밋 메시지에 적은 안전장치를 하나씩 보면, 각 장치가 어떤 실패를 막는지 드러납니다.

  • 자기 댓글 제외. 봇이 단 답글도 댓글 목록에 다시 올라옵니다. 이걸 거르지 않으면 봇이 자기 답글에 또 답글을 달 수 있습니다.
  • username이 없으면 fail-closed. 내 계정 이름을 모르면 무엇이 자기 댓글인지 판별할 수 없습니다. 그럴 때는 대충 진행하지 않고 멈추게 했습니다.
  • 성공했을 때만 ledger에 기록. 처리한 댓글을 적어 두는 장부에 실패 건까지 적으면, 그 댓글은 다시는 시도되지 않습니다. 성공한 것만 적어야 실패 건이 다음 실행에서 재시도됩니다.
  • 계정당 상한. 한 번 실행할 때 한 계정에서 처리할 수 있는 양에 상한을 뒀습니다.
  • null 가드와 출력 가드. API 응답의 빈 값을 막고, 모델이 내놓은 답글 텍스트를 그대로 게시하기 전에 한 번 거릅니다.
  • 다국어(ko/en/ja). 분류가 한국어 댓글에만 맞춰지지 않게 했습니다.
  • URL 토큰 1회 매칭. 커밋 메모에는 이렇게 한 줄로만 남아 있습니다.
  • 라이브 lock. 실제로 게시하는 실행이 겹치지 않게 잠금을 걸었습니다.

이 중 기능을 더해 주는 장치는 하나도 없습니다. 전부 '이 조건이면 하지 마라'입니다.

'병신년'은 욕이 아니라 해의 이름입니다

한국어라서 생긴 함정도 하나 있었습니다. 병신년(丙申年)은 육십갑자 중 한 해의 이름입니다. 그런데 앞 두 글자가 흔한 욕설과 똑같습니다. 욕설 목록을 문자열로만 매칭하면 간지를 이야기하는 정상 댓글이 악플로 분류될 수 있습니다.

이 봇에서 악플 판정은 곧 숨김 처리입니다. 오탐이 나면 정상 사용자의 댓글을 가리는 방향으로 틀립니다. 답글을 하나 덜 다는 실수보다 훨씬 나쁜 실수라서, 이 경우는 오탐이 나지 않게 따로 처리했습니다.

진짜 관문은 코드 밖에 있었습니다

안전장치를 다 넣어도 남는 문제가 있었습니다. 댓글을 숨기고 답글을 달려면 instagram_business_manage_comments 스코프가 필요합니다. 이 권한은 코드로 해결할 수 없습니다.

당신이라면 이 상태에서 바로 라이브로 켜시겠습니까, 아니면 아무것도 못 하는 상태로 먼저 배포하시겠습니까?

저는 후자를 택했습니다.

  • 기본값은 DRY. 따로 켜지 않으면 실제로 게시하지 않습니다.
  • 스코프가 없으면 no-op. 권한이 없을 때는 에러를 내며 시도하지 않고, 아무것도 하지 않고 끝납니다.
  • 스케줄은 launchd로 13:20, 20:20 하루 두 번.
  • 안전 리뷰는 Sonnet으로 두 라운드. 작성한 모델과 다른 모델에게 '이 코드가 잘못 움직일 수 있는 경우'를 검토하게 했습니다.

결국 배포한 것은 '권한이 생기면 안전하게 움직일 준비가 된 코드'입니다. 지금 당장 무언가를 해 주는 코드는 아닙니다.

자가진단 체크리스트

  • 내 자동화가 남긴 결과물(답글·게시물)이 다시 입력으로 들어왔을 때, 그것을 자기 것으로 알아보고 건너뜁니까?
  • 처리 기록(ledger)을 성공한 뒤에만 쓰고 있습니까, 아니면 실패한 건까지 '처리됨'으로 묻어 버리고 있습니까?
  • 권한이나 설정이 빠졌을 때 기본 동작이 '아무것도 안 함'입니까, 아니면 '일단 시도함'입니까?

솔직한 부분

이 글은 커밋 시점의 기록입니다. 제가 가진 정보는 커밋 메시지와 파일 목록뿐이라, 스코프 승인을 실제로 받았는지, 라이브로 전환했는지, 분류가 얼마나 정확했는지, 오탐이나 미탐이 몇 건이었는지는 모릅니다. 병신년 말고 다른 오탐 사례가 있었는지도 모르고, Sonnet 리뷰 두 라운드가 구체적으로 무엇을 잡아냈는지도 커밋에는 남아 있지 않습니다. 'URL 토큰 1회 매칭'도 정확히 어떤 상황을 막으려는 장치인지 커밋 문구만으로는 설명할 수 없어서 그대로 옮겼습니다.

관련 글