언어 학습을 위한 AI 기반 뉴스 기사 자동 생성 및 CEFR 레벨링 시스템
Google News RSS, Tavily Search, Diffbot, LangGraph를 활용한 완전 자동화 기사 생성 파이프라인입니다. 원본 기사를 수집하여 C1 레벨 영문 기사를 생성한 후, 7개의 CEFR 레벨(A0-C2)로 자동 변환합니다.
월 150개 기사 자동 생성 (일 8개)
- 5개 분야: Sports, Science, Business, Culture, Technology
- 2개 타겟 언어: 한국어, 일본어 (Science/Technology는 공통)
- 7개 CEFR 레벨: A0, A1, A2, B1, B2, C1, C2
카테고리 선택 → Google News RSS 헤드라인 수집 → AI 주제 선정 →
Tavily 기사 검색 → Diffbot 본문 파싱 → C1 기사 생성 →
커버 이미지 생성 → 7개 CEFR 레벨 병렬 변환 → S3 저장 → 백엔드 등록
- AI 주제 선정: Google News RSS에서 30개 헤드라인 수집 후 LLM이 3개 후보 선택
- 스마트 필터링: 가십/정치 뉴스 자동 제외, 중복 방지, 분야별 품질 기준 적용
- 고품질 크롤링: Tavily로 최대 20개 관련 기사 검색 → Diffbot으로 상위 3개 본문 파싱
- 다국어 지원: 한국/일본 타겟 시장 자동 구분 (Culture는 언어 무관)
- CEFR 자동 레벨링: 6개 레벨 병렬 처리 (A0-B2, C2), 원본은 C1 유지
- 품질 관리: Judge AI가 각 레벨 평가 (80점 이상 필수), 피드백 기반 재생성
- AI 커버 이미지: GPT-5-image-mini로 1:1 비율 이미지 자동 생성
- 비용 추적: 실시간 토큰 사용량 및 비용 계산, Discord 알림
- Python 3.12: 메인 언어
- LangGraph 0.2+: 상태 기반 워크플로우 오케스트레이션
- LangChain 0.3.27: LLM 통합 및 프롬프트 관리
- AWS Bedrock:
Llama3-2-90B: C1 기사 생성Llama4-Scout-17B: CEFR 레벨링 (A0-C2)Llama4-Maverick-17B: 기사 길이 조정Amazon Nova Lite: 품질 평가 (Judge AI)
- OpenRouter:
GPT-5-image-mini: 커버 이미지 생성
- Google News RSS: 카테고리별 헤드라인 수집
- Tavily Search API: 관련 기사 검색 (최대 20개)
- Diffbot API: 웹 페이지 본문 파싱 및 요약
- pysbd 0.3.4: 문장 분할
- Lingua: 언어 감지 (한국어, 영어, 일본어)
- AWS S3: 기사 메타데이터 및 이미지 저장
- MongoDB Atlas: 중복 방지를 위한 기사 이력 관리
- GitHub Actions: 매일 자동 실행 (UTC 23:00)
- Langfuse 3.5+: 프롬프트 버전 관리
- LangSmith: 워크플로우 추적
- Discord Webhooks: 성공/실패/비용 알림
pip install -r requirement.txt.env 파일 생성:
# AWS Credentials
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
OUTPUT_S3_BUCKET=your-bucket-name
# Content Collection APIs
TAVILY_API_KEY=your-tavily-key
DIFFBOT_API_TOKEN=your-diffbot-token
OPENROUTER_API_KEY=your-openrouter-key
# Backend Integration
SWAPPER_API_KEY=your-backend-api-key
READ_PAST_ARTICLE_URL=https://your-backend/api/articles/recent
ADD_NEW_ARTICLE_URL=https://your-backend/api/articles
ALARM_API_URL=https://your-backend/api/alarm
# Langfuse (Prompt Management)
LANGFUSE_PUBLIC_KEY=your-public-key
LANGFUSE_SECRET_KEY=your-secret-key
LANGFUSE_HOST=https://cloud.langfuse.com
USE_LANGFUSE=true
# Monitoring (Optional)
LANGSMITH_API_KEY=your-langsmith-key
DISCORD_WEBHOOK_URL=your-discord-webhook
# Execution Config
ARTICLE_CATEGORY=Sports # Sports, Science, Business, Culture, Technology
ARTICLE_LANGUAGE=ko # ko, ja, or nullexport ARTICLE_CATEGORY=Sports
export ARTICLE_LANGUAGE=ko
export OUTPUT_S3_BUCKET=your-bucket
python novel_transformer_project_article/main.py매일 UTC 23:00 (KST 익일 08:00)에 8개 기사 자동 생성:
- Business (ko, ja)
- Sports (ko, ja)
- Culture (ko, ja) - 언어 코드 null로 처리
- Technology (null) - 공통 컨텐츠
- Science (null) - 공통 컨텐츠
Job 1: generate-articles
- Matrix 전략으로 8개 기사 병렬 생성 (max-parallel: 1, fail-fast: false)
- 각 작업은 독립적으로 실행되며 실패해도 다른 작업에 영향 없음
- 결과는
article_result.json아티팩트로 저장 (1일 보관)
Job 2: notify-completion
- 모든 기사 생성 완료 후 실행 (runs always)
- 8개 아티팩트를 수집하여 백엔드에 일괄 알림
- 동일 articleId는 targetLanguageCodes 배열로 그룹화
Repository Settings → Secrets and variables → Actions:
# AWS (3개)
AWS_REGION
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
OUTPUT_S3_BUCKET
# APIs (5개)
TAVILY_API_KEY
DIFFBOT_API_TOKEN
OPENROUTER_API_KEY
LANGSMITH_API_KEY
DISCORD_WEBHOOK_URL
# Backend (3개)
SWAPPER_API_KEY
READ_PAST_ARTICLE_URL
ADD_NEW_ARTICLE_URL
ALARM_API_URL
# Langfuse (3개)
LANGFUSE_PUBLIC_KEY
LANGFUSE_SECRET_KEY
LANGFUSE_HOSTling-level-article-automation/
├── .github/
│ └── workflows/
│ └── article-automation.yml # GitHub Actions 워크플로우
├── docs/
│ ├── automation.md # 개발 과정 문서
│ ├── automationFinish.md # 완료 문서
│ └── relevant_news.md # 뉴스 관련 문서
├── novel_transformer_project_article/ # 메인 애플리케이션
│ ├── main.py # 진입점 및 워크플로우 오케스트레이션
│ ├── state.py # LangGraph 상태 정의
│ ├── nodes/ # 워크플로우 노드 구현
│ │ ├── fetch_topic_and_articles_node.py # 주제 선정 & 기사 수집
│ │ ├── generate_article_node.py # C1 기사 생성
│ │ ├── generate_cover_image_node.py # 커버 이미지 생성
│ │ ├── split_nodes.py # 텍스트 청킹
│ │ ├── parallel_cefr_processing_node.py # 병렬 CEFR 변환
│ │ ├── generate_level_specific_text_node.py # 개별 레벨 텍스트 생성
│ │ ├── navigation_nodes.py # 워크플로우 네비게이션
│ │ ├── conditional_nodes.py # 조건부 라우팅
│ │ └── final_summary_log_node.py # 최종 메트릭 로깅
│ ├── prompts/ # LLM 프롬프트 템플릿
│ │ ├── generate_article_prompt.py # 기사 생성 프롬프트
│ │ ├── select_topic_with_llm_prompt.py # 주제 선정 프롬프트
│ │ ├── generate_cover_image_prompt.py # 이미지 생성 프롬프트
│ │ ├── outputFixingParser_prompt.py # JSON 파싱 수정 프롬프트
│ │ ├── leveling/ # CEFR 레벨별 프롬프트
│ │ │ ├── a0_prompt.py ~ c2_prompt.py # 7개 레벨 프롬프트
│ │ └── judging/ # 품질 평가 프롬프트
│ │ └── a0_judge_prompt.py ~ c2_judge_prompt.py # 7개 평가 프롬프트
│ └── utils/ # 유틸리티 모듈
│ ├── workflow_helpers.py # Bedrock 설정, 토큰 추적, 알림
│ ├── langfuse_client.py # Langfuse 프롬프트 버전 관리
│ ├── judge_ai.py # 품질 평가 시스템
│ ├── logging_config.py # 로깅 설정
│ ├── data_models.py # 데이터 구조 정의
│ ├── json_handler.py # JSON 처리 유틸
│ ├── pysbd_handler.py # 문장 분할
│ ├── exceptions.py # 커스텀 예외
│ └── handlers/
│ └── article_handler.py # 기사 출력 & S3 저장
├── script/ # 유틸리티 스크립트
│ ├── upload_select_topic_prompt.py # Langfuse 프롬프트 업로드
│ └── langfuse_example.py # Langfuse 사용 예제
├── test/ # 테스트 파일
├── .env # 환경 변수 (로컬)
├── .gitignore # Git 무시 규칙
├── langgraph.json # LangGraph 설정
├── requirements.txt # Python 의존성
└── README.md # 프로젝트 문서
START
↓
1. fetch_topic_and_articles
- Google News RSS에서 30개 헤드라인 수집
- 백엔드에서 최근 기사 제목 가져와 중복 체크
- LLM이 3개 후보 주제 선정 (가십/정치 제외)
- 각 후보마다:
* Diffbot으로 주제 URL 요약 (200자)
* Tavily로 관련 기사 최대 20개 검색 (최소 점수 0.3)
* 상위 3개 기사를 Diffbot으로 본문 파싱
- 최소 2개 기사 확보될 때까지 후보 순회
↓
2. generate_article
- 수집된 기사를 기반으로 C1 레벨 영문 기사 생성
- 제목: 60자 목표, 80자 최대 (A1 레벨)
- 본문: 1000-1500자 목표, 900-1600자 허용 (C1 레벨)
- Lingua로 영어 검증, 길이 조건 미달 시 최대 3회 재조정
- target_language_code는 Culture 제외하고 null 처리
↓
3. generate_cover_image
- GPT-5-image-mini로 1:1 비율 커버 이미지 생성
- 예제 이미지 스타일 참조
- base64 형식으로 상태에 저장
↓
4. split_chapter_into_chunks
- 커버 이미지 태그 추가: [illustration: {json_data}]
- 특수 태그로 청킹: [illustration:...], <Preserve>...</Preserve>
- 일러스트레이션과 보존 구간은 별도 청크로 분리
↓
5. create_all_cefr_versions_parallel
- C1 원본: pysbd로 문장 분할
- 6개 레벨(A0-B2, C2): ThreadPoolExecutor로 병렬 생성
- 각 레벨마다:
* 레벨별 프롬프트로 텍스트 생성
* Judge AI가 품질 평가 (0-100점)
* 80점 미만 시 피드백 기반으로 최대 5회 재생성
* 생성된 텍스트를 문장 단위로 분할
- 일러스트레이션/보존 청크는 모든 레벨에 복사
↓
6. move_to_next_chunk (조건부)
- 현재 챕터에 더 많은 청크가 있으면 5번으로 복귀
- 없으면 7번으로 진행
↓
7. move_to_next_chapter (조건부)
- 더 많은 챕터가 있으면 4번으로 복귀
- 없으면 8번으로 진행
↓
8. log_final_summary
- 모델별 토큰 사용량 로깅
- 총 비용 계산
- 비용이 $0.04 초과 시 Discord 알림
↓
END
Judge AI 평가 기준 (각 25점):
- Vocabulary Usage: CEFR 레벨에 맞는 어휘 사용
- Grammar & Sentence Structure: 적절한 문법 및 문장 구조
- Content Preservation: 원본 내용 유지
- Context Continuity: 문맥 연속성
재생성 로직:
- 1차 생성 → Judge AI 평가 → 80점 미만 시 피드백 제공 → 재생성
- 최대 5회 시도, 모두 실패 시 가장 높은 점수의 결과 사용
- 각 레벨별 동적 temperature (A0/A1: 0.3, A2-B1: 0.6, B2-C2: 0.8)
- 수동 작업: 월 100시간 (기사당 40분)
- 자동화 후: 월 25분 (모니터링만)
- 효율성: 99.6% 시간 절감
| 항목 | 모델/서비스 | 단가 | 월 사용량 | 월 비용 |
|---|---|---|---|---|
| 주제 선정 | Llama3-2-90B | $0.72/M 토큰 | ~700K | $0.50 |
| 기사 생성 | Llama3-2-90B | $0.72/M 토큰 | ~2.8M | $2.00 |
| 길이 조정 | Llama4-Maverick-17B | $0.66/M 토큰 | ~500K | $0.33 |
| CEFR 레벨링 | Llama4-Scout-17B | $0.17/M 토큰 | ~15M | $2.55 |
| 품질 평가 | Nova Lite | $0.06/M 토큰 | ~10M | $0.60 |
| 커버 이미지 | GPT-5-image-mini | $0.013/이미지 | 150 | $1.95 |
| 기사 검색 | Tavily Search | 월 1,000 크레딧 | 900 크레딧 | $0 (무료) |
| 본문 파싱 | Diffbot | 월 10,000 요청 | 900 요청 | $0 (무료) |
총 월 비용: 약 $7.93 (기사당 $0.053)
- GitHub Actions: 무료 플랜 (월 2,000분 제공, 사용량 ~240분)
- AWS S3: 월 $0.50 미만 (메타데이터 150개 + 이미지 150개)
- MongoDB Atlas: 무료 Tier (기사 이력 관리)
월 $8.50 미만 (인건비 제외 시)
vs 수동 작업 시 100시간 × 시급 = 대폭 절감
- 모든 프롬프트를 Langfuse에서 중앙 관리
- 코드 배포 없이 프롬프트 실시간 업데이트 가능
- 환경별 프롬프트 분리 (production/staging/development)
- A/B 테스트 지원
- 로컬 fallback: Langfuse 장애 시 로컬 파일 사용
- 모델별 토큰 사용량 자동 계산
- 실시간 비용 집계 (입력/출력 토큰 구분)
- 비용 초과 시 Discord 알림 ($0.04 임계값)
- 최종 요약 로그에 상세 비용 분석 포함
- 백엔드 API를 통해 최근 30일 기사 제목 조회
- LLM 주제 선정 시 중복 제목 자동 제외
- MongoDB에 생성 이력 저장
- API 호출 실패 시 자동 재시도 (최대 3-5회)
- 품질 미달 시 피드백 기반 재생성
- 최종 실패 시 Discord 알림 + GitHub Actions 실패 표시
- 부분 실패 허용 (fail-fast: false)
s3://bucket/article/{articleId}/
├── metadata.json # 기사 메타데이터 (7개 레벨 모두 포함)
└── images/
└── cover.jpg # 커버 이미지
metadata.json 구조:
{
"id": "article-20250128-...",
"content_type": "article",
"title": "Article Title",
"author": "auto-generator",
"tags": ["tag1", "tag2"],
"targetLanguageCode": ["KO", "JA"] or null,
"targetCategory": "Sports",
"originUrl": "https://news.google.com/...",
"coverImageUrl": "s3://bucket/article/{id}/images/cover.jpg",
"leveledResults": [
{
"level": "A0",
"chapters": [
{
"chapterNum": 1,
"chapterTitle": "Chapter Title",
"chunks": [
{
"chunkNum": 1,
"chapterNum": 1,
"sentenceTexts": ["Sentence 1.", "Sentence 2."]
}
]
}
]
},
// ... A1, A2, B1, B2, C1, C2
]
}- Tavily 크레딧 제한: 월 1,000 크레딧 (기사당 6크레딧 → 최대 166개)
- Diffbot 요청 제한: 월 10,000 요청 (현재 사용량 900)
- GitHub Actions 실행 시간: 작업당 평균 12-15분 (최대 30분)
- LLM 안정성: 드물게 JSON 파싱 실패 시 OutputFixingParser로 복구
- 언어 감지: 일부 다국어 혼합 텍스트에서 오탐 가능
- 다른 언어 지원 확대 (중국어, 스페인어 등)
- 웹 UI 대시보드 (기사 현황, 비용 추적, 품질 모니터링)
- 커스텀 주제 입력 지원 (RSS 외 수동 입력)
- 이미지 다양성 향상 (DALL-E 3, Midjourney 통합)
- 음성 버전 생성 (TTS 통합)
MIT
이슈 및 PR은 GitHub Repository에서 환영합니다.