포스팅 개요
최근 프롬프트 엔지니어링(Prompt Engineering)이라는 용어가 많이 언급되고 있습니다. 대규모 언어 모델(Large Language Models, LLM)을 효과적으로 활용하기 위해서는 프롬프트 설계가 중요한데요. 본 포스팅에서는 구글의 프롬프트 엔지니어링 백서(Prompt Engineering white paper)를 기반으로 프롬프트 엔지니어링이란 무엇인지, 프롬프트 엔지니어링의 개념들과 다양한 기법들, 그리고 최적의 결과를 얻기 위한 방법과 팁들을 정리해보겠습니다.
제가 참고한 Google의 Prompt Engineering 자료의 출처는 아래와 같으며, 추가로 참고한 자료의 리스트는 아래와 같습니다.
- https://arxiv.org/pdf/2210.03629
- https://arxiv.org/pdf/2203.11171
- https://arxiv.org/pdf/2305.10601
- https://arxiv.org/pdf/2201.11903
- https://www.kaggle.com/whitepaper-prompt-engineering
Prompt Engineering
www.kaggle.com
포스팅 본문
1. 프롬프트 엔지니어링이란 무엇인가?
프롬프트 엔지니어링(Prompt Engineering)이라는 것은 이제 아마 많이들 익숙한 용어일 것입니다. 그럼에도, 프롬프트 엔지니어링에 대한 설명을 하는 자료이니 한 번 집고 넘어가겠습니다. 프롬프트 엔지니어링이란 LLM에 효과적인 입력을 설계하여 원하는 출력을 얻는 과정입니다. 언어 모델은 기본적으로 텍스트를 입력받아 다음에 올 토큰(단어 등)을 예측하는 방식으로 작동하는데요. 토큰 예측은 모델이 학습 중에 본 텍스트 간의 관계에 기반합니다.
이에, 프롬프트 엔지니어링의 핵심은 다음과 같이 정리될 수 있습니다.
- 언어 모델이 올바른 토큰 시퀀스를 예측하도록(생성하도록) 설정
- 고품질 프롬프트를 설계하여 정확한 출력 유도
- 프롬프트 길이, 스타일, 구조를 최적화
이러한 프롬프트 설계는 작성을 위해 흔히 데이터 과학자(Data Scientist)나 머신러닝(Machine Learning) 전문가가 아니어도 됩니다. 누구나 프롬프트를 작성할 수 있는 것이죠. 그렇지만, 효과적인 프롬프트를 설계하는 것은 노력을 요구합니다.
2. LLM 출력 구성 설정하기
이제 본격적으로 프롬프트 엔지니어링에 대해서 알아볼 것인데요. 그 전에, LLM의 출력 구성을 설정하는 방법에 대해서 본 whitepaper에서는 이야기합니다.
2.1 출력 길이 제어
출력 길이는 모델이 생성하는 토큰 수로 제어할 수 있습니다. 더 많은 토큰을 생성하면 더 많은 컴퓨팅 자원과 에너지가 소비되는데요. 그렇게 되면 응답 시간이 길어지고, OpenAI나 Claude, Gemini와 같은 모델을 사용할 경우 비용도 증가하게 됩니다.
특히, ReAct와 같은 기법을 사용할 때는 출력 길이 제한이 중요하다고 말합니다.
2.2 샘플링 제어
LLM은 실제로 단일 토큰을 예측하기보다, 각 토큰에 대한 확률을 예측합니다. 이러한 토큰 확률은 온도(Temperature)와 Top-K 그리고 Top-p을 통해 제어가 될 수 있습니다.
온도(Temperature)의 경우 토큰 선택의 '무작위(diverse or unexpected)'성 정도를 제어하는데요. 낮은 온도(0에 가까운)를 설정하면 예측 가능한(deterministic) 응답이 되며, 높은 값이면 예상치 못한(다양한) 값으로 설정되게 됩니다.
Top-K는 모델의 예측 분포에서 상위 K개의 가능성 높은 토큰만 선택하는 것이며, Top-K가 높을수록 출력이 더 창의적이고 다양해지는 것입니다. Top-P는 누적 확률이 특정 값(P)을 초과하지 않은 상위 토큰을 선택하는 것입니다.
3. 다양한 프롬프트(Prompt) 기법들
3.1 제로샷(Zero-shot) prompting
가장 간단한 프롬프트 유형으로, 작업 설명과 시작할 텍스트만 제공합니다. 예시 없이 질문, 이야기 시작 부분, 또는 지시사항을 제공하는 것이죠. 본 책에서는 아래와 같은 예시를 들어주고 있습니다.
영화 리뷰를 긍정적, 중립적 또는 부정적으로 분류하라는 프롬프트인데요. 지시어 다음에 바로 리뷰를 제공해주고 감정(Sentiment)를 표기하는 방식으로 매우 간단하게 동작되는 프롬프트 입니다.
3.2 원샷(one-host)과 few-shot prompting
one-shot 프롬프팅은 단일 예시를 제공하여 모델이 이를 참고하도록 합니다. 이와 유사하게 few-shot prompt는 예시를 여러게 제공하여 모델이 패턴을 더 정확하게 참조할 수 있도록 유도합니다.
본 E-book에서 예시로 든 one-shot, few-shot prompt는 아래와 같습니다.
고객의 주문을 JSON 형태로 파싱하라는 메세지인데요. EXAMPLE처럼 예시를 제공하여 LLM이 예시를 보고 사용자가 원하는 task를 수행하도록 합니다. 본 책에서는 few-shot의 경우 보통 3~5개 정도 사용하는 것이 좋다고 이야기하고 있습니다.
3.3 시스템, 역할(role), 맥락(context) prompting
시스템 프롬프팅(System prompting)
system prompt는 모델의 전반적인 전반적인 맥락과 목적을 설정하는 방법입니다. 모델이 수행해야 할 일종의 '큰 그림'을 정의하는 것이죠. 예를 들어, 언어 번역을 한다던가, 리뷰 분류를 수행한다 던가 등이 됩니다.
아래는 책에서 제시한 system prompting 예시입니다.
역할 프롬프팅(Role prompting)
role prompting은 LLM 모델에게 일종의 역할을 부여하는 것인데요. 어떻게 보면 system prompting과 비슷해 보일 수 도 있습니다. 제가 이해한 system promting과 role prompting의 차이점은 다음과 같습니다.
분류 | 시스템 프롬프팅(system prompting) | 역할 프롬프팅(role prompting) |
목적 | 모델의 전반적인 맥락과 작업 목적을 설정 | 모델에게 특정 역할이나 정체성을 부여 |
범위 | 큰 그림을 정의하고 모델이 수행해야 할 기본 기능을 결정하도록 합니다. | 특정 인물이나 직업의 관점에서 응답을 수행하도록 합니다. 시스템 프롬프팅보다는 조금 좁은 범위라고 볼 수 있습니다. |
집중하는 것 | 모델의 근본적인 능력과 작업 목적에 집중합니다. | 출력의 스타일, 톤, 전문성에 영향을 집중합니다. |
아래는 role prompting의 예시입니다. 예시를 보면, LLM에게 '여행 가이드'라는 역할을 부여하도록 합니다. 그리고 여행 가이드의 입장에서 여행지를 추천해달라는 것을 유도하죠.
또 다른 예시는, 스타일을 변경하도록 지시하는 것입니다. 마찬가지로 여행 가이드이지만, 출력의 스타일이나 톤 등을 다르게 하는 것이죠.
맥락 프롬프팅(Context prompting)
다음은 맥락(context) prompting 입니다. 이것은 현재 대화나 작업과 관련된 구체적인 세부 정보나 배경 정보 즉, 맥락(context) 정보를 제공하여 LLM이 task를 수행하도록 합니다.
3.4 Chain-of-Thought(CoT)
사고 연쇄(Chain of Thought, CoT)는 아마 많이 들어보셨을 법한 프롬프트 방법입니다. CoT prompt는 중간 추론 단계(reasoning step)을 생성하여 LLM의 추론 능력을 향상시키는 방법입니다. 이는 모델이 더 정확한 답변을 생성할 수 있도록 도와주죠. 이러한 CoT는 적은 노력으로도 충분히 높은 효과를 얻을 수 있다는 장점이 있으며, 모델의 응답과 reasoning 단계를 이해할 수 있는 해석 가능성도 제공해주는 장점도 있습니다. 단점도 존재합니다. CoT를 수행하면 더 많은 출력 토큰을 생성하기에 비용이 많이 발생할 수 있고 시간이 더 많이 소모 될 수 있습니다.
아래는 원본 Chain of Thought 논문에서 소개된 일반적인 프롬프트(Standard Prompting)과 Chain-of-Thought Prompting 방법의 비교 자료입니다.
결과를 보면 일반적인 프롬프트는 수식 계산이 틀렸지만, CoT를 활용한 프롬프트에서는 reasoning 단계를 포함시키기에 LLM이 올바른 대답을 제공할 수 있음을 확인할 수 있습니다.
본 prompt engineering whitepaper에서는 아래와 같이 CoT의 예시를 제공합니다. 마찬가리조 CoT를 사용하지 않았을 때 올바르지 않은 정답이 나오는 것을 보여주는데요.
이를 CoT 프롬프트로 변경하면 올바른 정답을 제공하는 것을 확인할 수 있습니다. 아래 사진은 Chain of Thought를 사용하는 데 zero-shot 형태로 CoT를 사용하는 것을 보여줍니다. Zero shot으로 사용할 경우 'think step by step'의 단계별로 생각해보라는 지시 형태로 task 수행을 유도합니다.
반면, CoT에 Few-shot을 적용한 것도 있습니다. 아래는 one-shot CoT의 예시입니다. Answer를 생성할 때 reasoning 단계의 예시를 하나 제공함으로써 똑같이 LLM이 추론을 수행하여 정답을 생성하도록 합니다.
3.5 자기 일관성(Self-consistency)
대규모 언어 모델(Large Language Model, LLM)은 다양한 NLP 작업에서 성공을 보여주었는데요. 추론 능력(reasoning ability)는 모델 크기를 키우는 것만으로 극복할 수 없는 한계로 간주되었습니다. 자기 일관성이라고 불리우는 self-consistency 프롬프트 방법은 이 문제를 해결하기 위한 방법인데요. self-consistency는 샘플링과 다수결 투표를 결합해서 다양한 추론 경로(reasoning path)를 생성하고 가장 일관된 답변을 선택하는 방법입니다.
아래는 self-consistency의 예시인데요. 이메일 분류 시스템에서 이메일을 중요하다(Important) 또는 중요하지 않다(Not important)로 분류하는 경우입니다.
총 세 번의 reasoning process를 수행하고 그 과정에서 나온 결과 중 가장 일관성 있는(다수결) 결과를 선택하는 것입니다.
위 예시에서는 최종적으로 이메일이 중요하다고 판단하였습니다.
답변이 맞을 가능성은 있지만, 아무래도 토큰을 많이 사용하기 때문에 비용이 더 발생한다는 단점은 있습니다.
3.6 생각 트리(Tree of Thoughts, ToT)
생각 트리(Tree of Thought, ToT)는 CoT의 개념을 좀 더 확장해 일반화한 개념입니다. LLM이 단일 선형 CoT(single linear Chain of Thought)가 아닌, 여러 다른 reasoning path를 동시에 탐색할 수 있게 하는 방법입니다.
아래 사진은 Tree of Thought의 원본 논문에서 설명하는 ToT 자료인데요. CoT와 ToT의 차이점에 대해서도 확인할 수 있습니다.
3.7 ReAct(Reason & Act)
Reason & Act의 약자인 ReAct는 LLM이 자연어적인 추론과 외부 도구(검색, code interpreter)를 결합하여 복잡한 작업을 해결할 수 있게 하는 패러다임이라고 본 백서에서는 소개하고 있습니다. 이는 LLM이 특정 작업을 수행하거나 정보를 검색하기 위해 외부 API와 상호 작용하는 등의 행동을 취할 수 있게 하는 에이전트 모델링을 위한 단계라고도 볼 수 있죠. 요즘 AI Agent, Agentic AI 등 에이전트와 관련된 이야기가 많이 나오고 있는데요. 이때 ReAct prompt 방법이 많이 사용되고 있습니다.
아래는 ReAct 논문에서 소개하는 ReAct의 방법인데요. Thought하고 Act하는 과정을 통해 추론과 행동을 수행하는 일종의 루프(loop)로 결합되 동작됩니다.
ReAct 프롬프닝은 아래와 같이 작동된다고 정리할 수 있습니다.
1. LLM이 문제에 대해 추론하고 행동 계획을 생성합니다.
2. 계획의 행동을 수행하고 결과를 관찰합니다.
3. LLM은 관찰 결과르 사용하여 reasoning을 업데이트하고 새로운 act 계획을 생성합니다.
4. LLM이 문제에 대한 해결책에 도달할 때까지 계속 수행합니다.
4. 코드 프롬프팅(Code prompting)
4.1 코드 작성을 위한 프롬프트(Prompt for writing code)
Gemni, Cladue의 Haiku, Sonnet, ChatGPT와 같은 LLM은 다양한 프로그래밍 언어로 코드 작성(일명 코딩)을 도울 수 있습니다. 저도 개발을 할 때 도움을 많이 받고 있는 부분인데요. 본 프롬프트 엔지니어링 백서에서도 이를 활용하면 코드 작성 프로세스를 가속화할 수 있다고 말하고 있습니다.
예를 들어, 수백 개의 파일이 있는 폴더에서 모든 파일의 이름을 변경해야 한다고 가정해보겠습니다. 각 파일의 일므을 수동으로 하나하나 변경하는 것은 매우 번거롭죠? 그리고 무엇보다 시간이 굉장히 많이 걸릴 것입니다. 이럴 때 다음과 같은 프롬프트를 작성할 수 있을 것입니다.
폴더의 모든 내용을 가져와서 모든 파일의 이름 앞에 draft라는 이름을 붙여 파일 이름을 저장하는 Bash 코드 스니펫을 작성해달라는 간단한 프롬프트 하나면 파일의 이름을 변경할 수 있는 간단한 코드를 빠른 시간에 받을 수 있습니다.
4.2 코드 설명을 위한 프롬프트(Prompt for explaining code)
개발을 할 때 많이 겪는 상황인데요. 오픈소스(Open source)에 올라와져 있는 코드 등을 해석하고 이해할 상황이 많이 있습니다. 이 과정이 즐겁기도하고 뿌듯한 과정이지만 아무래도 시간이 드는데요. LLM은 이런 코드 설명에 대해서도 기가막히게 해줍니다. 본 prompt engineering whitepaper에서는 아래와 같은 예시를 들어주고 있습니다.
코드를 복사해서 이 코드에 대해서 설명을 해달라는 간단한 프롬프트로도 LLM은 깔끔하면서도 명확하게 코드에 대한 설명을 제공해줍니다. . 개인적으로 저는 헷갈리는 코드와 라이브러리, 코드의 흐름 등을 파악할 때 LLM 서비스를 적극 활용하고 있습니다.
4.3 코드 번역을 위한 프롬프트(Prompt for translating code)
그리고 또 코딩 작업에서 많이 활용하는 것이 코드 번역(translate code)인데요. 코드 번역이라는 것은 예를 들어, Bash 코드를 Python 코드로 바꾼다던가 Python 코드를 R로 바꾼다던가 하는 작업입니다. 아래는 그 예시인데요.
위 예시에서는 Bash 코드를 Python 코드로 변환해달라는 프롬프트를 수행했습니다. 그리고 LLM은 그 역할을 충실하게 수행합니다.
5. 프롬프트 엔지니어링 모범 사례(Best Practices)
이제 마무리 단계입니다. 본 백서의 마지막 장에서는 Best Practice를 간단하게 소개하고 있습니다. Best Practice는 아래와 같은 것을 강조하고 있습니다.
1. 예시를 제공하여 사용하기: one-shot / few-shot 형태로 예제를 제공한다.
2. 단순하게 디자인하기: 프롬프트는 사용자와 모델 모두에게 간결하고 명확하며 이해하기 쉬워야 한다.
3. 출력에 대해 구체적으로 지정하기
4. 제약(constraints)보다 지시(Instruction) 사용하기: 긍정적인 지시에 집중하는 것이 제약에 의존하는 것보다 효과적이다.
5. 최대 토큰 길이 제어하기
6. 프롬프트에서 변수(variables) 사용하기: 프롬프트를 재사용하고 동적으로 만들기 위해 입력에 따라 변경할 수 있는 변수를 사용한다.
7. 입력 형태(input format)과 작성 스타일(writing styles)를 실험하기: 다른 모델, 모델 구성, 프롬프트 형식, 단어 선택 등에 따라 다른 결과를 얻을 수 있으므로 프롬프트 속성을 실험하는 것이 중요하다.
마무리
본 포스팅에서는 프롬프트 엔지니어링의 개념부터 다양한 기법, 모범 사례까지 Google의 'Prompt Engineering' 백서(White paper)를 기반으로 알아보았습니다.
효과적인 프롬프트 설계는 LLM의 힘을 최대한 활용하는 핵심 요소입니다. 각 프롬프트 기법은 특정 상황에서 더 효과적일 수도 있고, 때에 따라서 반복적인 과정을 지속하면서 프롬프트를 개선해 나갈 수도 있습니다. 그렇기에 계속 실험해보면서 지속적으로 개선해 나가는 자세가 필요할 것입니다.
이 포스팅이 프롬프트 엔지니어링에 대해서 독자님들의 이해에 도움이 되길 바랍니다.
긴 글 읽어주셔서 감사합니다.
혹시라도 저에게 연락을 주고 싶으시다면
- linkedin: https://www.linkedin.com/in/lsjsj92/
- 블로그 댓글 등
으로 연락주세요!