simple_rag
将P1~P7的代码整合,我们可以得到一个简单的RAG demo。
import os
# --- 基础组件 ---
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.documents import Document # 主要用于类型提示和理解
# --- 嵌入和向量存储 ---
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma
# --- 设置 DeepSeek API Key ---
# 确保你的 API Key 是有效的
os.environ["DEEPSEEK_API_KEY"] = 'sk-6d65661014664acd8267025af3bfe925' # 替换成你的真实有效 Key
# --- LLM 初始化 ---
def get_deepseek_key():
key = os.getenv('DEEPSEEK_API_KEY')
if key is None or not key.startswith('sk-'): # 简单校验
raise ValueError(
"DEEPSEEK_API_KEY 未在环境变量中找到或格式不正确。请确保它是有效的 sk- 开头的密钥。")
return key
def create_deepseek_llm():
api_key = get_deepseek_key()
return ChatDeepSeek(
model="deepseek-chat",
temperature=0.1, # RAG 应用中通常希望答案更具确定性
max_tokens=1024,
api_key=api_key
)
# --- 嵌入模型初始化 (与你之前的代码相同) ---
def initialize_embedding_model():
print("\n--- 初始化嵌入模型 ---")
model_name = "BAAI/bge-small-zh-v1.5" # 确保这个模型能被下载和加载
model_kwargs = {'device': 'cuda'} # 改为 'cpu' 以增加通用性,如果 GPU 可用且配置好,可以改回 'cuda'
encode_kwargs = {'normalize_embeddings': True}
try:
embeddings = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
print(f"HuggingFace 嵌入模型 '{model_name}' 初始化成功。")
return embeddings
except Exception as e:
print(f"初始化嵌入模型时出错: {e}")
print("请确保 sentence-transformers 和 PyTorch (CPU 或 GPU 版本) 已正确安装。")
return None
# --- 加载 Chroma 向量数据库 (与你之前的代码相同) ---
def load_chroma_vector_store(embedding_model, persist_directory="./my_chroma_data"):
if not embedding_model:
print("嵌入模型未初始化,无法加载 Chroma 数据库。")
return None
if not os.path.exists(persist_directory) or not os.listdir(persist_directory):
print(f"持久化目录 '{persist_directory}' 不存在或为空。请先运行创建数据库的脚本。")
return None
print(f"\n--- 从 '{persist_directory}' 加载 Chroma 数据库 ---")
try:
vector_store = Chroma(
persist_directory=persist_directory,
embedding_function=embedding_model,
collection_name="my_knowledge_base" # 确保与创建时一致
)
print("Chroma 数据库加载成功。")
return vector_store
except Exception as e:
print(f"加载 Chroma 数据库时出错: {e}")
return None
# --- 辅助函数:格式化检索到的文档 ---
def format_docs(docs: list[Document]) -> str:
"""将检索到的文档列表格式化为单一字符串上下文。"""
if not docs:
return "没有找到相关信息。"
return "\n\n".join(f"来源 {i+1} (ID: {doc.metadata.get('doc_id', '未知')}):\n{doc.page_content}"
for i, doc in enumerate(docs))
# --- 构建和测试 RAG 链 ---
def test_rag_chain(llm_instance, retriever_instance):
print("\n--- 测试 RAG 链 ---")
# 1. 定义 RAG 提示模板
rag_template = """
你是一个问答助手。请根据下面提供的“已知信息”来回答用户提出的“问题”。
你需要:
1. 仔细阅读“已知信息”,只依据这些信息回答问题。
2. 如果“已知信息”中没有相关内容来回答问题,请明确说明“根据我所掌握的信息,无法回答这个问题”,不要编造答案。
3. 你的回答应该简洁明了。
已知信息:
{context}
问题: {question}
回答:
"""
rag_prompt = ChatPromptTemplate.from_template(rag_template)
# 2. 构建 RAG 链 (使用 LCEL)
# RunnablePassthrough() 会将 invoke 的输入原样传递下去。
# 我们期望 invoke 的输入是一个字符串 (用户的问题)。
# RunnableLambda(lambda x: x) 也是一种方式来明确表示传递输入。
# retriever 希望输入是字符串(查询),输出是 Document 列表
# format_docs 希望输入是 Document 列表,输出是字符串
# rag_prompt 希望输入是字典 {"context": str, "question": str}
# llm 希望输入是 PromptValue 或 Message 列表
# StrOutputParser 希望输入是 AIMessage,输出是字符串
rag_chain = (
# RunnablePassthrough 将整个输入字典传递下去
# 我们也可以只传递问题给 retriever,并将原始问题保留下来
# 使用 RunnablePassthrough.assign 来创建新的键,或者使用字典推导来构建
{
"context": RunnableLambda(lambda user_input_dict: user_input_dict["question"]) | retriever_instance | RunnableLambda(format_docs),
"question": RunnableLambda(lambda user_input_dict: user_input_dict["question"])
}
| rag_prompt
| llm_instance
| StrOutputParser()
)
# 也可以这样写,如果 invoke 的输入直接是问题字符串:
# rag_chain_alternative = (
# {"context": retriever_instance | RunnableLambda(format_docs), "question": RunnablePassthrough()}
# | rag_prompt
# | llm_instance
# | StrOutputParser()
# )
# invoke 的时候直接 rag_chain_alternative.invoke("你的问题")
# 3. 测试 RAG 链
questions_to_ask = [
"LangChain 的主要价值是什么?",
"DeepSeek 是由哪家公司开发的?",
"文本分割在处理长文本时有什么用?",
"什么是机器学习?" # 这个问题应该在知识库中找不到答案
]
for q in questions_to_ask:
print(f"\n用户问题: {q}")
try:
# 我们链的输入现在期望是一个字典,因为我们是这样构造的
answer = rag_chain.invoke({"question": q})
# 如果用 rag_chain_alternative,则:
# answer = rag_chain_alternative.invoke(q)
print(f"AI 回答: {answer}")
except Exception as e:
import traceback
print(f" RAG 链执行失败: {e}")
traceback.print_exc()
if __name__ == '__main__':
# 步骤 A: 初始化 LLM
deepseek_llm = create_deepseek_llm()
if not deepseek_llm:
print("DeepSeek LLM 初始化失败,程序退出。")
exit()
# 步骤 B: 初始化嵌入模型
embeddings = initialize_embedding_model()
if not embeddings:
print("嵌入模型初始化失败,程序退出。")
exit()
# 步骤 C: 加载向量数据库
# 确保 "./my_chroma_data" 目录存在并且包含你之前创建并填充的数据
# 如果这是第一次运行有关 Chroma 的脚本,你需要先运行一个脚本来创建和填充它。
# 假设你已经有一个脚本(比如你之前提供的那个)可以创建 `my_chroma_data`。
chroma_vector_store = load_chroma_vector_store(embeddings, persist_directory="./my_chroma_data")
if not chroma_vector_store:
print("未能加载向量数据库。请确保 './my_chroma_data' 目录已正确创建并包含数据。")
print("你可以运行之前的 Chroma 创建脚本来生成它。")
exit()
# 步骤 D: 从 VectorStore 创建 Retriever
# 你可以根据需要配置 search_kwargs
knowledge_retriever = chroma_vector_store.as_retriever(
search_kwargs={"k": 3} # 检索3个最相关的文档块
)
print(f"\n已从 Chroma 数据库创建 Retriever (k=3)。")
# 步骤 E: 测试 RAG 链
test_rag_chain(deepseek_llm, knowledge_retriever)
# (你之前的 test_simple_sequential_chain() 可以保留或注释掉)
# print("\n--- 分割线,运行旧的 SimpleSequentialChain 测试 ---")
# test_simple_sequential_chain()
TODO(AI指导)
至此,我们已经快速过了一遍LangChain的内容。 LangChain总的来说是对于大模型应用控制的各方面操作的抽象总结或封装,如LLM接口、提示管理、记忆、数据连接、链、代理、数据处理等),是统一处理框架。 解耦、组件化做的很好,各组件功能基本都可以单独使用。
LangChain是学习大模型应用开发能力的敲门砖,它为我们提供了结构化的方法来构建LLM应用。
接下来,我们应该深入核心组件与概念,针对具体应用场景构建知识体系。 比如不同类型的提示技巧:零样本提示 、少样本提示、思维链提示、自我一致性 、指令调优、角色扮演提示... 还有很多问题细节的解决方法:如引导性偏见、模糊指令、过长提示等...
- 巩固基础,深化理解: 重新过一遍你已经学过的 LangChain 组件,确保你理解了它们的核心作用和参数。
- 死磕提示工程: 这是核心技能,投入时间学习和实践。
- 深入 RAG: 重点学习嵌入模型选型、高级 Retriever 策略和评估方法。构建一个完整的 RAG 项目。
- 掌握 LCEL: 这是 LangChain 未来的方向,用它来重构和构建你的链。
- 探索 Agents: 当你对链和 RAG 有了扎实的掌握后,可以开始尝试构建 Agent 应用。
- 持续实践和关注新技术: AI 领域发展迅速,保持学习的热情和好奇心。