llm api로 안정적인 출력을 뽑아내는 법
llm api 를 사용해서 체계적이고 예측 가능하게 제품을 개발하는 건 어렵습니다. llm api 는 일반적인 api와는 다르게 결과물이 결정적이지 않기 때문입니다. 자산을 관리하는 챗봇을 개발하면서 안정적인 제품을 만들기 위해 노력했던 결과물을 공유합니다.
예시 제품으로 usd와 krw 두 자산을 관리하는 챗봇을 가정할게요. 자신의 usd, krw 자산을 조회할 수 있고, 다른 누군가에게 전송하거나, 환전을 할 수 있는 제품입니다.
langgraph 를 활용하여 prototype 만들기
langgraph 를 사용하면 매우 쉽게 챗봇을 구현할 수 있습니다. langgraph의 create_react_agent 라는 함수로 간단한 agent를 만들 수 있어요. 프롬프트와 tool 만 추가하면 됩니다. 프롬프트도 간단하게 "너는 친절한 지갑 챗봇이야. krw / usd 두 자산을 관리해."
정도로만 설정하면 됩니다. tool로 getBalance
, transferAsset
, exchange
등의 tool을 추가해주면 됩니다. 그러면 langgraph에서 히스토리 관리를 자동으로 관리해줍니다.
prototype 의 한계
곧 한계가 왔습니다. 몇가지 중요한 문제가 발생했어요. 이전에 잘 대답했던 요청에 bot이 어느순간 대답을 잘 못하게 되었어요. 봇에 영향을 주는 건 거대한 하나의 prompt인 게 문제였어요. 내 자산 목록을 더 예쁘게 출력하기 위해서 프롬프트를 수정하면, 잘 되던 자산 전송 기능이 깨지는 경우들이 발생했습니다.
다른 문제는 과거 채팅 내역을 잘못 활용하는 문제였습니다. 예를 들어서 유저가 자산을 확인했는데 자산이 없었어요. 10분뒤 유저가 자산을 이체한 뒤 다시 자산을 물어봤을 때 ai가 자산 조회 tool을 호출하지 않고 자산이 그대로 없다고 말했습니다. 이전에 챗봇이 돈이 없다고 출력한 내용을 ai가 읽고 그대로 대답한 게 문제였습니다.
해결
첫 번째 문제를 해결하기 위해서 프롬프트를 쪼갰습니다. 예를 들어서 유저가 자산 조회를 요청하는 경우 llm api를 이렇게 여러번 호출합니다. 먼저 유저의 의도를 찾는 llm api 를 호출합니다. 유저의 의도가 자산 조회라는 게 확인되면 디비에서 자산을 조회합니다. 이 자산 정보를 예쁘게 포맷하는 llm api 를 호출합니다. 이를 유저에게 전달합니다. 이렇게 잘게 쪼개면 각각의 기능 구현이 이전 기능에 영향을 주지 않도록 만들 수 있습니다.
두 번째 문제는 첫 번째 문제를 해결하면서 기능을 추가하여 해결했습니다. 첫번째 문제에서 각각의 llm api 콜을 나누면서 history 를 포함시키는지 여부를 지정했습니다. 예를들어서 유저의 의도를 파악하거나, 유저가 입력한 인자를 파악할 때에는 히스토리가 있어야 똑똑해집니다. 사람들이 여러 메시지에 걸쳐서 요청을 하는 경우가 빈번하기 때문입니다. 반대로 결과물을 출력할 때에는 과거 내역이 없는 게 좋습니다. 과거에 대답한 답변과 지금 출력해야하는 결과물이 서로 내용이 충돌할 경우 잘못된 출력이 나올 수 있기 때문입니다. 자산조회의 결과물을 출력할 때는 tool 의 결과물만을 llm api에 input 으로 주어서 채팅 아웃풋을 만들어야 안전합니다.
위 두 과정을 거쳐서 llm을 사용한 챗봇을 안정적으로 구현하였습니다. 위 두가지 작업을 하면서 langgraph 를 사용하는 대신 open ai 의 공식 library 를 직접 사용하는 작업도 진행했습니다. langgraph 를 사용해서도 똑같이 구현할 수 있습니다. 몇가지 장단점을 비교한 뒤 langgraph보다 openai 라이브러리를 직접 호출하기로 결정했어요. 이에 대한 내용은 다음 글에서 이어서 하겠습니다.