书接上文,今天继续完成自动填写技术调研表的下篇,高级版本的 AI 助理员工的实现。
• 背景需求和设计思路(上篇)
• Anything LLM 使用(上篇)
• 基础版AI员工(LM Studio + Anything LLM + 知识库)(上篇)
• 高级版AI员工(Streamlit 程序)「下篇」
我们现在看下最终的效果:
前提
首先我们要把 LM Studio 以及 AnythingLLM 运行和配置好,具体内容可以参考上一篇文章。
Streamlit
Streamlit 是我们的老朋友了,是现在构建 AI 应用程序 UI 的最佳选择之一。
关于 Streamlit 的介绍和使用可以参考之前的文章:
• todo
安装
如果还没有安装,我们使用 pip 安装一下 Streamlit。
pip install streamlit
标题
首先给应用程序增加,标题我们可以使用 st.title 方法,
import streamlit as st
st.set_page_config(layout="wide") # 设置页面宽度
st.title("RAG Excel")
为了美观,我们使用图片来做标题:
import streamlit as st
st.set_page_config(layout="wide")
h1, h2 = st.columns([3,1])
with h1:
st.image('./assets/shuyi-llm-1.png', width=480)
with h2:
st.image('./assets/shuyi-llm-2.png', width=100)
AnythingLLM API 密钥
接下来为了调用 Anything LLM 来实现 RAG,需要获取 AnythingLLM 接口调用的密钥。
打开 Anything LLM 设置,选择「API密钥」,然后点击「生成」:
复制生成的密钥。
我们可以在程序中把 密钥记录下来,
api_key = '6DDGHVK-C9Q44NT-NGMVN8H-QG3086Y'
但是更好的方式是在程序上让用户可以自己设置密钥,这样密钥变化的时候就不用修改程序,也降低了密钥泄露的风险。
我们使用一个密码框,来记录密钥,提供一个默认值,也可让用户更改:
apikey = st.text_input('API KEY', type='password', value=api_key)
效果如下:
兼容 OpenAPI
我们知道 AnythingLLM 提供了兼容 OpenAI 的接口。
查看文档,可以看到其接口调用示例。
所需要的三个信息,除了 API Key,还有两个没确认:
• API Key
• 模型名称
• URL
BaseURL
如果是本机运行,兼容 OpenAI 接口的 BaseURL 是 : http://localhost:3001/api/v1/openai , 我们再程序中指定一下:
openai_base_url = "http://localhost:3001/api/v1/openai"
获取模型(工作区)
AnythingLLM 的工作区就是 OpenAI 接口要传的模型,有两种方法可以获取。
AnythingLLM 管理 API
AnythingLLM 提供了一整套管理接口,能在 UI 上做的任何事情,也能通过接口完成。
当然也包含列出工作区的接口:
我们通过 requests 模块发起请求来获取工作区列表:
url = 'http://127.0.0.1:3001/api/v1/workspaces'
headers = {
'accept': 'application/json',
'Authorization': 'Bearer ' + apikey
}
# 发送GET请求
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
workspaces = {}
for workspace in data['workspaces']:
workspaces[workspace['slug']] = workspace['name']
工作区列表接口返回了关于工作区很详细的信息:
使用 OpenAI API 获取模型
OpenAI 的 API 有一个 models 接口来返回模型列表,Anything LLM 也支持了这个接口:
Python 程序调用也很简单:
client = OpenAI(
api_key=api_key,
base_url=openai_base_url,
)
client.models.list()
接口返回符合 OpenAI API 的规范:
界面选择模型(工作区)
我们在程序上提供一个用下拉列表选择模型的功能。
model = st.selectbox("Workspaces", workspaces.keys(), format_func=lambda x: workspaces[x])
调整一下UI,把 API 和模型两个控件放到一行:
col1, col2 = st.columns(2)
apikey = col1.text_input('API KEY', type='password', value=api_key)
文件上传
然后我们创建一个文件上传按钮。
uploaded_file = st.file_uploader("上传 Excel 文件", type="xlsx")
文件上传成功显示文件内容,显示的时候预留一部分区域给未来的 LLM 输出实时显示。
划分出「文件内容」和「LLM实时输出」的显示区域。
col_grid, col_reply = st.columns([2, 1])
area_grid = col_grid.empty()
area_reply = col_reply.empty()
当文件上传的时候,显示文件内容:
if uploaded_file is not None:
df = get_df()
area_grid.data_editor(df, use_container_width=True)
看看效果:
RAG 调用
RAG 的调用也很简单,我们先写一个 Chat 函数:
def chat(model_select, prompt):
print(prompt)
chat_completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": prompt,
}
],
model=model_select,
temperature=0.7,
stream=True,
)
for completion in chat_completion:
print(completion.choices[0].delta.content)
yield completion.choices[0].delta.content
然后增加一个 「RAG」 的按钮:
rag = st.button('RAG', use_container_width=True, type='primary')
点击按钮的时候遍历数据,如果有描述那么针对描述做应答问答的生成, 同时在 LLM 应答区域实时输出 LLM & RAG 应答结果。
if rag:
for idx, row in df.iterrows():
condition = row[name_desc]
if condition and type(condition) == str:
print('>>>', condition)
reply = ''
for xx in chat(model, condition):
reply = reply + xx
area_reply.success(reply)
df.loc[idx, name_reply] = reply
processed.append(idx)
area_grid.dataframe(use_container_width=True, data=df)
文件保存
为了避免 LLM 失败等异常,每次成功回答我们都保存一次文件:
df.to_excel(uploaded_file.name.replace('.xlsx', '_reply.xlsx'), index=False)
回答过程中搞得文件可能是这样的:
显示进度
功能实现了,不过由于整个文档的时间很长,我们看不到当前生成到哪条了,生成之后的结果也不能在界面上很方便地看到。
我们做一些优化,显示一下文档的进度:
• 当前正在处理的行用黄色背景表示
• 已经处理成功的行用绿色背景表示
使用 df.style.apply 方法来实现:
def row_bg(row):
index = row.name
if index == idx:
return ['background-color: #ffffb8'] * len(row)
elif index in processed:
return ['background-color: #d9f7be'] * len(row)
else:
return [''] * len(row)
area_grid.dataframe(
df.style.apply(
row_bg, subset=(processed + [idx], slice(None),),
axis=1
),
use_container_width=True,
)
运行的效果如下:
最后看一个完整的视频吧:
--- END ---

