[TS] MelLearn 프로젝트 퀴즈 정확도 개선기

MelLearn 프로젝트에서 발생했던 정확도 문제와 프롬프트 개선기
정찬's avatar
Apr 10, 2025
[TS] MelLearn 프로젝트 퀴즈 정확도 개선기
 

목차

MelLearn 퀴즈 정확도 개선 기록

📝 개요

MelLearn은 음악을 활용하여 언어 학습용 퀴즈를 제공하는 서비스이다.
우리는 GPT 기반 LLM을 활용하여 학습용 퀴즈를 자동 생성했지만, 초기에는 정확도가 매우 낮은 문제가 있었다. 첫 주차 테스트 결과 10%의 결과가 나왔고 이렇게 부정확한 퀴즈를 사용자에게 제공할 수 없었다.
이를 해결하기 위해 다양한 프롬프트 엔지니어링 기법을 학습하고 적용하였으며, 반복적인 테스트를 통해 지속적으로 개선해나갔다. 이 문서는 그 과정을 정리한 기록이다.

✅ 요구사항

MelLearn은 언어 학습 서비스로서 다음과 같은 기능적 요구사항을 충족해야 했다.

학습 영역 (Category)

 
사용자에게 5가지 학습 영역을 제공하되, 퀴즈는 아래 4가지 영역에서만 생성한다.
  • Vocabulary
  • Grammar
  • Reading
  • Listening
    • (Speaking 영역은 퀴즈 형식이 아닌, STT를 사용해 사용자의 발음의 정확도를 측정)

학습 난이도 (Level)

 
사용자의 수준에 맞는 퀴즈를 제공해야 한다. 총 3단계로 구성된다.
  • Level 1 (Beginner)
  • Level 2 (Intermediate)
  • Level 3 (Advanced)
따라서 4개 영역 × 3단계 레벨 = 총 12가지 유형의 퀴즈를 지원해야 한다.

모델 및 환경

 
  • 사용 모델: GPT-3.5-turbo-0125
  • GPT-4도 고려했지만, 당시 기준으로 비용이 약 10배였기 때문에 효율성과 비용 측면에서 GPT-3.5 모델을 선택하였다.
  • GPT-3.5 모델을 효율적으로 사용하기 위해 프롬프트 개선 중심으로 접근하였다.

❌ 초기 퀴즈 생성 프롬프트의 문제점

 
초기에는 하나의 프롬프트로 모든 퀴즈를 생성했다. 문제가 발생했던 초기 프롬프트를 봐보자.
너는 외국어 문제 출제 위원이야. 너는 유저가 준 인풋들을 토대로 문제집을 만들어야해. 너가 만든 문제의 선택지와 정답은 반드시 명확해야해. 아래는 유저 input 설명이야. level : 난이도를 나타내는 1~3까지의 숫자가 주어져. 3단계로 갈수록 전문가 수준의 문제를 출제해야해. category : 너가 출제할 4가지 카테고리중 하나가 주어져 totalProblem : 너가 만들어야 하는 문제의 개수를 나타내. text: 유저가 제공하는 문장들이야. 이 문장들로 문제를 출제해. 아래는 Output 설명이야. question : 너는 문제 출제 방식으로 빈칸뚫기, 동의어 반의어를 물어보는 유형을 채택해야해. 한 문장에 두개 이상의 question을 물어볼 수 없어. 'text'에서 인용한 외국어를 제외하고는 한국어를 사용해서 질문해야해. selection : 4가지 선택지를 제공해야해. 각 선택지에 있는 정답과 오답이 명확해야해. 선택지는 외국어로 제공되어야 해. answer : 선택지중에 정답인 선택지를 정수로 알려줘야해. comment : 문제가 정답인 이유를 자세하게 적어줘야해. 유저가 알아 들을 수 있도록 반드시 한국어로 설명해야해. 너는 Output을 출력할 때 JSON 형식으로 출력해야해.
위 프롬프트는 영어로 된 프롬프트를 이해하기 쉽게 한국어로 번역한 것이다. GPT-3.5의 한국어 이해도가 낮기 때문에 실제 환경에서는 영어로 구성된 프롬프트를 사용했다.
 
초기 프롬프트는 크게 3가지 지시문으로 구성되어 있다.
  1. 모델에 역할 부여
  1. User Input 설명
  1. Model Ouput 설명
 
