Spaces:
Sleeping
Sleeping
| # sorac ์นด๋ฅด๋ฐ๋ผ โ ์ ์ฒด ๊ตฌ์กฐ ๋ช ์ธ์ | |
| > ์ด ๋ฌธ์ ํ๋๋ก "๋ฌด์จ ํ์ผ์ด ์ด๋ ์๊ณ , ์ด๋ค ์ญํ ์ด๋ฉฐ, ์ด๋ป๊ฒ ๋ง๋ฌผ๋ ค ๋๋์ง"๋ฅผ ํ์ ํ ์ ์๊ฒ ์ด ๋ช ์ธ์. | |
| > ๋ง์ง๋ง ๊ฐฑ์ : 2026-07-03 (MVP M0~M6 ์๋ฃ ์์ ) | |
| --- | |
| ## 0. ํ ๋ฌธ์ฅ ์์ฝ | |
| **๋นํธ์นด๋(๋ํ๋์ด ์ด ์์ฌ ์๊ณ )๋ฅผ YAML๋ก ๋ณํํด ์ ์ฌํ๊ณ , ํ์ด์ฌ ์ํ๋จธ์ ์ด "์ฑ ์ฝ๊ธฐ"๋ฅผ, | |
| google-adk LLM ํ์ดํ๋ผ์ธ์ด "์นด๋ฅด๋ฐ๋ผ์์ ๋ํ"๋ฅผ ๋ด๋นํ๋ฉฐ, ์น ๋ฆฌ๋๊ฐ ๊ทธ๊ฑธ ํ๋ฉด์ ๊ทธ๋ฆฐ๋ค.** | |
| ``` | |
| [๋ํ๋ ๋นํธ์นด๋ MD] โโ๋ณํโโโถ [content/ YAML] โโ์ ์ฌโโโถ โโ StoryEngine (์ฝ๊ธฐ: ๊ฒฐ์ ์ ์ฝ๋) | |
| โโ PocketService (๋ํ: ADK LLM) | |
| โ | |
| [web/index.html] โโโHTTPโโ [router.py] | |
| ``` | |
| ํต์ฌ ์ค๊ณ ์์น (CLAUDE.md): | |
| - **์์ฌ ์งํ์ ์ฝ๋๊ฐ, ๋์ฌ ์์ฑ๋ง LLM์ด** ํ๋ค โ ์คํ ๋ฆฌ๊ฐ ์ฐ์ผ๋ก ๊ฐ์ง ์๊ฒ | |
| - **์จ์ ์ํ(์ ๋ขฐ/์์ฌ/์ ์ฝ)๋ ์ ์ ์๊ฒ ์ ๋ ์ซ์๋ก ๋ ธ์ถํ์ง ์๋๋ค** | |
| - **์ฝํ ์ธ (YAML)์ ์ฝ๋์ ๋ถ๋ฆฌ** โ ์ด์ผ๊ธฐ ์์ ์ ์ฝํ ์ธ ์ชฝ์์๋ง | |
| --- | |
| ## 1. ๋ฃจํธ ํด๋ ์ง๋ | |
| | ๊ฒฝ๋ก | ๋๊ฐ ๋ง๋ ๊ฒ | ์ญํ | | |
| |---|---|---| | |
| | `CLAUDE.md` | ๊ณต๋ | ๊ฐ๋ฐ ํ๋ฒ. ์ํคํ ์ฒ ๊ท์นยท๊ธ์ง์ฌํญ. Claude Code๊ฐ ๋งค ์ธ์ ์ฝ์ | | |
| | `engine/` | Claude | **์์ง ๋ณธ์ฒด** (ํ์ด์ฌ ํจํค์ง) โ ์๋ ยง3 | | |
| | `content/` | ๋ณํ๊ธฐ ์ฐ์ถ๋ฌผ | **์์ง์ด ์ฝ๋ ์์ฌ ๋ฐ์ดํฐ** (YAML) โ ์๋ ยง2 | | |
| | `web/` | Claude | ์น ๋ฆฌ๋ ํ๋ก ํธ (๋จ์ผ HTML) | | |
| | `scripts/` | Claude | ๊ฐ๋ฐ์ฉ ์คํฌ๋ฆฝํธ (๋ณํ๊ธฐ, CLI ํ๋ ์ด์ด) | | |
| | `tests/` | Claude | ์๋ ํ ์คํธ 18๊ฐ | | |
| | `docs/` | Claude | ๊ณํ์ยทAPI ๊ณ์ฝยท๋ณธ ๋ช ์ธ์ | | |
| | `logs/` | ๋ฐํ์ ์์ฑ | ํ๋ ์ด ํด ๋ก๊ทธ (์ฐ๊ตฌ ๋ฐ์ดํฐ, git ์ ์ธ) | | |
| | `data/` | ๋ฐํ์ ์์ฑ | ํ๋ ์ด ์ธ์ ์์ํ SQLite (`sessions.db`, git ์ ์ธ) | | |
| | `.env` | ์ ์ | OPENAI_API_KEY (git ์ ์ธ โ ์ ๋ ์ปค๋ฐ ๊ธ์ง) | | |
| | `.venv/` | ์ค์น ์ฐ์ถ๋ฌผ | ํ์ด์ฌ ๊ฐ์ํ๊ฒฝ (google-adk 2.3.0 ๋ฑ) | | |
| | `์นด๋ฅด๋ฐ๋ผ/` | ๋ํ๋ | **์ฝํ ์ธ ์์ฒ** โ ๋นํธ์นด๋ MDยท์๊ณ ยท๊ธฐํยท์ด๋ฏธ์ง | | |
| | `reference/` | ๋ํ๋(ํ๋กํ ํ์ ) | ๋น-ADK ๊ฐ๋ ๊ฒ์ฆ ์์ง. **์ฐธ๊ณ ์ฉ, ์คํ ์ ํจ** | | |
| | `requirements.txt` / `pyproject.toml` | Claude | ์์กด์ฑ ๋ชฉ๋ก / pytest ์ค์ | | |
| --- | |
| ## 2. content/ โ ์์ง์ด ์ฝ๋ ์์ฌ ๋ฐ์ดํฐ (3๋ ์ด์ด) | |
| **์์ง์ ์ ์ผํ ์์ฌ ์ ๋ ฅ.** ์์ฒ์ `์นด๋ฅด๋ฐ๋ผ/0701_์นด๋ฅด๋ฐ๋ผ/๋นํธ์นด๋/`์ MD ํ์ผ(๋ํ๋ ์์ )์ด๊ณ , | |
| ์ฌ๊ธฐ ์๋ YAML์ `scripts/beatcard_convert.py`๊ฐ ๋ง๋ **ํ์๋ฌผ**์ด๋ค. ์ด์ผ๊ธฐ๋ฅผ ๊ณ ์น๋ ค๋ฉด | |
| MD๋ฅผ ๊ณ ์น๊ณ ๋ณํ๊ธฐ๋ฅผ ๋ค์ ๋๋ฆฐ๋ค (YAML ์ง์ ์์ ๊ธ์ง). | |
| | ํ์ผ | ๋ ์ด์ด | ๋ด์ฉ | | |
| |---|---|---| | |
| | `content/beatcards/E01.yaml` ~ `E06.yaml` | ๋ด๋ฌํฐ๋ธ | **๋นํธ์นด๋ 61์ฅ.** ์นด๋ = ํ๋ฉด ํ ์ฅ. ์ง๋ฌธ ๋ธ๋ก(prose/์ค์๋์ฌ/์ธ๋ฌผ๋์ฌ/๋ฌด๋์ง์), ์ ํ์ง(๋ผ๋ฒจยท๊ฒฐ๊ณผ์ฐ๋ฌธยท์ ๋ขฐ์ถยท๋ถ๊ธฐ), next ๋งํฌ, ํ๊ทธ | | |
| | `content/beatcards/R-ํํผ์ฌ๋ค๋ฆฌ.yaml` | ๋ด๋ฌํฐ๋ธ | ์ ์ ๊ฐ ์ ์ฒด๋ฅผ ์ง๊ฒฉํ์ ๋ ์นด๋ฅด๋ฐ๋ผ์ 3๋จ๊ณ ํํผ ๋์ (์ฌ์ฌ์ฉ ์นด๋) | | |
| | `content/beatcards/R-์ฝ์ ๋ฐ์.yaml` | ๋ด๋ฌํฐ๋ธ | ์ฑ๋ฌผยท๋ง๋ ๋ฑ ์ฝ์ ์ธ๊ธ ์ ๋ฐ์ ์์น | | |
| | `content/chapters/carmilla_chapter1.yaml` | ์ฑํฐ ๋ฉํ | ์์ ์นด๋(E01-01), ๋ฒ์(E01~E06), **์๋ฉ 3์ข ๊ท์น**, ์บ๋ ผ ๊ท์น, R์นด๋ ๋ฑ๋ก, ์ฑํ ํด ์ํ(6) | | |
| | `content/characters.yaml` | ์ ์ฒด์ฑ | ์ธ๋ฌผ ์ํธ 3๋ช (๋ก๋ผ=ํ๋ ์ด์ด, ์นด๋ฅด๋ฐ๋ผ, ์๋ฒ์ง). ์ฑ๊ฒฉยทํ๋ฒยท์ง์๊ฒฝ๊ณ | | |
| **์นด๋ ํ ์ฅ์ ๊ตฌ์กฐ (์)**: | |
| ```yaml | |
| - id: E06-09 # ์นด๋ ID | |
| title: ๋๋ง์ ๋น๋ฐ | |
| canon: true # ์์ ๋ผ๋ (๋ถ๋ณ) | |
| next: E06-10 # ๋ค์ ์นด๋ | |
| axis: null # ์นด๋ ์ง์ ์ ์ ๋ขฐ์ถ ์ด๋ (trust/doubt/null) | |
| frailty: false # ์ด ์นด๋์์ ์ ์ฝ๋ +1 ์ฌ๋ถ | |
| narration: # ์ง๋ฌธ ๋ธ๋ก๋ค (ํ๋ฉด์ ๊ทธ๋๋ก ์ถ๋ ฅ) | |
| - {type: prose, text: "..."} | |
| - {type: dialogue_center, text: "์ฃฝ์์ ์คํ๋ ค ์ถ๋ณต์ผ ์๋ ์์ด."} | |
| - {type: dialogue_center, text: "...์์์ด์ผ.", gate: trust_high} # ์ ๋ขฐ้ซ ์ ์ฉ(์ฌ์ธต ํด๊ธ) | |
| - {type: character_dialogue, speaker: ์นด๋ฅด๋ฐ๋ผ, text: "...", anchor: true} # ์ฑํ ๋ณ์ฃผ ๊ธฐ์ค | |
| choices: | |
| - {label: ๋๋ง์ ๋น๋ฐ์ ๋์กฐํ๋ค, axis: trust, result: "...", next: null} | |
| - {label: ๊ทธ๋๋ ๋๊ตฐ๊ฐ์๊ฒ ์๋ ค์ผ..., axis: doubt, result: "...", next: null} | |
| ``` | |
| --- | |
| ## 3. engine/ โ ์์ง ๋ณธ์ฒด | |
| ### 3-1. ์ต์์ ํ์ผ | |
| | ํ์ผ | ์ญํ | | |
| |---|---| | |
| | `model.py` | LLM ์ค์ . ๋ฃจํธ `.env`๋ฅผ ๋ก๋ฉํ๊ณ LiteLLM์ผ๋ก ๋ชจ๋ธ ์ด๊ธฐํ (`LLM_MODEL`/`CHARACTER_MODEL` env๋ก ๊ต์ฒด ๊ฐ๋ฅ, ๊ธฐ๋ณธ openai/gpt-5.4-nano) | | |
| | `agent.py` | **ADK ํ์ดํ๋ผ์ธ ์กฐ๋ฆฝ**: `pocket_session = Sequential[๋ก๋ โ ๊ฐ๋ โ ์นด๋ฅด๋ฐ๋ผ โ ๋๋ ํฐ]`. `adk web`์ฉ `root_agent`๋ ์ฌ๊ธฐ์ ๋ ธ์ถ | | |
| | `app.py` | ADK `App`(์ด๋ฆ+๋ฃจํธ์์ด์ ํธ+ํ๋ฌ๊ทธ์ธ) + `Runner`(์ธ์ ์๋น์ค) ์์ฑ | | |
| | `router.py` | **FastAPI ์๋ฒ.** HTTP ์๋ํฌ์ธํธ(์ธ์ /advance/choose/pocket) + ์น ๋ฆฌ๋ยท์ด๋ฏธ์ง ์๋น. ์ฐ์ถ ์ด๋ฏธ์ง ๋งคํ(`IMAGE_MAP`)๋ ์ฌ๊ธฐ | | |
| ### 3-2. services/ โ ๋น์ฆ๋์ค ๋ก์ง (๊ฒ์ ๊ท์น์ ์ฌ์ฅ) | |
| | ํ์ผ | ์ญํ | | |
| |---|---| | |
| | `story.py` | **์ฝ๊ธฐ ์ํ๋จธ์ .** ์นด๋ ์ ์ด(next), ์ ํ์ง ๋ฐ์, ์ฌ์ธต ๊ฒ์ดํธ ๋ ธ์ถ ํ์ , **์๋ฉ ์กฐ๊ฑด์ ํ๊ฐ**(์ ์ฉ ํ์ โ eval ๊ธ์ง). ๋๋ฌ์ ๋ณด์ฅ(preset_branch) ๋ก์ง ํฌํจ | | |
| | `state.py` | **์จ์ ์ํ 3๊ฐ.** ์ ๋ขฐ์ถ ์์(trust/doubt, ์ด๋ฒคํธ๋น ยฑ12), ์ ์ฝ๋(๋จ์กฐ ์ฆ๊ฐ๋ง ํ์ฉ), ์(๋์ 1ํ ํ๋ฆฝ). ์ ๋ขฐ ๋ ๋ฒจ ํ์ฐ(ยฑ12/ยฑ36 ๊ฒฝ๊ณ โ 5๋จ๊ณ) โ ์ด ๋ ๋ฒจ์ด ์นด๋ฅด๋ฐ๋ผ์ "๊ฒฐ"๊ณผ ์ฌ์ธต ํด๊ธ์ ๊ฒฐ์ | | |
| | `pocket.py` | **์ฑํ ์ธ์ ๊ด๋ฆฌ.** ํฌ์ผ ์ด๊ธฐ(์นด๋ฅด๋ฐ๋ผ ์นด๋๋ง)/ํด ์คํ/๋ซ๊ธฐ(์ํ ์ปค๋ฐ). **์๋ ด ์ฌ๊ฒ์ฆ**: LLM์ด "๋๋ด์"๊ณ ํด๋ ์ฝ๋๊ฐ ๊ฒ์ฌ โ ์ต์ 3ํด ์ฒด๋ฅ, ๋นํธ ์๋ ์ข ๋ฃ ๊ธฐ๊ฐ, 6ํด ๊ฐ์ ์๋ ด | | |
| | `paging.py` | **ํ์ด์ง ๋ณํฉ.** ์ฌ๋ฌ ์นด๋๋ฅผ ํ ํ๋ฉด์ผ๋ก โ ๊ฒฐ์ ์ ํํฐ์ (์ ํ ๋งํฌ ๊ตฌ๊ฐ๋ง, ์ ํ์งยท์ฐ์ถ ์ปทยท์ฑํ ์ต์ปคยท์ํผ์๋ ๊ฒฝ๊ณ์์ ๋ถ๋ฆฌ, `page_target_chars` ์ํํธ ์บก). ๋ถ์ํจ๊ณผ๋ ์ฌ์ ํ story.py๊ฐ ์นด๋๋น 1ํ | | |
| | `retrieval.py` | (2๋จ๊ณ ์คํ ) GraphRAG ๊ฒ์ ์๋ฆฌ. ์คํฌ์ผ๋ฌ ํํฐ ๊ฐ์ ์ง์ | | |
| ### 3-3. sub_agents/ + agents/ โ ADK ๋ํ ํ์ดํ๋ผ์ธ (์ฑํ ํ ํด์ 4๋ช ์ด ์์๋๋ก ์ผํจ) | |
| ``` | |
| ์ ์ ์ ๋ ฅ โ โ pocket_context_loader โ โก input_guard โ โข carmilla โ โฃ director โ ์๋ต | |
| ``` | |
| | ์์ | ํ์ผ | ํ์ | ํ๋ ์ผ | | |
| |---|---|---|---| | |
| | โ | `agents/pocket_context_loader.py` | ์ฝ๋(BaseAgent) | ํ์ฌ ์นด๋์ ์ง๋ฌธยท์ต์ปค๋์ฌยท์ ๋ขฐ ๋ ๋ฒจ๋ณ "๊ฒฐ"์ ์ธ์ ์ ์ ์ฌ. **์บ๋ฆญํฐ์ฉ ๋ธ๋ฆฌํ์ ๋๋ ํฐ์ฉ ๋ ธํธ๋ฅผ ๋ถ๋ฆฌ** (์คํฌ์ผ๋ฌ ๋์ ๋ฐฉ์ง) | | |
| | โก | `agents/input_guard.py` | ์ฝ๋(BaseAgent) | ์ ์ ์ ๋ ฅ ๊ฒ์ฌ: ์ ์ฒด ์ง๊ฒฉ("ํกํ๊ท์ง?") โ R-ํํผ์ฌ๋ค๋ฆฌ ๋จ๊ณ ์ง์, ์ฝ์ (์ญ์๊ฐ ๋ฑ) โ R-์ฝ์ ๋ฐ์ ์ง์, ๋ฉํ ๋ฐํ(AI/๊ฒ์) โ ์ธ๊ณ๊ด ๋ณต๊ท ์ง์ | | |
| | โข | `sub_agents/character/agent.py` | **LLM** | ์นด๋ฅด๋ฐ๋ผ ์๋ต ์์ฑ. instruction = `instructions/character_carmilla.md` + ์ธ๋ฌผ์ํธ + ์ฅ๋ฉด๋ธ๋ฆฌํ + ๊ฐ๋์ง์ | | |
| | โฃ | `sub_agents/director/agent.py` | **LLM** | ์ฌํ. ์ ์ ์๊ฒ ์ ๋ณด์. ๋๊ตฌ ํธ์ถ๋ก๋ง ๊ธฐ๋ก: ์ ๋ขฐ์ถ ์ด๋/๋ชฉํ ๋นํธ ๋ฌ์ฑ/์๋ ด ์ ์ธ | | |
| ### 3-4. tools/state/ โ ๋๋ ํฐ๊ฐ ์ฐ๋ ๋๊ตฌ 3์ข (1ํ์ผ 1ํจ์) | |
| | ํ์ผ | ๊ธฐ๋ฅ | | |
| |---|---| | |
| | `update_trust.py` | ์ด๋ฒ ํด ๋ํ๋ก ์ ๋ขฐ/์์ฌ ์ถ์ ยฑ12 ์ด๋ | | |
| | `mark_beat.py` | ์ฅ๋ฉด ๋ชฉํ ๋นํธ ๋ฌ์ฑ ๊ธฐ๋ก | | |
| | `finish_pocket.py` | ์๋ ด ์ ์ธ(escalate) โ ๋จ **์ต์ข ๊ฒฐ์ ์ pocket.py ์ฝ๋๊ฐ ์ฌ๊ฒ์ฆ** | | |
| ### 3-5. ๋๋จธ์ง | |
| | ๊ฒฝ๋ก | ์ญํ | | |
| |---|---| | |
| | `schemas/` | ์ฝํ ์ธ YAML์ Pydantic ์คํค๋ง(beatcard/chapter/character) + **validate CLI** (`python -m engine.schemas.validate content/` โ ์คํค๋งยท๋งํฌ ๋ฌด๊ฒฐ์ฑยท๋๋ฌ์ฑ ๊ฒ์ฌ) | | |
| | `repositories/content.py` | YAML โ ๋ฉ๋ชจ๋ฆฌ ์ ์ฌ. **์คํฌ์ผ๋ฌ ํํฐ**(ํ์ฌ ์นด๋ ์ดํ ์กฐํ ์ฐจ๋จ). ์นด๋ ์์/์ ์ฒด ์(์งํ๋์ฉ). ์ฝํ ์ธ ๋ฒ์ ์คํฌํ(`content_version`) | | |
| | `repositories/playlog.py` | ํด ๋ก๊ทธ jsonl ์ ์ฅ (`logs/playlog.jsonl`, ์ฐ๊ตฌ ๋ฐ์ดํฐ) | | |
| | `repositories/play_session.py` | **์ธ์ ์์ํ** โ ์์ฌ ์์น+์จ์ ์ํ ์ค๋ ์ท์ SQLite(`data/sessions.db`)์ ๋งค ๋ณ๊ฒฝ ์ฆ์ ์ ์ฅ. ์๋ฒ ์ฌ์์ ํ ๋ณต์(ํฌ์ผ ์ฑํ ์ ๋น์์ยทํ๊ธฐ). ์ฝํ ์ธ ๋ฒ์ ๋ถ์ผ์น ์ธ์ ์ ๋ฌดํจํ | | |
| | `plugins/logging.py` | ADK ํ๋ฌ๊ทธ์ธ โ ์ฑํ ์ ์ ์ ์ ๋ ฅ/์๋ต/๋๊ตฌํธ์ถ/์ํ๋ณํ๋ฅผ ์๋ ๊ธฐ๋ก | | |
| | `instructions/` | LLM ํ๋กฌํํธ ์๋ฌธ (์นด๋ฅด๋ฐ๋ผ ์ธ๊ฒฉ ๊ท์น, ๋๋ ํฐ ํ์ ์ ์ฐจ). **๋งํฌยทํ๋ ๊ท์น์ ๊ณ ์น๋ ค๋ฉด ์ฌ๊ธฐ** | | |
| | `callbacks/` | (๋น ์ค์บํด๋) before_model ์คํฌ์ผ๋ฌ ์ต์ข ๋ฐฉ์ด์ ์๋ฆฌ โ 2๋จ๊ณ | | |
| --- | |
| ## 4. web/ โ ์น ๋ฆฌ๋ (ํ๋ก ํธ์๋) | |
| `web/index.html` ๋จ์ผ ํ์ผ (๋ฐ๋๋ผ JS, ๋น๋ ์์). router.py๊ฐ ์๋น. | |
| - **์ฝ๊ธฐ**: ํ๋ฉด ๋จ์ ํ์ด์ง โ ์คํฌ๋กค ์์ด ๋๊น์ผ๋ก๋ง ์ฝ๋๋ค. ์๋ฒ ํ์ด์ง(์ฌ๋ฌ ์นด๋ ๋ณํฉ, `paging.py`๊ฐ run๋ณ ๊ท ํ ๋ถํ โ ๋ชฉํ 500์, ๋ณํฉ ํ์ด์ง ๋ฐด๋ ์ฝ 360~760์)๋ฅผ ํ๋ก ํธ๊ฐ ๋ทฐํฌํธ ์ค์ธก(`#page-measure`)์ผ๋ก ํ๋ฉด๋ค๋ก ๋ถํ (`buildScreens`), ๊ฐ์ ์งํ๋ ๋ฒํธ๋ฅผ ๊ณต์ . ์ฐ์ธก ํด๋ฆญ/โ = ๋ค์, ์ข์ธก ํด๋ฆญ/โ = ์ด์ (์ฝ๊ธฐ ์ ์ฉ ๋๋์๋ณด๊ธฐ โ ์ ํ ๋ฒ๋ณต ๋ถ๊ฐ). ์ฐ์ถ ์ปท(์ด๋ฏธ์ง)์ ์์ฒด ๋ฒํธ๋ฅผ ๊ฐ์ง ๋ ๋ฆฝ ํ์ด์ง | |
| - **ํ์ดํ**: ์ํผ์๋ ์ฒซ ํ์ด์ง์ "์ Nํ" ํค๋๋ง ํ์ โ ์นด๋ ์์ ๋ชฉ์ ๋ ๋ํ์ง ์๋๋ค(์ฑ ์ ํ๋ฆ). ์ปท ์บก์ ์ ์ ์ง | |
| - **์ด์ด ์ฝ๊ธฐ**: ์ธ์ ID๋ฅผ `localStorage`์ ๋ณด๊ด โ ์๋ก๊ณ ์นจยท์๋ฒ ์ฌ์์ ํ์๋ ํ์ฌ ํ์ด์ง๋ถํฐ ์ฌ๊ฐ (ํ์ง ๋ฒํผ์ด "๋ฐค์ ์๋๋ค"๋ก ๋ฐ๋). ์๋ฉ์์ ์ฌ์์ํ๋ฉด ํ๊ธฐ | |
| - **์บ๋ฆญํฐ ๋ฐํ(ํ์ด๋ธ๋ฆฌ๋)**: ์์ฌ ์ `character_dialogue`๋ ์ปดํฉํธ char-line(์ข ํ์๋ช ยท ์ฐ ๋์ฌ)์ผ๋ก ๋ณธ๋ฌธ์ ํก์ โ ์ฑ ์ ํ๋ฆ ์ฐ์ . ์ ์ ยท๋ก๋ผ๋ "๋"๋ก ํ๊ธฐ. `dialogue_center`๋ ์ค์ ์ธ์ฉ ์คํ์ผ ์ ์ง | |
| - **VN ์คํ ์ด์ง**: ์ธํฐ๋์ ํ์ด์ง(์ ํ์งยท์ฑํ )์ ๋ง์ง๋ง ์ด์ ๋ฐํ๋ง ์คํ ์ด์ง(๋จ๋ ํ๋ฉด) โ ์ด์์ด ํ๋ ์(76vh)์ ์ฑ์ฐ๊ณ ํ๋จ ํจ๋์ ์ฅ์ ์ด๋ฆํยท๋์ฌ, ๋ง์ง๋ง ๊ทธ๋ฃน์ด๋ฉด ์ ํ์งยท๋ง๊ฑธ๊ธฐ ๋ฒํผ ๋ด์ฅ. **๋ฐํ ์ง์ ์ ์งง์ ๋์ prose(role=lead_in, 140์ ์ดํ ์๋ ์ ๋)๋ ์คํ ์ด์ง ์๋จ์ ์นํ๋ค** โ ํ ๋ฌธ์ฅ ํํธ ํ๋ฉด ๋ฐฉ์ง. ์ฑํ ๋๋ก์ด๊ฐ ์ด๋ฆฌ๋ฉด ์ด์ ์์์ ๋ํํ๋ ๊ตฌ๋ (์ฐธ๊ณ : ํ๋์ด ์ด๋ฏธ์ง/7.png) | |
| - **์ ํ์ง**: `โบ` ๋ง์ปค + ๊ฐ๋ ๊ตฌ๋ถ์ ๋ฆฌ์คํธ (๋ํํ๋ฏ). ์ ํ ๊ฒฐ๊ณผ ์ฐ๋ฌธ ํ์ ํ ์ฐ์ธก ํด๋ฆญ์ผ๋ก ์งํ | |
| - **์ฑํ **: ์นด๋ฅด๋ฐ๋ผ ์นด๋์์ "โฆ๋ง์ ๊ฑด๋ค" โ ํ๋จ ๋๋ก์ด. ์๋ ด ์ ์ฌ์ด(3.4์ด) ํ ์๋ ๋ณต๊ท | |
| - **์ฐ์ถ**: ์๋จ ์งํ ๋ฐ(n/์ดํ์ด์ง = ์นด๋ 61 + ์ปท 5 = 66), ์ค์ ์ฅ๋ฉด ์ด๋ฏธ์ง ํ์ด์ง, ํ์ง | |
| - ์ด๋ฏธ์ง ์๋ณธ: `์นด๋ฅด๋ฐ๋ผ/0630โฆ/์ด๋ฏธ์ง/` (router์ `/assets`๋ก ์๋น, ๋งคํ์ `IMAGE_MAP`) | |
| --- | |
| ## 5. scripts/ ยท tests/ ยท docs/ | |
| | ๊ฒฝ๋ก | ์ญํ | | |
| |---|---| | |
| | `scripts/beatcard_convert.py` | **๋นํธ์นด๋ MD โ content/ YAML ๋ณํ๊ธฐ.** ์ ํ์ง-๊ฒฐ๊ณผ ๋งค์นญ, ๋ถ๊ธฐ ํด์, ๋๋ฌ์ ์ฐํ ๋ฐฉ์ง. ์ฝํ ์ธ ๊ฐฑ์ ์ ์ฌ์คํ | | |
| | `scripts/play.py` | ํฐ๋ฏธ๋ ํ๋ ์ด์ด. `--policy trust/doubt/neutral --quiet`๋ก ์๋ฉ 3์ข ์๋ ๊ฒ์ฆ ๊ฐ๋ฅ | | |
| | `tests/` (18๊ฐ) | `test_story.py` ์ฝ๊ธฐ/์๋ฉ/์คํฌ์ผ๋ฌ ยท `test_pocket.py` ์ฑํ ํ์ดํ๋ผ์ธ(๊ฐ์ง LLM์ผ๋ก ๊ฒฐ์ ์ ๊ฒ์ฆ) ยท `test_integration.py` ์ฝ๊ธฐโ์ฑํ โ์๋ฉ ์ ์ฒด ๊ดํต ยท `fakes.py` ์คํฌ๋ฆฝํธ LLM ํ ์คํธ ๋๋ธ | | |
| | `docs/plans/mvp.md` | ๋ง์ผ์คํค M0~M6 ๊ณํยท์ํยท๊ฒฐ์ ๋ก๊ทธ | | |
| | `docs/contract/demo_api.md` | ์น ๋ฆฌ๋ โ ์๋ฒ HTTP API ๊ณ์ฝ | | |
| | `docs/contract/beatcard_guide.md` | ๋นํธ์นด๋ ์์ฑ ๊ฐ์ด๋ (๊ถ์ฅ ๋ถ๋ยท3๋ง ํจํดยท๋ฆฌ๋์ธ ๊ท์น โ E07~ ์งํ ์ฐธ๊ณ , validate ๊ฒฝ๊ณ ์ ์ฐ๋) | | |
| | `docs/ARCHITECTURE.md` | ๋ณธ ๋ฌธ์ | | |
| --- | |
| ## 6. ๋ฐํ์ ํ๋ฆ (์ค์ ๋ก ์ด๋ป๊ฒ ๋๋) | |
| ### ์ฝ๊ธฐ ํด (LLM ์์, 0์) | |
| ``` | |
| [ํฐ/๋ธ๋ผ์ฐ์ ] ์ฐ์ธก ํด๋ฆญ | |
| โ POST /api/session/{id}/advance (router.py) | |
| โ StoryEngine.advance() (story.py) | |
| ์นด๋ next ๋ฐ๋ผ ์ด๋ ยท ์ ์ฝ๋/์ ๋ขฐ์ถ ๋ฐ์ ยท ์ฌ์ธต ๊ฒ์ดํธ ํ์ | |
| โ ์นด๋ JSON ์๋ต โ ํ๋ฉด ๋ ๋ | |
| ``` | |
| ### ์ ํ ํด (LLM ์์) | |
| ``` | |
| ์ ํ์ง ํด๋ฆญ โ POST /choose {index} | |
| โ StoryEngine.choose(): ์ ๋ขฐ์ถ ยฑ12 ยท ๋ถ๊ธฐ(next/preset) ๊ฒฐ์ ยท ๊ฒฐ๊ณผ ์ฐ๋ฌธ ๋ฐํ | |
| ``` | |
| ### ์ฑํ ํด (LLM 2ํ ํธ์ถ โ ์นด๋ฅด๋ฐ๋ผ 1 + ๋๋ ํฐ 1) | |
| ``` | |
| "โฆ๋ง์ ๊ฑด๋ค" โ POST /pocket/open โ PocketService.open() (์นด๋ฅด๋ฐ๋ผ ์นด๋ ๊ฒ์ฆ + ์ํ ์๋ฉ) | |
| ๋ฉ์์ง ์ ์ก โ POST /pocket/turn | |
| โ ADK Runner: ๋ก๋ โ ๊ฐ๋ โ ์นด๋ฅด๋ฐ๋ผ(LLM) โ ๋๋ ํฐ(LLM+๋๊ตฌ) | |
| โ PocketService๊ฐ ์๋ ด ์ฌ๊ฒ์ฆ (3ํด ๋ฏธ๋ง ๊ธฐ๊ฐ / 6ํด ๊ฐ์ ์ข ๋ฃ) | |
| โ ์๋ ด ์: ์จ์ ์ํ๋ฅผ ๋ณธํธ์ ์ปค๋ฐํ๊ณ ์ฝ๊ธฐ๋ก ๋ณต๊ท | |
| ``` | |
| ### ์๋ฉ (LLM ์์) | |
| ``` | |
| E06 ๋(โE07 ์ฐธ์กฐ) ๋๋ฌ โ story.py๊ฐ ending_rules ํ๊ฐ: | |
| doubt โฅ 30 โ โ ์ฒ๋จ (์์ ์บ๋ ผ) | |
| trust โฅ 60 & doubt<20 โ โก ์์ (๋คํฌ ๋ก๋งจ์ค) | |
| ๊ทธ ์ธ โ โข ๊ตฌ์ (์ด๋ณ) | |
| ``` | |
| --- | |
| ## 7. ์คํ ๋ช ๋ น ๋ชจ์ | |
| ```bash | |
| # ์น ๋ฆฌ๋ (ํฐ ์ ์ ํ์ฉ) | |
| .venv/bin/uvicorn engine.router:app --host 0.0.0.0 --port 8777 | |
| # ํฐ๋ฏธ๋ ํ๋ ์ด / ์๋ ์๋ฉ ๊ฒ์ฆ | |
| .venv/bin/python scripts/play.py | |
| .venv/bin/python scripts/play.py --policy trust --quiet | |
| # ์ฝํ ์ธ ๊ฐฑ์ (๋นํธ์นด๋ MD ์์ ํ) | |
| .venv/bin/python scripts/beatcard_convert.py "์นด๋ฅด๋ฐ๋ผ/0701_์นด๋ฅด๋ฐ๋ผ/๋นํธ์นด๋" -o content/beatcards | |
| .venv/bin/python -m engine.schemas.validate content/ | |
| # ํ ์คํธ | |
| .venv/bin/python -m pytest tests/ -q | |
| ``` | |
| --- | |
| ## 8. "์ด๊ฑธ ๋ฐ๊พธ๊ณ ์ถ์ผ๋ฉด ์ด๋๋ฅผ ๋ณด๋" ์นํธ์ํธ | |
| | ํ๊ณ ์ถ์ ๊ฒ | ๋ณด๋ ๊ณณ | | |
| |---|---| | |
| | ์ด์ผ๊ธฐ ๋ณธ๋ฌธยท์ ํ์ง ์์ | ๋นํธ์นด๋ MD (`์นด๋ฅด๋ฐ๋ผ/0701โฆ/๋นํธ์นด๋/`) โ ๋ณํ๊ธฐ ์ฌ์คํ | | |
| | ์นด๋ฅด๋ฐ๋ผ ๋งํฌยทํ๋ ๊ท์น | `engine/instructions/character_carmilla.md` | | |
| | ๋ํ ์ข ๋ฃ(์๋ ด) ํ์ ๊ธฐ์ค | `engine/instructions/director.md` + `engine/services/pocket.py` (MIN_TURNS ๋ฑ) | | |
| | ์๋ฉ ์กฐ๊ฑด ์ซ์ | `content/chapters/carmilla_chapter1.yaml` (ending_rules) | | |
| | ์ ๋ขฐ์ถ ์ด๋๋ยท๋ ๋ฒจ ๊ฒฝ๊ณ | `engine/services/state.py` (AXIS_DELTA, trust_level) | | |
| | ์ฑํ ํด ์ํ | `content/chapters/carmilla_chapter1.yaml` (pocket_max_turns) | | |
| | ํ์ด์ง๋น ๊ธ ๋ฐ๋ | `content/chapters/carmilla_chapter1.yaml`์ `page_target_chars` (๋ฏธ์ค์ ์ 500) | | |
| | ์ฐ์ถ ์ด๋ฏธ์ง ์ถ๊ฐยท๊ต์ฒด | `engine/router.py` (IMAGE_MAP) + `scripts/sync_assets.py` ์ฌ์คํ (์์ฒ: ์นด๋ฅด๋ฐ๋ผ/0701_์นด๋ฅด๋ฐ๋ผ/์ด๋ฏธ์ง/) | | |
| | ์ฅ์๋ณ ๋ฐฐ๊ฒฝ ์ถ๊ฐยท๊ต์ฒด | `engine/router.py` (BACKGROUND_MAP โ ์นด๋ `#์ฅ์/` ํ๊ทธ ๊ธฐ์ค) + sync_assets | | |
| | ํ์ ์ด์ ์ถ๊ฐยท๊ต์ฒด (VN ๋ฐํ) | `engine/router.py` (SPEAKER_PORTRAIT) + sync_assets | | |
| | ์ธํธ๋ก ์ํ์ค(์ํยท์ธ๋ฌผ ์๊ฐ) ๊ต์ฒด | `engine/router.py` (INTRO_IMAGES) | | |
| | ํ๋ฉด ๋์์ธยท์ธํฐ๋์ | `web/index.html` | | |
| | ์ธ์ ์ ์ฅยท๋ณต์ ์ ์ฑ (TTL, ๋ฒ์ ๋ฌดํจํ) | `engine/repositories/play_session.py` + `engine/router.py` (`_save`/`_restore`) | | |
| | ์ ์ฒด์ง๊ฒฉ/์ฝ์ ํธ๋ฆฌ๊ฑฐ ๋จ์ด | `engine/agents/input_guard.py` (TRIGGERS) | | |
| | LLM ๋ชจ๋ธ ๊ต์ฒด | `.env`์ `LLM_MODEL=...` / `CHARACTER_MODEL=...` | | |