interactive-chat / CLAUDE.md
Junhoee's picture
Upload 162 files
1c9c13f verified
|
Raw
History Blame Contribute Delete
18.1 kB

CLAUDE.md โ€” Interactive Novel Engine Backend

ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์›์ž‘ ๊ณ ์ „ ์†Œ์„ค(์นด๋ฅด๋ฐ€๋ผ)์„ ์˜จํ†จ๋กœ์ง€ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜•์‹ํ™”ํ•˜์—ฌ "์ฑ…์„ ์ฝ๋Š” ๋А๋‚Œ + ํ•ต์‹ฌ ์”ฌ์—์„œ์˜ ์ƒํ˜ธ์ž‘์šฉ"์„ ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๋…ธ๋ฒจ ์—”์ง„. Google ADK ๊ธฐ๋ฐ˜ ํŒŒ์ดํ”„๋ผ์ธ์ด ์œ ์ € ์ž…๋ ฅ์„ ๋ฐ›์•„ ๊ฐ€๋“œ โ†’ ์บ๋ฆญํ„ฐ ์‘๋‹ต โ†’ ๋””๋ ‰ํ„ฐ(์ˆ˜๋ ด ํŒ๋‹จ) ์ˆœ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ , ์›์ž‘ยท์˜จํ†จ๋กœ์ง€ ๊ฒ€์ƒ‰์€ GraphRAG ๊ณ„์ธต์ด ๋‹ด๋‹น.

์ตœ์ƒ์œ„ ๊ฐ€์น˜: ์›์ž‘ ์ถฉ์‹ค. ์›์ž‘์˜ ํ†ค๊ณผ ์„ธ๊ณ„๊ด€์ด ๊นจ์ง€๋Š” ์ˆœ๊ฐ„ ์‹คํŒจ๋‹ค. LLM์˜ ์ž์œ ๋„๋Š” ๊ธฐ๋Šฅ์ด ์•„๋‹ˆ๋ผ ๋ฆฌ์Šคํฌ๋กœ ์ทจ๊ธ‰ํ•œ๋‹ค.

์•„ํ‚คํ…์ฒ˜ 3์ธต ๊ตฌ์กฐ

[๋ฐ‘๋‹จ]  ์˜จํ†จ๋กœ์ง€/์ƒํƒœ ์„ค๊ณ„ ๋ฌธ์„œ โ†’ YAML v1.0 (๋Œ€ํ‘œ๋‹˜ ์‚ฐ์ถœ๋ฌผ) โ†’ ์—”์ง„์ด ํŒŒ์‹ฑ
[์ค‘๊ฐ„]  GraphRAG: ์˜จํ†จ๋กœ์ง€ YAML์„ Neo4j์— ์ ์žฌ + ์›๋ฌธ ๋ฐœ์ทŒ ๋ฒกํ„ฐ ๊ฒ€์ƒ‰
[์•ž๋‹จ]  ADK ์—์ด์ „ํŠธ ํŒŒ์ดํ”„๋ผ์ธ (guard โ†’ character โ†’ director)
  • ์˜จํ†จ๋กœ์ง€์˜ ๋‚ด์šฉ์€ ๋Œ€ํ‘œ๋‹˜ ์†Œ์œ , YAML **์Šคํ‚ค๋งˆ(ํ˜•์‹)**๋Š” ๊ฐœ๋ฐœํŒ€ ์†Œ์œ . ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์€ ์–‘์ชฝ ํ•ฉ์˜๋กœ๋งŒ.
  • ๊ทธ๋ž˜ํ”„๋Š” ์›๋ฌธ์—์„œ LLM์ด ์ž๋™ ์ถ”์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ๋ ˆ์ด์…˜๋œ YAML์„ ์ ์žฌํ•˜๋Š” ๊ฒƒ์ด ์œ ์ผํ•œ ๊ฒฝ๋กœ.

์ž‘์—… ์›์น™

1. ๋…ผ์˜ โ†’ ์„ค๊ณ„ โ†’ ๊ณ„ํš โ†’ ๊ตฌํ˜„ โ†’ ๋ฆฌ๋ทฐ

์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐ˜๋“œ์‹œ ์ด ์ˆœ์„œ๋ฅผ ๋”ฐ๋ฅธ๋‹ค:

  1. ๋…ผ์˜: ์š”๊ตฌ์‚ฌํ•ญ์„ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ณ  ์งˆ๋ฌธํ•œ๋‹ค
  2. ์„ค๊ณ„: 2-3๊ฐœ ์ ‘๊ทผ๋ฒ•์„ ๋น„๊ตํ•˜๊ณ  ์ถ”์ฒœํ•œ๋‹ค
  3. ๊ณ„ํš: ๊ตฌ์ฒด์ ์ธ ํŒŒ์ผ ๋ชฉ๋ก๊ณผ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฌธ์„œํ™”ํ•œ๋‹ค
  4. ๊ตฌํ˜„: ๊ณ„ํš๋Œ€๋กœ๋งŒ ๊ตฌํ˜„ํ•œ๋‹ค. ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค
  5. ๋ฆฌ๋ทฐ: ๊ตฌํ˜„ ๊ฒฐ๊ณผ๋ฅผ spec๊ณผ ๋Œ€์กฐํ•˜์—ฌ ๊ฒ€์ฆํ•œ๋‹ค
  6. ๊ฐฑ์‹ : ์ปค๋ฐ‹ ๋ฐ ์ฝ”๋“œ ๊ตฌํ˜„์ด ์™„๋ฃŒ๋˜๋ฉด ํ•ด๋‹น ๋ถ€๋ถ„์„ ๊ผผ๊ผผํ•˜๊ฒŒ ๋‹จ๊ณ„๋ณ„๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ˜์˜ ๋ฐ ๊ฐฑ์‹ ํ•˜์„ธ์š”

์ด ์‚ฌ์ดํด์„ ์ถฉ๋ถ„ํžˆ ๋ฐ˜๋ณตํ•œ๋‹ค. ๊ธ‰ํ•˜๋‹ค๊ณ  ๊ฑด๋„ˆ๋›ฐ์ง€ ์•Š๋Š”๋‹ค.

