1. 为什么你需要LangChain + Ollama这个组合?

如果你最近在玩大语言模型,可能已经发现了一个问题:模型本身很聪明,但让它真正帮你“干活”却有点费劲。比如,你想让它帮你查查最新的股票信息,或者让它分析一下你电脑里的PDF文档,它往往会告诉你:“抱歉,我的知识截止到2023年7月,无法获取实时信息。” 或者干脆开始一本正经地“胡说八道”。这感觉就像你请了一个博学的教授,但他却只记得几年前图书馆里的书,没法上网,也没法用你桌上的计算器。

这就是LangChainOllama这对组合要解决的问题。你可以把LangChain想象成一个超级智能的“乐高积木管理器”或者“机器人组装车间”。它不生产“大脑”(模型),但它提供了标准化的接口、丰富的工具和清晰的流程,让你能把不同的“大脑”、外部工具(比如搜索引擎、数据库、计算器)和记忆模块轻松地拼装在一起,形成一个能真正执行复杂任务的智能体。

Ollama,则是这个智能体最核心、最灵活的“大脑”供应站。它让你能在自己的电脑上,用一条简单的命令,就运行起Llama 3、Qwen、Mistral这些顶尖的开源模型。你不再需要依赖OpenAI的API,不用担心网络延迟,也不用为调用次数付费。所有的计算都在本地,数据隐私完全由你自己掌控。

所以,当LangChain的“组装能力”遇上Ollama的“本地大脑”,事情就变得有趣了。你可以在自己的笔记本上,构建一个具备记忆、能调用工具、能进行复杂推理的专属AI助手。无论是帮你总结几十份研究报告,还是作为一个24小时在线的编程导师,或者是一个能理解你所有个人笔记的私人知识库,这个组合都能帮你实现。接下来,我就带你从零开始,亲手搭建一个功能不断进化的“智能学习助手”,看看这个组合到底有多强大。

2. 环境搭建:5分钟搞定你的本地AI实验室

万事开头难?在这里不存在的。搭建LangChain和Ollama的环境,比安装大多数游戏还要简单。我们一步步来,确保你一次成功。

2.1 第一步:安装Ollama,召唤你的本地模型

Ollama的安装简单到令人发指。根据你的操作系统,访问 Ollama官网 下载安装包,像安装普通软件一样点击下一步即可。安装完成后,打开你的终端(Windows是CMD或PowerShell,Mac/Linux是Terminal),你会获得一个强大的 ollama 命令。

现在,让我们拉取第一个模型。我强烈推荐从 Llama 3.1 8B 开始,它在能力和资源消耗上取得了很好的平衡。

ollama pull llama3.1

这条命令会从Ollama的模型库下载Llama 3.1。你可以泡杯咖啡,等待几分钟。完成后,输入 ollama run llama3.1 就可以直接在命令行里和它对话了,试试问它“你好”,感受一下本地模型的速度。

模型选择小贴士:除了Llama 3.1,你还可以试试 qwen2.5:7b(中文能力很强)或 mistral(小巧精悍)。用 ollama list 可以查看你本地已有的所有模型,就像管理你的模型车库一样。

2.2 第二步:配置Python环境与LangChain生态

确保你的电脑上有Python 3.8或更高版本。我强烈建议使用condavenv创建一个独立的Python环境,避免包版本冲突。这里我们用conda举例:

conda create -n langchain-ollama python=3.10
conda activate langchain-ollama

环境激活后,就是安装核心依赖了。这里有个小坑需要注意:某些依赖(比如后面做RAG会用到的faiss)对numpy版本比较敏感。为了避开潜在的版本冲突,我们采用一个稳妥的安装顺序。

# 首先安装一个兼容性好的numpy版本
pip install numpy==1.26.4 -i https://pypi.tuna.tsinghua.edu.cn/simple

