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

Agentic streamlit chatbot #1656

Merged
merged 5 commits into from
Feb 13, 2025
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
5 changes: 5 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ it is useful when you want to quickly do ranking experiments without rewriting a
* [MLCon](https://mlconference.ai/machine-learning-advanced-development/adaptive-incontext-learning/)
* [data science connect COLLIDE](https://datasciconnect.com/events/collide/agenda/)

### Agentic Chatbot using Vespa

[![logo](/assets/vespa-logomark-tiny.png) agentic-streamlit-chatbot](agentic-streamlit-chatbot) This sample Streamlit application demonstrates how to use [LangGraph](https://www.langchain.com/langgraph) agentic framework to develop an E-commerce chatbot using Vespa as a retrieval tool.


For any questions, please register at the Vespa Slack and discuss in the general channel.

----
Expand Down
66 changes: 66 additions & 0 deletions examples/agentic-streamlit-chatbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Prerequisites


- You need a python virtual environment. For this demo, `Python 3.13.1` was used, but any Python environment 3.11+ should work.
- You will need [Vespa CLI](https://docs.vespa.ai/en/vespa-cli.html) that you can deploy on MacOS with `brew install vespa-cli`
- Libraries dependencies can be installed with: `pip install -R requirements.txt`
- Sign-up with [Tavily](https://tavily.com/) and Get an API key.
- Spin-up a Vespa Cloud [Trial](https://vespa.ai/free-trial) account:
- Login to the account you just created and create a tenant at [console.vespa-cloud.com](https://console.vespa-cloud.com/).
- Save the tenant name.
- A Valid OpenAI API key. Note that you have the option to use any other LLM, which may support Langgraph tools binding.
- Git clone the repo `https://github.com/vespa-engine/system-test.git`
- The ecommerce_hybrid_search app will be deployed. For more information about the app, please review the [README.md](https://github.com/vespa-engine/system-test/blob/master/tests/performance/ecommerce_hybrid_search/dataprep/README.md). You do not have to follow the data prep steps there. Follow the instructions below instead.
- Uncompress the data file: `zstd -d data/vespa_update-96k.json.zst`



# Deploy the Ecommerce Vespa Application


- In the system-test repo you just cloned, navigate to `tests/performance/ecommerce_hybrid_search/app`
- Choose a name for your app. For example `ecommercebot`
- Follow instructions in the [**Getting Started**](https://cloud.vespa.ai/en/getting-started) document. Please note the following as you go through the documentation:
- You will need your tenant name you created previously.
- When adding the public certificates with `vespa auth cert`, it will give you the absolute path of the certificate and the private key. Please note them down.
- To feed the application, return to the original directory and run:
```
vespa feed data/vespa_update-96k.json
```
- You can test the following query from the Vespa CLI:
```
vespa query "select id, category, title, price from sources * where default contains 'screwdriver'"
```
- You will need the URL of your Vespa application. Run the following command:
```
vespa status
```
This should return you an output like:
```
Container container at https://xxxxx.yyyyy.z.vespa-app.cloud/ is ready
```
Note down the URL.

# Configure and Launch your Streamlit Application

A template for `secrets.toml` file to store streamlit secrets has been provided. Please create a subdirectory `.streamlit` and copy the template there.

Update all the fields with all the information collected previously and save the file as `secrets.toml`

Launch your streamlit application:
```
streamlit run streamlit_vespa_app.py
```
# Testing the Application

You can try a mix of questions like:

`What is the weather in Toronto ?`

Followed by:

`I'm looking for a screwdriver`

And then:

`Which one do you recommend to fix a watch?`
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
6 changes: 6 additions & 0 deletions examples/agentic-streamlit-chatbot/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pyvespa
langchain_openai
langchain_core
langchain_community
langgraph
streamlit
141 changes: 141 additions & 0 deletions examples/agentic-streamlit-chatbot/streamlit_vespa_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

import streamlit as st
import getpass
import os

from vespa.application import Vespa
from vespa.io import VespaResponse, VespaQueryResponse
import json

from langchain_community.tools.tavily_search import TavilySearchResults

from langchain_openai import ChatOpenAI

from langgraph.graph import MessagesState
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage

from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition, ToolNode

from PIL import Image

st.title(":shopping_trolley: Vespa.ai Shopping Assistant")

# Load an image (ensure the file is in the correct path)
icon = Image.open("Vespa-logo-dark-RGB.png")

# Display the image in the sidebar
st.sidebar.image(icon, width=500)

# Fetch secrets
OPENAI_API_KEY = st.secrets["api_keys"]["llm"]
TAVILY_API_KEY = st.secrets["api_keys"]["tavily"]
VESPA_URL = st.secrets["vespa"]["url"]
PUBLIC_CERT_PATH = st.secrets["vespa"]["public_cert_path"]
PRIVATE_KEY_PATH = st.secrets["vespa"]["private_key_path"]

#if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
#if not os.environ.get("TAVILY_API_KEY"):
os.environ["TAVILY_API_KEY"] = TAVILY_API_KEY


def VespaRetriever(UserQuery: str) -> str:
"""Retrieves all items for sale matching the user query.

Args:
UserQuery: User Query for items they are looking for.
"""

vespa_app = Vespa(url=VESPA_URL,
cert=PUBLIC_CERT_PATH,
key=PRIVATE_KEY_PATH)

with vespa_app.syncio(connections=1) as session:
query = UserQuery
response: VespaQueryResponse = session.query(
yql="select id, category, title, average_rating, price from sources * where userQuery()",
query=query,
hits=5,
ranking="hybrid",
)
assert response.is_successful()

# Extract only the 'fields' content from each entry
filtered_data = [hit["fields"] for hit in response.hits]

# Convert to a JSON string
json_string = json.dumps(filtered_data, indent=1)

return json_string

TavilySearch = TavilySearchResults(max_results=2)

tools = [TavilySearch, VespaRetriever]

llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools)

# System message
sys_msg = SystemMessage(content="You are a helpful sales assistant willing to answer any user questions about items to sell. You will try your best to provide all the information regarding an item for sale to a customer.")

# Node
def assistant(state: MessagesState):
return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}

# Graph
builder = StateGraph(MessagesState)

# Define nodes: these do the work
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# Define edges: these determine how the control flow moves
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
"assistant",
# If the latest message (result) from assistant is a tool call -> tools_condition routes to tools
# If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END
tools_condition,
)
builder.add_edge("tools", "assistant")

react_graph = builder.compile()

#Initialize the chat messages history
if "messages" not in st.session_state.keys():
st.session_state.messages = [
{"role": "assistant", "content": "Hello, I'm your Vespa Shopping Assistant using an agentic architecture based on LangGraph. How can I assist you today ?"}
]

#Prompt the user input and save
if prompt := st.chat_input():
st.session_state.messages.append({"role": "user", "content": prompt})

#Display the existing chat messages
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.write(message["content"])

#If last message is not from the assistant, we need to generate a new response
if st.session_state.messages[-1]["role"] != "assistant":
question = st.session_state.messages[-1]["content"].replace('?','')

message_list = []
messages = react_graph.invoke({"messages": st.session_state.messages}, stream_mode="values")
print(messages)

for m in messages['messages']:
message_list.append(m)

print(message_list)

response_text = next(
(msg.content for msg in reversed(messages['messages']) if isinstance(msg, AIMessage)),
None
)
with st.chat_message("assistant"):
st.write("Response: ", response_text)

# **Add the assistant response to session state**
st.session_state.messages.append({"role": "assistant", "content": response_text})
9 changes: 9 additions & 0 deletions examples/agentic-streamlit-chatbot/template-secrets.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# .streamlit/secrets.toml

[vespa]
url = "https://<host>.vespa-app.cloud/"
public_cert_path = "PATH/data-plane-public-cert.pem"
private_key_path = "PATH/data-plane-private-key.pem"
[api_keys]
llm = "sk-"
tavily = "tvly-"