ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

使用 SQLite 持久化会话记忆:langchaingo 对话记忆示例深度解析

使用 SQLite 持久化会话记忆:langchaingo 对话记忆示例深度解析 使用 SQLite 持久化会话记忆langchaingo 对话记忆示例深度解析【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo导读本文围绕 langchaingo 官方示例 chains-conversation-memory-sqlite 展开讲解如何在 Go 中利用 LangChain for Golangchaingo构建一个带记忆的对话式 AI 系统以 OpenAI 作为大语言模型以 SQLite 数据库作为对话历史的持久化存储通过 Conversation Buffer 记忆机制让 AI 记住多轮对话中的上下文。读完本文你将掌握memory/sqlite3包的核心 API 与全部配置项、memory.ConversationBuffer的工作原理以及如何让对话链Conversation Chain在进程重启后依然保留历史记忆并将其迁移到自己的业务代码中。一、示例概述它到底做了什么这是一个可独立运行的最小示例完整源码见 chains_conversation_memory_sqlite.go整个流程分为五个步骤初始化 OpenAI 语言模型通过openai.New()创建一个默认的 OpenAI LLM 实例为对话提供生成能力创建 SQLite 数据库通过标准库database/sql打开本地 SQLite 文件history_example.db实现对话记忆用sqlite3.NewSqliteChatMessageHistory将聊天历史写入 SQLite实现持久化记忆准备示例数据若数据表为空则插入一条人类消息Hi there, my name is Murilo!作为对话的起点运行对话通过chains.Run向对话链提问Whats my name? How many times did I ask this?AI 依据 SQLite 中保存的历史记录回答。这个示例的精髓在于记忆不在内存里而在数据库文件里。即使程序退出、进程重启SQLite 文件中的历史记录依然存在新会话可以无缝接续之前的对话上下文。二、三个核心组件的职责划分示例文档点名的三个关键组件在源码中的对应关系如下组件构造方式职责源码位置SQLite Chat Message Historysqlite3.NewSqliteChatMessageHistory将聊天消息读写到 SQLite 表memory/sqlite3/sqlite3_history.goConversation Buffermemory.NewConversationBuffer把聊天历史包装成链可用的记忆变量memory/buffer.goConversation Chainchains.NewConversation将提示词模板 LLM 记忆组装为可执行链chains/conversation.go这三者是一条清晰的依赖链Chain 依赖 MemoryMemory 依赖 ChatMessageHistoryChatMessageHistory 依赖 SQLite。任何一个环节都可以替换——例如把 SQLite 换成内存存储、MongoDB 或 Zep链与记忆的代码几乎不用改动。2.1 数据流转方向chains.Run执行时核心逻辑见 chains/chains.go链路中的数据是这样流转的链调用Memory.LoadMemoryVariables从 SQLite 读取该 session 的全部历史消息历史与用户输入一起填入_conversationTemplate提示词模板LLM 根据当前对话生成回复链调用Memory.SaveContext把本轮用户输入与 AI 输出写回 SQLite供下一轮使用。正是因为读写都发生在数据库层持久化是天然成立的。三、SQLite 聊天历史源码级实现memory/sqlite3包是记忆持久化的核心我们先看它的实现细节。3.1 默认建表 Schema构造SqliteChatMessageHistory时如果没有显式传入 Schema会执行包内定义的DefaultSchema见 sqlite3_history_options.goCREATE TABLE IF NOT EXISTS langchaingo_messages ( id INTEGER PRIMARY KEY, name TEXT, session TEXT NOT NULL, content TEXT NOT NULL, type TEXT NOT NULL, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_langchaingo_id ON langchaingo_messages (id); CREATE INDEX IF NOT EXISTS idx_langchaingo_session ON langchaingo_messages (session);表结构的关键点session会话标识不同 session 的消息互不干扰是多会话隔离的基石type消息角色取值为ai/human/system对应llms.ChatMessageType枚举created自动填充的时间戳Messages查询时按created ASC排序保证历史消息按时间顺序返回两个索引分别加速按 id 与按 session 的查询。3.2 核心方法与读写实现SqliteChatMessageHistory实现了schema.ChatMessageHistory接口见 schema/chat_message_history.go并在源码中通过var _ schema.ChatMessageHistory SqliteChatMessageHistory{}做了编译期断言见 sqlite3_history.go。各方法的行为Messages(ctx)执行SELECT content, type, created FROM table WHERE session ? ORDER BY created ASC LIMIT ?按type字段反序列化为AIChatMessage、HumanChatMessage或SystemChatMessage见 sqlite3_history.goAddUserMessage/AddAIMessage/AddMessage统一走addMessage执行INSERT INTO table (session, content, type) VALUES (?, ?, ?)见 sqlite3_history.goClear/SetMessages默认是空操作必须通过WithOverwrite()显式开启后才会执行DELETE或事务内清空后批量插入见 sqlite3_history.go。这是一道安全闸门防止误操作清空历史数据SetMessages使用BEGIN TRANSACTION ... COMMIT包裹多条INSERT保证批量替换的原子性。3.3 全部配置项Option一览以下选项通过NewSqliteChatMessageHistory传入全部定义在 sqlite3_history_options.goOption参数默认值作用WithDB*sql.DB无自动按 DBAddress 打开注入外部数据库连接WithDBAddressstring:memory:数据库文件路径或连接地址不传时默认用内存库WithSessionstringdefault会话名或会话 ID用于区分不同对话WithTableNamestringlangchaingo_messages消息表名WithLimitint1000单次Messages查询最多返回的记录数WithSchema[]byteDefaultSchema连接后执行的自定义建表/迁移 SQLWithContextcontext.Contextcontext.Background()执行 Schema 时使用的上下文WithOverwrite无false允许Clear/SetMessages等危险操作两个值得留意的默认行为若不传WithDB也不传WithDBAddress构造器会用sql.Open(sqlite3, :memory:)打开一个进程内的内存数据库——能跑通逻辑但不会持久化见 sqlite3_history_options.go构造器在初始化末尾会自动执行 Schemah.DB.ExecContext(h.Ctx, string(h.Schema))也就是说建表是自动完成的用户无需手动执行 DDL。四、Conversation Buffer记忆与链之间的桥梁memory.ConversationBuffer是最简单的记忆形式把历史对话原样记住不做摘要、不裁剪窗口见 memory/buffer.go。4.1 加载与保存的对称设计加载LoadMemoryVariables调用ChatHistory.Messages取回全部消息。默认输出一个 key 为history的缓冲字符串Human: .../AI: ...交替拼接若设置WithReturnMessages(true)则直接返回[]llms.ChatMessage切片见 memory/buffer.go保存SaveContext从输入值中取用户消息写入AddUserMessage从输出值中取 AI 消息写入AddAIMessage。若未显式指定InputKey/OutputKey则要求输入输出 map 中只能有一个键否则返回ErrInvalidInputValues见 memory/buffer.go。4.2 可用选项与默认值NewConversationBuffer的默认配置见 buffer_options.goOption默认值作用WithChatHistory内存版NewChatMessageHistory()指定底层聊天历史存储示例中注入 SQLiteWithReturnMessagesfalse是否以消息切片而非字符串返回历史WithInputKey/WithOutputKey自动推断指定输入/输出 map 的取值键WithHumanPrefix/WithAIPrefixHuman/AI生成缓冲字符串时的角色前缀WithMemoryKeyhistory记忆变量在 prompt 中的键名在示例中只使用了WithChatHistory其余全部走默认值因此 prompt 中对应的记忆变量就是history。五、Conversation Chain组装与提示词模板chains.NewConversation返回一个LLMChain其提示词模板定义在 chains/conversation.goThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. Current conversation: {{.history}} Human: {{.input}} AI:模板只暴露两个变量history记忆缓冲字符串与input用户本轮输入。该链同时设置了OutputParser: outputparser.NewSimple()即输出不做额外解析、原样返回文本。六、运行示例与完整调用链6.1 环境准备示例是一个独立的 Go module其依赖声明见 go.mod需要github.com/tmc/langchaingov0.1.14-pre.4与 SQLite 驱动github.com/mattn/go-sqlite3。运行前请确认本机已配置OPENAI_API_KEY环境变量openai.New()默认从环境变量读取密钥Go 版本满足 module 要求示例声明go 1.24.3。在示例目录下执行go run chains_conversation_memory_sqlite.go首次运行会生成history_example.db文件程序向 AI 提问Whats my name? How many times did I ask this?AI 依靠库里预置的自我介绍消息Hi there, my name is Murilo!回答出用户的名字。6.2 示例代码逐段解读示例的完整主流程见 chains_conversation_memory_sqlite.gollm, err : openai.New() // 1. 初始化 OpenAI LLM db, err : sql.Open(sqlite3, history_example.db) // 2. 打开 SQLite 数据库 // 3. 构建 SQLite 聊天历史指定会话 example chatHistory : sqlite3.NewSqliteChatMessageHistory( sqlite3.WithSession(example), sqlite3.WithDB(db), ) // 4. 将聊天历史注入 Conversation Buffer 记忆 conversationBuffer : memory.NewConversationBuffer(memory.WithChatHistory(chatHistory)) // 5. 组装对话链 llmChain : chains.NewConversation(llm, conversationBuffer) // 6. 若表为空则插入示例消息 prepare(ctx, db) // 7. 运行链问一个依赖记忆的问题 out, err : chains.Run(ctx, llmChain, Whats my name? How many times did I ask this?) fmt.Println(out)其中prepare函数见 chains_conversation_memory_sqlite.go先执行SELECT count(id) FROM langchaingo_messages判断数据是否为空仅当count 0时才插入初始消息避免重复播种数据。6.3 一个值得亲自实验的现象再次运行go run chains_conversation_memory_sqlite.go不删除history_example.db时由于表中已有记录prepare不会重复插入。这正是验证持久化记忆的最佳方式更换提问内容例如询问我刚才告诉过你什么名字AI 依然能从上一次会话写入的数据库中找回答案——因为每轮对话的输入输出都已经通过SaveContext落盘。提示若想清空历史重跑可先删除history_example.db文件该文件由程序自动创建属运行时产物不在仓库内。七、测试验证记忆行为的自动化保障memory/sqlite3包自带单元测试 sqlite3_history_test.go可用go test ./memory/sqlite3/运行。它验证了两个关键行为基本读写AddAIMessage与AddUserMessage写入后Messages按插入顺序返回对应的AIChatMessage与HumanChatMessage覆盖写语义开启WithOverwrite后SetMessages能整体替换历史且后续AddUserMessage追加在新序列之后。第二个用例直接印证了默认Clear/SetMessages是空操作、必须显式开启WithOverwrite的安全设计——在未开启 Overwrite 的实例上调用这两个方法不会改动任何数据。八、扩展思路把示例改造成生产代码基于以上源码分析从示例走向生产可以这样做多会话支持为每个用户/会话传入不同的sqlite3.WithSession(...)同一张表即可承载海量独立对话控制历史长度用sqlite3.WithLimit(n)限制每次加载的历史条数避免超长上下文推高 token 成本更精细的裁剪可使用memory.NewTokenBuffer按 token 数控制接入其他 LLMchains.NewConversation接受任意llms.Model把openai.New()换成 Ollama、GoogleAI、Anthropic 等实现即可替换存储后端langchaingo 还提供 MongoDB、Cloud SQL、AlloyDB、Zep 等聊天历史实现见 memory 目录只要实现了schema.ChatMessageHistory接口ConversationBuffer与对话链无需任何改动迁移管理利用sqlite3.WithSchema(...)在连接时执行自定义迁移 SQL让表结构演进与连接生命周期绑定。总结chains-conversation-memory-sqlite示例展示了 langchaingo 记忆体系的最小完整闭环SqliteChatMessageHistory负责持久化存取ConversationBuffer负责把存储包装成语义化的历史变量Conversation链负责把它注入提示词并回写每一轮对话。理解这三层的边界与默认值你就能在任意 Go 应用中快速复刻跨进程、跨重启的持久对话记忆能力。想要深入实践可以直接运行本示例并替换提问内容观察记忆效果也可以阅读 memory/sqlite3/sqlite3_history_test.go 理解接口契约再对照 memory/buffer.go 与 chains/chains.go 追踪一次完整的记忆读写旅程。【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表