하지만 다음과 같은 문제가 다수 발생했다.

📌 주요 문제점

  1. 난이도 구분 불명확
    1. → level 별 퀴즈가 뚜렷하게 달라지지 않았다.
  1. 카테고리 간 구분 부족
    1. → Category 별 퀴즈가 뚜렷하게 달라지지 않았다. 단어 퀴즈와 읽기 퀴즈가 유사하게 생성되었다.
  1. Output 포맷 일관성 부족
    1. → 지시문까지 영어로 생성되거나, JSON 포맷이 일관되지 않아 역직렬화가 자주 실패했다.
       
초기 프롬프트의 한계는 결국 명확하지 않은 의도 전달구체성 부족에서 기인했다. LLM에게 "그럴싸한 퀴즈를 만들어줘"라고 말하는 건, 인간에게 "대충 알아서 해"라고 말하는 것과 다르지 않다. LLM은 마법을 부리는 것이 아니다. 퀴즈의 품질을 향상시키려면, 이에 따라 프롬프트를 전면적으로 리팩토링할 필요가 생겼다.
 

프롬프트를 개선해야된다는 것은 알겠는데, 어떻게 개선해야하지?

 
당시 프롬프트를 개선할 필요성을 느끼고 여러 레퍼런스를 찾아봤지만, LLM이 막 상용화되기 시작한 시기였기 때문에 문제 해결이 쉽지 않았다. 그러던 중 OpenAI의 공식 문서에서 프롬프트 엔지니어링(Prompt Engineering)이라는 키워드를 발견했고, 관련 기법을 찾아보며 해결 방안을 모색하게 되었다. 특히 Prompt Engineering Conference 2023 세션 영상이 큰 도움이 되었다.
 
문제를 해결한 시점에서 1년이 지난 지금, 이 영상의 주요 내용이 여전히 프롬프트 엔지니어링의 핵심으로 다뤄진다. 총 10시간 분량의 강연이지만, LLM을 활용한 프로젝트를 진행하거나 효과적인 프롬프트 설계 방법을 배우고 싶다면 꼭 시청해보길 강력히 추천한다.
 
저 세션에서 설명하는 기법들을 토대로 프롬프트를 개선했다.
아래에서 주요 3가지 개선 방법을 소개하겠다.
 

💡 Solution 1: 프롬프트 분할

🧩 문제

모든 요구사항을 하나의 프롬프트에 담다 보니, 지시문이 과도하게 많아졌고 이로 인해 여러 문제가 발생했다.
  • 토큰 수 증가
    • 모든 지시문을 하나의 프롬프트에 몰아넣다 보니 프롬프트 길이가 길어져 토큰 수가 증가했다. 이는 모델 호출 비용의 증가로 이어지며, 비효율적인 구조가 된다.
  • 지시문 충돌
    • 다양한 작업을 한 프롬프트에 동시에 지시하면, 서로 충돌하는 지시문이 생길 수 있다.
      예를 들어 퀴즈를 출제할 때 아래와 같은 프롬프트를 사용했다고 해보자.
      ... listening 영역의 퀴즈를 출제할 때는, 문장에 빈칸을 뚫어 출제해 reading 영역의 퀴즈를 출제할 때는, 문장의 의미를 물어보는 퀴즈를 출제해 다른 유형의 퀴즈 출제 지시문 나열 ...
      위와 같은 프롬프트 구조는 ‘Category’에 따라 다른 퀴즈를 출제하도록 요구하는 방식이다. 프로그래밍 관점에서 보면 일종의 분기문(if-else)처럼 보이지만, 언어 모델은 전통적인 프로그래밍 언어처럼 논리적 흐름을 정확히 따르지 않기 때문에 이 방식은 잘 작동하지 않는다. 즉, 지시문이 많고 복잡할수록 모델의 혼란 가능성이 높아진다.
       
  • 유지보수성 감소
    • 모든 로직을 하나의 프롬프트에 넣으면 구조가 복잡해져, 수정이나 기능 추가 시 프롬프트 전체를 다시 분석해야 한다. 결과적으로 유지보수가 어렵고, 협업 시 의사소통 비용도 증가하게 된다.

🔨 해결

 
프로그래밍에서 비즈니스 로직을 함수 단위로 분할하듯, 프롬프트도 단일 책임 원칙(SRP: Single Responsibility Principle)에 따라 작업 단위로 분리하는 것이 효과적이다.
작업을 세분화하면 모델이 명확한 지시를 받을 수 있고, 단계적 사고(chain-of-thought)도 유도할 수 있다.
 