# 然后安装LangChain全家桶和Ollama集成包
pip install langchain-ollama langchain langchain-community -i https://pypi.tuna.tsinghua.edu.cn/simple

# 安装用于构建RAG系统向量数据库的FAISS(CPU版本即可)
pip install faiss-cpu -i https://pypi.tuna.tsinghua.edu.cn/simple

# 安装图像处理库(为后续多模态功能准备)
pip install Pillow -i https://pinyun.tuna.tsinghua.edu.cn/simple

敲完这几行命令,你的“AI实验室”基础建设就完成了。你可以写一个最简单的脚本来测试连通性:

from langchain_ollama import ChatOllama

llm = ChatOllama(model="llama3.1")
response = llm.invoke("Hello, world!")
print(response.content)

如果顺利输出了模型的回复,那么恭喜你,最枯燥的部分已经过去了,好戏即将开场。

3. 从零构建第一个AI对话链:你的第一个“学习助手”

现在,我们让这个本地模型真正“接入”LangChain的体系,把它从一个单纯的聊天对象,变成一个可编程、可定制的组件。我们将构建一个专为学习场景设计的对话链。

3.1 基础对话:让模型扮演“导师”

直接调用模型回答,就像用锤子钉钉子,简单直接。但通过LangChain的ChatPromptTemplate,我们可以给模型戴上不同的“人格面具”,并结构化我们的交互。我们来创建一个鼓励深入思考的学习导师。

from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate

# 1. 定义导师的角色和对话风格
template = """
你是一位富有耐心、善于引导的苏格拉底式导师。你从不直接给出答案,而是通过提问和举例,启发学生自己思考并得出结论。
你的回答应该简洁、富有启发性,并且鼓励学生进行下一步的探索。

学生的问题:{question}

请开始你的引导:
"""

# 2. 初始化模型,temperature调低一些,让回答更专注
model = ChatOllama(model="llama3.1", temperature=0.3)

# 3. 从模板创建提示
prompt = ChatPromptTemplate.from_template(template)

# 4. 用管道符 | 将提示和模型“链接”起来,形成一个处理链(Chain)
learning_chain = prompt | model

# 5. 向链提问
student_question = “机器学习中的‘过拟合’是什么意思?”
result = learning_chain.invoke({"question": student_question})
print(result.content)

运行这段代码,你可能会得到这样的回答:“想象一下,你为了准备历史考试,不是去理解历史事件的因果,而是死记硬背了课本上所有的日期和名字。考试时,如果题目和你背的一模一样,你能得高分;但一旦题目换个问法,你就懵了。这种对训练数据‘死记硬背’,导致在新数据上表现很差的现象,就是‘过拟合’。你能想到生活中其他类似的例子吗?”

看,通过一个精心设计的提示模板,我们让模型从一个通用的聊天AI,变成了一个特定风格的导师。这就是LangChain“链”的基本思想:标准化输入,加工处理,得到结构化输出

3.2 实现流式输出:让对话更自然

你有没有注意到,在使用一些AI产品时,回答是一个字一个字“蹦”出来的,而不是等了好几秒后突然出现一大段?这种体验好很多,这就是流式输出。在构建对话应用时,这几乎是必备功能。

用LangChain实现流式输出异常简单:

from langchain_ollama import ChatOllama

model = ChatOllama(model="llama3.1", temperature=0.7)
messages = [("human", "请用简单的比喻解释一下神经网络是如何学习的?")]

print("导师:", end="", flush=True)
for chunk in model.stream(messages):
    print(chunk.content, end='', flush=True)

model.stream() 返回的是一个生成器。它不会等模型生成完所有文本再返回,而是模型每生成一小段(比如一个词或一句话),就立刻通过网络传回来并打印出来。flush=True 参数确保了内容能立即显示在屏幕上,而不是卡在缓冲区里。对于构建Web或桌面聊天应用,这个功能至关重要,它能极大提升用户的交互感和响应速度。

4. 赋予助手“手脚”:工具调用与记忆能力

