Hcc的Blog

嵌入式 · AI · 折腾不止

0%

LangChain 学习(6) LangChain 实现简易 RAG 以及 LangSmith

LangChain RAG

  RAG(Retrieval-Augmented Generation,检索增强生成)让 AI 能够基于你的私有文档回答问题,不需要微调模型,只需将文档向量化存储,Agent 就能检索相关内容来回答。普通的大模型智能回答训练数据中有的内容,如果你的文档是私有的(公司内部文档、个人笔记),模型就不知道。RAG 解决了这个问题,其工作流程如下:

  • 离线阶段:将文档切分成小块 -> 用 Embedding 模型转换为向量 -> 存入向量数据库。
  • 在线阶段:用户提问 -> 将问题转为向量 -> 在向量数据库中搜索最相似内容 -> 将检索到的内容作为上下文发给模型 -> 模型基于检索内容回答

  环境准备,打开终端输入如下命令

1
pip install langchain-deepseek langchain-chroma chromadb

接入 Embedding 模型

  我们使用百炼的 API,初始化代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
from dotenv import load_dotenv
import os
from langchain_community.embeddings import DashScopeEmbeddings
load_dotenv()

def embedding_init():
api_key = os.getenv("BAILIAN_API_KEY") # 百炼的 API Key
embeddings = DashScopeEmbeddings(
model="qwen3.7-text-embedding",
dashscope_api_key=api_key,
)
return embeddings

  在主文件中写下如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from model_init import embedding_init
# OpenAI 的文本嵌入模型
# 将文本转换为向量(一组浮点数)

embeddings = embedding_init()

# 测试:将一段文本转为向量
text = "菜鸟教程 RUNOOB 是一个编程学习平台"
input = {"input":f"{text}"}
vector = embeddings.embed_query(text)

print(f"文本: {text}")
print(f"向量维度: {len(vector)}")
print(f"向量前 5 个值: {vector[:5]}")

使用 ChromaDB 创建持久化存储

  示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from model_init import embedding_init
from langchain_chroma import Chroma
# OpenAI 的文本嵌入模型
# 将文本转换为向量(一组浮点数)

embeddings = embedding_init()

vector_store = Chroma(
collection_name="runoob_docs",
embedding_function=embeddings,
persist_directory="./chroma_db"
)

# 添加文档(最简单的形式:文本列表)
texts = [
"菜鸟教程(RUNOOB)是一个免费的编程学习网站,提供 HTML、CSS、JavaScript、Python 等教程。",
"Python3 基础教程共 30 章,适合零基础入门,包含环境搭建、语法基础、面向对象等内容。",
"HTML 基础教程共 25 章,覆盖 HTML 标签、表单、多媒体等基础知识。",
]

# add_texts 自动将文本转为向量并存储
vector_store.add_texts(texts)

print(f"已添加 {len(texts)} 个文档到向量存储")

语义检索

  使用 similarity_search 进行语义相近搜索:

1
2
3
4
results = vector_store.similarity_search(
"我想学 Python,有什么教程推荐?",
k=2, # 返回最相似的 2 个结果
)

创建 Retriever 检索器

  Retriever 是 Vector Store 的标准化接口:

1
2
3
4
5
6
7
8
9
10
# 从 vector_store 创建 retriever
retriever = vector_store.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 3}, # 返回前 3 个结果
)

# 使用 retriever
docs = retriever.invoke("Python 学习路线")
for doc in docs:
print(f"- {doc.page_content[:60]}...")

LangChain 文本加载与切分

  LangChain 提供了多种文档加载器,覆盖了常见的文件格式,如下:

Loader 来源 安装包
TextLoader .txt langchain
PyPDFLoader PDF 文件 langchain-community + pypdf
WebBase 网页 URL langchain-community + beautifulsoup4
CSVLoader CSV 文件 langchain-community
UnstructuredMarkdownLoader Markdown 文件 langchain-community + unstructured

  示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 加载文本文件(内置,无需额外安装)
from langchain_community.document_loaders import TextLoader

loader = TextLoader("knowledge.txt", encoding="utf-8")
docs = loader.load()

print(f"加载了 {len(docs)} 个文档")
print(f"内容预览: {docs[0].page_content[:150]}...")

# 加载网页
# pip install langchain-community beautifulsoup4
from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader("https://www.runoob.com/python/python-tutorial.html")
docs = loader.load()
print(f"\n网页内容: {docs[0].page_content[:150]}...")

  文档通常太长,需要切分成小块(chunk)才能有效检索。切分策略将直接影响 RAG 效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 创建切分器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块最多 500 个字符
chunk_overlap=50, # 块之间重叠 50 个字符
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""],
# 优先按段落分割,然后是句子,最后是字符
)

# 示例文档
long_text = """菜鸟教程(RUNOOB)是一个免费的编程学习平台。

平台提供了丰富的编程语言教程,包括但不限于:
- Python 教程:从基础语法到数据分析
- Java 教程:面向对象编程到 Spring 框架
- 前端教程:HTML、CSS、JavaScript 及其框架

所有教程都配有详细的代码示例和在线运行环境。
学习者可以通过边学边练的方式快速掌握编程技能。"""

# 切分文档
chunks = text_splitter.split_text(long_text)

print(f"原文长度: {len(long_text)} 字")
print(f"切分后: {len(chunks)} 块\n")

for i, chunk in enumerate(chunks):
print(f"--- 块 {i+1} ({len(chunk)} 字) ---")
print(chunk)
print()

  chunk_overlap 很重要。如果块之间没有重叠,一个完整的句子可能被切成两半,导致检索时遗漏关键信息。50-100 字符的重叠是常见的设置。

LangSmith

  LangSmith 是 LangChain 官方的可观测性平台,可以帮我我们追踪 Agent 的执行过程、监控性能以及调试问题。当 Agent 在后台运行时,我们看不到它内部发生了什么 ———— 调用了哪些模型、执行了哪些工具、每一步消耗了多少 Token。LangSmith 解决了这个”黑盒”问题。我们现在 .env 中写下如下内容:

1
2
3
4
# .env 文件
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=lsv2_pt_your_key_here
LANGCHAIN_PROJECT=my-agent-project

  示例代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from dotenv import load_dotenv
load_dotenv() # LangSmith 配置会自动加载

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain.tools import tool

# 设置环境变量后,所有 Agent 执行都会自动追踪
# 无需额外代码!

@tool
def search_course(keyword: str) -> str:
"""搜索课程"""
return f"搜索结果:{keyword} 相关课程"

model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[search_course],
system_prompt="你是菜鸟教程 RUNOOB 的助手。",
)

# 这次执行会被自动记录到 LangSmith
result = agent.invoke({
"messages": [HumanMessage(content="搜索 Python 课程")]
})

# 打开 https://smith.langchain.com 查看追踪记录
print("已完成,请到 LangSmith 控制台查看追踪详情")

  运行完成后,我们在 LangSmith 控制台中,可以看到每次 Agent 执行的完整轨迹:

  • 执行时间线:模型调用 → 工具调用 → 模型再调用的完整时间线
  • 输入/输出:每一步的输入消息和模型返回结果
  • Token 用量:每次模型调用的 Token 消耗和费用估算
  • 延迟分析:每一步的耗时分布
  • 错误信息:如果某步出错,可以看到完整的错误堆栈

  如下图所示
  

图 1 层级图及追踪效果