使用 `llama_index` 构建智能问答系统:多种文档切片方法的评估
在现代自然语言处理(NLP)应用中,构建一个高效的问答系统是一个常见的需求。llama_index
是一个强大的工具,可以帮助我们快速构建基于文档的问答系统。本文将介绍如何优化和解析一个基于 llama_index
的问答系统代码,并逐步解析其核心功能。
代码优化与解析
1. 代码结构优化
我们将代码拆分为多个函数,使得代码结构更清晰,便于维护和扩展。以下是优化后的代码结构:
update_prompt_template
:用于动态更新查询引擎的提示模板。ask_question
:向查询引擎提问并输出结果。load_documents
:加载指定目录下的文档。build_index_and_query_engine
:构建索引并创建查询引擎。main
:主函数,负责程序的整体逻辑。
这种模块化的设计使得代码更易于理解和扩展。
2. 日志管理
为了避免不必要的警告信息干扰,我们使用 logging.basicConfig(level=logging.ERROR)
来设置日志级别为 ERROR
。这样可以确保只有重要的错误信息被输出。
python
import logging
logging.basicConfig(level=logging.ERROR)
3. 环境变量管理
我们使用 dotenv
库加载 .env
文件中的环境变量,确保敏感信息(如 API Key)不会硬编码在代码中。
python
from dotenv import find_dotenv, load_dotenv
load_dotenv(find_dotenv())
4. 模型初始化
我们初始化 OpenAI 的 LLM 和 Embedding 模型,确保模型配置一致且易于修改。
python
llm_client = OpenAI(
model="gpt-4",
api_base=os.environ["OPENAI_BASE_URL"],
api_key=os.environ["OPENAI_API_KEY"],
is_chat_model=True,
seed=42,
)
embed_client = OpenAIEmbedding(
model="text-embedding-3-large",
api_base=os.environ["OPENAI_EMBED_BASE_URL"],
api_key=os.environ["OPENAI_API_KEY"],
)
5. 提示模板更新
update_prompt_template
函数用于动态更新查询引擎的提示模板,确保问答系统能够根据需求调整回答风格。
python
def update_prompt_template(query_engine, qa_prompt_tmpl_str=None):
if qa_prompt_tmpl_str is None:
qa_prompt_tmpl_str = (
"你叫公司小蜜,是公司的答疑机器人。你需要仔细阅读参考信息,然后回答大家提出的问题。"
"注意事项:\n"
"1. 根据上下文信息而非先验知识来回答问题。\n"
"2. 如果是工具咨询类问题,请务必给出下载地址链接。\n"
"3. 如果员工部门查询问题,请务必注意有同名员工的情况,可能有2个、3个甚至更多同名的人\n"
"以下是参考信息。"
"---------------------\n"
"{context_str}\n"
"---------------------\n"
"问题:{query_str}\n。"
"回答:"
)
qa_prompt_tmpl = PromptTemplate(qa_prompt_tmpl_str)
query_engine.update_prompts(
{"response_synthesizer:text_qa_template": qa_prompt_tmpl}
)
return query_engine
6. 问答函数优化
ask_question
函数负责处理用户的问题,输出问题和回答,并展示参考文档。通过检查 response
对象是否有 print_response_stream
方法,确保兼容不同的响应类型。
python
def ask_question(question, query_engine):
update_prompt_template(query_engine)
print('=' * 50)
print(f'🤔 问题:{question}')
print('=' * 50 + '\n')
response = query_engine.query(question)
print('🤖 回答:')
if hasattr(response, 'print_response_stream') and callable(response.print_response_stream):
response.print_response_stream()
else:
print(str(response))
print('\n' + '-' * 50)
print('📚 参考文档:\n')
for i, source_node in enumerate(response.source_nodes, start=1):
print(f'文档 {i}:')
print(source_node)
print()
print('-' * 50)
return response
7. 索引构建与查询引擎
build_index_and_query_engine
函数负责构建索引并创建查询引擎。根据不同的节点解析器(如 TokenTextSplitter
、SentenceSplitter
等),生成不同的查询引擎。
python
def build_index_and_query_engine(documents, embed_model, llm, node_parser, postprocessors=None):
print(f"\n{'=' * 50}")
print(f"🔍 正在使用 {node_parser.__class__.__name__} 方法进行测试...")
print(f"{'=' * 50}\n")
print("📑 正在处理文档...")
nodes = node_parser.get_nodes_from_documents(documents)
index = VectorStoreIndex(nodes, embed_model=embed_model)
query_engine = index.as_query_engine(
similarity_top_k=5,
streaming=True,
llm=llm,
node_postprocessors=postprocessors if postprocessors else []
)
return query_engine
8. 节点解析器测试
我们使用不同的节点解析器(如 TokenTextSplitter
、SentenceSplitter
等)对文档进行处理,并测试其效果。对于 SentenceWindowNodeParser
,还需要使用 MetadataReplacementPostProcessor
进行后处理。
python
node_parsers = [
TokenTextSplitter(chunk_size=1024, chunk_overlap=20),
SentenceSplitter(chunk_size=512, chunk_overlap=50),
SentenceWindowNodeParser.from_defaults(
window_size=3,
window_metadata_key="window",
original_text_metadata_key="original_text"
),
SemanticSplitterNodeParser(
buffer_size=1,
breakpoint_percentile_threshold=95,
embed_model=embed_client
),
MarkdownNodeParser()
]
for parser in node_parsers:
if isinstance(parser, SentenceWindowNodeParser):
postprocessors = [MetadataReplacementPostProcessor(target_metadata_key="window")]
else:
postprocessors = None
query_engine = build_index_and_query_engine(documents, embed_client, llm_client, parser, postprocessors)
ask_question(question, query_engine)
总结
通过优化代码结构、模块化处理、日志管理和环境变量管理,代码的可读性和可维护性得到了显著提升。同时,通过不同的节点解析器对文档进行处理,可以更好地理解不同解析器的效果和适用场景。希望这篇博客对你理解和使用 llama_index
库有所帮助!