나를 위해 GitHub와 뉴스를 큐레이션하는 개인용 인텔리전스 시스템을 구축했습니다 — 작동 방식 소개
요약
LLM과 멀티 에이전트 시스템을 활용하여 뉴스 및 GitHub 트렌드를 개인 맞춤형으로 큐레이션하는 자동화 시스템 구축 사례를 소개합니다. RSS 피드와 GitHub API를 스크래핑하여 사용자의 관심사에 맞는 정보를 Telegram으로 브리핑합니다.
핵심 포인트
- LLM을 활용한 뉴스 및 오픈소스 저장소의 맞춤형 큐레이션
- GitHub Trending의 인기도 중심 한계를 극복하고 관련성 기반 탐색
- Telegram을 인터페이스로 활용한 대시보드 없는 자동화 시스템
- Deepseek V4 Flash를 이용한 효율적인 정보 필터링
나를 위해 GitHub와 뉴스를 큐레이션하는 개인용 인텔리전스 시스템을 구축했습니다 — 작동 방식 소개
뉴스를 읽고 GitHub를 스크래핑(Scraping)하며, LLM (Large Language Model)으로 큐레이션하여, 당신이 커피를 다 마시기도 전에 휴대폰으로 전달해 주는 개인용 인텔리전스 시스템입니다. 대시보드도, 로그인도 필요 없습니다. 당신이 설정한 일정에 따라 세 번의 브리핑이 제공될 뿐입니다.
문제점
저는 이미 OpenRouter를 통해 LLM 기반으로 작동하며, 저가형 VPS (Virtual Private Server)에 호스팅되고, 완전히 Telegram을 통해 전달되는 멀티 에이전트 시스템인 TeachSim을 구축한 상태였습니다. 해당 스택이 존재하자, 이것으로 또 무엇을 할 수 있을지가 당연한 질문으로 떠올랐습니다. 가장 빠르게 성과를 낼 수 있는 분야는 바로 정보 소비 그 자체였습니다.
실제적인 갈증은 제가 뉴스를 소비하는 방식에 있었습니다. Google은 수년 동안 알고리즘에 의해 선택된, 완전히 수동적인 데일리 피드(Daily feeds)를 저에게 밀어 넣어 왔습니다. 저는 무엇이 나타날지 결정한 적이 없으며, 그저 도착한 것을 스크롤했을 뿐입니다. 저는 반대의 방식을 시도하고 싶었습니다. 인프라는 동일하게 유지하되, 역할은 완전히 다르게 말이죠. 제가 읽는 뉴스뿐만 아니라, 매일 살펴보고 싶을 만큼 점점 더 관심이 생기는 오픈 소스 생태계에 대해서도, 참여도(Engagement)에 최적화된 피드 대신 LLM이 선택을 수행하도록 하는 것입니다.
두 번째 부분(오픈 소스)은 첫 번째 부분만큼이나 중요했습니다. 저는 매일 GitHub Trending를 수동으로 브라우징할 시간이 없으며, Trending는 진정한 참신함보다는 절대적인 인기도에 보상을 줍니다. 저의 베팅은 광범위하게 스캔하고 별(Star) 개수가 아닌 관련성에 따라 판단하는 LLM이, 제가 그렇지 않았다면 절대 찾지 못했을 것들을 표면 위로 끌어올려 줄 것이라는 점이었습니다.
결과는 성공적이었습니다. 이 파이프라인은 제가 직접 찾아 나선 것이 아니라, 실제로 RTK, sst/opencode, AI Engineering From Scratch 등과 같은 진짜 도구들을 저에게 찾아주었습니다. 이것만으로도 구축할 가치는 충분했습니다. 자기 주도적인 버전은 인기도 순위 피드를 통해서는 절대 찾지 못했을 도구들을 보여줍니다.
GitHub Digest란 무엇인가
GitHub Digest는 세 번의 예정된 브리핑을 Telegram으로 직접 전달하는 개인용 자동화 시스템입니다:
- 모닝 뉴스 브리핑 (morning news briefing): 세계 정세, 기술, AI 연구 및 몇 가지 전문 분야를 아우르는 수십 개의 RSS 피드를 대상으로 합니다. 이 피드들은 Deepseek V4 Flash로 전달되어, 그날 정말로 주의를 기울일 가치가 있다고 판단된 소수의 이야기로 큐레이션됩니다.
- GitHub Discovery 다이제스트 (GitHub Discovery digest): GitHub Search API 쿼리를 광범위하게 사용하여 이전에 본 적 없는 저장소(repo)들을 찾아냅니다. 이는 "gem" (완전히 새롭고, 별(star) 개수는 적지만 성장 속도가 빠른), "exploding" (별 개수가 빠르게 가속화되는), "hot" (이미 자리 잡았지만 이번 주에 트렌드인)의 세 단계로 분류됩니다.
- 주간 모멘텀 브리핑 (weekly Momentum briefing): 개인적인 관심 도구 목록(watchlist)을 시간에 따라 추적하며, 단순히 별 개수의 변화량(delta)이 아닌 평이한 언어로 무엇이 변했는지 보고합니다.
여기에 온디맨드(on-demand) 명령 레이어가 추가되어 있어, 터미널을 사용하지 않고도 휴대폰에서 이 세 가지 중 무엇이든 수동으로 불러오거나, 시스템 상태를 확인하거나, 관심 목록을 관리할 수 있습니다.
아키텍처 (The Architecture)
Sources (RSS feeds / GitHub Search API / personal watchlist)
│
▼
...
각기 다른 일정에 따라 작동하는 세 개의 독립적인 '스크레이핑-분석-전달(scrape-to-analyze-to-deliver)' 파이프라인이 있으며, 이들은 단일 전달 레이어와 단일 리스너 프로세스를 공유합니다. 이 모든 것은 싱글 코어, 1GB 예산의 VPS에서 실행되는데, 이것이 결과적으로 이 빌드의 모든 다른 결정을 형성하는 가장 큰 제약 사항이 되었습니다.
큐레이션 단계는 OpenRouter를 통해 라우팅된 LLM 호출을 통해 실행됩니다. 현재는 빠르고 저렴한 모델을 사용하고 있는데, 이는 큐레이션 작업이 최첨단(frontier-level) 수준의 추론보다는 높은 빈도(매일 세 번 실행)로 일관성과 낮은 비용을 필요로 하기 때문에 특별히 선택되었습니다.
내부 구조 (Under the Hood)
모델의 실제 출력값이 항상 예상한 위치에 있는 것은 아니며, 이로 인해 조용히 실패(fails silently)하는 경우가 발생합니다. 큐레이션(Curation)을 수행하는 LLM은 내부적으로 랭킹(Ranking) 작업을 처리하는 방식에 따라, 표준 content 필드 대신 reasoning 필드에 실제 콘텐츠를 반환할 때가 있습니다. 이 현상이 처음 발생했을 때, 시스템은 충돌하지 않았고 로그 어디에도 에러가 나타나지 않았습니다. 파이프라인은 끝까지 실행되었고, 크론 잡(Cron job)은 성공을 보고했으며, Telegram으로 전달된 브리핑은 그저 비어 있었습니다. 이는 명확한 예외(Exception)가 발생하는 것보다 더 나쁜 실패 모드입니다. 빈 메시지는
Cron은 터미널과 같은 언어를 사용하지 않습니다. 모든 예약된 작업(Scheduled job)은 source venv/bin/activate 대신 . venv/bin/activate를 사용하여 Python 가상 환경(Virtual environment)을 활성화합니다. 그 이유는 단순한 스타일 선호도보다 더 근본적입니다. source는 bash 내장 명령어(Builtin)입니다. cron은 bash가 아닌 sh를 통해 작업을 실행하며, sh는 이를 인식하지 못합니다. 이 작업은 대화형 터미널(Interactive terminal)에서 수십 번 수동으로 테스트되었고, 터미널 자체가 bash이기에 source가 잘 작동했습니다. 따라서 활성화 라인이 올바르게 보였고, 처음으로 자동 실행(Unattended)될 때까지 문제가 없었습니다. 하지만 실행 시 환경 활성화에 실패했고, 프로젝트가 실제로 의존하는 패키지 대신 시스템 경로(System path)에 우연히 있는 Python과 패키지들을 조용히 사용하게 되었습니다. 에러도, 충돌(Crash)도 없이, 의존하는 무언가가 존재하지 않을 때까지 잘못된 환경에서 작업이 조용히 실행되었을 뿐입니다. .은 POSIX 호환(POSIX-portable) 방식이며 두 셸(Shell) 모두에서 작동합니다. 해결책은 단 한 글자이지만, 이는 왜 동일한 명령어가 대화형 테스트에서는 통과하고 자동 실행 버전에서는 실패하는지를 이해했을 때 비로소 의미가 있습니다.
리포지토리(Repos)가 실제로 선택되는 방식
"GitHub에서 새로운 것을 보여줘"라는 말은 그 자체로 나쁜 프롬프트(Prompt)입니다. 노이즈(Noise)가 섞여 나오거나, 이미 모두가 알고 있는 유명한 12개 정도의 리포지토리만 보게 됩니다. LLM이 후보를 확인하기 전에, 실제로 읽을 가치가 있는 매일의 몇 가지 항목을 추려내기 위해서는 몇 단계의 필터링(Filtering) 과정이 필요했습니다.
먼저 넓은 그물을 던집니다. 스크레이퍼(Scraper)는 여러 광범위한 기술 카테고리에 걸쳐 타겟팅된 검색 쿼리(Search queries)를 대량으로 실행하며, 각 쿼리는 서로 다른 종류의 리포지토리(Repo)를 찾아내도록 구성되었습니다. 어떤 쿼리는 완전히 새로운 활동을 추적하고, 어떤 쿼리는 최근 모멘텀(Momentum)이 있는 기존 프로젝트를, 또 다른 쿼리는 특정 기술적 니치(Niche)를 찾습니다. 단일 쿼리 스타일로는 모든 것을 찾을 수 없기에, 이들을 조합하는 것이 핵심입니다. 이 모든 과정을 결정짓는 제약 사항은 GitHub의 검색 전용 엔드포인트(Endpoint)가 분당 30회 요청으로 제한되어 있다는 점입니다. 이는 일반 API 제한보다 엄격하며, 이렇게 많은 카테고리를 루프(Loop)로 실행할 경우 쉽게 초과할 수 있습니다. 스크레이퍼는 호출 사이에 2.1초 동안 대기(Sleep)하여 분당 약 28회의 요청을 수행합니다. 이는 전체 실행을 몇 분 안에 끝낼 수 있을 만큼 상한선에 근접하면서도, 제한 장치(Limiter)를 건드리지 않을 만큼 충분히 여유를 둔 수치입니다. 어쨌든 403 오류가 발생하면, 속도를 줄이라는 신호를 보낸 엔드포인트를 계속 두드리는 대신 60초 동안 완전히 백오프(Backoff)한 후 재시도합니다.
코드 검색은 가장 중요한 수치 하나를 숨기고 있습니다. GitHub의 코드 검색(Code-search) 엔드포인트는 리포지토리의 이름이나 설명뿐만 아니라 실제 내용물로 리포지토리를 찾는 데 유용하지만, 반환되는 중첩된 리포지토리 객체(Nested repository object)에는 스타(Star) 수가 완전히 누락되어 있습니다. 모든 코드 검색 결과는 계층화(Tiering) 로직을 실행하기 전에 전체 리포지토리 레코드를 가져오기 위한 두 번째 별도의 API 호출이 필요합니다. 이 단계를 건너뛰면 후보군의 절반이 스타가 0개인 무명 프로젝트로 조용히 분류되어 버립니다.
계층화(Tiering)는 고정된 기준이 아니라 각 리포지토리를 자기 자신과 비교합니다. 세 가지 계층이 있으며, 그 중 어느 것도
그 마지막 차이점이 중요합니다. 별(star)이 10,000개 있고 최근 LLM 관련 활동이 없는 리포지토리는, 별이 200개인 리포지토리보다 특정 날에 더 흥미롭다고 할 수 없습니다. 측정되는 것은 절대적인 인기가 아니라, 리포지토리 자체의 패턴 대비 변화량입니다.
LLM에게 단순히 별 개수로 순위를 매기지 말라고 명시적으로 지시합니다. 큐레이션 프롬프트는 이 작업을 의미 있는 것을 선택하는 것으로 정의합니다: 즉, 진정한 참신함(novelty), 기술적 관련성(relevance), 그리고 이 파이프라인이 없었다면 내가 놓쳤을 법한 것인지 여부입니다. 별 성장 속도(star-velocity) 목록 상단에 있는 리포지토리라 할지라도, 포크(fork)된 것이거나, 튜토리얼 리포지토리이거나, 트렌딩 수치 뒤에 실질적인 내용이 없는 경우에는 제외될 수 있습니다.
주간 트래커(weekly tracker)는 스냅샷이 아니라 변화를 측정합니다. 개인 관심 목록(watchlist)에 있는 리포지토리의 경우, 매 실행 시 해당 날짜의 통계를 저장하고 이를 마지막으로 저장된 스냅샷과 비교합니다. 따라서 주간 보고서는 현재 상태를 단순히 다시 진술하는 것이 아니라, 진정한 변화량(획득한 별 개수, 모멘텀 방향)을 보여줍니다. 설명 방식은 각 도구를 처음 접하는 사람을 위해 의도적으로 작성되었습니다. 관심 목록은 일상적으로 사용하는 도구 이외의 것들까지 쉽게 확장될 수 있기 때문입니다.
이 관심 목록(watchlist)은 매일 제공되는 Discovery 요약(digest)처럼 자동으로 채워지는 방식이 아닙니다. 이는 제가 무언가 영구적인 자격을 얻었다고 판단할 때마다 채팅 명령어를 통해 수동으로 추가하는, 작고 의도적으로 큐레이션된 세트입니다. 현재는 세 개의 저장소(repo)가 포함되어 있습니다: langchain-ai/langgraph (제가 Teachsim에서 실행하는 멀티 에이전트 아키텍처(multi-agent architecture)의 기반이 되는 오케스트레이션 프레임워크(orchestration framework)이므로, 여기서 발생하는 중대한 변경 사항(breaking change)은 이미 출시된 기능들에 직접적인 하위 영향을 미칩니다), pydantic/pydantic-ai (제가 이미 검증 라이브러리(validation library)를 신뢰하고 있는 팀에서 만든 새로운 에이전트 프레임워크로, 에이전트 분야에서도 동일한 신뢰를 얻을 수 있을지 지켜볼 가치가 있습니다), 그리고 sst/opencode (Claude의 사용량 제한(rate limits)에 걸리거나 차단되었을 때 사용하기 시작한 코딩 에이전트이므로, 이 도구의 발전 속도는 해당 카테고리의 도구들이 어디로 향하고 있는지에 대한 신호가 됩니다). 이 중 그 어느 것도 파이프라인(pipeline)에 의해 발견된 것이 아닙니다. 이것들은 제가 이미 중요하다고 알고 있었으며, 수동으로 확인하는 대신 자동으로 추적되기를 원했던 것들입니다. 주간 보고서는 각 항목이 조용한 한 주를 보냈는지 아니면 의미 있는 한 주를 보냈는지를 평이한 언어로 알려줍니다.
뉴스가 실제로 판단되는 방식
"이것을 읽을 가치가 있는가"라는 질문은 단 한 번의 판단처럼 들리지만, 그렇게 처리하는 것이 바로 일관성 없는 브리핑을 만드는 원인이 됩니다. 즉, 어떤 날은 알차지만 다음 날은 알맹이 없는 내용으로 채워지게 됩니다. 대신 뉴스 분석기(news analyzer)는 상당히 주관적인 루브릭(rubric, 평가 기준)을 바탕으로 작동하며, 이는 매일 수백 개의 헤드라인에 동일한 방식으로 적용됩니다.
분석기에는 포함하지 말아야 할 것들이 명시적으로 전달됩니다. 연예 뉴스, 스포츠 결과, 재앙 수준이 아닌 기상 정보, 주식 가격, 그리고 새로운 정보가 없는 의견 기사(opinion pieces) 등은 제외됩니다. 이 제외 목록은 그 어떤 긍정적인 지침보다 더 큰 역할을 합니다. "당신의 시간을 쓸 가치가 있는 것"을 정확하게 정의하는 것보다 "당신의 시간을 쓸 가치가 없는 것"을 정확하게 정의하는 것이 훨씬 쉽기 때문입니다.
카테고리 전체를 완전히 건너뛰는 것도 허용됩니다. 만약 특정 섹션에 그날 포함할 만한 가치가 있는 내용이 없다면, 모델은 빈 자리를 채우기 위해 빈약한 이야기를 억지로 끼워 넣는 대신 해당 섹션을 제외하도록 지시받습니다. 그리고 모델은 마지막에 어떤 카테고리를 왜 건너뛰었는지 짧은 한 줄로 보고해야 합니다. 침묵은 단순히 기본 설정으로 이루어지는 것이 아니라, 반드시 정당화되어야 합니다.
유지된 모든 항목은 한 문장이 아닌 두 문장으로 자신의 자리를 증명해야 합니다. 즉, '무슨 일이 일어났는가'와 별도로 '왜 그것이 중요한가'를 작성해야 합니다. 어떤 이야기가 사실이고 주목할 만하더라도, 모델이 '그래서 무엇이 중요한가(so what)'를 깔끔한 한 문장으로 설명해내지 못한다면 삭제될 수 있습니다. 이는 단순히 "이런 일이 있었다" 식의 요약이 아니라, 실제적인 관련성 판단(relevance judgment)을 강제합니다.
한 섹션은 의도적으로 '안전하지 않게' 설계되었습니다. 예상치 못한 무언가를 위해 예약된 슬롯이 있습니다. 일반적인 카테고리 범위를 벗어나, 호기심 많은 사람이 누군가 지정하지 않았다는 이유만으로 흥미를 느낄 법한 종류의 내용입니다. 이러한 강제 장치(forcing function)가 없다면, 스스로 판단하도록 내버려진 LLM(Large Language Model)은 안전하고 명백히 중요한 이야기들만 선택할 뿐, 즐겁거나 기묘한 내용은 전혀 선택하지 않는 경향이 있습니다.
이 작업에서 모델은 낮은 온도(low temperature)로 실행됩니다. 창의적인 변주가 아니라 일관된 판단을 내리도록 조정되었습니다. 저는 매 실행마다 조금씩 다른 해석이 나오는 것이 아니라, 동일한 이야기가 오늘과 다음 주에 똑같이 판단되기를 원합니다.
따라서 이것은 마법 같은 큐레이션이 아닙니다. 이것은 채점 기준(rubric)이며, LLM의 역할은 사람이 직접 하기에는 규모가 너무 커서 감당할 수 없는 방대한 양의 헤드라인에 이 기준을 일관되게 적용하는 것입니다.
현재의 솔직한 상태
이것은 제품이 아니라 단일 사용자, 단일 지역을 위한 도구입니다. 실제 RAM 제약이 있는 의도적으로 저렴한 VPS(Virtual Private Server)에서 실행되는데, 이로 인해 고려했던 몇 가지 접근 방식들이 이미 제외되었습니다 (예를 들어, 자체 호스팅 텍스트 음성 변환(text-to-speech)의 경우, 모델과 코덱의 오버헤드만으로도 항상 켜져 있는 리스너 프로세스의 메모리와 경쟁해야 합니다). 현재 계속해서 반복 개선(iteration)을 진행 중이므로 코드베이스는 아직 오픈 소스가 아닙니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기