用 Dify 或 LangChain 串起来

站在巨人肩膀上,还是自己造轮子

前面几章我们把 RAG 链路的每一环都拆开讲透了。现在的问题很现实:这些零件要不要全部自己手撸一遍?答案取决于你的人手和耐心。多数团队不该从零写灌库脚本、自己维护检索逻辑、再包一层 Web 界面,那样项目还没有见到用户,精力先耗掉大半。成熟工具已经把流水线抽象成了可配置的模块,你需要做的是理解原理、做出选择,然后在关键环节做定制。

这一章介绍两条主流路线。一条是 Dify,开源的低代码平台,拖拖拽拽把模型、知识库、工具接进一个应用,适合快速交付和内部工具。另一条是 LangChain,一个代码库,把流程的每个环节都暴露成可编程接口,适合需要精细控制和深度定制的团队。它们不是竞争到底的对立两极,而是同一个图谱上的两端,理解两者的差异后你会发现很多项目里混着用更舒服。

选路线之前先盘一下家底:有没有前端和后端开发、应用形态是聊天窗口还是 API 服务、定制化需求有多深、要不要长期维护。这几个问题的答案几乎直接决定了你的选择。下面两节各自展开,看完你心里就有谱了。

顺带说一个常见的心态误区。许多人觉得用现成平台显得技术含量低,非要从零开始写才算实力。这种想法在知识库项目上要吃大亏:你的核心竞争力是对业务文档的理解和检索效果的调优,不是重新发明一遍 RAG 轮子。把拧螺丝的活交给工具,把脑力留给真正能拉开差距的那几个环节,这才是工程上的成熟。

Dify:一个下午搭出可用的知识库应用

Dify 的定位是 LLMOps 平台,说人话就是大模型应用搭建器。它自带用户界面、知识库管理、工作流编排、API 发布这一整套,目标是让不懂代码的人也能搭出应用。部署起来是标准 Docker 操作:

git clone https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
docker compose up -d

启动后浏览器访问本地 80 端口,注册管理员账号就能开始。第一次进系统的核心动作是两步:先在设置里把 Ollama 接进来作为模型供应商,地址填宿主机的内网 IP 而不是 localhost(容器里的 localhost 是容器自己,这个坑全书反复出现),模型选本地已经拉好的那几个;再在知识库页面导入文档,选好切片策略和 embedding 模型,等它跑完灌库。

注意 Dify 接 Ollama 时embedding 模型和聊天模型要分开填。聊天模型列表里选 llama3.1 这类生成模型,embedding 框里不要图省事也选它,要老老实实填 nomic-embed-text 这类专用嵌入模型。用生成模型跑嵌入不是报错就是慢到不可理喻,属于典型的新手坑。版本对下来后跑一条测试问句验证通路再继续往下配,出问题的永远是小步快跑比一通配完再调试省时间。

Dify 的知识库切分做得比较直观,可选自动分段或自定义规则,高级设置里能调每片大小和重叠,对照着 ol-0304 里学的参数填进去即可。检索模式有三个选项:向量检索、全文检索、混合检索,私有知识库一般选混合检索效果最好,它同时利用语义匹配和关键词精确匹配,互补短板。

顺带值得一说的是它的应用类型分四种:聊天助手、文本生成、Agent、工作流。聊天助手最常用,配好提示词和知识库直接出活;文本生成适合固定模板输出,比如摘要和翻译;Agent 能调用工具,比如查天气搜股票,私有场景用得少;工作流是重头戏,把一个复杂任务拆成可视化节点流转。知识库场景初级团队从聊天助手入手就够,别一上来就玩工作流,复杂度会劝退人。

应用搭好后右上角直接发布成 Web 应用或者一套 API,前端页面、会话管理、用量统计全都有。对小团队来说这一步省下的开发量是实打实的:你不必写一行界面代码就能拿到一个能用的产品。这很像用 ol-0303 里向量库的方式一样,先跑起来再优化,Dify 把先跑起来的成本压到了最低。

再补几条避坑心得。其一,Dify 全家桶容器比较多,初次启动要等各个服务就绪,看到 502 别慌,等一两分钟再刷新。其二,知识库导入大文档时进度条不动是正常现象,后台在排队切片加嵌入,几十兆的 PDF 跑个十分钟不奇怪。其三,会话历史在社区版里默认存数据库,用顺手了记得看一下留存策略,不然半年后库膨胀到查询都卡。其四,发布给用户前把公共链接的访问权限那一栏检查一遍,别做成无门槛开放。

