做智能体工作流绕不开一个现实问题:状态存哪里。跑一次对话很容易,但要实现多轮记忆、跨会话状态同步、多个智能体实例共享同一份上下文,就需要一个低延迟、支持实时同步的存储方案。我在 n8n 里调试过好几轮,最后把 Google Cloud Realtime Database(也就是 Firebase 旗下的实时数据库)节点用进了几个自动化项目,效果非常稳。
这篇就把这个节点的完整玩法讲透:凭证配置怎么弄、读写更新每种操作怎么用、多轮对话记忆的场景怎么搭、最常见的问题怎么排。无论你是刚接触 n8n 的智能体开发新手,还是已经在生产环境里折腾过的老手,都可以直接对照着抄。我知道很多人一听到 Google Cloud 就发怵,其实这套配置比想象中简单,今天一步步拆给你看。
1. 智能体开发为什么需要 Realtime Database 节点
1.1 智能体的“记忆”到底该存哪
先讲一段我自己的弯路。最早做 n8n 智能体时,我把对话历史直接写在工作流的静态数据里,或者塞进本地 JSON 文件。问题很快就暴露了:工作流一旦被并行触发多个实例,或者 n8n 重启,状态就乱成一团。后来换过 MySQL 存,持久化问题解决了,但每次读写都要先维护表结构,还得考虑字段迁移,对话这种嵌套的半结构化数据存进关系表,怎么设计都别扭。
智能体的状态数据有几个非常明显的特征:写入频繁、读取频繁、结构不固定、需要按用户或会话维度快速定位。这恰好是文档型 NoSQL 的舒适区。Google Cloud Realtime Database 以 JSON 树的形式组织数据,天生贴合对话记录这类嵌套结构,而且它的设计初衷就是实时场景,客户端和数据层之间的同步延迟可以做到亚秒级。对 n8n 工作流来说,它就像一个挂在外面的共享记忆库,不管工作流重启多少次、实例扩到多少个,记忆都原封不动躺在那里。
1.2 Realtime Database 的核心机制速览
Firebase Realtime Database(后面我统一叫 RTDB)是托管在 Google Cloud 上的 NoSQL 数据库,最核心的特征就是“实时同步”。数据以 JSON 树组织,树上的每个节点就是一条记录,客户端 SDK 可以对指定路径建立监听,路径下的数据一变化,所有在线终端在几百毫秒内就能收到推送。这正是它跟传统关系型数据库最大的体验差异,不需要频繁轮询,数据一变,大家都能感知到。
在 n8n 里使用这个节点,本质上是在“服务端到服务端”地操作这棵 JSON 树。n8n 节点发出的读写请求,走的是 Google 的 REST API,由服务账号完成认证。也就是说,你可以用同一套路径规则随时读取或写入树上任意节点,不需要预先建表,不需要定义字段,也不需要手动维护索引,路径本身就是你的数据模型。灵活度和自由度都很高,代价是不支持复杂查询和跨节点事务,不过绝大多数智能体场景也用不上那些能力。
1.3 和 Firestore、MySQL 等方案的取舍
很多人会问同一个问题:Google Cloud 下还有 Firestore,n8n 也有 MySQL、Postgres 节点,为什么偏偏选 RTDB?我的判断依据主要是两个维度:数据形态和同步需求。
| 方案 | 数据模型 | 实时同步 | 适合场景 |
|---|---|---|---|
| Realtime Database | JSON 树 | 原生支持,亚秒级 | 对话记忆、设备状态、实时缓存 |
| Firestore | 文档集合 | 原生支持 | 结构化业务数据、需要范围查询 |
| MySQL/Postgres | 关系表 | 需要额外轮询 | 强一致事务、复杂报表 |
核心取舍逻辑是这样的:如果诉求是“快速写入、快速读取、多端同步、结构灵活”,RTDB 是成本最低的选择。它做不了 SQL 那种复杂联表查询,但你的数据如果天然就长在“路径”上(比如按用户 ID 组织),那查询能力压根用不上。而 Firestore 的查询能力更强,但写入延迟和实时推送特性跟 RTDB 有细微差别,对纯“记忆存储”需求来说属于杀鸡用牛刀。我并不是说 RTDB 全面优于其他方案,而是它和智能体场景的匹配度最高,尤其是跟 n8n 这种低代码编排工具组合时,配置链路最短。
2. 前置准备:Google Cloud 项目、服务账号与凭证配置
2.1 在 Google Cloud 控制台开通数据库
要在 n8n 里接这个节点,第一步是保证对应的 Google Cloud 项目里已经启用了 Realtime Database。这一步很多人会漏,只创建了空的 Cloud 项目就来搞 n8n,结果节点怎么跑都是 404。
按这个顺序来操作:
- 打开 Google Cloud 控制台,新建一个项目,或者选已有的项目。
- 如果没有绑定 Firebase,先通过 Firebase 控制台把项目加入 Firebase,路径是 Firebase 控制台 → 添加项目 → 选择 Google Cloud 项目。
- 在 Firebase 控制台左侧菜单进入“构建” → “Realtime Database”,点击“创建数据库”。
- 选择数据所处区域。这里重点提醒:RTDB 的区域一旦确定,数据无法跨区迁移,生产环境一定要选离业务最近的区域。
- 设置安全规则。开发阶段可以直接选“测试模式”,它会允许所有读写,30 天之后自动失效,适合调试;生产环境务必选“锁定模式”,只让服务账号和业务规则访问。
有个细节我强调过很多次:光有 Google Cloud 项目但没有在 Firebase 控制台启用 RTDB,n8n 节点请求时通常会收到“项目未启用”或“数据库不存在”之类的错误。这个前置步骤真的绕不过去,十个来问我的新手里有七个卡在这里。如果你是照着这篇博文一路做下来的,建议在这个环节多花两分钟确认一遍 Firebase 控制台的数据库状态。
2.2 创建服务账号并下载密钥
n8n 的 Google Cloud Realtime Database 节点走服务端认证,需要在 IAM 里创建服务账号,并下载 JSON 密钥文件。
具体步骤如下:
- 进入 Google Cloud 控制台 → IAM 与管理员 → 服务账号 → 创建服务账号。
- 名称随便填,比如 n8n-rtdb-sa,说明里写上用途,方便以后审计。
- 角色分配:搜索并选择“Firebase Realtime Database Admin”或同级别的 RTDB 管理角色。如果只是做只读类工作流,可以选权限更小的 Viewer 角色,最小权限原则在项目治理里很重要。
- 创建完成后,进入该服务账号详情页,点击“密钥”标签 → “添加密钥” → “创建新密钥” → 密钥类型选择 JSON → 下载。
下载下来的文件里,最重要的两个字段是client_email和private_key,n8n 凭证配置时主要用它们。这里必须提醒一句:密钥文件等同于账号密码,别提交进 Git 仓库,别发到任何群里。我见过不止一次有开发者把私钥打进公开仓库,整个项目的数据库配置完全暴露在外,清洗成本非常高,千万别图省事把安全底线扔了。
2.3 在 n8n 中配置凭证
在 n8n 工作流里添加 Google Cloud Realtime Database 节点,然后按以下步骤配置凭证:
- 点击节点,打开 Credential 下拉框,选择“创建新凭证”。
- 认证方式选择 Service Account(服务账号),这是节点间通信最省事的方案。
- 依次填入服务账号邮箱(client_email)、私钥(private_key 的完整内容,包含
-----BEGIN到-----END的整段)、项目 ID(project_id)。 - 保存凭证,回到节点配置页面。
- 在节点参数里填写数据库 URL,格式类似
https://<项目ID>-default-rtdb.firebaseio.com/,如果你的项目创建时选了特定区域,URL 会带区域后缀,以 Firebase 控制台里显示的实际 URL 为准。
验证方法很简单:操作选 Get,路径填/,执行一次节点。配置正确会返回根节点 JSON 数据,可能只是一个空对象{}。返回 403 说明角色不对,返回 401 说明私钥出了问题,返回 404 说明项目 ID 或数据库 URL 填错了。把这几条背下来,后面排查能省一半时间。我第一次配这个节点时就在私钥换行上栽过跟头,后面第 5 章我会专门讲。
3. 节点完整操作解析:Get、Create、Update、Delete
3.1 Get:按路径读取数据
Get 是最基础也最常用的操作,核心字段只有一个:Path(路径)。路径用斜杠分割层级,语义很直观,/users/userId就是读取 users 下 userId 这个节点的内容。
实际使用中,路径往往是动态拼接的。在智能体记忆场景里,路径长这样:
/customers/{{ $json.userId }}/conversationsn8n 的表达式系统在这里很好用,只要前序节点解析出了 userId,路径就能锁定对应客户的数据。我建议所有读操作都这么设计,而不是把整张表读出来再在 Code 节点里过滤,那样既慢又浪费配额。Get 有个容易误判的行为:如果路径指向的节点不存在,节点不会报错,而是返回空对象或 null。排错时看到“返回空”先确认路径是否拼对了,别急着怀疑节点坏了,这是 RTDB 和关系型数据库非常不一样的地方——关系型数据库查不到会明确报错,RTDB 只会安静地返回空。
调试技巧:第一次跑通某个数据模型时,先 Get 根路径/,看看整棵树的实际结构,再逐步收窄到具体路径。这比对着文档猜路径高效得多,尤其当你接手别人搭了一半的工作流时,根路径一眼就能看出全部数据长什么样。
3.2 Create:写入数据
Create 操作会根据你是否指定完整路径,表现出两种行为:
- 路径留空或只指向父级:RTDB 自动生成一个唯一键(形如
-Nxyz...)作为新节点,相当于 Push 追加,适合消息列表、日志这类只增不改的数据。 - 路径指定完整:数据直接写入该路径,相当于 Set,如果该路径已有内容会被整体覆盖,适合用户画像这类“一人一档”的数据。
我在项目里通常这样组合:消息记录走 Push 追加,保证历史轨迹完整;用户当前状态走 Set,保证永远是最新值。这样既不会丢历史,也不会让状态字段残留过时数据。
Data 字段直接填 JSON 对象,可以引用上游输出:
{ "role": "user", "content": "{{ $json.body.message }}", "timestamp": "{{ Date.now() }}" }注意,传入的数据必须是合法 JSON 对象。如果某个字段表达式返回了空值或 undefined,整个写入请求都可能失败。写入前建议用一个 Code 节点做数据清洗,该补默认值的补默认值,该删空字段的删干净。我自己就在生产环境吃过亏,某次上游 Webhook 解析出的字段名为空,Create 直接报错,排查了半天才发现是字段缺失。
3.3 Update:局部更新
Update 和 Create(Set 模式)之间的区别,我见过不少人踩进去。Update 是“按字段合并”——只修改指定路径下的子节点,不影响其他已有字段;Create 是“整节点覆盖”——路径下的原有内容全部替换。
举个例子。路径/users/user123/profile下已有{name: "张三", age: 30}。想改年龄:
- Update 传
{age: 31},结果仍是{name: "张三", age: 31}。 - Create 传
{age: 31},name 字段会直接消失。
对智能体业务来说,Update 的使用频率远高于 Create。每轮对话结束后,把新消息合并到历史记录里,但不覆盖旧上下文,就是典型场景。我在第 4 章的实操案例里会详细展示这个用法,你到时候能直观感受到它和 Create 的差异。
并发更新要提前考虑:RTDB 的 Update 在单路径上是原子合并,但多个工作流实例同时对同一路径写不同字段,依然可能出现互相覆盖。高并发场景建议引入版本号或写队列,这个我在第 5 章会展开讲。
3.4 Delete:删除数据
Delete 按路径执行,删除节点时它的所有子节点会被一并移除,而且操作不可逆。最稳妥的习惯是:删除前先 Get 一次确认内容,尤其是删除生产环境的数据节点。我见过有些人把路径写错,一个手滑删掉了整个用户主路径,数据全没了才反应过来。
Delete 的实用场景不少:用户注销或会话过期时,清理该用户路径下的全部数据;定时任务里,清理超过保留期的日志节点;测试完某个数据模型后,把调试产生的脏数据一键清掉。用 n8n 节点做条件删除比较绕,你需要用 Get 查出匹配项,再用 Code 节点遍历生成删除路径列表,最后逐个执行 Delete。数据量大时性价比不高,这种情况我更建议在 Firebase 侧写 Admin SDK 的清理脚本,跑起来更利索。
3.5 路径与数据的几个实操约定
这些约定是我在多个项目里总结出来的,分享给你当参考:
- 路径统一小写开头,层级命名保持一致,避免大小写敏感引发的“找得到但读不到”。RTDB 的路径是大小写敏感的,
/Users/abc和/users/abc是两个完全不同的节点。 - 键名统一用下划线或驼峰,别一会儿 user_id 一会儿 userId,多端对接时字段名打架非常烦。
- 路径末尾不要加多余斜杠,
/users/a/和/users/a在部分场景会被当作不同写法,虽然 n8n 节点多数情况会做归一化,但徒增排查成本。 - 敏感数据不落明文。RTDB 不做字段级加密,token、密钥先加密再入库,至少要做好项目级的数据访问审计。
这些约定看起来是小事,但在团队协作时能省掉大量沟通成本。数据模型一旦上线,再改路径结构就是牵一发动全身的事,一开始定好规矩后面会轻松很多。
4. 智能体实操:多轮对话记忆与跨会话状态同步
4.1 场景设计:一个带记忆的客服智能体
拿一个我实际做过的场景来讲。需求是这样的:一个客服智能体,通过 Webhook 接收用户提问,调用大模型生成回答。要求用户第二次进来时能记住上次聊到哪、知道用户叫什么,不用每次从零开始。
这背后要解决三个问题:会话历史存哪里、n8n 工作流怎么读怎么写、读写会不会拖慢响应。数据模型上,我用用户 ID 作为主路径,按角色区分字段:
/customers/{userId}/ ├── profile: {name, plan, language} └── conversations/ ├── -Nabc...: {role: "user", content: "...", ts: ...} └── -Ndef...: {role: "assistant", content: "...", ts: ...}这个结构的好处很直接:读取时一个 Get 就能拿到整个用户的历史;写入时路径固定,天然按用户隔离;删除时整个路径一起删,不需要处理外键之类的逻辑。它不像关系型数据库要拆两张表再 join,RTDB 的嵌套结构就是为这种场景设计的。
4.2 工作流搭建四步走
整个 n8n 工作流分成四个阶段,每个阶段都有独立的节点承担职责。
第一阶段:接收消息并解析用户身份
Webhook 节点接收 POST 请求,请求体里包含 userId、message、name 等字段。建议用 Set 节点把 userId 单独提取成顶层字段,后面所有路径拼接都引用它。这里有个细节要注意:不同渠道的用户 ID 字段名不一样,企业微信可能是 FromUserName,飞书可能是 open_id,提前做好字段映射比在表达式里反复改强得多。
第二阶段:读取历史记忆
Google Cloud Realtime Database 节点,操作选 Get,路径填:
/customers/{{ $json.userId }}/conversationsRTDB 读取单节点的延迟通常稳定在几十到一百毫秒,对客服场景来说完全够用。如果实测发现响应明显变慢,优先检查 n8n 到 Google API 的网络链路,别第一时间怀疑数据库本身。
第三阶段:组装上下文并调用大模型
用 Code 节点把 Get 返回的历史整理成对话轮次列表,再拼上当前消息:
{ "history": [ {"role": "user", "content": "你好"}, {"role": "assistant", "content": "您好,请问有什么可以帮您"} ], "current_message": "我的订单什么时候到" }进入 AI Agent 节点或直接走 HTTP 请求大模型接口时,把 history 作为上下文传给模型。这一步的核心收益是:模型每次只需要处理最近的消息和历史概要,token 消耗可控,不会因为对话太长而爆炸。如果你用的是 n8n 里的 AI Agent 节点,记得把 history 字段映射到 System Message 或 Chat Memory,不同版本的节点字段名称略有差异,看着实际情况填就行。
第四阶段:写回新对话
大模型返回回答后,用 Google Cloud Realtime Database Update 节点,写入路径:
/customers/{{ $json.userId }}/conversations数据填:
{ "{{ Date.now() }}_user": { "role": "user", "content": "{{ $json.input }}", "ts": "{{ Date.now() }}" }, "{{ Date.now() }}_assistant": { "role": "assistant", "content": "{{ $json.output }}", "ts": "{{ Date.now() }}" } }我特意用时间戳做键名,配合 Update 做合并写。这样设计有个很实际的好处:同一条消息在毫秒级重试时,会覆盖而不是重复插入,天然幂等。如果改用 Create 的 Push 模式,每执行一次就多两个唯一键,重试一次就多两条垃圾记录,清理起来非常头疼。
4.3 实时监听与推送触发
n8n 原生的 Google Cloud Realtime Database 节点没有“监听”触发器,也就是说数据库内容变化时,不会主动唤醒 n8n 工作流。如果确实需要实时联动,我一般用两种方案补位:
- 方案一(推送,实时性高):在 Firebase 侧用 Cloud Functions 写一个数据库触发器 onWrite,当指定路径发生数据变更时,向 n8n 的 Webhook 节点发 HTTP 请求,n8n 再根据事件内容处理后续动作。这套方案要额外部署函数,但处理延迟最低。
- 方案二(拉取,实现简单):用 Schedule Trigger 定时轮询 n8n 节点,每隔几秒 Get 一次关键路径,比对数据变化后再触发后续流程。数据量小、实时性要求不高的场景,这是零成本方案。
一定要分清这两个方案适用的场景:方案一适合消息触发的实时业务,方案二适合状态同步的准实时业务。对绝大多数智能体应用,把 RTDB 当作“数据存储”而不是“事件源”已经足够,实时推送可以留给真正需要即时唤醒的场景,别一上来就上 Cloud Functions,维护成本会翻倍。
4.4 历史数据清理与成本控制
对话历史只增不减,时间久了,一个活跃用户可能积累几百条记录,既污染上下文,也烧存储配额。我在生产项目里加了两层防护:
第一层是每日清理。用 Schedule Trigger 在凌晨触发,遍历用户列表,用 Code 节点判断每条会话记录的时间戳,超过 30 天的标记为过期,再用 Delete 节点批量删除,或者挪到/archive归档路径。第二层是写入侧截断。每次 Update 之前,在 Code 节点里截取最近 20 轮对话,只保留热数据,更早的内容推到归档路径。这样单用户热数据体积被控制在几十 KB 以内,读取延迟稳定,成本也可预测。
这里要强调一个取舍:对话历史并不需要全量保留给大模型,模型真正需要的是“最近发生了什么”,而不是“一年前某次闲聊的逐字稿”。把历史分层管理,冷热数据分开存放,既省钱又提升响应速度,一举两得。
5. 常见问题与排查技巧实录
5.1 403 权限不足,八成是角色没配好
新手遇到最多的错误就是 403 Forbidden。原因几乎都是服务账号角色缺失。检查路径:IAM → 服务账号 → 点击对应账号 → 权限,看是否包含 Firebase Realtime Database Admin 或同等权限。只配置了 Viewer,执行 Update 或 Delete 时必报 403。
还有一种隐蔽情况:项目如果先在 Firebase 控制台创建,再关联到 Google Cloud,服务账号可能没有自动获得 RTDB 数据层的管理权限。需要回到 Firebase 控制台 → 项目设置 → 服务账号,确认授权关系。我建议在配置节点前,先单独用 REST API 或用 n8n 的 Get 操作测一次,如果能读出数据再继续配置写操作,排查范围会小很多。
5.2 私钥换行被吞,认证始终失败
下载的 JSON 密钥里,private_key 是一长串多行文本,首尾分别是-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----。在粘贴进 n8n 凭证时,如果换行被吞掉、内容被截断或者前面混入了空格,就会出现“配置都填了但一直 401”的诡异问题。
我的经验是:粘贴后把整个私钥展开检查一遍,确认结尾的END行原样存在。从文本编辑器里复制时,先关掉自动换行,避免不可见的中断符混进去。这个细节卡住了非常多的人,值得认真对待。如果是在 Windows 上编辑过文件再粘贴,尤其要注意换行符转换的问题,CRLF 和 LF 的差异有时会让私钥解析失败。
5.3 写入后读出来变了样?是类型归一化
RTDB 会自动做类型归一化:字符串形式的数字 "123" 可能被读成数字 123;某些带特殊字符的键名会被转义。很多人以为是 n8n 节点写坏了,其实是 RTDB 数据模型的固有行为。
规避方式很简单:如果你明确要保存字符串,就包一层{"value": "123"},或者在键名上带类型前缀。读取侧用 Code 节点做显式类型转换,别依赖“存进去什么样读出来就什么样”这个假设。在智能体场景里,这个问题常出现在消息内容本身是纯数字的场合,比如用户回复“12345”作为订单号,如果不做类型处理,后面传模型时可能变成一个数字类型的值,影响上下文组装。
5.4 并发写覆盖,客服场景最容易触发
用户连发消息时,同一用户的两条请求可能同时触发两个 n8n 工作流执行,两个 Update 并发写同一路径,后写的覆盖先写的。我在客服场景里真的踩过这个坑,用户的第二条消息把第一条的助手回复覆盖了,整个对话历史断层。
当前项目里的解法是“按用户路径原子化”:每个用户独享路径,消息记录用 Push 模式追加,自动生成唯一键,天然免疫并发覆盖;只有用户 profile 这类“最后一次写入为准”的字段才用 Update。真正需要串行更新的业务,RTDB 没有跨路径事务锁,得在应用侧引入队列或版本号机制。简单说就是:能追加的别覆盖,能按用户隔离的别共用路径,这样并发安全就解决了一大半。
5.5 费用与配额,提前算清楚
RTDB 有免费配额,Spark 计划大约每天五万次读、两万次写,具体以 Firebase 控制台实时显示为准。对一个中等规模的智能体应用,光靠对话读写就可能触顶。
做个粗略估算:每个用户每轮对话至少 2 次读(历史、状态)+ 1 次写(写回)。每天 1000 个活跃用户、每人 20 轮对话,读写合计约 6 万次,免费额度明显不够。这时候要么升级 Blaze 按量付费,要么压缩读写频次。我常用的一种压缩思路是:每轮对话结束后,把多轮历史在 Code 节点里合并成一条最新摘要写回,历史细节挪到独立的低频路径,用的时候再取。这样读写次数能降一个数量级,响应速度反而更快。
还有一个成本细节是套餐切换:项目从 Spark 升级到 Blaze 后,之前被限流的请求会全部放开,如果工作流里存在轮询死循环,账单会涨得非常难看。切套餐前务必审视所有 Schedule Trigger 的轮询频率,别让一个 5 秒一次的轮询在切换瞬间跑出天文数字的请求量。
最后分享一个我用了很久的小技巧:把 RTDB 路径设计成模板字符串,在 n8n 里用表达式动态拼接。调试时,先在表达式里写死一个测试 ID,跑通整个链路后,再替换成上游传来的变量。这比直接在完整工作流里调要快得多。另外,每次改了路径格式,记得在节点上重新执行一次,看清输入输出再继续搭下游——实时数据库没有严格 schema,路径写错它只会在运行时悄悄返回空,这种“一切正常但结果不对”的问题最难排查,养成随手验证的习惯能帮你少走很多弯路。