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 Langchain Pandas DataFrame Agent #83

Merged
merged 14 commits into from
Oct 23, 2023
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/videos/langchain_chat_pandas_df.mp4
Binary file not shown.
176 changes: 176 additions & 0 deletions docs/examples/langchain/langchain_chat_pandas_df.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"""
Demonstrates how to use the `ChatInterface` and `PanelCallbackHandler` to create a
chatbot to talk to your Pandas DataFrame. This is heavily inspired by the
[LangChain `chat_pandas_df` Reference Example](https://github.com/langchain-ai/streamlit-agent/blob/main/streamlit_agent/chat_pandas_df.py).
"""
from __future__ import annotations

MarcSkovMadsen marked this conversation as resolved.
Show resolved Hide resolved
import pandas as pd
import panel as pn
import param
from langchain.agents import AgentType, create_pandas_dataframe_agent
from langchain.chat_models import ChatOpenAI

from panel_chat_examples import EnvironmentWidgetBase

pn.extension("perspective", design="material")

PENGUINS_URL = (
"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv"
)


class Environment(EnvironmentWidgetBase):
"""Will be asking the user for the API Key if not set as environment variable"""

OPENAI_API_KEY = param.String()


class AgentConfig(param.Parameterized):
"""Configuration used for the Pandas Agent"""

user = "Pandas Agent"
avatar = "🐼"

show_chain_of_thought = param.Boolean(default=False)

def _get_agent_message(self, message: str) -> pn.chat.ChatMessage:
return pn.chat.ChatMessage(message, user=self.user, avatar=self.avatar)


class AppState(param.Parameterized):
data = param.DataFrame()

llm = param.Parameter(constant=True)
pandas_df_agent = param.Parameter(constant=True)

config: AgentConfig = param.ClassSelector(class_=AgentConfig)
environ: Environment = param.ClassSelector(class_=Environment, constant=True)

def __init__(
self, config: AgentConfig | None = None, environ: Environment | None = None
):
if not config:
config = AgentConfig()
if not environ:
environ = Environment()
super().__init__(config=config, environ=environ)

@param.depends("environ.OPENAI_API_KEY", on_init=True, watch=True)
def _reset_llm(self):
with param.edit_constant(self):
if self.environ.OPENAI_API_KEY:
self.llm = ChatOpenAI(
temperature=0,
model="gpt-3.5-turbo-0613",
api_key=self.environ.OPENAI_API_KEY,
streaming=True,
)
else:
self.llm = None

@param.depends("llm", "data", on_init=True, watch=True)
def _reset_pandas_df_agent(self):
with param.edit_constant(self):
if not self.error_message:
self.pandas_df_agent = create_pandas_dataframe_agent(
self.llm,
self.data,
verbose=True,
agent_type=AgentType.OPENAI_FUNCTIONS,
handle_parsing_errors=True,
)
else:
self.pandas_df_agent = None

@property
def error_message(self):
if not self.llm and self.data is None:
return """Please provide your `OPENAI_API_KEY`, **upload a `.csv` file**
and click the **send** button."""
if not self.llm:
return "Please provide your `OPENAI_API_KEY`."
if self.data is None:
return "Please **upload a `.csv` file** and click the **send** button."
return ""

@property
def welcome_message(self):
text = (
f"""I'm your <a href="\
https://python.langchain.com/docs/integrations/toolkits/pandas" target="_blank">\
LangChain Pandas DataFrame Agent</a>.

I execute LLM generated Python code under the hood - this can be bad if the `llm`
generated Python code is harmful. Use cautiously!

{self.error_message}"""
MarcSkovMadsen marked this conversation as resolved.
Show resolved Hide resolved
).strip()
MarcSkovMadsen marked this conversation as resolved.
Show resolved Hide resolved
if self.data is None:
text += f"""

Example: <a href="{PENGUINS_URL}" download>penguins.csv<a>
MarcSkovMadsen marked this conversation as resolved.
Show resolved Hide resolved
"""
MarcSkovMadsen marked this conversation as resolved.
Show resolved Hide resolved
return text

async def callback(self, contents, user, instance):
if isinstance(contents, pd.DataFrame):
self.data = contents
instance.active = 1
message = self.config._get_agent_message(
"""You can ask me anything about the data. For example 'how many
species are there?'"""
MarcSkovMadsen marked this conversation as resolved.
Show resolved Hide resolved
)
# We `send` instead of just `return` due to the bug
# https://github.com/holoviz/panel/issues/5708
instance.send(message, respond=False)
return # message
ahuang11 marked this conversation as resolved.
Show resolved Hide resolved

if self.error_message:
message = self.config._get_agent_message(self.error_message)
instance.send(message, respond=False)
return # message
ahuang11 marked this conversation as resolved.
Show resolved Hide resolved

if self.config.show_chain_of_thought:
langchain_callbacks = [
pn.chat.langchain.PanelCallbackHandler(instance=instance)
]
else:
langchain_callbacks = []
response = await state.pandas_df_agent.arun(
contents, callbacks=langchain_callbacks
)
message = self.config._get_agent_message(response)
instance.send(message, respond=False)
return


state = AppState()

chat_interface = pn.chat.ChatInterface(
widgets=[
pn.widgets.FileInput(name="Upload"),
pn.widgets.TextInput(name="Message", placeholder="Send a message"),
],
renderers=pn.pane.Perspective,
callback=state.callback,
callback_exception="verbose",
show_rerun=False,
show_undo=False,
show_clear=False,
min_height=400,
)
chat_interface.send(
state.welcome_message, user="Pandas Agent", avatar="🐼", respond=False
)

layout = pn.template.MaterialTemplate(
title="🦜 LangChain - Chat with Pandas DataFrame",
main=[chat_interface],
sidebar=["Agent Settings", state.config.param.show_chain_of_thought],
)

if state.environ.variables_not_set:
layout.sidebar.append(state.environ)

layout.servable()
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ dependencies = [
"openai",
"panel @ git+https://github.com/holoviz/panel.git@main",
"pypdf",
"tabulate",
MarcSkovMadsen marked this conversation as resolved.
Show resolved Hide resolved
"tiktoken",
]

Expand Down
Loading