大模型应用开发学习-基于 LangChain 框架实现的交互式问答脚本

带历史记忆的对话脚本

基于 LangChain 框架实现的交互式问答脚本,支持对话历史记忆功能,能够根据上下文进行连续对话。


设计思路

核心目标

实现一个能够"记住"对话内容的问答助手,支持多轮对话,模型可以根据之前的对话上下文来理解当前问题。

架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      交互式问答循环                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    用户输入问题                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                           ↓                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │        调用问答链(问题 + 历史消息)                    │   │
│  │  ┌───────────┐  ┌───────────┐  ┌───────────────┐   │   │
│  │  │  Prompt   │  │ ChatOllama│  │ StrOutputParser│   │   │
│  │  │ Template  │→ │    LLM    │→ │               │   │   │
│  │  └───────────┘  └───────────┘  └───────────────┘   │   │
│  └─────────────────────────────────────────────────────┘   │
│                           ↓                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              输出回答 + 更新历史消息                   │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

模块划分

|-------|-----------|---------------------------------------------|
| 模块 | 功能 | 关键技术 |
| 环境配置 | 加载环境变量 | python-dotenv |
| 模型构建 | 创建 LLM 实例 | ChatOllama |
| 提示词模板 | 构建对话模板 | ChatPromptTemplate, MessagesPlaceholder |
| 链式调用 | 组合处理流程 | LCEL (LangChain Expression Language) |
| 历史管理 | 维护对话上下文 | HumanMessage, AIMessage |


实现过程

1. 环境初始化

复制代码
from dotenv import load_dotenv
load_dotenv()

使用 .env 文件管理敏感配置,避免硬编码模型名称和 URL。

2. 构建 LLM 实例

复制代码
llm = ChatOllama(
    model=os.getenv("LOCAL_MODEL_NAME"),
    base_url=os.getenv("LOCAL_MODEL_URL")
)

为什么选择 ChatOllama而非 OllamaLLM

  • ChatOllama 原生支持消息格式 (List[BaseMessage])
  • 内置角色区分(system/human/ai)
  • 更适合多轮对话场景

3. 构建提示词模板

复制代码
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的文档问答助手..."),
    MessagesPlaceholder(variable_name="history"),  # 历史消息占位
    ("human", "{question}")  # 当前问题
])

关键点: MessagesPlaceholder

  • 动态插入历史消息
  • 保持消息的时间顺序
  • 无需手动拼接字符串

4. 构建 LCEL 链

复制代码
chain = prompt | llm | StrOutputParser()

使用 LangChain Expression Language (LCEL) 实现链式调用:

  • | 运算符连接组件
  • 自动传递数据流
  • 代码简洁清晰

5. 历史消息管理

复制代码
history: List[BaseMessage] = []

# 每轮对话后更新
history.append(HumanMessage(content=question))
history.append(AIMessage(content=answer))

消息类型说明:

  • SystemMessage:系统设定
  • HumanMessage:用户提问
  • AIMessage:助手回答

安装配置

依赖安装

复制代码
pip install langchain-core langchain-ollama python-dotenv

依赖包说明

|--------------------|-----------|-------------------|
| 包名 | 版本要求 | 用途 |
| langchain-core | >= 0.1.0 | 核心组件(消息、模板、链、解析器) |
| langchain-ollama | >= 0.1.0 | Ollama 模型集成 |
| python-dotenv | >= 1.0.0 | 环境变量管理 |

环境变量配置

在项目根目录创建 .env 文件:

复制代码
LOCAL_MODEL_NAME=qwen2.5
LOCAL_MODEL_URL=http://localhost:11434

前置条件

  1. 安装并启动 Ollama

  2. 下载所需模型:

    ollama pull qwen2.5


知识点详解

1. LangChain 核心概念

Chat Models vs LLMs

|------|-----------------|------|
| 特性 | Chat Models | LLMs |
| 输入格式 | 消息列表 | 字符串 |
| 角色支持 | system/human/ai | 无 |
| 适用场景 | 对话、多轮交互 | 文本生成 |

Messages 类型
复制代码
from langchain_core.messages import (
    SystemMessage,   # 系统指令
    HumanMessage,    # 用户消息
    AIMessage,       # AI 回复
    BaseMessage      # 基类
)

2. LCEL (LangChain Expression Language)

LCEL 是 LangChain 的声明式语法,用于组合组件:

复制代码
# 传统方式
output = parser(llm(prompt(input)))

# LCEL 方式
chain = prompt | llm | parser
output = chain.invoke(input)

优势:

  • 代码更清晰
  • 自动处理数据传递
  • 支持流式输出、异步调用

3. Output Parsers

StrOutputParser 将模型输出转换为字符串:

