
大模型在默认情况下是无状态的,每一次 API 请求对于大模型而言都是一次独立的调用。在客服、助理等需要多轮对话的场景中,必须让大模型理解上下文。要让 AI 助手拥有记忆,开发者需要在每次请求时,将之前的聊天记录合并后一并发送给模型。本文介绍如何使用 Go 语言与 LangChainGo 框架的 Memory 组件管理上下文。
大模型的“长期记忆”本质上是通过拼接历史对话实现的。由于模型无法主动记住之前的对话,应用层需要在本地或数据库中记录聊天历史。在发起下一次请求时,将历史记录作为 Prompt 的一部分传递给模型。LangChainGo 提供的 memory 模块将这一过程进行了标准化的封装,避免了手动处理 Message 数组拼接的繁琐逻辑。
在 LangChainGo 中,最基础的记忆组件是 ConversationBuffer 类型,它在内存中以键值对的形式维护对话历史:
// 初始化内存对话历史记录
mem := memory.NewConversationBuffer()
ctx := context.Background()
// 模拟第一轮对话输入与输出的保存
_= mem.SaveContext(ctx,
map[string]any{"input":"你好,我是研发部的开发人员。"},
map[string]any{"output":"您好!请问有什么我可以帮您的?"})
使用 SaveContext 保存完历史后,可以通过 LoadMemoryVariables 加载出来。如果每次请求都手动提取并拼接,代码会显得冗余,LangChainGo 提供了专用的 Conversation 链来自动化这一过程。
LangChainGo 提供了 NewConversation 方法,可自动将对话记忆与 LLM 调用链路打通,实现全自动的上下文组装与保存:
// 创建全自动对话链并提问
chain := chains.NewConversation(llm, mem)
res, err := chains.Call(ctx, chain,map[string]any{
"input":"我最喜欢的编程语言是 Go。",
})
if err !=nil{
log.Fatalf("调用对话链失败: %v", err)
}
在执行 chains.Call 时,Chain 会自动将 mem 中已有的历史对话加载并拼装到 Prompt 中发送给大模型,且在大模型返回响应后,自动将本次的问答对再次存入 mem 中。
随着对话轮数增加,历史上下文占用的 Token 会线性增长。这不仅增加了 API 的调用费用,也可能导致请求超出模型的最大上下文长度。常见的解决方法是采用滑动窗口,仅保留最近 N 轮对话:
// 限制内存中仅保留最近 5 轮的对话历史
memWindow := memory.NewConversationWindowBuffer(5)
chainWindow := chains.NewConversation(llm, memWindow)
// 再次发起提问,旧的对话将滚动失效
_,_= chains.Call(ctx, chainWindow,map[string]any{
"input":"接下来我们聊聊并发模型吧。",
})
ConversationWindowBuffer 会在底层自动裁剪过期的消息,保证发送给大模型的历史记录始终维持在预设的轮数内。
除了限制固定轮数,更精细化的做法是根据 Token 长度来进行动态裁剪。例如,要求历史记录占用的最大 Token 数不得超过 2000 个:
// 设定最大 Token 限制,超出时自动裁剪头部消息
memToken := memory.NewConversationTokenBuffer(llm,2000)
chainToken := chains.NewConversation(llm, memToken)
// 发起对话,底层将自动计算 Token 并裁剪
_,_= chains.Call(ctx, chainToken,map[string]any{
"input":"生成一段高性能的 TCP 粘包处理代码",
})
在多实例部署的生产环境下,内存型 Memory 无法实现跨节点的状态共享,需要将历史记录持久化至外部存储。LangChainGo 提供了 schema.ChatMessageHistory 接口,方便开发者自定义 Redis 等存储介质的对接逻辑:
// RedisHistory 结构体,实现 schema.ChatMessageHistory 接口
type RedisHistory struct{
Client *redis.Client
SessionID string
}
// 往 Redis 中追加新的消息
func(r *RedisHistory)AddMessage(ctx context.Context, msg llms.ChatMessage)error{
storeMsg :=map[string]string{
"type":string(msg.GetType()),
"content": msg.GetContent(),
}
data,_:= json.Marshal(storeMsg)
return r.Client.RPush(ctx, r.SessionID, data).Err()
}
这里省略了接口的其他几个方法。在实现自定义存储逻辑后,只需通过 Option 配置项将其注入到现有的 Memory 组件中:
// 将自定义的 Redis 历史存储注入到 ConversationBuffer
redisHistory :=&RedisHistory{Client: client, SessionID:"user_session_123"}
mem := memory.NewConversationBuffer(memory.WithChatHistory(redisHistory))
chain := chains.NewConversation(llm, mem)
通过该设计,无需改动原有的 Chain 调用逻辑,即可将对话历史集中管理,满足服务的横向扩容需求。
合理控制和持久化上下文,不仅是为了业务层面的连贯体验,也是控制大模型 API 开销和应对高可用部署的重要步骤。
LangChainGo 提供的 Memory 抽象和 Window、Token 缓冲区等预设组件,能够覆盖大部分多轮对话开发场景。在此基础上,通过实现 schema.ChatMessageHistory 接口,可以零侵入地接入 Redis、PostgreSQL 等外部数据库,这为 Go 语言 AI 应用从原型开发走向生产落地提供了坚实的工程支撑。