Skip to content

Commit

Permalink
Add Built-in Langchain Utility function
Browse files Browse the repository at this point in the history
  • Loading branch information
VVoruganti committed Mar 12, 2024
1 parent 1ffaff2 commit 880935d
Show file tree
Hide file tree
Showing 9 changed files with 615 additions and 439 deletions.
16 changes: 5 additions & 11 deletions example/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
from uuid import uuid4

from langchain.prompts import ChatPromptTemplate

from langchain.schema import AIMessage, HumanMessage, SystemMessage
from langchain_community.chat_models.fake import FakeListChatModel

from honcho import Honcho
from honcho.ext.langchain import langchain_message_converter

app_name = str(uuid4())

# honcho = Honcho(app_id=app_id, base_url="http://localhost:8000") # uncomment to use local
# honcho = Honcho(
# app_name=app_name, base_url="http://localhost:8000"
# ) # uncomment to use local
honcho = Honcho(app_name=app_name) # uses demo server at https://demo.honcho.dev
honcho.initialize()

Expand All @@ -24,16 +28,6 @@
session = user.create_session()


def langchain_message_converter(messages: List):
new_messages = []
for message in messages:
if message.is_user:
new_messages.append(HumanMessage(content=message.content))
else:
new_messages.append(AIMessage(content=message.content))
return new_messages


def chat():
while True:
user_input = input("User: ")
Expand Down
677 changes: 386 additions & 291 deletions example/cli/poetry.lock

Large diffs are not rendered by default.

24 changes: 16 additions & 8 deletions example/discord/honcho-dspy-personas/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from uuid import uuid1
import discord
from honcho import Honcho
from honcho.ext.langchain import langchain_message_converter
from graph import chat
from dspy import Example
from chain import langchain_message_converter

intents = discord.Intents.default()
intents.messages = True
Expand Down Expand Up @@ -51,7 +51,9 @@ async def on_message(message):
user = honcho.get_or_create_user(user_id)
location_id = str(message.channel.id)

sessions = list(user.get_sessions_generator(location_id, is_active=True, reverse=True))
sessions = list(
user.get_sessions_generator(location_id, is_active=True, reverse=True)
)

if len(sessions) > 0:
session = sessions[0]
Expand Down Expand Up @@ -86,7 +88,9 @@ async def on_reaction_add(reaction, user):
honcho_user = honcho.get_or_create_user(user_id)
location_id = str(reaction.message.channel.id)

sessions = list(honcho_user.get_sessions_generator(location_id, is_active=True, reverse=True))
sessions = list(
honcho_user.get_sessions_generator(location_id, is_active=True, reverse=True)
)
if len(sessions) > 0:
session = sessions[0]
else:
Expand All @@ -100,25 +104,29 @@ async def on_reaction_add(reaction, user):
user_response = user_responses[0]

user_state_storage = dict(honcho_user.metadata)
user_state = list(session.get_metamessages_generator(metamessage_type="user_state", message=user_response, reverse=True))[0].content
user_state = list(
session.get_metamessages_generator(
metamessage_type="user_state", message=user_response, reverse=True
)
)[0].content
examples = user_state_storage[user_state]["examples"]

# Check if the reaction is a thumbs up
if str(reaction.emoji) == "👍":
example = Example(
chat_input=user_response.content,
chat_input=user_response.content,
response=ai_response,
assessment_dimension=user_state,
label='yes'
label="yes",
).with_inputs("chat_input", "response", "assessment_dimension")
examples.append(example.toDict())
# Check if the reaction is a thumbs down
elif str(reaction.emoji) == "👎":
example = Example(
chat_input=user_response.content,
chat_input=user_response.content,
response=ai_response,
assessment_dimension=user_state,
label='no'
label="no",
).with_inputs("chat_input", "response", "assessment_dimension")
examples.append(example.toDict())

Expand Down
131 changes: 79 additions & 52 deletions example/discord/honcho-dspy-personas/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,92 +2,115 @@
from typing import List, Union
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, load_prompt
from langchain_core.prompts import (
ChatPromptTemplate,
SystemMessagePromptTemplate,
load_prompt,
)
from langchain_core.messages import AIMessage, HumanMessage

from honcho import Message

load_dotenv()

# langchain prompts
SYSTEM_STATE_COMMENTARY = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_commentary.yaml'))
SYSTEM_STATE_LABELING = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_labeling.yaml'))
SYSTEM_STATE_CHECK = load_prompt(os.path.join(os.path.dirname(__file__), 'langchain_prompts/state_check.yaml'))

# quick utility function to convert messages from honcho to langchain
def langchain_message_converter(messages: List[Message]) -> List[Union[AIMessage, HumanMessage]]:
new_messages = []
for message in messages:
if message.is_user:
new_messages.append(HumanMessage(content=message.content))
else:
new_messages.append(AIMessage(content=message.content))
return new_messages
SYSTEM_STATE_COMMENTARY = load_prompt(
os.path.join(os.path.dirname(__file__), "langchain_prompts/state_commentary.yaml")
)
SYSTEM_STATE_LABELING = load_prompt(
os.path.join(os.path.dirname(__file__), "langchain_prompts/state_labeling.yaml")
)
SYSTEM_STATE_CHECK = load_prompt(
os.path.join(os.path.dirname(__file__), "langchain_prompts/state_check.yaml")
)


