← 전체로 돌아가기
스킬 ai

논문 → 게임형 학습 코스 발행 (v2)

논문 아카이브 → Gemini로 게임형 코스 만들고 웹에 올림.

aillmpythondevopsnginx

논문 → 게임 코스 발행 (v2)

Gemini 2.5-pro로 논문 → 5~7개 레슨 코스 변환. 각 레슨: 이모지, 짧은 제목, 핵심 비유, matplotlib 시각화, 4지선다 퀴즈 포함. 중학생 수준 한국어, 일상 비유. HuggingFace 모델 있으면 inference 데모도 추가됨.

아키텍처 (v2: 두 머신 씀)

브라우저 ─HTTPS→ son-wtr (웹/cert/dns)
                  │
                  ├ /thesis/        → 정적 index.html
                  └ /thesis/<id>/   → proxy_pass son-pro6000:<port>
                                        ↓
                                     son-pro6000 (GPU)
                                     systemd: papergen-[id].service
                                     marimo run --base-url /thesis/[id]
                                     port 27000~27999

왜 두 머신 쓰냐면: GPU는 son-pro6000에만 있고, 외부 IP/DNS는 son-wtr에 묶여 있어서. son-pro6000의 마리모 프로세스가 GPU에서 데모 코드 실행, son-wtr의 nginx가 HTTPS 종료 + 리버스 프록시.

위치

son-pro6000 (GPU 서버, papergen 메인) - 프로젝트: /home/son/prj/thesis - venv: .venv (Python 3.10.19, torch+CUDA+transformers) - 출력 노트북: output/paper_[id].py - systemd unit: /etc/systemd/system/papergen-[id].service

son-wtr (웹 서버, 프록시 + cert) - nginx site: /etc/nginx/sites-enabled/learn.ericfromkorea.com - 논문별 location: /etc/nginx/snippets/learn-papers/[id].conf (SSH로 원격 작성) - 인덱스 HTML: /var/www/learn.ericfromkorea.com/thesis/index.html

SSH 백채널: son-pro6000son-wtr로 키 인증 SSH 가능. publish.py가 SSH로 nginx snippet 쓰고 reload, index 갱신 다 함. UFW (son-pro6000): 포트 27000~27999 LAN (192.168.1.0/24)만 허용. 외부 차단.

논문 추가 (반복 작업)

ssh 192.168.1.116
cd ~/prj/thesis
.venv/bin/python main.py [arXiv ID] --publish

자동으로 아래 작업 진행: 1. arxiv 메타 + PDF 다운로드 2. PyMuPDF 텍스트 추출 3. Gemini 2.5-pro 호출 (Course schema, 5~7 레슨) 4. marimo .py 노트북 생성 (각 레슨에 viz + quiz 셀) 5. systemd unit 작성 (로컬), nginx snippet + index (son-wtr 원격) 바로 https://learn.ericfromkorea.com/thesis/[arXiv ID]/ 접근 가능.

발행 취소

ssh 192.168.1.116
cd ~/prj/thesis
.venv/bin/python -c "from papergen.publish import unpublish; unpublish('[arXiv ID]')"

디버깅

  • 셀 실행 에러: ssh 192.168.1.116 sudo journalctl -u papergen-[id].service -n 100
  • HTTPS 상태: curl -sI https://learn.ericfromkorea.com/thesis/[id]/
  • WebSocket 직접 검증 (셀이 정말 실행되는지):
# son-pro6000에서 .venv/bin/python으로
import asyncio, json
from websockets.client import connect
async def t():
    async with connect("ws://127.0.0.1:27000/thesis/[id]/ws?session_id=x") as ws:
        for _ in range(50):
            print(json.loads(await asyncio.wait_for(ws.recv(), 5))[:200])
asyncio.run(t())
  • 셀 실행 일괄 테스트: marimo export html output/paper_[id].py -o /tmp/x.html → 모든 셀 서버 사이드 실행. stderr에 에러 표시. exit 0 이면 깨끗.

