n8n AI Agent 记忆节点为何不保存 Tool 消息?

👁 86 次阅读

n8n AI Agent 记忆节点为何不保存 Tool 消息?

来源链接:https://github.com/n8n-io/n8n/issues/14361

现象描述

在 n8n 的 AI Agent 与 Memory 节点组合使用场景中,用户发现记忆里只存了输入和输出两条消息,工具调用(Tool messages)的痕迹完全丢失。issue 作者在 Simple Memory 和 Postgres Memory 两个节点上做了验证,反馈的核心现象是:无论保存还是加载,tools_call 数组始终是空数组。 复现路径非常直接:使用 Simple Memory 节点或 Postgres Memory 节点,检查被保存下来的消息记录。issue 报告者给出的环境是 n8n 1.83.2、Node.js 20.18.3、PostgreSQL 数据库、main(默认)执行模式。据 issue 讨论,这个问题在多个记忆后端上都能观察到。 问题带来的行为影响相当隐蔽。作者在 Bug Description 中的描述是,上下文窗口会被这样一组消息填满:用户请求某个动作、AI 回复成功、用户给出正面反馈。由于工具消息缺失,LLM 会学到这个模式并重复它,下一次它不再调用工具,而是直接回复用户。评论者 davidsula 补充了在实际使用中的观察:AI Agent 头两次该调用工具时会调用,之后逐渐停止调用,因为它学到了「没有工具调用也能被接受」的模式。 真正让这个问题从「体验瑕疵」升级为「可信度问题」的,是它和模型幻觉的叠加效应:Agent 会声称自己调用了工具,但实际没有调用。

背景原因

从讨论中可以归纳出两个层面的原因。 第一层是数据持久化层面的缺失。记忆节点在写入和读取历史时,没有把 tool_calls 数组填充并回传。issue 作者提供的 fork 提交(fjrdomingues/n8n 的 commit 1af9450f3ff04aba16388c06ce8a48b2d17c351b)就是针对这一点,尝试在保存记忆时把 tool_calls 数组补上。注意这与「模型本身爱撒谎」是两个独立问题:即便模型完全可靠,缺失的记忆也会系统性地削弱它后续调用工具的动力。 第二层是架构层面的错配。评论者 ferhimedamine 在 2026-06-12 的评论中给出了一个更根本的判断:n8n 的记忆节点把 Agent 历史当作对话日志来处理(交替的 human/AI 消息对),但 Agent 的实际执行轨迹包含工具调用这类一等事件,它们不是对话。把 tool_calls 补回存储的 AI 消息里只是短期缓解,它仍然把工具使用建模为聊天消息的一个属性,而不是独立的结构化记录。 评论者 hegu-1 在 2026-07-07 的跟帖中延续了这个思路,提出的形态是每次工具调用一条 action receipt:
tool_call_id
requested_by assistant message id
tool name / args hash
result summary
status: success / failure / partial
resource touched
timestamp
这样未来的上下文可以携带紧凑的 receipt,而不必塞入完整的原始工具输出。这个方向目前停留在讨论层面。

排查与量化方法

要确认自己是否踩到这个问题,可以从可观测的痕迹入手。issue 作者与评论者给出的可验证路径有两条。 一条是直接检查记忆存储。用 Simple Memory 或 Postgres Memory 节点跑一次带工具调用的 Agent 流程,然后查看被保存的消息记录,观察 tools_call 数组的内容。issue 报告的结论是它在保存和加载两侧都为空数组。 另一条是观察 Agent 的长期行为曲线。评论者 davidsula 的描述提供了一个可量化的行为模式:同一个 Agent 在需要调用工具的重复任务上,头两次调用正常,随后调用频率下降直至停止。如果任务本身要求每次都必须调用工具,这种衰减就是记忆缺陷在行为层的直接投影。 评论者 IgorGanapolsky 在 2026-07-15 的多条评论中把这类问题归为契约问题,而非提示词质量问题。其给出的运维检查清单包含以下要点: 1. 把工具的实际墙钟时间、Agent 的动作预算、chat/webhook 的 TTL 分开对待 2. 持久化的成功判定应以产物 id 为准,再标记完成 3. 工具结果为空或 undefined 时应失败关闭,而不是当作成功 4. 超时后重试需要幂等 这些要点直接对应「声称调用了工具但实际没有」这一现象:如果没有持久化的成功凭据,Agent 的「成功」回复就没有事实支撑。

当前处理路径与局限

需要明确的是,本 issue 当前状态为 open,标签为 team:ai、status:in-linear、status:team-assigned。维护者 Joffcom 在 2025-04-02 的回复中提到已创建内部工单 GHC-1434,并询问这属于 bug 还是 enhancement request。issue 作者 fjrdomingues 在同日的回复中确认,如果存在这类分类,更适合归为 enhancement request。官方尚未合入修复方案。 社区侧的实际进展是一个未合并的 fork 建议。issue 作者提供的提交链接为:
https://github.com/fjrdomingues/n8n/commit/1af9450f3ff04aba16388c06ce8a48b2d17c351b
这是一份「如何修复」的工作性建议,据 issue 讨论,其作用范围是在保存记忆时填充 tool_calls 数组。它没有进入主干,也没有对应的合并记录可查。 在官方方案落地前,可用的缓解路径及其局限如下:
路径具体做法局限
补丁 fork参考 issue 作者的提交,在保存记忆时填充 tool_calls属于社区方案,未合并;据 ferhimedamine 评论,仍未解决把工具使用建模为聊天消息属性的根本问题
运维契约加固按 IgorGanapolsky 的清单,用持久化产物 id 作为成功判定,空 payload 失败关闭属于应用层补救,不改变记忆节点本身的存储行为
语义层改版按 hegu-1 提议,为每次工具调用记录 action receipt目前仅停留在讨论,无实现