一个只会说话的助手,顶多是个百科全书。一个真正的智能助手,应该能“动手”做事,并且能记住之前聊过什么。这就是智能体(Agent)的雏形。

4.1 工具调用:让AI学会使用“计算器”

假设我们的学习助手正在教学生数学,学生问:“请计算一下圆周率π的前10位乘以98765是多少?” 模型本身并不擅长精确计算,它可能会给出一个近似值,甚至出错。这时,我们就需要赋予它调用工具的能力。

from langchain_ollama import ChatOllama
from langchain_core.tools import tool
from decimal import Decimal, getcontext
import math

# 1. 定义一个高精度计算工具
@tool
def precise_calculation(expression: str) -> str:
    """
    执行精确的数学计算或常数查询。
    例如:‘pi 前10位’ 或 ‘sin(45度)’ 或 ‘98765 * 3.1415926535’
    """
    try:
        # 处理一些常见常数请求
        if "pi" in expression.lower() and "位" in expression:
            import re
            match = re.search(r"前(\d+)位", expression)
            if match:
                digits = int(match.group(1))
                getcontext().prec = digits + 2  # 设置精度
                pi_str = str(Decimal(math.pi))
                return f"圆周率π的前{digits}位是:{pi_str[:digits+2]}" # 包含3.
        # 尝试直接计算数学表达式
        # 注意:这里使用eval仅作演示,生产环境需使用更安全的库(如ast.literal_eval、numexpr)
        # 且必须严格限制表达式内容,防止代码注入。
        safe_expression = expression.replace('^', '**').replace('度', '*math.pi/180')
        result = eval(safe_expression, {"__builtins__": None}, {"math": math})
        return f"`{expression}` 的计算结果是:{result}"
    except Exception as e:
        return f"计算出错:{e}。请确保输入合法的数学表达式。"

# 2. 将工具绑定到模型
llm_with_tools = ChatOllama(model="llama3.1", temperature=0).bind_tools([precise_calculation])

# 3. 向绑定了工具的模型提问
result = llm_with_tools.invoke("请先告诉我圆周率π的前10位,然后用这个值乘以98765,并给出最终结果。")

print("模型的原始响应对象:", result)
print("\n模型决定调用的工具:", result.tool_calls)

# 4. 模拟执行工具调用(在实际的Agent中,这一步是自动的)
if result.tool_calls:
    for tool_call in result.tool_calls:
        tool_name = tool_call['name']
        tool_args = tool_call['args']
        print(f"\n执行工具 `{tool_name}`,参数为:{tool_args}")
        # 这里根据工具名找到对应函数并执行
        if tool_name == "precise_calculation":
            tool_result = precise_calculation.invoke(tool_args['expression'])
            print(f"工具执行结果:{tool_result}")
            # 在实际的Agent循环中,这个结果会被传回给模型,由模型整合后生成最终回答给用户

运行这段代码,你会发现模型的result对象里多了一个tool_calls字段,里面包含了它想调用的工具名称和参数。它可能会先调用一次工具查询π的前10位,拿到结果后,再计划第二次调用进行乘法计算(或者在高级的Agent框架中,它能一次性规划好所有步骤)。bind_tools 方法就像给模型递上了一个工具箱,模型学会了在遇到它不擅长(或无法精确完成)的任务时,主动去工具箱里寻找合适的工具。你可以把搜索引擎API、数据库查询、文件操作等都封装成工具,这样你的助手能力边界就大大扩展了。

4.2 短期记忆:让对话拥有连续性

没有记忆的对话是痛苦的。你每次都要说“还记得我们刚才聊的过拟合吗?”,而对方回答“什么是过拟合?”。我们需要给助手加上记忆模块。

from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferWindowMemory
from langchain_ollama import OllamaLLM

