원화 채용 공고에 엉뚱한 이미지가 붙으면 공고 정보 전체를 믿기 어려워져요. 이 글에서는 이미지 검증과 회사 대표작 보완, 키워드와 대화 상태 관리, Slack 전송 형식까지 다뤄요. 수치는 각 PR의 재실행 결과와 운영 중 관찰한 내용에 한정해요.
출처는 신뢰할 만했지만 이미지가 맞는지는 확인하지 못했다
2026년 10월 15일에 게시한 공고 5건에는 리그오브레전드 결과 화면, NCSOUND 배너, 인물 프로필, 대만 판매 순위표, 사옥 간판처럼 공고의 게임과 무관한 이미지가 붙었어요. 기존 이미지 선택 로직인 pickPreview는 카카오 검색 결과에서 폭이 300px 이상이고 개인 블로그에서 가져오지 않은 이미지를 고르면서 게임 매체를 우선했어요. 출처와 크기는 확인했지만 정작 그 이미지가 공고에서 다루는 게임인지는 검증하지 않았어요.
문제가 된 5건은 모두 제목에 게임명이 없어서 회사명 게임으로 검색했어요. 검색 결과 상단에는 그 회사가 만든 게임 이미지보다 회사를 다룬 기사 사진이 나왔고요. 인벤·게임포커스·루리웹 같은 게임 매체나 뉴스의 이미지도 공고와 무관할 수 있으니 매체를 믿고 허용하는 것만으로는 오탐을 막을 수 없었어요. 운영하면서 보니 독자는 캡션보다 이미지를 먼저 봤고, 스레드에 사진이 나타나는 순간 공고에 나온 게임의 이미지로 받아들일 수 있었어요.
PR #842에서는 검증하지 않은 정보를 게시하는 정책 자체가 문제라고 봤어요. 검색 미리보기를 없애고 게임을 확인하지 못한 공고에는 본문에 구글 이미지 검색 링크만 남겼어요. Slack 스레드에는 플레이스토어 이름 일치 또는 카카오 기사 제목 검증을 통과한 이미지만 올리도록 기준을 다시 정했어요.
미검증 이미지를 없애자 정보가 너무 비었다
정확도를 높이려고 미검증 미리보기를 없애자 다른 문제가 곧 드러났어요. 기존 게시물 33건 중 이미지가 붙은 공고는 2건뿐이었고 제목의 대괄호에서 게임명을 추출하지 못한 공고도 21건으로 전체의 64%였어요.
원화 채용 공고에는 프로젝트 A 같은 미공개 프로젝트가 많아요. 제목에 대괄호가 없거나 [AI본부]처럼 게임과 무관한 조직 태그가 들어가기도 해요. 반대로 구인, 회사명, 프로그램명을 게임명으로 잘못 읽으면 🎮 구인 같은 엉뚱한 정보가 생겼어요. 게임명이 있더라도 PC·Steam 게임이나 미출시작이면 플레이스토어에서 이름이 일치하는 결과를 찾지 못했어요.
미공개 프로젝트의 이미지를 외부 검색으로 알아내기는 불가능하거나 위험해요. PR #844에서는 공고의 게임과 회사의 대표작을 분명히 구분하면서 시각 정보를 보완했어요. 대표작 이름은 검색 결과로 추측하지 않고 회사가 게임잡 회사 정보에 직접 입력한 대표게임 값을 사용했어요. 그렇게 얻은 이름도 기존 검증 경로인 플레이스토어 이름 일치와 카카오 기사 제목 검증을 거쳤고, 이미지가 공고 대상 게임으로 오해받지 않도록 본문과 캡션에 회사 대표작이라고 표시했어요.
파이프라인을 간단히 정리하면 다음과 같아요.
공고 제목에서 게임명 추출
│
├─ 게임명 확인 가능
│ └─ 기존 검증 경로 실행
│ ├─ 플레이스토어 이름 일치
│ └─ 카카오 기사 제목 검증
│
└─ 게임명 확인 불가
└─ 게임잡 회사 정보의 대표게임 사용
└─ 같은 검증 경로 실행
검증 성공 → 출처와 의미를 표시해 이미지 게시
검증 실패 → 이미지 없이 검색 링크만 제공
이 구조에서도 대표작 이미지는 해당 채용 공고의 프로젝트를 증명하지 않아요. 회사가 공개적으로 대표작이라고 밝힌 게임을 보여 주는 보조 정보인 만큼, 데이터 출처와 화면에서 전달하는 의미를 따로 구분해야 했어요.
변경된 빌드 결과물로 같은 33건을 다시 실행해 보니 이미지가 포함된 공고가 2건에서 15건으로 늘었어요. 포함률은 약 6.1%에서 45.5%로 올랐지만 같은 33건을 재실행한 결과라 장기 운영 기간의 평균이나 전체 원화 공고를 대표하는 값은 아니에요.
검증된 이미지도 형식이 다르면 Slack에서 사라졌다
이미지 내용을 검증해도 게시까지 성공하는 건 아니었어요. 2026년 10월 14일 회차를 재게시할 때 루리웹의 크레센토 이미지가 빠졌어요. 파일 형식은 WebP였고 Slack의 chat.postMessage는 invalid_blocks를 반환했어요. 당시 ImageDownloader는 WebP를 허용 형식으로 받아 그대로 전달했고 소비자의 replyWithImage는 실패를 경고 로그로만 남겼어요. 사용자는 처음부터 이미지가 없었던 건지 전송에 실패한 건지 구분할 수 없었어요.
PR #843에서는 src/art-job-feed/infrastructure/image.downloader.ts에서 응답의 MIME 타입이 image/webp일 때 sharp로 PNG로 변환하고 파일 확장자도 .png로 바꿨어요. PNG·JPEG·GIF 같은 기존 형식은 그대로 두고 WebP만 변환했어요.
이미지 다운로드
│
├─ Content-Type: image/webp
│ └─ sharp로 PNG 변환
│ └─ 파일명 확장자를 .png로 변경
│
└─ 그 외 형식
└─ 원본 유지
Slack 이미지 블록으로 전송
이 변경으로 검증을 통과한 WebP 이미지가 Slack의 지원 형식 때문에 조용히 누락되던 경로를 막았어요. 최소 재현에는 WebP 응답을 반환하는 이미지 URL과 Slack 이미지 블록 전송 경로가 필요해요. 수정 전에는 image/webp 파일이 invalid_blocks로 거부되고 수정 후에는 PNG로 변환한 파일이 전달돼야 해요.
키워드를 코드에서 꺼내 운영 데이터로 옮겼다
이미지 검증만 고쳐서는 공고 자체의 오탐을 조정하기 어려웠어요. 운영 중 “캐주얼·북미풍 키워드도 찾아줘”, “공고 키워드 추가해줘” 같은 요청이 들어왔지만 기존 시스템에는 처리 기능이 없었어요. 2026년 10월 15일에 기록된 요청 7건은 분류기에서 모두 UNKNOWN으로 빠졌어요. 실제로는 아무것도 바뀌지 않았는데 대화 응답은 “추가하는 요청으로 이해했어요”처럼 처리한 것처럼 답했어요.
PR #849에서는 제목 필터 키워드를 코드 상수에서 DB로 옮겨 원화 공고 채널의 대화로 추가·제거·조회할 수 있게 했어요. 변경자와 변경 시각, 변경 내용도 이력으로 남겼어요. 운영 중 생긴 오탐을 코드 배포 없이 바로잡고 누가 무엇을 바꿨는지도 추적할 수 있게 된 거예요.
키워드는 역할에 따라 세 목록으로 나눴어요.
| 목록 | 처리 방식 | 사용 목적 |
|---|---|---|
| 허용 | 제목에 키워드가 있으면 공고를 받음 | 원화 공고 후보 포함 |
| 제외 | 제목에 키워드가 있으면 공고를 거름 | 명백한 비대상 공고 차단 |
| 화풍 | 공고를 거르지 않고 태그만 붙임 | 캐주얼·북미풍 같은 표현 제공 |
화풍을 필터 대신 태그로 둔 데에는 운영 중 확인한 근거가 있었어요. 조사한 공고 10건 중 6건은 본문이 이미지여서 추출된 텍스트가 약 100자에 그쳤고, 화풍 단어가 나온 공고는 0건이었어요. 이런 조건에서 본문의 화풍 키워드를 필수 필터로 쓰면 실제 대상 공고가 빠질 가능성이 커지므로 검색 범위를 좁히는 조건이 아니라 독자에게 보여 주는 보조 정보로만 사용했어요. 이 결과는 10건을 살펴본 것이며 본문 OCR이나 이미지 분석을 추가했을 때 결과가 어떻게 달라지는지는 측정하지 않았어요.
사용자가 어느 목록에 넣을지 밝히지 않으면 시스템은 추출한 키워드를 먼저 보여 주고 목록을 다시 물어요. “공고 가져와” 같은 요청이 오면 cron 회차를 하나 더 만드는 것처럼 같은 큐에 일회성 작업을 넣어요. 같은 소비자가 작업을 순서대로 실행하고 이미 게시한 공고는 장부를 확인해 다시 올리지 않아요. cron과 즉시 요청이 겹쳐도 작업을 한 번에 하나씩 처리하고 게시 장부로 중복을 막는 구조예요.
대화형 제어에는 상태 경계가 중요했다
대화로 키워드를 바꿀 수 있게 하자 배포 직후 새로운 오작동이 나타났어요. 봇이 “어느 목록에 넣을까요”라고 되물으며 허용(제목에 있으면 받기), 화풍(태그만) 같은 라벨을 보여 줬어요. 사용자가 그 라벨을 겹낫표로 감싸 답하자 extractTerms는 설명용 라벨까지 실제 키워드로 추출했어요.
이어 사용자가 '반실사' 키워드 제외라는 새 요청을 보냈지만 동사가 없다는 이유로 UNKNOWN으로 분류됐어요. mergeArtJobFeedCommands는 이 요청을 직전 NEEDS_LIST 상태에 대한 답으로 보고 합쳤어요. 결국 반실사가 아니라 앞서 잘못 추출한 라벨 조각 3개가 제외 목록에 기록됐어요. 2026년 10월 15일 실행 기록 9099와 art_job_keyword_change 31~33에서 확인된 사례예요.
PR #850에서는 이 실패가 일어난 세 지점을 고쳤어요. 되묻기에 대한 답에서는 화면에 표시한 목록 라벨을 키워드 후보에서 제외했어요. 새 메시지 자체에 키워드가 들어 있으면 직전 질문의 보충 답변으로 합치지 않고 독립된 요청으로 처리했어요. 지금 줘봐처럼 공고라는 단어를 생략한 표현이나 관련 공고 찾아줘도 즉시 수집 요청으로 해석하고 함께 언급한 키워드가 허용 목록인지, 화풍 태그인지, 어느 목록에도 없는지 응답에 표시했어요.
대화 기록을 많이 합칠수록 자연스럽다는 가정이 언제나 맞는 건 아니에요. 직전 상태를 참고해야 하는 답변과 새로운 명령을 구분하지 않으면 문맥을 유지하려던 기능이 오히려 잘못된 DB 변경을 키울 수 있어요. 검증할 때도 정상 문장 하나만 확인해서는 부족해요. 적어도 다음 네 가지 입력은 서로 다른 경우로 실행해야 해요.
1. "캐주얼 추가해줘"
→ 목록이 없으므로 키워드를 보존한 채 목록을 되물어요.
2. 되묻기에 "화풍에 추가"
→ 화면의 설명 라벨은 키워드로 저장하지 않고, 캐주얼을 화풍 목록에 넣어요.
3. 되묻기 직후 "'반실사' 키워드 제외"
→ 직전 요청과 합치지 않고 반실사에 대한 새 제외 요청으로 처리해요.
4. "지금 줘봐"
→ '공고'라는 단어가 없어도 즉시 수집 요청으로 분류해요.
중복 실행을 검증하려면 같은 공고가 즉시 요청과 cron에 동시에 포함되게 한 뒤 게시 장부가 두 번째 게시를 막는지 확인해야 해요. 실패와 재시작을 검증하려면 작업이 큐에 들어간 뒤, 수집 중, 게시 장부 기록 전후에 각각 프로세스를 중단해야 해요. 해당 구간을 실제로 검증한 결과는 근거 PR에 나와 있지 않아요. WebP 변환 전후의 화질·파일 크기·처리 시간, 변환 실패와 Slack 전송 실패 때의 재시도, 키워드 변경 후 오탐률과 대표작 이미지의 오인 여부도 아직 측정하지 않았어요.