함정 / 배운 점

  • Chromium snap + AppArmor: 헤드리스 스크린샷 안 됨. WS handshake 직접 검증으로 퉁침.
  • LLM이 import 빼먹음: plt 쓰는데 import matplotlib.pyplot as plt 없으면 NameError. 해결: _run 함수에 import torch, numpy as np, matplotlib.pyplot as plt 자동 삽입.
  • LLM이 dropdown 옵션에 따옴표 포함: ["'It'", "'was'"] 같이 보낼 때 있음. sanitize 고려.
  • multi-line 문자열 금지: prompt에 명시. cell-body indenter가 내용 망가뜨림.
  • 포트 범위 27000~27999: state.json이 정함. UFW LAN 허용 룰 한 번만 추가하면 끝.
  • 모든 모듈 150줄 미만 룰. publish.py, prompts.py가 제일 김.

여기서 배울 것

  1. 분산 아키텍처에서 GPU 서버와 웹 서버 역할 분리 중요함.
  2. systemd로 서비스 자동 관리, nginx로 리버스 프록시 설정.
  3. LLM 코드 생성 시 import 누락, 따옴표 문제 같은 함정 미리 대비해야 함.
  4. SSH 백채널로 원격 서버 설정 자동화 가능.
원본 파일 보기 (.claude/skills/tn-thesis-publish/SKILL.md)
---
name: 논문 인터랙티브 학습 코스 발행 (papergen v2)
description: Use when the user asks to turn an ML/DL paper (arXiv URL, arXiv ID, or local PDF) into a Duolingo/Elevate-style game-based interactive learning course at learn.ericfromkorea.com/thesis/<id>/. Wraps fetch -> Gemini course generation (lessons + quizzes + viz) -> marimo notebook -> systemd service on GPU server -> nginx proxy on web server.
version: 2.0.0
---

# 논문 → learn.ericfromkorea.com/thesis/<id>/ 게임형 학습 코스

Gemini 2.5-pro로 논문을 5~7개 레슨으로 구성된 게임형 코스로 변환:
- 각 레슨: 이모지 + 짧은 제목 + 핵심 비유 + matplotlib 시각화 + 4지선다 퀴즈
- 중학생 수준 한국어 + 일상 비유
- HuggingFace 모델 있으면 실제 inference 재현 데모 추가

## 아키텍처 (v2: 두 머신 분산)

```
브라우저 ─HTTPS→ son-wtr (192.168.1.121, web/cert/dns)
                  │
                  ├ /thesis/        → 정적 index.html
                  └ /thesis/<id>/   → proxy_pass 192.168.1.116:<port>
                                        ↓
                                     son-pro6000 (192.168.1.116, GPU)
                                     systemd: papergen-<id>.service
                                     marimo run --base-url /thesis/<id>
                                     port 27000~27999
```

**왜 두 머신**: GPU(RTX Pro 6000)는 son-pro6000에만 있고, 외부 IP/DNS는 son-wtr에 묶여 있어서. son-pro6000의 마리모 프로세스가 데모 코드를 GPU에서 실행, son-wtr 의 nginx가 HTTPS 종료 + 리버스 프록시.

## 위치

**son-pro6000 (GPU 서버, papergen 본진)**:
- 프로젝트: `/home/son/prj/thesis`
- venv: `.venv` (Python 3.10.19, torch+CUDA+transformers)
- 출력 노트북: `output/paper_<id>.py`
- 상태 파일: `state/state.json` (포트 할당, 논문 메타)
- systemd unit: `/etc/systemd/system/papergen-<id>.service`

**son-wtr (웹 서버, 프록시 + cert)**:
- nginx site: `/etc/nginx/sites-enabled/learn.ericfromkorea.com`
- 논문별 location: `/etc/nginx/snippets/learn-papers/<id>.conf` (cross-machine via SSH)
- 인덱스 HTML: `/var/www/learn.ericfromkorea.com/thesis/index.html`
- TLS cert: `/etc/letsencrypt/live/learn.ericfromkorea.com/`