2. ๊ธฐ์กด ์ฝ”๋“œ์˜ ์ผ๊ด€์„ฑ์„ ์ง€ํ‚จ๋‹ค

  • ์ƒˆ ์ฝ”๋“œ๋Š” ๊ธฐ์กด ํŒจํ„ด์„ ๋”ฐ๋ฅธ๋‹ค. ๋” ๋‚˜์€ ํŒจํ„ด์ด ์žˆ์–ด๋„ ํ˜ผ์ž ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค
  • ํด๋” ๊ตฌ์กฐ, ๋„ค์ด๋ฐ, import ์Šคํƒ€์ผ์„ ๊ธฐ์กด ์ฝ”๋“œ์—์„œ ๋จผ์ € ํ™•์ธํ•œ๋‹ค
  • "๊ฐœ์„ "์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๊ธฐ์กด ์ปจ๋ฒค์…˜์„ ๋ฌด์‹œํ•˜์ง€ ์•Š๋Š”๋‹ค

3. Google ADK ๊ณต์‹ ํŒจํ„ด์„ ๋”ฐ๋ฅธ๋‹ค

  • ADK๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ(LlmAgent, ParallelAgent, SequentialAgent, LoopAgent, AgentTool, FunctionTool)์œผ๋กœ ํ•ด๊ฒฐํ•œ๋‹ค
  • Python์œผ๋กœ ADK๋ฅผ ์šฐํšŒํ•˜๊ฑฐ๋‚˜ ๋Œ€์ฒดํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค
  • ํ”„๋กฌํ”„ํŠธ๋กœ ํ•ด๊ฒฐํ•˜๋ ค ํ•˜๊ธฐ ์ „์—, ์—์ด์ „ํŠธ ๊ตฌ์กฐ(๊ณ„์ธต, ํƒ€์ž…, ์กฐํ•ฉ)๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋จผ์ € ๊ฒ€ํ† ํ•œ๋‹ค
  • ์กฐ๊ธˆ์ด๋ผ๋„ ์˜์‹ฌ๋˜๋ฉด ๋ฐ”๋กœ ADK ๊ณต์‹๋ฌธ์„œ๋ฅผ ํ™•์ธํ•œ๋‹ค: https://google.github.io/adk-docs/

4. ํ™•์ธ ์—†์ด ์ถ”์ธกํ•˜์ง€ ์•Š๋Š”๋‹ค

  • ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜๊ธฐ ์ „์— ๋ฐ˜๋“œ์‹œ ์ฝ๋Š”๋‹ค
  • ADK API๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ธฐ์–ต์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ๋ฌธ์„œ๋‚˜ ์†Œ์Šค๋ฅผ ํ™•์ธํ•œ๋‹ค
  • Docker ๋กœ๊ทธ, grep ๊ฒฐ๊ณผ ๋“ฑ ์‹ค์ œ ์ฆ๊ฑฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํŒ๋‹จํ•œ๋‹ค
  • ์›์ž‘ ์›๋ฌธยท์˜จํ†จ๋กœ์ง€ ๋‚ด์šฉ์„ ๊ธฐ์–ต์œผ๋กœ ์ธ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐ˜๋“œ์‹œ content/ ๋˜๋Š” ๊ทธ๋ž˜ํ”„์—์„œ ์กฐํšŒํ•œ๋‹ค

5. ์„œ์‚ฌ ์ง„ํ–‰์€ ์ฝ”๋“œ๊ฐ€, ๋Œ€์‚ฌ๋Š” LLM์ด

  • ์ฑ•ํ„ฐ/์”ฌ ์ „ํ™˜, ๋ถ„๊ธฐ ์กฐ๊ฑด, ์ˆ˜๋ ด ํŒ์ •์˜ ์ตœ์ข… ๊ฒฐ์ •์€ ๊ฒฐ์ •์  ์ฝ”๋“œ(์ƒํƒœ๋จธ์‹  + director ์ถœ๋ ฅ์˜ ๊ตฌ์กฐํ™”๋œ ๊ฒ€์ฆ)๊ฐ€ ๋‚ด๋ฆฐ๋‹ค
  • LLM์€ "์ง€๊ธˆ ์ด ์”ฌ ์•ˆ์—์„œ ์ด ์บ๋ฆญํ„ฐ๋กœ ์‘๋‹ต"ํ•˜๋Š” ์ข์€ ์—ญํ• ๋งŒ ๋งก๋Š”๋‹ค
  • LLM ์ถœ๋ ฅ๋งŒ ๋ฏฟ๊ณ  state๋ฅผ ์ „์ด์‹œํ‚ค์ง€ ์•Š๋Š”๋‹ค. ์ „์ด ์กฐ๊ฑด์€ ๋ฐ˜๋“œ์‹œ ์ฝ”๋“œ์—์„œ ์žฌ๊ฒ€์ฆํ•œ๋‹ค

ํ•ต์‹ฌ ๋„๋ฉ”์ธ ๊ทœ์น™

์›์ž‘ ์ถฉ์‹ค (Canon Fidelity)

  • ์บ๋ฆญํ„ฐ๊ฐ€ ์›์ž‘์— ์—†๋Š” ์‚ฌ์‹ค์„ ๋‹จ์ •ํ•˜๋Š” ์‘๋‹ต์€ ๊ฒฐํ•จ์ด๋‹ค. ํ”„๋กฌํ”„ํŠธ์— ์ฃผ์ž…๋œ ์˜จํ†จ๋กœ์ง€ยท์›๋ฌธ ๋ฐœ์ทŒ ๋ฒ”์œ„ ์•ˆ์—์„œ๋งŒ ๋งํ•˜๊ฒŒ ํ•œ๋‹ค
  • ์œ ์ €๊ฐ€ ์„ธ๊ณ„๊ด€์„ ๋ฒ—์–ด๋‚œ ํ–‰๋™์„ ํ•˜๋ฉด director๊ฐ€ ์„ธ๊ณ„๊ด€ ๋‚ด ํ™”๋ฒ•์œผ๋กœ ๋˜๋Œ๋ฆฐ๋‹ค. ๋ฉ”ํƒ€ ๋ฐœํ™”("์ €๋Š” AI๋ผ์„œ...")๋Š” ์ ˆ๋Œ€ ๊ธˆ์ง€
  • ๋ฌธ์ฒด๋Š” content/style/ ๋ฌธ์ฒด์—ฐ์ถœ ๊ฐ€์ด๋“œ๊ฐ€ ๊ธฐ์ค€. style_critic ๋ฃจํ”„๊ฐ€ ๊ฒ€์ˆ˜ํ•œ๋‹ค

