docs: Update Mintlify Documentation (#60)
* Reworked Langchain guide and started on discord guide * docs(mintlify): Update discord and simple memory guides
This commit is contained in:
parent
2e0190ca9f
commit
6c31fed873
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
openapi: get /apps/{app_id}/users/{user_id}/collections/{name}
|
||||
---
|
||||
openapi: get /apps/{app_id}/users/{user_id}/collections/name/{name}
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
openapi: get /apps/{app_id}/users/{name}
|
||||
---
|
||||
openapi: get /apps/{app_id}/users/name/{name}
|
||||
---
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@ description: "Discord is a powerful chat application that handles many UI compli
|
|||
sidebarTitle: 'Discord'
|
||||
---
|
||||
|
||||
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.
|
||||
> 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
|
||||
|
||||
|
|
@ -17,27 +23,49 @@ async def on_message(message):
|
|||
return
|
||||
|
||||
user_id = f"discord_{str(message.author.id)}"
|
||||
user = honcho.get_or_create_user(user_id)
|
||||
location_id=str(message.channel.id)
|
||||
user = honcho.apps.users.get_or_create(name=user_id, app_id=app.id)
|
||||
|
||||
sessions = list(user.get_sessions_generator(location_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 = user.create_session(location_id)
|
||||
session = honcho.apps.users.sessions.create(user_id=user.id, app_id=app.id, location_id=location_id)
|
||||
|
||||
history = list(session.get_messages_generator())
|
||||
chat_history = langchain_message_converter(history)
|
||||
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
|
||||
session.create_message(is_user=True, content=inp)
|
||||
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)
|
||||
|
||||
session.create_message(is_user=False, content=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...
|
||||
|
|
@ -58,43 +86,80 @@ 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 = list(user.get_sessions_generator(location_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:
|
||||
session = sessions[0]
|
||||
else:
|
||||
session = user.create_session(user_id, location_id)
|
||||
session = honcho.apps.users.sessions.create(user_id=user.id, app_id=app.id, location_id=location_id)
|
||||
```
|
||||
|
||||
Here we're querying the `session` object for the user 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.
|
||||
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 = list(session.get_messages_generator())
|
||||
chat_history = langchain_message_converter(history)
|
||||
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
|
||||
session.create_message(is_user=True, content=inp)
|
||||
# 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)
|
||||
|
||||
session.create_message(is_user=False, content=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](../llm-frameworks/langchain) guide). Then, we access the user message via `message.content` and use `session.create_message` to 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`.
|
||||
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:
|
||||
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.get_or_create_user(user_id)
|
||||
user = honcho.apps.users.get_or_create(name=user_id, app_id=app.id)
|
||||
location_id=str(ctx.channel_id)
|
||||
sessions = list(user.get_sessions_generator(location_id))
|
||||
sessions[0].close() if len(sessions) > 0 else None
|
||||
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)
|
||||
|
|
@ -103,11 +168,17 @@ async def restart(ctx):
|
|||
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.get_or_create_user(user_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 messages associated with that metadata:
|
||||
Then we can retrieve and delete the session associated with that metadata:
|
||||
```python
|
||||
sessions = list(user.get_sessions_generator(user_id, location_id))
|
||||
sessions[0].close() if len(sessions) > 0 else None
|
||||
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)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
|
||||
from uuid import uuid4 # for generating random app and user names
|
||||
from dotenv import load_dotenv # for loading in LLM API keys
|
||||
|
||||
load_dotenv() # assumes you have a .env file with OPENAI_API_KEY defined
|
||||
|
|
@ -24,12 +23,12 @@ load_dotenv() # assumes you have a .env file with OPENAI_API_KEY defined
|
|||
Next let's instantiate our Honcho client:
|
||||
|
||||
```python
|
||||
app_name = str(uuid4())
|
||||
honcho = Honcho(environment="demo")
|
||||
app_name = "LangChain App"
|
||||
app = honcho.apps.get_or_create(name=app_name) # create or get app
|
||||
|
||||
honcho = Honcho(app_name=app_name)
|
||||
honcho.initialize()
|
||||
user_name = str(uuid4())
|
||||
user = honcho.create_or_get_user(user_name) # create or get user
|
||||
user = honcho.apps.users.get_or_create(app_id=app.id, name=user_name) # create or get user
|
||||
```
|
||||
|
||||
Then we can define our chain using the LangChain Expression Language ([LCEL](https://python.langchain.com/docs/expression_language/why)):
|
||||
|
|
@ -48,7 +47,7 @@ chain = prompt | model | output_parser
|
|||
Honcho returns lists of `Message` objects when queried using a built-in method like `get_messages()`, so a quick utility function is needed to change the list format to message objects LangChain expects:
|
||||
|
||||
```python
|
||||
def langchain_message_converter(messages: List):
|
||||
def messages_to_langchain(messages: List):
|
||||
new_messages = []
|
||||
for message in messages:
|
||||
if message.is_user:
|
||||
|
|
@ -58,18 +57,38 @@ def langchain_message_converter(messages: List):
|
|||
return new_messages
|
||||
```
|
||||
|
||||
This method is importable with the following statement
|
||||
|
||||
```python
|
||||
from honcho.lib.ext.langchain import messages_to_langchain
|
||||
```
|
||||
|
||||
Now we can structure Honcho calls around our LLM inference:
|
||||
```python
|
||||
sessions = list(user.get_sessions_generator(location_id)) # args come from application logic
|
||||
sessions = [
|
||||
session for session in
|
||||
honcho.apps.users.sessions.list(
|
||||
app_id=app.id,
|
||||
user_id=user.id,
|
||||
location_id=location_id
|
||||
)
|
||||
] # args come from application logic
|
||||
session = sessions[0] # most recent session for user
|
||||
history = list(session.get_messages_generator())
|
||||
chat_history = langchain_message_converter(history) # convert messages for LangChain
|
||||
history = [
|
||||
message for message in
|
||||
honcho.apps.users.sessions.messages.list(
|
||||
app_id=app.id,
|
||||
user_id=user.id,
|
||||
session_id=session.id
|
||||
)
|
||||
]
|
||||
chat_history = messages_to_langchain(history) # convert messages for LangChain
|
||||
inp = "Here's a user message!"
|
||||
session.create_message(is_user=True, content=inp)
|
||||
honcho.apps.users.sessions.messages.create(app_id=app.id, user_id=user.id, is_user=True, content=inp)
|
||||
|
||||
response = await chain.ainvoke({"chat_history": chat_history, "input": inp})
|
||||
|
||||
session.create_message(is_user=False, content=response)
|
||||
honcho.apps.users.sessions.messages.create(app_id=app.id, user_id=user.id, is_user=False, content=response)
|
||||
```
|
||||
|
||||
Here we query messages from a user's session using Honcho and construct a chat history object to send to the LLM alongside our immediate user input. Once the LLM has responded, we can add that to Honcho!
|
||||
Here we query messages from a user's session using Honcho and construct a chat history object to send to the LLM alongside our immediate user input. Once the LLM has responded, we can add that to Honcho!
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ description: "A simple example of how to store and derive facts about individual
|
|||
This guide shows how to implement a simple user memory system that derives and stores facts about users that
|
||||
are then referenced later on.
|
||||
|
||||
A fully working example can be found on [GitHub](https://github.com/plastic-labs/honcho/tree/main/example/discord/honcho-fact-memory).
|
||||
It's setup as a discord bot so view our [Discord guide](../interfaces/discord)
|
||||
A fully working example can be found on [GitHub](https://github.com/plastic-labs/honcho-python/tree/main/examples/discord/fact-memory).
|
||||
It's setup as a discord bot so view our [Discord guide](./discord)
|
||||
for more details on how to set that up.
|
||||
|
||||
## Initial Setup
|
||||
|
|
@ -31,7 +31,9 @@ from langchain.schema import (
|
|||
from langchain_core.output_parsers import NumberedListOutputParser
|
||||
|
||||
llm: ChatOpenAI = ChatOpenAI(model_name="gpt-4")
|
||||
honcho = Honcho(app_id="Simple User Memory")
|
||||
app_name = "Fact-Memory"
|
||||
|
||||
honcho = Honcho(environment="demo")
|
||||
```
|
||||
|
||||
The 3 steps to this project are:
|
||||
|
|
@ -81,23 +83,30 @@ template: >
|
|||
Output the facts as a numbered list.
|
||||
```
|
||||
|
||||
We then invoke the chain and parse the output to get our list of facts. We take advantage of one of LangChain's built in output parsers for this.
|
||||
We then invoke the chain and parse the output to get our list of facts. We take
|
||||
advantage of one of LangChain's built in output parsers for this.
|
||||
|
||||
## Step 2 - Storing Facts
|
||||
|
||||
This is where Honcho comes into play. With Honcho we can initialize `Collections` for each user and can store facts as vector embeddings. You can use multiple collections if you want to segment different types of facts or data, but for now we just need one.
|
||||
This is where Honcho comes into play. With Honcho we can initialize
|
||||
`Collections` for each user and can store facts as vector embeddings. You can
|
||||
use multiple collections if you want to segment different types of facts or
|
||||
data, but for now we just need one.
|
||||
|
||||
```python
|
||||
def store_facts(user_name, facts):
|
||||
user = honcho.get_or_create_user(user_name)
|
||||
def store_facts(app_id, user_id, facts):
|
||||
user = honcho.apps.users.get_or_create(name=user_id, app_id=app.id)
|
||||
|
||||
try:
|
||||
collection = honcho.apps.users.collections.get_by_name(app_id=app.id, user_id=user.id, name="discord")
|
||||
except NotFoundError as e:
|
||||
collection = honcho.apps.users.collections.create(app_id=app.id, user_id=user.id, name="discord")
|
||||
collection: Collection
|
||||
try: # Check if collection exists if not create it
|
||||
collection = user.get_collection("simple-memory")
|
||||
except Exception
|
||||
collection = user.create_collection("simple-memory")
|
||||
|
||||
for fact in facts: # store each fact in the collection
|
||||
collection.create_document(content=fact)
|
||||
honcho.apps.users.collections.documents.create(
|
||||
app_id=app_id, user_id=user_id, collection_id=collection.id, content=fact
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -181,4 +190,4 @@ template: >
|
|||
---
|
||||
|
||||
This is a very simple method of using Honcho to hold user context. For further reading on the limits read about
|
||||
[violation of expectation](https://arxiv.org/abs/2310.06983).
|
||||
[violation of expectation](https://arxiv.org/abs/2310.06983).
|
||||
|
|
|
|||
4427
docs/openapi.json
4427
docs/openapi.json
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue