Skip to content
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

Add Chatbot Example #166

Merged
merged 1 commit into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tools/run_node_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ npm_install_check $PROJECT_ROOT/patterns-use-cases/ticket-reservation/ticket-res

npm_install_check $PROJECT_ROOT/end-to-end-applications/typescript/ai-image-workflows
npm_install_check $PROJECT_ROOT/end-to-end-applications/typescript/food-ordering/app
npm_install_check $PROJECT_ROOT/end-to-end-applications/typescript/chat-bot
1 change: 1 addition & 0 deletions .tools/update_node_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ bump_ts_sdk $PROJECT_ROOT/patterns-use-cases/ticket-reservation/ticket-reservati
bump_ts_sdk $PROJECT_ROOT/end-to-end-applications/typescript/ai-image-workflows
bump_ts_sdk $PROJECT_ROOT/end-to-end-applications/typescript/food-ordering/app
bump_ts_sdk_clients $PROJECT_ROOT/end-to-end-applications/typescript/food-ordering/webui
bump_ts_sdk $PROJECT_ROOT/end-to-end-applications/typescript/chat-bot
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ challenges.
| Use Cases | [Async Tasks - Payments](patterns-use-cases/async-signals-payment/async-signals-payment-typescript/) |
| End-to-End | [Food Ordering App](end-to-end-applications/typescript/food-ordering) |
| End-to-End | [AI Image Processing Workflow](end-to-end-applications/typescript/ai-image-workflows) |
| End-to-End | [LLM-powered Chat Bot / Task Agent](end-to-end-applications/typescript/chat-bot) |
| Tutorial | [Tour of Restate](tutorials/tour-of-restate-typescript) |
| Templates | [Restate Node/TS Template](templates/typescript) |
| Templates | [Restate Bun/TS Template](templates/bun) |
Expand Down
12 changes: 12 additions & 0 deletions end-to-end-applications/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,15 @@ relaying the calls to services implementing the steps.

The example shows how to build a dynamic workflow interpreter and use workflows to drive work in
other services.


### Chatbot - LLM / Agents

The [Chat Bot](chat-bot) example implements an LLM-powered chat bot with Slack integration that can be asked to
handle tasks, like watching flight prices, or sending reminders.

It illustrates how to
* Use restate for stateful LLM interactions (state holds the chat history and active tasks)
* Create and interact the async tasks running the respective activities


79 changes: 79 additions & 0 deletions end-to-end-applications/typescript/chat-bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

# The Chatbot

This example is a chatbot for slack that interfaces with am LLM (GPT-4o) and maintains various tasks,
like reminders or watching flight prices. The video below (click the image) has a nice intro to the functionality and architecture.

<a href="http://www.youtube.com/watch?feature=player_embedded&v=qsmoHmNUXNg" target="_blank">
<img src="http://img.youtube.com/vi/qsmoHmNUXNg/mqdefault.jpg" alt="Watch the video" width="240" height="180" border="10" />
</a>

## Browing the example

The core chatbot logic is in [chat.ts](./src/chat.ts). This uses Restate's Virtual Objects to
have a chat session per user, with history and active tasks easily maintained in the object state.

The workflow-as-code execution of handlers ensures strict consistency and the single-writer semantics
eforce a consistent message history even under concurrent messages or task notifications.

The tasks are simple Restate workflows. See the [flight watch task](./src/tasks/flight_prices.ts) as an example.

Finally, the [slack integration](./src/slackbot.ts) handles slack webhooks, API calls,
verification, deduplication, tracking message timestamps, etc.


## Running the ChatBot

You need an [OpenAI]() access key to access GPT-4o. Export it as an environment variable like `export OPENAI_API_KEY=sk-proj-...`

