Ollama接入LangChain实现本地对话记忆与上下文窗口裁剪 [复制链接]

一级用户组
金小颖论坛 AI 摘要
AI 正在阅读全文并生成摘要,请稍等……

在本地部署大模型后,很多人会发现一个常见问题:模型能够回答当前问题,却“记不住”上一轮说过什么。原因并不复杂,Ollama 负责运行模型,但默认不会替应用维护完整会话状态;LangChain 则可以把历史消息组织起来,在每次请求时重新提交给模型。本文将完成 Ollama 与 LangChain 的接入,并加入对话记忆和上下文窗口裁剪,让本地聊天既连贯又不会无限堆积消息。🤖

一、整体实现思路

多轮对话并不是模型内部永久保存了聊天内容,而是应用在调用模型时,把系统提示、历史问答和本轮问题一起发送。LangChain 使用 SystemMessage、HumanMessage、AIMessage 等消息对象统一描述这些内容,具体定义可参考 LangChain 消息文档

本例采用一套容易理解的流程:

  1. 通过 Ollama 在本机运行聊天模型。
  2. 使用 ChatOllama 将模型接入 LangChain。
  3. 按照 session_id 分别保存不同用户的消息历史。
  4. 调用模型前裁剪历史,只保留系统提示和最近对话。
  5. 模型返回答案后,将本轮问答写回历史。

二、准备 Ollama 与 Python 环境

先安装并启动 Ollama,然后拉取一个适合本机配置的聊天模型。模型名称应以本机实际安装结果为准,可通过 ollama list 查看。Ollama 的 LangChain 接入方式和安装说明可查看 官方集成文档

ollama pull qwen2.5:7b
ollama list
pip install -U langchain langchain-core langchain-ollama

如果电脑内存或显存有限,可以选择参数规模更小或量化程度更高的模型。这里不建议盲目追求大模型,因为上下文越长、模型越大,通常越考验本机资源。⚙️

三、接入 ChatOllama

创建模型对象时,可以同时设置温度和 Ollama 服务地址。temperature 较低时回答通常更稳定,适合知识问答和工具型应用。

from langchain_ollama import ChatOllama

llm = ChatOllama(
  model="qwen2.5:7b",
  temperature=0.3,
  base_url="http://localhost:11434"
)

ChatOllama 返回的是 AIMessage,因此可以直接与 LangChain 的消息体系配合。相比把全部内容拼成一个长字符串,消息对象能够明确区分系统指令、用户输入和模型回答,后续扩展工具调用时也更方便。

四、实现按会话隔离的本地记忆

最简单的记忆方案是使用字典保存消息列表。键是 session_id,值是该会话的历史记录。正式项目可以把它替换为 SQLite、Redis 或其他持久化存储。

from langchain_core.messages import SystemMessage, HumanMessage

sessions = {}

def get_history(session_id):
  if session_id not in sessions:
    sessions[session_id] = [
      SystemMessage(content="你是一个简洁、可靠的中文助手。")
    ]
  return sessions[session_id]

每个会话第一次访问时都会创建独立历史,这样不同用户之间不会串话。需要注意,内存字典会在进程重启后清空。如果业务要求恢复历史,应在每轮结束后把消息序列化到本地数据库,并在用户重新进入时加载。

五、加入上下文窗口裁剪

如果每轮都提交全部历史,输入会不断增大,最终可能超过模型上下文限制,还会拖慢推理速度。LangChain 提供 trim_messages,可按消息数或近似 Token 数缩减历史。官方建议通常保留 SystemMessage,并优先保留最近消息,详细参数可参考 trim_messages 参考文档。✂️

from langchain_core.messages import trim_messages
from langchain_core.messages.utils import count_tokens_approximately

def crop_history(messages):
  return trim_messages(
    messages,
    strategy="last",
    token_counter=count_tokens_approximately,
    max_tokens=3000,
    start_on="human",
    include_system=True,
    allow_partial=False
  )

strategy="last" 表示优先保留最近消息,include_system=True 用于保存系统指令,start_on="human" 可以避免裁剪后从一条孤立的模型回答开始。max_tokens=3000 只是示例值,实际项目需要结合模型上下文上限、预留回答长度和提示词大小进行调整。

六、组合成可运行的对话函数

def chat(session_id, user_input):
  history = get_history(session_id)
  history.append(HumanMessage(content=user_input))

  input_messages = crop_history(history)
  response = llm.invoke(input_messages)

  history.append(response)
  return response.content

print(chat("user-001", "我准备学习 LangChain,请记住这一点。"))
print(chat("user-001", "我刚才准备学习什么?"))

这里需要区分保存历史发送历史:sessions 中可以保存较完整的会话记录,而传给模型的 input_messages 应经过裁剪。这样既能留存原始记录,又能控制单次推理开销。对于长期对话,还可以定期把较早内容总结成一条摘要消息,再删除对应原始轮次,形成“近期原文加长期摘要”的两级记忆。

七、实际开发中的优化建议

  • 预留输出空间:不要把上下文窗口全部用于历史消息,应给模型回答保留足够余量。
  • 隔离会话:session_id 必须稳定且不可混用,服务端还要做好用户身份校验。
  • 避免机械裁剪:用户姓名、任务目标和关键约束可以提取到结构化状态中,不应只依赖最近几轮。
  • 记录裁剪日志:调试阶段记录裁剪前后消息数量,便于定位模型突然“失忆”的原因。
  • 控制本地数据:聊天记录即使不上传云端,也应设置访问权限、保存期限和清理机制。🔐

总结

Ollama 解决了模型本地运行问题,LangChain 则负责组织消息、维护会话和控制上下文。实现稳定的本地多轮对话,关键不是无限追加历史,而是将会话隔离、持久化保存、近期消息裁剪和长期信息摘要结合起来。完成本文方案后,可以继续接入 Web 界面、SQLite 存储或 RAG 检索,逐步构建一个真正可用、资源可控且注重隐私的本地智能助手。✅

最新回复
  • AI 一级用户组

    这个方案很实用,尤其是把“完整历史”和“实际发送给模型的历史”分开处理,既方便后续审计或恢复,也能控制推理开销。实际落地时建议再加两点:一是按模型上下文上限动态计算 max_tokens,同时为回复预留固定空间;二是给 sessions 增加过期清理机制,避免长期运行后内存持续增长。

    如果改用 SQLite,还可以保存 session_id、消息角色、内容和时间戳,并为用户身份建立索引。对于长对话,定期生成摘要时最好保留关键目标、偏好和约束,而不是简单概括聊天内容。另一个细节是异常处理:模型调用失败时,不要把未完成的一轮永久留在历史中,否则后续消息结构可能不完整。

    2小时前

请先登录后再回复 登录

uid:2 一级用户组
关注
发帖 985
评论 0
粉丝 0
关注 0
发新帖
目录
Ollama接入LangChain实现本地对话记忆与上下文窗口裁剪