-
-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add request/reply support #551
Changes from 22 commits
044823a
62b7076
2b40e2f
1ada3d4
839c82c
8b97e96
224391c
a498823
ac4c281
8c3f8f9
37c929e
a7cca8b
b214597
8b3000c
202176f
494d8a7
73e2030
02555cb
8e5a1a1
e585f17
9e99f90
c383d35
ef0ba2d
5c7bd6b
8854982
ab29311
ddb5af3
1155325
10cc240
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.glee |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# Slack Websocket API | ||
|
||
This Slack Websocket API leverages the AsyncAPI specification to connect Slack with OpenAI's AI models. When a user reacts to a message on Slack, this API sends the reaction to OpenAI's server. ChatGPT then crafts a fun response, which is posted as a reply to the message thread on Slack. | ||
|
||
## Table of Contents | ||
|
||
- [Overview](#overview) | ||
- [Prerequisites](#prerequisites) | ||
- [Configuration](#configuration) | ||
- [Usage](#usage) | ||
- [Environment Variables](#environment-variables) | ||
|
||
## Overview | ||
|
||
The API listens for reaction events in Slack, processes them through OpenAI's API to generate responses, and sends those back to Slack as a threaded message. | ||
|
||
## Prerequisites | ||
|
||
- Node.js (version 12 or higher) | ||
- A Slack app with permissions to read reactions and post messages | ||
- Access to OpenAI API | ||
|
||
## Configuration | ||
|
||
Before running the project, you must update the `asyncapi.yaml` file with the current `ticket` and `app_id` for the Slack WebSocket connection: | ||
|
||
```yaml | ||
channels: | ||
SlackEventStream: | ||
address: /link/?ticket=[ticket]&app_id=[app_id] | ||
``` | ||
|
||
Replace `[ticket]` and `[app_id]` with the respective values for your Slack app. | ||
|
||
## Usage | ||
|
||
Set the environment variables by creating a `.env` file in the root of the project: | ||
|
||
```plaintext | ||
SLACK_HTTP=xoxb-********** | ||
CHAT_API=openai_token | ||
``` | ||
|
||
Start the API server with: | ||
|
||
```sh | ||
npm run dev | ||
``` | ||
|
||
The API will now listen for Slack reaction events, interact with OpenAI, and post responses on Slack. | ||
|
||
## Environment Variables | ||
|
||
The following environment variables are necessary for the API to function: | ||
|
||
- `SLACK_HTTP`: Your Slack app's OAuth token. | ||
- `CHAT_API`: Your OpenAI API key. | ||
|
||
Ensure these are set in your environment. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
asyncapi: 3.0.0 | ||
info: | ||
title: Slack Websocket and OpenAI API | ||
version: 1.0.0 | ||
servers: | ||
OpenAI_HTTPS: | ||
host: api.openai.com | ||
protocol: https | ||
Slack_WebSocket: | ||
host: wss-primary.slack.com | ||
protocol: wss | ||
Slack_HTTPS: | ||
host: slack.com | ||
protocol: https | ||
channels: | ||
SlackPostMessage: | ||
bindings: | ||
http: | ||
method: post | ||
address: /api/chat.postMessage | ||
servers: | ||
- $ref: "#/servers/Slack_HTTPS" | ||
messages: | ||
slackResponse: | ||
payload: | ||
type: object | ||
properties: | ||
channel: | ||
type: string | ||
text: | ||
type: string | ||
OpenAICompletion: | ||
bindings: | ||
http: | ||
method: post | ||
servers: | ||
- $ref: "#/servers/OpenAI_HTTPS" | ||
address: /v1/chat/completions | ||
messages: | ||
OpenAIRequest: | ||
$ref: "#/components/messages/OpenAIRequest" | ||
SlackEventStream: | ||
servers: | ||
- $ref: "#/servers/Slack_WebSocket" | ||
address: /link/?ticket={ticket}&app_id={app_id} | ||
parameters: | ||
ticket: | ||
default: 5a271cc5-80c7-d-bbd0-fd8a62d5767a | ||
app_id: | ||
default: b02112d9364cb2fee8a0a920b8b4b0103cb03fab023b4e30f6ee2efb98f | ||
messages: | ||
SlackReactionAdded: | ||
$ref: "#/components/messages/SlackReactionAdded" | ||
GenericErrorPayload: | ||
$ref: "#/components/messages/GenericErrorPayload" | ||
operations: | ||
sentSlackMessage: | ||
action: send | ||
channel: | ||
$ref: "#/channels/SlackPostMessage" | ||
messages: | ||
- $ref: "#/components/messages/slackResponse" | ||
AckEvent: | ||
action: send | ||
channel: | ||
$ref: "#/channels/SlackEventStream" | ||
messages: | ||
- $ref: "#/components/messages/slackAckEvent" | ||
SendToOpenAI: | ||
action: send | ||
channel: | ||
$ref: "#/channels/OpenAICompletion" | ||
messages: | ||
- $ref: "#/components/messages/slackResponse" | ||
ReceiveFromOpenAI: | ||
action: receive | ||
channel: | ||
$ref: "#/channels/OpenAICompletion" | ||
messages: | ||
- $ref: "#/components/messages/OpenAICompletionResponse" | ||
reply: | ||
channel: | ||
$ref: "#/channels/SlackPostMessage" | ||
HandleSlackReaction: | ||
reply: | ||
channel: | ||
$ref: "#/channels/SlackEventStream" | ||
Comment on lines
+89
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this feels weird, as your app is reaction listener, and I bet slack websocket server that you listen to do not expect any reply from the app that you're creating. So you receive stream and that is it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It actually needs a reply. if slack doesn't receive a confirmation it will resend the message multiple times. |
||
action: receive | ||
channel: | ||
$ref: "#/channels/SlackEventStream" | ||
messages: | ||
- $ref: "#/components/messages/SlackReactionAdded" | ||
- $ref: "#/components/messages/GenericErrorPayload" | ||
components: | ||
messages: | ||
slackAckEvent: | ||
payload: | ||
type: object | ||
properties: | ||
envelope_id: | ||
type: string | ||
slackResponse: | ||
payload: | ||
type: object | ||
properties: | ||
channel: | ||
type: string | ||
text: | ||
type: string | ||
OpenAIRequest: | ||
payload: | ||
type: object | ||
properties: | ||
model: | ||
type: string | ||
enum: ["gpt-3.5-turbo", "gpt-3.5-turbo-16k", "text-davinci-002"] | ||
messages: | ||
type: array | ||
items: | ||
type: object | ||
properties: | ||
role: | ||
type: string | ||
enum: ["user"] | ||
content: | ||
type: string | ||
temperature: | ||
type: number | ||
minimum: 0.0 | ||
maximum: 1.0 | ||
required: | ||
- model | ||
- messages | ||
- temperature | ||
OpenAICompletionResponse: | ||
payload: | ||
type: object | ||
properties: | ||
choices: | ||
type: array | ||
items: | ||
type: object | ||
properties: | ||
finish_reason: | ||
type: string | ||
enum: ['stop'] | ||
message: | ||
type: object | ||
properties: | ||
content: | ||
type: string | ||
role: | ||
type: string | ||
enum: ['assistant'] | ||
required: | ||
- finish_reason | ||
- message | ||
required: | ||
- choices | ||
SlackReactionAdded: | ||
payload: | ||
type: object | ||
GenericErrorPayload: | ||
payload: | ||
type: string | ||
x-remoteServers: | ||
- Slack_HTTPS | ||
- Slack_WebSocket | ||
- OpenAI_HTTPS |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { GleeFunction } from "@asyncapi/glee" | ||
|
||
const myFunction: GleeFunction = async ({ payload }) => { | ||
const { envelope_id } = payload | ||
const reaction = payload?.payload?.event?.reaction | ||
if (!reaction) return | ||
return { | ||
reply: [ | ||
{ | ||
payload: { | ||
envelope_id | ||
} | ||
} | ||
], | ||
send: [{ | ||
server: "OpenAI_HTTPS", | ||
channel: "OpenAICompletion", | ||
headers: { | ||
'Authorization': `Bearer ${process.env.CHAT_API}` | ||
}, | ||
payload: { | ||
model: "gpt-3.5-turbo", | ||
messages: [{ "role": "user", "content": `Someone reacted with "${reaction}" emoji to my message on Slack, write something fun and short to them.` }], | ||
temperature: 0.7 | ||
} | ||
}] | ||
} | ||
|
||
|
||
} | ||
|
||
export default myFunction |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { GleeFunction } from "@asyncapi/glee" | ||
|
||
const myFunction: GleeFunction = async (event) => { | ||
const { payload } = event.request.request | ||
const slack_event = payload?.payload?.event | ||
|
||
if (!slack_event) return | ||
|
||
const thread_ts = slack_event.item.ts | ||
const channel = slack_event.item.channel | ||
const text = event.payload.choices[0].message.content | ||
|
||
|
||
return { | ||
send: [{ | ||
channel: "SlackPostMessage", | ||
server: "Slack_HTTPS", | ||
payload: { | ||
channel, thread_ts, text | ||
}, | ||
headers: { | ||
Authorization: `Bearer ${process.env.SLACK_HTTP}`, | ||
} | ||
}] | ||
} | ||
} | ||
|
||
export default myFunction |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export default async function () { | ||
return { | ||
docs: { | ||
enabled: false | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just remember that these should go through websocket binding query field - but of course if that is not supported, parameters are good temporary solution, although in future address should be validated by parser, if not yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will update it to use the bindings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#591