复制代码
from langchain_core.output_parsers import StrOutputParser

# AIMessage -> str
parser = StrOutputParser()
result = parser.invoke(ai_message)

4. Prompt Templates

复制代码
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# from_messages: 构建多角色对话模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "系统提示词"),
    MessagesPlaceholder(variable_name="history"),  # 动态消息
    ("human", "{user_input}")  # 变量占位
])

# 调用时传入变量
prompt.invoke({
    "history": [...],
    "user_input": "你好"
})

5. 对话历史管理策略

本脚本使用内存列表存储历史,适合简单场景。生产环境可考虑:

|------------------|---------|---------|
| 方案 | 优点 | 缺点 |
| 内存列表 | 简单直接 | 进程重启丢失 |
| Redis | 持久化、跨进程 | 需要额外服务 |
| 数据库 | 持久化、可查询 | 复杂度高 |
| LangChain Memory | 开箱即用 | 已废弃,不推荐 |


交互示例

完整代码

复制代码
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, BaseMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

from langchain_ollama import ChatOllama
import os
from dotenv import load_dotenv
from typing import List

# 目标:调用大模型实现带有历史记忆的问答,当我输入exit的时候结束对话

# 1. 构建问答链
# 2. 调用问答链
# 3. 输出回答

# 环境变量
load_dotenv()


# 1. 构建问答链(带历史记忆)
def build_qa_chain():
    # 构建 ChatOllama 大模型(支持消息格式)
    llm = ChatOllama(
        model=os.getenv("LOCAL_MODEL_NAME"),
        base_url=os.getenv("LOCAL_MODEL_URL")
    )

    # 构建提示词模板,包含历史消息占位符
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个专业的文档问答助手。请遵循以下规则:\n1. 回答要简洁准确,不要编造内容\n2. 如果有历史对话,请结合上下文回答"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{question}")
    ])

    # 构建链
    chain = prompt | llm | StrOutputParser()
    return chain


# 2. 调用问答链(带历史记忆)
def call_qa_chain(chain, question: str, history: List[BaseMessage]):
    # 调用链,传入问题和历史
    response = chain.invoke({
        "question": question,
        "history": history
    })
    return response


# 3. 输出回答
def print_answer(answer):
    print(f"助手回答: {answer}")


# 交互式问答(带历史记忆)
def interactive_qa():
    # 1. 构建问答链
    chain = build_qa_chain()
    
    # 初始化历史消息列表
    history: List[BaseMessage] = []
    
    # 2. 调用问答链
    while True:
        question = input("\n请输入问题(输入 exit 退出):")
        
        if question == "exit":
            print("再见!")
            break
        
        if not question.strip():
            continue
        
        # 调用问答链
        answer = call_qa_chain(chain, question, history)
        
        # 输出回答
        print_answer(answer)
        
        # 更新历史:添加用户问题和助手回答
        history.append(HumanMessage(content=question))
        history.append(AIMessage(content=answer))
        
        # 可选:显示当前历史长度
        print(f"[已记录 {len(history)//2} 轮对话]")


if __name__ == '__main__':
    interactive_qa()

总结

大模型的组件依赖更新很快,现在使用的方法可能几天后就会不能用了,具体还需要参考官方文档学习:https://langchain-doc.cn/

这是一个小的完整案例,可以根据自己的需求进行改造,比如下面的一些功能,后面我也会分享一个完整的项目案例,现在还在想做一个什么应用。后续会不断更新大模型应用开发的探索和分享

  1. 支持更多文档格式 :扩展 load_documents() 添加 Markdown、Excel 等支持
  2. 优化检索策略:使用混合检索(向量 + 关键词)提升召回率
  3. 流式输出 :使用 rag_chain.stream() 实现打字机效果
  4. Web 界面:集成 Streamlit 或 Gradio 构建 Web 应用
相关推荐
章鱼丸-2 小时前
DAY32 官方文档的阅读
python
于慨2 小时前
docker
python
FPGA小迷弟2 小时前
FPGA 时序约束基础:从时钟定义到输入输出延迟的完整设置
前端·学习·fpga开发·verilog·fpga
GinoWi2 小时前
Chapter 7 Python中的函数
python
m0_518019482 小时前
使用Seaborn绘制统计图形:更美更简单
jvm·数据库·python
Hommy882 小时前
【剪映小助手-客户端】构建与部署
python·aigc·剪映小助手
GinoWi2 小时前
Chapter 6 Python中的字典
python
zh路西法2 小时前
【宇树机器人强化学习】(七):复杂地形的生成与训练
python·深度学习·机器学习·机器人
python猿2 小时前
打卡Python王者归来--第30天
开发语言·python