lesson-loop
๐ Lesson Loop
๊ฐ๋ ์ด๋ฆ ํ๋๋ฅผ ๋ฃ์ผ๋ฉด, ์ถ์ฒ ์กฐ์ฌ๋ถํฐ ๊ฒ์๊น์ง ์ค์ค๋ก ๋๋ ค์ ๋ฐ๋ก ์์ ์ ์ธ ์ ์๋ ๋ ์จ ํฉ์ ํ์ผ๋ก ๋ง๋ค์ด ์ฃผ๋ ์์ด์ ํฑ ์ํฌํ๋ก ์๋น์ค.
ํ์ต ์ฝํ ์ธ ์ ์์๋ ๊ฐ๋ ํ๋๋ง๋ค ๊ฐ์ 6๋จ๊ณ๋ฅผ ์์ผ๋ก ๋ฐ๋ณตํ๋ค โ ์๋ฌธ ์ฐพ๊ธฐ โ ํ์ต๋ชฉํ ์ชผ๊ฐ๊ธฐ โ ๋ฃจ๋ธ๋ฆญ ๋ง๋ค๊ธฐ โ ๋ฌธํญ ์ฐ๊ธฐ โ ๊ทผ๊ฑฐ ๋์กฐ โ ํ์ผ ์ ๋ฆฌ. ๊ฐ๋ ๋น 2~3์๊ฐ์ด ๋ ๋ค. Lesson Loop๋ ์ด ์ ์ฐจ ์ ์ฒด๋ฅผ ๋์ ๋๋ฆฌ๊ณ , ์ฌ๋์ ๋ ์ง์ ์์๋ง ํ์ธํ๋ค.
์๋ํํ๋ ๊ฒ์ "๋ฌธํญ ์์ฑ"์ด ์๋๋ผ "์ถ์ฒ ์กฐ์ฌ โ ์์ฑ โ ๊ทผ๊ฑฐ ๋์กฐ โ ์ฌ๋ ์น์ธ"์ด๋ผ๋ ์ ์ฐจ๋ค. ๊ทธ๋์ ๋ชจ๋ ๋ฌธํญ์ ์ถ์ฒ ์๋ฌธ์ ์ค์ ๋ฌธ์ฅ์ ๊ทผ๊ฑฐ๋ก ๊ฐ๋๋ค. ๊ทผ๊ฑฐ๋ฅผ ๋ชป ๋๋ฉด ์ฝ๋๊ฐ ์ ์ฅ์ ๊ฑฐ๋ถํ๋ค.
๐ ๊ธฐํ ๋ฌธ์: PRD.md
๐ ์คํ ๋ถ์: evals/results/ANALYSIS.md ยท ์์๋ฃ: RESULTS.md
๐ผ ์คํ ๊ฒฐ๊ณผ ์บก์ฒ: docs/screenshots/ โ ํ๋ฉด 7์ฅ
๐ ๊ฒฐ๊ณผ๋ฌผ ์์: docs/sample_lesson_pack.md โ ์ค์ ๋ก ๋ง๋ค์ด ๋ธ ๋ ์จ ํฉ
๐ง ๋๊ตฌ ์คํค๋ง ์๋ณธ: docs/tools_schema.json
1๋ถ ์์ฝ
๊ฐ๋
"HTTP ์บ์ฑ" โโ
โผ
โ ๊ณํ ๊ธฐํ์๊ฐ ํ์ ์ฃผ์ ์ ์์ด ๊ฒ์์ด๋ฅผ ๋ฝ๋๋ค
โก ์กฐ์ฌ web_search โ fetch_page ๋ก 1์ฐจ ์ถ์ฒ(MDNยทRFCยทWikipedia)๋ฅผ ํ๋ณด
โธ ์ฌ๋ ํ์ธ ์ถ์ฒ๋ฅผ ๋นผ๊ฑฐ๋ ์ง์ ์ถ๊ฐํ๋ค โ ๊ฐ์ฅ ์ผ ๊ต์ ์ง์
โข ์์ฑ ํ์ต๋ชฉํยท๋ฃจ๋ธ๋ฆญยท๋ฌธํญ. ์ธ์ฉ๋ฌธ์ ์๋ฌธ์์ ๊ทธ๋๋ก ๋ณต์ฌ
โฃ ๊ฒ์ ๋ค๋ฅธ ์์ด์ ํธ๊ฐ ์๋ฌธ๊ณผ ๋์กฐ. ๋ฌธ์ ์์ผ๋ฉด ํด๋น ๋ฌธํญ๋ง ์ฌ์์ฑ
โธ ์ฌ๋ ํ์ธ ๋ฐํ ์น์ธ โ ๋๋๋ฆฌ๊ธฐ ์ด๋ ค์ด ์์
์ง์
โค ๋ฐํ lesson_pack.md + lesson_pack.jsonํ๊ฐ ์ธํธ 10๊ฐ ์ค์ธก (gpt-4o-mini): ์๋ฃ์จ 10/10 ยท ๊ทผ๊ฑฐ์จ 100% (190๋ฌธํญ ์ ๋ถ) ยท ์ค์๊ฐ 71~83์ด ยท ํ๊ท $0.021 ยท ํ๊ท 26๋จ๊ณ. ์์ธํ ์์น์ ๊ฐ์ ์ด๋ ฅ์ ANALYSIS.md.
๋น ๋ฅด๊ฒ ์คํํ๊ธฐ
git clone https://github.com/C-Lake864/lesson-loop.git && cd lesson-loop
python -m venv .venv && .venv/Scripts/activate # macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # .env๋ฅผ ์ด์ด OPENAI_API_KEY๋ฅผ ์ฑ์ด๋ค
uvicorn app.main:app --reload --port 8000๋ธ๋ผ์ฐ์ ์์ http://localhost:8000 ์ ์ฐ๋ค.
์น ์์ด ํ ํ ๋๋ ค ๋ณด๊ธฐ (ํธ๋ ์ด์ค๊ฐ ํฐ๋ฏธ๋์ ๊ทธ๋๋ก ํ๋ฅธ๋ค):
python -m app.cli "HTTP ์บ์ฑ" --questions 5 --autoํ๊ฒฝ๋ณ์
๋ณ์ | ํ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
| โ | โ | ์๋ฒ์์๋ง ์ฐ์ธ๋ค. ํ๋ก ํธ์๋๋ก ์ ๋ ๋ด๋ ค๊ฐ์ง ์๋๋ค |
|
| ๊ธฐ๋ณธ ๋ชจ๋ธ | |
| (๋์ผ) | ๊ฒ์์๋ง ๋ค๋ฅธ ๋ชจ๋ธ๋ก ์ฌ๋ฆฌ๊ณ ์ถ์ ๋ | |
|
| run๋น LLM ํธ์ถ ์ํ | |
|
| run๋น ๋น์ฉ ์ํ (๋์ผ๋ฉด ์๋ ์ค๋จ) | |
|
| run๋น ์๊ฐ ์ํ | |
|
| ๊ฒ์ โ ์ฌ์์ฑ ์ต๋ ํ์ | |
| โ | ์์ผ๋ฉด ๊ฒ์ 1์์๋ก ์ฌ์ฉ. ์์ด๋ ๋์ํ๋ค | |
| โ | ์ค์ ํ๋ฉด | |
|
|
| |
|
| IP๋น ์๊ฐ๋น ์คํ ์ |
์ ์ฒด ๋ชฉ๋ก์ .env.example ์ฐธ๊ณ . .env๋ ์ ๋ ์ปค๋ฐํ์ง ์๋๋ค (.gitignore์ ์์).
ํ๋ฉด
ํ๋ฉด | ๊ฒฝ๋ก | ๋ด๋ ๊ฒ |
์ ์คํ |
| ๊ฐ๋ ์ ๋ ฅ, ์์ฐ ์ฌ๋ผ์ด๋, ์ค์ ์ ํ, ํ, ์ฐ๊ฒฐ๋ ๋๊ตฌ 8์ข ์ ์คํค๋ง |
์คํ ์์ธ |
| ์ค์๊ฐ ํธ๋ ์ด์ค(SSE) ยท ์ถ์ฒ ยท ๊ฒฐ๊ณผ๋ฌผ ๋ผ์ด๋ธ ๋ทฐ ยท ์น์ธ ํจ๋ ยท ๋ค์ด๋ก๋ |
ํ๊ฐยท๋น์ฉ |
| ์ค์ ๋ณ ๋น๊ตํ ยท ๋จ๊ณ๋ณ ๋ณ๋ชฉ ยท ์คํจ ์ฌ๋ก ํ |
API ๋ฌธ์ |
| FastAPI ์๋ ์์ฑ |
ํธ๋ ์ด์ค์ ๊ฐ ์คํ ์๋ ์ด๋ค ๋๊ตฌ๋ฅผ ์ ๋ถ๋ ๊ณ ๋ฌด์์ ๋ฐ์๋์ง๊ฐ ์ธ์ยท๊ฒฐ๊ณผ JSON์งธ๋ก ๋จ๊ณ , ์์ ์๊ฐยทํ ํฐยท๋น์ฉ์ด ํจ๊ป ์ฐํ๋ค.
์ค์ ํ๋ฉด
|
|
|
|
์ ์ฒด ํธ๋ ์ด์ค(115์คํ
)์ ์ธ์ยท๊ฒฐ๊ณผ JSON๊น์ง ํผ์น ํ๋ฉด, ๋๊ตฌ ์คํค๋ง ์ ๋ฌธ์
docs/screenshots/ ์ ์๋ค. ์ ๋ถ python docs/capture.py๋ก ์ฌํ๋๋ค.
์ฐ๊ฒฐ๋ ๋๊ตฌ 8์ข
๋๊ตฌ | ์ข ๋ฅ | ๋ถ์์ฉ | ๊ถํ | ์น์ธ |
| ์ธ๋ถ API | ์์ | ๊ฒ์์ด๋ง ์ ์ก | |
| ์ธ๋ถ HTTP | ์์ | ๋๋ฉ์ธ allowlist๋ง, ์ฌ์ค IP ์ฐจ๋จ, 15์ดยท200KB ์ํ, ์ฃผ์ ๊ด๋ จ์ฑ ๊ฒ์ฌ | |
| DB ์ฝ๊ธฐ | ์์ | ํ์ฌ run์ ์ถ์ฒ๋ง | |
| DB ์ฐ๊ธฐ | ์์ | ํ์ฌ run์๋ง insert | |
| DB ์ฐ๊ธฐ | ์์ | ํ์ฌ run์๋ง insert | |
| DB ์ฝ๊ธฐ | ์์ | ํ์ฌ run๋ง | |
| DB ์ฐ๊ธฐ | ์์ | ํ์ฌ run์๋ง insert | |
| ํ์ผ ์์ฑ | ๋๋๋ฆฌ๊ธฐ ์ด๋ ค์ |
| โ ํ์ |
๊ฐ ๋๊ตฌ์ ์
๋ ฅยท์ถ๋ ฅ ์คํค๋ง๋ ์คํ ํ๋ฉด ์ค๋ฅธ์ชฝ "์ฐ๊ฒฐ๋ ๋๊ตฌ"์์ ํผ์ณ ๋ณผ ์ ์๊ณ , GET /api/meta๋ก๋ ๋ฐ์ ์ ์๋ค.
์ค๊ณ์์ ์งํจ ๊ฒ
๋ถ์์ฉ์ ์ ๋ถ ๋๊ตฌ๋ฅผ ํต๊ณผํ๋ค. ์์ด์ ํธ๋ DB์ ์ง์ ์ฐ์ง ์๊ณ ํ์ผ๋ ์ง์ ๋ง๋ค์ง ์๋๋ค. ๊ทธ๋์ ๋ชจ๋ ๋ถ์์ฉ์ด 100% ๋ก๊ทธ์ ๋จ๋๋ค.
์น์ธ์ ํ๋กฌํํธ๊ฐ ์๋๋ผ ์ฝ๋๊ฐ ํ์ธํ๋ค. export_pack์ DB์ ์น์ธ ํ ํฐ์ด ์๋์ง ํ์ธํ๊ณ , ์์ผ๋ฉด ๋๊ตฌ ๋ ์ด์ด์์ ๊ฑฐ๋ถํ๋ค. ํ๋กฌํํธ๋ฅผ ์๋ฌด๋ฆฌ ์ค๋ํด๋ ์ฐํํ ์ ์๋ค.
์คํจ๋ ์์ธ๋ก ํฐ๋จ๋ฆฌ์ง ์๋๋ค. ๋ชจ๋ ๋๊ตฌ ์ค๋ฅ๋ {"ok": false, "error": {"code", "message", "hint"}}๋ก ๋ชจ๋ธ์๊ฒ ๋์๊ฐ๋ค. ์์ด์ ํธ๊ฐ ์ค์ค๋ก ํ๋ณตํ ๊ธฐํ๋ฅผ ์ฃผ๊ธฐ ์ํด์๋ค. ํ๋ณต ์๋๊น์ง ์ ๋ถ ํธ๋ ์ด์ค์ ๋จ๋๋ค.
์คํจ ์ ํ | ์ฒ๋ฆฌ |
ํ์์์ยท5xx | ์ฌ์๋ 2ํ (0.5s โ 2s) |
๊ฒ์ ๋ฐฑ์๋ ์คํจ | 4๋จ ํด๋ฐฑ: Tavily โ ๋ฌธ์ ์ฌ์ดํธ ์์ฒด ๊ฒ์(MDNยทWikipedia) โ DuckDuckGo โ ๋ด์ฅ ์๋ ์ฝํผ์ค |
ํ์ด์ง๋ฅผ ๋ชป ๊ฐ์ ธ์ด | ์๋ ์ฝํผ์ค์ ๊ฐ์ URL์ด ์์ผ๋ฉด ๊ทธ๊ฒ์ผ๋ก ๋์ฒด |
allowlist ๋ฐ / ์ฃผ์ ๋ฌด๊ด | ์ฌ์๋ ์์ด ๊ตฌ์กฐํ ์ค๋ฅ ๋ฐํ โ ์์ด์ ํธ๊ฐ ๋ค๋ฅธ URL ์ ํ |
์คํค๋งยท๊ทผ๊ฑฐ ๊ฒ์ฆ ์คํจ | ๋ฌด์์ด ์ ํ๋ ธ๋์ง + ์๋ฌธ์์ ๊ฐ์ฅ ๊ฐ๊น์ด ๊ตฌ๊ฐ์ ํจ๊ป ๋ฐํ |
๊ฐ์ ๋๊ตฌยท๊ฐ์ ์ธ์ 3ํ | ๋ฃจํ ๊ฐ์ง โ ์ฐจ๋จ |
๋ฌดํ ๋ฃจํ๋ ์ธ ๊ฒน์ผ๋ก ๋ง๋๋ค. โ ์์ฐ(๋จ๊ณ 50 / ๋น์ฉ $0.50 / ์๊ฐ 420์ด / ๋๊ตฌ ํธ์ถ 80ํ) โก ๊ฐ์ ํธ์ถ ๋ฐ๋ณต ๊ฐ์ง โข ์ ์ฒด ๊ฐ์ง โ ๋๊ตฌ๋ฅผ ๊ณ์ ๋ถ๋ฅด๋ฉด์ ๋ง๋ฌด๋ฆฌ ๋๊ตฌ๋ฅผ ์ ๋ถ๋ฅด๋ฉด "์ง๊ธ ๊ฐ์ง ์ ๋ณด๋ก ๋ง๋ฌด๋ฆฌํ๋ผ"๋ ์ง์๋ฅผ ์ฃผ์ ํ๋ค.
์ค๊ฐ์ ๋๊ฒจ๋ ์ด์ด์ง๋ค
์์ด์ ํธ์ ๋ํ ํ์คํ ๋ฆฌ๊ฐ ๋งค ํ์ฐจ DB์ ํต์งธ๋ก ์ ์ฅ๋๋ค. ๊ทธ๋์:
์น์ธ ๋๊ธฐ ์ค ๋ธ๋ผ์ฐ์ ๋ฅผ ๋ซ์๋ ๋์ค์ ๋งํฌ๋ก ๋์์ ์ด์ด์ ์น์ธํ๋ฉด ๊ทธ ์ง์ ๋ถํฐ ์งํ๋๋ค
์๋ฒ๋ฅผ ์ฌ์์ํด๋
running์ํ๋ก ์ผ์ด๋ถ์ run์ ์์ ์ ์๋์ผ๋ก ์ด์ด ๋๋ฆฐ๋ค (recover_interrupted)์คํจํ run๋ ํ๋ฉด์ "์ด์ด์ ์ฌ๊ฐ" ๋ฒํผ์ผ๋ก ์ ์ฅ๋ ๋จ๊ณ๋ถํฐ ๋ค์ ๋ฏผ๋ค
# ์ง์ ํ์ธํด ๋ณด๊ธฐ: ์คํ ์ค์ ์๋ฒ๋ฅผ ์ฃฝ์๋ค ์ด๋ฆฐ๋ค
uvicorn app.main:app --port 8000 # Ctrl+C ๋ก ์ค๋จ ํ ๋ค์ ์คํ
# โ ๋ก๊ทธ์ "[lessonloop] ์ค๋จ๋ run N๊ฐ๋ฅผ ์ฌ๊ฐํ๋ค."์ฌ๋ ๊ฐ์ ์ง์
์ง์ | ์์น | ํ ์ ์๋ ๊ฒ |
HITL #1 | ์กฐ์ฌ ์๋ฃ โ ์์ฑ ์์ ์ | ์ถ์ฒ ๊ฐ๋ณ ์ ์ธ ยท URL ์ง์ ์ถ๊ฐ ยท ์์ฑ์์๊ฒ ์์ฒญ ๋จ๊ธฐ๊ธฐ ยท ๊ฑฐ๋ถ ํ ์ฌ์กฐ์ฌ |
HITL #2 | ๋ฐํ ์ง์ | ๊ฒฐ๊ณผ๋ฌผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ์ธ ยท ์น์ธ ยท ๋ฐํ ์ทจ์ |
์์ | ์คํ ์ค ์ธ์ ๋ | ์ค๋จ(์ฐ์ถ๋ฌผ ๋ณด์กด) ยท ์คํจ ํ ์ฌ๊ฐ ยท ์ด์ ์ด์ ๋ฒ์ ์ผ๋ก ๋กค๋ฐฑ |
auto_approve๋ฅผ ์ผ๋ฉด ๋ ์ง์ ์ ์๋ ํต๊ณผํ๋ค. ํ๊ฐ์ฉ์ด๋ฉฐ, ์ผ๋ฉด ํ๋ฉด์ ๊ฒฝ๊ณ ๊ฐ ๋ฌ๋ค.
"์๋ฃํ๋ค"๋ ๋ง๊ณผ ๊ฒฐ๊ณผ๋ฌผ์ ๋ค๋ฅด๋ค. ๋ฐํ ์๋ฃ ๋ฐฐ๋๋ ํ์ผ์ด ๋์คํฌ์ ์ค์ ๋ก ์ฐ์๊ณ ํฌ๊ธฐ๊ฐ 0์ด ์๋์ ๋์ฝ์ด ํ์ธํ ๋ค์๋ง ๋จ๊ณ , ํ์ผ๋ช ยท๋ฐ์ดํธ ์ยท๋ฌธํญ ์ยท๊ทผ๊ฑฐ์จ์ ํจ๊ป ์ฐ๋๋ค.
์คํ & ํ๊ฐ
# ์ค์ 4์ข
ร ์๋๋ฆฌ์ค 4์ข
์ ๋๋ฆฌ๊ณ ํ๋ก ๋จ๊ธด๋ค
python -m evals.run --configs baseline,no_review,tools_terse,single_agent \
--only http-caching,css-box-model,hash-table,tx-isolation \
--workers 3ํ๊ฐ ์ธํธ: evals/dataset.jsonl โ ์ค์ ์ปค๋ฆฌํ๋ผ์ ๋ฃ์ ๋ฒํ ๊ฐ๋ 10๊ฐ. ๊ฐ ํญ๋ชฉ์
must_cover(๋ฐ๋์ ๋ค๋ค์ผ ํ ํ์ ๊ฐ๋ )์must_cite_domain(๊ธฐ๋ํ๋ 1์ฐจ ์ถ์ฒ)์ด ๋ถ์ด ์๋ค.๋น๊ต ์ค์ : app/configs.py โ ํ์ดํ๋ผ์ธ ๋ชจ์ ยท ๋๊ตฌ ์ค๋ช ์์ธ๋ ยท ๊ฒ์ ์ ๋ฌด ยท ๊ณํ ์ ๋ฌด ยท ๋ชจ๋ธ
ํด์ยท๊ฐ์ ์ด๋ ฅยท์คํจ ์ฌ๋ก ๋ถ์: evals/results/ANALYSIS.md
๋ฐฐ์น ์์๋ฃ: evals/results/RESULTS.md
๋ฐฐ์น ๊ฐ ๋น๊ต(ํ์ฌ ์ฑ์ ๊ธฐ์ค์ผ๋ก ์ฌ๊ณ์ฐ):
python -m evals.rescore์์ฉ ๊ธฐ์ค ์๋ ์ ๊ฒ:
python -m evals.acceptance --base <์ฃผ์> --live
์งํ๋ ์ ๋ถ ์ฝ๋๊ฐ ๊ณ์ฐํ๋ค. ์์ด์ ํธ๊ฐ "์ ๋๋๋ค"๊ณ ๋งํ ๊ฒ์ ์ธ์ง ์๋๋ค. ํนํ ๊ทผ๊ฑฐ์จ์ ๋ชจ๋ธ์ ์ฃผ์ฅ์ด ์๋๋ผ ์ธ์ฉ๋ฌธ์ ์๋ฌธ๊ณผ ๋ฌธ์ ๋จ์๋ก ๋์กฐํด ๋ค์ ๊ณ์ฐํ ๊ฐ์ด๋ค.
๋ฐฐํฌ
ํ์ฌ ์ํ: ๊ณต๊ฐ URL๋ก ๋ฐฐํฌํ์ง๋ ์์๋ค. ๋์ ๋ก์ปฌ์์ ์ค์ ๋ก ๋๋ฆฐ ํ๋ฉด์ docs/screenshots/์ ์บก์ฒ๋ก ๋จ๊ฒผ๊ณ , ์ฐ์ถ๋ฌผ ์์์ ๋๊ตฌ ์คํค๋ง ์๋ณธ๋ ์ ์ฅ์์ ํจ๊ป ๋์๋ค. ์๋ ์ ์ฐจ๋๋ก ํ๋ฉด ๊ทธ๋๋ก ๋ฐฐํฌ๋๋ค.
ํ ํ๋ก์ธ์ค๊ฐ API์ ์ ์ ํ๋ก ํธ์๋๋ฅผ ํจ๊ป ์๋นํ๋ฏ๋ก ๋ฐฐํฌ ๋จ์๊ฐ ํ๋๋ค.
๋ฐฉ๋ฒ A โ Hugging Face Spaces (์นด๋ ๋ฑ๋ก ๋ถํ์, ๊ฐ์ฅ ์ฌ์)
https://huggingface.co/new-space โ SDK Docker, ํ๋์จ์ด CPU basic (free) ๋ก Space ์์ฑ
Space์ Settings โ Variables and secrets โ New secret ์์
OPENAI_API_KEY์ถ๊ฐ (Secret์ผ๋ก ๋ฃ์ด์ผ ๋ก๊ทธยท์์ค์ ๋ ธ์ถ๋์ง ์๋๋ค)์ด ์ ์ฅ์๋ฅผ Space์ ์ฌ๋ฆฐ๋ค
git remote add space https://huggingface.co/spaces/<๊ณ์ >/lesson-loop git push space mainSpace์
README.md๋ YAML ํ๋ก ํธ๋งคํฐ๊ฐ ํ์ํ๋ค. deploy/huggingface-space-README.md ๋ฅผ Space ์ ์ฅ์ ๋ฃจํธ์README.md๋ก ๋ณต์ฌํด ์ฐ๋ฉด ๋๋ค (app_port: 7860์ด ๋ค์ด ์๋ค)๋น๋๊ฐ ๋๋๋ฉด
https://<๊ณ์ >-lesson-loop.hf.space/api/meta๊ฐ{"llm_ready": true}๋ฅผ ๋ฐํํด์ผ ํ๋ค
๋ฐฉ๋ฒ B โ Render
Render โ New โ Web Service โ ์ด GitHub ์ ์ฅ์ ์ฐ๊ฒฐ (
render.yaml์ด ์๋ ์ธ์๋๋ค)Environment ํญ์์
OPENAI_API_KEY๋ฅผ ์ง์ ์ ๋ ฅํ๋ค (์ ์ฅ์์๋ ๋ค์ด ์์ง ์๋ค)๋ฐฐํฌ ํ
/api/meta๊ฐ{"llm_ready": true}๋ฅผ ๋ฐํํ๋ฉด ์ ์
๋ฐฉ๋ฒ C โ ์ง์ Docker
docker build -t lesson-loop .
docker run -p 7860:7860 -e OPENAI_API_KEY=sk-... lesson-loopPORT ํ๊ฒฝ๋ณ์๋ฅผ ๋ฐ๋ฅด๋ฏ๋ก FlyยทCloud Run ๋ฑ์๋ ๊ทธ๋๋ก ์ฌ๋ผ๊ฐ๋ค.
์๋ ค์ง ํ๊ณ: SQLite์ ์ฐ์ถ๋ฌผ์ด ์ปจํ ์ด๋ ํ์ผ์์คํ ์ ์์ด ์ฌ๋ฐฐํฌ ์ ์ด๊ธฐํ๋๋ค.
LESSONLOOP_DB_PATHยทLESSONLOOP_ARTIFACT_DIR์ ์๊ตฌ ๋ณผ๋ฅจ์ผ๋ก ์ง์ ํ๋ฉด ์ ์ง๋๋ค. HF Spaces์์๋ Persistent Storage(์ ๋ฃ)๋ฅผ ๋ถ์ด๊ฑฐ๋, ๋ฐ๋ชจ ๋ชฉ์ ์ด๋ฉด ๊ทธ๋๋ก ๋์ด๋ ๋ฌด๋ฐฉํ๋ค.
๋ฐฐํฌ ์ ๊ถ์ฅ ์ค์ : ๋จ์ฉ ๋ฐฉ์ง๋ฅผ ์ํด
LESSONLOOP_MAX_COST_USD=0.30,LESSONLOOP_RATE_LIMIT_PER_HOUR=6์ ๋๋ก ๋ฎ์ถฐ ๋๋ ๊ฒ์ด ์ข๋ค.
๋ฐฐํฌ ํ ์์ฉ ๊ธฐ์ค
PRD.md ยง8.2์ A1~A7์ ์คํฌ๋ฆฝํธ๋ก ๋ค์ ํ์ธํ๋ค.
python -m evals.acceptance --base https://<๋ฐฐํฌ์ฃผ์> --live --writeํ์ธ ๊ฒฐ๊ณผ๋ evals/results/RESULTS.md ํ๋จ์ ๊ธฐ๋ก๋๋ค.
ํ์ฅ
MCP ์๋ฒ โ ์ฐ์ถ๋ฌผ์ ๋ค๋ฅธ ์์ด์ ํธ์ ๋๊ตฌ๋ก
์ด ์๋น์ค๋ ๋๊ตฌ๋ฅผ ์ฐ๋ ์ชฝ์ด์, ์๊ธฐ ์ฐ์ถ๋ฌผ์ ๋๊ตฌ๋ก ๋ด๋๋ ์ชฝ์ด๊ธฐ๋ ํ๋ค.
python mcp_server.py.mcp.json์ ๋ฑ๋กํ๋ฉด Claude Code ๊ฐ์ ํด๋ผ์ด์ธํธ๊ฐ ๋ง๋ค์ด์ง ๋ ์จ ํฉ์ ์ง์ ์ธ ์ ์๋ค.
{ "mcpServers": { "lesson-loop": {
"command": "python", "args": ["mcp_server.py"], "cwd": "<์ด ์ ์ฅ์ ๊ฒฝ๋ก>" } } }MCP ๋๊ตฌ | ํ๋ ์ผ |
| ๋ง๋ค์ด์ง ๋ ์จ ํฉ ๋ชฉ๋ก |
| ํฉ ํ๋ ์ ์ฒด (json ๋๋ md) |
| ๋ชจ๋ ํฉ์ ๊ฐ๋ก์ง๋ฌ ๋ฌธํญ ๊ฒ์ โ ๋ฌธ์ ์ํ์ฒ๋ผ |
| ํ์ต๋ชฉํยท๋ฃจ๋ธ๋ฆญ๋ง |
| ์ ๊ฐ๋ ์ ์ ์ ํ์ ๋ฑ๋ก |
๋ฐํยท์ญ์ ๋ ๋ ธ์ถํ์ง ์๋๋ค. ๋๋๋ฆฌ๊ธฐ ์ด๋ ค์ด ์ผ์ ์ฌ๋ ์น์ธ ์ง์ ์ด ์๋ ์น UI์๋ง ๋จ๊ฒจ ๋๋ค.
์๋ ํธ๋ฆฌ๊ฑฐ
# ์นํ
(LESSONLOOP_WEBHOOK_TOKEN ์ค์ ์)
curl -X POST https://<๋ฐฐํฌ์ฃผ์>/api/webhook/run \
-H "X-Webhook-Token: $TOKEN" -H "content-type: application/json" \
-d '{"concept":"CORS","n_questions":5,"auto_approve":true}'
# ์ค์ผ์ค๋ฌ โ ํ์ ์์ธ ๊ฐ๋
์ cron์ ๋ง์ถฐ ์๋ ์คํ
LESSONLOOP_SCHEDULER=1 LESSONLOOP_SCHEDULER_CRON="0 3 * * *" uvicorn app.main:app๊ทธ ๋ฐ์
๋ฉํฐ ์์ด์ ํธ โ ๊ธฐํ์ / ์กฐ์ฌ์ / ์์ฑ์ / ๊ฒ์์ 4์ญํ .
single_agent์ค์ ์ผ๋ก ๋จ์ผ ์์ด์ ํธ์ ๋น๊ตํ ์ ์๋ค๋กค๋ฐฑ โ ์ด์์ append-only๋ก ๋ฒ์ ์ด ์์ธ๋ค. ์คํ ํ๋ฉด์์ ์ด์ ๋ฒ์ ์ผ๋ก ๋์ด๋ฆด ์ ์๋ค
๋น์ฉ ๋์๋ณด๋ โ
/evals์์ ๋จ๊ณ๋ณ ๋์ ์๊ฐยท๋น์ฉ์ผ๋ก ๋ณ๋ชฉ์ ๋ณธ๋ค
๊ตฌ์กฐ
app/
main.py FastAPI โ API ยท SSE ยท ์ ์ ์๋น
orchestrator.py ๋จ๊ณ ์ํ ๋จธ์ ยท ์ฌ๋ ๊ฐ์
์ง์ ยท ์คํจ ๋ถ๋ฅ
agent.py ์์ด์ ํธ ๋ฃจํ (๊ณํ โ ๋๊ตฌ โ ๊ด์ฐฐ โ ๋ค์ ํ๋)
tools/
base.py ๊ถํ ยท ์น์ธ ยท ์ฌ์๋ ยท ๋ฃจํ ๊ฐ์ง๊ฐ ๋ชจ์ด๋ ๋๊ตฌ ์ง์
์
search.py web_search (4๋จ ํด๋ฐฑ)
fetch.py fetch_page (allowlist ยท SSRF ๋ฐฉ์ด ยท ์ฃผ์ ๊ด๋ จ์ฑ)
draft.py save_draft ยท save_questions (๊ฒ์ฆ ๊ฒ์ดํธ)
store.py read_sources (์ธ์ฉ๋ฌธ ์ถ์ถ ํฌํจ) ยท save_review
grounding.py ์ธ์ฉ๋ฌธ โ ์๋ฌธ ๋์กฐ โ ํ๊ฐ ๋ฐฉ์ง์ ํต์ฌ
export.py export_pack (์น์ธ ํ์ ยท ๋์ฝ์ด ํ์ธ)
db.py SQLite โ run ยท trace ยท ๋ฉ์์ง ํ์คํ ๋ฆฌ ยท ์น์ธ ยท ์ํฐํฉํธ
llm.py OpenAI ํธ์ถ + ํ ํฐยท๋น์ฉ ๊ณ๋
prompts.py ์ญํ ๋ณ ์์คํ
ํ๋กฌํํธ
configs.py ์คํ ์ค์
static/ ํ๋ก ํธ์๋ (๋น๋ ๋๊ตฌ ์์)
evals/ ํ๊ฐ ์ธํธ ยท ๋ฌ๋ ยท ๊ฒฐ๊ณผ
mcp_server.py MCP ์๋ฒ
data/seed_sources.json ๊ฒ์์ด ์ ๋ถ ๋งํ์ ๋์ ์ตํ ํด๋ฐฑ ์ฝํผ์ค์ธ๋ถ ์๋น์ค ์ฌ์ฉ์ ๋ํด
์ถ์ฒ๋ ๊ณต๊ฐ ๊ธฐ์ ๋ฌธ์ ์ฌ์ดํธ๋ก ํ์ ํ๋ค (app/config.py์
FETCH_ALLOWLIST).๋ณธ๋ฌธ์ ์ธ์ฉ ๋ชฉ์ ์ ๋ฐ์ท๋ง ์ ์ฅํ๊ณ , ๊ฒฐ๊ณผ๋ฌผ์๋ ์๋ฌธ URL์ ํญ์ ํ๊ธฐํ๋ค.
MDNยทWikipedia์ ๊ณต๊ฐ ๊ฒ์ ์๋ํฌ์ธํธ๋ฅผ ์ด๋ค. Wikipedia์ User-Agent ์ ์ฑ ์ ๋ง์ถฐ ์ฐ๋ฝ์ฒ๊ฐ ๋ด๊ธด UA๋ฅผ ๋ณด๋ธ๋ค.
๊ฐ์ ธ์จ ๋ณธ๋ฌธ์ ํญ์
<untrusted_document>๋ก ๊ฐ์ธ ๋ชจ๋ธ์๊ฒ "๋ฐ์ดํฐ์ด์ง ์ง์๊ฐ ์๋"์ ๋ช ์ํ๋ค. ๋ฌธ์์ ํ๋กฌํํธ ์ธ์ ์ ์ด ์ฌ๊ฒจ ์์ด๋ ๋๊ตฌ ๊ถํ์ ์ฝ๋๊ฐ ๋ง๋๋ค.
โ ์ ์คํ โ ์์ฐ ์ฌ๋ผ์ด๋, ์ค์ ์ ํ, ์ฐ๊ฒฐ๋ ๋๊ตฌ 8์ข
โก ์ฌ๋ ์น์ธ ๋๊ธฐ โ ์ถ์ฒ ์ ์ธยท์ถ๊ฐยท์์ฒญ ๋จ๊ธฐ๊ธฐ
โข ๋ฐํ ์๋ฃ โ ํ์ผ ํฌ๊ธฐ ํ์ธ ํ ๋จ๋ ๋ฐฐ๋, ๊ฒฐ๊ณผ๋ฌผ, ๊ฒ์ ์ง์ , ๋กค๋ฐฑ
โฃ ํ๊ฐยท๋น์ฉ โ ๋จ๊ณ๋ณ ๋ณ๋ชฉ, ์ค์ ๋ณ ๋น๊ต, ์คํจ ์ฌ๋ก