**SSH 백채널**: son-pro6000은 son-wtr로 키 인증 SSH 가능 (`son@192.168.1.121`).
publish.py가 nginx snippet 작성 + reload + index 갱신을 SSH로 수행.

**UFW (son-pro6000)**: 포트 27000~27999 LAN(192.168.1.0/24)만 허용. 외부 차단.

## 논문 추가 (반복)

```bash
ssh 192.168.1.116
cd ~/prj/thesis
.venv/bin/python main.py 2010.11929 --publish
# 자동으로:
# 1. arxiv 메타 + PDF 다운로드
# 2. PyMuPDF 텍스트 추출
# 3. Gemini 2.5-pro 호출 (Course schema, 5~7 레슨)
# 4. marimo .py 노트북 생성 (각 레슨에 viz + quiz 셀)
# 5. systemd unit 작성 (로컬), nginx snippet + index (son-wtr 원격)
# 6. 즉시 https://learn.ericfromkorea.com/thesis/2010.11929/ 에서 접근 가능
```

## 발행 취소

```bash
ssh 192.168.1.116
cd ~/prj/thesis
.venv/bin/python -c "from papergen.publish import unpublish; unpublish('1706.03762')"
```

## 디버깅

- **셀 실행 에러**: `ssh 192.168.1.116 sudo journalctl -u papergen-<id>.service -n 100`
- **HTTPS 상태**: `curl -sI https://learn.ericfromkorea.com/thesis/<id>/`
- **WebSocket 직접 검증** (셀이 정말 실행되는지):
  ```python
  # son-pro6000에서 .venv/bin/python으로
  import asyncio, json
  from websockets.client import connect
  async def t():
      async with connect("ws://127.0.0.1:27000/thesis/<id>/ws?session_id=x") as ws:
          for _ in range(50):
              print(json.loads(await asyncio.wait_for(ws.recv(), 5))[:200])
  asyncio.run(t())
  ```
- **셀 실행 일괄 테스트**: `marimo export html output/paper_<id>.py -o /tmp/x.html`
  → 모든 셀 서버 사이드 실행. stderr에 에러 표시. exit 0 이면 깨끗.

## 알려진 함정 / 학습된 교훈

- **chromium snap + AppArmor**: 헤드리스 스크린샷 안 됨 (WebSocket 차단). 실제 브라우저는
  멀쩡함. WS handshake 직접 검증으로 대체.
- **LLM이 import 빠뜨림**: viz code 본문이 `plt`를 쓰는데 `import matplotlib.pyplot as plt`가
  없으면 셀 실행 시 NameError. 해결: `blocks_viz.py`/`blocks_repro.py`가 `_run` 함수 본문에
  `import torch, numpy as np, matplotlib.pyplot as plt`를 자동 prepend.
- **LLM이 dropdown 옵션에 따옴표 포함**: `["'It'", "'was'"]` 같이 보낼 때가 있음. 프롬프트에
  명시했지만 100% 막을 수는 없음. 의심되면 옵션 list를 sanitize 추가 가능.
- **multi-line 문자열 금지**: prompt에 명시. triple-quoted 문자열을 쓰면 cell-body indenter가
  문자열 내부에 공백 끼워 마크다운/문자열 내용을 망가뜨림.
- **포트 범위 27000~27999**: state.json이 정한다. UFW에 LAN 허용 룰 한 번만 추가하면 끝.
- **150줄 룰**: 모든 모듈 150줄 미만. 가장 긴 게 publish.py (~110줄), prompts.py (~110줄).

## 관련 스킬

- `tn-namecheap-dns-provision` — 새 서브도메인 추가 + DNS 전파 대기 (cert 발급 전).
- `tn-namecheap-dns` — 단순 A/TXT 레코드 조작.
- `tn-https-lets-encrypt-nginx` — cert 발급 트러블슈팅.
- `tn-nginx-reverse-proxy-config` — nginx 설정 일반.