콴다는 LLM을 어떻게 활용할 수 있을까? — 2편

AI Tutor, Qutor 등장!

Dave Kwon
Team QANDA

--

Introduction

안녕하세요, 콴다에서 백엔드 개발하고 있는 Dave입니다.

콴다는 LLM을 어떻게 활용할 수 있을까? 블로그를 AI Researcher Peyton이 잘 써주셨는데, 저는 이번 블로그를 통해 콴다 팀은 어떻게 LLM을 프로덕트에 녹여내고 있는지, 어떤 시행착오가 있었는지, 속도와 비용개선을 어떻게 했는지에 대해 소개하고자 합니다.

지난 8월, 콴다는 Qutor (구 ‘Poly’)를 런칭했습니다.

Qutor는 LLM을 사용하여, 학생이 이미지나 텍스트의 형태로 질문한 문제를 해결해 주기 위해 탄생한 AI Tutor입니다.

ChatGPT 등장과 수학 문제 풀이

Web에서 접근 가능한 ChatGPT에서는 현재 기본적인 수학문제는 풀어줄 수 있습니다.

ChatGPT

언뜻 굉장한 성능을 보일 것 같지만, GPT-3.5모델은 수학 문제 풀이에 있어서 만족스러운 성능을 보여주지는 못했습니다.

그러나, GPT-4가 발표되고 나서, 이 똑똑한 모델이 어쩌면 세상의 모든 문제를 풀 수 있게 될 것 같다고 생각했고, 문제 검색이 메인 기능이었던 콴다에게 큰 위협이 될 수 있겠다 싶어 저희가 가지고 있는 “풀 수 있어야 하는 문제들"로 GPT-4를 평가해 보았습니다.

결과로, 한국 수학 문제 기준(2022.03–2023.03 고1 모의고사) 56%의 정답률을, 미국 수학 문제 기준(AP Calculus AB, AP Calculus BC) 63%의 정답률을 보였습니다.

저희는 완벽히 모든 문제를 풀지 않지만 절반 이상 정답률의 준수한 성능을 보이는 GPT-4를 뛰어넘는, 좀 더 교육에 치중된 좋은 AI Tutor 프로덕트를 만들고 싶었습니다. 그리고 콴다의 AI Tutor가 LLM 시대에서도 최고의 문제 해결 서비스가 되도록 만들고 싶었습니다.

그러려면, ChatGPT와는 차별화된 뾰족한 기능이 있어야 할 텐데, 수년간 문제 DB를 쌓아온 콴다는 이를 활용하여 어떻게 차별화를 할 수 있을까요?

Using QANDA’s modules

ChatGPT Flow Chart

ChatGPT를 사용해 문제를 풀 때 위와 같이 학생의 질문을 바로 ChatGPT에 요청하는 경우가 단순히 ‘ChatGPT를 사용한다’라고 할 수 있을 것 같습니다. 우리가 ChatGPT Web을 사용하여 수학 문제를 올렸을 때를 예시로 들 수 있겠습니다.

콴다에는 다년간 만들어 온 문제를 풀기 위한 여러 가지 모듈들이 있습니다.

  • 사용자의 문제를 자연어로 바꿔주는 OCR Model
  • OCR 결과를 다듬어 주는 OCR Text Refiner
  • 문제의 메타정보(학년, 과목 등)을 분석하는 Question Attribute Inference Model
  • 관련 문제를 검색할 수 있는 Search Engine
  • 검색된 문제와 원래 문제와의 유사도를 측정할 수 있는 Same Question Model
  • 수식 계산을 도와주는 QANDA Calculator

이러한 모듈들을 활용하여, 수학 문제 풀이의 정확도를 향상시키기 위해 다음과 같은 구조를 고안했습니다.

Qutor Flow Chart (1)

Qutor가 문제정보를 받으면 필요에 따라 Qutor 자체가 어떤 모듈을 불러올 것인지 적절히 판단합니다. OCR Text가 지저분하다 싶으면 OCR Text Refiner를, Question 상세 정보가 필요하다 싶으면 Question Attribute Inference를, 비슷한 문제 검색이 필요하다 싶으면 Search를 사용해 문제를 풀기 위한 정보를 최대한 불러오게 됩니다.

최종적으로 이 정보들을 적절한 Prompt와 함께 ChatGPT에게 제공하여 문제를 풀게 합니다. 아래는 Qutor가 ChatGPT에게 전달하는 프롬프트의 한 예시입니다.

너는 AI Tutor고 <Language>학생 의 <Grade>학년의 <Subject>과목 문제를 푸는 역할을 해. 아래 학생이 올리는 문제를 Step By Step으로 풀어줬으면 해.

여기서, 프롬프트를 구성하는 문제에 대한 부가정보들은 콴다의 모듈을 사용하여 불러올 수 있는 것을 확인할 수 있는데, 콴다의 핵심 기능인 검색 모듈은 어떻게 쓰이게 될까요?

Using Search Engine

검색 엔진 모듈은 문제 풀이와 밀접한 관련이 있는 만큼 별도의 로직을 통해 관리 되는데, Qutor의 경우 검색 엔진을 사용하여 유저 문제와 유사한 문제를 추출합니다.

그중에는 완전히 똑같거나, 숫자만 일부 변경된 문제가 있을 수도 있고, 주어진 문제와 비슷한 개념을 활용하면 해결할 수 있는 문제가 있을 수도 있습니다. 검색되는 문제의 특성이 넓게 분포되어 있기 때문에, 이를 잘 활용하기 위한 별도의 알고리즘이 필요합니다.

검색 엔진에서 준 “비슷한 문제"의 유사도가

  • 기준치 n%를 넘어가면 Same Question shot(정확히 같은 하나의 문제를 넘겨서 같은 방식으로 풀게한다, 이하 Same shot)
  • 기준치 n%를 넘어가지 못하면 Similar Question shot (비슷한 여러가지 경우의 문제를 넘겨서 풀게한다, 이하 Similar shot)
  • 비슷한 문제가 하나도 없으면 Zero shot(아무 문제도 주어지지 않은 채 풀게한다.)

을 사용하여 문제를 풀게 됩니다.

예컨데, 학생이 아래와 같은 문제를 질문했다고 합니다.

x² + 2x + 1 = 0의 두 근을 구하시오

그리고 우리가 다음 유사문제를 DB에서 찾았다고 하면,

2x² + 4x + 2 = 0의 두 근을 구하시오

아래 유사문제에 대한 풀이

2(x² + 2x + 1) = 2(x+1)² = 0, x = -1

에서 x² + 2x + 1 = 0을 푸는 실마리를 찾아 원래 문제를 풀어내는 식인 것이죠.

이 “유사문제”를 토대로 아래와 같이 ChatGPT가 참고할 수 있도록 프롬프트를 고쳐서 보냅니다.

너는 AI Tutor고 <Language>학생 의 <Grade>학년의 <Subject>과목 문제를 푸는 역할을 해. 학생은 <Searched Question Reference> 문제를 <Searched Question’s Solution Reference> 와 같은 방법으로 어떻게 푸는지 배웠어. 너도 이걸 참고해서 아래 학생이 올리는 문제를 Step By Step으로 풀어줬으면 해.

이렇게 유사문제를 줄 수 있는 상황이 콴다에서는 한국 기준 70%에 달하고, 실제로 정말 괜찮은 정답률을 보이게 됩니다. (검색이 성공했다는 가정하에 80% 이상의 정답률)

하지만..

실제로 이렇게 배포하고 며칠 간 매트릭을 주의깊게 보았습니다.

다른 매트릭들도 주요하게 트래킹 했지만, 특히 중점을 두었던 것은 응답속도 관련 부분이었습니다.

중간중간에 사용하는 OCR Text Refiner같은 모듈이나 최종 답변을 내는 방식에서 GPT-4 Chat Completion을 여러번 사용하기 때문에 요청에 대한 API 비용과 응답시간이 배수로 늘어날 수 있다는 우려가 있었기 때문입니다. 따라서 실제 유저들이 어떻게 반응할지 빠르게 파악해야 했습니다.

First Token Latency (1)

실제로, 위와 같이 배포 후 첫 토큰 응답 속도가 예상했던 이상적인 수치를 웃돌아, 느리게 나오게 되었습니다.

Bounce Rate Before First Token (1)

또한, 위는 답변이 나오기 전 유저 이탈율 그래프인데, 유저 답변이 나오기 전에 상당히 많은 유저가 못기다린 채 이탈하고 있었습니다.

GPT Average Request Cost (1)

마찬가지로, API를 여러번 사용하기에 비용도 큰 숫자를 기록하여 한 질문당 콴다에서 예상을 웃도는 비용을 부담하는 꼴이 되었습니다.

이런 상황에서, 어떻게 하면 Qutor의 비용과 속도를 줄일 수 있을까를 빠르게 고민하게 되었습니다.

Qutor 성능개선

먼저, Qutor를 좀 더 자세히 들여다 봐 성능 개선의 여지가 있는 부분을 살펴보았습니다.

Qutor는 여러 포맷의 사용자 인풋에서 의도를 추론하여 원하는 모듈을 직접 사용해 문제를 풀도록 하기 위해 OpenAI Chat Completion의 Function Calling을 사용하여 구축이 되었습니다. 해당 플로우를 아래와 같이 구조화할 수 있을 거 같습니다.

Qutor-ChatGPT Diagram (1)

(1) Chat Completion function calling을 사용하여 유저 의도를 추론하고
(2) 문제풀이 답변을 GPT-4에게 요청해 받은 뒤,
(3) 마지막으로 답변 포맷팅 등이 적용된 최종 응답을 받는 구조인 것이죠.

한 요청에 어떻게보면 3개의 Chat Completion요청이 가도록 되어있는건데, 총 걸리는 시간은 다음과 같게 됩니다.

(요청1 완료시간) + (요청2 완료시간) + (요청3 완료시간)

우리가 최종 답변을 받으려면, ChatGPT가 풀어준 문제풀이 답변이 있어야 하고 또한 문제풀이 답변을 받으려면 우선적으로 function calling 응답을 받아야 해서 모든 요청이 순차적으로 처리되어야 하는 상황이었습니다.

따라서, 이전 요청에 대한 디펜던시가 있는 ChatGPT를 여러번 사용하면 할 수록 응답 시간은 물론 비용도 같은 배수로 높아집니다.

Stream으로 전환

잠깐 Qutor 서버와 클라이언트가 소통하는 방식을 소개하도록 하겠습니다.

채팅을 구현할 때, 소캣 재연결성등 인프라적인 구현 복잡도를 낮추고 빠른 개발을 하기 위해 Server-Sent Event를 사용했습니다.

Qutor Server-Client Architecture (1)

정말 챗봇이 응답을 주는 것처럼 중간에 Chat 서버가 있어서 Qutor는 완성된 문자열을 보내주지만, Chat 서버가 이를 Character단위로 쪼개서 클라이언트에 보내주는 것이죠.

다시 매트릭 얘기로 돌아와서, 아까 “답변이 내려오기 전 이탈율"이 아닌, “답변이 내려오는 도중 이탈율"을 살펴보도록 합시다. 아래 그래프는 위 “답변이 내려오기 전 이탈율(파랑 선)”에 “답변이 내려오는 도중 이탈율(초록 선)"을 추가한 그래프인데, 이처럼 일단 답변이 내려오고 난 뒤 훨씬 적은 비율의 유저가 이탈한다는걸 볼 수 있습니다.

Bounce Rate During Streaming (1)

저희는 여기서 유저는 응답이 다 내려오는걸 기다린다기 보다는, 처음 봇의 질문에 대한 반응이 나오는 시간이 중요하다는 걸 깨달았습니다.

저는 우선 Charater 하나하나를 유저에게 주는 UX라면, ChatGPT에서 응답이 올 때 마다 유저에게 바로 꽂아주면 안되나 하는 의문이 생겨 다음과 같이 ChatGPT 요청을 OpenAI에서 제공하는 SSE support를 사용하도록 일단 변경해보았습니다.

Qutor-ChatGPT SSE Streaming

이렇게 되면, ChatGPT에서 첫 토큰 응답이 왔을 때 아래와 같이 바로 유저에게 응답을 줄 수 있습니다.

Qutor Server-Client Architecture (2)

(요청1 완료시간) + (요청2 완료시간) + (요청3 첫 토큰 내려오는 시간)

이로써, 위와 같이 마지막 요청에 대한 응답시간을 확 줄일 수 있었습니다.

물론 아직 비용은 그대로입니다.

Prompt 최적화

다음은, 비용을 줄이기 위해 불필요한 ChatGPT 요청이 있나 살펴보았습니다.

우선 두 문제 풀이 응답을 먼저 한 번 보시죠.

문제:
3+2*8

응답1

해결:

이 문제는 사칙연산의 우선순위를 알아야 해결할 수 있습니다. 사칙연산의 우선순위는 괄호, 지수, 곱셈과 나눗셈, 덧셈과 뺄셈 순서로 진행됩니다.

여기서는 곱셈이 덧셈보다 우선순위가 높으므로, 먼저 2와 8을 곱해야 합니다.

2×8=162×8=16

그 다음, 이 결과를 3에 더하면 됩니다.

3+16=19

따라서, 답은 19입니다.

응답2

Step #1:
이 문제를 해결하기 위해서 사칙연산의 우선순위를 알아야해요. 우선순위는 다음과 같이 진행됩니다:

* 괄호
* 지수
* 곱셈과 나눗셈
* 덧셈과 뺄셈

Step #2:
우선순위에 따라 먼저 2와 8을 곱합니다: 2×8 = 16

Step #3:
그 결과에 3을 더합니다: 3 + 16 = 19

해결:
따라서, 답은 19입니다.

위 응답1은 Qutor의 find_solution, 즉 문제 풀이 답변 응답을 받는 부분에서 받은 응답이고, 아래 응답2는 Qutor의 final response, 즉 마지막에 문제풀이 응답(응답1)을 다듬고 걸러내는 부분을 거친 응답입니다.

Poly Diagram
Qutor-ChatGPT Diagram (1)

final response를 받는 부분의 프롬프트에는 다음과 같은 내용이 들어가는데요,

Latex응답은 이러이러한 포맷으로 줘.
어미는 -해요체로 줘.
각 Step 앞에는 <Step #n>을 붙여줘.
한국학생이니 꼭 한국어로 줘.

이런 포매팅 관련 프롬프트는 사실 문제풀이 응답을 받을 때도 가능할 거 같고, 두 응답 사이에는 “정답"과 “풀이과정”이 일치하기 때문에 아래와 같이 Qutor의 구조를 변경하였습니다..

Qutor-ChatGPT Diagram (2)

해결 1을 받으면 바로 그 스트림을 유저에게 보내주는 형식이죠.

이 작업으로 총 속도가 (요청1 완료시간) + (요청2 첫 토큰이 내려오는 시간) 으로 줄어들게 되었습니다.

ChatGPT요청도 하나 줄어드니 비용도 싸지게 되었죠.

최종 배포 후 Metric

최종적으로 위 해결방안을 9월11일에 배포하고, 이제 매트릭을 확인할 순간 입니다.

First Token Latency (2)

우선 응답시간부터 보면, 첫 토큰 응답속도는 배포 이후 80% 가량 개선되었고,

Bounce Rate Before First Token (2)

첫 토큰 전 유저 이탈율도 마찬가지로 평균 80% 정도 개선되었습니다.

GPT Average Request Cost (2)

가격 또한 불필요한 ChatGPT 통신이 줄어서 절반이상의 절감 효과를 누릴 수 있게 되었습니다.

마지막으로, 유저는 첫 토큰이 오는 응답시간에만 민감하다는 저희 가설을 검증하기 위해서 스트리밍 중 유저 이탈율을 보면,

Bounce Rate During Streaming (2)

위와 같이 큰 차이를 보이지 않음을 확인할 수 있습니다.

마지막으로, Qutor는 실제 앱에서 사용하며 올릴 수 있는 유저 피드백 기능 또한 열어두고 있는데, 아래와 같이 많은 긍정적인 내용을 받을 수 있었습니다.

Qutor의 유저 피드백

Conclusion

지금까지, 콴다의 AI Tutor 프로덕트 Qutor에 대해 알아보았습니다.

정리하자면,

  1. function calling등을 활용해 유저의 질문에 능동적으로 반응하고 콴다의 수학 문제 풀이 모듈과 문제DB를 사용해 정확히 문제를 풀어내는 AI Tutor를 만들어냈고
  2. 다음은 사용자의 metric을 꾸준히 모니터링하며 프로덕트의 문제점을 찾아내고, 문제 해결방안을 코드 개선과 설계 변경을 넘나들며 다방면으로 제시하였고
  3. 실제 빠르게 적용하고 배포하여 유저의 제품 만족도를 높이며 많은 사용자의 긍정적인 반응을 끌어냈습니다.

이런 경험은 교육과 AI 프로덕트에 큰 관심을 가지고 있는 콴다여서 가능했다고 생각합니다.

또한, Qutor는 이제 첫 발을 내딛었지만, 콴다는 AI Tutor로서 Qutor의 가능성이 아직 무궁무진하다고 생각합니다.

저희가 위 패치가 나간 이후로,

  • 프로덕트 측면으로는 스트리밍 중 수식 latex 랜더링, 추천 질문, 질문 입력 UX변경, 답변 가독성 개선과 같은 피쳐들이 꾸준히 배포되었습니다.
  • 또한, Azure OpenAI등 다양한 provider의 ChatCompletion API를 붙여가면서 실험하고 있습니다.
  • 저희 문제 데이터로 LLM을 fine-tuning하여 붙이려는 시도도 하고 있습니다.
  • 마지막으로 피쳐가 나갈 때 마다 답변 정확도가 흔들리지 않도록 Evaluation Platform을 통해 계속 실험해나가고 있습니다.

아직 비용 등 문제로 일부 유저들에게만 노출되었지만, 콴다의 새로운 프로덕트로서 AI Tutor의 모습을 점차 완벽히 갖춰 유저들에게 좋은 교육의 기회를 제공해 줄 것이라 믿습니다.

긴 글 읽어주셔서 감사합니다.

🌏콴다 팀에서는 글로벌 Top AI Tutor를 함께 만들어 갈 AI Researcher를 찾고 있습니다!➡️ 공고 확인하기

--

--