别再无脑用 LangChain 了,90% 的 AI 应用原生 SDK 就够了

11 阅读3分钟

上个月我把项目里的 LangChain 全删了。

不是因为它不好,是因为我发现自己花了 80% 的时间在跟框架抽象层打架,而不是在解决实际问题。

事情的起因

去年年底接了个内部工具需求:给客服团队做一个 AI 问答系统,能查公司知识库回答问题。需求很清晰,我想都没想直接上了 LangChain + RAG。

结果两周后,我的代码长这样:

from langchain.chains import RetrievalQA
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import OpenAI
from langchain.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 还有七八个 import...

loader = DirectoryLoader('./docs')
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=1000)
chunks = splitter.split_documents(docs)
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever()
qa = RetrievalQA.from_chain_type(llm=OpenAI(), retriever=retriever)

看起来挺整洁的对吧?但当我需要:

  • 给检索结果加个相关性过滤
  • 在 prompt 里加入用户历史对话
  • 对某些问题走不同的处理逻辑

我就开始在文档里迷路了。每个需求都要找对应的 LangChain 抽象,每次升级版本都有 breaking change,调试的时候根本不知道哪一层出了问题。

我的重写版本

后来我花了两天把它重写了,核心逻辑大概 150 行:

import openai
from openai import OpenAI

client = OpenAI(
    base_url="https://api.ofox.ai/v1",
    api_key="sk-xxx"
)

def search_docs(query: str, top_k: int = 3) -> list[str]:
    # 自己实现检索,清晰可控
    query_embedding = client.embeddings.create(
        model="text-embedding-3-small",
        input=query
    ).data[0].embedding
    
    results = vector_db.search(query_embedding, top_k=top_k)
    return [r.content for r in results if r.score > 0.7]  # 自己加过滤

def answer_question(question: str, history: list = None) -> str:
    context = search_docs(question)
    
    messages = []
    if history:
        messages.extend(history[-4:])  # 只保留最近4轮
    
    messages.append({
        "role": "user",
        "content": f"根据以下资料回答问题:\n\n{chr(10).join(context)}\n\n问题:{question}"
    })
    
    response = client.chat.completions.create(
        model="claude-sonnet-4-6",
        messages=messages,
        max_tokens=1000
    )
    
    return response.choices[0].message.content

代码量差不多,但现在我能清楚地知道每一行在做什么。

什么时候该用框架

说了这么多,不是说 LangChain 没用。它在这些场景下确实省事:

1. 快速原型验证 如果你只是想验证一个想法,LangChain 的高层抽象能让你 30 分钟跑起来一个 demo。

2. 标准 RAG 流程 如果你的需求就是标准的「文档检索 + 问答」,不需要定制,LangChain 的默认实现已经够好。

3. 团队里有 LangChain 经验 学习成本是真实存在的,如果团队已经熟悉了,换原生 SDK 反而增加摩擦。

什么时候该直接用 SDK

1. 需要精细控制流程 任何需要「在第 N 步根据条件走不同分支」的逻辑,原生 SDK 写起来比框架清晰得多。

2. 生产环境 框架的抽象层在调试时是噩梦。出了问题你需要能快速定位,原生代码更容易加日志、加监控。

3. 性能敏感 框架有额外开销,虽然通常不大,但如果你的应用对延迟敏感,少一层抽象就少一层风险。

4. 多模型切换 如果你需要根据任务类型选不同模型(比如简单问题用便宜的模型,复杂推理用强模型),自己控制比框架更灵活。

def smart_route(question: str) -> str:
    # 简单问题用快速模型
    if len(question) < 50 and '?' in question:
        model = "claude-haiku-4-5-20251001"
    else:
        model = "claude-sonnet-4-6"
    
    return client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": question}]
    ).choices[0].message.content

我现在的工具链

重写之后的项目结构简单多了:

  • 向量数据库:直接用 pgvector(PostgreSQL 插件),不用单独维护一个 Chroma 服务
  • API 调用:OpenAI SDK,base_url 指向 ofox.ai,一个 key 能用所有主流模型
  • Embedding:直接调 API,不套框架
  • Prompt 管理:就是 Python 字符串,不需要 PromptTemplate

整个项目的 AI 相关代码从 400 行降到了 180 行,而且每一行我都能解释清楚它在做什么。

最后

框架是工具,不是信仰。LangChain 解决了它设计时要解决的问题,但不是所有问题都需要它。

下次开始新项目之前,先问自己:我真的需要这个抽象层吗?还是直接调 SDK 就够了?

大多数时候,答案是后者。