📌 프롬프트 분할 기준
 
  • OOP의 SRP 원칙을 기준으로 프롬프트를 나눔
    • SRP(Single Responsibility Principle): 하나의 모듈(또는 함수, 클래스)은 하나의 책임만 가져야 한다는 원칙이다. 변경의 이유가 하나뿐이어야 유지보수가 쉬워진다.
  • 프롬프트를 일종의 코드라고 보고, 불필요한 분기문을 제거하는 방향으로 설계
 
위의 기준들을 적용하여 유형(Category) 기준으로 먼저 프롬프트를 분할하였다. 그 후에 Vocabulary의 난이도에서도 모호성이 발생하여 Vocabulary 유형에서는 난이도를 기준으로도 프롬프트를 분할하였다.
 
전/후 차이 비교
 
변경 전 프롬프트
 
너는 외국어 문제 출제 위원이야. 너는 유저가 준 인풋들을 토대로 문제집을 만들어야해. 너가 만든 문제의 선택지와 정답은 반드시 명확해야해. 아래는 유저 input 설명이야. level : 난이도를 나타내는 1~3까지의 숫자가 주어져. 3단계로 갈수록 전문가 수준의 문제를 출제해야해. category : 너가 출제할 4가지 카테고리중 하나가 주어져 totalProblem : 너가 만들어야 하는 문제의 개수를 나타내. text: 유저가 제공하는 문장들이야. 이 문장들로 문제를 출제해. 아래는 Output 설명이야. question : 너는 문제 출제 방식으로 빈칸뚫기, 동의어 반의어를 물어보는 유형을 채택해야해. 한 문장에 두개 이상의 question을 물어볼 수 없어. 'text'에서 인용한 외국어를 제외하고는 한국어를 사용해서 질문해야해. selection : 4가지 선택지를 제공해야해. 각 선택지에 있는 정답과 오답이 명확해야해. 선택지는 외국어로 제공되어야 해. answer : 선택지중에 정답인 선택지를 정수로 알려줘야해. comment : 문제가 정답인 이유를 자세하게 적어줘야해. 유저가 알아 들을 수 있도록 반드시 한국어로 설명해야해. 너는 Output을 출력할 때 JSON 형식으로 출력해야해.
 
 
변경 후 프롬프트
(프롬프트에서 Category 지시문 삭제)
category : 너가 출제할 4가지 카테고리중 하나가 주어져
 
변경 후 디렉토리 구조 변화
# 변경 전 - prompt/ └── prompt.txt # 변경 후 - prompt/ ├── listening/ │ └── listening.txt ├── grammar/ │ └── grammar.txt ├── reading/ │ └── reading.txt └── vocabulary/ ├── beginner.txt ├── intermediate.txt └── advanced.txt
 

✅ 효과

  • 카테고리 간 혼동과 난이도 모호성이 크게 줄어들었다.
  • 유지보수가 쉬워졌다.

 

💡 Solution 2: 프롬프트 엔지니어링

 
프롬프트를 작은 단위로 나눈 뒤에는, 모델이 이를 더 정확히 이해할 수 있도록 지시문을 설계하는 작업이 필요하다. 이 과정을 프롬프트 엔지니어링(prompt engineering)이라고 하며, 다양한 기법들이 존재한다. 나는 아래의 기법들을 적용하여 최종 프롬프트를 완성하고, 응답 품질을 크게 개선할 수 있었다.
 

1. 명확하고 구체적인 지시 제공

 
AI에게 원하는 결과를 얻기 위해서는 지시문을 명확하고 구체적으로 작성해야 한다. 예를 들어, "블로그 글 써줘"보다는 "초보자를 위한 500자 분량의 블로그 글을 써줘"처럼 요구사항을 구체화해야 한다.
 
초기 프롬프트에는 사람이 보기엔 충분히 명확해 보이는 지시도, 실제로는 LLM에게는 추상적인 경우가 많았다.
예를 들어 "Listening 유형의 빈칸 채우기 형식의 퀴즈 출제"라는 지시문은 다음과 같은 문제를 야기하며, 해당 지시가 실제로는 매우 추상적이었음을 보여준다.
  • 한 문장에 여러 개의 빈칸을 생성
  • 빈칸의 대상이 단어가 아닌 명사구, 절, 문장 전체인 경우 발생
  • 감탄사, 접속사, 조사 등 의미 없는 품사에 빈칸을 뚫는 사례 발생
