Skip to content

Commit

Permalink
Adding RAG example; [TODO] Abstract a RAG module and add it into repo…
Browse files Browse the repository at this point in the history
…sitory (#64)
  • Loading branch information
ZiTao-Li authored Mar 29, 2024
1 parent 72e179b commit f762dd6
Show file tree
Hide file tree
Showing 10 changed files with 1,198 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ the following libraries.
- [Self-Organizing Conversation](./examples/conversation_self_organizing)
- [Basic Conversation with LangChain library](./examples/conversation_with_langchain)
- [Conversation with ReAct Agent](./examples/conversation_with_react_agent)
- [Conversation with RAG Agent](./examples/conversation_with_RAG_agents)

- Game
- [Gomoku](./examples/game_gomoku)
Expand Down
1 change: 1 addition & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ AgentScope支持使用以下库快速部署本地模型服务。
- [智能体自组织的对话](./examples/conversation_self_organizing)
- [兼容LangChain的基础对话](./examples/conversation_with_langchain)
- [与ReAct智能体对话](./examples/conversation_with_react_agent)
- [与RAG智能体对话](./examples/conversation_with_RAG_agents)

- 游戏
- [五子棋](./examples/game_gomoku)
Expand Down
57 changes: 57 additions & 0 deletions examples/conversation_with_RAG_agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# AgentScope Consultants: a Multi-Agent RAG Application

* **What is this example about?**
With the provided implementation and configuration,
you will obtain three different agents who can help you answer different questions about AgentScope.

* **What is this example for?** By this example, we want to show how the agent with retrieval augmented generation (RAG)
capability can be used to build easily.

**Notice:** This example is a Beta version of the AgentScope RAG agent. A formal version will soon be added to `src/agentscope/agents`, but it may be subject to changes.

## Prerequisites
* **Cloning repo:** This example requires cloning the whole AgentScope repo to local.
* **Packages:** This example is built on the LlamaIndex package. Thus, some packages need to be installed before running the example.
```bash
pip install llama-index tree_sitter tree-sitter-languages
```
* **Model APIs:** This example uses Dashscope APIs. Thus, we also need an API key for DashScope.
```bash
export DASH_SCOPE_API='YOUR_API_KEY'
```

**Note:** This example has been tested with `dashscope_chat` and `dashscope_text_embedding` model wrapper, with `qwen-max` and `text-embedding-v2` models.
However, you are welcome to replace the Dashscope language and embedding model wrappers or models with other models you like to test.

## Start AgentScope Consultants
* **Terminal:** The most simple way to execute the AgentScope Consultants is running in terminal.
```bash
python ./rag_example.py
```
Setting `log_retrieval` to `false` in `agent_config.json` can hide the retrieved information and provide only answers of agents.

* **AS studio:** If you want to have more organized, clean UI, you can also run with our `as_studio`.
```bash
as_studio ./rag_example.py
```

### Customize AgentScope Consultants to other consultants
After you run the example, you may notice that this example consists of three RAG agents:
* `AgentScope Tutorial Assistant`: responsible for answering questions based on AgentScope tutorials (markdown files).
* `AgentScope Framework Code Assistant`: responsible for answering questions based on AgentScope code base (python files).
* `Summarize Assistant`: responsible for summarize the questions from the above two agents.

These agents can be configured to answering questions based on other GitHub repo, by simply modifying the `input_dir` fields in the `agent_config.json`.

For more advanced customization, we may need to learn a little bit from the following.

**RAG modules:** In AgentScope, RAG modules are abstract to provide three basic functions: `load_data`, `store_and_index` and `retrieve`. Refer to `src/agentscope/rag` for more details.

**RAG configs:** In the example configuration (the `rag_config` field), all parameters are optional. But if you want to customize them, you may want to learn the following:
* `load_data`: contains all parameters for the the `rag.load_data` function.
Since the `load_data` accepts a dataloader object `loader`, the `loader` in the config need to have `"create_object": true` to let a internal parse create a LlamaIndex data loader object.
The loader object is an instance of `class` in module `module`, with initialization parameters in `init_args`.

* `store_and_index`: contains all parameters for the the `rag.store_and_index` function.
For example, you can pass `vector_store` and `retriever` configurations in a similar way as the `loader` mentioned above.
For the `transformations` parameter, you can pass a list of dicts, each of which corresponds to building a `NodeParser`-kind of preprocessor in Llamaindex.
79 changes: 79 additions & 0 deletions examples/conversation_with_RAG_agents/agent_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
[
{
"class": "LlamaIndexAgent",
"args": {
"name": "AgentScope Tutorial Assistant",
"sys_prompt": "You're a helpful assistant. You need to generate answers based on the provided context.",
"model_config_name": "qwen_config",
"emb_model_config_name": "qwen_emb_config",
"rag_config": {
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "../../docs/sphinx_doc/en/source/tutorial/",
"required_exts": [".md"]
}
}
},
"chunk_size": 2048,
"chunk_overlap": 40,
"similarity_top_k": 10,
"log_retrieval": false,
"recent_n_mem": 1
}
}
},
{
"class": "LlamaIndexAgent",
"args": {
"name": "AgentScope Framework Code Assistant",
"sys_prompt": "You're a helpful assistant about coding. You can very familiar with the framework code of AgentScope.",
"model_config_name": "qwen_config",
"emb_model_config_name": "qwen_emb_config",
"rag_config": {
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "../../src/agentscope",
"recursive": true,
"required_exts": [".py"]
}
}
},
"store_and_index": {
"transformations": [
{
"create_object": true,
"module": "llama_index.core.node_parser",
"class": "CodeSplitter",
"init_args": {
"language": "python",
"chunk_lines": 100
}
}
]
},
"chunk_size": 2048,
"chunk_overlap": 40,
"similarity_top_k": 10,
"log_retrieval": false,
"recent_n_mem": 1
}
}
},
{
"class": "DialogAgent",
"args": {
"name": "Summarize Assistant",
"sys_prompt": "You are a helpful assistant that can summarize the answers of the previous two messages.",
"model_config_name": "qwen_config",
"use_memory": true
}
}
]
18 changes: 18 additions & 0 deletions examples/conversation_with_RAG_agents/rag/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
""" Import all pipeline related modules in the package. """
from .rag import RAGBase

