大家好,我是 同学小张,+v: jasper_8017 一起交流,持续学习C++进阶、OpenGL、WebGL知识技能和AI大模型应用实战案例,持续分享,欢迎大家点赞+关注,共同学习和进步。
对于多智能体框架来说,RAG似乎并不是其框架内的内容,所以类似 AgentScope、LangGraph、MetaGPT 等框架,都没有提供特别明确的RAG实现流程。但RAG作为当前提高大模型能力、知识库问答等方向的主流方法,还是很重要的。所以这些框架也提供了实现RAG的一些示例。
前面文章我们分别在AgentScope中用 LangChain 和 LlamaIndex 封装了RAG的流程,本文来学习下AgentScope中如何使用这个RAG的流程:实现一个 RAG Agent。以后如果需要,可以直接复用。
文章目录
- 1. 自定义Agent实现 - RAGAgentBase
- 1.1 框架
- 1.2 初始化
- 1.3 reply 函数实现
- 2. LlamaIndexAgent 实现
- 2.1 LlamaIndexAgent 的初始化
- 2.2 init_rag 函数实现
- 3. LlamaIndexAgent 使用
- 3.1 LlamaIndexAgent 配置
- 4. 总结
1. 自定义Agent实现 - RAGAgentBase
在AgentScope中实现自定义的Agent,还是原来的套路:继承 AgentBase,实现 __init__ 函数和 reply 函数。
1.1 框架
示例代码中,首先是定义了一个 RAGAgentBase,这是为了后面方便使用不同的方式实现RAG基本流程。然后在此基础上,定义了 LlamaIndexAgent。
RAGAgentBase 就是我们实现的自定义Agent,它继承了 AgentBase。框架如下:
class RAGAgentBase(AgentBase, ABC):
"""
Base class for RAG agents
"""
def __init__(
self,
name: str,
sys_prompt: str,
model_config_name: str,
emb_model_config_name: str,
memory_config: Optional[dict] = None,
rag_config: Optional[dict] = None,
) -> None:
......
@abstractmethod
def init_rag(self) -> RAGBase:
......
def reply(
self,
x: dict = None,
) -> dict:
......
可以看到,除了基本的 初始化、reply 函数外,还定义了一个 init_rag 函数。这个函数是用来初始化 RAG 的,例如,决定是使用 LangChain 实现的RAG,还是使用 LlamaIndex 实现的RAG。
1.2 初始化
RAGAgentBase 的 __init__ 函数中,主要是初始化了一些基本变量。同时调用了其子类的 init_rag 函数,来初始化相应的 RAG。
def __init__(
self,
name: str,
sys_prompt: str,
model_config_name: str,
emb_model_config_name: str,
memory_config: Optional[dict] = None,
rag_config: Optional[dict] = None,
) -> None:
super().__init__(
name=name,
sys_prompt=sys_prompt,
model_config_name=model_config_name,
use_memory=True,
memory_config=memory_config,
)
# setup embedding model used in RAG
self.emb_model = load_model_by_config_name(emb_model_config_name)
self.rag_config = rag_config or {}
if "log_retrieval" not in self.rag_config:
self.rag_config["log_retrieval"] = True
# use LlamaIndexAgent OR LangChainAgent
self.rag = self.init_rag()
1.3 reply 函数实现
reply
函数实现的功能是查询相关知识库文档,然后传给大模型生成回复。
首先,是query的准备。第一优先级,是从对话历史中拿最近的一条消息来作为query:
history = self.memory.get_memory(
recent_n=self.rag_config.get("recent_n_mem", 1),
)
第二优先级才是用户输入的 x。
不知道这里为什么要这样处理?
有了 query 之后,拿这个query去检索知识库,得到检索结果:self.rag.retrieve
。
之后组装 Prompt,包括 system_prompt、query、检索结果。
最后调用大模型生成回复。注意最后返回的还是 Msg 对象。
def reply(
self,
x: dict = None,
) -> dict:
retrieved_docs_to_string = ""
# record the input if needed
if self.memory:
self.memory.add(x)
# in case no input is provided (e.g., in msghub),
# use the memory as query
history = self.memory.get_memory(
recent_n=self.rag_config.get("recent_n_mem", 1),
)
query = (
"/n".join(
[msg["content"] for msg in history],
)
if isinstance(history, list)
else str(history)
)
elif x is not None:
query = x["content"]
else:
query = ""
if len(query) > 0:
retrieved_docs = self.rag.retrieve(query, to_list_strs=True)
......
# prepare prompt
prompt = self.model.format(
Msg(
name="system",
role="system",
content=self.sys_prompt,
),
self.memory.get_memory(
recent_n=self.rag_config.get("recent_n_mem", 1),
),
Msg(
name="user",
role="user",
content="Context: " + retrieved_docs_to_string,
),
)
# call llm and generate response
response = self.model(prompt).text
msg = Msg(self.name, response)
......
return msg
2. LlamaIndexAgent 实现
上面实现了 RAGAgentBase,在此基础上实现 LlamaIndexAgent。LlamaIndexAgent 继承了 RAGAgentBase,并实现了 init_rag 函数。
class LlamaIndexAgent(RAGAgentBase):
2.1 LlamaIndexAgent 的初始化
这里的初始化,直接将参数传给父类,对父类进行初始化。
def __init__(
self,
name: str,
sys_prompt: str,
model_config_name: str,
emb_model_config_name: str = None,
memory_config: Optional[dict] = None,
rag_config: Optional[dict] = None,
) -> None:
super().__init__(
name=name,
sys_prompt=sys_prompt,
model_config_name=model_config_name,
emb_model_config_name=emb_model_config_name,
memory_config=memory_config,
rag_config=rag_config,
)
2.2 init_rag 函数实现
init_rag
函数实现的功能是初始化相应的RAG,例如前面文章我们实现的 LlamaIndexRAG 或 LangChainRAG。这里的例子我们使用了 LlamaIndexRAG。
rag = LlamaIndexRAG(
model=self.model,
emb_model=self.emb_model,
config=self.rag_config,
)
然后使用这个 rag 来加载数据:docs = rag.load_data(**load_data_args)
,其中这里的 load_data_args 是从配置文件中读取的。
然后使用这个 rag 来存储和索引数据:rag.store_and_index(docs, **store_and_index_args)
,其中这里的 store_and_index_args 是从配置文件中读取的。
def init_rag(self) -> LlamaIndexRAG:
rag = LlamaIndexRAG(
model=self.model,
emb_model=self.emb_model,
config=self.rag_config,
)
......
docs = rag.load_data(**load_data_args)
......
rag.store_and_index(docs, **store_and_index_args)
return rag
3. LlamaIndexAgent 使用
下面来看下如何使用 LlamaIndexAgent。
3.1 LlamaIndexAgent 配置
上面在 init_rag 函数中,我们使用了配置文件中的参数。下面是一个示例配置:
{
"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
}
}
},
其中重点看这几个参数:
-
sys_prompt:RAG的系统提示
-
model_config_name:模型配置名称
-
emb_model_config_name:嵌入模型配置名称
-
rag_config:RAG 配置,包括使用的 loader、知识库目录、chunk_size、chunk_overlap、similarity_top_k 等。
从文件中读取配置,传给 LlamaIndexAgent:
with open("./agent_config.json", "r", encoding="utf-8") as f:
agent_configs = json.load(f)
tutorial_agent = LlamaIndexAgent(**agent_configs[0]["args"])
后面就可以使用 tutorial_agent 来进行对话了。
使用部分,最主要的是这个配置文件的内容。为了读这个配置文件,代码中还要配备解析这个配置文件的相应函数。
4. 总结
本文介绍了在AgentScope中自定义RAGAgent的流程及源码。源码解析包括 RAGAgentBase 和 LlamaIndexAgent 的实现。RAGAgentBase 实现了基本的Agent流程,LlamaIndexAgent 继承了 RAGAgentBase,并实现了 init_rag 函数来实现差异化的RAG框架。
所有的参数都在配置文件中配置,虽然方便,但也导致了代码的可读性变差,新手很容易被一大堆的配置吓退。其实这些配置参数都可以直接当作参数传给类。
完整代码可参考:https://github.com/modelscope/agentscope/tree/main/examples/conversation_with_RAG_agents/rag
如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~
- 大家好,我是 同学小张,持续学习C++进阶、OpenGL、WebGL知识技能和AI大模型应用实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- +v: jasper_8017 一起交流💬,一起进步💪。
- 微信公众号搜【同学小张】 🙏
本站文章一览: