我用 LangChain 搭了一套 AI 工作流引擎:3 个实战案例 + 7 个踩坑记录

我用 LangChain 搭了一套 AI 工作流引擎:3 个实战案例 + 7 个踩坑记录

读者对象:想用 LangChain 做应用的 Python 开发者、在 LangChain 和原生 SDK 之间纠结的人
解决的问题:LangChain 教程很多,但真正踩过坑的人才知道哪些 API 能用、哪些早该淘汰了。本文来自 3 个月实战经验。


一、LangChain 到底值不值得用

先说结论:具体场景具体分析

场景建议
简单对话(一问一答)❌ 别用,直接调 OpenAI SDK
多步工具调用(搜索 → 分析 → 写报告)✅ 用 LangChain,Chain/Agent 编排省事
RAG(检索增强生成)⚠️ 用 LCEL,别用老版 VectorStoreRetriever
复杂的条件分支工作流✅ LangGraph(LangChain 的子项目)

我用了 3 个月,结论是:LangChain 最大的价值在 Chain 编排和多步骤协调,但对简单场景是过度设计


二、我不建议你用的 3 个 API

用了会后悔的:

1. LLMChain(已过时)

# ❌ 别这么写(LangChain 0.1 的旧 API)
from langchain.chains import LLMChain
chain = LLMChain(llm=llm, prompt=prompt, output_parser=parser)

# ✅ 用 LCEL(LangChain Expression Language)
chain = prompt | llm | parser

LLMChain 2024 年就标记为 deprecated,但网上教程 80% 还在用。

2. load_summarize_chain(不可控)

# ❌ 黑盒,你不知道它怎么分段、怎么合并
from langchain.chains.summarize import load_summarize_chain

# ✅ 自己写分段逻辑,明确可控
def my_summarize(text, chunk_size=3000):
    chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
    summaries = [llm.invoke(f"总结:{chunk}") for chunk in chunks]
    return llm.invoke(f"合并以下总结:{summaries}")

3. ConversationBufferMemory(内存泄漏)

# ❌ 对话长了之后,每次请求都带完整历史,token 爆炸
from langchain.memory import ConversationBufferMemory

# ✅ 用滑动窗口
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k=10)  # 只保留最近 10 轮

三、实战案例 1:自动生成周报

需求:读取 Git 提交记录 → 翻译成白话 → 结构化周报。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
import subprocess

# 初始化模型
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)

# Step 1:获取 Git 提交
def get_git_log(days=7):
    cmd = f"git log --since='{days} days ago' --oneline --no-merges"
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return result.stdout.strip()

# Step 2:翻译成白话
translate_prompt = ChatPromptTemplate.from_messages([
    ("system", "把 Git 提交记录翻译成可读的工作描述。保留关键信息,去掉无意义的提交信息。"),
    ("human", "{git_log}")
])

# Step 3:结构化周报
report_prompt = ChatPromptTemplate.from_messages([
    ("system", """根据工作描述,生成结构化的周报:

## 本周完成
- (用 bullet points)

## 关键进展
- (1-2 句话)

## 下周计划
- (推断可能的下一步工作)

## 遇到问题
- (如果有,根据提交频率推断)"""),
    ("human", "{work_desc}")
])

# Step 4:用 LCEL 串联
chain = (
    RunnablePassthrough.assign(git_log=get_git_log)
    | RunnablePassthrough.assign(work_desc=translate_prompt | llm | StrOutputParser())
    | RunnablePassthrough.assign(report=report_prompt | llm | StrOutputParser())
)

# 运行
result = chain.invoke({})
print(result["report"])

踩坑点RunnablePassthrough.assign 的键名决定了后续步骤能拿到什么数据。如果第二步的 key 是 work_desc,第三步的 prompt 里变量名必须是 {work_desc},否则拿不到。


实战案例 2:批量长文档问答

需求:上传多个 PDF → 提问 → AI 从文档中找答案。

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader

# 1. 加载 PDF
loader = PyPDFLoader("report_2026.pdf")
pages = loader.load()

# 2. 文本分割(注意 chunk_overlap 不要设为 0)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,  # 重叠避免截断语义
    separators=["\n\n", "\n", "。", ",", " "]  # 中文友好的分隔符
)
chunks = splitter.split_documents(pages)

# 3. 向量化(用便宜的模型)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# 4. RAG prompt
rag_prompt = ChatPromptTemplate.from_messages([
    ("system", """根据以下参考资料回答问题。如果参考资料中没有相关信息,请明确说"参考资料中未找到相关信息",不要编造。

参考资料:
{context}"""),
    ("human", "{question}")
])

# 5. RAG chain
def format_docs(docs):
    return "\n\n".join(f"[来源{i}] {doc.page_content[:500]}"
                       for i, doc in enumerate(docs))

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

# 提问
answer = rag_chain.invoke("你们公司的营收目标是多少?")
print(answer)

实战案例 3:多模型投票决策

需求:同一个问题发给 3 个模型 → 投票决定最终答案。用于需要高可靠性的场景。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel
import json

# 初始化 3 个模型
gpt4o = ChatOpenAI(model="gpt-4o", temperature=0)
gpt4o_mini = ChatOpenAI(model="gpt-4o-mini", temperature=0)
claude = ChatOpenAI(  # 假设你有 Claude API
    model="claude-3-5-sonnet",
    temperature=0,
    base_url="https://api.anthropic.com/v1"
)

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是分类专家。将文本分为:投诉/咨询/建议/其他。只返回类别名称。"),
    ("human", "{text}")
])

