중급
자기 반성 에이전트 구축하기
비평 및 개선 루프를 구현하여 에이전트 신뢰성 향상
30분
LangGraph
자기 반성(self-reflection)을 통해 에이전트가 자신의 출력을 비판하고 반복적으로 개선할 수 있습니다. 이 패턴은 신뢰성과 출력 품질을 획기적으로 향상시킵니다.
자기 반성이 필요한 이유?#
일반적인 에이전트 출력에는 다음과 같은 문제가 자주 포함됩니다:
- 불완전한 추론
- 사실적 오류
- 누락된 예외 사례
- 최적이 아닌 솔루션
자기 반성은 에이전트가 다음을 수행하여 이러한 문제를 해결합니다:
- 초기 응답 생성
- 해당 응답 비판
- 비판을 바탕으로 개선
- 만족할 때까지 반복
반성 패턴#
초기 응답 → 비판 → 개선 → 비판 → ... → 최종 출력기본 구현#
반성 프롬프트#
CRITIQUE_PROMPT = """다음 응답을 검토하고 문제점을 식별하세요:
원래 질문: {question}
응답: {response}
고려 사항:
1. 사실적 정확성 - 모든 진술이 올바른가?
2. 완전성 - 질문에 완전히 답변했는가?
3. 논리 - 추론이 타당한가?
4. 명확성 - 설명이 잘 되어 있는가?
개선을 위한 구체적이고 실행 가능한 피드백을 제공하세요.
응답이 만족스러우면 "APPROVED"로 응답하세요.
비판:"""
REFINE_PROMPT = """비판을 바탕으로 다음 응답을 개선하세요:
원래 질문: {question}
현재 응답: {response}
비판: {critique}
모든 피드백을 반영한 개선된 응답을 제공하세요.
개선된 응답:"""간단한 반성 루프#
from openai import OpenAI
client = OpenAI()
def generate(prompt: str) -> str:
"""모델로부터 응답을 생성합니다."""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=0.7
)
return response.choices[0].message.content
def reflect_and_refine(question: str, max_iterations: int = 3) -> str:
"""자기 반성을 통해 응답을 생성합니다."""
# 초기 생성
response = generate(f"이 질문에 답변하세요: {question}")
for i in range(max_iterations):
# 응답 비판
critique = generate(CRITIQUE_PROMPT.format(
question=question,
response=response
))
# 승인 여부 확인
if "APPROVED" in critique.upper():
print(f"{i+1}회 반복 후 승인됨")
return response
# 비판을 바탕으로 개선
response = generate(REFINE_PROMPT.format(
question=question,
response=response,
critique=critique
))
return responseLangGraph 구현#
LangGraph를 사용한 더 정교한 구현:
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
class ReflectionState(TypedDict):
question: str
response: str
critique: str
iteration: int
approved: bool
llm = ChatOpenAI(model="gpt-4", temperature=0.7)
critic_llm = ChatOpenAI(model="gpt-4", temperature=0) # 비판에는 낮은 온도 사용
def generate_node(state: ReflectionState) -> ReflectionState:
"""응답을 생성하거나 개선합니다."""
if state["iteration"] == 0:
# 초기 생성
response = llm.invoke(f"답변: {state['question']}")
else:
# 개선
prompt = REFINE_PROMPT.format(
question=state["question"],
response=state["response"],
critique=state["critique"]
)
response = llm.invoke(prompt)
return {
**state,
"response": response.content,
"iteration": state["iteration"] + 1
}
def critique_node(state: ReflectionState) -> ReflectionState:
"""현재 응답을 비판합니다."""
critique = critic_llm.invoke(CRITIQUE_PROMPT.format(
question=state["question"],
response=state["response"]
))
approved = "APPROVED" in critique.content.upper()
return {
**state,
"critique": critique.content,
"approved": approved
}
def should_continue(state: ReflectionState) -> Literal["generate", "end"]:
"""개선을 계속할지 결정합니다."""
if state["approved"] or state["iteration"] >= 3:
return "end"
return "generate"
# 그래프 구축
workflow = StateGraph(ReflectionState)
workflow.add_node("generate", generate_node)
workflow.add_node("critique", critique_node)
workflow.set_entry_point("generate")
workflow.add_edge("generate", "critique")
workflow.add_conditional_edges(
"critique",
should_continue,
{"generate": "generate", "end": END}
)
app = workflow.compile()고급 패턴#
다중 측면 비판#
각 측면을 별도로 평가:
ASPECTS = {
"accuracy": "모든 사실적 주장이 정확하고 검증 가능한가?",
"completeness": "응답이 질문의 모든 부분을 완전히 다루는가?",
"clarity": "응답이 잘 구성되어 있고 이해하기 쉬운가?",
"relevance": "응답이 질문에 집중되어 있는가?",
}
def multi_aspect_critique(question: str, response: str) -> dict:
"""여러 측면에서 응답을 비판합니다."""
critiques = {}
for aspect, prompt in ASPECTS.items():
critique = generate(f"""
질문: {question}
응답: {response}
평가: {prompt}
점수 (1-5) 및 설명:
""")
critiques[aspect] = critique
return critiques앙상블 반성#
여러 비판자 사용:
def ensemble_critique(question: str, response: str, num_critics: int = 3) -> str:
"""여러 관점에서 비판을 수집하고 종합합니다."""
critiques = []
personas = [
"도메인 전문가",
"회의적인 검토자",
"명확성 중심 편집자"
]
for persona in personas[:num_critics]:
critique = generate(f"""
{persona}로서 이 응답을 비판하세요:
질문: {question}
응답: {response}
비판:
""")
critiques.append(critique)
# 비판 종합
synthesis = generate(f"""
다음 비판들을 실행 가능한 피드백으로 결합하세요:
{chr(10).join(f'비판 {i+1}: {c}' for i, c in enumerate(critiques))}
종합된 피드백:
""")
return synthesis헌법적 AI 스타일 반성#
윤리 및 안전 검사 적용:
CONSTITUTIONAL_PRINCIPLES = [
"응답에 유해하거나 위험한 정보가 포함되어서는 안 됩니다",
"응답은 진실해야 하며 오해를 유발해서는 안 됩니다",
"응답은 개인정보를 존중하고 공개해서는 안 됩니다",
"응답은 공정하고 편향되지 않아야 합니다",
]
def constitutional_check(response: str) -> tuple[bool, str]:
"""헌법적 원칙에 따라 응답을 검사합니다."""
violations = []
for principle in CONSTITUTIONAL_PRINCIPLES:
check = generate(f"""
원칙: {principle}
응답: {response}
이 응답이 원칙을 위반합니까? (YES/NO)
YES인 경우, 어떻게 위반하는지 설명하세요:
""")
if "YES" in check.upper():
violations.append(check)
if violations:
return False, "\n".join(violations)
return True, "모든 원칙 충족"시각화#
반성 과정을 추적합니다:
class ReflectionTracer:
def __init__(self):
self.history = []
def log(self, iteration: int, response: str, critique: str, approved: bool):
self.history.append({
"iteration": iteration,
"response": response,
"critique": critique,
"approved": approved
})
def visualize(self):
for entry in self.history:
print(f"\n=== 반복 {entry['iteration']} ===")
print(f"응답: {entry['response'][:200]}...")
print(f"비평: {entry['critique'][:200]}...")
print(f"상태: {'✓ 승인됨' if entry['approved'] else '→ 개선 중'}")모범 사례#
- 생성과 비평에 다른 모델 사용: 비평에는 더 분석적인 모델을 사용하세요
- 온도 설정: 비평에는 낮은 온도, 창의적 생성에는 높은 온도를 사용하세요
- 반복 제한: 무한 루프를 방지하기 위해 항상 최대값을 설정하세요
- 명확한 기준: 구체적이고 측정 가능한 비평 기준을 정의하세요
- 추적 로깅: 디버깅 및 분석을 위해 전체 추적을 유지하세요
자기 반성을 사용해야 하는 경우#
적합한 경우:
- 중요도가 높은 출력(법률, 의료, 금융)
- 복잡한 추론 작업
- 정확성이 요구되는 콘텐츠
- 사용자 대상 텍스트 생성
과도할 수 있는 경우:
- 단순한 사실 질의
- 시간에 민감한 애플리케이션
- 중요도가 낮은 내부 작업