前几天,学习了langchain官方文档里的第一个例子“语义搜索”,这是官网的第二个示例,使用LangChain构建一个RAG智能体。链接在这里:https://docs.langchain.com/oss/python/langchain/rag
目标
这个示例,将读取一个博客网站,将文本进行分块处理,保存在向量库。用户发起对话之后,先从向量库中检索,再用大模型回答,这就是一个简单的RAG(检索增强生成)智能体。
获取网页
先安装bs4模块。
pip install bs4
这里使用WebBaseLoader加载网页,使用BeautifulSoup提取关心的内容,得到LangChain里的Document列表。这个Loader与以前的PdfLoader有相同的基类BaseLoader。
import bs4
from langchain_community.document_loaders import WebBaseLoader
# 从HTML中只保留我们关心的主要内容
bs4_strainer = bs4.SoupStrainer(class_=("post-title", "post-header", "post-content"))
loader = WebBaseLoader(
web_paths=["http://shenlb.me/gtd/"],
bs_kwargs={"parse_only": bs4_strainer},
)
# 读网页,生成List[Document]
docs = loader.load()
assert len(docs) == 1
print(f"文章字符数: {len(docs[0].page_content)}")
print(docs[0].page_content[:200])
文本分隔
如果文章很长,将所有内容直接送给大模型,大模型消化不了,所以需要分块处理。
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=5000, chunk_overlap=500, add_start_index=True
)
all_splits = text_splitter.split_documents(docs)
print("chunks: ", len(all_splits))
向量嵌入
这里访问阿里的文本嵌入模型,查询每一个文本块,得到一个1024维的浮点数向量,存入到chroma向量数据库中,以备后面的检索之用。
# 阿里百炼 Embeddings 适配器
class DashScopeEmbeddings(Embeddings):
def __init__(
self,
api_key: str,
model: str = "text-embedding-v4",
dimensions: int = 1024,
):
self.client = OpenAI(
api_key=api_key,
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
self.model = model
self.dimensions = dimensions
def embed_documents(self, texts: list[str]) -> list[list[float]]:
response = self.client.embeddings.create(
model=self.model,
input=texts,
dimensions=self.dimensions,
)
return [item.embedding for item in response.data]
def embed_query(self, text: str) -> list[float]:
response = self.client.embeddings.create(
model=self.model,
input=[text],
dimensions=self.dimensions,
)
return response.data[0].embedding
# 阿里的文本嵌入模型
embeddings = DashScopeEmbeddings(
api_key=os.getenv("DASHSCOPE_API_KEY"),
model="text-embedding-v4",
dimensions=1024,
)
vector_store = Chroma(
collection_name="gtd",
embedding_function=embeddings,
persist_directory="./gtd_chroma_db",
)
if vector_store._collection.count() == 0:
print("首次运行,写入网页向量库...")
batch_size = 10 # 百炼 embedding 限制
for i in range(0, len(splits), batch_size):
vector_store.add_documents(splits[i:i + batch_size])
else:
print("向量库已存在,直接使用")
RAG智能体
先定义一个工具,智能体可以调用这个工具,得到一些信息片段。
@tool 修饰符把一个普通的 Python 函数,声明成了“可以被大模型调用的工具”。
下面的@tool声明之后,用来告诉大模型:有一个工具,名字叫 retrieve_context,你在需要的时候可以用。
“content_and_artifact"意思是说有对话文本内容,还有原始素材。
docstring注释也是必须的,告诉大模型这个工具的用途。否则,会报ValueError: Function must have a docstring if description not provided.错误。
@tool(response_format="content_and_artifact")
def retrieve_context(query: str):
"""检索向量库,改善回答的准确性和可靠性。"""
docs = vector_store.similarity_search(query, k=3)
combined = "\n\n".join(
f"来源: {doc.metadata}\n内容: {doc.page_content}"
for doc in docs
)
return combined, docs
下面可以定义一个智能体,智能体会自动调用这个工具,完成回答任务。
llm = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
from langchain.agents import create_agent
tools = [retrieve_context]
prompt = (
"你是AI智能体,可以访问一些工具,获取某个博客文章的内容。"
"辅助问答用户的问题。"
"请严格基于原始素材,不要编造,并将答案控制在500字以内。"
)
agent = create_agent(llm, tools, system_prompt=prompt)
query = "什么是GTD?"
result = agent.invoke(
{"messages": [{"role": "user", "content": query}]}
)
print(result["messages"][-1].content)
上面智能体的内部是一个黑盒,我们不知道中间工具的调用情况,可以使用流式执行(Streaming),观察中间的一些运行细节。
智能体每往前走一步,会输出一些中间状态发给你。event里会不断地追加消息,所以event["messages"][-1]就是最新的一条消息。
for event in agent.stream(
{"messages": [{"role": "user", "content": query}]},
stream_mode="values",
):
event["messages"][-1].pretty_print()
可以看到如下的输出:
================================ Human Message =================================
什么是GTD?
================================== Ai Message ==================================
Tool Calls:
retrieve_context (call_9a847bb3974b45eca3a046)
Call ID: call_9a847bb3974b45eca3a046
Args:
query: GTD 时间管理方法
================================= Tool Message =================================
Name: retrieve_context
Source: {'source': 'http://shenlb.me/gtd/'}
Content: GTD是英文Getting Things Done的缩写,是一种行为管理的方法,也是David Allen写的一本书的书名。……
================================== Ai Message ==================================
GTD(Getting Things Done)是大卫·艾伦(David Allen)提出的一种时间与自我管理系统,核心理念是“把 头脑中的任务清空到外部系统中”,从而减少焦虑、提升专注力与执行力。……
根据用户的问题,智能体可以在内部多次调用工具,来得到更好的答案。比如把问题改为:
query = (
"什么是GTD?"
"当你得到答案之后,再进行第二次查询,详细解释一下这个概念里的每一个步骤。"
)
事件里就可以看到多个 ToolMessage。
代码里其实还有个地方需要完善,每次都查询网页其实是没必要的,chroma向量库里已经有相关信息了。