Skip to main content
Glama

๐Ÿ” 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.


Related MCP server: moodle-mcp

๋น ๋ฅด๊ฒŒ ์‹คํ–‰ํ•˜๊ธฐ

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

ํ™˜๊ฒฝ๋ณ€์ˆ˜

๋ณ€์ˆ˜

ํ•„์ˆ˜

๊ธฐ๋ณธ๊ฐ’

์„ค๋ช…

OPENAI_API_KEY

โœ…

โ€”

์„œ๋ฒ„์—์„œ๋งŒ ์“ฐ์ธ๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ๋กœ ์ ˆ๋Œ€ ๋‚ด๋ ค๊ฐ€์ง€ ์•Š๋Š”๋‹ค

LESSONLOOP_MODEL

gpt-4o-mini

๊ธฐ๋ณธ ๋ชจ๋ธ

LESSONLOOP_REVIEW_MODEL

(๋™์ผ)

๊ฒ€์ˆ˜์ž๋งŒ ๋‹ค๋ฅธ ๋ชจ๋ธ๋กœ ์˜ฌ๋ฆฌ๊ณ  ์‹ถ์„ ๋•Œ

LESSONLOOP_MAX_STEPS

50

run๋‹น LLM ํ˜ธ์ถœ ์ƒํ•œ

LESSONLOOP_MAX_COST_USD

0.50

run๋‹น ๋น„์šฉ ์ƒํ•œ (๋„˜์œผ๋ฉด ์ž๋™ ์ค‘๋‹จ)

LESSONLOOP_MAX_SECONDS

420

run๋‹น ์‹œ๊ฐ„ ์ƒํ•œ

LESSONLOOP_MAX_REVISIONS

1

๊ฒ€์ˆ˜ โ†’ ์žฌ์ž‘์„ฑ ์ตœ๋Œ€ ํšŸ์ˆ˜

TAVILY_API_KEY

โ€”

์žˆ์œผ๋ฉด ๊ฒ€์ƒ‰ 1์ˆœ์œ„๋กœ ์‚ฌ์šฉ. ์—†์–ด๋„ ๋™์ž‘ํ•œ๋‹ค

LESSONLOOP_WEBHOOK_TOKEN

โ€”

์„ค์ •ํ•˜๋ฉด POST /api/webhook/run ํ™œ์„ฑํ™”

LESSONLOOP_SCHEDULER

0

1์ด๋ฉด ํ๋ฅผ cron์œผ๋กœ ์ž๋™ ์‹คํ–‰

LESSONLOOP_RATE_LIMIT_PER_HOUR

10

IP๋‹น ์‹œ๊ฐ„๋‹น ์‹คํ–‰ ์ˆ˜

์ „์ฒด ๋ชฉ๋ก์€ .env.example ์ฐธ๊ณ . .env๋Š” ์ ˆ๋Œ€ ์ปค๋ฐ‹ํ•˜์ง€ ์•Š๋Š”๋‹ค (.gitignore์— ์žˆ์Œ).


ํ™”๋ฉด

ํ™”๋ฉด

๊ฒฝ๋กœ

๋‹ด๋Š” ๊ฒƒ

์ƒˆ ์‹คํ–‰

/

๊ฐœ๋… ์ž…๋ ฅ, ์˜ˆ์‚ฐ ์Šฌ๋ผ์ด๋”, ์„ค์ • ์„ ํƒ, ํ, ์—ฐ๊ฒฐ๋œ ๋„๊ตฌ 8์ข…์˜ ์Šคํ‚ค๋งˆ

์‹คํ–‰ ์ƒ์„ธ

/run/{id}

์‹ค์‹œ๊ฐ„ ํŠธ๋ ˆ์ด์Šค(SSE) ยท ์ถœ์ฒ˜ ยท ๊ฒฐ๊ณผ๋ฌผ ๋ผ์ด๋ธŒ ๋ทฐ ยท ์Šน์ธ ํŒจ๋„ ยท ๋‹ค์šด๋กœ๋“œ

ํ‰๊ฐ€ยท๋น„์šฉ

/evals

์„ค์ •๋ณ„ ๋น„๊ตํ‘œ ยท ๋‹จ๊ณ„๋ณ„ ๋ณ‘๋ชฉ ยท ์‹คํŒจ ์‚ฌ๋ก€ ํ‘œ

API ๋ฌธ์„œ

/api/docs

FastAPI ์ž๋™ ์ƒ์„ฑ

