Skip to content

Commit

Permalink
Feat/llama index readme (#471)
Browse files Browse the repository at this point in the history
Co-authored-by: skyline2006 <skyline2006@163.com>
Co-authored-by: Zhikaiiii <1658973216@qq.com>
  • Loading branch information
3 people authored Jun 11, 2024
1 parent 18a97f2 commit 4eb809c
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 6 deletions.
179 changes: 179 additions & 0 deletions modelscope_agent/rag/README_zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Modelscope-Agent知识库

# 背景

[modelscope-agent](https://github.com/modelscope/modelscope-agent)是GPTs 开源的实现方案,允许用户通过聊天、直接配置的方式进行llm的定制,可以允许用户使用自定义知识库以及接入多工具的能力。modelscope\_agent当前支持的知识库功能存在:可读文件类型有限、效果不稳定、召回策略固定、对大文件和多文件的支持较弱等问题。

为增强modelscope-agent的知识库能力,我们选择使用llama-index。LlamaIndex 是一个简单、灵活的数据框架,用于将自定义数据源连接到大型语言模型 (LLM)。同时llama-index提供[插件市场](https://llamahub.ai/?tab=tools),支持社区开发者贡献不同类型文件reader、不同召回策略、chunk方法等。结合社区能力,可很好地对modelscope\_agent的知识库能力做补充。

# 简介

知识库的处理分为2个阶段:对知识库内容构建索引 -> 根据query召回知识库对应的内容并生成返回。

```python
from modelscope_agent.memory import MemoryWithRag

memory = MemoryWithRag(urls=['tests/samples/常见QA.pdf'])
print(memory.run(query='高德天气api怎么申请'))
```

基本内部流程如图,其中知识库内容的索引构建沿着浅绿色箭头所示,根据query召回知识库对应内容如深绿色箭头所示。

![image](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4jKqmV382GrYOw19/img/187517cb-63a5-43a1-b8d6-3908084f41b4.png)

按照流程,内部各模块都支持灵活配置。接下来将依次介绍。

## 文件读取

文件读取(read)模块会从不同来源、不同类型文档中读取信息。read中默认提供如下类型文件的读取:\`.hwp\`\`.pdf\`\`.docx\`\`.pptx\`\`.ppt\`\`.pptm\`,  \`.jpg\`\`.png\`\`.jpeg\`\`.mp3\`\`.mp4\`\`.csv\`\`.epub\`\`.md\`\`.mbox\`\`.ipynb\`\`txt\`\`.pd\`,  \`.html\`。其他类型的文件未配置默认reader,可在[插件市场](https://llamahub.ai/?tab=readers)选取更多类型文件reader、或自定义reader传入使用。未配置reader的其他类型文件传入时将被忽略。

```python
from modelscope_agent.memory import MemoryWithRag
from llama_index.readers.json import JSONReader

memory = MemoryWithRag(urls=['/home/test.json'], loaders={'.json': JSONReader})
```

## 构建索引

构建索引(indexing)包括对文档切片、将每个文档片段向量化(如有)等。由于索引构建方式与召回方式强相关,因此这部分内容与retriever的初始化(get\_root\_retriever)在一个函数内实现。其中,大文档chunk策略默认使用按语义切片(sentence\_spliter)、默认使用向量召回、默认的embedding模型为dashscope提供的text-embedding-v2。

如果您想使用其他chunk方式或emb模型,可以通过transformations和emb参数传入。其中,transformations参数允许接收的类包括:TextSplitter、NodeParser、MetadataExtractor,详情可参考[llama-index相关文档](https://github.com/run-llama/llama_index/blob/main/docs/docs/module_guides/loading/ingestion_pipeline/transformations.md);emb模型可在[插件市场](https://llamahub.ai/?tab=embeddings)选用

```python
from modelscope_agent.memory import MemoryWithRag
from llama_index.core.extractors import TitleExtractor
from llama_index.embeddings.openai import OpenAIEmbedding

# transformations参数以TitleExtractor为例
# emb模型切换以OpenAIEmbedding为例。注意,使用该emb模型时,需要在可访问openai接口的环境中(在环境变量中配置openai的api-key,且需要在能够访问openai的网络环境中)
memory = MemoryWithRag(transformations=[TitleExtractor],emb=OpenAIEmbedding)
```

## 缓存加载

缓存(storing)将indexing后的信息保存成文件,以便后续再次使用时无需重新indexing,同时也方便将indexing文件移动切换到其他环境使用。默认存储路径在./run下。可以通过storage\_path配置。同时通过use\_knowledge\_cache控制初始化时是否使用cache。

```python
from modelscope_agent.memory import MemoryWithRag

# 将2个文件indexing后存储到./tmp/目录
MemoryWithRag(
urls=['tests/samples/modelscope_qa_2.txt', 'tests/samples/常见QA.pdf'],
storage_path='./tmp/',
use_knowledge_cache=False,
)
# 从./tmp/目录加载
memory = MemoryWithRag(
storage_path='./tmp/',
use_knowledge_cache=True,
)
```

## 查询

查询(querying):根据query内容从候选的indexed数据中进行召回,用召回的chunks访问llm,得到整合总结后的结果返回给用户。使用上可以在初始化时将文档链接通过urls传入。

```python
from modelscope_agent.memory import MemoryWithRag

memory = MemoryWithRag(urls=['tests/samples/常见QA.pdf', 'tests/samples/modelscope_qa_2.txt'])
print(memory.run(query='高德天气api怎么申请'))
```

在运行过程中,可以指定本次访问使用的文档范围。如果某个文档在初始化时未被传入,在run的过程中也会先对该文档进行加载、索引、存储。

```python
from modelscope_agent.memory import MemoryWithRag

memory = MemoryWithRag()
print(memory.run(query='模型大文件上传失败怎么办', url=['tests/samples/modelscope_qa_2.txt']))
```

querying的流程主要可分为3步:从候选文档中召回相关片段、对召回内容后处理、传入llm进行总结。

### 召回

根据查询的请求内容,在候选知识库中找到相关性最高的一个或多个。前面介绍index时提到,召回方法默认为向量召回。如果您想使用其他召回方法,可以通过配置retriever参数实现。

```python
from modelscope_agent.memory import MemoryWithRag
from llama_index.retrievers.bm25 import BM25Retriever

memory = MemoryWithRag(retriever=BM25Retriever)
```

### 后处理

在querying的流程中,支持对召回的知识库片段内容进行自定义后处理。比如召回多条内容时,可按照与query的相关性进行重排;您可以在llama-index的[插件市场](https://llamahub.ai/?tab=postprocessor)找到不同的后处理方法。后处理方法可通过post\_processors传入;如果该参数不传入,默认不进行后处理。

```python
from modelscope_agent.memory import MemoryWithRag
from llama_index.postprocessor.dashscope_rerank import DashScopeRerank

memory = MemoryWithRag(post_processors=[DashScopeRerank])
```

### llm调用

召回的文档片段可能内容很多,其中与query查询相关的内容可能仅有一两句,或者需要总结。因此需要llm对召回后的内容进行有效信息抽取总结。您可以配置不同的llm,这个llm可以是modelscope-agent的模型对象或llm\_config配置方法;也可以是[llama-index插件市场](https://llamahub.ai/?tab=llms)中的,初始化完成的llm对象。不配置时,默认使用dashscope提供的qwen-max。

```python
from modelscope_agent.memory import MemoryWithRag

llm_config = {'model': 'qwen-max', 'model_server': 'dashscope'}
memory = MemoryWithRag(llm=llm_config)
```

# 深度使用

## 多策略

如果单一的召回策略无法满足使用需求,需要定制复杂多策略。可以自定义实现多种召回器混用的召回策略。以下示例实现了一个混用向量召回和最佳匹配的召回器:

```python
from typing import List
from llama_index.core import VectorStoreIndex
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core.base.base_retriever import BaseRetriever
from llama_index.core.schema import NodeWithScore
from llama_index.core.schema import QueryBundle
from modelscope_agent.memory import MemoryWithRag

class MyRetriever(BaseRetriever):
def __init__(self, index: VectorStoreIndex, **kwargs) -> None:
self._vector_retriever = index.as_retriever()
self._best_match_retriever = BM25Retriever.from_defaults(index)
super().__init__(**kwargs)

def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
vector_nodes = self._vector_retriever.retrieve(query_bundle)
bs_nodes = self._best_match_retriever.retrieve(query_bundle)

vector_ids = {n.node.node_id for n in vector_nodes}
bs_ids = {n.node.node_id for n in bs_nodes}

combined_dict = {n.node.node_id: n for n in vector_nodes}
combined_dict.update({n.node.node_id: n for n in bs_nodes})

retrieve_ids = vector_ids.intersection(bs_ids)

retrieve_nodes = [combined_dict[rid] for rid in retrieve_ids]
return retrieve_nodes

memory = MemoryWithRag(retriever=MyRetriever, urls=['tests/samples/modelscope_qa_2.txt'])
print(memory.run(query='模型大文件上传失败怎么办'))
```

## 多模态

前面介绍文件读取时有提到,默认支持的除文本文件外,还支持 `.jpg``.png``.jpeg``.mp3``.mp4`等图片、音频、视频模态文件。以图片模态为例,图片对应的阅读器是[ImageReader](https://github.com/run-llama/llama_index/blob/main/llama-index-integrations/readers/llama-index-readers-file/llama_index/readers/file/image/base.py),其参数\`parse\_text\`为True时,会自动从hf下载调用识图模型`naver-clova-ix/donut-base-finetuned-cord-v2`,对图像内容进行理解,作为图像信息供后续的召回参考。由于从hf下载模型需要在特定的网络环境下,所以我们默认不使用读图功能,因此在默认配置中,图像模态给到召回器可参考的信息只有:这是个图像+图片路径与文件名。

后续我们会对此进行优化,增加图像理解模型的可选范围。如果当前您想使用图像理解功能,在能够使用 [naver-clova-ix/donut-base-finetuned-cord-v2](https://huggingface.co/naver-clova-ix/donut-base-finetuned-cord-v2) 的环境下可以这样操作:

```python
from modelscope_agent.memory import MemoryWithRag
from llama_index.readers.file import ImageReader

memory = MemoryWithRag(urls=['tests/samples/rag.png'], loaders={'.png': ImageReader(parse_text=True)})
```
14 changes: 8 additions & 6 deletions modelscope_agent/rag/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __init__(self,
llm: Union[LLM, BaseChatModel, Dict] = {},
retriever: Optional[Type[BaseRetriever]] = None,
emb: Optional[Type[BaseEmbedding]] = None,
loaders: Dict[str, Type[BaseReader]] = {},
loaders: Dict[str, Union[BaseReader, Type[BaseReader]]] = {},
transformations: List[Type[TransformComponent]] = [],
post_processors: List[Type[BaseNodePostprocessor]] = [],
use_cache: bool = True,
Expand Down Expand Up @@ -220,16 +220,18 @@ def get_root_retriever(self,
return index.as_retriever()

def get_extra_readers(
self, loaders: Dict[str,
Type[BaseReader]]) -> Dict[str, BaseReader]:
self, loaders: Dict[str, Union[BaseReader, Type[BaseReader]]]
) -> Dict[str, BaseReader]:
extra_readers = {}
for file_type, loader_cls in loaders.items():
for file_type, loader_or_cls in loaders.items():
if isinstance(loader_or_cls, BaseReader):
extra_readers[file_type] = loader_or_cls
try:
loader = loader_cls()
loader = loader_or_cls()
extra_readers[file_type] = loader
except Exception as e:
print(
f'Using {loader_cls} failed. Can not read {file_type} file. Detail: {e}'
f'Using {loader_or_cls} failed. Can not read {file_type} file. Detail: {e}'
)

# lazy import
Expand Down

0 comments on commit 4eb809c

Please sign in to comment.