이번 포스팅에는 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
정식 출시되면 연동 및 작업은 크게 의미가 없어지지만 그 전까지 사용할 수 있도록 작업해보도록 하자
우선 슬랙봇을 생성하도록 하자 슬랙 데스크톱 기준으로
앱추가를 누르고 디렉토리로 이동한다
여기에서 구축을 눌러 앱을 생성하는 화면으로 이동한다
이후 앱을 생성하고 앱 이름과 workspace를 선택한다음 생성해주도록한다
슬랙 앱은 다음과같은 행동을 하도록 만들 것이다
위와 같은 환경에서 사용할 수 있게 구성하도록 하자
권한으로 이동해서
Add an OAuth Scope 버튼을 클릭하여 위와같은 권한을 추가해준다
일반 DM과 채팅방에서 채팅을 쓸 수 있게 하기 위함이다
App Home으로 이동하여
Show Tabs 하위에 Message Tab을 켜주고 DM을 받을 수 있도록 설정해준다
그리고 다시 OAuth로 이동하여
Workspace에 Install 해준다 (허용/거절창은 생략하였다)
그러면 다음과 같은 화면으로 바뀌는데
Bot User OAuth Token을 Copy하여 .env에 SLACK_BOT_OAUTH를 추가해준다
OPENAI_SECRET_KEY=sk-...
SLACK_BOT_OAUTH=xo...
이제 webhook과 slash를 구성해야되는데 그전에 앞서 Ngrok으로 서버를 포트포워딩 해야된다
Nuxt-ngrok이라는 패키지가 있지만 이 포스팅 작성중에는 3버전은 지원하지 않고있다
심플하게 ngrok을 직접 다운받아서 ngrok cli를 바로 구동하도록 하자
ngrok 사이트로 이동하자
https://ngrok.com/
해당 다운로드버튼을 클릭하거나 아래 url로 이동한다
https://ngrok.com/download
포스트 작성기준으로 위와같은 화면인데 다운로드를 누르면 포터블버전으로 cli를 받을 수 있다
이제 앞서 만든 Nuxt 서버를 구동하고
포트를 바꿨거나, 별다른걸 구동하지 않았다면 localhost:3000으로 나올 것이다
ngrok.exe를 실행하자
위와 같은 화면이 나온다
ngrok http 3000
위 코드를 입력하자 그러면 ngrok이 구동된다
포워딩된 url을 복사해놓는다
ngrok 사이트에 가입하고 키를 입력하면 세션이 무제한으로 바뀐다
그 전에는 2시간 정도만 유지되는데
귀찮으면 가입을하고 안 귀찮으면 2시간마다 재 실행하면 된다
이제 slack 테스트를 위한 server 코드를 작성해보자
api 하위에 slack 디렉토리를 추가하고 event.post.ts, slash.post.ts 파일을 추가해준다
event.post.ts는 webhook이나 dm을 감지하고
slash.post.ts는 slack이벤트를 post로 받는다
lib 하위에 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부터 작성한다
// 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을 체크하지 않으면 봇이 대답하고 그걸 다시 대답하고 무한 반복하기 때문에 봇 메시지가 아닌경우만 대답한다
// 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 app 화면으로 돌아와서 webhook과 event를 구성해준다
Command는 /qeust로 작성하고
request url은 ngrok의 url과 slash의 엔드포인트로 넣어준다 (ex: https://b3....ngrok.io/api/slack/slash)
description과 hint도 적절히 작성한후 저장한다
reinstall your app 배너가 뜰건데 나중에 한번에 해주기위해 지금은 하지않는다
이제 event를 구독해야하는데
앞서 반환한 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가 됐다면 Add Bot User Event에서 app_mention을
Add Woekspace Event에서는 message.im을 추가해준다
그리고 저장한뒤 reinstall을 해준다
이제 slask는 준비가 되었고 테스트를 해보자
채널의 경우는 추가할지 물어보는데
추가되면 잘 대답한다
DM에 대해선 접근권한을 주지 않았고, 앱에서 직접 보내면 되기 때문에 DM에서 멘션하면 응답하지 않는다
🤔DM에서 멘션하는 경우는 굳이 없을 것 같아서 추가하지않았다
슬랙이 정상 동작하는 것을 확인했으니 GPT까지 연동해보자
기존에 작성한 코드만 몇개 수정해주면 간단하게 작업할 수 있다
// 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)에 직접 결과값을 전달한다
// 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 함수에 값을 바로 전달한다
이제 slack으로 GPT의 대답을 들을 수 있게되었다! 🎉
하지만 아직 GPT에게 물어보고 답변받는 형태밖에 되지않아서
동일한 질문을 계속해도 앞서 브라우저에서 간단히 구현한 대화형을 적용할 수 없다
다음 포스팅에서는 json으로 저장, 불러오기를 추가하여 대화가 가능하게 만들고 프로젝트를 마무리해보자
Vue3, unit test, e2e, chromatic github ci구성(1) - 프로젝트 구성 (0) | 2023.10.18 |
---|---|
Nuxt 직접로그인, 소셜로그인 구현하기 (Nuxt-auth) (1) | 2023.06.15 |
Nuxt.js로 openai api 연동하여 slack GPT채팅 만들기(4) - 대화 저장 (0) | 2023.03.30 |
Nuxt.js로 openai api 연동하여 slack GPT채팅 만들기(2) - gpt 연동 (0) | 2023.03.26 |
Nuxt.js로 openai api 연동하여 slack GPT채팅 만들기(1) - nuxt 서버설정 (0) | 2023.03.26 |
댓글 영역