ํŠธ๋ ˆ์ด์Šค์˜ ๊ฐ ์Šคํ…์—๋Š” ์–ด๋–ค ๋„๊ตฌ๋ฅผ ์™œ ๋ถˆ๋ €๊ณ  ๋ฌด์—‡์„ ๋ฐ›์•˜๋Š”์ง€๊ฐ€ ์ธ์žยท๊ฒฐ๊ณผ JSON์งธ๋กœ ๋‚จ๊ณ , ์†Œ์š” ์‹œ๊ฐ„ยทํ† ํฐยท๋น„์šฉ์ด ํ•จ๊ป˜ ์ฐํžŒ๋‹ค.

์‹ค์ œ ํ™”๋ฉด

์‹คํ–‰ ํผโ‘  ์ƒˆ ์‹คํ–‰ โ€” ์˜ˆ์‚ฐ ์Šฌ๋ผ์ด๋”, ์„ค์ • ์„ ํƒ, ์—ฐ๊ฒฐ๋œ ๋„๊ตฌ 8์ข…

์Šน์ธ ๋Œ€๊ธฐโ‘ก ์‚ฌ๋žŒ ์Šน์ธ ๋Œ€๊ธฐ โ€” ์ถœ์ฒ˜ ์ œ์™ธยท์ถ”๊ฐ€ยท์š”์ฒญ ๋‚จ๊ธฐ๊ธฐ

๋ฐœํ–‰ ์™„๋ฃŒโ‘ข ๋ฐœํ–‰ ์™„๋ฃŒ โ€” ํŒŒ์ผ ํฌ๊ธฐ ํ™•์ธ ํ›„ ๋œจ๋Š” ๋ฐฐ๋„ˆ, ๊ฒฐ๊ณผ๋ฌผ, ๊ฒ€์ˆ˜ ์ง€์ , ๋กค๋ฐฑ

ํ‰๊ฐ€ ๋Œ€์‹œ๋ณด๋“œโ‘ฃ ํ‰๊ฐ€ยท๋น„์šฉ โ€” ๋‹จ๊ณ„๋ณ„ ๋ณ‘๋ชฉ, ์„ค์ •๋ณ„ ๋น„๊ต, ์‹คํŒจ ์‚ฌ๋ก€

์ „์ฒด ํŠธ๋ ˆ์ด์Šค(115์Šคํ…)์™€ ์ธ์žยท๊ฒฐ๊ณผ JSON๊นŒ์ง€ ํŽผ์นœ ํ™”๋ฉด, ๋„๊ตฌ ์Šคํ‚ค๋งˆ ์ „๋ฌธ์€ docs/screenshots/ ์— ์žˆ๋‹ค. ์ „๋ถ€ python docs/capture.py๋กœ ์žฌํ˜„๋œ๋‹ค.


์—ฐ๊ฒฐ๋œ ๋„๊ตฌ 8์ข…

๋„๊ตฌ

์ข…๋ฅ˜

๋ถ€์ž‘์šฉ

๊ถŒํ•œ

์Šน์ธ

web_search

์™ธ๋ถ€ API

์—†์Œ

๊ฒ€์ƒ‰์–ด๋งŒ ์ „์†ก

fetch_page

์™ธ๋ถ€ HTTP

์—†์Œ

๋„๋ฉ”์ธ allowlist๋งŒ, ์‚ฌ์„ค IP ์ฐจ๋‹จ, 15์ดˆยท200KB ์ƒํ•œ, ์ฃผ์ œ ๊ด€๋ จ์„ฑ ๊ฒ€์‚ฌ

read_sources

DB ์ฝ๊ธฐ

์—†์Œ

ํ˜„์žฌ run์˜ ์ถœ์ฒ˜๋งŒ

save_draft

DB ์“ฐ๊ธฐ

์žˆ์Œ

ํ˜„์žฌ run์—๋งŒ insert

save_questions

DB ์“ฐ๊ธฐ

์žˆ์Œ

ํ˜„์žฌ run์—๋งŒ insert

read_draft

DB ์ฝ๊ธฐ

์—†์Œ

ํ˜„์žฌ run๋งŒ

save_review

DB ์“ฐ๊ธฐ

์žˆ์Œ

ํ˜„์žฌ run์—๋งŒ insert

export_pack

ํŒŒ์ผ ์ƒ์„ฑ

๋˜๋Œ๋ฆฌ๊ธฐ ์–ด๋ ค์›€

artifacts/{run_id}/ ๋ฐ–์œผ๋กœ ๋ชป ๋‚˜๊ฐ

โœ… ํ•„์ˆ˜

