-
Notifications
You must be signed in to change notification settings - Fork 345
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding RAG example; [TODO] Abstract a RAG module and add it into repo…
…sitory (#64)
- Loading branch information
Showing
10 changed files
with
1,198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
208
examples/conversation_with_RAG_agents/rag/langchain_rag.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.