# 1. 初始化模型和记忆
# 使用 OllamaLLM (适用于普通链) 或 ChatOllama (适用于对话链) 均可,这里用OllamaLLM
model = OllamaLLM(model="llama3.1")
# 使用窗口记忆,只保留最近3轮对话,避免上下文过长
memory = ConversationBufferWindowMemory(k=3)

# 2. 创建对话链
conversation = ConversationChain(
    llm=model,
    memory=memory,
    verbose=True  # 设为True可以看到链的思考过程,调试时非常有用
)

print("=== 第一轮对话 ===")
response1 = conversation.predict(input="什么是神经网络?")
print(f"助手: {response1}\n")

print("=== 第二轮对话 (我用了‘它’) ===")
response2 = conversation.predict(input="它通常由哪些层组成?")
print(f"助手: {response2}\n")

print("=== 第三轮对话 (我用了‘这些层’) ===")
response3 = conversation.predict(input="这些层是如何连接和训练的?")
print(f"助手: {response3}\n")

# 查看当前记忆内容
print("当前记忆缓冲区的内容:")
print(memory.buffer)

当你将 verbose=True 打开时,在控制台你会看到LangChain在背后组装的完整提示词,其中就包含了记忆缓冲区里的历史对话。这样,模型在回答“它通常由哪些层组成?”时,就能知道“它”指代的是上一轮提到的“神经网络”。ConversationBufferWindowMemory(k=3) 意味着它只记住最近3轮对话,这对于管理有限的模型上下文长度非常有效。你也可以使用 ConversationSummaryMemory,它会自动用模型将长篇对话总结成一段摘要,从而记住更久远的核心信息。

5. 构建专属知识库:实现一个本地RAG问答系统

这是让很多开发者兴奋的功能:让模型能够回答你私有的、最新的、它原本不知道的知识。比如,你想让助手基于你公司的内部文档、你的个人读书笔记或者最新的技术博客来回答问题。这就需要用到 RAG(检索增强生成) 技术。

它的原理很简单:先把你的知识文档“喂”给一个向量数据库(把它变成一堆数学向量存储起来),当用户提问时,将问题也变成向量,去数据库里找到最相似的几段文本,然后把“问题+相关文本片段”一起交给模型,让模型基于这些“参考资料”来生成答案。这样,答案既准确又有据可依。

5.1 准备知识库与向量化

我们以Datawhale的组织介绍文本为例,构建一个微型知识库。

from langchain_community.vectorstores import FAISS
from langchain_ollama import OllamaEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 1. 准备你的知识文档
knowledge_text = """
Datawhale 是一个专注于数据科学与 AI 领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。
Datawhale 以“ for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。
同时 Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。
如果你想在Datawhale开源社区发起一个开源项目,请详细阅读Datawhale开源项目指南。
"""

# 2. 初始化嵌入模型(负责把文字变成向量)
# 需要先运行 `ollama pull nomic-embed-text` 下载嵌入模型
embeddings = OllamaEmbeddings(model="nomic-embed-text")

# 3. 分割文本。模型有上下文限制,不能把整本书塞进去,需要切成小块。
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=150,      # 每个文本块的最大字符数
    chunk_overlap=30     # 块与块之间的重叠字符,避免语义被切断
)
text_chunks = text_splitter.split_text(knowledge_text)
print(f"将知识库分割成了 {len(text_chunks)} 个文本块。")
for i, chunk in enumerate(text_chunks[:2]):  # 打印前两个看看
    print(f"块 {i+1}: {chunk[:80]}...\n")

# 4. 将文本块转换为向量,并存入FAISS向量数据库
vectorstore = FAISS.from_texts(text_chunks, embeddings)
# 将向量数据库转换为检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})  # 每次检索返回最相似的2个块

这里有几个关键点:OllamaEmbeddings 使用了一个专门的嵌入模型(nomic-embed-text)来将文本转换为高维向量,这个模型和聊天模型是分开的,专门为理解语义相似性而优化。FAISS 是Meta开源的一个高效的向量相似性搜索库,能快速在海量向量中找到最相似的几个。chunk_overlap 设置重叠很重要,它能防止一个完整的句子或概念被生硬地切分到两个块中,导致检索时信息不全。

