LangChain接入Ollama本地大模型完全指南:以Qwen为例

163 阅读8分钟

本文将详细介绍如何将本地部署的Ollama大模型(特别是Qwen系列)接入LangChain框架,让你能够充分利用LangChain提供的丰富工具和组件,构建功能强大的本地AI应用。

1. 环境准备与安装

在开始之前,需要确保已经完成以下基础环境的配置。

1.1 安装必要的库

bash

# 安装LangChain核心库
pip install langchain

# 安装LangChain社区库和Ollama集成包
pip install langchain-core langchain-community

# 安装Ollama LangChain专用支持包
pip install -U langchain-ollama

# 如需要处理文档,安装额外依赖
pip install chromadb transformers torch sentence-transformers

1.2 启动Ollama服务

确保Ollama服务正在运行,并且已经拉取了所需的模型:

bash

# 启动Ollama服务
ollama serve

# 拉取Qwen模型(以Qwen3 8B为例)
ollama pull qwen3:8b

# 启动模型(以Qwen3 8B为例)
ollama run qwen3:8b

可以通过以下命令验证模型是否可用:

bash

curl http://localhost:11434/api/tags

2. 基础连接与对话

2.1 最简单的连接方式

python

from langchain_community.chat_models import ChatOllama
from langchain_core.messages import HumanMessage

# 初始化Ollama聊天模型
llm = ChatOllama(
    model="qwen3:8b",  # 使用已下载的模型名称
    base_url="http://localhost:11434",  # Ollama服务地址
    temperature=0.7,  # 控制生成随机性
    num_predict=512   # 最大生成长度
)

# 测试单轮对话
messages = [HumanMessage(content="你好,请介绍一下你自己")]
response = llm.invoke(messages)
def main():
    print(response.content)

if __name__ == "__main__":
    main()

会有如下输出

<think>
好的,用户让我介绍一下自己。首先,我需要确保回答符合规定,不涉及任何敏感内容。用户可能只是想了解我的基本信息,或者他们可能对我的功能和用途感兴趣。我应该先介绍我的身份,即通义千问,然后说明我的主要功能,比如回答问题、创作文字、逻辑推理等。同时,要提到我的训练数据和语言支持,这样用户能知道我的能力和限制。还要注意保持口语化,避免使用专业术语,让用户容易理解。另外,可能需要询问用户是否有具体的问题或需要帮助的地方,这样能更好地引导对话。需要检查是否有遗漏的重要信息,比如多语言支持,或者是否需要强调我的应用场景。最后,确保整体回答简洁明了,结构清晰,符合用户的需求。
</think>

你好!我是通义千问,是阿里巴巴集团旗下的通义实验室自主研发的超大规模语言模型。我的训练数据来自互联网上的大量文本,涵盖了广泛的领域和知识,能够理解和生成多种语言的内容。

我的主要功能包括:
1. **回答问题**:无论是日常生活中的疑问,还是专业领域的知识,我都可以尽力提供帮助。
2. **创作文字**:我可以撰写文章、故事、诗歌、剧本等,满足不同场景的创作需求。
3. **逻辑推理**:在数学、编程、逻辑谜题等方面,我能够进行分析和推导。
4. **多语言支持**:我支持多种语言,包括中文、英文、日文、韩文、法文、西班牙文等,能够与不同语言的用户进行交流。
5. **代码生成与解释**:我可以编写和解释多种编程语言的代码,帮助用户解决编程问题。

我的目标是成为用户在各种场景下的智能助手,提供高效、准确的信息和服务。如果你有任何问题或需要帮助,欢迎随时告诉我!

3. 实现多轮对话系统

要实现有记忆的对话,需要使用对话链和记忆组件:

python

"""
pip install -qU langchain langchain-ollama
"""
import httpx
from langchain_core.runnables import RunnableLambda
from langchain_ollama import ChatOllama
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import trim_messages, filter_messages, BaseMessage


class LocalTokenCounter:
    """用 /api/generate 的 prompt_eval_count 拿真实 token 数"""
    def __init__(self, model: str, base_url: str = "http://localhost:11434"):
        self.model = model
        self.base_url = base_url.rstrip("/")

    def invoke(self, messages: list[BaseMessage]) -> int:
        prompt = "\n".join(f"{m.type}: {m.content}" for m in messages)
        # 开一次空生成,只算 token 不采样
        resp = httpx.post(
            f"{self.base_url}/api/generate",
            json={
                "model": self.model,
                "prompt": prompt,
                "options": {"num_predict": 0},  # 不生成新 token
                "stream": False,
            },
            timeout=10,
        )
        resp.raise_for_status()
        return resp.json()["prompt_eval_count"]   # 真实 token 数


