新手入门:用 LangChain+LlamaIndex 构建 RAG,通义千问 API 免费够用
文章目录
- 一、环境准备:从虚拟环境到 API 配置
- 1.1 用 Conda 创建独立虚拟环境
- 1.2 配置通义千问 API Key(免费获取)
- 二、用 LangChain 构建 RAG:灵活定制版
- 2.1 完整代码与步骤解析
- 2.2 运行与验证
- 三、用 LlamaIndex 构建 RAG:低代码版
- 3.1 核心优势
- 3.2 完整实现代码
- 3.3 新手必看提示
- 四、两种方案对比与总结
- 后续进阶方向
在 RAG(检索增强生成)实践中,新手常被两个问题困扰:一是 API 成本高,二是代码配置复杂。本文选择 阿里通义千问 API(注册即送免费额度),结合 LangChain(灵活定制)和 LlamaIndex(低代码)两大框架,手把手教你搭建可运行的 RAG 系统,全程避开复杂运维,适合零基础入门。
一、环境准备:从虚拟环境到 API 配置
1.1 用 Conda 创建独立虚拟环境
为避免依赖冲突,先创建专门的 RAG 虚拟环境(Python 3.12.7 兼容性好,推荐新手使用):
# 1. 创建虚拟环境
conda create --name all-in-rag python=3.12.7 -y
# 2. 激活环境(Windows用:conda activate all-in-rag)
conda activate all-in-rag
# 3. 进入项目目录(自行替换为你的路径)
cd code
# 4. 安装依赖(后续会用到的库一次性装齐)
pip install -r requirements.txt
1.2 配置通义千问 API Key(免费获取)
通义千问的 API 密钥是调用模型的关键,步骤如下:
-
获取密钥:
访问阿里云 DashScope 控制台,注册后进入「模型广场」,选择免费的qwen-turbo
系列模型(如qwen-turbo-2025-07-15
),再到「秘钥管理」创建 API 密钥(保存好,只显示一次)。 -
配置环境变量:
用vim
编辑 Shell 配置文件,将密钥注入环境变量(避免硬编码):# 1. 打开配置文件(Linux/Mac通用) vim ~/.bashrc # 2. 按「i」进入编辑模式,在文件末尾添加(替换[你的密钥]) export DASHSCOPE_API_KEY=[你的通义千问API密钥] # 3. 按「Esc」,输入「:wq」保存退出 # 4. 让配置立即生效 source ~/.bashrc
-
验证配置:
输入echo $DASHSCOPE_API_KEY
,若显示你的密钥,则配置成功。
二、用 LangChain 构建 RAG:灵活定制版
LangChain 适合想深入理解 RAG 流程的新手,我们会按「数据准备→索引构建→检索→生成」四步实现,全程用内存向量存储(无需部署数据库,新手友好)。
2.1 完整代码与步骤解析
创建langchain_rag.py
文件,代码逐段解析如下:
# 1. 导入依赖库
import os
from dotenv import load_dotenv
# 加载Markdown文档(处理格式更友好)
from langchain_community.document_loaders import UnstructuredMarkdownLoader
# 文本分块工具
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 中文嵌入模型(轻量、免费、中文优化)
from langchain_huggingface import HuggingFaceEmbeddings
# 内存向量存储(无需部署,适合新手)
from langchain_core.vectorstores import InMemoryVectorStore
# 提示词模板
from langchain_core.prompts import ChatPromptTemplate
# 通义千问的LangChain封装
from langchain_community.chat_models import ChatTongyi# 加载环境变量(读取DASHSCOPE_API_KEY)
load_dotenv()# 2. 数据准备:加载本地Markdown文档
# 替换为你的MD文件路径(如技术手册、读书笔记)
markdown_path = "../../data/C1/markdown/easy-rl-chapter1.md"
# 加载文档(UnstructuredMarkdownLoader支持解析MD格式)
loader = UnstructuredMarkdownLoader(markdown_path)
docs = loader.load()
print(f"加载到文档数量:{len(docs)}") # 新手可验证是否加载成功# 3. 文本分块:拆分长文档(避免语义断裂)
# 默认参数:chunk_size=4000(每块4000字符)、chunk_overlap=200(块间重叠200字符)
text_splitter = RecursiveCharacterTextSplitter()
chunks = text_splitter.split_documents(docs)
print(f"分块后文本片段数量:{len(chunks)}") # 验证分块效果# 4. 索引构建:文本转向量并存储
# 选择中文友好的轻量嵌入模型(BAAI/bge-small-zh-v1.5)
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5",model_kwargs={'device': 'cpu'}, # 无需GPU,CPU即可运行encode_kwargs={'normalize_embeddings': True} # 向量归一化,提升检索精度
)
# 构建内存向量存储
vectorstore = InMemoryVectorStore(embeddings)
vectorstore.add_documents(chunks)
print("向量索引构建完成")# 5. 检索:根据问题找相关文档
user_question = "文中举了哪些例子?" # 你的查询问题
# 召回Top3最相关的文本片段(k值不宜过大,避免冗余)
retrieved_docs = vectorstore.similarity_search(user_question, k=3)
# 合并检索结果为上下文(双换行分隔,让LLM清晰区分片段)
context = "\n\n".join(doc.page_content for doc in retrieved_docs)# 6. 生成:调用通义千问生成回答
# 提示词模板(约束LLM仅用上下文回答,避免幻觉)
prompt = ChatPromptTemplate.from_template("""
请严格根据以下上下文回答问题,不添加外部知识:
1. 若上下文有相关信息,条理清晰地总结;
2. 若信息不足,直接回复:“抱歉,无法从上下文找到答案。”上下文:
{context}问题:{question}回答:
""")# 配置通义千问模型
llm = ChatTongyi(model_name="qwen-turbo-2025-07-15", # 模型名(免费版可选qwen-turbo)temperature=0.7, # 随机性:0(严谨)~1(灵活),新手建议0.5-0.7max_tokens=2048, # 最大生成长度,足够回答大多数问题api_key=os.getenv("DASHSCOPE_API_KEY") # 从环境变量读取密钥
)# 调用LLM生成回答(注意:通义千问响应需用.content获取内容)
response = llm.invoke(prompt.format(context=context, question=user_question))
print("\n===== RAG回答结果 =====")
print(response.content)
2.2 运行与验证
-
首次运行注意:会自动下载
BAAI/bge-small-zh-v1.5
模型(约 100MB),若速度慢,可启用 HF 镜像(代码中注释的os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
)。 -
预期输出:类似如下结构化回答(基于easy-rl-chapter1.md内容):
文中举了以下例子: 1. 选择餐馆:“利用”是去熟悉的喜欢的餐馆,“探索”是尝试新餐馆; 2. 做广告:“利用”是用已知最优策略,“探索”是尝试新策略; 3. 挖油:“利用”是在已知地点挖油,“探索”是尝试新地点; 4. 玩《街头霸王》:“利用”是重复固定招式,“探索”是尝试新招式; 5. 小车上山(MountainCar-v0)任务:智能体通过决策到达山顶。
-
实际输出:
content='文中举了以下例子:\n\n1. 羚羊通过试错学会奔跑,适应环境。\n2. 股票交易:通过不断买卖股票,根据市场反馈学习如何最大化奖励。\n3. 玩雅达利游戏(如 Breakout 和 Pong):通过不断试错学习如何通关。\n4. 小车上山(MountainCar-v0)任务:智能体通过决策控制小车到达山顶。\n5. 选择餐馆:利用是指去熟悉的喜欢的餐馆,探索是指尝试新的餐馆。\n6. 做广告:利用是指采用已知最优广告策略,探索是指尝试新策略以获得更好效果。\n7. 挖油:利用是指在已知地点挖油,探索是指在新地点挖油,可能发现更大油田。\n8. 玩《街头霸王》游戏:利用是指一直使用固定策略(如蹲角落出脚),探索是指尝试新招式(如大招)。' additional_kwargs={} response_metadata={'model_name': 'qwen-turbo-2025-07-15', 'finish_reason': 'stop', 'request_id': '7a7457ba-923f-9bbd-823d-f5d96059d552', 'token_usage': {'input_tokens': 6021, 'output_tokens': 202, 'total_tokens': 6223}} id='run--b7ef53bb-98b4-4997-826f-e2554d9f5d7b-0'
- 输出参数解析:
content
: 这是最核心的部分,即大型语言模型(LLM)根据你的问题和提供的上下文生成的具体回答。additional_kwargs
: 包含一些额外的参数,在这个例子中是{'refusal': None}
,表示模型没有拒绝回答。response_metadata
: 包含了关于LLM响应的元数据。token_usage
: 显示了本次调用消耗的token数量,包括完成(completion_tokens)、提示(prompt_tokens)和总量(total_tokens)。model_name
: 使用的LLM模型名称,当前是deepseek-chat
。system_fingerprint
,id
,service_tier
,finish_reason
,logprobs
: 这些是更详细的API响应信息,例如finish_reason: 'stop'
表示模型正常完成了生成。
id
: 本次运行的唯一标识符。usage_metadata
: 与response_metadata
中的token_usage
类似,提供了输入和输出token的统计。
三、用 LlamaIndex 构建 RAG:低代码版
LlamaIndex 对 RAG 流程做了更高封装,无需手动拆分 “检索 - 生成” 步骤,适合追求效率的新手。
3.1 核心优势
- 低代码:一行代码构建索引,一行代码执行查询;
- 兼容性强:通过
OpenAILike
类适配通义千问的 OpenAI 兼容接口; - 少踩坑:内置文档加载、分块逻辑,无需手动配置。
3.2 完整实现代码
创建llamaindex_rag.py
文件:
import os
# 1. 配置HF国内镜像(加速嵌入模型下载)
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from dotenv import load_dotenv
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
# 适配“类OpenAI API”的LLM类(关键:通义千问支持此格式)
from llama_index.llms.openai_like import OpenAILike
# 中文嵌入模型
from llama_index.embeddings.huggingface import HuggingFaceEmbedding# 加载环境变量
load_dotenv()# 2. 关键配置:通义千问的OpenAI兼容接口
# 原因:LlamaIndex的OpenAILike类需对接此格式接口
os.environ["OPENAI_API_BASE"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"# 3. 全局配置:LLM+嵌入模型
# 3.1 配置通义千问
Settings.llm = OpenAILike(model="qwen-turbo-2025-07-15", # 免费版模型api_key=os.getenv("DASHSCOPE_API_KEY"), # 通义千问密钥api_base=os.getenv("OPENAI_API_BASE"), # 兼容接口地址temperature=0.7,max_tokens=2048,is_chat_model=True # 关键参数:通义千问是聊天模型
)# 3.2 配置嵌入模型(同LangChain,中文友好)
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-zh-v1.5")# 4. 构建索引:一行代码加载文档+生成向量索引
# 替换为你的MD文件路径
docs = SimpleDirectoryReader(input_files=["../../data/C1/markdown/easy-rl-chapter1.md"]).load_data()
index = VectorStoreIndex.from_documents(docs) # 自动完成分块、向量化、存储# 5. 执行查询:一行代码完成检索+生成
query_engine = index.as_query_engine() # 创建查询引擎(封装RAG全流程)
print("\n===== LlamaIndex RAG回答 =====")
# 执行查询并直接打印结果(无需手动处理.content)
print(query_engine.query("文中举了哪些例子?"))
输出为:
文中举了以下例子:1. 选择餐馆:利用是去最喜欢的餐馆,探索是尝试新的餐馆。
2. 做广告:利用是采取最优的广告策略,探索是尝试新的广告策略。
3. 挖油:利用是在已知地方挖油,探索是在新地方挖油。
4. 玩游戏:利用是重复使用一种有效策略,探索是尝试新的招式。
3.3 新手必看提示
- 接口配置错误:若提示 “API 调用失败”,检查
OPENAI_API_BASE
是否为https://dashscope.aliyuncs.com/compatible-mode/v1
(官方最新地址)。 - 模型名选择:免费版通义千问支持
qwen-turbo-2025-07-15
,若想尝试更强模型(如qwen-plus
),需确认 API 密钥是否有对应额度。 - 索引存储:默认存储在内存,重启脚本后需重新构建;若想持久化,可替换为
Milvus
或FAISS
(后续进阶内容)。
四、两种方案对比与总结
维度 | LangChain 实现 | LlamaIndex 低代码实现 |
---|---|---|
代码量 | 需手动写分块、检索逻辑(约 50 行) | 核心逻辑仅 5 行(封装度高) |
灵活性 | 高(可定制分块、检索策略) | 中(适合快速验证,定制需进阶) |
新手友好度 | 中等(需理解 RAG 流程) | 高(几乎零配置) |
适用场景 | 定制化需求(如多模态 RAG) | 快速原型、简单文档查询 |
后续进阶方向
- 向量数据库替换:用
FAISS
(本地)或Milvus
(分布式)替代InMemoryVectorStore
,支持大规模文档; - 检索优化:添加
BM25
关键词检索 + 向量检索的混合策略,提升召回率; - 多模态支持:扩展加载 PDF、图片文档,用 CLIP 模型处理多模态检索。
对于新手,建议先从 LlamaIndex 入手,感受 RAG 的便捷性;再用 LangChain 拆解流程,理解每一步的原理。通义千问的免费额度足够完成入门实践,赶紧动手试试吧!