5.2 组装RAG链,进行智能问答

有了检索器,我们就可以组装最终的问答链了。

from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough

# 1. 初始化聊天模型
llm = ChatOllama(model="llama3.1", temperature=0.1)  # temperature调低,让回答更基于检索内容

# 2. 设计提示模板,强制模型基于检索到的上下文回答
template = """
你是一个专业的问答助手,请严格根据以下提供的上下文信息来回答问题。
如果你在上下文中找不到明确答案,请直接说“根据提供的资料,我无法回答这个问题”,不要编造信息。

上下文信息:
{context}

问题:{question}

请根据上下文给出答案:
"""
prompt = ChatPromptTemplate.from_template(template)

# 3. 组装RAG链
# 链的流程:输入问题 -> RunnablePassthrough (原样传递问题) -> retriever (检索上下文) -> prompt (组装提示词) -> llm (生成答案)
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
)

# 4. 提问!
questions = [
    "Datawhale是一个什么样的组织?",
    "它的愿景是什么?",
    "如何在Datawhale发起开源项目?",
    "Datawhale的总部在哪里?"  # 这是一个知识库中没有的问题
]

for q in questions:
    print(f"\n用户:{q}")
    response = rag_chain.invoke(q)
    print(f"助手:{response.content}")

对于前三个问题,助手能准确从我们提供的文本中找到答案。对于最后一个问题“总部在哪里?”,由于上下文中没有相关信息,一个设计良好的RAG系统会如实告知“无法回答”,而不是胡编乱造一个地址。这就是RAG的核心价值:增强模型的知识,同时控制幻觉。你可以把knowledge_text替换成任意你拥有的文档(比如用langchain的文档加载器读取PDF、Word、网页),就能立刻打造一个属于任何领域的专家问答系统。

6. 进阶实战:组装一个具备多轮对话和工具调用能力的智能体

现在,让我们把前面学到的所有零件——对话链、记忆、工具调用、RAG——组装起来,构建一个更强大的“智能学习助手”原型。这个助手能记住我们的学习进度,能回答基于我们个人笔记(知识库)的问题,还能在需要计算时调用工具。

由于构建一个完整的、可自动规划工具的智能体(Agent)涉及更复杂的框架(如LangChain的AgentExecutor),代码量较大,这里我为你勾勒出核心架构和关键代码片段,你可以在此基础上扩展。

智能体核心架构思路:

  1. 工具集:定义好助手能用的所有工具,比如计算器搜索个人笔记(RAG检索器)查询学习计划(访问数据库)等。
  2. 记忆模块:使用ConversationBufferWindowMemoryConversationSummaryMemory来维持对话历史。
  3. 模型与代理:使用ChatOllama模型,并利用LangChain的create_react_agentcreate_openai_tools_agent等函数,将模型、工具、提示模板组合成一个“代理”(Agent)。这个代理具备关键能力:根据对话历史和用户问题,自主决定是否需要调用工具、调用哪个工具、以及如何整合工具结果来生成最终回答
  4. 执行器:使用AgentExecutor来运行这个代理,处理工具调用的循环(模型思考->调用工具->返回结果->再思考...),直到得出最终答案。
# 以下是概念性代码框架,展示核心组件如何集成
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub  # 用于拉取预定义的Agent提示模板

# 1. 定义工具列表
tools = [precise_calculation, notes_retriever_tool]  # precise_calculation是之前定义的,notes_retriever_tool需要基于RAG部分创建

# 2. 获取一个适合ReAct框架的提示模板
prompt = hub.pull("hwchase17/react-chat")  # 这是一个经典的鼓励模型“思考-行动”的模板