๊ฐ ๋„๊ตฌ์˜ ์ž…๋ ฅยท์ถœ๋ ฅ ์Šคํ‚ค๋งˆ๋Š” ์‹คํ–‰ ํ™”๋ฉด ์˜ค๋ฅธ์ชฝ "์—ฐ๊ฒฐ๋œ ๋„๊ตฌ"์—์„œ ํŽผ์ณ ๋ณผ ์ˆ˜ ์žˆ๊ณ , 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 (์นด๋“œ ๋“ฑ๋ก ๋ถˆํ•„์š”, ๊ฐ€์žฅ ์‰ฌ์›€)

  1. https://huggingface.co/new-space โ†’ SDK Docker, ํ•˜๋“œ์›จ์–ด CPU basic (free) ๋กœ Space ์ƒ์„ฑ

  2. Space์˜ Settings โ†’ Variables and secrets โ†’ New secret ์—์„œ OPENAI_API_KEY ์ถ”๊ฐ€ (Secret์œผ๋กœ ๋„ฃ์–ด์•ผ ๋กœ๊ทธยท์†Œ์Šค์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค)

  3. ์ด ์ €์žฅ์†Œ๋ฅผ Space์— ์˜ฌ๋ฆฐ๋‹ค

    git remote add space https://huggingface.co/spaces/<๊ณ„์ •>/lesson-loop
    git push space main
  4. Space์˜ README.md๋Š” YAML ํ”„๋ก ํŠธ๋งคํ„ฐ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. deploy/huggingface-space-README.md ๋ฅผ Space ์ €์žฅ์†Œ ๋ฃจํŠธ์˜ README.md๋กœ ๋ณต์‚ฌํ•ด ์“ฐ๋ฉด ๋œ๋‹ค (app_port: 7860์ด ๋“ค์–ด ์žˆ๋‹ค)

  5. ๋นŒ๋“œ๊ฐ€ ๋๋‚˜๋ฉด https://<๊ณ„์ •>-lesson-loop.hf.space/api/meta ๊ฐ€ {"llm_ready": true} ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค

๋ฐฉ๋ฒ• B โ€” Render

  1. Render โ†’ New โ†’ Web Service โ†’ ์ด GitHub ์ €์žฅ์†Œ ์—ฐ๊ฒฐ (render.yaml์ด ์ž๋™ ์ธ์‹๋œ๋‹ค)

  2. Environment ํƒญ์—์„œ OPENAI_API_KEY๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•œ๋‹ค (์ €์žฅ์†Œ์—๋Š” ๋“ค์–ด ์žˆ์ง€ ์•Š๋‹ค)

  3. ๋ฐฐํฌ ํ›„ /api/meta๊ฐ€ {"llm_ready": true}๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์ •์ƒ

๋ฐฉ๋ฒ• C โ€” ์ง์ ‘ Docker

docker build -t lesson-loop .
docker run -p 7860:7860 -e OPENAI_API_KEY=sk-... lesson-loop

PORT ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ๋”ฐ๋ฅด๋ฏ€๋กœ 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 ๋„๊ตฌ

ํ•˜๋Š” ์ผ

list_packs

๋งŒ๋“ค์–ด์ง„ ๋ ˆ์Šจ ํŒฉ ๋ชฉ๋ก

get_pack

ํŒฉ ํ•˜๋‚˜ ์ „์ฒด (json ๋˜๋Š” md)

search_questions

๋ชจ๋“  ํŒฉ์„ ๊ฐ€๋กœ์งˆ๋Ÿฌ ๋ฌธํ•ญ ๊ฒ€์ƒ‰ โ€” ๋ฌธ์ œ์€ํ–‰์ฒ˜๋Ÿผ

get_rubric

ํ•™์Šต๋ชฉํ‘œยท๋ฃจ๋ธŒ๋ฆญ๋งŒ

enqueue_concept

์ƒˆ ๊ฐœ๋…์„ ์ œ์ž‘ ํ์— ๋“ฑ๋ก

๋ฐœํ–‰ยท์‚ญ์ œ๋Š” ๋…ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋˜๋Œ๋ฆฌ๊ธฐ ์–ด๋ ค์šด ์ผ์€ ์‚ฌ๋žŒ ์Šน์ธ ์ง€์ ์ด ์žˆ๋Š” ์›น 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>๋กœ ๊ฐ์‹ธ ๋ชจ๋ธ์—๊ฒŒ "๋ฐ์ดํ„ฐ์ด์ง€ ์ง€์‹œ๊ฐ€ ์•„๋‹˜"์„ ๋ช…์‹œํ•œ๋‹ค. ๋ฌธ์„œ์— ํ”„๋กฌํ”„ํŠธ ์ธ์ ์…˜์ด ์‹ฌ๊ฒจ ์žˆ์–ด๋„ ๋„๊ตฌ ๊ถŒํ•œ์€ ์ฝ”๋“œ๊ฐ€ ๋ง‰๋Š”๋‹ค.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers