정글에서 온 개발자

LLM으로 챗봇 만들기를 하며 배운 것들 본문

머신러닝

LLM으로 챗봇 만들기를 하며 배운 것들

dev-diver 2024. 8. 26. 23:36

프롬프트 엔지니어링

개발을 생각하지 않고 프롬프트 엔지니어링을 살펴보며 ‘one shot’, ‘few shot’ 등이 나오면 ‘챗GPT 쓰는데 언제 이런 예시를 일일히 나열하고 있나’ 생각했습니다. ‘프름프트 엔지니어’가 연봉을 높게 받을 수도 있다는 얘기도 흘려들었는데, 고연봉까지는 모르겠지만 중요한 작업이라는 걸 알게 됐습니다.

프롬프트 엔지니어링이 중요한 이유는 다음과 같은 사실 때문입니다.

  1. LLM 이용 시 요청하는 API는 ‘토큰’ 이라고 하는 입력 단위 (단어일수도, 단어보다 더 쪼개진 걸 수도 있다.) 의 양에 따라 비용이 청구된다.
  2. 로컬에서 돌리는 머신에 요청하더라도 토큰이 많아지면, 연산이 많아지고 따라서 느려지게 된다. 빠른 서비스를 하려면 좋은 GPU를 달아야 하므로 결국 돈이다.
  3. GPT4.0 같은 파라미터가 많은(그래서 GPU도 좋은걸 써줘야 하는) 모델들은 개떡같이 말해도 찰떡같이 알아듣지만, 보급용 모델들은 그렇지 못하고, 이때 ‘few shot prompting’ 만 해줘도 결과가 확 좋아집니다.

결론적으로, 프롬프팅을 압축적이면서도 원하는 결과를 내도록 잘하면 돈을 아낄 수 있습니다.

프롬프트 엔지니어링을 배우는 건 이 사이트가 유명합니다.

언어 모델 매개변수

언어모델의 핵심 작업인 ‘시퀀스의 확률’ 을 어느 범위까지 허용할 것인가, 반복적인 말을 어느정도까지 허용할 것인가 하는 값들입니다.

딥러닝 이론을 같이 공부하니 더 와닿았습니다.

  • LLM 과 관련한 이론은 이 자료(딥자입)가 좋았습니다. 코딩 없이 이론만 읽어도 좋았습니다.
  • 어텐션, 트랜스포머 등을 더 간단하고 직관적인 그림자료와 함께 보기는 이 유튜버가 좋았습니다.’
  • 딥자입에서도 머신러닝 내용을 알려주지만, 더 쉽게 보기에는 ‘혼자 공부하는 머신러닝+딥러닝’ 책도 쉽고 재밌었습니다.

당장 LLM이론이 급하면 윗쪽부터, 기초부터 하고 싶다면 아래 소개한 자료부터 보면 좋을 것 같습니다.

챗봇의 구조

가장 중요한 깨달음은, ‘모델 하나가 모든걸 해주진 않는다’는 것입니다.

책에서는 ‘Agent’라는 개념으로 소개했는데, 각각이 프로그래밍에서의 ‘함수’처럼 논리 분기를 정해주거나, 필요한 값을 반환하거나 했습니다.

예를 들어, 아래 구조에서 need_memory 는 llm에 사용자의 질문이 기억에 대해 묻는 것인지 true/false 로 답하도록 합니다. 이후 filter는 llm에 가져온 기억이 사용자가 한 질문과 적절히 맞는 기억인지 점수를 매겨서 반환하도록 합니다.

챗봇 구조

다만, 답을 계속 생성하는데 특화된 Generative model의 특성상 json 형식을 맞추지 않거나, 출력을 추가로 하는 경우도 있어서 (few shot prompting으로 겨우 잡음..) 로직이 고정된다면 따로 Fine-tuning해서 정말 ‘논리 게이트’ 처럼 쓰는 것이 좋을 것 같습니다.

이 부분은 공부와 실제 경험이 좀 더 필요합니다.

컨텍스트

공부 전에는 기억을 모델에 따로 저장하는 방법이 있는 줄 알았는데 모델이 계속 기억하고 있는 내용은 학습된 내용 뿐입니다.

모델은 ‘상태’가 없는 그냥 함수 덩어리입니다.

저와의 대화 내용은 실제로 기억하고 있는 것이 아니라, 어플리케이션(서버)단에서 따로 저장하고 있다가, 새로운 대화 내용을 계속 더해가며 실제로는 ‘모든 대화내용을 프롬프트로 때려넣는’ 방식이 현재 LLM의 유일한 기억 방법입니다.

# 실제로 프롬프트에 들어가는 대화량
[
'안녕',
'네, 안녕하세요',
'아이스크림 먹고 싶다.'.
'아이스크림 좋죠!',
'어떤 게 좋을까?',
]

# 사용자가 프롬프팅하고 있다고 생각하는 대화량
[ '어떤 게 좋을까?', ]

위 예시에서 , 챗봇에 ‘어떤 게 좋을까?’ 만 보내면 뜬금없이 뭐가 좋다는 건지 알아들을 길이 없습니다. 지금까지의 대화내용까지 발화자를 구분해서 넣어줘야 모델이 알아들을 수 있습니다. ChatGPT도 실제로는 이렇게 작동합니다.

너무 긴 컨텍스트

여기서 토큰 제한을 극복하기 위해, 같은 세션에서도 예전에 했던 대화는 컨텍스트에서 제거하거나, 요약(또다른 요약 Agent llm을 사용) 해서 압축하는 등의 기술이 들어갑니다.

Function Calling

OpenAI 모델에만 있는 줄 알고 넘기려던 기능이였는데, Ollama에서 돌아가는 유명한 모델들도 이 기능을 지원합니다.
크게 두 파트로 나누어져 있고, 결과적으로 모델에 request를 두 번 보내는 겁니다.

Intent에 따른 Function 선택

  1. 모델에 request message와 함께, 사용할 수 있는 function 명세 목록을 보냅니다. ( 매개변수, 리턴값, 함수 설명 등)
  2. 모델이 응답으로 어떤 function 호출이 필요할 것 같다고 응답

Function 실행결과를 바탕으로 응답

  1. 모델이 골라준 Function을 실제로 실행, 결과를 컨텍스트에 추가
  2. 결과가 포함된 컨텍스트를 포함해 요청

활용

Intent에 따라 로직이 분기되는 경우가 많은데, 이 때 intent를 구분해서 알려달라고 할 수도 있지만, Function Calling을 활용해 아예 Intent별로 실행 가능한 함수들을 알려주고 매개변수까지 만들어서 보내달라고 하면 일이 수월할 수 있겠습니다.

주의

  • format화 됐지만 다른 json 형식처럼 function calling에서도 hallucination이 일어날 수 있습니다.
  • fallback 로직을 구현하는 것이 좋습니다.

추가사항

Assistants API

OpenAI에서 제공하는 편한 API입니다. 컨텍스트도 알아서 관리해줍니다. 하지만 내부적으로는 역시 관리된 컨텍스트 전체를 언어모델에 보내기 때문에, 한 세션의 대화가 길어질수록 토큰 소모량(비용)은 증가합니다. 편리한 대신 관리 포인트를 잃는 trade-off입니다.

개발 역량이 있다면 굳이 안 쓰는 게 좋을 것 같습니다.

종합 의견

  • LLM은 언어에 특화된 범용적이고 창의적인 대신, 무겁고, 비싸고, 불안정한 함수라는 생각이 듭니다. 함수처럼 논리 분기에도 적용할 수 있지만, 불안정한 걸 항상 인지하고 Fallback 로직을 짜는 것이 중요합니다.
  • 구조를 짤 때 LLM이 각 위치에서 창의적인 생성을 해야 하는지, 이후 로직을 위해 논리 분기를 해야 하는지 역할을 분명히 하면, 정말 LLM이 필요한 부분인지 판단하기 편하겠습니다.
  • 조건 분기용 LLM은 가능하다면 출력하는 format이라도 보장 받을 수 있도록 fine-tuning 하거나 더 작은 모델로 바꾸어 단일한 책임을 지도록 하면 좋다고 생각합니다.

참고