3년간의 도서관 챗봇 프로젝트 여정 Dialogflow를 활용한 초기 개발부터 GPT 통합까지, 인공지능 기술의 진화와 함께한 챗봇 개발의 도전! Node.js와 NestJS를 활용하여 Dialogflow와 GPT를 구현하여 챗봇으로 어떤 서비스를 할 수 있었는지 그리고 앞으로 어떤 서비스가 가능할지에 대해서 작성했습니다.
챗봇 프로젝트 3년
챗봇 프로젝트를 회사에서 맡아 내년이면 거의 3년이 되어간다. 그동안 제품 기획부터 개발까지 많은 시간을 챗봇 개발에 투자해왔다. 그렇게 개발된 챗봇은 실제로 납품 되었다. 그렇게 제품이 마무리가 되어 가는 듯 했지만 갑작스러운 GPT의 등장으로 챗봇 기술은 새로운 국면을 맞이했다. 다른 프로그램들도 비슷한 상황을 겪고 있겠지만, 챗봇은 그 변화가 특히 크게 느껴졌다.
처음에는 키오스크 형태로 도서관 관련 정보를 서술 형태로 제공하는 챗봇을 만들어 납품했다. 그러나 사람들의 다양한 기호를 맞추는 데 한계가 있었다. 도서관 관련 인텐트를 지정하고 해당 인텐트의 키워드가 포함된 질문에만 답하는 형태의 시스템이었기 때문에, 엉뚱한 질문에 대해 답변을 하지 못하거나 부적절한 답변을 하게 되는 경우가 많았다.
이를 개선하기 위해 구글 클라우드 서비스인 Dialogflow 기능을 더욱 다양하게 활용하여 챗봇을 개발을 했다. 좀 더 단계적인 대화 흐름을 처리하기 위해 엔티티(Entity)와 컨텍스트(Context)를 활용하는 기능을 확장했고 이를 관리할 수 있는 엔티티 및 컨텍스트 관리 툴도 제작했다.
🍳여기서 잠깐! Dialogflow 더 많이 알아보기
그리고 나아가서 도서관의 특정 서비스를 api로 호출하여 챗봇의 단순 답변에서 특정 서비스를 수행하는 프로그램으로 나아갈 수 있었다.
하지만 이제는 GPT로 한 단계 업그레이드 할 수 있는 챗봇의 탄생이 기대되는 상황이 되었다.
Dialogflow 무쓸모?
현재 GPT가 탑재된 기능을 사용하고 있지만, 여전히 Dialogflow는 필요한 도구이며 현재 챗봇에서 필요한 서비스이다. AGI(인공지능 일반화)가 등장하면 상황이 달라질 수 있지만, 현재로서는 Dialogflow가 챗봇에 여전히 중요한 역할을 한다. 구체적으로, 프로그램처럼 단계별로 정보를 습득하고 행동을 취하는 데 있어 Dialogflow는 훨씬 적합한 기능을 제공하며, 무엇보다 GPT와 달리 비용 면에서도 강점을 가진다.
Dialogflow API를 구현하는 Node.js 코드
챗봇 프로젝트에서 Dialogflow API를 Node.js 코드로 구현을 했다. 프레임워크는 nest.js를 활용하여 프로그램 전반에 node.js의 단점을 최소화하려고 했다. 다음은 Dialogflow의 주요 기능인 질문의 의도를 파악할 수 있는 메서드 구현 예제이다.
import { Injectable, Inject } from '@nestjs/common';
import * as dialogflow from '@google-cloud/dialogflow';
import { readFileSync } from 'fs';
import { Logger } from 'winston';
@Injectable()
export class DialogflowService {
private projectId: string;
private sessionClient: any;
constructor(@Inject('winston') private readonly logger: Logger) {
// Google Cloud Platform에서 제공하는 키 파일 로드
const keyFilePath = 'path/to/your/dialogflow-key.json';
const keyFile = JSON.parse(readFileSync(keyFilePath, { encoding: 'utf-8' }));
const credentials = {
private_key: keyFile['private_key'],
client_email: keyFile['client_email'],
};
this.projectId = keyFile['project_id'];
this.sessionClient = new dialogflow.SessionsClient({ credentials });
}
/**
* Dialogflow의 인텐트를 감지하여 사용자 입력에 응답
* @param {string} sessionId - 세션 ID
* @param {string} text - 사용자 입력 텍스트
* @returns {Promise<object>} - Dialogflow 응답 결과
*/
async detectIntent(sessionId: string, text: string): Promise<object> {
try {
const sessionPath = this.sessionClient.projectAgentSessionPath(this.projectId, sessionId);
const request = {
session: sessionPath,
queryInput: {
text: {
text,
languageCode: 'ko', // 한국어 설정
},
},
};
const [response] = await this.sessionClient.detectIntent(request);
const result = response.queryResult;
this.logger.info(`Query Text: ${result.queryText}`);
this.logger.info(`Detected Intent: ${result.intent.displayName}`);
this.logger.info(`Response Text: ${result.fulfillmentText}`);
return result;
} catch (error) {
this.logger.error('Error detecting intent:', error);
throw error;
}
}
}
코드 설명
dialogflow-key.json
파일을 통해 Google Cloud 인증 정보를 설정한 후, SessionsClient
를 사용하여 Dialogflow 세션을 초기화 한다. 이후, 구현된 detectIntent
메서드를 사용하여 사용자의 입력 텍스트를 Dialogflow에 전달하고, 반환된 응답에서 인텐트 이름과 응답 메시지를 추출하는 것이다.
detectIntent
메서드는 사용자가 입력한 텍스트와 특정 키워드를 매칭하여 해당 인텐트를 식별하고 이 인텐트에 연결된 응답 메시지를 반환받아, 이를 기반으로 데이터베이스의 관련 컬럼 값을 매핑하여 비용 효율적이고 구조화된 대화 흐름을 제공할 수 있게 만들었다. 이를 통해 더욱 정확하고 효율적인 사용자 경험을 제공할 수 있었다.
GPT 연동으로 더욱 강력한 답변 가능
여기에서 나아가 gpt와 연동하여 더욱 강력하고 정확한 질의 응답을 가능하게 했다.
async function handleDialogResponse(dialogInfo) {
try {
// 조건: dialogInfo.id가 존재하고 값이 'gptCheckNeeds'가 아닌 경우만 실행
if (dialogInfo.id && dialogInfo.id !== 'gptCheckNeeds') {
console.log('dialogInfo.id:', dialogInfo.id); // 현재 dialogInfo.id를 콘솔에 출력
// API 엔드포인트 URL (환경 변수로 관리하는 것이 바람직함)
const url = process.env.API_URL || "http://gptservice.botfire:8029/datset";
// API에 전달할 요청 본문
const requestBody = {
msg: dialogInfo.msg, // 사용자 입력 메시지
type: dialogInfo.id, // 인텐트 ID
stream: false // 스트리밍 사용 여부
};
// API 요청 헤더
const requestHeaders = {
headers: {
'Content-Type': 'application/json' // JSON 형식으로 데이터 전송
}
};
// POST 요청을 통해 외부 API 호출
const response = await this.httpService.axiosRef.post(url, requestBody, requestHeaders);
// API 응답에서 'answer' 필드를 추출
const textResult = response.data['answer'];
console.log('API 응답 메시지:', textResult); // 디버깅용 응답 출력
// 추가적으로 textResult를 처리하거나 반환
return textResult;
} else {
console.log('dialogInfo.id가 없거나 gptCheckNeeds와 일치합니다.');
return null; // 조건에 맞지 않는 경우 null 반환
}
} catch (error) {
// 오류 처리: 요청 실패 시 오류 메시지 출력
console.error('API 요청 중 오류 발생:', error.message);
throw error; // 오류를 호출한 곳으로 전달
}
}
Dialogflow로 부족하거나 응답하기 어려운 대답의 경우 인 경우 위와 같은 코드를 활용하여 GPT API에 메시지를 보내고 답변을 받아 사용자의 질문에 답변하는 식으로 한다. 이러한 연계는 기존의 Dialogflow 상에 없는 인텐트에 대한 대응이 가능하게 해준다. 하지만 그 이상으로 유용한 것은 기획자나 개발자의 의도 대로 짜여진 Dialogflow의 단계적 서비스를 유도하거나 방향을 잃지 않게 잡아줄 수 있다는 것이다.
gpt가 존재하기 전에는 한 번 엉뚱한 질문이 중간에 나오게 되면 단순 답변으로 대화를 종료하거나 문맥과 맞지 않는 서비스로 유도하는 정도 밖에 안되었다. 하지만 gpt 기능으로 인해 엉뚱한 질문에도 원활하게 서비스로 이어지게 할 수 있게 되었다.