상세 컨텐츠

본문 제목

Nuxt.js로 openai api 연동하여 slack GPT채팅 만들기(3) - nuxt, slack, gpt연동

Vue & Nuxt

by citykim 2023. 3. 26. 18:14

본문

이번 포스팅에는 slack bot을 생성하고 앞서만든 openai api와 채팅을 하도록 추가해보자

slack GPT앱은 현재는 정식 출시되진 않았고 베타로 이용신청을 할 수 있다
https://slack.com/intl/ko-kr/blog/news/how-i-built-the-chatgpt-app-for-slack

 

How I built the ChatGPT app for Slack

A conversation with Simón Posada Fishman, the OpenAI solutions engineer who developed the breakthrough app

slack.com

정식 출시되면 연동 및 작업은 크게 의미가 없어지지만 그 전까지 사용할 수 있도록 작업해보도록 하자

 

 

 

 

Slack bot🤖

우선 슬랙봇을 생성하도록 하자 슬랙 데스크톱 기준으로

앱추가 버튼을 누른다

 

앱 디렉터리로 이동한다

 

 

 

 


앱추가를 누르고 디렉토리로 이동한다

 

구축

여기에서 구축을 눌러 앱을 생성하는 화면으로 이동한다

 

 

 


Create an app
생성하자

이후 앱을 생성하고 앱 이름과 workspace를 선택한다음 생성해주도록한다

 

 

 

 

 


슬랙 앱은 다음과같은 행동을 하도록 만들 것이다

  1. 슬랙 앱 메시지에 직접 보내서 질문을 하게한다
  2. @mention 메시지로 앱이 있는 특정 채널에서 질문 및 답변을 받도록한다
  3. slash 커맨드로 DM 질문을 할 수 있게한다

위와 같은 환경에서 사용할 수 있게 구성하도록 하자

 

 

 

permission📔

Permission

권한으로 이동해서

Add an OAuth Scope

Add an OAuth Scope 버튼을 클릭하여 위와같은 권한을 추가해준다
일반 DM과 채팅방에서 채팅을 쓸 수 있게 하기 위함이다

 

 

 

 

 


Message Tab💬

App Home

 

App Home으로 이동하여

Show Tabs

Show Tabs 하위에 Message Tab을 켜주고 DM을 받을 수 있도록 설정해준다

 

 

그리고 다시 OAuth로 이동하여

install

Workspace에 Install 해준다 (허용/거절창은 생략하였다)

 

그러면 다음과 같은 화면으로 바뀌는데

Bot User OAuth Token

 

Bot User OAuth Token을 Copy하여 .env에  SLACK_BOT_OAUTH를 추가해준다

OPENAI_SECRET_KEY=sk-...
SLACK_BOT_OAUTH=xo...

 

 

 

 

 


Ngrok📜

이제 webhook과 slash를 구성해야되는데 그전에 앞서 Ngrok으로 서버를 포트포워딩 해야된다
Nuxt-ngrok이라는 패키지가 있지만 이 포스팅 작성중에는 3버전은 지원하지 않고있다

심플하게 ngrok을 직접 다운받아서 ngrok cli를 바로 구동하도록 하자

 

ngrok 사이트로 이동하자
https://ngrok.com/

download

해당 다운로드버튼을 클릭하거나 아래 url로 이동한다
https://ngrok.com/download

 

 

포스트 작성기준으로 위와같은 화면인데 다운로드를 누르면 포터블버전으로 cli를 받을 수 있다

exe파일 하나있다

 

이제 앞서 만든 Nuxt 서버를 구동하고

localhost:3000이다

포트를 바꿨거나, 별다른걸 구동하지 않았다면 localhost:3000으로 나올 것이다

 

ngrok.exe를 실행하자

ngrok

위와 같은 화면이 나온다

ngrok http 3000

위 코드를 입력하자 그러면 ngrok이 구동된다

url은 랜덤이다

포워딩된 url을 복사해놓는다

ngrok 사이트에 가입하고 키를 입력하면 세션이 무제한으로 바뀐다

그 전에는 2시간 정도만 유지되는데

귀찮으면 가입을하고 안 귀찮으면 2시간마다 재 실행하면 된다

 

 

 

 


Server💻