์Šคํฌ์ผ๋Ÿฌ ์Šค์ฝ”ํ”„ (๋งค์šฐ ์ค‘์š”)

  • ๋ชจ๋“  ์›๋ฌธ ์ฒญํฌ, ๊ทธ๋ž˜ํ”„ ๋…ธ๋“œ/์—ฃ์ง€, ์˜จํ†จ๋กœ์ง€ ํ•ญ๋ชฉ์—๋Š” state_id(์„œ์‚ฌ ์ง„ํ–‰ ๋‹จ์œ„)๊ฐ€ ๋ถ™๋Š”๋‹ค
  • ๋ชจ๋“  ๊ฒ€์ƒ‰์€ state_id <= ํ˜„์žฌ state ํ•„ํ„ฐ๋ฅผ ๊ฐ•์ œํ•œ๋‹ค. ํ•„ํ„ฐ ์—†๋Š” ๊ฒ€์ƒ‰ ์ฝ”๋“œ๋Š” ๋ฆฌ๋ทฐ์—์„œ ๋ฐ˜๋ ค
  • ์ง€์‹ ์ƒํƒœ: "state N ์‹œ์ ์— ์ด ์บ๋ฆญํ„ฐ๊ฐ€ ์•„๋Š” ๊ฒƒ"์€ ์˜จํ†จ๋กœ์ง€์˜ ์ง€์‹ ์ƒํƒœ ํ•ญ๋ชฉ์ด ๊ถŒ์œ„. ์บ๋ฆญํ„ฐ ํ”„๋กฌํ”„ํŠธ์—๋Š” ํ•ด๋‹น ์‹œ์  ์ง€์‹๋งŒ ์ฃผ์ž…ํ•œ๋‹ค

์ˆ˜๋ ด (Convergence)

  • ์”ฌ๋งˆ๋‹ค YAML์— ์ •์˜๋œ ๋ชฉํ‘œ ๋น„ํŠธ(goal beats)์™€ ์ˆ˜๋ ด ์กฐ๊ฑด(exit condition)์ด ์žˆ๋‹ค
  • director๋Š” ๋งค ํ„ด "๋ชฉํ‘œ ๋น„ํŠธ ๋‹ฌ์„ฑ ์—ฌ๋ถ€ / ์ดํƒˆ ์ •๋„ / ์ˆ˜๋ ด ์‹œ์  ๋„๋‹ฌ ์—ฌ๋ถ€"๋ฅผ ๊ตฌ์กฐํ™” ์ถœ๋ ฅ์œผ๋กœ ํŒ๋‹จํ•œ๋‹ค
  • ์ตœ๋Œ€ ํ„ด ์ˆ˜๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด director๊ฐ€ ๊ฐ•์ œ ์ˆ˜๋ ด ๋‚ด๋ ˆ์ด์…˜์œผ๋กœ ์”ฌ์„ ๋‹ซ๋Š”๋‹ค (๋ฌดํ•œ ์ฒด๋ฅ˜ ๋ฐฉ์ง€)

์—์ด์ „ํŠธ ํด๋” ๊ทœ์น™

์—์ด์ „ํŠธ = ํด๋” + agent.py            โ† ํ•˜์œ„ ์—์ด์ „ํŠธ๊ฐ€ ์žˆ๋Š” ๊ณ„์ธตํ˜•
ํ•˜์œ„ ์—์ด์ „ํŠธ = sub_agents/ ํด๋”        โ† ๋ถ€๋ชจ-์ž์‹ ๊ณ„์ธต
๊ฐ™์€ ๋ށ์Šค ๋ณ‘๋ ฌ ์—์ด์ „ํŠธ = agents/ ํŒŒ์ผ  โ† ์ž์ฒด ํ•˜์œ„ ์—†๋Š” ๋‹จ์ˆœ ์—์ด์ „ํŠธ (๊ฐ€๋“œ ๋“ฑ)
๋„๊ตฌ = tools/ ์•ˆ์— 1ํŒŒ์ผ = 1ํ•จ์ˆ˜
instruction 10์ค„ ์ดˆ๊ณผ = instructions/ ํด๋”๋กœ ๋ถ„๋ฆฌ
ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ = ์กฐ๋ฆฝ๋งŒ ๋‹ด๋‹น (ํด๋ž˜์Šค ์ •์˜/DB ํ—ฌํผ ํฌํ•จ ๊ธˆ์ง€)

sub_agents/ vs agents/ ๊ตฌ๋ถ„

  • sub_agents/X/agent.py ํŒจํ„ด์€ X๊ฐ€ ์ถ”๊ฐ€๋กœ ํ•˜์œ„ ๊ณ„์ธต์„ ๊ฐ€์งˆ ๋•Œ ์‚ฌ์šฉ. ์˜ˆ: sub_agents/director/sub_agents/style_critic/...
  • agents/X.py ํŒจํ„ด์€ X๊ฐ€ ์ž์ฒด ํ•˜์œ„ ์—†์ด ๋‹จ์ผ BaseAgent/LlmAgent๋กœ ๋๋‚  ๋•Œ ์‚ฌ์šฉ. ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜(create_X())์™€ ํด๋ž˜์Šค๋ฅผ ๊ฐ™์€ ํŒŒ์ผ์— ๋ชจ์•„๋‘ . ์˜ˆ: engine/agents/input_guard.py, spoiler_guard.py, scene_context_loader.py

ํŒฉํ† ๋ฆฌ ํŒŒ์ผ ์ฑ…์ž„ ๋ถ„๋ฆฌ

factory.py๋Š” ์กฐ๋ฆฝ๋งŒ ๋‹ด๋‹น. ์—์ด์ „ํŠธ ํด๋ž˜์Šค ์ •์˜ยทDB ํ—ฌํผ ํ•จ์ˆ˜๋Š” agents/*.py๋‚˜ sub_agents/*/agent.py์— ์œ„์น˜. ํŒฉํ† ๋ฆฌ๋Š” ๊ทธ๊ฒƒ๋“ค์„ importํ•ด์„œ SequentialAgent(sub_agents=[create_guard(scene_id), create_character(scene_id), ...]) ํ˜•ํƒœ๋กœ ์—ฎ๋Š” ์—ญํ• ๋งŒ.

