上周我发布了一篇用llama_index制作文档机器人的文章,感兴趣的朋友不少。于是,这周我多花了一些时间研究它,将一些使用上的常见问题记录了下来。
从本文开始,笔者将会让GPT更多地参与到文字撰写中。
GPT:在本文中,我们将深入讨论llama_index的使用,并提供一些关键的注意事项,以确保你能够成功实现你的目标。请坐稳,放松自己,准备好进入llama_index的令人兴奋的世界吧!
笔者:倒也没有那么令人兴奋……
1. 两个注意事项
调试:查看实际发送给GPT的内容
GPT:调试是使用任何技术的过程中都必须考虑到的重要步骤。在llama_index中,你可能会好奇它真正传给GPT模型的文字是什么。这样可以帮助你更好地理解llama_index和GPT模型之间的交互,并确保它们之间的通信是正确的。
gpt-index.readthedocs.io/en/latest/g…
LLM模型的选择
GPT:当你开始看log时,会意识到它query时默认使用的大预言模型是text-davinci-002模型。如果你想要采用gpt-3.5-turbo模型,你需要进行一些修改。
gpt-index.readthedocs.io/en/latest/g…
2. llama_index运作流程、如何进行更复杂的查询(重点)
GPT:在第一篇文章中,我们介绍了GPTSimpleVectorIndex将所有文档作为一个连续的文本进行查询。其流程大概是这样的:
+-----------+ +-----------------+ +-------------+
| 相关文档 | ——> | Embedding API | ——> | VectorIndex |
+-----------+ +-----------------+ +-------------+
|
------------------+
|
V
+-----------+ +-------+ +--------------+
| 用户问题 | ——----------> | Query | ——> | GPT API |
+-----------+ +-------+ +--------------+
|
|
V
+--------+
| Answer |
+--------+
笔者:图画得很好。注意其中VectorIndex可以被存到磁盘,不需要每次都创建。
GPT:但是,如果你的资料库很多,一个vecIndex可能不够用,你可以创建多个Index,并用不同的方式组织你的文档。llama_index官方提供了一个示例来帮助你实现这个目标:
gpt-index.readthedocs.io/en/latest/h…
笔者:对,这个示例有两个难点:
不同的index(索引方式)
笔者:官方提供的几个典型Index如下:
gpt-index.readthedocs.io/en/latest/g…
图中只画了query操作的步骤,并且看起来也比较难理解,你可以加上我的总结来服用:
● ListIndex:query一个listIndex时,它会按照顺序,逐个对它的子index进行query操作。每一次答案都会对前一个答案进行refine操作(GPT优化)。
+-----------+ +-----------------+ +-------------+
| 相关文档 | ——> | 多个子Index | ——> | ListIndex |
+-----------+ +-----------------+ +-------------+
|
------------------+
|
V
+-----------+ +-------+ +----------------+ +----------------+ +---------------+
| 用户问题 | ——----------> | Query | ——> | 子Index调query |-->| 子Index调query |-->| 子Index调query |
+-----------+ +-------+ +----------------+ +----------------+ +---------------+
| | |
V V V
+--------+ +---------------+ +----------------+
| Answer | ----> | 新旧Answer一起 | ----> | 新旧Answer一起 |
| | | 让GPT优化 | | 让GPT优化 |
+--------+ +---------------+ +----------------+
|
V
+-------+
| Answer|
+-------+
● KeywordTableIndex:创建index时,会先提取所有子index的内容的关键词;到了query这个TableIndex时,会将疑问句里的关键词提取出来,然后对子index的关键词进行匹配。拿出所有匹配的子index,逐个进行query。然后像List一样,会逐个进行refine操作(对子index提取关键词、对问题提取关键词的算法都有三个:正则、RAKE、问GPT)
+-----------+ +-----------------+ +------------------+ +-------------------+
| 相关文档 | ——> | 多个子Index | ——> | 提取各自的关键词 | ——> | KeyWordTableIndex |
+-----------+ +-----------------+ +------------------+ +-------------------+
|
--------------------------------------------+
|
V
+-----------+ +-------+ +---------------------+
| 用户问题 | ——----------> | Query | ——> | 提取用户问题的keyword|
+-----------+ +-------+ +---------------------+
| |
V |
+----------------+ |
| 找出所有keyword| <-----|
| 符合的子index |
+----------------+
|
V
+----------------+ +---------------+ +----------------+ +---------------+
| 子Index调query |-->| 子Index调query | -->| 子Index调query |-->| 子Index调query |
| | | | | | | |
+----------------+ +---------------+ +----------------+ +---------------+
| | | |
V V V V
+--------+ +---------------+ +---------------+ +----------------+
| Answer | ----> | 新旧Answer一起 | ----> | 新旧Answer一起 | ----> | 新旧Answer一起 |
| | | 让GPT优化 | | 让GPT优化 | | 让GPT优化 |
+--------+ +---------------+ +---------------+ +----------------+
|
V
+-----------+
| 最终Answer|
+-----------+
● TreeIndex:创建index时,会先找GPT提取所有子index的总结内容。到了用户query阶段,会将这些index的总结词发给GPT,让GPT选择其中的几个节点,然后query所选中的节点,得到最终结果。
+-----------+ +-----------------+ +--------------------------+ +-------------------+
| 相关文档 | ——> | 多个子Index | ——> | 找GPT总结它们内容(summary) | ——> | TreeIndex |
+-----------+ +-----------------+ +--------------------------+ +-------------------+
|
---------------------------------------------------+
|
V
+-----------+ +-------+
| 用户问题 | ——----------> | Query |
+-----------+ +-------+
|
V
+-------------------+
| 询问GPT,哪n个节点 |
| 的summary最匹配问题 |
+--------------------+
|
V
+----------------+ +---------------+ +----------------+ +---------------+
| 子Index调query |-->| 子Index调query | -->| 子Index调query |-->| 子Index调query |
| | | | | | | |
+----------------+ +---------------+ +----------------+ +---------------+
| | | |
V V V V
+--------+ +---------------+ +---------------+ +----------------+
| Answer | ----> | 新旧Answer一起 | ----> | 新旧Answer一起 | ----> | 新旧Answer一起 |
| | | 让GPT优化 | | 让GPT优化 | | 让GPT优化 |
+--------+ +---------------+ +---------------+ +----------------+
|
V
+-----------+
| 最终Answer|
+-----------+
像ListIndex和TableIndex,都有一个特点:如果子index的数量会不停增长,那么在用户提问时,你无法控制最终会产生多少次query。笔者认为如果你的应用实时性要求较高,尽量不要滥用它们。只有在表达确定长度的编排关系的时候,query次数是可控的,就可以用他们。
另外。上图中多个answer合并优化这一个阶段称为“response合成”阶段,图里画的是 create and refine
方式,llama_index另外还提供了tree_summarize
方式,这里就不赘述了。
ComposableGraph
笔者:例子中提到的另一个可能比较困惑的东西:ComposableGraph,个人觉得比较恰当的理解是:index之间的嵌套只代表它们的父子关系,如果你希望你的查询是递归进行的,就要用ComposableGraph。
笔者:另外,有了ComposableGraph之后,你给每个Index传递其参数的方式就得通过ComposableGraph.query函数的query_config参数,见下方代码。其中struct_id、index_struct_type都是用于筛选子index的。query_mode、query_kwargs则是要传递给它们的参数。
[
{
"index_struct_id": vectorIndex1.doc_id(),
"index_struct_type": "simple_dict",
"query_mode": "default",
"query_kwargs": {
"similarity_top_k": 3
}
},
{
"index_struct_type": "simple_dict",
"query_mode": "default",
"query_kwargs": {
"similarity_top_k": 1
}
}
]
3. prompt
GPT:上文所提到的一些操作。比如query、refine、KeywordTable的提取关键字、TreeIndex的summary等操作,都需要请求GPT模型来完成。如果你想要更好地理解llama_index的工作原理,那么找到这些操作的prompt是非常重要的。在llama_index的代码中,你可以找到这些操作对应的prompt,
笔者:我在使用的过程中,遇到过一个Bug:在上文提到的ListIndex执行query操作后,在response合成时,gpt-3.5在前一个answer里得到了有用的东西,然后在下一次refine时,却变成这样的返回:“上文已经提供合适的答案,不需要进行优化”。
究其原因,就是gpt-3.5-turbo
没有遵循llama_index内置提示词的规则做事。所以,笔者只能通过自定义提示词的方式解决。
gpt-index.readthedocs.io/en/latest/h…
如果你用的是ComposableGraph,则可以在query_configs里传入。
另外,由于默认提示词是英文的,用于做中文产品时,有时你输入中文问题它会回答英文。也要通过该问题解决,只需要将它内部的default_prompt翻译一下即可。
4. 显示引用来源
GPT:将你的资料库整理成树形后。如果你正在开发问答类应用,那么出处引用功能就非常重要了。因为被GPT总结过后的内容很可能会丢失一些信息,所以引用原始内容就变得尤为必要,new bing就有这个功能。在llama_index中,你可以很容易地找到它所使用的index,并从返回结果中获取它的内容。下面提供一个例子来帮助你更好地理解如何实现这个功能:假设你正在使用llama_index的query操作,得到了一个含有多个答案的返回结果。接下来,你可以通过查看每个答案所用到的index来确定哪个答案是正确的,然后使用index的内容作为出处引用。在下面的段落中,我将为你提供更多详细的指导和帮助,以确保你能够成功实现出处引用功能。
res = graph.query('UsingAction怎么用?')
responseByGPT = str(res) # 文字结果
refDoc = []
for node in res.source_nodes:
if node.similarity != None:
refDoc.append(f'(相似度{node.similarity}) https://puerts.github.io/docs/puerts' + node.doc_id) # 此处我提前将文档路径设为了doc_id,你也可以设在extra_info里
print("\n".join([
responseByGPT,
"related Doc",
"\n".join(refDoc)
]))
5. 总结
笔者:我在写这一篇的时候,chatgpt-plugin已经官宣了,其配套开源项目(相当于插件开发套件) openai/chatgpt-retrieval-plugin也已经发布。技术方案和llama_index
一致。很显然这套提前搜索的方案,以后会是GPT定制的常态操作。所以理论上来说只要理解了llama_index
,理解chatgpt-plugin
就不难。
chatgpt-retrieval-plugin
和llama_index
不一样的是,它只提供了vectorIndex的搜索方法(其实最有用的就是这个),并且它内部没有实现与GPT QA的操作 —— 在chatgpt-plugin
线上运行时,这部分工作是运作在他们服务器的。所以它和llama_index
并不完全重叠,他们是可以共存使用的。
另一个不一样的点:他们内置了一个简单的Rest服务器提供这个vector搜索服务,这样我们可以用别的语言使用它,或是对接到别的LLM上。但更亮的是:会给这个Rest服务器生成非常利于GPT理解的自然语言API说明文件,这是chatgpt-plugin运作的核心。
更多的有缘再写吧。