Start the Restate Server (see ["Get Restate"](https://restate.dev/get-restate/) if you don't have it downloaded, yet.)

Make sure you install the dependencies via `npm install`.

Start the *reminder* and *flight-watch* tasks via `npm run reminder-task` and `npm run flights-task` respectively
(e.g., in different shells). In this example, we run them as separate endpoints. Since they are mostly suspending,
they would be prime candidates to be deployed on a FaaS platform.

Let Restate know about your task services via
```shell
restate dep add -y localhost:9081
restate dep add -y localhost:9082
```

### Option (1) Without Slack

Start the main chatbot service via `npm run app` and register it at Restate Server `restate dep add -y localhost:9080`.

To chat with the bot, make calls to the chatbot service, like
```
curl localhost:8080/chatSession/<session-name>/chatMessage --json '"Hey, I am Malik, what tasks can you do?"'
curl localhost:8080/chatSession/<session-name>/chatMessage --json '"Create a reminder for in 30 minutes to get a coffee."'
```

The `<session-name>` path identifies the session - each one separately maintains its tasks and history.

Async notifications from tasks (like that a cheap flight was found) come in the chat bot's log, which is a bit hidden,
but a result of the fact that this was initially written to be used with a chat app like Slack.

### Option (2): As a Slack Bot

The chat bot can also be used as a [Slack](https://slack.com/) bot, as shown in the video.
Each slack channel that the bot participates in and each direct message user are separate chat sessions.

The setup is a bit more involved, because you need to create a Slack App as the connection between Slack and
the bot. [This tutorial](https://slack.com/help/articles/13345326945043-Build-apps-with-Slacks-developer-tools)
is a starting point.

For those with some experience in building slack apps, the requirements for this bot are:
* The following *Bot Token OAuth Scopes*: `channels:history`, `chat:write`, `groups:history`, `im:history`, `im:read`, `im:write`, `mpim:history`
* Event subscription for the following *Bot Events*: `message.channels`, `message.groups`, `message.im`, `message.mpim`.

After installing the app to your workspace, export the following tokens and IDs as environment variables:
```shell
export SLACK_BOT_USER_ID=U...
export SLACK_BOT_TOKEN=xoxb-...
export SLACK_SIGNING_SECRET=...
```

Once all keys are set up, start the app together with the slack adapter: `npm run app -- SLACK`.
Use a publicly reachable Restate server URL as Slack's event Request URL: `https://my-restate-uri:8080/slackbot/message`
23 changes: 23 additions & 0 deletions end-to-end-applications/typescript/chat-bot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "restate-announcement-demo-chatbot",
"version": "1.0.0",
"description": "ChatBot demo for the Restate 1.0 announcement",
"main": "app.js",
"type": "commonjs",
"scripts": {
"build": "tsc --noEmitOnError",
"app": "ts-node-dev --watch ./src --transpile-only ./src/app.ts",
"reminder-task": "RESTATE_LOGGING=INFO ts-node-dev --watch ./src --transpile-only ./src/tasks/reminder.ts",
"flights-task": "RESTATE_LOGGING=INFO ts-node-dev --watch ./src --transpile-only ./src/tasks/flight_prices.ts"
},
"dependencies": {
"@restatedev/restate-sdk": "1.1.0",
"@slack/bolt": "^3.19.0",
"@slack/web-api": "^7.0.4"
},
"devDependencies": {
"@types/node": "^20.12.7",
"ts-node-dev": "^1.1.8",
"typescript": "^5.0.2"
}
}
31 changes: 31 additions & 0 deletions end-to-end-applications/typescript/chat-bot/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as restate from "@restatedev/restate-sdk"
import * as tm from "./taskmanager"
import * as slackbot from "./slackbot"
import * as chat from "./chat"

import { reminderTaskDefinition } from "./tasks/reminder";
import { flightPricesTaskDefinition } from "./tasks/flight_prices";

const mode = process.argv[2];

// (1) register the task types we have at the task manager
// so that the task manager knows where to send certain commands to

tm.registerTaskWorkflow(reminderTaskDefinition);
tm.registerTaskWorkflow(flightPricesTaskDefinition)

// (2) build the endpoint with the core handlers for the chat

const endpoint = restate.endpoint()
.bind(chat.chatSessionService)
.bind(tm.workflowInvoker)

// (3) add slackbot, if in slack mode

if (mode === "SLACK") {
endpoint.bindBundle(slackbot.services)
chat.notificationHandler(slackbot.notificationHandler)
}

// start the defaut http2 server (alternatively export as lambda handler, http handler, ...)
endpoint.listen(9080);
Loading
Loading