사람이라면 "빈칸 채우기"라고 했을 때, 중요한 핵심 단어 하나를 제거하는 것을 자연스럽게 떠올린다. 하지만 LLM은 이러한 전제를 공유하지 않기 때문에, 프롬프트에서 그 의미를 명시적으로 표현해야만 제대로 작동한다.
그리고 이러한 추상적인 지시를 한 번 구체화했다고 해서 끝나는 것이 아니다. 하나의 문제를 해결하면, 그 해결을 통해 드러나는 또 다른 추상성이 다시 문제를 만든다. 즉, 프롬프트 엔지니어링은 단순히 지시를 명확하게 만드는 작업이 아니라, 지속적으로 개선해 나가는 반복적인 과정임을 직접 경험하며 체감하게 되었다. (이러한 반복적 개선의 중요성은 아래에서 별도로 설명하겠다.)
 

 

❓ 그렇다면, 추상적인 표현을 구체화하는 반복 작업만이 전부일까?

그렇지 않다. LLM의 이해를 간접적으로 돕는 다양한 방법이 존재한다. 이는 지시를 명시적으로 쓰는 것 외에도, 프롬프트 자체의 형식과 구조를 최적화하는 전략을 포함한다.

📌 LLM의 이해를 돕는 프롬프트 작성 전략

  • 마크다운 문법 사용
    • 프롬프트 내에 # 제목, - 리스트, 1. 순서 등의 마크다운 문법을 활용하면, 모델이 구조를 파악하고 구분해서 이해하는 데 도움이 된다. 특히 지시문이 여러 개일 때는 시각적으로도 구분이 쉬워져 정확도가 크게 향상된다.
  • 출력 예시 제공
    • 모델이 어떤 형태의 응답을 만들어야 하는지 감이 오지 않을 때, 직접 예시를 보여주는 것이 가장 확실한 방법이다. 이를 Few-shot prompting이라고도 하며, 반복적인 오류를 사전에 줄이는 데 효과적이다. 이는 추상화된 표현을 구체화하는것 뿐만아니라, 형식을 일치시키는데도 탁월한 효과가 있다. 자세한건 프롬프트 엔지니어링 2번에서 이어 설명하겠다.
  • Chain-of-thought prompting (Step By Step)
    • "하나씩 단계적으로 생각해봐"와 같은 문장을 프롬프트에 포함시키고 순차적으로 단계를 나열한다면, 모델이 사고 과정을 명시적으로 표현하게 되어 복잡한 문제를 더 잘 해결하는 경향이 있다. 이는 Chain-of-thought prompting이라 부른다.
       
이처럼 프롬프트의 구조를 개선하는 것 자체가 구체화의 한 방법이 될 수 있다. 단순히 내용을 나열하는 것이 아니라, 이해하기 쉬운 템플릿 형태로 설계하면 모델의 혼란을 줄이고 원하는 결과를 더 안정적으로 얻을 수 있다.
 

2. 예시 제공 (Few-shot Prompting)

 
AI에게 원하는 출력 형식을 예시로 보여주면, 그 형식에 맞춰 더 정확하게 응답한다.
예시: "다음 형식으로 요약해줘: 1. 주요 내용 / 2. 결론"
 
모델이 어떤 형태의 응답을 만들어야 할지 감을 잡기 어려워할 때는, 출력 예시를 함께 제시하는 것이 매우 효과적이다. 이를 Few-shot Prompting이라고 하며, 사람이 예제를 보고 따라 하듯, LLM도 제시된 예시를 기반으로 응답의 형태와 방향을 유추한다.
이 기법은 특히 다음과 같은 상황에서 유용하다:
  • 출력 형식이 정해져 있을 때 (예: JSON, 리스트, 표 등)
  • 일정한 말투나 스타일을 유지해야 할 때
  • 일관된 구조가 필요한 반복 작업일 때

✅ 적용

### Output format. { "answerList": [ { "answerWord": "", "lineIndex": } // ... more answers ] }
 
