【LangChain】P18 LangChain 之 Chain 深度解析(三):基于 LCEL 语法的数据库与文档处理新型 Chain
目录
- 前言
- create_sql_query_chain —— 让语言模型成为你的 SQL 专家
- 环境准备与数据库连接
- 准备大语言模型(LLM)
- 创建并运行 Chain
- 工作原理揭秘
- 高级技巧
- create_stuff_documents_chain —— 文档内容填充的艺术
- 适用场景分析
- 完整实例
- 总结
前言
在 LangChain 的世界里,LangChain 表达式语言(LCEL)的出现无疑是一场革命。它以其标志性的管道符(|
),不仅为我们提供了一种声明式、可组合且可流式传输的方式来构建强大的语言模型应用,还促使许多 LangChain 中与封装的高级 Chains 重新基于 LCEL 重构,使得其内部逻辑更加透明,并赋予了开发者前所未有的灵活性。
本博文将深入探讨两个基于 LCEL 构建的基础且极为强大的 Chain:
create_sql_query_chain
create_stuff_documents_chain
前者是连接自然语言与结构化数据库(Database)的桥梁,我们将使用 MySQL 数据库作为示例。后者则是处理文档内容、进行问答与总结的基础。让我们一同揭开它们的神秘面纱。
create_sql_query_chain —— 让语言模型成为你的 SQL 专家
想象一下,如果你的业务人员或数据分析师不再需要编写复杂的 SQL,只需用自然语言提问,就能从数据库中获取所需数据,这将是多么高效的场景。create_sql_query_chain
正是为此而生,它的核心使命就是将人类的自然语言问题精准地转换成特定方言的 SQL 查询语句。
环境准备与数据库连接
首先,我们需要一个可以交互的数据库。本文将以 MySQL 为例。为了让 LangChain 能够连接并操作 MySQL,请确保你已经安装了必要的 Python 包。
pip install pymysql
接下来,我们使用 LangChain 提供的 SQLDatabase
工具类来连接数据库。这个工具类非常方便,它不仅能执行查询,还能智能地获取数据库的元信息(如表结构、字段名等),为后续 LLM 生成查询提供关键上下文。
注:
假设你的 user 数据库中有一个名为 employees 的表。
from langchain_community.utilities import SQLDatabase
import os# --- 数据库连接配置 ---
# 建议将敏感信息存储在环境变量 .env 中
db_user = "root"
db_password = "abc123"
db_host = "localhost"
db_port = "3306"
db_database = "emp" # 假设我们有一个名为 'emp' 的数据库# 使用 from_uri 方法可以方便地创建连接
# URI 格式: mysql+pymysql://<user>:<password>@<host>:<port>/<database>
db = SQLDatabase.from_uri(f"mysql+pymysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_database}"
)# --- 验证连接 ---
print(f"数据库方言 (Dialect): {db.dialect}")
# get_usable_table_names() 会获取数据库中所有可用的表名
print(f"可用的数据表: {db.get_usable_table_names()}")
# 尝试执行一个简单的SQL查询来验证
employee_count = db.run("SELECT COUNT(*) FROM employees;")
print(f"employees 表中的记录总数: {employee_count}")
准备大语言模型(LLM)
我们需要一个强大的语言模型来理解自然语言并生成 SQL。这里我们使用 Deepseek-R1 的模型作为示例,相关内容已封装在 .env
文件中。
import dotenv
from langchain_openai import ChatOpenAI# 加载 .env 文件中的环境变量
dotenv.load_dotenv()os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
os.environ["OPENAI_BASE_URL"] = os.getenv("OPENAI_BASE_URL")# 初始化模型
llm = ChatOpenAI(model=os.getenv("CHAT_MODEL")
)
创建并运行 Chain
现在,万事俱备。创建 create_sql_query_chain
实例非常简单,只需将 LLM 和数据库连接实例传递给它即可。
from langchain.chains import create_sql_query_chain# 创建 Text-to-SQL Chain
sql_chain = create_sql_query_chain(llm, db)# 发起调用
question = "数据表employees中有多少员工?"
response = sql_chain.invoke({"question": question})print("自然语言问题:", question)
print("生成的SQL查询:", response)
输出的SQL语句如下。可以看到,Chain 成功地将我们的问题转换为了准确的 SQL 查询。
自然语言问题: 数据表employees中有多少员工?
生成的SQL查询: SELECT count(*) FROM employees
工作原理揭秘
create_sql_query_chain
的背后,是一个精心设计的 Prompt。当你调用它时,它会:
- 自动从
db
对象中提取数据库的表结构信息(表名、字段、类型等)。 - 将这些结构信息连同你的问题(question)一起,填充到一个为 Text-to-SQL 任务优化的 Prompt 模板中。
- 将这个完整的 Prompt 发送给 LLM,LLM 则基于这些上下文生成最终的 SQL 语句。
高级技巧
如果你的数据库中有很多表,但某个查询只涉及其中一两个,为了提高准确性并节省 Token,你可以在调用时使用 table_names_to_use
参数指定表的范围。
response_with_tables = sql_chain.invoke({"question": "列出部门'IT'中所有员工的名字","table_names_to_use": ["employees", "departments"]
})
这只是第一步。在实际应用中,你可以将这个 sql_chain
的输出(即 SQL 字符串)通过管道传递给 db.run
来执行,从而构成一个完整的“自然语言提问 -> SQL生成 -> SQL执行 -> 返回结果”的完整流程。这就是 LCEL 的魅力所在!
create_stuff_documents_chain —— 文档内容填充的艺术
当我们处理 RAG(检索增强生成)或文档问答任务时,经常需要将检索到的多个文档片段整合起来,作为上下文提供给 LLM。create_stuff_documents_chain
是实现这一目标最直接、最基础的方法。
它的工作方式正如其名——“Stuff”(填充)。它会粗暴但有效地将所有文档的内容(page_content
)连接成一个单一的长文本,然后将其插入到 Prompt 的指定位置。
适用场景分析
优点:
- 上下文完整: LLM 在一次调用中就能看到所有相关文档的全部内容,非常适合需要全局理解才能完成的任务,如对一组报告进行全面总结。
- 实现简单: 逻辑清晰,易于理解和调试。
缺点:
- 上下文窗口限制: 这是其最大的局限。如果所有文档的总长度超过了 LLM 的上下文窗口限制,调用将会失败。因此,它只适合处理少量或中等长度的文档。
完整实例
让我们构建一个完整的例子,用 create_stuff_documents_chain
来总结几篇关于人工智能的短文。
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains.combine_documents import create_stuff_documents_chain# --- Step 1: 准备文档 ---
# 在实际应用中,这些文档通常来自一个 Retriever 的检索结果
docs = [Document(page_content="LangChain是一个用于开发由语言模型驱动的应用程序的框架。它提供了模块化的组件和链式调用的思想,极大地简化了复杂应用的构建。"),Document(page_content="LCEL,即LangChain表达式语言,是LangChain的核心。它允许开发者通过管道符 `|` 灵活地组合不同的组件,代码更简洁,可读性更强。"),Document(page_content="RAG(检索增强生成)是当前LLM应用的主流范式,它将外部知识库的检索与LLM的生成能力相结合,有效减少了模型幻觉。")
]# --- Step 2: 定义 Prompt ---
# 这个 Prompt 必须包含一个变量,用于接收被“填充”后的文档内容。
# 我们将其命名为 "context"。
prompt_template_str = """
请根据以下上下文内容,写出一个简洁的总结:上下文:
{context}总结:
"""
prompt = ChatPromptTemplate.from_template(prompt_template_str)# --- Step 3: 创建并运行 Chain ---
# 注意这里的三个核心参数:
# 1. llm: 我们之前已经初始化好的大模型实例
# 2. prompt: 刚刚创建的提示词模板
# 3. document_variable_name: 这个参数的值必须与 prompt 中定义的变量名("context")完全一致!
stuff_chain = create_stuff_documents_chain(llm=llm,prompt=prompt,document_variable_name="context" # 关键参数
)# --- 发起调用 ---
# 输入是一个字典,key 必须与 prompt 中的变量名匹配,value 是我们的文档列表
result = stuff_chain.invoke({"context": docs})print(result)
大模型输出如下。这个例子清晰地展示了 create_stuff_documents_chain
的工作流程:
- 它接收一个文档列表,将它们的内容合并
- 然后作为一个名为
context
的变量传递给 Prompt - 最后由 LLM 完成指定的任务
读者可能对这个的感觉并不深,但是等后期博文涉及到 RAG 部分时,我们会深度应用。
LangChain是一个利用LCEL(LangChain表达式语言)来简化语言模型应用开发的框架。它通过模块化组件和管道式组合,使得构建如RAG(检索增强生成)这类先进应用变得更加高效和直观。RAG结合了外部知识检索与模型生成,有效提升了回答的准确性。
总结
create_sql_query_chain
和 create_stuff_documents_chain
体现了 LangChain LCEL 的设计哲学:将复杂任务拆解为一系列标准化的、可组合的步骤。
create_sql_query_chain
: 为我们打通了从自然语言到结构化数据的查询路径,是构建企业级智能数据应用的关键一环。create_stuff_documents_chain
: 则是处理文档集合的基础工具,是构建文档问答、摘要等功能的核心组件之一。
理解并掌握这两个基础 Chain,你就能开启构建更复杂、更强大应用的大门。例如,你可以将一个 create_retrieval_chain
(检索链)的输出直接用管道符传递给 create_stuff_documents_chain
,轻松构建起一个完整的 RAG 应用。
LCEL 的世界充满了无限可能,期待你在实践中探索出更多创新的组合!
2025.10.09 金融街