์—”์ง„ ๊ตฌ์กฐ

engine/
โ”œโ”€โ”€ agent.py              โ† ๋ฃจํŠธ ์—์ด์ „ํŠธ (ํ„ด ํŒŒ์ดํ”„๋ผ์ธ)
โ”œโ”€โ”€ app.py                โ† ADK App + Runner + Plugin ๋“ฑ๋ก
โ”œโ”€โ”€ model.py              โ† LLM ๋ชจ๋ธ ์„ค์ • (env์—์„œ ์ฝ์Œ)
โ”œโ”€โ”€ router.py             โ† FastAPI ์—”๋“œํฌ์ธํŠธ (HTTP๋งŒ ๋‹ด๋‹น)
โ”œโ”€โ”€ instructions/         โ† instruction ํ…์ŠคํŠธ (์บ๋ฆญํ„ฐ/๋””๋ ‰ํ„ฐ/๊ฐ€๋“œ)
โ”œโ”€โ”€ callbacks/            โ† ์—์ด์ „ํŠธ๋ณ„ ์ฝœ๋ฐฑ
โ”œโ”€โ”€ plugins/              โ† ADK Plugin (ํšก์ข…๋‹จ ๊ด€์‹ฌ์‚ฌ)
โ”‚   โ””โ”€โ”€ logging.py        โ† ํ„ด ๋กœ๊น… (์ž…๋ ฅ/์ปจํ…์ŠคํŠธ/์‘๋‹ต/๊ฐ€๋“œ๋ฐœ๋™/์ˆ˜๋ ด) โ€” ์—ฐ๊ตฌ ๋ฐ์ดํ„ฐ ๊ฒธ์šฉ
โ”œโ”€โ”€ services/             โ† ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
โ”‚   โ”œโ”€โ”€ story.py          โ† ์นด๋“œ ์ง„ํ–‰ ์ƒํƒœ๋จธ์‹  (canon ์ฝ๊ธฐ/next ์ „์ด/์—”๋”ฉ ํŒ์ •)
โ”‚   โ”œโ”€โ”€ pocket.py         โ† ๊ฐœ์ž… ํฌ์ผ“: ADK ์„ธ์…˜ ๊ด€๋ฆฌ + run_turn() + ์ˆ˜๋ ด ์ฝ”๋“œ ์žฌ๊ฒ€์ฆ
โ”‚   โ”œโ”€โ”€ state.py          โ† ์ˆจ์€ ์ƒํƒœ (์‹ ๋ขฐ์ถ•/์‡ ์•ฝ๋„/์•Ž) ์ปค๋ฐ‹
โ”‚   โ””โ”€โ”€ retrieval.py      โ† ๊ฒ€์ƒ‰ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ (์Šคํฌ์ผ๋Ÿฌ ํ•„ํ„ฐ ๊ฐ•์ œ ์ง€์ , GraphRAG๋Š” 2๋‹จ๊ณ„)
โ”œโ”€โ”€ repositories/         โ† ๋ฐ์ดํ„ฐ ์ ‘๊ทผ
โ”‚   โ”œโ”€โ”€ ontology.py       โ† ์˜จํ†จ๋กœ์ง€ ์กฐํšŒ ์ธํ„ฐํŽ˜์ด์Šค (๊ตฌํ˜„: yaml_direct / neo4j)
โ”‚   โ”œโ”€โ”€ manuscript.py     โ† ์›๋ฌธ ์ฒญํฌ + ๋ฒกํ„ฐ ๊ฒ€์ƒ‰
โ”‚   โ””โ”€โ”€ playlog.py        โ† ํ„ด ๋กœ๊ทธ ์ €์žฅ
โ”œโ”€โ”€ tools/                โ† 1ํŒŒ์ผ = 1๋„๊ตฌํ•จ์ˆ˜
โ”‚   โ”œโ”€โ”€ retrieval/        โ† query_ontology, search_excerpt
โ”‚   โ””โ”€โ”€ state/            โ† set_flag, update_affinity, mark_beat
โ”œโ”€โ”€ sub_agents/           โ† ์—์ด์ „ํŠธ ํŠธ๋ฆฌ
โ”‚   โ”œโ”€โ”€ character/        โ† ์บ๋ฆญํ„ฐ ์‘๋‹ต (์ธ๋ฌผ ์‹œํŠธ + ์”ฌ ๋ธŒ๋ฆฌํ”„ + ๋ฐœ์ทŒ ์ฃผ์ž…)
โ”‚   โ”œโ”€โ”€ director/         โ† ๋น„ํŠธ ๋‹ฌ์„ฑ/์ดํƒˆ/์ˆ˜๋ ด ํŒ๋‹จ (๊ตฌ์กฐํ™” ์ถœ๋ ฅ)
โ”‚   โ””โ”€โ”€ style_critic/     โ† LoopAgent (generator + critic) ๋ฌธ์ฒด ๊ฒ€์ˆ˜
โ”œโ”€โ”€ schemas/              โ† Pydantic: ์ฝ˜ํ…์ธ  3๋ ˆ์ด์–ด ์Šคํ‚ค๋งˆ + validate CLI
โ””โ”€โ”€ content/              โ† (์ฝ๊ธฐ ์ „์šฉ) ์—”์ง„์˜ ์œ ์ผํ•œ ์„œ์‚ฌ ์ž…๋ ฅ โ€” ์›์ฒœ์€ ๋น„ํŠธ์นด๋“œ MD(๋Œ€ํ‘œ๋‹˜ ์†Œ์œ )
    โ”œโ”€โ”€ beatcards/        โ† ๋น„ํŠธ์นด๋“œ YAML (scripts/beatcard_convert.py ์‚ฐ์ถœ๋ฌผ, E01~)
    โ”œโ”€โ”€ chapters/         โ† ์ฑ•ํ„ฐ ๋ฉ”ํƒ€ (entry/exit beats, ending_rules, R์นด๋“œ ๋“ฑ๋ก)
    โ”œโ”€โ”€ characters.yaml   โ† ์ธ๋ฌผ ์ •์ฒด์„ฑ ์‹œํŠธ (ENOS, ์ง€์‹๊ฒฝ๊ณ„, kb_query ํ›…)
    โ”œโ”€โ”€ style/            โ† ๋ฌธ์ฒด์—ฐ์ถœ ๊ฐ€์ด๋“œ
    โ””โ”€โ”€ narrative/        โ† ์„œ์‚ฌ/ํ•ธ๋“œ์˜คํ”„ ๋ฌธ์„œ

