Stuck in the same dev.to topics? An open model fixed it.
dev.to에서 늘 같은 주제만 보이나요? 오픈 모델로 주간 요약을 만들었습니다
개발자가 dev.to 글을 매일 모아 주간 이메일로 보내는 오픈소스 스크립트 devto-digest를 만들었습니다. 팔로우한 태그의 인기 글과 관심 영역 밖의 글을 나눠 보여주고, 오픈 웨이트 모델로 짧은 요약을 작성합니다. 태그가 119개일 때 발견 영역이 좁아지는 문제와 API 호출 제한, 이메일 렌더링 문제를 해결한 과정도 다룹니다.
- 주제
에디터 노트
팔로우 태그가 119개면 Discover 후보가 사실상 사라진다는 수치는 발견 기능의 한계를 구체적으로 보여줍니다. 작성자는 태그 하나가 겹치는 글까지 허용해 후보를 넓혔고, 겹치지 않는 글을 먼저 고르는 규칙도 유지합니다. 댓글에서 제안한 덜 비슷한 태그를 활용하는 방식이나 직접 탐색 태그를 고르는 방법은 아직 구현하지 않았습니다. 이 프로젝트의 장점은 이런 순위 규칙과 실패 대체 경로를 공개해 사용자가 직접 바꿀 수 있다는 점입니다. 반면 기본 설정은 글 본문을 호스팅 모델에 보내며, 로컬 모델 사용은 형식상 가능하지만 시험하지 않았습니다. 따라서 개인 정보가 중요한 사용자는 설정만 바꾼 뒤 같은 결과와 동작을 기대하기 어렵습니다. API 매개변수 오류나 모바일 화면 넘침처럼 테스트가 놓친 문제도 있어 실제 계정과 브라우저에서 확인하는 과정이 필요합니다.
AI 요약
devto-digest는 dev.to 계정의 팔로우 태그와 공개 API를 이용해 글을 모은 뒤 주간 이메일로 보내는 오픈소스 Node.js 스크립트입니다. 개발자는 매일 피드를 훑지 않고도 관심 분야 글을 확인하고, 팔로우하지 않은 태그의 글도 발견할 수 있도록 두 섹션을 구성했습니다. For you에는 팔로우한 태그의 인기 글을, Discover에는 기존 관심 영역 밖의 인기 글을 넣습니다. 각 글은 표지, 제목, 태그, 작성자와 오픈 웨이트 LLM이 쓴 한두 문장 요약으로 표시합니다.
수집과 순위 산정
스크립트는 매일 후보 글을 모으고 반응 수와 댓글 수에 각각 가중치를 적용해 점수를 매깁니다. 댓글은 반응의 두 배로 계산하며, 최근 글에 유리하도록 점수에 완만한 시간 감쇠를 적용합니다. 매일 수집해 두면 수요일에 인기를 얻은 글도 주간 발송 때 후보에 남습니다. 발송 직전에는 dev.to API에서 글을 다시 확인하고 삭제되거나 비공개로 바뀐 글을 제외합니다. 빠진 자리는 다음 순위 글로 채웁니다.
팔로우한 태그에는 희소성에 따른 가중치를 줍니다. 예를 들어 htmx처럼 드문 태그는 discuss처럼 흔한 태그보다 높은 비중을 받습니다. 다만 작성자가 태그 119개를 팔로우한 계정으로 시험했을 때 후보 글 약 2,150개 가운데 팔로우 태그가 하나도 없는 글은 31개였고, 그중 반응이 20개 이상인 글은 2개뿐이었습니다. Discover 글을 넓히기 위해 discoveryMaxOverlap 설정을 추가했습니다. Discover 글이 팔로우한 태그 하나까지 공유하도록 허용하되, 태그가 겹치지 않는 글을 먼저 고릅니다.
모델과 실행 환경
기본 요약 모델은 Mistral의 호스팅 API를 통해 호출하는 mistral-small-latest입니다. Node.js와 의존성 하나로 구성하며, OpenAI 호환 /chat/completions 엔드포인트를 사용합니다. config.json의 baseUrl과 model 값을 바꾸면 다른 제공자나 로컬 Ollama를 지정할 수 있습니다. 다만 작성자는 로컬 모델을 벤치마크하지 않았다고 밝혔습니다. 모델 호출에 실패하거나 API 키가 없으면 dev.to가 제공하는 설명을 사용해 이메일을 보냅니다.
요약 프롬프트는 25단어 이내, 핵심 표현 두 개에서 네 개를 굵게 표시하도록 제한합니다. 따옴표와 URL은 넣지 않게 하며, 글 속 내용은 신뢰할 수 없는 데이터로 취급해 그 안의 지시를 따르지 않도록 합니다. 결과는 이메일에 넣기 전에 HTML 이스케이프합니다. 기본 설정에서는 글 본문이 Mistral 호스팅 API로 전송됩니다. 로컬 Ollama를 지정하면 글을 로컬에서 처리할 수 있을 것으로 보지만, 작성자는 이를 검증하지 않았습니다.
GitHub Actions와 구현 중 발견한 문제
수집과 발송은 GitHub Actions cron 또는 로컬 cron으로 실행합니다. Actions에서는 매일 후보를 갱신하고 설정한 요일에 이메일을 보냅니다. 수동 실행에는 collect, run, force-send 명령이 있습니다. collect는 후보만 새로 모으고, run은 일일 작업처럼 동작하며, force-send는 당일 발송 여부와 관계없이 즉시 이메일을 보냅니다. state.json은 Actions 캐시에 보관합니다. 작성자는 전체 실행 시간이 2분 이내라고 설명합니다. 개발자는 자신의 dev.to API 키를 넣고 yarn dry-run을 실행해 이메일을 보내지 않은 채 출력과 로컬 미리보기를 확인할 수 있습니다.
dev.to API에서 복수형 tags= 매개변수에 top=을 함께 붙이면 top= 조건이 조용히 무시되는 문제도 발견했습니다. 요청은 성공한 것처럼 보였지만 약 3,000일 전 글이 반환됐습니다. 단수형 tag=만 기간 조건을 적용해 이를 사용하도록 수정했습니다. 태그 119개를 각각 요청하자 HTTP 429 응답을 받았고, 요청 간격을 500밀리초로 두며 Retry-After를 따르도록 바꿨습니다. 또 411픽셀 너비 화면에서 이메일 카드가 오른쪽으로 넘쳤습니다. 셀에 width: 100%와 패딩을 함께 적용한 탓이었고, 대비율이나 HTML 이스케이프 테스트로는 발견하지 못해 실제 브라우저 렌더링으로 확인했습니다.
이메일은 어두운 테마를 요청하는 클라이언트에 맞춰 색을 바꾸며, 텍스트와 배경 조합은 WCAG AA 기준인 대비율 4.5:1을 만족해야 합니다. 태그 이름도 항상 #tag 형태로 표시해 색상만으로 태그를 구분하지 않게 했습니다. 데이터는 공개 dev.to API의 GET /api/articles와 GET /api/follows/tags에서 가져오며 스크래핑은 하지 않습니다. 프로젝트는 저장소에서 프롬프트와 파싱, 대체 동작을 확인할 수 있도록 공개했습니다.
dev.to 반응
- @thomasbnt — 자동화 작업에 관심이 있다면 GitHub에서 최신 Actions 실행을 확인해 보세요.
- @abhisekroy169 — 좋은 아이디어네요.
- @thomasbnt — 감사합니다!
- @alexcodebytes — tags=와 tag=의 함정은 정말 거칠고, 실제 운영 환경에서야 드러나는 문제입니다. 매개변수를 하나 더 붙였을 때 API가 그 매개변수를 조용히 무시하는 비슷한 사례를 겪었습니다. 요청은 성공하지만 결과는 엉망이 됩니다. Discover 영역이 거의 비어 있다는 점이 가장 흥미롭습니다. 글 2,150개 중 31개만 팔로우 태그가 하나도 없었다면 팔로우한 태그가 사실상 사이트 전체를 덮는 셈입니다. 코드 버그라기보다 dev.to의 태그 시스템 문제라고 봅니다. 시간이 더 있다면 덜 비슷한 태그에 가중치를 주겠나요, 아니면 직접 탐색할 태그 목록을 추가하겠나요?
- @thomasbnt — 읽어주셔서 감사합니다, Oleksandr! 예전에는 태그 가중치 시스템이 있었는데 현재 페이지에서는 보이지 않네요. 이 페이지에서 일부 태그를 숨길 수는 있습니다.
- @ben — 코어 제품에 반영할 아이디어를 시도해 볼 좋은 영감입니다. 멋지네요.
- @thomasbnt — 감사합니다, Ben! 이미 매일 또는 매주 요약 이메일이 있다는 건 알고 있습니다. 하지만 너무 관련성이 높거나 충분히 관련성이 없어서, 원할 때 더 많이 발견하고 싶습니다.
- @francistrdev — 멋지네요. 잘했습니다.
- @thomasbnt — 감사합니다, Francis!
원문: dev.to / 번역·요약: Trawling