# convert chat history and user input into a string
def format_chat_history(chat_history: List[Message], user_input=None):
messages = [("user: " + message.content if isinstance(message, HumanMessage) else "ai: " + message.content) for message in chat_history]
messages = [
(
"user: " + message.content
if isinstance(message, HumanMessage)
else "ai: " + message.content
)
for message in chat_history
]
if user_input:
messages.append(f"user: {user_input}")

return "\n".join(messages)



class StateExtractor:
"""Wrapper class for all the DSPy and LangChain code for user state labeling and pipeline optimization"""
lc_gpt_4: ChatOpenAI = ChatOpenAI(model_name = "gpt-4")
lc_gpt_turbo: ChatOpenAI = ChatOpenAI(model_name = "gpt-3.5-turbo")
system_state_commentary: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_COMMENTARY)
system_state_labeling: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_LABELING)
system_state_check: SystemMessagePromptTemplate = SystemMessagePromptTemplate(prompt=SYSTEM_STATE_CHECK)

lc_gpt_4: ChatOpenAI = ChatOpenAI(model_name="gpt-4")
lc_gpt_turbo: ChatOpenAI = ChatOpenAI(model_name="gpt-3.5-turbo")
system_state_commentary: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
prompt=SYSTEM_STATE_COMMENTARY
)
system_state_labeling: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
prompt=SYSTEM_STATE_LABELING
)
system_state_check: SystemMessagePromptTemplate = SystemMessagePromptTemplate(
prompt=SYSTEM_STATE_CHECK
)

def __init__(self) -> None:
pass

@classmethod
async def generate_state_commentary(cls, existing_states: List[str], chat_history: List[Message], input: str) -> str:
async def generate_state_commentary(
cls, existing_states: List[str], chat_history: List[Message], input: str
) -> str:
"""Generate a commentary on the current state of the user"""
# format existing states
existing_states = "\n".join(existing_states)
# format prompt
state_commentary = ChatPromptTemplate.from_messages([
cls.system_state_commentary
])
state_commentary = ChatPromptTemplate.from_messages(
[cls.system_state_commentary]
)
# LCEL
chain = state_commentary | cls.lc_gpt_4
# inference
response = await chain.ainvoke({
"chat_history": chat_history,
"user_input": input,
"existing_states": existing_states,
})
response = await chain.ainvoke(
{
"chat_history": chat_history,
"user_input": input,
"existing_states": existing_states,
}
)
# return output
return response.content

@classmethod
async def generate_state_label(cls, existing_states: List[str], state_commentary: str) -> str:
async def generate_state_label(
cls, existing_states: List[str], state_commentary: str
) -> str:
"""Generate a state label from a commetary on the user's state"""
# format existing states
existing_states = "\n".join(existing_states)
# format prompt
state_labeling = ChatPromptTemplate.from_messages([
cls.system_state_labeling,
])
state_labeling = ChatPromptTemplate.from_messages(
[
cls.system_state_labeling,
]
)
# LCEL
chain = state_labeling | cls.lc_gpt_4
# inference
response = await chain.ainvoke({
"state_commentary": state_commentary,
"existing_states": existing_states,
})
response = await chain.ainvoke(
{
"state_commentary": state_commentary,
"existing_states": existing_states,
}
)

# strip anything that's not letters
clean_response = ''.join(c for c in response.content if c.isalpha())
clean_response = "".join(c for c in response.content if c.isalpha())
# return output
return clean_response

@classmethod
async def check_state_exists(cls, existing_states: List[str], state: str):
"""Check if a user state is new or already is stored"""
Expand All @@ -96,32 +119,36 @@ async def check_state_exists(cls, existing_states: List[str], state: str):
existing_states = "\n".join(existing_states)

# format prompt
state_check = ChatPromptTemplate.from_messages([
cls.system_state_check
])
state_check = ChatPromptTemplate.from_messages([cls.system_state_check])
# LCEL
chain = state_check | cls.lc_gpt_turbo
# inference
response = await chain.ainvoke({
"existing_states": existing_states,
"state": state,
})
response = await chain.ainvoke(
{
"existing_states": existing_states,
"state": state,
}
)
# return output
return response.content

@classmethod
async def generate_state(cls, existing_states: List[str], chat_history: List[Message], input: str):
""""Determine the user's state from the current conversation state"""
async def generate_state(
cls, existing_states: List[str], chat_history: List[Message], input: str
):
""" "Determine the user's state from the current conversation state"""

# Generate label
state_commentary = await cls.generate_state_commentary(existing_states, chat_history, input)
state_commentary = await cls.generate_state_commentary(
existing_states, chat_history, input
)
state_label = await cls.generate_state_label(existing_states, state_commentary)

# Determine if state is new
# if True, it doesn't exist, state is new
# if False, it does exist, state is not new, existing_state was returned
existing_state = await cls.check_state_exists(existing_states, state_label)
is_state_new = existing_state == "None"
is_state_new = existing_state == "None"

# return existing state if we found one
if is_state_new:
Expand Down
3 changes: 2 additions & 1 deletion example/discord/honcho-fact-memory/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from uuid import uuid1
import discord
from honcho import Honcho
from chain import langchain_message_converter, LMChain
from honcho.ext.langchain import langchain_message_converter
from chain import LMChain


intents = discord.Intents.default()
Expand Down
Loading

0 comments on commit 880935d

Please sign in to comment.