인티그레이션의 데이터 엔지니어 류중경입니다. 혹시 LLM, GPT, 대규모 언어모델. 이런 키워드에 관심이 있으시다면 오늘의 주제도 흥미롭게 보실 수 있을것 같습니다.
올해 초 chatGPT의 광풍을 기억하시나요? 제가 속해있는 비교적 평화(?)로왔던 데이터팀도 이 광풍에 휩싸이고 말았습니다.
🤠 대표님 : 데이터팀 너희들도 chatGPT로 뭐좀 해야하지 않을까??
아... 뭐든 해야만 하는 상황이 왔습니다. 다행히도 저는 예전부터 자연어처리에 관심이 많았고, LLM에 대한 호기심이 충만했기에 메인 담당자가 되어있었습니다.
먼저, 남들이 뭐하는지 한번 살펴보았습니다. 아래는 OpeanAI 와 GPT 를 활용한 다양한 오픈소스입니다. 2023년 10월 18일 기준 github topics 에서 openai
검색결과 7797개, gpt
는 2522개네요.
LLM을 API 형태로 제공하다보니, 여러 방면에서 다양한 방식으로 붙여서 사용을 하더군요.
💡 그렇다면! Slack 에서 chatGPT를 연동해볼까?
결론부터 말씀드리면, 아래와 같이 회사 슬랙에 @물어보살
봇을 등록시켜서 여러모로 잘 사용하고 있습니다.
오늘의 이야기는 슬랙에 chatGPT를 붙이려고 파이썬 코드를 가져다가 GPT와 함께 node 코드로 번역하고 서비스로 배포하는 과정입니다.
슬랙 GPT챗봇 근본소스는 당근마켓에서 근무하시는 nalbam 님의 깃헙에서 찾으실 수 있습니다. (python으로 작성됨)
일단 GPT 셋팅이 필요합니다. GPT custom instructions 는 GPT 의 커스터마이징이 가능합니다. 저는 아래와 같이 입력해두고 쓰고 있는데, 답변과 근거를 같이 알려주기 때문에 할루시네이션이 최소화 되는 효과가 있어 자주 사용하고 있습니다.
When you provide an answer, please explain the reasoning and assumtions behind your answer. Explain your choices and address and potential limitations or edge cases.
LLM은 여타 다른 기계들과 아주 다른 특성이 있습니다. 보통 우리가 주로 사용하는 컴퓨터나 자동차와 같은 기계는 곧이곧대로 합니다. 시키는대로만 하지만 빠르고, 군말이 없고, 정확하게 작업을 수행하죠. 하지만 LLM은 지금까지 다뤄왔던 기계들은 할 수 없는 '추론'기능을 할 수 있는대신, 정확성을 담보하지 않습니다. 아주 많이 배우고 똑똑하지만, 경험이 없는 주니어와 같죠. 아 그리고, 아주 뻔뻔합니다. 그럴싸하게 없는 사실과 근거를 지어냅니다. 모르면 깜박 속게마련입니다. 그래서 사실상 LLM을 활용해서 무언가를 시도하기 위해서는 창의성 파라미터(temperature
)를 적절한 수준으로 세팅할 필요가 있습니다.
또한 공식 모범사례를 잘 참고하시면 많은 도움이 되시리라 생각합니다.
위의 nalbam 님이 만드신 lambda-openai-slack-bot 은 aws
의 dynamodb
를 사용해 gpt 에게 이전 대화내용과 같이 전달합니다. 이전 대화내용은 TTL(Time To Live) 을 활성화시켜 일정시간동안만 저장됩니다. 그리고 그 결과 아래와 같이 맥락을 이어가면서 대화 할 수 있습니다.
기본 프로젝트 자체가 훌륭했지만, 앞으로 기능 확장 및 유지보수를 고려할 때, 아무래도 회사의 1급언어인 nodejs 로 서비스를 유지하는 편이 좋을 것 같았습니다.
(저는 개발자 포지션이 아니라 내부 서비스 연동 등의 작업을 하기엔 부담이 됩니다. 이런건 개발자님들이 잘하시니까 그분들 입맛에 맞게 차려드리고 저는 이만...)
뭐 잘모르면 용감하다고, 소스가 몇줄 안되길래, 어찌저찌 chatGPT
에게 물어가면서 되겠지! 하는 기대감으로~
변환 대상 스크립트는 214줄 정도네요. 이제 ~~ 해줘 와 함께 스크립트를 던져보았습니다. (저는 좀더 똑똑한 GPT-4를 돈을 주면서 시킵니다)
Sure, below is a JavaScript code that attempts to replicate the functionality of your Python code. However, it's worth noting that the Python and JavaScript ecosystems are quite different, so there might be some differences or limitations.
First, you'll need to install a few npm packages:
npm install aws-sdk axios @slack/bolt
Here's how you could potentially rewrite your Python code in JavaScript using Node.js:
const { App } = require('@slack/bolt');
const AWS = require('aws-sdk');
const axios = require('axios');
const BOT_CURSOR = process.env.BOT_CURSOR || ":robot_face:";
const DYNAMODB_TABLE_NAME = process.env.DYNAMODB_TABLE_NAME || "openai-slack-bot-context";
const dynamodb = new AWS.DynamoDB.DocumentClient();
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
const SLACK_SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_MODEL = process.env.OPENAI_MODEL;
const OPENAI_HISTORY = parseInt(process.env.OPENAI_HISTORY || 6);
const OPENAI_TEMPERATURE = parseFloat(process.env.OPENAI_TEMPERATURE || 0.5);
const app = new App({
token: SLACK_BOT_TOKEN,
signingSecret: SLACK_SIGNING_SECRET,
});
async function get_context(id, defaultValue = "") {
const params = {
TableName: DYNAMODB_TABLE_NAME,
Key: { id }
};
try {
const data = await dynamodb.get(params).promise();
return data.Item ? data.Item.conversation : defaultValue;
} catch (err) {
console.error(err);
return defaultValue;
}
}
async function put_context(id, conversation = "") {
const current_time = Math.floor(Date.now() / 1000);
const expire_at = current_time + (86400 * 10);
const params = {
TableName: DYNAMODB_TABLE_NAME,
Item: { id, conversation, expire_at }
};
try {
await dynamodb.put(params).promise();
} catch (err) {
console.error(err);
}
}
// More code for handling chat update, translation, and conversations here...
// ...
app.event('app_mention', async ({ event, say }) => {
// More logic here
});
(async () => {
await app.start();
console.log('⚡️ Bolt app is running!');
})();
async
and await
to handle asynchronous operations.Remember to set up your package.json and install the npm packages before running this code.
위와 같이 뚝딱 뭔가를 줍니다. 요놈참... 세상 좋아졌네 ㅋㅋ 아~주 그럴싸~ 합니다..
그런데 어째 라인수가 좀 많이 줄어들었네요?? 에엥??
// More code for handling chat update, translation, and conversations here...
// ...
친절하게도 위와같이 여기에 세부구현 넣으세요
뭐야 다시 나에게 일을 시키네??
Certainly! Below is a more detailed JavaScript code that includes the logic for handling chat updates and OpenAI conversations. This uses the Node.js environment.
First, make sure to install the npm packages:
npm install aws-sdk axios @slack/bolt
Now, you can use the following code:
const { App } = require('@slack/bolt');
const AWS = require('aws-sdk');
const axios = require('axios');
const BOT_CURSOR = process.env.BOT_CURSOR || ":robot_face:";
const DYNAMODB_TABLE_NAME = process.env.DYNAMODB_TABLE_NAME || "openai-slack-bot-context";
AWS.config.update({ region: 'your-region' }); // Update with your AWS region
const dynamodb = new AWS.DynamoDB.DocumentClient();
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
const SLACK_SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_MODEL = process.env.OPENAI_MODEL;
const OPENAI_HISTORY = parseInt(process.env.OPENAI_HISTORY || 6);
const OPENAI_TEMPERATURE = parseFloat(process.env.OPENAI_TEMPERATURE || 0.5);
const app = new App({
token: SLACK_BOT_TOKEN,
signingSecret: SLACK_SIGNING_SECRET,
});
async function get_context(id, defaultValue = "") {
const params = {
TableName: DYNAMODB_TABLE_NAME,
Key: { id }
};
try {
const data = await dynamodb.get(params).promise();
return data.Item ? data.Item.conversation : defaultValue;
} catch (err) {
console.error(err);
return defaultValue;
}
}
async function put_context(id, conversation = "") {
const current_time = Math.floor(Date.now() / 1000);
const expire_at = current_time + (86400 * 10);
const params = {
TableName: DYNAMODB_TABLE_NAME,
Item: { id, conversation, expire_at }
};
try {
await dynamodb.put(params).promise();
} catch (err) {
console.error(err);
}
}
async function chat_update(channel, message, latest_ts) {
console.log(`chat_update: ${message}`);
try {
await app.client.chat.update({
channel: channel,
text: message,
ts: latest_ts,
token: SLACK_BOT_TOKEN
});
} catch (err) {
console.error(err);
}
}
async function conversation(thread_ts, prompt, channel, say) {
console.log(thread_ts, prompt);
// Initial message to show bot is thinking
const result = await say({ text: BOT_CURSOR, thread_ts: thread_ts });
let latest_ts = result.ts;
// Get conversation history
let messages = JSON.parse(await get_context(thread_ts, "[]"));
messages = messages.slice(-OPENAI_HISTORY);
messages.push({
role: 'user',
content: prompt
});
let chat_message = messages;
let message = "";
try {
// Call OpenAI API
const response = await axios.post(
'<https://api.openai.com/v1/chat/completions>',
{
model: OPENAI_MODEL,
messages: chat_message,
temperature: OPENAI_TEMPERATURE,
},
{
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`
}
}
);
// You will need to handle message streaming logic here based on your OpenAI API response.
// Assume 'response.data.choices[0].message.content' contains the assistant's message.
message = response.data.choices[0].message.content;
await chat_update(channel, message, latest_ts);
if (message) {
messages.push({
role: 'assistant',
content: message
});
await put_context(thread_ts, JSON.stringify(messages));
}
} catch (err) {
console.error(err);
await chat_update(channel, "Error handling message", latest_ts);
await say({ text: "Sorry, I could not process your request.", thread_ts: thread_ts });
}
}
app.event('app_mention', async ({ event, say }) => {
const thread_ts = event.thread_ts || event.ts;
const prompt = event.text.split("<@")[1].split(">")[1].trim();
await conversation(thread_ts, prompt, event.channel, say);
});
(async () => {
await app.start();
console.log('⚡️ Bolt app is running!');
})();
AWS.config.update
is called.Remember to add the required environment variables before running the script.
오호 진짜 해주네요!
이런식으로 전 node 에 숙련되지 않았지만, 코드와 에러를 물어가며 번역을 마칠 수 있었습니다.
그리고 눈치 채셨겠지만, 답변의 마지막에 항상
라는 문단이 추가되며, 주의사항등을 적어줍니다. 물론 전부 믿으면 안되지만, 그래도 마음이 좀 놓이네요.
이후 아래와 같은 문제점들 또한 수정하였습니다.
슬랙 api 작동법을 알아야했고, 아니 왜 openAI sdk는 언어마다 기대하는 결과가 달라야하는지도 잘 이해 안가긴 했습니다만..
세상은 원래 그런거니까.. 난 문제해결하면 그만
산은 산, 물은 물.
우여곡절이 있었지만, 다행히도 아직까지 잘 작동하고 있습니다. 또 팀원들이 잘 사용하는 모습을 볼 때 참 뿌듯한 기분입니다.
이런 과정을 통해 저는 Python에서 javascript로 번역을 시도해 볼 수 있었고 덕분에 많이 배울 수 있었습니다. 특히 function들 같은 경우 거의 완벽하게 변환해줄 것 같네요.
현재 GPT 의 발전속도를 보면 앞으로가 더 기대되는군요. 저의 ~~ 해줘를 매번 들어주는 GPT를 보면 매달나가는 $22 이 아깝지 않습니다.
변환된 nodejs 버전 깃헙입니다.
얼마전 GPT 에서 많은 업데이트를 하면서 이미지 생성 & 이미지 분석도 가능해졌는데, 이와 관련된 오픈소스가 나오면 얼른 테스트 해봐야겠습니다.
그럼 다음에 또 다른 흥미로운 이야기로 찾아뵙겠습니다.
읽어주셔서 감사합니다.