
LLM을 활용하는 방식은 다양하다. 이번 글에서는 그중에서도 사용자에게 직접 보여줄 자연어를 생성하는 태스크에 초점을 맞춰보려 한다.
예를 들어 주식 앱에서는 최근 주가가 변동한 이유를 사용자에게 설명할 수 있다. 데이팅 앱에서는 두 사람의 공통점이나 매칭 근거를 바탕으로 “이 사람을 만나보는 건 어떨까요?”와 같은 추천 문구를 생성할 수 있다.
이런 기능을 위한 프롬프트 엔지니어링 과정은 겉으로 보면 단순하다.
- 서비스 정책을 정의하고 프롬프트를 작성한다.
- 프롬프트의 출력 결과를 확인한다.
- 결과를 바탕으로 정책과 프롬프트를 수정한다.

정책, 프롬프트, 출력, 평가는 한 번으로 끝나는 단계가 아니라 반복되는 개선 사이클을 이룬다.
간단한 문제라면 이 사이클을 빠르게 반복할 수 있다. 하지만 해결하려는 문제가 복잡하고 출력에 적용할 정책이 많아질수록 반복 속도는 급격히 느려진다. 프롬프트 한 줄을 고치는 일보다 무엇이 좋은 출력인지 합의하고, 실제로 개선됐는지 검증하는 일이 더 어려워지기 때문이다.
이 글에서는 동일한 성능의 모델을 계속 사용한다고 가정하고, 자연어 출력 프롬프트를 개선하는 과정이 왜 어려운지 살펴본다.
프롬프트 개선이 필요한 두 가지 상황
프롬프트를 개선해야 하는 상황은 크게 두 가지로 나눌 수 있다.

정책은 있는데 모델이 제대로 실행하지 못한 경우와, 출력이 정책의 빈틈을 드러낸 경우는 서로 다른 문제다.
1. 정책은 있지만 출력이 정책을 따르지 않는 경우
프롬프트에 규칙을 명시했는데도 결과가 원하는 대로 나오지 않는 경우다. 규칙이 모호하거나 서로 충돌할 수도 있고, 우선순위가 충분히 명확하지 않을 수도 있다. 이때는 정책의 의도를 모델이 더 잘 이해하고 따를 수 있도록 프롬프트의 구조와 표현을 개선해야 한다.
2. 출력을 본 뒤에야 새로운 정책이 필요하다는 사실을 발견한 경우
미처 예상하지 못한 출력이 생성되고, 이를 본 뒤에 “이런 경우에는 이렇게 동작해야 한다”는 새로운 요구사항이 생길 수 있다. 이것은 단순한 프롬프트 작성의 문제가 아니다. 먼저 제품이나 운영 정책을 새롭게 정의해야 하는 문제다.
두 상황을 구분하는 것은 중요하다. 첫 번째는 이미 정해진 정책을 더 잘 실행하도록 만드는 문제이고, 두 번째는 정책 자체의 빈틈을 채우는 문제이기 때문이다. 원인이 다른 만큼 해결 방법도 달라야 한다.
프롬프트 최적화가 느리고 어려운 이유

