LangChain基础
demo6-3.py 代码知识点详解
📋 目录
版本信息
代码原始版本分析
这份代码是几年前的旧代码,根据导入语句和 API 使用方式判断:
原始代码对应的 LangChain 版本
- LangChain: 0.2.x 或 0.3.x(2024年5月-9月期间)
- 关键特征:
- 使用
from langchain.chains.sql_database.query import create_sql_query_chain - 使用
from langchain_community.utilities import SQLDatabase - 使用 LCEL (LangChain Expression Language) 语法
- 使用
RunnablePassthrough.assign()方法
- 使用
你当前的环境版本(最新)
根据实际检测:
- langchain: 1.3.1 (2025年最新版本)
- langchain-core: 1.4.0
- langchain-openai: 1.2.1
- langgraph: 1.2.0
⚠️ 重要提示: 代码是旧版本的,但你当前环境是最新的 LangChain 1.x。虽然大部分 API 保持向后兼容,但需要注意一些细微差异。
LangChain 版本演进历史
| 版本 | 发布时间 | 主要变化 |
|---|---|---|
| 0.1.x | 2023年底 | 首个稳定版,基础架构确立 |
| 0.2.x | 2024年5月 | 🔥 重大架构重构: - langchain 与 langchain-community 完全分离- 统一使用 invoke 接口- 引入迁移工具 langchain-cli migrate- 推荐复杂 Agent 使用 LangGraph |
| 0.3.x | 2024年9月 | 🔥 Pydantic v2 升级: - 全面迁移到 Pydantic v2 - 停止支持 Python 3.8 - 更多集成拆分为独立包 - 性能提升数倍 |
| 1.0 | 2025年 | 🎯 生产级框架: - 承诺到 2.0 无破坏性变更 - 核心包精简 80% - 遗留组件移至 langchain-classic- 全新文档站点 |
核心知识点概览
本代码演示了如何使用 LangChain 构建一个 Text-to-SQL 应用,主要涉及以下核心技术点:
- 数据库连接与管理 - SQLDatabase
- SQL 查询生成 - create_sql_query_chain
- SQL 查询执行 - QuerySQLDataBaseTool
- 提示词工程 - PromptTemplate
- 链式编排 - RunnablePassthrough, LCEL (LangChain Expression Language)
- 输出解析 - StrOutputParser
- 大模型集成 - ChatOpenAI (智谱 GLM-4)
- 函数式编程 - Lambda 表达式、itemgetter
详细知识点解析
1. MySQL 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'test_db8'
USERNAME = 'root'
PASSWORD = '123123'
MYSQL_URI = 'mysql+mysqldb://{}:{}@{}:{}/{}?charset=utf8mb4'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
知识点:
-
SQLAlchemy URI 格式:
dialect+driver://username:password@host:port/databasemysql: 数据库方言 (dialect)mysqldb: 驱动名称 (使用 mysqlclient 库)charset=utf8mb4: 支持完整的 Unicode 字符集(包括 emoji)
-
数据库驱动选择:
mysqlclient(mysqldb): C 语言实现,性能最好pymysql: 纯 Python 实现,易于安装mysql-connector-python: Oracle 官方驱动
-
安全提醒:
- ❌ 硬编码密码不安全
- ✅ 应使用环境变量或配置文件管理敏感信息
2. ChatOpenAI 模型初始化
model = ChatOpenAI(
model='glm-4-0520',
temperature=0,
api_key='0884a4262379e6b9e98d08be606f2192.TOaCwXTLNYo1GlRM',
base_url='https://open.bigmodel.cn/api/paas/v4/'
)
知识点:
ChatOpenAI 参数详解:
-
model: 指定使用的模型名称- 这里使用的是智谱 AI 的
glm-4-0520模型 - GLM-4 是智谱 AI 的最新一代基座模型
- 这里使用的是智谱 AI 的
-
temperature: 控制输出的随机性0: 确定性输出,适合需要准确答案的场景(如 SQL 生成)0.7-1.0: 创造性输出,适合创意写作- 范围通常是 0-2
-
api_key: API 认证密钥- 智谱 AI 的 API Key 格式:
{key_id}.{key_secret}
- 智谱 AI 的 API Key 格式:
-
base_url: API 端点地址- 智谱 AI:
https://open.bigmodel.cn/api/paas/v4/ - OpenAI:
https://api.openai.com/v1/ - 硅基流动:
https://api.siliconflow.cn/v1/
- 智谱 AI:
为什么用 ChatOpenAI 调用智谱模型?
- LangChain 的
ChatOpenAI类兼容 OpenAI API 格式的接口 - 智谱 AI、硅基流动等提供商都实现了 OpenAI 兼容的 API
- 无需安装额外的 SDK,统一接口调用不同厂商的模型
⚠️ 安全问题提醒:
# ❌ 当前代码:硬编码 API Key(不安全!)
api_key='0884a4262379e6b9e98d08be606f2192.TOaCwXTLNYo1GlRM'
# ✅ 推荐做法:使用环境变量或配置文件
import os
api_key = os.getenv('GLM_API_KEY')
# 或者使用 YAML 配置文件(参考项目中的 config.yaml)
import yaml
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
api_key = config['glm']['api_key']
3. SQLDatabase 初始化
db = SQLDatabase.from_uri(MYSQL_URI)
知识点:
SQLDatabase 功能:
- 自动 introspect(内省)数据库结构
- 获取表名、列名、数据类型、外键关系等信息
- 提供安全的 SQL 执行接口
- 支持多种数据库:MySQL, PostgreSQL, SQLite, Oracle 等
from_uri 方法:
- 从 SQLAlchemy URI 创建数据库连接
- 内部使用 SQLAlchemy 引擎
- 自动检测数据库类型和版本
数据库元数据获取:
# SQLDatabase 会自动获取以下信息:
- 表列表 (table names)
- 列信息 (column names, types)
- 索引 (indexes)
- 外键关系 (foreign keys)
- 样本数据 (sample rows)
这些信息会被传递给 LLM,帮助其生成准确的 SQL 查询。
4. 创建 SQL 查询链
create_sql = create_sql_query_chain(llm=model, db=db)
知识点:
create_sql_query_chain 函数:
- LangChain 内置的工具链,用于将自然语言转换为 SQL 查询
- 返回一个 Runnable 对象(可执行的链)
内部工作流程:
- 获取数据库 schema 信息
- 构建提示词模板,包含:
- 数据库表结构
- 用户问题
- SQL 语法指导
- 调用 LLM 生成 SQL
- 返回生成的 SQL 字符串
默认提示词模板示例:
You are a MySQL expert. Given an input question, first create a
syntactically correct MySQL query to run, then look at the results
of the query and return the answer to the input question.
Unless the user specifies in the question a specific number of
examples to obtain, query for at most 5 results using the LIMIT
clause as per MySQL. You can order the results to return the most
informative data in the database.
Never query for all columns from a table. You must query only the
columns that are needed to answer the question. Wrap each column
name in backticks (`) to denote them as delimited identifiers.
Pay attention to use only the column names you can see in the
tables below. Be careful to not query for columns that do not exist.
Also, pay attention to which column is in which table.
Pay attention to use CURDATE() function to get the current date,
if the question involves "today".
Use the following format:
Question: Question here
SQLQuery: SQL Query to run
SQLResult: Result of the SQLQuery
Answer: Final answer here
Only use the tables listed below.
{table_info}
Question: {input}
5. SQL 查询执行工具
execute_sql = QuerySQLDataBaseTool(db=db)
知识点:
QuerySQLDataBaseTool:
- LangChain 内置的工具类(Tool)
- 用于安全地执行 SQL 查询
- 继承自
BaseTool,符合 LangChain 工具规范
工具特性:
- ✅ 只读操作(SELECT 查询)
- ❌ 阻止写操作(INSERT, UPDATE, DELETE)
- 限制返回行数(防止大数据量)
- 错误处理和异常捕获
与 Agent 集成:
# 可以在 Agent 中使用
from langchain.agents import initialize_agent
agent = initialize_agent(
tools=[execute_sql],
llm=model,
agent_type="zero-shot-react-description"
)
6. SQL 结果清理(Lambda 表达式)
create_sql = create_sql | (lambda x: x.replace('```sql', '').replace('```', ''))
知识点:
Lambda 表达式:
-
Python 匿名函数:
lambda 参数: 表达式 -
等价于:
def clean_sql(x): return x.replace('```sql', '').replace('```', '')
为什么要清理?
-
LLM 生成的 SQL 可能包含 Markdown 代码块标记:
SELECT COUNT(*) FROM employees; -
需要移除 ````
sql ` 和 `` 才能执行
管道操作符 |:
- LangChain Expression Language (LCEL) 的核心语法
- 表示"串联"操作,前一个的输出作为后一个的输入
- 类似 Unix 管道的概念
7. 答案生成提示词模板
answer_prompt = PromptTemplate.from_template(
"""Given the following user question, corresponding SQL query, and SQL result, answer the user question. 用中文回答最终答案
Question: {question}
SQL Query: {query}
SQL Result: {result}
Answer: """
)
知识点:
PromptTemplate:
- LangChain 的提示词模板类
- 使用
{variable}占位符进行变量替换
from_template 方法:
- 快速创建简单模板
- 自动检测模板中的变量
高级用法 - ChatPromptTemplate:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个数据库专家"),
("human", "{question}")
])
提示词设计要点:
- 角色设定: "用中文回答最终答案"
- 上下文提供: 原始问题 + SQL + 结果
- 明确指令: 告诉模型如何组织答案
Few-shot Learning(少样本学习):
可以添加示例来提高准确性:
"""
Example 1:
Question: 有多少员工?
SQL Query: SELECT COUNT(*) FROM employees
SQL Result: [(100,)]
Answer: 共有 100 名员工。
Now answer the following question:
Question: {question}
...
"""
8. 答案生成链
answer_chain = answer_prompt | model | StrOutputParser()
知识点:
LCEL (LangChain Expression Language):
- LangChain 1.x 的核心特性
- 声明式的方式组合组件
- 支持同步/异步执行、流式输出、并行处理
链的组成:
PromptTemplate → ChatModel → OutputParser
↓ ↓ ↓
格式化提示词 调用LLM生成 解析输出为字符串
StrOutputParser:
- 将 LLM 的输出转换为纯字符串
- 处理不同类型的消息对象(AIMessage, HumanMessage 等)
- 提取
message.content字段
其他常用 Parser:
JsonOutputParser: 解析 JSON 格式输出PydanticOutputParser: 解析为 Pydantic 模型CommaSeparatedListOutputParser: 解析逗号分隔列表
9. 完整链的组装(RunnablePassthrough)
chain = RunnablePassthrough.assign(query=create_sql).assign(result=itemgetter('query') | execute_sql) | answer_chain
知识点:
RunnablePassthrough:
- 传递输入的 Runnable
assign()方法可以添加新的键值对到输入字典
逐步拆解:
# 第 1 步:assign(query=create_sql)
# 输入: {'question': '有多少员工?'}
# 执行 create_sql,得到 SQL 查询
# 输出: {'question': '...', 'query': 'SELECT COUNT(*) FROM employees'}
# 第 2 步:assign(result=itemgetter('query') | execute_sql)
# 从字典中提取 'query' 字段
# 执行 SQL 查询
# 输出: {'question': '...', 'query': '...', 'result': [(100,)]}
# 第 3 步:| answer_chain
# 将完整字典传递给答案生成链
# 输出: "共有 100 名员工。"
itemgetter:
- Python
operator模块的函数 itemgetter('query')等价于lambda x: x['query']- 更高效,推荐用于字典取值
数据流图示:
输入字典
↓
┌─────────────────────────┐
│ assign(query=create_sql) │ ← 生成 SQL
└─────────────────────────┘
↓
{'question': ..., 'query': ...}
↓
┌──────────────────────────────────┐
│ assign(result=itemgetter('query') │ ← 执行 SQL
│ | execute_sql) │
└──────────────────────────────────┘
↓
{'question': ..., 'query': ..., 'result': ...}
↓
┌──────────────┐
│ answer_chain │ ← 生成最终答案
└──────────────┘
↓
最终答案字符串
10. 链的执行
resp = chain.invoke({'question': '请问:一共有多少个员工?'})
print(resp)
知识点:
invoke 方法:
- 同步执行链
- 传入字典作为输入
- 返回最终输出
异步执行:
# 异步版本(适合高并发场景)
resp = await chain.ainvoke({'question': '...'})
流式执行:
# 流式输出版本(实时显示生成过程)
for chunk in chain.stream({'question': '...'}):
print(chunk, end='', flush=True)
批量执行:
# 批量处理多个问题
questions = [
{'question': '有多少员工?'},
{'question': '最高工资是多少?'}
]
results = chain.batch(questions)
🔄 版本差异对比:0.2/0.3 vs 1.x
API 变化对比表
| 特性 | LangChain 0.2/0.3 (代码版本) | LangChain 1.x (当前环境) | 兼容性 |
|---|---|---|---|
| 核心导入 | from langchain_community.utilities import SQLDatabase |
✅ 相同 | ✅ 兼容 |
| SQL 链创建 | create_sql_query_chain(llm, db) |
✅ 相同 | ✅ 兼容 |
| LCEL 语法 | | 管道操作符 |
✅ 相同 | ✅ 兼容 |
| RunnablePassthrough | .assign() 方法 |
✅ 相同 | ✅ 兼容 |
| 模型调用 | ChatOpenAI(...) |
✅ 相同 | ✅ 兼容 |
| 工具类 | QuerySQLDataBaseTool |
✅ 相同 | ✅ 兼容 |
| 包结构 | langchain-community 分离 |
✅ 保持 | ✅ 兼容 |
| Pydantic | v2 (0.3+) | v2 | ✅ 兼容 |
| Agent 推荐 | 开始推荐 LangGraph | 🔥 强烈推荐 LangGraph | ⚠️ 建议迁移 |
主要改进(0.2/0.3 → 1.x)
✅ 向后兼容的部分
- 大部分 LCEL API 保持不变
create_sql_query_chain仍然可用SQLDatabase和工具类接口稳定RunnablePassthrough.assign()继续工作
🔧 需要注意的变化
1. 包依赖管理更严格
# 0.2/0.3 时期:可能一个包搞定
pip install langchain
# 1.x 时期:需要明确安装各个组件
pip install langchain-core
pip install langchain-community # SQLDatabase 在这里
pip install langchain-openai # ChatOpenAI 在这里
2. Agent 构建方式的演进
# 0.2/0.3: 可以使用传统 Agent
from langchain.agents import initialize_agent
# 1.x: 推荐使用 LangGraph
from langgraph.prebuilt import create_react_agent
3. 文档和示例更新
- 官方文档现在以 1.x 为准
- 旧版示例可能标记为 deprecated
- 但核心 API 仍然有效
你的代码在两个版本中的运行情况
# ✅ 这段代码在 0.2、0.3、1.x 中都能正常运行!
# 因为使用的是稳定的 LCEL API
from operator import itemgetter
from langchain.chains.sql_database.query import create_sql_query_chain
from langchain_community.utilities import SQLDatabase
from langchain_community.tools import QuerySQLDataBaseTool
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
# ... 其余代码完全兼容
原因分析:
- ✅ 使用的是 LCEL(LangChain Expression Language),这是从 0.1 引入并持续优化的核心特性
- ✅ 导入路径符合 0.2+ 的模块化架构
- ✅ 没有使用已废弃的旧 API(如
LLMChain、SimpleSequentialChain等) - ✅
create_sql_query_chain是官方推荐的 Text-to-SQL 方式,保持稳定
💡 LCEL 语法深度解析
什么是 LCEL?
LCEL (LangChain Expression Language) 是 LangChain 的核心编排语言,它提供了一种声明式的方式来组合和链式调用各种组件。
核心设计理念:
- 🎯 一切皆 Runnable:所有组件都实现统一的
Runnable接口 - 🔗 管道操作符:使用
|串联组件,类似 Unix 管道 - ⚡ 自动优化:支持异步、流式、并行执行,无需额外代码
LCEL 核心组件体系
1. RunnableLambda - 自定义函数封装
将普通 Python 函数包装成可执行的 Runnable 组件:
from langchain_core.runnables import RunnableLambda
# 定义普通函数
def test1(x: int):
return x + 10
# 包装成 Runnable
r1 = RunnableLambda(test1)
# 使用方式与普通 Runnable 一致
res = r1.invoke(4) # 输出: 14
res = r1.batch([4, 5]) # 批量处理: [14, 15]
应用场景:
- 数据清洗和转换
- 自定义后处理逻辑
- 调试和日志记录
2. 串行执行 - 管道操作符 |
最基本的链式组合方式:
from langchain_core.runnables import RunnableLambda
r1 = RunnableLambda(lambda x: x + 10)
r2 = RunnableLambda(lambda x: x * 2)
# 串行执行:先执行 r1,再执行 r2
chain = r1 | r2
result = chain.invoke(2) # (2 + 10) * 2 = 24
数据流向:
输入 → r1 → r2 → 输出
2 → 12 → 24
3. 并行执行 - RunnableParallel
同时执行多个 Runnable,结果以字典形式返回:
from langchain_core.runnables import RunnableParallel, RunnableLambda
r1 = RunnableLambda(lambda x: x + 10)
r2 = RunnableLambda(lambda x: x * 2)
# 并行执行
chain = RunnableParallel(r1=r1, r2=r2)
result = chain.invoke(2)
# 输出: {'r1': 12, 'r2': 4}
# 控制并发数
result = chain.invoke(2, config={'max_concurrency': 3})
应用场景:
- 同时调用多个 LLM 进行比较
- 并行处理多个数据源
- 提高整体执行效率
4. 数据传递与转换 - RunnablePassthrough
允许数据原样传递,同时可以添加新字段:
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
# 基础用法:原样传递
chain = RunnablePassthrough()
result = chain.invoke({'key': 'value'}) # {'key': 'value'}
# assign():添加新字段
r1 = RunnableLambda(lambda x: {'key1': x})
r2 = RunnableLambda(lambda x: x['key1'] + 10)
chain = r1 | RunnablePassthrough.assign(new_key=r2)
result = chain.invoke(2)
# 输出: {'key1': 2, 'new_key': 12}
# pick():过滤字段(只保留指定字段)
chain = r1 | RunnablePassthrough.assign(new_key=r2) | RunnablePassthrough().pick(['new_key'])
result = chain.invoke(2)
# 输出: {'new_key': 12}
嵌套使用:
# 复杂的嵌套结构
chain = (
r1
| RunnableParallel(
foo=RunnablePassthrough(),
new_key=RunnablePassthrough.assign(key2=r2)
)
)
result = chain.invoke(2)
# 输出: {'foo': {'key1': 2}, 'new_key': {'key2': 12}}
5. 容错机制 - with_fallbacks()
当主节点失败时,自动切换到备用节点:
from langchain_core.runnables import RunnableLambda
r1 = RunnableLambda(lambda x: x + 10)
r2 = RunnableLambda(lambda x: int(x) + 20) # 备用方案
# 设置后备选项
chain = r1.with_fallbacks([r2])
# 正常情况
result = chain.invoke(2) # 12 (使用 r1)
# r1 失败时,自动使用 r2
result = chain.invoke('2') # 22 (r1 报错,回退到 r2)
多级后备:
# 可以有多个后备方案,按顺序尝试
chain = r1.with_fallbacks([r2, r3, r4])
应用场景:
- LLM API 超时或失败时的降级策略
- 不同模型的切换(如 GPT-4 → GPT-3.5)
- 提高系统稳定性
6. 重试机制 - with_retry()
自动重试失败的节点:
from langchain_core.runnables import RunnableLambda
counter = 0
def unstable_function(x):
global counter
counter += 1
print(f'执行了 {counter} 次')
if counter < 3:
raise Exception("临时错误")
return x / counter
# 设置重试策略
r1 = RunnableLambda(unstable_function).with_retry(
stop_after_attempt=4, # 最多重试 4 次
wait_exponential_jitter=True # 指数退避
)
result = r1.invoke(10) # 最终成功
配置选项:
stop_after_attempt: 最大重试次数wait_exponential_jitter: 是否使用指数退避策略
7. 条件分支 - 动态路由
根据输入动态选择不同的执行路径:
方式一:Lambda 条件判断
from langchain_core.runnables import RunnableLambda
r1 = RunnableLambda(lambda x: x + 10)
r2 = RunnableLambda(lambda x: [x] * 2)
# 根据条件动态选择
chain = r1 | RunnableLambda(
lambda x: r2 if x > 12 else RunnableLambda(lambda x: x)
)
result = chain.invoke(1) # 11 (不满足条件,直接返回)
result = chain.invoke(5) # [15, 15] (满足条件,执行 r2)
方式二:RouterRunnable(推荐)
from langchain_core.runnables import RouterRunnable, RunnableSequence
# 定义多个子链
physics_chain = physics_prompt | llm
math_chain = math_prompt | llm
history_chain = history_prompt | llm
# 路由函数
def route(input_data):
if '物理' in input_data['type']:
return {"key": 'physics', "input": input_data['input']}
elif '数学' in input_data['type']:
return {"key": 'math', "input": input_data['input']}
else:
return {"key": 'default', "input": input_data['input']}
route_runnable = RunnableLambda(route)
# 创建路由器
router = RouterRunnable(runnables={
'physics': physics_chain,
'math': math_chain,
'history': history_chain,
'default': default_chain
})
# 完整链路
classifier = first_prompt | llm | JsonOutputParser()
chain = RunnableSequence(classifier, route_runnable, router, StrOutputParser())
result = chain.invoke({"input": "什么是黑体辐射?"})
应用场景:
- 多领域问答系统
- 根据用户意图选择不同处理流程
- A/B 测试不同模型
8. 生命周期监听 - with_listeners()
监控节点的执行状态和时间:
from langchain_core.runnables import RunnableLambda
from langchain_core.tracers import Run
import time
def slow_function(n: int):
time.sleep(n)
return n * 2
r1 = RunnableLambda(slow_function)
def on_start(run_obj: Run):
"""节点启动时自动调用"""
print(f'r1 启动时间: {run_obj.start_time}')
def on_end(run_obj: Run):
"""节点结束时自动调用"""
print(f'r1 结束时间: {run_obj.end_time}')
print(f'耗时: {run_obj.end_time - run_obj.start_time}')
# 添加监听器
chain = r1.with_listeners(on_start=on_start, on_end=on_end)
result = chain.invoke(2)
应用场景:
- 性能监控和分析
- 日志记录和追踪
- 调试复杂链路
9. 流式输出 - stream()
实时获取生成内容:
from langchain_core.runnables import RunnableLambda
def text_generator(prompt: str):
"""模拟流式输出"""
for item in prompt.split(' '):
yield item
r1 = RunnableLambda(text_generator)
# 流式调用
for chunk in r1.stream('This is a Dog.'):
print(chunk, end=' ', flush=True)
# 输出: This is a Dog.
与 LLM 结合:
chain = prompt | llm | StrOutputParser()
# 流式输出生成的文本
for chunk in chain.stream({"question": "讲个笑话"}):
print(chunk, end='', flush=True)
LCEL 实战案例
案例 1:多阶段文本生成与评估
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(temperature=0, model='deepseek-chat', ...)
# 第一阶段:生成文本
prompt1 = PromptTemplate.from_template(
'给我写一篇关于{key_word}的{type},字数不超过{count}。'
)
chain1 = prompt1 | llm | StrOutputParser()
# 第二阶段:评估文本
prompt2 = PromptTemplate.from_template(
'请简单评价一下这篇短文,如果总分是10分,请给这篇短文打分: {text_content}'
)
# 方式一:字典映射
chain2 = {'text_content': chain1} | prompt2 | llm | StrOutputParser()
# 方式二:使用 RunnableLambda 调试
def debug_print(input):
print("生成的文本:", input)
print('--' * 30)
return {'text_content': input}
chain2 = (
chain1
| RunnableLambda(debug_print) # 调试输出
| prompt2
| llm
| StrOutputParser()
)
result = chain2.invoke({
'key_word': '青春',
'type': '散文',
'count': 400
})
数据流:
输入 → prompt1 → llm → 文本 → prompt2 → llm → 评分
案例 2:多轮对话链式处理
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
gather_preferences_prompt = ChatPromptTemplate.from_template(
"用户输入了一些餐厅偏好:{input1}\n"
"请将用户的偏好总结为清晰的需求:"
)
recommend_restaurants_prompt = ChatPromptTemplate.from_template(
"基于用户需求:{input2}\n"
"请推荐 3 家适合的餐厅,并说明推荐理由:"
)
summarize_recommendations_prompt = ChatPromptTemplate.from_template(
"以下是餐厅推荐和推荐理由:\n{input3}\n"
"请总结成 2-3 句话,供用户快速参考:"
)
# 链式串联多个 LLM 调用
chain = (
gather_preferences_prompt
| llm
| recommend_restaurants_prompt
| llm
| summarize_recommendations_prompt
| llm
| StrOutputParser()
)
result = chain.invoke({
'input1': '我喜欢安静的地方,有素食的餐厅更好,而且价格也不贵。'
})
优势:
- ✅ 每个步骤职责单一,易于维护
- ✅ 可以单独测试每个环节
- ✅ 自动处理中间结果的传递
案例 3:智能路由系统(完整实现)
from langchain_core.output_parsers import JsonOutputParser, StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda, RouterRunnable, RunnableSequence
# 定义各领域的提示词模板
physics_template = ChatPromptTemplate.from_template(
"你是一位物理学教授,擅长用简洁易懂的方式回答物理问题。以下是问题内容:{input}"
)
math_template = ChatPromptTemplate.from_template(
"你是一位数学家,擅长分步骤解决数学问题,并提供详细的解决过程。以下是问题内容:{input}"
)
history_template = ChatPromptTemplate.from_template(
"你是一位历史学家,对历史事件和背景有深入研究。以下是问题内容:{input}"
)
computerscience_template = ChatPromptTemplate.from_template(
"你是一位计算机科学专家,擅长算法、数据结构和编程问题。以下是问题内容:{input}"
)
default_template = ChatPromptTemplate.from_template(
"输入内容无法归类,请直接回答:{input}"
)
# 创建各领域的处理链
physics_chain = physics_template | llm
math_chain = math_template | llm
history_chain = history_template | llm
computerscience_chain = computerscience_template | llm
default_chain = default_template | llm
# 路由函数
def route(input_data):
"""根据分类结果动态选择处理链"""
category_map = {
'物理': 'physics',
'数学': 'math',
'历史': 'history',
'计算机': 'computer_science'
}
for keyword, key in category_map.items():
if keyword in input_data['type']:
print(f'路由到: {key}')
return {"key": key, "input": input_data['input']}
print('路由到: default')
return {"key": 'default', "input": input_data['input']}
# 创建路由器
router = RouterRunnable(runnables={
'physics': physics_chain,
'math': math_chain,
'history': history_chain,
'computer_science': computerscience_chain,
'default': default_chain
})
# 分类器:首先判断问题类型
first_prompt = ChatPromptTemplate.from_template(
"不要回答下面用户的问题,只要根据用户的输入来判断分类,"
"一共有[物理,历史,计算机,数学,其他]5种类别。\n\n"
"用户的输入:{input} \n\n"
"最后的输出包含分类的类别和用户输入的内容,输出格式为json. "
"其中,类别的key为type,用户输入内容的key为input"
)
classifier = first_prompt | llm | JsonOutputParser()
# 完整链路
classifier_route = RunnableLambda(route)
chain = RunnableSequence(
classifier, # 步骤1: 分类
classifier_route, # 步骤2: 路由
router, # 步骤3: 执行对应链
StrOutputParser() # 步骤4: 解析输出
)
# 批量测试
inputs = [
{"input": "什么是黑体辐射?"}, # 物理问题
{"input": "计算 2 + 2 的结果。"}, # 数学问题
{"input": "介绍一次世界大战的背景。"}, # 历史问题
{"input": "如何实现快速排序算法?"} # 计算机科学问题
]
for inp in inputs:
result = chain.invoke(inp)
print(f'问题: {inp["input"]}')
print(f'回答: {result}\n')
执行流程:
用户问题
↓
分类器 (LLM) → JSON {type: "物理", input: "..."}
↓
路由函数 → {key: "physics", input: "..."}
↓
RouterRunnable → physics_chain
↓
LLM 回答
↓
StrOutputParser → 最终答案
LCEL 高级技巧
1. 可视化链路图
# 打印链路的 ASCII 图
chain.get_graph().print_ascii()
# 输出示例:
# +-----------------+
# | PromptTemplate |
# +-----------------+
# *
# *
# v
# +-----------------+
# | ChatOpenAI |
# +-----------------+
# *
# *
# v
# +-----------------+
# | StrOutputParser |
# +-----------------+
2. 组合多种 Runnable
from langchain_core.runnables import (
RunnableParallel,
RunnablePassthrough,
RunnableLambda
)
# 复杂的组合示例
complex_chain = (
RunnablePassthrough.assign(step1=chain1)
| RunnableParallel(
branch_a=chain_a,
branch_b=chain_b
)
| RunnableLambda(lambda x: merge_results(x))
| final_chain
)
3. 性能优化建议
批量处理:
# 使用 batch 而非多次 invoke
questions = [
{'question': '问题1'},
{'question': '问题2'},
{'question': '问题3'}
]
results = chain.batch(questions) # 更高效
异步执行:
# 高并发场景使用异步
import asyncio
async def process_questions():
results = await chain.abatch(questions)
return results
results = asyncio.run(process_questions())
缓存机制:
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
# 启用缓存
set_llm_cache(InMemoryCache())
# 相同输入会使用缓存结果
result1 = chain.invoke({'question': '有多少员工?'})
result2 = chain.invoke({'question': '有多少员工?'}) # 秒回
LCEL vs 传统 Chain 对比
| 特性 | 传统 Chain | LCEL Runnable |
|---|---|---|
| 代码复杂度 | 高,需要定义多个类 | 低,一行搞定 |
| 可读性 | 一般 | 优秀,声明式 |
| 灵活性 | 低,耦合度高 | 高,自由组合 |
| 异步支持 | 需单独实现 _acall |
自动支持 ainvoke |
| 流式输出 | 复杂 | 简单的 stream() |
| 并行处理 | 困难 | RunnableParallel |
| 错误处理 | 手动 try-except | with_fallbacks() |
| 重试机制 | 手动实现 | with_retry() |
| 调试难度 | 高 | 低,可插桩监听 |
| 性能优化 | 手动优化 | 自动批处理和并发 |
示例对比:
# ❌ 传统 Chain 方式(繁琐)
from langchain.chains import LLMChain
chain1 = LLMChain(llm=model, prompt=prompt1)
chain2 = LLMChain(llm=model, prompt=prompt2)
result1 = chain1.run(question="...")
result2 = chain2.run(text=result1)
# ✅ LCEL 方式(简洁)
chain = prompt1 | model | prompt2 | model | StrOutputParser()
result = chain.invoke({"question": "..."})
LCEL 最佳实践
- 小步组合:每个 Runnable 只做一件事
- 命名清晰:使用描述性的变量名
- 善用 Parallel:独立任务并行执行
- 添加 Fallback:关键节点设置后备方案
- 监控性能:使用
with_listeners追踪耗时 - 批量处理:多用
batch少用循环invoke - 调试友好:插入
RunnableLambda打印中间结果
代码执行流程
完整流程图
执行步骤详解
以问题 "一共有多少个员工?" 为例:
-
输入:
{'question': '一共有多少个员工?'} -
生成 SQL:
create_sql接收问题- 结合数据库 schema 构建提示词
- 调用 GLM-4 模型
- 输出: ````
sql\nSELECT COUNT(*) FROM employees\n`
-
清理 SQL:
- Lambda 函数移除 Markdown 标记
- 输出:
SELECT COUNT(*) FROM employees
-
执行 SQL:
QuerySQLDataBaseTool执行查询- 数据库返回:
[(100,)]
-
生成答案:
-
构建提示词:
Question: 一共有多少个员工? SQL Query: SELECT COUNT(*) FROM employees SQL Result: [(100,)] Answer: -
调用 GLM-4 生成答案
-
输出: "共有 100 名员工。"
-
-
返回结果:
"共有 100 名员工。"
关键技术概念总结
1. LCEL (LangChain Expression Language)
核心优势:
- ✅ 声明式编程,代码更简洁
- ✅ 自动支持异步、流式、批处理
- ✅ 内置日志记录和追踪
- ✅ 易于调试和优化
基本操作符:
|: 串联(pipe)RunnablePassthrough.assign(): 添加字段RunnableParallel(): 并行执行RunnableBranch(): 条件分支
2. Chain vs Runnable
传统 Chain (旧版):
from langchain.chains import LLMChain
chain = LLMChain(llm=model, prompt=prompt)
Runnable (新版 LCEL):
chain = prompt | model | parser
优势对比:
| 特性 | 传统 Chain | LCEL Runnable |
|---|---|---|
| 灵活性 | 低 | 高 |
| 可读性 | 一般 | 优秀 |
| 异步支持 | 需单独实现 | 自动支持 |
| 流式输出 | 复杂 | 简单 |
| 并行处理 | 困难 | 容易 |
3. Tool 设计模式
Tool 的标准结构:
class CustomTool(BaseTool):
name: str = "tool_name"
description: str = "工具描述"
def _run(self, arg1: str, arg2: int) -> str:
# 执行逻辑
return result
async def _arun(self, arg1: str, arg2: int) -> str:
# 异步版本
return result
使用场景:
- Agent 系统中的工具调用
- 函数调用 (Function Calling)
- ReAct 推理框架
4. 提示词工程最佳实践
结构化提示词:
"""
角色: {role}
任务: {task}
约束: {constraints}
示例: {examples}
输入: {input}
输出格式: {output_format}
"""
提高 SQL 生成准确率:
- 提供清晰的表结构说明
- 添加常见查询示例
- 指定 SQL 方言(MySQL/PostgreSQL)
- 限制返回行数(LIMIT)
- 提醒注意 NULL 值处理
相关依赖安装
原始代码(0.2/0.3 版本)的依赖
# LangChain 0.3.x 时期的典型安装方式
pip install langchain==0.3.0
pip install langchain-core==0.3.0
pip install langchain-community==0.3.0
pip install langchain-openai==0.2.0
# 数据库相关
pip install sqlalchemy>=2.0.0
pip install mysqlclient>=2.2.0 # 或使用 pymysql
你当前环境(1.x 最新版本)的依赖
根据实际检测,你的环境已安装:
langchain==1.3.1
langchain-core==1.4.0
langchain-openai==1.2.1
langgraph==1.2.0
需要补充安装的包(用于运行 demo6-3.py):
# SQLDatabase 和工具在 community 包中
pip install langchain-community
# 数据库驱动
pip install sqlalchemy
pip install mysqlclient # 或使用 pip install pymysql
完整 requirements.txt 示例(适配两个版本)
方案 1: 使用最新 1.x 版本(推荐)
# LangChain 核心
langchain>=1.3.0
langchain-core>=1.4.0
langchain-community>=0.3.0
langchain-openai>=1.2.0
# Database
sqlalchemy>=2.0.0
mysqlclient>=2.2.0
# Pydantic (数据验证)
pydantic>=2.0.0
# Other
pyyaml>=6.0
requests>=2.31.0
operator # Python 内置,无需安装
方案 2: 锁定到 0.3 版本(与代码原作者一致)
# LangChain 0.3.x
langchain==0.3.0
langchain-core==0.3.0
langchain-community==0.3.0
langchain-openai==0.2.0
# Database
sqlalchemy>=2.0.0
mysqlclient>=2.2.0
# Pydantic v2 (0.3 的要求)
pydantic>=2.0.0,<3.0.0
# Other
pyyaml>=6.0
requests>=2.31.0
验证安装
# 测试导入(两个版本都适用)
try:
from langchain_community.utilities import SQLDatabase
from langchain_community.tools import QuerySQLDataBaseTool
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from operator import itemgetter
print("✓ 所有依赖安装成功!")
print(f"✓ LangChain 版本: {import langchain; langchain.__version__}")
except ImportError as e:
print(f"✗ 导入失败: {e}")
print("请运行: pip install langchain-community sqlalchemy mysqlclient")
常见问题与解决方案
Q1: 为什么 SQL 生成不准确?
原因:
- 数据库 schema 信息不完整
- 提示词不够清晰
- 模型不理解业务逻辑
解决方案:
# 1. 手动指定要包含的表
db = SQLDatabase.from_uri(MYSQL_URI, include_tables=['employees', 'departments'])
# 2. 自定义提示词
from langchain.chains.sql_database.prompt import PROMPT_SUFFIX
custom_prompt = YOUR_CUSTOM_PROMPT + PROMPT_SUFFIX
# 3. 添加few-shot示例
Q2: 如何处理中文表名/列名?
解决方案:
# 确保数据库使用 utf8mb4 编码
MYSQL_URI = 'mysql+mysqldb://...?charset=utf8mb4'
# 在提示词中说明
"""
注意:表名和列名可能是中文,请保持原样使用。
"""
Q3: 如何限制查询复杂度?
解决方案:
# 1. 限制返回行数
db = SQLDatabase.from_uri(MYSQL_URI, sample_rows_in_table_info=3)
# 2. 自定义验证逻辑
def validate_sql(sql: str) -> bool:
forbidden_keywords = ['DROP', 'DELETE', 'UPDATE', 'INSERT']
return not any(kw in sql.upper() for kw in forbidden_keywords)
# 3. 使用只读数据库用户
Q4: 如何缓存 SQL 查询结果?
解决方案:
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache
# 启用缓存
set_llm_cache(InMemoryCache())
# 相同问题会使用缓存结果
resp1 = chain.invoke({'question': '有多少员工?'})
resp2 = chain.invoke({'question': '有多少员工?'}) # 快速返回
扩展阅读
官方文档
相关技术
- SQLAlchemy: Python ORM 和数据库工具包
- Pandas: 数据分析库(可与 SQL 结合使用)
- Streamlit: 快速构建数据应用 UI
进阶主题
- Text-to-SQL 的微调模型(如 SQLCoder)
- RAG (Retrieval Augmented Generation) 与 SQL 结合
- Multi-agent 协作处理复杂查询
总结
本代码展示了如何使用 LangChain 1.x 构建一个完整的 Text-to-SQL 应用,涵盖了:
✅ 数据库连接与管理
✅ LLM 集成与配置
✅ SQL 自动生成与执行
✅ 链式编排与数据流管理
✅ 提示词工程设计
✅ 输出解析与格式化
这是一个典型的 RAG (检索增强生成) 应用场景,将自然语言转换为结构化查询,再将查询结果转换回自然语言答案。
核心价值:
- 降低非技术人员使用数据库的门槛
- 提高数据查询效率
- 减少手写 SQL 的错误率
适用场景:
- 企业内部数据查询助手
- BI 报表自动生成
- 数据分析辅助工具
- 客服系统中的数据查询
文档生成时间: 2026-05-30
LangChain 版本: 1.3.1

浙公网安备 33010602011771号