토론·반성 패턴
같은 에게 한 번 더 물어보면 답이 좋아진다. 자기 출력을 자기가 비평하면 더 좋아진다. 두 에이전트가 싸우게 하면 또 좋아진다. 이게 ··의 출발점.
네 가지 패턴 한눈에
같은 가족인데 역할 분리 정도가 다르다.
- : 한 에이전트가 자기 출력을 다시 본다.
- : 작가/비평가 두 역할 분리.
- : 후보 N개 중 1개 선택.
- : 두 입장이 N라운드 주고받고, 심판이 결론.
도구는 같다 — 다 LLM 호출. 다른 건 호출 순서와 시스템 프롬프트 뿐.
왜 효과가 있는가
사고를 외화하면 본인이 그 사고를 다시 검토할 수 있다.
LLM은 자기 출력을 새 입력으로 받으면, 처음엔 보지 못한 결함을 본다. 토큰이 컨텍스트에 들어와야 비로소 모델이 “본다”. 의 결과를 다시 에 넣는 것만으로 정확도가 오른다 — 의 핵심 메커니즘이다.
자기 반성 — 가장 단순한 버전
async def reflect_once(question):
draft = await generate(question) # 1) 초안
feedback = await critique(question, draft) # 2) 자기 비평
return await revise(question, draft, feedback) # 3) 수정LLM을 세 번 호출하니 비용은 그만큼 늘어난다. 정확도 효과는 케이스마다 다르지만 코드·수학 도메인에서 의미 있는 향상 보고가 많다. 같은 가 세 다른 로 세 번 등장한다. 은 별도 에이전트 없이도 성립한다.
비평가를 분리하면
작가와 비평가 시스템 프롬프트를 명시적으로 다르게 가져가면, 비평가가 더 가차없어진다. 의 가장 좁은 적용이다.
AUTHOR = "정확한 답을 한 단락으로 쓴다. 자기 검열 없이 자신감 있게."
CRITIC = "비평가다. 사실 오류·논리 결함만 짚어라. 글 다시 쓰지 마라."
async def critique(question, draft):
r = await client.messages.create(
model="claude-sonnet-4-6", max_tokens=400,
system=CRITIC,
messages=[{"role": "user",
"content": f"질문: {question}\n초안: {draft}"}],
)
return r.content[0].text는 글을 다시 쓰지 않는다. 비평만 한다. 역할이 좁아야 잡소리가 줄어든다. 보다 한 단계 진화한 형태.
LLM-as-Judge
여러 후보를 두고 누가 더 나은지 판단하는 역할이 이다. 자동 평가에 가장 흔히 쓰인다 — .
JUDGE_SYSTEM = (
"너는 엄격한 심판이다. 두 후보 답을 받아 채점한다.\n"
"출력은 JSON: {winner:'A'|'B', scoreA:int, scoreB:int, reason:str}.\n"
"루브릭: 정확성(0-5), 완결성(0-5), 명확성(0-5)."
)
async def judge(question, a, b):
r = await client.messages.create(
model="claude-sonnet-4-6", max_tokens=500,
system=JUDGE_SYSTEM,
messages=[{"role": "user",
"content": f"질문:{question}\n[A]{a}\n[B]{b}"}],
)
return json.loads(r.content[0].text)reason 필드가 중요하다. 점수만 받으면 디버깅 불가. 24장의 평가 챕터에서 설계는 더 자세히 다룬다.
Judge 의 편향
는 마법이 아니다. 알려진 편향들 —
| 편향 | 설명 | 완화 |
|---|---|---|
| 위치 편향 | A 가 먼저 나오면 더 잘 보임 | A/B 위치 무작위화 |
| 길이 편향 | 긴 답을 좋게 평가 | 길이 정규화 |
| 자기 편향 | 같은 모델이 쓴 답을 선호 | 다른 모델로 채점 |
| 표현 편향 | 화려한 문체를 정확도로 오인 | 명시화 |
평가 셋() 위에서 심판 자체를 평가해야 진짜 신뢰할 수 있다. 도 결국 또 하나의 일 뿐.
토론 패턴
두 입장을 강제로 분리하고 N라운드 주고받게 한다. 양극단이 서로 약점을 찾아내며, 마지막에 심판이 결론을 낸다.
async def debate(question, rounds=2):
pro_history = [{"role": "user", "content": question}]
con_history = [{"role": "user", "content": question}]
for _ in range(rounds):
pro = await speak("찬성", pro_history)
pro_history.append({"role": "assistant", "content": pro})
con_history.append({"role": "user", "content": pro})
con = await speak("반대", con_history)
con_history.append({"role": "assistant", "content": con})
pro_history.append({"role": "user", "content": con})
return {"pro": pro_history, "con": con_history}호출 수 = 2 × rounds + 심판 1회. 은 가장 비싼 패턴이다. 도덕적·법적 판단처럼 다각도가 필요한 케이스에 어울린다. 만으로 부족하고 만으론 비교 후보가 없을 때.
투표 합의
여러 가 같은 질문에 독립적으로 답하고 다수결로 결정한다. self-consistency 라고도 부른다.
분류·추출처럼 답이 좁은 작업에서 효과적. 환각 한 번이 다수결로 묻힌다 — 의 강점이다. 자유 텍스트엔 잘 안 맞는다 — 같은 문장이 나오지 않으니까. 전략과 본질적으로 같은 아이디어를 정확도 향상 관점에서 본 셈.
비용·지연 정량
| 패턴 | LLM 호출 수 | 지연 (대략) |
|---|---|---|
| 단일 | 1 | t |
| 1라운드 | 3 | 3t |
| 분리 | 3 | 3t |
| + 후보 2 | 3 | 2t (병렬) + t |
| 2라운드 + 심판 | 5 | 5t |
| N=5 | 5 | t (병렬) |
병렬화 가능 여부가 지연을 가른다. 반성은 본질적으로 직렬, 투표 합의는 본질적으로 병렬. 안에서 어떤 패턴이 들어갈지 먼저 따져야 한다.
정확도 향상은 공짜가 아니다
토론·반성으로 정답률이 의미 있게 올랐다고 가정하자. 비용은 호출 횟수만큼 몇 배로 늘고, 지연도 같이. 자주 호출되는 경로에선 감당 불가다.
토론·반성은 정확도가 비용보다 비싼 케이스에만 쓴다.
쓰는 데가 좁아진다. 보통은 — 평가 어려운 의사결정, 한 번 틀리면 비싼 실수 (의료·법률·금융 보고서 검토). 일상 응대에선 한 번이면 충분한 경우가 많다. 측면에서 · 호출을 캐시하는 것도 표준.
다음 챕터
끼리 합의해도 사람의 마지막 승인이 필요한 순간이 있다. 결제, 외부 메일 발송, 데이터 영구 삭제 — 으로도, 으로도 안 되는 영역. 가 아니라 사람이 게이트키퍼가 되어야 한다. 다음 챕터에서 HITL 을 다룬다.