interactive-chat / docs /plans /persistence.md
Junhoee's picture
Upload 113 files
7d46a91 verified
|
Raw
History Blame Contribute Delete
9.23 kB
# ์ธํ„ฐ์Šคํ‹ฐ์…œ ํžˆ์Šคํ† ๋ฆฌ + ์„ธ์…˜ ์˜์†ํ™” โ€” ๊ตฌํ˜„ ๊ณ„ํš
> ์ž‘์„ฑ์ผ: 2026-07-06 ยท ์ƒํƒœ: **๊ตฌํ˜„ ์™„๋ฃŒ** (2026-07-06, feature/persistence)
> ๊ฒ€์ฆ: pytest 26๊ฑด ํ†ต๊ณผ(์‹ ๊ทœ 8๊ฑด ํฌํ•จ) + ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ e2e (E01-11 ์ง„ํ–‰ โ†’ kill/์žฌ๊ธฐ๋™ โ†’
> GET ๋ณต์› ๋™์ผ ์นด๋“œ โ†’ advance ๊ณ„์† ์ง„ํ–‰ ยท ๋ฏธ์กด์žฌ ์„ธ์…˜ 404)
> ํ›„์† ์ˆ˜์ • (2026-07-06, ์œ ์ € ์‹คํ”Œ๋ ˆ์ด ํ”ผ๋“œ๋ฐฑ 2ํšŒ): ์˜ค๋ฒ„๋ ˆ์ด ๋ฐฉ์‹ ์ž์ฒด๋ฅผ ํ๊ธฐ โ€”
> ์—ฐ์ถœ ์ปท์„ **์ž์ฒด ๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ง„ ๋…๋ฆฝ ํŽ˜์ด์ง€**๋กœ (์˜ˆ: 6 ๋ณธ๋ฌธ โ†’ 7 ์ปท โ†’ 8 ๋ณธ๋ฌธ, total 66 = 61+5).
> ๋ฒˆํ˜ธ๋Š” ์„œ๋ฒ„(router `_page_index`)๊ฐ€ ๊ถŒ์œ„, ์ปท ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ = progress.index - 1.
> ํ”„๋ก ํŠธ์˜ ์˜ค๋ฒ„๋ ˆ์ด/pendingPage/ํด๋ฆญ๊ฐ€๋“œ ๋กœ์ง ์ „๋ถ€ ์ œ๊ฑฐ โ†’ ์ผ๋ฐ˜ ํŽ˜์ด์ง€ ํ๋ฆ„์œผ๋กœ ๋‹จ์ˆœํ™”.
> Node DOM ์Šคํ… ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ์ •/์—ญ๋ฐฉํ–ฅยท์ขŒํด๋ฆญ ํ†ต๊ณผ ๊ฒ€์ฆ
> ์„ ํ–‰ ๊ฒฐ์ • (์œ ์ € ํ™•์ •):
> - ํฌ์ผ“(์ฑ„ํŒ…) ์ค‘๊ฐ„ ์ƒํƒœ๋Š” ์˜์†ํ™”ํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” ์„œ๋ฒ„ ์žฌ์‹œ์ž‘/๋ณต์› ์‹œ ํฌ์ผ“์€ ๋ฒ„๋ฆฌ๊ณ  ์ฝ๊ธฐ ํ™”๋ฉด์œผ๋กœ
> - ๋ณต์› ๋ฒ”์œ„๋Š” "ํ˜„์žฌ ํŽ˜์ด์ง€๋ถ€ํ„ฐ ์ด์–ด ์ฝ๊ธฐ"๋งŒ โ€” ๋’ค๋กœ๊ฐ€๊ธฐ ํžˆ์Šคํ† ๋ฆฌ๋Š” ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ํฌ๊ธฐ
> ๊ถŒ์žฅ์•ˆ ์ฑ„ํƒ: SQLite + ์Šค๋ƒ…์ƒท + ๋งค ๋ณ€๊ฒฝ ์ฆ‰์‹œ ์“ฐ๊ธฐ(write-through) + localStorage ์„ธ์…˜ ID + ์ฝ˜ํ…์ธ  ๋ฒ„์ „ ์Šคํƒฌํ”„๋กœ ๋ฌดํšจํ™”
## ๋ฌธ์ œ ์ •์˜
1. **์ธํ„ฐ์Šคํ‹ฐ์…œ ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„๊ทธ**: ์—ฐ์ถœ ์ปท์ด ํžˆ์Šคํ† ๋ฆฌ์˜ ํŽ˜์ด์ง€๊ฐ€ ์•„๋‹ˆ๋ผ ์ผํšŒ์„ฑ ์˜ค๋ฒ„๋ ˆ์ด๋ผ์„œ,
๋’ค๋กœ ๋„˜๊ธฐ๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ๊ฑด๋„ˆ๋›ฐ์–ด์ง€๊ณ  ์ด์ „ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋‚˜์˜จ๋‹ค. ์˜๋„๋Š” ์ •๋ฐฉํ–ฅ๊ณผ ๋™์ผํ•˜๊ฒŒ
"์Šคํฌ๋ฆฝํŠธ โ†’ ์ด๋ฏธ์ง€ ํŽ˜์ด์ง€ โ†’ ์Šคํฌ๋ฆฝํŠธ"๊ฐ€ ์—ญ๋ฐฉํ–ฅ์—์„œ๋„ ์œ ์ง€๋˜๋Š” ๊ฒƒ.
2. **์˜์†ํ™” ๋ถ€์žฌ**: ํ”Œ๋ ˆ์ด ์„ธ์…˜์ด 3์ธต ๋ชจ๋‘ ํœ˜๋ฐœ์„ฑ โ€”
`router._sessions`(ํŒŒ์ด์ฌ dict) ยท ADK `InMemorySessionService` ยท ๋ธŒ๋ผ์šฐ์ € `SID`(JS ๋ณ€์ˆ˜).
์ƒˆ๋กœ๊ณ ์นจ์ด๋ฉด ์„ธ์…˜ ID๋ฅผ ์žƒ๊ณ , ์„œ๋ฒ„ ์žฌ์‹œ์ž‘์ด๋ฉด ์ „ ์œ ์ € ์ง„ํ–‰์ด ์‚ฌ๋ผ์ง„๋‹ค.
CLAUDE.md ๊ถŒ์œ„ ์›์น™("ํ˜„์žฌ ์œ ์ €์˜ ์„œ์‚ฌ ์œ„์น˜ ๊ถŒ์œ„: DB `play_session.current_state_id`")์ด ์˜ˆ์ •ํ•œ DB๊ฐ€ ์•„์ง ์—†๋‹ค.
## ์„ค๊ณ„ ํ•ต์‹ฌ
### ๋ณต์›์˜ ํ•จ์ • โ€” `_enter()` ๋ถ€์ˆ˜ํšจ๊ณผ
`StoryEngine._enter()`๋Š” ์ง„์ž… ์‹œ ์‡ ์•ฝ๋„ ์ฆ๊ฐ€ยท์ถ• ์ ์šฉยท์•Ž ํ™•๋ฆฝ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
๋ณต์›์„ `_enter(current_id)` ์žฌํ˜ธ์ถœ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด **์ƒํƒœ๊ฐ€ ์ด์ค‘ ์ ์šฉ**๋œ๋‹ค.
๋”ฐ๋ผ์„œ ๋ณต์›์€ ๋ถ€์ˆ˜ํšจ๊ณผ ์—†๋Š” ์ „์šฉ ๊ฒฝ๋กœ๋กœ: ํ•„๋“œ๋ฅผ ์ง์ ‘ ๋ณต์›ํ•˜๊ณ ,
ํ˜„์žฌ ์นด๋“œ์˜ StepResult๋Š” ๋ Œ๋” ์ „์šฉ ๋ฉ”์„œ๋“œ๋กœ ์žฌ๊ตฌ์„ฑํ•œ๋‹ค (`_visible_narration` + `card.choices`๋Š” ์ˆœ์ˆ˜ ์กฐํšŒ).
### ์Šค๋ƒ…์ƒท ํŽ˜์ด๋กœ๋“œ (JSON ์ง๋ ฌํ™” โ€” ์ „ ํ•„๋“œ int/str/bool/list)
```json
{
"story": {"current_id": "E04-07", "visited": [...], "preset_branch": null, "ending_id": null},
"hidden": {"trust_score": 24, "doubt_score": 12, "frailty": 3,
"awareness_established": true, "axis_log": [...]}
}
```
- `ending_id`: ์—”๋”ฉ ๋„๋‹ฌ ์„ธ์…˜๋„ ๋ณต์› ๋Œ€์ƒ (์ƒˆ๋กœ๊ณ ์นจ ์‹œ ์—”๋”ฉ ํ™”๋ฉด ์œ ์ง€). ๋ณต์› ์‹œ `chapter.ending_rules`์—์„œ id๋กœ ์—ญ์กฐํšŒ
- ํฌ์ผ“ ์ง„ํ–‰ ์ค‘ ์ €์žฅ๋ถ„์€ ์—†๋‹ค โ€” ํฌ์ผ“ ์•ˆ์˜ ์ฆ๋ถ„์€ close ์ปค๋ฐ‹ ํ›„์—๋งŒ ์˜์† ์ƒํƒœ์— ๋ฐ˜์˜ (๊ธฐ์กด ๋‹จ์ผ ๊ฒฝ๋กœ ์œ ์ง€).
ํฌ์ผ“ ๋„์ค‘ ์„œ๋ฒ„๊ฐ€ ์ฃฝ์œผ๋ฉด ๊ทธ ๋Œ€ํ™”์˜ ์‹ ๋ขฐ์ถ• ์ฆ๋ถ„์€ ์œ ์‹ค๋œ๋‹ค (์Šน์ธ๋œ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„)
- ๊ธฐ์กด `HiddenState.to_session()`์€ ํฌ์ผ“ ์ „์šฉ(๋ถ€๋ถ„ยทํ‚ค ์ƒ์ด)์ด๋ฏ€๋กœ ์žฌ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์Šค๋ƒ…์ƒท ๋ฉ”์„œ๋“œ๋ฅผ ๋ณ„๋„ ์ถ”๊ฐ€
### ์ฝ˜ํ…์ธ  ๋ฒ„์ „ ์Šคํƒฌํ”„
- `ContentRepository`์— `content_version` ํ”„๋กœํผํ‹ฐ: `chapter.id` + ์ „์—ญ ์ˆœ์„œ์˜ ์นด๋“œ ID ๋ชฉ๋ก์„ sha256 ํ•ด์‹œ
- ์ €์žฅ ์‹œ ์Šคํƒฌํ”„, ๋ณต์› ์‹œ ๋ถˆ์ผ์น˜๋ฉด **๋ฌดํšจํ™”**(404 โ†’ ํ”„๋ก ํŠธ๊ฐ€ ์ƒˆ ์„ธ์…˜ ์‹œ์ž‘).
์นด๋“œ ๋ณธ๋ฌธ๋งŒ ๋ฐ”๋€ ๊ฒฝ์šฐ๋Š” ์žก์ง€ ๋ชปํ•˜์ง€๋งŒ(ID/์ˆœ์„œ ๋™์ผ) ๋ณต์›์ด ๊นจ์ง€์ง€๋Š” ์•Š์œผ๋ฏ€๋กœ ๋ฐ๋ชจ ์ˆ˜์šฉ
### ์ €์žฅ ์‹œ์  (write-through)
์˜์† ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋Š” ๋ชจ๋“  ์ง€์ ์—์„œ ์ฆ‰์‹œ ์ €์žฅ โ€” `POST /session`(์ƒ์„ฑ) ยท `advance` ยท `choose` ยท
`pocket/turn`(์ˆ˜๋ ด ์ปค๋ฐ‹ ์‹œ) ยท `pocket/close`. `pocket/open`์€ ์˜์† ์ƒํƒœ ๋ฌด๋ณ€๊ฒฝ์ด๋ผ ์ €์žฅ ์—†์Œ.
### ๋™์‹œ์„ฑยท์ˆ˜๋ช…
- uvicorn ์›Œ์ปค 1๊ฐœ ์ „์ œ ์œ ์ง€ (์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ + ADK InMemory ์„ธ์…˜ ๋•Œ๋ฌธ). SQLite๋Š” WAL ๋ชจ๋“œ
- ๊ฐ™์€ ์„ธ์…˜ ๋™์‹œ ์ ‘๊ทผ์€ ๋งˆ์ง€๋ง‰ ์“ฐ๊ธฐ ์Šน๋ฆฌ (๋ฐ๋ชจ ์ˆ˜์šฉ)
- ์„œ๋ฒ„ ๊ธฐ๋™ ์‹œ 30์ผ ๋ฏธ๊ฐฑ์‹  ์„ธ์…˜ purge
## ๋ณ€๊ฒฝ ํŒŒ์ผ ๋ชฉ๋ก
### A. ์ธํ„ฐ์Šคํ‹ฐ์…œ ํžˆ์Šคํ† ๋ฆฌ (๋ฒ„๊ทธ ์ˆ˜์ • โ€” ํ”„๋ก ํŠธ ๋‹จ๋…)
| ํŒŒ์ผ | ๋ณ€๊ฒฝ |
|---|---|
| `web/index.html` | ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ์„ 2์ข…์œผ๋กœ ํƒ€์ž…ํ™”: `{kind:'page', html, progress, ep}` ยท `{kind:'inter', image, caption, progress, ep}`. โ‘  `showStep()`: `step.image` ์žˆ์œผ๋ฉด inter ํ•ญ๋ชฉ์„ ๋จผ์ € pushํ•˜๊ณ  ์˜ค๋ฒ„๋ ˆ์ด ํ‘œ์‹œ, ์˜ค๋ฒ„๋ ˆ์ด ํด๋ฆญ ์‹œ page ํ•ญ๋ชฉ push + ๋ Œ๋” โ‘ก `go()`: ์ปค์„œ ์ด๋™์„ ๊ณตํ†ต `renderEntry(cursor)`๋กœ ํ†ต์ผ โ€” inter ํ•ญ๋ชฉ์ด๋ฉด ์˜ค๋ฒ„๋ ˆ์ด ํ‘œ์‹œ, page ํ•ญ๋ชฉ์ด๋ฉด HTML ๋ณต์› + ์˜ค๋ฒ„๋ ˆ์ด ๋‹ซ๊ธฐ โ‘ข ๋ธŒ๋ผ์šฐ์ง• ์ค‘ ์˜ค๋ฒ„๋ ˆ์ด ํด๋ฆญ = `go(+1)` (์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ๋‚ด๋น„ ์กด์„ ๋ฎ์œผ๋ฏ€๋กœ; โ† ํ‚ค๋Š” ๊ณ„์† ๋™์ž‘) โ‘ฃ `choose()`์˜ ์Šค๋ƒ…์ƒท ๊ฐฑ์‹ (`history[cursor].html`)์€ page ํ•ญ๋ชฉ์—๋งŒ ๋‹ฟ์œผ๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ |
### B. ์˜์†ํ™”
| ํŒŒ์ผ | ๋ณ€๊ฒฝ |
|---|---|
| `engine/services/state.py` | `HiddenState.snapshot() -> dict` / `HiddenState.from_snapshot(d)` ์ถ”๊ฐ€ (์ „ ํ•„๋“œ. `to_session()`๊ณผ ๋ณ„๊ฐœ โ€” ํฌ์ผ“ ๊ฒฝ๋กœ ๋ถˆ๋ณ€) |
| `engine/services/story.py` | โ‘  `snapshot() -> dict` (story ํŒŒํŠธ) โ‘ก `StoryEngine.restore(repo, snap) -> StoryEngine` ํด๋ž˜์Šค๋ฉ”์„œ๋“œ โ€” **๋ถ€์ˆ˜ํšจ๊ณผ ์—†์ด** ํ•„๋“œ ์ง์ ‘ ๋ณต์› โ‘ข `current_step() -> StepResult` โ€” ํ˜„์žฌ ์นด๋“œ/์—”๋”ฉ์˜ StepResult๋ฅผ ๋ Œ๋” ์ „์šฉ์œผ๋กœ ์žฌ๊ตฌ์„ฑ |
| `engine/repositories/content.py` | `content_version` ํ”„๋กœํผํ‹ฐ (sha256 ์Šคํƒฌํ”„) |
| `engine/repositories/play_session.py` | **์‹ ๊ทœ.** stdlib `sqlite3`, WAL. ํ…Œ์ด๋ธ” `play_session(sid TEXT PK, payload TEXT, content_version TEXT, updated_at TEXT)`. ๋ฉ”์„œ๋“œ: `save(sid, payload, version)` / `load(sid, version) -> dict | None`(๋ฒ„์ „ ๋ถˆ์ผ์น˜ ์‹œ None) / `purge(days=30)` |
| `engine/router.py` | โ‘  `PlaySessionRepository(ROOT/"data"/"sessions.db")` ์ƒ์„ฑ + ๊ธฐ๋™ ์‹œ `purge()` โ‘ก `_sessions` dict๋Š” ์บ์‹œ๋กœ ์œ ์ง€, `_get()` ๋ฏธ์Šค ์‹œ ์ €์žฅ์†Œ์—์„œ ๋กœ๋“œ โ†’ `StoryEngine.restore` + `current_step()`์œผ๋กœ PlaySession ์žฌ๊ตฌ์„ฑ (`pocket_sid=None` โ€” ํฌ์ผ“ ํ๊ธฐ) โ‘ข ์ €์žฅ ํ—ฌํผ `_save(sid, ps)`๋ฅผ ์ƒ์„ฑ/advance/choose/pocket ์ˆ˜๋ ด/close์— write-through |
| `web/index.html` | โ‘  ๊ธฐ๋™ ์‹œ `localStorage.carmilla_sid` ์žˆ์œผ๋ฉด `GET /api/session/{sid}` ์‹œ๋„ โ€” ์„ฑ๊ณต ์‹œ ํ‘œ์ง€ ๋ฒ„ํŠผ์ด "์ด์–ด ์ฝ๊ธฐ"๋กœ ๋™์ž‘(์ƒˆ ์„ธ์…˜ ์ƒ์„ฑ ์ƒ๋žต, ํ˜„์žฌ ํŽ˜์ด์ง€๋ถ€ํ„ฐ), 404๋ฉด sid ํ๊ธฐ ํ›„ ๊ธฐ์กด ํ๋ฆ„ โ‘ก ์„ธ์…˜ ์ƒ์„ฑ ์‹œ sid๋ฅผ localStorage์— ์ €์žฅ โ‘ข ์—”๋”ฉ "๋‹ค์‹œ, ์ฒ˜์Œ์˜ ๋ฐค์œผ๋กœ" ํด๋ฆญ ์‹œ sid ์ œ๊ฑฐ ํ›„ reload |
| `.gitignore` | `data/` ์ถ”๊ฐ€ |
| `tests/test_persistence.py` | **์‹ ๊ทœ.** โ‘  ์Šค๋ƒ…์ƒทโ†’๋ณต์› ๋ผ์šด๋“œํŠธ๋ฆฝ: ๋ช‡ ์นด๋“œ ์ง„ํ–‰ ํ›„ ๋ณต์›ํ–ˆ์„ ๋•Œ `current_id`/์ˆจ์€ ์ƒํƒœ/์ง„ํ–‰๋„ ๋™์ผ โ‘ก **๋ณต์›์ด ์‡ ์•ฝ๋„ยท์ถ•์„ ์žฌ์ ์šฉํ•˜์ง€ ์•Š์Œ** (ํ•ต์‹ฌ ํšŒ๊ท€) โ‘ข ์—”๋”ฉ ์„ธ์…˜ ๋ณต์› โ‘ฃ ์ €์žฅ์†Œ save/load + ๋ฒ„์ „ ๋ถˆ์ผ์น˜ ์‹œ None โ‘ค ๋ผ์šฐํ„ฐ ๋ ˆ๋ฒจ: `_sessions` ์บ์‹œ ๋น„์šด ๋’ค(์žฌ์‹œ์ž‘ ๋ชจ์‚ฌ) GET์œผ๋กœ ๋ณต์›๋˜๊ณ  ํฌ์ผ“์€ ๋‹ซํ˜€ ์žˆ์Œ |
| `docs/contract/demo_api.md` | `GET /api/session/{sid}`๊ฐ€ ๋ณต์› ์ง„์ž…์ ์ž„์„ ๋ช…์‹œ (404 = ๋ฏธ์กด์žฌ ๋˜๋Š” ์ฝ˜ํ…์ธ  ๋ฒ„์ „ ๋ถˆ์ผ์น˜ โ†’ ์ƒˆ ์„ธ์…˜ ์ƒ์„ฑํ•  ๊ฒƒ). ํฌ์ผ“ ๋น„์˜์† ๋ช…์‹œ |
| `docs/plans/persistence.md` | ๋ณธ ๋ฌธ์„œ |
### ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š” ๊ฒƒ
- `engine/app.py` โ€” ADK `InMemorySessionService` ์œ ์ง€ (ํฌ์ผ“ ๋น„์˜์† ๊ฒฐ์ •์— ๋”ฐ๋ผ `SqliteSessionService` ๋ถˆํ•„์š”)
- `engine/services/pocket.py` โ€” ํฌ์ผ“ ์ปค๋ฐ‹ ๋‹จ์ผ ๊ฒฝ๋กœ ๋ถˆ๋ณ€
- `content/` โ€” ๋ฌด๋ณ€๊ฒฝ (์ฝ˜ํ…์ธ  ์ปค๋ฐ‹ ๊ธˆ์ง€ ๊ทœ์น™)
## ์ž‘์—… ์ˆœ์„œ
1. **P1 โ€” ์ธํ„ฐ์Šคํ‹ฐ์…œ ํžˆ์Šคํ† ๋ฆฌ** (`web/index.html` ๋‹จ๋…, ์„œ๋ฒ„ ๋ฌด๊ด€) โ†’ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ •/์—ญ๋ฐฉํ–ฅ ์ˆ˜๋™ ํ™•์ธ
2. **P2 โ€” ์Šค๋ƒ…์ƒท/๋ณต์› ์—”์ง„** (`state.py`, `story.py`) + ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (๋ถ€์ˆ˜ํšจ๊ณผ ๋ฏธ์žฌ์ ์šฉ ํšŒ๊ท€ ํฌํ•จ)
3. **P3 โ€” SQLite ์ €์žฅ์†Œ + ๋ผ์šฐํ„ฐ write-through/๋ณต์›** (`play_session.py`, `content.py`, `router.py`) + ๋ผ์šฐํ„ฐ ํ…Œ์ŠคํŠธ
4. **P4 โ€” ํ”„๋ก ํŠธ ์ด์–ด ์ฝ๊ธฐ** (`web/index.html` localStorage) โ†’ ์ƒˆ๋กœ๊ณ ์นจยท์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ํ›„ ์ด์–ด ์ฝ๊ธฐ ์ˆ˜๋™ ํ™•์ธ
5. **P5 โ€” ๋ฌธ์„œ ๊ฐฑ์‹ ** (`demo_api.md`, ARCHITECTURE.md ์น˜ํŠธ์‹œํŠธ 1์ค„)
๊ฒ€์ฆ: `pytest tests/ -q` ์ „๊ฑด ํ†ต๊ณผ + ์ˆ˜๋™ ์‹œ๋‚˜๋ฆฌ์˜ค 3์ข…
(โ‘  ์ด๋ฏธ์ง€ ํŽ˜์ด์ง€ ๋’ค๋กœ๊ฐ€๊ธฐ/์•ž์œผ๋กœ๊ฐ€๊ธฐ โ‘ก ์ƒˆ๋กœ๊ณ ์นจ ํ›„ ์ด์–ด ์ฝ๊ธฐ โ‘ข uvicorn ์žฌ์‹œ์ž‘ ํ›„ ์ด์–ด ์ฝ๊ธฐ โ€” ํฌ์ผ“ ์ค‘์ด์—ˆ๋‹ค๋ฉด ์ฝ๊ธฐ ํ™”๋ฉด์œผ๋กœ).
## ๋ฆฌ์Šคํฌ / ํ•œ๊ณ„ (์Šน์ธ๋œ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„ ํฌํ•จ)
- ํฌ์ผ“ ๋„์ค‘ ์„œ๋ฒ„ ์‚ฌ๋ง ์‹œ ๊ทธ ๋Œ€ํ™”์˜ ์‹ ๋ขฐ์ถ• ์ฆ๋ถ„ ์œ ์‹ค (ํฌ์ผ“ ๋น„์˜์† ๊ฒฐ์ •์˜ ๊ท€๊ฒฐ)
- ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ๋’ค๋กœ๊ฐ€๊ธฐ ํžˆ์Šคํ† ๋ฆฌยท์„ ํƒ ๊ฒฐ๊ณผ ์‚ฐ๋ฌธ์€ ์†Œ์‹ค, ํ˜„์žฌ ํŽ˜์ด์ง€๋ถ€ํ„ฐ (๊ฒฐ์ • โ‘ค)
- ์นด๋“œ ๋ณธ๋ฌธ๋งŒ ๋ฐ”๋€ ์ฝ˜ํ…์ธ  ๊ฐฑ์‹ ์€ ๋ฒ„์ „ ์Šคํƒฌํ”„๊ฐ€ ๊ฐ์ง€ ๋ชปํ•จ (ID/์ˆœ์„œ ๋™์ผ ์‹œ) โ€” ๋ณต์›์€ ์•ˆ ๊นจ์ง
- ๋ฉ€ํ‹ฐ ์›Œ์ปค/๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค๋Š” ์—ฌ์ „ํžˆ ๋น„์ง€์› (ADK ์„ธ์…˜ยท์บ์‹œ๊ฐ€ ์ธ๋ฉ”๋ชจ๋ฆฌ)