# 并行调用
parallel_chain = RunnableParallel(
    gpt4o=prompt | gpt4o,
    gpt4o_mini=prompt | gpt4o_mini,
    claude=prompt | claude
)

# 投票
def majority_vote(results):
    votes = {}
    for model, result in results.items():
        category = result.content.strip()
        votes[category] = votes.get(category, 0) + 1
    
    winner = max(votes, key=votes.get)
    confidence = votes[winner] / len(results)
    return {
        "category": winner,
        "confidence": confidence,
        "votes": votes
    }

# 运行
text = "你们的 APP 太慢了,点一下要等 10 秒,能不能修一下?"
results = parallel_chain.invoke({"text": text})
decision = majority_vote(results)

print(f"分类结果:{decision['category']}")
print(f"置信度:{decision['confidence']*100:.0f}%")
print(f"各模型投票:{decision['votes']}")
# 输出:
# 分类结果:投诉
# 置信度:100%
# 各模型投票:{'投诉': 3}

四、7 个踩坑记录

坑 1:chunk_overlap=0,关键信息被截断

症状:用户问"Q2 营收",文档里写的是"Q2 营收为 5000 万,同比增长 20%",但正好"Q2 营收为"在 chunk A 结尾,“5000 万"在 chunk B 开头。检索召回 A,没回 B,AI 回答"不知道”。

原因RecursiveCharacterTextSplitter 按固定长度切,不关心语义边界。

解决方案chunk_overlap 设 200-300,保证相邻 chunk 有重叠:

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=300,  # 30% 重叠
)

坑 2:用 stuff 模式处理长文档,token 爆了

症状:用 load_qa_chain(llm, chain_type="stuff") 处理 50 页 PDF,API 直接报 context length exceeded

原因stuff 模式把所有检索到的文档拼成一个大 prompt,超 token 限制。

解决方案:用 map_reduce 模式(每个文档独立分析,再汇总),或者自己控制 chunk 数量:

# 在 RAG 里限制检索文档数
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})  # 只取 top 3

坑 3:Chroma 默认存内存,重启全丢

症状:每次重启应用,向量库是空的,重新 embedding 一遍又花 5 分钟。

原因Chroma.from_documents() 不传 persist_directory 默认存内存。

解决方案

vectorstore = Chroma.from_documents(
    chunks, embeddings, 
    persist_directory="./chroma_db"  # 持久化到磁盘
)
vectorstore.persist()  # 显式保存

# 下次直接用
vectorstore = Chroma(
    embedding_function=embeddings,
    persist_directory="./chroma_db"
)

坑 4:ChatPromptTemplate 的变量名写错,不报错但输出不正常

症状:Prompt 里写了 {contexts}(复数),但代码里传的是 {"context": docs}(单数)。不报错,只是 AI 回复质量很差。

原因:LCEL 没找到 {contexts} 这个变量,替换成了空字符串,等于没给上下文。

解决方案:用 prompt.input_variables 检查变量名是否一致:

print(prompt.input_variables)  # ['context', 'question']
# 如果和代码里传的不一致,马上能发现

坑 5:OpenAIEmbeddings 被限流后 LangChain 不自动重试

症状:批量 embedding 5000 个 chunk 时,中途被限流,LangChain 直接抛异常退出。

原因:LangChain 的默认 OpenAIEmbeddings 没配 retry。

解决方案

from tenacity import retry, stop_after_attempt, wait_exponential

embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small",
    max_retries=5,           # 重试 5 次
    request_timeout=60       # 超时 60 秒
)

坑 6:串行跑大吞吐任务,LangChain 的 RunnableParallel 是异步的

症状:有 3 个独立的 API 调用,用 asyncio.gather 并行跑,但 LangChain 的 RunnableParallel 用了 invoke() 而非 ainvoke(),还是串行。

原因invoke() 是同步的,在 RunnableParallel 里是伪并行(quick switch 而非真正并发)。

解决方案:用 ainvoke()

# ❌ 假并行
parallel_chain.invoke({"text": text})

# ✅ 真并行(asyncio 事件循环)
import asyncio
result = asyncio.run(parallel_chain.ainvoke({"text": text}))

坑 7:Prompt 模板里的 { 导致解析错误

症状:Prompt 里包含 JSON 示例 {"key": "value"},LangChain 把 { 识别为变量占位符。

原因:LangChain 默认用 {variable} 作为变量标记,遇到 { 就尝试解析。

解决方案:JSON 示例里用 {{}} 转义:

prompt = ChatPromptTemplate.from_messages([
    ("system", """请输出 JSON 格式:
{{"category": "投诉/咨询/建议", "confidence": 0-1}}"""),
    ("human", "{text}")
])

五、总结

要点说明
LangChain 适合什么多步编排、Agent 工具调用、RAG 管线
LangChain 不适合什么简单对话、一次性脚本
最大的坑老 API 还在网上大量传播(LLMChain / stuff 模式 / 内存 Memory)
我的建议用 LCEL(prompt | llm | parser),别用老式 Chain 类

三条经验

  1. 看文档别看教程:网上 80% 的 LangChain 教程用了已废弃的 API,直接看 LangChain 官方文档 的 LCEL 部分。
  2. 先搭原型再上 LangChain:先用原生 OpenAI SDK 搭好单步逻辑,确认可行后再用 LangChain 做编排。
  3. 持久化一切:向量库、Memory、配置,全存磁盘,别依赖内存。

互动:你用过 LangChain 吗?最大的感受是什么——真香还是后悔?评论区聊聊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值