LangChain:每一行都在你掌控之中

Dify 快,但快有代价。它的检索逻辑、切片实现、提示拼接都被封装在平台里,想改一个细节就得翻平台的源码或者等官方更新。当需求精细到比如要把检索到的片段按自定义规则二次筛选、或者接入一套自研的权限系统,LangChain 这类代码库的优势就出来了。它把 RAG 链路抽象成链条与检索器两大组件,每个组件都能替换成你自己的实现。

一套最小可运行的 LangChain 加 Ollama 知识库代码,可以直接照着改:

from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.llms import Ollama
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain import hub
from langchain_core.runnables import RunnablePassthrough

embed = OllamaEmbeddings(model="nomic-embed-text")
splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=100)
docs = splitter.split_text(open("rules.txt", encoding="utf-8").read())
store = Chroma.from_texts(docs, embed, collection_name="hr")

rag_chain = (
    {"context": store.as_retriever(search_kwargs={"k": 5}), "question": RunnablePassthrough()}
    | hub.pull("rlm/rag-prompt")
    | Ollama(model="llama3.1")
)
print(rag_chain.invoke("新人第一年有几天年假?"))

十几行代码串起了上几章讲过的所有概念:切片用 RecursiveCharacterTextSplitter、向量库用 Chroma、检索 top 5、提示模板直接从仓库拉现成的、生成模型用本地 Ollama。每个零件都可单独替换:换 Qdrant 就把 Chroma 换成 Qdrant 对应的 vectorstore,换 embedding 模型就改一行 model 参数,加 reranker 就把检索器包一层。

逐段读一遍这段代码,你会发现它就是前面理论的镜像。第二行的分词器告诉你切片不是玄学,第三行的 from_texts 一步完成嵌入和入库,第四行的 as_retriever 把向量库变成链条能用的接口,search_kwargs 里那个 k 就是上一章讨论的 top_k。整段代码没有任何隐藏魔法,看不懂的地方点进源码都能看到实现——这正是代码路线最让人安心的地方。理解了这一段,你已经具备了手搓整套流水线的能力。

代码级掌控的另一面是所有坑你得自己踩。长会话的内存管理、并发控制、流式输出的回调、错误重试,这些在 Dify 里开箱即用的东西,在 LangChain 里都得自己写。我的建议是这部分从官方 cookbook 抄起,别凭感觉从空白文件开始。

再补两个写代码时的具体建议。一是 embedding 客户端记得显式关连接,有些版本不会自动释放 HTTP 连接,脚本跑半天资源泄漏到怀疑人生。二是 invoke 返回结果的结构每个组件都不太一样,打印出来看清楚再写处理逻辑,文档里示例参数和实际对象常常对不齐,靠猜写代码浪费的时间比读类型多得多。

两条路线怎么选

直接给对照,这张表是我把两条都跑过之后的真实感受:

维度 Dify LangChain
上手成本 几小时,有界面引导 一两天,需读文档写代码
定制深度 中,受平台能力边界限制 极高,代码全在手
界面 开箱即用,含管理与统计 无,需自己开发
部署依赖 一整套 Docker 容器 只是一个 Python 依赖
适合人群 产品经理、小团队、快速验证 开发者有明确定制需求
长期维护 跟随官方升级 代码自己维护

选择策略很简单:如果应用就是标准的聊天加知识库形态,三个月内要看到成果,选 Dify;如果检索逻辑本身就是你产品的核心竞争力,或者要嵌入进已有系统做深度集成,选 LangChain。还有个折中方案:用 Dify 快速验证想法和展示效果,同时用 LangChain 做核心链路的并行开发,等定制版稳定了再切过去。一试一并行看起来浪费,实际是风险最低的做法。

别忽略的一个维度是人员更迭。Dify 应用换个人接手,点几下界面也摸熟了;LangChain 代码库换个人接手,读代码就得好几天,注释和文档不到位更久。小团队流动性高的话,这个隐性成本比技术指标更值得纳入考量。反过来也成立:如果细到每行代码的掌控是你产品立身之本,那再难找人也得走代码路线,外包都接不走你的核心竞争力。

需要提醒的是,LangChain 的抽象层有时会让简单的事变绕。早期版本接口频繁变动、版本之间不兼容的问题没少折腾人,锁定版本号、勤跑测试是基本操作。如果觉得它太重,LangChain 之外的轻量替代也很多,比如直接拿 Chroma 的客户端加上几行提示拼装就能跑起来,没必要为了用框架而用框架。