흩어진 정책과 다양한 출력은 복잡한 평가 과정을 거쳐 결국 사람의 검토 지점에 병목을 만든다.
정책이 여러 곳에 흩어져 있다
여러 이해관계자가 결과물을 검토하면 다양한 피드백이 나온다. 문제는 그 피드백이 메신저, 회의록, 이슈 트래커 등 여러 곳에 흩어질 때 발생한다.
누군가는 “문장이 너무 딱딱하다”고 하고, 다른 누군가는 “표현이 지나치게 가볍다”고 할 수 있다. 이런 의견을 한곳에 모으지 않으면 서로 충돌하는 요구사항을 발견하기 어렵고, 일회성 피드백을 일관된 정책으로 발전시키기도 어렵다. 결국 프롬프트에는 일부 의견만 임시로 반영되고, 시간이 지날수록 왜 해당 규칙이 추가됐는지조차 알기 어려워진다.
**따라서 정책은 하나의 SSOT(Single Source of Truth)**에서 관리되어야 한다. 여기에는 최종 규칙뿐 아니라 규칙이 생긴 이유, 예외 조건, 우선순위도 함께 기록되어야 한다.
수정한 프롬프트가 실제로 더 나아졌는지 확인하기 어렵다
특정 실패 사례 하나를 고친 프롬프트가 다른 입력에서도 더 좋은 결과를 만든다는 보장은 없다. 한 가지 표현 문제를 해결하는 동안 기존에 잘 처리하던 사례가 나빠질 수도 있다. 즉, 프롬프트 수정에도 일반적인 소프트웨어 변경과 마찬가지로 회귀(regression)가 발생한다.
명확한 정책과 평가 기준이 없다면 회귀 테스트 자체가 불가능하다. 결과가 마음에 드는지 몇 건 살펴보는 것만으로는 전체 성능이 개선됐다고 판단하기 어렵다.
자연어 출력에는 하나의 정답만 존재하지 않는다
분류나 추출 태스크는 정답을 비교적 명확하게 정의할 수 있다. 반면 자연어 생성에서는 서로 다른 여러 문장이 모두 좋은 답이 될 수 있다. 그렇기 때문에 GT(Ground Truth)를 단 하나의 모범 문장으로만 정의하면 표현이 조금 다르다는 이유로 올바른 결과까지 실패로 판정할 수 있다.
자연어 생성에서의 GT는 정답 문장이라기보다 좋은 출력이 만족해야 할 조건의 집합에 가깝다. 예를 들면 다음과 같다.
- 반드시 포함해야 하는 정보
- 포함해서는 안 되는 표현이나 내용
- 지켜야 할 말투와 문장 길이
- 입력 데이터와의 사실적 일관성
- 정책 간 충돌이 발생했을 때의 우선순위
따라서 테스트 케이스에는 입력과 기대 출력만 저장할 것이 아니라, 해당 사례가 통과해야 할 평가 기준과 실패 사유도 함께 관리해야 한다.
모든 결과를 사람이 직접 검토할 수 없다
사람의 평가는 중요하지만, 모든 프롬프트 버전과 모든 테스트 케이스의 조합을 매번 직접 검토하는 방식은 확장하기 어렵다. 테스트 케이스가 늘어날수록 검토 시간은 길어지고, 평가자마다 판단이 달라질 가능성도 커진다.
따라서 명확한 금칙어나 형식처럼 기계적으로 판단할 수 있는 항목은 규칙 기반 검사로 처리하고, 의미나 말투처럼 정성적인 항목은 모델 기반 평가를 활용할 수 있다. 사람은 경계 사례와 평가 결과가 불확실한 사례를 검토하는 데 집중하는 편이 효율적이다.
사람의 감에만 의존한 수정은 재현하기 어렵다
사람이 직접 프롬프트를 수정하는 행위 자체가 문제인 것은 아니다. 다만 수정 이유와 평가 결과를 남기지 않은 채 감에 의존해 문구를 바꾸는 방식은 실수를 만들기 쉽다. 어떤 변경이 성능을 높였는지 설명하기 어렵고, 문제가 생겨도 이전 상태로 안전하게 돌아가기 힘들다.
프롬프트, 정책, 평가 데이터셋, 평가 결과는 서로 연결된 상태로 버전이 관리되어야 한다. 그래야 변경 전후의 결과를 비교하고, 개선 효과를 재현하며, 필요할 때 되돌릴 수 있다.
빠르고 안정적인 개선을 위해 필요한 것
결국 프롬프트 개선의 속도와 품질을 높이려면 다음과 같은 기반이 필요하다.

정책, 평가 데이터셋, 자동 회귀 평가, 사람의 경계 사례 검토가 하나의 피드백 루프로 연결되어야 한다.
- 정책의 중앙화: 정책과 예외, 우선순위, 변경 이유를 하나의 SSOT에서 관리한다.
- 평가 데이터셋의 버전 관리: 실제 실패 사례를 테스트 케이스로 축적하고, 각 사례의 평가 기준과 실패 사유를 함께 기록한다.
- 자동화된 회귀 평가: 프롬프트가 바뀔 때마다 전체 테스트 케이스를 실행하고 변경 전후의 결과를 비교한다.
- 효율적인 사람 검토: 모든 결과가 아니라 자동 평가가 어렵거나 불확실한 사례를 중심으로 검토한다.
- 실험의 추적 가능성: 어떤 정책과 프롬프트, 모델, 평가셋을 사용해 어떤 결과가 나왔는지 연결해서 남긴다.
GT가 명확한 경우 프롬프트 최적화를 자동화하는 방법은 이미 다양하다. 하지만 평가 기준이 모호하다면 자동화된 최적화는 잘못된 목표를 더 빠르게 최적화할 뿐이다. 따라서 자동화보다 먼저 해야 할 일은 무엇을 좋은 출력으로 볼 것인지 명확하게 정의하는 것이다.
마치며
복잡한 자연어 생성 태스크에서 진짜 최적화 대상은 프롬프트 문구 하나가 아니다. 정책을 정의하고, 실제 사례를 평가 데이터로 축적하고, 변경의 효과를 반복해서 검증하는 전체 과정이 최적화 대상이다.
사람은 정책을 결정하고 모호한 경계 사례를 판단하는 데 집중해야 한다. 시스템은 이미 합의된 기준을 반복해서 검사하고 변경 이력을 추적해야 한다. 이 역할이 분리될 때 프롬프트 개선은 개인의 감각에 의존하는 작업에서 재현 가능한 엔지니어링 과정으로 발전할 수 있다.
이 글에 대한 생각을 남겨주세요.
GitHub 계정으로 로그인하면 댓글과 반응을 남길 수 있습니다.