from .llama_index_rag import LlamaIndexRAG


try:
from .langchain_rag import LangChainRAG
except Exception:
LangChainRAG = None # type: ignore # NOQA


__all__ = [
"RAGBase",
"LlamaIndexRAG",
"LangChainRAG",
]
208 changes: 208 additions & 0 deletions examples/conversation_with_RAG_agents/rag/langchain_rag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
"""
This module is integrate the LangChain RAG model into our AgentScope package
"""


from typing import Any, Optional, Union

try:
from langchain_core.vectorstores import VectorStore
from langchain_core.documents import Document
from langchain_core.embeddings import Embeddings
from langchain_community.document_loaders.base import BaseLoader
from langchain_community.vectorstores import Chroma
from langchain_text_splitters.base import TextSplitter
from langchain_text_splitters import CharacterTextSplitter
except ImportError:
VectorStore = None
Document = None
Embeddings = None
BaseLoader = None
Chroma = None
TextSplitter = None
CharacterTextSplitter = None

from examples.conversation_with_RAG_agents.rag import RAGBase
from examples.conversation_with_RAG_agents.rag.rag import (
DEFAULT_CHUNK_OVERLAP,
DEFAULT_CHUNK_SIZE,
)
from agentscope.models import ModelWrapperBase


class _LangChainEmbModel(Embeddings):
"""
Dummy wrapper to convert the ModelWrapperBase embedding model
to a LanguageChain RAG model
"""

def __init__(self, emb_model: ModelWrapperBase) -> None:
"""
Dummy wrapper
Args:
emb_model (ModelWrapperBase): embedding model of
ModelWrapperBase type
"""
self._emb_model_wrapper = emb_model

def embed_documents(self, texts: list[str]) -> list[list[float]]:
"""
Wrapper function for embedding list of documents
Args:
texts (list[str]): list of texts to be embedded
"""
results = [
list(self._emb_model_wrapper(t).embedding[0]) for t in texts
]
return results

def embed_query(self, text: str) -> list[float]:
"""
Wrapper function for embedding a single query
Args:
text (str): query to be embedded
"""
return list(self._emb_model_wrapper(text).embedding[0])


class LangChainRAG(RAGBase):
"""
This class is a wrapper around the LangChain RAG.
"""

def __init__(
self,
model: Optional[ModelWrapperBase],
emb_model: Union[ModelWrapperBase, Embeddings, None],
config: Optional[dict] = None,
**kwargs: Any,
) -> None:
"""
Initializes the LangChainRAG
Args:
model (ModelWrapperBase):
The language model used for final synthesis
emb_model ( Union[ModelWrapperBase, Embeddings, None]):
The embedding model used for generate embeddings
config (dict):
The additional configuration for llama index rag
"""
super().__init__(model, emb_model, **kwargs)

self.loader = None
self.splitter = None
self.retriever = None
self.vector_store = None

if VectorStore is None:
raise ImportError(
"Please install LangChain RAG packages to use LangChain RAG.",
)

self.config = config or {}
if isinstance(emb_model, ModelWrapperBase):
self.emb_model = _LangChainEmbModel(emb_model)
elif isinstance(emb_model, Embeddings):
self.emb_model = emb_model
else:
raise TypeError(
f"Embedding model does not support {type(self.emb_model)}.",
)

def load_data(
self,
loader: BaseLoader,
query: Optional[Any] = None,
**kwargs: Any,
) -> list[Document]:
# pylint: disable=unused-argument
"""
Loading data from a directory
Args:
loader (BaseLoader):
accepting a LangChain loader instance
query (str):
accepting a query, LangChain does not rely on this
Returns:
list[Document]: a list of documents loaded
"""
self.loader = loader
docs = self.loader.load()
return docs

def store_and_index(
self,
docs: Any,
vector_store: Optional[VectorStore] = None,
splitter: Optional[TextSplitter] = None,
**kwargs: Any,
) -> Any:
# pylint: disable=unused-argument
"""
Preprocessing the loaded documents.
Args:
docs (Any):
documents to be processed
vector_store (Optional[VectorStore]):
vector store in LangChain RAG
splitter (Optional[TextSplitter]):
optional, specifies the splitter to preprocess
the documents
Returns:
None
In LlamaIndex terms, an Index is a data structure composed
of Document objects, designed to enable querying by an LLM.
For example:
1) preprocessing documents with
2) generate embedding,
3) store the embedding-content to vdb
"""
self.splitter = splitter or CharacterTextSplitter(
chunk_size=self.config.get("chunk_size", DEFAULT_CHUNK_SIZE),
chunk_overlap=self.config.get(
"chunk_overlap",
DEFAULT_CHUNK_OVERLAP,
),
)
all_splits = self.splitter.split_documents(docs)

# indexing the chunks and store them into the vector store
if vector_store is None:
vector_store = Chroma()
self.vector_store = vector_store.from_documents(
documents=all_splits,
embedding=self.emb_model,
)

# build retriever
search_type = self.config.get("search_type", "similarity")
self.retriever = self.vector_store.as_retriever(
search_type=search_type,
search_kwargs={
"k": self.config.get("similarity_top_k", 6),
},
)

def retrieve(self, query: Any, to_list_strs: bool = False) -> list[Any]:
"""
This is a basic retrieve function with LangChain APIs
Args:
query: query is expected to be a question in string
Returns:
list of answers
More advanced retriever can refer to
https://python.langchain.com/docs/modules/data_connection/retrievers/
"""

retrieved_docs = self.retriever.invoke(query)
if to_list_strs:
results = []
for doc in retrieved_docs:
results.append(doc.page_content)
return results
return retrieved_docs
Loading

0 comments on commit f762dd6

Please sign in to comment.