185 lines
6.4 KiB
Plaintext
185 lines
6.4 KiB
Plaintext
---
|
|
title: "Discord Bots with Honcho"
|
|
icon: 'discord'
|
|
description: "Discord is a powerful chat application that handles many UI complications"
|
|
sidebarTitle: 'Discord'
|
|
---
|
|
|
|
> Example code is available on [GitHub](https://github.com/plastic-labs/honcho-python/blob/main/examples/discord/roast-bot/main.py)
|
|
|
|
Any application interface that defines logic based on events and supports
|
|
special commands can work easily with Honcho. Here's how to use Honcho with
|
|
**Discord** as an interface. If you're not familiar with Discord bot
|
|
application logic, the [py-cord](https://pycord.dev/) docs would be a good
|
|
place to start.
|
|
|
|
## Events
|
|
|
|
Most Discord bots have async functions that listen for specific events, the most common one being messages. We can use Honcho to store messages by user and session based on an interface's event logic. Take the following function definition for example:
|
|
```python
|
|
@bot.event
|
|
async def on_message(message):
|
|
if message.author == bot.user:
|
|
return
|
|
|
|
user_id = f"discord_{str(message.author.id)}"
|
|
user = honcho.apps.users.get_or_create(name=user_id, app_id=app.id)
|
|
|
|
# Get the session associated with the user and location
|
|
location_id = str(message.channel.id) # Get the channel id for the message
|
|
|
|
sessions = [
|
|
session
|
|
for session in honcho.apps.users.sessions.list(
|
|
user_id=user.id, app_id=app.id, is_active=True, location_id=location_id
|
|
)
|
|
]
|
|
|
|
if len(sessions) > 0:
|
|
session = sessions[0]
|
|
else:
|
|
session = honcho.apps.users.sessions.create(user_id=user.id, app_id=app.id, location_id=location_id)
|
|
|
|
history = [
|
|
message
|
|
for message in honcho.apps.users.sessions.messages.list(session_id=session.id, app_id=app.id, user_id=user.id)
|
|
]
|
|
chat_history = messages_to_langchain(history)
|
|
|
|
inp = message.content
|
|
honcho.apps.users.sessions.messages.create(
|
|
app_id=app.id,
|
|
user_id=user.id,
|
|
session_id=session.id,
|
|
content=input,
|
|
is_user=True,
|
|
)
|
|
|
|
async with message.channel.typing():
|
|
response = await chain.ainvoke({"chat_history": chat_history, "input": inp})
|
|
await message.channel.send(response)
|
|
|
|
honcho.apps.users.sessions.messages.create(
|
|
app_id=app.id,
|
|
user_id=user.id,
|
|
session_id=session.id,
|
|
content=response,
|
|
is_user=False,
|
|
)
|
|
```
|
|
|
|
Let's break down what each chunk of code is doing...
|
|
```python
|
|
@bot.event
|
|
async def on_message(message):
|
|
if message.author == bot.user:
|
|
return
|
|
```
|
|
|
|
This is how you define an event function in `py-cord` that listens for messages and checks that the bot doesn't respond to itself.
|
|
|
|
```python
|
|
user_id = f"discord_{str(message.author.id)}"
|
|
location_id = str(message.channel.id)
|
|
```
|
|
|
|
Honcho accepts a `location_id` argument to help separate out locations messages were sent (which is convenient for Discord channels).
|
|
|
|
```python
|
|
sessions = [
|
|
session
|
|
for session in honcho.apps.users.sessions.list(
|
|
user_id=user.id, app_id=app.id, is_active=True, location_id=location_id
|
|
)
|
|
]
|
|
if len(sessions) > 0:
|
|
session = sessions[0]
|
|
else:
|
|
session = honcho.apps.users.sessions.create(user_id=user.id, app_id=app.id, location_id=location_id)
|
|
```
|
|
|
|
Here we're querying honcho for the user's sessions based on the location (channel) they're in. This will get all the sessions, so the if statement just pops the most recent one (if there are many) or creates a new one if none exist.
|
|
|
|
```python
|
|
history = [
|
|
message
|
|
for message in honcho.apps.users.sessions.messages.list(session_id=session.id, app_id=app.id, user_id=user.id)
|
|
]
|
|
chat_history = messages_to_langchain(history)
|
|
|
|
# Add user message to session
|
|
input = message.content
|
|
honcho.apps.users.sessions.messages.create(
|
|
app_id=app.id,
|
|
user_id=user.id,
|
|
session_id=session.id,
|
|
content=input,
|
|
is_user=True,
|
|
)
|
|
|
|
async with message.channel.typing():
|
|
response = await chain.ainvoke({"chat_history": chat_history, "input": inp})
|
|
await message.channel.send(response)
|
|
|
|
# Add bot message to session
|
|
honcho.apps.users.sessions.messages.create(
|
|
app_id=app.id,
|
|
user_id=user.id,
|
|
session_id=session.id,
|
|
content=response,
|
|
is_user=False,
|
|
)
|
|
```
|
|
|
|
This chunk is all about constructing the object to send to an LLM API. We get
|
|
the messages from a `session` and construct a `chat_history` object with a
|
|
quick utility function (more on that in the [Langchain](./langchain) guide).
|
|
Then, we access the user message via `message.content` and add it to Honcho.
|
|
The `async with` method allows the bot to show that it's "typing" while waiting
|
|
for an LLM response and then uses `message.channel.send` to respond to the
|
|
user. We can then add that AI response to Honcho with the same
|
|
`session.create_message` method, this time specifying that this message did not
|
|
come from a user with `is_user=False`.
|
|
|
|
## Slash Commands
|
|
|
|
Discord bots also offer slash command functionality. We can use Honcho to do
|
|
interesting things via slash commands. Here's a simple example:
|
|
|
|
```python
|
|
@bot.slash_command(name = "restart", description = "Restart the Conversation")
|
|
async def restart(ctx):
|
|
user_id=f"discord_{str(ctx.author.id)}"
|
|
user = honcho.apps.users.get_or_create(name=user_id, app_id=app.id)
|
|
location_id=str(ctx.channel_id)
|
|
sessions = [
|
|
session
|
|
for session in honcho.apps.users.sessions.list(
|
|
user_id=user.id, app_id=app.id, is_active=True, location_id=location_id
|
|
)
|
|
]
|
|
if len(sessions) > 0:
|
|
honcho.apps.users.sessions.delete(app_id=app.id, user_id=user.id, session_id=sessions[0].id)
|
|
|
|
msg = "Great! The conversation has been restarted. What would you like to talk about?"
|
|
await ctx.respond(msg)
|
|
```
|
|
|
|
This slash command restarts a conversation with a bot. In that case, we want to remove that session from storage. You can see we follow the same steps to access the user metadata via commands from the application interface:
|
|
```python
|
|
user_id=f"discord_{str(ctx.author.id)}"
|
|
user = honcho.apps.users.get_or_create(name=user_id, app_id=app.id)
|
|
location_id=str(ctx.channel_id)
|
|
```
|
|
Then we can retrieve and delete the session associated with that metadata:
|
|
```python
|
|
sessions = [
|
|
session
|
|
for session in honcho.apps.users.sessions.list(
|
|
user_id=user.id, app_id=app.id, is_active=True, location_id=location_id
|
|
)
|
|
]
|
|
if len(sessions) > 0:
|
|
honcho.apps.users.sessions.delete(app_id=app.id, user_id=user.id, session_id=sessions[0].id)
|
|
```
|