LangChain Go 使用 Google AlloyDB 持久化 Chat Message History:完整实战指南
【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo
本指南围绕 langchaingo 仓库中 google-alloydb-chat-message-history-example 示例,讲解如何将 Google Cloud 的 AlloyDB for PostgreSQL 作为 Go 版 LangChain 对话记忆(ChatMessageHistory)的持久化后端。读完本文,你将掌握从环境配置、连接池初始化、建表到消息增删查改的完整链路,并深入理解memory/alloydb与util/alloydbutil两个包的源码实现原理。
背景:为什么用数据库承载对话记忆
在构建 LLM 应用时,多轮对话需要把历史消息保存下来,才能在下一轮请求中作为上下文提供给模型。LangChain Go 通过 schema.ChatMessageHistory 接口抽象了这一能力,其定义了六个核心方法:
AddMessage:追加一条llms.ChatMessage消息;AddUserMessage/AddAIMessage:便捷地追加用户或 AI 消息字符串;Clear:清空全部消息;Messages:取回全部消息;SetMessages:整体替换现有消息。
默认的 memory 实现把消息放在进程内存里,进程重启即丢失。当应用需要多实例共享、会话跨进程恢复时,就需要一个数据库后端。本示例展示的正是将 AlloyDB for PostgreSQL(Google Cloud 的托管 Postgres 兼容数据库)作为该接口实现的存储层。
准备工作:前置条件与运行环境
在运行示例前需要完成以下云环境准备(对应 memory/alloydb/README.md 中的 Quick Start 步骤):
- 创建或选择一个 Google Cloud 项目;
- 为项目启用结算(billing);
- 在控制台启用 AlloyDB API;
- 通过
gcloud auth application-default login完成 Cloud SDK 认证。
代码层面,examples/go.mod 显示该示例基于go 1.24.3,依赖github.com/tmc/langchaingo v0.1.14-pre.4,并通过cloud.google.com/go/alloydbconn建立安全连接。仓库中memory/alloydb包要求Go 版本不低于 1.22.0。示例的模块声明位于examples/google-alloydb-chat-message-history-example/go.mod,进入该目录即可独立构建运行。
第一步:配置环境变量
示例程序通过环境变量注入连接信息与业务参数,运行时任何一项缺失都会以log.Fatal立即退出(见 getEnvVariables 函数):
export PROJECT_ID=<your project Id> export ALLOYDB_USERNAME=<your user> export ALLOYDB_PASSWORD=<your password> export ALLOYDB_REGION=<your region> export ALLOYDB_CLUSTER=<your cluster> export ALLOYDB_INSTANCE=<your instance> export ALLOYDB_DATABASE=<your database> export ALLOYDB_TABLE=<your tablename> export ALLOYDB_SESSION_ID=<your sessionID>其中PROJECT_ID、ALLOYDB_REGION、ALLOYDB_CLUSTER、ALLOYDB_INSTANCE用于定位 AlloyDB 实例,可在 Google Cloud 控制台的 AlloyDB 集群页面找到;ALLOYDB_TABLE指定存放消息的数据表名;ALLOYDB_SESSION_ID用于区分不同的会话,是消息读写隔离的关键。
第二步:初始化 PostgresEngine 连接引擎
连接的核心是alloydbutil.PostgresEngine,它内部持有一个*pgxpool.Pool连接池(见 util/alloydbutil/engine.go)。示例中的初始化方式:
pgEngine, err := alloydbutil.NewPostgresEngine(ctx, alloydbutil.WithUser(username), alloydbutil.WithPassword(password), alloydbutil.WithDatabase(database), alloydbutil.WithAlloyDBInstance(projectID, region, cluster, instance), ) if err != nil { log.Fatal(err) }底层连接原理
从 createPool 实现 可以看到连接建立的完整逻辑:
- 使用
alloydbconn.NewDialer创建连接拨号器,DSN 为user=... password=... dbname=... sslmode=disable; - 依据传入的项目、区域、集群、实例拼接出 AlloyDB 实例 URI:
projects/<project>/locations/<region>/clusters/<cluster>/instances/<instance>; - 通过自定义
DialFunc调用d.Dial建立安全隧道,默认使用公网 IP(PUBLIC),可通过WithIPType("PRIVATE")切换到私网; - 最终基于
pgxpool.NewWithConfig生成连接池。
其他可用 Option
util/alloydbutil/options.go 定义了全部引擎选项:
| Option | 作用 |
|---|---|
WithAlloyDBInstance(project, region, cluster, instance) | 设置实例定位信息 |
WithUser/WithPassword | 设置数据库账号密码 |
WithDatabase | 设置目标数据库名 |
WithPool | 直接注入外部创建的pgxpool.Pool(适用于 AlloyDB Omni 或自定义连接池) |
WithIPType | 指定公网(PUBLIC,默认)或私网(PRIVATE)连接 |
WithIAMAccountEmail | 使用 IAM 账号邮箱认证 |
关于认证方式,getUser 函数 展示了三种分支:优先使用用户名 + 密码;若提供WithIAMAccountEmail则走 IAM 认证(DSN 中不再含密码,并追加alloydbconn.WithIAMAuthN());若两者皆无,则尝试通过默认凭据获取服务账号邮箱。这意味着示例展示的是最简单的账号密码方式,实际生产环境还可以切换到 IAM 认证以规避口令管理。
第三步:创建消息表
AlloyDB 要求消息表具备固定结构。示例调用:
err = pgEngine.InitChatHistoryTable(ctx, tableName)对应 InitChatHistoryTable 实现,其生成的 DDL 为:
CREATE TABLE IF NOT EXISTS "<schema>"."<table>" ( id SERIAL PRIMARY KEY, session_id TEXT NOT NULL, data JSONB NOT NULL, type TEXT NOT NULL );表结构中的四个列与ChatMessageHistory.validateTable的校验逻辑一一对应(见 memory/alloydb/chat_message_history.go):id为整数自增主键(保证消息读取顺序),session_id用于会话隔离,data以 JSONB 存储消息正文,type记录消息类型。创建时默认使用publicschema,也可通过WithSchemaName指定自定义 schema。
第四步:创建 ChatMessageHistory 并验证表结构
cmh, err := alloydb.NewChatMessageHistory(ctx, pgEngine, tableName, sessionID) if err != nil { log.Fatal(err) }NewChatMessageHistory 会做三件事:
- 校验必需参数:连接池非空、表名非空、会话 ID 非空,缺失即返回明确错误(如
"table name must be provided"、"session ID must be provided"); - 应用
WithSchemaName等可选配置(默认 schema 为public,见 chat_message_history_options.go); - 调用
validateTable查询information_schema,确认表存在且四个必需列的名称与类型完全匹配,否则返回形如error, column 'data' is missing in table 'xxx'的错误。
值得注意的是var _ schema.ChatMessageHistory = &ChatMessageHistory{}这一编译期断言(memory/alloydb/chat_message_history.go),它保证alloydb.ChatMessageHistory完整实现了上文提到的标准接口,从而可以无缝嵌入 LangChain Go 的对话链路。
第五步:消息的增、查、改、清
示例随后演示了针对同一sessionID的完整消息生命周期操作。
逐条添加消息
aiMessage := llms.AIChatMessage{Content: "test AI message"} humanMessage := llms.HumanChatMessage{Content: "test HUMAN message"} err = cmh.AddUserMessage(ctx, aiMessage.GetContent()) err = cmh.AddUserMessage(ctx, humanMessage.GetContent()) printMessages(ctx, cmh)底层 addMessage 先将内容 JSON 序列化,再执行INSERT INTO "<schema>"."<table>" (session_id, data, type) VALUES ($1, $2, $3)。此外还提供AddMessage(接收任意llms.ChatMessage)与AddAIMessage(固定标记为 AI 类型)等便捷方法。
批量添加消息
multipleMessages := []llms.ChatMessage{ llms.AIChatMessage{Content: "first AI test message from AddMessages"}, llms.AIChatMessage{Content: "second AI test message from AddMessages"}, llms.HumanChatMessage{Content: "first HUMAN test message from AddMessages"}, } err = cmh.AddMessages(ctx, multipleMessages)AddMessages 使用pgx.Batch把所有插入语句打包后经SendBatch一次提交,避免逐条往返的网络开销,是批量写入的高效路径。
查询消息
示例中printMessages辅助函数调用cmh.Messages(ctx)遍历输出。对应的 Messages 实现 执行SELECT ... WHERE session_id = $1 ORDER BY id,确保消息按写入顺序返回;随后反序列化 JSON 内容,并根据type字段还原为AIChatMessage、HumanChatMessage或SystemChatMessage。若遇到未知类型则返回"unsupported message type"错误。
覆盖消息
overWrittingMessages := []llms.ChatMessage{ llms.AIChatMessage{Content: "overwritten AI test message"}, llms.HumanChatMessage{Content: "overwritten HUMAN test message"}, } err = cmh.SetMessages(ctx, overWrittingMessages)SetMessages 的语义是"先清空该会话旧消息,再批量写入新消息",实现上先调用Clear再走批量插入,适合重写整段对话上下文的场景。
清空会话消息
err = cmh.Clear(ctx)Clear 执行DELETE FROM "<schema>"."<table>" WHERE session_id = $1,只删除当前会话的消息,不影响其他会话数据。
运行示例
在配置好环境变量后,进入示例目录执行:
cd examples/google-alloydb-chat-message-history-example go run google_alloydb_chat_message_history_example.go程序会依次打印三批消息:初始添加的两条、批量追加后的五条(含前两条)、覆盖后的两条,最后清空会话。仓库中的集成测试 memory/alloydb/chat_message_history_test.go 验证了相同路径:测试用例覆盖了"表名缺失报错""会话 ID 缺失报错"以及AddMessage、AddAIMessage、AddUserMessage、Clear的正常调用链;由于需要真实数据库,测试在缺少ALLOYDB_USERNAME等环境变量时会自动t.Skip跳过。
关键特性小结
对照原示例文档,这套方案的核心价值可以归纳为:
- AlloyDB 深度集成:
alloydbutil负责与 AlloyDB 实例建立安全连接池,memory/alloydb负责消息的存储与管理,职责清晰; - 会话级消息管理:所有读写操作都以
session_id为维度,天然支持多会话并存与隔离; - 完整操作能力:覆盖逐条添加、批量添加、整体覆盖、查询与清空五种操作,满足对话记忆的常见需求;
- 源码级可靠性:创建时即校验表结构与列类型,接口实现有编译期断言保障,批量操作利用
pgx.Batch提升性能。
如需进一步了解引擎 API 的更多细节,可继续阅读 util/alloydbutil/options.go 与 memory/alloydb/chat_message_history_options.go;仓库中还提供了对应的 Postgres Vector Store 示例,可在同一 AlloyDB 实例上同时承载向量检索与对话记忆两类数据。
【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考