이제 slack 테스트를 위한 server 코드를 작성해보자

directory

api 하위에 slack 디렉토리를 추가하고 event.post.ts, slash.post.ts 파일을 추가해준다
event.post.ts는 webhook이나 dm을 감지하고

slash.post.ts는 slack이벤트를 post로 받는다

 

lib 하위에 slack_message.ts 파일도 추가해준다

해당 파일은 이벤트 감지 후 메시지를 발송하는데 사용된다

 

 

 

 

 


slack_message.ts📄

메시지 전송을 만들어야되는데 우선 패키지를 설치해준다

yarn add @slack/web-api

 

이후 코드를 작성하도록 하자

// server/lib/slack_message.ts
import { WebClient } from '@slack/web-api'

const web = new WebClient(process.env.SLACK_BOT_OAUTH)

export function sendMessage (channel: string, content: string) {
  web.chat.postMessage({
    channel: channel, // 이벤트 수신된 채널에 게시
    text: content,
  })
}

.env의 SLACK_BOT_OAUTH를 전달하여 webClient인스턴스를 생성하고

parameter로 channel을 string으로 수신받고 content도 string으로 전달받은뒤 post해준다

 

 

 

 

 


event.post.ts📄

이후 event.post.ts부터 작성한다

// server/api/slack/event.post.ts
import { sendMessage } from '@/server/lib/slack_message'

export default defineEventHandler(async (event) => {
  console.log('event 수신')
  const body = await readBody(event)
  let quest = body.event.text as string // 질문
    
  if (!body.event.bot_profile) {
    // 봇 메시지가 아닌경우만 답변함
    sendMessage(body.event.channel, quest)
  }
  return {
    challenge: body.challenge // hook 검증용으로 반환해야된다
  }
})

추후 slack에서 webhook을 연동할때 확인할 수 있는데 webhook은 항상 challenge값을 전달하고 수신했다는 의미로 다시 return 해야한다

bot_profile을 체크하지 않으면 봇이 대답하고 그걸 다시 대답하고 무한 반복하기 때문에 봇 메시지가 아닌경우만 대답한다

 

 

 

 

 


slash.post.ts📄

// server/api/slack/slash.post.ts
import { sendMessage } from '@/server/lib/slack_message'

export default defineEventHandler(async (event) => {
  console.log('slash 이벤트 수신')
  const body = await readBody(event)
  let channel = ''
  if (body.channel_name == 'directmessage') {
    // DM은 userid
    channel = body.user_id
  } else {
    // 채널은 채널id
    channel = body.channel_id
  }
  sendMessage(channel, body.text)
  return '정상적으로 수신하였습니다.'
})

DM에서 slash를 한경우 userid를 특정 채널에서 입력한경우 채널아이디를 전달해준다
slask는 webhook과는 다르게 메시지에 대한 객체만 전달하므로 string을 return해주자

 

 

 

 

 

 

 


slack event🎈

다시 slack app 화면으로 돌아와서 webhook과 event를 구성해준다

 

 

 

slash✏️

Ccreate New Command
Command

Command는 /qeust로 작성하고