常见错误排查

Agent 声称调用了工具但实际没有。 优先排查记忆中的 tools_call 数组是否为空,而不是先怀疑模型。据 issue 讨论,空数组会让模型学到「不调用也能被接受」的模式。 Agent 在重复任务上逐渐停止调用工具。 这与 davidsula 描述的行为一致:头两次正常,之后衰减。检查记忆加载回来的历史里是否缺少工具消息。 换用不同记忆后端仍复现。 issue 作者在 Simple Memory 与 Postgres Memory 上都验证过该现象。更换后端不是有效的规避手段。 工具返回空结果却记为成功。 按 IgorGanapolsky 的建议,空或 undefined 的工具结果应失败关闭。这一条独立于记忆缺陷,但会放大「假成功」问题。 超时后盲目重试导致副作用重复触发。 同一条检查清单中提到,副作用发生后再盲目重试会造成双重触发,应先落幂等键。

实践建议

在官方合入修复之前,把「Agent 是否真的调用了工具」当作一个需要外部证据验证的契约,而不是信任 Agent 的自然语言回复。IgorGanapolsky 给出的判定标准是:成功等于持久的产物 id(CRM、预约、消息),而不是节点变绿。 对于依赖工具调用一致性的生产流程,建议把工具执行的产物写入外部存储,并在后续上下文里以紧凑摘要的形式回填,而不是依赖记忆节点自动携带完整工具输出。这与 hegu-1 提出的 receipt 形态方向一致。 如果打算跟踪官方进展,可以关注 issue 关联的内部工单 GHC-1434 的后续动向,以及 team:ai 标签下的状态变化。据 issue 讨论,这是目前唯一被维护者确认的追踪入口。

FAQ

这个问题的当前状态是什么? issue 状态为 open。维护者已创建内部工单 GHC-1434 进行追踪,标签包含 team:ai、status:in-linear、status:team-assigned。据 issue 讨论,尚无合并进主干的修复。 不保存 Tool 消息会导致什么具体后果? 上下文里会形成「用户请求 → AI 回复成功 → 用户正面反馈」的循环。缺少工具消息后,LLM 学到这个模式并重复,下一次不再调用工具,而是直接回复用户。极端情况是 Agent 声称调用了工具但实际没有。 哪些记忆节点受影响? issue 作者在 Simple Memory 和 Postgres Memory 上做了验证,并说明这是他测试过的两个。据 issue 讨论,tools_call 数组在保存和加载时均为空。 有可用的修复吗? issue 作者提供了一个 fork 提交(1af9450f3ff04aba16388c06ce8a48b2d17c351b),思路是在保存记忆时填充 tool_calls 数组。据 issue 讨论,该提交未被合并。评论者 ferhimedamine 认为,补丁只是短期缓解,根本问题是记忆节点把工具使用建模为聊天消息属性而非独立事件。 在官方修复前,运维上应如何降低风险? 按 IgorGanapolsky 的清单:以持久化产物 id 而非节点状态作为成功判据;工具结果为空的失败要显式失败;超时重试要幂等;副作用发生后不盲目重试。这些属于应用层契约加固,不改变记忆节点本身的行为。

订阅 AI 百事知

每周精选 AI 工具评测、行业资讯与使用教程,第一时间送达。

🔗 相关阅读(你可能还想看)

📄 Outlook 节点创建草稿添加附件为何报 UnableToDeserializePostBody?
📄 Flowise 点击市场或聊天页为何整页白屏?
📄 如何解决 Azure AI Search 向量存储的 $filter 递归深度超限错误?
📄 如何解决 shadcn/ui Sidebar 与 Header 重叠的问题?
📄 Next.js 开发服务器为何会 JavaScript heap out of memory?
📄 Nuxt 偶发 Cannot read properties of undefined 报错是怎么回事
📄 如何解决「Visual Studio Code for ipad」相关的问题?
📄 如何在AutoGen中实现企业级代理治理的加密行动收据(AAR)?
📄 记忆内容未清洗直接注入系统提示词,如何防范间接提示注入?
crewAI LiteAgent 将记忆内容直接拼接到系统提示词,存在间接提示注入风险。社区提出分层防御方案,但 iss
🤖 想直接和 AI 对话?
免费体验 DeepSeek V4 等顶级模型,无需注册,随问随答
🎯 立即体验 AI 工具

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

🤖 嘟嘟客服
滚动至顶部
🤖 嘟嘟客服
🤖 嘟嘟客服
';i.value='';m.innerHTML+='
...
';fetch('/dify-chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:q,user:'w'+Date.now()})}).then(function(r){return r.json()}).then(function(d){var a=d.answer||'暂时无法回复';m.removeChild(m.lastChild);m.innerHTML+='
'+a+'
'}).catch(function(){m.removeChild(m.lastChild);m.innerHTML+='
网络异常
'})">发送