[Bug] Presidio guardrail: end-to-end PII masking broken with Anthropic native API

该问题出现在 LiteLLM 使用 Anthropic 原生 API(非 OpenAI 兼容路径)并开启 Presidio guardrail 与 output_parse_pii: true 时:输入侧 PII 会被遮蔽,但响应侧不会还原;请求中携带工具时还会触发 400 错误。优先排查是否走了

快速结论:该问题出现在 LiteLLM 使用 Anthropic 原生 API(非 OpenAI 兼容路径)并开启 Presidio guardrail 与 output_parse_pii: true 时:输入侧 PII 会被遮蔽,但响应侧不会还原;请求中携带工具时还会触发 400 错误。优先排查是否走了 anthropic/ 原生路径,以及 guardrail 是否导致工具定义被改写。

适用环境:Issue 已确认的场景为 LiteLLM + Presidio guardrail + output_parse_pii: true + Anthropic 原生 provider(anthropic/claude-sonnet-4-5,非 openai-compat);正文只给出 YAML 配置,未提供 OS、Python、CUDA、显卡或依赖版本信息。

最快修复方案:暂无确认的一步修复方案。Issue 中原先的 PR(#22879–#22882、#23037)已被关闭并由 #24290、#24291 取代,但讨论中未确认这些 PR 已合并发布。

注意事项:Issue 中提到的“升级到包含修复的 LiteLLM 版本”在评论里没有得到合并或发布确认;流式响应路径、工具调用、非 ASCII 字符以及 byte/char offset 等问题在评论中仍有讨论,修复后也需要针对这些场景回归验证。

问题场景

用户在 LiteLLM 中以 anthropic/ 原生路径调用 Claude(例如 model: anthropic/claude-sonnet-4-5),同时启用 Presidio guardrail、default_on: true 和 output_parse_pii: true。典型触发场景包括:普通非流式对话(输出中出现 <PERSON> 占位符而没有被还原)、流式对话(原生路径返回 SSE bytes,未执行还原),以及携带工具调用的请求(例如通过 Claude Code 使用 bash_20250124、text_editor_20250124 等原生工具)。

报错原文

Input tag 'function' found using 'type' does not match expected tags: 'bash_20250124', 'text_editor_20250124', ...

原因分析

根据 Issue 作者的分析,问题由多个原因叠加造成:

  • 工具键污染:apply_guardrail() 返回了整个被修改过的 inputs dict,其中包含 process_input_messages() 添加的 OpenAI 转换键(tools、structured_messages、model、images)。在 Anthropic 原生路径上,这些键会用 OpenAI 的 type:"function" 格式覆盖原生工具定义,从而触发 400。
  • 缺少还原路径:apply_guardrail() 只有遮蔽路径;当以 input_type="response" 和 output_parse_pii=True 调用时,PII token 从未被恢复。
  • 抽象接口不同步:BaseGuardrailTranslation 未声明 request_data 参数,而各 process_output_response() 实现已接收该参数,导致类型/mypy 不匹配。
  • 流式钩子只处理 ModelResponseStream:async_post_call_streaming_iterator_hook 的还原逻辑期望收到 ModelResponseStream 对象,但 Anthropic 原生路径返回的是原始 bytes(SSE 格式),因此直接被透传,还原逻辑没有执行。此外 AnthropicMessagesHandler.process_output_response() 新建了 request_data dict,丢弃了输入遮蔽阶段保存的 pii_tokens。

评论中还提到一个可能相关的历史问题:presidio.py 中 pii_tokens 的偏移 bug——从左到右替换而不重新计算索引,会破坏字符串;PR #5068 曾提出从右到左的修复,但以 stale 状态关闭、未合并。

环境排查

  • 确认 model 是否为 anthropic/... 原生 provider,而不是 OpenAI 兼容路径。
  • 确认 guardrail 配置中包含 guardrail: presidio 与 output_parse_pii: true。
  • 确认请求中是否携带工具(MCP、Claude Code 等),并检查工具 type 是否被改为 function。
  • 区分非流式与流式响应,两者在 Issue 中属于不同代码路径。
  • 如果返回内容包含非 ASCII 文本,注意评论中提到的 byte/char offset 不一致风险。
  • Issue 未提供 OS、Python、CUDA、显卡及依赖版本,这些项无法从原文确认。

解决步骤

  1. 先确认触发路径:将 model 固定为 anthropic/ 原生路径,并复现一次带工具的请求,确认 400 报错与 PII 未还原是否为同一配置下出现。
  2. 关注 LiteLLM 对 #24290 与 #24291 的合并与发布状态。Issue 中原先的四个 PR(#22879–#22882、#23037)已关闭并被这两个 PR 取代;在确认合并版本之前,不要假定升级即可修复。
  3. 若暂时无法升级,可优先尝试评论中提到的临时方案:不要直接暴露 Presidio guardrail,而是把它包成 custom-guardrail,从而绕开 LiteLLM 内置的 Presidio 处理路径。该方案在评论中仅被描述为个人做法,未经验证,且会关闭内置还原能力,需要自行实现还原逻辑。
  4. 如条件允许,可在 LiteLLM 上游部署独立的 PII 代理,在请求到达 provider 之前完成遮蔽与还原,从而避开 Anthropic 与 OpenAI 之间的 API 差异。评论中提到的 PrivAiTe 属于外部项目,不是 Issue 验证过的官方方案。
  5. 不要仅凭 output_parse_pii: true 就认为流式响应会被还原;原生 SSE 字节需要专门的解包逻辑,#24291 声称处理了这一点,但以实际发布版本为准。

验证方法

在 Anthropic 原生路径下分别测试:1)包含人名等 PII 的非流式请求,确认响应中不残留 <PERSON> 等占位符,而是还原为原始值;2)流式请求,确认 SSE 输出在保持实时流式的同时完成还原;3)携带工具的请求,确认不再出现 Input tag 'function' ... 的 400,且工具 type 保持原生格式(如 bash_20250124)。同时检查 pii_tokens 在输出阶段是否被正确传入,而没有被新 dict 丢弃。

参考来源

BerriAI/litellm #22821

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29934

发表回复

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