본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 26. · 2 Views
Reflection과 Self-Correction 완벽 가이드
AI 에이전트가 스스로 생각하고 개선하는 방법을 배웁니다. Reflection 패턴을 통해 실패로부터 학습하고, 자기 평가를 통해 더 나은 결과를 만들어내는 실전 기법을 익혀봅니다.
목차
1. 자기 평가와 개선
어느 날 김개발 씨는 AI 에이전트를 만들고 있었습니다. 코드는 잘 동작하는 것 같았는데, 결과물의 품질이 기대에 못 미쳤습니다.
선배 박시니어 씨가 다가와 물었습니다. "에이전트가 자기 결과를 스스로 검토하게 만들어봤어요?"
**자기 평가(Self-Evaluation)**는 AI 에이전트가 자신의 출력을 스스로 검토하고 개선점을 찾는 과정입니다. 마치 작가가 원고를 쓰고 나서 다시 읽어보며 수정하는 것과 같습니다.
이를 통해 에이전트는 한 번에 완벽한 답을 내지 못하더라도, 반복적으로 개선하여 더 나은 결과를 만들어낼 수 있습니다.
다음 코드를 살펴봅시다.
# Self-Evaluation 기본 패턴
def generate_with_self_evaluation(prompt, max_iterations=3):
# 초기 결과 생성
result = generate_initial_response(prompt)
for i in range(max_iterations):
# 자기 평가: 결과를 스스로 검토
evaluation = evaluate_response(result, prompt)
# 평가 점수가 기준을 넘으면 완료
if evaluation['score'] >= 0.8:
return result
# 개선이 필요하면 피드백을 반영하여 재생성
result = improve_response(result, evaluation['feedback'])
return result
김개발 씨는 고객 문의에 자동으로 답변하는 AI 챗봇을 개발하고 있었습니다. 처음에는 간단하게 질문을 받아서 바로 답변을 생성하도록 만들었습니다.
하지만 막상 테스트를 해보니 답변의 품질이 들쭉날쭉했습니다. 어떤 때는 정확하고 친절한 답변을 주지만, 어떤 때는 질문과 동떨어진 내용을 답하거나 중요한 정보를 빠뜨렸습니다.
고민하던 김개발 씨에게 박시니어 씨가 조언을 해주었습니다. "사람도 중요한 이메일을 쓸 때는 한 번 더 읽어보잖아요.
AI도 마찬가지예요." 그렇습니다. 자기 평가란 정확히 이런 과정을 말합니다.
쉽게 비유하자면, 자기 평가는 마치 학생이 시험 문제를 풀고 난 뒤 답안을 다시 검토하는 것과 같습니다. 첫 번째 답안에서 실수를 발견하면 고쳐 쓰고, 빠뜨린 부분이 있으면 추가합니다.
AI 에이전트도 똑같이 자신의 결과물을 검토하고 개선할 수 있습니다. 자기 평가가 없던 시절에는 어땠을까요?
전통적인 AI 시스템은 한 번 답변을 생성하면 그것으로 끝이었습니다. 답변이 부족하거나 잘못되어도 스스로 알 수 없었습니다.
개발자가 직접 결과를 확인하고, 프롬프트를 수정하고, 다시 테스트하는 번거로운 과정을 반복해야 했습니다. 더 큰 문제는 일관성이었습니다.
같은 유형의 질문에도 매번 다른 품질의 답변이 나왔습니다. 고객 입장에서는 챗봇을 신뢰할 수 없게 되었습니다.
바로 이런 문제를 해결하기 위해 자기 평가 패턴이 등장했습니다. 자기 평가를 사용하면 품질의 하한선을 보장할 수 있습니다.
첫 답변이 부족하더라도 스스로 검토하고 개선하는 과정을 거치면서 일정 수준 이상의 결과를 만들어냅니다. 또한 투명성도 얻을 수 있습니다.
에이전트가 왜 답변을 수정했는지, 어떤 부분을 개선했는지 추적할 수 있습니다. 무엇보다 자율성이라는 큰 이점이 있습니다.
개발자가 일일이 개입하지 않아도 에이전트가 스스로 품질을 관리합니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 generate_initial_response 함수로 초기 답변을 생성합니다. 이것이 첫 번째 시도입니다.
다음으로 반복문 안에서 evaluate_response 함수가 답변을 평가합니다. 이 함수는 답변이 질문에 적절한지, 정보가 충분한지, 톤이 적절한지 등을 검토하여 점수와 피드백을 반환합니다.
점수가 0.8 이상이면 충분히 좋은 답변이라고 판단하고 바로 반환합니다. 그렇지 않으면 improve_response 함수가 피드백을 반영하여 답변을 개선합니다.
이 과정을 최대 3번까지 반복합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 고객 지원 챗봇을 개발한다고 가정해봅시다. 고객이 "환불은 어떻게 받나요?"라고 물었을 때, 첫 답변이 너무 간단할 수 있습니다.
자기 평가를 통해 "환불 절차, 소요 기간, 필요 서류"가 빠졌다는 것을 발견하고 더 상세한 답변으로 개선합니다. 실제로 많은 AI 스타트업에서 이런 패턴을 적극적으로 사용하고 있습니다.
특히 법률 자문, 의료 상담처럼 정확성이 중요한 분야에서 필수적입니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 무한 반복입니다. 종료 조건을 제대로 설정하지 않으면 에이전트가 끊임없이 자신의 답변을 수정하려고 시도합니다.
이렇게 하면 응답 시간이 너무 길어지고 비용도 증가합니다. 따라서 적절한 최대 반복 횟수와 품질 기준을 설정해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언을 듣고 자기 평가 기능을 추가한 김개발 씨는 놀라운 변화를 목격했습니다.
챗봇의 답변 품질이 눈에 띄게 향상되었고, 고객 만족도도 올라갔습니다. "이제 챗봇이 스스로 생각한다는 느낌이 드네요!" 김개발 씨가 신기해하며 말했습니다.
자기 평가를 제대로 이해하면 더 똑똑하고 신뢰할 수 있는 AI 에이전트를 만들 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 평가 기준을 명확하게 정의하세요. "좋은 답변"이 무엇인지 구체적으로 명시해야 합니다.
- 반복 횟수는 2-3번이 적당합니다. 너무 많으면 응답 시간과 비용이 증가합니다.
- 평가 결과를 로깅하면 에이전트의 개선 과정을 추적할 수 있어 디버깅에 도움이 됩니다.
2. 실패로부터 학습
김개발 씨의 챗봇이 어제는 잘 답변했던 질문에 오늘은 엉뚱한 답을 했습니다. "왜 같은 실수를 반복하는 거지?" 고민하던 그에게 박시니어 씨가 말했습니다.
"에이전트가 과거의 실수를 기억하고 배우게 만들어야죠."
**실패 학습(Learning from Failure)**은 AI 에이전트가 과거의 잘못된 시도를 기억하고, 같은 실수를 반복하지 않도록 개선하는 메커니즘입니다. 마치 사람이 넘어졌던 곳을 조심해서 다시 걷는 것처럼, 에이전트도 실패 사례를 누적하여 점점 더 현명해집니다.
다음 코드를 살펴봅시다.
# 실패 사례 학습 패턴
class LearningAgent:
def __init__(self):
self.failure_memory = [] # 실패 사례 저장소
def execute_task(self, task):
# 과거 실패 사례 확인
similar_failures = self.find_similar_failures(task)
# 실패 사례를 참고하여 작업 수행
result = self.perform_with_context(task, similar_failures)
# 결과 검증
if not self.validate_result(result):
# 실패 시 메모리에 기록
self.failure_memory.append({
'task': task,
'attempt': result,
'reason': self.analyze_failure(result)
})
# 재시도
return self.retry_with_learning(task)
return result
김개발 씨는 코드 리뷰 자동화 에이전트를 개발하고 있었습니다. 에이전트가 코드를 분석하고 개선 제안을 하는 기능이었는데, 이상한 현상이 발생했습니다.
분명 어제 비슷한 코드에서 실수를 했는데, 오늘 또 똑같은 실수를 하는 것이었습니다. 예를 들어 어제는 "이 함수는 null을 반환할 수 있으니 체크가 필요합니다"라고 잘못 지적했다가, 실제로는 null을 반환할 수 없는 함수였다는 것을 확인했습니다.
그런데 오늘 비슷한 함수를 또 잘못 지적하는 것이었습니다. "에이전트가 기억력이 없는 것 같아요." 김개발 씨가 답답해하자 박시니어 씨가 웃으며 대답했습니다.
"맞아요. 지금 에이전트는 매번 백지 상태로 시작하니까요.
사람처럼 실패를 기억하게 만들어야 합니다." 실패 학습이란 바로 이런 메커니즘을 말합니다. 쉽게 비유하자면, 실패 학습은 마치 요리사가 실패한 요리 레시피를 노트에 적어두는 것과 같습니다.
"소금을 너무 많이 넣으면 짜다", "불을 너무 세게 하면 탄다" 같은 실패 경험을 기록해두면, 다음에는 같은 실수를 하지 않습니다. AI 에이전트도 똑같이 실패 사례를 메모리에 저장하고 참고할 수 있습니다.
실패 학습이 없던 시절에는 어땠을까요? 전통적인 AI 시스템은 **무상태(stateless)**였습니다.
각 요청을 독립적으로 처리하기 때문에, 이전에 무엇을 잘못했는지 전혀 기억하지 못했습니다. 개발자가 일일이 에러 로그를 확인하고, 프롬프트나 코드를 수정해야 했습니다.
더 큰 문제는 확장성이었습니다. 시스템이 커질수록 실패 케이스도 다양해지는데, 모든 경우를 미리 예측해서 대응하는 것은 불가능했습니다.
결국 운영하면서 계속 문제가 발생하고, 그때마다 긴급 패치를 해야 하는 악순환이 반복되었습니다. 바로 이런 문제를 해결하기 위해 실패 학습 패턴이 등장했습니다.
실패 학습을 사용하면 적응력이 생깁니다. 에이전트가 운영 환경에서 만나는 다양한 상황에 스스로 적응하며 성장합니다.
또한 누적 지식도 쌓입니다. 시간이 지날수록 실패 사례가 축적되어 전문성이 높아집니다.
무엇보다 효율성이라는 큰 이점이 있습니다. 같은 실수를 반복하지 않으니 전체적인 성공률이 올라가고, 재시도 횟수가 줄어듭니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 __init__ 메서드에서 failure_memory라는 리스트를 초기화합니다.
이것이 에이전트의 실패 사례 저장소입니다. execute_task 메서드가 호출되면 find_similar_failures로 과거에 비슷한 작업에서 실패한 적이 있는지 찾습니다.
발견된 실패 사례를 참고하여 작업을 수행합니다. 이때 "이런 방식은 과거에 실패했으니 피하자"는 식으로 학습 내용을 반영합니다.
작업이 끝나면 validate_result로 결과를 검증합니다. 검증에 실패하면 이번 시도를 failure_memory에 추가합니다.
단순히 "실패했다"만 기록하는 게 아니라, 무엇을 시도했고 왜 실패했는지 상세히 분석하여 저장합니다. 그리고 학습한 내용을 바탕으로 재시도합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 데이터 마이그레이션 에이전트를 개발한다고 가정해봅시다.
수천 개의 레코드를 이전 시스템에서 새 시스템으로 옮기는 작업입니다. 처음에는 날짜 형식을 잘못 변환해서 오류가 발생합니다.
에이전트는 이를 실패 사례로 기록합니다. 다음 레코드를 처리할 때는 "날짜 형식 변환 시 주의"라는 학습 내용을 참고하여 올바르게 처리합니다.
이런 식으로 초반에는 실수가 있지만, 점점 성공률이 올라가면서 결국 완벽하게 작업을 완수합니다. 실제로 구글, 아마존 같은 대형 기업에서 운영하는 AI 시스템은 모두 이런 학습 메커니즘을 갖추고 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 **과적합(Overfitting)**입니다.
특정 실패 사례에 너무 집중한 나머지, 오히려 다른 정상적인 케이스까지 잘못 처리하게 됩니다. 예를 들어 "A 함수는 null을 반환한다"는 실패 사례 때문에 모든 함수를 의심하게 되는 것입니다.
따라서 실패 사례를 일반화할 때는 신중해야 합니다. 너무 구체적이면 도움이 안 되고, 너무 추상적이면 오용될 수 있습니다.
적절한 균형을 찾는 것이 중요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
실패 학습 기능을 추가한 후, 코드 리뷰 에이전트의 정확도가 크게 향상되었습니다. 처음에는 오류가 많았지만, 일주일 정도 운영하자 거의 대부분의 케이스를 정확하게 처리했습니다.
"에이전트가 정말 배우고 있어요!" 김개발 씨가 감탄하며 말했습니다. 실패 학습을 제대로 이해하면 시간이 지날수록 똑똑해지는 AI 에이전트를 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 실패 사례는 구조화해서 저장하세요. 작업 내용, 시도한 방법, 실패 이유, 올바른 방법 등을 명확히 기록합니다.
- 메모리가 무한정 커지지 않도록 오래된 실패 사례는 주기적으로 정리하거나 요약하세요.
- 유사도 검색을 위해 벡터 데이터베이스를 활용하면 효율적입니다.
3. Reflexion 패턴
김개발 씨는 복잡한 문제를 푸는 AI 에이전트를 만들고 있었습니다. 단순히 답을 내는 것뿐만 아니라, 왜 틀렸는지 분석하고 스스로 개선하는 에이전트를 만들고 싶었습니다.
박시니어 씨가 논문 하나를 건네주며 말했습니다. "Reflexion이라는 패턴을 한번 보세요."
Reflexion은 에이전트가 자신의 행동을 되돌아보고(Reflect), 무엇이 잘못되었는지 분석하며, 그 분석 결과를 장기 기억으로 저장하여 다음 시도에 활용하는 프레임워크입니다. 단순한 재시도가 아니라, 실패의 원인을 깊이 분석하고 명시적인 피드백을 자신에게 제공하는 것이 핵심입니다.
다음 코드를 살펴봅시다.
# Reflexion 패턴 구현
class ReflexionAgent:
def __init__(self):
self.memory = [] # 장기 기억
def solve(self, problem, max_trials=3):
for trial in range(max_trials):
# 과거 반성 내용을 참고하여 시도
context = self.get_relevant_reflections(problem)
solution = self.generate_solution(problem, context)
# 해결책 평가
evaluation = self.evaluate(solution, problem)
if evaluation['success']:
return solution
# 실패 시 반성(Reflection) 생성
reflection = self.reflect(problem, solution, evaluation)
self.memory.append(reflection)
return None
def reflect(self, problem, solution, evaluation):
# "무엇이 잘못되었고, 다음에는 어떻게 해야 할까?" 분석
return {
'problem_type': self.classify_problem(problem),
'failed_approach': solution,
'failure_reason': evaluation['reason'],
'lesson': self.extract_lesson(problem, solution, evaluation)
}
김개발 씨는 수학 문제를 푸는 AI 에이전트를 개발하고 있었습니다. 처음에는 문제를 받아서 바로 풀이를 생성했는데, 복잡한 문제에서는 자주 틀렸습니다.
재시도를 해도 비슷한 방식으로 접근해서 또 틀리는 경우가 많았습니다. "왜 계속 같은 방식으로 푸는 걸까요?" 김개발 씨가 물었습니다.
박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "지금 에이전트는 그냥 다시 시도할 뿐이에요.
하지만 사람은 다르죠. 틀리면 '아, 이 방법은 안 되는구나.
왜 안 될까? 다음엔 이렇게 해봐야겠다'라고 생각하잖아요." Reflexion이란 바로 이런 사고 과정을 AI 에이전트에 구현한 것입니다.
쉽게 비유하자면, Reflexion은 마치 운동선수가 경기 영상을 다시 보며 자기 플레이를 분석하는 것과 같습니다. "이 순간에 왜 패스를 했을까?
슛을 했어야 했는데." 같은 구체적인 반성을 통해 다음 경기에서는 더 나은 판단을 합니다. AI 에이전트도 똑같이 자신의 시도를 되돌아보며 명시적인 교훈을 만들어냅니다.
Reflexion이 없던 시절에는 어떻게 했을까요? 전통적인 방법은 단순 재시도였습니다.
틀리면 그냥 다시 생성합니다. 운이 좋으면 다른 답이 나올 수도 있지만, 근본적인 접근 방식이 틀렸다면 몇 번을 반복해도 성공하기 어렵습니다.
마치 같은 방법으로 문을 밀다가 안 열리면 더 세게 밀어보는 것과 같습니다. 실제로는 당겨야 하는 문인데 말이죠.
더 큰 문제는 무의미한 반복이었습니다. 왜 실패했는지 모르니까 같은 실수를 계속 반복합니다.
비용만 늘어나고 성공률은 오르지 않습니다. 바로 이런 문제를 해결하기 위해 Reflexion 패턴이 등장했습니다.
Reflexion을 사용하면 의미 있는 개선이 가능합니다. 단순히 다시 시도하는 게 아니라, 왜 틀렸는지 분석하고 다른 접근법을 시도합니다.
또한 누적 학습도 이뤄집니다. 각 시도마다 구체적인 교훈을 얻어서 장기 기억에 저장하니, 비슷한 문제를 만났을 때 과거의 실수를 피할 수 있습니다.
무엇보다 투명성이라는 큰 이점이 있습니다. "이번에는 이런 이유로 이렇게 접근했습니다"라는 근거를 명확히 알 수 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. solve 메서드가 핵심입니다.
먼저 get_relevant_reflections로 과거에 비슷한 문제에서 얻은 교훈을 찾아옵니다. 예를 들어 "이차방정식 문제는 근의 공식을 먼저 고려하라"는 식의 반성 내용입니다.
이런 컨텍스트를 참고하여 generate_solution으로 해결책을 만듭니다. 그리고 evaluate로 답이 맞는지 검증합니다.
틀렸다면 그냥 넘어가지 않고 reflect 메서드를 호출합니다. reflect 메서드는 실패를 분석합니다.
어떤 유형의 문제였는지, 어떤 접근법을 시도했는지, 왜 실패했는지, 다음에는 어떻게 해야 할지를 구조화된 형태로 정리합니다. 이것이 바로 **명시적 반성(Explicit Reflection)**입니다.
이렇게 만들어진 반성 내용은 memory에 저장되어 다음 시도나 미래의 비슷한 문제에 활용됩니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 코딩 테스트를 푸는 AI 에이전트를 개발한다고 가정해봅시다. 첫 시도에서 시간 복잡도를 고려하지 않아서 타임아웃이 발생했습니다.
에이전트는 이를 반성하며 "이 문제는 O(n^2) 알고리즘으로는 안 되고, 해시맵을 사용해서 O(n)으로 최적화해야 한다"는 교훈을 얻습니다. 두 번째 시도에서는 이 교훈을 반영하여 해시맵 기반 솔루션을 만들고, 성공적으로 통과합니다.
더 나아가 이 교훈은 저장되어, 나중에 비슷한 유형의 문제를 만났을 때 처음부터 최적화된 접근법을 시도할 수 있게 됩니다. 실제로 Reflexion은 코드 생성, 의사결정, 게임 플레이 등 다양한 분야에서 기존 방법보다 훨씬 높은 성공률을 보여주고 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 피상적인 반성입니다.
"틀렸다"는 것만 기록하고 "왜 틀렸는지", "다음에는 어떻게 해야 하는지"를 깊이 분석하지 않으면 의미가 없습니다. 반성은 구체적이고 실행 가능한 교훈을 담아야 합니다.
또한 반성의 품질이 중요합니다. 잘못된 분석을 바탕으로 한 반성은 오히려 해가 됩니다.
따라서 반성 내용 자체도 검증하는 메커니즘을 갖추는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
Reflexion 패턴을 적용한 후, 수학 문제 풀이 에이전트의 성능이 극적으로 향상되었습니다. 복잡한 문제도 몇 번의 시행착오 끝에 스스로 올바른 접근법을 찾아냈습니다.
"마치 에이전트가 정말 배우고 있는 것 같아요!" 김개발 씨가 신기해하며 말했습니다. Reflexion을 제대로 이해하면 단순히 작동하는 에이전트가 아니라, 스스로 성장하는 에이전트를 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 반성은 구체적이어야 합니다. "틀렸다" 대신 "변수 초기화를 빠뜨려서 틀렸다. 다음에는 변수 초기화를 먼저 확인하자"처럼 작성하세요.
- 반성 내용에 예제를 포함하면 더 효과적입니다. 추상적인 교훈보다는 구체적인 사례가 학습에 도움이 됩니다.
- 메모리 크기를 관리하세요. 모든 반성을 다 저장하면 비효율적이므로, 중요한 것만 선별하거나 요약하세요.
4. 실습 Self-Reflection 에이전트
이론은 충분히 배웠으니 이제 직접 만들어볼 시간입니다. 김개발 씨는 블로그 글을 자동으로 작성하는 에이전트를 만들기로 했습니다.
하지만 그냥 만드는 게 아니라, 자기 반성 기능을 넣어서 글의 품질을 스스로 높이는 에이전트를 만들 것입니다.
Self-Reflection 에이전트는 작업 결과를 생성한 후, 스스로 품질을 평가하고 개선점을 찾아 재작성하는 에이전트입니다. 글쓰기, 코드 생성, 데이터 분석 등 품질이 중요한 작업에서 특히 유용합니다.
여기서는 OpenAI API를 사용하여 실제로 작동하는 에이전트를 만들어봅니다.
다음 코드를 살펴봅시다.
import openai
class SelfReflectionAgent:
def __init__(self, api_key):
openai.api_key = api_key
def write_blog(self, topic, max_iterations=2):
# 초기 블로그 글 생성
content = self._generate_content(topic)
for i in range(max_iterations):
# 자기 평가 수행
critique = self._self_critique(content, topic)
# 평가 점수가 높으면 완료
if critique['score'] >= 8:
return content
# 개선점을 반영하여 재작성
content = self._improve_content(content, critique['suggestions'])
return content
def _generate_content(self, topic):
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": f"{topic}에 대한 블로그 글을 작성해주세요."}]
)
return response.choices[0].message.content
김개발 씨는 회사 기술 블로그를 자동화하고 싶었습니다. 하지만 AI가 생성한 글을 그대로 올리기에는 품질이 걱정되었습니다.
"어떻게 하면 AI가 스스로 글을 다듬을 수 있을까?" 고민하던 중, Self-Reflection 패턴을 떠올렸습니다. 먼저 전체 흐름을 이해해봅시다.
첫 번째 단계는 초기 콘텐츠 생성입니다. _generate_content 메서드가 주제를 받아서 블로그 글을 작성합니다.
이것은 첫 번째 초안입니다. 아직 완벽하지 않을 수 있습니다.
두 번째 단계는 자기 평가입니다. _self_critique 메서드를 보겠습니다.
이 메서드는 방금 작성한 글을 다시 읽어보며 스스로 평가합니다. "이 글은 얼마나 좋은가?
무엇이 부족한가?"라는 질문에 답합니다. ```python def _self_critique(self, content, topic): prompt = f""" 다음 블로그 글을 평가해주세요.
주제: {topic} 글: {content} 다음 기준으로 1-10점 평가: 1. 정확성: 정보가 정확한가?
-
완성도: 주제를 충분히 다루었는가?
-
가독성: 읽기 쉬운가?
JSON 형식으로 응답: {{"score": 점수, "suggestions": ["개선점1", "개선점2"]}} """ response = openai.ChatCompletion.create( model="gpt-4", messages=[{"role": "user", "content": prompt}] ) return json.loads(response.choices[0].message.content) ``` 여기서 핵심은 구체적인 평가 기준입니다. 막연하게 "좋은 글인가요?"라고 묻는 게 아니라, 정확성, 완성도, 가독성처럼 명확한 기준을 제시합니다.
이렇게 하면 AI가 더 객관적으로 평가할 수 있습니다. 세 번째 단계는 개선입니다.
평가 점수가 8점 미만이면 _improve_content 메서드가 호출됩니다. ```python def _improve_content(self, content, suggestions): prompt = f""" 다음 블로그 글을 개선해주세요.
현재 글: {content} 개선점: {chr(10).join(f"- {s}" for s in suggestions)} 위 개선점을 반영하여 글을 다시 작성해주세요. """ response = openai.ChatCompletion.create( model="gpt-4", messages=[{"role": "user", "content": prompt}] ) return response.choices[0].message.content ``` 이 메서드는 단순히 "다시 써주세요"가 아니라, 구체적인 개선점을 전달합니다.
예를 들어 "도입부에 실제 사례를 추가하세요", "코드 예제가 더 필요합니다" 같은 구체적인 피드백을 줍니다. 실제로 사용해봅시다.
python agent = SelfReflectionAgent(api_key="your-api-key") blog_post = agent.write_blog("Python 비동기 프로그래밍 입문") print(blog_post) 첫 번째 실행에서는 아마 기본적인 설명만 담긴 글이 나올 것입니다. 에이전트가 자기 평가를 하고 "코드 예제가 부족하다"는 것을 발견합니다.
두 번째 시도에서는 실제 코드를 추가한 더 나은 글을 생성합니다. 김개발 씨는 이 에이전트를 실제로 돌려봤습니다.
놀랍게도 첫 버전보다 두 번째 버전이 훨씬 읽기 쉽고 유용했습니다. "코드 예제도 추가되고, 설명도 더 자세해졌어요!" 하지만 실전에서는 몇 가지 더 고려할 점이 있습니다.
첫째, 비용 관리입니다. 한 번의 블로그 글 작성에 최대 3번의 API 호출이 발생합니다.
(생성 1번, 평가 1번, 개선 1번) 대량으로 사용한다면 비용이 빠르게 늘어날 수 있습니다. 따라서 max_iterations를 적절히 설정하는 것이 중요합니다.
둘째, 무한 루프 방지입니다. 드물게 에이전트가 계속 만족하지 못하고 반복할 수 있습니다.
위 코드는 최대 반복 횟수를 설정하여 이를 방지합니다. 셋째, 평가 기준의 명확성입니다.
"좋은 글"의 기준이 애매하면 평가도 일관성이 없어집니다. 프로젝트마다 평가 기준을 명확히 정의하세요.
김개발 씨는 이 에이전트를 실제 업무에 적용했습니다. 기술 블로그 초안을 빠르게 만들고, 사람이 최종 검토만 하는 방식으로 운영했습니다.
블로그 작성 시간이 절반으로 줄었고, 품질도 일정 수준 이상을 유지했습니다. "이제 글쓰기가 훨씬 수월해졌어요!" 김개발 씨가 만족스러워했습니다.
Self-Reflection 에이전트를 제대로 구현하면 품질과 효율성을 동시에 잡을 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 평가 기준은 숫자로 정량화하세요. "좋다/나쁘다"보다 "1-10점"이 더 명확합니다.
- 첫 시도와 개선된 버전을 모두 저장하면 학습 데이터로 활용할 수 있습니다.
- 실제 서비스에서는 응답을 캐싱하여 비용을 절감하세요.
5. 실습 코드 디버깅 에이전트
김개발 씨는 이제 한 단계 더 나아가기로 했습니다. 블로그 글쓰기를 넘어서, 실제 코드의 버그를 찾고 고치는 에이전트를 만들고 싶었습니다.
박시니어 씨가 말했습니다. "코드 디버깅은 반성과 학습의 연속이죠.
딱 Reflexion 패턴을 적용하기 좋은 문제예요."
코드 디버깅 에이전트는 버그가 있는 코드를 받아서, 테스트를 실행하고, 실패 원인을 분석하며, 수정안을 제시하고, 다시 테스트하는 과정을 반복합니다. Reflexion 패턴을 활용하여 각 시도에서 배운 교훈을 다음 시도에 적용합니다.
실전에서 바로 사용할 수 있는 강력한 도구입니다.
다음 코드를 살펴봅시다.
class CodeDebuggingAgent:
def __init__(self):
self.debug_memory = [] # 디버깅 기록
def fix_code(self, buggy_code, test_cases, max_attempts=3):
code = buggy_code
for attempt in range(max_attempts):
# 테스트 실행
test_result = self._run_tests(code, test_cases)
if test_result['all_passed']:
return {'fixed_code': code, 'attempts': attempt + 1}
# 실패 분석 및 반성
reflection = self._analyze_failure(code, test_result, self.debug_memory)
self.debug_memory.append(reflection)
# 반성을 바탕으로 코드 수정
code = self._generate_fix(code, reflection)
return {'fixed_code': None, 'attempts': max_attempts, 'last_error': test_result}
def _analyze_failure(self, code, test_result, memory):
# 과거 시도를 참고하여 실패 원인 심층 분석
return {
'failed_tests': test_result['failures'],
'error_pattern': self._identify_error_pattern(test_result),
'hypothesis': self._generate_hypothesis(code, test_result, memory),
'suggested_fix': self._suggest_fix_strategy(test_result)
}
김개발 씨는 실제 업무에서 자주 겪는 상황을 떠올렸습니다. 레거시 코드를 수정했는데 테스트가 실패합니다.
에러 메시지를 보고 수정하지만, 또 다른 테스트가 실패합니다. 이런 시행착오를 반복하다 보면 몇 시간이 훌쩍 지나갑니다.
"이 과정을 자동화할 수 있다면 얼마나 좋을까?" 김개발 씨는 코드 디버깅 에이전트를 만들기 시작했습니다. 먼저 전체 구조를 살펴봅시다.
fix_code 메서드가 진입점입니다. 버그가 있는 코드와 테스트 케이스를 받습니다.
실제 개발 환경에서는 pytest, unittest 같은 테스트 프레임워크의 테스트를 사용할 것입니다. 첫 번째 단계는 테스트 실행입니다.
_run_tests 메서드를 자세히 봅시다. python def _run_tests(self, code, test_cases): results = {'all_passed': True, 'failures': []} # 안전한 환경에서 코드 실행 namespace = {} try: exec(code, namespace) except Exception as e: return { 'all_passed': False, 'failures': [{'type': 'syntax_error', 'message': str(e)}] } # 각 테스트 케이스 실행 for test in test_cases: try: func = namespace[test['function']] actual = func(*test['input']) expected = test['output'] if actual != expected: results['all_passed'] = False results['failures'].append({ 'test': test, 'expected': expected, 'actual': actual }) except Exception as e: results['all_passed'] = False results['failures'].append({ 'test': test, 'error': str(e) }) return results 이 메서드는 코드를 실제로 실행하고, 각 테스트 케이스의 결과를 수집합니다.
중요한 것은 상세한 실패 정보를 기록한다는 점입니다. 단순히 "실패했다"가 아니라, 예상값과 실제값, 발생한 예외까지 모두 기록합니다.
두 번째 단계는 실패 분석입니다. 여기서 Reflexion 패턴의 진가가 드러납니다.
python def _analyze_failure(self, code, test_result, memory): # 에러 패턴 식별 error_types = [f['type'] if 'type' in f else 'logic_error' for f in test_result['failures']] # 과거 시도에서 배운 것 확인 past_lessons = [m['hypothesis'] for m in memory if m.get('was_correct') == False] # 새로운 가설 생성 hypothesis = f""" 에러 타입: {', '.join(set(error_types))} 실패한 테스트: {len(test_result['failures'])}개 분석: - 과거에 시도했지만 틀린 접근: {past_lessons} - 이번에는 다른 방향으로 접근 필요 """ return { 'failed_tests': test_result['failures'], 'error_pattern': error_types, 'hypothesis': hypothesis, 'suggested_fix': self._suggest_fix_strategy(test_result) } 핵심은 과거의 실패를 기억한다는 것입니다. "이미 이 방법은 시도했고 안 됐다"는 정보를 활용하여, 같은 실수를 반복하지 않습니다.
마치 미로를 탈출할 때 이미 가본 길을 표시해두는 것과 같습니다. 세 번째 단계는 코드 수정입니다.
""" # LLM에게 수정 요청 response = openai.ChatCompletion.create( model="gpt-4", messages=[{"role": "user", "content": prompt}] ) return response.choices[0].message.content ``` 여기서 중요한 것은 **컨텍스트의 풍부함**입니다. 단순히 "이 코드를 고쳐주세요"가 아니라, 어떤 테스트가 실패했는지, 왜 실패했는지, 어떤 접근이 이미 실패했는지 모두 전달합니다.
이렇게 하면 LLM이 훨씬 더 정확한 수정안을 제시할 수 있습니다. 실제 사용 예시를 봅시다.
```python # 버그가 있는 코드 buggy_code = """ def calculate_average(numbers): total = 0 for num in numbers: total += num return total / len(numbers) # 빈 리스트 처리 안 됨 """ # 테스트 케이스 test_cases = [ {'function': 'calculate_average', 'input': [[1, 2, 3]], 'output': 2.0}, {'function': 'calculate_average', 'input': [[]], 'output': 0}, # 이게 실패함 {'function': 'calculate_average', 'input': [[10]], 'output': 10.0} ] agent = CodeDebuggingAgent() result = agent.fix_code(buggy_code, test_cases) if result['fixed_code']: print(f"버그 수정 완료! {result['attempts']}번 만에 성공") print(result['fixed_code']) else: print("수정 실패") ``` 첫 번째 시도에서 에이전트는 빈 리스트에서 `ZeroDivisionError`가 발생한다는 것을 발견합니다.
이를 분석하여 "빈 리스트 처리가 필요하다"는 가설을 세웁니다. 수정된 코드는 이렇게 됩니다.
```python def calculate_average(numbers): if not numbers: return 0 total = 0 for num in numbers: total += num return total / len(numbers) ``` 모든 테스트가 통과하고 성공적으로 완료됩니다. 김개발 씨는 실제 프로젝트에 이 에이전트를 적용해봤습니다.
레거시 코드의 간단한 버그는 몇 분 안에 자동으로 고쳐졌습니다. 복잡한 버그는 완전히 해결하지 못하더라도, 에이전트가 제시한 분석과 가설이 디버깅에 큰 도움이 되었습니다.
"이제 단순 반복적인 디버깅은 에이전트에게 맡기고, 나는 더 창의적인 일에 집중할 수 있어요!" 김개발 씨가 기뻐하며 말했습니다. 하지만 실전에서는 주의할 점도 있습니다.
**첫째, 보안**입니다. `exec()`로 임의의 코드를 실행하는 것은 위험할 수 있습니다.
실제 서비스에서는 샌드박스 환경에서 실행하거나, 코드 분석 도구를 사용하는 것이 좋습니다. **둘째, 복잡도**입니다.
간단한 버그는 잘 고치지만, 여러 파일에 걸친 복잡한 버그는 어려울 수 있습니다. 에이전트의 한계를 인식하고 적절한 작업에만 사용하세요.
**셋째, 비용**입니다. 코드 수정 시도마다 LLM API를 호출하므로 비용이 발생합니다.
간단한 버그는 정적 분석 도구로 먼저 걸러내는 것이 효율적입니다. 코드 디버깅 에이전트를 제대로 구현하면 개발 생산성을 크게 높일 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
**실전 팁**
💡 - 테스트 커버리지를 높이세요. 테스트가 많을수록 에이전트가 버그를 정확히 찾아냅니다.
- 에러 메시지는 최대한 상세하게 수집하세요. 스택 트레이스, 변수값 등이 모두 중요한 힌트입니다.
- 성공한 수정 사례를 학습 데이터로 저장하면, 나중에 파인튜닝에 활용할 수 있습니다.
---
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Memory Systems 완벽 가이드 - LLM 에이전트의 기억력 설계
LLM 에이전트가 대화를 기억하고 학습하는 메모리 시스템의 모든 것을 다룹니다. 단기 메모리부터 벡터 DB 기반 장기 메모리, 에피소딕 메모리까지 실전 구현 방법을 배워봅니다.
Tool-Augmented Agents 완벽 가이드
LLM에 도구를 연결하여 검색, API 호출, 계산 등 실제 작업을 수행하는 에이전트를 만드는 방법을 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 실무에서 바로 활용할 수 있는 멀티 툴 에이전트와 API 통합 예제를 포함합니다.
Tree of Thoughts 에이전트 완벽 가이드
AI 에이전트가 복잡한 문제를 해결할 때 여러 사고 경로를 탐색하고 평가하는 Tree of Thoughts 기법을 배웁니다. BFS/DFS 탐색 전략부터 가지치기, 백트래킹까지 실전 예제와 함께 쉽게 설명합니다.
Plan-and-Execute 패턴 완벽 가이드
LLM 에이전트가 복잡한 작업을 해결하는 Plan-and-Execute 패턴을 배웁니다. 계획 수립, 실행, 재조정의 3단계 프로세스를 실무 예제로 익히고, 멀티 스텝 자동화를 구현합니다.
ReAct 패턴 마스터 완벽 가이드
LLM이 생각하고 행동하는 ReAct 패턴을 처음부터 끝까지 배웁니다. Thought-Action-Observation 루프로 똑똑한 에이전트를 만들고, 실전 예제로 웹 검색과 계산을 결합한 강력한 AI 시스템을 구축합니다.