ADK App + Plugin ๊ทœ์น™

  • app.py์—์„œ App ๊ฐ์ฒด + Runner๋ฅผ ์ •์˜ํ•œ๋‹ค
  • ํšก์ข…๋‹จ ๊ด€์‹ฌ์‚ฌ(๋กœ๊น…, ๋ชจ๋‹ˆํ„ฐ๋ง ๋“ฑ)๋Š” Plugin์œผ๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค (plugins/)
  • Runner๋Š” App ๊ธฐ๋ฐ˜์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค (Runner(app=app, ...))
  • agent๋ฅผ Runner์— ์ง์ ‘ ์ „๋‹ฌํ•˜์ง€ ์•Š๋Š”๋‹ค (๋ ˆ๊ฑฐ์‹œ ํŒจํ„ด)
  • ์Šค์บํด๋“œ ํŒŒ์ผ(๋ฏธ๊ตฌํ˜„ ๋„๊ตฌ, ๋นˆ ์ฝœ๋ฐฑ, ๋นˆ instruction)์€ ์‚ญ์ œํ•˜์ง€ ์•Š๋Š”๋‹ค
    • Claude Code๊ฐ€ ADK ํŒจํ„ด์„ ๋ฒ—์–ด๋‚˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ฐ€๋“œ๋ ˆ์ผ ์—ญํ• 
  • ํ˜ธ์ถœ ํ๋ฆ„: Agent โ†’ Tool(FunctionTool) โ†’ Service โ†’ Repository โ†’ ๋ฐ์ดํ„ฐ(YAML/Neo4j/DB)

์ฝ˜ํ…์ธ  ํŒŒ์ดํ”„๋ผ์ธ (A์•ˆ ์•„ํ‚คํ…์ฒ˜ โ€” 2026-07-03 ํ™•์ •)

์ฝ˜ํ…์ธ  3๋ ˆ์ด์–ด

