需求是,能根据上下文自动生成各部门的工作总结,并能根据上下文回答具体问题。
先看下实现效果,生成工作总结:
针对具体问题的回答:
实现的基本思路:
- 使用
LlamaIndex - 创建 2 个检索器
- 基于摘要索引(
SummaryIndex)检索器,用于检索工作总结方面的问题 - 基于向量索引 (
VectorStoreIndex) 检索器,用于检索具体工作的具体问题
- 基于摘要索引(
- 通过
RouterQueryEngine实现一个基于智能体的 RAG,由RouterQueryEngine通过 LLM 判断使用上述哪个检索器回答问题。
实现的笔记见 工作任务查询的实现原型笔记
下面介绍实现的步骤和使用中的设想和总结。
加载数据
数据来源于对每日个人工作任务日志的非结构化文档,在原型中简化为:
"开发部韩宇轩,simple-rag 产品,实现新特性:针对文档的摘要索引。通过摘要索引,为用户生成的总结性能有大幅度提升。经测试评估,准确率达到了95%,以前是45%。",
"开发部陈逸飞,simple-rag 产品,修正bug:中文断句不完整。之前断句不完整,造成嵌入相似度准确率不高。目前的做法是更换splitter实现,并且对分块参数做了优化。",
"开发部郑千,simple-rag 产品,修正bug:聊天界面 markdown 格式显示错误。bug的现象是部分字体显示不全,更换实现库,解决了这个问题。",
"产品部林晓琪,simple-rag 产品,提交新特性需求:多数据源的融合检索查询。用户反映以及我们测试,在某些功能下,大文档的检索召回率不高,造成回答效果不好。希望通过增加新的检索方法,并融合现在的检索结果,提高检索召回率。",
"产品部林晓琪,江南皮革厂客服助手项目,和客户开会,沟通需求。确定了客服助手的基本功能,并约定了下一次沟通时间和沟通内容。",
"运维部李泽言,江南皮革厂客服平台,系统扩容,解决了负载过高造成服务卡的问题。服务卡的主要原因是算力不足,原来是2张a100 40G,目前扩到了4张。今天业务高峰时间负载不到60%,问题解决。",
"销售部韩雪薇,天津化工集团后勤服务平台,准备标书商务部分,完成公司资质方面的文案。资质文案主要包括: 相关软件著作权证书、银行资质证明、国安资质证书和验资报告等,已经整理成word文件。",
"法务部张峻宁,完成审核了与深圳做大事公司的商务合同。之前对方提出了多个版权方面的条款,经过沟通,以及有关方面的查询,修改了相关条款,消除了商业风险,和客户达成了共识。目前已经提交给办公室。",
实际数据可能还需要编写自定义的解析器,增加元数据(比如,所属部门、姓名、日志日期和所属项目等)。
数据在后续索引使用前,需要再切分为文本块。这里简化了,直接手动生成文本块(Node):
nodes=[ TextNode(text=item, id_=f"node_{index}") for index, item in enumerate(items)]
创建和使用摘要索引
摘要索引用于创建总结或者摘要形式的问答,比如 郑千完成了哪些工作? 等。
创建摘要索引
summary_index = SummaryIndex(nodes)
摘要索引在使用时,通过 LLM 逐条文本块生成摘要,因此可以最大程度的保留原始语义,并能大幅减少文本长度,实现总结和摘要的作用。
这种索引的特点是,创建过程很快,因为啥也不做,但是检索时很慢,因为要逐条文本块使用 LLM 生成摘要。
选取合适的 LLM
因此这里需要设置 LLM 供后面使用:
Settings.llm=OpenAILike(
model="qwen2",
...
测试了多个 LLM,云端 LLM 比较稳定正确的有:
- gpt-3.5-turbo/gpt-4-turbo
- qwen-turbo 及更高级版本
- glm-3-turbo/glm-4/glm-4-air
- Baichuan3-Turbo (但是Baichuan4有问题)
- ERNIE-4.0-8K(之前的版本都不行,生成内容时报错)
- hunyuan-pro (standard/lite 不行)
本地 LLM 测试的情况:
- glm4:8b, 基本可用,明显好于 qwen2:7b, 但也不是很稳定,有时候生成的内容有遗漏
- qwen2:7b, 不可用,路由时经常解析错
- qwen2:72b, 不可用,路由时经常解析错
- llama3.1:8b,不可用,工作摘要混杂了举例部分文字
- yi:9b,不可用,工作摘要混杂了举例部分文字
- yi-34b-chat, 能运行,有问题,一个任务描述摘要会拆分成好几个条目
- deepseek-v2:16b, 不可用,摘要遗漏内容并混杂示例内容
因为生产环境要求本地模型,为了解决 markdown 格式生成摘要遗漏问题和正确路由问题,做了如下处理:
LLMSingleSelector单独指定glm4模型,确保路由正常- 文本生成使用
qwen2:72b, 为了解决markdown格式摘要偶尔遗漏内容问题:- 提示词改为基于
json格式,未出现遗漏内容情况 - 程序解决
json格式的输出展示
- 提示词改为基于
创建查询引擎
创建:
summary_retriever=summary_index.as_retriever()
response_synthesizer = get_response_synthesizer(
response_mode="tree_summarize",
streaming=True,
)
summary_query_engine = RetrieverQueryEngine(
retriever=summary_retriever,
response_synthesizer=response_synthesizer,
)
生成工作总结:
response = summary_query_engine.query(
"给出涉及的所有部门所有人的任务摘要输出,输出示例如下:"
"""
产品部:
- 张三
- xxx产品,提交新特性需求: xxx
- xxx项目,和客户开会,xxxxx
- ...
- 李四
- xxxx
- ...
- 王五
- ...
开发部:
- 赵六
- xxx产品,修正bug:xxxx
- xxx项目,实现新特性:xxxx
...
- 于七
- xxx产品,修正bug:xxxx
- xxx项目,实现新特性:xxxx
...
...
xxx部:
...
"""
)
response.print_response_stream()
运行:
也可以生成指定人员的工作总结:
response = summary_query_engine.query("林晓琪完成哪些任务,列表说明")
# 林晓琪完成了以下任务:
# - 提交了simple-rag产品的多数据源融合检索查询的新特性需求,旨在提高大文档的检索召回率,以# 改善回答效果。
# - 参与了江南皮革厂客服助手项目的客户需求沟通会议,确认了客服助手的基本功能,并安排了下次会议的时间和内容。
创建和使用向量索引
创建向量索引
向量索引需要使用嵌入模型:
Settings.embed_model = OllamaEmbedding(
model_name="quentinz/bge-large-zh-v1.5",
...
使用相同的文本节点列表创建向量索引(这一步需要耗时不到2秒,因为要将节点文本向量化,用于后面的向量检索):
vector_index = VectorStoreIndex(nodes)
创建向量检索器
向量查询的第一步是检索,默认是 top_k=2, 即从向量索引中选取相似度最大的2个节点:
vector_retriever=vector_index.as_retriever()
可以测试效果:
vector_retriever.retrieve("为什么要做多数据源的融合检索查询?是谁提出的?")
输出检索到的结果:
[NodeWithScore(node=TextNode(id_='node_2', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, text='产品部林晓琪,simple-rag 产品,提交新特性需求:多数据源的融合检索查询。用户反映以及我们测试,在某些功能下,大文档的检索召回率不高,造成回答效果不好。希望通过增加新的检索方法,并融合现在的检索结果,提高检索召回率。', mimetype='text/plain', start_char_idx=None, end_char_idx=None, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'), score=0.5919122817844211), NodeWithScore(node=TextNode(id_='node_0', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, text='开发部韩宇轩,simple-rag 产品,实现新特性:针对文档的摘要索引。通过摘要索引,为用户生成的总结性能有大幅度提升。经测试评估,准确率达到了95%,以前是45%。', mimetype='text/plain', start_char_idx=None, end_char_idx=None, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'), score=0.3938835184407688)]
创建查询器并执行查询:
response_synthesizer = get_response_synthesizer(
response_mode="tree_summarize",
streaming=True,
)
vector_query_engine = RetrieverQueryEngine(
retriever=vector_retriever,
response_synthesizer=response_synthesizer,
)
response = vector_query_engine.query("为什么要做多数据源的融合检索查询?是谁提出的")
response.print_response_stream()
# 做多数据源的融合检索查询是为了提高大文档的检索召回率,从而改善回答效果。这个需求是由产品部的林晓琪提出的。
路由查询引擎合
路由查询引擎,封装和使用了 LLM 智能体(Agent)功能,让 LLM 根据提示词决定具体使用哪个查询器生成回答。
创建查询工具
将之前的摘要查询引擎和向量查询引擎分别分装为工具(Tool),设置提示词:
summary_tool = QueryEngineTool.from_defaults(
query_engine=summary_query_engine,
description=(
"对工作任务总结方面的问题很有用。"
),
)
vector_tool = QueryEngineTool.from_defaults(
query_engine=vector_query_engine,
description=(
"适用于回答工作任务相关的具体问题。"
),
)
创建路由查询器
路由查询器支持单选和多选路由,这里使用单选 LLMSingleSelector.
summarizer = TreeSummarize(streaming=True, use_async=False)
query_engine = RouterQueryEngine(
selector=LLMSingleSelector.from_defaults(
llm=OpenAILike(
model="glm4",
...
)
),
query_engine_tools=[
summary_tool,
vector_tool,
],
summarizer=summarizer
)
使用路由查询器查询
总结性的问题,路由将根据提示词识别并使用摘要索引查询:
response = query_engine.query(
"给出涉及的所有部门所有人的任务摘要输出,输出示例如下:"
"""
产品部:
- 张三
- xxx产品,提交新特性需求: xxx
- xxx项目,和客户开会,xxxxx
- ...
- 李四
- xxxx
- ...
- 王五
- ...
开发部:
- 赵六
- xxx产品,修正bug:xxxx
- xxx项目,实现新特性:xxxx
...
- 于七
- xxx产品,修正bug:xxxx
- xxx项目,实现新特性:xxxx
...
...
xxx部:
...
"""
)
response.print_response_stream()
有时需要通过提示词明确要求总结,否则会有不稳定情况:
response = query_engine.query(
"列表简要回答,林晓琪做了哪些工作任务"
"这是总结性质的问题"
)
response.print_response_stream()
具体问题, 路由将调用向量索引查询器回答问题:
response = query_engine.query("为什么要做多数据源的融合检索查询?是谁提出的")
response.print_response_stream()
总结
- 本文介绍了使用
LlamaIndex的RouterQueryEngine根据提示词切换使用不同的索引查询器回答问题的方法步骤 - 为演示基本过程,简化了生产环境下的细节处理,包括
- 为了保持生成内容不遗漏内容,生产系统使用的是json格式输出
- 复杂的提示词对用户不友好,总结任务是通过标签和按钮生成提示词的
- 数据加载实际上复杂的多,需要单独编写解析器
- 本文的结论是,使用本地模型是可以稳定实现一般的工作日志场景的 RAG 需求的
- 需要针对具体环境和需求测试选型 LLM
- 一般模型越大,执行越稳定
- 随着开源 LLM 的快速迭代发展,小尺寸模型会趋于符合要求