프롬프트 엔지니어링 vs 블라인드 프롬프팅
“프롬프트 엔지니어링”은 언어 모델의 성장과 함께 등장한 용어로, 주로 실제 애플리케이션에서 활용하기 위해 프롬프팅을 이용해 언어 모델로부터 정보를 효과적으로 추출하는 과정을 설명합니다.
오늘날 프롬프트 엔지니어링을 하고 있다고 주장하는 많은 사람들이 실제로는 그저 블라인드 프롬프팅을 하고 있을 뿐입니다.1 “블라인드 프롬프팅”은 최소한의 테스트도 없이 피상적인 수준의 프롬프팅 지식만을 바탕으로 조잡한 시행착오 방식으로 프롬프트를 만드는 방법을 가리키기 위해 제가 사용하는 용어입니다. 블라인드 프롬프팅은 프롬프트 엔지니어링이 아닙니다.
프롬프트 엔지니어링을 과연 ‘엔지니어링’이라 부를 수 있는지, 아니면 그저 유행을 좇는 사람들이 떠드는 “주술”에 불과한지에 대한 회의론도 많습니다. 제 생각에 대부분의 회의론은 제가 트위터나 블로그 포스트에서 프롬프트 엔지니어링이라 주장하는 글들을 봤을 때, 그 글들이 기껏해야 블라인드 프롬프팅보다 한 겹 정도 나은 수준에 머물러 있다는 사실에서 비롯됩니다.
이 글에서는 프롬프트 엔지니어링이 실제 실험적 방법론에 기반해 연마할 수 있는 진짜 기술이라는 점을 주장하고자 합니다. 애플리케이션에 실질적인 가치를 제공하는 문제를 해결하기 위해 프롬프트 엔지니어링으로 솔루션을 만드는 과정을 현실적인 예시를 통해 단계별로 살펴보겠습니다.
이 글 전체는 텍스트 출력을 전제로 합니다. 텍스트 출력이 제가 언어 모델을 활용해 온 주된 사용 사례이기 때문입니다. 테스트 기법과 같은 일부 기법은 이미지와 같은 다른 형태의 출력에는 1대1로 그대로 적용되지 않습니다. 다만 이 글의 내용은 다중 모달 입력에는 충분히 잘 적용됩니다.
프롬프팅이란 무엇인가?
“프롬프팅”이나 “프롬프트”라는 용어에 이미 익숙하다면 이 섹션은 건너뛰어도 좋습니다.
언어 모델(용어가 익숙하지 않다면 ChatGPT를 떠올리면 됩니다)에서 “프롬프트”는 모델에 입력하는 사용자가 생성한 입력값을 말합니다. ChatGPT에서는 입력 창에 직접 타이핑하는 텍스트라고 이해하면 됩니다. 언어 모델은 프롬프트에 대한 “완성(completion)”을 추론해 냅니다. 예를 들어 ChatGPT에 “4 + 3 = ”이라고 입력하면 모델은 “7”이라고 응답할 것입니다(아마도). 이 경우 “4 + 3 = ”이 프롬프트이고 “7”이 완성입니다.
“프롬프팅”은 프롬프트를 이용해 모델로부터 원하는 정보를 추출하는 행위입니다. 대규모 오프라인 학습 데이터셋이 필요하지 않고, 모델에 대한 오프라인 접근 권한이 필요하지 않으며, 비전문가도 직관적으로 이해할 수 있다는 점에서 매력적인 접근 방식입니다. 프롬프팅은 모델을 튜닝하는 여러 방법 중 하나일 뿐입니다.
마지막으로 “프롬프트 엔지니어링”은 프롬프팅을 활용해 실제 애플리케이션을 위한 안정적인 기능을 구축하려는 보다 엄밀한 분야를 의미합니다(이 글에서 그 내용을 보여드릴 것입니다). ChatGPT 스타일의 프롬프팅과 다른 점은, 프롬프트 엔지니어링을 통해 만들어진 프롬프트는 일반적으로 특정 문제를 안정적으로 해결하기 위해 대량의 다양한 상황에서 반복적으로 사용될 것을 전제로 한다는 것입니다.
문제
먼저 해결하려는 문제가 있어야 합니다. 문제를 통해 프롬프팅이 최선의 해결책인지, 혹은 더 적합할 수 있는 다른 대안이 있는지 평가할 수 있습니다. 엔지니어링은 방법을 위한 방법을 쓰는 것이 아니라, 그 방법이 올바른 방법이라는 믿음에서 출발합니다.
이 예시에서는 우리가 캘린더 클라이언트를 만드는 회사라고 가정해 보겠습니다. 사용자가 자연어를 이용해 일정을 입력할 수 있도록 하고 싶습니다. 예를 들면 다음과 같습니다.
Dinner with Alice next Tuesday at Taco Bell
CorpConf on 11/4
1:1 with Bob tomorrow at 10 AM
언어 모델은 이러한 자연어 입력을 받아 애플리케이션에서 활용할 수 있는 구조화된 출력으로 일정을 추출하는 좋은 해결책이 될 수 있습니다.
물론 다른 잠재적인 해결책도 있습니다. 정규 표현식과 문자열 검색을 활용해 일반적인 표현(on <Day of Week>, tomorrow, today, next week 등)을 찾는 방법을 사용할 수 있습니다.
언어 모델만의 장점도 있습니다. 다른 언어를 더 잘 처리할 수도 있고, 오타나 문법 오류를 더 잘 처리할 수도 있으며, 최악의 경우에도 정규 표현식이 실패했을 때 좋은 대비책이 될 수 있습니다. 어쨌든 프롬프팅을 잠재적인 해결책으로 계속 추진해 볼 만한 가능성은 충분합니다.
데모 세트
다음으로 데모 세트를 구성해야 합니다. 데모 세트는 예상 입력과 예상 출력을 함께 담고 있습니다. 이 세트는 여러 가지 목적을 수행합니다.
프롬프트의 정확도를 측정하는 데 사용됩니다. 단일 데모의 입력을 이용해 예상 출력이 제대로 나오는지 검증할 수 있습니다.
프롬프트의 입력과 출력이 어떤 형태여야 하는지 명시하여, 엔지니어로서 그것이 문제에 적합한 형태인지 판단할 수 있게 합니다.
퓨샷(few-shot) 방식을 사용하기로 한다면, 이 데모 세트의 일부를 예시(exemplar)로 활용할 수 있습니다. “퓨샷”이라는 용어가 익숙하지 않다면, 프롬프트에 예시를 함께 제공하는 스타일을 말합니다. 퓨샷과 제로샷 프롬프팅에 대한 좋은 개요는 여기를 참고하세요.
위에서 (2)번은 매우 중요합니다. 입력으로 무엇을 기대하고 출력으로 무엇을 기대하는지에 대한 전반적인 이해가 필요합니다. 양쪽 모두 [대개] 데이터를 특정 형식으로 보장하고 특정 형식을 돌려받을 것을 기대하는 소프트웨어가 존재하기 때문입니다. 이는 입출력 기대치가 있는 일련의 함수로 문제를 분해하는 일반적인 소프트웨어 엔지니어링과 다르지 않습니다.
앞선 예시를 활용해 완전한 데모 형태로 확장할 수 있습니다.
Q: Dinner with Alice next Tuesday at Taco Bell
A: next Tuesday
Q: CorpConf on 11/4
A: 11/4
Q: 1:1 with Bob tomorrow at 10 AM
A: tomorrow
데모 세트 크기에 대한 참고: 이 글에서는 데모가 세 개뿐입니다. 실제로는 최소 열 개 이상을 갖추는 것이 좋습니다. 데모가 많을수록 더 잘 테스트할 수 있지만, 토큰 사용량 때문에 비용도 더 많이 듭니다. 일정 규모를 넘어서면 언어 모델을 파인튜닝하는 것이 더 경제적일 때가 많습니다.
위의 데모에는 두 가지 중요한 결정이 담겨 있습니다. 어떤 프롬프팅 문제에서도 비슷한 결정을 내려야 합니다.
첫째, 하나의 정보만 추출합니다. 일정 이름, 참석자, 시간, 장소 등 전체 일정을 추출해 바로 사용할 수 있는 멋진 JSON이나 다른 형식으로 출력하고 싶은 유혹이 들 수 있습니다. 모델이 그것을 해낼 수도 있습니다. 하지만 새로운 문제에 접근할 때는 먼저 단일 문제로 분해하는 것을 권장합니다. 이렇게 하면 문제를 다루기 쉬워지고, 나중에 다중 출력 방식이 실제로 그만한 가치가 있는지 판단할 수 있는 기준 정확도도 얻을 수 있습니다.
둘째, 출력을 변환하지 않습니다. 모든 것을 날짜로 변환하거나 모든 대소문자를 올바르게 정리하려는 시도를 하지 않습니다. 말 그대로 텍스트를 추출하는 것입니다. “next Tuesday”와 같은 문자열을 타임스탬프로 변환하는 작업은 기존의 결정론적 라이브러리로도 매우 높은 정확도로 처리할 수 있는 훌륭한 라이브러리들이 있습니다. 이는 언어 모델에 맡길 필요가 없는 일입니다. 따라서 일단 거친 날짜 형태(“next Tuesday”, “11/4”, “tomorrow”)만 추출하고, 이를 타임스탬프로 변환하는 문제는 자명한 문제이므로 전통적인 프로그래밍 방식으로 해결하면 됩니다. 출력이 단순할수록 더 높은 정확도를 얻기가 쉽습니다.
마지막으로 출력 디코딩에 대한 간단한 참고: LLM은 프롬프트를 다양한 방식으로 완성합니다. 완전한 문장으로 완성할 수도 있고, 마침표를 붙일 수도 있으며, 대문자로 시작할 수도 있습니다. LLM의 출력이 얼마나 완벽해야 하는지, 그리고 데모 세트를 검증하기 전에 어느 정도까지 정규화할 것인지 결정해야 합니다.
예를 들어 텍스트 추출을 할 때는 앞뒤 공백과 마침표를 제거하고 전체 출력을 소문자로 변환하는 정도는 합리적이라고 봅니다. JSON 생성과 같이 더 발전된 작업을 할 때는 JSON을 파싱한 뒤 결정론적인 순서와 스타일로 다시 인코딩하여 비교가 결정론적으로 이루어지도록 할 수도 있습니다. 등등 여러 방법이 있습니다.
제 권장 사항은 LLM의 출력을 가능한 한 단순하고 유연하게 유지하고, 애플리케이션에서 일부 정규화 작업을 수행하라는 것입니다. 처음부터 LLM이 완벽하게 정확한 형식으로 출력하도록 강제하지 마세요. 초기에 LLM에서 너무 많은 “출력 형태 다듬기”를 수행하면 LLM의 핵심 작업(여기서는 정보 추출) 수행 능력과 출력 구조화 능력을 분리하기 어려워집니다.
프롬프트 후보
이제 몇 가지 프롬프트 후보를 만들어 봅니다. 프롬프트 후보는 우리가 원하는 동작을 언어 모델로부터 이끌어낼 수 있을 것 같은 프롬프트입니다. 여러 후보를 만드는 이유는 처음부터 최고의 프롬프트를 고를 가능성이 낮기 때문입니다.
입문 수준의 글을 위해 여기서는 수동으로 프롬프트를 만들어 보겠습니다. 효과적으로 작성하려면 프롬프트 엔지니어가 프롬프트를 만들 때 활용해야 할 몇 가지 기본 지식이 있습니다. 예를 들어 방어적으로 쓰기보다 단호하게 쓰는 것이 낫고, 반복적이고 길게 쓰기보다 명확하고 간결하게 쓰는 것이 종종 더 좋습니다. 퓨샷 프롬프트를 만들 때는 레이블의 균등한 분포가 중요하고, 전체 레이블 집합을 보여주는 것이 중요하다는 등의 원칙이 있습니다. 예시를 고를 때는 LLM이 틀릴 가능성이 높은 예시가 일반적으로 가장 좋은 성능을 보이고, 예시는 짧은 것부터 긴 것 순으로 정렬했을 때 종종 가장 좋은 성능을 보인다는 등의 연구 결과도 있습니다.
출처 필요! 죄송하지만 이러한 권장 사항을 뒷받침하는 실험적 연구를 인용하지 않았습니다. 솔직히 말하면 관련 논문(항목 하나당 종종 여러 편)을 찾아보는 것이 귀찮았기 때문입니다. 저를 믿지 않으셔도 괜찮습니다. 더 중요한 점은 프롬프팅 기법과 그 효과에 대한 실험적 연구가 존재한다는 사실입니다. 하지만 제가 이것들을 지어낸 것은 아니라고 약속드리며, 일부는 최신 모델에서는 더 이상 유효하지 않을 수도 있습니다.
이러한 기법들을 하나하나 다루는 것만으로도 별도의 글이 필요하므로 이 글의 목표는 아닙니다. 이 글의 목표는 전체적인 종단간 과정을 보여주고, LLM으로부터 가치를 추출하는 데 엔지니어링적인 방법이 존재함을 보여주는 것입니다.
이 단계에서의 목표는 괜찮은 제로샷 프롬프트를 몇 개 만드는 것입니다. 제로샷 프롬프트는 퓨샷으로 변환될 수 있고, 다시 사고의 연쇄(chain of thought)로 변환될 수 있습니다. 그리고 각각은 다시 배치 프롬프트 등으로 변환될 수 있습니다. 따라서 제로샷은 반드시 시도해야 하는 기본 요건이므로 여기에 집중합니다.
제가 생각해 낸 세 가지 프롬프트 후보는 다음과 같습니다.
Identify the date or day mentioned in the given text and provide it as the output.
Identify the date or day mentioned in the given event description.
Determine the date or day from each input and provide the output accordingly as a single word or date.
모두 합리적인 프롬프트입니다. 어느 정도 교육을 받은 사람이라면 모든 프롬프트에서 매우 높은 정확도를 보일 것입니다. 하지만 언어 모델은 사람이 아니므로 동일한 성능을 자동으로 기대할 수는 없습니다. 저는 이전에 아주 합리적으로 보이는 프롬프트도 형편없는 성능을 보일 수 있음을 보여드린 적이 있습니다. 따라서 다음 단계는 테스트를 통해 측정을 하고 의사결정에 참고하는 것입니다.
프롬프트 테스트
후보 프롬프트 세트와 데모 세트가 준비되면 이제 정확도를 측정할 수 있습니다. 오늘날 제가 가장 효과적이라고 생각하는 방법은 LangChain과 같은 라이브러리를 이용해 간단한 Python 스크립트를 만드는 것입니다. 제 테스트에서는 보통 각 데모에 대해 다음과 같은 프롬프트 템플릿을 사용해 실행합니다.
{{prompt}}. Q: {{input}} A:
저는 항상 제로샷을 먼저 테스트합니다. 기준 정확도 지표를 얻고 싶기 때문입니다. 그다음 퓨샷을 테스트해 후보별, 프롬프팅 유형별로 비교할 수 있습니다. 그리고 계속해서 확장해 나갑니다.
이는 모델별로 수행해야 합니다. 더 강력한 모델에서도 동일한 프롬프트가 동일하거나 더 나은 정확도를 보장하지는 않습니다. 정확도가 오히려 떨어질 수도 있습니다2.
프롬프트 테스트의 가장 기본적인 결과는 아래와 같은 표가 되어야 합니다. 모델(GPT-3.5 대 GPT-4 등)과 같은 추가적인 차원이 있을 수도 있습니다.
| Zero-Shot | Few-Shot | ... |
-----------------------------------------
Prompt 1 | 64 | 68 | ... |
-----------------------------------------
Prompt ... | 44 | 52 | ... |
-----------------------------------------
Prompt N | 23 | 22 | ... |
이 표는 Y축에 프롬프트 후보를, X축에 해당 프롬프트를 이용한 프롬프트 유형을 나타냅니다. 값은 정답 비율을 백분율로 나타낸 정확도입니다. 캘린더 앱 예시를 계속 이어가면, 하나의 데모와 함께한 제로샷 프롬프트로서 “prompt 1”은 다음과 같이 보일 수 있습니다.
Identify the date or day mentioned in the given text and provide it as the output. Q: CorpConf on 11/4 A:
그리고 정답으로 “11/4”를 기대합니다. 퓨샷 버전은 다음과 같이 보일 수 있습니다.
Identify the date or day mentioned in the given text and provide it as the output.
Q: Dinner with Alice next Tuesday at Taco Bell. A: next Tuesday
Q: 1:1 with Bob tomorrow at 10 AM. A: tomorrow
Q: CorpConf on 11/4. A:
경험 많은 프롬프터를 위한 참고: 퓨샷 예시에는 “아래 예시를 따라 하세요”와 같은 문구가 없습니다. 실험 연구에 따르면 이러한 문구가 정확도를 안정적으로 높이지 않으므로, 토큰을 절약하기 위해 저는 먼저 이 문구 없이 테스트하는 것을 선호합니다. 둘째, 퓨샷 예시의 예시들은 “MM/DD” 추출을 예시로 보여주지 않는데, 이는 바람직하지 않은 형태입니다. 실제 퓨샷 설정에서는 모든 스타일의 추출을 보여주는 것이 중요할 수 있습니다(Zhao 외 2021).
분류 문제와 같은 특정 유형의 문제에서는 혼동 행렬(confusion matrix)(Strobelt 외 2022)을 이용해 다른 레이블의 확률을 시각화하고, 이를 통해 레이블 집합을 더 잘 조정할 수 있는지 판단할 수 있습니다.
정확도 외에도 사용된 토큰 수, 요청 수 등을 측정해야 합니다. 이 모든 요소는 최종 프롬프트를 선택할 때 함께 고려해야 합니다.
프롬프트 선택
마지막으로 애플리케이션에 통합할 프롬프트 후보 중 하나를 선택합니다. 이는 반드시 가장 정확한 프롬프트일 필요는 없습니다. 사용된 모델, 필요한 토큰, 그리고 제시된 정확도를 바탕으로 한 비용 대 정확도 분석의 문제입니다.
예를 들어 퓨샷 변형이 가장 좋은 성능을 보이지만 테스트 세트에서 정확도가 4% 높을 뿐이면서 토큰은 200% 더 필요하다는(현재 API 기반 모델에서는 사실상 비용이 두 배가 된다는 의미) 것을 알게 될 수도 있습니다. 비즈니스 관점에서 비용을 절반으로 줄이기 위해 4% 낮은 정확도를 감수하는 것이 더 가치 있다고 판단할 수도 있습니다.
혹은 정확도를 높이기 위한 다른 접근법을 다시 테스트해 보기로 결정할 수도 있습니다. 예를 들어 더 저렴한 모델에 자기 일관성(self-consistency) 디코딩 전략(Wang 외 2022)을 시도해 정확도가 충분히 개선되는지 확인해 볼 수 있습니다. 때로는 더 저렴한 모델에서 더 많은 토큰을 사용하는 것이 더 비싼 모델에서 적은 토큰을 사용하는 것보다 비용을 크게 절약해 줍니다. 예를 들어 현재 GPT-4는 GPT-3.5보다 약 15배 비쌉니다. 즉, GPT-3.5 프롬프트의 정확도를 높이는 데 사실상 15배의 토큰 예산이 있다는 의미입니다(속도 제한에 대한 주의 사항은 감안해야 합니다).
이 글의 예시에서는 표에서 64%의 정확도를 가지면서 토큰이 훨씬 적게 들 가능성이 높은 제로샷 버전의 Prompt 1을 선택할 수도 있습니다. 캘린더 애플리케이션의 일정 템플릿을 채우는 용도로는 64% 정확도도 일단 충분하다고 판단할 수도 있습니다. 이 특정 문제에서는 64%보다 훨씬 더 잘할 수 있다고 생각하지만, 여기서는 예시로 그 숫자를 사용한 것입니다.
가장 중요한 점은 정보에 입각한 결정을 내릴 수 있는 데이터를 갖게 된다는 것입니다.
신뢰하되 검증하고, 지속적으로 개선하기
생성형 AI의 확률적 특성 때문에 프롬프트에는 문제가 있을 가능성이 높습니다. 테스트 세트에서 정확도가 100%라 하더라도 잘못된 출력을 생성하는 알려지지 않은 입력이 존재할 수 있습니다. 따라서 신뢰하되 검증해야 하며, 검증에 실패한 사례를 데모 세트에 추가해 새로운 프롬프트를 개발하고 정확도를 높여야 합니다.
검증 방법은 문제에 따라 크게 달라집니다. 캘린더 애플리케이션 예시에서는 사용자에게 명시적으로 “이 일정이 맞나요?”라고 물어볼 수 있습니다. 사용자가 “아니오”라고 하면 자연어 입력을 사람이 검토할 수 있도록 로그로 남깁니다. 혹은 자동 정보 추출 후 사용자가 직접 수정한 일정을 자동으로 추적하는 더 나은 방법을 사용할 수도 있습니다3.
다른 예로, 프롬프트가 코드(정규 표현식이나 프로그래밍 언어 텍스트 등)를 생성한다면 최소한 파싱을 시도해 볼 수 있습니다. 파싱은 절대 보안 문제가 되어서는 안 되며4, 적어도 구문은 올바르다는 가장 기본적인 검증이라도 제공합니다. 그리고 다시, 이 검증에 실패하면 입력과 출력을 로그로 남기고 데모 세트를 확장해 더 나은 프롬프트를 개발할 수 있습니다.
검증은 적대적 프롬프팅에 대한 방어에도 도움이 됩니다. 적대적 프롬프팅 자체가 하나의 큰 주제이므로 이 글에서는 다루지 않겠습니다.
앞으로 나아가며...
이 글에서는 프롬프트를 개발하는 과정이 — 제 생각에는 — 어떻게 엔지니어링적 실천이 될 수 있는지를 보여줍니다. 문제를 식별하고, 솔루션을 구성하며, 그 솔루션을 검증하고, 지속적인 개선을 통해 솔루션을 다듬어 나가는 체계적인 접근 방식을 설명합니다.
이 글에서 소개한 접근 방식을 일화적인 경험과 만연한 시행착오에 의존해 어떤 해결책을 도출하고, 시간이 지나도 프롬프트를 안정적으로 반복 개선할 수 있는 체계적인 인프라를 제대로 구축하지 않는 “블라인드 프롬프팅”과 비교해 보세요.
이 글은 매우 기초적인 수준이라는 점을 밝히고 싶습니다. 이 글의 여러 부분은 이미 잘 알려진 더 발전된 기법으로 개선될 수 있습니다. 또한 적대적 프롬프팅과 같은 중요한 주제도 다루지 않았습니다. 구체적인 예로, 퓨샷 프롬프트에 가장 적합한 예시를 고르는 더 과학적인 접근법이 있지만, 가능한 한 기초에 충실하기 위해 이 글에서는 생략했습니다. 더 발전된 기법을 배우고 싶다면 Lilian Weng의 Prompt Engineering이 훌륭한 개요를 제공합니다.
또한 모두가 빠르게 고차원적인 LLM 통합, 즉 프롬프트 체이닝, 에이전트 등으로 나아가고 있습니다. 이러한 미래의 혁신 등이 인간의 프롬프팅을 쓸모없게 만들 것이라고 주장하는 사람들도 있습니다. 그것이 사실이든 아니든, 저는 “제1원칙(first principles)”5부터 배우는 것이 가치 있다고 믿는 사람이며, 이러한 프롬프팅 기법을 배우는 것이 고차원적인 언어 모델 기법을 활용하는 능력을 향상시켰다고 생각합니다. 또한 이러한 기본적인 프롬프팅이 여전히 고차원 개념의 성능을 더 좋게 만든다고 믿습니다.
각주
아마 시끄러운 사람들이겠죠. 저는 이 분야에 훌륭한 엔지니어링 실천을 적용하고 있는 정말 뛰어난 프롬프트 엔지니어들을 많이 만나고 배웠습니다. 안타깝게도 트위터 등 플랫폼에서 제가 보는 노이즈는 대개 그렇지 않습니다. ↩
역시 출처가 필요합니다. 다시 말씀드리지만 귀찮아서 생략한 것이지만, 여러 논문에서 읽은 실험 연구에 기반한 내용입니다. 저를 믿지 않으셔도 됩니다. 직접 테스트해 보세요. ↩
여기에는 당연히 개인정보 보호 문제가 따릅니다. 하나의 예시로 공유하는 것일 뿐이며, 실제 상황에 따라 적절한 기법이 아닐 수도 있습니다. ↩
YAML. 😐 ↩
실제 “제1원칙”은 더 낮은 수준일 것입니다. 여기서는 “제1원칙”이라는 표현을 그 위에 더 많은 지식을 쌓을 수 있는 임의의 낮은 수준 지점을 가리키는 일반적인 관용구로 사용했습니다. ↩
글을 무작위로 읽기