๋ ˆ์ด์–ด ํŒŒ์ผ ๋‚ด์šฉ ๋Œ€์‘ ์Šคํ‚ค๋งˆ
์ •์ฒด์„ฑ (๋ถˆ๋ณ€) content/characters.yaml ์ธ๋ฌผ ์‹œํŠธ(ENOS), player=laura, ์ง€์‹๊ฒฝ๊ณ„, kb_query ํ›… schemas/character.py
์ฑ•ํ„ฐ ๋ฉ”ํƒ€ content/chapters/*.yaml entry/exit beats, beat_range, ์—”๋”ฉ ๊ทœ์น™, R์นด๋“œ ๋“ฑ๋ก schemas/chapter.py
๋‚ด๋Ÿฌํ‹ฐ๋ธŒ content/beatcards/E##/*.yaml ์นด๋“œ: narration ๋ธ”๋ก/choices/next/canon/๋ณต์„  ํƒœ๊ทธ/์—ฐ์ถœ schemas/beatcard.py
  • ์ฝ˜ํ…์ธ  ์›์ฒœ(๊ถŒ์œ„)์€ ๋น„ํŠธ์นด๋“œ MD(์นด๋ฅด๋ฐ€๋ผ/.../๋น„ํŠธ์นด๋“œ/, ๋Œ€ํ‘œ๋‹˜ ์†Œ์œ ). ๋ณ€ํ™˜ YAML์€ ํŒŒ์ƒ๋ฌผ์ด๋ฉฐ scripts/beatcard_convert.py(๊ฐœ๋ฐœํŒ€ ์†Œ์œ )๋กœ๋งŒ ์ƒ์„ฑ
  • ์ฝ˜ํ…์ธ  ๋‚ด์šฉ ์ˆ˜์ •์€ ๊ฐœ๋ฐœํŒ€์ด ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์Šคํ‚ค๋งˆ ์œ„๋ฐ˜ ๋ฐœ๊ฒฌ ์‹œ ๊ฒ€์ฆ ๋ฆฌํฌํŠธ๋กœ ๋Œ€ํ‘œ๋‹˜๊ป˜ ์ „๋‹ฌ
  • python -m engine.schemas.validate content/ ๊ฐ€ ํ†ต๊ณผํ•˜์ง€ ์•Š๋Š” YAML์€ ์ ์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค
  • MVP์˜ ์„œ์‚ฌ ์ง„ํ–‰ ๋‹จ์œ„๋Š” ๋น„ํŠธ์นด๋“œ ์ˆœ์„œ: ์Šคํฌ์ผ๋Ÿฌ ํ•„ํ„ฐ์˜ state_id๋Š” beat_id โ‰ค ํ˜„์žฌ๋กœ ํ•ด์„ํ•œ๋‹ค

์ˆจ์€ ์ƒํƒœ ๋ชจ๋ธ (P0 4์ข… ์—”์ง„ ๋ช…์„ธ ์ค€๊ฑฐ)

  • ์‹ ๋ขฐ์ถ•(์˜์‹ฌโ†”์‹ ๋ขฐ ์‹œ์†Œ) ยท ์‡ ์•ฝ๋„(๋‹จ์กฐ ์ฆ๊ฐ€, ์‹ ๋ขฐ์™€ ๋…๋ฆฝ โ€” ํ•ต์‹ฌ ์•„์ด๋Ÿฌ๋‹ˆ) ยท ์•Ž(์œ ์ €, ๋„์ž… 1ํšŒ ํ™•๋ฆฝ ํ›„ ๋ถˆ๋ณ€)
  • ์ˆซ์žยท๊ฒŒ์ด์ง€ยท์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๋ฅผ ์œ ์ €์—๊ฒŒ ์ ˆ๋Œ€ ๋…ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ƒํƒœ๋Š” ๋‚ด๋ ˆ์ด์…˜ ํ†คยท์นด๋ฅด๋ฐ€๋ผ์˜ ๊ฒฐยทํ•ด๊ธˆ์œผ๋กœ๋งŒ ๋“œ๋Ÿฌ๋‚ธ๋‹ค
  • ์—”๋”ฉ ํŒ์ •์€ ์ฑ•ํ„ฐ ๋ฉ”ํƒ€์˜ ending_rules ์กฐ๊ฑด์‹์„ ์ฝ”๋“œ๋กœ ํ‰๊ฐ€ํ•œ๋‹ค (LLM ํŒ์ • ๊ธˆ์ง€)

GraphRAG ๊ณ„์ธต (2๋‹จ๊ณ„ โ€” MVP ์ œ์™ธ)

  • MVP๋Š” pre_kb ์ƒํƒœ: content/์˜ kb_query ํ›…(source: kb_query, status: pending)์€ ์ฑ„์šฐ์ง€ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ๋‘”๋‹ค
  • MVP ์ฝ˜ํ…์ธ  ๋กœ๋”๋Š” repositories/content.py(YAML ๋ฉ”๋ชจ๋ฆฌ ๋กœ๋“œ). ์ดํ›„ GraphRAG ๋„์ž… ์‹œ kb_query ํ›…์„ ์ฑ„์šฐ๋Š” ๊ตฌํ˜„์œผ๋กœ ๊ต์ฒด. ์ƒ์œ„ ๊ณ„์ธต์€ ๊ตฌํ˜„์„ ๋ชฐ๋ผ์•ผ ํ•œ๋‹ค (ablation ์‹คํ—˜ ์š”๊ฑด)
  • Neo4j ์ ์žฌ ์‹œ LLM ์ž๋™ ์ถ”์ถœ KG ๋นŒ๋”(SimpleKGPipeline ๋ฅ˜)๋Š” ์‚ฌ์šฉ ๊ธˆ์ง€ โ€” ํ๋ ˆ์ด์…˜ ์ฝ˜ํ…์ธ ์™€ ์ถฉ๋Œ
  • ๋…์ž ์ƒํ˜ธ์ž‘์šฉ ๋ฐ์ดํ„ฐ์˜ ๊ทธ๋ž˜ํ”„ํ™”๋„ 2๋‹จ๊ณ„. MVP์—์„œ๋Š” playlog ์Šคํ‚ค๋งˆ ์„ค๊ณ„๊นŒ์ง€๋งŒ

ADK ํ•ต์‹ฌ ๊ทœ์น™

Instruction

์ข…๋ฅ˜ ์šฉ๋„ ์ฃผ์˜
instruction ๋™์ . {var}๋กœ state ๋ณ€์ˆ˜ ์น˜ํ™˜ {}๋Š” state ๋ณ€์ˆ˜๋กœ ํŒŒ์‹ฑ๋จ. ์˜ˆ์‹œ ํ…์ŠคํŠธ์— {} ์“ฐ์ง€ ๋ง ๊ฒƒ
global_instruction ์ „์ฒด sub_agent์— ์ „ํŒŒ ์˜๋„์ ์œผ๋กœ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ์ฑ„์šธ ๊ฒƒ
static_instruction ๊ณ ์ •. ๋ฉ€ํ‹ฐ๋ชจ๋‹ฌ ์ง€์› ์บ๋ฆญํ„ฐ ์‹œํŠธ์ฒ˜๋Ÿผ ์”ฌ ๋‚ด ๋ถˆ๋ณ€ ์ปจํ…์ŠคํŠธ์— ๊ฒ€ํ† 
  • ์›์ž‘ ๋ฐœ์ทŒยท์˜จํ†จ๋กœ์ง€ ์ปจํ…์ŠคํŠธ๋ฅผ instruction์— ๋„ฃ์„ ๋•Œ ์›๋ฌธ์— {, }๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์ž… ์ „ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ๋ฅผ ๊ฑฐ์นœ๋‹ค (์ „์ฒ˜๋ฆฌ ์œ ํ‹ธ ํ•„์ˆ˜)

Callback (8์ข…)

before_agent, after_agent, before_model, after_model, before_tool, after_tool, on_model_error, on_tool_error

  • before_model: ์Šคํฌ์ผ๋Ÿฌ ํ•„ํ„ฐ ์ตœ์ข… ๋ฐฉ์–ด์„  (์ฃผ์ž… ์ปจํ…์ŠคํŠธ์˜ state_id ๊ฒ€์‚ฌ)
  • after_model: ํ„ด ๋กœ๊น… ํ›…

์—์ด์ „ํŠธ ํƒ€์ž…

ํƒ€์ž… ์šฉ๋„ ์˜ˆ์‹œ
LlmAgent LLM์ด ํŒ๋‹จ/๋„๊ตฌ ํ˜ธ์ถœ character, director
ParallelAgent ํ•˜์œ„ ์—์ด์ „ํŠธ ๋ณ‘๋ ฌ ์‹คํ–‰ (ํ•„์š” ์‹œ) ๋‹ค์ค‘ ๊ฒ€์ƒ‰
SequentialAgent ํ•˜์œ„ ์—์ด์ „ํŠธ ์ˆœ์ฐจ ์‹คํ–‰ turn_pipeline (guard โ†’ character โ†’ director)
LoopAgent ํ•˜์œ„ ์—์ด์ „ํŠธ ๋ฐ˜๋ณต (escalate๋กœ ํƒˆ์ถœ) style_critic (generator + critic), ์”ฌ ๋‚ด ํ„ด ๋ฃจํ”„
BaseAgent LLM ์—†์ด ์ฝ”๋“œ๋กœ ์ง์ ‘ ์ฒ˜๋ฆฌ input_guard(๊ทœ์น™ ๊ธฐ๋ฐ˜ 1์ฐจ), scene_context_loader, ์ƒํƒœ ์ „์ด ๊ฒ€์ฆ
CustomAgent BaseAgent ํ™•์žฅ. ์กฐ๊ฑด ๋ถ„๊ธฐ, ๋™์  ์—์ด์ „ํŠธ ์„ ํƒ ์”ฌ ํƒ€์ž…๋ณ„ ํŒŒ์ดํ”„๋ผ์ธ ์„ ํƒ

์ ˆ๋Œ€ ํ•˜์ง€ ๋ง ๊ฒƒ

  • output_schema์™€ tools ๋™์‹œ ์‚ฌ์šฉ โ€” Gemini 3.0๋งŒ ์ง€์›, ๋‹ค๋ฅธ ๋ชจ๋ธ์—์„œ๋Š” ์•ˆ ๋จ. ํ•„์š”ํ•˜๋ฉด sub-agent๋กœ ์ถœ๋ ฅ ํฌ๋งทํŒ…์„ ๋ถ„๋ฆฌํ•  ๊ฒƒ
  • mode="ANY" ์‚ฌ์šฉ (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐœ์ƒ)
  • instruction ์•ˆ์— ์˜ˆ์‹œ ํ…์ŠคํŠธ๋กœ {ํ•œ๊ตญ์–ด} ์‚ฌ์šฉ (state ๋ณ€์ˆ˜๋กœ ํŒŒ์‹ฑ๋จ โ†’ ์—๋Ÿฌ)
  • Python ์ฝ”๋“œ๋กœ ADK ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๋Œ€์ฒด (keyword matching, hardcoded fallback ๋“ฑ)
  • LlmAgent์˜ output_key๋กœ ๊ตฌ์กฐํ™”๋œ ๊ฐ’์„ ๊ธฐ๋Œ€ โ€” LLM ํ…์ŠคํŠธ๋งŒ ์ €์žฅ๋จ. director์˜ ์ˆ˜๋ ด ํŒ์ •์ฒ˜๋Ÿผ ๊ตฌ์กฐํ™” ๊ฐ’์ด ํ•„์š”ํ•˜๋ฉด FunctionTool์ด tool_context.state์— ์ง์ ‘ ์“ฐ๊ฑฐ๋‚˜ BaseAgent๋กœ ๊ตฌํ˜„
  • ๊ฒ€์ƒ‰/์ปจํ…์ŠคํŠธ ์ฃผ์ž… ๊ฒฝ๋กœ์—์„œ state_id ํ•„ํ„ฐ๋ฅผ ์ƒ๋žต (์Šคํฌ์ผ๋Ÿฌ ์‚ฌ๊ณ )
  • LLM ํŒ๋‹จ๋งŒ์œผ๋กœ ์”ฌ/์ฑ•ํ„ฐ ์ „์ด (๋ฐ˜๋“œ์‹œ services/scene.py์˜ ์ฝ”๋“œ ๊ฒ€์ฆ์„ ๊ฑฐ์น  ๊ฒƒ)
  • ์›์ž‘ ์›๋ฌธ์„ ํ”„๋กฌํ”„ํŠธ์— ๊ธฐ์–ต์œผ๋กœ ์žฌํ˜„ (๋ฐ˜๋“œ์‹œ repository ์กฐํšŒ)

Escalate scope ๊ฒฉ๋ฆฌ (๋งค์šฐ ์ค‘์š”)

ADK LoopAgent๋Š” sub_agent์˜ escalate=True ์ด๋ฒคํŠธ๋ฅผ ์ƒ์œ„๋กœ yieldํ•œ๋‹ค. ์ฆ‰ ์ค‘์ฒฉ LoopAgent๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ”๊นฅ ๋ฃจํ”„๊นŒ์ง€ ํ•จ๊ป˜ ํƒˆ์ถœํ•œ๋‹ค(์ „ ํ”„๋กœ์ ํŠธ ์‹ค์ธก ํ™•์ธ). ์ด๋กœ ์ธํ•ด:

  • SequentialAgent๋Š” escalate๋ฅผ ์ธ์‹ํ•˜์ง€ ์•Š์Œ. escalate๊ฐ€ ํ•„์š”ํ•œ ์ž๋ฆฌ์— ๋†“์œผ๋ ค๋ฉด ํ•ด๋‹น ๋ฒ”์œ„๋งŒ LoopAgent(max_iterations=1)๋กœ ๊ฐ์Œ€ ๊ฒƒ
  • ๋‚ด๋ถ€ LoopAgent์˜ ์„ฑ๊ณต-escalate๊ฐ€ ๋ฐ”๊นฅ์„ ์˜ค์—ผ์‹œํ‚ค๋ฉด ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ์—” ๋ฐ˜๋“œ์‹œ ๋ฐ”๊นฅ์„ SequentialAgent๋กœ ๋‘๊ณ , escalate๋ฅผ ์†Œํ™”ํ•  scope๋งŒ LoopAgent๋กœ
  • ์”ฌ ํ„ด ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„ ์‹œ ์ด ๊ทœ์น™์„ ์ ์šฉํ•œ ๊ธฐ์ค€ ๊ตฌ์กฐ:
    scene_session (SequentialAgent)
    โ”œโ”€โ”€ scene_context_loader                  โ† BaseAgent, YAML/๊ทธ๋ž˜ํ”„์—์„œ ์”ฌ ๋ธŒ๋ฆฌํ”„ ๋กœ๋“œ
    โ”œโ”€โ”€ turn_loop (LoopAgent max=์”ฌ๋ณ„ max_turns) โ† director์˜ ์ˆ˜๋ ด-escalate๊ฐ€ ์—ฌ๊ธฐ์„œ ๊ทธ์นจ
    โ”‚   โ”œโ”€โ”€ input_guard
    โ”‚   โ”œโ”€โ”€ character
    โ”‚   โ””โ”€โ”€ director                          โ† ์ˆ˜๋ ด ํŒ์ • ์‹œ escalate
    โ””โ”€โ”€ scene_closer (LoopAgent max=1)        โ† ๊ฐ•์ œ ์ˆ˜๋ ด ๋‚ด๋ ˆ์ด์…˜ + ์ƒํƒœ ์ „์ด ์ปค๋ฐ‹
    

๊ถŒ์œ„ ์†Œ์Šค ์›์น™

"ํ•œ ์ •๋ณด์˜ ์ง„์‹ค์€ ํ•œ ๊ณณ์—๋งŒ ์žˆ์–ด์•ผ ํ•œ๋‹ค."

  • ์„œ์‚ฌ ๋‚ด์šฉ(์ธ๋ฌผ/๊ด€๊ณ„/์‚ฌ๊ฑด/์ง€์‹์ƒํƒœ) ๊ถŒ์œ„: content/yaml/ (์ ์žฌ๋œ Neo4j๋Š” ๊ทธ ์‚ฌ๋ณธ). ์ฝ”๋“œ์— ์„œ์‚ฌ ๋‚ด์šฉ ํ•˜๋“œ์ฝ”๋”ฉ ๊ธˆ์ง€
  • ์”ฌ ์ง„ํ–‰ ์ƒํƒœ ๊ถŒ์œ„: session.state (์”ฌ ๋‚ด) โ†’ ์”ฌ ์ข…๋ฃŒ ์‹œ scene.py๊ฐ€ DB์— ์ปค๋ฐ‹ (์˜์†). ๋‘ ๊ณณ์„ ๋™์‹œ์— ๊ฐฑ์‹ ํ•˜๋Š” ์ฝ”๋“œ ๊ธˆ์ง€
  • ํ˜„์žฌ ์œ ์ €์˜ ์„œ์‚ฌ ์œ„์น˜ ๊ถŒ์œ„: DB play_session.current_state_id. ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋Š” ์ด ๊ฐ’์œผ๋กœ๋งŒ
  • ๋ฌธ์ฒด ๊ธฐ์ค€ ๊ถŒ์œ„: content/style/. ํ”„๋กฌํ”„ํŠธ๋งˆ๋‹ค ๋ฌธ์ฒด ์ง€์‹œ๋ฅผ ์ฆ‰ํฅ ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค
  • ์ˆ˜๋ ด ํŒ์ • ๊ถŒ์œ„: director์˜ ๊ตฌ์กฐํ™” ์ถœ๋ ฅ + scene.py์˜ ์ฝ”๋“œ ๊ฒ€์ฆ. LLM ํ…์ŠคํŠธ ํŒŒ์‹ฑ์œผ๋กœ ํŒ์ • ๊ธˆ์ง€

LLM ๋ชจ๋ธ

# engine/model.py
from google.adk.models.lite_llm import LiteLlm
_MODEL_ID = os.environ.get("LLM_MODEL", "openai/gpt-5.4-nano")
LLM_MODEL = LiteLlm(model=_MODEL_ID)
  • ๋กœ์ปฌ ํ…Œ์ŠคํŠธ: ์ €๋น„์šฉ ๋ชจ๋ธ๋กœ ํŒŒ์ดํ”„๋ผ์ธ ๊ฒ€์ฆ
  • ์บ๋ฆญํ„ฐ/๋ฌธ์ฒด ํ’ˆ์งˆ์ด ์ค‘์š”ํ•œ character, style_critic์€ ์ƒ์œ„ ๋ชจ๋ธ์„ ๋ณ„๋„ env๋กœ ๋ถ„๋ฆฌ ๊ฐ€๋Šฅํ•˜๊ฒŒ (CHARACTER_MODEL)
  • ๋ชจ๋ธ ๊ต์ฒด๊ฐ€ ์žฆ์œผ๋ฏ€๋กœ ํ”„๋กฌํ”„ํŠธ์— ํŠน์ • ๋ชจ๋ธ ์˜์กด ๋ฌธ๋ฒ•์„ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค

ํ…Œ์ŠคํŠธ & ๊ฒ€์ฆ

# ๋‹จ์œ„ ํ…Œ์ŠคํŠธ
python3 -m pytest tests/ -v

# ์ฝ˜ํ…์ธ  ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ
python3 -m engine.schemas.validate content/

# ์Šคํฌ์ผ๋Ÿฌ ํ•„ํ„ฐ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ (state N์—์„œ N+1 ์ •๋ณด ๋ˆ„์ถœ ์—ฌ๋ถ€)
python3 -m pytest tests/test_spoiler_scope.py -v

# ํ‰๊ฐ€ ํ•˜๋„ค์Šค (LLM judge: ์›์ž‘ ์ถฉ์‹ค๋„ / ๋ฌธ์ฒด / ์ˆ˜๋ ด ์„ฑ๊ณต๋ฅ )
python3 -m eval.run --scene <scene_id> --n 10

# Docker ๋นŒ๋“œ ํ™•์ธ
docker compose up -d --build backend
docker compose logs --tail=20 backend
  • ํ”„๋กฌํ”„ํŠธ ๋ณ€๊ฒฝ์€ ํ‰๊ฐ€ ํ•˜๋„ค์Šค ์ ์ˆ˜์™€ ํ•จ๊ป˜ PR์— ์ฒจ๋ถ€ํ•œ๋‹ค (๊ฐ์ด ์•„๋‹ˆ๋ผ ์ˆซ์ž๋กœ)
  • ํ„ด ๋กœ๊ทธ๋Š” ์‚ญ์ œํ•˜์ง€ ์•Š๋Š”๋‹ค (์—ฐ๊ตฌ ๋ฐ์ดํ„ฐ)

์ปค๋ฐ‹ ๊ทœ์น™

  • main์— ์ง์ ‘ ํ‘ธ์‹œ ๊ธˆ์ง€. ์ž‘์—…์€ ๋ฌด์กฐ๊ฑด feature ๋ธŒ๋žœ์น˜์—์„œ ํ•˜๊ณ  PR๋กœ ์˜ฌ๋ฆฐ๋‹ค
  • ํŒŒ์ผ ์ˆ˜์ • ์ „ ๋ฐ˜๋“œ์‹œ Read๋กœ ํ˜„์žฌ ๋‚ด์šฉ ํ™•์ธ
  • ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ "๋ณต์‚ฌ โ†’ ์ „ํ™˜ โ†’ ์‚ญ์ œ" ์ˆœ์„œ (๊ธฐ์กด ๊ธฐ๋Šฅ ์œ ์ง€)
  • Docker ๋นŒ๋“œ๋กœ ๊ฒ€์ฆ ํ›„ ์ปค๋ฐ‹
  • ๋น„ํŠธ์นด๋“œ MD ์›๋ณธ๊ณผ content/ ํ•˜์œ„ ์ฝ˜ํ…์ธ  ๋‚ด์šฉ์€ ๊ฐœ๋ฐœ ์ปค๋ฐ‹์—์„œ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค (์ฝ˜ํ…์ธ  ๋ณ€๊ฒฝ์€ ๋ณ„๋„ PR + ๋Œ€ํ‘œ๋‹˜ ์Šน์ธ. ๋ณ€ํ™˜ ์Šคํฌ๋ฆฝํŠธ ์žฌ์‹คํ–‰ ์‚ฐ์ถœ๋ฌผ ๊ฐฑ์‹ ์€ ์˜ˆ์™ธ)

์ฐธ๊ณ  ๋งํฌ