File size: 18,139 Bytes
1c9c13f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# 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 ๋ชจ๋ธ

```python
# 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`)
- ๋ชจ๋ธ ๊ต์ฒด๊ฐ€ ์žฆ์œผ๋ฏ€๋กœ ํ”„๋กฌํ”„ํŠธ์— ํŠน์ • ๋ชจ๋ธ ์˜์กด ๋ฌธ๋ฒ•์„ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค

---

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

```bash
# ๋‹จ์œ„ ํ…Œ์ŠคํŠธ
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 + ๋Œ€ํ‘œ๋‹˜ ์Šน์ธ. ๋ณ€ํ™˜ ์Šคํฌ๋ฆฝํŠธ ์žฌ์‹คํ–‰ ์‚ฐ์ถœ๋ฌผ ๊ฐฑ์‹ ์€ ์˜ˆ์™ธ)

---

## ์ฐธ๊ณ  ๋งํฌ

- ADK ๊ณต์‹๋ฌธ์„œ: https://google.github.io/adk-docs/
- ADK GitHub: https://github.com/google/adk-python
- Neo4j GraphRAG Python: https://github.com/neo4j/neo4j-graphrag-python
- LiteLLM ๋ฌธ์„œ: https://docs.litellm.ai/docs/providers