# 3. 创建智能体
llm = ChatOllama(model="llama3.1", temperature=0)
agent = create_react_agent(llm, tools, prompt)

# 4. 创建执行器,并传入记忆
memory = ConversationBufferWindowMemory(k=5, memory_key="chat_history", return_messages=True)
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=memory,
    verbose=True,  # 详细输出,可以看到Agent的“思考”过程
    handle_parsing_errors=True  # 处理模型输出解析错误
)

# 5. 运行智能体
result = agent_executor.invoke({
    "input": “我之前跟你讨论过神经网络。现在,请根据我的笔记(里面提到过反向传播),帮我计算一下当学习率为0.01,梯度为2.5时,权重应该更新多少?”
})
print(result["output"])

在这个设想场景中,智能体会先检索你的笔记中找到“反向传播”的相关解释,理解“权重更新 = 学习率 * 梯度”这个公式,然后调用precise_calculation工具计算0.01 * 2.5,最后将工具返回的结果和笔记中的解释整合起来,给你一个完整的、有上下文的、且计算准确的回答。verbose=True模式下,你会看到类似“Thought: 用户需要计算...我需要先用检索工具查一下笔记...然后需要调用计算器...”这样的内部推理链,非常有趣。

7. 避坑指南与实用技巧

在我自己折腾的过程中,踩过不少坑,这里分享几个最常见的,帮你节省时间。

坑1:版本冲突,特别是numpy。 正如原始文章提到的,在安装faiss时,很容易遇到与numpy版本不兼容的问题。报错信息里常提到“NumPy 1.x”和“NumPy 2.x”不兼容。最稳妥的解决方案就是像我之前在环境搭建部分所做的那样,先手动安装一个较低版本的numpy(如1.26.4),然后再安装其他依赖。如果已经出现问题,就按文章里的方法卸载重装。

坑2:模型加载慢或首次响应慢。 Ollama在首次运行某个模型或长时间未使用后,需要从磁盘加载模型到内存,这可能需要几秒到十几秒。这是正常现象。后续的对话响应速度会快很多。确保你的电脑有足够的内存(8GB是底线,16GB或以上体验更佳)。

坑3:提示词(Prompt)设计不佳。 这是影响效果最大的因素。给模型的指令一定要清晰、具体。比如,与其说“总结这篇文章”,不如说“请用三个要点总结这篇文章的核心观点,每个要点不超过20字”。多使用“角色扮演”(你是一个...的专家)、输出格式要求(请以JSON格式输出)、示例(Few-shot)等技巧,能极大提升模型输出的质量。

技巧1:多模型切换测试。 Ollama换模型成本极低。如果你对Llama 3.1的回答不满意,可以立刻换Qwen或Gemma试试。不同的模型在不同任务上各有千秋。你可以写一个简单的测试脚本,用同一个问题询问多个模型,对比结果。

ollama pull qwen2.5:7b
ollama pull gemma:7b

技巧2:利用LangChain Hub。 LangChain社区维护了一个提示词和链的“Hub”,里面有大量现成的、经过优化的模板。当你不知道如何设计一个复杂任务的提示时,可以去那里找找灵感。hub.pull() 函数可以直接拉取使用。

技巧3:从简单链开始,逐步复杂化。 不要一开始就想着构建一个全能的智能体。先从一条简单的问答链开始,确保跑通。然后加上记忆,测试多轮对话。再引入一个简单的工具,测试工具调用。最后再把RAG加进来。每一步都测试验证,这样当出现问题时,你很容易定位到是哪个新加入的环节出了错。

这条路走下来,你会发现,借助LangChain的模块化设计和Ollama的本地化能力,构建一个功能丰富的AI应用不再是一个庞大的工程,而更像是在用高级积木进行创意拼接。你可以快速原型化一个想法,并随着需求的深入,不断给你的智能体添加新的“技能模块”。这种迭代和探索的过程,本身就是一种极大的乐趣和生产力。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