Clova Studio 활용하기

JSM·2023년 12월 11일
0

프로젝트

목록 보기
9/10
post-thumbnail

Clova Studio

  • 클로바 스튜디오가 무엇일까?
  • 사실 생소한 단어지만 GPT 네이버 버전이라고 생각하면 된다.
  • 즉 네이버에서 제공하는 LLM 서비스입니다.
  • 이번 프로젝트에서 AI에게 채팅으로 질문을 할 수 있고 이를 통해 Clova Studio로 부터 답변을 받을 수 있었습니다.

NOSQL을 활용해서 컨텍스트 기억하기

  • GPT를 쓰면 우리가 메시지를 입력할때 해당 메시지 이전 내용도 분석하여 답변하는 모습을 볼 수 있을것입니다.
  • GPT가 이전 대화 컨텍스트를 가지고 있다가 요청이 오게 되면 이전 컨텍스트 내용도 같이 보내는 것입니다.
  • 따라서 NOSQL을 통해 대화 방에 대한 컨텍스트를 유지하게 하였습니다.

왜 NOSQL을 선택했을까?

  • 메모리와 잦은 삽입과 삭제 때문입니다.
  • 저희는 방마다 대화 컨텍스트를 가지도록 하였습니다.
  • 따라서 방을 생성하면 대화 컨텍스트가 생겨나고 모두 방에 나가게 되면 대화 컨텍스트가 삭제됩니다.
  • 즉 삽입과 삭제가 잦게 발생하는데 이때 이 과정이 RDB보다는 NOSQL이 적합하다고 생각했습니다.
  • 또한 REDIS 인메모리 DB를 활용해볼 생각을 했지만 메모리와 데이터 구조의 이유로 NOSQL을 선택했습니다.
  • REDIS는 메모리에 데이터를 저장하므로 휘발성이 크고 (물론 AOF나 RDB 기능이 있긴하지만...) 메모리 용량에 의존적입니다. 만약 사용자가 많아진다면 메모리 용량에 효율적이지 않다고 생각했습니다.
  • 또한 구조상 룸 마다 채팅 컨텍스트를 가지고 채팅 컨텍스트 안에 채팅에 대한 정보가 들어가 있습니다. 즉 채팅 컨텍스트는 채팅 Object를 가지고 있어야 하는데 Document 구조가 더 적합하다고 생각되었습니다.
export type LLMHistoryDocument = Document & LLMHistory;

const options: SchemaOptions = {
  timestamps: true,
};

@Schema(options)
export class LLMHistory {
  @Prop({ required: true })
  room: string;

  @Prop({
    required: true,
    type: [
      {
        role: { type: String, required: true },
        content: { type: String, required: true },
      },
    ],
  })
  messages: LLMMessageDto[];
}

export const LLMHistorySchema = SchemaFactory.createForClass(LLMHistory);
  • 이런 LLMHistory라는 Document 모델을 만들고 이를 삽입하도록 하였습니다.

LLM 서비스로 요청 보내기

  • Clova Studio로 요청을 보내는 것은 API 호출하는 것이기 때문에 크게 어렵지 않습니다.
  • NOSQL을 조회하여 대화 컨텍스트를 가지고 온 후 여기에 새롭게 질문한 채팅을 더하여 이 내용을 ClovaStudio로 보냅니다.
  • 하지만 문제는 반환값에서 발생합니다.
  • 반환이 stream 형태로 오고 이를 한번에 보여주기 원했으므로 적절히 응답을 가공했어야 했습니다.
  • 따라서 저희가 생각한 방식은 stream으로 오는 데이터를 data 이벤트와 end 이벤트로 나누어 관리했습니다.
  • data 이벤트가 올때 새 배열에 값을 넣어주고 모든 stream이 오면 end 이벤트가 호출됩니다.
  • end 이벤트에서는 stream으로 온 데이터 중 event가 result인 데이터를 찾아 해당 데이터를 반환합니다.
async processAIResponse(
  room: string,
  message: string,
  socketId: string,
): Promise<LLMMessageDto> {
  const llmHistoryStream = await this.useLLM(room, message, socketId);
const response = [];

llmHistoryStream.on('data', (chunk) => {
  const lines = chunk.toString().split('\n\n');
  lines.forEach((line) => {
    response.push(line);
  });
});

return new Promise((resolve, reject) => {
  llmHistoryStream.on('end', () => {
    const resultIndex = response.findIndex((line) =>
                                           line.includes('event:result'),
                                          );
    const resultLine = response[resultIndex];
    const data = resultLine.split('data:')[1].trim();
    try {
      const dataJson = JSON.parse(data);
      const message = dataJson.message;
      resolve({
        role: message.role,
        content: message.content,
      });
    } catch (error) {
      reject('JSON parsing error');
    }
  });
});
}
profile
내 기술적 고민들을 모은 곳...

0개의 댓글