위의 예시는 출력 형식을 명시함으로써 모델의 응답 형식을 일정하게 유지하도록 유도한 사례다. 이처럼 하나의 예시만 제공하는 방식은 One-shot Prompting이라고 하며, 간단한 구조의 작업이나 형식을 고정할 때 유용하다.
초기에는 출력 구조가 들쭉날쭉하여 역직렬화 과정에서 오류가 빈번히 발생했지만, 이 예시를 제공함으로써 문제를 효과적으로 해결할 수 있었다.
예시를 제공하는 것은 가장 단순하면서도 강력한 프롬프트 엔지니어링 기법 중 하나다.
이 방식은 설계 시간도 짧고, 사용되는 토큰 수도 적지만, 결과는 이전과 비교해 큰 차이를 만든다.
예시는 하나만 제시해도 효과적이지만, 두세 개의 예시를 함께 제공하면 더욱 안정적인 결과를 얻을 수 있다.
다만, 예시가 많아질수록 토큰 수가 증가하므로, 비용과 응답 성능 간의 균형을 고려할 필요가 있다.
 

 

3. 반복적 개선 (Iterative Refinement)

 
AI의 응답을 바탕으로 프롬프트를 지속적으로 수정해 나가며, 점진적으로 원하는 결과에 가까워질 수 있다.
 
LLM에게 원하는 응답을 받는 일은 생각보다 단순하지 않다. 초기 프롬프트가 아무리 명확해 보여도, 모델의 응답은 종종 예기치 않은 방식으로 왜곡되거나 부족하게 나타난다. 이때 가장 중요한 것은 ‘한 번의 시도로 완성하려고 하지 않는 것’이다. 처음에는 실패하더라도, 응답 결과를 관찰하고, 분석하며, 그에 맞춰 프롬프트를 수정해 나가는 반복적 과정 을 거치는 것이 핵심이다. 이를 Iterative Refinement 라고 한다.
 

📌 이 방식이 중요한 이유

  • LLM은 사람과 다르게 암묵적 전제나 맥락을 공유하지 않기 때문에, 원하는 결과가 나올 때까지 계속해서 그 맥락을 "주입"해야 한다.
  • 잘 작동하던 프롬프트도 입력 데이터가 조금만 바뀌어도 실패할 수 있다. 따라서 다양한 케이스를 실험하며 프롬프트의 범용성과 안정성을 확보해 가야 한다.
  • 한 단계의 수정을 통해 새로운 문제가 드러나는 경우도 많다. 즉, 프롬프트 설계는 디버깅과 유사한 반복적인 과정이다.
  • Prompt Engineering Conference 2023에서도 이 반복적 개선의 중요성은 핵심적으로 다뤄졌다. 연사들은 공통적으로 "처음부터 완벽한 프롬프트는 없다", "실패한 응답에서 배워야 한다"는 점을 강조했다.
 
나의 사례도 그랬다. 한번의 엔지니어링으로 개선이 되지 않았다. 한달동안 끊임없이 개선하고, 테스트하여 문제를 찾고 다시 개선 작업을 반복했다.
 

 

💡 Solution 3: 프롬프트 체이닝 (Prompt Chaining)

 
복잡한 작업을 해결할 때, 한 번에 모든 과정을 처리하기보다 작업을 단계별로 나누고, 각 단계의 출력을 다음 프롬프트의 입력으로 사용하는 전략이다.
 
예시:
  • Step 1: "문장에서 핵심 단어를 추출해줘"
  • Step 2: "이 단어를 활용해 퀴즈를 만들어줘"

 
이 전략은 앞서 설명한 Chain of Thought Prompting과는 분명히 구분된다.
  • Chain of Thought하나의 프롬프트 안에서 모델이 사고 과정을 단계적으로 설명하게 유도하는 방식이다.
  • Prompt Chaining각 작업을 개별 프롬프트로 분리하고, 프롬프트 → 응답 → 프롬프트 → 응답… 식으로 결과를 순차적으로 연결하는 방식이다.
즉, 하나는 한 번의 호출 안에서 사고의 흐름을 유도하고, 다른 하나는 여러 번의 호출로 단계를 나누어 처리한다는 점에서 차이가 있다.
 

✅ 적용 사례

 
듣기 유형의 빈칸 채우기 퀴즈를 자동 생성하는 과정에서, 처음에는 Chain of Thought 방식으로 하나의 프롬프트에 모든 작업을 담았다. 그러나 단어 선정과 빈칸 위치 지정이라는 두 작업이 섞여 오류가 자주 발생했고, 응답의 일관성도 떨어졌다.
 
