RAG + AI 에이전트 개발 by LangChain, LangGraph
이 강좌는 LLM 애플리케이션 개발인데, LLM 애플리케이션을 개발하다 보면 LLM에 다음과 같은 동작을 지정하고 싶을 때가 많습니다.
LLM의 출력을 프로그램에서 다루기 쉽도록 지정한 JSON 형식으로 출력해 주었으면 한다.
사내 업무 전문가의 지식을 참고하여 사용자의 질문에 답변해 주었으면 한다.
실제로 해보면 알겠지만, 이것이 의외로 어렵다는 것을 알 수 있습니다. 일반 프로그래밍과 달리, LLM은 제 지시를 잘 따르지 않는 경우가 많습니다. 프롬프트를 나름대로 고안해도,
적지 않은 비율로 지시를 무시당한다.
프롬프트를 조금만 바꿔도 지시를 무시해 버린다.
하는 경우가 종종 있습니다. 아무리 노력해도 LLM이 100% 지시를 따라오는 것은 아닙니다. 하지만, 실용적인 비율로 지시를 따를 수 있도록 해야 합니다. 이때 사용할 수 있는 테크닉이 바로 '프롬프트 엔지니어링'입니다. 프롬프트 엔지니어링에 대한 지식을 습득하면 LLM에서 큰 잠재력을 끌어낼 수 있으며, LLM 애플리케이션을 개발할 때 안정적인 결과물을 얻기 위해서도 프롬프트 엔지니어링에 대한 지식은 매우 중요합니다. 나중에 LLM 애플리케이션 개발 프레임워크인 'LangChain'을 사용하는데, LangChain의 많은 기능이 LLM에 입력되는 프롬프트를 조립하는 것과 관련이 있습니다. 따라서 프롬프트 엔지니어링의 기초 지식을 익히면 LangChain도 쉽게 배울 수 있습니다.
지금까지 프롬프트 엔지니어링이란 단어를 무심코 사용했지만, 과연 프롬프트 엔지니어링이란 무엇일까요? Chat Completions API를 통해 GPT-4o mini에게 “프롬프트 엔지니어링이란 무엇인가?”라고 물어보겠습니다.
GPT-4o나 GPT-4o mini가 질문에 간결하게 답변하기를 원하지만, 이렇게 필요 이상으로 긴 답변을 출력하는 경우가 많습니다. 그래서 짧게 답변할 수 있도록 프롬프트를 고안해 보았습니다.
프롬프트 엔지니어링은 AI 모델, 특히 언어 모델에 효과적으로 질문이나 지시를 구성하여 원하는 출력을 얻는 기술입니다. 최적의 결과를 도출하기 위해 문구, 형식, 맥락을 조정하는 과정입니다.
프롬프트를 고안하여 의도한 길이의 답변을 얻을 수 있었습니다. 이것이 바로 프롬프트 엔지니어링의 예입니다. 프롬프트 엔지니어링에 대해 정리한 유명한 웹사이트 'Prompt Engineering Guide'에는 다음과 같이 적혀 있습니다.
프롬프트 엔지니어링은 다양한 애플리케이션과 연구 주제에 언어모델(LM)을 효율적으로 사용할 수 있도록 프롬프트를 개발하고 최적화하는 비교적 새로운 분야입니다.
프롬프트 엔지니어링에는 다양한 방법이 있지만, 우선 프롬프트 구성 요소의 기본을 이해하는 것이 좋습니다. 이번에는 GPT-4o와 GPT-4o mini를 통합한 애플리케이션을 개발하는 예를 들어 프롬프트 구성 요소의 기본을 설명합니다.
예를 들어 '레시피 생성 AI 앱'을 생각해보겠습니다. 이 앱은 요리 이름을 입력하면 AI가 해당 요리의 재료 목록과 조리 과정을 생성해줍니다.
이런 애플리케이션을 만들 때, 일반적인 구성은 아래와 같습니다.
웹 애플리케이션이나 모바일 애플리케이션 화면이 있고, 사용자는 '카레'와 같은 요리 이름을 입력합니다. 사용자가 입력한 내용은 Python과 같은 프로그램으로 전송되고, Python과 같은 프로그램은 사용자가 입력한 내용을 바탕으로 프롬프트를 만들어 OpenAI의 채팅 API에 요청을 보냅니다. 이러한 애플리케이션을 개발할 때 프롬프트에 대해 생각해 보겠습니다.
레시피 생성 AI 앱을 개발할 때 간단한 프롬프트의 예는 다음과 같습니다.
이 프롬프트 전체를 사용자가 입력하는 것은 아닙니다. 사용자가 입력하는 것은 '카레'와 같은 요리 이름뿐입니다. 애플리케이션에서는 사용자가 입력하는 부분을 템플릿화하여 다음과 같은 문자열을 준비해 둡니다.
사용자의 입력을 받으면, 그 내용으로 {dish} 부분을 채운 후, OpenAI의 채팅 API(Chat Completions API)에 요청을 보냅니다. 이러한 코드를 실제로 작성해 보면 다음과 같습니다.
generate_recipe라는 함수에서 프롬프트의 {dish}라는 부분을 문자열(str)의 format 메서드로 대체한 후 Chat Completions API를 호출하고 있습니다. 비슷한 프롬프트는 '“role”: “system”'을 사용하여 다음과 같이 구현할 수도 있습니다.
이렇게 프롬프트를 템플릿화하여 많은 프롬프트에서 명령어와 입력 데이터를 분리하게 됩니다.
LLM이 수행하기를 원하는 작업을 명령어로 작성하여 사용자의 입력 데이터와 독립적으로 작성합니다. 그리고 입력 데이터는 이해하기 쉽도록 “”“”나 “###”"와 같은 기호로 구분하는 경우가 많습니다.
전제 조건이나 외부 정보 등을 컨텍스트(context)로 제공하면 컨텍스트에 따른 답변을 얻을 수 있습니다. 애플리케이션에 따라 다양한 정보를 문맥으로 제공할 수 있습니다. 예를 들어, 레시피 생성 AI 앱이라면 '1인분은 1인분', '맛은 매운맛을 선호한다'와 같은 정보를 제공할 수 있습니다. 이러한 전제조건을 사용자 정보로 등록해두고 그 내용을 프롬프트에 포함시키면 사용자에게 적합한 레시피를 쉽게 생성할 수 있습니다.
또 다른 방법으로는 데이터베이스에 다양한 요리 레시피 목록을 준비해 두었다가, 이번에 입력한 내용과 유사한 레시피를 프롬프트에 포함시켜 참고할 수 있도록 하는 방법도 생각해 볼 수 있습니다.
이처럼 프롬프트에 포함된 외부 정보를 바탕으로 답변하게 하는 것도 LLM 애플리케이션의 단골 메뉴입니다.
또한, 프롬프트에서 출력 형식을 지정하는 경우가 많은데, LLM의 응답을 그대로 사용자에게 보여주기도 하지만, 일부만 추출하거나 정형화하여 표시하고 싶은 경우도 있습니다. 예를 들어 다음과 같이 프롬프트에서 출력 형식을 지정하는 경우를 생각해 볼 수 있습니다.
이렇게 JSON 형식으로 출력할 수 있다면 프로그램에서 다루기 쉬워집니다. 참고로 실제로는 보다 안정적으로 JSON 형식으로 출력시키기 위해 JSON 모드나 Function calling 등을 사용하는 경우가 많습니다.
지금까지 프롬프트의 구성 요소로 다음 네 가지를 소개했습니다.
명령어
입력 데이터
컨텍스트(context)
출력 형식 지정
프롬프트가 이러한 요소로 구성되기 쉽다는 것은 'Prompt Engineering Guide'에도 나와 있습니다. 'Prompt Engineering Guide'는 DAIR.AI가 오픈소스로 공개하고 있습니다. 'Prompt Engineering Guide'와 같이 프롬프트 엔지니어링의 기법을 정리한 자료는 많이 있습니다. 이러한 정보를 참고하면 LLM의 가능성을 이끌어내는 프롬프트의 고안 방법을 알 수 있습니다.
OpenAI 프롬프트 엔지니어링: https://platform.openai.com/docs/guides/prompt-engineering
Google 프롬프트 엔지니어링: https://ai.google.dev/gemini-api/docs/prompting-strategies?hl=ko
Anthropic 프롬프트 엔지니어링: https://docs.anthropic.com/ko/docs/build-with-claude/prompt-engineering/overview
프롬프트 엔지니어링에는 디자인 패턴과 같이 이름이 붙여진 기법도 있습니다. 이번에는 프롬프트 엔지니어링의 기법 중 가장 먼저 확실히 알아두어야 할 것들을 소개합니다.
먼저 '제로샷 프롬프트(Zero-shot prompting)'를 소개하자면, LLM은 특정 작업에 대한 미세 조정 없이도 프롬프트가 지시하는 작업을 수행할 수 있는 경우가 많습니다. 예를 들어, 입력된 텍스트가 긍정적인지 부정적인지 판단하는 이른바 네거티브 포지티브 판단 프롬프트의 예는 다음과 같습니다.
긍정
다음에 소개할 Few-shot 프롬프트와 달리 이렇게 프롬프트에 예시를 주지 않고 작업을 처리하도록 하는 것을 Zero-shot 프롬프트라고 합니다.
이제 GPT-4o mini에게 입력이 AI와 관련이 있는지 대답해 보겠습니다. 먼저 Zero-shot 프롬프팅으로 “입력이 AI와 관련이 있는지 대답해 주세요.”라고 지시해 봅시다.
네, ChatGPT는 많은 사용자들에게 편리한 도구로 평가받고 있습니다. 정보를 제공하고, 질문에 답변하며, 다양한 작업을 돕는 데 유용합니다. 어떤 기능이 특히 도움이 되었나요?
이 판정의 결과에 따라 프로그램의 처리를 분기하고 싶은 경우, 단순히 'true' 또는 'false'만 출력하게 하고 싶습니다. 'true 또는 false로 출력하세요'라고 프롬프트로 지시할 수도 있지만, 대신 몇 가지 예시를 제시하여 출력의 형식을 알려줄 수도 있습니다. 응답을 원하는 텍스트 앞에 몇 가지 예시를 포함한 프롬프트를 작성합니다.
true
의도한 대로 'true'라고 간단하게 대답이 나왔습니다. 프롬프트에서 몇 가지 시범을 보여줌으로써 원하는 답변을 쉽게 얻을 수 있습니다. LLM 애플리케이션에서는 LLM이 특정 형식으로 응답하기를 원하는 경우가 많은데, 이런 상황에서 Few-shot 프롬프트는 매우 유용합니다. 프롬프트 내의 몇 가지 예시를 통해 언어 모델이 작업을 학습하게 하는 것을 ICL(In-context Learning)이라고도 합니다. 또한, Few-shot 프롬프트와 같은 형식으로, 특히 예제가 하나인 경우에는 One-shot 프롬프트라고 부르기도 합니다.
프롬프트 엔지니어링 기법 중 마지막으로 Zero-shot Chain-of-Thought(Zero-shot CoT) 프롬프트를 소개합니다. Zero-shot CoT 프롬프트의 유용성을 설명하기 위해 우선 GPT-4o mini에 '10 + 2 * 2 * 3 * 4 * 2'라는 계산 결과만 출력하도록 하는 코드를 준비했습니다.
10 + 2 * 3 - 4 * 2 = 10 + 6 - 8 = 8
2024년 8월까지만 해도 GPT-4o-mini 답은 10로 나왔지만 9월 이후 정답인 8이 출력됩니다. 이런 오답을 줄이기 위해 프롬프트에 "단계별로 생각해 보십시오."라는 지시문을 추가해서 시도해 봅시다.
프롬프트의 지시대로 순서대로 생각하다 보면 결국 정답을 맞출 수 있습니다. 이렇게 “단계별로 생각해 봅시다.”라는 말을 추가하여 정확한 응답을 유도하는 방법을 'Zero-shot Chain-of-Thought'라고 합니다. Zero-shot CoT 프롬프트는 매우 간단한 방법이지만 많은 작업에서 효과적이고 있다고 합니다.
참고로 Zero-shot CoT 프롬프팅이라고 부르는 이유는 앞서 고안된 'Chain-of-Thought(CoT) 프롬프팅'에서는 Few-shot 프롬프팅을 사용하여 단계별로 생각하는 몇 가지 예시를 포함하고 있었기 때문입니다.
© 2024 ZeR0, Hand-crafted & made with Damon JW Kim.
Profile: https://gaebal.site
개발문의: https://naver.me/GalVgGKH
블로그: https://blog.naver.com/beyond-zero