Llama Index
有很多文档完备的用例(语义搜索、摘要等)。然而,这并不意味着我们不能将Llama Index应用到非常具体的用例中!
在本教程中,我们将介绍使用Llama Index
从文本中提取术语和定义的设计过程,同时允许用户稍后查询这些术语。使用Streamlit
,我们可以提供一种简单的方法来构建用于运行和测试所有这些的前端,并快速迭代我们的设计。
本教程假设您已经安装了Python3.9+
和以下软件包:
- llama-index-core
- llama-index-llms-dashscope
- llama-index-embeddings-dashscope
- streamlit
在基础级别,我们的目标是从文档中获取文本,提取术语和定义,然后为用户提供查询术语和定义知识库的方法。本教程将介绍Llama Index
和Streamlit
的功能,并希望为出现的常见问题提供一些有趣的解决方案。
上传文本
第一步是为用户提供一种手动输入文本的方法。让我们使用Streamlit编写一些代码来提供这个接口!使用以下代码并使用streamlit run app.py启动应用程序。
import streamlit as stst.title("🦙 骆驼索引术语提取器 🦙")document_text = st.text_area("输入原始文本")
if st.button("提取术语和定义") and document_text:with st.spinner("提取中..."):extracted_terms = document_text # 这是一个临时的,下面是提取术语实现st.write(extracted_terms)
超级简单,对吧!但你会注意到这个应用程序还没有做任何有用的事情。要使用llama_index
,我们还需要设置DashScope LLM
。LLM
有很多可能的设置,所以我们可以让用户自己选择最好的。我们还应该让用户设置提取术语的提示符(这也将帮助我们调试最有效的内容)。
LLM 设置
下一步将引入一些选项卡到我们的应用程序中,将其分成不同的窗格,提供不同的功能。让我们为LLM
设置和上传文本创建一个选项卡:
import streamlit as stDEFAULT_TERM_STR = ("列出在上下文中定义的术语和定义的列表, ""每一行上有一对。 ""如果一个术语缺少它的定义,用你最好的判断。""按如下方式书写每一行:\n术语: <term> 定义: <definition>"
)st.title("🦙 骆驼索引术语提取器 🦙")setup_tab, upload_tab = st.tabs(["设置", "上传/提取术语"])with setup_tab:st.subheader("LLM 设置")api_key = st.text_input("在这里输入OpenAI API密钥", type="password")llm_name = st.selectbox("选择LLM模型?", ["qwen-turbo", "qwen-plus"])model_temperature = st.slider("LLM Temperature", min_value=0.0, max_value=1.0, step=0.1)term_extract_str = st.text_area("提取术语和定义的查询。",value=DEFAULT_TERM_STR,)with upload_tab:st.subheader("提取和查询定义")document_text = st.text_area("输入原始文本")if st.button("提取术语和定义") and document_text:with st.spinner("提取中..."):extracted_terms = document_text #默认提示符 临时定义,后面实现st.write(extracted_terms)
现在我们的应用程序有两个标签,这对组织很有帮助。您还会注意到,我添加了一个默认提示符来提取术语——您可以在稍后尝试提取某些术语时更改此提示符,这只是我在进行了一些实验后得出的提示符。
说到提取术语,是时候添加一些函数来完成这项工作了!
提取和存储术语
现在我们已经能够定义LLM
设置和输入文本,我们可以尝试使用Llama Index
从文本中提取专业术语!
我们可以添加以下函数来初始化LLM
,并使用它从输入文本中提取术语。
import osimport streamlit as st
from llama_index.core import Document, SummaryIndex
from llama_index.llms.dashscope import DashScopeDEFAULT_TERM_STR = ("列出在上下文中定义的术语和定义的列表, ""每一行上有一对。 ""如果一个术语缺少它的定义,用你最好的判断。""按如下方式书写每一行:\nTerm: <term> Definition: <definition>"
)def get_llm(llm_name, model_temperature, api_key, max_tokens=256):os.environ["DASHSCOPE_API_KEY"] = api_keyreturn DashScope(emperature=model_temperature,model_name=llm_name, api_key=api_key, max_tokens=max_tokens)def extract_terms(documents, term_extract_str, llm_name, model_temperature, api_key
):llm = get_llm(llm_name, model_temperature, api_key, max_tokens=1024)temp_index = SummaryIndex.from_documents(documents,)query_engine = temp_index.as_query_engine(response_mode="tree_summarize", llm=llm)terms_definitions = str(query_engine.query(term_extract_str))print(terms_definitions)terms_definitions = [xfor x in terms_definitions.split("\n")if x and "Term:" in x and "Definition:" in x]# 将文本解析为字典terms_to_definition = {x.split("Definition:")[0].split("Term:")[-1].strip(): x.split("Definition:")[-1].strip()for x in terms_definitions}return terms_to_definitionst.title("🦙 骆驼索引术语提取器 🦙")setup_tab, upload_tab = st.tabs(["设置", "上传/提取术语"])with setup_tab:st.subheader("LLM 设置")api_key = st.text_input("在这里输入OpenAI API密钥", type="password")llm_name = st.selectbox("选择LLM模型?", ["qwen-turbo", "qwen-plus"])model_temperature = st.slider("LLM Temperature", min_value=0.0, max_value=1.0, step=0.1)term_extract_str = st.text_area("提取术语和定义的查询。",value=DEFAULT_TERM_STR,)with upload_tab:st.subheader("提取和查询定义")document_text = st.text_area("输入原始文本")if st.button("提取术语和定义") and document_text:with st.spinner("提取中..."):extracted_terms = extract_terms([Document(text=document_text)],term_extract_str,llm_name,model_temperature,api_key,)st.write(extracted_terms)
代码发生了很多变动,让我们花点时间来回顾一下代码发生的改变。
get_llm()
基于setup
选项卡中的用户配置实例化LLM
。基于模型名称,我们需要使用适当的类(DashScope
)。
extract_terms()
是所有好东西发生的地方。首先,我们使用max_tokens=1024
调用get_llm()
,因为我们不想在提取术语和定义时过多地限制模型(如果没有设置,默认值是256)。然后,我们定义Settings对象,将num_output
与max_tokens
值对齐,并将块大小设置为不大于输出。当文档被Llama Index
索引时,如果文档很大,它们会被分解成块(也称为节点),chunk_size
设置这些块的大小。
接下来,我们创建一个临时摘要索引并传入我们的llm
。摘要索引将读取索引中的每一条文本,这对于提取术语来说是完美的。最后,使用response_mode="tree_summarize "
使用预定义的查询文本提取术语。此响应模式将自底向上生成一个摘要树,其中每个父节点总结其子节点。最后,返回树的顶部,它将包含我们提取的所有术语和定义。
最后,我们做一些次要的后期处理。我们假设模型遵循了说明,并在每行上放置了一个术语/定义对。如果一行缺少Term
:或Definition
:标签,我们跳过它。然后,我们将其转换为便于存储的字典!
保存提取的术语
现在我们可以提取术语了,我们需要将它们放在某个地方,以便以后查询它们。VectorStoreIndex
现在应该是一个完美的选择!但除此之外,我们的应用程序还应该跟踪哪些项被插入到索引中,以便我们稍后可以检查它们。使用st.session_state
,我们可以将当前的术语列表存储在会话字典中,每个用户都是唯一的!
首先,让我们添加一个特性来初始化全局向量索引,并添加另一个函数来插入提取的术语。
import osimport streamlit as st
from llama_index.core import Document, SummaryIndex, Settings, VectorStoreIndex
from llama_index.embeddings.dashscope import DashScopeEmbedding, DashScopeTextEmbeddingModels, \DashScopeTextEmbeddingType
from llama_index.llms.dashscope import DashScopeDEFAULT_TERM_STR = ("列出在上下文中定义的术语和定义的列表, ""每一行上有一对。 ""如果一个术语缺少它的定义,用你最好的判断。""按如下方式书写每一行:\nTerm: <term> Definition: <definition>"
)if "all_terms" not in st.session_state:st.session_state["all_terms"] = {}def insert_terms(terms_to_definition):for term, definition in terms_to_definition.items():doc = Document(text=f"Term: {term}\nDefinition: {definition}")st.session_state["llama_index"].insert(doc)@st.cache_resource
def initialize_index(api_key):Settings.embed_model = DashScopeEmbedding(api_key=api_key,model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V2,text_type=DashScopeTextEmbeddingType.TEXT_TYPE_DOCUMENT,)return VectorStoreIndex([])def get_llm(llm_name, model_temperature, api_key, max_tokens=256):return DashScope(emperature=model_temperature, model_name=llm_name, api_key=api_key, max_tokens=max_tokens)def extract_terms(documents, term_extract_str, llm_name, model_temperature, api_key
):llm = get_llm(llm_name, model_temperature, api_key, max_tokens=1024)temp_index = SummaryIndex.from_documents(documents,)query_engine = temp_index.as_query_engine(response_mode="tree_summarize", llm=llm)terms_definitions = str(query_engine.query(term_extract_str))print(terms_definitions)terms_definitions = [xfor x in terms_definitions.split("\n")if x and "Term:" in x and "Definition:" in x]# 将文本解析为字典terms_to_definition = {x.split("Definition:")[0].split("Term:")[-1].strip(): x.split("Definition:")[-1].strip()for x in terms_definitions}return terms_to_definitionst.title("🦙 骆驼索引术语提取器 🦙")setup_tab, upload_tab = st.tabs(["设置", "上传/提取术语"])with setup_tab:st.subheader("LLM 设置")api_key = st.text_input("在这里输入OpenAI API密钥", type="password")llm_name = st.selectbox("选择LLM模型?", ["qwen-turbo", "qwen-plus"])model_temperature = st.slider("LLM Temperature", min_value=0.0, max_value=1.0, step=0.1)term_extract_str = st.text_area("提取术语和定义的查询。",value=DEFAULT_TERM_STR,)with upload_tab:st.subheader("提取和查询定义")if st.button("初始化索引和重置术语"):st.session_state["llama_index"] = initialize_index(api_key)st.session_state["all_terms"] = {}if "llama_index" in st.session_state:st.markdown("请在下面输入要提取术语的文本。")document_text = st.text_area("输入原始文本")if st.button("提取术语和定义") and document_text:st.session_state["terms"] = {}terms_docs = {}with st.spinner("提取中..."):terms_docs.update(extract_terms([Document(text=document_text)],term_extract_str,llm_name,model_temperature,api_key,))st.session_state["terms"].update(terms_docs)if "terms" in st.session_state and st.session_state["terms"]:st.markdown("提取术语")st.json(st.session_state["terms"])if st.button("是否插入术语?"):with st.spinner("正在插入术语"):insert_terms(st.session_state["terms"])st.session_state["all_terms"].update(st.session_state["terms"])st.session_state["terms"] = {}st.rerun()
现在你真的开始利用流光的力量了!让我们从upload
选项卡下的代码开始。我们添加了一个按钮来初始化向量索引,并将其存储在全局streamlit
状态字典中,同时重置当前提取的术语。然后,在从输入文本中提取术语之后,我们再次将提取的术语存储在全局状态中,并让用户有机会在插入之前查看它们。如果按下插入按钮,则调用插入术语函数,更新对插入术语的全局跟踪,并从会话状态中删除最近提取的术语。
查询提取的术语/定义
提取并保存了术语和定义后,我们如何使用它们?用户如何记住之前保存的内容?我们可以简单地在应用程序中添加一些选项卡来处理这些功能。
import streamlit as st
from llama_index.core import Document, SummaryIndex, Settings, VectorStoreIndex
from llama_index.embeddings.dashscope import DashScopeEmbedding, DashScopeTextEmbeddingModels, \DashScopeTextEmbeddingType
from llama_index.llms.dashscope import DashScope, DashScopeGenerationModelsDEFAULT_TERM_STR = ("列出在上下文中定义的术语和定义的列表, ""每一行上有一对。 ""如果一个术语缺少它的定义,用你最好的判断。""按如下方式书写每一行:\nTerm: <term> Definition: <definition>"
)if "all_terms" not in st.session_state:st.session_state["all_terms"] = {}def insert_terms(terms_to_definition):for term, definition in terms_to_definition.items():doc = Document(text=f"Term: {term}\nDefinition: {definition}")st.session_state["llama_index"].insert(doc)def initialize_index(api_key: str):print('api_key', api_key)Settings.llm = DashScope(model_name=DashScopeGenerationModels.QWEN_TURBO, api_key=api_key, max_tokens=512)Settings.embed_model = DashScopeEmbedding(api_key=api_key,model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V2,text_type=DashScopeTextEmbeddingType.TEXT_TYPE_DOCUMENT,)return VectorStoreIndex([])def get_llm(llm_name, model_temperature, api_key, max_tokens=256):return DashScope(emperature=model_temperature, model_name=llm_name, api_key=api_key, max_tokens=max_tokens)def extract_terms(documents, term_extract_str, llm_name, model_temperature, api_key
):llm = get_llm(llm_name, model_temperature, api_key, max_tokens=1024)temp_index = SummaryIndex.from_documents(documents,)query_engine = temp_index.as_query_engine(response_mode="tree_summarize", llm=llm)terms_definitions = str(query_engine.query(term_extract_str))print(terms_definitions)terms_definitions = [xfor x in terms_definitions.split("\n")if x and "Term:" in x and "Definition:" in x]# 将文本解析为字典terms_to_definition = {x.split("Definition:")[0].split("Term:")[-1].strip(): x.split("Definition:")[-1].strip()for x in terms_definitions}return terms_to_definitionst.title("🦙 骆驼索引术语提取器 🦙")setup_tab, terms_tab, upload_tab, query_tab = st.tabs(["设置", "所有术语", "上传/提取术语", "查询术语"]
)with setup_tab:st.subheader("LLM 设置")api_key = st.text_input("在这里输入OpenAI API密钥", type="password")llm_name = st.selectbox("选择LLM模型?", ["qwen-turbo", "qwen-plus"])model_temperature = st.slider("LLM Temperature", min_value=0.0, max_value=1.0, step=0.1)term_extract_str = st.text_area("提取术语和定义的查询。",value=DEFAULT_TERM_STR,)with upload_tab:st.subheader("提取和查询定义")if st.button("初始化索引和重置术语"):st.session_state["llama_index"] = initialize_index(api_key)st.session_state["all_terms"] = {}if "llama_index" in st.session_state:st.markdown("请在下面输入要提取术语的文本。")document_text = st.text_area("输入原始文本")if st.button("提取术语和定义") and document_text:st.session_state["terms"] = {}terms_docs = {}with st.spinner("提取中..."):terms_docs.update(extract_terms([Document(text=document_text)],term_extract_str,llm_name,model_temperature,api_key,))st.session_state["terms"].update(terms_docs)if "terms" in st.session_state and st.session_state["terms"]:st.markdown("提取术语")st.json(st.session_state["terms"])if st.button("是否插入术语?"):with st.spinner("正在插入术语"):insert_terms(st.session_state["terms"])st.session_state["all_terms"].update(st.session_state["terms"])st.session_state["terms"] = {}st.rerun()with terms_tab:with terms_tab:st.subheader("当前提取的术语和定义")st.json(st.session_state["all_terms"])with query_tab:st.subheader("查询术语/定义!")st.markdown(("LLM将尝试回答您的查询,并使用您插入的术语/定义来扩展它的答案。 ""如果一个项不在索引中,它将使用它的内部知识来回答。"))if st.button("初始化索引和重置术语", key="init_index_2"):st.session_state["llama_index"] = initialize_index(api_key)st.session_state["all_terms"] = {}if "llama_index" in st.session_state:query_text = st.text_input("询问一个术语或定义:")if query_text:query_text = (query_text+ "\n如果你找不到答案,那就尽你所能去回答这个问题。")with st.spinner("生成回答..."):response = (st.session_state["llama_index"].as_query_engine(similarity_top_k=5,response_mode="compact",).query(query_text))st.markdown(str(response))
虽然这是最基本的,但有一些重要的事情需要注意:
初始化按钮的文本与其他按钮相同。Streamlit会抱怨这个,所以我们提供一个唯一的键。
查询中添加了一些额外的文本!这是为了尝试弥补索引没有答案的情况。
在我们的索引查询中,我们指定了两个选项:
Similarity_top_k =5表示索引将获取与查询最匹配的前5个术语/定义。
response_mode="compact"意味着将在每次LLM调用中使用5个匹配术语/定义中的尽可能多的文本。如果不这样做,索引将至少对LLM进行5次调用,这可能会减慢用户的速度。
测试运行
实际上,我希望你们在我们上课的时候做过测试。现在,让我们来做一个完整的测试。
- 刷新应用程序
- 输入您的LLM设置
- 转到查询选项卡
- 问以下问题:什么是·bunnyhug·?
- 应用程序应该给出一些无意义的响应。如果你不知道,·bunnyhug·是帽衫的另一种说法,是加拿大草原地区的人使用的!
- 让我们将这个定义添加到应用程序中。打开上传选项卡并输入以下文本
·bunnyhug·是用来描述连帽衫的常用术语。这个词被来自加拿大大草原的人们使用。 - 单击提取按钮。几分钟后,应用程序应该显示正确提取的术语/定义。单击插入术语按钮保存它!
- 如果我们打开术语选项卡,应该会显示我们刚刚提取的术语和定义
- 回到查询选项卡,尝试询问兔抱是什么。现在,答案应该是正确的!