이에 작업을 두 단계로 나누는 프롬프트 체이닝 전략을 적용했다.
  1. 1단계 프롬프트
    1. → 문장에서 학습에 적합한 단어를 추출하고, 그 단어가 문장 내 어디에 위치하는지까지 확인
  1. 2단계 프롬프트
    1. → 1단계에서 선정된 단어와 위치를 활용해 문장에 적절한 빈칸을 뚫고, 퀴즈 형태로 출력

적용 결과

  • 단계를 분리하자 역할이 명확해져 정확도가 향상
  • 각 단계의 동작을 개별적으로 검증할 수 있어 디버깅이 쉬워짐
  • 복잡한 요구사항도 더 안정적으로 처리 가능

❌ 이후 롤백 적용

 
하지만 이 방식은 프롬프트를 두 번 호출해야 하므로, API 호출 비용과 시간이 증가한다는 단점이 있었다.
이를 개선하기 위해, 2단계 작업(빈칸 뚫기)을 코드 레벨에서 처리하도록 리팩토링했다.
1단계 프롬프트에서 단어와 위치만 받아오고, 이후는 직접 문자열을 파싱하여 빈칸을 생성하도록 수정한 것이다.
결과적으로 초기 요구사항에서, 코드레벨에서 할 수 있는 작업을 제거하여 호출은 한 번으로 줄고, 전체 시스템의 효율성과 응답 속도가 향상되었다.

📊 퀴즈 정확도 테스트

 
캡스톤 프로젝트 기간 동안, 생성된 퀴즈의 정확도를 정량적으로 검증하기 위해 매주 스크럼 시간에 팀원들과 직접 문제를 풀고 피드백을 기록했다.
  • 참여자: 팀원 4명
  • 진행 기간: 시험 기간을 제외한 총 4주
  • 주간 풀이 문항 수: 각 팀원은 매주 4가지 유형(듣기, 어휘, 문법, 독해)별로 20문제씩 총 80문제를 풀이
  • 총 문항 수: 80문제 × 4명 × 4주 = 1,280문제
 

테스트 결과

주차
총 문항 수
오류 문항 수
정확도
누적 문항 수
1주차
320
287
10.31%
320
2주차
320
159
50.31%
640
3주차
320
79
75.31%
960
4주차
320
47
85.31%
1,280

📈 정확도 10% → 85%까지, 주차별 개선 요약

  • 1주차
    • 초기 프롬프트는 단어 추출 기준이 불명확하고, 빈칸 위치 선정도 모델이 자의적으로 처리했다.
      → 대부분의 문제에서 부적절한 단어 선정, 문장 구조 파손, 빈칸이 어색한 위치 등 오류 다수 발생했다.
  • 2주차
    • 프롬프트를 카테고리 별로 분할하고 Few-shot Prompting을 적용해 정확도를 50%로 크게 개선했다.
      대부분의 퀴즈에서 정확도와 난이도 별 모호성이 해소되었다.
      → 하지만 Vocabulary 유형에서는 난이도 별 모호성이 존재했다.
  • 3주차
    • Vocabulary 유형을 출제하면서 난이도 모호성이 계속 발생하였다. 그래서 Vocabulary 프롬프트를 난이도에 따라 프롬프트를 3개로 분리하였다.
      → 다른 영역의 프롬프트의 지시문도 지속적으로 구체화하여 75%로 개선했다.
  • 4주차
    • Listening 유형의 퀴즈를 출제하는 방식을 분할하여 Prompt Chaining을 도입했다. 따라서 듣기 영역의 정확도가 크게 증가했다.
      → 최종 약 85%의 정확도로 마무리했다.
 
테스트 과정에서 단순히 프롬프트를 "잘" 작성하는 것만으로는 한계가 있었고, 지속적인 피드백과 반복적인 개선이 결정적인 역할을 했다.
매주 팀원들과 함께한 정량적 테스트와 피드백 기반의 개선 작업은 성능 향상에 매우 효과적이었다.
이번 경험을 통해 프롬프트 엔지니어링은 단발성 작업이 아닌, 설계 → 검증 → 개선의 끊임없는 반복 과정임을 실감할 수 있었다.
 
퀴즈의 오류 유형과 오류 판단 기준Github MelLearn Docs - 현재 모델이 만들어준 문제의 오류 종류 에서 확인할 수 있다.

