一、简介
我们之前已经掌握了很多关于和LLM交互的操作了,现在我们需要在这些操作上面构建自己的机器人,进而在客户端操作服务。
我们需要使用的工具是streamlit
你就把他理解为python的uniapp吧。
二、开发环境准备
我们这次是真实的操作代码了,所以就不用jupyter这种交互式的开发了。我们用pycharm这种ide来开发。
1、创建项目,选择虚拟环境
我们创建一个project,继续选择之前的虚拟环境。
操作方式其实我在使用text2vec向量化文本一文中谈到过,这里再复述一遍。
我们之前在vscode中写的时候创建了一个虚拟环境venv3110。我们用pycharm打开项目之后点击seeting->project->project Interpreter
2、安装streamlit依赖
我们需要安装一下对应的依赖。你可以在终端进入你的虚拟环境
source 你的虚拟环境路径/bin/activate
在虚拟环境下执行
pip3 install streamlit
或者是
pip install streamlit
取决于你是不是用的python3
pip list 检查你的安装包
也可以在ide中安装。
然后安装好的就可以在列表中看到了。
此时我们的项目结构就很简单,两个文件。一个python文件,一个.env文件存储环境变量。
至此我们完成了环境的准备。
三、使用streamlit实现交互
我们依然引入之前的那些依赖包,只不过这次要多一个streamlit,并且记得启动es和ollama。
chatBot.py
from dotenv import load_dotenv
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ElasticsearchChatMessageHistory
from streamlit import streamlit as st
# load env file
load = load_dotenv("./.env")
# init llm
llm = ChatOllama(
base_url = "http://127.0.0.1:11434",
model = "deepseek-r1:8b",
temperature = 0.5,
num_predict = 10000,
max_tokens = 250
)
# streamlit的一个标题
st.title("哥哥你好,这里是橘子GPT,我是小橘?")
st.write("哥哥,请把您的问题输入,小橘会认真回答的哦。")
# 无密码的elasticsearch配置
es_url = "http://localhost:9200"
# 存储的索引,我们不用预先创建索引,因为说实话我也不知道字段,langchain会创建,并且自动映射字段
index_name = "chat_history"
# 构建一个session id,暂时写死
session_id = "levi1122322"
template = ChatPromptTemplate.from_messages([
('human',"{prompt}"),
('placeholder',"{history}")
])
chain = template | llm | StrOutputParser()
# 构建ElasticsearchChatMessageHistory
def get_session_history(session_id: str) :
return ElasticsearchChatMessageHistory(
index=index_name,
session_id=session_id,
es_url=es_url,
ensure_ascii=False
)
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="prompt",
history_messages_key="history",
)
# 构建st的结构,添加一个数组进去
st.session_state.chat_history = []
# 获取st在页面的输入,也就是用户输入的问题
user_prompt = st.chat_input("我是你的妹妹小橘,请输入你的问题吧哥哥")
# 如果拿到了输入
if user_prompt :
# 根据输入去调用大模型,并且指定session_id
response = chain_with_history.invoke(
{"prompt": user_prompt},
config={"configurable": {"session_id": session_id}})
# 在数组中挂一些信息,这个目前还没用
st.session_state.chat_history.append({'role':'user','content':user_prompt})
# 最后执行之后返回结果,如果是用户输入的显示用户的问题
with st.chat_message('user'):
st.markdown(user_prompt)
# 在数组中挂一些信息,这个目前还没用
st.session_state.chat_history.append({'role':'assistant','content':response})
# 最后执行之后返回结果,如果是ai结果的显示ai的答案
with st.chat_message('assistant'):
st.markdown(response)
然后使用streamlit启动这个类。
streamlit run 这个py文件的路径地址
比如我是在这个目录下,我直接就streamlit run ./chatbot.py
然后就跳转到浏览器了。
最后的结果如下。
但是这个还是有问题,因为当前你要问到第二个问题,他会覆盖前面的问题和回答,你的页面永远只有一个问题。不会是那种类似于聊天的一问一答那样的。
所以我们需要修改代码把历史保存的记录也显示出来。
from dotenv import load_dotenv
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ElasticsearchChatMessageHistory
from streamlit import streamlit as st
# load env file
load = load_dotenv("./.env")
# init llm
llm = ChatOllama(
base_url = "http://127.0.0.1:11434",
model = "deepseek-r1:8b",
temperature = 0.5,
num_predict = 10000,
max_tokens = 250
)
st.title("你好,这里是橘子GPT,我是小橘")
st.write("请把您的问题输入,小橘会认真回答的哦。")
# 无密码的elasticsearch配置
es_url = "http://localhost:9200"
# 存储的索引,我们不用预先创建索引,因为说实话我也不知道字段,langchain会创建,并且自动映射字段
index_name = "chat_history"
session_id = "levi23422"
template = ChatPromptTemplate.from_messages([
('human',"{prompt}"),
('placeholder',"{history}")
])
chain = template | llm | StrOutputParser()
# 构建ElasticsearchChatMessageHistory
def get_session_history(session_id: str) :
return ElasticsearchChatMessageHistory(
index=index_name,
session_id=session_id,
es_url=es_url,
ensure_ascii=False
)
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="prompt",
history_messages_key="history",
)
user_prompt = st.chat_input("我是小橘,请输入你的问题吧")
# 如果没有就创建,不要每次都建立新的
if 'chat_history' not in st.session_state:
st.session_state.chat_history = []
# 遍历里面的内容,取出来存进去的每一组role 和 content
for message in st.session_state.chat_history :
# 通过取出来的信息,构建st.chat_message,不同的角色会有不同的ui样式,这里就是做这个的
with st.chat_message(message['role']):
# 把内容展示出来
st.markdown(message['content'])
if user_prompt :
response = chain_with_history.invoke(
{"prompt": user_prompt},
config={"configurable": {"session_id": session_id}})
# 保存历史,上面用来遍历显示,避免后面覆盖前面的显示
st.session_state.chat_history.append({'role':'user','content':user_prompt})
with st.chat_message('user'):
st.markdown(user_prompt)
# 保存历史,上面用来遍历显示,避免后面覆盖前面的显示
st.session_state.chat_history.append({'role':'assistant','content':response})
with st.chat_message('assistant'):
st.markdown(response)
我们遍历显示之后就好了。
你会发现中间那个没回复,现在还不到优化他的时候。目前我们基本完成了单session下面的功能。后面我们继续完善,争取对标各大商用gpt。
至此我们还是遗留了一些问题,session_id是写死的,没有从前端传入。
无法开启新的session,只能改代码重启服务。