class OllamaChatBot:
    def __init__(self, model_name: str = "qwen3:8b"):
        # 1. 大模型
        self.llm = ChatOllama(
            model=model_name,
            base_url="http://localhost:11434",
            temperature=0.7,
            num_predict=1000,
        )

        # 2. 轻量级内存历史(可换成 Redis / File 持久化)
        self.store = {}  # session_id -> InMemoryChatMessageHistory

        token_counter = LocalTokenCounter(model_name)

        # 3. 滑动窗口:保留最近 5 轮(10 条消息)

        self.trimmer = trim_messages(
            max_tokens=2048,  # 也可改成 max_tokens=xxx
            strategy="last",
            token_counter=token_counter.invoke,
            include_system=True,
            allow_partial=False,
        )


        # 4. 提示模板(新写法)
        self.prompt = ChatPromptTemplate.from_messages(
            [
                ("system", "你是一个友好的 AI 助手,用中文与用户进行自然流畅的对话。"),
                MessagesPlaceholder(variable_name="history"),
                ("human", "{input}"),
            ]
        )

        # 5. 链:trimmer -> prompt -> llm
        # 新增包装函数
        def _format_messages(messages: list):
            return {"history": messages}

        # 链:trimmer -> 格式化 -> prompt -> llm
        chain = (
                RunnableLambda(lambda data: {
                    "history": self.trimmer.invoke(data["history"]),  # 只裁剪历史
                    "input": data["input"]  # 原样保留
                })
                | self.prompt
                | self.llm
        )

        # 6. 包装成带历史记录的 Runnable
        self.chain_with_history = RunnableWithMessageHistory(
            chain,
            self._get_session_history,
            input_messages_key="input",
            history_messages_key="history",
        )

    def _get_session_history(self, session_id: str) -> InMemoryChatMessageHistory:
        if session_id not in self.store:
            self.store[session_id] = InMemoryChatMessageHistory()
        return self.store[session_id]

    # 2. chat 方法把两个字段一起传
    def chat(self, user_input: str, session_id: str = "default") -> str:
        history = self._get_session_history(session_id).messages
        output = self.chain_with_history.invoke(
            {"history": history, "input": user_input},  # ① 一次性给齐
            config={"configurable": {"session_id": session_id}},
        )
        return output.content

# ---------------- 使用示例 ----------------
if __name__ == "__main__":
    bot = OllamaChatBot("qwen3:8b")
    print("开始对话,输入 退出/q 结束\n")
    while True:
        user = input("你: ").strip()
        if user.lower() in {"退出", "q", "quit", "exit"}:
            break
        reply = bot.chat(user)
        print(f"AI:{reply}\n")

输出如下

开始对话,输入 退出/q 结束

你: dd
AI:<think>
嗯,用户发来了“dd”,看起来像是一个简短的输入。首先,我需要确认用户的需求是什么。在中文交流中,“dd”可能有多种含义,比如缩写、网络用语,或者是误输入。考虑到用户可能是在测试我的反应,或者想开始一段对话,但没有提供足够的信息。

接下来,我要保持友好和开放的态度,避免让用户感到被忽视。可以先回应一个简单的问候,比如“你好!有什么我可以帮助你的吗?”,这样既礼貌又鼓励用户进一步说明需求。同时,也要准备好处理可能的其他情况,比如用户可能想进行某种特定的互动,或者有更深层次的问题需要解答。

另外,考虑到用户可能是在使用某种快捷方式或表情符号,但“dd”通常不常见,所以更可能是简短的输入。这时候,提供一个友好的回应,引导用户详细说明他们的需求,会更有效。确保回复简洁,但足够开放,让用户感到被重视和支持。
</think>

你好!有什么我可以帮助你的吗?😊

你: 今晚的月色真好
AI:<think>
好的,用户说"今晚的月色真好",我需要回应。首先,用户可能是在表达对今晚月亮的欣赏,所以我要用温暖、友好的语气回应。可以提到月色的美好,比如像轻纱、银色的光辉,或者营造一种宁静的氛围。

接下来,考虑用户可能的意图。他们可能只是单纯地分享感受,或者想展开关于自然的对话。我应该回应得具体一些,让用户感受到被理解和共鸣。比如,可以提到月亮的圆缺、月光洒在地面的样子,或者引发一些诗意的联想。

然后,要确保语言自然流畅,避免生硬。使用比喻和生动的描述会让回应更生动。比如"像轻纱一样笼罩大地"或者"仿佛整个世界都被镀上了一层银色的光辉"。这些表达能增加画面感,让对话更有趣味性。

同时,要注意保持对话的开放性,鼓励用户继续分享他们的感受。比如问"你是在窗边看的还是在户外?"或者"有没有特别喜欢的赏月地点?"这样可以引导用户进一步交流,让对话更深入。

另外,考虑到用户可能有不同的喜好,比如有人喜欢安静赏月,有人喜欢和朋友一起。所以回应中可以加入一些选项,比如"你是在窗边看的还是在户外?",这样既符合用户当前的表达,又为后续对话留下空间。

还要注意语气要友好,带点温暖和关怀。比如用"月色真美"这样的感叹,或者用"真美"来表达赞赏。同时,加入一些情感元素,比如"让人心情平静",可以增强回应的感染力。

最后,确保回应简洁自然,不过于冗长。避免使用复杂的句子结构,保持口语化,让对话轻松愉快。例如,用"今晚的月色确实很美,像轻纱一样笼罩大地,银色的光辉洒在大地上,让人的心情也跟着平静下来。你是在窗边看的还是在户外?"这样的句子,既生动又自然。

总结来说,回应需要包含:1. 对月色的赞美和生动描述;2. 询问用户的具体情境,引导进一步交流;3. 保持友好和温暖的语气;4. 语言自然流畅,避免生硬。
</think>

今晚的月色确实很美,像轻纱一样笼罩大地,银色的光辉洒在大地上,让人的心情也跟着平静下来。你是在窗边看的还是在户外?✨

你: 

PS:朋友,看到这儿,如果正巧也是晚上,祝你有一个美好的夜晚!!!!