request url은 ngrok의 url과 slash의 엔드포인트로 넣어준다 (ex: https://b3....ngrok.io/api/slack/slash) 

description과 hint도 적절히 작성한후 저장한다

reinstall your app 배너가 뜰건데 나중에 한번에 해주기위해 지금은 하지않는다

 

 

 

 

 


Event Subscriptions🎈

이제 event를 구독해야하는데

Request URL

앞서 반환한 challenge값을 항상 반환해야된다고 나온다
https://api.slack.com/events/url_verification

 

url_verification eventAPI event

Verifies ownership of an Events API Request URL

api.slack.com

해당 코드는 이미 작성했으니 Request URL에 slash와 같이 nrgok url과 엔드포인트를 입력해준다 (ex: https://b3....ngrok.io/api/slack/event) 

 

 

Verified!

 

Verified가 됐다면 Add Bot User Event에서 app_mention을
Add Woekspace Event에서는 message.im을 추가해준다

그리고 저장한뒤 reinstall을 해준다

어디서하든 결과는 같다

 

 

 

 

 

 


Slack Test🔍

이제 slask는 준비가 되었고 테스트를 해보자

 

 

slash✏️

public 채널의 경우 직접대답

 

 

DM인 경우 app 메시지에서 대답

 

 

 

 


event🎈

app 메시지에서 질문하면 바로대답한다

 

채널의 경우는 추가할지 물어보는데

채널에 추가

 

 

public

 

 

private

 

추가되면 잘 대답한다

 

DM에 대해선 접근권한을 주지 않았고, 앱에서 직접 보내면 되기 때문에 DM에서 멘션하면 응답하지 않는다
🤔DM에서 멘션하는 경우는 굳이 없을 것 같아서 추가하지않았다

 

슬랙이 정상 동작하는 것을 확인했으니 GPT까지 연동해보자

 

 

 

 

 

 

 

 


Slack with GPT

기존에 작성한 코드만 몇개 수정해주면 간단하게 작업할 수 있다

 

 

 

openai_completion.ts📄

// server/lib/openai_completion.ts
import { Configuration, OpenAIApi } from 'openai'
import type { ChatList } from '@/types/openai'
import { sendMessage } from '@/server/lib/slack_message'

export async function chat(messages: Array<ChatList>, channel?: string) {
  const configuration = new Configuration({ // configuration 인스턴트 생성
    apiKey: process.env.OPENAI_SECRET_KEY, // env의 key를 넣어준다
  })
  const openai = new OpenAIApi(configuration) // 인스턴스 생성
  console.log('질문시작')
  
  const result = await openai.createChatCompletion({ // 비동기로 결과를 받아온다
    model: 'gpt-3.5-turbo', // 사용할 모델
    messages: messages, // 사용될 메시지
  }).catch((err) => {
    console.log(err)
  })

  if (result && result.data.choices[0].message) {
    console.log('답변종료 : ' + JSON.stringify(result.data.choices[0].message))
    if (channel) {
      // 채널이 전달되었다면 직접 메시지를 보낸다
      sendMessage(channel, result.data.choices[0].message.content)
    } else {
      return Promise.resolve(result.data.choices[0].message)
    }
  }
}

parameter의 channel을 optional로 추가하고 채널을 전달받는다
채널이 전달되었다면 sendMessage(channel, result.data.choices[0].message.content)에 직접 결과값을 전달한다

 

 

 

 

 


event.post.ts📄

// server/api/slack/event.post.ts
import { chat } from '@/server/lib/openai_completion'

export default defineEventHandler(async (event) => {
  console.log('event 수신')
  const body = await readBody(event)
  let quest = body.event.text as string // 질문
    
  if (!body.event.bot_profile) {
    // 봇 메시지가 아닌경우만 답변함
    chat([{role: 'user', content: quest}], body.event.channel)
  }
  return {
    challenge: body.challenge // hook 검증용으로 반환해야된다
  }
})

슬랙 메시지보내는 부분을 삭제하고 chat 함수에 값을 바로 전달한다

 

 

 

 

 


// server/api/slack/slash.post.ts
import { chat } from '@/server/lib/openai_completion'

export default defineEventHandler(async (event) => {
  console.log('slash 이벤트 수신')
  const body = await readBody(event)
  let channel = ''
  if (body.channel_name == 'directmessage') {
    // DM은 userid
    channel = body.user_id
  } else {
    // 채널은 채널id
    channel = body.channel_id
  }
  const rerult = await chat([{role: 'user', content: body.text}], channel)
  return '정상적으로 수신하였습니다.'
})

이벤트와 마찬가지로 슬랙 메시지보내는 부분을 삭제하고 chat 함수에 값을 바로 전달한다

 

app 메시지로 바로 보낸경우

 

채팅방에서 멘션한경우

 

slash로 보낸경우

 

이제 slack으로 GPT의 대답을 들을 수 있게되었다! 🎉

 

하지만 아직 GPT에게 물어보고 답변받는 형태밖에 되지않아서

동일한 질문을 계속해도 앞서 브라우저에서 간단히 구현한 대화형을 적용할 수 없다

항상 처음 대화하는 것 처럼 대답한다

다음 포스팅에서는 json으로 저장, 불러오기를 추가하여 대화가 가능하게 만들고 프로젝트를 마무리해보자

관련글 더보기

댓글 영역