❓ 85%에서 더 개선할 순 없었을까?

 
결론부터 말하자면 가능했다. 5% 정도 더 정확도를 끌어올릴 수 있었을 것이라 생각한다.
하지만 남은 10%의 오류는 구조적으로 완벽하게 해결하기 어려웠다. 그 이유는 다음과 같다:
  • LLM의 할루시네이션 현상
    • 모델이 존재하지 않는 단어나 문장을 지어내는 현상으로, 특히 특정 패턴이 반복되거나 문맥상 불분명할 때 자주 발생했다. 이는 프롬프트 수정만으로 완전히 제어하기 어려운 LLM의 근본적인 문제이다.
  • GPT-3.5의 다국어 처리 한계
    • GPT-3.5는 영어 기반으로 최적화되어 있어, 한국어에서 문맥 해석이 약간 비틀어지는 경우가 있었다. 외국어를 학습하지만, 퀴즈의 포맷은 한국어로 출제해야 했기 때문에 부 정확했던 15%에서 대부분이 여기에 해당됐다. e.g) 다음 문장에서 빈칸에 들어갈 단어로 가장 알맞은 것은? I usually ___ coffee in the morning. 위의 예시와 같이 한국어 지시문 + 영어 문장이 혼합된 포맷을 유지해야 했지만, GPT-3.5는 때때로 지시문을 누락하거나, 문제 구조를 의도와 다르게 재구성해버리는 경우가 많았다.
      이로 인해 문제의 일관된 포맷 유지가 어려워졌고, 그 결과 응답 품질이 불안정해지는 원인이 되었다.
  • 프롬프트 토큰 길이 제한
    • 긴 문장이나 복잡한 지시가 필요한 경우, 토큰 수 제한으로 인해 프롬프트가 잘리거나 응답이 누락되는 문제가 발생했다.
 
따라서 85%라는 수치는 단순한 정확도 그 이상의 의미를 지닌다. 이는 우리가 사용할 수 있었던 자원(GPT-3.5)과 제한된 프로젝트 일정을 고려했을 때, 현실적인 최적 성과에 가까운 결과였다.
프로젝트 후반에는 GPT-4가 출시되었고, 실제로 동일한 프롬프트를 적용했을 때 한국어 처리 성능에서 GPT-3.5보다 눈에 띄는 향상을 보였다. 다국어 지문 해석이나 포맷 일관성 문제도 상당 부분 개선되어, 정확도가 추가로 상승했다.
하지만 더 좋은 모델을 사용하는 것이 언제나 정답은 아니었다. 앞서 언급했듯, GPT-4는 GPT-3.5 대비 API 호출 비용이 약 10배 이상으로, 대규모 문제를 생성해야 했던 우리 프로젝트에서는 예산과 운영 효율성 측면에서 지속 가능한 선택이 아니었다.
결국 우리는 GPT-3.5라는 한정된 리소스 안에서, 프롬프트 엔지니어링을 통해 꾸준히 개선해나가는 전략을 택했고, 그 결과 85%라는 수치에 충분히 만족할 수 있었다.
 

✍️ 회고 및 느낀 점

 
처음에는 "LLM이 알아서 잘 해주겠지"라는 막연한 기대가 있었다.
하지만 실제로 적용해보니, 프롬프트 설계가 곧 성능이라는 사실을 뼈저리게 느꼈다.
단순히 지시문을 잘 쓰는 것만으로는 부족했다.
  • 모델의 이해도에 맞춘 구조화
  • 문제를 쪼개는 체계적인 설계
  • 반복적인 테스트와 정량·정성 분석
이 세 가지가 유기적으로 작동해야만 정확하고 일관된 결과를 만들 수 있었다.
무엇보다도, 매주 팀원들과 함께 퀴즈를 풀고 점검하며 피드백을 주고받는 과정에서 긍정적인 마인드 공유와 유대감이 큰 자산이 되었다.
처음엔 레퍼런스도 거의 없고, 개선이 막막하게 느껴졌지만, 팀원들과의 지속적인 논의와 공유가 있었기에 끝까지 해낼 수 있었다.
이 경험을 통해, 단순히 기능 구현을 넘어 LLM 기반 서비스의 퀄리티는 프롬프트 설계와 협업 과정에 달려 있다는 사실을 깊이 깨달았다.
 
Share article

lushlife99