快速结论:当你在 LangChain 的 create_agent 中使用 PIIMiddleware 等输出中间件(middleware)、同时又用 agent.stream(..., stream_mode="messages") 流式输出时,模型生成的原始 token 会在中间件执行之前就打印到用户面前,导致输出护栏被完全绕过。优先排查你的流式输出是否在中间件生效前就把 token 直接返回给了调用方。
适用环境:Issue 中报告的运行环境为 macOS(路径含 /Users/ 与 Homebrew),Python 3.10.16,使用 langchain 与 langchain-core 两个包,模型为 gpt-4o-mini。其余 CUDA、显卡等未在 Issue 中说明。
最快修复方案:暂无确认的一步修复方案。Issue 中维护者明确表示,在现有 Middleware 架构下没有直接的逐 token 执行中间件的方法,该问题需要 AgentMiddleware 层面的架构调整,Issue 因此长期保持 open 状态。可优先尝试的临时方案是:改用非流式的 agent.invoke() 先让完整输出经过中间件,再对最终消息做“伪流式”逐 token 输出(见下方解决步骤)。
注意事项:上述伪流式方案会牺牲真实流式的首字延迟体验,且属于社区提出的 workaround,并非官方修复;它在输出被中间件拦截时只能整段拒绝,无法保证真正的逐块安全。Issue 评论中另有人建议在流式过程中对缓冲区做增量扫描(buffered window scanning),但这依赖第三方工具且同样未获官方确认。另有评论建议至少在文档中加入警告提示,说明当前流式与输出护栏不兼容。
问题场景
用户在 LangChain 中通过 create_agent 创建 agent,并挂载了 PIIMiddleware(strategy="block"、apply_to_output=True)用于拦截输出中的邮箱等 PII 信息,然后用 agent.stream(stream_mode="messages") 逐条消费模型消息并直接打印 msg.content。结果是:即使启用了输出侧 PII 中间件,包含邮箱地址的完整内容依然先被打印出来,随后中间件才执行并触发拦截,抛出异常。用户同时还观察到,如果直接打印流式 token,敏感内容已被暴露给使用者。
报错原文
Sure! Here's a funny greeting incorporating your email:
...
you can reach me at my secret lair: **john.doe@example.com**!
...
Traceback (most recent call last):
File ".../test_hiddenlayer.py", line 79, in <module>
test_streaming_malicious()
File ".../test_hiddenlayer.py", line 64, in test_streaming_malicious
for msg, metadata in agent.stream(
File ".../langgraph/pregel/main.py", line 2646, in stream
for _ in runner.tick(
File ".../langgraph/pregel/_runner.py", line 258, in tick
_panic_or_proceed(
File ".../langgraph/pregel/_runner.py", line 520, in _panic_or_proceed
raise exc
File ".../langgraph/pregel/_executor.py", line 80, in done
task.result()
...
核心问题可概括为:Streaming bypasses guardrails/middleware。即流式输出在中间件完成检查之前就把原始内容交给了调用方。
原因分析
根据维护者在 Issue 中的回复,Middleware 节点只在整段流式内容全部 yield 回用户之后才执行一次,因此当前 Middleware 架构下没有直接办法在流式过程中逐 token 执行中间件。也就是说,输出侧的 PII 中间件本质上是“事后处理”,而 stream_mode="messages" 会在处理之前就把 token 交付给你并打印,从而出现“先泄漏、后拦截”的结果。Issue 评论进一步指出:任何把中间件实现为后处理步骤的系统,在流式场景下都会有同样的问题,修复需要在 LangGraph / Middleware 架构层面支持“流式感知的中间件”。
需要注意:这一原因来自维护者与评论者的分析,尚未在 Issue 中给出最终合并的官方修复结论。
环境排查
- 确认 Python 版本,Issue 中为 3.10.16。
- 确认
langchain与langchain-core均已安装,并确认其版本。 - 确认相关运行栈:Issue 堆栈中出现
langgraph/pregel/main.py、_runner.py、_executor.py,说明经过 LangGraph 执行层,需一并确认 langgraph 版本。 - 确认模型:Issue 使用
gpt-4o-mini。 - 确认中间件配置:
PIIMiddleware("email", strategy="block", apply_to_input=False, apply_to_output=True)。 - 确认调用方式是否为
agent.stream(..., stream_mode="messages")并直接打印 token。
解决步骤
- 先复现确认:使用 Issue 中的最小示例,传入包含邮箱的 user 消息,观察邮箱是否在中间件拦截之前就被打印出来。
- 若确认存在绕过行为,可优先尝试社区给出的“伪流式”临时方案:不要用
stream_mode="messages"直接消费 token,而是先用agent.invoke()拿到完整输出,让输出侧中间件完整执行(strategy="block"时会被拦截)。 - 从
output["messages"][-1]取出最终消息内容,用分词器(Issue 示例使用tiktoken.encoding_for_model("gpt-4o-mini"))将其编码为 token,再逐个decode并打印,模拟流式输出效果。 - 用
try/except包裹invoke与打印过程;一旦中间件因 PII 触发拦截,就在except中做统一降级处理,不要输出任何原始内容。 - 如仍需保留真实流式,考虑在流式过程中使用带缓冲的增量扫描方案(例如 Issue 评论提到的第三方工具 ClawMoat)。注意该方案并非官方修复,仅在你有能力自行维护状态机与误报处理时使用。
- 持续关注 Issue 中提到的修复 PR(#35470),其思路是在中间件侧缓冲模型输出,确保原始 token 不会在
after_model拦截/脱敏之前暴露。该修复是否覆盖 sync/async 流式与各输出策略,需以 PR 最终状态为准。
验证方法
用同一个包含邮箱的提示词重新运行:如果改用 invoke + 伪流式,确认邮箱内容要么被 PIIMiddleware 阻止、要么在通过中间件后才显示,且不会出现“先完整打印敏感内容、再报错”的顺序。若采用缓冲扫描方案,则应验证邮箱的第一个字符出现之前就已被标记或阻断。建议同时覆盖同步与异步流式、以及 block 与脱敏策略,确认没有残留暴露路径。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Bug]: Knowledge base creators cannot access datasets created via upload or RAG Pipeline](https://www.chat-gpts.plus/wp-content/uploads/2026/09/42836-411fb8f3-768x403.jpg)
![[Bug]: Multi card issue and multi token prediction (mtp) issue with Intel/Qwen3.6-35B-A3B-int4-mixed-AutoRound](https://www.chat-gpts.plus/wp-content/uploads/2026/09/53119-9a400882-768x403.jpg)
