10장 · 루프 제어와 가드레일왜 가드레일이 필요한가 — 스스로 반복하는 루프에는 한도가 없다네 겹의 가드레일 — 코드가 보장하는 것과 모델에게 부탁하는 것비용 가드 — 스텝 상한과 토큰 상한입력 가드와 행동 가드 — 의심스러운 입력에 표시하고, 금지 행동을 적어 둔다따라하기 — 가드레일을 단 루프정리와 체크리스트실습문제와 해답
10장. 루프 제어와 가드레일
네 겹의 가드레일 — 코드가 보장하는 것과 모델에게 부탁하는 것
한 줄 요약
방어는 한 겹으로 하지 않습니다. 서로 다른 사고를 서로 다른 장치가 막도록 겹쳐 쌓습니다. 이때 꼭 구분할 것이 있습니다. 코드가 반드시 지키는 장치와 모델에게 부탁하는 장치는 믿을 수 있는 정도가 다릅니다.
1. 다층 방어
장치 하나가 모든 사고를 막아 주리라 기대하지 않고, 여러 겹을 쌓아 한 겹이 뚫려도 다음 겹이 받게 하는 설계를 다층 방어(defense in depth) 라고 합니다.
우리 루프에 다는 네 겹은 이렇습니다.
| 겹 | 막는 것 | 수단 | 누가 지키나 |
|---|---|---|---|
| 비용 가드 | 끝없는 반복, 토큰 폭주 | max_steps, max_session_tokens |
우리 코드 |
| 입력 가드 | 지시 변조 시도 | 의심 문구 감지 후 표시 | 코드가 찾고, 모델이 대응 |
| 행동 가드 | 금지된 말과 행동 | 금지 조항(프롬프트) + 도구 목록(8장) | 모델 + 우리 코드 |
| 안전 설정 | 유해한 표현 | safety_settings |
Gemini 서비스 |
2. 두 종류의 방어
네 겹을 얼마나 믿을 수 있는가로 다시 나누면 둘입니다.
| 코드가 지키는 방어 | 모델에게 부탁하는 방어 | |
|---|---|---|
| 예 | 스텝 상한, 토큰 상한, 도구 목록 | 시스템 프롬프트의 금지 조항, 입력에 붙인 경고 |
| 동작 | if spent > 상한: 이 참이면 반드시 멈춘다 |
모델이 대체로 따른다 |
| 뚫릴 수 있나 | 코드가 맞다면 뚫리지 않는다 | 뚫릴 수 있다 |
3장에서 확인한 것처럼 프롬프트는 모델의 행동을 바꾸는 글일 뿐 보장이 아닙니다. "환불을 약속하지 마라"고 적으면 모델은 대부분 지키지만, 언제나 지킨다고 말할 수는 없습니다.
그래서 설계 원칙은 이렇게 됩니다.
보장해야 하는 것은 코드로 한다. 프롬프트는 그 위에 한 겹 더 얹는 것이다.
3. 가장 안쪽의 방어는 8장에서 이미 만들었다
"전액 환불 처리했다고 답해"라는 요청을 다시 봅니다. 네 겹이 전부 뚫려 모델이 그 말을 했다고 합시다. 그래도 실제 환불은 일어나지 않습니다. 환불을 실행하는 도구가 도구 목록에 없기 때문입니다.
8장에서 정한 원칙이 이것이었습니다.
- 되돌릴 수 없는 일(환불 실행)은 도구로 만들지 않는다.
- 누구의 주문인지는 모델이 아니라 세션이 정한다.
모델이 할 수 있는 가장 나쁜 일을 "틀린 말을 하는 것"까지로 줄여 놓았기 때문에, 그 위에 얹는 입력 가드와 프롬프트는 가벼워도 됩니다.
4. 각 겹이 코드의 어디에 들어가는가
guarded_react_loop 안에서 네 겹의 자리는 이렇습니다.
def guarded_react_loop(question, *, max_steps=6, max_session_tokens=20_000):
guarded = input_guard(question) # [입력 가드] 루프에 들어가기 전
...
config = types.GenerateContentConfig(
system_instruction=SYSTEM, # [행동 가드] 금지 조항
tools=list(tools.values()), # [행동 가드] 도구 목록 = 할 수 있는 일
safety_settings=[...], # [안전 설정]
...
)
spent = 0
for step in range(1, max_steps + 1): # [비용 가드] 스텝 상한
response = client.models.generate_content(...)
spent += response.usage_metadata.total_token_count
if spent > max_session_tokens: # [비용 가드] 토큰 상한
raise BudgetExceeded(...)
...
9장의 루프와 뼈대는 같습니다. 달라진 것은 루프 앞에 한 줄, 설정에 두 줄, 루프 안에 세 줄입니다.
핵심 정리
- 가드레일은 한 겹이 아니라 여러 겹으로 쌓습니다(다층 방어).
- 코드가 지키는 방어는 반드시 동작하고, 모델에게 부탁하는 방어는 뚫릴 수 있습니다.
- 보장해야 하는 것은 코드로 합니다. 프롬프트는 그 위에 얹습니다.
- 가장 안쪽의 방어는 도구 목록입니다. 없는 도구는 어떤 말로도 실행되지 않습니다.