讲两个真实场景帮你对号入座。第一个是某二十来人的创业公司,产品就是个内部 HR 问答机器人,需求标准、没有特殊集成,两个同事一个下午在 Dify 上搭完发布,后来维护也无须开发投入。第二个是某金融机构,检索要做行级权限过滤、回答要走审计、提示要在不同产品线下切换,这种定制深度 Dify 根本装不下,团队直接用 LangChain 自建核心链路,Dify 只留着做演示原型。你看,选择从来不是哪个更好,而是哪个刚好够着你的需求边界。

进阶一点:工作流和记住上下文

两条路线到了进阶阶段都绕不开两个词:工作流和记忆。工作流解决的是多步骤任务,比如用户问的问题需要先查库再算数最后生成表格,一条线性的问答链搞不定,需要把任务拆成节点编排。Dify 里有可视化工作流编辑器,拖节点连线即可;LangChain 里有 LangGraph 负责这类有状态的复杂编排。工具是各家的,思路一脉相承:把 RAG 从一问一答扩展成多步推理。

记忆则是让应用记住同一用户的对话上下文。Dify 在应用配置里直接开启会话记忆,选个保留轮数就行;LangChain 需要手动接 memory 组件。注意这里的记忆是会话级的临时记忆,和 ol-0301 里讨论的知识记忆是两码事,关掉窗口就清空,别混淆。

还有个介于两者之间的概念:长期记忆。用户上周问过的事,这周新开会话还想接着聊。成熟做法是把对话摘要写进向量库作为一类特殊文档,检索时连这些摘要一起召回,等于把用户的个人上下文也外置化了。这个模式在客服场景特别值钱,客户不用每次把前情提要复述一遍,体验直接上档次。Dify 这种平台暂时还没把这个模式做成标准件,想实现还得自己写,此时代码路线又占了上风。

最后无论哪条路线,上线前请回到评测集上跑一遍。界面再炫、编排再复杂,检索质量不及格,用户一样做鸟兽散,工具从来没有坏工具只有用错的地方。评测方法就不重复了,流程那篇里讲过三个指标的拆分与用法,直接照做。

常见问题

Docker 里部署的 Dify 怎么都连不上 Ollama

九成是网络地址的问题。容器内的 localhost 指向容器自己,填这个地址 Dify 永远找不到 Ollama。正确做法是填宿主机的局域网 IP,或者让两个容器接入同一个 Docker 网络,用 Ollama 容器的名字直接访问。另外检查 Ollama 的监听地址,默认只绑 127.0.0.1,改成 0.0.0.0 后才允许其他容器访问,这一步在报错排查那篇里有详细命令,此处不展开。

Dify 自动切片的效果不好,能自己改吗

能,但分两种情况。知识库的高级设置里可以自定义每片大小、重叠和分隔规则,这个改起来零成本,先在这里调。如果还达不到要求,先把文档在本地按上一章的清洗规则处理好再传,往往比调平台参数管用。Dify 也支持外部知识库 API,意味着你可以完全用自己的 LangChain 灌库服务对 Dify 暴露检索接口,两全其美,但工作量就上去了。

LangChain 现在的版本怎么到处都搜不到老教程里的 LLMChain

因为官方经过一轮大重构。新版本用 LCEL(LangChain 表达式语言)那种 | 管道语法替代了旧的 LLMChain 链式调用,上面的示例就是新写法。老教程多如牛毛但写法已过时,学新东西直接看官方文档和 cookbook,比照着旧博客改错误省事。版本锁定特别重要,requirements 文件里把版本钉死,哪天升级毁了链路不至于两眼一黑。

两条路线产出的应用,性能差距大吗

普通规模下几乎感觉不到,瓶颈不在框架而在模型和检索本身。Dify 前面套了一层 Web 服务和鉴权,响应里多出十几毫秒;LangChain 直连 Ollama 少一层损耗,但你也得自己实现那些被省掉的功能。真到了高并发场景,两边都要做异步和缓存,谁也不比谁轻松。别在性能预期上做选型依据,按团队能力和定制需求来选准没错。

我想两个都试试,有没有省事的迁移路径

有,把知识库这一层与上层应用解耦就行。向量库是标准化的,Dify 可以接外部知识库 API,LangChain 也能直连同一个库。先用其中一条把文档灌进 Chroma 或 Qdrant,另一条直接指向这个库做检索,文档不重复处理就谈不上迁移成本。上层应用两三天重搭一次不心疼,沉的是数据。记住这条原则:数据存得足够标准和通用,工具想换就换。